@jonit-dev/night-watch-cli 1.8.8-beta.9 → 1.8.9

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 (42) hide show
  1. package/dist/cli.js +1073 -519
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/audit.d.ts.map +1 -1
  4. package/dist/commands/audit.js +10 -2
  5. package/dist/commands/audit.js.map +1 -1
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +1 -0
  8. package/dist/commands/init.js.map +1 -1
  9. package/dist/commands/install.d.ts +4 -0
  10. package/dist/commands/install.d.ts.map +1 -1
  11. package/dist/commands/install.js +25 -0
  12. package/dist/commands/install.js.map +1 -1
  13. package/dist/commands/logs.d.ts +1 -1
  14. package/dist/commands/logs.d.ts.map +1 -1
  15. package/dist/commands/logs.js +25 -5
  16. package/dist/commands/logs.js.map +1 -1
  17. package/dist/commands/merge.d.ts +26 -0
  18. package/dist/commands/merge.d.ts.map +1 -0
  19. package/dist/commands/merge.js +159 -0
  20. package/dist/commands/merge.js.map +1 -0
  21. package/dist/commands/review.d.ts +0 -1
  22. package/dist/commands/review.d.ts.map +1 -1
  23. package/dist/commands/review.js +0 -13
  24. package/dist/commands/review.js.map +1 -1
  25. package/dist/commands/slice.d.ts +1 -0
  26. package/dist/commands/slice.d.ts.map +1 -1
  27. package/dist/commands/slice.js +19 -19
  28. package/dist/commands/slice.js.map +1 -1
  29. package/dist/commands/status.d.ts.map +1 -1
  30. package/dist/commands/status.js +54 -0
  31. package/dist/commands/status.js.map +1 -1
  32. package/dist/scripts/night-watch-merger-cron.sh +321 -0
  33. package/dist/scripts/night-watch-pr-reviewer-cron.sh +1 -137
  34. package/dist/templates/slicer.md +54 -64
  35. package/dist/web/assets/index-B1BnOpiO.css +1 -0
  36. package/dist/web/assets/index-CPQbZ1BL.css +1 -0
  37. package/dist/web/assets/index-CiRJZI4z.js +386 -0
  38. package/dist/web/assets/index-DcgNAi4A.js +386 -0
  39. package/dist/web/assets/index-SQlBKu_s.js +386 -0
  40. package/dist/web/assets/index-ZE5lOeJp.js +386 -0
  41. package/dist/web/index.html +2 -2
  42. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -17,6 +17,15 @@ var init_types = __esm({
17
17
  }
18
18
  });
19
19
 
20
+ // ../core/dist/board/types.js
21
+ var BOARD_COLUMNS;
22
+ var init_types2 = __esm({
23
+ "../core/dist/board/types.js"() {
24
+ "use strict";
25
+ BOARD_COLUMNS = ["Draft", "Ready", "In Progress", "Review", "Done"];
26
+ }
27
+ });
28
+
20
29
  // ../core/dist/jobs/job-registry.js
