@jonit-dev/night-watch-cli 1.8.14-beta.5 → 1.8.14-beta.7

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 (37) hide show
  1. package/dist/cli.js +1439 -374
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/dashboard/tab-schedules.d.ts +1 -1
  4. package/dist/commands/dashboard/tab-schedules.d.ts.map +1 -1
  5. package/dist/commands/dashboard/tab-schedules.js +15 -6
  6. package/dist/commands/dashboard/tab-schedules.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +1 -0
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/install.d.ts +4 -0
  11. package/dist/commands/install.d.ts.map +1 -1
  12. package/dist/commands/install.js +26 -1
  13. package/dist/commands/install.js.map +1 -1
  14. package/dist/commands/logs.d.ts.map +1 -1
  15. package/dist/commands/logs.js +14 -4
  16. package/dist/commands/logs.js.map +1 -1
  17. package/dist/commands/manager.d.ts +23 -0
  18. package/dist/commands/manager.d.ts.map +1 -0
  19. package/dist/commands/manager.js +220 -0
  20. package/dist/commands/manager.js.map +1 -0
  21. package/dist/commands/queue.d.ts.map +1 -1
  22. package/dist/commands/queue.js +5 -1
  23. package/dist/commands/queue.js.map +1 -1
  24. package/dist/commands/serve.d.ts +1 -0
  25. package/dist/commands/serve.d.ts.map +1 -1
  26. package/dist/commands/serve.js +10 -4
  27. package/dist/commands/serve.js.map +1 -1
  28. package/dist/commands/uninstall.d.ts.map +1 -1
  29. package/dist/commands/uninstall.js +2 -0
  30. package/dist/commands/uninstall.js.map +1 -1
  31. package/dist/scripts/night-watch-manager-cron.sh +61 -0
  32. package/dist/web/assets/index-BbiKFOgi.css +1 -0
  33. package/dist/web/assets/index-DatF4suf.css +1 -0
  34. package/dist/web/assets/index-Q3IYCcdZ.js +447 -0
  35. package/dist/web/assets/index-uBao8iYf.js +447 -0
  36. package/dist/web/index.html +2 -2
  37. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -30,7 +30,9 @@ var init_types = __esm({
30
30
  "pr_resolver_conflict_resolved",
31
31
  "pr_resolver_failed",
32
32
  "merge_completed",
33
- "merge_failed"
33
+ "merge_failed",
34
+ "manager_blocked",
35
+ "manager_weekly_summary"
34
36
  ];
35
37
  }
36
38
  });
@@ -273,6 +275,52 @@ var init_job_registry = __esm({
273
275
  maxRuntime: 0
274
276
  }
275
277
  },
278
+ {
279
+ id: "manager",
280
+ name: "Manager",
281
+ description: "Monitors roadmap progress and creates draft PRDs for project gaps",
282
+ cliCommand: "manager",
283
+ logName: "manager",
284
+ lockSuffix: "-manager.lock",
285
+ queuePriority: 25,
286
+ envPrefix: "NW_MANAGER",
287
+ extraFields: [
288
+ {
289
+ name: "authority",
290
+ type: "enum",
291
+ enumValues: ["draft", "ready", "workflow"],
292
+ defaultValue: "draft"
293
+ },
294
+ {
295
+ name: "outputMode",
296
+ type: "enum",
297
+ enumValues: ["board-draft", "filesystem-prd", "report-only"],
298
+ defaultValue: "board-draft"
299
+ },
300
+ {
301
+ name: "targetColumn",
302
+ type: "enum",
303
+ enumValues: [...BOARD_COLUMNS],
304
+ defaultValue: "Draft"
305
+ },
306
+ { name: "memoryPath", type: "string", defaultValue: ".night-watch/manager/memory.md" },
307
+ { name: "docsDir", type: "string", defaultValue: ".night-watch/manager/docs" },
308
+ { name: "weeklySummaryEnabled", type: "boolean", defaultValue: true },
309
+ { name: "weeklySummaryDay", type: "number", defaultValue: 1 }
310
+ ],
311
+ defaultConfig: {
312
+ enabled: true,
313
+ schedule: "15 7 * * *",
314
+ maxRuntime: 0,
315
+ authority: "draft",
316
+ outputMode: "board-draft",
317
+ targetColumn: "Draft",
318
+ memoryPath: ".night-watch/manager/memory.md",
319
+ docsDir: ".night-watch/manager/docs",
320
+ weeklySummaryEnabled: true,
321
+ weeklySummaryDay: 1
322
+ }
323
+ },
276
324
  {
277
325
  id: "qa",
278
326
  name: "QA",
@@ -423,7 +471,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
423
471
  return `claude-proxy:${baseUrl}`;
424
472
  }
425
473
  }
