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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cli.js +1086 -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-DGpU39Cp.css +1 -0
  39. package/dist/web/assets/index-DcgNAi4A.js +386 -0
  40. package/dist/web/assets/index-SQlBKu_s.js +386 -0
  41. package/dist/web/assets/index-ZE5lOeJp.js +386 -0
  42. package/dist/web/assets/index-rfU713Zm.js +386 -0
  43. package/dist/web/index.html +2 -2
  44. 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,9 @@ 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: "pr-resolver", fileName: `${PR_RESOLVER_LOG_NAME}.log` },
4039
+ { name: "merger", fileName: `${MERGER_LOG_NAME}.log` }
3954
4040
  ];
3955
4041
  return logEntries.map(({ name, fileName }) => {
3956
4042
  const logPath = path6.join(projectDir, LOG_DIR, fileName);
@@ -3980,13 +4066,17 @@ async function fetchStatusSnapshot(projectDir, config) {
3980
4066
  const auditLock = checkLockFile(auditLockPath(projectDir));
3981
4067
  const plannerLock = checkLockFile(plannerLockPath(projectDir));
3982
4068
  const analyticsLock = checkLockFile(analyticsLockPath(projectDir));
4069
+ const prResolverLock = checkLockFile(prResolverLockPath(projectDir));
4070
+ const mergerLock = checkLockFile(mergerLockPath(projectDir));
3983
4071
  const processes = [
3984
4072
  { name: "executor", running: executorLock.running, pid: executorLock.pid },
3985
4073
  { name: "reviewer", running: reviewerLock.running, pid: reviewerLock.pid },
3986
4074
  { name: "qa", running: qaLock.running, pid: qaLock.pid },
3987
4075
  { name: "audit", running: auditLock.running, pid: auditLock.pid },
3988
4076
  { name: "planner", running: plannerLock.running, pid: plannerLock.pid },
3989
- { name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid }
4077
+ { name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid },
4078
+ { name: "pr-resolver", running: prResolverLock.running, pid: prResolverLock.pid },
4079
+ { name: "merger", running: mergerLock.running, pid: mergerLock.pid }
3990
4080
  ];
3991
4081
  const prds = collectPrdInfo(projectDir, config.prdDir, config.maxRuntime);
3992
4082
  const prs = await collectPrInfo(projectDir, config.branchPatterns);
@@ -4900,6 +4990,10 @@ function getEventEmoji(event) {
4900
4990
  return "\u2705";
4901
4991
  case "pr_resolver_failed":
4902
4992
  return "\u274C";
4993
+ case "merge_completed":
4994
+ return "\u{1F500}";
4995
+ case "merge_failed":
4996
+ return "\u274C";
4903
4997
  }
4904
4998
  }
4905
4999
  function getEventTitle(event) {
@@ -4930,6 +5024,10 @@ function getEventTitle(event) {
4930
5024
  return "PR Conflict Resolved";
4931
5025
  case "pr_resolver_failed":
4932
5026
  return "PR Resolver Failed";
5027
+ case "merge_completed":
5028
+ return "PR Merged";
5029
+ case "merge_failed":
5030
+ return "Merge Failed";
4933
5031
  }
4934
5032
  }
4935
5033
  function getEventColor(event) {
@@ -4960,6 +5058,10 @@ function getEventColor(event) {
4960
5058
  return 65280;
4961
5059
  case "pr_resolver_failed":
4962
5060
  return 16711680;
5061
+ case "merge_completed":
5062
+ return 10181046;
5063
+ case "merge_failed":
5064
+ return 16711680;
4963
5065
  }
4964
5066
  }
4965
5067
  function buildDescription(ctx) {
@@ -5562,19 +5664,96 @@ var init_roadmap_parser = __esm({
5562
5664
  }
5563
5665
  });
5564
5666
 
5565
- // ../core/dist/utils/roadmap-state.js
5667
+ // ../core/dist/audit/report.js
5566
5668
  import * as fs14 from "fs";
5567
5669
  import * as path13 from "path";
5670
+ function normalizeAuditSeverity(raw) {
5671
+ const normalized = raw.trim().toLowerCase();
5672
+ if (normalized === "critical")
5673
+ return "critical";
5674
+ if (normalized === "high")
5675
+ return "high";
5676
+ if (normalized === "low")
5677
+ return "low";
5678
+ return "medium";
5679
+ }
5680
+ function extractAuditField(block, field) {
5681
+ const pattern = new RegExp(`- \\*\\*${field}\\*\\*:\\s*([\\s\\S]*?)(?=\\n- \\*\\*|\\n###\\s+Finding\\s+\\d+|$)`, "i");
5682
+ const match = block.match(pattern);
5683
+ if (!match)
5684
+ return "";
5685
+ return match[1].replace(/`/g, "").replace(/\r/g, "").trim();
5686
+ }
5687
+ function parseAuditFindings(reportContent) {
5688
+ const headingRegex = /^###\s+Finding\s+(\d+)\s*$/gm;
5689
+ const headings = [];
5690
+ let match;
5691
+ while ((match = headingRegex.exec(reportContent)) !== null) {
5692
+ const number = parseInt(match[1], 10);
5693
+ if (!Number.isNaN(number)) {
5694
+ headings.push({
5695
+ number,
5696
+ bodyStart: headingRegex.lastIndex,
5697
+ headingStart: match.index
5698
+ });
5699
+ }
5700
+ }
5701
+ if (headings.length === 0) {
5702
+ return [];
5703
+ }
5704
+ const findings = [];
5705
+ for (let i = 0; i < headings.length; i++) {
5706
+ const current = headings[i];
5707
+ const next = headings[i + 1];
5708
+ const block = reportContent.slice(current.bodyStart, next?.headingStart ?? reportContent.length);
5709
+ const severityRaw = extractAuditField(block, "Severity");
5710
+ const category = extractAuditField(block, "Category") || "uncategorized";
5711
+ const location = extractAuditField(block, "Location") || "unknown location";
5712
+ const description = extractAuditField(block, "Description") || "No description provided";
5713
+ const snippet = extractAuditField(block, "Snippet");
5714
+ const suggestedFix = extractAuditField(block, "Suggested Fix") || "No suggested fix provided";
5715
+ findings.push({
5716
+ number: current.number,
5717
+ severity: normalizeAuditSeverity(severityRaw),
5718
+ category,
5719
+ location,
5720
+ description,
5721
+ snippet,
5722
+ suggestedFix
5723
+ });
5724
+ }
5725
+ return findings;
5726
+ }
5727
+ function loadAuditFindings(projectDir) {
5728
+ const reportPath = path13.join(projectDir, "logs", "audit-report.md");
5729
+ if (!fs14.existsSync(reportPath)) {
5730
+ return [];
5731
+ }
5732
+ const reportContent = fs14.readFileSync(reportPath, "utf-8");
5733
+ if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
5734
+ return [];
5735
+ }
5736
+ return parseAuditFindings(reportContent);
5737
+ }
5738
+ var init_report = __esm({
5739
+ "../core/dist/audit/report.js"() {
5740
+ "use strict";
5741
+ }
5742
+ });
5743
+
5744
+ // ../core/dist/utils/roadmap-state.js
5745
+ import * as fs15 from "fs";
5746
+ import * as path14 from "path";
5568
5747
  function getStateFilePath(prdDir) {
5569
- return path13.join(prdDir, STATE_FILE_NAME);
5748
+ return path14.join(prdDir, STATE_FILE_NAME);
5570
5749
  }
5571
5750
  function readJsonState(prdDir) {
5572
5751
  const statePath = getStateFilePath(prdDir);
5573
- if (!fs14.existsSync(statePath)) {
5752
+ if (!fs15.existsSync(statePath)) {
5574
5753
  return null;
5575
5754
  }
5576
5755
  try {
5577
- const content = fs14.readFileSync(statePath, "utf-8");
5756
+ const content = fs15.readFileSync(statePath, "utf-8");
5578
5757
  const parsed = JSON.parse(content);
5579
5758
  if (typeof parsed !== "object" || parsed === null) {
5580
5759
  return null;
@@ -5612,11 +5791,11 @@ function saveRoadmapState(prdDir, state) {
5612
5791
  const { roadmapState } = getRepositories();
5613
5792
  roadmapState.save(prdDir, state);
5614
5793
  const statePath = getStateFilePath(prdDir);
5615
- const dir = path13.dirname(statePath);
5616
- if (!fs14.existsSync(dir)) {
5617
- fs14.mkdirSync(dir, { recursive: true });
5794
+ const dir = path14.dirname(statePath);
5795
+ if (!fs15.existsSync(dir)) {
5796
+ fs15.mkdirSync(dir, { recursive: true });
5618
5797
  }
5619
- fs14.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
5798
+ fs15.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
5620
5799
  }
5621
5800
  function createEmptyState() {
5622
5801
  return {
@@ -5656,16 +5835,16 @@ var init_roadmap_state = __esm({
5656
5835
  });
5657
5836
 
5658
5837
  // ../core/dist/templates/slicer-prompt.js
5659
- import * as fs15 from "fs";
5660
- import * as path14 from "path";
5838
+ import * as fs16 from "fs";
5839
+ import * as path15 from "path";
5661
5840
  import { fileURLToPath as fileURLToPath2 } from "url";
5662
5841
  function loadSlicerTemplate(templateDir) {
5663
5842
  if (cachedTemplate) {
5664
5843
  return cachedTemplate;
5665
5844
  }
5666
- const templatePath = templateDir ? path14.join(templateDir, "slicer.md") : path14.resolve(__dirname, "..", "..", "templates", "slicer.md");
5845
+ const templatePath = templateDir ? path15.join(templateDir, "slicer.md") : path15.resolve(__dirname, "..", "..", "templates", "slicer.md");
5667
5846
  try {
5668
- cachedTemplate = fs15.readFileSync(templatePath, "utf-8");
5847
+ cachedTemplate = fs16.readFileSync(templatePath, "utf-8");
5669
5848
  return cachedTemplate;
5670
5849
  } catch (error2) {
5671
5850
  console.warn(`Warning: Could not load slicer template from ${templatePath}, using default:`, error2 instanceof Error ? error2.message : String(error2));
@@ -5690,7 +5869,7 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
5690
5869
  title,
5691
5870
  section,
5692
5871
  description: description || "(No description provided)",
5693
- outputFilePath: path14.join(prdDir, prdFilename),
5872
+ outputFilePath: path15.join(prdDir, prdFilename),
5694
5873
  prdDir
5695
5874
  };
5696
5875
  }
@@ -5699,10 +5878,10 @@ var init_slicer_prompt = __esm({
5699
5878
  "../core/dist/templates/slicer-prompt.js"() {
5700
5879
  "use strict";
5701
5880
  __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.
5881
+ __dirname = path15.dirname(__filename);
5882
+ 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
5883
 
5705
- When this activates: \`PRD Creator: Initializing\`
5884
+ When this activates: \`Planning Mode: Principal Architect\`
5706
5885
 
5707
5886
  ---
5708
5887
 
@@ -5723,22 +5902,16 @@ The PRD directory is: \`{{PRD_DIR}}\`
5723
5902
 
5724
5903
  ## Your Task
5725
5904
 
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}}\`.
5905
+ 1. **Explore the Codebase** \u2014 Read relevant existing files to understand structure, patterns, and conventions.
5906
+ 2. **Assess Complexity** \u2014 Score using the rubric below and determine LOW / MEDIUM / HIGH.
5907
+ 3. **Write a Complete PRD** \u2014 Follow the exact template structure below. Every section must be filled with concrete information.
5908
+ 4. **Write the PRD File** \u2014 Use the Write tool to create the PRD file at \`{{OUTPUT_FILE_PATH}}\`.
5735
5909
 
5736
5910
  ---
5737
5911
 
5738
5912
  ## Complexity Scoring
5739
5913
 
5740
5914
  \`\`\`
5741
- COMPLEXITY SCORE (sum all that apply):
5742
5915
  +1 Touches 1-5 files
5743
5916
  +2 Touches 6-10 files
5744
5917
  +3 Touches 10+ files
@@ -5750,7 +5923,7 @@ COMPLEXITY SCORE (sum all that apply):
5750
5923
 
5751
5924
  | Score | Level | Template Mode |
5752
5925
  | ----- | ------ | ----------------------------------------------- |
5753
- | 1-3 | LOW | Minimal (skip sections marked with MEDIUM/HIGH) |
5926
+ | 1-3 | LOW | Minimal (skip sections marked MEDIUM/HIGH) |
5754
5927
  | 4-6 | MEDIUM | Standard (all sections) |
5755
5928
  | 7+ | HIGH | Full + mandatory checkpoints every phase |
5756
5929
  \`\`\`
@@ -5759,25 +5932,77 @@ COMPLEXITY SCORE (sum all that apply):
5759
5932
 
5760
5933
  ## PRD Template Structure
5761
5934
 
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
5935
+ Your PRD MUST use this structure. Replace every [bracketed placeholder] with real content.
5936
+
5937
+ # PRD: [Title]
5938
+
5939
+ **Complexity: [SCORE] \u2192 [LEVEL] mode**
5940
+
5941
+ ## 1. Context
5942
+
5943
+ **Problem:** [1-2 sentences]
5944
+
5945
+ **Files Analyzed:**
5946
+ - \`path/to/file.ts\` \u2014 [what you found]
5947
+
5948
+ **Current Behavior:**
5949
+ - [3-5 bullets]
5950
+
5951
+ ### Integration Points
5952
+ - Entry point: [cron / CLI / event / route]
5953
+ - Caller file: [file invoking new code]
5954
+ - User flow: User does X \u2192 triggers Y \u2192 result Z
5955
+
5956
+ ## 2. Solution
5957
+
5958
+ **Approach:**
5959
+ - [3-5 bullets]
5960
+
5961
+ **Key Decisions:** [library choices, error handling, reused utilities]
5962
+
5963
+ **Data Changes:** [schema changes, or "None"]
5964
+
5965
+ ## 3. Sequence Flow (MEDIUM/HIGH only)
5966
+
5967
+ [mermaid sequenceDiagram]
5968
+
5969
+ ## 4. Execution Phases
5970
+
5971
+ ### Phase N: [Name] \u2014 [User-visible outcome]
5972
+
5973
+ **Files (max 5):**
5974
+ - \`src/path/file.ts\` \u2014 [what changes]
5975
+
5976
+ **Implementation:**
5977
+ - [ ] Step 1
5978
+
5979
+ **Tests Required:**
5980
+ | Test File | Test Name | Assertion |
5981
+ |-----------|-----------|-----------|
5982
+ | \`src/__tests__/feature.test.ts\` | \`should X when Y\` | \`expect(r).toBe(Z)\` |
5983
+
5984
+ **Checkpoint:** Run \`yarn verify\` and related tests after this phase.
5985
+
5986
+ ## 5. Acceptance Criteria
5987
+
5988
+ - [ ] All phases complete
5989
+ - [ ] All tests pass
5990
+ - [ ] \`yarn verify\` passes
5991
+ - [ ] Feature is reachable (not orphaned code)
5768
5992
 
5769
5993
  ---
5770
5994
 
5771
5995
  ## Critical Instructions
5772
5996
 
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**
5997
+ 1. Read all relevant files BEFORE writing the PRD
5998
+ 2. Follow existing patterns \u2014 use \`@/*\` path aliases, match naming conventions
5999
+ 3. Include concrete file paths and implementation steps
6000
+ 4. Include specific test names and assertions
6001
+ 5. Use the Write tool to create the file at \`{{OUTPUT_FILE_PATH}}\`
6002
+ 6. No placeholder text in the final PRD
6003
+ 7. The PRD is the GitHub issue body \u2014 make it self-contained
5779
6004
 
5780
- DO NOT leave placeholder text like "[Name]" or "[description]" in the final PRD.
6005
+ DO NOT leave [bracketed placeholder] text in the output.
5781
6006
  DO NOT skip any sections.
5782
6007
  DO NOT forget to write the file.
5783
6008
  `;
@@ -5786,65 +6011,10 @@ DO NOT forget to write the file.
5786
6011
  });
5787
6012
 
5788
6013
  // ../core/dist/utils/roadmap-scanner.js
5789
- import * as fs16 from "fs";
5790
- import * as path15 from "path";
6014
+ import * as fs17 from "fs";
6015
+ import * as path16 from "path";
5791
6016
  import { spawn } from "child_process";
5792
6017
  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
6018
  function auditSeverityRank(severity) {
5849
6019
  switch (severity) {
5850
6020
  case "critical":
@@ -5888,22 +6058,14 @@ function auditFindingToRoadmapItem(finding) {
5888
6058
  };
5889
6059
  }
5890
6060
  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);
6061
+ const findings = loadAuditFindings(projectDir);
5900
6062
  findings.sort((a, b) => auditSeverityRank(a.severity) - auditSeverityRank(b.severity) || a.number - b.number);
5901
6063
  return findings.map(auditFindingToRoadmapItem);
5902
6064
  }
5903
6065
  function getRoadmapStatus(projectDir, config) {
5904
- const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
6066
+ const roadmapPath = path16.join(projectDir, config.roadmapScanner.roadmapPath);
5905
6067
  const scannerEnabled = config.roadmapScanner.enabled;
5906
- if (!fs16.existsSync(roadmapPath)) {
6068
+ if (!fs17.existsSync(roadmapPath)) {
5907
6069
  return {
5908
6070
  found: false,
5909
6071
  enabled: scannerEnabled,
@@ -5914,9 +6076,9 @@ function getRoadmapStatus(projectDir, config) {
5914
6076
  items: []
5915
6077
  };
5916
6078
  }
5917
- const content = fs16.readFileSync(roadmapPath, "utf-8");
6079
+ const content = fs17.readFileSync(roadmapPath, "utf-8");
5918
6080
  const items = parseRoadmap(content);
5919
- const prdDir = path15.join(projectDir, config.prdDir);
6081
+ const prdDir = path16.join(projectDir, config.prdDir);
5920
6082
  const state = loadRoadmapState(prdDir);
5921
6083
  const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
5922
6084
  const statusItems = items.map((item) => {
@@ -5953,10 +6115,10 @@ function getRoadmapStatus(projectDir, config) {
5953
6115
  }
5954
6116
  function scanExistingPrdSlugs(prdDir) {
5955
6117
  const slugs = /* @__PURE__ */ new Set();
5956
- if (!fs16.existsSync(prdDir)) {
6118
+ if (!fs17.existsSync(prdDir)) {
5957
6119
  return slugs;
5958
6120
  }
5959
- const files = fs16.readdirSync(prdDir);
6121
+ const files = fs17.readdirSync(prdDir);
5960
6122
  for (const file of files) {
5961
6123
  if (!file.endsWith(".md")) {
5962
6124
  continue;
@@ -5992,20 +6154,20 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
5992
6154
  const nextNum = getNextPrdNumber(prdDir);
5993
6155
  const padded = String(nextNum).padStart(2, "0");
5994
6156
  const filename = `${padded}-${itemSlug}.md`;
5995
- const filePath = path15.join(prdDir, filename);
5996
- if (!fs16.existsSync(prdDir)) {
5997
- fs16.mkdirSync(prdDir, { recursive: true });
6157
+ const filePath = path16.join(prdDir, filename);
6158
+ if (!fs17.existsSync(prdDir)) {
6159
+ fs17.mkdirSync(prdDir, { recursive: true });
5998
6160
  }
5999
6161
  const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
6000
6162
  const prompt = renderSlicerPrompt(promptVars);
6001
6163
  const provider = resolveJobProvider(config, "slicer");
6002
6164
  const providerArgs = buildProviderArgs(provider, prompt, projectDir);
6003
- const logDir = path15.join(projectDir, "logs");
6004
- if (!fs16.existsSync(logDir)) {
6005
- fs16.mkdirSync(logDir, { recursive: true });
6165
+ const logDir = path16.join(projectDir, "logs");
6166
+ if (!fs17.existsSync(logDir)) {
6167
+ fs17.mkdirSync(logDir, { recursive: true });
6006
6168
  }
6007
- const logFile = path15.join(logDir, `slicer-${itemSlug}.log`);
6008
- const logStream = fs16.createWriteStream(logFile, { flags: "w" });
6169
+ const logFile = path16.join(logDir, `slicer-${itemSlug}.log`);
6170
+ const logStream = fs17.createWriteStream(logFile, { flags: "w" });
6009
6171
  logStream.on("error", () => {
6010
6172
  });
6011
6173
  return new Promise((resolve9) => {
@@ -6042,7 +6204,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
6042
6204
  });
6043
6205
  return;
6044
6206
  }
6045
- if (!fs16.existsSync(filePath)) {
6207
+ if (!fs17.existsSync(filePath)) {
6046
6208
  resolve9({
6047
6209
  sliced: false,
6048
6210
  error: `Provider did not create expected file: ${filePath}`,
@@ -6065,23 +6227,23 @@ async function sliceNextItem(projectDir, config) {
6065
6227
  error: "Roadmap scanner is disabled"
6066
6228
  };
6067
6229
  }
6068
- const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
6230
+ const roadmapPath = path16.join(projectDir, config.roadmapScanner.roadmapPath);
6069
6231
  const auditItems = collectAuditPlannerItems(projectDir);
6070
- const roadmapExists = fs16.existsSync(roadmapPath);
6232
+ const roadmapExists = fs17.existsSync(roadmapPath);
6071
6233
  if (!roadmapExists && auditItems.length === 0) {
6072
6234
  return {
6073
6235
  sliced: false,
6074
6236
  error: "No pending items to process"
6075
6237
  };
6076
6238
  }
6077
- const roadmapItems = roadmapExists ? parseRoadmap(fs16.readFileSync(roadmapPath, "utf-8")) : [];
6239
+ const roadmapItems = roadmapExists ? parseRoadmap(fs17.readFileSync(roadmapPath, "utf-8")) : [];
6078
6240
  if (roadmapExists && roadmapItems.length === 0 && auditItems.length === 0) {
6079
6241
  return {
6080
6242
  sliced: false,
6081
6243
  error: "No items in roadmap"
6082
6244
  };
6083
6245
  }
6084
- const prdDir = path15.join(projectDir, config.prdDir);
6246
+ const prdDir = path16.join(projectDir, config.prdDir);
6085
6247
  const state = loadRoadmapState(prdDir);
6086
6248
  const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
6087
6249
  const pickEligibleItem = (items) => {
@@ -6150,6 +6312,7 @@ function hasNewItems(projectDir, config) {
6150
6312
  var init_roadmap_scanner = __esm({
6151
6313
  "../core/dist/utils/roadmap-scanner.js"() {
6152
6314
  "use strict";
6315
+ init_report();
6153
6316
  init_config();
6154
6317
  init_prd_utils();
6155
6318
  init_roadmap_parser();
@@ -6252,8 +6415,8 @@ var init_shell = __esm({
6252
6415
  });
6253
6416
 
6254
6417
  // ../core/dist/utils/scheduling.js
6255
- import * as fs17 from "fs";
6256
- import * as path16 from "path";
6418
+ import * as fs18 from "fs";
6419
+ import * as path17 from "path";
6257
6420
  function normalizeSchedulingPriority(priority) {
6258
6421
  if (!Number.isFinite(priority)) {
6259
6422
  return DEFAULT_SCHEDULING_PRIORITY;
@@ -6288,7 +6451,7 @@ function getJobSchedule(config, jobType) {
6288
6451
  }
6289
6452
  }
6290
6453
  function loadPeerConfig(projectPath) {
6291
- if (!fs17.existsSync(projectPath) || !fs17.existsSync(path16.join(projectPath, CONFIG_FILE_NAME))) {
6454
+ if (!fs18.existsSync(projectPath) || !fs18.existsSync(path17.join(projectPath, CONFIG_FILE_NAME))) {
6292
6455
  return null;
6293
6456
  }
6294
6457
  try {
@@ -6299,10 +6462,10 @@ function loadPeerConfig(projectPath) {
6299
6462
  }
6300
6463
  function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
6301
6464
  const peers = /* @__PURE__ */ new Map();
6302
- const currentPath = path16.resolve(currentProjectDir);
6465
+ const currentPath = path17.resolve(currentProjectDir);
6303
6466
  const currentSchedule = getJobSchedule(currentConfig, jobType);
6304
6467
  const addPeer = (projectPath, config) => {
6305
- const resolvedPath = path16.resolve(projectPath);
6468
+ const resolvedPath = path17.resolve(projectPath);
6306
6469
  if (!isJobTypeEnabled(config, jobType)) {
6307
6470
  return;
6308
6471
  }
@@ -6313,12 +6476,12 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
6313
6476
  path: resolvedPath,
6314
6477
  config,
6315
6478
  schedulingPriority: normalizeSchedulingPriority(config.schedulingPriority),
6316
- sortKey: `${path16.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
6479
+ sortKey: `${path17.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
6317
6480
  });
6318
6481
  };
6319
6482
  addPeer(currentPath, currentConfig);
6320
6483
  for (const entry of loadRegistry()) {
6321
- const resolvedPath = path16.resolve(entry.path);
6484
+ const resolvedPath = path17.resolve(entry.path);
6322
6485
  if (resolvedPath === currentPath || peers.has(resolvedPath)) {
6323
6486
  continue;
6324
6487
  }
@@ -6336,7 +6499,7 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
6336
6499
  }
6337
6500
  function getSchedulingPlan(projectDir, config, jobType) {
6338
6501
  const peers = collectSchedulingPeers(projectDir, config, jobType);
6339
- const currentPath = path16.resolve(projectDir);
6502
+ const currentPath = path17.resolve(projectDir);
6340
6503
  const slotIndex = Math.max(0, peers.findIndex((peer) => peer.path === currentPath));
6341
6504
  const peerCount = Math.max(1, peers.length);
6342
6505
  const balancedDelayMinutes = peerCount <= 1 ? 0 : Math.floor(slotIndex * 60 / peerCount);
@@ -6428,8 +6591,8 @@ var init_webhook_validator = __esm({
6428
6591
 
6429
6592
  // ../core/dist/utils/worktree-manager.js
6430
6593
  import { execFileSync as execFileSync4 } from "child_process";
6431
- import * as fs18 from "fs";
6432
- import * as path17 from "path";
6594
+ import * as fs19 from "fs";
6595
+ import * as path18 from "path";
6433
6596
  function gitExec(args, cwd, logFile) {
6434
6597
  try {
6435
6598
  const result = execFileSync4("git", args, {
@@ -6439,7 +6602,7 @@ function gitExec(args, cwd, logFile) {
6439
6602
  });
6440
6603
  if (logFile && result) {
6441
6604
  try {
6442
- fs18.appendFileSync(logFile, result);
6605
+ fs19.appendFileSync(logFile, result);
6443
6606
  } catch {
6444
6607
  }
6445
6608
  }
@@ -6448,7 +6611,7 @@ function gitExec(args, cwd, logFile) {
6448
6611
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
6449
6612
  if (logFile) {
6450
6613
  try {
6451
- fs18.appendFileSync(logFile, errorMessage + "\n");
6614
+ fs19.appendFileSync(logFile, errorMessage + "\n");
6452
6615
  } catch {
6453
6616
  }
6454
6617
  }
@@ -6473,11 +6636,11 @@ function branchExistsRemotely(projectDir, branchName) {
6473
6636
  }
6474
6637
  function prepareBranchWorktree(options) {
6475
6638
  const { projectDir, worktreeDir, branchName, defaultBranch, logFile } = options;
6476
- if (fs18.existsSync(worktreeDir)) {
6639
+ if (fs19.existsSync(worktreeDir)) {
6477
6640
  const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
6478
6641
  if (!isRegistered) {
6479
6642
  try {
6480
- fs18.rmSync(worktreeDir, { recursive: true, force: true });
6643
+ fs19.rmSync(worktreeDir, { recursive: true, force: true });
6481
6644
  } catch {
6482
6645
  }
6483
6646
  }
@@ -6516,11 +6679,11 @@ function prepareBranchWorktree(options) {
6516
6679
  }
6517
6680
  function prepareDetachedWorktree(options) {
6518
6681
  const { projectDir, worktreeDir, defaultBranch, logFile } = options;
6519
- if (fs18.existsSync(worktreeDir)) {
6682
+ if (fs19.existsSync(worktreeDir)) {
6520
6683
  const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
6521
6684
  if (!isRegistered) {
6522
6685
  try {
6523
- fs18.rmSync(worktreeDir, { recursive: true, force: true });
6686
+ fs19.rmSync(worktreeDir, { recursive: true, force: true });
6524
6687
  } catch {
6525
6688
  }
6526
6689
  }
@@ -6562,7 +6725,7 @@ function isWorktreeRegistered(projectDir, worktreePath) {
6562
6725
  }
6563
6726
  }
6564
6727
  function cleanupWorktrees(projectDir, scope) {
6565
- const projectName = path17.basename(projectDir);
6728
+ const projectName = path18.basename(projectDir);
6566
6729
  const matchToken = scope ? scope : `${projectName}-nw`;
6567
6730
  const removed = [];
6568
6731
  try {
@@ -6598,11 +6761,11 @@ var init_worktree_manager = __esm({
6598
6761
 
6599
6762
  // ../core/dist/utils/job-queue.js
6600
6763
  import * as os7 from "os";
6601
- import * as path18 from "path";
6764
+ import * as path19 from "path";
6602
6765
  import Database7 from "better-sqlite3";
6603
6766
  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);
6767
+ const base = process.env.NIGHT_WATCH_HOME || path19.join(os7.homedir(), GLOBAL_CONFIG_DIR);
6768
+ return path19.join(base, STATE_DB_FILE_NAME);
6606
6769
  }
6607
6770
  function openDb() {
6608
6771
  const dbPath = getStateDbPath();
@@ -6647,6 +6810,8 @@ function getLockPathForJob(projectPath, jobType) {
6647
6810
  return analyticsLockPath(projectPath);
6648
6811
  case "pr-resolver":
6649
6812
  return prResolverLockPath(projectPath);
6813
+ case "merger":
6814
+ return mergerLockPath(projectPath);
6650
6815
  }
6651
6816
  }
6652
6817
  function reconcileStaleRunningJobs(db) {
@@ -7361,9 +7526,9 @@ var init_amplitude_client = __esm({
7361
7526
  });
7362
7527
 
7363
7528
  // ../core/dist/analytics/analytics-runner.js
7364
- import * as fs19 from "fs";
7529
+ import * as fs20 from "fs";
7365
7530
  import * as os8 from "os";
7366
- import * as path19 from "path";
7531
+ import * as path20 from "path";
7367
7532
  function parseIssuesFromResponse(text) {
7368
7533
  const start = text.indexOf("[");
7369
7534
  const end = text.lastIndexOf("]");
@@ -7392,9 +7557,9 @@ async function runAnalytics(config, projectDir) {
7392
7557
 
7393
7558
  --- AMPLITUDE DATA ---
7394
7559
  ${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");
7560
+ const tmpDir = fs20.mkdtempSync(path20.join(os8.tmpdir(), "nw-analytics-"));
7561
+ const promptFile = path20.join(tmpDir, "analytics-prompt.md");
7562
+ fs20.writeFileSync(promptFile, prompt, "utf-8");
7398
7563
  try {
7399
7564
  const provider = resolveJobProvider(config, "analytics");
7400
7565
  let providerCmd = PROVIDER_COMMANDS[provider];
@@ -7415,8 +7580,8 @@ set -euo pipefail
7415
7580
  ${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
7416
7581
  `;
7417
7582
  }
7418
- const scriptFile = path19.join(tmpDir, "run-analytics.sh");
7419
- fs19.writeFileSync(scriptFile, scriptContent, { mode: 493 });
7583
+ const scriptFile = path20.join(tmpDir, "run-analytics.sh");
7584
+ fs20.writeFileSync(scriptFile, scriptContent, { mode: 493 });
7420
7585
  const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], config.providerEnv ?? {});
7421
7586
  if (exitCode !== 0) {
7422
7587
  throw new Error(`AI provider exited with code ${exitCode}: ${stderr || stdout}`);
@@ -7463,7 +7628,7 @@ ${stderr}`;
7463
7628
  };
7464
7629
  } finally {
7465
7630
  try {
7466
- fs19.rmSync(tmpDir, { recursive: true, force: true });
7631
+ fs20.rmSync(tmpDir, { recursive: true, force: true });
7467
7632
  } catch {
7468
7633
  }
7469
7634
  }
@@ -7491,6 +7656,160 @@ var init_analytics = __esm({
7491
7656
  }
7492
7657
  });
7493
7658
 
7659
+ // ../core/dist/audit/board-sync.js
7660
+ function humanizeCategory(category) {
7661
+ return category.trim().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
7662
+ }
7663
+ function severityToPriorityLabel(severity) {
7664
+ switch (severity) {
7665
+ case "critical":
7666
+ return "P0";
7667
+ case "high":
7668
+ return "P1";
7669
+ default:
7670
+ return "P2";
7671
+ }
7672
+ }
7673
+ function truncateTitle(title, maxLength = 240) {
7674
+ const normalized = title.replace(/\s+/g, " ").trim();
7675
+ if (normalized.length <= maxLength) {
7676
+ return normalized;
7677
+ }
7678
+ return `${normalized.slice(0, maxLength - 1).trimEnd()}\u2026`;
7679
+ }
7680
+ function buildIssueTitle(finding) {
7681
+ return truncateTitle(`Audit: ${finding.severity} ${humanizeCategory(finding.category)} in ${finding.location}`);
7682
+ }
7683
+ function buildIssueBody(finding) {
7684
+ const lines = [
7685
+ "## Summary",
7686
+ "",
7687
+ `Night Watch audit detected a **${finding.severity}** finding in \`${finding.location}\`.`,
7688
+ "",
7689
+ "## Category",
7690
+ "",
7691
+ `\`${finding.category}\``,
7692
+ "",
7693
+ "## Description",
7694
+ "",
7695
+ finding.description,
7696
+ "",
7697
+ "## Suggested Fix",
7698
+ "",
7699
+ finding.suggestedFix
7700
+ ];
7701
+ if (finding.snippet) {
7702
+ lines.push("", "## Snippet", "", "```", finding.snippet, "```");
7703
+ }
7704
+ lines.push("", "## Source", "", "- Report: `logs/audit-report.md`", `- Finding: ${finding.number}`);
7705
+ return lines.join("\n");
7706
+ }
7707
+ async function syncAuditFindingsToBoard(config, projectDir) {
7708
+ const findings = loadAuditFindings(projectDir);
7709
+ const targetColumn = config.audit.targetColumn;
7710
+ if (findings.length === 0) {
7711
+ return {
7712
+ status: "skipped",
7713
+ findingsCount: 0,
7714
+ issuesCreated: 0,
7715
+ issuesFailed: 0,
7716
+ targetColumn: null,
7717
+ summary: "no actionable audit findings to sync"
7718
+ };
7719
+ }
7720
+ if (!config.boardProvider.enabled) {
7721
+ return {
7722
+ status: "skipped",
7723
+ findingsCount: findings.length,
7724
+ issuesCreated: 0,
7725
+ issuesFailed: 0,
7726
+ targetColumn: null,
7727
+ summary: `found ${findings.length} actionable audit finding(s); board sync skipped because board provider is disabled`
7728
+ };
7729
+ }
7730
+ let boardProvider;
7731
+ try {
7732
+ boardProvider = createBoardProvider(config.boardProvider, projectDir);
7733
+ } catch (err) {
7734
+ const message = err instanceof Error ? err.message : String(err);
7735
+ logger5.error("Failed to create board provider for audit sync", { error: message });
7736
+ return {
7737
+ status: "failed",
7738
+ findingsCount: findings.length,
7739
+ issuesCreated: 0,
7740
+ issuesFailed: findings.length,
7741
+ targetColumn,
7742
+ summary: `found ${findings.length} actionable audit finding(s), but board sync failed: ${message}`
7743
+ };
7744
+ }
7745
+ let created = 0;
7746
+ let failed = 0;
7747
+ for (const finding of findings) {
7748
+ try {
7749
+ await boardProvider.createIssue({
7750
+ title: buildIssueTitle(finding),
7751
+ body: buildIssueBody(finding),
7752
+ column: targetColumn,
7753
+ labels: [severityToPriorityLabel(finding.severity)]
7754
+ });
7755
+ created++;
7756
+ } catch (err) {
7757
+ failed++;
7758
+ logger5.error("Failed to create board issue for audit finding", {
7759
+ finding: finding.number,
7760
+ error: err instanceof Error ? err.message : String(err)
7761
+ });
7762
+ }
7763
+ }
7764
+ if (failed === 0) {
7765
+ return {
7766
+ status: "success",
7767
+ findingsCount: findings.length,
7768
+ issuesCreated: created,
7769
+ issuesFailed: 0,
7770
+ targetColumn,
7771
+ summary: `created ${created} audit issue(s) in ${targetColumn}`
7772
+ };
7773
+ }
7774
+ if (created === 0) {
7775
+ return {
7776
+ status: "failed",
7777
+ findingsCount: findings.length,
7778
+ issuesCreated: 0,
7779
+ issuesFailed: failed,
7780
+ targetColumn,
7781
+ summary: `found ${findings.length} actionable audit finding(s), but failed to create board issue(s)`
7782
+ };
7783
+ }
7784
+ return {
7785
+ status: "partial",
7786
+ findingsCount: findings.length,
7787
+ issuesCreated: created,
7788
+ issuesFailed: failed,
7789
+ targetColumn,
7790
+ summary: `created ${created} of ${findings.length} audit issue(s) in ${targetColumn} (${failed} failed)`
7791
+ };
7792
+ }
7793
+ var logger5;
7794
+ var init_board_sync = __esm({
7795
+ "../core/dist/audit/board-sync.js"() {
7796
+ "use strict";
7797
+ init_factory();
7798
+ init_logger();
7799
+ init_report();
7800
+ logger5 = createLogger("audit-sync");
7801
+ }
7802
+ });
7803
+
7804
+ // ../core/dist/audit/index.js
7805
+ var init_audit = __esm({
7806
+ "../core/dist/audit/index.js"() {
7807
+ "use strict";
7808
+ init_report();
7809
+ init_board_sync();
7810
+ }
7811
+ });
7812
+
7494
7813
  // ../core/dist/templates/prd-template.js
7495
7814
  function renderDependsOn(deps) {
7496
7815
  if (deps.length === 0) {
@@ -7698,6 +8017,7 @@ __export(dist_exports, {
7698
8017
  DEFAULT_AUDIT_ENABLED: () => DEFAULT_AUDIT_ENABLED,
7699
8018
  DEFAULT_AUDIT_MAX_RUNTIME: () => DEFAULT_AUDIT_MAX_RUNTIME,
7700
8019
  DEFAULT_AUDIT_SCHEDULE: () => DEFAULT_AUDIT_SCHEDULE,
8020
+ DEFAULT_AUDIT_TARGET_COLUMN: () => DEFAULT_AUDIT_TARGET_COLUMN,
7701
8021
  DEFAULT_AUTO_MERGE: () => DEFAULT_AUTO_MERGE,
7702
8022
  DEFAULT_AUTO_MERGE_METHOD: () => DEFAULT_AUTO_MERGE_METHOD,
7703
8023
  DEFAULT_BOARD_PROVIDER: () => DEFAULT_BOARD_PROVIDER,
@@ -7714,6 +8034,14 @@ __export(dist_exports, {
7714
8034
  DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
7715
8035
  DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
7716
8036
  DEFAULT_MAX_RUNTIME: () => DEFAULT_MAX_RUNTIME,
8037
+ DEFAULT_MERGER: () => DEFAULT_MERGER,
8038
+ DEFAULT_MERGER_ENABLED: () => DEFAULT_MERGER_ENABLED,
8039
+ DEFAULT_MERGER_MAX_PRS_PER_RUN: () => DEFAULT_MERGER_MAX_PRS_PER_RUN,
8040
+ DEFAULT_MERGER_MAX_RUNTIME: () => DEFAULT_MERGER_MAX_RUNTIME,
8041
+ DEFAULT_MERGER_MERGE_METHOD: () => DEFAULT_MERGER_MERGE_METHOD,
8042
+ DEFAULT_MERGER_MIN_REVIEW_SCORE: () => DEFAULT_MERGER_MIN_REVIEW_SCORE,
8043
+ DEFAULT_MERGER_REBASE_BEFORE_MERGE: () => DEFAULT_MERGER_REBASE_BEFORE_MERGE,
8044
+ DEFAULT_MERGER_SCHEDULE: () => DEFAULT_MERGER_SCHEDULE,
7717
8045
  DEFAULT_MIN_REVIEW_SCORE: () => DEFAULT_MIN_REVIEW_SCORE,
7718
8046
  DEFAULT_NOTIFICATIONS: () => DEFAULT_NOTIFICATIONS,
7719
8047
  DEFAULT_PRD_DIR: () => DEFAULT_PRD_DIR,
@@ -7772,6 +8100,7 @@ __export(dist_exports, {
7772
8100
  LocalKanbanProvider: () => LocalKanbanProvider,
7773
8101
  Logger: () => Logger,
7774
8102
  MAX_HISTORY_RECORDS_PER_PRD: () => MAX_HISTORY_RECORDS_PER_PRD,
8103
+ MERGER_LOG_NAME: () => MERGER_LOG_NAME,
7775
8104
  NIGHT_WATCH_LABELS: () => NIGHT_WATCH_LABELS,
7776
8105
  PLANNER_LOG_NAME: () => PLANNER_LOG_NAME,
7777
8106
  PRD_STATES_FILE_NAME: () => PRD_STATES_FILE_NAME,
@@ -7927,6 +8256,7 @@ __export(dist_exports, {
7927
8256
  isValidPriority: () => isValidPriority,
7928
8257
  label: () => label,
7929
8258
  listPrdStatesByStatus: () => listPrdStatesByStatus,
8259
+ loadAuditFindings: () => loadAuditFindings,
7930
8260
  loadConfig: () => loadConfig,
7931
8261
  loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
7932
8262
  loadHistory: () => loadHistory,
@@ -7936,9 +8266,12 @@ __export(dist_exports, {
7936
8266
  markItemProcessed: () => markItemProcessed,
7937
8267
  markJobRunning: () => markJobRunning,
7938
8268
  markPrdDone: () => markPrdDone,
8269
+ mergerLockPath: () => mergerLockPath,
7939
8270
  migrateJsonToSqlite: () => migrateJsonToSqlite,
8271
+ normalizeAuditSeverity: () => normalizeAuditSeverity,
7940
8272
  normalizeJobConfig: () => normalizeJobConfig,
7941
8273
  normalizeSchedulingPriority: () => normalizeSchedulingPriority,
8274
+ parseAuditFindings: () => parseAuditFindings,
7942
8275
  parsePrdDependencies: () => parsePrdDependencies,
7943
8276
  parseRoadmap: () => parseRoadmap,
7944
8277
  parseScriptResult: () => parseScriptResult,
@@ -7988,6 +8321,7 @@ __export(dist_exports, {
7988
8321
  sortByPriority: () => sortByPriority,
7989
8322
  step: () => step,
7990
8323
  success: () => success,
8324
+ syncAuditFindingsToBoard: () => syncAuditFindingsToBoard,
7991
8325
  unmarkItemProcessed: () => unmarkItemProcessed,
7992
8326
  unregisterProject: () => unregisterProject,
7993
8327
  updateJobStatus: () => updateJobStatus,
@@ -8044,6 +8378,7 @@ var init_dist = __esm({
8044
8378
  init_job_queue();
8045
8379
  init_summary();
8046
8380
  init_analytics();
8381
+ init_audit();
8047
8382
  init_prd_template();
8048
8383
  init_slicer_prompt();
8049
8384
  init_jobs();
@@ -8053,30 +8388,30 @@ var init_dist = __esm({
8053
8388
  // src/cli.ts
8054
8389
  import "reflect-metadata";
8055
8390
  import { Command as Command3 } from "commander";
8056
- import { existsSync as existsSync30, readFileSync as readFileSync19 } from "fs";
8391
+ import { existsSync as existsSync31, readFileSync as readFileSync20 } from "fs";
8057
8392
  import { fileURLToPath as fileURLToPath6 } from "url";
8058
- import { dirname as dirname12, join as join36 } from "path";
8393
+ import { dirname as dirname12, join as join37 } from "path";
8059
8394
 
8060
8395
  // src/commands/init.ts
8061
8396
  init_dist();
8062
- import fs20 from "fs";
8063
- import path20 from "path";
8397
+ import fs21 from "fs";
8398
+ import path21 from "path";
8064
8399
  import { execSync as execSync3 } from "child_process";
8065
8400
  import { fileURLToPath as fileURLToPath3 } from "url";
8066
- import { dirname as dirname6, join as join18 } from "path";
8401
+ import { dirname as dirname6, join as join19 } from "path";
8067
8402
  import * as readline from "readline";
8068
8403
  var __filename2 = fileURLToPath3(import.meta.url);
8069
8404
  var __dirname2 = dirname6(__filename2);
8070
8405
  function findTemplatesDir(startDir) {
8071
8406
  let d = startDir;
8072
8407
  for (let i = 0; i < 8; i++) {
8073
- const candidate = join18(d, "templates");
8074
- if (fs20.existsSync(candidate) && fs20.statSync(candidate).isDirectory()) {
8408
+ const candidate = join19(d, "templates");
8409
+ if (fs21.existsSync(candidate) && fs21.statSync(candidate).isDirectory()) {
8075
8410
  return candidate;
8076
8411
  }
8077
8412
  d = dirname6(d);
8078
8413
  }
8079
- return join18(startDir, "templates");
8414
+ return join19(startDir, "templates");
8080
8415
  }
8081
8416
  var TEMPLATES_DIR = findTemplatesDir(__dirname2);
8082
8417
  var NW_SKILLS = [
@@ -8088,12 +8423,12 @@ var NW_SKILLS = [
8088
8423
  "nw-review"
8089
8424
  ];
8090
8425
  function hasPlaywrightDependency(cwd) {
8091
- const packageJsonPath = path20.join(cwd, "package.json");
8092
- if (!fs20.existsSync(packageJsonPath)) {
8426
+ const packageJsonPath = path21.join(cwd, "package.json");
8427
+ if (!fs21.existsSync(packageJsonPath)) {
8093
8428
  return false;
8094
8429
  }
8095
8430
  try {
8096
- const packageJson2 = JSON.parse(fs20.readFileSync(packageJsonPath, "utf-8"));
8431
+ const packageJson2 = JSON.parse(fs21.readFileSync(packageJsonPath, "utf-8"));
8097
8432
  return Boolean(
8098
8433
  packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
8099
8434
  );
@@ -8105,7 +8440,7 @@ function detectPlaywright(cwd) {
8105
8440
  if (hasPlaywrightDependency(cwd)) {
8106
8441
  return true;
8107
8442
  }
8108
- if (fs20.existsSync(path20.join(cwd, "node_modules", ".bin", "playwright"))) {
8443
+ if (fs21.existsSync(path21.join(cwd, "node_modules", ".bin", "playwright"))) {
8109
8444
  return true;
8110
8445
  }
8111
8446
  try {
@@ -8121,10 +8456,10 @@ function detectPlaywright(cwd) {
8121
8456
  }
8122
8457
  }
8123
8458
  function resolvePlaywrightInstallCommand(cwd) {
8124
- if (fs20.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
8459
+ if (fs21.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) {
8125
8460
  return "pnpm add -D @playwright/test";
8126
8461
  }
8127
- if (fs20.existsSync(path20.join(cwd, "yarn.lock"))) {
8462
+ if (fs21.existsSync(path21.join(cwd, "yarn.lock"))) {
8128
8463
  return "yarn add -D @playwright/test";
8129
8464
  }
8130
8465
  return "npm install -D @playwright/test";
@@ -8270,8 +8605,8 @@ function promptProviderSelection(providers) {
8270
8605
  });
8271
8606
  }
8272
8607
  function ensureDir(dirPath) {
8273
- if (!fs20.existsSync(dirPath)) {
8274
- fs20.mkdirSync(dirPath, { recursive: true });
8608
+ if (!fs21.existsSync(dirPath)) {
8609
+ fs21.mkdirSync(dirPath, { recursive: true });
8275
8610
  }
8276
8611
  }
8277
8612
  function buildInitConfig(params) {
@@ -8319,6 +8654,7 @@ function buildInitConfig(params) {
8319
8654
  },
8320
8655
  audit: { ...defaults.audit },
8321
8656
  analytics: { ...defaults.analytics },
8657
+ merger: { ...defaults.merger },
8322
8658
  prResolver: { ...defaults.prResolver },
8323
8659
  jobProviders: { ...defaults.jobProviders },
8324
8660
  queue: {
@@ -8329,30 +8665,30 @@ function buildInitConfig(params) {
8329
8665
  }
8330
8666
  function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
8331
8667
  if (customTemplatesDir !== null) {
8332
- const customPath = join18(customTemplatesDir, templateName);
8333
- if (fs20.existsSync(customPath)) {
8668
+ const customPath = join19(customTemplatesDir, templateName);
8669
+ if (fs21.existsSync(customPath)) {
8334
8670
  return { path: customPath, source: "custom" };
8335
8671
  }
8336
8672
  }
8337
- return { path: join18(bundledTemplatesDir, templateName), source: "bundled" };
8673
+ return { path: join19(bundledTemplatesDir, templateName), source: "bundled" };
8338
8674
  }
8339
8675
  function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
8340
- if (fs20.existsSync(targetPath) && !force) {
8676
+ if (fs21.existsSync(targetPath) && !force) {
8341
8677
  console.log(` Skipped (exists): ${targetPath}`);
8342
8678
  return { created: false, source: source ?? "bundled" };
8343
8679
  }
8344
- const templatePath = sourcePath ?? join18(TEMPLATES_DIR, templateName);
8680
+ const templatePath = sourcePath ?? join19(TEMPLATES_DIR, templateName);
8345
8681
  const resolvedSource = source ?? "bundled";
8346
- let content = fs20.readFileSync(templatePath, "utf-8");
8682
+ let content = fs21.readFileSync(templatePath, "utf-8");
8347
8683
  for (const [key, value] of Object.entries(replacements)) {
8348
8684
  content = content.replaceAll(key, value);
8349
8685
  }
8350
- fs20.writeFileSync(targetPath, content);
8686
+ fs21.writeFileSync(targetPath, content);
8351
8687
  console.log(` Created: ${targetPath} (${resolvedSource})`);
8352
8688
  return { created: true, source: resolvedSource };
8353
8689
  }
8354
8690
  function addToGitignore(cwd) {
8355
- const gitignorePath = path20.join(cwd, ".gitignore");
8691
+ const gitignorePath = path21.join(cwd, ".gitignore");
8356
8692
  const entries = [
8357
8693
  {
8358
8694
  pattern: "/logs/",
@@ -8366,13 +8702,13 @@ function addToGitignore(cwd) {
8366
8702
  },
8367
8703
  { pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
8368
8704
  ];
8369
- if (!fs20.existsSync(gitignorePath)) {
8705
+ if (!fs21.existsSync(gitignorePath)) {
8370
8706
  const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
8371
- fs20.writeFileSync(gitignorePath, lines.join("\n"));
8707
+ fs21.writeFileSync(gitignorePath, lines.join("\n"));
8372
8708
  console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
8373
8709
  return;
8374
8710
  }
8375
- const content = fs20.readFileSync(gitignorePath, "utf-8");
8711
+ const content = fs21.readFileSync(gitignorePath, "utf-8");
8376
8712
  const missing = entries.filter((e) => !e.check(content));
8377
8713
  if (missing.length === 0) {
8378
8714
  console.log(` Skipped (exists): Night Watch entries in .gitignore`);
@@ -8380,59 +8716,59 @@ function addToGitignore(cwd) {
8380
8716
  }
8381
8717
  const additions = missing.map((e) => e.pattern).join("\n");
8382
8718
  const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
8383
- fs20.writeFileSync(gitignorePath, newContent);
8719
+ fs21.writeFileSync(gitignorePath, newContent);
8384
8720
  console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
8385
8721
  }
8386
8722
  function installSkills(cwd, provider, force, templatesDir) {
8387
- const skillsTemplatesDir = path20.join(templatesDir, "skills");
8388
- if (!fs20.existsSync(skillsTemplatesDir)) {
8723
+ const skillsTemplatesDir = path21.join(templatesDir, "skills");
8724
+ if (!fs21.existsSync(skillsTemplatesDir)) {
8389
8725
  return { location: "", installed: 0, skipped: 0, type: "none" };
8390
8726
  }
8391
8727
  const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
8392
8728
  const isCodexProvider = provider === "codex";
8393
- const claudeDir = path20.join(cwd, ".claude");
8394
- if (isClaudeProvider || fs20.existsSync(claudeDir)) {
8729
+ const claudeDir = path21.join(cwd, ".claude");
8730
+ if (isClaudeProvider || fs21.existsSync(claudeDir)) {
8395
8731
  ensureDir(claudeDir);
8396
- const skillsDir = path20.join(claudeDir, "skills");
8732
+ const skillsDir = path21.join(claudeDir, "skills");
8397
8733
  ensureDir(skillsDir);
8398
8734
  let installed = 0;
8399
8735
  let skipped = 0;
8400
8736
  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);
8737
+ const templateFile = path21.join(skillsTemplatesDir, `${skillName}.md`);
8738
+ if (!fs21.existsSync(templateFile)) continue;
8739
+ const skillDir = path21.join(skillsDir, skillName);
8404
8740
  ensureDir(skillDir);
8405
- const target = path20.join(skillDir, "SKILL.md");
8406
- if (fs20.existsSync(target) && !force) {
8741
+ const target = path21.join(skillDir, "SKILL.md");
8742
+ if (fs21.existsSync(target) && !force) {
8407
8743
  skipped++;
8408
8744
  continue;
8409
8745
  }
8410
- fs20.copyFileSync(templateFile, target);
8746
+ fs21.copyFileSync(templateFile, target);
8411
8747
  installed++;
8412
8748
  }
8413
8749
  return { location: ".claude/skills/", installed, skipped, type: "claude" };
8414
8750
  }
8415
8751
  if (isCodexProvider) {
8416
- const agentsFile = path20.join(cwd, "AGENTS.md");
8417
- const blockFile = path20.join(skillsTemplatesDir, "_codex-block.md");
8418
- if (!fs20.existsSync(blockFile)) {
8752
+ const agentsFile = path21.join(cwd, "AGENTS.md");
8753
+ const blockFile = path21.join(skillsTemplatesDir, "_codex-block.md");
8754
+ if (!fs21.existsSync(blockFile)) {
8419
8755
  return { location: "", installed: 0, skipped: 0, type: "none" };
8420
8756
  }
8421
- const block = fs20.readFileSync(blockFile, "utf-8");
8757
+ const block = fs21.readFileSync(blockFile, "utf-8");
8422
8758
  const marker = "## Night Watch Skills";
8423
- if (!fs20.existsSync(agentsFile)) {
8424
- fs20.writeFileSync(agentsFile, block);
8759
+ if (!fs21.existsSync(agentsFile)) {
8760
+ fs21.writeFileSync(agentsFile, block);
8425
8761
  return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
8426
8762
  }
8427
- const existing = fs20.readFileSync(agentsFile, "utf-8");
8763
+ const existing = fs21.readFileSync(agentsFile, "utf-8");
8428
8764
  if (existing.includes(marker)) {
8429
8765
  if (!force) {
8430
8766
  return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
8431
8767
  }
8432
8768
  const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
8433
- fs20.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
8769
+ fs21.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
8434
8770
  } else {
8435
- fs20.appendFileSync(agentsFile, "\n\n" + block);
8771
+ fs21.appendFileSync(agentsFile, "\n\n" + block);
8436
8772
  }
8437
8773
  return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
8438
8774
  }
@@ -8556,28 +8892,28 @@ function initCommand(program2) {
8556
8892
  "${DEFAULT_BRANCH}": defaultBranch
8557
8893
  };
8558
8894
  step(6, totalSteps, "Creating PRD directory structure...");
8559
- const prdDirPath = path20.join(cwd, prdDir);
8560
- const doneDirPath = path20.join(prdDirPath, "done");
8895
+ const prdDirPath = path21.join(cwd, prdDir);
8896
+ const doneDirPath = path21.join(prdDirPath, "done");
8561
8897
  ensureDir(doneDirPath);
8562
8898
  success(`Created ${prdDirPath}/`);
8563
8899
  success(`Created ${doneDirPath}/`);
8564
8900
  step(7, totalSteps, "Creating logs directory...");
8565
- const logsPath = path20.join(cwd, LOG_DIR);
8901
+ const logsPath = path21.join(cwd, LOG_DIR);
8566
8902
  ensureDir(logsPath);
8567
8903
  success(`Created ${logsPath}/`);
8568
8904
  addToGitignore(cwd);
8569
8905
  step(8, totalSteps, "Creating instructions directory...");
8570
- const instructionsDir = path20.join(cwd, "instructions");
8906
+ const instructionsDir = path21.join(cwd, "instructions");
8571
8907
  ensureDir(instructionsDir);
8572
8908
  success(`Created ${instructionsDir}/`);
8573
8909
  const existingConfig = loadConfig(cwd);
8574
- const customTemplatesDirPath = path20.join(cwd, existingConfig.templatesDir);
8575
- const customTemplatesDir = fs20.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
8910
+ const customTemplatesDirPath = path21.join(cwd, existingConfig.templatesDir);
8911
+ const customTemplatesDir = fs21.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
8576
8912
  const templateSources = [];
8577
8913
  const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
8578
8914
  const nwResult = processTemplate(
8579
8915
  "executor.md",
8580
- path20.join(instructionsDir, "executor.md"),
8916
+ path21.join(instructionsDir, "executor.md"),
8581
8917
  replacements,
8582
8918
  force,
8583
8919
  nwResolution.path,
@@ -8591,7 +8927,7 @@ function initCommand(program2) {
8591
8927
  );
8592
8928
  const peResult = processTemplate(
8593
8929
  "prd-executor.md",
8594
- path20.join(instructionsDir, "prd-executor.md"),
8930
+ path21.join(instructionsDir, "prd-executor.md"),
8595
8931
  replacements,
8596
8932
  force,
8597
8933
  peResolution.path,
@@ -8601,7 +8937,7 @@ function initCommand(program2) {
8601
8937
  const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
8602
8938
  const prResult = processTemplate(
8603
8939
  "pr-reviewer.md",
8604
- path20.join(instructionsDir, "pr-reviewer.md"),
8940
+ path21.join(instructionsDir, "pr-reviewer.md"),
8605
8941
  replacements,
8606
8942
  force,
8607
8943
  prResolution.path,
@@ -8611,7 +8947,7 @@ function initCommand(program2) {
8611
8947
  const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
8612
8948
  const qaResult = processTemplate(
8613
8949
  "qa.md",
8614
- path20.join(instructionsDir, "qa.md"),
8950
+ path21.join(instructionsDir, "qa.md"),
8615
8951
  replacements,
8616
8952
  force,
8617
8953
  qaResolution.path,
@@ -8621,7 +8957,7 @@ function initCommand(program2) {
8621
8957
  const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
8622
8958
  const auditResult = processTemplate(
8623
8959
  "audit.md",
8624
- path20.join(instructionsDir, "audit.md"),
8960
+ path21.join(instructionsDir, "audit.md"),
8625
8961
  replacements,
8626
8962
  force,
8627
8963
  auditResolution.path,
@@ -8635,7 +8971,7 @@ function initCommand(program2) {
8635
8971
  );
8636
8972
  const plannerResult = processTemplate(
8637
8973
  "prd-creator.md",
8638
- path20.join(instructionsDir, "prd-creator.md"),
8974
+ path21.join(instructionsDir, "prd-creator.md"),
8639
8975
  replacements,
8640
8976
  force,
8641
8977
  plannerResolution.path,
@@ -8643,8 +8979,8 @@ function initCommand(program2) {
8643
8979
  );
8644
8980
  templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
8645
8981
  step(9, totalSteps, "Creating configuration file...");
8646
- const configPath = path20.join(cwd, CONFIG_FILE_NAME);
8647
- if (fs20.existsSync(configPath) && !force) {
8982
+ const configPath = path21.join(cwd, CONFIG_FILE_NAME);
8983
+ if (fs21.existsSync(configPath) && !force) {
8648
8984
  console.log(` Skipped (exists): ${configPath}`);
8649
8985
  } else {
8650
8986
  const config = buildInitConfig({
@@ -8654,11 +8990,11 @@ function initCommand(program2) {
8654
8990
  reviewerEnabled,
8655
8991
  prdDir
8656
8992
  });
8657
- fs20.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
8993
+ fs21.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
8658
8994
  success(`Created ${configPath}`);
8659
8995
  }
8660
8996
  step(10, totalSteps, "Setting up GitHub Project board...");
8661
- const existingRaw = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
8997
+ const existingRaw = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
8662
8998
  const existingBoard = existingRaw.boardProvider;
8663
8999
  let boardSetupStatus = "Skipped";
8664
9000
  if (existingBoard?.projectNumber && !force) {
@@ -8680,13 +9016,13 @@ function initCommand(program2) {
8680
9016
  const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
8681
9017
  const boardTitle = `${projectName} Night Watch`;
8682
9018
  const board = await provider.setupBoard(boardTitle);
8683
- const rawConfig = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
9019
+ const rawConfig = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
8684
9020
  rawConfig.boardProvider = {
8685
9021
  enabled: true,
8686
9022
  provider: "github",
8687
9023
  projectNumber: board.number
8688
9024
  };
8689
- fs20.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
9025
+ fs21.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
8690
9026
  boardSetupStatus = `Created (#${board.number})`;
8691
9027
  success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
8692
9028
  } catch (boardErr) {
@@ -8869,8 +9205,8 @@ function getTelegramStatusWebhooks(config) {
8869
9205
  }
8870
9206
 
8871
9207
  // src/commands/run.ts
8872
- import * as fs21 from "fs";
8873
- import * as path21 from "path";
9208
+ import * as fs22 from "fs";
9209
+ import * as path22 from "path";
8874
9210
  function resolveRunNotificationEvent(exitCode, scriptStatus) {
8875
9211
  if (exitCode === 124) {
8876
9212
  return "run_timeout";
@@ -8902,12 +9238,12 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
8902
9238
  return scriptStatus === "skip_no_eligible_prd";
8903
9239
  }
8904
9240
  function getCrossProjectFallbackCandidates(currentProjectDir) {
8905
- const current = path21.resolve(currentProjectDir);
9241
+ const current = path22.resolve(currentProjectDir);
8906
9242
  const { valid, invalid } = validateRegistry();
8907
9243
  for (const entry of invalid) {
8908
9244
  warn(`Skipping invalid registry entry: ${entry.path}`);
8909
9245
  }
8910
- return valid.filter((entry) => path21.resolve(entry.path) !== current);
9246
+ return valid.filter((entry) => path22.resolve(entry.path) !== current);
8911
9247
  }
8912
9248
  async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
8913
9249
  if (isRateLimitFallbackTriggered(scriptResult?.data)) {
@@ -8917,7 +9253,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
8917
9253
  if (nonTelegramWebhooks.length > 0) {
8918
9254
  const _rateLimitCtx = {
8919
9255
  event: "rate_limit_fallback",
8920
- projectName: path21.basename(projectDir),
9256
+ projectName: path22.basename(projectDir),
8921
9257
  exitCode,
8922
9258
  provider: config.provider
8923
9259
  };
@@ -8949,7 +9285,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
8949
9285
  const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
8950
9286
  const _ctx = {
8951
9287
  event,
8952
- projectName: path21.basename(projectDir),
9288
+ projectName: path22.basename(projectDir),
8953
9289
  exitCode,
8954
9290
  provider: config.provider,
8955
9291
  prdName: scriptResult?.data.prd,
@@ -9111,20 +9447,20 @@ function applyCliOverrides(config, options) {
9111
9447
  return overridden;
9112
9448
  }
9113
9449
  function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
9114
- const absolutePrdDir = path21.join(projectDir, prdDir);
9115
- const doneDir = path21.join(absolutePrdDir, "done");
9450
+ const absolutePrdDir = path22.join(projectDir, prdDir);
9451
+ const doneDir = path22.join(absolutePrdDir, "done");
9116
9452
  const pending = [];
9117
9453
  const completed = [];
9118
- if (fs21.existsSync(absolutePrdDir)) {
9119
- const entries = fs21.readdirSync(absolutePrdDir, { withFileTypes: true });
9454
+ if (fs22.existsSync(absolutePrdDir)) {
9455
+ const entries = fs22.readdirSync(absolutePrdDir, { withFileTypes: true });
9120
9456
  for (const entry of entries) {
9121
9457
  if (entry.isFile() && entry.name.endsWith(".md")) {
9122
- const claimPath = path21.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
9458
+ const claimPath = path22.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
9123
9459
  let claimed = false;
9124
9460
  let claimInfo = null;
9125
- if (fs21.existsSync(claimPath)) {
9461
+ if (fs22.existsSync(claimPath)) {
9126
9462
  try {
9127
- const content = fs21.readFileSync(claimPath, "utf-8");
9463
+ const content = fs22.readFileSync(claimPath, "utf-8");
9128
9464
  const data = JSON.parse(content);
9129
9465
  const age = Math.floor(Date.now() / 1e3) - data.timestamp;
9130
9466
  if (age < maxRuntime) {
@@ -9138,8 +9474,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
9138
9474
  }
9139
9475
  }
9140
9476
  }
9141
- if (fs21.existsSync(doneDir)) {
9142
- const entries = fs21.readdirSync(doneDir, { withFileTypes: true });
9477
+ if (fs22.existsSync(doneDir)) {
9478
+ const entries = fs22.readdirSync(doneDir, { withFileTypes: true });
9143
9479
  for (const entry of entries) {
9144
9480
  if (entry.isFile() && entry.name.endsWith(".md")) {
9145
9481
  completed.push(entry.name);
@@ -9299,7 +9635,7 @@ ${stderr}`);
9299
9635
  // src/commands/review.ts
9300
9636
  init_dist();
9301
9637
  import { execFileSync as execFileSync5 } from "child_process";
9302
- import * as path22 from "path";
9638
+ import * as path23 from "path";
9303
9639
  function shouldSendReviewNotification(scriptStatus) {
9304
9640
  if (!scriptStatus) {
9305
9641
  return true;
@@ -9399,10 +9735,6 @@ function buildEnvVars2(config, options) {
9399
9735
  env.NW_BRANCH_PATTERNS = config.branchPatterns.join(",");
9400
9736
  env.NW_PRD_DIR = config.prdDir;
9401
9737
  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
9738
  return env;
9407
9739
  }
9408
9740
  function applyCliOverrides2(config, options) {
@@ -9416,9 +9748,6 @@ function applyCliOverrides2(config, options) {
9416
9748
  if (options.provider) {
9417
9749
  overridden._cliProviderOverride = options.provider;
9418
9750
  }
9419
- if (options.autoMerge !== void 0) {
9420
- overridden.autoMerge = options.autoMerge;
9421
- }
9422
9751
  return overridden;
9423
9752
  }
9424
9753
  function isFailingCheck(check) {
@@ -9479,7 +9808,7 @@ function getOpenPrsNeedingWork(branchPatterns) {
9479
9808
  }
9480
9809
  }
9481
9810
  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) => {
9811
+ 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
9812
  const projectDir = process.cwd();
9484
9813
  let config = loadConfig(projectDir);
9485
9814
  config = applyCliOverrides2(config, options);
@@ -9502,10 +9831,6 @@ function reviewCommand(program2) {
9502
9831
  ]);
9503
9832
  configTable.push(["Min Review Score", `${config.minReviewScore}/100`]);
9504
9833
  configTable.push(["Branch Patterns", config.branchPatterns.join(", ")]);
9505
- configTable.push([
9506
- "Auto-merge",
9507
- config.autoMerge ? `Enabled (${config.autoMergeMethod})` : "Disabled"
9508
- ]);
9509
9834
  configTable.push(["Max Retry Attempts", String(config.reviewerMaxRetries)]);
9510
9835
  configTable.push(["Retry Delay", `${config.reviewerRetryDelay}s`]);
9511
9836
  configTable.push([
@@ -9610,7 +9935,7 @@ ${stderr}`);
9610
9935
  const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
9611
9936
  await sendNotifications(config, {
9612
9937
  event: reviewEvent,
9613
- projectName: path22.basename(projectDir),
9938
+ projectName: path23.basename(projectDir),
9614
9939
  exitCode,
9615
9940
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9616
9941
  prUrl: fallbackPrDetails?.url,
@@ -9632,7 +9957,7 @@ ${stderr}`);
9632
9957
  const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
9633
9958
  await sendNotifications(config, {
9634
9959
  event: reviewEvent,
9635
- projectName: path22.basename(projectDir),
9960
+ projectName: path23.basename(projectDir),
9636
9961
  exitCode,
9637
9962
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9638
9963
  prUrl: prDetails?.url,
@@ -9654,7 +9979,7 @@ ${stderr}`);
9654
9979
  const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
9655
9980
  const _mergeCtx = {
9656
9981
  event: "pr_auto_merged",
9657
- projectName: path22.basename(projectDir),
9982
+ projectName: path23.basename(projectDir),
9658
9983
  exitCode,
9659
9984
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9660
9985
  prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
@@ -9679,7 +10004,7 @@ ${stderr}`);
9679
10004
 
9680
10005
  // src/commands/qa.ts
9681
10006
  init_dist();
9682
- import * as path23 from "path";
10007
+ import * as path24 from "path";
9683
10008
  function shouldSendQaNotification(scriptStatus) {
9684
10009
  if (!scriptStatus) {
9685
10010
  return true;
@@ -9820,7 +10145,7 @@ ${stderr}`);
9820
10145
  const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
9821
10146
  const _qaCtx = {
9822
10147
  event: "qa_completed",
9823
- projectName: path23.basename(projectDir),
10148
+ projectName: path24.basename(projectDir),
9824
10149
  exitCode,
9825
10150
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9826
10151
  prNumber: prDetails?.number ?? primaryQaPr,
@@ -9846,8 +10171,8 @@ ${stderr}`);
9846
10171
 
9847
10172
  // src/commands/audit.ts
9848
10173
  init_dist();
9849
- import * as fs22 from "fs";
9850
- import * as path24 from "path";
10174
+ import * as fs23 from "fs";
10175
+ import * as path25 from "path";
9851
10176
  function buildEnvVars4(config, options) {
9852
10177
  const env = buildBaseEnvVars(config, "audit", options.dryRun);
9853
10178
  env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
@@ -9890,7 +10215,8 @@ function auditCommand(program2) {
9890
10215
  configTable.push(["Provider", auditProvider]);
9891
10216
  configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
9892
10217
  configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
9893
- configTable.push(["Report File", path24.join(projectDir, "logs", "audit-report.md")]);
10218
+ configTable.push(["Target Column", config.audit.targetColumn]);
10219
+ configTable.push(["Report File", path25.join(projectDir, "logs", "audit-report.md")]);
9894
10220
  console.log(configTable.toString());
9895
10221
  header("Provider Invocation");
9896
10222
  const providerCmd = PROVIDER_COMMANDS[auditProvider];
@@ -9925,21 +10251,27 @@ ${stderr}`);
9925
10251
  } else if (scriptResult?.status?.startsWith("skip_")) {
9926
10252
  spinner.succeed("Code audit skipped");
9927
10253
  } else {
9928
- const reportPath = path24.join(projectDir, "logs", "audit-report.md");
9929
- if (!fs22.existsSync(reportPath)) {
10254
+ const reportPath = path25.join(projectDir, "logs", "audit-report.md");
10255
+ if (!fs23.existsSync(reportPath)) {
9930
10256
  spinner.fail("Code audit finished without a report file");
9931
10257
  process.exit(1);
9932
10258
  }
9933
- spinner.succeed(`Code audit complete \u2014 report written to ${reportPath}`);
10259
+ const syncResult = await syncAuditFindingsToBoard(config, projectDir);
10260
+ const message = `Code audit complete \u2014 report written to ${reportPath}; ${syncResult.summary}`;
10261
+ if (syncResult.status === "failed" || syncResult.status === "partial") {
10262
+ spinner.warn(message);
10263
+ } else {
10264
+ spinner.succeed(message);
10265
+ }
9934
10266
  }
9935
10267
  } else {
9936
10268
  const statusSuffix = scriptResult?.status ? ` (${scriptResult.status})` : "";
9937
10269
  const providerExit = scriptResult?.data?.provider_exit;
9938
10270
  const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
9939
10271
  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);
10272
+ const logPath = path25.join(projectDir, "logs", "audit.log");
10273
+ if (fs23.existsSync(logPath)) {
10274
+ const logLines = fs23.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
9943
10275
  if (logLines.length > 0) {
9944
10276
  process.stderr.write(logLines.join("\n") + "\n");
9945
10277
  }
@@ -10013,16 +10345,16 @@ function analyticsCommand(program2) {
10013
10345
  // src/commands/install.ts
10014
10346
  init_dist();
10015
10347
  import { execSync as execSync4 } from "child_process";
10016
- import * as path25 from "path";
10017
- import * as fs23 from "fs";
10348
+ import * as path26 from "path";
10349
+ import * as fs24 from "fs";
10018
10350
  function shellQuote(value) {
10019
10351
  return `'${value.replace(/'/g, `'"'"'`)}'`;
10020
10352
  }
10021
10353
  function getNightWatchBinPath() {
10022
10354
  try {
10023
10355
  const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
10024
- const binPath = path25.join(npmBin, "night-watch");
10025
- if (fs23.existsSync(binPath)) {
10356
+ const binPath = path26.join(npmBin, "night-watch");
10357
+ if (fs24.existsSync(binPath)) {
10026
10358
  return binPath;
10027
10359
  }
10028
10360
  } catch {
@@ -10035,17 +10367,17 @@ function getNightWatchBinPath() {
10035
10367
  }
10036
10368
  function getNodeBinDir() {
10037
10369
  if (process.execPath && process.execPath !== "node") {
10038
- return path25.dirname(process.execPath);
10370
+ return path26.dirname(process.execPath);
10039
10371
  }
10040
10372
  try {
10041
10373
  const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
10042
- return path25.dirname(nodePath);
10374
+ return path26.dirname(nodePath);
10043
10375
  } catch {
10044
10376
  return "";
10045
10377
  }
10046
10378
  }
10047
10379
  function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
10048
- const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path25.dirname(nightWatchBin) : "";
10380
+ const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path26.dirname(nightWatchBin) : "";
10049
10381
  const pathParts = Array.from(
10050
10382
  new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
10051
10383
  );
@@ -10061,12 +10393,12 @@ function performInstall(projectDir, config, options) {
10061
10393
  const nightWatchBin = getNightWatchBinPath();
10062
10394
  const projectName = getProjectName(projectDir);
10063
10395
  const marker = generateMarker(projectName);
10064
- const logDir = path25.join(projectDir, LOG_DIR);
10065
- if (!fs23.existsSync(logDir)) {
10066
- fs23.mkdirSync(logDir, { recursive: true });
10396
+ const logDir = path26.join(projectDir, LOG_DIR);
10397
+ if (!fs24.existsSync(logDir)) {
10398
+ fs24.mkdirSync(logDir, { recursive: true });
10067
10399
  }
10068
- const executorLog = path25.join(logDir, "executor.log");
10069
- const reviewerLog = path25.join(logDir, "reviewer.log");
10400
+ const executorLog = path26.join(logDir, "executor.log");
10401
+ const reviewerLog = path26.join(logDir, "reviewer.log");
10070
10402
  if (!options?.force) {
10071
10403
  const existingEntries2 = Array.from(
10072
10404
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
@@ -10103,7 +10435,7 @@ function performInstall(projectDir, config, options) {
10103
10435
  const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
10104
10436
  if (installSlicer) {
10105
10437
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
10106
- const slicerLog = path25.join(logDir, "slicer.log");
10438
+ const slicerLog = path26.join(logDir, "slicer.log");
10107
10439
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
10108
10440
  entries.push(slicerEntry);
10109
10441
  }
@@ -10111,7 +10443,7 @@ function performInstall(projectDir, config, options) {
10111
10443
  const installQa = disableQa ? false : config.qa.enabled;
10112
10444
  if (installQa) {
10113
10445
  const qaSchedule = config.qa.schedule;
10114
- const qaLog = path25.join(logDir, "qa.log");
10446
+ const qaLog = path26.join(logDir, "qa.log");
10115
10447
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
10116
10448
  entries.push(qaEntry);
10117
10449
  }
@@ -10119,7 +10451,7 @@ function performInstall(projectDir, config, options) {
10119
10451
  const installAudit = disableAudit ? false : config.audit.enabled;
10120
10452
  if (installAudit) {
10121
10453
  const auditSchedule = config.audit.schedule;
10122
- const auditLog = path25.join(logDir, "audit.log");
10454
+ const auditLog = path26.join(logDir, "audit.log");
10123
10455
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
10124
10456
  entries.push(auditEntry);
10125
10457
  }
@@ -10127,7 +10459,7 @@ function performInstall(projectDir, config, options) {
10127
10459
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
10128
10460
  if (installAnalytics) {
10129
10461
  const analyticsSchedule = config.analytics.schedule;
10130
- const analyticsLog = path25.join(logDir, "analytics.log");
10462
+ const analyticsLog = path26.join(logDir, "analytics.log");
10131
10463
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
10132
10464
  entries.push(analyticsEntry);
10133
10465
  }
@@ -10135,10 +10467,18 @@ function performInstall(projectDir, config, options) {
10135
10467
  const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
10136
10468
  if (installPrResolver) {
10137
10469
  const prResolverSchedule = config.prResolver.schedule;
10138
- const prResolverLog = path25.join(logDir, "pr-resolver.log");
10470
+ const prResolverLog = path26.join(logDir, "pr-resolver.log");
10139
10471
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
10140
10472
  entries.push(prResolverEntry);
10141
10473
  }
10474
+ const disableMerger = options?.noMerger === true || options?.merger === false;
10475
+ const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
10476
+ if (installMerger) {
10477
+ const mergerSchedule = config.merger.schedule;
10478
+ const mergerLog = path26.join(logDir, "merger.log");
10479
+ const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
10480
+ entries.push(mergerEntry);
10481
+ }
10142
10482
  const existingEntries = new Set(
10143
10483
  Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
10144
10484
  );
@@ -10157,7 +10497,7 @@ function performInstall(projectDir, config, options) {
10157
10497
  }
10158
10498
  }
10159
10499
  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) => {
10500
+ 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
10501
  try {
10162
10502
  const projectDir = process.cwd();
10163
10503
  const config = loadConfig(projectDir);
@@ -10166,12 +10506,12 @@ function installCommand(program2) {
10166
10506
  const nightWatchBin = getNightWatchBinPath();
10167
10507
  const projectName = getProjectName(projectDir);
10168
10508
  const marker = generateMarker(projectName);
10169
- const logDir = path25.join(projectDir, LOG_DIR);
10170
- if (!fs23.existsSync(logDir)) {
10171
- fs23.mkdirSync(logDir, { recursive: true });
10509
+ const logDir = path26.join(projectDir, LOG_DIR);
10510
+ if (!fs24.existsSync(logDir)) {
10511
+ fs24.mkdirSync(logDir, { recursive: true });
10172
10512
  }
10173
- const executorLog = path25.join(logDir, "executor.log");
10174
- const reviewerLog = path25.join(logDir, "reviewer.log");
10513
+ const executorLog = path26.join(logDir, "executor.log");
10514
+ const reviewerLog = path26.join(logDir, "reviewer.log");
10175
10515
  const existingEntries = Array.from(
10176
10516
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
10177
10517
  );
@@ -10207,7 +10547,7 @@ function installCommand(program2) {
10207
10547
  const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
10208
10548
  let slicerLog;
10209
10549
  if (installSlicer) {
10210
- slicerLog = path25.join(logDir, "slicer.log");
10550
+ slicerLog = path26.join(logDir, "slicer.log");
10211
10551
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
10212
10552
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
10213
10553
  entries.push(slicerEntry);
@@ -10216,7 +10556,7 @@ function installCommand(program2) {
10216
10556
  const installQa = disableQa ? false : config.qa.enabled;
10217
10557
  let qaLog;
10218
10558
  if (installQa) {
10219
- qaLog = path25.join(logDir, "qa.log");
10559
+ qaLog = path26.join(logDir, "qa.log");
10220
10560
  const qaSchedule = config.qa.schedule;
10221
10561
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
10222
10562
  entries.push(qaEntry);
@@ -10225,7 +10565,7 @@ function installCommand(program2) {
10225
10565
  const installAudit = disableAudit ? false : config.audit.enabled;
10226
10566
  let auditLog;
10227
10567
  if (installAudit) {
10228
- auditLog = path25.join(logDir, "audit.log");
10568
+ auditLog = path26.join(logDir, "audit.log");
10229
10569
  const auditSchedule = config.audit.schedule;
10230
10570
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
10231
10571
  entries.push(auditEntry);
@@ -10234,7 +10574,7 @@ function installCommand(program2) {
10234
10574
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
10235
10575
  let analyticsLog;
10236
10576
  if (installAnalytics) {
10237
- analyticsLog = path25.join(logDir, "analytics.log");
10577
+ analyticsLog = path26.join(logDir, "analytics.log");
10238
10578
  const analyticsSchedule = config.analytics.schedule;
10239
10579
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
10240
10580
  entries.push(analyticsEntry);
@@ -10243,11 +10583,20 @@ function installCommand(program2) {
10243
10583
  const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
10244
10584
  let prResolverLog;
10245
10585
  if (installPrResolver) {
10246
- prResolverLog = path25.join(logDir, "pr-resolver.log");
10586
+ prResolverLog = path26.join(logDir, "pr-resolver.log");
10247
10587
  const prResolverSchedule = config.prResolver.schedule;
10248
10588
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
10249
10589
  entries.push(prResolverEntry);
10250
10590
  }
10591
+ const disableMerger = options.noMerger === true || options.merger === false;
10592
+ const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
10593
+ let mergerLog;
10594
+ if (installMerger) {
10595
+ mergerLog = path26.join(logDir, "merger.log");
10596
+ const mergerSchedule = config.merger.schedule;
10597
+ const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
10598
+ entries.push(mergerEntry);
10599
+ }
10251
10600
  const existingEntrySet = new Set(existingEntries);
10252
10601
  const currentCrontab = readCrontab();
10253
10602
  const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
@@ -10280,6 +10629,9 @@ function installCommand(program2) {
10280
10629
  if (installPrResolver && prResolverLog) {
10281
10630
  dim(` PR Resolver: ${prResolverLog}`);
10282
10631
  }
10632
+ if (installMerger && mergerLog) {
10633
+ dim(` Merger: ${mergerLog}`);
10634
+ }
10283
10635
  console.log();
10284
10636
  dim("To uninstall, run: night-watch uninstall");
10285
10637
  dim("To check status, run: night-watch status");
@@ -10294,8 +10646,8 @@ function installCommand(program2) {
10294
10646
 
10295
10647
  // src/commands/uninstall.ts
10296
10648
  init_dist();
10297
- import * as path26 from "path";
10298
- import * as fs24 from "fs";
10649
+ import * as path27 from "path";
10650
+ import * as fs25 from "fs";
10299
10651
  function performUninstall(projectDir, options) {
10300
10652
  try {
10301
10653
  const projectName = getProjectName(projectDir);
@@ -10310,8 +10662,8 @@ function performUninstall(projectDir, options) {
10310
10662
  const removedCount = removeEntriesForProject(projectDir, marker);
10311
10663
  unregisterProject(projectDir);
10312
10664
  if (!options?.keepLogs) {
10313
- const logDir = path26.join(projectDir, "logs");
10314
- if (fs24.existsSync(logDir)) {
10665
+ const logDir = path27.join(projectDir, "logs");
10666
+ if (fs25.existsSync(logDir)) {
10315
10667
  const logFiles = [
10316
10668
  "executor.log",
10317
10669
  "reviewer.log",
@@ -10320,15 +10672,15 @@ function performUninstall(projectDir, options) {
10320
10672
  "pr-resolver.log"
10321
10673
  ];
10322
10674
  logFiles.forEach((logFile) => {
10323
- const logPath = path26.join(logDir, logFile);
10324
- if (fs24.existsSync(logPath)) {
10325
- fs24.unlinkSync(logPath);
10675
+ const logPath = path27.join(logDir, logFile);
10676
+ if (fs25.existsSync(logPath)) {
10677
+ fs25.unlinkSync(logPath);
10326
10678
  }
10327
10679
  });
10328
10680
  try {
10329
- const remainingFiles = fs24.readdirSync(logDir);
10681
+ const remainingFiles = fs25.readdirSync(logDir);
10330
10682
  if (remainingFiles.length === 0) {
10331
- fs24.rmdirSync(logDir);
10683
+ fs25.rmdirSync(logDir);
10332
10684
  }
10333
10685
  } catch {
10334
10686
  }
@@ -10361,8 +10713,8 @@ function uninstallCommand(program2) {
10361
10713
  existingEntries.forEach((entry) => dim(` ${entry}`));
10362
10714
  const removedCount = removeEntriesForProject(projectDir, marker);
10363
10715
  if (!options.keepLogs) {
10364
- const logDir = path26.join(projectDir, "logs");
10365
- if (fs24.existsSync(logDir)) {
10716
+ const logDir = path27.join(projectDir, "logs");
10717
+ if (fs25.existsSync(logDir)) {
10366
10718
  const logFiles = [
10367
10719
  "executor.log",
10368
10720
  "reviewer.log",
@@ -10372,16 +10724,16 @@ function uninstallCommand(program2) {
10372
10724
  ];
10373
10725
  let logsRemoved = 0;
10374
10726
  logFiles.forEach((logFile) => {
10375
- const logPath = path26.join(logDir, logFile);
10376
- if (fs24.existsSync(logPath)) {
10377
- fs24.unlinkSync(logPath);
10727
+ const logPath = path27.join(logDir, logFile);
10728
+ if (fs25.existsSync(logPath)) {
10729
+ fs25.unlinkSync(logPath);
10378
10730
  logsRemoved++;
10379
10731
  }
10380
10732
  });
10381
10733
  try {
10382
- const remainingFiles = fs24.readdirSync(logDir);
10734
+ const remainingFiles = fs25.readdirSync(logDir);
10383
10735
  if (remainingFiles.length === 0) {
10384
- fs24.rmdirSync(logDir);
10736
+ fs25.rmdirSync(logDir);
10385
10737
  }
10386
10738
  } catch {
10387
10739
  }
@@ -10425,11 +10777,15 @@ function statusCommand(program2) {
10425
10777
  const qaProc = snapshot.processes.find((p) => p.name === "qa");
10426
10778
  const auditProc = snapshot.processes.find((p) => p.name === "audit");
10427
10779
  const plannerProc = snapshot.processes.find((p) => p.name === "planner");
10780
+ const analyticsProc = snapshot.processes.find((p) => p.name === "analytics");
10781
+ const mergerProc = snapshot.processes.find((p) => p.name === "merger");
10428
10782
  const executorLog = snapshot.logs.find((l) => l.name === "executor");
10429
10783
  const reviewerLog = snapshot.logs.find((l) => l.name === "reviewer");
10430
10784
  const qaLog = snapshot.logs.find((l) => l.name === "qa");
10431
10785
  const auditLog = snapshot.logs.find((l) => l.name === "audit");
10432
10786
  const plannerLog = snapshot.logs.find((l) => l.name === "planner");
10787
+ const analyticsLog = snapshot.logs.find((l) => l.name === "analytics");
10788
+ const mergerLog = snapshot.logs.find((l) => l.name === "merger");
10433
10789
  const pendingPrds = snapshot.prds.filter(
10434
10790
  (p) => p.status === "ready" || p.status === "blocked"
10435
10791
  ).length;
@@ -10447,6 +10803,8 @@ function statusCommand(program2) {
10447
10803
  qa: { running: qaProc?.running ?? false, pid: qaProc?.pid ?? null },
10448
10804
  audit: { running: auditProc?.running ?? false, pid: auditProc?.pid ?? null },
10449
10805
  planner: { running: plannerProc?.running ?? false, pid: plannerProc?.pid ?? null },
10806
+ analytics: { running: analyticsProc?.running ?? false, pid: analyticsProc?.pid ?? null },
10807
+ merger: { running: mergerProc?.running ?? false, pid: mergerProc?.pid ?? null },
10450
10808
  prds: { pending: pendingPrds, claimed: claimedPrds, done: donePrds },
10451
10809
  prs: { open: snapshot.prs.length },
10452
10810
  crontab: snapshot.crontab,
@@ -10480,6 +10838,18 @@ function statusCommand(program2) {
10480
10838
  lastLines: plannerLog.lastLines,
10481
10839
  exists: plannerLog.exists,
10482
10840
  size: plannerLog.size
10841
+ } : void 0,
10842
+ analytics: analyticsLog ? {
10843
+ path: analyticsLog.path,
10844
+ lastLines: analyticsLog.lastLines,
10845
+ exists: analyticsLog.exists,
10846
+ size: analyticsLog.size
10847
+ } : void 0,
10848
+ merger: mergerLog ? {
10849
+ path: mergerLog.path,
10850
+ lastLines: mergerLog.lastLines,
10851
+ exists: mergerLog.exists,
10852
+ size: mergerLog.size
10483
10853
  } : void 0
10484
10854
  }
10485
10855
  };
@@ -10517,6 +10887,14 @@ function statusCommand(program2) {
10517
10887
  "Planner",
10518
10888
  formatRunningStatus(status.planner.running, status.planner.pid)
10519
10889
  ]);
10890
+ processTable.push([
10891
+ "Analytics",
10892
+ formatRunningStatus(status.analytics.running, status.analytics.pid)
10893
+ ]);
10894
+ processTable.push([
10895
+ "Merger",
10896
+ formatRunningStatus(status.merger.running, status.merger.pid)
10897
+ ]);
10520
10898
  console.log(processTable.toString());
10521
10899
  header("PRD Status");
10522
10900
  const prdTable = createTable({ head: ["Status", "Count"] });
@@ -10573,6 +10951,20 @@ function statusCommand(program2) {
10573
10951
  status.logs.planner.exists ? "Exists" : "Not found"
10574
10952
  ]);
10575
10953
  }
10954
+ if (status.logs.analytics) {
10955
+ logTable.push([
10956
+ "Analytics",
10957
+ status.logs.analytics.exists ? formatBytes(status.logs.analytics.size) : "-",
10958
+ status.logs.analytics.exists ? "Exists" : "Not found"
10959
+ ]);
10960
+ }
10961
+ if (status.logs.merger) {
10962
+ logTable.push([
10963
+ "Merger",
10964
+ status.logs.merger.exists ? formatBytes(status.logs.merger.size) : "-",
10965
+ status.logs.merger.exists ? "Exists" : "Not found"
10966
+ ]);
10967
+ }
10576
10968
  console.log(logTable.toString());
10577
10969
  if (options.verbose) {
10578
10970
  if (status.logs.executor?.exists && status.logs.executor.lastLines.length > 0) {
@@ -10595,6 +10987,14 @@ function statusCommand(program2) {
10595
10987
  dim(" Planner last 5 lines:");
10596
10988
  status.logs.planner.lastLines.forEach((line) => dim(` ${line}`));
10597
10989
  }
10990
+ if (status.logs.analytics?.exists && status.logs.analytics.lastLines.length > 0) {
10991
+ dim(" Analytics last 5 lines:");
10992
+ status.logs.analytics.lastLines.forEach((line) => dim(` ${line}`));
10993
+ }
10994
+ if (status.logs.merger?.exists && status.logs.merger.lastLines.length > 0) {
10995
+ dim(" Merger last 5 lines:");
10996
+ status.logs.merger.lastLines.forEach((line) => dim(` ${line}`));
10997
+ }
10598
10998
  }
10599
10999
  header("Commands");
10600
11000
  dim(" night-watch install - Install crontab entries");
@@ -10604,6 +11004,8 @@ function statusCommand(program2) {
10604
11004
  dim(" night-watch qa - Run QA now");
10605
11005
  dim(" night-watch audit - Run audit now");
10606
11006
  dim(" night-watch planner - Run planner now");
11007
+ dim(" night-watch analytics - Run analytics now");
11008
+ dim(" night-watch merge - Run merger now");
10607
11009
  console.log();
10608
11010
  } catch (error2) {
10609
11011
  console.error(
@@ -10617,14 +11019,14 @@ function statusCommand(program2) {
10617
11019
  // src/commands/logs.ts
10618
11020
  init_dist();
10619
11021
  import { spawn as spawn3 } from "child_process";
10620
- import * as path27 from "path";
10621
- import * as fs25 from "fs";
11022
+ import * as path28 from "path";
11023
+ import * as fs26 from "fs";
10622
11024
  function getLastLines(filePath, lineCount) {
10623
- if (!fs25.existsSync(filePath)) {
11025
+ if (!fs26.existsSync(filePath)) {
10624
11026
  return `Log file not found: ${filePath}`;
10625
11027
  }
10626
11028
  try {
10627
- const content = fs25.readFileSync(filePath, "utf-8");
11029
+ const content = fs26.readFileSync(filePath, "utf-8");
10628
11030
  const lines = content.trim().split("\n");
10629
11031
  return lines.slice(-lineCount).join("\n");
10630
11032
  } catch (error2) {
@@ -10632,7 +11034,7 @@ function getLastLines(filePath, lineCount) {
10632
11034
  }
10633
11035
  }
10634
11036
  function followLog(filePath) {
10635
- if (!fs25.existsSync(filePath)) {
11037
+ if (!fs26.existsSync(filePath)) {
10636
11038
  console.log(`Log file not found: ${filePath}`);
10637
11039
  console.log("The log file will be created when the first execution runs.");
10638
11040
  return;
@@ -10649,32 +11051,42 @@ function followLog(filePath) {
10649
11051
  });
10650
11052
  }
10651
11053
  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) => {
11054
+ 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(
11055
+ "-t, --type <type>",
11056
+ "Log type to view (executor|reviewer|qa|audit|planner|analytics|merger|all)",
11057
+ "all"
11058
+ ).action(async (options) => {
10653
11059
  try {
10654
11060
  const projectDir = process.cwd();
10655
- const logDir = path27.join(projectDir, LOG_DIR);
11061
+ const logDir = path28.join(projectDir, LOG_DIR);
10656
11062
  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`);
11063
+ const executorLog = path28.join(logDir, EXECUTOR_LOG_FILE);
11064
+ const reviewerLog = path28.join(logDir, REVIEWER_LOG_FILE);
11065
+ const qaLog = path28.join(logDir, `${QA_LOG_NAME}.log`);
11066
+ const auditLog = path28.join(logDir, `${AUDIT_LOG_NAME}.log`);
11067
+ const plannerLog = path28.join(logDir, `${PLANNER_LOG_NAME}.log`);
11068
+ const analyticsLog = path28.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
11069
+ const mergerLog = path28.join(logDir, `${MERGER_LOG_NAME}.log`);
10662
11070
  const logType = options.type?.toLowerCase() || "all";
10663
11071
  const showExecutor = logType === "all" || logType === "run" || logType === "executor";
10664
11072
  const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
10665
11073
  const showQa = logType === "all" || logType === "qa";
10666
11074
  const showAudit = logType === "all" || logType === "audit";
10667
11075
  const showPlanner = logType === "all" || logType === "planner" || logType === "slice" || logType === "slicer";
11076
+ const showAnalytics = logType === "all" || logType === "analytics";
11077
+ const showMerger = logType === "all" || logType === "merge" || logType === "merger";
10668
11078
  if (options.follow) {
10669
11079
  if (logType === "all") {
10670
11080
  dim("Note: Following all logs is not supported. Showing executor log.");
10671
- dim("Use --type reviewer|qa|audit|planner for other logs.\n");
11081
+ dim("Use --type reviewer|qa|audit|planner|analytics|merger for other logs.\n");
10672
11082
  }
10673
11083
  let targetLog = executorLog;
10674
11084
  if (showReviewer) targetLog = reviewerLog;
10675
11085
  else if (showQa) targetLog = qaLog;
10676
11086
  else if (showAudit) targetLog = auditLog;
10677
11087
  else if (showPlanner) targetLog = plannerLog;
11088
+ else if (showAnalytics) targetLog = analyticsLog;
11089
+ else if (showMerger) targetLog = mergerLog;
10678
11090
  followLog(targetLog);
10679
11091
  return;
10680
11092
  }
@@ -10709,10 +11121,24 @@ function logsCommand(program2) {
10709
11121
  console.log();
10710
11122
  console.log(getLastLines(plannerLog, lineCount));
10711
11123
  }
11124
+ if (showAnalytics) {
11125
+ header("Analytics Log");
11126
+ dim(`File: ${analyticsLog}`);
11127
+ console.log();
11128
+ console.log(getLastLines(analyticsLog, lineCount));
11129
+ }
11130
+ if (showMerger) {
11131
+ header("Merger Log");
11132
+ dim(`File: ${mergerLog}`);
11133
+ console.log();
11134
+ console.log(getLastLines(mergerLog, lineCount));
11135
+ }
10712
11136
  console.log();
10713
11137
  dim("---");
10714
11138
  dim("Tip: Use -f to follow logs in real-time");
10715
- dim(" Use --type executor|reviewer|qa|audit|planner to view specific logs");
11139
+ dim(
11140
+ " Use --type executor|reviewer|qa|audit|planner|analytics|merger to view specific logs"
11141
+ );
10716
11142
  } catch (err) {
10717
11143
  console.error(`Error reading logs: ${err instanceof Error ? err.message : String(err)}`);
10718
11144
  process.exit(1);
@@ -10723,8 +11149,8 @@ function logsCommand(program2) {
10723
11149
  // src/commands/prd.ts
10724
11150
  init_dist();
10725
11151
  import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
10726
- import * as fs26 from "fs";
10727
- import * as path28 from "path";
11152
+ import * as fs27 from "fs";
11153
+ import * as path29 from "path";
10728
11154
  import { fileURLToPath as fileURLToPath4 } from "url";
10729
11155
  import { dirname as dirname9 } from "path";
10730
11156
  var __filename3 = fileURLToPath4(import.meta.url);
@@ -10732,21 +11158,21 @@ var __dirname3 = dirname9(__filename3);
10732
11158
  function findTemplatesDir2(startDir) {
10733
11159
  let current = startDir;
10734
11160
  for (let i = 0; i < 8; i++) {
10735
- const candidate = path28.join(current, "templates");
10736
- if (fs26.existsSync(candidate) && fs26.statSync(candidate).isDirectory()) {
11161
+ const candidate = path29.join(current, "templates");
11162
+ if (fs27.existsSync(candidate) && fs27.statSync(candidate).isDirectory()) {
10737
11163
  return candidate;
10738
11164
  }
10739
- current = path28.dirname(current);
11165
+ current = path29.dirname(current);
10740
11166
  }
10741
- return path28.join(startDir, "templates");
11167
+ return path29.join(startDir, "templates");
10742
11168
  }
10743
11169
  var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
10744
11170
  function slugify2(name) {
10745
11171
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
10746
11172
  }
10747
11173
  function getNextPrdNumber2(prdDir) {
10748
- if (!fs26.existsSync(prdDir)) return 1;
10749
- const files = fs26.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
11174
+ if (!fs27.existsSync(prdDir)) return 1;
11175
+ const files = fs27.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
10750
11176
  const numbers = files.map((f) => {
10751
11177
  const match = f.match(/^(\d+)-/);
10752
11178
  return match ? parseInt(match[1], 10) : 0;
@@ -10827,13 +11253,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
10827
11253
  return null;
10828
11254
  }
10829
11255
  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("/")}`;
11256
+ return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path29.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
10831
11257
  } catch {
10832
11258
  return null;
10833
11259
  }
10834
11260
  }
10835
11261
  function buildGithubIssueBody(prdPath, projectDir, prdContent) {
10836
- const relPath = path28.relative(projectDir, prdPath);
11262
+ const relPath = path29.relative(projectDir, prdPath);
10837
11263
  const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
10838
11264
  const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
10839
11265
  return `${fileLine}
@@ -10844,13 +11270,13 @@ ${prdContent}
10844
11270
  Created via \`night-watch prd create\`.`;
10845
11271
  }
10846
11272
  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)) {
11273
+ const bundledTemplatePath = path29.join(TEMPLATES_DIR2, "prd-creator.md");
11274
+ const installedTemplatePath = path29.join(projectDir, "instructions", "prd-creator.md");
11275
+ const templatePath = fs27.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
11276
+ if (!fs27.existsSync(templatePath)) {
10851
11277
  return null;
10852
11278
  }
10853
- const planningPrinciples = fs26.readFileSync(templatePath, "utf-8");
11279
+ const planningPrinciples = fs27.readFileSync(templatePath, "utf-8");
10854
11280
  const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
10855
11281
  const modelId = model ?? CLAUDE_MODEL_IDS.opus;
10856
11282
  const env = buildNativeClaudeEnv(process.env);
@@ -10918,17 +11344,17 @@ function runGh(args, cwd) {
10918
11344
  return null;
10919
11345
  }
10920
11346
  function createGithubIssue(title, prdPath, projectDir, prdContent) {
10921
- const tmpFile = path28.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
11347
+ const tmpFile = path29.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
10922
11348
  try {
10923
11349
  const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
10924
- fs26.writeFileSync(tmpFile, body, "utf-8");
11350
+ fs27.writeFileSync(tmpFile, body, "utf-8");
10925
11351
  const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
10926
11352
  return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
10927
11353
  } catch {
10928
11354
  return null;
10929
11355
  } finally {
10930
11356
  try {
10931
- fs26.unlinkSync(tmpFile);
11357
+ fs27.unlinkSync(tmpFile);
10932
11358
  } catch {
10933
11359
  }
10934
11360
  }
@@ -10940,10 +11366,10 @@ function parseDependencies(content) {
10940
11366
  }
10941
11367
  function isClaimActive(claimPath, maxRuntime) {
10942
11368
  try {
10943
- if (!fs26.existsSync(claimPath)) {
11369
+ if (!fs27.existsSync(claimPath)) {
10944
11370
  return { active: false };
10945
11371
  }
10946
- const content = fs26.readFileSync(claimPath, "utf-8");
11372
+ const content = fs27.readFileSync(claimPath, "utf-8");
10947
11373
  const claim = JSON.parse(content);
10948
11374
  const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
10949
11375
  if (age < maxRuntime) {
@@ -10958,9 +11384,9 @@ function prdCommand(program2) {
10958
11384
  const prd = program2.command("prd").description("Manage PRD files");
10959
11385
  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
11386
  const projectDir = process.cwd();
10961
- const prdDir = path28.join(projectDir, resolvePrdCreateDir());
10962
- if (!fs26.existsSync(prdDir)) {
10963
- fs26.mkdirSync(prdDir, { recursive: true });
11387
+ const prdDir = path29.join(projectDir, resolvePrdCreateDir());
11388
+ if (!fs27.existsSync(prdDir)) {
11389
+ fs27.mkdirSync(prdDir, { recursive: true });
10964
11390
  }
10965
11391
  const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
10966
11392
  const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
@@ -10974,13 +11400,13 @@ function prdCommand(program2) {
10974
11400
  const prdTitle = extractPrdTitle(generated) ?? name;
10975
11401
  const slug = slugify2(prdTitle);
10976
11402
  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)) {
11403
+ const filePath = path29.join(prdDir, filename);
11404
+ if (fs27.existsSync(filePath)) {
10979
11405
  error(`File already exists: ${filePath}`);
10980
11406
  dim("Use a different name or remove the existing file.");
10981
11407
  process.exit(1);
10982
11408
  }
10983
- fs26.writeFileSync(filePath, generated, "utf-8");
11409
+ fs27.writeFileSync(filePath, generated, "utf-8");
10984
11410
  header("PRD Created");
10985
11411
  success(`Created: ${filePath}`);
10986
11412
  const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
@@ -10993,15 +11419,15 @@ function prdCommand(program2) {
10993
11419
  prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
10994
11420
  const projectDir = process.cwd();
10995
11421
  const config = loadConfig(projectDir);
10996
- const absolutePrdDir = path28.join(projectDir, config.prdDir);
10997
- const doneDir = path28.join(absolutePrdDir, "done");
11422
+ const absolutePrdDir = path29.join(projectDir, config.prdDir);
11423
+ const doneDir = path29.join(absolutePrdDir, "done");
10998
11424
  const pending = [];
10999
- if (fs26.existsSync(absolutePrdDir)) {
11000
- const files = fs26.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
11425
+ if (fs27.existsSync(absolutePrdDir)) {
11426
+ const files = fs27.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
11001
11427
  for (const file of files) {
11002
- const content = fs26.readFileSync(path28.join(absolutePrdDir, file), "utf-8");
11428
+ const content = fs27.readFileSync(path29.join(absolutePrdDir, file), "utf-8");
11003
11429
  const deps = parseDependencies(content);
11004
- const claimPath = path28.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
11430
+ const claimPath = path29.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
11005
11431
  const claimStatus = isClaimActive(claimPath, config.maxRuntime);
11006
11432
  pending.push({
11007
11433
  name: file,
@@ -11012,10 +11438,10 @@ function prdCommand(program2) {
11012
11438
  }
11013
11439
  }
11014
11440
  const done = [];
11015
- if (fs26.existsSync(doneDir)) {
11016
- const files = fs26.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
11441
+ if (fs27.existsSync(doneDir)) {
11442
+ const files = fs27.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
11017
11443
  for (const file of files) {
11018
- const content = fs26.readFileSync(path28.join(doneDir, file), "utf-8");
11444
+ const content = fs27.readFileSync(path29.join(doneDir, file), "utf-8");
11019
11445
  const deps = parseDependencies(content);
11020
11446
  done.push({ name: file, dependencies: deps });
11021
11447
  }
@@ -11052,7 +11478,7 @@ import blessed6 from "blessed";
11052
11478
  // src/commands/dashboard/tab-status.ts
11053
11479
  init_dist();
11054
11480
  import blessed from "blessed";
11055
- import * as fs27 from "fs";
11481
+ import * as fs28 from "fs";
11056
11482
  function sortPrdsByPriority(prds, priority) {
11057
11483
  if (priority.length === 0) return prds;
11058
11484
  const priorityMap = /* @__PURE__ */ new Map();
@@ -11148,7 +11574,7 @@ function renderLogPane(projectDir, logs) {
11148
11574
  let newestMtime = 0;
11149
11575
  for (const log of existingLogs) {
11150
11576
  try {
11151
- const stat = fs27.statSync(log.path);
11577
+ const stat = fs28.statSync(log.path);
11152
11578
  if (stat.mtimeMs > newestMtime) {
11153
11579
  newestMtime = stat.mtimeMs;
11154
11580
  newestLog = log;
@@ -12803,8 +13229,8 @@ function createActionsTab() {
12803
13229
  // src/commands/dashboard/tab-logs.ts
12804
13230
  init_dist();
12805
13231
  import blessed5 from "blessed";
12806
- import * as fs28 from "fs";
12807
- import * as path29 from "path";
13232
+ import * as fs29 from "fs";
13233
+ import * as path30 from "path";
12808
13234
  var LOG_NAMES = ["executor", "reviewer"];
12809
13235
  var LOG_LINES = 200;
12810
13236
  function createLogsTab() {
@@ -12845,7 +13271,7 @@ function createLogsTab() {
12845
13271
  let activeKeyHandlers = [];
12846
13272
  let activeCtx = null;
12847
13273
  function getLogPath(projectDir, logName) {
12848
- return path29.join(projectDir, "logs", `${logName}.log`);
13274
+ return path30.join(projectDir, "logs", `${logName}.log`);
12849
13275
  }
12850
13276
  function updateSelector() {
12851
13277
  const tabs = LOG_NAMES.map((name, idx) => {
@@ -12859,7 +13285,7 @@ function createLogsTab() {
12859
13285
  function loadLog(ctx) {
12860
13286
  const logName = LOG_NAMES[selectedLogIndex];
12861
13287
  const logPath = getLogPath(ctx.projectDir, logName);
12862
- if (!fs28.existsSync(logPath)) {
13288
+ if (!fs29.existsSync(logPath)) {
12863
13289
  logContent.setContent(
12864
13290
  `{yellow-fg}No ${logName}.log file found{/yellow-fg}
12865
13291
 
@@ -12869,7 +13295,7 @@ Log will appear here once the ${logName} runs.`
12869
13295
  return;
12870
13296
  }
12871
13297
  try {
12872
- const stat = fs28.statSync(logPath);
13298
+ const stat = fs29.statSync(logPath);
12873
13299
  const sizeKB = (stat.size / 1024).toFixed(1);
12874
13300
  logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
12875
13301
  } catch {
@@ -13367,12 +13793,12 @@ function doctorCommand(program2) {
13367
13793
 
13368
13794
  // src/commands/serve.ts
13369
13795
  init_dist();
13370
- import * as fs33 from "fs";
13796
+ import * as fs34 from "fs";
13371
13797
 
13372
13798
  // ../server/dist/index.js
13373
13799
  init_dist();
13374
- import * as fs32 from "fs";
13375
- import * as path35 from "path";
13800
+ import * as fs33 from "fs";
13801
+ import * as path36 from "path";
13376
13802
  import { dirname as dirname11 } from "path";
13377
13803
  import { fileURLToPath as fileURLToPath5 } from "url";
13378
13804
  import cors from "cors";
@@ -13457,8 +13883,8 @@ function setupGracefulShutdown(server, beforeClose) {
13457
13883
 
13458
13884
  // ../server/dist/middleware/project-resolver.middleware.js
13459
13885
  init_dist();
13460
- import * as fs29 from "fs";
13461
- import * as path30 from "path";
13886
+ import * as fs30 from "fs";
13887
+ import * as path31 from "path";
13462
13888
  function resolveProject(req, res, next) {
13463
13889
  const projectId = req.params.projectId;
13464
13890
  const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
@@ -13468,7 +13894,7 @@ function resolveProject(req, res, next) {
13468
13894
  res.status(404).json({ error: `Project not found: ${decodedId}` });
13469
13895
  return;
13470
13896
  }
13471
- if (!fs29.existsSync(entry.path) || !fs29.existsSync(path30.join(entry.path, CONFIG_FILE_NAME))) {
13897
+ if (!fs30.existsSync(entry.path) || !fs30.existsSync(path31.join(entry.path, CONFIG_FILE_NAME))) {
13472
13898
  res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
13473
13899
  return;
13474
13900
  }
@@ -13513,8 +13939,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
13513
13939
 
13514
13940
  // ../server/dist/routes/action.routes.js
13515
13941
  init_dist();
13516
- import * as fs30 from "fs";
13517
- import * as path31 from "path";
13942
+ import * as fs31 from "fs";
13943
+ import * as path32 from "path";
13518
13944
  import { execSync as execSync6, spawn as spawn6 } from "child_process";
13519
13945
  import { Router } from "express";
13520
13946
 
@@ -13552,17 +13978,17 @@ function getBoardProvider(config, projectDir) {
13552
13978
  function cleanOrphanedClaims(dir) {
13553
13979
  let entries;
13554
13980
  try {
13555
- entries = fs30.readdirSync(dir, { withFileTypes: true });
13981
+ entries = fs31.readdirSync(dir, { withFileTypes: true });
13556
13982
  } catch {
13557
13983
  return;
13558
13984
  }
13559
13985
  for (const entry of entries) {
13560
- const fullPath = path31.join(dir, entry.name);
13986
+ const fullPath = path32.join(dir, entry.name);
13561
13987
  if (entry.isDirectory() && entry.name !== "done") {
13562
13988
  cleanOrphanedClaims(fullPath);
13563
13989
  } else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
13564
13990
  try {
13565
- fs30.unlinkSync(fullPath);
13991
+ fs31.unlinkSync(fullPath);
13566
13992
  } catch {
13567
13993
  }
13568
13994
  }
@@ -13717,19 +14143,19 @@ function createActionRouteHandlers(ctx) {
13717
14143
  res.status(400).json({ error: "Invalid PRD name" });
13718
14144
  return;
13719
14145
  }
13720
- const prdDir = path31.join(projectDir, config.prdDir);
14146
+ const prdDir = path32.join(projectDir, config.prdDir);
13721
14147
  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)) {
14148
+ const pendingPath = path32.join(prdDir, normalized);
14149
+ const donePath = path32.join(prdDir, "done", normalized);
14150
+ if (fs31.existsSync(pendingPath)) {
13725
14151
  res.json({ message: `"${normalized}" is already pending` });
13726
14152
  return;
13727
14153
  }
13728
- if (!fs30.existsSync(donePath)) {
14154
+ if (!fs31.existsSync(donePath)) {
13729
14155
  res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
13730
14156
  return;
13731
14157
  }
13732
- fs30.renameSync(donePath, pendingPath);
14158
+ fs31.renameSync(donePath, pendingPath);
13733
14159
  res.json({ message: `Moved "${normalized}" back to pending` });
13734
14160
  } catch (error2) {
13735
14161
  res.status(500).json({
@@ -13747,11 +14173,11 @@ function createActionRouteHandlers(ctx) {
13747
14173
  res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
13748
14174
  return;
13749
14175
  }
13750
- if (fs30.existsSync(lockPath)) {
13751
- fs30.unlinkSync(lockPath);
14176
+ if (fs31.existsSync(lockPath)) {
14177
+ fs31.unlinkSync(lockPath);
13752
14178
  }
13753
- const prdDir = path31.join(projectDir, config.prdDir);
13754
- if (fs30.existsSync(prdDir)) {
14179
+ const prdDir = path32.join(projectDir, config.prdDir);
14180
+ if (fs31.existsSync(prdDir)) {
13755
14181
  cleanOrphanedClaims(prdDir);
13756
14182
  }
13757
14183
  broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
@@ -14306,6 +14732,9 @@ function validateConfigChanges(changes, currentConfig) {
14306
14732
  if (audit.maxRuntime !== void 0 && (typeof audit.maxRuntime !== "number" || audit.maxRuntime < 60)) {
14307
14733
  return "audit.maxRuntime must be a number >= 60";
14308
14734
  }
14735
+ if (audit.targetColumn !== void 0 && !BOARD_COLUMNS.includes(audit.targetColumn)) {
14736
+ return `audit.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
14737
+ }
14309
14738
  }
14310
14739
  if (changes.analytics !== void 0) {
14311
14740
  if (typeof changes.analytics !== "object" || changes.analytics === null) {
@@ -14326,9 +14755,8 @@ function validateConfigChanges(changes, currentConfig) {
14326
14755
  return "analytics.lookbackDays must be a number between 1 and 90";
14327
14756
  }
14328
14757
  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(", ")}`;
14758
+ if (!BOARD_COLUMNS.includes(analytics.targetColumn)) {
14759
+ return `analytics.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
14332
14760
  }
14333
14761
  }
14334
14762
  if (analytics.analysisPrompt !== void 0 && typeof analytics.analysisPrompt !== "string") {
@@ -14353,16 +14781,8 @@ function validateConfigChanges(changes, currentConfig) {
14353
14781
  if (typeof queue.priority !== "object" || queue.priority === null) {
14354
14782
  return "queue.priority must be an object";
14355
14783
  }
14356
- const validQueueJobs = [
14357
- "executor",
14358
- "reviewer",
14359
- "qa",
14360
- "audit",
14361
- "slicer",
14362
- "analytics"
14363
- ];
14364
14784
  for (const [jobType, value] of Object.entries(queue.priority)) {
14365
- if (!validQueueJobs.includes(jobType)) {
14785
+ if (!VALID_JOB_TYPES.includes(jobType)) {
14366
14786
  return `queue.priority contains invalid job type: ${jobType}`;
14367
14787
  }
14368
14788
  if (typeof value !== "number" || Number.isNaN(value)) {
@@ -14516,8 +14936,8 @@ function createProjectConfigRoutes() {
14516
14936
 
14517
14937
  // ../server/dist/routes/doctor.routes.js
14518
14938
  init_dist();
14519
- import * as fs31 from "fs";
14520
- import * as path32 from "path";
14939
+ import * as fs32 from "fs";
14940
+ import * as path33 from "path";
14521
14941
  import { execSync as execSync7 } from "child_process";
14522
14942
  import { Router as Router4 } from "express";
14523
14943
  function runDoctorChecks(projectDir, config) {
@@ -14550,7 +14970,7 @@ function runDoctorChecks(projectDir, config) {
14550
14970
  });
14551
14971
  }
14552
14972
  try {
14553
- const projectName = path32.basename(projectDir);
14973
+ const projectName = path33.basename(projectDir);
14554
14974
  const marker = generateMarker(projectName);
14555
14975
  const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
14556
14976
  if (crontabEntries.length > 0) {
@@ -14573,8 +14993,8 @@ function runDoctorChecks(projectDir, config) {
14573
14993
  detail: "Failed to check crontab"
14574
14994
  });
14575
14995
  }
14576
- const configPath = path32.join(projectDir, CONFIG_FILE_NAME);
14577
- if (fs31.existsSync(configPath)) {
14996
+ const configPath = path33.join(projectDir, CONFIG_FILE_NAME);
14997
+ if (fs32.existsSync(configPath)) {
14578
14998
  checks.push({ name: "config", status: "pass", detail: "Config file exists" });
14579
14999
  } else {
14580
15000
  checks.push({
@@ -14583,9 +15003,9 @@ function runDoctorChecks(projectDir, config) {
14583
15003
  detail: "Config file not found (using defaults)"
14584
15004
  });
14585
15005
  }
14586
- const prdDir = path32.join(projectDir, config.prdDir);
14587
- if (fs31.existsSync(prdDir)) {
14588
- const prds = fs31.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
15006
+ const prdDir = path33.join(projectDir, config.prdDir);
15007
+ if (fs32.existsSync(prdDir)) {
15008
+ const prds = fs32.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
14589
15009
  checks.push({
14590
15010
  name: "prdDir",
14591
15011
  status: "pass",
@@ -14628,7 +15048,7 @@ function createProjectDoctorRoutes() {
14628
15048
 
14629
15049
  // ../server/dist/routes/log.routes.js
14630
15050
  init_dist();
14631
- import * as path33 from "path";
15051
+ import * as path34 from "path";
14632
15052
  import { Router as Router5 } from "express";
14633
15053
  function createLogRoutes(deps) {
14634
15054
  const { projectDir } = deps;
@@ -14636,7 +15056,7 @@ function createLogRoutes(deps) {
14636
15056
  router.get("/:name", (req, res) => {
14637
15057
  try {
14638
15058
  const { name } = req.params;
14639
- const validNames = ["executor", "reviewer", "qa", "audit", "planner", "analytics"];
15059
+ const validNames = Object.keys(LOG_FILE_NAMES);
14640
15060
  if (!validNames.includes(name)) {
14641
15061
  res.status(400).json({
14642
15062
  error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
@@ -14647,7 +15067,7 @@ function createLogRoutes(deps) {
14647
15067
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
14648
15068
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
14649
15069
  const fileName = LOG_FILE_NAMES[name] || name;
14650
- const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
15070
+ const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
14651
15071
  const logLines = getLastLogLines(logPath, linesToRead);
14652
15072
  res.json({ name, lines: logLines });
14653
15073
  } catch (error2) {
@@ -14662,7 +15082,7 @@ function createProjectLogRoutes() {
14662
15082
  try {
14663
15083
  const projectDir = req.projectDir;
14664
15084
  const { name } = req.params;
14665
- const validNames = ["executor", "reviewer", "qa", "audit", "planner", "analytics"];
15085
+ const validNames = Object.keys(LOG_FILE_NAMES);
14666
15086
  if (!validNames.includes(name)) {
14667
15087
  res.status(400).json({
14668
15088
  error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
@@ -14673,7 +15093,7 @@ function createProjectLogRoutes() {
14673
15093
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
14674
15094
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
14675
15095
  const fileName = LOG_FILE_NAMES[name] || name;
14676
- const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
15096
+ const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
14677
15097
  const logLines = getLastLogLines(logPath, linesToRead);
14678
15098
  res.json({ name, lines: logLines });
14679
15099
  } catch (error2) {
@@ -14708,7 +15128,7 @@ function createProjectPrdRoutes() {
14708
15128
 
14709
15129
  // ../server/dist/routes/roadmap.routes.js
14710
15130
  init_dist();
14711
- import * as path34 from "path";
15131
+ import * as path35 from "path";
14712
15132
  import { Router as Router7 } from "express";
14713
15133
  function createRoadmapRouteHandlers(ctx) {
14714
15134
  const router = Router7({ mergeParams: true });
@@ -14718,7 +15138,7 @@ function createRoadmapRouteHandlers(ctx) {
14718
15138
  const config = ctx.getConfig(req);
14719
15139
  const projectDir = ctx.getProjectDir(req);
14720
15140
  const status = getRoadmapStatus(projectDir, config);
14721
- const prdDir = path34.join(projectDir, config.prdDir);
15141
+ const prdDir = path35.join(projectDir, config.prdDir);
14722
15142
  const state = loadRoadmapState(prdDir);
14723
15143
  res.json({
14724
15144
  ...status,
@@ -14843,12 +15263,16 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
14843
15263
  const auditPlan = getSchedulingPlan(projectDir, config, "audit");
14844
15264
  const plannerPlan = getSchedulingPlan(projectDir, config, "slicer");
14845
15265
  const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
15266
+ const prResolverPlan = getSchedulingPlan(projectDir, config, "pr-resolver");
15267
+ const mergerPlan = getSchedulingPlan(projectDir, config, "merger");
14846
15268
  const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
14847
15269
  const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
14848
15270
  const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
14849
15271
  const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
14850
15272
  const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
14851
15273
  const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
15274
+ const prResolverInstalled = installed && (config.prResolver?.enabled ?? true) && hasScheduledCommand(entries, "resolve");
15275
+ const mergerInstalled = installed && (config.merger?.enabled ?? false) && hasScheduledCommand(entries, "merge");
14852
15276
  return {
14853
15277
  executor: {
14854
15278
  schedule: config.cronSchedule,
@@ -14898,6 +15322,22 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
14898
15322
  manualDelayMinutes: analyticsPlan.manualDelayMinutes,
14899
15323
  balancedDelayMinutes: analyticsPlan.balancedDelayMinutes
14900
15324
  },
15325
+ prResolver: {
15326
+ schedule: config.prResolver?.schedule ?? "15 6,14,22 * * *",
15327
+ installed: prResolverInstalled,
15328
+ nextRun: prResolverInstalled ? addDelayToIsoString(computeNextRun(config.prResolver?.schedule ?? "15 6,14,22 * * *"), prResolverPlan.totalDelayMinutes) : null,
15329
+ delayMinutes: prResolverPlan.totalDelayMinutes,
15330
+ manualDelayMinutes: prResolverPlan.manualDelayMinutes,
15331
+ balancedDelayMinutes: prResolverPlan.balancedDelayMinutes
15332
+ },
15333
+ merger: {
15334
+ schedule: config.merger?.schedule ?? "55 */4 * * *",
15335
+ installed: mergerInstalled,
15336
+ nextRun: mergerInstalled ? addDelayToIsoString(computeNextRun(config.merger?.schedule ?? "55 */4 * * *"), mergerPlan.totalDelayMinutes) : null,
15337
+ delayMinutes: mergerPlan.totalDelayMinutes,
15338
+ manualDelayMinutes: mergerPlan.manualDelayMinutes,
15339
+ balancedDelayMinutes: mergerPlan.balancedDelayMinutes
15340
+ },
14901
15341
  paused: !installed,
14902
15342
  schedulingPriority: config.schedulingPriority,
14903
15343
  entries
@@ -15052,14 +15492,14 @@ function createQueueRoutes(deps) {
15052
15492
  var __filename4 = fileURLToPath5(import.meta.url);
15053
15493
  var __dirname4 = dirname11(__filename4);
15054
15494
  function resolveWebDistPath() {
15055
- const bundled = path35.join(__dirname4, "web");
15056
- if (fs32.existsSync(path35.join(bundled, "index.html")))
15495
+ const bundled = path36.join(__dirname4, "web");
15496
+ if (fs33.existsSync(path36.join(bundled, "index.html")))
15057
15497
  return bundled;
15058
15498
  let d = __dirname4;
15059
15499
  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")))
15500
+ if (fs33.existsSync(path36.join(d, "turbo.json"))) {
15501
+ const dev = path36.join(d, "web/dist");
15502
+ if (fs33.existsSync(path36.join(dev, "index.html")))
15063
15503
  return dev;
15064
15504
  break;
15065
15505
  }
@@ -15069,7 +15509,7 @@ function resolveWebDistPath() {
15069
15509
  }
15070
15510
  function setupStaticFiles(app) {
15071
15511
  const webDistPath = resolveWebDistPath();
15072
- if (fs32.existsSync(webDistPath)) {
15512
+ if (fs33.existsSync(webDistPath)) {
15073
15513
  app.use(express.static(webDistPath));
15074
15514
  }
15075
15515
  app.use((req, res, next) => {
@@ -15077,8 +15517,8 @@ function setupStaticFiles(app) {
15077
15517
  next();
15078
15518
  return;
15079
15519
  }
15080
- const indexPath = path35.resolve(webDistPath, "index.html");
15081
- if (fs32.existsSync(indexPath)) {
15520
+ const indexPath = path36.resolve(webDistPath, "index.html");
15521
+ if (fs33.existsSync(indexPath)) {
15082
15522
  res.sendFile(indexPath, (err) => {
15083
15523
  if (err)
15084
15524
  next();
@@ -15211,7 +15651,7 @@ function createGlobalApp() {
15211
15651
  return app;
15212
15652
  }
15213
15653
  function bootContainer() {
15214
- initContainer(path35.dirname(getDbPath()));
15654
+ initContainer(path36.dirname(getDbPath()));
15215
15655
  }
15216
15656
  function startServer(projectDir, port) {
15217
15657
  bootContainer();
@@ -15264,8 +15704,8 @@ function isProcessRunning2(pid) {
15264
15704
  }
15265
15705
  function readPid(lockPath) {
15266
15706
  try {
15267
- if (!fs33.existsSync(lockPath)) return null;
15268
- const raw = fs33.readFileSync(lockPath, "utf-8").trim();
15707
+ if (!fs34.existsSync(lockPath)) return null;
15708
+ const raw = fs34.readFileSync(lockPath, "utf-8").trim();
15269
15709
  const pid = parseInt(raw, 10);
15270
15710
  return Number.isFinite(pid) ? pid : null;
15271
15711
  } catch {
@@ -15277,10 +15717,10 @@ function acquireServeLock(mode, port) {
15277
15717
  let stalePidCleaned;
15278
15718
  for (let attempt = 0; attempt < 2; attempt++) {
15279
15719
  try {
15280
- const fd = fs33.openSync(lockPath, "wx");
15281
- fs33.writeFileSync(fd, `${process.pid}
15720
+ const fd = fs34.openSync(lockPath, "wx");
15721
+ fs34.writeFileSync(fd, `${process.pid}
15282
15722
  `);
15283
- fs33.closeSync(fd);
15723
+ fs34.closeSync(fd);
15284
15724
  return { acquired: true, lockPath, stalePidCleaned };
15285
15725
  } catch (error2) {
15286
15726
  const err = error2;
@@ -15301,7 +15741,7 @@ function acquireServeLock(mode, port) {
15301
15741
  };
15302
15742
  }
15303
15743
  try {
15304
- fs33.unlinkSync(lockPath);
15744
+ fs34.unlinkSync(lockPath);
15305
15745
  if (existingPid) {
15306
15746
  stalePidCleaned = existingPid;
15307
15747
  }
@@ -15324,10 +15764,10 @@ function acquireServeLock(mode, port) {
15324
15764
  }
15325
15765
  function releaseServeLock(lockPath) {
15326
15766
  try {
15327
- if (!fs33.existsSync(lockPath)) return;
15767
+ if (!fs34.existsSync(lockPath)) return;
15328
15768
  const lockPid = readPid(lockPath);
15329
15769
  if (lockPid !== null && lockPid !== process.pid) return;
15330
- fs33.unlinkSync(lockPath);
15770
+ fs34.unlinkSync(lockPath);
15331
15771
  } catch {
15332
15772
  }
15333
15773
  }
@@ -15423,14 +15863,14 @@ function historyCommand(program2) {
15423
15863
  // src/commands/update.ts
15424
15864
  init_dist();
15425
15865
  import { spawnSync as spawnSync2 } from "child_process";
15426
- import * as fs34 from "fs";
15427
- import * as path36 from "path";
15866
+ import * as fs35 from "fs";
15867
+ import * as path37 from "path";
15428
15868
  var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
15429
15869
  function parseProjectDirs(projects, cwd) {
15430
15870
  if (!projects || projects.trim().length === 0) {
15431
15871
  return [cwd];
15432
15872
  }
15433
- const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path36.resolve(cwd, entry));
15873
+ const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path37.resolve(cwd, entry));
15434
15874
  return Array.from(new Set(dirs));
15435
15875
  }
15436
15876
  function shouldInstallGlobal(options) {
@@ -15472,7 +15912,7 @@ function updateCommand(program2) {
15472
15912
  }
15473
15913
  const nightWatchBin = resolveNightWatchBin();
15474
15914
  for (const projectDir of projectDirs) {
15475
- if (!fs34.existsSync(projectDir) || !fs34.statSync(projectDir).isDirectory()) {
15915
+ if (!fs35.existsSync(projectDir) || !fs35.statSync(projectDir).isDirectory()) {
15476
15916
  warn(`Skipping invalid project directory: ${projectDir}`);
15477
15917
  continue;
15478
15918
  }
@@ -15516,8 +15956,8 @@ function prdStateCommand(program2) {
15516
15956
 
15517
15957
  // src/commands/retry.ts
15518
15958
  init_dist();
15519
- import * as fs35 from "fs";
15520
- import * as path37 from "path";
15959
+ import * as fs36 from "fs";
15960
+ import * as path38 from "path";
15521
15961
  function normalizePrdName(name) {
15522
15962
  if (!name.endsWith(".md")) {
15523
15963
  return `${name}.md`;
@@ -15525,26 +15965,26 @@ function normalizePrdName(name) {
15525
15965
  return name;
15526
15966
  }
15527
15967
  function getDonePrds(doneDir) {
15528
- if (!fs35.existsSync(doneDir)) {
15968
+ if (!fs36.existsSync(doneDir)) {
15529
15969
  return [];
15530
15970
  }
15531
- return fs35.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
15971
+ return fs36.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
15532
15972
  }
15533
15973
  function retryCommand(program2) {
15534
15974
  program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
15535
15975
  const projectDir = process.cwd();
15536
15976
  const config = loadConfig(projectDir);
15537
- const prdDir = path37.join(projectDir, config.prdDir);
15538
- const doneDir = path37.join(prdDir, "done");
15977
+ const prdDir = path38.join(projectDir, config.prdDir);
15978
+ const doneDir = path38.join(prdDir, "done");
15539
15979
  const normalizedPrdName = normalizePrdName(prdName);
15540
- const pendingPath = path37.join(prdDir, normalizedPrdName);
15541
- if (fs35.existsSync(pendingPath)) {
15980
+ const pendingPath = path38.join(prdDir, normalizedPrdName);
15981
+ if (fs36.existsSync(pendingPath)) {
15542
15982
  info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
15543
15983
  return;
15544
15984
  }
15545
- const donePath = path37.join(doneDir, normalizedPrdName);
15546
- if (fs35.existsSync(donePath)) {
15547
- fs35.renameSync(donePath, pendingPath);
15985
+ const donePath = path38.join(doneDir, normalizedPrdName);
15986
+ if (fs36.existsSync(donePath)) {
15987
+ fs36.renameSync(donePath, pendingPath);
15548
15988
  success(`Moved "${normalizedPrdName}" back to pending.`);
15549
15989
  dim(`From: ${donePath}`);
15550
15990
  dim(`To: ${pendingPath}`);
@@ -15796,7 +16236,7 @@ function prdsCommand(program2) {
15796
16236
 
15797
16237
  // src/commands/cancel.ts
15798
16238
  init_dist();
15799
- import * as fs36 from "fs";
16239
+ import * as fs37 from "fs";
15800
16240
  import * as readline2 from "readline";
15801
16241
  function getLockFilePaths2(projectDir) {
15802
16242
  const runtimeKey = projectRuntimeKey(projectDir);
@@ -15843,7 +16283,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
15843
16283
  const pid = lockStatus.pid;
15844
16284
  if (!lockStatus.running) {
15845
16285
  try {
15846
- fs36.unlinkSync(lockPath);
16286
+ fs37.unlinkSync(lockPath);
15847
16287
  return {
15848
16288
  success: true,
15849
16289
  message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
@@ -15881,7 +16321,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
15881
16321
  await sleep2(3e3);
15882
16322
  if (!isProcessRunning3(pid)) {
15883
16323
  try {
15884
- fs36.unlinkSync(lockPath);
16324
+ fs37.unlinkSync(lockPath);
15885
16325
  } catch {
15886
16326
  }
15887
16327
  return {
@@ -15916,7 +16356,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
15916
16356
  await sleep2(500);
15917
16357
  if (!isProcessRunning3(pid)) {
15918
16358
  try {
15919
- fs36.unlinkSync(lockPath);
16359
+ fs37.unlinkSync(lockPath);
15920
16360
  } catch {
15921
16361
  }
15922
16362
  return {
@@ -15977,67 +16417,69 @@ function cancelCommand(program2) {
15977
16417
 
15978
16418
  // src/commands/slice.ts
15979
16419
  init_dist();
15980
- import * as fs37 from "fs";
15981
- import * as path38 from "path";
16420
+ import * as fs38 from "fs";
16421
+ import * as path39 from "path";
15982
16422
  function plannerLockPath2(projectDir) {
15983
16423
  return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
15984
16424
  }
15985
16425
  function acquirePlannerLock(projectDir) {
15986
16426
  const lockFile = plannerLockPath2(projectDir);
15987
- if (fs37.existsSync(lockFile)) {
15988
- const pidRaw = fs37.readFileSync(lockFile, "utf-8").trim();
16427
+ if (fs38.existsSync(lockFile)) {
16428
+ const pidRaw = fs38.readFileSync(lockFile, "utf-8").trim();
15989
16429
  const pid = parseInt(pidRaw, 10);
15990
16430
  if (!Number.isNaN(pid) && isProcessRunning(pid)) {
15991
16431
  return { acquired: false, lockFile, pid };
15992
16432
  }
15993
16433
  try {
15994
- fs37.unlinkSync(lockFile);
16434
+ fs38.unlinkSync(lockFile);
15995
16435
  } catch {
15996
16436
  }
15997
16437
  }
15998
- fs37.writeFileSync(lockFile, String(process.pid));
16438
+ fs38.writeFileSync(lockFile, String(process.pid));
15999
16439
  return { acquired: true, lockFile };
16000
16440
  }
16001
16441
  function releasePlannerLock(lockFile) {
16002
16442
  try {
16003
- if (fs37.existsSync(lockFile)) {
16004
- fs37.unlinkSync(lockFile);
16443
+ if (fs38.existsSync(lockFile)) {
16444
+ fs38.unlinkSync(lockFile);
16005
16445
  }
16006
16446
  } catch {
16007
16447
  }
16008
16448
  }
16009
16449
  function resolvePlannerIssueColumn(config) {
16010
- return config.roadmapScanner.issueColumn === "Ready" ? "Ready" : "Draft";
16450
+ return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
16011
16451
  }
16012
16452
  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 ?? "");
16453
+ const relativePrdPath = path39.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
16454
+ const absolutePrdPath = path39.join(projectDir, config.prdDir, result.file ?? "");
16015
16455
  const sourceItem = result.item;
16016
16456
  let prdContent;
16017
16457
  try {
16018
- prdContent = fs37.readFileSync(absolutePrdPath, "utf-8");
16458
+ prdContent = fs38.readFileSync(absolutePrdPath, "utf-8");
16019
16459
  } catch {
16020
16460
  prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
16021
16461
  }
16022
16462
  const maxBodyChars = 6e4;
16023
16463
  const truncated = prdContent.length > maxBodyChars;
16024
- const prdPreview = truncated ? `${prdContent.slice(0, maxBodyChars)}
16464
+ const prdBody = truncated ? `${prdContent.slice(0, maxBodyChars)}
16025
16465
 
16026
16466
  ...[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) : [];
16467
+ const metaLines = [`- PRD file: \`${relativePrdPath}\``];
16468
+ if (sourceItem) {
16469
+ metaLines.push(`- Source section: ${sourceItem.section}`);
16470
+ if (sourceItem.description) {
16471
+ metaLines.push(`- Source summary: ${sourceItem.description}`);
16472
+ }
16473
+ }
16032
16474
  return [
16033
- "## Planner Generated PRD",
16475
+ prdBody,
16034
16476
  "",
16035
- `- PRD file: \`${relativePrdPath}\``,
16036
- ...sourceLines,
16477
+ "<details>",
16478
+ "<summary>Source metadata</summary>",
16037
16479
  "",
16038
- "---",
16480
+ ...metaLines,
16039
16481
  "",
16040
- prdPreview
16482
+ "</details>"
16041
16483
  ].join("\n");
16042
16484
  }
16043
16485
  async function createPlannerIssue(projectDir, config, result) {
@@ -16052,9 +16494,11 @@ async function createPlannerIssue(projectDir, config, result) {
16052
16494
  if (!board) {
16053
16495
  return { created: false, skippedReason: "board-not-configured" };
16054
16496
  }
16497
+ const issueTitle = `PRD: ${result.item.title}`;
16498
+ const normalizeTitle = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
16055
16499
  const existingIssues = await provider.getAllIssues();
16056
16500
  const existing = existingIssues.find(
16057
- (issue2) => issue2.title.trim().toLowerCase() === result.item.title.trim().toLowerCase()
16501
+ (issue2) => normalizeTitle(issue2.title) === normalizeTitle(result.item.title)
16058
16502
  );
16059
16503
  if (existing) {
16060
16504
  return {
@@ -16065,7 +16509,7 @@ async function createPlannerIssue(projectDir, config, result) {
16065
16509
  };
16066
16510
  }
16067
16511
  const issue = await provider.createIssue({
16068
- title: result.item.title,
16512
+ title: issueTitle,
16069
16513
  body: buildPlannerIssueBody(projectDir, config, result),
16070
16514
  column: resolvePlannerIssueColumn(config)
16071
16515
  });
@@ -16218,7 +16662,7 @@ function sliceCommand(program2) {
16218
16662
  if (!options.dryRun && result.sliced) {
16219
16663
  await sendNotifications(config, {
16220
16664
  event: "run_succeeded",
16221
- projectName: path38.basename(projectDir),
16665
+ projectName: path39.basename(projectDir),
16222
16666
  exitCode,
16223
16667
  provider: config.provider,
16224
16668
  prTitle: result.item?.title
@@ -16226,7 +16670,7 @@ function sliceCommand(program2) {
16226
16670
  } else if (!options.dryRun && !nothingPending) {
16227
16671
  await sendNotifications(config, {
16228
16672
  event: "run_failed",
16229
- projectName: path38.basename(projectDir),
16673
+ projectName: path39.basename(projectDir),
16230
16674
  exitCode,
16231
16675
  provider: config.provider
16232
16676
  });
@@ -16243,20 +16687,20 @@ function sliceCommand(program2) {
16243
16687
  // src/commands/state.ts
16244
16688
  init_dist();
16245
16689
  import * as os9 from "os";
16246
- import * as path39 from "path";
16690
+ import * as path40 from "path";
16247
16691
  import chalk5 from "chalk";
16248
16692
  import { Command } from "commander";
16249
16693
  function createStateCommand() {
16250
16694
  const state = new Command("state");
16251
16695
  state.description("Manage Night Watch state");
16252
16696
  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);
16697
+ const nightWatchHome = process.env.NIGHT_WATCH_HOME || path40.join(os9.homedir(), GLOBAL_CONFIG_DIR);
16254
16698
  if (opts.dryRun) {
16255
16699
  console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
16256
16700
  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")}`);
16701
+ console.log(` ${path40.join(nightWatchHome, "projects.json")}`);
16702
+ console.log(` ${path40.join(nightWatchHome, "history.json")}`);
16703
+ console.log(` ${path40.join(nightWatchHome, "prd-states.json")}`);
16260
16704
  console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
16261
16705
  console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
16262
16706
  return;
@@ -16294,8 +16738,8 @@ function createStateCommand() {
16294
16738
  init_dist();
16295
16739
  init_dist();
16296
16740
  import { execFileSync as execFileSync6 } from "child_process";
16297
- import * as fs38 from "fs";
16298
- import * as path40 from "path";
16741
+ import * as fs39 from "fs";
16742
+ import * as path41 from "path";
16299
16743
  import * as readline3 from "readline";
16300
16744
  import chalk6 from "chalk";
16301
16745
  async function run(fn) {
@@ -16317,7 +16761,7 @@ function getProvider(config, cwd) {
16317
16761
  return createBoardProvider(bp, cwd);
16318
16762
  }
16319
16763
  function defaultBoardTitle(cwd) {
16320
- return `${path40.basename(cwd)} Night Watch`;
16764
+ return `${path41.basename(cwd)} Night Watch`;
16321
16765
  }
16322
16766
  async function ensureBoardConfigured(config, cwd, provider, options) {
16323
16767
  if (config.boardProvider?.projectNumber) {
@@ -16516,11 +16960,11 @@ function boardCommand(program2) {
16516
16960
  let body = options.body ?? "";
16517
16961
  if (options.bodyFile) {
16518
16962
  const filePath = options.bodyFile;
16519
- if (!fs38.existsSync(filePath)) {
16963
+ if (!fs39.existsSync(filePath)) {
16520
16964
  console.error(`File not found: ${filePath}`);
16521
16965
  process.exit(1);
16522
16966
  }
16523
- body = fs38.readFileSync(filePath, "utf-8");
16967
+ body = fs39.readFileSync(filePath, "utf-8");
16524
16968
  }
16525
16969
  const labels = [];
16526
16970
  if (options.label) {
@@ -16761,12 +17205,12 @@ function boardCommand(program2) {
16761
17205
  const config = loadConfig(cwd);
16762
17206
  const provider = getProvider(config, cwd);
16763
17207
  await ensureBoardConfigured(config, cwd, provider);
16764
- const roadmapPath = options.roadmap ?? path40.join(cwd, "ROADMAP.md");
16765
- if (!fs38.existsSync(roadmapPath)) {
17208
+ const roadmapPath = options.roadmap ?? path41.join(cwd, "ROADMAP.md");
17209
+ if (!fs39.existsSync(roadmapPath)) {
16766
17210
  console.error(`Roadmap file not found: ${roadmapPath}`);
16767
17211
  process.exit(1);
16768
17212
  }
16769
- const roadmapContent = fs38.readFileSync(roadmapPath, "utf-8");
17213
+ const roadmapContent = fs39.readFileSync(roadmapPath, "utf-8");
16770
17214
  const items = parseRoadmap(roadmapContent);
16771
17215
  const uncheckedItems = getUncheckedItems(items);
16772
17216
  if (uncheckedItems.length === 0) {
@@ -16890,11 +17334,11 @@ function boardCommand(program2) {
16890
17334
  // src/commands/queue.ts
16891
17335
  init_dist();
16892
17336
  init_dist();
16893
- import * as path41 from "path";
17337
+ import * as path42 from "path";
16894
17338
  import { spawn as spawn7 } from "child_process";
16895
17339
  import chalk7 from "chalk";
16896
17340
  import { Command as Command2 } from "commander";
16897
- var logger5 = createLogger("queue");
17341
+ var logger6 = createLogger("queue");
16898
17342
  var VALID_JOB_TYPES2 = ["executor", "reviewer", "qa", "audit", "slicer", "planner"];
16899
17343
  function formatTimestamp(unixTs) {
16900
17344
  if (unixTs === null) return "-";
@@ -17010,7 +17454,7 @@ function createQueueCommand() {
17010
17454
  process.exit(1);
17011
17455
  }
17012
17456
  }
17013
- const projectName = path41.basename(projectDir);
17457
+ const projectName = path42.basename(projectDir);
17014
17458
  const queueConfig = loadConfig(projectDir).queue;
17015
17459
  const id = enqueueJob(
17016
17460
  projectDir,
@@ -17043,13 +17487,13 @@ function createQueueCommand() {
17043
17487
  const configDir = _opts.projectDir ?? process.cwd();
17044
17488
  const entry = dispatchNextJob(loadConfig(configDir).queue);
17045
17489
  if (!entry) {
17046
- logger5.info("No pending jobs to dispatch");
17490
+ logger6.info("No pending jobs to dispatch");
17047
17491
  return;
17048
17492
  }
17049
- logger5.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
17493
+ logger6.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
17050
17494
  const scriptName = getScriptNameForJobType(entry.jobType);
17051
17495
  if (!scriptName) {
17052
- logger5.error(`Unknown job type: ${entry.jobType}`);
17496
+ logger6.error(`Unknown job type: ${entry.jobType}`);
17053
17497
  return;
17054
17498
  }
17055
17499
  let projectEnv;
@@ -17068,7 +17512,7 @@ function createQueueCommand() {
17068
17512
  NW_QUEUE_ENTRY_ID: String(entry.id)
17069
17513
  };
17070
17514
  const scriptPath = getScriptPath(scriptName);
17071
- logger5.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
17515
+ logger6.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
17072
17516
  try {
17073
17517
  const child = spawn7("bash", [scriptPath, entry.projectPath], {
17074
17518
  detached: true,
@@ -17077,11 +17521,11 @@ function createQueueCommand() {
17077
17521
  cwd: entry.projectPath
17078
17522
  });
17079
17523
  child.unref();
17080
- logger5.info(`Spawned PID: ${child.pid}`);
17524
+ logger6.info(`Spawned PID: ${child.pid}`);
17081
17525
  markJobRunning(entry.id);
17082
17526
  } catch (error2) {
17083
17527
  updateJobStatus(entry.id, "pending");
17084
- logger5.error(
17528
+ logger6.error(
17085
17529
  `Failed to dispatch ${entry.jobType} for ${entry.projectName}: ${error2 instanceof Error ? error2.message : String(error2)}`
17086
17530
  );
17087
17531
  process.exit(1);
@@ -17096,7 +17540,7 @@ function createQueueCommand() {
17096
17540
  process.exit(1);
17097
17541
  }
17098
17542
  const queueConfig = loadConfig(projectDir).queue;
17099
- const projectName = path41.basename(projectDir);
17543
+ const projectName = path42.basename(projectDir);
17100
17544
  const result = claimJobSlot(
17101
17545
  projectDir,
17102
17546
  projectName,
@@ -17233,7 +17677,7 @@ function notifyCommand(program2) {
17233
17677
 
17234
17678
  // src/commands/summary.ts
17235
17679
  init_dist();
17236
- import path42 from "path";
17680
+ import path43 from "path";
17237
17681
  import chalk8 from "chalk";
17238
17682
  function formatDuration2(seconds) {
17239
17683
  if (seconds === null) return "-";
@@ -17263,7 +17707,7 @@ function formatJobStatus(status) {
17263
17707
  return chalk8.dim(status);
17264
17708
  }
17265
17709
  function getProjectName2(projectPath) {
17266
- return path42.basename(projectPath) || projectPath;
17710
+ return path43.basename(projectPath) || projectPath;
17267
17711
  }
17268
17712
  function formatProvider(providerKey) {
17269
17713
  return providerKey.split(":")[0] || providerKey;
@@ -17382,7 +17826,7 @@ function summaryCommand(program2) {
17382
17826
  // src/commands/resolve.ts
17383
17827
  init_dist();
17384
17828
  import { execFileSync as execFileSync7 } from "child_process";
17385
- import * as path43 from "path";
17829
+ import * as path44 from "path";
17386
17830
  function buildEnvVars6(config, options) {
17387
17831
  const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
17388
17832
  env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
@@ -17512,7 +17956,7 @@ ${stderr}`);
17512
17956
  const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
17513
17957
  await sendNotifications(config, {
17514
17958
  event: notificationEvent,
17515
- projectName: path43.basename(projectDir),
17959
+ projectName: path44.basename(projectDir),
17516
17960
  exitCode,
17517
17961
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
17518
17962
  });
@@ -17525,19 +17969,141 @@ ${stderr}`);
17525
17969
  });
17526
17970
  }
17527
17971
 
17972
+ // src/commands/merge.ts
17973
+ init_dist();
17974
+ import * as path45 from "path";
17975
+ function buildEnvVars7(config, options) {
17976
+ const env = buildBaseEnvVars(config, "merger", options.dryRun);
17977
+ env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
17978
+ env.NW_MERGER_MERGE_METHOD = config.merger.mergeMethod;
17979
+ env.NW_MERGER_MIN_REVIEW_SCORE = String(config.merger.minReviewScore);
17980
+ env.NW_MERGER_BRANCH_PATTERNS = (config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns : config.branchPatterns).join(",");
17981
+ env.NW_MERGER_REBASE_BEFORE_MERGE = config.merger.rebaseBeforeMerge ? "1" : "0";
17982
+ env.NW_MERGER_MAX_PRS_PER_RUN = String(config.merger.maxPrsPerRun);
17983
+ return env;
17984
+ }
17985
+ function applyCliOverrides6(config, options) {
17986
+ const overridden = { ...config, merger: { ...config.merger } };
17987
+ if (options.timeout) {
17988
+ const timeout = parseInt(options.timeout, 10);
17989
+ if (!isNaN(timeout)) {
17990
+ overridden.merger.maxRuntime = timeout;
17991
+ }
17992
+ }
17993
+ if (options.provider) {
17994
+ overridden._cliProviderOverride = options.provider;
17995
+ }
17996
+ return overridden;
17997
+ }
17998
+ function resolveMergeNotificationEvent(exitCode, mergedCount, failedCount) {
17999
+ if (exitCode === 0 && mergedCount > 0) {
18000
+ return "merge_completed";
18001
+ }
18002
+ if (exitCode !== 0 || failedCount > 0) {
18003
+ return "merge_failed";
18004
+ }
18005
+ return null;
18006
+ }
18007
+ function printDryRun(config, envVars, scriptPath, projectDir) {
18008
+ header("Dry Run: Merge Orchestrator");
18009
+ const mergerProvider = resolveJobProvider(config, "merger");
18010
+ header("Configuration");
18011
+ const configTable = createTable({ head: ["Setting", "Value"] });
18012
+ configTable.push(["Provider", mergerProvider]);
18013
+ configTable.push([
18014
+ "Max Runtime",
18015
+ `${config.merger.maxRuntime}s (${Math.floor(config.merger.maxRuntime / 60)}min)`
18016
+ ]);
18017
+ configTable.push(["Merge Method", config.merger.mergeMethod]);
18018
+ configTable.push(["Min Review Score", `${config.merger.minReviewScore}/100`]);
18019
+ configTable.push([
18020
+ "Branch Patterns",
18021
+ config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns.join(", ") : "(top-level)"
18022
+ ]);
18023
+ configTable.push(["Rebase Before Merge", config.merger.rebaseBeforeMerge ? "Yes" : "No"]);
18024
+ configTable.push([
18025
+ "Max PRs Per Run",
18026
+ config.merger.maxPrsPerRun === 0 ? "Unlimited" : String(config.merger.maxPrsPerRun)
18027
+ ]);
18028
+ console.log(configTable.toString());
18029
+ header("Environment Variables");
18030
+ for (const [key, value] of Object.entries(envVars)) {
18031
+ dim(` ${key}=${value}`);
18032
+ }
18033
+ header("Command");
18034
+ dim(` bash ${scriptPath} ${projectDir}`);
18035
+ console.log();
18036
+ }
18037
+ function mergeCommand(program2) {
18038
+ 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) => {
18039
+ const projectDir = process.cwd();
18040
+ let config = loadConfig(projectDir);
18041
+ config = applyCliOverrides6(config, options);
18042
+ if (!config.merger.enabled && !options.dryRun) {
18043
+ info("Merge orchestrator is disabled in config; skipping.");
18044
+ process.exit(0);
18045
+ }
18046
+ const envVars = buildEnvVars7(config, options);
18047
+ const scriptPath = getScriptPath("night-watch-merger-cron.sh");
18048
+ if (options.dryRun) {
18049
+ printDryRun(config, envVars, scriptPath, projectDir);
18050
+ process.exit(0);
18051
+ }
18052
+ const spinner = createSpinner("Running merge orchestrator...");
18053
+ spinner.start();
18054
+ try {
18055
+ await maybeApplyCronSchedulingDelay(config, "merger", projectDir);
18056
+ const { exitCode, stdout, stderr } = await executeScriptWithOutput(
18057
+ scriptPath,
18058
+ [projectDir],
18059
+ envVars
18060
+ );
18061
+ const scriptResult = parseScriptResult(`${stdout}
18062
+ ${stderr}`);
18063
+ if (exitCode === 0) {
18064
+ if (scriptResult?.status === "queued") {
18065
+ spinner.succeed("Merge orchestrator queued \u2014 another job is currently running");
18066
+ } else if (scriptResult?.status?.startsWith("skip_")) {
18067
+ spinner.succeed("Merge orchestrator completed (no eligible PRs)");
18068
+ } else {
18069
+ spinner.succeed("Merge orchestrator completed successfully");
18070
+ }
18071
+ } else {
18072
+ spinner.fail(`Merge orchestrator exited with code ${exitCode}`);
18073
+ }
18074
+ const mergedCount = parseInt(scriptResult?.data?.merged ?? "0", 10);
18075
+ const failedCount = parseInt(scriptResult?.data?.failed ?? "0", 10);
18076
+ const notificationEvent = resolveMergeNotificationEvent(exitCode, mergedCount, failedCount);
18077
+ if (notificationEvent) {
18078
+ await sendNotifications(config, {
18079
+ event: notificationEvent,
18080
+ projectName: path45.basename(projectDir),
18081
+ exitCode,
18082
+ provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
18083
+ });
18084
+ }
18085
+ process.exit(exitCode);
18086
+ } catch (err) {
18087
+ spinner.fail("Failed to execute merge command");
18088
+ error(`${err instanceof Error ? err.message : String(err)}`);
18089
+ process.exit(1);
18090
+ }
18091
+ });
18092
+ }
18093
+
17528
18094
  // src/cli.ts
17529
18095
  var __filename5 = fileURLToPath6(import.meta.url);
17530
18096
  var __dirname5 = dirname12(__filename5);
17531
18097
  function findPackageRoot(dir) {
17532
18098
  let d = dir;
17533
18099
  for (let i = 0; i < 5; i++) {
17534
- if (existsSync30(join36(d, "package.json"))) return d;
18100
+ if (existsSync31(join37(d, "package.json"))) return d;
17535
18101
  d = dirname12(d);
17536
18102
  }
17537
18103
  return dir;
17538
18104
  }
17539
18105
  var packageRoot = findPackageRoot(__dirname5);
17540
- var packageJson = JSON.parse(readFileSync19(join36(packageRoot, "package.json"), "utf-8"));
18106
+ var packageJson = JSON.parse(readFileSync20(join37(packageRoot, "package.json"), "utf-8"));
17541
18107
  var program = new Command3();
17542
18108
  program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
17543
18109
  initCommand(program);
@@ -17568,4 +18134,5 @@ queueCommand(program);
17568
18134
  notifyCommand(program);
17569
18135
  summaryCommand(program);
17570
18136
  resolveCommand(program);
18137
+ mergeCommand(program);
17571
18138
  program.parse();