21
30
  function getJobDef(id) {
22
31
  return JOB_MAP.get(id);
@@ -170,6 +179,7 @@ var JOB_REGISTRY, JOB_MAP;
170
179
  var init_job_registry = __esm({
171
180
  "../core/dist/jobs/job-registry.js"() {
172
181
  "use strict";
182
+ init_types2();
173
183
  JOB_REGISTRY = [
174
184
  {
175
185
  id: "executor",
@@ -286,10 +296,19 @@ var init_job_registry = __esm({
286
296
  lockSuffix: "-audit.lock",
287
297
  queuePriority: 10,
288
298
  envPrefix: "NW_AUDIT",
299
+ extraFields: [
300
+ {
301
+ name: "targetColumn",
302
+ type: "enum",
303
+ enumValues: [...BOARD_COLUMNS],
304
+ defaultValue: "Draft"
305
+ }
306
+ ],
289
307
  defaultConfig: {
290
308
  enabled: true,
291
309
  schedule: "50 3 * * 1",
292
- maxRuntime: 1800
310
+ maxRuntime: 1800,
311
+ targetColumn: "Draft"
293
312
  }
294
313
  },
295
314
  {
@@ -306,7 +325,7 @@ var init_job_registry = __esm({
306
325
  {
307
326
  name: "targetColumn",
308
327
  type: "enum",
309
- enumValues: ["Draft", "Ready", "In Progress", "Done", "Closed"],
328
+ enumValues: [...BOARD_COLUMNS],
310
329
  defaultValue: "Draft"
311
330
  },
312
331
  { name: "analysisPrompt", type: "string", defaultValue: "" }
@@ -319,6 +338,38 @@ var init_job_registry = __esm({
319
338
  targetColumn: "Draft",
320
339
  analysisPrompt: ""
321
340
  }
341
+ },
342
+ {
343
+ id: "merger",
344
+ name: "Merge Orchestrator",
345
+ description: "Repo-wide PR merge coordinator \u2014 scans, rebases, and merges in FIFO order",
346
+ cliCommand: "merge",
347
+ logName: "merger",
348
+ lockSuffix: "-merger.lock",
349
+ queuePriority: 45,
350
+ envPrefix: "NW_MERGER",
351
+ extraFields: [
352
+ {
353
+ name: "mergeMethod",
354
+ type: "enum",
355
+ enumValues: ["squash", "merge", "rebase"],
356
+ defaultValue: "squash"
357
+ },
358
+ { name: "minReviewScore", type: "number", defaultValue: 80 },
359
+ { name: "branchPatterns", type: "string[]", defaultValue: [] },
360
+ { name: "rebaseBeforeMerge", type: "boolean", defaultValue: true },
361
+ { name: "maxPrsPerRun", type: "number", defaultValue: 0 }
362
+ ],
363
+ defaultConfig: {
364
+ enabled: false,
365
+ schedule: "55 */4 * * *",
366
+ maxRuntime: 1800,
367
+ mergeMethod: "squash",
368
+ minReviewScore: 80,
369
+ branchPatterns: [],
370
+ rebaseBeforeMerge: true,
371
+ maxPrsPerRun: 0
372
+ }
322
373
  }
323
374
  ];
324
375
  JOB_MAP = new Map(JOB_REGISTRY.map((job) => [job.id, job]));
@@ -339,7 +390,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
339
390
  return `claude-proxy:${baseUrl}`;
340
391
  }
341
392
  }
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;
393
+ 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_TARGET_COLUMN, 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, DEFAULT_MERGER_ENABLED, DEFAULT_MERGER_SCHEDULE, DEFAULT_MERGER_MAX_RUNTIME, DEFAULT_MERGER_MERGE_METHOD, DEFAULT_MERGER_MIN_REVIEW_SCORE, DEFAULT_MERGER_REBASE_BEFORE_MERGE, DEFAULT_MERGER_MAX_PRS_PER_RUN, DEFAULT_MERGER, MERGER_LOG_NAME, 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;
343
394
  var init_constants = __esm({
344
395
  "../core/dist/constants.js"() {
345
396
  "use strict";
@@ -384,7 +435,7 @@ var init_constants = __esm({
384
435
  slicerSchedule: DEFAULT_SLICER_SCHEDULE,
385
436
  slicerMaxRuntime: DEFAULT_SLICER_MAX_RUNTIME,
386
437
  priorityMode: "roadmap-first",
387
- issueColumn: "Draft"
438
+ issueColumn: "Ready"
388
439
  };
389
440
  DEFAULT_TEMPLATES_DIR = ".night-watch/templates";
390
441
  DEFAULT_BOARD_PROVIDER = {
@@ -416,10 +467,12 @@ var init_constants = __esm({
416
467
  DEFAULT_AUDIT_ENABLED = true;
417
468
  DEFAULT_AUDIT_SCHEDULE = "50 3 * * 1";
418
469
  DEFAULT_AUDIT_MAX_RUNTIME = 1800;
470
+ DEFAULT_AUDIT_TARGET_COLUMN = "Draft";
419
471
  DEFAULT_AUDIT = {
420
472
  enabled: DEFAULT_AUDIT_ENABLED,
421
473
  schedule: DEFAULT_AUDIT_SCHEDULE,
422
- maxRuntime: DEFAULT_AUDIT_MAX_RUNTIME
474
+ maxRuntime: DEFAULT_AUDIT_MAX_RUNTIME,
475
+ targetColumn: DEFAULT_AUDIT_TARGET_COLUMN
423
476
  };
424
477
  DEFAULT_ANALYTICS_ENABLED = false;
425
478
  DEFAULT_ANALYTICS_SCHEDULE = "0 6 * * 1";
@@ -458,6 +511,24 @@ If no issues are warranted, output an empty array: []`;
458
511
  aiReviewResolution: DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION,
459
512
  readyLabel: DEFAULT_PR_RESOLVER_READY_LABEL
460
513
  };
514
+ DEFAULT_MERGER_ENABLED = false;
515
+ DEFAULT_MERGER_SCHEDULE = "55 */4 * * *";
516
+ DEFAULT_MERGER_MAX_RUNTIME = 1800;
517
+ DEFAULT_MERGER_MERGE_METHOD = "squash";
518
+ DEFAULT_MERGER_MIN_REVIEW_SCORE = 80;
519
+ DEFAULT_MERGER_REBASE_BEFORE_MERGE = true;
520
+ DEFAULT_MERGER_MAX_PRS_PER_RUN = 0;
521
+ DEFAULT_MERGER = {
522
+ enabled: DEFAULT_MERGER_ENABLED,
523
+ schedule: DEFAULT_MERGER_SCHEDULE,
524
+ maxRuntime: DEFAULT_MERGER_MAX_RUNTIME,
525
+ mergeMethod: DEFAULT_MERGER_MERGE_METHOD,
526
+ minReviewScore: DEFAULT_MERGER_MIN_REVIEW_SCORE,
527
+ branchPatterns: [],
528
+ rebaseBeforeMerge: DEFAULT_MERGER_REBASE_BEFORE_MERGE,
529
+ maxPrsPerRun: DEFAULT_MERGER_MAX_PRS_PER_RUN
530
+ };
531
+ MERGER_LOG_NAME = "merger";
461
532
  AUDIT_LOG_NAME = "audit";
462
533
  PLANNER_LOG_NAME = "slicer";
463
534
  ANALYTICS_LOG_NAME = "analytics";
@@ -734,7 +805,7 @@ function normalizeConfig(rawConfig) {
734
805
  if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
735
806
  normalized.autoMergeMethod = mergeMethod;
736
807
  }
737
- for (const jobId of ["qa", "audit", "analytics"]) {
808
+ for (const jobId of ["qa", "audit", "analytics", "merger"]) {
738
809
  const jobDef = getJobDef(jobId);
739
810
  if (!jobDef)
740
811
  continue;
@@ -1068,6 +1139,17 @@ function buildEnvOverrideConfig(fileConfig) {
1068
1139
  env.prResolver = overrides;
1069
1140
  }
1070
1141
  }
1142
+ const mergerDef = getJobDef("merger");
1143
+ if (mergerDef) {
1144
+ const currentBase = (
1145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1146
+ env.merger ?? fileConfig?.merger ?? mergerDef.defaultConfig
1147
+ );
1148
+ const overrides = buildJobEnvOverrides(mergerDef.envPrefix, currentBase, mergerDef.extraFields);
1149
+ if (overrides) {
1150
+ env.merger = overrides;
1151
+ }
1152
+ }
1071
1153
  const jobProvidersEnv = {};
1072
1154
  for (const jobType of VALID_JOB_TYPES) {
1073
1155
  const val = process.env[`NW_JOB_PROVIDER_${jobType.toUpperCase()}`];
@@ -1163,6 +1245,7 @@ function getDefaultConfig() {
1163
1245
  audit: { ...DEFAULT_AUDIT },
1164
1246
  analytics: { ...DEFAULT_ANALYTICS },
1165
1247
  prResolver: { ...DEFAULT_PR_RESOLVER },
1248
+ merger: { ...DEFAULT_MERGER },
1166
1249
  jobProviders: { ...DEFAULT_JOB_PROVIDERS },
1167
1250
  providerScheduleOverrides: [...DEFAULT_PROVIDER_SCHEDULE_OVERRIDES],
1168
1251
  queue: { ...DEFAULT_QUEUE }
@@ -1230,7 +1313,7 @@ function mergeConfigLayer(base, layer) {
1230
1313
  ...layerQueue,
1231
1314
  providerBuckets: { ...baseQueue.providerBuckets, ...layerQueue.providerBuckets }
1232
1315
  };
1233
- } else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver") {
1316
+ } else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver" || _key === "merger") {
1234
1317
  base[_key] = {
1235
1318
  ...base[_key],
1236
1319
  ...value
@@ -1253,6 +1336,13 @@ function mergeConfigs(base, fileConfig, envConfig) {
1253
1336
  if (fileConfig)
1254
1337
  mergeConfigLayer(merged, fileConfig);
1255
1338
  mergeConfigLayer(merged, envConfig);
1339
+ if (merged.autoMerge === true && !fileConfig?.merger) {
1340
+ merged.merger = {
1341
+ ...merged.merger,
1342
+ enabled: true,
1343
+ mergeMethod: merged.autoMergeMethod ?? "squash"
1344
+ };
1345
+ }
1256
1346
  merged.maxRetries = sanitizeMaxRetries(merged.maxRetries, DEFAULT_MAX_RETRIES);
1257
1347
  merged.reviewerMaxRetries = sanitizeReviewerMaxRetries(merged.reviewerMaxRetries, DEFAULT_REVIEWER_MAX_RETRIES);
1258
1348
  merged.reviewerRetryDelay = sanitizeReviewerRetryDelay(merged.reviewerRetryDelay, DEFAULT_REVIEWER_RETRY_DELAY);
@@ -1377,15 +1467,6 @@ var init_config = __esm({
1377
1467
  }
1378
1468
  });
1379
1469
 
1380
- // ../core/dist/board/types.js
1381
- var BOARD_COLUMNS;
1382
- var init_types2 = __esm({
1383
- "../core/dist/board/types.js"() {
1384
- "use strict";
1385
- BOARD_COLUMNS = ["Draft", "Ready", "In Progress", "Review", "Done"];
1386
- }
1387
- });
1388
-
1389
1470
  // ../core/dist/storage/repositories/sqlite/execution-history.repository.js
1390
1471
  import Database from "better-sqlite3";
1391
1472
  import { inject, injectable } from "tsyringe";
@@ -3590,6 +3671,9 @@ function analyticsLockPath(projectDir) {
3590
3671
  function prResolverLockPath(projectDir) {
3591
3672
  return `${LOCK_FILE_PREFIX}pr-resolver-${projectRuntimeKey(projectDir)}.lock`;
3592
3673
  }
3674
+ function mergerLockPath(projectDir) {
3675
+ return `${LOCK_FILE_PREFIX}merger-${projectRuntimeKey(projectDir)}.lock`;
3676
+ }
3593
3677
  function isProcessRunning(pid) {
3594
3678
  try {
3595
3679
  process.kill(pid, 0);
@@ -3950,7 +4034,8 @@ function collectLogInfo(projectDir) {
3950
4034
  { name: "qa", fileName: `${QA_LOG_NAME}.log` },
3951
4035
  { name: "audit", fileName: `${AUDIT_LOG_NAME}.log` },
3952
4036
  { name: "planner", fileName: `${PLANNER_LOG_NAME}.log` },
3953
- { name: "analytics", fileName: `${ANALYTICS_LOG_NAME}.log` }
4037
+ { name: "analytics", fileName: `${ANALYTICS_LOG_NAME}.log` },
4038
+ { name: "merger", fileName: `${MERGER_LOG_NAME}.log` }
3954
4039
  ];
3955
4040
  return logEntries.map(({ name, fileName }) => {
3956
4041
  const logPath = path6.join(projectDir, LOG_DIR, fileName);
@@ -3980,13 +4065,15 @@ async function fetchStatusSnapshot(projectDir, config) {
3980
4065
  const auditLock = checkLockFile(auditLockPath(projectDir));
3981
4066
  const plannerLock = checkLockFile(plannerLockPath(projectDir));
3982
4067
  const analyticsLock = checkLockFile(analyticsLockPath(projectDir));
4068
+ const mergerLock = checkLockFile(mergerLockPath(projectDir));
3983
4069
  const processes = [
3984
4070
  { name: "executor", running: executorLock.running, pid: executorLock.pid },
3985
4071
  { name: "reviewer", running: reviewerLock.running, pid: reviewerLock.pid },
3986
4072
  { name: "qa", running: qaLock.running, pid: qaLock.pid },
3987
4073
  { name: "audit", running: auditLock.running, pid: auditLock.pid },
3988
4074
  { name: "planner", running: plannerLock.running, pid: plannerLock.pid },
3989
- { name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid }
4075
+ { name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid },
4076
+ { name: "merger", running: mergerLock.running, pid: mergerLock.pid }
3990
4077
  ];
3991
4078
  const prds = collectPrdInfo(projectDir, config.prdDir, config.maxRuntime);
3992
4079
  const prs = await collectPrInfo(projectDir, config.branchPatterns);
@@ -4900,6 +4987,10 @@ function getEventEmoji(event) {
4900
4987
  return "\u2705";
4901
4988
  case "pr_resolver_failed":
4902
4989
  return "\u274C";
4990
+ case "merge_completed":
4991
+ return "\u{1F500}";
4992
+ case "merge_failed":
4993
+ return "\u274C";
4903
4994
  }
4904
4995
  }
4905
4996
  function getEventTitle(event) {
@@ -4930,6 +5021,10 @@ function getEventTitle(event) {
4930
5021
  return "PR Conflict Resolved";
4931
5022
  case "pr_resolver_failed":
4932
5023
  return "PR Resolver Failed";
5024
+ case "merge_completed":
5025
+ return "PR Merged";
5026
+ case "merge_failed":
5027
+ return "Merge Failed";
4933
5028
  }
4934
5029
  }
4935
5030
  function getEventColor(event) {
@@ -4960,6 +5055,10 @@ function getEventColor(event) {
4960
5055
  return 65280;
4961
5056
  case "pr_resolver_failed":
4962
5057
  return 16711680;
5058
+ case "merge_completed":
5059
+ return 10181046;
5060
+ case "merge_failed":
5061
+ return 16711680;
4963
5062
  }
4964
5063
  }
4965
5064
  function buildDescription(ctx) {
@@ -5562,19 +5661,96 @@ var init_roadmap_parser = __esm({
5562
5661
  }
5563
5662
  });
5564
5663
 
5565
- // ../core/dist/utils/roadmap-state.js
5664
+ // ../core/dist/audit/report.js
5566
5665
  import * as fs14 from "fs";
5567
5666
  import * as path13 from "path";
5667
+ function normalizeAuditSeverity(raw) {
5668
+ const normalized = raw.trim().toLowerCase();
5669
+ if (normalized === "critical")
5670
+ return "critical";
5671
+ if (normalized === "high")
5672
+ return "high";
5673
+ if (normalized === "low")
5674
+ return "low";
5675
+ return "medium";
5676
+ }
5677
+ function extractAuditField(block, field) {
5678
+ const pattern = new RegExp(`- \\*\\*${field}\\*\\*:\\s*([\\s\\S]*?)(?=\\n- \\*\\*|\\n###\\s+Finding\\s+\\d+|$)`, "i");
5679
+ const match = block.match(pattern);
5680
+ if (!match)
5681
+ return "";
5682
+ return match[1].replace(/`/g, "").replace(/\r/g, "").trim();
5683
+ }
5684
+ function parseAuditFindings(reportContent) {
5685
+ const headingRegex = /^###\s+Finding\s+(\d+)\s*$/gm;
5686
+ const headings = [];
5687
+ let match;
5688
+ while ((match = headingRegex.exec(reportContent)) !== null) {
5689
+ const number = parseInt(match[1], 10);
5690
+ if (!Number.isNaN(number)) {
5691
+ headings.push({
5692
+ number,
5693
+ bodyStart: headingRegex.lastIndex,
5694
+ headingStart: match.index
5695
+ });
5696
+ }
5697
+ }
5698
+ if (headings.length === 0) {
5699
+ return [];
5700
+ }
5701
+ const findings = [];
5702
+ for (let i = 0; i < headings.length; i++) {
5703
+ const current = headings[i];
5704
+ const next = headings[i + 1];
5705
+ const block = reportContent.slice(current.bodyStart, next?.headingStart ?? reportContent.length);
5706
+ const severityRaw = extractAuditField(block, "Severity");
5707
+ const category = extractAuditField(block, "Category") || "uncategorized";
5708
+ const location = extractAuditField(block, "Location") || "unknown location";
5709
+ const description = extractAuditField(block, "Description") || "No description provided";
5710
+ const snippet = extractAuditField(block, "Snippet");
5711
+ const suggestedFix = extractAuditField(block, "Suggested Fix") || "No suggested fix provided";
5712
+ findings.push({
5713
+ number: current.number,
5714
+ severity: normalizeAuditSeverity(severityRaw),
5715
+ category,
5716
+ location,
5717
+ description,
5718
+ snippet,
5719
+ suggestedFix
5720
+ });
5721
+ }
5722
+ return findings;
5723
+ }
5724
+ function loadAuditFindings(projectDir) {
5725
+ const reportPath = path13.join(projectDir, "logs", "audit-report.md");
5726
+ if (!fs14.existsSync(reportPath)) {
5727
+ return [];
5728
+ }
5729
+ const reportContent = fs14.readFileSync(reportPath, "utf-8");
5730
+ if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
5731
+ return [];
5732
+ }
5733
+ return parseAuditFindings(reportContent);
5734
+ }
5735
+ var init_report = __esm({
5736
+ "../core/dist/audit/report.js"() {
5737
+ "use strict";
5738
+ }
5739
+ });
5740
+
5741
+ // ../core/dist/utils/roadmap-state.js
5742
+ import * as fs15 from "fs";
5743
+ import * as path14 from "path";
5568
5744
  function getStateFilePath(prdDir) {
5569
- return path13.join(prdDir, STATE_FILE_NAME);
5745
+ return path14.join(prdDir, STATE_FILE_NAME);
5570
5746
  }
5571
5747
  function readJsonState(prdDir) {
5572
5748
  const statePath = getStateFilePath(prdDir);
5573
- if (!fs14.existsSync(statePath)) {
5749
+ if (!fs15.existsSync(statePath)) {
5574
5750
  return null;
5575
5751
  }
5576
5752
  try {
5577
- const content = fs14.readFileSync(statePath, "utf-8");
5753
+ const content = fs15.readFileSync(statePath, "utf-8");
5578
5754
  const parsed = JSON.parse(content);
5579
5755
  if (typeof parsed !== "object" || parsed === null) {
5580
5756
  return null;
@@ -5612,11 +5788,11 @@ function saveRoadmapState(prdDir, state) {
5612
5788
  const { roadmapState } = getRepositories();
5613
5789
  roadmapState.save(prdDir, state);
5614
5790
  const statePath = getStateFilePath(prdDir);
5615
- const dir = path13.dirname(statePath);
5616
- if (!fs14.existsSync(dir)) {
5617
- fs14.mkdirSync(dir, { recursive: true });
5791
+ const dir = path14.dirname(statePath);
5792
+ if (!fs15.existsSync(dir)) {
5793
+ fs15.mkdirSync(dir, { recursive: true });
5618
5794
  }
5619
- fs14.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
5795
+ fs15.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
5620
5796
  }
5621
5797
  function createEmptyState() {
5622
5798
  return {
@@ -5656,16 +5832,16 @@ var init_roadmap_state = __esm({
5656
5832
  });
5657
5833
 
5658
5834
  // ../core/dist/templates/slicer-prompt.js
5659
- import * as fs15 from "fs";
5660
- import * as path14 from "path";
5835
+ import * as fs16 from "fs";
5836
+ import * as path15 from "path";
5661
5837
  import { fileURLToPath as fileURLToPath2 } from "url";
5662
5838
  function loadSlicerTemplate(templateDir) {
5663
5839
  if (cachedTemplate) {
5664
5840
  return cachedTemplate;
5665
5841
  }
5666
- const templatePath = templateDir ? path14.join(templateDir, "slicer.md") : path14.resolve(__dirname, "..", "..", "templates", "slicer.md");
5842
+ const templatePath = templateDir ? path15.join(templateDir, "slicer.md") : path15.resolve(__dirname, "..", "..", "templates", "slicer.md");
5667
5843
  try {
5668
- cachedTemplate = fs15.readFileSync(templatePath, "utf-8");
5844
+ cachedTemplate = fs16.readFileSync(templatePath, "utf-8");
5669
5845
  return cachedTemplate;
5670
5846
  } catch (error2) {
5671
5847
  console.warn(`Warning: Could not load slicer template from ${templatePath}, using default:`, error2 instanceof Error ? error2.message : String(error2));
@@ -5690,7 +5866,7 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
5690
5866
  title,
5691
5867
  section,
5692
5868
  description: description || "(No description provided)",
5693
- outputFilePath: path14.join(prdDir, prdFilename),
5869
+ outputFilePath: path15.join(prdDir, prdFilename),
5694
5870
  prdDir
5695
5871
  };
5696
5872
  }
@@ -5699,10 +5875,10 @@ var init_slicer_prompt = __esm({
5699
5875
  "../core/dist/templates/slicer-prompt.js"() {
5700
5876
  "use strict";
5701
5877
  __filename = fileURLToPath2(import.meta.url);
5702
- __dirname = path14.dirname(__filename);
5703
- DEFAULT_SLICER_TEMPLATE = `You are a **PRD Creator Agent**. Your job: analyze the codebase and write a complete Product Requirements Document (PRD) for a feature.
5878
+ __dirname = path15.dirname(__filename);
5879
+ DEFAULT_SLICER_TEMPLATE = `You are a **Principal Software Architect**. Your job: analyze the codebase and write a complete Product Requirements Document (PRD) for a feature. The PRD will be used directly as a GitHub issue body, so it must be self-contained and immediately actionable by an engineer.
5704
5880
 
5705
- When this activates: \`PRD Creator: Initializing\`
5881
+ When this activates: \`Planning Mode: Principal Architect\`
5706
5882
 
5707
5883
  ---
5708
5884
 
@@ -5723,22 +5899,16 @@ The PRD directory is: \`{{PRD_DIR}}\`
5723
5899
 
5724
5900
  ## Your Task
5725
5901
 
5726
- 0. **Load Planner Skill** - Read and apply \`.claude/skills/prd-creator/SKILL.md\` before writing the PRD. If unavailable, continue with this template.
5727
-
5728
- 1. **Explore the Codebase** - Read relevant existing files to understand the project structure, patterns, and conventions.
5729
-
5730
- 2. **Assess Complexity** - Score the complexity using the rubric and determine whether this is LOW, MEDIUM, or HIGH complexity.
5731
-
5732
- 3. **Write a Complete PRD** - Create a full PRD following the prd-creator template structure with Context, Solution, Phases, Tests, and Acceptance Criteria.
5733
-
5734
- 4. **Write the PRD File** - Use the Write tool to create the PRD file at the exact path specified in \`{{OUTPUT_FILE_PATH}}\`.
5902
+ 1. **Explore the Codebase** \u2014 Read relevant existing files to understand structure, patterns, and conventions.
5903
+ 2. **Assess Complexity** \u2014 Score using the rubric below and determine LOW / MEDIUM / HIGH.
5904
+ 3. **Write a Complete PRD** \u2014 Follow the exact template structure below. Every section must be filled with concrete information.
5905
+ 4. **Write the PRD File** \u2014 Use the Write tool to create the PRD file at \`{{OUTPUT_FILE_PATH}}\`.
5735
5906
 
5736
5907
  ---
5737
5908
 
5738
5909
  ## Complexity Scoring
5739
5910
 
5740
5911
  \`\`\`
5741
- COMPLEXITY SCORE (sum all that apply):
5742
5912
  +1 Touches 1-5 files
5743
5913
  +2 Touches 6-10 files
5744
5914
  +3 Touches 10+ files
@@ -5750,7 +5920,7 @@ COMPLEXITY SCORE (sum all that apply):
5750
5920
 
5751
5921
  | Score | Level | Template Mode |
5752
5922
  | ----- | ------ | ----------------------------------------------- |
5753
- | 1-3 | LOW | Minimal (skip sections marked with MEDIUM/HIGH) |
5923
+ | 1-3 | LOW | Minimal (skip sections marked MEDIUM/HIGH) |
5754
5924
  | 4-6 | MEDIUM | Standard (all sections) |
5755
5925
  | 7+ | HIGH | Full + mandatory checkpoints every phase |
5756
5926
  \`\`\`
@@ -5759,25 +5929,77 @@ COMPLEXITY SCORE (sum all that apply):
5759
5929
 
5760
5930
  ## PRD Template Structure
5761
5931
 
5762
- Your PRD MUST follow this exact structure with these sections:
5763
- 1. **Context** - Problem, files analyzed, current behavior, integration points
5764
- 2. **Solution** - Approach, architecture diagram, key decisions, data changes
5765
- 3. **Sequence Flow** (MEDIUM/HIGH) - Mermaid sequence diagram
5766
- 4. **Execution Phases** - Concrete phases with files, implementation steps, and tests
5767
- 5. **Acceptance Criteria** - Checklist of completion requirements
5932
+ Your PRD MUST use this structure. Replace every [bracketed placeholder] with real content.
5933
+
5934
+ # PRD: [Title]
5935
+
5936
+ **Complexity: [SCORE] \u2192 [LEVEL] mode**
5937
+
5938
+ ## 1. Context
5939
+
5940
+ **Problem:** [1-2 sentences]
5941
+
5942
+ **Files Analyzed:**
5943
+ - \`path/to/file.ts\` \u2014 [what you found]
5944
+
5945
+ **Current Behavior:**
5946
+ - [3-5 bullets]
5947
+
5948
+ ### Integration Points
5949
+ - Entry point: [cron / CLI / event / route]
5950
+ - Caller file: [file invoking new code]
5951
+ - User flow: User does X \u2192 triggers Y \u2192 result Z
5952
+
5953
+ ## 2. Solution
5954
+
5955
+ **Approach:**
5956
+ - [3-5 bullets]
5957
+
5958
+ **Key Decisions:** [library choices, error handling, reused utilities]
5959
+
5960
+ **Data Changes:** [schema changes, or "None"]
5961
+
5962
+ ## 3. Sequence Flow (MEDIUM/HIGH only)
5963
+
5964
+ [mermaid sequenceDiagram]
5965
+
5966
+ ## 4. Execution Phases
5967
+
5968
+ ### Phase N: [Name] \u2014 [User-visible outcome]
5969
+
5970
+ **Files (max 5):**
5971
+ - \`src/path/file.ts\` \u2014 [what changes]
5972
+
5973
+ **Implementation:**
5974
+ - [ ] Step 1
5975
+
5976
+ **Tests Required:**
5977
+ | Test File | Test Name | Assertion |
5978
+ |-----------|-----------|-----------|
5979
+ | \`src/__tests__/feature.test.ts\` | \`should X when Y\` | \`expect(r).toBe(Z)\` |
5980
+
5981
+ **Checkpoint:** Run \`yarn verify\` and related tests after this phase.
5982
+
5983
+ ## 5. Acceptance Criteria
5984
+
5985
+ - [ ] All phases complete
5986
+ - [ ] All tests pass
5987
+ - [ ] \`yarn verify\` passes
5988
+ - [ ] Feature is reachable (not orphaned code)
5768
5989
 
5769
5990
  ---
5770
5991
 
5771
5992
  ## Critical Instructions
5772
5993
 
5773
- 1. **Read all relevant existing files BEFORE writing any code**
5774
- 2. **Follow existing patterns in the codebase**
5775
- 3. **Write the PRD with concrete file paths and implementation details**
5776
- 4. **Include specific test names and assertions**
5777
- 5. **Use the Write tool to create the PRD file at \`{{OUTPUT_FILE_PATH}}\`**
5778
- 6. **The PRD must be complete and actionable - no TODO placeholders**
5994
+ 1. Read all relevant files BEFORE writing the PRD
5995
+ 2. Follow existing patterns \u2014 use \`@/*\` path aliases, match naming conventions
5996
+ 3. Include concrete file paths and implementation steps
5997
+ 4. Include specific test names and assertions
5998
+ 5. Use the Write tool to create the file at \`{{OUTPUT_FILE_PATH}}\`
5999
+ 6. No placeholder text in the final PRD
6000
+ 7. The PRD is the GitHub issue body \u2014 make it self-contained
5779
6001
 
5780
- DO NOT leave placeholder text like "[Name]" or "[description]" in the final PRD.
6002
+ DO NOT leave [bracketed placeholder] text in the output.
5781
6003
  DO NOT skip any sections.
5782
6004
  DO NOT forget to write the file.
5783
6005
  `;
@@ -5786,65 +6008,10 @@ DO NOT forget to write the file.
5786
6008
  });
5787
6009
 
5788
6010
  // ../core/dist/utils/roadmap-scanner.js
5789
- import * as fs16 from "fs";
5790
- import * as path15 from "path";
6011
+ import * as fs17 from "fs";
6012
+ import * as path16 from "path";
5791
6013
  import { spawn } from "child_process";
5792
6014
  import { createHash as createHash3 } from "crypto";
5793
- function normalizeAuditSeverity(raw) {
5794
- const normalized = raw.trim().toLowerCase();
5795
- if (normalized === "critical")
5796
- return "critical";
5797
- if (normalized === "high")
5798
- return "high";
5799
- if (normalized === "low")
5800
- return "low";
5801
- return "medium";
5802
- }
5803
- function extractAuditField(block, field) {
5804
- const pattern = new RegExp(`- \\*\\*${field}\\*\\*:\\s*([\\s\\S]*?)(?=\\n- \\*\\*|\\n###\\s+Finding\\s+\\d+|$)`, "i");
5805
- const match = block.match(pattern);
5806
- if (!match)
5807
- return "";
5808
- return match[1].replace(/`/g, "").replace(/\r/g, "").trim();
5809
- }
5810
- function parseAuditFindings(reportContent) {
5811
- const headingRegex = /^###\s+Finding\s+(\d+)\s*$/gm;
5812
- const headings = [];
5813
- let match;
5814
- while ((match = headingRegex.exec(reportContent)) !== null) {
5815
- const number = parseInt(match[1], 10);
5816
- if (!Number.isNaN(number)) {
5817
- headings.push({
5818
- number,
5819
- bodyStart: headingRegex.lastIndex,
5820
- headingStart: match.index
5821
- });
5822
- }
5823
- }
5824
- if (headings.length === 0) {
5825
- return [];
5826
- }
5827
- const findings = [];
5828
- for (let i = 0; i < headings.length; i++) {
5829
- const current = headings[i];
5830
- const next = headings[i + 1];
5831
- const block = reportContent.slice(current.bodyStart, next?.headingStart ?? reportContent.length);
5832
- const severityRaw = extractAuditField(block, "Severity");
5833
- const category = extractAuditField(block, "Category") || "uncategorized";
5834
- const location = extractAuditField(block, "Location") || "unknown location";
5835
- const description = extractAuditField(block, "Description") || "No description provided";
5836
- const suggestedFix = extractAuditField(block, "Suggested Fix") || "No suggested fix provided";
5837
- findings.push({
5838
- number: current.number,
5839
- severity: normalizeAuditSeverity(severityRaw),
5840
- category,
5841
- location,
5842
- description,
5843
- suggestedFix
5844
- });
5845
- }
5846
- return findings;
5847
- }
5848
6015
  function auditSeverityRank(severity) {
5849
6016
  switch (severity) {
5850
6017
  case "critical":
@@ -5888,22 +6055,14 @@ function auditFindingToRoadmapItem(finding) {
5888
6055
  };
5889
6056
  }
5890
6057
  function collectAuditPlannerItems(projectDir) {
5891
- const reportPath = path15.join(projectDir, "logs", "audit-report.md");
5892
- if (!fs16.existsSync(reportPath)) {
5893
- return [];
5894
- }
5895
- const reportContent = fs16.readFileSync(reportPath, "utf-8");
5896
- if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
5897
- return [];
5898
- }
5899
- const findings = parseAuditFindings(reportContent);
6058
+ const findings = loadAuditFindings(projectDir);
5900
6059
  findings.sort((a, b) => auditSeverityRank(a.severity) - auditSeverityRank(b.severity) || a.number - b.number);
5901
6060
  return findings.map(auditFindingToRoadmapItem);
5902
6061
  }
5903
6062
  function getRoadmapStatus(projectDir, config) {
5904
- const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
6063
+ const roadmapPath = path16.join(projectDir, config.roadmapScanner.roadmapPath);
5905
6064
  const scannerEnabled = config.roadmapScanner.enabled;
5906
- if (!fs16.existsSync(roadmapPath)) {
6065
+ if (!fs17.existsSync(roadmapPath)) {
5907
6066
  return {
5908
6067
  found: false,
5909
6068
  enabled: scannerEnabled,
@@ -5914,9 +6073,9 @@ function getRoadmapStatus(projectDir, config) {
5914
6073
  items: []
5915
6074
  };
5916
6075
  }
5917
- const content = fs16.readFileSync(roadmapPath, "utf-8");
6076
+ const content = fs17.readFileSync(roadmapPath, "utf-8");
5918
6077
  const items = parseRoadmap(content);
5919
- const prdDir = path15.join(projectDir, config.prdDir);
6078
+ const prdDir = path16.join(projectDir, config.prdDir);
5920
6079
  const state = loadRoadmapState(prdDir);
5921
6080
  const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
5922
6081
  const statusItems = items.map((item) => {
@@ -5953,10 +6112,10 @@ function getRoadmapStatus(projectDir, config) {
5953
6112
  }
5954
6113
  function scanExistingPrdSlugs(prdDir) {
5955
6114
  const slugs = /* @__PURE__ */ new Set();
5956
- if (!fs16.existsSync(prdDir)) {
6115
+ if (!fs17.existsSync(prdDir)) {
5957
6116
  return slugs;
5958
6117
  }
5959
- const files = fs16.readdirSync(prdDir);
6118
+ const files = fs17.readdirSync(prdDir);
5960
6119
  for (const file of files) {
5961
6120
  if (!file.endsWith(".md")) {
5962
6121
  continue;
@@ -5992,20 +6151,20 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
5992
6151
  const nextNum = getNextPrdNumber(prdDir);
5993
6152
  const padded = String(nextNum).padStart(2, "0");
5994
6153
  const filename = `${padded}-${itemSlug}.md`;
5995
- const filePath = path15.join(prdDir, filename);
5996
- if (!fs16.existsSync(prdDir)) {
5997
- fs16.mkdirSync(prdDir, { recursive: true });
6154
+ const filePath = path16.join(prdDir, filename);
6155
+ if (!fs17.existsSync(prdDir)) {
6156
+ fs17.mkdirSync(prdDir, { recursive: true });
5998
6157
  }
5999
6158
  const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
6000
6159
  const prompt = renderSlicerPrompt(promptVars);
6001
6160
  const provider = resolveJobProvider(config, "slicer");
6002
6161
  const providerArgs = buildProviderArgs(provider, prompt, projectDir);
6003
- const logDir = path15.join(projectDir, "logs");
6004
- if (!fs16.existsSync(logDir)) {
6005
- fs16.mkdirSync(logDir, { recursive: true });
6162
+ const logDir = path16.join(projectDir, "logs");
6163
+ if (!fs17.existsSync(logDir)) {
6164
+ fs17.mkdirSync(logDir, { recursive: true });
6006
6165
  }
6007
- const logFile = path15.join(logDir, `slicer-${itemSlug}.log`);
6008
- const logStream = fs16.createWriteStream(logFile, { flags: "w" });
6166
+ const logFile = path16.join(logDir, `slicer-${itemSlug}.log`);
6167
+ const logStream = fs17.createWriteStream(logFile, { flags: "w" });
6009
6168
  logStream.on("error", () => {
6010
6169
  });
6011
6170
  return new Promise((resolve9) => {
@@ -6042,7 +6201,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
6042
6201
  });
6043
6202
  return;
6044
6203
  }
6045
- if (!fs16.existsSync(filePath)) {
6204
+ if (!fs17.existsSync(filePath)) {
6046
6205
  resolve9({
6047
6206
  sliced: false,
6048
6207
  error: `Provider did not create expected file: ${filePath}`,
@@ -6065,23 +6224,23 @@ async function sliceNextItem(projectDir, config) {
6065
6224
  error: "Roadmap scanner is disabled"
6066
6225
  };
6067
6226
  }
6068
- const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
6227
+ const roadmapPath = path16.join(projectDir, config.roadmapScanner.roadmapPath);
6069
6228
  const auditItems = collectAuditPlannerItems(projectDir);
6070
- const roadmapExists = fs16.existsSync(roadmapPath);
6229
+ const roadmapExists = fs17.existsSync(roadmapPath);
6071
6230
  if (!roadmapExists && auditItems.length === 0) {
6072
6231
  return {
6073
6232
  sliced: false,
6074
6233
  error: "No pending items to process"
6075
6234
  };
6076
6235
  }
6077
- const roadmapItems = roadmapExists ? parseRoadmap(fs16.readFileSync(roadmapPath, "utf-8")) : [];
6236
+ const roadmapItems = roadmapExists ? parseRoadmap(fs17.readFileSync(roadmapPath, "utf-8")) : [];
6078
6237
  if (roadmapExists && roadmapItems.length === 0 && auditItems.length === 0) {
6079
6238
  return {
6080
6239
  sliced: false,
6081
6240
  error: "No items in roadmap"
6082
6241
  };
6083
6242
  }
6084
- const prdDir = path15.join(projectDir, config.prdDir);
6243
+ const prdDir = path16.join(projectDir, config.prdDir);
6085
6244
  const state = loadRoadmapState(prdDir);
6086
6245
  const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
6087
6246
  const pickEligibleItem = (items) => {
@@ -6150,6 +6309,7 @@ function hasNewItems(projectDir, config) {
6150
6309
  var init_roadmap_scanner = __esm({
6151
6310
  "../core/dist/utils/roadmap-scanner.js"() {
6152
6311
  "use strict";
6312
+ init_report();
6153
6313
  init_config();
6154
6314
  init_prd_utils();
6155
6315
  init_roadmap_parser();
@@ -6252,8 +6412,8 @@ var init_shell = __esm({
6252
6412
  });
6253
6413
 
6254
6414
  // ../core/dist/utils/scheduling.js
6255
- import * as fs17 from "fs";
6256
- import * as path16 from "path";
6415
+ import * as fs18 from "fs";
6416
+ import * as path17 from "path";
6257
6417
  function normalizeSchedulingPriority(priority) {
6258
6418
  if (!Number.isFinite(priority)) {
6259
6419
  return DEFAULT_SCHEDULING_PRIORITY;
@@ -6288,7 +6448,7 @@ function getJobSchedule(config, jobType) {
6288
6448
  }
6289
6449
  }
6290
6450
  function loadPeerConfig(projectPath) {
6291
- if (!fs17.existsSync(projectPath) || !fs17.existsSync(path16.join(projectPath, CONFIG_FILE_NAME))) {
6451
+ if (!fs18.existsSync(projectPath) || !fs18.existsSync(path17.join(projectPath, CONFIG_FILE_NAME))) {
6292
6452
  return null;
6293
6453
  }
6294
6454
  try {
@@ -6299,10 +6459,10 @@ function loadPeerConfig(projectPath) {
6299
6459
  }
6300
6460
  function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
6301
6461
  const peers = /* @__PURE__ */ new Map();
6302
- const currentPath = path16.resolve(currentProjectDir);
6462
+ const currentPath = path17.resolve(currentProjectDir);
6303
6463
  const currentSchedule = getJobSchedule(currentConfig, jobType);
6304
6464
  const addPeer = (projectPath, config) => {
6305
- const resolvedPath = path16.resolve(projectPath);
6465
+ const resolvedPath = path17.resolve(projectPath);
6306
6466
  if (!isJobTypeEnabled(config, jobType)) {
6307
6467
  return;
6308
6468
  }
@@ -6313,12 +6473,12 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
6313
6473
  path: resolvedPath,
6314
6474
  config,
6315
6475
  schedulingPriority: normalizeSchedulingPriority(config.schedulingPriority),
6316
- sortKey: `${path16.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
6476
+ sortKey: `${path17.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
6317
6477
  });
6318
6478
  };
6319
6479
  addPeer(currentPath, currentConfig);
6320
6480
  for (const entry of loadRegistry()) {
6321
- const resolvedPath = path16.resolve(entry.path);
6481
+ const resolvedPath = path17.resolve(entry.path);
6322
6482
  if (resolvedPath === currentPath || peers.has(resolvedPath)) {
6323
6483
  continue;
6324
6484
  }
@@ -6336,7 +6496,7 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
6336
6496
  }
6337
6497
  function getSchedulingPlan(projectDir, config, jobType) {
6338
6498
  const peers = collectSchedulingPeers(projectDir, config, jobType);
6339
- const currentPath = path16.resolve(projectDir);
6499
+ const currentPath = path17.resolve(projectDir);
6340
6500
  const slotIndex = Math.max(0, peers.findIndex((peer) => peer.path === currentPath));
6341
6501
  const peerCount = Math.max(1, peers.length);
6342
6502
  const balancedDelayMinutes = peerCount <= 1 ? 0 : Math.floor(slotIndex * 60 / peerCount);
@@ -6428,8 +6588,8 @@ var init_webhook_validator = __esm({
6428
6588
 
6429
6589
  // ../core/dist/utils/worktree-manager.js
6430
6590
  import { execFileSync as execFileSync4 } from "child_process";
6431
- import * as fs18 from "fs";
6432
- import * as path17 from "path";
6591
+ import * as fs19 from "fs";
6592
+ import * as path18 from "path";
6433
6593
  function gitExec(args, cwd, logFile) {
6434
6594
  try {
6435
6595
  const result = execFileSync4("git", args, {
@@ -6439,7 +6599,7 @@ function gitExec(args, cwd, logFile) {
6439
6599
  });
6440
6600
  if (logFile && result) {
6441
6601
  try {
6442
- fs18.appendFileSync(logFile, result);
6602
+ fs19.appendFileSync(logFile, result);
6443
6603
  } catch {
6444
6604
  }
6445
6605
  }
@@ -6448,7 +6608,7 @@ function gitExec(args, cwd, logFile) {
6448
6608
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
6449
6609
  if (logFile) {
6450
6610
  try {
6451
- fs18.appendFileSync(logFile, errorMessage + "\n");
6611
+ fs19.appendFileSync(logFile, errorMessage + "\n");
6452
6612
  } catch {
6453
6613
  }
6454
6614
  }
@@ -6473,11 +6633,11 @@ function branchExistsRemotely(projectDir, branchName) {
6473
6633
  }
6474
6634
  function prepareBranchWorktree(options) {
6475
6635
  const { projectDir, worktreeDir, branchName, defaultBranch, logFile } = options;
6476
- if (fs18.existsSync(worktreeDir)) {
6636
+ if (fs19.existsSync(worktreeDir)) {
6477
6637
  const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
6478
6638
  if (!isRegistered) {
6479
6639
  try {
6480
- fs18.rmSync(worktreeDir, { recursive: true, force: true });
6640
+ fs19.rmSync(worktreeDir, { recursive: true, force: true });
6481
6641
  } catch {
6482
6642
  }
6483
6643
  }
@@ -6516,11 +6676,11 @@ function prepareBranchWorktree(options) {
6516
6676
  }
6517
6677
  function prepareDetachedWorktree(options) {
6518
6678
  const { projectDir, worktreeDir, defaultBranch, logFile } = options;
6519
- if (fs18.existsSync(worktreeDir)) {
6679
+ if (fs19.existsSync(worktreeDir)) {
6520
6680
  const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
6521
6681
  if (!isRegistered) {
6522
6682
  try {
6523
- fs18.rmSync(worktreeDir, { recursive: true, force: true });
6683
+ fs19.rmSync(worktreeDir, { recursive: true, force: true });
6524
6684
  } catch {
6525
6685
  }
6526
6686
  }
@@ -6562,7 +6722,7 @@ function isWorktreeRegistered(projectDir, worktreePath) {
6562
6722
  }
6563
6723
  }
6564
6724
  function cleanupWorktrees(projectDir, scope) {
6565
- const projectName = path17.basename(projectDir);
6725
+ const projectName = path18.basename(projectDir);
6566
6726
  const matchToken = scope ? scope : `${projectName}-nw`;
6567
6727
  const removed = [];
6568
6728
  try {
@@ -6598,11 +6758,11 @@ var init_worktree_manager = __esm({
6598
6758
 
6599
6759
  // ../core/dist/utils/job-queue.js
6600
6760
  import * as os7 from "os";
6601
- import * as path18 from "path";
6761
+ import * as path19 from "path";
6602
6762
  import Database7 from "better-sqlite3";
6603
6763
  function getStateDbPath() {
6604
- const base = process.env.NIGHT_WATCH_HOME || path18.join(os7.homedir(), GLOBAL_CONFIG_DIR);
6605
- return path18.join(base, STATE_DB_FILE_NAME);
6764
+ const base = process.env.NIGHT_WATCH_HOME || path19.join(os7.homedir(), GLOBAL_CONFIG_DIR);
6765
+ return path19.join(base, STATE_DB_FILE_NAME);
6606
6766
  }
6607
6767
  function openDb() {
6608
6768
  const dbPath = getStateDbPath();
@@ -6647,6 +6807,8 @@ function getLockPathForJob(projectPath, jobType) {
6647
6807
  return analyticsLockPath(projectPath);
6648
6808
  case "pr-resolver":
6649
6809
  return prResolverLockPath(projectPath);
6810
+ case "merger":
6811
+ return mergerLockPath(projectPath);
6650
6812
  }
6651
6813
  }
6652
6814
  function reconcileStaleRunningJobs(db) {
@@ -7361,9 +7523,9 @@ var init_amplitude_client = __esm({
7361
7523
  });
7362
7524
 
7363
7525
  // ../core/dist/analytics/analytics-runner.js
7364
- import * as fs19 from "fs";
7526
+ import * as fs20 from "fs";
7365
7527
  import * as os8 from "os";
7366
- import * as path19 from "path";
7528
+ import * as path20 from "path";
7367
7529
  function parseIssuesFromResponse(text) {
7368
7530
  const start = text.indexOf("[");
7369
7531
  const end = text.lastIndexOf("]");
@@ -7392,9 +7554,9 @@ async function runAnalytics(config, projectDir) {
7392
7554
 
7393
7555
  --- AMPLITUDE DATA ---
7394
7556
  ${JSON.stringify(data, null, 2)}`;
7395
- const tmpDir = fs19.mkdtempSync(path19.join(os8.tmpdir(), "nw-analytics-"));
7396
- const promptFile = path19.join(tmpDir, "analytics-prompt.md");
7397
- fs19.writeFileSync(promptFile, prompt, "utf-8");
7557
+ const tmpDir = fs20.mkdtempSync(path20.join(os8.tmpdir(), "nw-analytics-"));
7558
+ const promptFile = path20.join(tmpDir, "analytics-prompt.md");
7559
+ fs20.writeFileSync(promptFile, prompt, "utf-8");
7398
7560
  try {
7399
7561
  const provider = resolveJobProvider(config, "analytics");
7400
7562
  let providerCmd = PROVIDER_COMMANDS[provider];
@@ -7415,8 +7577,8 @@ set -euo pipefail
7415
7577
  ${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
7416
7578
  `;
7417
7579
  }
7418
- const scriptFile = path19.join(tmpDir, "run-analytics.sh");
7419
- fs19.writeFileSync(scriptFile, scriptContent, { mode: 493 });
7580
+ const scriptFile = path20.join(tmpDir, "run-analytics.sh");
7581
+ fs20.writeFileSync(scriptFile, scriptContent, { mode: 493 });
7420
7582
  const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], config.providerEnv ?? {});
7421
7583
  if (exitCode !== 0) {
7422
7584
  throw new Error(`AI provider exited with code ${exitCode}: ${stderr || stdout}`);
@@ -7463,7 +7625,7 @@ ${stderr}`;
7463
7625
  };
7464
7626
  } finally {
7465
7627
  try {
7466
- fs19.rmSync(tmpDir, { recursive: true, force: true });
7628
+ fs20.rmSync(tmpDir, { recursive: true, force: true });
7467
7629
  } catch {
7468
7630
  }
7469
7631
  }
@@ -7491,6 +7653,160 @@ var init_analytics = __esm({
7491
7653
  }
7492
7654
  });
7493
7655
 
7656
+ // ../core/dist/audit/board-sync.js
7657
+ function humanizeCategory(category) {
7658
+ return category.trim().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
7659
+ }
7660
+ function severityToPriorityLabel(severity) {
7661
+ switch (severity) {
7662
+ case "critical":
7663
+ return "P0";
7664
+ case "high":
7665
+ return "P1";
7666
+ default:
7667
+ return "P2";
7668
+ }
7669
+ }
7670
+ function truncateTitle(title, maxLength = 240) {
7671
+ const normalized = title.replace(/\s+/g, " ").trim();
7672
+ if (normalized.length <= maxLength) {
7673
+ return normalized;
7674
+ }
7675
+ return `${normalized.slice(0, maxLength - 1).trimEnd()}\u2026`;
7676
+ }
7677
+ function buildIssueTitle(finding) {
7678
+ return truncateTitle(`Audit: ${finding.severity} ${humanizeCategory(finding.category)} in ${finding.location}`);
7679
+ }
7680
+ function buildIssueBody(finding) {
7681
+ const lines = [
7682
+ "## Summary",
7683
+ "",
7684
+ `Night Watch audit detected a **${finding.severity}** finding in \`${finding.location}\`.`,
7685
+ "",
7686
+ "## Category",
7687
+ "",
7688
+ `\`${finding.category}\``,
7689
+ "",
7690
+ "## Description",
7691
+ "",
7692
+ finding.description,
7693
+ "",
7694
+ "## Suggested Fix",
7695
+ "",
7696
+ finding.suggestedFix
7697
+ ];
7698
+ if (finding.snippet) {
7699
+ lines.push("", "## Snippet", "", "```", finding.snippet, "```");
7700
+ }
7701
+ lines.push("", "## Source", "", "- Report: `logs/audit-report.md`", `- Finding: ${finding.number}`);
7702
+ return lines.join("\n");
7703
+ }
7704
+ async function syncAuditFindingsToBoard(config, projectDir) {
7705
+ const findings = loadAuditFindings(projectDir);
7706
+ const targetColumn = config.audit.targetColumn;
7707
+ if (findings.length === 0) {
7708
+ return {
7709
+ status: "skipped",
7710
+ findingsCount: 0,
7711
+ issuesCreated: 0,
7712
+ issuesFailed: 0,
7713
+ targetColumn: null,
7714
+ summary: "no actionable audit findings to sync"
7715
+ };
7716
+ }
7717
+ if (!config.boardProvider.enabled) {
7718
+ return {
7719
+ status: "skipped",
7720
+ findingsCount: findings.length,
7721
+ issuesCreated: 0,
7722
+ issuesFailed: 0,
7723
+ targetColumn: null,
7724
+ summary: `found ${findings.length} actionable audit finding(s); board sync skipped because board provider is disabled`
7725
+ };
7726
+ }
7727
+ let boardProvider;
7728
+ try {
7729
+ boardProvider = createBoardProvider(config.boardProvider, projectDir);
7730
+ } catch (err) {
7731
+ const message = err instanceof Error ? err.message : String(err);
7732
+ logger5.error("Failed to create board provider for audit sync", { error: message });
7733
+ return {
7734
+ status: "failed",
7735
+ findingsCount: findings.length,
7736
+ issuesCreated: 0,
7737
+ issuesFailed: findings.length,
7738
+ targetColumn,
7739
+ summary: `found ${findings.length} actionable audit finding(s), but board sync failed: ${message}`
7740
+ };
7741
+ }
7742
+ let created = 0;
7743
+ let failed = 0;
7744
+ for (const finding of findings) {
7745
+ try {
7746
+ await boardProvider.createIssue({
7747
+ title: buildIssueTitle(finding),
7748
+ body: buildIssueBody(finding),
7749
+ column: targetColumn,
7750
+ labels: [severityToPriorityLabel(finding.severity)]
7751
+ });
7752
+ created++;
7753
+ } catch (err) {
7754
+ failed++;
7755
+ logger5.error("Failed to create board issue for audit finding", {
7756
+ finding: finding.number,
7757
+ error: err instanceof Error ? err.message : String(err)
7758
+ });
7759
+ }
7760
+ }
7761
+ if (failed === 0) {
7762
+ return {
7763
+ status: "success",
7764
+ findingsCount: findings.length,
7765
+ issuesCreated: created,
7766
+ issuesFailed: 0,
7767
+ targetColumn,
7768
+ summary: `created ${created} audit issue(s) in ${targetColumn}`
7769
+ };
7770
+ }
7771
+ if (created === 0) {
7772
+ return {
7773
+ status: "failed",
7774
+ findingsCount: findings.length,
7775
+ issuesCreated: 0,
7776
+ issuesFailed: failed,
7777
+ targetColumn,
7778
+ summary: `found ${findings.length} actionable audit finding(s), but failed to create board issue(s)`
7779
+ };
7780
+ }
7781
+ return {
7782
+ status: "partial",
7783
+ findingsCount: findings.length,
7784
+ issuesCreated: created,
7785
+ issuesFailed: failed,
7786
+ targetColumn,
7787
+ summary: `created ${created} of ${findings.length} audit issue(s) in ${targetColumn} (${failed} failed)`
7788
+ };
7789
+ }
7790
+ var logger5;
7791
+ var init_board_sync = __esm({
7792
+ "../core/dist/audit/board-sync.js"() {
7793
+ "use strict";
7794
+ init_factory();
7795
+ init_logger();
7796
+ init_report();
7797
+ logger5 = createLogger("audit-sync");
7798
+ }
7799
+ });
7800
+
7801
+ // ../core/dist/audit/index.js
7802
+ var init_audit = __esm({
7803
+ "../core/dist/audit/index.js"() {
7804
+ "use strict";
7805
+ init_report();
7806
+ init_board_sync();
7807
+ }
7808
+ });
7809
+
7494
7810
  // ../core/dist/templates/prd-template.js
7495
7811
  function renderDependsOn(deps) {
7496
7812
  if (deps.length === 0) {
@@ -7698,6 +8014,7 @@ __export(dist_exports, {
7698
8014
  DEFAULT_AUDIT_ENABLED: () => DEFAULT_AUDIT_ENABLED,
7699
8015
  DEFAULT_AUDIT_MAX_RUNTIME: () => DEFAULT_AUDIT_MAX_RUNTIME,
7700
8016
  DEFAULT_AUDIT_SCHEDULE: () => DEFAULT_AUDIT_SCHEDULE,
8017
+ DEFAULT_AUDIT_TARGET_COLUMN: () => DEFAULT_AUDIT_TARGET_COLUMN,
7701
8018
  DEFAULT_AUTO_MERGE: () => DEFAULT_AUTO_MERGE,
7702
8019
  DEFAULT_AUTO_MERGE_METHOD: () => DEFAULT_AUTO_MERGE_METHOD,
7703
8020
  DEFAULT_BOARD_PROVIDER: () => DEFAULT_BOARD_PROVIDER,
@@ -7714,6 +8031,14 @@ __export(dist_exports, {
7714
8031
  DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
7715
8032
  DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
7716
8033
  DEFAULT_MAX_RUNTIME: () => DEFAULT_MAX_RUNTIME,
8034
+ DEFAULT_MERGER: () => DEFAULT_MERGER,
8035
+ DEFAULT_MERGER_ENABLED: () => DEFAULT_MERGER_ENABLED,
8036
+ DEFAULT_MERGER_MAX_PRS_PER_RUN: () => DEFAULT_MERGER_MAX_PRS_PER_RUN,
8037
+ DEFAULT_MERGER_MAX_RUNTIME: () => DEFAULT_MERGER_MAX_RUNTIME,
8038
+ DEFAULT_MERGER_MERGE_METHOD: () => DEFAULT_MERGER_MERGE_METHOD,
8039
+ DEFAULT_MERGER_MIN_REVIEW_SCORE: () => DEFAULT_MERGER_MIN_REVIEW_SCORE,
8040
+ DEFAULT_MERGER_REBASE_BEFORE_MERGE: () => DEFAULT_MERGER_REBASE_BEFORE_MERGE,
8041
+ DEFAULT_MERGER_SCHEDULE: () => DEFAULT_MERGER_SCHEDULE,
7717
8042
  DEFAULT_MIN_REVIEW_SCORE: () => DEFAULT_MIN_REVIEW_SCORE,
7718
8043
  DEFAULT_NOTIFICATIONS: () => DEFAULT_NOTIFICATIONS,
7719
8044
  DEFAULT_PRD_DIR: () => DEFAULT_PRD_DIR,
@@ -7772,6 +8097,7 @@ __export(dist_exports, {
7772
8097
  LocalKanbanProvider: () => LocalKanbanProvider,
7773
8098
  Logger: () => Logger,
7774
8099
  MAX_HISTORY_RECORDS_PER_PRD: () => MAX_HISTORY_RECORDS_PER_PRD,
8100
+ MERGER_LOG_NAME: () => MERGER_LOG_NAME,
7775
8101
  NIGHT_WATCH_LABELS: () => NIGHT_WATCH_LABELS,
7776
8102
  PLANNER_LOG_NAME: () => PLANNER_LOG_NAME,
7777
8103
  PRD_STATES_FILE_NAME: () => PRD_STATES_FILE_NAME,
@@ -7927,6 +8253,7 @@ __export(dist_exports, {
7927
8253
  isValidPriority: () => isValidPriority,
7928
8254
  label: () => label,
7929
8255
  listPrdStatesByStatus: () => listPrdStatesByStatus,
8256
+ loadAuditFindings: () => loadAuditFindings,
7930
8257
  loadConfig: () => loadConfig,
7931
8258
  loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
7932
8259
  loadHistory: () => loadHistory,
@@ -7936,9 +8263,12 @@ __export(dist_exports, {
7936
8263
  markItemProcessed: () => markItemProcessed,
7937
8264
  markJobRunning: () => markJobRunning,
7938
8265
  markPrdDone: () => markPrdDone,
8266
+ mergerLockPath: () => mergerLockPath,
7939
8267
  migrateJsonToSqlite: () => migrateJsonToSqlite,
8268
+ normalizeAuditSeverity: () => normalizeAuditSeverity,
7940
8269
  normalizeJobConfig: () => normalizeJobConfig,
7941
8270
  normalizeSchedulingPriority: () => normalizeSchedulingPriority,
8271
+ parseAuditFindings: () => parseAuditFindings,
7942
8272
  parsePrdDependencies: () => parsePrdDependencies,
7943
8273
  parseRoadmap: () => parseRoadmap,
7944
8274
  parseScriptResult: () => parseScriptResult,
@@ -7988,6 +8318,7 @@ __export(dist_exports, {
7988
8318
  sortByPriority: () => sortByPriority,
7989
8319
  step: () => step,
7990
8320
  success: () => success,
8321
+ syncAuditFindingsToBoard: () => syncAuditFindingsToBoard,
7991
8322
  unmarkItemProcessed: () => unmarkItemProcessed,
7992
8323
  unregisterProject: () => unregisterProject,
7993
8324
  updateJobStatus: () => updateJobStatus,
@@ -8044,6 +8375,7 @@ var init_dist = __esm({
8044
8375
  init_job_queue();
8045
8376
  init_summary();
8046
8377
  init_analytics();
8378
+ init_audit();
8047
8379
  init_prd_template();
8048
8380
  init_slicer_prompt();
8049
8381
  init_jobs();
@@ -8053,30 +8385,30 @@ var init_dist = __esm({
8053
8385
  // src/cli.ts
8054
8386
  import "reflect-metadata";
8055
8387
  import { Command as Command3 } from "commander";
8056
- import { existsSync as existsSync30, readFileSync as readFileSync19 } from "fs";
8388
+ import { existsSync as existsSync31, readFileSync as readFileSync20 } from "fs";
8057
8389
  import { fileURLToPath as fileURLToPath6 } from "url";
8058
- import { dirname as dirname12, join as join36 } from "path";
8390
+ import { dirname as dirname12, join as join37 } from "path";
8059
8391
 
8060
8392
  // src/commands/init.ts
8061
8393
  init_dist();
8062
- import fs20 from "fs";
8063
- import path20 from "path";
8394
+ import fs21 from "fs";
8395
+ import path21 from "path";
8064
8396
  import { execSync as execSync3 } from "child_process";
8065
8397
  import { fileURLToPath as fileURLToPath3 } from "url";
8066
- import { dirname as dirname6, join as join18 } from "path";
8398
+ import { dirname as dirname6, join as join19 } from "path";
8067
8399
  import * as readline from "readline";
8068
8400
  var __filename2 = fileURLToPath3(import.meta.url);
8069
8401
  var __dirname2 = dirname6(__filename2);
8070
8402
  function findTemplatesDir(startDir) {
8071
8403
  let d = startDir;
8072
8404
  for (let i = 0; i < 8; i++) {
8073
- const candidate = join18(d, "templates");
8074
- if (fs20.existsSync(candidate) && fs20.statSync(candidate).isDirectory()) {
8405
+ const candidate = join19(d, "templates");
8406
+ if (fs21.existsSync(candidate) && fs21.statSync(candidate).isDirectory()) {
8075
8407
  return candidate;
8076
8408
  }
8077
8409
  d = dirname6(d);
8078
8410
  }
8079
- return join18(startDir, "templates");
8411
+ return join19(startDir, "templates");
8080
8412
  }
8081
8413
  var TEMPLATES_DIR = findTemplatesDir(__dirname2);
8082
8414
  var NW_SKILLS = [
@@ -8088,12 +8420,12 @@ var NW_SKILLS = [
8088
8420
  "nw-review"
8089
8421
  ];
8090
8422
  function hasPlaywrightDependency(cwd) {
8091
- const packageJsonPath = path20.join(cwd, "package.json");
8092
- if (!fs20.existsSync(packageJsonPath)) {
8423
+ const packageJsonPath = path21.join(cwd, "package.json");
8424
+ if (!fs21.existsSync(packageJsonPath)) {
8093
8425
  return false;
8094
8426
  }
8095
8427
  try {
8096
- const packageJson2 = JSON.parse(fs20.readFileSync(packageJsonPath, "utf-8"));
8428
+ const packageJson2 = JSON.parse(fs21.readFileSync(packageJsonPath, "utf-8"));
8097
8429
  return Boolean(
8098
8430
  packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
8099
8431
  );
@@ -8105,7 +8437,7 @@ function detectPlaywright(cwd) {
8105
8437
  if (hasPlaywrightDependency(cwd)) {
8106
8438
  return true;
8107
8439
  }
8108
- if (fs20.existsSync(path20.join(cwd, "node_modules", ".bin", "playwright"))) {
8440
+ if (fs21.existsSync(path21.join(cwd, "node_modules", ".bin", "playwright"))) {
8109
8441
  return true;
8110
8442
  }
8111
8443
  try {
@@ -8121,10 +8453,10 @@ function detectPlaywright(cwd) {
8121
8453
  }
8122
8454
  }
8123
8455
  function resolvePlaywrightInstallCommand(cwd) {
8124
- if (fs20.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
8456
+ if (fs21.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) {
8125
8457
  return "pnpm add -D @playwright/test";
8126
8458
  }
8127
- if (fs20.existsSync(path20.join(cwd, "yarn.lock"))) {
8459
+ if (fs21.existsSync(path21.join(cwd, "yarn.lock"))) {
8128
8460
  return "yarn add -D @playwright/test";
8129
8461
  }
8130
8462
  return "npm install -D @playwright/test";
@@ -8270,8 +8602,8 @@ function promptProviderSelection(providers) {
8270
8602
  });
8271
8603
  }
8272
8604
  function ensureDir(dirPath) {
8273
- if (!fs20.existsSync(dirPath)) {
8274
- fs20.mkdirSync(dirPath, { recursive: true });
8605
+ if (!fs21.existsSync(dirPath)) {
8606
+ fs21.mkdirSync(dirPath, { recursive: true });
8275
8607
  }
8276
8608
  }
8277
8609
  function buildInitConfig(params) {
@@ -8319,6 +8651,7 @@ function buildInitConfig(params) {
8319
8651
  },
8320
8652
  audit: { ...defaults.audit },
8321
8653
  analytics: { ...defaults.analytics },
8654
+ merger: { ...defaults.merger },
8322
8655
  prResolver: { ...defaults.prResolver },
8323
8656
  jobProviders: { ...defaults.jobProviders },
8324
8657
  queue: {
@@ -8329,30 +8662,30 @@ function buildInitConfig(params) {
8329
8662
  }
8330
8663
  function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
8331
8664
  if (customTemplatesDir !== null) {
8332
- const customPath = join18(customTemplatesDir, templateName);
8333
- if (fs20.existsSync(customPath)) {
8665
+ const customPath = join19(customTemplatesDir, templateName);
8666
+ if (fs21.existsSync(customPath)) {
8334
8667
  return { path: customPath, source: "custom" };
8335
8668
  }
8336
8669
  }
8337
- return { path: join18(bundledTemplatesDir, templateName), source: "bundled" };
8670
+ return { path: join19(bundledTemplatesDir, templateName), source: "bundled" };
8338
8671
  }
8339
8672
  function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
8340
- if (fs20.existsSync(targetPath) && !force) {
8673
+ if (fs21.existsSync(targetPath) && !force) {
8341
8674
  console.log(` Skipped (exists): ${targetPath}`);
8342
8675
  return { created: false, source: source ?? "bundled" };
8343
8676
  }
8344
- const templatePath = sourcePath ?? join18(TEMPLATES_DIR, templateName);
8677
+ const templatePath = sourcePath ?? join19(TEMPLATES_DIR, templateName);
8345
8678
  const resolvedSource = source ?? "bundled";
8346
- let content = fs20.readFileSync(templatePath, "utf-8");
8679
+ let content = fs21.readFileSync(templatePath, "utf-8");
8347
8680
  for (const [key, value] of Object.entries(replacements)) {
8348
8681
  content = content.replaceAll(key, value);
8349
8682
  }
8350
- fs20.writeFileSync(targetPath, content);
8683
+ fs21.writeFileSync(targetPath, content);
8351
8684
  console.log(` Created: ${targetPath} (${resolvedSource})`);
8352
8685
  return { created: true, source: resolvedSource };
8353
8686
  }
8354
8687
  function addToGitignore(cwd) {
8355
- const gitignorePath = path20.join(cwd, ".gitignore");
8688
+ const gitignorePath = path21.join(cwd, ".gitignore");
8356
8689
  const entries = [
8357
8690
  {
8358
8691
  pattern: "/logs/",
@@ -8366,13 +8699,13 @@ function addToGitignore(cwd) {
8366
8699
  },
8367
8700
  { pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
8368
8701
  ];
8369
- if (!fs20.existsSync(gitignorePath)) {
8702
+ if (!fs21.existsSync(gitignorePath)) {
8370
8703
  const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
8371
- fs20.writeFileSync(gitignorePath, lines.join("\n"));
8704
+ fs21.writeFileSync(gitignorePath, lines.join("\n"));
8372
8705
  console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
8373
8706
  return;
8374
8707
  }
8375
- const content = fs20.readFileSync(gitignorePath, "utf-8");
8708
+ const content = fs21.readFileSync(gitignorePath, "utf-8");
8376
8709
  const missing = entries.filter((e) => !e.check(content));
8377
8710
  if (missing.length === 0) {
8378
8711
  console.log(` Skipped (exists): Night Watch entries in .gitignore`);
@@ -8380,59 +8713,59 @@ function addToGitignore(cwd) {
8380
8713
  }
8381
8714
  const additions = missing.map((e) => e.pattern).join("\n");
8382
8715
  const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
8383
- fs20.writeFileSync(gitignorePath, newContent);
8716
+ fs21.writeFileSync(gitignorePath, newContent);
8384
8717
  console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
8385
8718
  }
8386
8719
  function installSkills(cwd, provider, force, templatesDir) {
8387
- const skillsTemplatesDir = path20.join(templatesDir, "skills");
8388
- if (!fs20.existsSync(skillsTemplatesDir)) {
8720
+ const skillsTemplatesDir = path21.join(templatesDir, "skills");
8721
+ if (!fs21.existsSync(skillsTemplatesDir)) {
8389
8722
  return { location: "", installed: 0, skipped: 0, type: "none" };
8390
8723
  }
8391
8724
  const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
8392
8725
  const isCodexProvider = provider === "codex";
8393
- const claudeDir = path20.join(cwd, ".claude");
8394
- if (isClaudeProvider || fs20.existsSync(claudeDir)) {
8726
+ const claudeDir = path21.join(cwd, ".claude");
8727
+ if (isClaudeProvider || fs21.existsSync(claudeDir)) {
8395
8728
  ensureDir(claudeDir);
8396
- const skillsDir = path20.join(claudeDir, "skills");
8729
+ const skillsDir = path21.join(claudeDir, "skills");
8397
8730
  ensureDir(skillsDir);
8398
8731
  let installed = 0;
8399
8732
  let skipped = 0;
8400
8733
  for (const skillName of NW_SKILLS) {
8401
- const templateFile = path20.join(skillsTemplatesDir, `${skillName}.md`);
8402
- if (!fs20.existsSync(templateFile)) continue;
8403
- const skillDir = path20.join(skillsDir, skillName);
8734
+ const templateFile = path21.join(skillsTemplatesDir, `${skillName}.md`);
8735
+ if (!fs21.existsSync(templateFile)) continue;
8736
+ const skillDir = path21.join(skillsDir, skillName);
8404
8737
  ensureDir(skillDir);
8405
- const target = path20.join(skillDir, "SKILL.md");
8406
- if (fs20.existsSync(target) && !force) {
8738
+ const target = path21.join(skillDir, "SKILL.md");
8739
+ if (fs21.existsSync(target) && !force) {
8407
8740
  skipped++;
8408
8741
  continue;
8409
8742
  }
8410
- fs20.copyFileSync(templateFile, target);
8743
+ fs21.copyFileSync(templateFile, target);
8411
8744
  installed++;
8412
8745
  }
8413
8746
  return { location: ".claude/skills/", installed, skipped, type: "claude" };
8414
8747
  }
8415
8748
  if (isCodexProvider) {
8416
- const agentsFile = path20.join(cwd, "AGENTS.md");
8417
- const blockFile = path20.join(skillsTemplatesDir, "_codex-block.md");
8418
- if (!fs20.existsSync(blockFile)) {
8749
+ const agentsFile = path21.join(cwd, "AGENTS.md");
8750
+ const blockFile = path21.join(skillsTemplatesDir, "_codex-block.md");
8751
+ if (!fs21.existsSync(blockFile)) {
8419
8752
  return { location: "", installed: 0, skipped: 0, type: "none" };
8420
8753
  }
8421
- const block = fs20.readFileSync(blockFile, "utf-8");
8754
+ const block = fs21.readFileSync(blockFile, "utf-8");
8422
8755
  const marker = "## Night Watch Skills";
8423
- if (!fs20.existsSync(agentsFile)) {
8424
- fs20.writeFileSync(agentsFile, block);
8756
+ if (!fs21.existsSync(agentsFile)) {
8757
+ fs21.writeFileSync(agentsFile, block);
8425
8758
  return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
8426
8759
  }
8427
- const existing = fs20.readFileSync(agentsFile, "utf-8");
8760
+ const existing = fs21.readFileSync(agentsFile, "utf-8");
8428
8761
  if (existing.includes(marker)) {
8429
8762
  if (!force) {
8430
8763
  return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
8431
8764
  }
8432
8765
  const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
8433
- fs20.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
8766
+ fs21.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
8434
8767
  } else {
8435
- fs20.appendFileSync(agentsFile, "\n\n" + block);
8768
+ fs21.appendFileSync(agentsFile, "\n\n" + block);
8436
8769
  }
8437
8770
  return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
8438
8771
  }
@@ -8556,28 +8889,28 @@ function initCommand(program2) {
8556
8889
  "${DEFAULT_BRANCH}": defaultBranch
8557
8890
  };
8558
8891
  step(6, totalSteps, "Creating PRD directory structure...");
8559
- const prdDirPath = path20.join(cwd, prdDir);
8560
- const doneDirPath = path20.join(prdDirPath, "done");
8892
+ const prdDirPath = path21.join(cwd, prdDir);
8893
+ const doneDirPath = path21.join(prdDirPath, "done");
8561
8894
  ensureDir(doneDirPath);
8562
8895
  success(`Created ${prdDirPath}/`);
8563
8896
  success(`Created ${doneDirPath}/`);
8564
8897
  step(7, totalSteps, "Creating logs directory...");
8565
- const logsPath = path20.join(cwd, LOG_DIR);
8898
+ const logsPath = path21.join(cwd, LOG_DIR);
8566
8899
  ensureDir(logsPath);
8567
8900
  success(`Created ${logsPath}/`);
8568
8901
  addToGitignore(cwd);
8569
8902
  step(8, totalSteps, "Creating instructions directory...");
8570
- const instructionsDir = path20.join(cwd, "instructions");
8903
+ const instructionsDir = path21.join(cwd, "instructions");
8571
8904
  ensureDir(instructionsDir);
8572
8905
  success(`Created ${instructionsDir}/`);
8573
8906
  const existingConfig = loadConfig(cwd);
8574
- const customTemplatesDirPath = path20.join(cwd, existingConfig.templatesDir);
8575
- const customTemplatesDir = fs20.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
8907
+ const customTemplatesDirPath = path21.join(cwd, existingConfig.templatesDir);
8908
+ const customTemplatesDir = fs21.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
8576
8909
  const templateSources = [];
8577
8910
  const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
8578
8911
  const nwResult = processTemplate(
8579
8912
  "executor.md",
8580
- path20.join(instructionsDir, "executor.md"),
8913
+ path21.join(instructionsDir, "executor.md"),
8581
8914
  replacements,
8582
8915
  force,
8583
8916
  nwResolution.path,
@@ -8591,7 +8924,7 @@ function initCommand(program2) {
8591
8924
  );
8592
8925
  const peResult = processTemplate(
8593
8926
  "prd-executor.md",
8594
- path20.join(instructionsDir, "prd-executor.md"),
8927
+ path21.join(instructionsDir, "prd-executor.md"),
8595
8928
  replacements,
8596
8929
  force,
8597
8930
  peResolution.path,
@@ -8601,7 +8934,7 @@ function initCommand(program2) {
8601
8934
  const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
8602
8935
  const prResult = processTemplate(
8603
8936
  "pr-reviewer.md",
8604
- path20.join(instructionsDir, "pr-reviewer.md"),
8937
+ path21.join(instructionsDir, "pr-reviewer.md"),
8605
8938
  replacements,
8606
8939
  force,
8607
8940
  prResolution.path,
@@ -8611,7 +8944,7 @@ function initCommand(program2) {
8611
8944
  const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
8612
8945
  const qaResult = processTemplate(
8613
8946
  "qa.md",
8614
- path20.join(instructionsDir, "qa.md"),
8947
+ path21.join(instructionsDir, "qa.md"),
8615
8948
  replacements,
8616
8949
  force,
8617
8950
  qaResolution.path,
@@ -8621,7 +8954,7 @@ function initCommand(program2) {
8621
8954
  const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
8622
8955
  const auditResult = processTemplate(
8623
8956
  "audit.md",
8624
- path20.join(instructionsDir, "audit.md"),
8957
+ path21.join(instructionsDir, "audit.md"),
8625
8958
  replacements,
8626
8959
  force,
8627
8960
  auditResolution.path,
@@ -8635,7 +8968,7 @@ function initCommand(program2) {
8635
8968
  );
8636
8969
  const plannerResult = processTemplate(
8637
8970
  "prd-creator.md",
8638
- path20.join(instructionsDir, "prd-creator.md"),
8971
+ path21.join(instructionsDir, "prd-creator.md"),
8639
8972
  replacements,
8640
8973
  force,
8641
8974
  plannerResolution.path,
@@ -8643,8 +8976,8 @@ function initCommand(program2) {
8643
8976
  );
8644
8977
  templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
8645
8978
  step(9, totalSteps, "Creating configuration file...");
8646
- const configPath = path20.join(cwd, CONFIG_FILE_NAME);
8647
- if (fs20.existsSync(configPath) && !force) {
8979
+ const configPath = path21.join(cwd, CONFIG_FILE_NAME);
8980
+ if (fs21.existsSync(configPath) && !force) {
8648
8981
  console.log(` Skipped (exists): ${configPath}`);
8649
8982
  } else {
8650
8983
  const config = buildInitConfig({
@@ -8654,11 +8987,11 @@ function initCommand(program2) {
8654
8987
  reviewerEnabled,
8655
8988
  prdDir
8656
8989
  });
8657
- fs20.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
8990
+ fs21.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
8658
8991
  success(`Created ${configPath}`);
8659
8992
  }
8660
8993
  step(10, totalSteps, "Setting up GitHub Project board...");
8661
- const existingRaw = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
8994
+ const existingRaw = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
8662
8995
  const existingBoard = existingRaw.boardProvider;
8663
8996
  let boardSetupStatus = "Skipped";
8664
8997
  if (existingBoard?.projectNumber && !force) {
@@ -8680,13 +9013,13 @@ function initCommand(program2) {
8680
9013
  const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
8681
9014
  const boardTitle = `${projectName} Night Watch`;
8682
9015
  const board = await provider.setupBoard(boardTitle);
8683
- const rawConfig = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
9016
+ const rawConfig = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
8684
9017
  rawConfig.boardProvider = {
8685
9018
  enabled: true,
8686
9019
  provider: "github",
8687
9020
  projectNumber: board.number
8688
9021
  };
8689
- fs20.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
9022
+ fs21.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
8690
9023
  boardSetupStatus = `Created (#${board.number})`;
8691
9024
  success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
8692
9025
  } catch (boardErr) {
@@ -8869,8 +9202,8 @@ function getTelegramStatusWebhooks(config) {
8869
9202
  }
8870
9203
 
8871
9204
  // src/commands/run.ts
8872
- import * as fs21 from "fs";
8873
- import * as path21 from "path";
9205
+ import * as fs22 from "fs";
9206
+ import * as path22 from "path";
8874
9207
  function resolveRunNotificationEvent(exitCode, scriptStatus) {
8875
9208
  if (exitCode === 124) {
8876
9209
  return "run_timeout";
@@ -8902,12 +9235,12 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
8902
9235
  return scriptStatus === "skip_no_eligible_prd";
8903
9236
  }
8904
9237
  function getCrossProjectFallbackCandidates(currentProjectDir) {
8905
- const current = path21.resolve(currentProjectDir);
9238
+ const current = path22.resolve(currentProjectDir);
8906
9239
  const { valid, invalid } = validateRegistry();
8907
9240
  for (const entry of invalid) {
8908
9241
  warn(`Skipping invalid registry entry: ${entry.path}`);
8909
9242
  }
8910
- return valid.filter((entry) => path21.resolve(entry.path) !== current);
9243
+ return valid.filter((entry) => path22.resolve(entry.path) !== current);
8911
9244
  }
8912
9245
  async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
8913
9246
  if (isRateLimitFallbackTriggered(scriptResult?.data)) {
@@ -8917,7 +9250,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
8917
9250
  if (nonTelegramWebhooks.length > 0) {
8918
9251
  const _rateLimitCtx = {
8919
9252
  event: "rate_limit_fallback",
8920
- projectName: path21.basename(projectDir),
9253
+ projectName: path22.basename(projectDir),
8921
9254
  exitCode,
8922
9255
  provider: config.provider
8923
9256
  };
@@ -8949,7 +9282,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
8949
9282
  const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
8950
9283
  const _ctx = {
8951
9284
  event,
8952
- projectName: path21.basename(projectDir),
9285
+ projectName: path22.basename(projectDir),
8953
9286
  exitCode,
8954
9287
  provider: config.provider,
8955
9288
  prdName: scriptResult?.data.prd,
@@ -9111,20 +9444,20 @@ function applyCliOverrides(config, options) {
9111
9444
  return overridden;
9112
9445
  }
9113
9446
  function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
9114
- const absolutePrdDir = path21.join(projectDir, prdDir);
9115
- const doneDir = path21.join(absolutePrdDir, "done");
9447
+ const absolutePrdDir = path22.join(projectDir, prdDir);
9448
+ const doneDir = path22.join(absolutePrdDir, "done");
9116
9449
  const pending = [];
9117
9450
  const completed = [];
9118
- if (fs21.existsSync(absolutePrdDir)) {
9119
- const entries = fs21.readdirSync(absolutePrdDir, { withFileTypes: true });
9451
+ if (fs22.existsSync(absolutePrdDir)) {
9452
+ const entries = fs22.readdirSync(absolutePrdDir, { withFileTypes: true });
9120
9453
  for (const entry of entries) {
9121
9454
  if (entry.isFile() && entry.name.endsWith(".md")) {
9122
- const claimPath = path21.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
9455
+ const claimPath = path22.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
9123
9456
  let claimed = false;
9124
9457
  let claimInfo = null;
9125
- if (fs21.existsSync(claimPath)) {
9458
+ if (fs22.existsSync(claimPath)) {
9126
9459
  try {
9127
- const content = fs21.readFileSync(claimPath, "utf-8");
9460
+ const content = fs22.readFileSync(claimPath, "utf-8");
9128
9461
  const data = JSON.parse(content);
9129
9462
  const age = Math.floor(Date.now() / 1e3) - data.timestamp;
9130
9463
  if (age < maxRuntime) {
@@ -9138,8 +9471,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
9138
9471
  }
9139
9472
  }
9140
9473
  }
9141
- if (fs21.existsSync(doneDir)) {
9142
- const entries = fs21.readdirSync(doneDir, { withFileTypes: true });
9474
+ if (fs22.existsSync(doneDir)) {
9475
+ const entries = fs22.readdirSync(doneDir, { withFileTypes: true });
9143
9476
  for (const entry of entries) {
9144
9477
  if (entry.isFile() && entry.name.endsWith(".md")) {
9145
9478
  completed.push(entry.name);
@@ -9299,7 +9632,7 @@ ${stderr}`);
9299
9632
  // src/commands/review.ts
9300
9633
  init_dist();
9301
9634
  import { execFileSync as execFileSync5 } from "child_process";
9302
- import * as path22 from "path";
9635
+ import * as path23 from "path";
9303
9636
  function shouldSendReviewNotification(scriptStatus) {
9304
9637
  if (!scriptStatus) {
9305
9638
  return true;
@@ -9399,10 +9732,6 @@ function buildEnvVars2(config, options) {
9399
9732
  env.NW_BRANCH_PATTERNS = config.branchPatterns.join(",");
9400
9733
  env.NW_PRD_DIR = config.prdDir;
9401
9734
  env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? "sonnet"];
9402
- if (config.autoMerge) {
9403
- env.NW_AUTO_MERGE = "1";
9404
- }
9405
- env.NW_AUTO_MERGE_METHOD = config.autoMergeMethod;
9406
9735
  return env;
9407
9736
  }
9408
9737
  function applyCliOverrides2(config, options) {
@@ -9416,9 +9745,6 @@ function applyCliOverrides2(config, options) {
9416
9745
  if (options.provider) {
9417
9746
  overridden._cliProviderOverride = options.provider;
9418
9747
  }
9419
- if (options.autoMerge !== void 0) {
9420
- overridden.autoMerge = options.autoMerge;
9421
- }
9422
9748
  return overridden;
9423
9749
  }
9424
9750
  function isFailingCheck(check) {
@@ -9479,7 +9805,7 @@ function getOpenPrsNeedingWork(branchPatterns) {
9479
9805
  }
9480
9806
  }
9481
9807
  function reviewCommand(program2) {
9482
- program2.command("review").description("Run PR reviewer now").option("--dry-run", "Show what would be executed without running").option("--timeout <seconds>", "Override max runtime in seconds for reviewer").option("--provider <string>", "AI provider to use (claude or codex)").option("--auto-merge", "Enable auto-merge for this run").action(async (options) => {
9808
+ program2.command("review").description("Run PR reviewer now").option("--dry-run", "Show what would be executed without running").option("--timeout <seconds>", "Override max runtime in seconds for reviewer").option("--provider <string>", "AI provider to use (claude or codex)").action(async (options) => {
9483
9809
  const projectDir = process.cwd();
9484
9810
  let config = loadConfig(projectDir);
9485
9811
  config = applyCliOverrides2(config, options);
@@ -9502,10 +9828,6 @@ function reviewCommand(program2) {
9502
9828
  ]);
9503
9829
  configTable.push(["Min Review Score", `${config.minReviewScore}/100`]);
9504
9830
  configTable.push(["Branch Patterns", config.branchPatterns.join(", ")]);
9505
- configTable.push([
9506
- "Auto-merge",
9507
- config.autoMerge ? `Enabled (${config.autoMergeMethod})` : "Disabled"
9508
- ]);
9509
9831
  configTable.push(["Max Retry Attempts", String(config.reviewerMaxRetries)]);
9510
9832
  configTable.push(["Retry Delay", `${config.reviewerRetryDelay}s`]);
9511
9833
  configTable.push([
@@ -9610,7 +9932,7 @@ ${stderr}`);
9610
9932
  const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
9611
9933
  await sendNotifications(config, {
9612
9934
  event: reviewEvent,
9613
- projectName: path22.basename(projectDir),
9935
+ projectName: path23.basename(projectDir),
9614
9936
  exitCode,
9615
9937
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9616
9938
  prUrl: fallbackPrDetails?.url,
@@ -9632,7 +9954,7 @@ ${stderr}`);
9632
9954
  const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
9633
9955
  await sendNotifications(config, {
9634
9956
  event: reviewEvent,
9635
- projectName: path22.basename(projectDir),
9957
+ projectName: path23.basename(projectDir),
9636
9958
  exitCode,
9637
9959
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9638
9960
  prUrl: prDetails?.url,
@@ -9654,7 +9976,7 @@ ${stderr}`);
9654
9976
  const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
9655
9977
  const _mergeCtx = {
9656
9978
  event: "pr_auto_merged",
9657
- projectName: path22.basename(projectDir),
9979
+ projectName: path23.basename(projectDir),
9658
9980
  exitCode,
9659
9981
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9660
9982
  prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
@@ -9679,7 +10001,7 @@ ${stderr}`);
9679
10001
 
9680
10002
  // src/commands/qa.ts
9681
10003
  init_dist();
9682
- import * as path23 from "path";
10004
+ import * as path24 from "path";
9683
10005
  function shouldSendQaNotification(scriptStatus) {
9684
10006
  if (!scriptStatus) {
9685
10007
  return true;
@@ -9820,7 +10142,7 @@ ${stderr}`);
9820
10142
  const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
9821
10143
  const _qaCtx = {
9822
10144
  event: "qa_completed",
9823
- projectName: path23.basename(projectDir),
10145
+ projectName: path24.basename(projectDir),
9824
10146
  exitCode,
9825
10147
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9826
10148
  prNumber: prDetails?.number ?? primaryQaPr,
@@ -9846,8 +10168,8 @@ ${stderr}`);
9846
10168
 
9847
10169
  // src/commands/audit.ts
9848
10170
  init_dist();
9849
- import * as fs22 from "fs";
9850
- import * as path24 from "path";
10171
+ import * as fs23 from "fs";
10172
+ import * as path25 from "path";
9851
10173
  function buildEnvVars4(config, options) {
9852
10174
  const env = buildBaseEnvVars(config, "audit", options.dryRun);
9853
10175
  env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
@@ -9890,7 +10212,8 @@ function auditCommand(program2) {
9890
10212
  configTable.push(["Provider", auditProvider]);
9891
10213
  configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
9892
10214
  configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
9893
- configTable.push(["Report File", path24.join(projectDir, "logs", "audit-report.md")]);
10215
+ configTable.push(["Target Column", config.audit.targetColumn]);
10216
+ configTable.push(["Report File", path25.join(projectDir, "logs", "audit-report.md")]);
9894
10217
  console.log(configTable.toString());
9895
10218
  header("Provider Invocation");
9896
10219
  const providerCmd = PROVIDER_COMMANDS[auditProvider];
@@ -9925,21 +10248,27 @@ ${stderr}`);
9925
10248
  } else if (scriptResult?.status?.startsWith("skip_")) {
9926
10249
  spinner.succeed("Code audit skipped");
9927
10250
  } else {
9928
- const reportPath = path24.join(projectDir, "logs", "audit-report.md");
9929
- if (!fs22.existsSync(reportPath)) {
10251
+ const reportPath = path25.join(projectDir, "logs", "audit-report.md");
10252
+ if (!fs23.existsSync(reportPath)) {
9930
10253
  spinner.fail("Code audit finished without a report file");
9931
10254
  process.exit(1);
9932
10255
  }
9933
- spinner.succeed(`Code audit complete \u2014 report written to ${reportPath}`);
10256
+ const syncResult = await syncAuditFindingsToBoard(config, projectDir);
10257
+ const message = `Code audit complete \u2014 report written to ${reportPath}; ${syncResult.summary}`;
10258
+ if (syncResult.status === "failed" || syncResult.status === "partial") {
10259
+ spinner.warn(message);
10260
+ } else {
10261
+ spinner.succeed(message);
10262
+ }
9934
10263
  }
9935
10264
  } else {
9936
10265
  const statusSuffix = scriptResult?.status ? ` (${scriptResult.status})` : "";
9937
10266
  const providerExit = scriptResult?.data?.provider_exit;
9938
10267
  const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
9939
10268
  spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
9940
- const logPath = path24.join(projectDir, "logs", "audit.log");
9941
- if (fs22.existsSync(logPath)) {
9942
- const logLines = fs22.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
10269
+ const logPath = path25.join(projectDir, "logs", "audit.log");
10270
+ if (fs23.existsSync(logPath)) {
10271
+ const logLines = fs23.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
9943
10272
  if (logLines.length > 0) {
9944
10273
  process.stderr.write(logLines.join("\n") + "\n");
9945
10274
  }
@@ -10013,16 +10342,16 @@ function analyticsCommand(program2) {
10013
10342
  // src/commands/install.ts
10014
10343
  init_dist();
10015
10344
  import { execSync as execSync4 } from "child_process";
10016
- import * as path25 from "path";
10017
- import * as fs23 from "fs";
10345
+ import * as path26 from "path";
10346
+ import * as fs24 from "fs";
10018
10347
  function shellQuote(value) {
10019
10348
  return `'${value.replace(/'/g, `'"'"'`)}'`;
10020
10349
  }
10021
10350
  function getNightWatchBinPath() {
10022
10351
  try {
10023
10352
  const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
10024
- const binPath = path25.join(npmBin, "night-watch");
10025
- if (fs23.existsSync(binPath)) {
10353
+ const binPath = path26.join(npmBin, "night-watch");
10354
+ if (fs24.existsSync(binPath)) {
10026
10355
  return binPath;
10027
10356
  }
10028
10357
  } catch {
@@ -10035,17 +10364,17 @@ function getNightWatchBinPath() {
10035
10364
  }
10036
10365
  function getNodeBinDir() {
10037
10366
  if (process.execPath && process.execPath !== "node") {
10038
- return path25.dirname(process.execPath);
10367
+ return path26.dirname(process.execPath);
10039
10368
  }
10040
10369
  try {
10041
10370
  const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
10042
- return path25.dirname(nodePath);
10371
+ return path26.dirname(nodePath);
10043
10372
  } catch {
10044
10373
  return "";
10045
10374
  }
10046
10375
  }
10047
10376
  function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
10048
- const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path25.dirname(nightWatchBin) : "";
10377
+ const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path26.dirname(nightWatchBin) : "";
10049
10378
  const pathParts = Array.from(
10050
10379
  new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
10051
10380
  );
@@ -10061,12 +10390,12 @@ function performInstall(projectDir, config, options) {
10061
10390
  const nightWatchBin = getNightWatchBinPath();
10062
10391
  const projectName = getProjectName(projectDir);
10063
10392
  const marker = generateMarker(projectName);
10064
- const logDir = path25.join(projectDir, LOG_DIR);
10065
- if (!fs23.existsSync(logDir)) {
10066
- fs23.mkdirSync(logDir, { recursive: true });
10393
+ const logDir = path26.join(projectDir, LOG_DIR);
10394
+ if (!fs24.existsSync(logDir)) {
10395
+ fs24.mkdirSync(logDir, { recursive: true });
10067
10396
  }
10068
- const executorLog = path25.join(logDir, "executor.log");
10069
- const reviewerLog = path25.join(logDir, "reviewer.log");
10397
+ const executorLog = path26.join(logDir, "executor.log");
10398
+ const reviewerLog = path26.join(logDir, "reviewer.log");
10070
10399
  if (!options?.force) {
10071
10400
  const existingEntries2 = Array.from(
10072
10401
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
@@ -10103,7 +10432,7 @@ function performInstall(projectDir, config, options) {
10103
10432
  const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
10104
10433
  if (installSlicer) {
10105
10434
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
10106
- const slicerLog = path25.join(logDir, "slicer.log");
10435
+ const slicerLog = path26.join(logDir, "slicer.log");
10107
10436
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
10108
10437
  entries.push(slicerEntry);
10109
10438
  }
@@ -10111,7 +10440,7 @@ function performInstall(projectDir, config, options) {
10111
10440
  const installQa = disableQa ? false : config.qa.enabled;
10112
10441
  if (installQa) {
10113
10442
  const qaSchedule = config.qa.schedule;
10114
- const qaLog = path25.join(logDir, "qa.log");
10443
+ const qaLog = path26.join(logDir, "qa.log");
10115
10444
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
10116
10445
  entries.push(qaEntry);
10117
10446
  }
@@ -10119,7 +10448,7 @@ function performInstall(projectDir, config, options) {
10119
10448
  const installAudit = disableAudit ? false : config.audit.enabled;
10120
10449
  if (installAudit) {
10121
10450
  const auditSchedule = config.audit.schedule;
10122
- const auditLog = path25.join(logDir, "audit.log");
10451
+ const auditLog = path26.join(logDir, "audit.log");
10123
10452
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
10124
10453
  entries.push(auditEntry);
10125
10454
  }
@@ -10127,7 +10456,7 @@ function performInstall(projectDir, config, options) {
10127
10456
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
10128
10457
  if (installAnalytics) {
10129
10458
  const analyticsSchedule = config.analytics.schedule;
10130
- const analyticsLog = path25.join(logDir, "analytics.log");
10459
+ const analyticsLog = path26.join(logDir, "analytics.log");
10131
10460
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
10132
10461
  entries.push(analyticsEntry);
10133
10462
  }
@@ -10135,10 +10464,18 @@ function performInstall(projectDir, config, options) {
10135
10464
  const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
10136
10465
  if (installPrResolver) {
10137
10466
  const prResolverSchedule = config.prResolver.schedule;
10138
- const prResolverLog = path25.join(logDir, "pr-resolver.log");
10467
+ const prResolverLog = path26.join(logDir, "pr-resolver.log");
10139
10468
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
10140
10469
  entries.push(prResolverEntry);
10141
10470
  }
10471
+ const disableMerger = options?.noMerger === true || options?.merger === false;
10472
+ const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
10473
+ if (installMerger) {
10474
+ const mergerSchedule = config.merger.schedule;
10475
+ const mergerLog = path26.join(logDir, "merger.log");
10476
+ const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
10477
+ entries.push(mergerEntry);
10478
+ }
10142
10479
  const existingEntries = new Set(
10143
10480
  Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
10144
10481
  );
@@ -10157,7 +10494,7 @@ function performInstall(projectDir, config, options) {
10157
10494
  }
10158
10495
  }
10159
10496
  function installCommand(program2) {
10160
- program2.command("install").description("Add crontab entries for automated execution").option("-s, --schedule <cron>", "Cron schedule for PRD executor").option("--reviewer-schedule <cron>", "Cron schedule for reviewer").option("--no-reviewer", "Skip installing reviewer cron").option("--no-slicer", "Skip installing slicer cron").option("--no-qa", "Skip installing QA cron").option("--no-audit", "Skip installing audit cron").option("--no-analytics", "Skip installing analytics cron").option("--no-pr-resolver", "Skip installing PR resolver cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
10497
+ 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("--no-merger", "Skip installing merger cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
10161
10498
  try {
10162
10499
  const projectDir = process.cwd();
10163
10500
  const config = loadConfig(projectDir);
@@ -10166,12 +10503,12 @@ function installCommand(program2) {
10166
10503
  const nightWatchBin = getNightWatchBinPath();
10167
10504
  const projectName = getProjectName(projectDir);
10168
10505
  const marker = generateMarker(projectName);
10169
- const logDir = path25.join(projectDir, LOG_DIR);
10170
- if (!fs23.existsSync(logDir)) {
10171
- fs23.mkdirSync(logDir, { recursive: true });
10506
+ const logDir = path26.join(projectDir, LOG_DIR);
10507
+ if (!fs24.existsSync(logDir)) {
10508
+ fs24.mkdirSync(logDir, { recursive: true });
10172
10509
  }
10173
- const executorLog = path25.join(logDir, "executor.log");
10174
- const reviewerLog = path25.join(logDir, "reviewer.log");
10510
+ const executorLog = path26.join(logDir, "executor.log");
10511
+ const reviewerLog = path26.join(logDir, "reviewer.log");
10175
10512
  const existingEntries = Array.from(
10176
10513
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
10177
10514
  );
@@ -10207,7 +10544,7 @@ function installCommand(program2) {
10207
10544
  const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
10208
10545
  let slicerLog;
10209
10546
  if (installSlicer) {
10210
- slicerLog = path25.join(logDir, "slicer.log");
10547
+ slicerLog = path26.join(logDir, "slicer.log");
10211
10548
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
10212
10549
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
10213
10550
  entries.push(slicerEntry);
@@ -10216,7 +10553,7 @@ function installCommand(program2) {
10216
10553
  const installQa = disableQa ? false : config.qa.enabled;
10217
10554
  let qaLog;
10218
10555
  if (installQa) {
10219
- qaLog = path25.join(logDir, "qa.log");
10556
+ qaLog = path26.join(logDir, "qa.log");
10220
10557
  const qaSchedule = config.qa.schedule;
10221
10558
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
10222
10559
  entries.push(qaEntry);
@@ -10225,7 +10562,7 @@ function installCommand(program2) {
10225
10562
  const installAudit = disableAudit ? false : config.audit.enabled;
10226
10563
  let auditLog;
10227
10564
  if (installAudit) {
10228
- auditLog = path25.join(logDir, "audit.log");
10565
+ auditLog = path26.join(logDir, "audit.log");
10229
10566
  const auditSchedule = config.audit.schedule;
10230
10567
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
10231
10568
  entries.push(auditEntry);
@@ -10234,7 +10571,7 @@ function installCommand(program2) {
10234
10571
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
10235
10572
  let analyticsLog;
10236
10573
  if (installAnalytics) {
10237
- analyticsLog = path25.join(logDir, "analytics.log");
10574
+ analyticsLog = path26.join(logDir, "analytics.log");
10238
10575
  const analyticsSchedule = config.analytics.schedule;
10239
10576
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
10240
10577
  entries.push(analyticsEntry);
@@ -10243,11 +10580,20 @@ function installCommand(program2) {
10243
10580
  const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
10244
10581
  let prResolverLog;
10245
10582
  if (installPrResolver) {
10246
- prResolverLog = path25.join(logDir, "pr-resolver.log");
10583
+ prResolverLog = path26.join(logDir, "pr-resolver.log");
10247
10584
  const prResolverSchedule = config.prResolver.schedule;
10248
10585
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
10249
10586
  entries.push(prResolverEntry);
10250
10587
  }
10588
+ const disableMerger = options.noMerger === true || options.merger === false;
10589
+ const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
10590
+ let mergerLog;
10591
+ if (installMerger) {
10592
+ mergerLog = path26.join(logDir, "merger.log");
10593
+ const mergerSchedule = config.merger.schedule;
10594
+ const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
10595
+ entries.push(mergerEntry);
10596
+ }
10251
10597
  const existingEntrySet = new Set(existingEntries);
10252
10598
  const currentCrontab = readCrontab();
10253
10599
  const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
@@ -10280,6 +10626,9 @@ function installCommand(program2) {
10280
10626
  if (installPrResolver && prResolverLog) {
10281
10627
  dim(` PR Resolver: ${prResolverLog}`);
10282
10628
  }
10629
+ if (installMerger && mergerLog) {
10630
+ dim(` Merger: ${mergerLog}`);
10631
+ }
10283
10632
  console.log();
10284
10633
  dim("To uninstall, run: night-watch uninstall");
10285
10634
  dim("To check status, run: night-watch status");
@@ -10294,8 +10643,8 @@ function installCommand(program2) {
10294
10643
 
10295
10644
  // src/commands/uninstall.ts
10296
10645
  init_dist();
10297
- import * as path26 from "path";
10298
- import * as fs24 from "fs";
10646
+ import * as path27 from "path";
10647
+ import * as fs25 from "fs";
10299
10648
  function performUninstall(projectDir, options) {
10300
10649
  try {
10301
10650
  const projectName = getProjectName(projectDir);
@@ -10310,8 +10659,8 @@ function performUninstall(projectDir, options) {
10310
10659
  const removedCount = removeEntriesForProject(projectDir, marker);
10311
10660
  unregisterProject(projectDir);
10312
10661
  if (!options?.keepLogs) {
10313
- const logDir = path26.join(projectDir, "logs");
10314
- if (fs24.existsSync(logDir)) {
10662
+ const logDir = path27.join(projectDir, "logs");
10663
+ if (fs25.existsSync(logDir)) {
10315
10664
  const logFiles = [
10316
10665
  "executor.log",
10317
10666
  "reviewer.log",
@@ -10320,15 +10669,15 @@ function performUninstall(projectDir, options) {
10320
10669
  "pr-resolver.log"
10321
10670
  ];
10322
10671
  logFiles.forEach((logFile) => {
10323
- const logPath = path26.join(logDir, logFile);
10324
- if (fs24.existsSync(logPath)) {
10325
- fs24.unlinkSync(logPath);
10672
+ const logPath = path27.join(logDir, logFile);
10673
+ if (fs25.existsSync(logPath)) {
10674
+ fs25.unlinkSync(logPath);
10326
10675
  }
10327
10676
  });
10328
10677
  try {
10329
- const remainingFiles = fs24.readdirSync(logDir);
10678
+ const remainingFiles = fs25.readdirSync(logDir);
10330
10679
  if (remainingFiles.length === 0) {
10331
- fs24.rmdirSync(logDir);
10680
+ fs25.rmdirSync(logDir);
10332
10681
  }
10333
10682
  } catch {
10334
10683
  }
@@ -10361,8 +10710,8 @@ function uninstallCommand(program2) {
10361
10710
  existingEntries.forEach((entry) => dim(` ${entry}`));
10362
10711
  const removedCount = removeEntriesForProject(projectDir, marker);
10363
10712
  if (!options.keepLogs) {
10364
- const logDir = path26.join(projectDir, "logs");
10365
- if (fs24.existsSync(logDir)) {
10713
+ const logDir = path27.join(projectDir, "logs");
10714
+ if (fs25.existsSync(logDir)) {
10366
10715
  const logFiles = [
10367
10716
  "executor.log",
10368
10717
  "reviewer.log",
@@ -10372,16 +10721,16 @@ function uninstallCommand(program2) {
10372
10721
  ];
10373
10722
  let logsRemoved = 0;
10374
10723
  logFiles.forEach((logFile) => {
10375
- const logPath = path26.join(logDir, logFile);
10376
- if (fs24.existsSync(logPath)) {
10377
- fs24.unlinkSync(logPath);
10724
+ const logPath = path27.join(logDir, logFile);
10725
+ if (fs25.existsSync(logPath)) {
10726
+ fs25.unlinkSync(logPath);
10378
10727
  logsRemoved++;
10379
10728
  }
10380
10729
  });
10381
10730
  try {
10382
- const remainingFiles = fs24.readdirSync(logDir);
10731
+ const remainingFiles = fs25.readdirSync(logDir);
10383
10732
  if (remainingFiles.length === 0) {
10384
- fs24.rmdirSync(logDir);
10733
+ fs25.rmdirSync(logDir);
10385
10734
  }
10386
10735
  } catch {
10387
10736
  }
@@ -10425,11 +10774,15 @@ function statusCommand(program2) {
10425
10774
  const qaProc = snapshot.processes.find((p) => p.name === "qa");
10426
10775
  const auditProc = snapshot.processes.find((p) => p.name === "audit");
10427
10776
  const plannerProc = snapshot.processes.find((p) => p.name === "planner");
10777
+ const analyticsProc = snapshot.processes.find((p) => p.name === "analytics");
10778
+ const mergerProc = snapshot.processes.find((p) => p.name === "merger");
10428
10779
  const executorLog = snapshot.logs.find((l) => l.name === "executor");
10429
10780
  const reviewerLog = snapshot.logs.find((l) => l.name === "reviewer");
10430
10781
  const qaLog = snapshot.logs.find((l) => l.name === "qa");
10431
10782
  const auditLog = snapshot.logs.find((l) => l.name === "audit");
10432
10783
  const plannerLog = snapshot.logs.find((l) => l.name === "planner");
10784
+ const analyticsLog = snapshot.logs.find((l) => l.name === "analytics");
10785
+ const mergerLog = snapshot.logs.find((l) => l.name === "merger");
10433
10786
  const pendingPrds = snapshot.prds.filter(
10434
10787
  (p) => p.status === "ready" || p.status === "blocked"
10435
10788
  ).length;
@@ -10447,6 +10800,8 @@ function statusCommand(program2) {
10447
10800
  qa: { running: qaProc?.running ?? false, pid: qaProc?.pid ?? null },
10448
10801
  audit: { running: auditProc?.running ?? false, pid: auditProc?.pid ?? null },
10449
10802
  planner: { running: plannerProc?.running ?? false, pid: plannerProc?.pid ?? null },
10803
+ analytics: { running: analyticsProc?.running ?? false, pid: analyticsProc?.pid ?? null },
10804
+ merger: { running: mergerProc?.running ?? false, pid: mergerProc?.pid ?? null },
10450
10805
  prds: { pending: pendingPrds, claimed: claimedPrds, done: donePrds },
10451
10806
  prs: { open: snapshot.prs.length },
10452
10807
  crontab: snapshot.crontab,
@@ -10480,6 +10835,18 @@ function statusCommand(program2) {
10480
10835
  lastLines: plannerLog.lastLines,
10481
10836
  exists: plannerLog.exists,
10482
10837
  size: plannerLog.size
10838
+ } : void 0,
10839
+ analytics: analyticsLog ? {
10840
+ path: analyticsLog.path,
10841
+ lastLines: analyticsLog.lastLines,
10842
+ exists: analyticsLog.exists,
10843
+ size: analyticsLog.size
10844
+ } : void 0,
10845
+ merger: mergerLog ? {
10846
+ path: mergerLog.path,
10847
+ lastLines: mergerLog.lastLines,
10848
+ exists: mergerLog.exists,
10849
+ size: mergerLog.size
10483
10850
  } : void 0
10484
10851
  }
10485
10852
  };
@@ -10517,6 +10884,14 @@ function statusCommand(program2) {
10517
10884
  "Planner",
10518
10885
  formatRunningStatus(status.planner.running, status.planner.pid)
10519
10886
  ]);
10887
+ processTable.push([
10888
+ "Analytics",
10889
+ formatRunningStatus(status.analytics.running, status.analytics.pid)
10890
+ ]);
10891
+ processTable.push([
10892
+ "Merger",
10893
+ formatRunningStatus(status.merger.running, status.merger.pid)
10894
+ ]);
10520
10895
  console.log(processTable.toString());
10521
10896
  header("PRD Status");
10522
10897
  const prdTable = createTable({ head: ["Status", "Count"] });
@@ -10573,6 +10948,20 @@ function statusCommand(program2) {
10573
10948
  status.logs.planner.exists ? "Exists" : "Not found"
10574
10949
  ]);
10575
10950
  }
10951
+ if (status.logs.analytics) {
10952
+ logTable.push([
10953
+ "Analytics",
10954
+ status.logs.analytics.exists ? formatBytes(status.logs.analytics.size) : "-",
10955
+ status.logs.analytics.exists ? "Exists" : "Not found"
10956
+ ]);
10957
+ }
10958
+ if (status.logs.merger) {
10959
+ logTable.push([
10960
+ "Merger",
10961
+ status.logs.merger.exists ? formatBytes(status.logs.merger.size) : "-",
10962
+ status.logs.merger.exists ? "Exists" : "Not found"
10963
+ ]);
10964
+ }
10576
10965
  console.log(logTable.toString());
10577
10966
  if (options.verbose) {
10578
10967
  if (status.logs.executor?.exists && status.logs.executor.lastLines.length > 0) {
@@ -10595,6 +10984,14 @@ function statusCommand(program2) {
10595
10984
  dim(" Planner last 5 lines:");
10596
10985
  status.logs.planner.lastLines.forEach((line) => dim(` ${line}`));
10597
10986
  }
10987
+ if (status.logs.analytics?.exists && status.logs.analytics.lastLines.length > 0) {
10988
+ dim(" Analytics last 5 lines:");
10989
+ status.logs.analytics.lastLines.forEach((line) => dim(` ${line}`));
10990
+ }
10991
+ if (status.logs.merger?.exists && status.logs.merger.lastLines.length > 0) {
10992
+ dim(" Merger last 5 lines:");
10993
+ status.logs.merger.lastLines.forEach((line) => dim(` ${line}`));
10994
+ }
10598
10995
  }
10599
10996
  header("Commands");
10600
10997
  dim(" night-watch install - Install crontab entries");
@@ -10604,6 +11001,8 @@ function statusCommand(program2) {
10604
11001
  dim(" night-watch qa - Run QA now");
10605
11002
  dim(" night-watch audit - Run audit now");
10606
11003
  dim(" night-watch planner - Run planner now");
11004
+ dim(" night-watch analytics - Run analytics now");
11005
+ dim(" night-watch merge - Run merger now");
10607
11006
  console.log();
10608
11007
  } catch (error2) {
10609
11008
  console.error(
@@ -10617,14 +11016,14 @@ function statusCommand(program2) {
10617
11016
  // src/commands/logs.ts
10618
11017
  init_dist();
10619
11018
  import { spawn as spawn3 } from "child_process";
10620
- import * as path27 from "path";
10621
- import * as fs25 from "fs";
11019
+ import * as path28 from "path";
11020
+ import * as fs26 from "fs";
10622
11021
  function getLastLines(filePath, lineCount) {
10623
- if (!fs25.existsSync(filePath)) {
11022
+ if (!fs26.existsSync(filePath)) {
10624
11023
  return `Log file not found: ${filePath}`;
10625
11024
  }
10626
11025
  try {
10627
- const content = fs25.readFileSync(filePath, "utf-8");
11026
+ const content = fs26.readFileSync(filePath, "utf-8");
10628
11027
  const lines = content.trim().split("\n");
10629
11028
  return lines.slice(-lineCount).join("\n");
10630
11029
  } catch (error2) {
@@ -10632,7 +11031,7 @@ function getLastLines(filePath, lineCount) {
10632
11031
  }
10633
11032
  }
10634
11033
  function followLog(filePath) {
10635
- if (!fs25.existsSync(filePath)) {
11034
+ if (!fs26.existsSync(filePath)) {
10636
11035
  console.log(`Log file not found: ${filePath}`);
10637
11036
  console.log("The log file will be created when the first execution runs.");
10638
11037
  return;
@@ -10649,32 +11048,42 @@ function followLog(filePath) {
10649
11048
  });
10650
11049
  }
10651
11050
  function logsCommand(program2) {
10652
- program2.command("logs").description("View night-watch log output").option("-n, --lines <count>", "Number of lines to show", "50").option("-f, --follow", "Follow log output (tail -f)").option("-t, --type <type>", "Log type to view (executor|reviewer|qa|audit|planner|all)", "all").action(async (options) => {
11051
+ program2.command("logs").description("View night-watch log output").option("-n, --lines <count>", "Number of lines to show", "50").option("-f, --follow", "Follow log output (tail -f)").option(
11052
+ "-t, --type <type>",
11053
+ "Log type to view (executor|reviewer|qa|audit|planner|analytics|merger|all)",
11054
+ "all"
11055
+ ).action(async (options) => {
10653
11056
  try {
10654
11057
  const projectDir = process.cwd();
10655
- const logDir = path27.join(projectDir, LOG_DIR);
11058
+ const logDir = path28.join(projectDir, LOG_DIR);
10656
11059
  const lineCount = parseInt(options.lines || "50", 10);
10657
- const executorLog = path27.join(logDir, EXECUTOR_LOG_FILE);
10658
- const reviewerLog = path27.join(logDir, REVIEWER_LOG_FILE);
10659
- const qaLog = path27.join(logDir, `${QA_LOG_NAME}.log`);
10660
- const auditLog = path27.join(logDir, `${AUDIT_LOG_NAME}.log`);
10661
- const plannerLog = path27.join(logDir, `${PLANNER_LOG_NAME}.log`);
11060
+ const executorLog = path28.join(logDir, EXECUTOR_LOG_FILE);
11061
+ const reviewerLog = path28.join(logDir, REVIEWER_LOG_FILE);
11062
+ const qaLog = path28.join(logDir, `${QA_LOG_NAME}.log`);
11063
+ const auditLog = path28.join(logDir, `${AUDIT_LOG_NAME}.log`);
11064
+ const plannerLog = path28.join(logDir, `${PLANNER_LOG_NAME}.log`);
11065
+ const analyticsLog = path28.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
11066
+ const mergerLog = path28.join(logDir, `${MERGER_LOG_NAME}.log`);
10662
11067
  const logType = options.type?.toLowerCase() || "all";
10663
11068
  const showExecutor = logType === "all" || logType === "run" || logType === "executor";
10664
11069
  const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
10665
11070
  const showQa = logType === "all" || logType === "qa";
10666
11071
  const showAudit = logType === "all" || logType === "audit";
10667
11072
  const showPlanner = logType === "all" || logType === "planner" || logType === "slice" || logType === "slicer";
11073
+ const showAnalytics = logType === "all" || logType === "analytics";
11074
+ const showMerger = logType === "all" || logType === "merge" || logType === "merger";
10668
11075
  if (options.follow) {
10669
11076
  if (logType === "all") {
10670
11077
  dim("Note: Following all logs is not supported. Showing executor log.");
10671
- dim("Use --type reviewer|qa|audit|planner for other logs.\n");
11078
+ dim("Use --type reviewer|qa|audit|planner|analytics|merger for other logs.\n");
10672
11079
  }
10673
11080
  let targetLog = executorLog;
10674
11081
  if (showReviewer) targetLog = reviewerLog;
10675
11082
  else if (showQa) targetLog = qaLog;
10676
11083
  else if (showAudit) targetLog = auditLog;
10677
11084
  else if (showPlanner) targetLog = plannerLog;
11085
+ else if (showAnalytics) targetLog = analyticsLog;
11086
+ else if (showMerger) targetLog = mergerLog;
10678
11087
  followLog(targetLog);
10679
11088
  return;
10680
11089
  }
@@ -10709,10 +11118,24 @@ function logsCommand(program2) {
10709
11118
  console.log();
10710
11119
  console.log(getLastLines(plannerLog, lineCount));
10711
11120
  }
11121
+ if (showAnalytics) {
11122
+ header("Analytics Log");
11123
+ dim(`File: ${analyticsLog}`);
11124
+ console.log();
11125
+ console.log(getLastLines(analyticsLog, lineCount));
11126
+ }
11127
+ if (showMerger) {
11128
+ header("Merger Log");
11129
+ dim(`File: ${mergerLog}`);
11130
+ console.log();
11131
+ console.log(getLastLines(mergerLog, lineCount));
11132
+ }
10712
11133
  console.log();
10713
11134
  dim("---");
10714
11135
  dim("Tip: Use -f to follow logs in real-time");
10715
- dim(" Use --type executor|reviewer|qa|audit|planner to view specific logs");
11136
+ dim(
11137
+ " Use --type executor|reviewer|qa|audit|planner|analytics|merger to view specific logs"
11138
+ );
10716
11139
  } catch (err) {
10717
11140
  console.error(`Error reading logs: ${err instanceof Error ? err.message : String(err)}`);
10718
11141
  process.exit(1);
@@ -10723,8 +11146,8 @@ function logsCommand(program2) {
10723
11146
  // src/commands/prd.ts
10724
11147
  init_dist();
10725
11148
  import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
10726
- import * as fs26 from "fs";
10727
- import * as path28 from "path";
11149
+ import * as fs27 from "fs";
11150
+ import * as path29 from "path";
10728
11151
  import { fileURLToPath as fileURLToPath4 } from "url";
10729
11152
  import { dirname as dirname9 } from "path";
10730
11153
  var __filename3 = fileURLToPath4(import.meta.url);
@@ -10732,21 +11155,21 @@ var __dirname3 = dirname9(__filename3);
10732
11155
  function findTemplatesDir2(startDir) {
10733
11156
  let current = startDir;
10734
11157
  for (let i = 0; i < 8; i++) {
10735
- const candidate = path28.join(current, "templates");
10736
- if (fs26.existsSync(candidate) && fs26.statSync(candidate).isDirectory()) {
11158
+ const candidate = path29.join(current, "templates");
11159
+ if (fs27.existsSync(candidate) && fs27.statSync(candidate).isDirectory()) {
10737
11160
  return candidate;
10738
11161
  }
10739
- current = path28.dirname(current);
11162
+ current = path29.dirname(current);
10740
11163
  }
10741
- return path28.join(startDir, "templates");
11164
+ return path29.join(startDir, "templates");
10742
11165
  }
10743
11166
  var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
10744
11167
  function slugify2(name) {
10745
11168
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
10746
11169
  }
10747
11170
  function getNextPrdNumber2(prdDir) {
10748
- if (!fs26.existsSync(prdDir)) return 1;
10749
- const files = fs26.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
11171
+ if (!fs27.existsSync(prdDir)) return 1;
11172
+ const files = fs27.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
10750
11173
  const numbers = files.map((f) => {
10751
11174
  const match = f.match(/^(\d+)-/);
10752
11175
  return match ? parseInt(match[1], 10) : 0;
@@ -10827,13 +11250,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
10827
11250
  return null;
10828
11251
  }
10829
11252
  const ref = branch && branch !== "HEAD" ? branch : "main";
10830
- return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path28.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
11253
+ return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path29.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
10831
11254
  } catch {
10832
11255
  return null;
10833
11256
  }
10834
11257
  }
10835
11258
  function buildGithubIssueBody(prdPath, projectDir, prdContent) {
10836
- const relPath = path28.relative(projectDir, prdPath);
11259
+ const relPath = path29.relative(projectDir, prdPath);
10837
11260
  const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
10838
11261
  const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
10839
11262
  return `${fileLine}
@@ -10844,13 +11267,13 @@ ${prdContent}
10844
11267
  Created via \`night-watch prd create\`.`;
10845
11268
  }
10846
11269
  async function generatePrdWithClaude(description, projectDir, model) {
10847
- const bundledTemplatePath = path28.join(TEMPLATES_DIR2, "prd-creator.md");
10848
- const installedTemplatePath = path28.join(projectDir, "instructions", "prd-creator.md");
10849
- const templatePath = fs26.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
10850
- if (!fs26.existsSync(templatePath)) {
11270
+ const bundledTemplatePath = path29.join(TEMPLATES_DIR2, "prd-creator.md");
11271
+ const installedTemplatePath = path29.join(projectDir, "instructions", "prd-creator.md");
11272
+ const templatePath = fs27.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
11273
+ if (!fs27.existsSync(templatePath)) {
10851
11274
  return null;
10852
11275
  }
10853
- const planningPrinciples = fs26.readFileSync(templatePath, "utf-8");
11276
+ const planningPrinciples = fs27.readFileSync(templatePath, "utf-8");
10854
11277
  const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
10855
11278
  const modelId = model ?? CLAUDE_MODEL_IDS.opus;
10856
11279
  const env = buildNativeClaudeEnv(process.env);
@@ -10918,17 +11341,17 @@ function runGh(args, cwd) {
10918
11341
  return null;
10919
11342
  }
10920
11343
  function createGithubIssue(title, prdPath, projectDir, prdContent) {
10921
- const tmpFile = path28.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
11344
+ const tmpFile = path29.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
10922
11345
  try {
10923
11346
  const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
10924
- fs26.writeFileSync(tmpFile, body, "utf-8");
11347
+ fs27.writeFileSync(tmpFile, body, "utf-8");
10925
11348
  const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
10926
11349
  return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
10927
11350
  } catch {
10928
11351
  return null;
10929
11352
  } finally {
10930
11353
  try {
10931
- fs26.unlinkSync(tmpFile);
11354
+ fs27.unlinkSync(tmpFile);
10932
11355
  } catch {
10933
11356
  }
10934
11357
  }
@@ -10940,10 +11363,10 @@ function parseDependencies(content) {
10940
11363
  }
10941
11364
  function isClaimActive(claimPath, maxRuntime) {
10942
11365
  try {
10943
- if (!fs26.existsSync(claimPath)) {
11366
+ if (!fs27.existsSync(claimPath)) {
10944
11367
  return { active: false };
10945
11368
  }
10946
- const content = fs26.readFileSync(claimPath, "utf-8");
11369
+ const content = fs27.readFileSync(claimPath, "utf-8");
10947
11370
  const claim = JSON.parse(content);
10948
11371
  const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
10949
11372
  if (age < maxRuntime) {
@@ -10958,9 +11381,9 @@ function prdCommand(program2) {
10958
11381
  const prd = program2.command("prd").description("Manage PRD files");
10959
11382
  prd.command("create").description("Generate a new PRD markdown file using Claude").argument("<name>", "PRD description").option("--number", "Add auto-numbering prefix to the filename", false).option("--model <model>", "Claude model to use (e.g. sonnet, opus, or a full model ID)").action(async (name, options) => {
10960
11383
  const projectDir = process.cwd();
10961
- const prdDir = path28.join(projectDir, resolvePrdCreateDir());
10962
- if (!fs26.existsSync(prdDir)) {
10963
- fs26.mkdirSync(prdDir, { recursive: true });
11384
+ const prdDir = path29.join(projectDir, resolvePrdCreateDir());
11385
+ if (!fs27.existsSync(prdDir)) {
11386
+ fs27.mkdirSync(prdDir, { recursive: true });
10964
11387
  }
10965
11388
  const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
10966
11389
  const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
@@ -10974,13 +11397,13 @@ function prdCommand(program2) {
10974
11397
  const prdTitle = extractPrdTitle(generated) ?? name;
10975
11398
  const slug = slugify2(prdTitle);
10976
11399
  const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
10977
- const filePath = path28.join(prdDir, filename);
10978
- if (fs26.existsSync(filePath)) {
11400
+ const filePath = path29.join(prdDir, filename);
11401
+ if (fs27.existsSync(filePath)) {
10979
11402
  error(`File already exists: ${filePath}`);
10980
11403
  dim("Use a different name or remove the existing file.");
10981
11404
  process.exit(1);
10982
11405
  }
10983
- fs26.writeFileSync(filePath, generated, "utf-8");
11406
+ fs27.writeFileSync(filePath, generated, "utf-8");
10984
11407
  header("PRD Created");
10985
11408
  success(`Created: ${filePath}`);
10986
11409
  const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
@@ -10993,15 +11416,15 @@ function prdCommand(program2) {
10993
11416
  prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
10994
11417
  const projectDir = process.cwd();
10995
11418
  const config = loadConfig(projectDir);
10996
- const absolutePrdDir = path28.join(projectDir, config.prdDir);
10997
- const doneDir = path28.join(absolutePrdDir, "done");
11419
+ const absolutePrdDir = path29.join(projectDir, config.prdDir);
11420
+ const doneDir = path29.join(absolutePrdDir, "done");
10998
11421
  const pending = [];
10999
- if (fs26.existsSync(absolutePrdDir)) {
11000
- const files = fs26.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
11422
+ if (fs27.existsSync(absolutePrdDir)) {
11423
+ const files = fs27.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
11001
11424
  for (const file of files) {
11002
- const content = fs26.readFileSync(path28.join(absolutePrdDir, file), "utf-8");
11425
+ const content = fs27.readFileSync(path29.join(absolutePrdDir, file), "utf-8");
11003
11426
  const deps = parseDependencies(content);
11004
- const claimPath = path28.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
11427
+ const claimPath = path29.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
11005
11428
  const claimStatus = isClaimActive(claimPath, config.maxRuntime);
11006
11429
  pending.push({
11007
11430
  name: file,
@@ -11012,10 +11435,10 @@ function prdCommand(program2) {
11012
11435
  }
11013
11436
  }
11014
11437
  const done = [];
11015
- if (fs26.existsSync(doneDir)) {
11016
- const files = fs26.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
11438
+ if (fs27.existsSync(doneDir)) {
11439
+ const files = fs27.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
11017
11440
  for (const file of files) {
11018
- const content = fs26.readFileSync(path28.join(doneDir, file), "utf-8");
11441
+ const content = fs27.readFileSync(path29.join(doneDir, file), "utf-8");
11019
11442
  const deps = parseDependencies(content);
11020
11443
  done.push({ name: file, dependencies: deps });
11021
11444
  }
@@ -11052,7 +11475,7 @@ import blessed6 from "blessed";
11052
11475
  // src/commands/dashboard/tab-status.ts
11053
11476
  init_dist();
11054
11477
  import blessed from "blessed";
11055
- import * as fs27 from "fs";
11478
+ import * as fs28 from "fs";
11056
11479
  function sortPrdsByPriority(prds, priority) {
11057
11480
  if (priority.length === 0) return prds;
11058
11481
  const priorityMap = /* @__PURE__ */ new Map();
@@ -11148,7 +11571,7 @@ function renderLogPane(projectDir, logs) {
11148
11571
  let newestMtime = 0;
11149
11572
  for (const log of existingLogs) {
11150
11573
  try {
11151
- const stat = fs27.statSync(log.path);
11574
+ const stat = fs28.statSync(log.path);
11152
11575
  if (stat.mtimeMs > newestMtime) {
11153
11576
  newestMtime = stat.mtimeMs;
11154
11577
  newestLog = log;
@@ -12803,8 +13226,8 @@ function createActionsTab() {
12803
13226
  // src/commands/dashboard/tab-logs.ts
12804
13227
  init_dist();
12805
13228
  import blessed5 from "blessed";
12806
- import * as fs28 from "fs";
12807
- import * as path29 from "path";
13229
+ import * as fs29 from "fs";
13230
+ import * as path30 from "path";
12808
13231
  var LOG_NAMES = ["executor", "reviewer"];
12809
13232
  var LOG_LINES = 200;
12810
13233
  function createLogsTab() {
@@ -12845,7 +13268,7 @@ function createLogsTab() {
12845
13268
  let activeKeyHandlers = [];
12846
13269
  let activeCtx = null;
12847
13270
  function getLogPath(projectDir, logName) {
12848
- return path29.join(projectDir, "logs", `${logName}.log`);
13271
+ return path30.join(projectDir, "logs", `${logName}.log`);
12849
13272
  }
12850
13273
  function updateSelector() {
12851
13274
  const tabs = LOG_NAMES.map((name, idx) => {
@@ -12859,7 +13282,7 @@ function createLogsTab() {
12859
13282
  function loadLog(ctx) {
12860
13283
  const logName = LOG_NAMES[selectedLogIndex];
12861
13284
  const logPath = getLogPath(ctx.projectDir, logName);
12862
- if (!fs28.existsSync(logPath)) {
13285
+ if (!fs29.existsSync(logPath)) {
12863
13286
  logContent.setContent(
12864
13287
  `{yellow-fg}No ${logName}.log file found{/yellow-fg}
12865
13288
 
@@ -12869,7 +13292,7 @@ Log will appear here once the ${logName} runs.`
12869
13292
  return;
12870
13293
  }
12871
13294
  try {
12872
- const stat = fs28.statSync(logPath);
13295
+ const stat = fs29.statSync(logPath);
12873
13296
  const sizeKB = (stat.size / 1024).toFixed(1);
12874
13297
  logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
12875
13298
  } catch {
@@ -13367,12 +13790,12 @@ function doctorCommand(program2) {
13367
13790
 
13368
13791
  // src/commands/serve.ts
13369
13792
  init_dist();
13370
- import * as fs33 from "fs";
13793
+ import * as fs34 from "fs";
13371
13794
 
13372
13795
  // ../server/dist/index.js
13373
13796
  init_dist();
13374
- import * as fs32 from "fs";
13375
- import * as path35 from "path";
13797
+ import * as fs33 from "fs";
13798
+ import * as path36 from "path";
13376
13799
  import { dirname as dirname11 } from "path";
13377
13800
  import { fileURLToPath as fileURLToPath5 } from "url";
13378
13801
  import cors from "cors";
@@ -13457,8 +13880,8 @@ function setupGracefulShutdown(server, beforeClose) {
13457
13880
 
13458
13881
  // ../server/dist/middleware/project-resolver.middleware.js
13459
13882
  init_dist();
13460
- import * as fs29 from "fs";
13461
- import * as path30 from "path";
13883
+ import * as fs30 from "fs";
13884
+ import * as path31 from "path";
13462
13885
  function resolveProject(req, res, next) {
13463
13886
  const projectId = req.params.projectId;
13464
13887
  const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
@@ -13468,7 +13891,7 @@ function resolveProject(req, res, next) {
13468
13891
  res.status(404).json({ error: `Project not found: ${decodedId}` });
13469
13892
  return;
13470
13893
  }
13471
- if (!fs29.existsSync(entry.path) || !fs29.existsSync(path30.join(entry.path, CONFIG_FILE_NAME))) {
13894
+ if (!fs30.existsSync(entry.path) || !fs30.existsSync(path31.join(entry.path, CONFIG_FILE_NAME))) {
13472
13895
  res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
13473
13896
  return;
13474
13897
  }
@@ -13513,8 +13936,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
13513
13936
 
13514
13937
  // ../server/dist/routes/action.routes.js
13515
13938
  init_dist();
13516
- import * as fs30 from "fs";
13517
- import * as path31 from "path";
13939
+ import * as fs31 from "fs";
13940
+ import * as path32 from "path";
13518
13941
  import { execSync as execSync6, spawn as spawn6 } from "child_process";
13519
13942
  import { Router } from "express";
13520
13943
 
@@ -13552,17 +13975,17 @@ function getBoardProvider(config, projectDir) {
13552
13975
  function cleanOrphanedClaims(dir) {
13553
13976
  let entries;
13554
13977
  try {
13555
- entries = fs30.readdirSync(dir, { withFileTypes: true });
13978
+ entries = fs31.readdirSync(dir, { withFileTypes: true });
13556
13979
  } catch {
13557
13980
  return;
13558
13981
  }
13559
13982
  for (const entry of entries) {
13560
- const fullPath = path31.join(dir, entry.name);
13983
+ const fullPath = path32.join(dir, entry.name);
13561
13984
  if (entry.isDirectory() && entry.name !== "done") {
13562
13985
  cleanOrphanedClaims(fullPath);
13563
13986
  } else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
13564
13987
  try {
13565
- fs30.unlinkSync(fullPath);
13988
+ fs31.unlinkSync(fullPath);
13566
13989
  } catch {
13567
13990
  }
13568
13991
  }
@@ -13717,19 +14140,19 @@ function createActionRouteHandlers(ctx) {
13717
14140
  res.status(400).json({ error: "Invalid PRD name" });
13718
14141
  return;
13719
14142
  }
13720
- const prdDir = path31.join(projectDir, config.prdDir);
14143
+ const prdDir = path32.join(projectDir, config.prdDir);
13721
14144
  const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
13722
- const pendingPath = path31.join(prdDir, normalized);
13723
- const donePath = path31.join(prdDir, "done", normalized);
13724
- if (fs30.existsSync(pendingPath)) {
14145
+ const pendingPath = path32.join(prdDir, normalized);
14146
+ const donePath = path32.join(prdDir, "done", normalized);
14147
+ if (fs31.existsSync(pendingPath)) {
13725
14148
  res.json({ message: `"${normalized}" is already pending` });
13726
14149
  return;
13727
14150
  }
13728
- if (!fs30.existsSync(donePath)) {
14151
+ if (!fs31.existsSync(donePath)) {
13729
14152
  res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
13730
14153
  return;
13731
14154
  }
13732
- fs30.renameSync(donePath, pendingPath);
14155
+ fs31.renameSync(donePath, pendingPath);
13733
14156
  res.json({ message: `Moved "${normalized}" back to pending` });
13734
14157
  } catch (error2) {
13735
14158
  res.status(500).json({
@@ -13747,11 +14170,11 @@ function createActionRouteHandlers(ctx) {
13747
14170
  res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
13748
14171
  return;
13749
14172
  }
13750
- if (fs30.existsSync(lockPath)) {
13751
- fs30.unlinkSync(lockPath);
14173
+ if (fs31.existsSync(lockPath)) {
14174
+ fs31.unlinkSync(lockPath);
13752
14175
  }
13753
- const prdDir = path31.join(projectDir, config.prdDir);
13754
- if (fs30.existsSync(prdDir)) {
14176
+ const prdDir = path32.join(projectDir, config.prdDir);
14177
+ if (fs31.existsSync(prdDir)) {
13755
14178
  cleanOrphanedClaims(prdDir);
13756
14179
  }
13757
14180
  broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
@@ -14306,6 +14729,9 @@ function validateConfigChanges(changes, currentConfig) {
14306
14729
  if (audit.maxRuntime !== void 0 && (typeof audit.maxRuntime !== "number" || audit.maxRuntime < 60)) {
14307
14730
  return "audit.maxRuntime must be a number >= 60";
14308
14731
  }
14732
+ if (audit.targetColumn !== void 0 && !BOARD_COLUMNS.includes(audit.targetColumn)) {
14733
+ return `audit.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
14734
+ }
14309
14735
  }
14310
14736
  if (changes.analytics !== void 0) {
14311
14737
  if (typeof changes.analytics !== "object" || changes.analytics === null) {
@@ -14326,9 +14752,8 @@ function validateConfigChanges(changes, currentConfig) {
14326
14752
  return "analytics.lookbackDays must be a number between 1 and 90";
14327
14753
  }
14328
14754
  if (analytics.targetColumn !== void 0) {
14329
- const validColumns = ["Draft", "Ready", "In Progress", "Review", "Done"];
14330
- if (!validColumns.includes(analytics.targetColumn)) {
14331
- return `analytics.targetColumn must be one of: ${validColumns.join(", ")}`;
14755
+ if (!BOARD_COLUMNS.includes(analytics.targetColumn)) {
14756
+ return `analytics.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
14332
14757
  }
14333
14758
  }
14334
14759
  if (analytics.analysisPrompt !== void 0 && typeof analytics.analysisPrompt !== "string") {
@@ -14353,16 +14778,8 @@ function validateConfigChanges(changes, currentConfig) {
14353
14778
  if (typeof queue.priority !== "object" || queue.priority === null) {
14354
14779
  return "queue.priority must be an object";
14355
14780
  }
14356
- const validQueueJobs = [
14357
- "executor",
14358
- "reviewer",
14359
- "qa",
14360
- "audit",
14361
- "slicer",
14362
- "analytics"
14363
- ];
14364
14781
  for (const [jobType, value] of Object.entries(queue.priority)) {
14365
- if (!validQueueJobs.includes(jobType)) {
14782
+ if (!VALID_JOB_TYPES.includes(jobType)) {
14366
14783
  return `queue.priority contains invalid job type: ${jobType}`;
14367
14784
  }
14368
14785
  if (typeof value !== "number" || Number.isNaN(value)) {
@@ -14516,8 +14933,8 @@ function createProjectConfigRoutes() {
14516
14933
 
14517
14934
  // ../server/dist/routes/doctor.routes.js
14518
14935
  init_dist();
14519
- import * as fs31 from "fs";
14520
- import * as path32 from "path";
14936
+ import * as fs32 from "fs";
14937
+ import * as path33 from "path";
14521
14938
  import { execSync as execSync7 } from "child_process";
14522
14939
  import { Router as Router4 } from "express";
14523
14940
  function runDoctorChecks(projectDir, config) {
@@ -14550,7 +14967,7 @@ function runDoctorChecks(projectDir, config) {
14550
14967
  });
14551
14968
  }
14552
14969
  try {
14553
- const projectName = path32.basename(projectDir);
14970
+ const projectName = path33.basename(projectDir);
14554
14971
  const marker = generateMarker(projectName);
14555
14972
  const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
14556
14973
  if (crontabEntries.length > 0) {
@@ -14573,8 +14990,8 @@ function runDoctorChecks(projectDir, config) {
14573
14990
  detail: "Failed to check crontab"
14574
14991
  });
14575
14992
  }
14576
- const configPath = path32.join(projectDir, CONFIG_FILE_NAME);
14577
- if (fs31.existsSync(configPath)) {
14993
+ const configPath = path33.join(projectDir, CONFIG_FILE_NAME);
14994
+ if (fs32.existsSync(configPath)) {
14578
14995
  checks.push({ name: "config", status: "pass", detail: "Config file exists" });
14579
14996
  } else {
14580
14997
  checks.push({
@@ -14583,9 +15000,9 @@ function runDoctorChecks(projectDir, config) {
14583
15000
  detail: "Config file not found (using defaults)"
14584
15001
  });
14585
15002
  }
14586
- const prdDir = path32.join(projectDir, config.prdDir);
14587
- if (fs31.existsSync(prdDir)) {
14588
- const prds = fs31.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
15003
+ const prdDir = path33.join(projectDir, config.prdDir);
15004
+ if (fs32.existsSync(prdDir)) {
15005
+ const prds = fs32.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
14589
15006
  checks.push({
14590
15007
  name: "prdDir",
14591
15008
  status: "pass",
@@ -14628,7 +15045,7 @@ function createProjectDoctorRoutes() {
14628
15045
 
14629
15046
  // ../server/dist/routes/log.routes.js
14630
15047
  init_dist();
14631
- import * as path33 from "path";
15048
+ import * as path34 from "path";
14632
15049
  import { Router as Router5 } from "express";
14633
15050
  function createLogRoutes(deps) {
14634
15051
  const { projectDir } = deps;
@@ -14636,7 +15053,7 @@ function createLogRoutes(deps) {
14636
15053
  router.get("/:name", (req, res) => {
14637
15054
  try {
14638
15055
  const { name } = req.params;
14639
- const validNames = ["executor", "reviewer", "qa", "audit", "planner", "analytics"];
15056
+ const validNames = Object.keys(LOG_FILE_NAMES);
14640
15057
  if (!validNames.includes(name)) {
14641
15058
  res.status(400).json({
14642
15059
  error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
@@ -14647,7 +15064,7 @@ function createLogRoutes(deps) {
14647
15064
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
14648
15065
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
14649
15066
  const fileName = LOG_FILE_NAMES[name] || name;
14650
- const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
15067
+ const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
14651
15068
  const logLines = getLastLogLines(logPath, linesToRead);
14652
15069
  res.json({ name, lines: logLines });
14653
15070
  } catch (error2) {
@@ -14662,7 +15079,7 @@ function createProjectLogRoutes() {
14662
15079
  try {
14663
15080
  const projectDir = req.projectDir;
14664
15081
  const { name } = req.params;
14665
- const validNames = ["executor", "reviewer", "qa", "audit", "planner", "analytics"];
15082
+ const validNames = Object.keys(LOG_FILE_NAMES);
14666
15083
  if (!validNames.includes(name)) {
14667
15084
  res.status(400).json({
14668
15085
  error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
@@ -14673,7 +15090,7 @@ function createProjectLogRoutes() {
14673
15090
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
14674
15091
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
14675
15092
  const fileName = LOG_FILE_NAMES[name] || name;
14676
- const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
15093
+ const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
14677
15094
  const logLines = getLastLogLines(logPath, linesToRead);
14678
15095
  res.json({ name, lines: logLines });
14679
15096
  } catch (error2) {
@@ -14708,7 +15125,7 @@ function createProjectPrdRoutes() {
14708
15125
 
14709
15126
  // ../server/dist/routes/roadmap.routes.js
14710
15127
  init_dist();
14711
- import * as path34 from "path";
15128
+ import * as path35 from "path";
14712
15129
  import { Router as Router7 } from "express";
14713
15130
  function createRoadmapRouteHandlers(ctx) {
14714
15131
  const router = Router7({ mergeParams: true });
@@ -14718,7 +15135,7 @@ function createRoadmapRouteHandlers(ctx) {
14718
15135
  const config = ctx.getConfig(req);
14719
15136
  const projectDir = ctx.getProjectDir(req);
14720
15137
  const status = getRoadmapStatus(projectDir, config);
14721
- const prdDir = path34.join(projectDir, config.prdDir);
15138
+ const prdDir = path35.join(projectDir, config.prdDir);
14722
15139
  const state = loadRoadmapState(prdDir);
14723
15140
  res.json({
14724
15141
  ...status,
@@ -14843,12 +15260,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
14843
15260
  const auditPlan = getSchedulingPlan(projectDir, config, "audit");
14844
15261
  const plannerPlan = getSchedulingPlan(projectDir, config, "slicer");
14845
15262
  const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
15263
+ const mergerPlan = getSchedulingPlan(projectDir, config, "merger");
14846
15264
  const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
14847
15265
  const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
14848
15266
  const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
14849
15267
  const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
14850
15268
  const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
14851
15269
  const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
15270
+ const mergerInstalled = installed && (config.merger?.enabled ?? false) && hasScheduledCommand(entries, "merge");
14852
15271
  return {
14853
15272
  executor: {
14854
15273
  schedule: config.cronSchedule,
@@ -14898,6 +15317,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
14898
15317
  manualDelayMinutes: analyticsPlan.manualDelayMinutes,
14899
15318
  balancedDelayMinutes: analyticsPlan.balancedDelayMinutes
14900
15319
  },
15320
+ merger: {
15321
+ schedule: config.merger?.schedule ?? "55 */4 * * *",
15322
+ installed: mergerInstalled,
15323
+ nextRun: mergerInstalled ? addDelayToIsoString(computeNextRun(config.merger?.schedule ?? "55 */4 * * *"), mergerPlan.totalDelayMinutes) : null,
15324
+ delayMinutes: mergerPlan.totalDelayMinutes,
15325
+ manualDelayMinutes: mergerPlan.manualDelayMinutes,
15326
+ balancedDelayMinutes: mergerPlan.balancedDelayMinutes
15327
+ },
14901
15328
  paused: !installed,
14902
15329
  schedulingPriority: config.schedulingPriority,
14903
15330
  entries
@@ -15052,14 +15479,14 @@ function createQueueRoutes(deps) {
15052
15479
  var __filename4 = fileURLToPath5(import.meta.url);
15053
15480
  var __dirname4 = dirname11(__filename4);
15054
15481
  function resolveWebDistPath() {
15055
- const bundled = path35.join(__dirname4, "web");
15056
- if (fs32.existsSync(path35.join(bundled, "index.html")))
15482
+ const bundled = path36.join(__dirname4, "web");
15483
+ if (fs33.existsSync(path36.join(bundled, "index.html")))
15057
15484
  return bundled;
15058
15485
  let d = __dirname4;
15059
15486
  for (let i = 0; i < 8; i++) {
15060
- if (fs32.existsSync(path35.join(d, "turbo.json"))) {
15061
- const dev = path35.join(d, "web/dist");
15062
- if (fs32.existsSync(path35.join(dev, "index.html")))
15487
+ if (fs33.existsSync(path36.join(d, "turbo.json"))) {
15488
+ const dev = path36.join(d, "web/dist");
15489
+ if (fs33.existsSync(path36.join(dev, "index.html")))
15063
15490
  return dev;
15064
15491
  break;
15065
15492
  }
@@ -15069,7 +15496,7 @@ function resolveWebDistPath() {
15069
15496
  }
15070
15497
  function setupStaticFiles(app) {
15071
15498
  const webDistPath = resolveWebDistPath();
15072
- if (fs32.existsSync(webDistPath)) {
15499
+ if (fs33.existsSync(webDistPath)) {
15073
15500
  app.use(express.static(webDistPath));
15074
15501
  }
15075
15502
  app.use((req, res, next) => {
@@ -15077,8 +15504,8 @@ function setupStaticFiles(app) {
15077
15504
  next();
15078
15505
  return;
15079
15506
  }
15080
- const indexPath = path35.resolve(webDistPath, "index.html");
15081
- if (fs32.existsSync(indexPath)) {
15507
+ const indexPath = path36.resolve(webDistPath, "index.html");
15508
+ if (fs33.existsSync(indexPath)) {
15082
15509
  res.sendFile(indexPath, (err) => {
15083
15510
  if (err)
15084
15511
  next();
@@ -15211,7 +15638,7 @@ function createGlobalApp() {
15211
15638
  return app;
15212
15639
  }
15213
15640
  function bootContainer() {
15214
- initContainer(path35.dirname(getDbPath()));
15641
+ initContainer(path36.dirname(getDbPath()));
15215
15642
  }
15216
15643
  function startServer(projectDir, port) {
15217
15644
  bootContainer();
@@ -15264,8 +15691,8 @@ function isProcessRunning2(pid) {
15264
15691
  }
15265
15692
  function readPid(lockPath) {
15266
15693
  try {
15267
- if (!fs33.existsSync(lockPath)) return null;
15268
- const raw = fs33.readFileSync(lockPath, "utf-8").trim();
15694
+ if (!fs34.existsSync(lockPath)) return null;
15695
+ const raw = fs34.readFileSync(lockPath, "utf-8").trim();
15269
15696
  const pid = parseInt(raw, 10);
15270
15697
  return Number.isFinite(pid) ? pid : null;
15271
15698
  } catch {
@@ -15277,10 +15704,10 @@ function acquireServeLock(mode, port) {
15277
15704
  let stalePidCleaned;
15278
15705
  for (let attempt = 0; attempt < 2; attempt++) {
15279
15706
  try {
15280
- const fd = fs33.openSync(lockPath, "wx");
15281
- fs33.writeFileSync(fd, `${process.pid}
15707
+ const fd = fs34.openSync(lockPath, "wx");
15708
+ fs34.writeFileSync(fd, `${process.pid}
15282
15709
  `);
15283
- fs33.closeSync(fd);
15710
+ fs34.closeSync(fd);
15284
15711
  return { acquired: true, lockPath, stalePidCleaned };
15285
15712
  } catch (error2) {
15286
15713
  const err = error2;
@@ -15301,7 +15728,7 @@ function acquireServeLock(mode, port) {
15301
15728
  };
15302
15729
  }
15303
15730
  try {
15304
- fs33.unlinkSync(lockPath);
15731
+ fs34.unlinkSync(lockPath);
15305
15732
  if (existingPid) {
15306
15733
  stalePidCleaned = existingPid;
15307
15734
  }
@@ -15324,10 +15751,10 @@ function acquireServeLock(mode, port) {
15324
15751
  }
15325
15752
  function releaseServeLock(lockPath) {
15326
15753
  try {
15327
- if (!fs33.existsSync(lockPath)) return;
15754
+ if (!fs34.existsSync(lockPath)) return;
15328
15755
  const lockPid = readPid(lockPath);
15329
15756
  if (lockPid !== null && lockPid !== process.pid) return;
15330
- fs33.unlinkSync(lockPath);
15757
+ fs34.unlinkSync(lockPath);
15331
15758
  } catch {
15332
15759
  }
15333
15760
  }
@@ -15423,14 +15850,14 @@ function historyCommand(program2) {
15423
15850
  // src/commands/update.ts
15424
15851
  init_dist();
15425
15852
  import { spawnSync as spawnSync2 } from "child_process";
15426
- import * as fs34 from "fs";
15427
- import * as path36 from "path";
15853
+ import * as fs35 from "fs";
15854
+ import * as path37 from "path";
15428
15855
  var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
15429
15856
  function parseProjectDirs(projects, cwd) {
15430
15857
  if (!projects || projects.trim().length === 0) {
15431
15858
  return [cwd];
15432
15859
  }
15433
- const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path36.resolve(cwd, entry));
15860
+ const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path37.resolve(cwd, entry));
15434
15861
  return Array.from(new Set(dirs));
15435
15862
  }
15436
15863
  function shouldInstallGlobal(options) {
@@ -15472,7 +15899,7 @@ function updateCommand(program2) {
15472
15899
  }
15473
15900
  const nightWatchBin = resolveNightWatchBin();
15474
15901
  for (const projectDir of projectDirs) {
15475
- if (!fs34.existsSync(projectDir) || !fs34.statSync(projectDir).isDirectory()) {
15902
+ if (!fs35.existsSync(projectDir) || !fs35.statSync(projectDir).isDirectory()) {
15476
15903
  warn(`Skipping invalid project directory: ${projectDir}`);
15477
15904
  continue;
15478
15905
  }
@@ -15516,8 +15943,8 @@ function prdStateCommand(program2) {
15516
15943
 
15517
15944
  // src/commands/retry.ts
15518
15945
  init_dist();
15519
- import * as fs35 from "fs";
15520
- import * as path37 from "path";
15946
+ import * as fs36 from "fs";
15947
+ import * as path38 from "path";
15521
15948
  function normalizePrdName(name) {
15522
15949
  if (!name.endsWith(".md")) {
15523
15950
  return `${name}.md`;
@@ -15525,26 +15952,26 @@ function normalizePrdName(name) {
15525
15952
  return name;
15526
15953
  }
15527
15954
  function getDonePrds(doneDir) {
15528
- if (!fs35.existsSync(doneDir)) {
15955
+ if (!fs36.existsSync(doneDir)) {
15529
15956
  return [];
15530
15957
  }
15531
- return fs35.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
15958
+ return fs36.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
15532
15959
  }
15533
15960
  function retryCommand(program2) {
15534
15961
  program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
15535
15962
  const projectDir = process.cwd();
15536
15963
  const config = loadConfig(projectDir);
15537
- const prdDir = path37.join(projectDir, config.prdDir);
15538
- const doneDir = path37.join(prdDir, "done");
15964
+ const prdDir = path38.join(projectDir, config.prdDir);
15965
+ const doneDir = path38.join(prdDir, "done");
15539
15966
  const normalizedPrdName = normalizePrdName(prdName);
15540
- const pendingPath = path37.join(prdDir, normalizedPrdName);
15541
- if (fs35.existsSync(pendingPath)) {
15967
+ const pendingPath = path38.join(prdDir, normalizedPrdName);
15968
+ if (fs36.existsSync(pendingPath)) {
15542
15969
  info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
15543
15970
  return;
15544
15971
  }
15545
- const donePath = path37.join(doneDir, normalizedPrdName);
15546
- if (fs35.existsSync(donePath)) {
15547
- fs35.renameSync(donePath, pendingPath);
15972
+ const donePath = path38.join(doneDir, normalizedPrdName);
15973
+ if (fs36.existsSync(donePath)) {
15974
+ fs36.renameSync(donePath, pendingPath);
15548
15975
  success(`Moved "${normalizedPrdName}" back to pending.`);
15549
15976
  dim(`From: ${donePath}`);
15550
15977
  dim(`To: ${pendingPath}`);
@@ -15796,7 +16223,7 @@ function prdsCommand(program2) {
15796
16223
 
15797
16224
  // src/commands/cancel.ts
15798
16225
  init_dist();
15799
- import * as fs36 from "fs";
16226
+ import * as fs37 from "fs";
15800
16227
  import * as readline2 from "readline";
15801
16228
  function getLockFilePaths2(projectDir) {
15802
16229
  const runtimeKey = projectRuntimeKey(projectDir);
@@ -15843,7 +16270,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
15843
16270
  const pid = lockStatus.pid;
15844
16271
  if (!lockStatus.running) {
15845
16272
  try {
15846
- fs36.unlinkSync(lockPath);
16273
+ fs37.unlinkSync(lockPath);
15847
16274
  return {
15848
16275
  success: true,
15849
16276
  message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
@@ -15881,7 +16308,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
15881
16308
  await sleep2(3e3);
15882
16309
  if (!isProcessRunning3(pid)) {
15883
16310
  try {
15884
- fs36.unlinkSync(lockPath);
16311
+ fs37.unlinkSync(lockPath);
15885
16312
  } catch {
15886
16313
  }
15887
16314
  return {
@@ -15916,7 +16343,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
15916
16343
  await sleep2(500);
15917
16344
  if (!isProcessRunning3(pid)) {
15918
16345
  try {
15919
- fs36.unlinkSync(lockPath);
16346
+ fs37.unlinkSync(lockPath);
15920
16347
  } catch {
15921
16348
  }
15922
16349
  return {
@@ -15977,67 +16404,69 @@ function cancelCommand(program2) {
15977
16404
 
15978
16405
  // src/commands/slice.ts
15979
16406
  init_dist();
15980
- import * as fs37 from "fs";
15981
- import * as path38 from "path";
16407
+ import * as fs38 from "fs";
16408
+ import * as path39 from "path";
15982
16409
  function plannerLockPath2(projectDir) {
15983
16410
  return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
15984
16411
  }
15985
16412
  function acquirePlannerLock(projectDir) {
15986
16413
  const lockFile = plannerLockPath2(projectDir);
15987
- if (fs37.existsSync(lockFile)) {
15988
- const pidRaw = fs37.readFileSync(lockFile, "utf-8").trim();
16414
+ if (fs38.existsSync(lockFile)) {
16415
+ const pidRaw = fs38.readFileSync(lockFile, "utf-8").trim();
15989
16416
  const pid = parseInt(pidRaw, 10);
15990
16417
  if (!Number.isNaN(pid) && isProcessRunning(pid)) {
15991
16418
  return { acquired: false, lockFile, pid };
15992
16419
  }
15993
16420
  try {
15994
- fs37.unlinkSync(lockFile);
16421
+ fs38.unlinkSync(lockFile);
15995
16422
  } catch {
15996
16423
  }
15997
16424
  }
15998
- fs37.writeFileSync(lockFile, String(process.pid));
16425
+ fs38.writeFileSync(lockFile, String(process.pid));
15999
16426
  return { acquired: true, lockFile };
16000
16427
  }
16001
16428
  function releasePlannerLock(lockFile) {
16002
16429
  try {
16003
- if (fs37.existsSync(lockFile)) {
16004
- fs37.unlinkSync(lockFile);
16430
+ if (fs38.existsSync(lockFile)) {
16431
+ fs38.unlinkSync(lockFile);
16005
16432
  }
16006
16433
  } catch {
16007
16434
  }
16008
16435
  }
16009
16436
  function resolvePlannerIssueColumn(config) {
16010
- return config.roadmapScanner.issueColumn === "Ready" ? "Ready" : "Draft";
16437
+ return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
16011
16438
  }
16012
16439
  function buildPlannerIssueBody(projectDir, config, result) {
16013
- const relativePrdPath = path38.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
16014
- const absolutePrdPath = path38.join(projectDir, config.prdDir, result.file ?? "");
16440
+ const relativePrdPath = path39.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
16441
+ const absolutePrdPath = path39.join(projectDir, config.prdDir, result.file ?? "");
16015
16442
  const sourceItem = result.item;
16016
16443
  let prdContent;
16017
16444
  try {
16018
- prdContent = fs37.readFileSync(absolutePrdPath, "utf-8");
16445
+ prdContent = fs38.readFileSync(absolutePrdPath, "utf-8");
16019
16446
  } catch {
16020
16447
  prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
16021
16448
  }
16022
16449
  const maxBodyChars = 6e4;
16023
16450
  const truncated = prdContent.length > maxBodyChars;
16024
- const prdPreview = truncated ? `${prdContent.slice(0, maxBodyChars)}
16451
+ const prdBody = truncated ? `${prdContent.slice(0, maxBodyChars)}
16025
16452
 
16026
16453
  ...[truncated]` : prdContent;
16027
- const sourceLines = sourceItem ? [
16028
- `- Source section: ${sourceItem.section}`,
16029
- `- Source item: ${sourceItem.title}`,
16030
- sourceItem.description ? `- Source summary: ${sourceItem.description}` : ""
16031
- ].filter((line) => line.length > 0) : [];
16454
+ const metaLines = [`- PRD file: \`${relativePrdPath}\``];
16455
+ if (sourceItem) {
16456
+ metaLines.push(`- Source section: ${sourceItem.section}`);
16457
+ if (sourceItem.description) {
16458
+ metaLines.push(`- Source summary: ${sourceItem.description}`);
16459
+ }
16460
+ }
16032
16461
  return [
16033
- "## Planner Generated PRD",
16462
+ prdBody,
16034
16463
  "",
16035
- `- PRD file: \`${relativePrdPath}\``,
16036
- ...sourceLines,
16464
+ "<details>",
16465
+ "<summary>Source metadata</summary>",
16037
16466
  "",
16038
- "---",
16467
+ ...metaLines,
16039
16468
  "",
16040
- prdPreview
16469
+ "</details>"
16041
16470
  ].join("\n");
16042
16471
  }
16043
16472
  async function createPlannerIssue(projectDir, config, result) {
@@ -16052,9 +16481,11 @@ async function createPlannerIssue(projectDir, config, result) {
16052
16481
  if (!board) {
16053
16482
  return { created: false, skippedReason: "board-not-configured" };
16054
16483
  }
16484
+ const issueTitle = `PRD: ${result.item.title}`;
16485
+ const normalizeTitle = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
16055
16486
  const existingIssues = await provider.getAllIssues();
16056
16487
  const existing = existingIssues.find(
16057
- (issue2) => issue2.title.trim().toLowerCase() === result.item.title.trim().toLowerCase()
16488
+ (issue2) => normalizeTitle(issue2.title) === normalizeTitle(result.item.title)
16058
16489
  );
16059
16490
  if (existing) {
16060
16491
  return {
@@ -16065,7 +16496,7 @@ async function createPlannerIssue(projectDir, config, result) {
16065
16496
  };
16066
16497
  }
16067
16498
  const issue = await provider.createIssue({
16068
- title: result.item.title,
16499
+ title: issueTitle,
16069
16500
  body: buildPlannerIssueBody(projectDir, config, result),
16070
16501
  column: resolvePlannerIssueColumn(config)
16071
16502
  });
@@ -16218,7 +16649,7 @@ function sliceCommand(program2) {
16218
16649
  if (!options.dryRun && result.sliced) {
16219
16650
  await sendNotifications(config, {
16220
16651
  event: "run_succeeded",
16221
- projectName: path38.basename(projectDir),
16652
+ projectName: path39.basename(projectDir),
16222
16653
  exitCode,
16223
16654
  provider: config.provider,
16224
16655
  prTitle: result.item?.title
@@ -16226,7 +16657,7 @@ function sliceCommand(program2) {
16226
16657
  } else if (!options.dryRun && !nothingPending) {
16227
16658
  await sendNotifications(config, {
16228
16659
  event: "run_failed",
16229
- projectName: path38.basename(projectDir),
16660
+ projectName: path39.basename(projectDir),
16230
16661
  exitCode,
16231
16662
  provider: config.provider
16232
16663
  });
@@ -16243,20 +16674,20 @@ function sliceCommand(program2) {
16243
16674
  // src/commands/state.ts
16244
16675
  init_dist();
16245
16676
  import * as os9 from "os";
16246
- import * as path39 from "path";
16677
+ import * as path40 from "path";
16247
16678
  import chalk5 from "chalk";
16248
16679
  import { Command } from "commander";
16249
16680
  function createStateCommand() {
16250
16681
  const state = new Command("state");
16251
16682
  state.description("Manage Night Watch state");
16252
16683
  state.command("migrate").description("Migrate legacy JSON state files to SQLite").option("--dry-run", "Show what would be migrated without making changes").action((opts) => {
16253
- const nightWatchHome = process.env.NIGHT_WATCH_HOME || path39.join(os9.homedir(), GLOBAL_CONFIG_DIR);
16684
+ const nightWatchHome = process.env.NIGHT_WATCH_HOME || path40.join(os9.homedir(), GLOBAL_CONFIG_DIR);
16254
16685
  if (opts.dryRun) {
16255
16686
  console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
16256
16687
  console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
16257
- console.log(` ${path39.join(nightWatchHome, "projects.json")}`);
16258
- console.log(` ${path39.join(nightWatchHome, "history.json")}`);
16259
- console.log(` ${path39.join(nightWatchHome, "prd-states.json")}`);
16688
+ console.log(` ${path40.join(nightWatchHome, "projects.json")}`);
16689
+ console.log(` ${path40.join(nightWatchHome, "history.json")}`);
16690
+ console.log(` ${path40.join(nightWatchHome, "prd-states.json")}`);
16260
16691
  console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
16261
16692
  console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
16262
16693
  return;
@@ -16294,8 +16725,8 @@ function createStateCommand() {
16294
16725
  init_dist();
16295
16726
  init_dist();
16296
16727
  import { execFileSync as execFileSync6 } from "child_process";
16297
- import * as fs38 from "fs";
16298
- import * as path40 from "path";
16728
+ import * as fs39 from "fs";
16729
+ import * as path41 from "path";
16299
16730
  import * as readline3 from "readline";
16300
16731
  import chalk6 from "chalk";
16301
16732
  async function run(fn) {
@@ -16317,7 +16748,7 @@ function getProvider(config, cwd) {
16317
16748
  return createBoardProvider(bp, cwd);
16318
16749
  }
16319
16750
  function defaultBoardTitle(cwd) {
16320
- return `${path40.basename(cwd)} Night Watch`;
16751
+ return `${path41.basename(cwd)} Night Watch`;
16321
16752
  }
16322
16753
  async function ensureBoardConfigured(config, cwd, provider, options) {
16323
16754
  if (config.boardProvider?.projectNumber) {
@@ -16516,11 +16947,11 @@ function boardCommand(program2) {
16516
16947
  let body = options.body ?? "";
16517
16948
  if (options.bodyFile) {
16518
16949
  const filePath = options.bodyFile;
16519
- if (!fs38.existsSync(filePath)) {
16950
+ if (!fs39.existsSync(filePath)) {
16520
16951
  console.error(`File not found: ${filePath}`);
16521
16952
  process.exit(1);
16522
16953
  }
16523
- body = fs38.readFileSync(filePath, "utf-8");
16954
+ body = fs39.readFileSync(filePath, "utf-8");
16524
16955
  }
16525
16956
  const labels = [];
16526
16957
  if (options.label) {
@@ -16761,12 +17192,12 @@ function boardCommand(program2) {
16761
17192
  const config = loadConfig(cwd);
16762
17193
  const provider = getProvider(config, cwd);
16763
17194
  await ensureBoardConfigured(config, cwd, provider);
16764
- const roadmapPath = options.roadmap ?? path40.join(cwd, "ROADMAP.md");
16765
- if (!fs38.existsSync(roadmapPath)) {
17195
+ const roadmapPath = options.roadmap ?? path41.join(cwd, "ROADMAP.md");
17196
+ if (!fs39.existsSync(roadmapPath)) {
16766
17197
  console.error(`Roadmap file not found: ${roadmapPath}`);
16767
17198
  process.exit(1);
16768
17199
  }
16769
- const roadmapContent = fs38.readFileSync(roadmapPath, "utf-8");
17200
+ const roadmapContent = fs39.readFileSync(roadmapPath, "utf-8");
16770
17201
  const items = parseRoadmap(roadmapContent);
16771
17202
  const uncheckedItems = getUncheckedItems(items);
16772
17203
  if (uncheckedItems.length === 0) {
@@ -16890,11 +17321,11 @@ function boardCommand(program2) {
16890
17321
  // src/commands/queue.ts
16891
17322
  init_dist();
16892
17323
  init_dist();
16893
- import * as path41 from "path";
17324
+ import * as path42 from "path";
16894
17325
  import { spawn as spawn7 } from "child_process";
16895
17326
  import chalk7 from "chalk";
16896
17327
  import { Command as Command2 } from "commander";
16897
- var logger5 = createLogger("queue");
17328
+ var logger6 = createLogger("queue");
16898
17329
  var VALID_JOB_TYPES2 = ["executor", "reviewer", "qa", "audit", "slicer", "planner"];
16899
17330
  function formatTimestamp(unixTs) {
16900
17331
  if (unixTs === null) return "-";
@@ -17010,7 +17441,7 @@ function createQueueCommand() {
17010
17441
  process.exit(1);
17011
17442
  }
17012
17443
  }
17013
- const projectName = path41.basename(projectDir);
17444
+ const projectName = path42.basename(projectDir);
17014
17445
  const queueConfig = loadConfig(projectDir).queue;
17015
17446
  const id = enqueueJob(
17016
17447
  projectDir,
@@ -17043,13 +17474,13 @@ function createQueueCommand() {
17043
17474
  const configDir = _opts.projectDir ?? process.cwd();
17044
17475
  const entry = dispatchNextJob(loadConfig(configDir).queue);
17045
17476
  if (!entry) {
17046
- logger5.info("No pending jobs to dispatch");
17477
+ logger6.info("No pending jobs to dispatch");
17047
17478
  return;
17048
17479
  }
17049
- logger5.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
17480
+ logger6.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
17050
17481
  const scriptName = getScriptNameForJobType(entry.jobType);
17051
17482
  if (!scriptName) {
17052
- logger5.error(`Unknown job type: ${entry.jobType}`);
17483
+ logger6.error(`Unknown job type: ${entry.jobType}`);
17053
17484
  return;
17054
17485
  }
17055
17486
  let projectEnv;
@@ -17068,7 +17499,7 @@ function createQueueCommand() {
17068
17499
  NW_QUEUE_ENTRY_ID: String(entry.id)
17069
17500
  };
17070
17501
  const scriptPath = getScriptPath(scriptName);
17071
- logger5.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
17502
+ logger6.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
17072
17503
  try {
17073
17504
  const child = spawn7("bash", [scriptPath, entry.projectPath], {
17074
17505
  detached: true,
@@ -17077,11 +17508,11 @@ function createQueueCommand() {
17077
17508
  cwd: entry.projectPath
17078
17509
  });
17079
17510
  child.unref();
17080
- logger5.info(`Spawned PID: ${child.pid}`);
17511
+ logger6.info(`Spawned PID: ${child.pid}`);
17081
17512
  markJobRunning(entry.id);
17082
17513
  } catch (error2) {
17083
17514
  updateJobStatus(entry.id, "pending");
17084
- logger5.error(
17515
+ logger6.error(
17085
17516
  `Failed to dispatch ${entry.jobType} for ${entry.projectName}: ${error2 instanceof Error ? error2.message : String(error2)}`
17086
17517
  );
17087
17518
  process.exit(1);
@@ -17096,7 +17527,7 @@ function createQueueCommand() {
17096
17527
  process.exit(1);
17097
17528
  }
17098
17529
  const queueConfig = loadConfig(projectDir).queue;
17099
- const projectName = path41.basename(projectDir);
17530
+ const projectName = path42.basename(projectDir);
17100
17531
  const result = claimJobSlot(
17101
17532
  projectDir,
17102
17533
  projectName,
@@ -17233,7 +17664,7 @@ function notifyCommand(program2) {
17233
17664
 
17234
17665
  // src/commands/summary.ts
17235
17666
  init_dist();
17236
- import path42 from "path";
17667
+ import path43 from "path";
17237
17668
  import chalk8 from "chalk";
17238
17669
  function formatDuration2(seconds) {
17239
17670
  if (seconds === null) return "-";
@@ -17263,7 +17694,7 @@ function formatJobStatus(status) {
17263
17694
  return chalk8.dim(status);
17264
17695
  }
17265
17696
  function getProjectName2(projectPath) {
17266
- return path42.basename(projectPath) || projectPath;
17697
+ return path43.basename(projectPath) || projectPath;
17267
17698
  }
17268
17699
  function formatProvider(providerKey) {
17269
17700
  return providerKey.split(":")[0] || providerKey;
@@ -17382,7 +17813,7 @@ function summaryCommand(program2) {
17382
17813
  // src/commands/resolve.ts
17383
17814
  init_dist();
17384
17815
  import { execFileSync as execFileSync7 } from "child_process";
17385
- import * as path43 from "path";
17816
+ import * as path44 from "path";
17386
17817
  function buildEnvVars6(config, options) {
17387
17818
  const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
17388
17819
  env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
@@ -17512,7 +17943,7 @@ ${stderr}`);
17512
17943
  const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
17513
17944
  await sendNotifications(config, {
17514
17945
  event: notificationEvent,
17515
- projectName: path43.basename(projectDir),
17946
+ projectName: path44.basename(projectDir),
17516
17947
  exitCode,
17517
17948
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
17518
17949
  });
@@ -17525,19 +17956,141 @@ ${stderr}`);
17525
17956
  });
17526
17957
  }
17527
17958
 
17959
+ // src/commands/merge.ts
17960
+ init_dist();
17961
+ import * as path45 from "path";
17962
+ function buildEnvVars7(config, options) {
17963
+ const env = buildBaseEnvVars(config, "merger", options.dryRun);
17964
+ env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
17965
+ env.NW_MERGER_MERGE_METHOD = config.merger.mergeMethod;
17966
+ env.NW_MERGER_MIN_REVIEW_SCORE = String(config.merger.minReviewScore);
17967
+ env.NW_MERGER_BRANCH_PATTERNS = (config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns : config.branchPatterns).join(",");
17968
+ env.NW_MERGER_REBASE_BEFORE_MERGE = config.merger.rebaseBeforeMerge ? "1" : "0";
17969
+ env.NW_MERGER_MAX_PRS_PER_RUN = String(config.merger.maxPrsPerRun);
17970
+ return env;
17971
+ }
17972
+ function applyCliOverrides6(config, options) {
17973
+ const overridden = { ...config, merger: { ...config.merger } };
17974
+ if (options.timeout) {
17975
+ const timeout = parseInt(options.timeout, 10);
17976
+ if (!isNaN(timeout)) {
17977
+ overridden.merger.maxRuntime = timeout;
17978
+ }
17979
+ }
17980
+ if (options.provider) {
17981
+ overridden._cliProviderOverride = options.provider;
17982
+ }
17983
+ return overridden;
17984
+ }
17985
+ function resolveMergeNotificationEvent(exitCode, mergedCount, failedCount) {
17986
+ if (exitCode === 0 && mergedCount > 0) {
17987
+ return "merge_completed";
17988
+ }
17989
+ if (exitCode !== 0 || failedCount > 0) {
17990
+ return "merge_failed";
17991
+ }
17992
+ return null;
17993
+ }
17994
+ function printDryRun(config, envVars, scriptPath, projectDir) {
17995
+ header("Dry Run: Merge Orchestrator");
17996
+ const mergerProvider = resolveJobProvider(config, "merger");
17997
+ header("Configuration");
17998
+ const configTable = createTable({ head: ["Setting", "Value"] });
17999
+ configTable.push(["Provider", mergerProvider]);
18000
+ configTable.push([
18001
+ "Max Runtime",
18002
+ `${config.merger.maxRuntime}s (${Math.floor(config.merger.maxRuntime / 60)}min)`
18003
+ ]);
18004
+ configTable.push(["Merge Method", config.merger.mergeMethod]);
18005
+ configTable.push(["Min Review Score", `${config.merger.minReviewScore}/100`]);
18006
+ configTable.push([
18007
+ "Branch Patterns",
18008
+ config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns.join(", ") : "(top-level)"
18009
+ ]);
18010
+ configTable.push(["Rebase Before Merge", config.merger.rebaseBeforeMerge ? "Yes" : "No"]);
18011
+ configTable.push([
18012
+ "Max PRs Per Run",
18013
+ config.merger.maxPrsPerRun === 0 ? "Unlimited" : String(config.merger.maxPrsPerRun)
18014
+ ]);
18015
+ console.log(configTable.toString());
18016
+ header("Environment Variables");
18017
+ for (const [key, value] of Object.entries(envVars)) {
18018
+ dim(` ${key}=${value}`);
18019
+ }
18020
+ header("Command");
18021
+ dim(` bash ${scriptPath} ${projectDir}`);
18022
+ console.log();
18023
+ }
18024
+ function mergeCommand(program2) {
18025
+ program2.command("merge").description("Merge eligible PRs in FIFO order").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) => {
18026
+ const projectDir = process.cwd();
18027
+ let config = loadConfig(projectDir);
18028
+ config = applyCliOverrides6(config, options);
18029
+ if (!config.merger.enabled && !options.dryRun) {
18030
+ info("Merge orchestrator is disabled in config; skipping.");
18031
+ process.exit(0);
18032
+ }
18033
+ const envVars = buildEnvVars7(config, options);
18034
+ const scriptPath = getScriptPath("night-watch-merger-cron.sh");
18035
+ if (options.dryRun) {
18036
+ printDryRun(config, envVars, scriptPath, projectDir);
18037
+ process.exit(0);
18038
+ }
18039
+ const spinner = createSpinner("Running merge orchestrator...");
18040
+ spinner.start();
18041
+ try {
18042
+ await maybeApplyCronSchedulingDelay(config, "merger", projectDir);
18043
+ const { exitCode, stdout, stderr } = await executeScriptWithOutput(
18044
+ scriptPath,
18045
+ [projectDir],
18046
+ envVars
18047
+ );
18048
+ const scriptResult = parseScriptResult(`${stdout}
18049
+ ${stderr}`);
18050
+ if (exitCode === 0) {
18051
+ if (scriptResult?.status === "queued") {
18052
+ spinner.succeed("Merge orchestrator queued \u2014 another job is currently running");
18053
+ } else if (scriptResult?.status?.startsWith("skip_")) {
18054
+ spinner.succeed("Merge orchestrator completed (no eligible PRs)");
18055
+ } else {
18056
+ spinner.succeed("Merge orchestrator completed successfully");
18057
+ }
18058
+ } else {
18059
+ spinner.fail(`Merge orchestrator exited with code ${exitCode}`);
18060
+ }
18061
+ const mergedCount = parseInt(scriptResult?.data?.merged ?? "0", 10);
18062
+ const failedCount = parseInt(scriptResult?.data?.failed ?? "0", 10);
18063
+ const notificationEvent = resolveMergeNotificationEvent(exitCode, mergedCount, failedCount);
18064
+ if (notificationEvent) {
18065
+ await sendNotifications(config, {
18066
+ event: notificationEvent,
18067
+ projectName: path45.basename(projectDir),
18068
+ exitCode,
18069
+ provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
18070
+ });
18071
+ }
18072
+ process.exit(exitCode);
18073
+ } catch (err) {
18074
+ spinner.fail("Failed to execute merge command");
18075
+ error(`${err instanceof Error ? err.message : String(err)}`);
18076
+ process.exit(1);
18077
+ }
18078
+ });
18079
+ }
18080
+
17528
18081
  // src/cli.ts
17529
18082
  var __filename5 = fileURLToPath6(import.meta.url);
17530
18083
  var __dirname5 = dirname12(__filename5);
17531
18084
  function findPackageRoot(dir) {
17532
18085
  let d = dir;
17533
18086
  for (let i = 0; i < 5; i++) {
17534
- if (existsSync30(join36(d, "package.json"))) return d;
18087
+ if (existsSync31(join37(d, "package.json"))) return d;
17535
18088
  d = dirname12(d);
17536
18089
  }
17537
18090
  return dir;
17538
18091
  }
17539
18092
  var packageRoot = findPackageRoot(__dirname5);
17540
- var packageJson = JSON.parse(readFileSync19(join36(packageRoot, "package.json"), "utf-8"));
18093
+ var packageJson = JSON.parse(readFileSync20(join37(packageRoot, "package.json"), "utf-8"));
17541
18094
  var program = new Command3();
17542
18095
  program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
17543
18096
  initCommand(program);
@@ -17568,4 +18121,5 @@ queueCommand(program);
17568
18121
  notifyCommand(program);
17569
18122
  summaryCommand(program);
17570
18123
  resolveCommand(program);
18124
+ mergeCommand(program);
17571
18125
  program.parse();