@jonit-dev/night-watch-cli 1.8.8-beta.10 → 1.8.8-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -319,6 +319,38 @@ var init_job_registry = __esm({
319
319
  targetColumn: "Draft",
320
320
  analysisPrompt: ""
321
321
  }
322
+ },
323
+ {
324
+ id: "merger",
325
+ name: "Merge Orchestrator",
326
+ description: "Repo-wide PR merge coordinator \u2014 scans, rebases, and merges in FIFO order",
327
+ cliCommand: "merge",
328
+ logName: "merger",
329
+ lockSuffix: "-merger.lock",
330
+ queuePriority: 45,
331
+ envPrefix: "NW_MERGER",
332
+ extraFields: [
333
+ {
334
+ name: "mergeMethod",
335
+ type: "enum",
336
+ enumValues: ["squash", "merge", "rebase"],
337
+ defaultValue: "squash"
338
+ },
339
+ { name: "minReviewScore", type: "number", defaultValue: 80 },
340
+ { name: "branchPatterns", type: "string[]", defaultValue: [] },
341
+ { name: "rebaseBeforeMerge", type: "boolean", defaultValue: true },
342
+ { name: "maxPrsPerRun", type: "number", defaultValue: 0 }
343
+ ],
344
+ defaultConfig: {
345
+ enabled: false,
346
+ schedule: "55 */4 * * *",
347
+ maxRuntime: 1800,
348
+ mergeMethod: "squash",
349
+ minReviewScore: 80,
350
+ branchPatterns: [],
351
+ rebaseBeforeMerge: true,
352
+ maxPrsPerRun: 0
353
+ }
322
354
  }
323
355
  ];
324
356
  JOB_MAP = new Map(JOB_REGISTRY.map((job) => [job.id, job]));
@@ -339,7 +371,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
339
371
  return `claude-proxy:${baseUrl}`;
340
372
  }