426
- 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_FEEDBACK, 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_CREATE_ISSUES, 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_CI_POLICY, DEFAULT_MERGER_LOCAL_CHECK_COMMAND, 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, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS, DEFAULT_WEBHOOK_TRIGGERS, 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;
474
+ 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_FEEDBACK, 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_CREATE_ISSUES, 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_MANAGER_ENABLED, DEFAULT_MANAGER_SCHEDULE, DEFAULT_MANAGER_MAX_RUNTIME, DEFAULT_MANAGER_AUTHORITY, DEFAULT_MANAGER_OUTPUT_MODE, DEFAULT_MANAGER_TARGET_COLUMN, DEFAULT_MANAGER_MEMORY_PATH, DEFAULT_MANAGER_DOCS_DIR, DEFAULT_MANAGER_WEEKLY_SUMMARY_ENABLED, DEFAULT_MANAGER_WEEKLY_SUMMARY_DAY, DEFAULT_MANAGER, 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_CI_POLICY, DEFAULT_MERGER_LOCAL_CHECK_COMMAND, DEFAULT_MERGER, MERGER_LOG_NAME, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, MANAGER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS, DEFAULT_WEBHOOK_TRIGGERS, 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;
427
475
  var init_constants = __esm({
428
476
  "../core/dist/constants.js"() {
429
477
  "use strict";
@@ -534,6 +582,28 @@ If no issues are warranted, output an empty array: []`;
534
582
  targetColumn: DEFAULT_ANALYTICS_TARGET_COLUMN,
535
583
  analysisPrompt: DEFAULT_ANALYTICS_PROMPT
536
584
  };
585
+ DEFAULT_MANAGER_ENABLED = true;
586
+ DEFAULT_MANAGER_SCHEDULE = "15 7 * * *";
587
+ DEFAULT_MANAGER_MAX_RUNTIME = 0;
588
+ DEFAULT_MANAGER_AUTHORITY = "draft";
589
+ DEFAULT_MANAGER_OUTPUT_MODE = "board-draft";
590
+ DEFAULT_MANAGER_TARGET_COLUMN = "Draft";
591
+ DEFAULT_MANAGER_MEMORY_PATH = ".night-watch/manager/memory.md";
592
+ DEFAULT_MANAGER_DOCS_DIR = ".night-watch/manager/docs";
593
+ DEFAULT_MANAGER_WEEKLY_SUMMARY_ENABLED = true;
594
+ DEFAULT_MANAGER_WEEKLY_SUMMARY_DAY = 1;
595
+ DEFAULT_MANAGER = {
596
+ enabled: DEFAULT_MANAGER_ENABLED,
597
+ schedule: DEFAULT_MANAGER_SCHEDULE,
598
+ maxRuntime: DEFAULT_MANAGER_MAX_RUNTIME,
599
+ authority: DEFAULT_MANAGER_AUTHORITY,
600
+ outputMode: DEFAULT_MANAGER_OUTPUT_MODE,
601
+ targetColumn: DEFAULT_MANAGER_TARGET_COLUMN,
602
+ memoryPath: DEFAULT_MANAGER_MEMORY_PATH,
603
+ docsDir: DEFAULT_MANAGER_DOCS_DIR,
604
+ weeklySummaryEnabled: DEFAULT_MANAGER_WEEKLY_SUMMARY_ENABLED,
605
+ weeklySummaryDay: DEFAULT_MANAGER_WEEKLY_SUMMARY_DAY
606
+ };
537
607
  DEFAULT_PR_RESOLVER_ENABLED = true;
538
608
  DEFAULT_PR_RESOLVER_SCHEDULE = "15 6,14,22 * * *";
539
609
  DEFAULT_PR_RESOLVER_MAX_RUNTIME = 0;
@@ -579,6 +649,7 @@ If no issues are warranted, output an empty array: []`;
579
649
  PLANNER_LOG_NAME = "slicer";
580
650
  ANALYTICS_LOG_NAME = "analytics";
581
651
  PR_RESOLVER_LOG_NAME = "pr-resolver";
652
+ MANAGER_LOG_NAME = "manager";
582
653
  VALID_PROVIDERS = ["claude", "codex"];
583
654
  VALID_JOB_TYPES = getValidJobTypes();
584
655
  DEFAULT_JOB_PROVIDERS = {};
@@ -903,7 +974,7 @@ function normalizeConfig(rawConfig) {
903
974
  if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
904
975
  normalized.autoMergeMethod = mergeMethod;
905
976
  }
906
- for (const jobId of ["qa", "audit", "analytics", "merger"]) {
977
+ for (const jobId of ["qa", "audit", "analytics", "merger", "manager"]) {
907
978
  const jobDef = getJobDef(jobId);
908
979
  if (!jobDef)
909
980
  continue;
@@ -1254,7 +1325,7 @@ function buildEnvOverrideConfig(fileConfig) {
1254
1325
  env.claudeModel = model;
1255
1326
  }
1256
1327
  }
1257
- for (const jobId of ["qa", "audit", "analytics"]) {
1328
+ for (const jobId of ["qa", "audit", "analytics", "manager"]) {
1258
1329
  const jobDef = getJobDef(jobId);
1259
1330
  if (!jobDef)
1260
1331
  continue;
@@ -1381,6 +1452,7 @@ function getDefaultConfig() {
1381
1452
  qa: { ...DEFAULT_QA },
1382
1453
  audit: { ...DEFAULT_AUDIT },
1383
1454
  analytics: { ...DEFAULT_ANALYTICS },
1455
+ manager: { ...DEFAULT_MANAGER },
1384
1456
  feedback: { ...DEFAULT_FEEDBACK },
1385
1457
  prResolver: { ...DEFAULT_PR_RESOLVER },
1386
1458
  merger: { ...DEFAULT_MERGER },
@@ -1580,7 +1652,7 @@ function mergeConfigLayer(base, layer) {
1580
1652
  })
1581
1653
  }
1582
1654
  };
1583
- } else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "feedback" || _key === "prResolver" || _key === "merger") {
1655
+ } else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "manager" || _key === "feedback" || _key === "prResolver" || _key === "merger") {
1584
1656
  base[_key] = {
1585
1657
  ...base[_key],
1586
1658
  ...value
@@ -1621,6 +1693,10 @@ function mergeConfigs(base, fileConfig, envConfig) {
1621
1693
  maxActiveAugmentations: Math.max(0, Math.min(10, Math.floor(merged.feedback.maxActiveAugmentations))),
1622
1694
  successStreakToExpire: Math.max(0, Math.min(20, Math.floor(merged.feedback.successStreakToExpire)))
1623
1695
  };
1696
+ merged.manager = {
1697
+ ...merged.manager,
1698
+ weeklySummaryDay: Math.max(0, Math.min(6, Math.floor(merged.manager.weeklySummaryDay)))
1699
+ };
1624
1700
  if (merged.secondaryFallbackModel === void 0) {
1625
1701
  merged.secondaryFallbackModel = merged.primaryFallbackModel === void 0 ? DEFAULT_SECONDARY_FALLBACK_MODEL : merged.primaryFallbackModel;
1626
1702
  }
@@ -4431,7 +4507,8 @@ var init_crontab = __esm({
4431
4507
  "night-watch-qa-cron.sh",
4432
4508
  "night-watch-audit-cron.sh",
4433
4509
  "night-watch-slice-cron.sh",
4434
- "night-watch-slicer-cron.sh"
4510
+ "night-watch-slicer-cron.sh",
4511
+ "night-watch-manager-cron.sh"
4435
4512
  ];
4436
4513
  }
4437
4514
  });
@@ -4484,6 +4561,9 @@ function prResolverLockPath(projectDir) {
4484
4561
  function mergerLockPath(projectDir) {
4485
4562
  return `${LOCK_FILE_PREFIX}merger-${projectRuntimeKey(projectDir)}.lock`;
4486
4563
  }
4564
+ function managerLockPath(projectDir) {
4565
+ return `${LOCK_FILE_PREFIX}manager-${projectRuntimeKey(projectDir)}.lock`;
4566
+ }
4487
4567
  function isProcessRunning(pid) {
4488
4568
  if (pid <= 0)
4489
4569
  return false;
@@ -4874,7 +4954,8 @@ function collectLogInfo(projectDir) {
4874
4954
  { name: "planner", fileName: `${PLANNER_LOG_NAME}.log` },
4875
4955
  { name: "analytics", fileName: `${ANALYTICS_LOG_NAME}.log` },
4876
4956
  { name: "pr-resolver", fileName: `${PR_RESOLVER_LOG_NAME}.log` },
4877
- { name: "merger", fileName: `${MERGER_LOG_NAME}.log` }
4957
+ { name: "merger", fileName: `${MERGER_LOG_NAME}.log` },
4958
+ { name: "manager", fileName: `${MANAGER_LOG_NAME}.log` }
4878
4959
  ];
4879
4960
  return logEntries.map(({ name, fileName }) => {
4880
4961
  const logPath = path6.join(projectDir, LOG_DIR, fileName);
@@ -4906,6 +4987,7 @@ async function fetchStatusSnapshot(projectDir, config) {
4906
4987
  const analyticsLock = checkLockFile(analyticsLockPath(projectDir));
4907
4988
  const prResolverLock = checkLockFile(prResolverLockPath(projectDir));
4908
4989
  const mergerLock = checkLockFile(mergerLockPath(projectDir));
4990
+ const managerLock = checkLockFile(managerLockPath(projectDir));
4909
4991
  const processes = [
4910
4992
  { name: "executor", running: executorLock.running, pid: executorLock.pid },
4911
4993
  { name: "reviewer", running: reviewerLock.running, pid: reviewerLock.pid },
@@ -4914,7 +4996,8 @@ async function fetchStatusSnapshot(projectDir, config) {
4914
4996
  { name: "planner", running: plannerLock.running, pid: plannerLock.pid },
4915
4997
  { name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid },
4916
4998
  { name: "pr-resolver", running: prResolverLock.running, pid: prResolverLock.pid },
4917
- { name: "merger", running: mergerLock.running, pid: mergerLock.pid }
4999
+ { name: "merger", running: mergerLock.running, pid: mergerLock.pid },
5000
+ { name: "manager", running: managerLock.running, pid: managerLock.pid }
4918
5001
  ];
4919
5002
  const prds = collectPrdInfo(projectDir, config.prdDir, config.maxRuntime);
4920
5003
  const prs = await collectPrInfo(projectDir, config.branchPatterns);
@@ -4955,7 +5038,7 @@ function getLockFilePaths(projectDir) {
4955
5038
  };
4956
5039
  }
4957
5040
  function sleep(ms) {
4958
- return new Promise((resolve9) => setTimeout(resolve9, ms));
5041
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
4959
5042
  }
4960
5043
  async function cancelProcess(processType, lockPath, force = false) {
4961
5044
  const lockStatus = checkLockFile(lockPath);
@@ -5951,6 +6034,10 @@ function getEventEmoji(event) {
5951
6034
  return "\u{1F500}";
5952
6035
  case "merge_failed":
5953
6036
  return "\u274C";
6037
+ case "manager_blocked":
6038
+ return "\u26A0\uFE0F";
6039
+ case "manager_weekly_summary":
6040
+ return "\u{1F4CA}";
5954
6041
  }
5955
6042
  }
5956
6043
  function getEventTitle(event) {
@@ -5985,6 +6072,10 @@ function getEventTitle(event) {
5985
6072
  return "PR Merged";
5986
6073
  case "merge_failed":
5987
6074
  return "Merge Failed";
6075
+ case "manager_blocked":
6076
+ return "Manager Needs Human Input";
6077
+ case "manager_weekly_summary":
6078
+ return "Manager Weekly Summary";
5988
6079
  }
5989
6080
  }
5990
6081
  function getEventColor(event) {
@@ -6019,6 +6110,10 @@ function getEventColor(event) {
6019
6110
  return 10181046;
6020
6111
  case "merge_failed":
6021
6112
  return 16711680;
6113
+ case "manager_blocked":
6114
+ return 16753920;
6115
+ case "manager_weekly_summary":
6116
+ return 3447003;
6022
6117
  }
6023
6118
  }
6024
6119
  function buildDescription(ctx) {
@@ -7273,7 +7368,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
7273
7368
  const logStream = fs18.createWriteStream(logFile, { flags: "w" });
7274
7369
  logStream.on("error", () => {
7275
7370
  });
7276
- return new Promise((resolve9) => {
7371
+ return new Promise((resolve11) => {
7277
7372
  const childEnv = {
7278
7373
  ...process.env,
7279
7374
  ...config.providerEnv,
@@ -7292,7 +7387,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
7292
7387
  });
7293
7388
  child.on("error", (error2) => {
7294
7389
  logStream.end();
7295
- resolve9({
7390
+ resolve11({
7296
7391
  sliced: false,
7297
7392
  error: `Failed to spawn provider: ${error2.message}`,
7298
7393
  item
@@ -7301,7 +7396,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
7301
7396
  child.on("close", (code) => {
7302
7397
  logStream.end();
7303
7398
  if (code !== 0) {
7304
- resolve9({
7399
+ resolve11({
7305
7400
  sliced: false,
7306
7401
  error: `Provider exited with code ${code ?? 1}`,
7307
7402
  item
@@ -7309,14 +7404,14 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
7309
7404
  return;
7310
7405
  }
7311
7406
  if (!fs18.existsSync(filePath)) {
7312
- resolve9({
7407
+ resolve11({
7313
7408
  sliced: false,
7314
7409
  error: `Provider did not create expected file: ${filePath}`,
7315
7410
  item
7316
7411
  });
7317
7412
  return;
7318
7413
  }
7319
- resolve9({
7414
+ resolve11({
7320
7415
  sliced: true,
7321
7416
  file: filename,
7322
7417
  item
@@ -7478,7 +7573,7 @@ async function executeScript(scriptPath, args = [], env = {}, options = {}) {
7478
7573
  return result.exitCode;
7479
7574
  }
7480
7575
  async function executeScriptWithOutput(scriptPath, args = [], env = {}, options = {}) {
7481
- return new Promise((resolve9, reject) => {
7576
+ return new Promise((resolve11, reject) => {
7482
7577
  const childEnv = {
7483
7578
  ...process.env,
7484
7579
  ...env
@@ -7504,7 +7599,7 @@ async function executeScriptWithOutput(scriptPath, args = [], env = {}, options
7504
7599
  reject(error2);
7505
7600
  });
7506
7601
  child.on("close", (code) => {
7507
- resolve9({
7602
+ resolve11({
7508
7603
  exitCode: code ?? 1,
7509
7604
  stdout: stdoutChunks.join(""),
7510
7605
  stderr: stderrChunks.join("")
@@ -7541,6 +7636,8 @@ function isJobTypeEnabled(config, jobType) {
7541
7636
  return config.roadmapScanner.enabled;
7542
7637
  case "analytics":
7543
7638
  return config.analytics.enabled;
7639
+ case "manager":
7640
+ return config.manager.enabled;
7544
7641
  default:
7545
7642
  return true;
7546
7643
  }
@@ -7549,6 +7646,8 @@ function getJobSchedule(config, jobType) {
7549
7646
  switch (jobType) {
7550
7647
  case "reviewer":
7551
7648
  return config.reviewerSchedule ?? "";
7649
+ case "manager":
7650
+ return config.manager.schedule ?? "";
7552
7651
  case "executor":
7553
7652
  default:
7554
7653
  return config.cronSchedule ?? "";
@@ -7909,6 +8008,8 @@ function getLockPathForJob(projectPath, jobType) {
7909
8008
  return prResolverLockPath(projectPath);
7910
8009
  case "merger":
7911
8010
  return mergerLockPath(projectPath);
8011
+ case "manager":
8012
+ return managerLockPath(projectPath);
7912
8013
  }
7913
8014
  }
7914
8015
  function isRunningEntryStale(entry) {
@@ -8929,6 +9030,562 @@ var init_audit = __esm({
8929
9030
  }
8930
9031
  });
8931
9032
 
9033
+ // ../core/dist/manager/manager-memory.js
9034
+ import * as fs23 from "fs";
9035
+ import * as path22 from "path";
9036
+ import { createHash as createHash4 } from "crypto";
9037
+ function createFindingFingerprint(parts) {
9038
+ const normalized = parts.map((part) => part.toLowerCase().trim().replace(/\s+/g, " ")).join("|");
9039
+ return createHash4("sha256").update(normalized).digest("hex").slice(0, 16);
9040
+ }
9041
+ function loadManagerMemory(memoryPath) {
9042
+ if (!fs23.existsSync(memoryPath)) {
9043
+ return { fingerprints: /* @__PURE__ */ new Set(), lastWeeklySummaryAt: null, raw: "" };
9044
+ }
9045
+ const raw = fs23.readFileSync(memoryPath, "utf-8");
9046
+ const fingerprints = /* @__PURE__ */ new Set();
9047
+ let match;
9048
+ while ((match = FINGERPRINT_PATTERN.exec(raw)) !== null) {
9049
+ fingerprints.add(match[1]);
9050
+ }
9051
+ const weeklyMatch = raw.match(WEEKLY_SUMMARY_PATTERN);
9052
+ const lastWeeklySummaryAt = weeklyMatch ? parseDate(weeklyMatch[1].trim()) : null;
9053
+ return { fingerprints, lastWeeklySummaryAt, raw };
9054
+ }
9055
+ function isKnownFinding(memory, fingerprint) {
9056
+ return memory.fingerprints.has(fingerprint);
9057
+ }
9058
+ function renderManagerMemory(result, previous) {
9059
+ const lines = [
9060
+ "# Night Watch Manager Memory",
9061
+ "",
9062
+ `Last run: ${(/* @__PURE__ */ new Date()).toISOString()}`,
9063
+ `Last weekly summary: ${getLastWeeklySummary(result, previous)}`,
9064
+ "",
9065
+ "## Latest Run",
9066
+ "",
9067
+ `- Findings: ${result.findings.length}`,
9068
+ `- Proposed drafts: ${result.proposedDrafts.length}`,
9069
+ `- Created drafts: ${result.createdDrafts.length}`,
9070
+ `- Skipped duplicates: ${result.skippedFindings.length}`,
9071
+ "",
9072
+ "## Findings",
9073
+ ""
9074
+ ];
9075
+ for (const finding of result.findings) {
9076
+ lines.push(...renderFinding(finding));
9077
+ }
9078
+ lines.push("## Created Drafts", "");
9079
+ for (const draft of result.createdDrafts) {
9080
+ lines.push(`- ${draft.title} (#${draft.issue.number}) - fingerprint: \`${draft.fingerprint}\``);
9081
+ }
9082
+ if (result.createdDrafts.length === 0) {
9083
+ lines.push("- None");
9084
+ }
9085
+ lines.push("", "## Skipped Duplicates", "");
9086
+ for (const skipped of result.skippedFindings) {
9087
+ lines.push(`- ${skipped.title} (${skipped.reason}) - fingerprint: \`${skipped.fingerprint}\``);
9088
+ }
9089
+ if (result.skippedFindings.length === 0) {
9090
+ lines.push("- None");
9091
+ }
9092
+ const previousFingerprints = [...previous.fingerprints].filter((fingerprint) => !result.findings.some((finding) => finding.fingerprint === fingerprint));
9093
+ if (previousFingerprints.length > 0) {
9094
+ lines.push("", "## Previous Fingerprints", "");
9095
+ for (const fingerprint of previousFingerprints) {
9096
+ lines.push(`- fingerprint: \`${fingerprint}\``);
9097
+ }
9098
+ }
9099
+ return `${lines.join("\n")}
9100
+ `;
9101
+ }
9102
+ function writeManagerMemory(memoryPath, result, previous) {
9103
+ fs23.mkdirSync(path22.dirname(memoryPath), { recursive: true });
9104
+ fs23.writeFileSync(memoryPath, renderManagerMemory(result, previous), "utf-8");
9105
+ }
9106
+ function renderFinding(finding) {
9107
+ return [
9108
+ `### ${finding.title}`,
9109
+ "",
9110
+ `- kind: ${finding.kind}`,
9111
+ `- severity: ${finding.severity}`,
9112
+ `- source: ${finding.source}`,
9113
+ `- fingerprint: \`${finding.fingerprint}\``,
9114
+ "",
9115
+ finding.body,
9116
+ ""
9117
+ ];
9118
+ }
9119
+ function getLastWeeklySummary(result, previous) {
9120
+ const weekly = result.notificationDecisions.find((decision) => decision.event === "manager_weekly_summary" && decision.shouldNotify);
9121
+ if (weekly) {
9122
+ return (/* @__PURE__ */ new Date()).toISOString();
9123
+ }
9124
+ return previous.lastWeeklySummaryAt?.toISOString() ?? "never";
9125
+ }
9126
+ function parseDate(value) {
9127
+ if (value === "never")
9128
+ return null;
9129
+ const date = new Date(value);
9130
+ return Number.isNaN(date.getTime()) ? null : date;
9131
+ }
9132
+ function summarizeCreatedDrafts(drafts) {
9133
+ if (drafts.length === 0)
9134
+ return "No board drafts created.";
9135
+ return `${drafts.length} board draft${drafts.length === 1 ? "" : "s"} created.`;
9136
+ }
9137
+ function summarizeSkippedFindings(skipped) {
9138
+ if (skipped.length === 0)
9139
+ return "No duplicate findings skipped.";
9140
+ return `${skipped.length} duplicate finding${skipped.length === 1 ? "" : "s"} skipped.`;
9141
+ }
9142
+ var FINGERPRINT_PATTERN, WEEKLY_SUMMARY_PATTERN;
9143
+ var init_manager_memory = __esm({
9144
+ "../core/dist/manager/manager-memory.js"() {
9145
+ "use strict";
9146
+ FINGERPRINT_PATTERN = /fingerprint:\s*`([^`]+)`/g;
9147
+ WEEKLY_SUMMARY_PATTERN = /Last weekly summary:\s*([^\n]+)/;
9148
+ }
9149
+ });
9150
+
9151
+ // ../core/dist/manager/manager-analysis.js
9152
+ import * as fs24 from "fs";
9153
+ import * as path23 from "path";
9154
+ function analyzeManagerInputs(context) {
9155
+ const roadmapPath = path23.resolve(context.projectDir, context.config.roadmapScanner?.roadmapPath || "ROADMAP.md");
9156
+ const roadmapContent = fs24.existsSync(roadmapPath) ? fs24.readFileSync(roadmapPath, "utf-8") : "";
9157
+ const roadmapItems = roadmapContent ? parseRoadmap(roadmapContent) : [];
9158
+ const prds = collectPrdFiles(path23.resolve(context.projectDir, context.config.prdDir || "docs/prds"));
9159
+ const findings = [];
9160
+ const searchableWork = buildSearchableWork(context.boardIssues.map((issue) => issue.title), prds);
9161
+ for (const item of roadmapItems.filter((roadmapItem) => !roadmapItem.checked)) {
9162
+ if (searchableWork.has(slugify(item.title))) {
9163
+ continue;
9164
+ }
9165
+ const fingerprint = createFindingFingerprint(["roadmap_gap", item.hash, item.title]);
9166
+ findings.push({
9167
+ kind: "roadmap_gap",
9168
+ severity: "warning",
9169
+ title: `Roadmap item needs an owner: ${item.title}`,
9170
+ body: item.description || `The roadmap item "${item.title}" is still unchecked and does not appear to have a matching board issue or PRD.`,
9171
+ fingerprint,
9172
+ requiresHuman: false,
9173
+ source: `roadmap:${item.section}`,
9174
+ labels: ["manager", "roadmap"]
9175
+ });
9176
+ }
9177
+ for (const prd of context.statusSnapshot?.prds ?? []) {
9178
+ if (prd.status !== "blocked")
9179
+ continue;
9180
+ const fingerprint = createFindingFingerprint(["blocked_prd", prd.name, ...prd.unmetDependencies]);
9181
+ findings.push({
9182
+ kind: "blocked_prd",
9183
+ severity: "blocker",
9184
+ title: `Blocked PRD needs human triage: ${prd.name}`,
9185
+ body: `PRD "${prd.name}" is blocked by unmet dependencies: ${prd.unmetDependencies.join(", ") || "unknown"}.`,
9186
+ fingerprint,
9187
+ requiresHuman: true,
9188
+ source: "status:prds",
9189
+ labels: ["manager", "blocked"]
9190
+ });
9191
+ }
9192
+ const oldestPendingAge = context.queueStatus?.oldestPendingAge;
9193
+ if (oldestPendingAge !== null && oldestPendingAge !== void 0 && oldestPendingAge > 6 * 60 * 60) {
9194
+ const fingerprint = createFindingFingerprint(["stale_queue", String(Math.floor(oldestPendingAge / 3600))]);
9195
+ findings.push({
9196
+ kind: "stale_queue",
9197
+ severity: "blocker",
9198
+ title: "Queue has stale pending work",
9199
+ body: `The oldest pending queue item has waited ${oldestPendingAge} seconds. This may need human capacity or credentials.`,
9200
+ fingerprint,
9201
+ requiresHuman: true,
9202
+ source: "queue",
9203
+ labels: ["manager", "queue", "blocked"]
9204
+ });
9205
+ }
9206
+ if (!fs24.existsSync(path23.join(context.managerConfig.docsDirectory, "overview.md"))) {
9207
+ const fingerprint = createFindingFingerprint(["missing_manager_doc", context.managerConfig.docsDirectory]);
9208
+ findings.push({
9209
+ kind: "missing_manager_doc",
9210
+ severity: "info",
9211
+ title: "Manager overview document is missing",
9212
+ body: "The Manager has not written its generated overview document yet.",
9213
+ fingerprint,
9214
+ requiresHuman: false,
9215
+ source: "manager-docs",
9216
+ labels: ["manager", "docs"]
9217
+ });
9218
+ }
9219
+ return { findings, roadmapItems: roadmapItems.length, prds };
9220
+ }
9221
+ function collectPrdFiles(prdDir) {
9222
+ if (!fs24.existsSync(prdDir))
9223
+ return [];
9224
+ const files = [];
9225
+ const visit = (dir) => {
9226
+ for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
9227
+ const fullPath = path23.join(dir, entry.name);
9228
+ if (entry.isDirectory()) {
9229
+ visit(fullPath);
9230
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
9231
+ files.push({
9232
+ name: entry.name.replace(/\.md$/, ""),
9233
+ path: fullPath,
9234
+ content: fs24.readFileSync(fullPath, "utf-8")
9235
+ });
9236
+ }
9237
+ }
9238
+ };
9239
+ visit(prdDir);
9240
+ return files;
9241
+ }
9242
+ function buildSearchableWork(boardTitles, prds) {
9243
+ const values = /* @__PURE__ */ new Set();
9244
+ for (const title of boardTitles) {
9245
+ values.add(slugify(stripManagerPrefix(title)));
9246
+ }
9247
+ for (const prd of prds) {
9248
+ values.add(slugify(prd.name));
9249
+ const heading = prd.content.split("\n").find((line) => line.startsWith("# "))?.slice(2).trim();
9250
+ if (heading)
9251
+ values.add(slugify(heading));
9252
+ }
9253
+ return values;
9254
+ }
9255
+ function stripManagerPrefix(title) {
9256
+ let normalized = title.trim();
9257
+ if (normalized.toLowerCase().startsWith("[manager] ")) {
9258
+ normalized = normalized.slice("[manager] ".length).trim();
9259
+ }
9260
+ const roadmapPrefix = "roadmap item needs an owner: ";
9261
+ if (normalized.toLowerCase().startsWith(roadmapPrefix)) {
9262
+ normalized = normalized.slice(roadmapPrefix.length).trim();
9263
+ }
9264
+ return normalized;
9265
+ }
9266
+ var init_manager_analysis = __esm({
9267
+ "../core/dist/manager/manager-analysis.js"() {
9268
+ "use strict";
9269
+ init_roadmap_parser();
9270
+ init_prd_utils();
9271
+ init_manager_memory();
9272
+ }
9273
+ });
9274
+
9275
+ // ../core/dist/manager/manager-prompts.js
9276
+ function buildManagerDraftTitle(finding) {
9277
+ return `[Manager] ${finding.title}`;
9278
+ }
9279
+ function buildManagerDraftBody(finding) {
9280
+ return [
9281
+ "# PRD: Manager Draft",
9282
+ "",
9283
+ "## 1. Context",
9284
+ "",
9285
+ finding.body,
9286
+ "",
9287
+ `Source: ${finding.source}`,
9288
+ `Manager fingerprint: \`${finding.fingerprint}\``,
9289
+ "",
9290
+ "## 2. Proposed Outcome",
9291
+ "",
9292
+ "Turn this finding into a reviewed, executable PRD or close it with a short rationale.",
9293
+ "",
9294
+ "## 3. Acceptance Criteria",
9295
+ "",
9296
+ "- [ ] Confirm the finding is still relevant.",
9297
+ "- [ ] Define the implementation scope and ownership.",
9298
+ "- [ ] Add or update tests appropriate for the selected implementation.",
9299
+ "- [ ] Close this draft when the work is represented by an approved PRD or issue.",
9300
+ "",
9301
+ "## 4. Manager Notes",
9302
+ "",
9303
+ `- Kind: ${finding.kind}`,
9304
+ `- Severity: ${finding.severity}`,
9305
+ `- Requires human input: ${finding.requiresHuman ? "yes" : "no"}`
9306
+ ].join("\n");
9307
+ }
9308
+ var init_manager_prompts = __esm({
9309
+ "../core/dist/manager/manager-prompts.js"() {
9310
+ "use strict";
9311
+ }
9312
+ });
9313
+
9314
+ // ../core/dist/manager/manager-board.js
9315
+ function prepareManagerDrafts(input) {
9316
+ const boardTitles = new Set(input.boardIssues.map((issue) => normalizeTitle(issue.title)));
9317
+ const drafts = [];
9318
+ const skipped = [];
9319
+ for (const finding of input.findings) {
9320
+ const title = buildManagerDraftTitle(finding);
9321
+ if (isKnownFinding(input.memory, finding.fingerprint)) {
9322
+ skipped.push({ fingerprint: finding.fingerprint, title, reason: "memory" });
9323
+ continue;
9324
+ }
9325
+ if (boardTitles.has(normalizeTitle(title)) || boardTitles.has(normalizeTitle(finding.title))) {
9326
+ skipped.push({ fingerprint: finding.fingerprint, title, reason: "board" });
9327
+ continue;
9328
+ }
9329
+ drafts.push({
9330
+ title,
9331
+ body: buildManagerDraftBody(finding),
9332
+ labels: finding.labels,
9333
+ column: input.managerConfig.targetColumn,
9334
+ fingerprint: finding.fingerprint
9335
+ });
9336
+ }
9337
+ return { drafts, skipped };
9338
+ }
9339
+ async function createManagerBoardDrafts(input) {
9340
+ if (input.dryRun || input.outputMode !== "board-draft" || !input.provider) {
9341
+ return [];
9342
+ }
9343
+ const created = [];
9344
+ for (const draft of input.drafts) {
9345
+ const issue = await input.provider.createIssue({
9346
+ title: draft.title,
9347
+ body: draft.body,
9348
+ column: draft.column,
9349
+ labels: draft.labels
9350
+ });
9351
+ created.push({ ...draft, issue });
9352
+ }
9353
+ return created;
9354
+ }
9355
+ function normalizeTitle(title) {
9356
+ return title.toLowerCase().replace(/^\[manager\]\s*/i, "").replace(/\s+/g, " ").trim();
9357
+ }
9358
+ var init_manager_board = __esm({
9359
+ "../core/dist/manager/manager-board.js"() {
9360
+ "use strict";
9361
+ init_manager_prompts();
9362
+ init_manager_memory();
9363
+ }
9364
+ });
9365
+
9366
+ // ../core/dist/manager/manager-notifications.js
9367
+ function prepareManagerNotificationDecisions(input) {
9368
+ const blockers = input.findings.filter((finding) => finding.requiresHuman || finding.severity === "blocker");
9369
+ const decisions = [
9370
+ {
9371
+ event: "manager_blocked",
9372
+ shouldNotify: blockers.length > 0,
9373
+ title: blockers.length === 1 ? "Manager found 1 blocker" : `Manager found ${blockers.length} blockers`,
9374
+ body: blockers.map((finding) => `- ${finding.title}`).join("\n") || "No blockers found.",
9375
+ findings: blockers
9376
+ }
9377
+ ];
9378
+ const weeklyDue = isWeeklySummaryDue(input.managerConfig.weeklySummaryEnabled, input.managerConfig.weeklySummaryDay, input.memory.lastWeeklySummaryAt, input.now);
9379
+ decisions.push({
9380
+ event: "manager_weekly_summary",
9381
+ shouldNotify: weeklyDue,
9382
+ title: "Manager weekly summary",
9383
+ body: [
9384
+ `Findings: ${input.findings.length}`,
9385
+ `Blockers: ${blockers.length}`,
9386
+ `Generated at: ${input.now.toISOString()}`
9387
+ ].join("\n"),
9388
+ findings: input.findings
9389
+ });
9390
+ return decisions;
9391
+ }
9392
+ function isWeeklySummaryDue(enabled, configuredDay, lastWeeklySummaryAt, now) {
9393
+ if (!enabled || now.getDay() !== configuredDay) {
9394
+ return false;
9395
+ }
9396
+ if (!lastWeeklySummaryAt) {
9397
+ return true;
9398
+ }
9399
+ const elapsedMs = now.getTime() - lastWeeklySummaryAt.getTime();
9400
+ return elapsedMs >= 6.5 * 24 * 60 * 60 * 1e3;
9401
+ }
9402
+ var init_manager_notifications = __esm({
9403
+ "../core/dist/manager/manager-notifications.js"() {
9404
+ "use strict";
9405
+ }
9406
+ });
9407
+
9408
+ // ../core/dist/manager/manager-runner.js
9409
+ import * as fs25 from "fs";
9410
+ import * as path24 from "path";
9411
+ async function runManager(projectDir, config, options = {}) {
9412
+ const dryRun = options.dryRun ?? false;
9413
+ const now = options.now ?? /* @__PURE__ */ new Date();
9414
+ const managerConfig = resolveManagerConfig(config, projectDir);
9415
+ const memory = loadManagerMemory(managerConfig.memoryFile);
9416
+ const boardProvider = await resolveBoardProvider(config, projectDir, options.boardProvider);
9417
+ const boardIssues = await readBoardIssues(boardProvider);
9418
+ const queueStatus = resolveQueueStatus(options.queueStatus);
9419
+ const statusSnapshot = await resolveStatusSnapshot(projectDir, config, options.statusSnapshot);
9420
+ const context = {
9421
+ projectDir,
9422
+ config,
9423
+ managerConfig,
9424
+ dryRun,
9425
+ now,
9426
+ boardIssues,
9427
+ statusSnapshot,
9428
+ queueStatus
9429
+ };
9430
+ const analysis = analyzeManagerInputs(context);
9431
+ const { drafts, skipped } = prepareManagerDrafts({
9432
+ findings: analysis.findings,
9433
+ memory,
9434
+ boardIssues,
9435
+ managerConfig
9436
+ });
9437
+ const createdDrafts = await createManagerBoardDrafts({
9438
+ provider: boardProvider,
9439
+ drafts,
9440
+ dryRun,
9441
+ outputMode: managerConfig.outputMode
9442
+ });
9443
+ const notificationDecisions = prepareManagerNotificationDecisions({
9444
+ findings: analysis.findings,
9445
+ memory,
9446
+ managerConfig,
9447
+ now
9448
+ });
9449
+ const result = {
9450
+ dryRun,
9451
+ projectDir,
9452
+ config: managerConfig,
9453
+ analyzed: {
9454
+ roadmapItems: analysis.roadmapItems,
9455
+ boardIssues: boardIssues.length,
9456
+ prds: analysis.prds.length
9457
+ },
9458
+ findings: analysis.findings,
9459
+ proposedDrafts: drafts,
9460
+ createdDrafts,
9461
+ skippedFindings: skipped,
9462
+ docsWritten: [],
9463
+ memoryWritten: false,
9464
+ notificationDecisions,
9465
+ summary: ""
9466
+ };
9467
+ if (!dryRun) {
9468
+ result.docsWritten = writeManagerDocs(managerConfig.docsDirectory, result, now);
9469
+ writeManagerMemory(managerConfig.memoryFile, result, memory);
9470
+ result.memoryWritten = true;
9471
+ }
9472
+ result.summary = [
9473
+ `${analysis.findings.length} finding${analysis.findings.length === 1 ? "" : "s"} found.`,
9474
+ summarizeCreatedDrafts(createdDrafts),
9475
+ summarizeSkippedFindings(skipped)
9476
+ ].join(" ");
9477
+ return result;
9478
+ }
9479
+ function resolveManagerConfig(config, projectDir) {
9480
+ const raw = config.manager ?? {};
9481
+ const merged = {
9482
+ ...DEFAULT_MANAGER,
9483
+ ...raw,
9484
+ authority: raw.authority === "draft" || raw.authority === "ready" || raw.authority === "workflow" ? raw.authority : DEFAULT_MANAGER.authority,
9485
+ outputMode: raw.outputMode === "board-draft" || raw.outputMode === "filesystem-prd" || raw.outputMode === "report-only" ? raw.outputMode : DEFAULT_MANAGER.outputMode,
9486
+ targetColumn: raw.targetColumn ?? DEFAULT_MANAGER.targetColumn,
9487
+ weeklySummaryDay: typeof raw.weeklySummaryDay === "number" && raw.weeklySummaryDay >= 0 && raw.weeklySummaryDay <= 6 ? raw.weeklySummaryDay : DEFAULT_MANAGER.weeklySummaryDay
9488
+ };
9489
+ const weeklySummaryDay = Math.floor(merged.weeklySummaryDay);
9490
+ return {
9491
+ ...merged,
9492
+ weeklySummaryDay,
9493
+ memoryFile: path24.resolve(projectDir, merged.memoryPath),
9494
+ docsDirectory: path24.resolve(projectDir, merged.docsDir)
9495
+ };
9496
+ }
9497
+ async function resolveBoardProvider(config, projectDir, injected) {
9498
+ if (injected !== void 0) {
9499
+ return injected;
9500
+ }
9501
+ if (!config.boardProvider?.enabled) {
9502
+ return null;
9503
+ }
9504
+ return createBoardProvider(config.boardProvider, projectDir);
9505
+ }
9506
+ async function readBoardIssues(provider) {
9507
+ if (!provider)
9508
+ return [];
9509
+ try {
9510
+ return await provider.getAllIssues();
9511
+ } catch {
9512
+ return [];
9513
+ }
9514
+ }
9515
+ function resolveQueueStatus(injected) {
9516
+ if (injected !== void 0) {
9517
+ return injected;
9518
+ }
9519
+ try {
9520
+ return getQueueStatus();
9521
+ } catch {
9522
+ return null;
9523
+ }
9524
+ }
9525
+ async function resolveStatusSnapshot(projectDir, config, injected) {
9526
+ if (injected !== void 0) {
9527
+ return injected;
9528
+ }
9529
+ try {
9530
+ return await fetchStatusSnapshot(projectDir, config);
9531
+ } catch {
9532
+ return null;
9533
+ }
9534
+ }
9535
+ function writeManagerDocs(docsDir, result, now) {
9536
+ const overviewPath = path24.resolve(docsDir, "overview.md");
9537
+ const relative3 = path24.relative(docsDir, overviewPath);
9538
+ if (relative3.startsWith("..") || path24.isAbsolute(relative3)) {
9539
+ throw new Error(`Refusing to write Manager docs outside docsDir: ${overviewPath}`);
9540
+ }
9541
+ fs25.mkdirSync(docsDir, { recursive: true });
9542
+ fs25.writeFileSync(overviewPath, [
9543
+ "# Manager Overview",
9544
+ "",
9545
+ `Generated: ${now.toISOString()}`,
9546
+ "",
9547
+ `Findings: ${result.findings.length}`,
9548
+ `Proposed drafts: ${result.proposedDrafts.length}`,
9549
+ `Created drafts: ${result.createdDrafts.length}`,
9550
+ ""
9551
+ ].join("\n"), "utf-8");
9552
+ return [overviewPath];
9553
+ }
9554
+ var init_manager_runner = __esm({
9555
+ "../core/dist/manager/manager-runner.js"() {
9556
+ "use strict";
9557
+ init_factory();
9558
+ init_constants();
9559
+ init_job_queue();
9560
+ init_status_data();
9561
+ init_manager_analysis();
9562
+ init_manager_board();
9563
+ init_manager_memory();
9564
+ init_manager_notifications();
9565
+ }
9566
+ });
9567
+
9568
+ // ../core/dist/manager/manager-types.js
9569
+ var init_manager_types = __esm({
9570
+ "../core/dist/manager/manager-types.js"() {
9571
+ "use strict";
9572
+ }
9573
+ });
9574
+
9575
+ // ../core/dist/manager/index.js
9576
+ var init_manager = __esm({
9577
+ "../core/dist/manager/index.js"() {
9578
+ "use strict";
9579
+ init_manager_analysis();
9580
+ init_manager_board();
9581
+ init_manager_memory();
9582
+ init_manager_notifications();
9583
+ init_manager_prompts();
9584
+ init_manager_runner();
9585
+ init_manager_types();
9586
+ }
9587
+ });
9588
+
8932
9589
  // ../core/dist/feedback/outcome-parser.js
8933
9590
  function stripAnsi2(value) {
8934
9591
  return value.replace(ANSI_PATTERN, "");
@@ -9791,6 +10448,17 @@ __export(dist_exports, {
9791
10448
  DEFAULT_FEEDBACK: () => DEFAULT_FEEDBACK,
9792
10449
  DEFAULT_JOB_PROVIDERS: () => DEFAULT_JOB_PROVIDERS,
9793
10450
  DEFAULT_LOCAL_BOARD_INFO: () => DEFAULT_LOCAL_BOARD_INFO,
10451
+ DEFAULT_MANAGER: () => DEFAULT_MANAGER,
10452
+ DEFAULT_MANAGER_AUTHORITY: () => DEFAULT_MANAGER_AUTHORITY,
10453
+ DEFAULT_MANAGER_DOCS_DIR: () => DEFAULT_MANAGER_DOCS_DIR,
10454
+ DEFAULT_MANAGER_ENABLED: () => DEFAULT_MANAGER_ENABLED,
10455
+ DEFAULT_MANAGER_MAX_RUNTIME: () => DEFAULT_MANAGER_MAX_RUNTIME,
10456
+ DEFAULT_MANAGER_MEMORY_PATH: () => DEFAULT_MANAGER_MEMORY_PATH,
10457
+ DEFAULT_MANAGER_OUTPUT_MODE: () => DEFAULT_MANAGER_OUTPUT_MODE,
10458
+ DEFAULT_MANAGER_SCHEDULE: () => DEFAULT_MANAGER_SCHEDULE,
10459
+ DEFAULT_MANAGER_TARGET_COLUMN: () => DEFAULT_MANAGER_TARGET_COLUMN,
10460
+ DEFAULT_MANAGER_WEEKLY_SUMMARY_DAY: () => DEFAULT_MANAGER_WEEKLY_SUMMARY_DAY,
10461
+ DEFAULT_MANAGER_WEEKLY_SUMMARY_ENABLED: () => DEFAULT_MANAGER_WEEKLY_SUMMARY_ENABLED,
9794
10462
  DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
9795
10463
  DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
9796
10464
  DEFAULT_MAX_RUNTIME: () => DEFAULT_MAX_RUNTIME,
@@ -9868,6 +10536,7 @@ __export(dist_exports, {
9868
10536
  LOG_FILE_NAMES: () => LOG_FILE_NAMES,
9869
10537
  LocalKanbanProvider: () => LocalKanbanProvider,
9870
10538
  Logger: () => Logger,
10539
+ MANAGER_LOG_NAME: () => MANAGER_LOG_NAME,
9871
10540
  MAX_HISTORY_RECORDS_PER_PRD: () => MAX_HISTORY_RECORDS_PER_PRD,
9872
10541
  MERGER_LOG_NAME: () => MERGER_LOG_NAME,
9873
10542
  NIGHT_WATCH_LABELS: () => NIGHT_WATCH_LABELS,
@@ -9896,9 +10565,12 @@ __export(dist_exports, {
9896
10565
  addEntry: () => addEntry,
9897
10566
  analyticsLockPath: () => analyticsLockPath,
9898
10567
  analyzeFeedbackOutcome: () => analyzeFeedbackOutcome,
10568
+ analyzeManagerInputs: () => analyzeManagerInputs,
9899
10569
  auditLockPath: () => auditLockPath,
9900
10570
  buildDescription: () => buildDescription,
9901
10571
  buildJobEnvOverrides: () => buildJobEnvOverrides,
10572
+ buildManagerDraftBody: () => buildManagerDraftBody,
10573
+ buildManagerDraftTitle: () => buildManagerDraftTitle,
9902
10574
  buildProjectFeedbackPromptBlock: () => buildProjectFeedbackPromptBlock,
9903
10575
  buildSessionOutcomeInput: () => buildSessionOutcomeInput,
9904
10576
  calculateStringSimilarity: () => calculateStringSimilarity,
@@ -9933,7 +10605,9 @@ __export(dist_exports, {
9933
10605
  createBoardProvider: () => createBoardProvider,
9934
10606
  createDbForDir: () => createDbForDir,
9935
10607
  createEmptyState: () => createEmptyState,
10608
+ createFindingFingerprint: () => createFindingFingerprint,
9936
10609
  createLogger: () => createLogger,
10610
+ createManagerBoardDrafts: () => createManagerBoardDrafts,
9937
10611
  createSlicerPromptVars: () => createSlicerPromptVars,
9938
10612
  createSpinner: () => createSpinner,
9939
10613
  createTable: () => createTable,
@@ -10027,20 +10701,24 @@ __export(dist_exports, {
10027
10701
  isInCooldown: () => isInCooldown,
10028
10702
  isItemProcessed: () => isItemProcessed,
10029
10703
  isJobTypeEnabled: () => isJobTypeEnabled,
10704
+ isKnownFinding: () => isKnownFinding,
10030
10705
  isProcessAliveSince: () => isProcessAliveSince,
10031
10706
  isProcessRunning: () => isProcessRunning,
10032
10707
  isValidCategory: () => isValidCategory,
10033
10708
  isValidHorizon: () => isValidHorizon,
10034
10709
  isValidPriority: () => isValidPriority,
10710
+ isWeeklySummaryDue: () => isWeeklySummaryDue,
10035
10711
  label: () => label,
10036
10712
  listPrdStatesByStatus: () => listPrdStatesByStatus,
10037
10713
  loadAuditFindings: () => loadAuditFindings,
10038
10714
  loadConfig: () => loadConfig,
10039
10715
  loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
10040
10716
  loadHistory: () => loadHistory,
10717
+ loadManagerMemory: () => loadManagerMemory,
10041
10718
  loadRegistry: () => loadRegistry,
10042
10719
  loadRoadmapState: () => loadRoadmapState,
10043
10720
  loadSlicerTemplate: () => loadSlicerTemplate,
10721
+ managerLockPath: () => managerLockPath,
10044
10722
  markItemProcessed: () => markItemProcessed,
10045
10723
  markJobRunning: () => markJobRunning,
10046
10724
  markPrdDone: () => markPrdDone,
@@ -10060,6 +10738,8 @@ __export(dist_exports, {
10060
10738
  prResolverLockPath: () => prResolverLockPath,
10061
10739
  prepareBranchWorktree: () => prepareBranchWorktree,
10062
10740
  prepareDetachedWorktree: () => prepareDetachedWorktree,
10741
+ prepareManagerDrafts: () => prepareManagerDrafts,
10742
+ prepareManagerNotificationDecisions: () => prepareManagerNotificationDecisions,
10063
10743
  projectRuntimeKey: () => projectRuntimeKey,
10064
10744
  pruneProjectData: () => pruneProjectData,
10065
10745
  qaLockPath: () => qaLockPath,
@@ -10074,11 +10754,13 @@ __export(dist_exports, {
10074
10754
  removeEntriesForProject: () => removeEntriesForProject,
10075
10755
  removeJob: () => removeJob,
10076
10756
  removeProject: () => removeProject,
10757
+ renderManagerMemory: () => renderManagerMemory,
10077
10758
  renderPrdTemplate: () => renderPrdTemplate,
10078
10759
  renderProjectFeedbackBlock: () => renderProjectFeedbackBlock,
10079
10760
  renderSlicerPrompt: () => renderSlicerPrompt,
10080
10761
  resetRepositories: () => resetRepositories,
10081
10762
  resolveJobProvider: () => resolveJobProvider,
10763
+ resolveManagerConfig: () => resolveManagerConfig,
10082
10764
  resolvePreset: () => resolvePreset,
10083
10765
  resolveProviderBucketKey: () => resolveProviderBucketKey,
10084
10766
  resolveWorktreeBaseRef: () => resolveWorktreeBaseRef,
@@ -10086,6 +10768,7 @@ __export(dist_exports, {
10086
10768
  rotateLog: () => rotateLog,
10087
10769
  runAllChecks: () => runAllChecks,
10088
10770
  runAnalytics: () => runAnalytics,
10771
+ runManager: () => runManager,
10089
10772
  runMigrations: () => runMigrations,
10090
10773
  saveConfig: () => saveConfig,
10091
10774
  saveGlobalNotificationsConfig: () => saveGlobalNotificationsConfig,
@@ -10104,6 +10787,8 @@ __export(dist_exports, {
10104
10787
  sortByPriority: () => sortByPriority,
10105
10788
  step: () => step,
10106
10789
  success: () => success,
10790
+ summarizeCreatedDrafts: () => summarizeCreatedDrafts,
10791
+ summarizeSkippedFindings: () => summarizeSkippedFindings,
10107
10792
  syncAuditFindingsToBoard: () => syncAuditFindingsToBoard,
10108
10793
  unmarkItemProcessed: () => unmarkItemProcessed,
10109
10794
  unregisterProject: () => unregisterProject,
@@ -10113,6 +10798,7 @@ __export(dist_exports, {
10113
10798
  validateWebhook: () => validateWebhook,
10114
10799
  warn: () => warn,
10115
10800
  writeCrontab: () => writeCrontab,
10801
+ writeManagerMemory: () => writeManagerMemory,
10116
10802
  writePrdState: () => writePrdState
10117
10803
  });
10118
10804
  var init_dist = __esm({
@@ -10163,6 +10849,7 @@ var init_dist = __esm({
10163
10849
  init_summary();
10164
10850
  init_analytics();
10165
10851
  init_audit();
10852
+ init_manager();
10166
10853
  init_outcome_parser();
10167
10854
  init_pattern_analyzer();
10168
10855
  init_prompt_augmenter();
@@ -10175,30 +10862,30 @@ var init_dist = __esm({
10175
10862
  // src/cli.ts
10176
10863
  import "reflect-metadata";
10177
10864
  import { Command as Command3 } from "commander";
10178
- import { existsSync as existsSync34, readFileSync as readFileSync21 } from "fs";
10865
+ import { existsSync as existsSync36, readFileSync as readFileSync23 } from "fs";
10179
10866
  import { fileURLToPath as fileURLToPath6 } from "url";
10180
- import { dirname as dirname12, join as join38 } from "path";
10867
+ import { dirname as dirname13, join as join39 } from "path";
10181
10868
 
10182
10869
  // src/commands/init.ts
10183
10870
  init_dist();
10184
- import fs23 from "fs";
10185
- import path22 from "path";
10871
+ import fs26 from "fs";
10872
+ import path25 from "path";
10186
10873
  import { execSync as execSync3 } from "child_process";
10187
10874
  import { fileURLToPath as fileURLToPath3 } from "url";
10188
- import { dirname as dirname6, join as join20 } from "path";
10875
+ import { dirname as dirname7, join as join21 } from "path";
10189
10876
  import * as readline from "readline";
10190
10877
  var __filename2 = fileURLToPath3(import.meta.url);
10191
- var __dirname2 = dirname6(__filename2);
10878
+ var __dirname2 = dirname7(__filename2);
10192
10879
  function findTemplatesDir(startDir) {
10193
10880
  let d = startDir;
10194
10881
  for (let i = 0; i < 8; i++) {
10195
- const candidate = join20(d, "templates");
10196
- if (fs23.existsSync(candidate) && fs23.statSync(candidate).isDirectory()) {
10882
+ const candidate = join21(d, "templates");
10883
+ if (fs26.existsSync(candidate) && fs26.statSync(candidate).isDirectory()) {
10197
10884
  return candidate;
10198
10885
  }
10199
- d = dirname6(d);
10886
+ d = dirname7(d);
10200
10887
  }
10201
- return join20(startDir, "templates");
10888
+ return join21(startDir, "templates");
10202
10889
  }
10203
10890
  var TEMPLATES_DIR = findTemplatesDir(__dirname2);
10204
10891
  var NW_SKILLS = [
@@ -10210,12 +10897,12 @@ var NW_SKILLS = [
10210
10897
  "nw-review"
10211
10898
  ];
10212
10899
  function hasPlaywrightDependency(cwd) {
10213
- const packageJsonPath = path22.join(cwd, "package.json");
10214
- if (!fs23.existsSync(packageJsonPath)) {
10900
+ const packageJsonPath = path25.join(cwd, "package.json");
10901
+ if (!fs26.existsSync(packageJsonPath)) {
10215
10902
  return false;
10216
10903
  }
10217
10904
  try {
10218
- const packageJson2 = JSON.parse(fs23.readFileSync(packageJsonPath, "utf-8"));
10905
+ const packageJson2 = JSON.parse(fs26.readFileSync(packageJsonPath, "utf-8"));
10219
10906
  return Boolean(
10220
10907
  packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
10221
10908
  );
@@ -10227,7 +10914,7 @@ function detectPlaywright(cwd) {
10227
10914
  if (hasPlaywrightDependency(cwd)) {
10228
10915
  return true;
10229
10916
  }
10230
- if (fs23.existsSync(path22.join(cwd, "node_modules", ".bin", "playwright"))) {
10917
+ if (fs26.existsSync(path25.join(cwd, "node_modules", ".bin", "playwright"))) {
10231
10918
  return true;
10232
10919
  }
10233
10920
  try {
@@ -10243,10 +10930,10 @@ function detectPlaywright(cwd) {
10243
10930
  }
10244
10931
  }
10245
10932
  function resolvePlaywrightInstallCommand(cwd) {
10246
- if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) {
10933
+ if (fs26.existsSync(path25.join(cwd, "pnpm-lock.yaml"))) {
10247
10934
  return "pnpm add -D @playwright/test";
10248
10935
  }
10249
- if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) {
10936
+ if (fs26.existsSync(path25.join(cwd, "yarn.lock"))) {
10250
10937
  return "yarn add -D @playwright/test";
10251
10938
  }
10252
10939
  return "npm install -D @playwright/test";
@@ -10255,7 +10942,7 @@ function promptYesNo(question, defaultNo = true) {
10255
10942
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
10256
10943
  return Promise.resolve(false);
10257
10944
  }
10258
- return new Promise((resolve9) => {
10945
+ return new Promise((resolve11) => {
10259
10946
  const rl = readline.createInterface({
10260
10947
  input: process.stdin,
10261
10948
  output: process.stdout
@@ -10265,10 +10952,10 @@ function promptYesNo(question, defaultNo = true) {
10265
10952
  rl.close();
10266
10953
  const normalized = answer.trim().toLowerCase();
10267
10954
  if (normalized === "") {
10268
- resolve9(!defaultNo);
10955
+ resolve11(!defaultNo);
10269
10956
  return;
10270
10957
  }
10271
- resolve9(normalized === "y" || normalized === "yes");
10958
+ resolve11(normalized === "y" || normalized === "yes");
10272
10959
  });
10273
10960
  });
10274
10961
  }
@@ -10371,7 +11058,7 @@ function getDefaultBranch(cwd) {
10371
11058
  }
10372
11059
  }
10373
11060
  function promptProviderSelection(providers) {
10374
- return new Promise((resolve9, reject) => {
11061
+ return new Promise((resolve11, reject) => {
10375
11062
  const rl = readline.createInterface({
10376
11063
  input: process.stdin,
10377
11064
  output: process.stdout
@@ -10387,13 +11074,13 @@ function promptProviderSelection(providers) {
10387
11074
  reject(new Error("Invalid selection. Please run init again and select a valid number."));
10388
11075
  return;
10389
11076
  }
10390
- resolve9(providers[selection - 1]);
11077
+ resolve11(providers[selection - 1]);
10391
11078
  });
10392
11079
  });
10393
11080
  }
10394
11081
  function ensureDir(dirPath) {
10395
- if (!fs23.existsSync(dirPath)) {
10396
- fs23.mkdirSync(dirPath, { recursive: true });
11082
+ if (!fs26.existsSync(dirPath)) {
11083
+ fs26.mkdirSync(dirPath, { recursive: true });
10397
11084
  }
10398
11085
  }
10399
11086
  function buildInitConfig(params) {
@@ -10443,6 +11130,7 @@ function buildInitConfig(params) {
10443
11130
  },
10444
11131
  audit: { ...defaults.audit },
10445
11132
  analytics: { ...defaults.analytics },
11133
+ manager: { ...defaults.manager },
10446
11134
  feedback: { ...defaults.feedback },
10447
11135
  merger: { ...defaults.merger },
10448
11136
  prResolver: { ...defaults.prResolver },
@@ -10467,30 +11155,30 @@ function buildInitConfig(params) {
10467
11155
  }
10468
11156
  function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
10469
11157
  if (customTemplatesDir !== null) {
10470
- const customPath = join20(customTemplatesDir, templateName);
10471
- if (fs23.existsSync(customPath)) {
11158
+ const customPath = join21(customTemplatesDir, templateName);
11159
+ if (fs26.existsSync(customPath)) {
10472
11160
  return { path: customPath, source: "custom" };
10473
11161
  }
10474
11162
  }
10475
- return { path: join20(bundledTemplatesDir, templateName), source: "bundled" };
11163
+ return { path: join21(bundledTemplatesDir, templateName), source: "bundled" };
10476
11164
  }
10477
11165
  function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
10478
- if (fs23.existsSync(targetPath) && !force) {
11166
+ if (fs26.existsSync(targetPath) && !force) {
10479
11167
  console.log(` Skipped (exists): ${targetPath}`);
10480
11168
  return { created: false, source: source ?? "bundled" };
10481
11169
  }
10482
- const templatePath = sourcePath ?? join20(TEMPLATES_DIR, templateName);
11170
+ const templatePath = sourcePath ?? join21(TEMPLATES_DIR, templateName);
10483
11171
  const resolvedSource = source ?? "bundled";
10484
- let content = fs23.readFileSync(templatePath, "utf-8");
11172
+ let content = fs26.readFileSync(templatePath, "utf-8");
10485
11173
  for (const [key, value] of Object.entries(replacements)) {
10486
11174
  content = content.replaceAll(key, value);
10487
11175
  }
10488
- fs23.writeFileSync(targetPath, content);
11176
+ fs26.writeFileSync(targetPath, content);
10489
11177
  console.log(` Created: ${targetPath} (${resolvedSource})`);
10490
11178
  return { created: true, source: resolvedSource };
10491
11179
  }
10492
11180
  function addToGitignore(cwd) {
10493
- const gitignorePath = path22.join(cwd, ".gitignore");
11181
+ const gitignorePath = path25.join(cwd, ".gitignore");
10494
11182
  const entries = [
10495
11183
  {
10496
11184
  pattern: "/logs/",
@@ -10504,13 +11192,13 @@ function addToGitignore(cwd) {
10504
11192
  },
10505
11193
  { pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
10506
11194
  ];
10507
- if (!fs23.existsSync(gitignorePath)) {
11195
+ if (!fs26.existsSync(gitignorePath)) {
10508
11196
  const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
10509
- fs23.writeFileSync(gitignorePath, lines.join("\n"));
11197
+ fs26.writeFileSync(gitignorePath, lines.join("\n"));
10510
11198
  console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
10511
11199
  return;
10512
11200
  }
10513
- const content = fs23.readFileSync(gitignorePath, "utf-8");
11201
+ const content = fs26.readFileSync(gitignorePath, "utf-8");
10514
11202
  const missing = entries.filter((e) => !e.check(content));
10515
11203
  if (missing.length === 0) {
10516
11204
  console.log(` Skipped (exists): Night Watch entries in .gitignore`);
@@ -10518,59 +11206,59 @@ function addToGitignore(cwd) {
10518
11206
  }
10519
11207
  const additions = missing.map((e) => e.pattern).join("\n");
10520
11208
  const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
10521
- fs23.writeFileSync(gitignorePath, newContent);
11209
+ fs26.writeFileSync(gitignorePath, newContent);
10522
11210
  console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
10523
11211
  }
10524
11212
  function installSkills(cwd, provider, force, templatesDir) {
10525
- const skillsTemplatesDir = path22.join(templatesDir, "skills");
10526
- if (!fs23.existsSync(skillsTemplatesDir)) {
11213
+ const skillsTemplatesDir = path25.join(templatesDir, "skills");
11214
+ if (!fs26.existsSync(skillsTemplatesDir)) {
10527
11215
  return { location: "", installed: 0, skipped: 0, type: "none" };
10528
11216
  }
10529
11217
  const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
10530
11218
  const isCodexProvider = provider === "codex";
10531
- const claudeDir = path22.join(cwd, ".claude");
10532
- if (isClaudeProvider || fs23.existsSync(claudeDir)) {
11219
+ const claudeDir = path25.join(cwd, ".claude");
11220
+ if (isClaudeProvider || fs26.existsSync(claudeDir)) {
10533
11221
  ensureDir(claudeDir);
10534
- const skillsDir = path22.join(claudeDir, "skills");
11222
+ const skillsDir = path25.join(claudeDir, "skills");
10535
11223
  ensureDir(skillsDir);
10536
11224
  let installed = 0;
10537
11225
  let skipped = 0;
10538
11226
  for (const skillName of NW_SKILLS) {
10539
- const templateFile = path22.join(skillsTemplatesDir, `${skillName}.md`);
10540
- if (!fs23.existsSync(templateFile)) continue;
10541
- const skillDir = path22.join(skillsDir, skillName);
11227
+ const templateFile = path25.join(skillsTemplatesDir, `${skillName}.md`);
11228
+ if (!fs26.existsSync(templateFile)) continue;
11229
+ const skillDir = path25.join(skillsDir, skillName);
10542
11230
  ensureDir(skillDir);
10543
- const target = path22.join(skillDir, "SKILL.md");
10544
- if (fs23.existsSync(target) && !force) {
11231
+ const target = path25.join(skillDir, "SKILL.md");
11232
+ if (fs26.existsSync(target) && !force) {
10545
11233
  skipped++;
10546
11234
  continue;
10547
11235
  }
10548
- fs23.copyFileSync(templateFile, target);
11236
+ fs26.copyFileSync(templateFile, target);
10549
11237
  installed++;
10550
11238
  }
10551
11239
  return { location: ".claude/skills/", installed, skipped, type: "claude" };
10552
11240
  }
10553
11241
  if (isCodexProvider) {
10554
- const agentsFile = path22.join(cwd, "AGENTS.md");
10555
- const blockFile = path22.join(skillsTemplatesDir, "_codex-block.md");
10556
- if (!fs23.existsSync(blockFile)) {
11242
+ const agentsFile = path25.join(cwd, "AGENTS.md");
11243
+ const blockFile = path25.join(skillsTemplatesDir, "_codex-block.md");
11244
+ if (!fs26.existsSync(blockFile)) {
10557
11245
  return { location: "", installed: 0, skipped: 0, type: "none" };
10558
11246
  }
10559
- const block = fs23.readFileSync(blockFile, "utf-8");
11247
+ const block = fs26.readFileSync(blockFile, "utf-8");
10560
11248
  const marker = "## Night Watch Skills";
10561
- if (!fs23.existsSync(agentsFile)) {
10562
- fs23.writeFileSync(agentsFile, block);
11249
+ if (!fs26.existsSync(agentsFile)) {
11250
+ fs26.writeFileSync(agentsFile, block);
10563
11251
  return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
10564
11252
  }
10565
- const existing = fs23.readFileSync(agentsFile, "utf-8");
11253
+ const existing = fs26.readFileSync(agentsFile, "utf-8");
10566
11254
  if (existing.includes(marker)) {
10567
11255
  if (!force) {
10568
11256
  return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
10569
11257
  }
10570
11258
  const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
10571
- fs23.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
11259
+ fs26.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
10572
11260
  } else {
10573
- fs23.appendFileSync(agentsFile, "\n\n" + block);
11261
+ fs26.appendFileSync(agentsFile, "\n\n" + block);
10574
11262
  }
10575
11263
  return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
10576
11264
  }
@@ -10694,28 +11382,28 @@ function initCommand(program2) {
10694
11382
  "${DEFAULT_BRANCH}": defaultBranch
10695
11383
  };
10696
11384
  step(6, totalSteps, "Creating PRD directory structure...");
10697
- const prdDirPath = path22.join(cwd, prdDir);
10698
- const doneDirPath = path22.join(prdDirPath, "done");
11385
+ const prdDirPath = path25.join(cwd, prdDir);
11386
+ const doneDirPath = path25.join(prdDirPath, "done");
10699
11387
  ensureDir(doneDirPath);
10700
11388
  success(`Created ${prdDirPath}/`);
10701
11389
  success(`Created ${doneDirPath}/`);
10702
11390
  step(7, totalSteps, "Creating logs directory...");
10703
- const logsPath = path22.join(cwd, LOG_DIR);
11391
+ const logsPath = path25.join(cwd, LOG_DIR);
10704
11392
  ensureDir(logsPath);
10705
11393
  success(`Created ${logsPath}/`);
10706
11394
  addToGitignore(cwd);
10707
11395
  step(8, totalSteps, "Creating instructions directory...");
10708
- const instructionsDir = path22.join(cwd, "instructions");
11396
+ const instructionsDir = path25.join(cwd, "instructions");
10709
11397
  ensureDir(instructionsDir);
10710
11398
  success(`Created ${instructionsDir}/`);
10711
11399
  const existingConfig = loadConfig(cwd);
10712
- const customTemplatesDirPath = path22.join(cwd, existingConfig.templatesDir);
10713
- const customTemplatesDir = fs23.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
11400
+ const customTemplatesDirPath = path25.join(cwd, existingConfig.templatesDir);
11401
+ const customTemplatesDir = fs26.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
10714
11402
  const templateSources = [];
10715
11403
  const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
10716
11404
  const nwResult = processTemplate(
10717
11405
  "executor.md",
10718
- path22.join(instructionsDir, "executor.md"),
11406
+ path25.join(instructionsDir, "executor.md"),
10719
11407
  replacements,
10720
11408
  force,
10721
11409
  nwResolution.path,
@@ -10729,7 +11417,7 @@ function initCommand(program2) {
10729
11417
  );
10730
11418
  const peResult = processTemplate(
10731
11419
  "prd-executor.md",
10732
- path22.join(instructionsDir, "prd-executor.md"),
11420
+ path25.join(instructionsDir, "prd-executor.md"),
10733
11421
  replacements,
10734
11422
  force,
10735
11423
  peResolution.path,
@@ -10739,7 +11427,7 @@ function initCommand(program2) {
10739
11427
  const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
10740
11428
  const prResult = processTemplate(
10741
11429
  "pr-reviewer.md",
10742
- path22.join(instructionsDir, "pr-reviewer.md"),
11430
+ path25.join(instructionsDir, "pr-reviewer.md"),
10743
11431
  replacements,
10744
11432
  force,
10745
11433
  prResolution.path,
@@ -10749,7 +11437,7 @@ function initCommand(program2) {
10749
11437
  const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
10750
11438
  const qaResult = processTemplate(
10751
11439
  "qa.md",
10752
- path22.join(instructionsDir, "qa.md"),
11440
+ path25.join(instructionsDir, "qa.md"),
10753
11441
  replacements,
10754
11442
  force,
10755
11443
  qaResolution.path,
@@ -10759,7 +11447,7 @@ function initCommand(program2) {
10759
11447
  const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
10760
11448
  const auditResult = processTemplate(
10761
11449
  "audit.md",
10762
- path22.join(instructionsDir, "audit.md"),
11450
+ path25.join(instructionsDir, "audit.md"),
10763
11451
  replacements,
10764
11452
  force,
10765
11453
  auditResolution.path,
@@ -10773,7 +11461,7 @@ function initCommand(program2) {
10773
11461
  );
10774
11462
  const plannerResult = processTemplate(
10775
11463
  "prd-creator.md",
10776
- path22.join(instructionsDir, "prd-creator.md"),
11464
+ path25.join(instructionsDir, "prd-creator.md"),
10777
11465
  replacements,
10778
11466
  force,
10779
11467
  plannerResolution.path,
@@ -10781,8 +11469,8 @@ function initCommand(program2) {
10781
11469
  );
10782
11470
  templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
10783
11471
  step(9, totalSteps, "Creating configuration file...");
10784
- const configPath = path22.join(cwd, CONFIG_FILE_NAME);
10785
- if (fs23.existsSync(configPath) && !force) {
11472
+ const configPath = path25.join(cwd, CONFIG_FILE_NAME);
11473
+ if (fs26.existsSync(configPath) && !force) {
10786
11474
  console.log(` Skipped (exists): ${configPath}`);
10787
11475
  } else {
10788
11476
  const config = buildInitConfig({
@@ -10792,11 +11480,11 @@ function initCommand(program2) {
10792
11480
  reviewerEnabled,
10793
11481
  prdDir
10794
11482
  });
10795
- fs23.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
11483
+ fs26.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
10796
11484
  success(`Created ${configPath}`);
10797
11485
  }
10798
11486
  step(10, totalSteps, "Setting up GitHub Project board...");
10799
- const existingRaw = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
11487
+ const existingRaw = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
10800
11488
  const existingBoard = existingRaw.boardProvider;
10801
11489
  let boardSetupStatus = "Skipped";
10802
11490
  if (existingBoard?.projectNumber && !force) {
@@ -10818,14 +11506,14 @@ function initCommand(program2) {
10818
11506
  const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
10819
11507
  const boardTitle = `${projectName} Night Watch`;
10820
11508
  const board = await provider.setupBoard(boardTitle);
10821
- const rawConfig = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
11509
+ const rawConfig = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
10822
11510
  rawConfig.boardProvider = {
10823
11511
  enabled: true,
10824
11512
  provider: "github",
10825
11513
  projectNumber: board.number,
10826
11514
  projectTitle: board.title
10827
11515
  };
10828
- fs23.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
11516
+ fs26.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
10829
11517
  boardSetupStatus = `Created (#${board.number})`;
10830
11518
  success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
10831
11519
  } catch (boardErr) {
@@ -10987,7 +11675,7 @@ async function maybeApplyCronSchedulingDelay(config, jobType, projectDir) {
10987
11675
  return plan;
10988
11676
  }
10989
11677
  if (plan.totalDelayMinutes > 0) {
10990
- await new Promise((resolve9) => setTimeout(resolve9, plan.totalDelayMinutes * 6e4));
11678
+ await new Promise((resolve11) => setTimeout(resolve11, plan.totalDelayMinutes * 6e4));
10991
11679
  }
10992
11680
  return getSchedulingPlan(projectDir, config, jobType);
10993
11681
  }
@@ -11051,8 +11739,8 @@ function recordJobOutcome(input) {
11051
11739
  }
11052
11740
 
11053
11741
  // src/commands/run.ts
11054
- import * as fs24 from "fs";
11055
- import * as path23 from "path";
11742
+ import * as fs27 from "fs";
11743
+ import * as path26 from "path";
11056
11744
  function resolveRunNotificationEvent(exitCode, scriptStatus) {
11057
11745
  if (exitCode === 124) {
11058
11746
  return "run_timeout";
@@ -11150,7 +11838,7 @@ function buildRunNotificationContext(config, projectDir, event, exitCode, script
11150
11838
  const checkpointStatus = checkpointValue === "created" || checkpointValue === "available" || checkpointValue === "none" ? checkpointValue : void 0;
11151
11839
  return {
11152
11840
  event,
11153
- projectName: path23.basename(projectDir),
11841
+ projectName: path26.basename(projectDir),
11154
11842
  exitCode,
11155
11843
  provider: config.provider,
11156
11844
  prdName: scriptResult?.data.prd ?? extractResultValueFromOutput(rawOutput, "prd"),
@@ -11170,12 +11858,12 @@ function buildRunNotificationContext(config, projectDir, event, exitCode, script
11170
11858
  };
11171
11859
  }
11172
11860
  function getCrossProjectFallbackCandidates(currentProjectDir) {
11173
- const current = path23.resolve(currentProjectDir);
11861
+ const current = path26.resolve(currentProjectDir);
11174
11862
  const { valid, invalid } = validateRegistry();
11175
11863
  for (const entry of invalid) {
11176
11864
  warn(`Skipping invalid registry entry: ${entry.path}`);
11177
11865
  }
11178
- return valid.filter((entry) => path23.resolve(entry.path) !== current);
11866
+ return valid.filter((entry) => path26.resolve(entry.path) !== current);
11179
11867
  }
11180
11868
  async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult, rawOutput) {
11181
11869
  if (isRateLimitFallbackTriggered(scriptResult?.data)) {
@@ -11185,7 +11873,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
11185
11873
  if (nonTelegramWebhooks.length > 0) {
11186
11874
  const _rateLimitCtx = {
11187
11875
  event: "rate_limit_fallback",
11188
- projectName: path23.basename(projectDir),
11876
+ projectName: path26.basename(projectDir),
11189
11877
  exitCode,
11190
11878
  provider: config.provider
11191
11879
  };
@@ -11423,20 +12111,20 @@ function applyCliOverrides(config, options) {
11423
12111
  }
11424
12112
  function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
11425
12113
  const claimStaleAfter = maxRuntime > 0 ? maxRuntime : 14400;
11426
- const absolutePrdDir = path23.join(projectDir, prdDir);
11427
- const doneDir = path23.join(absolutePrdDir, "done");
12114
+ const absolutePrdDir = path26.join(projectDir, prdDir);
12115
+ const doneDir = path26.join(absolutePrdDir, "done");
11428
12116
  const pending = [];
11429
12117
  const completed = [];
11430
- if (fs24.existsSync(absolutePrdDir)) {
11431
- const entries = fs24.readdirSync(absolutePrdDir, { withFileTypes: true });
12118
+ if (fs27.existsSync(absolutePrdDir)) {
12119
+ const entries = fs27.readdirSync(absolutePrdDir, { withFileTypes: true });
11432
12120
  for (const entry of entries) {
11433
12121
  if (entry.isFile() && entry.name.endsWith(".md")) {
11434
- const claimPath = path23.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
12122
+ const claimPath = path26.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
11435
12123
  let claimed = false;
11436
12124
  let claimInfo = null;
11437
- if (fs24.existsSync(claimPath)) {
12125
+ if (fs27.existsSync(claimPath)) {
11438
12126
  try {
11439
- const content = fs24.readFileSync(claimPath, "utf-8");
12127
+ const content = fs27.readFileSync(claimPath, "utf-8");
11440
12128
  const data = JSON.parse(content);
11441
12129
  const age = Math.floor(Date.now() / 1e3) - data.timestamp;
11442
12130
  if (age < claimStaleAfter) {
@@ -11450,8 +12138,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
11450
12138
  }
11451
12139
  }
11452
12140
  }
11453
- if (fs24.existsSync(doneDir)) {
11454
- const entries = fs24.readdirSync(doneDir, { withFileTypes: true });
12141
+ if (fs27.existsSync(doneDir)) {
12142
+ const entries = fs27.readdirSync(doneDir, { withFileTypes: true });
11455
12143
  for (const entry of entries) {
11456
12144
  if (entry.isFile() && entry.name.endsWith(".md")) {
11457
12145
  completed.push(entry.name);
@@ -11636,7 +12324,7 @@ ${stderr}`
11636
12324
  // src/commands/review.ts
11637
12325
  init_dist();
11638
12326
  import { execFileSync as execFileSync5 } from "child_process";
11639
- import * as path24 from "path";
12327
+ import * as path27 from "path";
11640
12328
  function shouldSendReviewNotification(scriptStatus) {
11641
12329
  if (!scriptStatus) {
11642
12330
  return true;
@@ -11964,7 +12652,7 @@ ${stderr}`);
11964
12652
  const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
11965
12653
  await sendNotifications(config, {
11966
12654
  event: reviewEvent,
11967
- projectName: path24.basename(projectDir),
12655
+ projectName: path27.basename(projectDir),
11968
12656
  exitCode,
11969
12657
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
11970
12658
  prUrl: fallbackPrDetails?.url,
@@ -11983,7 +12671,7 @@ ${stderr}`);
11983
12671
  const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
11984
12672
  await sendNotifications(config, {
11985
12673
  event: reviewEvent,
11986
- projectName: path24.basename(projectDir),
12674
+ projectName: path27.basename(projectDir),
11987
12675
  exitCode,
11988
12676
  provider: formatProviderDisplay(
11989
12677
  envVars.NW_PROVIDER_CMD,
@@ -12008,7 +12696,7 @@ ${stderr}`);
12008
12696
  const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
12009
12697
  const _mergeCtx = {
12010
12698
  event: "pr_auto_merged",
12011
- projectName: path24.basename(projectDir),
12699
+ projectName: path27.basename(projectDir),
12012
12700
  exitCode,
12013
12701
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
12014
12702
  prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
@@ -12033,7 +12721,7 @@ ${stderr}`);
12033
12721
 
12034
12722
  // src/commands/qa.ts
12035
12723
  init_dist();
12036
- import * as path25 from "path";
12724
+ import * as path28 from "path";
12037
12725
  function shouldSendQaNotification(scriptStatus) {
12038
12726
  if (!scriptStatus) {
12039
12727
  return true;
@@ -12195,7 +12883,7 @@ ${stderr}`);
12195
12883
  const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
12196
12884
  const _qaCtx = {
12197
12885
  event: "qa_completed",
12198
- projectName: path25.basename(projectDir),
12886
+ projectName: path28.basename(projectDir),
12199
12887
  exitCode,
12200
12888
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
12201
12889
  prNumber: prDetails?.number ?? primaryQaPr,
@@ -12221,8 +12909,8 @@ ${stderr}`);
12221
12909
 
12222
12910
  // src/commands/audit.ts
12223
12911
  init_dist();
12224
- import * as fs25 from "fs";
12225
- import * as path26 from "path";
12912
+ import * as fs28 from "fs";
12913
+ import * as path29 from "path";
12226
12914
  function buildEnvVars4(config, options) {
12227
12915
  const env = buildBaseEnvVars(config, "audit", options.dryRun);
12228
12916
  env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
@@ -12270,7 +12958,7 @@ function auditCommand(program2) {
12270
12958
  if (config.audit.createIssues) {
12271
12959
  configTable.push(["Target Column", config.audit.targetColumn]);
12272
12960
  }
12273
- configTable.push(["Report File", path26.join(projectDir, "logs", "audit-report.md")]);
12961
+ configTable.push(["Report File", path29.join(projectDir, "logs", "audit-report.md")]);
12274
12962
  console.log(configTable.toString());
12275
12963
  header("Provider Invocation");
12276
12964
  const providerCmd = PROVIDER_COMMANDS[auditProvider];
@@ -12328,8 +13016,8 @@ ${stderr}`);
12328
13016
  } else if (scriptResult?.status?.startsWith("skip_")) {
12329
13017
  spinner.succeed("Code audit skipped");
12330
13018
  } else {
12331
- const reportPath = path26.join(projectDir, "logs", "audit-report.md");
12332
- if (!fs25.existsSync(reportPath)) {
13019
+ const reportPath = path29.join(projectDir, "logs", "audit-report.md");
13020
+ if (!fs28.existsSync(reportPath)) {
12333
13021
  spinner.fail("Code audit finished without a report file");
12334
13022
  process.exit(1);
12335
13023
  }
@@ -12346,9 +13034,9 @@ ${stderr}`);
12346
13034
  const providerExit = scriptResult?.data?.provider_exit;
12347
13035
  const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
12348
13036
  spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
12349
- const logPath = path26.join(projectDir, "logs", "audit.log");
12350
- if (fs25.existsSync(logPath)) {
12351
- const logLines = fs25.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
13037
+ const logPath = path29.join(projectDir, "logs", "audit.log");
13038
+ if (fs28.existsSync(logPath)) {
13039
+ const logLines = fs28.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
12352
13040
  if (logLines.length > 0) {
12353
13041
  process.stderr.write(logLines.join("\n") + "\n");
12354
13042
  }
@@ -12492,16 +13180,16 @@ function analyticsCommand(program2) {
12492
13180
  // src/commands/install.ts
12493
13181
  init_dist();
12494
13182
  import { execSync as execSync4 } from "child_process";
12495
- import * as path27 from "path";
12496
- import * as fs26 from "fs";
13183
+ import * as path30 from "path";
13184
+ import * as fs29 from "fs";
12497
13185
  function shellQuote(value) {
12498
13186
  return `'${value.replace(/'/g, `'"'"'`)}'`;
12499
13187
  }
12500
13188
  function getNightWatchBinPath() {
12501
13189
  try {
12502
13190
  const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
12503
- const binPath = path27.join(npmBin, "night-watch");
12504
- if (fs26.existsSync(binPath)) {
13191
+ const binPath = path30.join(npmBin, "night-watch");
13192
+ if (fs29.existsSync(binPath)) {
12505
13193
  return binPath;
12506
13194
  }
12507
13195
  } catch {
@@ -12514,17 +13202,17 @@ function getNightWatchBinPath() {
12514
13202
  }
12515
13203
  function getNodeBinDir() {
12516
13204
  if (process.execPath && process.execPath !== "node") {
12517
- return path27.dirname(process.execPath);
13205
+ return path30.dirname(process.execPath);
12518
13206
  }
12519
13207
  try {
12520
13208
  const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
12521
- return path27.dirname(nodePath);
13209
+ return path30.dirname(nodePath);
12522
13210
  } catch {
12523
13211
  return "";
12524
13212
  }
12525
13213
  }
12526
13214
  function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
12527
- const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path27.dirname(nightWatchBin) : "";
13215
+ const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path30.dirname(nightWatchBin) : "";
12528
13216
  const pathParts = Array.from(
12529
13217
  new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
12530
13218
  );
@@ -12540,12 +13228,12 @@ function performInstall(projectDir, config, options) {
12540
13228
  const nightWatchBin = getNightWatchBinPath();
12541
13229
  const projectName = getProjectName(projectDir);
12542
13230
  const marker = generateMarker(projectName);
12543
- const logDir = path27.join(projectDir, LOG_DIR);
12544
- if (!fs26.existsSync(logDir)) {
12545
- fs26.mkdirSync(logDir, { recursive: true });
13231
+ const logDir = path30.join(projectDir, LOG_DIR);
13232
+ if (!fs29.existsSync(logDir)) {
13233
+ fs29.mkdirSync(logDir, { recursive: true });
12546
13234
  }
12547
- const executorLog = path27.join(logDir, "executor.log");
12548
- const reviewerLog = path27.join(logDir, "reviewer.log");
13235
+ const executorLog = path30.join(logDir, "executor.log");
13236
+ const reviewerLog = path30.join(logDir, "reviewer.log");
12549
13237
  if (!options?.force) {
12550
13238
  const existingEntries2 = Array.from(
12551
13239
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
@@ -12582,7 +13270,7 @@ function performInstall(projectDir, config, options) {
12582
13270
  const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
12583
13271
  if (installSlicer) {
12584
13272
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
12585
- const slicerLog = path27.join(logDir, "slicer.log");
13273
+ const slicerLog = path30.join(logDir, "slicer.log");
12586
13274
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
12587
13275
  entries.push(slicerEntry);
12588
13276
  }
@@ -12590,7 +13278,7 @@ function performInstall(projectDir, config, options) {
12590
13278
  const installQa = disableQa ? false : config.qa.enabled;
12591
13279
  if (installQa) {
12592
13280
  const qaSchedule = config.qa.schedule;
12593
- const qaLog = path27.join(logDir, "qa.log");
13281
+ const qaLog = path30.join(logDir, "qa.log");
12594
13282
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
12595
13283
  entries.push(qaEntry);
12596
13284
  }
@@ -12598,7 +13286,7 @@ function performInstall(projectDir, config, options) {
12598
13286
  const installAudit = disableAudit ? false : config.audit.enabled;
12599
13287
  if (installAudit) {
12600
13288
  const auditSchedule = config.audit.schedule;
12601
- const auditLog = path27.join(logDir, "audit.log");
13289
+ const auditLog = path30.join(logDir, "audit.log");
12602
13290
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
12603
13291
  entries.push(auditEntry);
12604
13292
  }
@@ -12606,7 +13294,7 @@ function performInstall(projectDir, config, options) {
12606
13294
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
12607
13295
  if (installAnalytics) {
12608
13296
  const analyticsSchedule = config.analytics.schedule;
12609
- const analyticsLog = path27.join(logDir, "analytics.log");
13297
+ const analyticsLog = path30.join(logDir, "analytics.log");
12610
13298
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
12611
13299
  entries.push(analyticsEntry);
12612
13300
  }
@@ -12614,7 +13302,7 @@ function performInstall(projectDir, config, options) {
12614
13302
  const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
12615
13303
  if (installPrResolver) {
12616
13304
  const prResolverSchedule = config.prResolver.schedule;
12617
- const prResolverLog = path27.join(logDir, "pr-resolver.log");
13305
+ const prResolverLog = path30.join(logDir, "pr-resolver.log");
12618
13306
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
12619
13307
  entries.push(prResolverEntry);
12620
13308
  }
@@ -12622,10 +13310,18 @@ function performInstall(projectDir, config, options) {
12622
13310
  const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
12623
13311
  if (installMerger) {
12624
13312
  const mergerSchedule = config.merger.schedule;
12625
- const mergerLog = path27.join(logDir, "merger.log");
13313
+ const mergerLog = path30.join(logDir, "merger.log");
12626
13314
  const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
12627
13315
  entries.push(mergerEntry);
12628
13316
  }
13317
+ const disableManager = options?.noManager === true || options?.manager === false;
13318
+ const installManager = disableManager ? false : config.manager?.enabled ?? false;
13319
+ if (installManager) {
13320
+ const managerSchedule = config.manager.schedule;
13321
+ const managerLog = path30.join(logDir, `${MANAGER_LOG_NAME}.log`);
13322
+ const managerEntry = `${managerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} manager >> ${shellQuote(managerLog)} 2>&1 ${marker}`;
13323
+ entries.push(managerEntry);
13324
+ }
12629
13325
  const existingEntries = new Set(
12630
13326
  Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
12631
13327
  );
@@ -12644,7 +13340,7 @@ function performInstall(projectDir, config, options) {
12644
13340
  }
12645
13341
  }
12646
13342
  function installCommand(program2) {
12647
- 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) => {
13343
+ 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("--no-manager", "Skip installing manager cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
12648
13344
  try {
12649
13345
  const projectDir = process.cwd();
12650
13346
  const config = loadConfig(projectDir);
@@ -12653,12 +13349,12 @@ function installCommand(program2) {
12653
13349
  const nightWatchBin = getNightWatchBinPath();
12654
13350
  const projectName = getProjectName(projectDir);
12655
13351
  const marker = generateMarker(projectName);
12656
- const logDir = path27.join(projectDir, LOG_DIR);
12657
- if (!fs26.existsSync(logDir)) {
12658
- fs26.mkdirSync(logDir, { recursive: true });
13352
+ const logDir = path30.join(projectDir, LOG_DIR);
13353
+ if (!fs29.existsSync(logDir)) {
13354
+ fs29.mkdirSync(logDir, { recursive: true });
12659
13355
  }
12660
- const executorLog = path27.join(logDir, "executor.log");
12661
- const reviewerLog = path27.join(logDir, "reviewer.log");
13356
+ const executorLog = path30.join(logDir, "executor.log");
13357
+ const reviewerLog = path30.join(logDir, "reviewer.log");
12662
13358
  const existingEntries = Array.from(
12663
13359
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
12664
13360
  );
@@ -12694,7 +13390,7 @@ function installCommand(program2) {
12694
13390
  const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
12695
13391
  let slicerLog;
12696
13392
  if (installSlicer) {
12697
- slicerLog = path27.join(logDir, "slicer.log");
13393
+ slicerLog = path30.join(logDir, "slicer.log");
12698
13394
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
12699
13395
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
12700
13396
  entries.push(slicerEntry);
@@ -12703,7 +13399,7 @@ function installCommand(program2) {
12703
13399
  const installQa = disableQa ? false : config.qa.enabled;
12704
13400
  let qaLog;
12705
13401
  if (installQa) {
12706
- qaLog = path27.join(logDir, "qa.log");
13402
+ qaLog = path30.join(logDir, "qa.log");
12707
13403
  const qaSchedule = config.qa.schedule;
12708
13404
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
12709
13405
  entries.push(qaEntry);
@@ -12712,7 +13408,7 @@ function installCommand(program2) {
12712
13408
  const installAudit = disableAudit ? false : config.audit.enabled;
12713
13409
  let auditLog;
12714
13410
  if (installAudit) {
12715
- auditLog = path27.join(logDir, "audit.log");
13411
+ auditLog = path30.join(logDir, "audit.log");
12716
13412
  const auditSchedule = config.audit.schedule;
12717
13413
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
12718
13414
  entries.push(auditEntry);
@@ -12721,7 +13417,7 @@ function installCommand(program2) {
12721
13417
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
12722
13418
  let analyticsLog;
12723
13419
  if (installAnalytics) {
12724
- analyticsLog = path27.join(logDir, "analytics.log");
13420
+ analyticsLog = path30.join(logDir, "analytics.log");
12725
13421
  const analyticsSchedule = config.analytics.schedule;
12726
13422
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
12727
13423
  entries.push(analyticsEntry);
@@ -12730,7 +13426,7 @@ function installCommand(program2) {
12730
13426
  const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
12731
13427
  let prResolverLog;
12732
13428
  if (installPrResolver) {
12733
- prResolverLog = path27.join(logDir, "pr-resolver.log");
13429
+ prResolverLog = path30.join(logDir, "pr-resolver.log");
12734
13430
  const prResolverSchedule = config.prResolver.schedule;
12735
13431
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
12736
13432
  entries.push(prResolverEntry);
@@ -12739,11 +13435,20 @@ function installCommand(program2) {
12739
13435
  const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
12740
13436
  let mergerLog;
12741
13437
  if (installMerger) {
12742
- mergerLog = path27.join(logDir, "merger.log");
13438
+ mergerLog = path30.join(logDir, "merger.log");
12743
13439
  const mergerSchedule = config.merger.schedule;
12744
13440
  const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
12745
13441
  entries.push(mergerEntry);
12746
13442
  }
13443
+ const disableManager = options.noManager === true || options.manager === false;
13444
+ const installManager = disableManager ? false : config.manager?.enabled ?? false;
13445
+ let managerLog;
13446
+ if (installManager) {
13447
+ managerLog = path30.join(logDir, `${MANAGER_LOG_NAME}.log`);
13448
+ const managerSchedule = config.manager.schedule;
13449
+ const managerEntry = `${managerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} manager >> ${shellQuote(managerLog)} 2>&1 ${marker}`;
13450
+ entries.push(managerEntry);
13451
+ }
12747
13452
  const existingEntrySet = new Set(existingEntries);
12748
13453
  const currentCrontab = readCrontab();
12749
13454
  const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
@@ -12779,6 +13484,9 @@ function installCommand(program2) {
12779
13484
  if (installMerger && mergerLog) {
12780
13485
  dim(` Merger: ${mergerLog}`);
12781
13486
  }
13487
+ if (installManager && managerLog) {
13488
+ dim(` Manager: ${managerLog}`);
13489
+ }
12782
13490
  console.log();
12783
13491
  dim("To uninstall, run: night-watch uninstall");
12784
13492
  dim("To check status, run: night-watch status");
@@ -12793,8 +13501,8 @@ function installCommand(program2) {
12793
13501
 
12794
13502
  // src/commands/uninstall.ts
12795
13503
  init_dist();
12796
- import * as path28 from "path";
12797
- import * as fs27 from "fs";
13504
+ import * as path31 from "path";
13505
+ import * as fs30 from "fs";
12798
13506
  function performUninstall(projectDir, options) {
12799
13507
  try {
12800
13508
  const projectName = getProjectName(projectDir);
@@ -12809,25 +13517,26 @@ function performUninstall(projectDir, options) {
12809
13517
  const removedCount = removeEntriesForProject(projectDir, marker);
12810
13518
  unregisterProject(projectDir);
12811
13519
  if (!options?.keepLogs) {
12812
- const logDir = path28.join(projectDir, "logs");
12813
- if (fs27.existsSync(logDir)) {
13520
+ const logDir = path31.join(projectDir, "logs");
13521
+ if (fs30.existsSync(logDir)) {
12814
13522
  const logFiles = [
12815
13523
  "executor.log",
12816
13524
  "reviewer.log",
12817
13525
  "slicer.log",
12818
13526
  "audit.log",
12819
- "pr-resolver.log"
13527
+ "pr-resolver.log",
13528
+ "manager.log"
12820
13529
  ];
12821
13530
  logFiles.forEach((logFile) => {
12822
- const logPath = path28.join(logDir, logFile);
12823
- if (fs27.existsSync(logPath)) {
12824
- fs27.unlinkSync(logPath);
13531
+ const logPath = path31.join(logDir, logFile);
13532
+ if (fs30.existsSync(logPath)) {
13533
+ fs30.unlinkSync(logPath);
12825
13534
  }
12826
13535
  });
12827
13536
  try {
12828
- const remainingFiles = fs27.readdirSync(logDir);
13537
+ const remainingFiles = fs30.readdirSync(logDir);
12829
13538
  if (remainingFiles.length === 0) {
12830
- fs27.rmdirSync(logDir);
13539
+ fs30.rmdirSync(logDir);
12831
13540
  }
12832
13541
  } catch {
12833
13542
  }
@@ -12860,27 +13569,28 @@ function uninstallCommand(program2) {
12860
13569
  existingEntries.forEach((entry) => dim(` ${entry}`));
12861
13570
  const removedCount = removeEntriesForProject(projectDir, marker);
12862
13571
  if (!options.keepLogs) {
12863
- const logDir = path28.join(projectDir, "logs");
12864
- if (fs27.existsSync(logDir)) {
13572
+ const logDir = path31.join(projectDir, "logs");
13573
+ if (fs30.existsSync(logDir)) {
12865
13574
  const logFiles = [
12866
13575
  "executor.log",
12867
13576
  "reviewer.log",
12868
13577
  "slicer.log",
12869
13578
  "audit.log",
12870
- "pr-resolver.log"
13579
+ "pr-resolver.log",
13580
+ "manager.log"
12871
13581
  ];
12872
13582
  let logsRemoved = 0;
12873
13583
  logFiles.forEach((logFile) => {
12874
- const logPath = path28.join(logDir, logFile);
12875
- if (fs27.existsSync(logPath)) {
12876
- fs27.unlinkSync(logPath);
13584
+ const logPath = path31.join(logDir, logFile);
13585
+ if (fs30.existsSync(logPath)) {
13586
+ fs30.unlinkSync(logPath);
12877
13587
  logsRemoved++;
12878
13588
  }
12879
13589
  });
12880
13590
  try {
12881
- const remainingFiles = fs27.readdirSync(logDir);
13591
+ const remainingFiles = fs30.readdirSync(logDir);
12882
13592
  if (remainingFiles.length === 0) {
12883
- fs27.rmdirSync(logDir);
13593
+ fs30.rmdirSync(logDir);
12884
13594
  }
12885
13595
  } catch {
12886
13596
  }
@@ -13166,14 +13876,14 @@ function statusCommand(program2) {
13166
13876
  // src/commands/logs.ts
13167
13877
  init_dist();
13168
13878
  import { spawn as spawn3 } from "child_process";
13169
- import * as path29 from "path";
13170
- import * as fs28 from "fs";
13879
+ import * as path32 from "path";
13880
+ import * as fs31 from "fs";
13171
13881
  function getLastLines(filePath, lineCount) {
13172
- if (!fs28.existsSync(filePath)) {
13882
+ if (!fs31.existsSync(filePath)) {
13173
13883
  return `Log file not found: ${filePath}`;
13174
13884
  }
13175
13885
  try {
13176
- const content = fs28.readFileSync(filePath, "utf-8");
13886
+ const content = fs31.readFileSync(filePath, "utf-8");
13177
13887
  const lines = content.trim().split("\n");
13178
13888
  return lines.slice(-lineCount).join("\n");
13179
13889
  } catch (error2) {
@@ -13181,7 +13891,7 @@ function getLastLines(filePath, lineCount) {
13181
13891
  }
13182
13892
  }
13183
13893
  function followLog(filePath) {
13184
- if (!fs28.existsSync(filePath)) {
13894
+ if (!fs31.existsSync(filePath)) {
13185
13895
  console.log(`Log file not found: ${filePath}`);
13186
13896
  console.log("The log file will be created when the first execution runs.");
13187
13897
  return;
@@ -13200,20 +13910,21 @@ function followLog(filePath) {
13200
13910
  function logsCommand(program2) {
13201
13911
  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(
13202
13912
  "-t, --type <type>",
13203
- "Log type to view (executor|reviewer|qa|audit|planner|analytics|merger|all)",
13913
+ "Log type to view (executor|reviewer|qa|audit|planner|analytics|merger|manager|all)",
13204
13914
  "all"
13205
13915
  ).action(async (options) => {
13206
13916
  try {
13207
13917
  const projectDir = process.cwd();
13208
- const logDir = path29.join(projectDir, LOG_DIR);
13918
+ const logDir = path32.join(projectDir, LOG_DIR);
13209
13919
  const lineCount = parseInt(options.lines || "50", 10);
13210
- const executorLog = path29.join(logDir, EXECUTOR_LOG_FILE);
13211
- const reviewerLog = path29.join(logDir, REVIEWER_LOG_FILE);
13212
- const qaLog = path29.join(logDir, `${QA_LOG_NAME}.log`);
13213
- const auditLog = path29.join(logDir, `${AUDIT_LOG_NAME}.log`);
13214
- const plannerLog = path29.join(logDir, `${PLANNER_LOG_NAME}.log`);
13215
- const analyticsLog = path29.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
13216
- const mergerLog = path29.join(logDir, `${MERGER_LOG_NAME}.log`);
13920
+ const executorLog = path32.join(logDir, EXECUTOR_LOG_FILE);
13921
+ const reviewerLog = path32.join(logDir, REVIEWER_LOG_FILE);
13922
+ const qaLog = path32.join(logDir, `${QA_LOG_NAME}.log`);
13923
+ const auditLog = path32.join(logDir, `${AUDIT_LOG_NAME}.log`);
13924
+ const plannerLog = path32.join(logDir, `${PLANNER_LOG_NAME}.log`);
13925
+ const analyticsLog = path32.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
13926
+ const mergerLog = path32.join(logDir, `${MERGER_LOG_NAME}.log`);
13927
+ const managerLog = path32.join(logDir, `${MANAGER_LOG_NAME}.log`);
13217
13928
  const logType = options.type?.toLowerCase() || "all";
13218
13929
  const showExecutor = logType === "all" || logType === "run" || logType === "executor";
13219
13930
  const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
@@ -13222,10 +13933,11 @@ function logsCommand(program2) {
13222
13933
  const showPlanner = logType === "all" || logType === "planner" || logType === "slice" || logType === "slicer";
13223
13934
  const showAnalytics = logType === "all" || logType === "analytics";
13224
13935
  const showMerger = logType === "all" || logType === "merge" || logType === "merger";
13936
+ const showManager = logType === "all" || logType === "manager";
13225
13937
  if (options.follow) {
13226
13938
  if (logType === "all") {
13227
13939
  dim("Note: Following all logs is not supported. Showing executor log.");
13228
- dim("Use --type reviewer|qa|audit|planner|analytics|merger for other logs.\n");
13940
+ dim("Use --type reviewer|qa|audit|planner|analytics|merger|manager for other logs.\n");
13229
13941
  }
13230
13942
  let targetLog = executorLog;
13231
13943
  if (showReviewer) targetLog = reviewerLog;
@@ -13234,6 +13946,7 @@ function logsCommand(program2) {
13234
13946
  else if (showPlanner) targetLog = plannerLog;
13235
13947
  else if (showAnalytics) targetLog = analyticsLog;
13236
13948
  else if (showMerger) targetLog = mergerLog;
13949
+ else if (showManager) targetLog = managerLog;
13237
13950
  followLog(targetLog);
13238
13951
  return;
13239
13952
  }
@@ -13280,11 +13993,17 @@ function logsCommand(program2) {
13280
13993
  console.log();
13281
13994
  console.log(getLastLines(mergerLog, lineCount));
13282
13995
  }
13996
+ if (showManager) {
13997
+ header("Manager Log");
13998
+ dim(`File: ${managerLog}`);
13999
+ console.log();
14000
+ console.log(getLastLines(managerLog, lineCount));
14001
+ }
13283
14002
  console.log();
13284
14003
  dim("---");
13285
14004
  dim("Tip: Use -f to follow logs in real-time");
13286
14005
  dim(
13287
- " Use --type executor|reviewer|qa|audit|planner|analytics|merger to view specific logs"
14006
+ " Use --type executor|reviewer|qa|audit|planner|analytics|merger|manager to view specific logs"
13288
14007
  );
13289
14008
  } catch (err) {
13290
14009
  console.error(`Error reading logs: ${err instanceof Error ? err.message : String(err)}`);
@@ -13296,30 +14015,30 @@ function logsCommand(program2) {
13296
14015
  // src/commands/prd.ts
13297
14016
  init_dist();
13298
14017
  import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
13299
- import * as fs29 from "fs";
13300
- import * as path30 from "path";
14018
+ import * as fs32 from "fs";
14019
+ import * as path33 from "path";
13301
14020
  import { fileURLToPath as fileURLToPath4 } from "url";
13302
- import { dirname as dirname9 } from "path";
14021
+ import { dirname as dirname10 } from "path";
13303
14022
  var __filename3 = fileURLToPath4(import.meta.url);
13304
- var __dirname3 = dirname9(__filename3);
14023
+ var __dirname3 = dirname10(__filename3);
13305
14024
  function findTemplatesDir2(startDir) {
13306
14025
  let current = startDir;
13307
14026
  for (let i = 0; i < 8; i++) {
13308
- const candidate = path30.join(current, "templates");
13309
- if (fs29.existsSync(candidate) && fs29.statSync(candidate).isDirectory()) {
14027
+ const candidate = path33.join(current, "templates");
14028
+ if (fs32.existsSync(candidate) && fs32.statSync(candidate).isDirectory()) {
13310
14029
  return candidate;
13311
14030
  }
13312
- current = path30.dirname(current);
14031
+ current = path33.dirname(current);
13313
14032
  }
13314
- return path30.join(startDir, "templates");
14033
+ return path33.join(startDir, "templates");
13315
14034
  }
13316
14035
  var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
13317
14036
  function slugify2(name) {
13318
14037
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
13319
14038
  }
13320
14039
  function getNextPrdNumber2(prdDir) {
13321
- if (!fs29.existsSync(prdDir)) return 1;
13322
- const files = fs29.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
14040
+ if (!fs32.existsSync(prdDir)) return 1;
14041
+ const files = fs32.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
13323
14042
  const numbers = files.map((f) => {
13324
14043
  const match = f.match(/^(\d+)-/);
13325
14044
  return match ? parseInt(match[1], 10) : 0;
@@ -13400,13 +14119,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
13400
14119
  return null;
13401
14120
  }
13402
14121
  const ref = branch && branch !== "HEAD" ? branch : "main";
13403
- return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path30.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
14122
+ return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path33.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
13404
14123
  } catch {
13405
14124
  return null;
13406
14125
  }
13407
14126
  }
13408
14127
  function buildGithubIssueBody(prdPath, projectDir, prdContent) {
13409
- const relPath = path30.relative(projectDir, prdPath);
14128
+ const relPath = path33.relative(projectDir, prdPath);
13410
14129
  const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
13411
14130
  const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
13412
14131
  return `${fileLine}
@@ -13417,17 +14136,17 @@ ${prdContent}
13417
14136
  Created via \`night-watch prd create\`.`;
13418
14137
  }
13419
14138
  async function generatePrdWithClaude(description, projectDir, model) {
13420
- const bundledTemplatePath = path30.join(TEMPLATES_DIR2, "prd-creator.md");
13421
- const installedTemplatePath = path30.join(projectDir, "instructions", "prd-creator.md");
13422
- const templatePath = fs29.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
13423
- if (!fs29.existsSync(templatePath)) {
14139
+ const bundledTemplatePath = path33.join(TEMPLATES_DIR2, "prd-creator.md");
14140
+ const installedTemplatePath = path33.join(projectDir, "instructions", "prd-creator.md");
14141
+ const templatePath = fs32.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
14142
+ if (!fs32.existsSync(templatePath)) {
13424
14143
  return null;
13425
14144
  }
13426
- const planningPrinciples = fs29.readFileSync(templatePath, "utf-8");
14145
+ const planningPrinciples = fs32.readFileSync(templatePath, "utf-8");
13427
14146
  const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
13428
14147
  const modelId = model ?? CLAUDE_MODEL_IDS.opus;
13429
14148
  const env = buildNativeClaudeEnv(process.env);
13430
- return await new Promise((resolve9) => {
14149
+ return await new Promise((resolve11) => {
13431
14150
  const child = spawn4(
13432
14151
  "claude",
13433
14152
  [
@@ -13480,9 +14199,9 @@ async function generatePrdWithClaude(description, projectDir, model) {
13480
14199
  }
13481
14200
  }
13482
14201
  process.stdout.write("\n");
13483
- resolve9(code === 0 && finalResult ? extractPrdMarkdown(finalResult) : null);
14202
+ resolve11(code === 0 && finalResult ? extractPrdMarkdown(finalResult) : null);
13484
14203
  });
13485
- child.on("error", () => resolve9(null));
14204
+ child.on("error", () => resolve11(null));
13486
14205
  });
13487
14206
  }
13488
14207
  function runGh(args, cwd) {
@@ -13491,17 +14210,17 @@ function runGh(args, cwd) {
13491
14210
  return null;
13492
14211
  }
13493
14212
  function createGithubIssue(title, prdPath, projectDir, prdContent) {
13494
- const tmpFile = path30.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
14213
+ const tmpFile = path33.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
13495
14214
  try {
13496
14215
  const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
13497
- fs29.writeFileSync(tmpFile, body, "utf-8");
14216
+ fs32.writeFileSync(tmpFile, body, "utf-8");
13498
14217
  const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
13499
14218
  return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
13500
14219
  } catch {
13501
14220
  return null;
13502
14221
  } finally {
13503
14222
  try {
13504
- fs29.unlinkSync(tmpFile);
14223
+ fs32.unlinkSync(tmpFile);
13505
14224
  } catch {
13506
14225
  }
13507
14226
  }
@@ -13514,10 +14233,10 @@ function parseDependencies(content) {
13514
14233
  function isClaimActive(claimPath, maxRuntime) {
13515
14234
  const claimStaleAfter = maxRuntime > 0 ? maxRuntime : 14400;
13516
14235
  try {
13517
- if (!fs29.existsSync(claimPath)) {
14236
+ if (!fs32.existsSync(claimPath)) {
13518
14237
  return { active: false };
13519
14238
  }
13520
- const content = fs29.readFileSync(claimPath, "utf-8");
14239
+ const content = fs32.readFileSync(claimPath, "utf-8");
13521
14240
  const claim = JSON.parse(content);
13522
14241
  const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
13523
14242
  if (age < claimStaleAfter) {
@@ -13532,9 +14251,9 @@ function prdCommand(program2) {
13532
14251
  const prd = program2.command("prd").description("Manage PRD files");
13533
14252
  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) => {
13534
14253
  const projectDir = process.cwd();
13535
- const prdDir = path30.join(projectDir, resolvePrdCreateDir());
13536
- if (!fs29.existsSync(prdDir)) {
13537
- fs29.mkdirSync(prdDir, { recursive: true });
14254
+ const prdDir = path33.join(projectDir, resolvePrdCreateDir());
14255
+ if (!fs32.existsSync(prdDir)) {
14256
+ fs32.mkdirSync(prdDir, { recursive: true });
13538
14257
  }
13539
14258
  const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
13540
14259
  const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
@@ -13550,13 +14269,13 @@ function prdCommand(program2) {
13550
14269
  const prdTitle = extractPrdTitle(generated) ?? name;
13551
14270
  const slug = slugify2(prdTitle);
13552
14271
  const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
13553
- const filePath = path30.join(prdDir, filename);
13554
- if (fs29.existsSync(filePath)) {
14272
+ const filePath = path33.join(prdDir, filename);
14273
+ if (fs32.existsSync(filePath)) {
13555
14274
  error(`File already exists: ${filePath}`);
13556
14275
  dim("Use a different name or remove the existing file.");
13557
14276
  process.exit(1);
13558
14277
  }
13559
- fs29.writeFileSync(filePath, generated, "utf-8");
14278
+ fs32.writeFileSync(filePath, generated, "utf-8");
13560
14279
  header("PRD Created");
13561
14280
  success(`Created: ${filePath}`);
13562
14281
  const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
@@ -13569,15 +14288,15 @@ function prdCommand(program2) {
13569
14288
  prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
13570
14289
  const projectDir = process.cwd();
13571
14290
  const config = loadConfig(projectDir);
13572
- const absolutePrdDir = path30.join(projectDir, config.prdDir);
13573
- const doneDir = path30.join(absolutePrdDir, "done");
14291
+ const absolutePrdDir = path33.join(projectDir, config.prdDir);
14292
+ const doneDir = path33.join(absolutePrdDir, "done");
13574
14293
  const pending = [];
13575
- if (fs29.existsSync(absolutePrdDir)) {
13576
- const files = fs29.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
14294
+ if (fs32.existsSync(absolutePrdDir)) {
14295
+ const files = fs32.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
13577
14296
  for (const file of files) {
13578
- const content = fs29.readFileSync(path30.join(absolutePrdDir, file), "utf-8");
14297
+ const content = fs32.readFileSync(path33.join(absolutePrdDir, file), "utf-8");
13579
14298
  const deps = parseDependencies(content);
13580
- const claimPath = path30.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
14299
+ const claimPath = path33.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
13581
14300
  const claimStatus = isClaimActive(claimPath, config.maxRuntime);
13582
14301
  pending.push({
13583
14302
  name: file,
@@ -13588,10 +14307,10 @@ function prdCommand(program2) {
13588
14307
  }
13589
14308
  }
13590
14309
  const done = [];
13591
- if (fs29.existsSync(doneDir)) {
13592
- const files = fs29.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
14310
+ if (fs32.existsSync(doneDir)) {
14311
+ const files = fs32.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
13593
14312
  for (const file of files) {
13594
- const content = fs29.readFileSync(path30.join(doneDir, file), "utf-8");
14313
+ const content = fs32.readFileSync(path33.join(doneDir, file), "utf-8");
13595
14314
  const deps = parseDependencies(content);
13596
14315
  done.push({ name: file, dependencies: deps });
13597
14316
  }
@@ -13628,7 +14347,7 @@ import blessed6 from "blessed";
13628
14347
  // src/commands/dashboard/tab-status.ts
13629
14348
  init_dist();
13630
14349
  import blessed from "blessed";
13631
- import * as fs30 from "fs";
14350
+ import * as fs33 from "fs";
13632
14351
  function sortPrdsByPriority(prds, priority) {
13633
14352
  if (priority.length === 0) return prds;
13634
14353
  const priorityMap = /* @__PURE__ */ new Map();
@@ -13724,7 +14443,7 @@ function renderLogPane(projectDir, logs) {
13724
14443
  let newestMtime = 0;
13725
14444
  for (const log of existingLogs) {
13726
14445
  try {
13727
- const stat = fs30.statSync(log.path);
14446
+ const stat = fs33.statSync(log.path);
13728
14447
  if (stat.mtimeMs > newestMtime) {
13729
14448
  newestMtime = stat.mtimeMs;
13730
14449
  newestLog = log;
@@ -14900,6 +15619,7 @@ function createSchedulesTab() {
14900
15619
  const { config } = ctx;
14901
15620
  const executorHuman = cronToHuman(config.cronSchedule);
14902
15621
  const reviewerHuman = cronToHuman(config.reviewerSchedule);
15622
+ const managerHuman = cronToHuman(config.manager.schedule);
14903
15623
  const lines = [
14904
15624
  `{bold}Executor Schedule:{/bold} ${config.cronSchedule}`,
14905
15625
  ` ${executorHuman}`,
@@ -14907,14 +15627,19 @@ function createSchedulesTab() {
14907
15627
  `{bold}Reviewer Schedule:{/bold} ${config.reviewerSchedule}`,
14908
15628
  ` ${reviewerHuman}`,
14909
15629
  "",
15630
+ `{bold}Manager Schedule:{/bold} ${config.manager.schedule}`,
15631
+ ` ${managerHuman}`,
15632
+ "",
14910
15633
  `{bold}Reviewer Enabled:{/bold} ${config.reviewerEnabled ? "{green-fg}Yes{/green-fg}" : "{red-fg}No{/red-fg}"}`,
15634
+ `{bold}Manager Enabled:{/bold} ${config.manager.enabled ? "{green-fg}Yes{/green-fg}" : "{red-fg}No{/red-fg}"}`,
14911
15635
  "",
14912
- "{#888888-fg}Keys: e:Edit Executor v:Edit Reviewer i:Install x:Uninstall R:Reinstall{/#888888-fg}"
15636
+ "{#888888-fg}Keys: e:Edit Executor v:Edit Reviewer m:Edit Manager i:Install x:Uninstall R:Reinstall{/#888888-fg}"
14913
15637
  ];
14914
15638
  scheduleBox.setContent(lines.join("\n"));
14915
15639
  }
14916
15640
  function applySchedule(ctx, field, cronExpr) {
14917
- const result = saveConfig(ctx.projectDir, { [field]: cronExpr });
15641
+ const patch = field === "manager.schedule" ? { manager: { ...ctx.config.manager, schedule: cronExpr } } : { [field]: cronExpr };
15642
+ const result = saveConfig(ctx.projectDir, patch);
14918
15643
  if (!result.success) {
14919
15644
  ctx.showMessage(`Save failed: ${result.error}`, "error");
14920
15645
  return;
@@ -14939,7 +15664,7 @@ function createSchedulesTab() {
14939
15664
  });
14940
15665
  }
14941
15666
  function showCustomCronInput(ctx, field, label2) {
14942
- const currentValue = ctx.config[field];
15667
+ const currentValue = field === "manager.schedule" ? ctx.config.manager.schedule : ctx.config[field];
14943
15668
  const inputBox = blessed3.textbox({
14944
15669
  top: "center",
14945
15670
  left: "center",
@@ -14993,7 +15718,7 @@ function createSchedulesTab() {
14993
15718
  interactive: true
14994
15719
  });
14995
15720
  selectorList.setItems(presetItems);
14996
- const currentCron = ctx.config[field];
15721
+ const currentCron = field === "manager.schedule" ? ctx.config.manager.schedule : ctx.config[field];
14997
15722
  const matchIdx = SCHEDULE_PRESETS.findIndex((p) => p.cron === currentCron);
14998
15723
  if (matchIdx >= 0) {
14999
15724
  selectorList.select(matchIdx);
@@ -15025,6 +15750,7 @@ function createSchedulesTab() {
15025
15750
  const handlers = [
15026
15751
  [["e"], () => editSchedule(ctx, "cronSchedule", "Executor Schedule")],
15027
15752
  [["v"], () => editSchedule(ctx, "reviewerSchedule", "Reviewer Schedule")],
15753
+ [["m"], () => editSchedule(ctx, "manager.schedule", "Manager Schedule")],
15028
15754
  [
15029
15755
  ["i"],
15030
15756
  () => {
@@ -15098,7 +15824,7 @@ function createSchedulesTab() {
15098
15824
  name: "Schedules",
15099
15825
  container: container2,
15100
15826
  activate(ctx) {
15101
- ctx.setFooter(" e:Executor v:Reviewer i:Install x:Uninstall R:Reinstall q:Quit");
15827
+ ctx.setFooter(" e:Executor v:Reviewer m:Manager i:Install x:Uninstall R:Reinstall q:Quit");
15102
15828
  renderCrontab(ctx);
15103
15829
  renderScheduleSettings(ctx);
15104
15830
  activeCtx = ctx;
@@ -15370,8 +16096,8 @@ function createActionsTab() {
15370
16096
  // src/commands/dashboard/tab-logs.ts
15371
16097
  init_dist();
15372
16098
  import blessed5 from "blessed";
15373
- import * as fs31 from "fs";
15374
- import * as path31 from "path";
16099
+ import * as fs34 from "fs";
16100
+ import * as path34 from "path";
15375
16101
  var LOG_NAMES = ["executor", "reviewer"];
15376
16102
  var LOG_LINES = 200;
15377
16103
  function createLogsTab() {
@@ -15412,7 +16138,7 @@ function createLogsTab() {
15412
16138
  let activeKeyHandlers = [];
15413
16139
  let activeCtx = null;
15414
16140
  function getLogPath(projectDir, logName) {
15415
- return path31.join(projectDir, "logs", `${logName}.log`);
16141
+ return path34.join(projectDir, "logs", `${logName}.log`);
15416
16142
  }
15417
16143
  function updateSelector() {
15418
16144
  const tabs = LOG_NAMES.map((name, idx) => {
@@ -15426,7 +16152,7 @@ function createLogsTab() {
15426
16152
  function loadLog(ctx) {
15427
16153
  const logName = LOG_NAMES[selectedLogIndex];
15428
16154
  const logPath = getLogPath(ctx.projectDir, logName);
15429
- if (!fs31.existsSync(logPath)) {
16155
+ if (!fs34.existsSync(logPath)) {
15430
16156
  logContent.setContent(
15431
16157
  `{yellow-fg}No ${logName}.log file found{/yellow-fg}
15432
16158
 
@@ -15436,7 +16162,7 @@ Log will appear here once the ${logName} runs.`
15436
16162
  return;
15437
16163
  }
15438
16164
  try {
15439
- const stat = fs31.statSync(logPath);
16165
+ const stat = fs34.statSync(logPath);
15440
16166
  const sizeKB = (stat.size / 1024).toFixed(1);
15441
16167
  logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
15442
16168
  } catch {
@@ -15886,13 +16612,13 @@ function doctorCommand(program2) {
15886
16612
 
15887
16613
  // src/commands/serve.ts
15888
16614
  init_dist();
15889
- import * as fs36 from "fs";
16615
+ import * as fs39 from "fs";
15890
16616
 
15891
16617
  // ../server/dist/index.js
15892
16618
  init_dist();
15893
- import * as fs35 from "fs";
15894
- import * as path37 from "path";
15895
- import { dirname as dirname11 } from "path";
16619
+ import * as fs38 from "fs";
16620
+ import * as path40 from "path";
16621
+ import { dirname as dirname12 } from "path";
15896
16622
  import { fileURLToPath as fileURLToPath5 } from "url";
15897
16623
  import cors from "cors";
15898
16624
  import express from "express";
@@ -15976,8 +16702,8 @@ function setupGracefulShutdown(server, beforeClose) {
15976
16702
 
15977
16703
  // ../server/dist/middleware/project-resolver.middleware.js
15978
16704
  init_dist();
15979
- import * as fs32 from "fs";
15980
- import * as path32 from "path";
16705
+ import * as fs35 from "fs";
16706
+ import * as path35 from "path";
15981
16707
  function resolveProject(req, res, next) {
15982
16708
  const projectId = req.params.projectId;
15983
16709
  const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
@@ -15987,7 +16713,7 @@ function resolveProject(req, res, next) {
15987
16713
  res.status(404).json({ error: `Project not found: ${decodedId}` });
15988
16714
  return;
15989
16715
  }
15990
- if (!fs32.existsSync(entry.path) || !fs32.existsSync(path32.join(entry.path, CONFIG_FILE_NAME))) {
16716
+ if (!fs35.existsSync(entry.path) || !fs35.existsSync(path35.join(entry.path, CONFIG_FILE_NAME))) {
15991
16717
  res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
15992
16718
  return;
15993
16719
  }
@@ -16030,10 +16756,128 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
16030
16756
  }, 2e3);
16031
16757
  }
16032
16758
 
16759
+ // ../server/dist/global-startup-summary.js
16760
+ init_dist();
16761
+ var RESET = "\x1B[0m";
16762
+ var DIM = "\x1B[2m";
16763
+ var BOLD = "\x1B[1m";
16764
+ var GREEN = "\x1B[32m";
16765
+ var YELLOW = "\x1B[33m";
16766
+ var RED = "\x1B[31m";
16767
+ var CYAN = "\x1B[36m";
16768
+ var FAILURE_OUTCOME_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
16769
+ function paint(enabled, code, value) {
16770
+ return enabled ? `${code}${value}${RESET}` : value;
16771
+ }
16772
+ function palette(color) {
16773
+ return {
16774
+ bold: (value) => paint(color, BOLD, value),
16775
+ dim: (value) => paint(color, DIM, value),
16776
+ green: (value) => paint(color, GREEN, value),
16777
+ yellow: (value) => paint(color, YELLOW, value),
16778
+ red: (value) => paint(color, RED, value),
16779
+ cyan: (value) => paint(color, CYAN, value)
16780
+ };
16781
+ }
16782
+ function shouldUseColor() {
16783
+ return Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
16784
+ }
16785
+ function failureRateFromSummary(summary) {
16786
+ if (summary.totalCount === 0)
16787
+ return null;
16788
+ const failed = summary.failureCount + summary.timeoutCount + summary.rateLimitedCount;
16789
+ return Math.round(failed / summary.totalCount * 100);
16790
+ }
16791
+ function getExecutorStatus(config, snapshot) {
16792
+ if (snapshot.processes.some((processInfo) => processInfo.running)) {
16793
+ return "running";
16794
+ }
16795
+ const executorActive = config.executorEnabled !== false && !config.pausedJobs?.executor;
16796
+ return executorActive ? "active" : "paused";
16797
+ }
16798
+ function describeError(error2) {
16799
+ const message = error2 instanceof Error ? error2.message : String(error2);
16800
+ return message.split("\n")[0]?.trim() || "unknown error";
16801
+ }
16802
+ function formatStatusMarker(status, colors) {
16803
+ if (status === "error")
16804
+ return colors.red("x");
16805
+ if (status === "paused")
16806
+ return colors.yellow("\u25CB");
16807
+ return colors.green("\u25CF");
16808
+ }
16809
+ function buildProjectStartupSummary(entry, config, snapshot, outcomeSummary) {
16810
+ return {
16811
+ name: entry.name,
16812
+ path: entry.path,
16813
+ status: getExecutorStatus(config, snapshot),
16814
+ readyPrds: snapshot.prds.filter((prd) => prd.status === "ready").length,
16815
+ openPrs: snapshot.prs.length,
16816
+ failedPrs: snapshot.prs.filter((pr) => pr.ciStatus === "fail").length,
16817
+ pendingPrs: snapshot.prs.filter((pr) => pr.ciStatus === "pending").length,
16818
+ failureRate: failureRateFromSummary(outcomeSummary),
16819
+ provider: String(config.provider),
16820
+ cronInstalled: snapshot.crontab.installed,
16821
+ runningProcesses: snapshot.processes.filter((processInfo) => processInfo.running).map((processInfo) => processInfo.name)
16822
+ };
16823
+ }
16824
+ async function collectProjectStartupSummary(entry) {
16825
+ try {
16826
+ const config = loadConfig(entry.path);
16827
+ const snapshot = await fetchStatusSnapshot(entry.path, config);
16828
+ const fromFinishedAt = Date.now() - FAILURE_OUTCOME_WINDOW_MS;
16829
+ const outcomeSummary = getRepositories().sessionOutcomes.querySummary({
16830
+ projectPath: entry.path,
16831
+ fromFinishedAt
16832
+ });
16833
+ return buildProjectStartupSummary(entry, config, snapshot, outcomeSummary);
16834
+ } catch (error2) {
16835
+ return {
16836
+ name: entry.name,
16837
+ path: entry.path,
16838
+ status: "error",
16839
+ readyPrds: 0,
16840
+ openPrs: 0,
16841
+ failedPrs: 0,
16842
+ pendingPrs: 0,
16843
+ failureRate: null,
16844
+ provider: "n/a",
16845
+ cronInstalled: false,
16846
+ runningProcesses: [],
16847
+ error: describeError(error2)
16848
+ };
16849
+ }
16850
+ }
16851
+ function formatProjectStartupSummaryLine(summary, options = {}) {
16852
+ const color = options.color ?? shouldUseColor();
16853
+ const c = palette(color);
16854
+ const statusColor = summary.status === "active" || summary.status === "running" ? c.green : c.yellow;
16855
+ const statusText = summary.status === "error" ? c.red("error") : statusColor(summary.status.padEnd(7, " "));
16856
+ const marker = formatStatusMarker(summary.status, c);
16857
+ if (summary.status === "error") {
16858
+ return ` ${marker} ${statusText} ${c.bold(summary.name)} ${c.red(summary.error ?? "unknown error")} ${c.dim(summary.path)}`;
16859
+ }
16860
+ const prStatus = summary.failedPrs > 0 || summary.pendingPrs > 0 ? ` (${summary.failedPrs} fail, ${summary.pendingPrs} pending)` : "";
16861
+ const failureText = summary.failureRate === null ? c.dim("fail n/a") : `fail ${c.cyan(`${summary.failureRate}%`)}`;
16862
+ const cronText = summary.cronInstalled ? c.green("cron on") : c.dim("cron off");
16863
+ const runningText = summary.runningProcesses.length > 0 ? ` ${c.dim(`run ${summary.runningProcesses.join(",")}`)}` : "";
16864
+ return [
16865
+ ` ${marker}`,
16866
+ statusText,
16867
+ c.bold(summary.name),
16868
+ `ready ${c.cyan(String(summary.readyPrds))}`,
16869
+ `PRs ${c.cyan(String(summary.openPrs))}${prStatus}`,
16870
+ failureText,
16871
+ `provider ${c.cyan(summary.provider)}`,
16872
+ cronText,
16873
+ `${c.dim(summary.path)}${runningText}`
16874
+ ].join(" ");
16875
+ }
16876
+
16033
16877
  // ../server/dist/routes/action.routes.js
16034
16878
  init_dist();
16035
- import * as fs33 from "fs";
16036
- import * as path33 from "path";
16879
+ import * as fs36 from "fs";
16880
+ import * as path36 from "path";
16037
16881
  import { execSync as execSync6, spawn as spawn6 } from "child_process";
16038
16882
  import { Router } from "express";
16039
16883
 
@@ -16071,17 +16915,17 @@ function getBoardProvider(config, projectDir) {
16071
16915
  function cleanOrphanedClaims(dir) {
16072
16916
  let entries;
16073
16917
  try {
16074
- entries = fs33.readdirSync(dir, { withFileTypes: true });
16918
+ entries = fs36.readdirSync(dir, { withFileTypes: true });
16075
16919
  } catch {
16076
16920
  return;
16077
16921
  }
16078
16922
  for (const entry of entries) {
16079
- const fullPath = path33.join(dir, entry.name);
16923
+ const fullPath = path36.join(dir, entry.name);
16080
16924
  if (entry.isDirectory() && entry.name !== "done") {
16081
16925
  cleanOrphanedClaims(fullPath);
16082
16926
  } else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
16083
16927
  try {
16084
- fs33.unlinkSync(fullPath);
16928
+ fs36.unlinkSync(fullPath);
16085
16929
  } catch {
16086
16930
  }
16087
16931
  }
@@ -16236,19 +17080,19 @@ function createActionRouteHandlers(ctx) {
16236
17080
  res.status(400).json({ error: "Invalid PRD name" });
16237
17081
  return;
16238
17082
  }
16239
- const prdDir = path33.join(projectDir, config.prdDir);
17083
+ const prdDir = path36.join(projectDir, config.prdDir);
16240
17084
  const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
16241
- const pendingPath = path33.join(prdDir, normalized);
16242
- const donePath = path33.join(prdDir, "done", normalized);
16243
- if (fs33.existsSync(pendingPath)) {
17085
+ const pendingPath = path36.join(prdDir, normalized);
17086
+ const donePath = path36.join(prdDir, "done", normalized);
17087
+ if (fs36.existsSync(pendingPath)) {
16244
17088
  res.json({ message: `"${normalized}" is already pending` });
16245
17089
  return;
16246
17090
  }
16247
- if (!fs33.existsSync(donePath)) {
17091
+ if (!fs36.existsSync(donePath)) {
16248
17092
  res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
16249
17093
  return;
16250
17094
  }
16251
- fs33.renameSync(donePath, pendingPath);
17095
+ fs36.renameSync(donePath, pendingPath);
16252
17096
  res.json({ message: `Moved "${normalized}" back to pending` });
16253
17097
  } catch (error2) {
16254
17098
  res.status(500).json({
@@ -16266,11 +17110,11 @@ function createActionRouteHandlers(ctx) {
16266
17110
  res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
16267
17111
  return;
16268
17112
  }
16269
- if (fs33.existsSync(lockPath)) {
16270
- fs33.unlinkSync(lockPath);
17113
+ if (fs36.existsSync(lockPath)) {
17114
+ fs36.unlinkSync(lockPath);
16271
17115
  }
16272
- const prdDir = path33.join(projectDir, config.prdDir);
16273
- if (fs33.existsSync(prdDir)) {
17116
+ const prdDir = path36.join(projectDir, config.prdDir);
17117
+ if (fs36.existsSync(prdDir)) {
16274
17118
  cleanOrphanedClaims(prdDir);
16275
17119
  }
16276
17120
  broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
@@ -17038,8 +17882,8 @@ function createProjectConfigRoutes() {
17038
17882
 
17039
17883
  // ../server/dist/routes/doctor.routes.js
17040
17884
  init_dist();
17041
- import * as fs34 from "fs";
17042
- import * as path34 from "path";
17885
+ import * as fs37 from "fs";
17886
+ import * as path37 from "path";
17043
17887
  import { execSync as execSync7 } from "child_process";
17044
17888
  import { Router as Router4 } from "express";
17045
17889
  function runDoctorChecks(projectDir, config) {
@@ -17072,7 +17916,7 @@ function runDoctorChecks(projectDir, config) {
17072
17916
  });
17073
17917
  }
17074
17918
  try {
17075
- const projectName = path34.basename(projectDir);
17919
+ const projectName = path37.basename(projectDir);
17076
17920
  const marker = generateMarker(projectName);
17077
17921
  const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
17078
17922
  if (crontabEntries.length > 0) {
@@ -17095,8 +17939,8 @@ function runDoctorChecks(projectDir, config) {
17095
17939
  detail: "Failed to check crontab"
17096
17940
  });
17097
17941
  }
17098
- const configPath = path34.join(projectDir, CONFIG_FILE_NAME);
17099
- if (fs34.existsSync(configPath)) {
17942
+ const configPath = path37.join(projectDir, CONFIG_FILE_NAME);
17943
+ if (fs37.existsSync(configPath)) {
17100
17944
  checks.push({ name: "config", status: "pass", detail: "Config file exists" });
17101
17945
  } else {
17102
17946
  checks.push({
@@ -17105,9 +17949,9 @@ function runDoctorChecks(projectDir, config) {
17105
17949
  detail: "Config file not found (using defaults)"
17106
17950
  });
17107
17951
  }
17108
- const prdDir = path34.join(projectDir, config.prdDir);
17109
- if (fs34.existsSync(prdDir)) {
17110
- const prds = fs34.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
17952
+ const prdDir = path37.join(projectDir, config.prdDir);
17953
+ if (fs37.existsSync(prdDir)) {
17954
+ const prds = fs37.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
17111
17955
  checks.push({
17112
17956
  name: "prdDir",
17113
17957
  status: "pass",
@@ -17548,6 +18392,8 @@ function getLockPathForJob2(projectDir, jobId) {
17548
18392
  return prResolverLockPath(projectDir);
17549
18393
  case "merger":
17550
18394
  return mergerLockPath(projectDir);
18395
+ case "manager":
18396
+ return managerLockPath(projectDir);
17551
18397
  }
17552
18398
  }
17553
18399
  function createJobRouteHandlers(ctx) {
@@ -17674,7 +18520,7 @@ function createProjectJobRoutes() {
17674
18520
 
17675
18521
  // ../server/dist/routes/log.routes.js
17676
18522
  init_dist();
17677
- import * as path35 from "path";
18523
+ import * as path38 from "path";
17678
18524
  import { Router as Router7 } from "express";
17679
18525
  function createLogRoutes(deps) {
17680
18526
  const { projectDir } = deps;
@@ -17693,7 +18539,7 @@ function createLogRoutes(deps) {
17693
18539
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
17694
18540
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
17695
18541
  const fileName = LOG_FILE_NAMES[name] || name;
17696
- const logPath = path35.join(projectDir, LOG_DIR, `${fileName}.log`);
18542
+ const logPath = path38.join(projectDir, LOG_DIR, `${fileName}.log`);
17697
18543
  const logLines = getLastLogLines(logPath, linesToRead);
17698
18544
  res.json({ name, lines: logLines });
17699
18545
  } catch (error2) {
@@ -17719,7 +18565,7 @@ function createProjectLogRoutes() {
17719
18565
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
17720
18566
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
17721
18567
  const fileName = LOG_FILE_NAMES[name] || name;
17722
- const logPath = path35.join(projectDir, LOG_DIR, `${fileName}.log`);
18568
+ const logPath = path38.join(projectDir, LOG_DIR, `${fileName}.log`);
17723
18569
  const logLines = getLastLogLines(logPath, linesToRead);
17724
18570
  res.json({ name, lines: logLines });
17725
18571
  } catch (error2) {
@@ -17754,7 +18600,7 @@ function createProjectPrdRoutes() {
17754
18600
 
17755
18601
  // ../server/dist/routes/roadmap.routes.js
17756
18602
  init_dist();
17757
- import * as path36 from "path";
18603
+ import * as path39 from "path";
17758
18604
  import { Router as Router9 } from "express";
17759
18605
  function createRoadmapRouteHandlers(ctx) {
17760
18606
  const router = Router9({ mergeParams: true });
@@ -17764,7 +18610,7 @@ function createRoadmapRouteHandlers(ctx) {
17764
18610
  const config = ctx.getConfig(req);
17765
18611
  const projectDir = ctx.getProjectDir(req);
17766
18612
  const status = getRoadmapStatus(projectDir, config);
17767
- const prdDir = path36.join(projectDir, config.prdDir);
18613
+ const prdDir = path39.join(projectDir, config.prdDir);
17768
18614
  const state = loadRoadmapState(prdDir);
17769
18615
  res.json({
17770
18616
  ...status,
@@ -17891,6 +18737,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
17891
18737
  const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
17892
18738
  const prResolverPlan = getSchedulingPlan(projectDir, config, "pr-resolver");
17893
18739
  const mergerPlan = getSchedulingPlan(projectDir, config, "merger");
18740
+ const managerPlan = getSchedulingPlan(projectDir, config, "manager");
17894
18741
  const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
17895
18742
  const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
17896
18743
  const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
@@ -17899,6 +18746,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
17899
18746
  const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
17900
18747
  const prResolverInstalled = installed && (config.prResolver?.enabled ?? true) && hasScheduledCommand(entries, "resolve");
17901
18748
  const mergerInstalled = installed && (config.merger?.enabled ?? false) && hasScheduledCommand(entries, "merge");
18749
+ const managerInstalled = installed && (config.manager?.enabled ?? true) && hasScheduledCommand(entries, "manager");
17902
18750
  return {
17903
18751
  executor: {
17904
18752
  schedule: config.cronSchedule,
@@ -17964,6 +18812,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
17964
18812
  manualDelayMinutes: mergerPlan.manualDelayMinutes,
17965
18813
  balancedDelayMinutes: mergerPlan.balancedDelayMinutes
17966
18814
  },
18815
+ manager: {
18816
+ schedule: config.manager?.schedule ?? "15 7 * * *",
18817
+ installed: managerInstalled,
18818
+ nextRun: managerInstalled ? addDelayToIsoString(computeNextRun(config.manager?.schedule ?? "15 7 * * *"), managerPlan.totalDelayMinutes) : null,
18819
+ delayMinutes: managerPlan.totalDelayMinutes,
18820
+ manualDelayMinutes: managerPlan.manualDelayMinutes,
18821
+ balancedDelayMinutes: managerPlan.balancedDelayMinutes
18822
+ },
17967
18823
  paused: !installed,
17968
18824
  schedulingPriority: config.schedulingPriority,
17969
18825
  entries
@@ -18116,31 +18972,31 @@ function createQueueRoutes(deps) {
18116
18972
 
18117
18973
  // ../server/dist/index.js
18118
18974
  var __filename4 = fileURLToPath5(import.meta.url);
18119
- var __dirname4 = dirname11(__filename4);
18975
+ var __dirname4 = dirname12(__filename4);
18120
18976
  var JOB_RAW_BODY_LIMIT = "1mb";
18121
18977
  function setupJobRawBodyParsing(app) {
18122
18978
  app.use("/api/jobs", express.raw({ type: "*/*", limit: JOB_RAW_BODY_LIMIT }));
18123
18979
  app.use("/api/projects/:projectId/jobs", express.raw({ type: "*/*", limit: JOB_RAW_BODY_LIMIT }));
18124
18980
  }
18125
18981
  function resolveWebDistPath() {
18126
- const bundled = path37.join(__dirname4, "web");
18127
- if (fs35.existsSync(path37.join(bundled, "index.html")))
18982
+ const bundled = path40.join(__dirname4, "web");
18983
+ if (fs38.existsSync(path40.join(bundled, "index.html")))
18128
18984
  return bundled;
18129
18985
  let d = __dirname4;
18130
18986
  for (let i = 0; i < 8; i++) {
18131
- if (fs35.existsSync(path37.join(d, "turbo.json"))) {
18132
- const dev = path37.join(d, "web/dist");
18133
- if (fs35.existsSync(path37.join(dev, "index.html")))
18987
+ if (fs38.existsSync(path40.join(d, "turbo.json"))) {
18988
+ const dev = path40.join(d, "web/dist");
18989
+ if (fs38.existsSync(path40.join(dev, "index.html")))
18134
18990
  return dev;
18135
18991
  break;
18136
18992
  }
18137
- d = dirname11(d);
18993
+ d = dirname12(d);
18138
18994
  }
18139
18995
  return bundled;
18140
18996
  }
18141
18997
  function setupStaticFiles(app) {
18142
18998
  const webDistPath = resolveWebDistPath();
18143
- if (fs35.existsSync(webDistPath)) {
18999
+ if (fs38.existsSync(webDistPath)) {
18144
19000
  app.use(express.static(webDistPath));
18145
19001
  }
18146
19002
  app.use((req, res, next) => {
@@ -18148,8 +19004,8 @@ function setupStaticFiles(app) {
18148
19004
  next();
18149
19005
  return;
18150
19006
  }
18151
- const indexPath = path37.resolve(webDistPath, "index.html");
18152
- if (fs35.existsSync(indexPath)) {
19007
+ const indexPath = path40.resolve(webDistPath, "index.html");
19008
+ if (fs38.existsSync(indexPath)) {
18153
19009
  res.sendFile(indexPath, (err) => {
18154
19010
  if (err)
18155
19011
  next();
@@ -18288,7 +19144,7 @@ function createGlobalApp() {
18288
19144
  return app;
18289
19145
  }
18290
19146
  function bootContainer() {
18291
- initContainer(path37.dirname(getDbPath()));
19147
+ initContainer(path40.dirname(getDbPath()));
18292
19148
  }
18293
19149
  function startServer(projectDir, port) {
18294
19150
  bootContainer();
@@ -18303,7 +19159,7 @@ Night Watch UI http://localhost:${port}`);
18303
19159
  });
18304
19160
  setupGracefulShutdown(server);
18305
19161
  }
18306
- function startGlobalServer(port) {
19162
+ async function startGlobalServer(port) {
18307
19163
  bootContainer();
18308
19164
  const entries = loadRegistry();
18309
19165
  if (entries.length === 0) {
@@ -18314,12 +19170,14 @@ function startGlobalServer(port) {
18314
19170
  if (invalid.length > 0) {
18315
19171
  console.warn(`Warning: ${invalid.length} registered project(s) have invalid paths and will be skipped.`);
18316
19172
  }
19173
+ const summaries = await Promise.all(valid.map((entry) => collectProjectStartupSummary(entry)));
18317
19174
  console.log(`
18318
19175
  Night Watch Global UI`);
18319
19176
  console.log(`Managing ${valid.length} project(s):`);
18320
- for (const p of valid) {
18321
- console.log(` - ${p.name} (${p.path})`);
19177
+ for (const summary of summaries) {
19178
+ console.log(formatProjectStartupSummaryLine(summary));
18322
19179
  }
19180
+ console.log("");
18323
19181
  const app = createGlobalApp();
18324
19182
  const server = app.listen(port, () => {
18325
19183
  console.log(`Night Watch Global UI running at http://localhost:${port}`);
@@ -18341,8 +19199,8 @@ function isProcessRunning2(pid) {
18341
19199
  }
18342
19200
  function readPid(lockPath) {
18343
19201
  try {
18344
- if (!fs36.existsSync(lockPath)) return null;
18345
- const raw = fs36.readFileSync(lockPath, "utf-8").trim();
19202
+ if (!fs39.existsSync(lockPath)) return null;
19203
+ const raw = fs39.readFileSync(lockPath, "utf-8").trim();
18346
19204
  const pid = parseInt(raw, 10);
18347
19205
  return Number.isFinite(pid) ? pid : null;
18348
19206
  } catch {
@@ -18354,10 +19212,10 @@ function acquireServeLock(mode, port) {
18354
19212
  let stalePidCleaned;
18355
19213
  for (let attempt = 0; attempt < 2; attempt++) {
18356
19214
  try {
18357
- const fd = fs36.openSync(lockPath, "wx");
18358
- fs36.writeFileSync(fd, `${process.pid}
19215
+ const fd = fs39.openSync(lockPath, "wx");
19216
+ fs39.writeFileSync(fd, `${process.pid}
18359
19217
  `);
18360
- fs36.closeSync(fd);
19218
+ fs39.closeSync(fd);
18361
19219
  return { acquired: true, lockPath, stalePidCleaned };
18362
19220
  } catch (error2) {
18363
19221
  const err = error2;
@@ -18378,7 +19236,7 @@ function acquireServeLock(mode, port) {
18378
19236
  };
18379
19237
  }
18380
19238
  try {
18381
- fs36.unlinkSync(lockPath);
19239
+ fs39.unlinkSync(lockPath);
18382
19240
  if (existingPid) {
18383
19241
  stalePidCleaned = existingPid;
18384
19242
  }
@@ -18401,15 +19259,18 @@ function acquireServeLock(mode, port) {
18401
19259
  }
18402
19260
  function releaseServeLock(lockPath) {
18403
19261
  try {
18404
- if (!fs36.existsSync(lockPath)) return;
19262
+ if (!fs39.existsSync(lockPath)) return;
18405
19263
  const lockPid = readPid(lockPath);
18406
19264
  if (lockPid !== null && lockPid !== process.pid) return;
18407
- fs36.unlinkSync(lockPath);
19265
+ fs39.unlinkSync(lockPath);
18408
19266
  } catch {
18409
19267
  }
18410
19268
  }
19269
+ function isServeDebugEnabled() {
19270
+ return process.env.NIGHT_WATCH_DEBUG_SERVE === "1";
19271
+ }
18411
19272
  function serveCommand(program2) {
18412
- program2.command("serve").description("Start the Night Watch web UI server").option("-p, --port <number>", "Port to run the server on", "7575").option("-g, --global", "Start in global mode (manage all registered projects)").action((options) => {
19273
+ program2.command("serve").description("Start the Night Watch web UI server").option("-p, --port <number>", "Port to run the server on", "7575").option("-g, --global", "Start in global mode (manage all registered projects)").action(async (options) => {
18413
19274
  const port = parseInt(options.port, 10);
18414
19275
  if (isNaN(port) || port < 1 || port > 65535) {
18415
19276
  console.error(`Invalid port: ${options.port}. Port must be between 1 and 65535.`);
@@ -18433,11 +19294,14 @@ function serveCommand(program2) {
18433
19294
  `[serve] cleaned stale lock from PID ${lock.stalePidCleaned} (${lock.lockPath})`
18434
19295
  );
18435
19296
  }
18436
- console.log(`[serve] lock acquired ${lock.lockPath} pid=${process.pid}`);
19297
+ const debugServe = isServeDebugEnabled();
19298
+ if (debugServe) {
19299
+ console.log(`[serve] lock acquired ${lock.lockPath} pid=${process.pid}`);
19300
+ }
18437
19301
  process.on("exit", () => {
18438
19302
  releaseServeLock(lock.lockPath);
18439
19303
  });
18440
- if (options.global) {
19304
+ if (debugServe && options.global) {
18441
19305
  const execArgv = process.execArgv.length > 0 ? process.execArgv.join(" ") : "(none)";
18442
19306
  console.log(`[serve] mode=global port=${port} pid=${process.pid} node=${process.version}`);
18443
19307
  console.log(`[serve] execPath=${process.execPath}`);
@@ -18445,7 +19309,7 @@ function serveCommand(program2) {
18445
19309
  console.log(`[serve] argv=${process.argv.join(" ")}`);
18446
19310
  }
18447
19311
  if (options.global) {
18448
- startGlobalServer(port);
19312
+ await startGlobalServer(port);
18449
19313
  } else {
18450
19314
  const projectDir = process.cwd();
18451
19315
  startServer(projectDir, port);
@@ -18500,14 +19364,14 @@ function historyCommand(program2) {
18500
19364
  // src/commands/update.ts
18501
19365
  init_dist();
18502
19366
  import { spawnSync as spawnSync2 } from "child_process";
18503
- import * as fs37 from "fs";
18504
- import * as path38 from "path";
19367
+ import * as fs40 from "fs";
19368
+ import * as path41 from "path";
18505
19369
  var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
18506
19370
  function parseProjectDirs(projects, cwd) {
18507
19371
  if (!projects || projects.trim().length === 0) {
18508
19372
  return [cwd];
18509
19373
  }
18510
- const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path38.resolve(cwd, entry));
19374
+ const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path41.resolve(cwd, entry));
18511
19375
  return Array.from(new Set(dirs));
18512
19376
  }
18513
19377
  function shouldInstallGlobal(options) {
@@ -18549,7 +19413,7 @@ function updateCommand(program2) {
18549
19413
  }
18550
19414
  const nightWatchBin = resolveNightWatchBin();
18551
19415
  for (const projectDir of projectDirs) {
18552
- if (!fs37.existsSync(projectDir) || !fs37.statSync(projectDir).isDirectory()) {
19416
+ if (!fs40.existsSync(projectDir) || !fs40.statSync(projectDir).isDirectory()) {
18553
19417
  warn(`Skipping invalid project directory: ${projectDir}`);
18554
19418
  continue;
18555
19419
  }
@@ -18593,8 +19457,8 @@ function prdStateCommand(program2) {
18593
19457
 
18594
19458
  // src/commands/retry.ts
18595
19459
  init_dist();
18596
- import * as fs38 from "fs";
18597
- import * as path39 from "path";
19460
+ import * as fs41 from "fs";
19461
+ import * as path42 from "path";
18598
19462
  function normalizePrdName(name) {
18599
19463
  if (!name.endsWith(".md")) {
18600
19464
  return `${name}.md`;
@@ -18602,26 +19466,26 @@ function normalizePrdName(name) {
18602
19466
  return name;
18603
19467
  }
18604
19468
  function getDonePrds(doneDir) {
18605
- if (!fs38.existsSync(doneDir)) {
19469
+ if (!fs41.existsSync(doneDir)) {
18606
19470
  return [];
18607
19471
  }
18608
- return fs38.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
19472
+ return fs41.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
18609
19473
  }
18610
19474
  function retryCommand(program2) {
18611
19475
  program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
18612
19476
  const projectDir = process.cwd();
18613
19477
  const config = loadConfig(projectDir);
18614
- const prdDir = path39.join(projectDir, config.prdDir);
18615
- const doneDir = path39.join(prdDir, "done");
19478
+ const prdDir = path42.join(projectDir, config.prdDir);
19479
+ const doneDir = path42.join(prdDir, "done");
18616
19480
  const normalizedPrdName = normalizePrdName(prdName);
18617
- const pendingPath = path39.join(prdDir, normalizedPrdName);
18618
- if (fs38.existsSync(pendingPath)) {
19481
+ const pendingPath = path42.join(prdDir, normalizedPrdName);
19482
+ if (fs41.existsSync(pendingPath)) {
18619
19483
  info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
18620
19484
  return;
18621
19485
  }
18622
- const donePath = path39.join(doneDir, normalizedPrdName);
18623
- if (fs38.existsSync(donePath)) {
18624
- fs38.renameSync(donePath, pendingPath);
19486
+ const donePath = path42.join(doneDir, normalizedPrdName);
19487
+ if (fs41.existsSync(donePath)) {
19488
+ fs41.renameSync(donePath, pendingPath);
18625
19489
  success(`Moved "${normalizedPrdName}" back to pending.`);
18626
19490
  dim(`From: ${donePath}`);
18627
19491
  dim(`To: ${pendingPath}`);
@@ -18873,7 +19737,7 @@ function prdsCommand(program2) {
18873
19737
 
18874
19738
  // src/commands/cancel.ts
18875
19739
  init_dist();
18876
- import * as fs39 from "fs";
19740
+ import * as fs42 from "fs";
18877
19741
  import * as readline2 from "readline";
18878
19742
  function getLockFilePaths2(projectDir) {
18879
19743
  const runtimeKey = projectRuntimeKey(projectDir);
@@ -18890,16 +19754,16 @@ async function promptConfirmation(prompt) {
18890
19754
  input: process.stdin,
18891
19755
  output: process.stdout
18892
19756
  });
18893
- return new Promise((resolve9) => {
19757
+ return new Promise((resolve11) => {
18894
19758
  rl.question(`${prompt} `, (answer) => {
18895
19759
  rl.close();
18896
19760
  const normalized = answer.toLowerCase().trim();
18897
- resolve9(normalized === "y" || normalized === "yes");
19761
+ resolve11(normalized === "y" || normalized === "yes");
18898
19762
  });
18899
19763
  });
18900
19764
  }
18901
19765
  function sleep2(ms) {
18902
- return new Promise((resolve9) => setTimeout(resolve9, ms));
19766
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
18903
19767
  }
18904
19768
  function isProcessRunning3(pid) {
18905
19769
  try {
@@ -18920,7 +19784,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
18920
19784
  const pid = lockStatus.pid;
18921
19785
  if (!lockStatus.running) {
18922
19786
  try {
18923
- fs39.unlinkSync(lockPath);
19787
+ fs42.unlinkSync(lockPath);
18924
19788
  return {
18925
19789
  success: true,
18926
19790
  message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
@@ -18958,7 +19822,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
18958
19822
  await sleep2(3e3);
18959
19823
  if (!isProcessRunning3(pid)) {
18960
19824
  try {
18961
- fs39.unlinkSync(lockPath);
19825
+ fs42.unlinkSync(lockPath);
18962
19826
  } catch {
18963
19827
  }
18964
19828
  return {
@@ -18993,7 +19857,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
18993
19857
  await sleep2(500);
18994
19858
  if (!isProcessRunning3(pid)) {
18995
19859
  try {
18996
- fs39.unlinkSync(lockPath);
19860
+ fs42.unlinkSync(lockPath);
18997
19861
  } catch {
18998
19862
  }
18999
19863
  return {
@@ -19054,31 +19918,31 @@ function cancelCommand(program2) {
19054
19918
 
19055
19919
  // src/commands/slice.ts
19056
19920
  init_dist();
19057
- import * as fs40 from "fs";
19058
- import * as path40 from "path";
19921
+ import * as fs43 from "fs";
19922
+ import * as path43 from "path";
19059
19923
  function plannerLockPath2(projectDir) {
19060
19924
  return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
19061
19925
  }
19062
19926
  function acquirePlannerLock(projectDir) {
19063
19927
  const lockFile = plannerLockPath2(projectDir);
19064
- if (fs40.existsSync(lockFile)) {
19065
- const pidRaw = fs40.readFileSync(lockFile, "utf-8").trim();
19928
+ if (fs43.existsSync(lockFile)) {
19929
+ const pidRaw = fs43.readFileSync(lockFile, "utf-8").trim();
19066
19930
  const pid = parseInt(pidRaw, 10);
19067
19931
  if (!Number.isNaN(pid) && isProcessRunning(pid)) {
19068
19932
  return { acquired: false, lockFile, pid };
19069
19933
  }
19070
19934
  try {
19071
- fs40.unlinkSync(lockFile);
19935
+ fs43.unlinkSync(lockFile);
19072
19936
  } catch {
19073
19937
  }
19074
19938
  }
19075
- fs40.writeFileSync(lockFile, String(process.pid));
19939
+ fs43.writeFileSync(lockFile, String(process.pid));
19076
19940
  return { acquired: true, lockFile };
19077
19941
  }
19078
19942
  function releasePlannerLock(lockFile) {
19079
19943
  try {
19080
- if (fs40.existsSync(lockFile)) {
19081
- fs40.unlinkSync(lockFile);
19944
+ if (fs43.existsSync(lockFile)) {
19945
+ fs43.unlinkSync(lockFile);
19082
19946
  }
19083
19947
  } catch {
19084
19948
  }
@@ -19087,12 +19951,12 @@ function resolvePlannerIssueColumn(config) {
19087
19951
  return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
19088
19952
  }
19089
19953
  function buildPlannerIssueBody(projectDir, config, result) {
19090
- const relativePrdPath = path40.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
19091
- const absolutePrdPath = path40.join(projectDir, config.prdDir, result.file ?? "");
19954
+ const relativePrdPath = path43.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
19955
+ const absolutePrdPath = path43.join(projectDir, config.prdDir, result.file ?? "");
19092
19956
  const sourceItem = result.item;
19093
19957
  let prdContent;
19094
19958
  try {
19095
- prdContent = fs40.readFileSync(absolutePrdPath, "utf-8");
19959
+ prdContent = fs43.readFileSync(absolutePrdPath, "utf-8");
19096
19960
  } catch {
19097
19961
  prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
19098
19962
  }
@@ -19132,10 +19996,10 @@ async function createPlannerIssue(projectDir, config, result) {
19132
19996
  return { created: false, skippedReason: "board-not-configured" };
19133
19997
  }
19134
19998
  const issueTitle = `PRD: ${result.item.title}`;
19135
- const normalizeTitle = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
19999
+ const normalizeTitle2 = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
19136
20000
  const existingIssues = await provider.getAllIssues();
19137
20001
  const existing = existingIssues.find(
19138
- (issue2) => normalizeTitle(issue2.title) === normalizeTitle(result.item.title)
20002
+ (issue2) => normalizeTitle2(issue2.title) === normalizeTitle2(result.item.title)
19139
20003
  );
19140
20004
  if (existing) {
19141
20005
  return {
@@ -19322,7 +20186,7 @@ function sliceCommand(program2) {
19322
20186
  if (!options.dryRun && result.sliced) {
19323
20187
  await sendNotifications(config, {
19324
20188
  event: "run_succeeded",
19325
- projectName: path40.basename(projectDir),
20189
+ projectName: path43.basename(projectDir),
19326
20190
  exitCode,
19327
20191
  provider: config.provider,
19328
20192
  prTitle: result.item?.title
@@ -19330,7 +20194,7 @@ function sliceCommand(program2) {
19330
20194
  } else if (!options.dryRun && !nothingPending) {
19331
20195
  await sendNotifications(config, {
19332
20196
  event: "run_failed",
19333
- projectName: path40.basename(projectDir),
20197
+ projectName: path43.basename(projectDir),
19334
20198
  exitCode,
19335
20199
  provider: config.provider
19336
20200
  });
@@ -19363,20 +20227,20 @@ function sliceCommand(program2) {
19363
20227
  // src/commands/state.ts
19364
20228
  init_dist();
19365
20229
  import * as os9 from "os";
19366
- import * as path41 from "path";
20230
+ import * as path44 from "path";
19367
20231
  import chalk5 from "chalk";
19368
20232
  import { Command } from "commander";
19369
20233
  function createStateCommand() {
19370
20234
  const state = new Command("state");
19371
20235
  state.description("Manage Night Watch state");
19372
20236
  state.command("migrate").description("Migrate legacy JSON state files to SQLite").option("--dry-run", "Show what would be migrated without making changes").action((opts) => {
19373
- const nightWatchHome = process.env.NIGHT_WATCH_HOME || path41.join(os9.homedir(), GLOBAL_CONFIG_DIR);
20237
+ const nightWatchHome = process.env.NIGHT_WATCH_HOME || path44.join(os9.homedir(), GLOBAL_CONFIG_DIR);
19374
20238
  if (opts.dryRun) {
19375
20239
  console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
19376
20240
  console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
19377
- console.log(` ${path41.join(nightWatchHome, "projects.json")}`);
19378
- console.log(` ${path41.join(nightWatchHome, "history.json")}`);
19379
- console.log(` ${path41.join(nightWatchHome, "prd-states.json")}`);
20241
+ console.log(` ${path44.join(nightWatchHome, "projects.json")}`);
20242
+ console.log(` ${path44.join(nightWatchHome, "history.json")}`);
20243
+ console.log(` ${path44.join(nightWatchHome, "prd-states.json")}`);
19380
20244
  console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
19381
20245
  console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
19382
20246
  return;
@@ -19414,8 +20278,8 @@ function createStateCommand() {
19414
20278
  init_dist();
19415
20279
  init_dist();
19416
20280
  import { execFileSync as execFileSync6 } from "child_process";
19417
- import * as fs41 from "fs";
19418
- import * as path42 from "path";
20281
+ import * as fs44 from "fs";
20282
+ import * as path45 from "path";
19419
20283
  import * as readline3 from "readline";
19420
20284
  import chalk6 from "chalk";
19421
20285
  async function run(fn) {
@@ -19437,7 +20301,7 @@ function getProvider(config, cwd) {
19437
20301
  return createBoardProvider(bp, cwd);
19438
20302
  }
19439
20303
  function defaultBoardTitle(cwd) {
19440
- return `${path42.basename(cwd)} Night Watch`;
20304
+ return `${path45.basename(cwd)} Night Watch`;
19441
20305
  }
19442
20306
  async function ensureBoardConfigured(config, cwd, provider, options) {
19443
20307
  if (config.boardProvider?.projectNumber) {
@@ -19469,10 +20333,10 @@ async function confirmPrompt(question) {
19469
20333
  input: process.stdin,
19470
20334
  output: process.stdout
19471
20335
  });
19472
- return new Promise((resolve9) => {
20336
+ return new Promise((resolve11) => {
19473
20337
  rl.question(question, (answer) => {
19474
20338
  rl.close();
19475
- resolve9(answer.trim().toLowerCase() === "y");
20339
+ resolve11(answer.trim().toLowerCase() === "y");
19476
20340
  });
19477
20341
  });
19478
20342
  }
@@ -19638,11 +20502,11 @@ function boardCommand(program2) {
19638
20502
  let body = options.body ?? "";
19639
20503
  if (options.bodyFile) {
19640
20504
  const filePath = options.bodyFile;
19641
- if (!fs41.existsSync(filePath)) {
20505
+ if (!fs44.existsSync(filePath)) {
19642
20506
  console.error(`File not found: ${filePath}`);
19643
20507
  process.exit(1);
19644
20508
  }
19645
- body = fs41.readFileSync(filePath, "utf-8");
20509
+ body = fs44.readFileSync(filePath, "utf-8");
19646
20510
  }
19647
20511
  const labels = [];
19648
20512
  if (options.label) {
@@ -19883,12 +20747,12 @@ function boardCommand(program2) {
19883
20747
  const config = loadConfig(cwd);
19884
20748
  const provider = getProvider(config, cwd);
19885
20749
  await ensureBoardConfigured(config, cwd, provider);
19886
- const roadmapPath = options.roadmap ?? path42.join(cwd, "ROADMAP.md");
19887
- if (!fs41.existsSync(roadmapPath)) {
20750
+ const roadmapPath = options.roadmap ?? path45.join(cwd, "ROADMAP.md");
20751
+ if (!fs44.existsSync(roadmapPath)) {
19888
20752
  console.error(`Roadmap file not found: ${roadmapPath}`);
19889
20753
  process.exit(1);
19890
20754
  }
19891
- const roadmapContent = fs41.readFileSync(roadmapPath, "utf-8");
20755
+ const roadmapContent = fs44.readFileSync(roadmapPath, "utf-8");
19892
20756
  const items = parseRoadmap(roadmapContent);
19893
20757
  const uncheckedItems = getUncheckedItems(items);
19894
20758
  if (uncheckedItems.length === 0) {
@@ -20012,7 +20876,7 @@ function boardCommand(program2) {
20012
20876
  // src/commands/queue.ts
20013
20877
  init_dist();
20014
20878
  init_dist();
20015
- import * as path43 from "path";
20879
+ import * as path46 from "path";
20016
20880
  import { spawn as spawn8 } from "child_process";
20017
20881
  import chalk7 from "chalk";
20018
20882
  import { Command as Command2 } from "commander";
@@ -20025,7 +20889,8 @@ var VALID_JOB_TYPES2 = [
20025
20889
  "slicer",
20026
20890
  "planner",
20027
20891
  "pr-resolver",
20028
- "merger"
20892
+ "merger",
20893
+ "manager"
20029
20894
  ];
20030
20895
  function formatTimestamp(unixTs) {
20031
20896
  if (unixTs === null) return "-";
@@ -20148,7 +21013,7 @@ function createQueueCommand() {
20148
21013
  process.exit(1);
20149
21014
  }
20150
21015
  }
20151
- const projectName = path43.basename(projectDir);
21016
+ const projectName = path46.basename(projectDir);
20152
21017
  const queueConfig = loadConfig(projectDir).queue;
20153
21018
  if (isJobPaused(projectDir, jobType)) {
20154
21019
  logger6.info(`Skipping enqueue for paused job: ${jobType}`);
@@ -20166,7 +21031,7 @@ function createQueueCommand() {
20166
21031
  });
20167
21032
  queue.command("resolve-key").description("Resolve the provider bucket key for a given project and job type").requiredOption("--project <dir>", "Project directory").requiredOption(
20168
21033
  "--job-type <type>",
20169
- "Job type (executor, reviewer, qa, audit, slicer, planner, pr-resolver, merger)"
21034
+ "Job type (executor, reviewer, qa, audit, slicer, planner, pr-resolver, merger, manager)"
20170
21035
  ).action((opts) => {
20171
21036
  try {
20172
21037
  const config = loadConfig(opts.project);
@@ -20249,7 +21114,7 @@ function createQueueCommand() {
20249
21114
  if (isJobPaused(projectDir, jobType)) {
20250
21115
  process.exit(2);
20251
21116
  }
20252
- const projectName = path43.basename(projectDir);
21117
+ const projectName = path46.basename(projectDir);
20253
21118
  const callerPid = opts.pid ? parseInt(opts.pid, 10) : void 0;
20254
21119
  const result = claimJobSlot(
20255
21120
  projectDir,
@@ -20316,7 +21181,8 @@ var QUEUE_MARKER_KEYS = /* @__PURE__ */ new Set([
20316
21181
  "NW_AUTO_MERGE",
20317
21182
  "NW_AUTO_MERGE_METHOD",
20318
21183
  "NW_MAX_RUNTIME",
20319
- "NW_QA_MAX_RUNTIME"
21184
+ "NW_QA_MAX_RUNTIME",
21185
+ "NW_MANAGER_MAX_RUNTIME"
20320
21186
  ]);
20321
21187
  function filterQueueMarkers(envJson) {
20322
21188
  const result = {};
@@ -20345,6 +21211,8 @@ function getScriptNameForJobType(jobType) {
20345
21211
  return "night-watch-pr-resolver-cron.sh";
20346
21212
  case "merger":
20347
21213
  return "night-watch-merger-cron.sh";
21214
+ case "manager":
21215
+ return "night-watch-manager-cron.sh";
20348
21216
  default:
20349
21217
  return null;
20350
21218
  }
@@ -20382,7 +21250,7 @@ function notifyCommand(program2) {
20382
21250
 
20383
21251
  // src/commands/summary.ts
20384
21252
  init_dist();
20385
- import path44 from "path";
21253
+ import path47 from "path";
20386
21254
  import chalk8 from "chalk";
20387
21255
  function formatDuration2(seconds) {
20388
21256
  if (seconds === null) return "-";
@@ -20412,7 +21280,7 @@ function formatJobStatus(status) {
20412
21280
  return chalk8.dim(status);
20413
21281
  }
20414
21282
  function getProjectName2(projectPath) {
20415
- return path44.basename(projectPath) || projectPath;
21283
+ return path47.basename(projectPath) || projectPath;
20416
21284
  }
20417
21285
  function formatProvider(providerKey) {
20418
21286
  return providerKey.split(":")[0] || providerKey;
@@ -20531,7 +21399,7 @@ function summaryCommand(program2) {
20531
21399
  // src/commands/resolve.ts
20532
21400
  init_dist();
20533
21401
  import { execFileSync as execFileSync7 } from "child_process";
20534
- import * as path45 from "path";
21402
+ import * as path48 from "path";
20535
21403
  function buildEnvVars6(config, options) {
20536
21404
  const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
20537
21405
  env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
@@ -20684,7 +21552,7 @@ ${stderr}`);
20684
21552
  }
20685
21553
  await sendNotifications(config, {
20686
21554
  event: notificationEvent,
20687
- projectName: path45.basename(projectDir),
21555
+ projectName: path48.basename(projectDir),
20688
21556
  exitCode,
20689
21557
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
20690
21558
  });
@@ -20699,7 +21567,7 @@ ${stderr}`);
20699
21567
 
20700
21568
  // src/commands/merge.ts
20701
21569
  init_dist();
20702
- import * as path46 from "path";
21570
+ import * as path49 from "path";
20703
21571
  function buildEnvVars7(config, options) {
20704
21572
  const env = buildBaseEnvVars(config, "merger", options.dryRun);
20705
21573
  env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
@@ -20835,7 +21703,7 @@ ${stderr}`);
20835
21703
  if (notificationEvent) {
20836
21704
  await sendNotifications(config, {
20837
21705
  event: notificationEvent,
20838
- projectName: path46.basename(projectDir),
21706
+ projectName: path49.basename(projectDir),
20839
21707
  exitCode,
20840
21708
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
20841
21709
  });
@@ -20849,12 +21717,208 @@ ${stderr}`);
20849
21717
  });
20850
21718
  }
20851
21719
 
21720
+ // src/commands/manager.ts
21721
+ init_dist();
21722
+ import * as path50 from "path";
21723
+ function resolveRunManager() {
21724
+ const runManager2 = runManager;
21725
+ if (typeof runManager2 !== "function") {
21726
+ throw new Error(
21727
+ "Manager runner is not available in @night-watch/core. Update core to include runManager(projectDir, config, options)."
21728
+ );
21729
+ }
21730
+ return runManager2;
21731
+ }
21732
+ function writeJson(value) {
21733
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
21734
+ `);
21735
+ }
21736
+ function parseTimeout(timeout) {
21737
+ if (!timeout) return void 0;
21738
+ const parsed = parseInt(timeout, 10);
21739
+ return Number.isNaN(parsed) || parsed < 0 ? void 0 : parsed;
21740
+ }
21741
+ function buildManagerRunOptions(options) {
21742
+ const timeout = parseTimeout(options.timeout);
21743
+ return {
21744
+ dryRun: options.dryRun === true,
21745
+ ...timeout !== void 0 ? { timeout } : {},
21746
+ ...options.provider ? { provider: options.provider } : {}
21747
+ };
21748
+ }
21749
+ function applyManagerCliOverrides(config, options) {
21750
+ const timeout = parseTimeout(options.timeout);
21751
+ let overridden = config;
21752
+ if (timeout !== void 0) {
21753
+ overridden = {
21754
+ ...overridden,
21755
+ manager: {
21756
+ ...overridden.manager,
21757
+ maxRuntime: timeout
21758
+ }
21759
+ };
21760
+ }
21761
+ if (options.provider) {
21762
+ overridden = {
21763
+ ...overridden,
21764
+ _cliProviderOverride: options.provider
21765
+ };
21766
+ }
21767
+ return overridden;
21768
+ }
21769
+ function getManagerConfig(config) {
21770
+ return config.manager;
21771
+ }
21772
+ function buildJsonResult(result, options) {
21773
+ if (result && typeof result === "object" && !Array.isArray(result)) {
21774
+ return {
21775
+ ...result,
21776
+ dryRun: options.dryRun
21777
+ };
21778
+ }
21779
+ return {
21780
+ dryRun: options.dryRun,
21781
+ result
21782
+ };
21783
+ }
21784
+ function resultExitCode(result) {
21785
+ if (!result || typeof result !== "object") return 0;
21786
+ const record = result;
21787
+ if (record.ok === false || record.success === false || typeof record.error === "string") {
21788
+ return 1;
21789
+ }
21790
+ return 0;
21791
+ }
21792
+ async function sendManagerNotifications(config, projectDir, result) {
21793
+ if (!result || typeof result !== "object") return;
21794
+ const decisions = result.notificationDecisions;
21795
+ if (!Array.isArray(decisions)) return;
21796
+ for (const decision of decisions) {
21797
+ if (!decision || typeof decision !== "object") continue;
21798
+ const item = decision;
21799
+ if (!item.shouldNotify || !item.event) continue;
21800
+ await sendNotifications(config, {
21801
+ event: item.event,
21802
+ projectName: path50.basename(projectDir),
21803
+ provider: resolveJobProvider(config, "manager"),
21804
+ exitCode: 0,
21805
+ failureReason: item.title,
21806
+ failureDetail: item.body
21807
+ });
21808
+ }
21809
+ }
21810
+ function printHumanResult(result, options) {
21811
+ const payload = buildJsonResult(result, options);
21812
+ header(options.dryRun ? "Dry Run: Manager" : "Manager Result");
21813
+ const table = createTable({ head: ["Metric", "Value"] });
21814
+ for (const key of [
21815
+ "summary",
21816
+ "findings",
21817
+ "createdIssues",
21818
+ "createdDrafts",
21819
+ "skippedDuplicates",
21820
+ "blockedItems"
21821
+ ]) {
21822
+ const value = payload[key];
21823
+ if (value === void 0) continue;
21824
+ table.push([key, Array.isArray(value) ? String(value.length) : String(value)]);
21825
+ }
21826
+ console.log(table.length > 0 ? table.toString() : JSON.stringify(payload, null, 2));
21827
+ }
21828
+ function managerCommand(program2) {
21829
+ program2.command("manager").description("Run Manager to analyze roadmap, board, job status, and docs alignment").option("--dry-run", "Analyze without writing memory, docs, board issues, or notifications").option("--json", "Output structured JSON").option("--timeout <seconds>", "Override max runtime in seconds").option("--provider <string>", "AI provider to use (claude or codex)").action(async (options) => {
21830
+ const projectDir = process.cwd();
21831
+ let config = loadConfig(projectDir);
21832
+ config = applyManagerCliOverrides(config, options);
21833
+ const managerConfig = getManagerConfig(config);
21834
+ const runOptions = buildManagerRunOptions(options);
21835
+ if (!managerConfig.enabled && !runOptions.dryRun) {
21836
+ if (options.json) {
21837
+ writeJson({ dryRun: false, skipped: true, reason: "manager-disabled" });
21838
+ } else {
21839
+ info("Manager is disabled in config; skipping run.");
21840
+ }
21841
+ process.exit(0);
21842
+ }
21843
+ const startedAt = Date.now();
21844
+ let exitCode = 0;
21845
+ const run2 = async () => {
21846
+ if (!runOptions.dryRun) {
21847
+ await maybeApplyCronSchedulingDelay(config, "manager", projectDir);
21848
+ }
21849
+ const runner = resolveRunManager();
21850
+ return runner(projectDir, config, runOptions);
21851
+ };
21852
+ try {
21853
+ const spinner = options.json ? null : createSpinner("Running Manager...");
21854
+ spinner?.start();
21855
+ const result = await run2();
21856
+ exitCode = resultExitCode(result);
21857
+ if (!runOptions.dryRun) {
21858
+ await sendManagerNotifications(config, projectDir, result);
21859
+ try {
21860
+ recordJobOutcome({
21861
+ config,
21862
+ exitCode,
21863
+ finishedAt: Date.now(),
21864
+ jobType: "manager",
21865
+ metadata: buildJsonResult(result, runOptions),
21866
+ projectDir,
21867
+ providerKey: resolveJobProvider(config, "manager"),
21868
+ startedAt,
21869
+ stdout: typeof result === "string" ? result : JSON.stringify(result)
21870
+ });
21871
+ } catch {
21872
+ }
21873
+ }
21874
+ if (options.json) {
21875
+ writeJson(buildJsonResult(result, runOptions));
21876
+ } else {
21877
+ if (exitCode === 0) {
21878
+ spinner?.succeed("Manager completed successfully");
21879
+ } else {
21880
+ spinner?.fail("Manager completed with errors");
21881
+ }
21882
+ printHumanResult(result, runOptions);
21883
+ }
21884
+ } catch (err) {
21885
+ const message = err instanceof Error ? err.message : String(err);
21886
+ if (!runOptions.dryRun) {
21887
+ try {
21888
+ recordJobOutcome({
21889
+ config,
21890
+ exitCode: 1,
21891
+ finishedAt: Date.now(),
21892
+ jobType: "manager",
21893
+ metadata: { error: message },
21894
+ projectDir,
21895
+ providerKey: resolveJobProvider(config, "manager"),
21896
+ startedAt,
21897
+ stderr: message
21898
+ });
21899
+ } catch {
21900
+ }
21901
+ }
21902
+ if (options.json) {
21903
+ process.stderr.write(
21904
+ `${JSON.stringify({ dryRun: runOptions.dryRun, ok: false, error: message }, null, 2)}
21905
+ `
21906
+ );
21907
+ } else {
21908
+ error(`Manager failed: ${message}`);
21909
+ }
21910
+ process.exit(1);
21911
+ }
21912
+ process.exit(exitCode);
21913
+ });
21914
+ }
21915
+
20852
21916
  // src/commands/agent.ts
20853
21917
  init_dist();
20854
21918
  var SCHEMA_VERSION2 = 1;
20855
21919
  var JSON_OPTION = "--json";
20856
21920
  var JSON_OPTION_DESCRIPTION = "Output as JSON";
20857
- function writeJson(value) {
21921
+ function writeJson2(value) {
20858
21922
  process.stdout.write(`${JSON.stringify(value, null, 2)}
20859
21923
  `);
20860
21924
  }
@@ -21011,7 +22075,7 @@ function normalizeJobType(job) {
21011
22075
  function agentCommand(program2) {
21012
22076
  const agent = program2.command("agent").description("Machine-readable agent operations");
21013
22077
  agent.command("status").description("Print a stable machine-readable project snapshot").requiredOption(JSON_OPTION, "Output status as JSON").action(async () => {
21014
- writeJson(await buildAgentStatus(process.cwd()));
22078
+ writeJson2(await buildAgentStatus(process.cwd()));
21015
22079
  });
21016
22080
  }
21017
22081
  function configCommand(program2) {
@@ -21019,18 +22083,18 @@ function configCommand(program2) {
21019
22083
  config.command("list").description("Print resolved config").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((options) => {
21020
22084
  const value = loadConfig(process.cwd());
21021
22085
  if (options.json) {
21022
- writeJson({ schemaVersion: SCHEMA_VERSION2, config: value });
22086
+ writeJson2({ schemaVersion: SCHEMA_VERSION2, config: value });
21023
22087
  } else {
21024
- writeJson(value);
22088
+ writeJson2(value);
21025
22089
  }
21026
22090
  });
21027
22091
  config.command("get <path>").description("Read a resolved config value by dot path").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((dotPath, options) => {
21028
22092
  try {
21029
22093
  const result = getConfigValue(process.cwd(), dotPath);
21030
22094
  if (options.json) {
21031
- writeJson({ schemaVersion: SCHEMA_VERSION2, ...result });
22095
+ writeJson2({ schemaVersion: SCHEMA_VERSION2, ...result });
21032
22096
  } else {
21033
- writeJson(result.value);
22097
+ writeJson2(result.value);
21034
22098
  }
21035
22099
  } catch (error2) {
21036
22100
  fail(error2 instanceof Error ? error2.message : String(error2), options);
@@ -21040,7 +22104,7 @@ function configCommand(program2) {
21040
22104
  try {
21041
22105
  const result = setConfigValue(process.cwd(), dotPath, parseConfigValue(rawValue));
21042
22106
  if (options.json) {
21043
- writeJson({ schemaVersion: SCHEMA_VERSION2, ok: true, ...result });
22107
+ writeJson2({ schemaVersion: SCHEMA_VERSION2, ok: true, ...result });
21044
22108
  } else {
21045
22109
  process.stdout.write(`Updated ${result.path}
21046
22110
  `);
@@ -21056,7 +22120,7 @@ function healthCommand(program2) {
21056
22120
  const snapshot = await fetchStatusSnapshot(process.cwd(), config);
21057
22121
  const health = buildHealth(snapshot, config);
21058
22122
  if (options.json) {
21059
- writeJson(health);
22123
+ writeJson2(health);
21060
22124
  } else {
21061
22125
  for (const check of health.checks) {
21062
22126
  process.stdout.write(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}
@@ -21075,7 +22139,7 @@ function jobCommand(program2) {
21075
22139
  const jobType = normalizeJobType(jobName);
21076
22140
  const result = setConfigValue(process.cwd(), `pausedJobs.${jobType}`, true);
21077
22141
  if (options.json) {
21078
- writeJson({
22142
+ writeJson2({
21079
22143
  schemaVersion: SCHEMA_VERSION2,
21080
22144
  ok: true,
21081
22145
  job: jobType,
@@ -21094,7 +22158,7 @@ function jobCommand(program2) {
21094
22158
  const jobType = normalizeJobType(jobName);
21095
22159
  const result = setConfigValue(process.cwd(), `pausedJobs.${jobType}`, false);
21096
22160
  if (options.json) {
21097
- writeJson({
22161
+ writeJson2({
21098
22162
  schemaVersion: SCHEMA_VERSION2,
21099
22163
  ok: true,
21100
22164
  job: jobType,
@@ -21121,17 +22185,17 @@ function jobCommand(program2) {
21121
22185
 
21122
22186
  // src/cli.ts
21123
22187
  var __filename5 = fileURLToPath6(import.meta.url);
21124
- var __dirname5 = dirname12(__filename5);
22188
+ var __dirname5 = dirname13(__filename5);
21125
22189
  function findPackageRoot(dir) {
21126
22190
  let d = dir;
21127
22191
  for (let i = 0; i < 5; i++) {
21128
- if (existsSync34(join38(d, "package.json"))) return d;
21129
- d = dirname12(d);
22192
+ if (existsSync36(join39(d, "package.json"))) return d;
22193
+ d = dirname13(d);
21130
22194
  }
21131
22195
  return dir;
21132
22196
  }
21133
22197
  var packageRoot = findPackageRoot(__dirname5);
21134
- var packageJson = JSON.parse(readFileSync21(join38(packageRoot, "package.json"), "utf-8"));
22198
+ var packageJson = JSON.parse(readFileSync23(join39(packageRoot, "package.json"), "utf-8"));
21135
22199
  var program = new Command3();
21136
22200
  program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
21137
22201
  initCommand(program);
@@ -21163,6 +22227,7 @@ notifyCommand(program);
21163
22227
  summaryCommand(program);
21164
22228
  resolveCommand(program);
21165
22229
  mergeCommand(program);
22230
+ managerCommand(program);
21166
22231
  agentCommand(program);
21167
22232
  configCommand(program);
21168
22233
  healthCommand(program);