341
373
  }
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;
374
+ 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, 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
375
  var init_constants = __esm({
344
376
  "../core/dist/constants.js"() {
345
377
  "use strict";
@@ -384,7 +416,7 @@ var init_constants = __esm({
384
416
  slicerSchedule: DEFAULT_SLICER_SCHEDULE,
385
417
  slicerMaxRuntime: DEFAULT_SLICER_MAX_RUNTIME,
386
418
  priorityMode: "roadmap-first",
387
- issueColumn: "Draft"
419
+ issueColumn: "Ready"
388
420
  };
389
421
  DEFAULT_TEMPLATES_DIR = ".night-watch/templates";
390
422
  DEFAULT_BOARD_PROVIDER = {
@@ -458,6 +490,24 @@ If no issues are warranted, output an empty array: []`;
458
490
  aiReviewResolution: DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION,
459
491
  readyLabel: DEFAULT_PR_RESOLVER_READY_LABEL
460
492
  };
493
+ DEFAULT_MERGER_ENABLED = false;
494
+ DEFAULT_MERGER_SCHEDULE = "55 */4 * * *";
495
+ DEFAULT_MERGER_MAX_RUNTIME = 1800;
496
+ DEFAULT_MERGER_MERGE_METHOD = "squash";
497
+ DEFAULT_MERGER_MIN_REVIEW_SCORE = 80;
498
+ DEFAULT_MERGER_REBASE_BEFORE_MERGE = true;
499
+ DEFAULT_MERGER_MAX_PRS_PER_RUN = 0;
500
+ DEFAULT_MERGER = {
501
+ enabled: DEFAULT_MERGER_ENABLED,
502
+ schedule: DEFAULT_MERGER_SCHEDULE,
503
+ maxRuntime: DEFAULT_MERGER_MAX_RUNTIME,
504
+ mergeMethod: DEFAULT_MERGER_MERGE_METHOD,
505
+ minReviewScore: DEFAULT_MERGER_MIN_REVIEW_SCORE,
506
+ branchPatterns: [],
507
+ rebaseBeforeMerge: DEFAULT_MERGER_REBASE_BEFORE_MERGE,
508
+ maxPrsPerRun: DEFAULT_MERGER_MAX_PRS_PER_RUN
509
+ };
510
+ MERGER_LOG_NAME = "merger";
461
511
  AUDIT_LOG_NAME = "audit";
462
512
  PLANNER_LOG_NAME = "slicer";
463
513
  ANALYTICS_LOG_NAME = "analytics";
@@ -734,7 +784,7 @@ function normalizeConfig(rawConfig) {
734
784
  if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
735
785
  normalized.autoMergeMethod = mergeMethod;
736
786
  }
737
- for (const jobId of ["qa", "audit", "analytics"]) {
787
+ for (const jobId of ["qa", "audit", "analytics", "merger"]) {
738
788
  const jobDef = getJobDef(jobId);
739
789
  if (!jobDef)
740
790
  continue;
@@ -1068,6 +1118,17 @@ function buildEnvOverrideConfig(fileConfig) {
1068
1118
  env.prResolver = overrides;
1069
1119
  }
1070
1120
  }
1121
+ const mergerDef = getJobDef("merger");
1122
+ if (mergerDef) {
1123
+ const currentBase = (
1124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1125
+ env.merger ?? fileConfig?.merger ?? mergerDef.defaultConfig
1126
+ );
1127
+ const overrides = buildJobEnvOverrides(mergerDef.envPrefix, currentBase, mergerDef.extraFields);
1128
+ if (overrides) {
1129
+ env.merger = overrides;
1130
+ }
1131
+ }
1071
1132
  const jobProvidersEnv = {};
1072
1133
  for (const jobType of VALID_JOB_TYPES) {
1073
1134
  const val = process.env[`NW_JOB_PROVIDER_${jobType.toUpperCase()}`];
@@ -1163,6 +1224,7 @@ function getDefaultConfig() {
1163
1224
  audit: { ...DEFAULT_AUDIT },
1164
1225
  analytics: { ...DEFAULT_ANALYTICS },
1165
1226
  prResolver: { ...DEFAULT_PR_RESOLVER },
1227
+ merger: { ...DEFAULT_MERGER },
1166
1228
  jobProviders: { ...DEFAULT_JOB_PROVIDERS },
1167
1229
  providerScheduleOverrides: [...DEFAULT_PROVIDER_SCHEDULE_OVERRIDES],
1168
1230
  queue: { ...DEFAULT_QUEUE }
@@ -1230,7 +1292,7 @@ function mergeConfigLayer(base, layer) {
1230
1292
  ...layerQueue,
1231
1293
  providerBuckets: { ...baseQueue.providerBuckets, ...layerQueue.providerBuckets }
1232
1294
  };
1233
- } else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver") {
1295
+ } else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver" || _key === "merger") {
1234
1296
  base[_key] = {
1235
1297
  ...base[_key],
1236
1298
  ...value
@@ -1253,6 +1315,13 @@ function mergeConfigs(base, fileConfig, envConfig) {
1253
1315
  if (fileConfig)
1254
1316
  mergeConfigLayer(merged, fileConfig);
1255
1317
  mergeConfigLayer(merged, envConfig);
1318
+ if (merged.autoMerge === true && !fileConfig?.merger) {
1319
+ merged.merger = {
1320
+ ...merged.merger,
1321
+ enabled: true,
1322
+ mergeMethod: merged.autoMergeMethod ?? "squash"
1323
+ };
1324
+ }
1256
1325
  merged.maxRetries = sanitizeMaxRetries(merged.maxRetries, DEFAULT_MAX_RETRIES);
1257
1326
  merged.reviewerMaxRetries = sanitizeReviewerMaxRetries(merged.reviewerMaxRetries, DEFAULT_REVIEWER_MAX_RETRIES);
1258
1327
  merged.reviewerRetryDelay = sanitizeReviewerRetryDelay(merged.reviewerRetryDelay, DEFAULT_REVIEWER_RETRY_DELAY);
@@ -3590,6 +3659,9 @@ function analyticsLockPath(projectDir) {
3590
3659
  function prResolverLockPath(projectDir) {
3591
3660
  return `${LOCK_FILE_PREFIX}pr-resolver-${projectRuntimeKey(projectDir)}.lock`;
3592
3661
  }
3662
+ function mergerLockPath(projectDir) {
3663
+ return `${LOCK_FILE_PREFIX}merger-${projectRuntimeKey(projectDir)}.lock`;
3664
+ }
3593
3665
  function isProcessRunning(pid) {
3594
3666
  try {
3595
3667
  process.kill(pid, 0);
@@ -4900,6 +4972,10 @@ function getEventEmoji(event) {
4900
4972
  return "\u2705";
4901
4973
  case "pr_resolver_failed":
4902
4974
  return "\u274C";
4975
+ case "merge_completed":
4976
+ return "\u{1F500}";
4977
+ case "merge_failed":
4978
+ return "\u274C";
4903
4979
  }
4904
4980
  }
4905
4981
  function getEventTitle(event) {
@@ -4930,6 +5006,10 @@ function getEventTitle(event) {
4930
5006
  return "PR Conflict Resolved";
4931
5007
  case "pr_resolver_failed":
4932
5008
  return "PR Resolver Failed";
5009
+ case "merge_completed":
5010
+ return "PR Merged";
5011
+ case "merge_failed":
5012
+ return "Merge Failed";
4933
5013
  }
4934
5014
  }
4935
5015
  function getEventColor(event) {
@@ -4960,6 +5040,10 @@ function getEventColor(event) {
4960
5040
  return 65280;
4961
5041
  case "pr_resolver_failed":
4962
5042
  return 16711680;
5043
+ case "merge_completed":
5044
+ return 10181046;
5045
+ case "merge_failed":
5046
+ return 16711680;
4963
5047
  }
4964
5048
  }
4965
5049
  function buildDescription(ctx) {
@@ -5700,9 +5784,9 @@ var init_slicer_prompt = __esm({
5700
5784
  "use strict";
5701
5785
  __filename = fileURLToPath2(import.meta.url);
5702
5786
  __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.
5787
+ 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
5788
 
5705
- When this activates: \`PRD Creator: Initializing\`
5789
+ When this activates: \`Planning Mode: Principal Architect\`
5706
5790
 
5707
5791
  ---
5708
5792
 
@@ -5723,22 +5807,16 @@ The PRD directory is: \`{{PRD_DIR}}\`
5723
5807
 
5724
5808
  ## Your Task
5725
5809
 
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}}\`.
5810
+ 1. **Explore the Codebase** \u2014 Read relevant existing files to understand structure, patterns, and conventions.
5811
+ 2. **Assess Complexity** \u2014 Score using the rubric below and determine LOW / MEDIUM / HIGH.
5812
+ 3. **Write a Complete PRD** \u2014 Follow the exact template structure below. Every section must be filled with concrete information.
5813
+ 4. **Write the PRD File** \u2014 Use the Write tool to create the PRD file at \`{{OUTPUT_FILE_PATH}}\`.
5735
5814
 
5736
5815
  ---
5737
5816
 
5738
5817
  ## Complexity Scoring
5739
5818
 
5740
5819
  \`\`\`
5741
- COMPLEXITY SCORE (sum all that apply):
5742
5820
  +1 Touches 1-5 files
5743
5821
  +2 Touches 6-10 files
5744
5822
  +3 Touches 10+ files
@@ -5750,7 +5828,7 @@ COMPLEXITY SCORE (sum all that apply):
5750
5828
 
5751
5829
  | Score | Level | Template Mode |
5752
5830
  | ----- | ------ | ----------------------------------------------- |
5753
- | 1-3 | LOW | Minimal (skip sections marked with MEDIUM/HIGH) |
5831
+ | 1-3 | LOW | Minimal (skip sections marked MEDIUM/HIGH) |
5754
5832
  | 4-6 | MEDIUM | Standard (all sections) |
5755
5833
  | 7+ | HIGH | Full + mandatory checkpoints every phase |
5756
5834
  \`\`\`
@@ -5759,25 +5837,77 @@ COMPLEXITY SCORE (sum all that apply):
5759
5837
 
5760
5838
  ## PRD Template Structure
5761
5839
 
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
5840
+ Your PRD MUST use this structure. Replace every [bracketed placeholder] with real content.
5841
+
5842
+ # PRD: [Title]
5843
+
5844
+ **Complexity: [SCORE] \u2192 [LEVEL] mode**
5845
+
5846
+ ## 1. Context
5847
+
5848
+ **Problem:** [1-2 sentences]
5849
+
5850
+ **Files Analyzed:**
5851
+ - \`path/to/file.ts\` \u2014 [what you found]
5852
+
5853
+ **Current Behavior:**
5854
+ - [3-5 bullets]
5855
+
5856
+ ### Integration Points
5857
+ - Entry point: [cron / CLI / event / route]
5858
+ - Caller file: [file invoking new code]
5859
+ - User flow: User does X \u2192 triggers Y \u2192 result Z
5860
+
5861
+ ## 2. Solution
5862
+
5863
+ **Approach:**
5864
+ - [3-5 bullets]
5865
+
5866
+ **Key Decisions:** [library choices, error handling, reused utilities]
5867
+
5868
+ **Data Changes:** [schema changes, or "None"]
5869
+
5870
+ ## 3. Sequence Flow (MEDIUM/HIGH only)
5871
+
5872
+ [mermaid sequenceDiagram]
5873
+
5874
+ ## 4. Execution Phases
5875
+
5876
+ ### Phase N: [Name] \u2014 [User-visible outcome]
5877
+
5878
+ **Files (max 5):**
5879
+ - \`src/path/file.ts\` \u2014 [what changes]
5880
+
5881
+ **Implementation:**
5882
+ - [ ] Step 1
5883
+
5884
+ **Tests Required:**
5885
+ | Test File | Test Name | Assertion |
5886
+ |-----------|-----------|-----------|
5887
+ | \`src/__tests__/feature.test.ts\` | \`should X when Y\` | \`expect(r).toBe(Z)\` |
5888
+
5889
+ **Checkpoint:** Run \`yarn verify\` and related tests after this phase.
5890
+
5891
+ ## 5. Acceptance Criteria
5892
+
5893
+ - [ ] All phases complete
5894
+ - [ ] All tests pass
5895
+ - [ ] \`yarn verify\` passes
5896
+ - [ ] Feature is reachable (not orphaned code)
5768
5897
 
5769
5898
  ---
5770
5899
 
5771
5900
  ## Critical Instructions
5772
5901
 
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**
5902
+ 1. Read all relevant files BEFORE writing the PRD
5903
+ 2. Follow existing patterns \u2014 use \`@/*\` path aliases, match naming conventions
5904
+ 3. Include concrete file paths and implementation steps
5905
+ 4. Include specific test names and assertions
5906
+ 5. Use the Write tool to create the file at \`{{OUTPUT_FILE_PATH}}\`
5907
+ 6. No placeholder text in the final PRD
5908
+ 7. The PRD is the GitHub issue body \u2014 make it self-contained
5779
5909
 
5780
- DO NOT leave placeholder text like "[Name]" or "[description]" in the final PRD.
5910
+ DO NOT leave [bracketed placeholder] text in the output.
5781
5911
  DO NOT skip any sections.
5782
5912
  DO NOT forget to write the file.
5783
5913
  `;
@@ -6647,6 +6777,8 @@ function getLockPathForJob(projectPath, jobType) {
6647
6777
  return analyticsLockPath(projectPath);
6648
6778
  case "pr-resolver":
6649
6779
  return prResolverLockPath(projectPath);
6780
+ case "merger":
6781
+ return mergerLockPath(projectPath);
6650
6782
  }
6651
6783
  }
6652
6784
  function reconcileStaleRunningJobs(db) {
@@ -7714,6 +7846,14 @@ __export(dist_exports, {
7714
7846
  DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
7715
7847
  DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
7716
7848
  DEFAULT_MAX_RUNTIME: () => DEFAULT_MAX_RUNTIME,
7849
+ DEFAULT_MERGER: () => DEFAULT_MERGER,
7850
+ DEFAULT_MERGER_ENABLED: () => DEFAULT_MERGER_ENABLED,
7851
+ DEFAULT_MERGER_MAX_PRS_PER_RUN: () => DEFAULT_MERGER_MAX_PRS_PER_RUN,
7852
+ DEFAULT_MERGER_MAX_RUNTIME: () => DEFAULT_MERGER_MAX_RUNTIME,
7853
+ DEFAULT_MERGER_MERGE_METHOD: () => DEFAULT_MERGER_MERGE_METHOD,
7854
+ DEFAULT_MERGER_MIN_REVIEW_SCORE: () => DEFAULT_MERGER_MIN_REVIEW_SCORE,
7855
+ DEFAULT_MERGER_REBASE_BEFORE_MERGE: () => DEFAULT_MERGER_REBASE_BEFORE_MERGE,
7856
+ DEFAULT_MERGER_SCHEDULE: () => DEFAULT_MERGER_SCHEDULE,
7717
7857
  DEFAULT_MIN_REVIEW_SCORE: () => DEFAULT_MIN_REVIEW_SCORE,
7718
7858
  DEFAULT_NOTIFICATIONS: () => DEFAULT_NOTIFICATIONS,
7719
7859
  DEFAULT_PRD_DIR: () => DEFAULT_PRD_DIR,
@@ -7772,6 +7912,7 @@ __export(dist_exports, {
7772
7912
  LocalKanbanProvider: () => LocalKanbanProvider,
7773
7913
  Logger: () => Logger,
7774
7914
  MAX_HISTORY_RECORDS_PER_PRD: () => MAX_HISTORY_RECORDS_PER_PRD,
7915
+ MERGER_LOG_NAME: () => MERGER_LOG_NAME,
7775
7916
  NIGHT_WATCH_LABELS: () => NIGHT_WATCH_LABELS,
7776
7917
  PLANNER_LOG_NAME: () => PLANNER_LOG_NAME,
7777
7918
  PRD_STATES_FILE_NAME: () => PRD_STATES_FILE_NAME,
@@ -7936,6 +8077,7 @@ __export(dist_exports, {
7936
8077
  markItemProcessed: () => markItemProcessed,
7937
8078
  markJobRunning: () => markJobRunning,
7938
8079
  markPrdDone: () => markPrdDone,
8080
+ mergerLockPath: () => mergerLockPath,
7939
8081
  migrateJsonToSqlite: () => migrateJsonToSqlite,
7940
8082
  normalizeJobConfig: () => normalizeJobConfig,
7941
8083
  normalizeSchedulingPriority: () => normalizeSchedulingPriority,
@@ -8319,6 +8461,7 @@ function buildInitConfig(params) {
8319
8461
  },
8320
8462
  audit: { ...defaults.audit },
8321
8463
  analytics: { ...defaults.analytics },
8464
+ merger: { ...defaults.merger },
8322
8465
  prResolver: { ...defaults.prResolver },
8323
8466
  jobProviders: { ...defaults.jobProviders },
8324
8467
  queue: {
@@ -9399,10 +9542,6 @@ function buildEnvVars2(config, options) {
9399
9542
  env.NW_BRANCH_PATTERNS = config.branchPatterns.join(",");
9400
9543
  env.NW_PRD_DIR = config.prdDir;
9401
9544
  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
9545
  return env;
9407
9546
  }
9408
9547
  function applyCliOverrides2(config, options) {
@@ -9416,9 +9555,6 @@ function applyCliOverrides2(config, options) {
9416
9555
  if (options.provider) {
9417
9556
  overridden._cliProviderOverride = options.provider;
9418
9557
  }
9419
- if (options.autoMerge !== void 0) {
9420
- overridden.autoMerge = options.autoMerge;
9421
- }
9422
9558
  return overridden;
9423
9559
  }
9424
9560
  function isFailingCheck(check) {
@@ -9479,7 +9615,7 @@ function getOpenPrsNeedingWork(branchPatterns) {
9479
9615
  }
9480
9616
  }
9481
9617
  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) => {
9618
+ 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
9619
  const projectDir = process.cwd();
9484
9620
  let config = loadConfig(projectDir);
9485
9621
  config = applyCliOverrides2(config, options);
@@ -9502,10 +9638,6 @@ function reviewCommand(program2) {
9502
9638
  ]);
9503
9639
  configTable.push(["Min Review Score", `${config.minReviewScore}/100`]);
9504
9640
  configTable.push(["Branch Patterns", config.branchPatterns.join(", ")]);
9505
- configTable.push([
9506
- "Auto-merge",
9507
- config.autoMerge ? `Enabled (${config.autoMergeMethod})` : "Disabled"
9508
- ]);
9509
9641
  configTable.push(["Max Retry Attempts", String(config.reviewerMaxRetries)]);
9510
9642
  configTable.push(["Retry Delay", `${config.reviewerRetryDelay}s`]);
9511
9643
  configTable.push([
@@ -10139,6 +10271,14 @@ function performInstall(projectDir, config, options) {
10139
10271
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
10140
10272
  entries.push(prResolverEntry);
10141
10273
  }
10274
+ const disableMerger = options?.noMerger === true || options?.merger === false;
10275
+ const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
10276
+ if (installMerger) {
10277
+ const mergerSchedule = config.merger.schedule;
10278
+ const mergerLog = path25.join(logDir, "merger.log");
10279
+ const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
10280
+ entries.push(mergerEntry);
10281
+ }
10142
10282
  const existingEntries = new Set(
10143
10283
  Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
10144
10284
  );
@@ -10157,7 +10297,7 @@ function performInstall(projectDir, config, options) {
10157
10297
  }
10158
10298
  }
10159
10299
  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) => {
10300
+ 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
10301
  try {
10162
10302
  const projectDir = process.cwd();
10163
10303
  const config = loadConfig(projectDir);
@@ -10248,6 +10388,15 @@ function installCommand(program2) {
10248
10388
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
10249
10389
  entries.push(prResolverEntry);
10250
10390
  }
10391
+ const disableMerger = options.noMerger === true || options.merger === false;
10392
+ const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
10393
+ let mergerLog;
10394
+ if (installMerger) {
10395
+ mergerLog = path25.join(logDir, "merger.log");
10396
+ const mergerSchedule = config.merger.schedule;
10397
+ const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
10398
+ entries.push(mergerEntry);
10399
+ }
10251
10400
  const existingEntrySet = new Set(existingEntries);
10252
10401
  const currentCrontab = readCrontab();
10253
10402
  const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
@@ -10280,6 +10429,9 @@ function installCommand(program2) {
10280
10429
  if (installPrResolver && prResolverLog) {
10281
10430
  dim(` PR Resolver: ${prResolverLog}`);
10282
10431
  }
10432
+ if (installMerger && mergerLog) {
10433
+ dim(` Merger: ${mergerLog}`);
10434
+ }
10283
10435
  console.log();
10284
10436
  dim("To uninstall, run: night-watch uninstall");
10285
10437
  dim("To check status, run: night-watch status");
@@ -14843,12 +14995,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
14843
14995
  const auditPlan = getSchedulingPlan(projectDir, config, "audit");
14844
14996
  const plannerPlan = getSchedulingPlan(projectDir, config, "slicer");
14845
14997
  const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
14998
+ const mergerPlan = getSchedulingPlan(projectDir, config, "merger");
14846
14999
  const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
14847
15000
  const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
14848
15001
  const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
14849
15002
  const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
14850
15003
  const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
14851
15004
  const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
15005
+ const mergerInstalled = installed && (config.merger?.enabled ?? false) && hasScheduledCommand(entries, "merge");
14852
15006
  return {
14853
15007
  executor: {
14854
15008
  schedule: config.cronSchedule,
@@ -14898,6 +15052,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
14898
15052
  manualDelayMinutes: analyticsPlan.manualDelayMinutes,
14899
15053
  balancedDelayMinutes: analyticsPlan.balancedDelayMinutes
14900
15054
  },
15055
+ merger: {
15056
+ schedule: config.merger?.schedule ?? "55 */4 * * *",
15057
+ installed: mergerInstalled,
15058
+ nextRun: mergerInstalled ? addDelayToIsoString(computeNextRun(config.merger?.schedule ?? "55 */4 * * *"), mergerPlan.totalDelayMinutes) : null,
15059
+ delayMinutes: mergerPlan.totalDelayMinutes,
15060
+ manualDelayMinutes: mergerPlan.manualDelayMinutes,
15061
+ balancedDelayMinutes: mergerPlan.balancedDelayMinutes
15062
+ },
14901
15063
  paused: !installed,
14902
15064
  schedulingPriority: config.schedulingPriority,
14903
15065
  entries
@@ -16007,7 +16169,7 @@ function releasePlannerLock(lockFile) {
16007
16169
  }
16008
16170
  }
16009
16171
  function resolvePlannerIssueColumn(config) {
16010
- return config.roadmapScanner.issueColumn === "Ready" ? "Ready" : "Draft";
16172
+ return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
16011
16173
  }
16012
16174
  function buildPlannerIssueBody(projectDir, config, result) {
16013
16175
  const relativePrdPath = path38.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
@@ -16021,23 +16183,25 @@ function buildPlannerIssueBody(projectDir, config, result) {
16021
16183
  }
16022
16184
  const maxBodyChars = 6e4;
16023
16185
  const truncated = prdContent.length > maxBodyChars;
16024
- const prdPreview = truncated ? `${prdContent.slice(0, maxBodyChars)}
16186
+ const prdBody = truncated ? `${prdContent.slice(0, maxBodyChars)}
16025
16187
 
16026
16188
  ...[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) : [];
16189
+ const metaLines = [`- PRD file: \`${relativePrdPath}\``];
16190
+ if (sourceItem) {
16191
+ metaLines.push(`- Source section: ${sourceItem.section}`);
16192
+ if (sourceItem.description) {
16193
+ metaLines.push(`- Source summary: ${sourceItem.description}`);
16194
+ }
16195
+ }
16032
16196
  return [
16033
- "## Planner Generated PRD",
16197
+ prdBody,
16034
16198
  "",
16035
- `- PRD file: \`${relativePrdPath}\``,
16036
- ...sourceLines,
16199
+ "<details>",
16200
+ "<summary>Source metadata</summary>",
16037
16201
  "",
16038
- "---",
16202
+ ...metaLines,
16039
16203
  "",
16040
- prdPreview
16204
+ "</details>"
16041
16205
  ].join("\n");
16042
16206
  }
16043
16207
  async function createPlannerIssue(projectDir, config, result) {
@@ -16052,9 +16216,11 @@ async function createPlannerIssue(projectDir, config, result) {
16052
16216
  if (!board) {
16053
16217
  return { created: false, skippedReason: "board-not-configured" };
16054
16218
  }
16219
+ const issueTitle = `PRD: ${result.item.title}`;
16220
+ const normalizeTitle = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
16055
16221
  const existingIssues = await provider.getAllIssues();
16056
16222
  const existing = existingIssues.find(
16057
- (issue2) => issue2.title.trim().toLowerCase() === result.item.title.trim().toLowerCase()
16223
+ (issue2) => normalizeTitle(issue2.title) === normalizeTitle(result.item.title)
16058
16224
  );
16059
16225
  if (existing) {
16060
16226
  return {
@@ -16065,7 +16231,7 @@ async function createPlannerIssue(projectDir, config, result) {
16065
16231
  };
16066
16232
  }
16067
16233
  const issue = await provider.createIssue({
16068
- title: result.item.title,
16234
+ title: issueTitle,
16069
16235
  body: buildPlannerIssueBody(projectDir, config, result),
16070
16236
  column: resolvePlannerIssueColumn(config)
16071
16237
  });
@@ -17525,6 +17691,128 @@ ${stderr}`);
17525
17691
  });
17526
17692
  }
17527
17693
 
17694
+ // src/commands/merge.ts
17695
+ init_dist();
17696
+ import * as path44 from "path";
17697
+ function buildEnvVars7(config, options) {
17698
+ const env = buildBaseEnvVars(config, "merger", options.dryRun);
17699
+ env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
17700
+ env.NW_MERGER_MERGE_METHOD = config.merger.mergeMethod;
17701
+ env.NW_MERGER_MIN_REVIEW_SCORE = String(config.merger.minReviewScore);
17702
+ env.NW_MERGER_BRANCH_PATTERNS = (config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns : config.branchPatterns).join(",");
17703
+ env.NW_MERGER_REBASE_BEFORE_MERGE = config.merger.rebaseBeforeMerge ? "1" : "0";
17704
+ env.NW_MERGER_MAX_PRS_PER_RUN = String(config.merger.maxPrsPerRun);
17705
+ return env;
17706
+ }
17707
+ function applyCliOverrides6(config, options) {
17708
+ const overridden = { ...config, merger: { ...config.merger } };
17709
+ if (options.timeout) {
17710
+ const timeout = parseInt(options.timeout, 10);
17711
+ if (!isNaN(timeout)) {
17712
+ overridden.merger.maxRuntime = timeout;
17713
+ }
17714
+ }
17715
+ if (options.provider) {
17716
+ overridden._cliProviderOverride = options.provider;
17717
+ }
17718
+ return overridden;
17719
+ }
17720
+ function resolveMergeNotificationEvent(exitCode, mergedCount, failedCount) {
17721
+ if (exitCode === 0 && mergedCount > 0) {
17722
+ return "merge_completed";
17723
+ }
17724
+ if (exitCode !== 0 || failedCount > 0) {
17725
+ return "merge_failed";
17726
+ }
17727
+ return null;
17728
+ }
17729
+ function printDryRun(config, envVars, scriptPath, projectDir) {
17730
+ header("Dry Run: Merge Orchestrator");
17731
+ const mergerProvider = resolveJobProvider(config, "merger");
17732
+ header("Configuration");
17733
+ const configTable = createTable({ head: ["Setting", "Value"] });
17734
+ configTable.push(["Provider", mergerProvider]);
17735
+ configTable.push([
17736
+ "Max Runtime",
17737
+ `${config.merger.maxRuntime}s (${Math.floor(config.merger.maxRuntime / 60)}min)`
17738
+ ]);
17739
+ configTable.push(["Merge Method", config.merger.mergeMethod]);
17740
+ configTable.push(["Min Review Score", `${config.merger.minReviewScore}/100`]);
17741
+ configTable.push([
17742
+ "Branch Patterns",
17743
+ config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns.join(", ") : "(top-level)"
17744
+ ]);
17745
+ configTable.push(["Rebase Before Merge", config.merger.rebaseBeforeMerge ? "Yes" : "No"]);
17746
+ configTable.push([
17747
+ "Max PRs Per Run",
17748
+ config.merger.maxPrsPerRun === 0 ? "Unlimited" : String(config.merger.maxPrsPerRun)
17749
+ ]);
17750
+ console.log(configTable.toString());
17751
+ header("Environment Variables");
17752
+ for (const [key, value] of Object.entries(envVars)) {
17753
+ dim(` ${key}=${value}`);
17754
+ }
17755
+ header("Command");
17756
+ dim(` bash ${scriptPath} ${projectDir}`);
17757
+ console.log();
17758
+ }
17759
+ function mergeCommand(program2) {
17760
+ 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) => {
17761
+ const projectDir = process.cwd();
17762
+ let config = loadConfig(projectDir);
17763
+ config = applyCliOverrides6(config, options);
17764
+ if (!config.merger.enabled && !options.dryRun) {
17765
+ info("Merge orchestrator is disabled in config; skipping.");
17766
+ process.exit(0);
17767
+ }
17768
+ const envVars = buildEnvVars7(config, options);
17769
+ const scriptPath = getScriptPath("night-watch-merger-cron.sh");
17770
+ if (options.dryRun) {
17771
+ printDryRun(config, envVars, scriptPath, projectDir);
17772
+ process.exit(0);
17773
+ }
17774
+ const spinner = createSpinner("Running merge orchestrator...");
17775
+ spinner.start();
17776
+ try {
17777
+ await maybeApplyCronSchedulingDelay(config, "merger", projectDir);
17778
+ const { exitCode, stdout, stderr } = await executeScriptWithOutput(
17779
+ scriptPath,
17780
+ [projectDir],
17781
+ envVars
17782
+ );
17783
+ const scriptResult = parseScriptResult(`${stdout}
17784
+ ${stderr}`);
17785
+ if (exitCode === 0) {
17786
+ if (scriptResult?.status === "queued") {
17787
+ spinner.succeed("Merge orchestrator queued \u2014 another job is currently running");
17788
+ } else if (scriptResult?.status?.startsWith("skip_")) {
17789
+ spinner.succeed("Merge orchestrator completed (no eligible PRs)");
17790
+ } else {
17791
+ spinner.succeed("Merge orchestrator completed successfully");
17792
+ }
17793
+ } else {
17794
+ spinner.fail(`Merge orchestrator exited with code ${exitCode}`);
17795
+ }
17796
+ const mergedCount = parseInt(scriptResult?.data?.merged ?? "0", 10);
17797
+ const failedCount = parseInt(scriptResult?.data?.failed ?? "0", 10);
17798
+ const notificationEvent = resolveMergeNotificationEvent(exitCode, mergedCount, failedCount);
17799
+ if (notificationEvent) {
17800
+ await sendNotifications(config, {
17801
+ event: notificationEvent,
17802
+ projectName: path44.basename(projectDir),
17803
+ exitCode,
17804
+ provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
17805
+ });
17806
+ }
17807
+ process.exit(exitCode);
17808
+ } catch (err) {
17809
+ spinner.fail("Failed to execute merge command");
17810
+ error(`${err instanceof Error ? err.message : String(err)}`);
17811
+ process.exit(1);
17812
+ }
17813
+ });
17814
+ }
17815
+
17528
17816
  // src/cli.ts
17529
17817
  var __filename5 = fileURLToPath6(import.meta.url);
17530
17818
  var __dirname5 = dirname12(__filename5);
@@ -17568,4 +17856,5 @@ queueCommand(program);
17568
17856
  notifyCommand(program);
17569
17857
  summaryCommand(program);
17570
17858
  resolveCommand(program);
17859
+ mergeCommand(program);
17571
17860
  program.parse();