@jonit-dev/night-watch-cli 1.8.14-beta.4 → 1.8.14-beta.6

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 (50) hide show
  1. package/dist/cli.js +1342 -397
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/dashboard/tab-config.js +2 -2
  4. package/dist/commands/dashboard/tab-config.js.map +1 -1
  5. package/dist/commands/dashboard/tab-schedules.d.ts +1 -1
  6. package/dist/commands/dashboard/tab-schedules.d.ts.map +1 -1
  7. package/dist/commands/dashboard/tab-schedules.js +15 -6
  8. package/dist/commands/dashboard/tab-schedules.js.map +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +1 -0
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/install.d.ts +4 -0
  13. package/dist/commands/install.d.ts.map +1 -1
  14. package/dist/commands/install.js +26 -1
  15. package/dist/commands/install.js.map +1 -1
  16. package/dist/commands/logs.d.ts.map +1 -1
  17. package/dist/commands/logs.js +14 -4
  18. package/dist/commands/logs.js.map +1 -1
  19. package/dist/commands/manager.d.ts +23 -0
  20. package/dist/commands/manager.d.ts.map +1 -0
  21. package/dist/commands/manager.js +220 -0
  22. package/dist/commands/manager.js.map +1 -0
  23. package/dist/commands/plan.js +1 -1
  24. package/dist/commands/plan.js.map +1 -1
  25. package/dist/commands/prd.d.ts.map +1 -1
  26. package/dist/commands/prd.js +6 -4
  27. package/dist/commands/prd.js.map +1 -1
  28. package/dist/commands/queue.d.ts.map +1 -1
  29. package/dist/commands/queue.js +5 -1
  30. package/dist/commands/queue.js.map +1 -1
  31. package/dist/commands/run.d.ts.map +1 -1
  32. package/dist/commands/run.js +2 -1
  33. package/dist/commands/run.js.map +1 -1
  34. package/dist/commands/uninstall.d.ts.map +1 -1
  35. package/dist/commands/uninstall.js +2 -0
  36. package/dist/commands/uninstall.js.map +1 -1
  37. package/dist/scripts/night-watch-audit-cron.sh +3 -3
  38. package/dist/scripts/night-watch-cron.sh +8 -7
  39. package/dist/scripts/night-watch-helpers.sh +23 -0
  40. package/dist/scripts/night-watch-manager-cron.sh +61 -0
  41. package/dist/scripts/night-watch-merger-cron.sh +18 -12
  42. package/dist/scripts/night-watch-plan-cron.sh +3 -3
  43. package/dist/scripts/night-watch-pr-resolver-cron.sh +8 -11
  44. package/dist/scripts/night-watch-pr-reviewer-cron.sh +31 -21
  45. package/dist/scripts/night-watch-qa-cron.sh +3 -3
  46. package/dist/scripts/night-watch-slicer-cron.sh +3 -3
  47. package/dist/web/assets/index-DatF4suf.css +1 -0
  48. package/dist/web/assets/index-Q3IYCcdZ.js +447 -0
  49. package/dist/web/index.html +2 -2
  50. 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
  });
@@ -144,7 +146,7 @@ function buildJobEnvOverrides(envPrefix, currentBase, extraFields) {
144
146
  const maxRuntimeVal = process.env[`${envPrefix}_MAX_RUNTIME`];
145
147
  if (maxRuntimeVal) {
146
148
  const v = parseInt(maxRuntimeVal, 10);
147
- if (!isNaN(v) && v > 0) {
149
+ if (!isNaN(v) && v >= 0) {
148
150
  result.maxRuntime = v;
149
151
  changed = true;
150
152
  }
@@ -169,7 +171,7 @@ function buildJobEnvOverrides(envPrefix, currentBase, extraFields) {
169
171
  break;
170
172
  case "number": {
171
173
  const v = parseInt(envVal, 10);
172
- if (!isNaN(v) && v > 0) {
174
+ if (!isNaN(v) && v >= 0) {
173
175
  result[field.name] = v;
174
176
  changed = true;
175
177
  }
@@ -211,7 +213,7 @@ var init_job_registry = __esm({
211
213
  defaultConfig: {
212
214
  enabled: true,
213
215
  schedule: "5 */2 * * *",
214
- maxRuntime: 7200
216
+ maxRuntime: 0
215
217
  }
216
218
  },
217
219
  {
@@ -226,7 +228,7 @@ var init_job_registry = __esm({
226
228
  defaultConfig: {
227
229
  enabled: true,
228
230
  schedule: "25 */3 * * *",
229
- maxRuntime: 3600
231
+ maxRuntime: 0
230
232
  }
231
233
  },
232
234
  {
@@ -241,7 +243,7 @@ var init_job_registry = __esm({
241
243
  extraFields: [
242
244
  { name: "branchPatterns", type: "string[]", defaultValue: [] },
243
245
  { name: "maxPrsPerRun", type: "number", defaultValue: 0 },
244
- { name: "perPrTimeout", type: "number", defaultValue: 600 },
246
+ { name: "perPrTimeout", type: "number", defaultValue: 0 },
245
247
  { name: "aiConflictResolution", type: "boolean", defaultValue: true },
246
248
  { name: "aiReviewResolution", type: "boolean", defaultValue: false },
247
249
  { name: "readyLabel", type: "string", defaultValue: "ready-to-merge" }
@@ -249,10 +251,10 @@ var init_job_registry = __esm({
249
251
  defaultConfig: {
250
252
  enabled: true,
251
253
  schedule: "15 6,14,22 * * *",
252
- maxRuntime: 3600,
254
+ maxRuntime: 0,
253
255
  branchPatterns: [],
254
256
  maxPrsPerRun: 0,
255
- perPrTimeout: 600,
257
+ perPrTimeout: 0,
256
258
  aiConflictResolution: true,
257
259
  aiReviewResolution: false,
258
260
  readyLabel: "ready-to-merge"
@@ -270,7 +272,53 @@ var init_job_registry = __esm({
270
272
  defaultConfig: {
271
273
  enabled: true,
272
274
  schedule: "35 */6 * * *",
273
- maxRuntime: 600
275
+ maxRuntime: 0
276
+ }
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
274
322
  }
275
323
  },
276
324
  {
@@ -297,7 +345,7 @@ var init_job_registry = __esm({
297
345
  defaultConfig: {
298
346
  enabled: true,
299
347
  schedule: "45 2,10,18 * * *",
300
- maxRuntime: 3600,
348
+ maxRuntime: 0,
301
349
  branchPatterns: [],
302
350
  artifacts: "both",
303
351
  skipLabel: "skip-qa",
@@ -326,7 +374,7 @@ var init_job_registry = __esm({
326
374
  defaultConfig: {
327
375
  enabled: false,
328
376
  schedule: "50 3 * * 1",
329
- maxRuntime: 1800,
377
+ maxRuntime: 0,
330
378
  createIssues: false,
331
379
  targetColumn: "Draft"
332
380
  }
@@ -353,7 +401,7 @@ var init_job_registry = __esm({
353
401
  defaultConfig: {
354
402
  enabled: false,
355
403
  schedule: "0 6 * * 1",
356
- maxRuntime: 900,
404
+ maxRuntime: 0,
357
405
  lookbackDays: 7,
358
406
  targetColumn: "Draft",
359
407
  analysisPrompt: ""
@@ -394,7 +442,7 @@ var init_job_registry = __esm({
394
442
  defaultConfig: {
395
443
  enabled: false,
396
444
  schedule: "55 */4 * * *",
397
- maxRuntime: 1800,
445
+ maxRuntime: 0,
398
446
  mergeMethod: "squash",
399
447
  minReviewScore: 80,
400
448
  branchPatterns: [],
@@ -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";
@@ -431,8 +479,8 @@ var init_constants = __esm({
431
479
  DEFAULT_DEFAULT_BRANCH = "";
432
480
  DEFAULT_PRD_DIR = "docs/prds";
433
481
  DEFAULT_SUMMARY_WINDOW_HOURS = 12;
434
- DEFAULT_MAX_RUNTIME = 7200;
435
- DEFAULT_REVIEWER_MAX_RUNTIME = 3600;
482
+ DEFAULT_MAX_RUNTIME = 0;
483
+ DEFAULT_REVIEWER_MAX_RUNTIME = 0;
436
484
  DEFAULT_CRON_SCHEDULE = "5 * * * *";
437
485
  DEFAULT_REVIEWER_SCHEDULE = "25 */3 * * *";
438
486
  DEFAULT_CRON_SCHEDULE_OFFSET = 0;
@@ -467,7 +515,7 @@ var init_constants = __esm({
467
515
  DEFAULT_NOTIFICATIONS = { webhooks: [] };
468
516
  DEFAULT_PRD_PRIORITY = [];
469
517
  DEFAULT_SLICER_SCHEDULE = "35 */6 * * *";
470
- DEFAULT_SLICER_MAX_RUNTIME = 600;
518
+ DEFAULT_SLICER_MAX_RUNTIME = 0;
471
519
  DEFAULT_ROADMAP_SCANNER = {
472
520
  enabled: true,
473
521
  roadmapPath: "ROADMAP.md",
@@ -488,7 +536,7 @@ var init_constants = __esm({
488
536
  VALID_MERGE_METHODS = ["squash", "merge", "rebase"];
489
537
  DEFAULT_QA_ENABLED = true;
490
538
  DEFAULT_QA_SCHEDULE = "45 2,10,18 * * *";
491
- DEFAULT_QA_MAX_RUNTIME = 3600;
539
+ DEFAULT_QA_MAX_RUNTIME = 0;
492
540
  DEFAULT_QA_ARTIFACTS = "both";
493
541
  DEFAULT_QA_SKIP_LABEL = "skip-qa";
494
542
  DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT = true;
@@ -506,7 +554,7 @@ var init_constants = __esm({
506
554
  QA_LOG_NAME = "night-watch-qa";
507
555
  DEFAULT_AUDIT_ENABLED = false;
508
556
  DEFAULT_AUDIT_SCHEDULE = "50 3 * * 1";
509
- DEFAULT_AUDIT_MAX_RUNTIME = 1800;
557
+ DEFAULT_AUDIT_MAX_RUNTIME = 0;
510
558
  DEFAULT_AUDIT_CREATE_ISSUES = false;
511
559
  DEFAULT_AUDIT_TARGET_COLUMN = "Draft";
512
560
  DEFAULT_AUDIT = {
@@ -518,7 +566,7 @@ var init_constants = __esm({
518
566
  };
519
567
  DEFAULT_ANALYTICS_ENABLED = false;
520
568
  DEFAULT_ANALYTICS_SCHEDULE = "0 6 * * 1";
521
- DEFAULT_ANALYTICS_MAX_RUNTIME = 900;
569
+ DEFAULT_ANALYTICS_MAX_RUNTIME = 0;
522
570
  DEFAULT_ANALYTICS_LOOKBACK_DAYS = 7;
523
571
  DEFAULT_ANALYTICS_TARGET_COLUMN = "Draft";
524
572
  DEFAULT_ANALYTICS_PROMPT = `You are an analytics reviewer. Analyze the following Amplitude product analytics data.
@@ -534,11 +582,33 @@ 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
- DEFAULT_PR_RESOLVER_MAX_RUNTIME = 3600;
609
+ DEFAULT_PR_RESOLVER_MAX_RUNTIME = 0;
540
610
  DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN = 0;
541
- DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT = 600;
611
+ DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT = 0;
542
612
  DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION = true;
543
613
  DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION = false;
544
614
  DEFAULT_PR_RESOLVER_READY_LABEL = "ready-to-merge";
@@ -555,7 +625,7 @@ If no issues are warranted, output an empty array: []`;
555
625
  };
556
626
  DEFAULT_MERGER_ENABLED = false;
557
627
  DEFAULT_MERGER_SCHEDULE = "55 */4 * * *";
558
- DEFAULT_MERGER_MAX_RUNTIME = 1800;
628
+ DEFAULT_MERGER_MAX_RUNTIME = 0;
559
629
  DEFAULT_MERGER_MERGE_METHOD = "squash";
560
630
  DEFAULT_MERGER_MIN_REVIEW_SCORE = 80;
561
631
  DEFAULT_MERGER_REBASE_BEFORE_MERGE = true;
@@ -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;
@@ -1203,7 +1274,7 @@ function buildEnvOverrideConfig(fileConfig) {
1203
1274
  }
1204
1275
  if (process.env.NW_SLICER_MAX_RUNTIME) {
1205
1276
  const v = parseInt(process.env.NW_SLICER_MAX_RUNTIME, 10);
1206
- if (!isNaN(v) && v > 0) {
1277
+ if (!isNaN(v) && v >= 0) {
1207
1278
  env.roadmapScanner = { ...roadmapBase(), slicerMaxRuntime: v };
1208
1279
  }
1209
1280
  }
@@ -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;
@@ -4565,6 +4645,7 @@ function releaseLock(lockPath) {
4565
4645
  }
4566
4646
  }
4567
4647
  function countPRDs(projectDir, prdDir, maxRuntime) {
4648
+ const claimStaleAfter = maxRuntime > 0 ? maxRuntime : 14400;
4568
4649
  const fullPrdPath = path6.join(projectDir, prdDir);
4569
4650
  if (!fs6.existsSync(fullPrdPath)) {
4570
4651
  return { pending: 0, claimed: 0, done: 0 };
@@ -4593,7 +4674,7 @@ function countPRDs(projectDir, prdDir, maxRuntime) {
4593
4674
  const content = fs6.readFileSync(claimPath, "utf-8");
4594
4675
  const claimData = JSON.parse(content);
4595
4676
  const age = Math.floor(Date.now() / 1e3) - claimData.timestamp;
4596
- if (age < maxRuntime) {
4677
+ if (age < claimStaleAfter) {
4597
4678
  claimed++;
4598
4679
  } else {
4599
4680
  pending++;
@@ -4625,6 +4706,7 @@ function parsePrdDependencies(prdPath) {
4625
4706
  }
4626
4707
  }
4627
4708
  function collectPrdInfo(projectDir, prdDir, maxRuntime) {
4709
+ const claimStaleAfter = maxRuntime > 0 ? maxRuntime : 14400;
4628
4710
  const fullPrdPath = path6.join(projectDir, prdDir);
4629
4711
  const prds = [];
4630
4712
  if (!fs6.existsSync(fullPrdPath)) {
@@ -4669,7 +4751,7 @@ function collectPrdInfo(projectDir, prdDir, maxRuntime) {
4669
4751
  const content = fs6.readFileSync(claimPath, "utf-8");
4670
4752
  const claimData = JSON.parse(content);
4671
4753
  const age = Math.floor(Date.now() / 1e3) - claimData.timestamp;
4672
- if (age < maxRuntime) {
4754
+ if (age < claimStaleAfter) {
4673
4755
  if (executorLock.running) {
4674
4756
  status = "in-progress";
4675
4757
  } else {
@@ -4872,7 +4954,8 @@ function collectLogInfo(projectDir) {
4872
4954
  { name: "planner", fileName: `${PLANNER_LOG_NAME}.log` },
4873
4955
  { name: "analytics", fileName: `${ANALYTICS_LOG_NAME}.log` },
4874
4956
  { name: "pr-resolver", fileName: `${PR_RESOLVER_LOG_NAME}.log` },
4875
- { name: "merger", fileName: `${MERGER_LOG_NAME}.log` }
4957
+ { name: "merger", fileName: `${MERGER_LOG_NAME}.log` },
4958
+ { name: "manager", fileName: `${MANAGER_LOG_NAME}.log` }
4876
4959
  ];
4877
4960
  return logEntries.map(({ name, fileName }) => {
4878
4961
  const logPath = path6.join(projectDir, LOG_DIR, fileName);
@@ -4904,6 +4987,7 @@ async function fetchStatusSnapshot(projectDir, config) {
4904
4987
  const analyticsLock = checkLockFile(analyticsLockPath(projectDir));
4905
4988
  const prResolverLock = checkLockFile(prResolverLockPath(projectDir));
4906
4989
  const mergerLock = checkLockFile(mergerLockPath(projectDir));
4990
+ const managerLock = checkLockFile(managerLockPath(projectDir));
4907
4991
  const processes = [
4908
4992
  { name: "executor", running: executorLock.running, pid: executorLock.pid },
4909
4993
  { name: "reviewer", running: reviewerLock.running, pid: reviewerLock.pid },
@@ -4912,7 +4996,8 @@ async function fetchStatusSnapshot(projectDir, config) {
4912
4996
  { name: "planner", running: plannerLock.running, pid: plannerLock.pid },
4913
4997
  { name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid },
4914
4998
  { name: "pr-resolver", running: prResolverLock.running, pid: prResolverLock.pid },
4915
- { 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 }
4916
5001
  ];
4917
5002
  const prds = collectPrdInfo(projectDir, config.prdDir, config.maxRuntime);
4918
5003
  const prs = await collectPrInfo(projectDir, config.branchPatterns);
@@ -4953,7 +5038,7 @@ function getLockFilePaths(projectDir) {
4953
5038
  };
4954
5039
  }
4955
5040
  function sleep(ms) {
4956
- return new Promise((resolve9) => setTimeout(resolve9, ms));
5041
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
4957
5042
  }
4958
5043
  async function cancelProcess(processType, lockPath, force = false) {
4959
5044
  const lockStatus = checkLockFile(lockPath);
@@ -5949,6 +6034,10 @@ function getEventEmoji(event) {
5949
6034
  return "\u{1F500}";
5950
6035
  case "merge_failed":
5951
6036
  return "\u274C";
6037
+ case "manager_blocked":
6038
+ return "\u26A0\uFE0F";
6039
+ case "manager_weekly_summary":
6040
+ return "\u{1F4CA}";
5952
6041
  }
5953
6042
  }
5954
6043
  function getEventTitle(event) {
@@ -5983,6 +6072,10 @@ function getEventTitle(event) {
5983
6072
  return "PR Merged";
5984
6073
  case "merge_failed":
5985
6074
  return "Merge Failed";
6075
+ case "manager_blocked":
6076
+ return "Manager Needs Human Input";
6077
+ case "manager_weekly_summary":
6078
+ return "Manager Weekly Summary";
5986
6079
  }
5987
6080
  }
5988
6081
  function getEventColor(event) {
@@ -6017,6 +6110,10 @@ function getEventColor(event) {
6017
6110
  return 10181046;
6018
6111
  case "merge_failed":
6019
6112
  return 16711680;
6113
+ case "manager_blocked":
6114
+ return 16753920;
6115
+ case "manager_weekly_summary":
6116
+ return 3447003;
6020
6117
  }
6021
6118
  }
6022
6119
  function buildDescription(ctx) {
@@ -7271,7 +7368,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
7271
7368
  const logStream = fs18.createWriteStream(logFile, { flags: "w" });
7272
7369
  logStream.on("error", () => {
7273
7370
  });
7274
- return new Promise((resolve9) => {
7371
+ return new Promise((resolve11) => {
7275
7372
  const childEnv = {
7276
7373
  ...process.env,
7277
7374
  ...config.providerEnv,
@@ -7290,7 +7387,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
7290
7387
  });
7291
7388
  child.on("error", (error2) => {
7292
7389
  logStream.end();
7293
- resolve9({
7390
+ resolve11({
7294
7391
  sliced: false,
7295
7392
  error: `Failed to spawn provider: ${error2.message}`,
7296
7393
  item
@@ -7299,7 +7396,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
7299
7396
  child.on("close", (code) => {
7300
7397
  logStream.end();
7301
7398
  if (code !== 0) {
7302
- resolve9({
7399
+ resolve11({
7303
7400
  sliced: false,
7304
7401
  error: `Provider exited with code ${code ?? 1}`,
7305
7402
  item
@@ -7307,14 +7404,14 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
7307
7404
  return;
7308
7405
  }
7309
7406
  if (!fs18.existsSync(filePath)) {
7310
- resolve9({
7407
+ resolve11({
7311
7408
  sliced: false,
7312
7409
  error: `Provider did not create expected file: ${filePath}`,
7313
7410
  item
7314
7411
  });
7315
7412
  return;
7316
7413
  }
7317
- resolve9({
7414
+ resolve11({
7318
7415
  sliced: true,
7319
7416
  file: filename,
7320
7417
  item
@@ -7476,7 +7573,7 @@ async function executeScript(scriptPath, args = [], env = {}, options = {}) {
7476
7573
  return result.exitCode;
7477
7574
  }
7478
7575
  async function executeScriptWithOutput(scriptPath, args = [], env = {}, options = {}) {
7479
- return new Promise((resolve9, reject) => {
7576
+ return new Promise((resolve11, reject) => {
7480
7577
  const childEnv = {
7481
7578
  ...process.env,
7482
7579
  ...env
@@ -7502,7 +7599,7 @@ async function executeScriptWithOutput(scriptPath, args = [], env = {}, options
7502
7599
  reject(error2);
7503
7600
  });
7504
7601
  child.on("close", (code) => {
7505
- resolve9({
7602
+ resolve11({
7506
7603
  exitCode: code ?? 1,
7507
7604
  stdout: stdoutChunks.join(""),
7508
7605
  stderr: stderrChunks.join("")
@@ -7539,6 +7636,8 @@ function isJobTypeEnabled(config, jobType) {
7539
7636
  return config.roadmapScanner.enabled;
7540
7637
  case "analytics":
7541
7638
  return config.analytics.enabled;
7639
+ case "manager":
7640
+ return config.manager.enabled;
7542
7641
  default:
7543
7642
  return true;
7544
7643
  }
@@ -7547,6 +7646,8 @@ function getJobSchedule(config, jobType) {
7547
7646
  switch (jobType) {
7548
7647
  case "reviewer":
7549
7648
  return config.reviewerSchedule ?? "";
7649
+ case "manager":
7650
+ return config.manager.schedule ?? "";
7550
7651
  case "executor":
7551
7652
  default:
7552
7653
  return config.cronSchedule ?? "";
@@ -7907,6 +8008,8 @@ function getLockPathForJob(projectPath, jobType) {
7907
8008
  return prResolverLockPath(projectPath);
7908
8009
  case "merger":
7909
8010
  return mergerLockPath(projectPath);
8011
+ case "manager":
8012
+ return managerLockPath(projectPath);
7910
8013
  }
7911
8014
  }
7912
8015
  function isRunningEntryStale(entry) {
@@ -8927,6 +9030,562 @@ var init_audit = __esm({
8927
9030
  }
8928
9031
  });
8929
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
+
8930
9589
  // ../core/dist/feedback/outcome-parser.js
8931
9590
  function stripAnsi2(value) {
8932
9591
  return value.replace(ANSI_PATTERN, "");
@@ -9789,6 +10448,17 @@ __export(dist_exports, {
9789
10448
  DEFAULT_FEEDBACK: () => DEFAULT_FEEDBACK,
9790
10449
  DEFAULT_JOB_PROVIDERS: () => DEFAULT_JOB_PROVIDERS,
9791
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,
9792
10462
  DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
9793
10463
  DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
9794
10464
  DEFAULT_MAX_RUNTIME: () => DEFAULT_MAX_RUNTIME,
@@ -9866,6 +10536,7 @@ __export(dist_exports, {
9866
10536
  LOG_FILE_NAMES: () => LOG_FILE_NAMES,
9867
10537
  LocalKanbanProvider: () => LocalKanbanProvider,
9868
10538
  Logger: () => Logger,
10539
+ MANAGER_LOG_NAME: () => MANAGER_LOG_NAME,
9869
10540
  MAX_HISTORY_RECORDS_PER_PRD: () => MAX_HISTORY_RECORDS_PER_PRD,
9870
10541
  MERGER_LOG_NAME: () => MERGER_LOG_NAME,
9871
10542
  NIGHT_WATCH_LABELS: () => NIGHT_WATCH_LABELS,
@@ -9894,9 +10565,12 @@ __export(dist_exports, {
9894
10565
  addEntry: () => addEntry,
9895
10566
  analyticsLockPath: () => analyticsLockPath,
9896
10567
  analyzeFeedbackOutcome: () => analyzeFeedbackOutcome,
10568
+ analyzeManagerInputs: () => analyzeManagerInputs,
9897
10569
  auditLockPath: () => auditLockPath,
9898
10570
  buildDescription: () => buildDescription,
9899
10571
  buildJobEnvOverrides: () => buildJobEnvOverrides,
10572
+ buildManagerDraftBody: () => buildManagerDraftBody,
10573
+ buildManagerDraftTitle: () => buildManagerDraftTitle,
9900
10574
  buildProjectFeedbackPromptBlock: () => buildProjectFeedbackPromptBlock,
9901
10575
  buildSessionOutcomeInput: () => buildSessionOutcomeInput,
9902
10576
  calculateStringSimilarity: () => calculateStringSimilarity,
@@ -9931,7 +10605,9 @@ __export(dist_exports, {
9931
10605
  createBoardProvider: () => createBoardProvider,
9932
10606
  createDbForDir: () => createDbForDir,
9933
10607
  createEmptyState: () => createEmptyState,
10608
+ createFindingFingerprint: () => createFindingFingerprint,
9934
10609
  createLogger: () => createLogger,
10610
+ createManagerBoardDrafts: () => createManagerBoardDrafts,
9935
10611
  createSlicerPromptVars: () => createSlicerPromptVars,
9936
10612
  createSpinner: () => createSpinner,
9937
10613
  createTable: () => createTable,
@@ -10025,20 +10701,24 @@ __export(dist_exports, {
10025
10701
  isInCooldown: () => isInCooldown,
10026
10702
  isItemProcessed: () => isItemProcessed,
10027
10703
  isJobTypeEnabled: () => isJobTypeEnabled,
10704
+ isKnownFinding: () => isKnownFinding,
10028
10705
  isProcessAliveSince: () => isProcessAliveSince,
10029
10706
  isProcessRunning: () => isProcessRunning,
10030
10707
  isValidCategory: () => isValidCategory,
10031
10708
  isValidHorizon: () => isValidHorizon,
10032
10709
  isValidPriority: () => isValidPriority,
10710
+ isWeeklySummaryDue: () => isWeeklySummaryDue,
10033
10711
  label: () => label,
10034
10712
  listPrdStatesByStatus: () => listPrdStatesByStatus,
10035
10713
  loadAuditFindings: () => loadAuditFindings,
10036
10714
  loadConfig: () => loadConfig,
10037
10715
  loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
10038
10716
  loadHistory: () => loadHistory,
10717
+ loadManagerMemory: () => loadManagerMemory,
10039
10718
  loadRegistry: () => loadRegistry,
10040
10719
  loadRoadmapState: () => loadRoadmapState,
10041
10720
  loadSlicerTemplate: () => loadSlicerTemplate,
10721
+ managerLockPath: () => managerLockPath,
10042
10722
  markItemProcessed: () => markItemProcessed,
10043
10723
  markJobRunning: () => markJobRunning,
10044
10724
  markPrdDone: () => markPrdDone,
@@ -10058,6 +10738,8 @@ __export(dist_exports, {
10058
10738
  prResolverLockPath: () => prResolverLockPath,
10059
10739
  prepareBranchWorktree: () => prepareBranchWorktree,
10060
10740
  prepareDetachedWorktree: () => prepareDetachedWorktree,
10741
+ prepareManagerDrafts: () => prepareManagerDrafts,
10742
+ prepareManagerNotificationDecisions: () => prepareManagerNotificationDecisions,
10061
10743
  projectRuntimeKey: () => projectRuntimeKey,
10062
10744
  pruneProjectData: () => pruneProjectData,
10063
10745
  qaLockPath: () => qaLockPath,
@@ -10072,11 +10754,13 @@ __export(dist_exports, {
10072
10754
  removeEntriesForProject: () => removeEntriesForProject,
10073
10755
  removeJob: () => removeJob,
10074
10756
  removeProject: () => removeProject,
10757
+ renderManagerMemory: () => renderManagerMemory,
10075
10758
  renderPrdTemplate: () => renderPrdTemplate,
10076
10759
  renderProjectFeedbackBlock: () => renderProjectFeedbackBlock,
10077
10760
  renderSlicerPrompt: () => renderSlicerPrompt,
10078
10761
  resetRepositories: () => resetRepositories,
10079
10762
  resolveJobProvider: () => resolveJobProvider,
10763
+ resolveManagerConfig: () => resolveManagerConfig,
10080
10764
  resolvePreset: () => resolvePreset,
10081
10765
  resolveProviderBucketKey: () => resolveProviderBucketKey,
10082
10766
  resolveWorktreeBaseRef: () => resolveWorktreeBaseRef,
@@ -10084,6 +10768,7 @@ __export(dist_exports, {
10084
10768
  rotateLog: () => rotateLog,
10085
10769
  runAllChecks: () => runAllChecks,
10086
10770
  runAnalytics: () => runAnalytics,
10771
+ runManager: () => runManager,
10087
10772
  runMigrations: () => runMigrations,
10088
10773
  saveConfig: () => saveConfig,
10089
10774
  saveGlobalNotificationsConfig: () => saveGlobalNotificationsConfig,
@@ -10102,6 +10787,8 @@ __export(dist_exports, {
10102
10787
  sortByPriority: () => sortByPriority,
10103
10788
  step: () => step,
10104
10789
  success: () => success,
10790
+ summarizeCreatedDrafts: () => summarizeCreatedDrafts,
10791
+ summarizeSkippedFindings: () => summarizeSkippedFindings,
10105
10792
  syncAuditFindingsToBoard: () => syncAuditFindingsToBoard,
10106
10793
  unmarkItemProcessed: () => unmarkItemProcessed,
10107
10794
  unregisterProject: () => unregisterProject,
@@ -10111,6 +10798,7 @@ __export(dist_exports, {
10111
10798
  validateWebhook: () => validateWebhook,
10112
10799
  warn: () => warn,
10113
10800
  writeCrontab: () => writeCrontab,
10801
+ writeManagerMemory: () => writeManagerMemory,
10114
10802
  writePrdState: () => writePrdState
10115
10803
  });
10116
10804
  var init_dist = __esm({
@@ -10161,6 +10849,7 @@ var init_dist = __esm({
10161
10849
  init_summary();
10162
10850
  init_analytics();
10163
10851
  init_audit();
10852
+ init_manager();
10164
10853
  init_outcome_parser();
10165
10854
  init_pattern_analyzer();
10166
10855
  init_prompt_augmenter();
@@ -10173,30 +10862,30 @@ var init_dist = __esm({
10173
10862
  // src/cli.ts
10174
10863
  import "reflect-metadata";
10175
10864
  import { Command as Command3 } from "commander";
10176
- import { existsSync as existsSync34, readFileSync as readFileSync21 } from "fs";
10865
+ import { existsSync as existsSync36, readFileSync as readFileSync23 } from "fs";
10177
10866
  import { fileURLToPath as fileURLToPath6 } from "url";
10178
- import { dirname as dirname12, join as join38 } from "path";
10867
+ import { dirname as dirname13, join as join39 } from "path";
10179
10868
 
10180
10869
  // src/commands/init.ts
10181
10870
  init_dist();
10182
- import fs23 from "fs";
10183
- import path22 from "path";
10871
+ import fs26 from "fs";
10872
+ import path25 from "path";
10184
10873
  import { execSync as execSync3 } from "child_process";
10185
10874
  import { fileURLToPath as fileURLToPath3 } from "url";
10186
- import { dirname as dirname6, join as join20 } from "path";
10875
+ import { dirname as dirname7, join as join21 } from "path";
10187
10876
  import * as readline from "readline";
10188
10877
  var __filename2 = fileURLToPath3(import.meta.url);
10189
- var __dirname2 = dirname6(__filename2);
10878
+ var __dirname2 = dirname7(__filename2);
10190
10879
  function findTemplatesDir(startDir) {
10191
10880
  let d = startDir;
10192
10881
  for (let i = 0; i < 8; i++) {
10193
- const candidate = join20(d, "templates");
10194
- if (fs23.existsSync(candidate) && fs23.statSync(candidate).isDirectory()) {
10882
+ const candidate = join21(d, "templates");
10883
+ if (fs26.existsSync(candidate) && fs26.statSync(candidate).isDirectory()) {
10195
10884
  return candidate;
10196
10885
  }
10197
- d = dirname6(d);
10886
+ d = dirname7(d);
10198
10887
  }
10199
- return join20(startDir, "templates");
10888
+ return join21(startDir, "templates");
10200
10889
  }
10201
10890
  var TEMPLATES_DIR = findTemplatesDir(__dirname2);
10202
10891
  var NW_SKILLS = [
@@ -10208,12 +10897,12 @@ var NW_SKILLS = [
10208
10897
  "nw-review"
10209
10898
  ];
10210
10899
  function hasPlaywrightDependency(cwd) {
10211
- const packageJsonPath = path22.join(cwd, "package.json");
10212
- if (!fs23.existsSync(packageJsonPath)) {
10900
+ const packageJsonPath = path25.join(cwd, "package.json");
10901
+ if (!fs26.existsSync(packageJsonPath)) {
10213
10902
  return false;
10214
10903
  }
10215
10904
  try {
10216
- const packageJson2 = JSON.parse(fs23.readFileSync(packageJsonPath, "utf-8"));
10905
+ const packageJson2 = JSON.parse(fs26.readFileSync(packageJsonPath, "utf-8"));
10217
10906
  return Boolean(
10218
10907
  packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
10219
10908
  );
@@ -10225,7 +10914,7 @@ function detectPlaywright(cwd) {
10225
10914
  if (hasPlaywrightDependency(cwd)) {
10226
10915
  return true;
10227
10916
  }
10228
- if (fs23.existsSync(path22.join(cwd, "node_modules", ".bin", "playwright"))) {
10917
+ if (fs26.existsSync(path25.join(cwd, "node_modules", ".bin", "playwright"))) {
10229
10918
  return true;
10230
10919
  }
10231
10920
  try {
@@ -10241,10 +10930,10 @@ function detectPlaywright(cwd) {
10241
10930
  }
10242
10931
  }
10243
10932
  function resolvePlaywrightInstallCommand(cwd) {
10244
- if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) {
10933
+ if (fs26.existsSync(path25.join(cwd, "pnpm-lock.yaml"))) {
10245
10934
  return "pnpm add -D @playwright/test";
10246
10935
  }
10247
- if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) {
10936
+ if (fs26.existsSync(path25.join(cwd, "yarn.lock"))) {
10248
10937
  return "yarn add -D @playwright/test";
10249
10938
  }
10250
10939
  return "npm install -D @playwright/test";
@@ -10253,7 +10942,7 @@ function promptYesNo(question, defaultNo = true) {
10253
10942
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
10254
10943
  return Promise.resolve(false);
10255
10944
  }
10256
- return new Promise((resolve9) => {
10945
+ return new Promise((resolve11) => {
10257
10946
  const rl = readline.createInterface({
10258
10947
  input: process.stdin,
10259
10948
  output: process.stdout
@@ -10263,10 +10952,10 @@ function promptYesNo(question, defaultNo = true) {
10263
10952
  rl.close();
10264
10953
  const normalized = answer.trim().toLowerCase();
10265
10954
  if (normalized === "") {
10266
- resolve9(!defaultNo);
10955
+ resolve11(!defaultNo);
10267
10956
  return;
10268
10957
  }
10269
- resolve9(normalized === "y" || normalized === "yes");
10958
+ resolve11(normalized === "y" || normalized === "yes");
10270
10959
  });
10271
10960
  });
10272
10961
  }
@@ -10369,7 +11058,7 @@ function getDefaultBranch(cwd) {
10369
11058
  }
10370
11059
  }
10371
11060
  function promptProviderSelection(providers) {
10372
- return new Promise((resolve9, reject) => {
11061
+ return new Promise((resolve11, reject) => {
10373
11062
  const rl = readline.createInterface({
10374
11063
  input: process.stdin,
10375
11064
  output: process.stdout
@@ -10385,13 +11074,13 @@ function promptProviderSelection(providers) {
10385
11074
  reject(new Error("Invalid selection. Please run init again and select a valid number."));
10386
11075
  return;
10387
11076
  }
10388
- resolve9(providers[selection - 1]);
11077
+ resolve11(providers[selection - 1]);
10389
11078
  });
10390
11079
  });
10391
11080
  }
10392
11081
  function ensureDir(dirPath) {
10393
- if (!fs23.existsSync(dirPath)) {
10394
- fs23.mkdirSync(dirPath, { recursive: true });
11082
+ if (!fs26.existsSync(dirPath)) {
11083
+ fs26.mkdirSync(dirPath, { recursive: true });
10395
11084
  }
10396
11085
  }
10397
11086
  function buildInitConfig(params) {
@@ -10441,6 +11130,7 @@ function buildInitConfig(params) {
10441
11130
  },
10442
11131
  audit: { ...defaults.audit },
10443
11132
  analytics: { ...defaults.analytics },
11133
+ manager: { ...defaults.manager },
10444
11134
  feedback: { ...defaults.feedback },
10445
11135
  merger: { ...defaults.merger },
10446
11136
  prResolver: { ...defaults.prResolver },
@@ -10465,30 +11155,30 @@ function buildInitConfig(params) {
10465
11155
  }
10466
11156
  function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
10467
11157
  if (customTemplatesDir !== null) {
10468
- const customPath = join20(customTemplatesDir, templateName);
10469
- if (fs23.existsSync(customPath)) {
11158
+ const customPath = join21(customTemplatesDir, templateName);
11159
+ if (fs26.existsSync(customPath)) {
10470
11160
  return { path: customPath, source: "custom" };
10471
11161
  }
10472
11162
  }
10473
- return { path: join20(bundledTemplatesDir, templateName), source: "bundled" };
11163
+ return { path: join21(bundledTemplatesDir, templateName), source: "bundled" };
10474
11164
  }
10475
11165
  function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
10476
- if (fs23.existsSync(targetPath) && !force) {
11166
+ if (fs26.existsSync(targetPath) && !force) {
10477
11167
  console.log(` Skipped (exists): ${targetPath}`);
10478
11168
  return { created: false, source: source ?? "bundled" };
10479
11169
  }
10480
- const templatePath = sourcePath ?? join20(TEMPLATES_DIR, templateName);
11170
+ const templatePath = sourcePath ?? join21(TEMPLATES_DIR, templateName);
10481
11171
  const resolvedSource = source ?? "bundled";
10482
- let content = fs23.readFileSync(templatePath, "utf-8");
11172
+ let content = fs26.readFileSync(templatePath, "utf-8");
10483
11173
  for (const [key, value] of Object.entries(replacements)) {
10484
11174
  content = content.replaceAll(key, value);
10485
11175
  }
10486
- fs23.writeFileSync(targetPath, content);
11176
+ fs26.writeFileSync(targetPath, content);
10487
11177
  console.log(` Created: ${targetPath} (${resolvedSource})`);
10488
11178
  return { created: true, source: resolvedSource };
10489
11179
  }
10490
11180
  function addToGitignore(cwd) {
10491
- const gitignorePath = path22.join(cwd, ".gitignore");
11181
+ const gitignorePath = path25.join(cwd, ".gitignore");
10492
11182
  const entries = [
10493
11183
  {
10494
11184
  pattern: "/logs/",
@@ -10502,13 +11192,13 @@ function addToGitignore(cwd) {
10502
11192
  },
10503
11193
  { pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
10504
11194
  ];
10505
- if (!fs23.existsSync(gitignorePath)) {
11195
+ if (!fs26.existsSync(gitignorePath)) {
10506
11196
  const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
10507
- fs23.writeFileSync(gitignorePath, lines.join("\n"));
11197
+ fs26.writeFileSync(gitignorePath, lines.join("\n"));
10508
11198
  console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
10509
11199
  return;
10510
11200
  }
10511
- const content = fs23.readFileSync(gitignorePath, "utf-8");
11201
+ const content = fs26.readFileSync(gitignorePath, "utf-8");
10512
11202
  const missing = entries.filter((e) => !e.check(content));
10513
11203
  if (missing.length === 0) {
10514
11204
  console.log(` Skipped (exists): Night Watch entries in .gitignore`);
@@ -10516,59 +11206,59 @@ function addToGitignore(cwd) {
10516
11206
  }
10517
11207
  const additions = missing.map((e) => e.pattern).join("\n");
10518
11208
  const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
10519
- fs23.writeFileSync(gitignorePath, newContent);
11209
+ fs26.writeFileSync(gitignorePath, newContent);
10520
11210
  console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
10521
11211
  }
10522
11212
  function installSkills(cwd, provider, force, templatesDir) {
10523
- const skillsTemplatesDir = path22.join(templatesDir, "skills");
10524
- if (!fs23.existsSync(skillsTemplatesDir)) {
11213
+ const skillsTemplatesDir = path25.join(templatesDir, "skills");
11214
+ if (!fs26.existsSync(skillsTemplatesDir)) {
10525
11215
  return { location: "", installed: 0, skipped: 0, type: "none" };
10526
11216
  }
10527
11217
  const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
10528
11218
  const isCodexProvider = provider === "codex";
10529
- const claudeDir = path22.join(cwd, ".claude");
10530
- if (isClaudeProvider || fs23.existsSync(claudeDir)) {
11219
+ const claudeDir = path25.join(cwd, ".claude");
11220
+ if (isClaudeProvider || fs26.existsSync(claudeDir)) {
10531
11221
  ensureDir(claudeDir);
10532
- const skillsDir = path22.join(claudeDir, "skills");
11222
+ const skillsDir = path25.join(claudeDir, "skills");
10533
11223
  ensureDir(skillsDir);
10534
11224
  let installed = 0;
10535
11225
  let skipped = 0;
10536
11226
  for (const skillName of NW_SKILLS) {
10537
- const templateFile = path22.join(skillsTemplatesDir, `${skillName}.md`);
10538
- if (!fs23.existsSync(templateFile)) continue;
10539
- 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);
10540
11230
  ensureDir(skillDir);
10541
- const target = path22.join(skillDir, "SKILL.md");
10542
- if (fs23.existsSync(target) && !force) {
11231
+ const target = path25.join(skillDir, "SKILL.md");
11232
+ if (fs26.existsSync(target) && !force) {
10543
11233
  skipped++;
10544
11234
  continue;
10545
11235
  }
10546
- fs23.copyFileSync(templateFile, target);
11236
+ fs26.copyFileSync(templateFile, target);
10547
11237
  installed++;
10548
11238
  }
10549
11239
  return { location: ".claude/skills/", installed, skipped, type: "claude" };
10550
11240
  }
10551
11241
  if (isCodexProvider) {
10552
- const agentsFile = path22.join(cwd, "AGENTS.md");
10553
- const blockFile = path22.join(skillsTemplatesDir, "_codex-block.md");
10554
- 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)) {
10555
11245
  return { location: "", installed: 0, skipped: 0, type: "none" };
10556
11246
  }
10557
- const block = fs23.readFileSync(blockFile, "utf-8");
11247
+ const block = fs26.readFileSync(blockFile, "utf-8");
10558
11248
  const marker = "## Night Watch Skills";
10559
- if (!fs23.existsSync(agentsFile)) {
10560
- fs23.writeFileSync(agentsFile, block);
11249
+ if (!fs26.existsSync(agentsFile)) {
11250
+ fs26.writeFileSync(agentsFile, block);
10561
11251
  return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
10562
11252
  }
10563
- const existing = fs23.readFileSync(agentsFile, "utf-8");
11253
+ const existing = fs26.readFileSync(agentsFile, "utf-8");
10564
11254
  if (existing.includes(marker)) {
10565
11255
  if (!force) {
10566
11256
  return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
10567
11257
  }
10568
11258
  const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
10569
- fs23.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
11259
+ fs26.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
10570
11260
  } else {
10571
- fs23.appendFileSync(agentsFile, "\n\n" + block);
11261
+ fs26.appendFileSync(agentsFile, "\n\n" + block);
10572
11262
  }
10573
11263
  return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
10574
11264
  }
@@ -10692,28 +11382,28 @@ function initCommand(program2) {
10692
11382
  "${DEFAULT_BRANCH}": defaultBranch
10693
11383
  };
10694
11384
  step(6, totalSteps, "Creating PRD directory structure...");
10695
- const prdDirPath = path22.join(cwd, prdDir);
10696
- const doneDirPath = path22.join(prdDirPath, "done");
11385
+ const prdDirPath = path25.join(cwd, prdDir);
11386
+ const doneDirPath = path25.join(prdDirPath, "done");
10697
11387
  ensureDir(doneDirPath);
10698
11388
  success(`Created ${prdDirPath}/`);
10699
11389
  success(`Created ${doneDirPath}/`);
10700
11390
  step(7, totalSteps, "Creating logs directory...");
10701
- const logsPath = path22.join(cwd, LOG_DIR);
11391
+ const logsPath = path25.join(cwd, LOG_DIR);
10702
11392
  ensureDir(logsPath);
10703
11393
  success(`Created ${logsPath}/`);
10704
11394
  addToGitignore(cwd);
10705
11395
  step(8, totalSteps, "Creating instructions directory...");
10706
- const instructionsDir = path22.join(cwd, "instructions");
11396
+ const instructionsDir = path25.join(cwd, "instructions");
10707
11397
  ensureDir(instructionsDir);
10708
11398
  success(`Created ${instructionsDir}/`);
10709
11399
  const existingConfig = loadConfig(cwd);
10710
- const customTemplatesDirPath = path22.join(cwd, existingConfig.templatesDir);
10711
- const customTemplatesDir = fs23.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
11400
+ const customTemplatesDirPath = path25.join(cwd, existingConfig.templatesDir);
11401
+ const customTemplatesDir = fs26.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
10712
11402
  const templateSources = [];
10713
11403
  const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
10714
11404
  const nwResult = processTemplate(
10715
11405
  "executor.md",
10716
- path22.join(instructionsDir, "executor.md"),
11406
+ path25.join(instructionsDir, "executor.md"),
10717
11407
  replacements,
10718
11408
  force,
10719
11409
  nwResolution.path,
@@ -10727,7 +11417,7 @@ function initCommand(program2) {
10727
11417
  );
10728
11418
  const peResult = processTemplate(
10729
11419
  "prd-executor.md",
10730
- path22.join(instructionsDir, "prd-executor.md"),
11420
+ path25.join(instructionsDir, "prd-executor.md"),
10731
11421
  replacements,
10732
11422
  force,
10733
11423
  peResolution.path,
@@ -10737,7 +11427,7 @@ function initCommand(program2) {
10737
11427
  const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
10738
11428
  const prResult = processTemplate(
10739
11429
  "pr-reviewer.md",
10740
- path22.join(instructionsDir, "pr-reviewer.md"),
11430
+ path25.join(instructionsDir, "pr-reviewer.md"),
10741
11431
  replacements,
10742
11432
  force,
10743
11433
  prResolution.path,
@@ -10747,7 +11437,7 @@ function initCommand(program2) {
10747
11437
  const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
10748
11438
  const qaResult = processTemplate(
10749
11439
  "qa.md",
10750
- path22.join(instructionsDir, "qa.md"),
11440
+ path25.join(instructionsDir, "qa.md"),
10751
11441
  replacements,
10752
11442
  force,
10753
11443
  qaResolution.path,
@@ -10757,7 +11447,7 @@ function initCommand(program2) {
10757
11447
  const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
10758
11448
  const auditResult = processTemplate(
10759
11449
  "audit.md",
10760
- path22.join(instructionsDir, "audit.md"),
11450
+ path25.join(instructionsDir, "audit.md"),
10761
11451
  replacements,
10762
11452
  force,
10763
11453
  auditResolution.path,
@@ -10771,7 +11461,7 @@ function initCommand(program2) {
10771
11461
  );
10772
11462
  const plannerResult = processTemplate(
10773
11463
  "prd-creator.md",
10774
- path22.join(instructionsDir, "prd-creator.md"),
11464
+ path25.join(instructionsDir, "prd-creator.md"),
10775
11465
  replacements,
10776
11466
  force,
10777
11467
  plannerResolution.path,
@@ -10779,8 +11469,8 @@ function initCommand(program2) {
10779
11469
  );
10780
11470
  templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
10781
11471
  step(9, totalSteps, "Creating configuration file...");
10782
- const configPath = path22.join(cwd, CONFIG_FILE_NAME);
10783
- if (fs23.existsSync(configPath) && !force) {
11472
+ const configPath = path25.join(cwd, CONFIG_FILE_NAME);
11473
+ if (fs26.existsSync(configPath) && !force) {
10784
11474
  console.log(` Skipped (exists): ${configPath}`);
10785
11475
  } else {
10786
11476
  const config = buildInitConfig({
@@ -10790,11 +11480,11 @@ function initCommand(program2) {
10790
11480
  reviewerEnabled,
10791
11481
  prdDir
10792
11482
  });
10793
- fs23.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
11483
+ fs26.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
10794
11484
  success(`Created ${configPath}`);
10795
11485
  }
10796
11486
  step(10, totalSteps, "Setting up GitHub Project board...");
10797
- const existingRaw = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
11487
+ const existingRaw = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
10798
11488
  const existingBoard = existingRaw.boardProvider;
10799
11489
  let boardSetupStatus = "Skipped";
10800
11490
  if (existingBoard?.projectNumber && !force) {
@@ -10816,14 +11506,14 @@ function initCommand(program2) {
10816
11506
  const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
10817
11507
  const boardTitle = `${projectName} Night Watch`;
10818
11508
  const board = await provider.setupBoard(boardTitle);
10819
- const rawConfig = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
11509
+ const rawConfig = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
10820
11510
  rawConfig.boardProvider = {
10821
11511
  enabled: true,
10822
11512
  provider: "github",
10823
11513
  projectNumber: board.number,
10824
11514
  projectTitle: board.title
10825
11515
  };
10826
- fs23.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
11516
+ fs26.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
10827
11517
  boardSetupStatus = `Created (#${board.number})`;
10828
11518
  success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
10829
11519
  } catch (boardErr) {
@@ -10985,7 +11675,7 @@ async function maybeApplyCronSchedulingDelay(config, jobType, projectDir) {
10985
11675
  return plan;
10986
11676
  }
10987
11677
  if (plan.totalDelayMinutes > 0) {
10988
- await new Promise((resolve9) => setTimeout(resolve9, plan.totalDelayMinutes * 6e4));
11678
+ await new Promise((resolve11) => setTimeout(resolve11, plan.totalDelayMinutes * 6e4));
10989
11679
  }
10990
11680
  return getSchedulingPlan(projectDir, config, jobType);
10991
11681
  }
@@ -11049,8 +11739,8 @@ function recordJobOutcome(input) {
11049
11739
  }
11050
11740
 
11051
11741
  // src/commands/run.ts
11052
- import * as fs24 from "fs";
11053
- import * as path23 from "path";
11742
+ import * as fs27 from "fs";
11743
+ import * as path26 from "path";
11054
11744
  function resolveRunNotificationEvent(exitCode, scriptStatus) {
11055
11745
  if (exitCode === 124) {
11056
11746
  return "run_timeout";
@@ -11148,7 +11838,7 @@ function buildRunNotificationContext(config, projectDir, event, exitCode, script
11148
11838
  const checkpointStatus = checkpointValue === "created" || checkpointValue === "available" || checkpointValue === "none" ? checkpointValue : void 0;
11149
11839
  return {
11150
11840
  event,
11151
- projectName: path23.basename(projectDir),
11841
+ projectName: path26.basename(projectDir),
11152
11842
  exitCode,
11153
11843
  provider: config.provider,
11154
11844
  prdName: scriptResult?.data.prd ?? extractResultValueFromOutput(rawOutput, "prd"),
@@ -11168,12 +11858,12 @@ function buildRunNotificationContext(config, projectDir, event, exitCode, script
11168
11858
  };
11169
11859
  }
11170
11860
  function getCrossProjectFallbackCandidates(currentProjectDir) {
11171
- const current = path23.resolve(currentProjectDir);
11861
+ const current = path26.resolve(currentProjectDir);
11172
11862
  const { valid, invalid } = validateRegistry();
11173
11863
  for (const entry of invalid) {
11174
11864
  warn(`Skipping invalid registry entry: ${entry.path}`);
11175
11865
  }
11176
- return valid.filter((entry) => path23.resolve(entry.path) !== current);
11866
+ return valid.filter((entry) => path26.resolve(entry.path) !== current);
11177
11867
  }
11178
11868
  async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult, rawOutput) {
11179
11869
  if (isRateLimitFallbackTriggered(scriptResult?.data)) {
@@ -11183,7 +11873,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
11183
11873
  if (nonTelegramWebhooks.length > 0) {
11184
11874
  const _rateLimitCtx = {
11185
11875
  event: "rate_limit_fallback",
11186
- projectName: path23.basename(projectDir),
11876
+ projectName: path26.basename(projectDir),
11187
11877
  exitCode,
11188
11878
  provider: config.provider
11189
11879
  };
@@ -11420,23 +12110,24 @@ function applyCliOverrides(config, options) {
11420
12110
  return overridden;
11421
12111
  }
11422
12112
  function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
11423
- const absolutePrdDir = path23.join(projectDir, prdDir);
11424
- const doneDir = path23.join(absolutePrdDir, "done");
12113
+ const claimStaleAfter = maxRuntime > 0 ? maxRuntime : 14400;
12114
+ const absolutePrdDir = path26.join(projectDir, prdDir);
12115
+ const doneDir = path26.join(absolutePrdDir, "done");
11425
12116
  const pending = [];
11426
12117
  const completed = [];
11427
- if (fs24.existsSync(absolutePrdDir)) {
11428
- const entries = fs24.readdirSync(absolutePrdDir, { withFileTypes: true });
12118
+ if (fs27.existsSync(absolutePrdDir)) {
12119
+ const entries = fs27.readdirSync(absolutePrdDir, { withFileTypes: true });
11429
12120
  for (const entry of entries) {
11430
12121
  if (entry.isFile() && entry.name.endsWith(".md")) {
11431
- const claimPath = path23.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
12122
+ const claimPath = path26.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
11432
12123
  let claimed = false;
11433
12124
  let claimInfo = null;
11434
- if (fs24.existsSync(claimPath)) {
12125
+ if (fs27.existsSync(claimPath)) {
11435
12126
  try {
11436
- const content = fs24.readFileSync(claimPath, "utf-8");
12127
+ const content = fs27.readFileSync(claimPath, "utf-8");
11437
12128
  const data = JSON.parse(content);
11438
12129
  const age = Math.floor(Date.now() / 1e3) - data.timestamp;
11439
- if (age < maxRuntime) {
12130
+ if (age < claimStaleAfter) {
11440
12131
  claimed = true;
11441
12132
  claimInfo = { hostname: data.hostname, pid: data.pid, timestamp: data.timestamp };
11442
12133
  }
@@ -11447,8 +12138,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
11447
12138
  }
11448
12139
  }
11449
12140
  }
11450
- if (fs24.existsSync(doneDir)) {
11451
- const entries = fs24.readdirSync(doneDir, { withFileTypes: true });
12141
+ if (fs27.existsSync(doneDir)) {
12142
+ const entries = fs27.readdirSync(doneDir, { withFileTypes: true });
11452
12143
  for (const entry of entries) {
11453
12144
  if (entry.isFile() && entry.name.endsWith(".md")) {
11454
12145
  completed.push(entry.name);
@@ -11633,7 +12324,7 @@ ${stderr}`
11633
12324
  // src/commands/review.ts
11634
12325
  init_dist();
11635
12326
  import { execFileSync as execFileSync5 } from "child_process";
11636
- import * as path24 from "path";
12327
+ import * as path27 from "path";
11637
12328
  function shouldSendReviewNotification(scriptStatus) {
11638
12329
  if (!scriptStatus) {
11639
12330
  return true;
@@ -11961,7 +12652,7 @@ ${stderr}`);
11961
12652
  const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
11962
12653
  await sendNotifications(config, {
11963
12654
  event: reviewEvent,
11964
- projectName: path24.basename(projectDir),
12655
+ projectName: path27.basename(projectDir),
11965
12656
  exitCode,
11966
12657
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
11967
12658
  prUrl: fallbackPrDetails?.url,
@@ -11980,7 +12671,7 @@ ${stderr}`);
11980
12671
  const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
11981
12672
  await sendNotifications(config, {
11982
12673
  event: reviewEvent,
11983
- projectName: path24.basename(projectDir),
12674
+ projectName: path27.basename(projectDir),
11984
12675
  exitCode,
11985
12676
  provider: formatProviderDisplay(
11986
12677
  envVars.NW_PROVIDER_CMD,
@@ -12005,7 +12696,7 @@ ${stderr}`);
12005
12696
  const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
12006
12697
  const _mergeCtx = {
12007
12698
  event: "pr_auto_merged",
12008
- projectName: path24.basename(projectDir),
12699
+ projectName: path27.basename(projectDir),
12009
12700
  exitCode,
12010
12701
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
12011
12702
  prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
@@ -12030,7 +12721,7 @@ ${stderr}`);
12030
12721
 
12031
12722
  // src/commands/qa.ts
12032
12723
  init_dist();
12033
- import * as path25 from "path";
12724
+ import * as path28 from "path";
12034
12725
  function shouldSendQaNotification(scriptStatus) {
12035
12726
  if (!scriptStatus) {
12036
12727
  return true;
@@ -12192,7 +12883,7 @@ ${stderr}`);
12192
12883
  const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
12193
12884
  const _qaCtx = {
12194
12885
  event: "qa_completed",
12195
- projectName: path25.basename(projectDir),
12886
+ projectName: path28.basename(projectDir),
12196
12887
  exitCode,
12197
12888
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
12198
12889
  prNumber: prDetails?.number ?? primaryQaPr,
@@ -12218,8 +12909,8 @@ ${stderr}`);
12218
12909
 
12219
12910
  // src/commands/audit.ts
12220
12911
  init_dist();
12221
- import * as fs25 from "fs";
12222
- import * as path26 from "path";
12912
+ import * as fs28 from "fs";
12913
+ import * as path29 from "path";
12223
12914
  function buildEnvVars4(config, options) {
12224
12915
  const env = buildBaseEnvVars(config, "audit", options.dryRun);
12225
12916
  env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
@@ -12267,7 +12958,7 @@ function auditCommand(program2) {
12267
12958
  if (config.audit.createIssues) {
12268
12959
  configTable.push(["Target Column", config.audit.targetColumn]);
12269
12960
  }
12270
- configTable.push(["Report File", path26.join(projectDir, "logs", "audit-report.md")]);
12961
+ configTable.push(["Report File", path29.join(projectDir, "logs", "audit-report.md")]);
12271
12962
  console.log(configTable.toString());
12272
12963
  header("Provider Invocation");
12273
12964
  const providerCmd = PROVIDER_COMMANDS[auditProvider];
@@ -12325,8 +13016,8 @@ ${stderr}`);
12325
13016
  } else if (scriptResult?.status?.startsWith("skip_")) {
12326
13017
  spinner.succeed("Code audit skipped");
12327
13018
  } else {
12328
- const reportPath = path26.join(projectDir, "logs", "audit-report.md");
12329
- if (!fs25.existsSync(reportPath)) {
13019
+ const reportPath = path29.join(projectDir, "logs", "audit-report.md");
13020
+ if (!fs28.existsSync(reportPath)) {
12330
13021
  spinner.fail("Code audit finished without a report file");
12331
13022
  process.exit(1);
12332
13023
  }
@@ -12343,9 +13034,9 @@ ${stderr}`);
12343
13034
  const providerExit = scriptResult?.data?.provider_exit;
12344
13035
  const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
12345
13036
  spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
12346
- const logPath = path26.join(projectDir, "logs", "audit.log");
12347
- if (fs25.existsSync(logPath)) {
12348
- 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);
12349
13040
  if (logLines.length > 0) {
12350
13041
  process.stderr.write(logLines.join("\n") + "\n");
12351
13042
  }
@@ -12489,16 +13180,16 @@ function analyticsCommand(program2) {
12489
13180
  // src/commands/install.ts
12490
13181
  init_dist();
12491
13182
  import { execSync as execSync4 } from "child_process";
12492
- import * as path27 from "path";
12493
- import * as fs26 from "fs";
13183
+ import * as path30 from "path";
13184
+ import * as fs29 from "fs";
12494
13185
  function shellQuote(value) {
12495
13186
  return `'${value.replace(/'/g, `'"'"'`)}'`;
12496
13187
  }
12497
13188
  function getNightWatchBinPath() {
12498
13189
  try {
12499
13190
  const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
12500
- const binPath = path27.join(npmBin, "night-watch");
12501
- if (fs26.existsSync(binPath)) {
13191
+ const binPath = path30.join(npmBin, "night-watch");
13192
+ if (fs29.existsSync(binPath)) {
12502
13193
  return binPath;
12503
13194
  }
12504
13195
  } catch {
@@ -12511,17 +13202,17 @@ function getNightWatchBinPath() {
12511
13202
  }
12512
13203
  function getNodeBinDir() {
12513
13204
  if (process.execPath && process.execPath !== "node") {
12514
- return path27.dirname(process.execPath);
13205
+ return path30.dirname(process.execPath);
12515
13206
  }
12516
13207
  try {
12517
13208
  const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
12518
- return path27.dirname(nodePath);
13209
+ return path30.dirname(nodePath);
12519
13210
  } catch {
12520
13211
  return "";
12521
13212
  }
12522
13213
  }
12523
13214
  function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
12524
- const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path27.dirname(nightWatchBin) : "";
13215
+ const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path30.dirname(nightWatchBin) : "";
12525
13216
  const pathParts = Array.from(
12526
13217
  new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
12527
13218
  );
@@ -12537,12 +13228,12 @@ function performInstall(projectDir, config, options) {
12537
13228
  const nightWatchBin = getNightWatchBinPath();
12538
13229
  const projectName = getProjectName(projectDir);
12539
13230
  const marker = generateMarker(projectName);
12540
- const logDir = path27.join(projectDir, LOG_DIR);
12541
- if (!fs26.existsSync(logDir)) {
12542
- fs26.mkdirSync(logDir, { recursive: true });
13231
+ const logDir = path30.join(projectDir, LOG_DIR);
13232
+ if (!fs29.existsSync(logDir)) {
13233
+ fs29.mkdirSync(logDir, { recursive: true });
12543
13234
  }
12544
- const executorLog = path27.join(logDir, "executor.log");
12545
- const reviewerLog = path27.join(logDir, "reviewer.log");
13235
+ const executorLog = path30.join(logDir, "executor.log");
13236
+ const reviewerLog = path30.join(logDir, "reviewer.log");
12546
13237
  if (!options?.force) {
12547
13238
  const existingEntries2 = Array.from(
12548
13239
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
@@ -12579,7 +13270,7 @@ function performInstall(projectDir, config, options) {
12579
13270
  const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
12580
13271
  if (installSlicer) {
12581
13272
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
12582
- const slicerLog = path27.join(logDir, "slicer.log");
13273
+ const slicerLog = path30.join(logDir, "slicer.log");
12583
13274
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
12584
13275
  entries.push(slicerEntry);
12585
13276
  }
@@ -12587,7 +13278,7 @@ function performInstall(projectDir, config, options) {
12587
13278
  const installQa = disableQa ? false : config.qa.enabled;
12588
13279
  if (installQa) {
12589
13280
  const qaSchedule = config.qa.schedule;
12590
- const qaLog = path27.join(logDir, "qa.log");
13281
+ const qaLog = path30.join(logDir, "qa.log");
12591
13282
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
12592
13283
  entries.push(qaEntry);
12593
13284
  }
@@ -12595,7 +13286,7 @@ function performInstall(projectDir, config, options) {
12595
13286
  const installAudit = disableAudit ? false : config.audit.enabled;
12596
13287
  if (installAudit) {
12597
13288
  const auditSchedule = config.audit.schedule;
12598
- const auditLog = path27.join(logDir, "audit.log");
13289
+ const auditLog = path30.join(logDir, "audit.log");
12599
13290
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
12600
13291
  entries.push(auditEntry);
12601
13292
  }
@@ -12603,7 +13294,7 @@ function performInstall(projectDir, config, options) {
12603
13294
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
12604
13295
  if (installAnalytics) {
12605
13296
  const analyticsSchedule = config.analytics.schedule;
12606
- const analyticsLog = path27.join(logDir, "analytics.log");
13297
+ const analyticsLog = path30.join(logDir, "analytics.log");
12607
13298
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
12608
13299
  entries.push(analyticsEntry);
12609
13300
  }
@@ -12611,7 +13302,7 @@ function performInstall(projectDir, config, options) {
12611
13302
  const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
12612
13303
  if (installPrResolver) {
12613
13304
  const prResolverSchedule = config.prResolver.schedule;
12614
- const prResolverLog = path27.join(logDir, "pr-resolver.log");
13305
+ const prResolverLog = path30.join(logDir, "pr-resolver.log");
12615
13306
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
12616
13307
  entries.push(prResolverEntry);
12617
13308
  }
@@ -12619,10 +13310,18 @@ function performInstall(projectDir, config, options) {
12619
13310
  const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
12620
13311
  if (installMerger) {
12621
13312
  const mergerSchedule = config.merger.schedule;
12622
- const mergerLog = path27.join(logDir, "merger.log");
13313
+ const mergerLog = path30.join(logDir, "merger.log");
12623
13314
  const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
12624
13315
  entries.push(mergerEntry);
12625
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
+ }
12626
13325
  const existingEntries = new Set(
12627
13326
  Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
12628
13327
  );
@@ -12641,7 +13340,7 @@ function performInstall(projectDir, config, options) {
12641
13340
  }
12642
13341
  }
12643
13342
  function installCommand(program2) {
12644
- 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) => {
12645
13344
  try {
12646
13345
  const projectDir = process.cwd();
12647
13346
  const config = loadConfig(projectDir);
@@ -12650,12 +13349,12 @@ function installCommand(program2) {
12650
13349
  const nightWatchBin = getNightWatchBinPath();
12651
13350
  const projectName = getProjectName(projectDir);
12652
13351
  const marker = generateMarker(projectName);
12653
- const logDir = path27.join(projectDir, LOG_DIR);
12654
- if (!fs26.existsSync(logDir)) {
12655
- fs26.mkdirSync(logDir, { recursive: true });
13352
+ const logDir = path30.join(projectDir, LOG_DIR);
13353
+ if (!fs29.existsSync(logDir)) {
13354
+ fs29.mkdirSync(logDir, { recursive: true });
12656
13355
  }
12657
- const executorLog = path27.join(logDir, "executor.log");
12658
- const reviewerLog = path27.join(logDir, "reviewer.log");
13356
+ const executorLog = path30.join(logDir, "executor.log");
13357
+ const reviewerLog = path30.join(logDir, "reviewer.log");
12659
13358
  const existingEntries = Array.from(
12660
13359
  /* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
12661
13360
  );
@@ -12691,7 +13390,7 @@ function installCommand(program2) {
12691
13390
  const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
12692
13391
  let slicerLog;
12693
13392
  if (installSlicer) {
12694
- slicerLog = path27.join(logDir, "slicer.log");
13393
+ slicerLog = path30.join(logDir, "slicer.log");
12695
13394
  const slicerSchedule = config.roadmapScanner.slicerSchedule;
12696
13395
  const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
12697
13396
  entries.push(slicerEntry);
@@ -12700,7 +13399,7 @@ function installCommand(program2) {
12700
13399
  const installQa = disableQa ? false : config.qa.enabled;
12701
13400
  let qaLog;
12702
13401
  if (installQa) {
12703
- qaLog = path27.join(logDir, "qa.log");
13402
+ qaLog = path30.join(logDir, "qa.log");
12704
13403
  const qaSchedule = config.qa.schedule;
12705
13404
  const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
12706
13405
  entries.push(qaEntry);
@@ -12709,7 +13408,7 @@ function installCommand(program2) {
12709
13408
  const installAudit = disableAudit ? false : config.audit.enabled;
12710
13409
  let auditLog;
12711
13410
  if (installAudit) {
12712
- auditLog = path27.join(logDir, "audit.log");
13411
+ auditLog = path30.join(logDir, "audit.log");
12713
13412
  const auditSchedule = config.audit.schedule;
12714
13413
  const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
12715
13414
  entries.push(auditEntry);
@@ -12718,7 +13417,7 @@ function installCommand(program2) {
12718
13417
  const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
12719
13418
  let analyticsLog;
12720
13419
  if (installAnalytics) {
12721
- analyticsLog = path27.join(logDir, "analytics.log");
13420
+ analyticsLog = path30.join(logDir, "analytics.log");
12722
13421
  const analyticsSchedule = config.analytics.schedule;
12723
13422
  const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
12724
13423
  entries.push(analyticsEntry);
@@ -12727,7 +13426,7 @@ function installCommand(program2) {
12727
13426
  const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
12728
13427
  let prResolverLog;
12729
13428
  if (installPrResolver) {
12730
- prResolverLog = path27.join(logDir, "pr-resolver.log");
13429
+ prResolverLog = path30.join(logDir, "pr-resolver.log");
12731
13430
  const prResolverSchedule = config.prResolver.schedule;
12732
13431
  const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
12733
13432
  entries.push(prResolverEntry);
@@ -12736,11 +13435,20 @@ function installCommand(program2) {
12736
13435
  const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
12737
13436
  let mergerLog;
12738
13437
  if (installMerger) {
12739
- mergerLog = path27.join(logDir, "merger.log");
13438
+ mergerLog = path30.join(logDir, "merger.log");
12740
13439
  const mergerSchedule = config.merger.schedule;
12741
13440
  const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
12742
13441
  entries.push(mergerEntry);
12743
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
+ }
12744
13452
  const existingEntrySet = new Set(existingEntries);
12745
13453
  const currentCrontab = readCrontab();
12746
13454
  const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
@@ -12776,6 +13484,9 @@ function installCommand(program2) {
12776
13484
  if (installMerger && mergerLog) {
12777
13485
  dim(` Merger: ${mergerLog}`);
12778
13486
  }
13487
+ if (installManager && managerLog) {
13488
+ dim(` Manager: ${managerLog}`);
13489
+ }
12779
13490
  console.log();
12780
13491
  dim("To uninstall, run: night-watch uninstall");
12781
13492
  dim("To check status, run: night-watch status");
@@ -12790,8 +13501,8 @@ function installCommand(program2) {
12790
13501
 
12791
13502
  // src/commands/uninstall.ts
12792
13503
  init_dist();
12793
- import * as path28 from "path";
12794
- import * as fs27 from "fs";
13504
+ import * as path31 from "path";
13505
+ import * as fs30 from "fs";
12795
13506
  function performUninstall(projectDir, options) {
12796
13507
  try {
12797
13508
  const projectName = getProjectName(projectDir);
@@ -12806,25 +13517,26 @@ function performUninstall(projectDir, options) {
12806
13517
  const removedCount = removeEntriesForProject(projectDir, marker);
12807
13518
  unregisterProject(projectDir);
12808
13519
  if (!options?.keepLogs) {
12809
- const logDir = path28.join(projectDir, "logs");
12810
- if (fs27.existsSync(logDir)) {
13520
+ const logDir = path31.join(projectDir, "logs");
13521
+ if (fs30.existsSync(logDir)) {
12811
13522
  const logFiles = [
12812
13523
  "executor.log",
12813
13524
  "reviewer.log",
12814
13525
  "slicer.log",
12815
13526
  "audit.log",
12816
- "pr-resolver.log"
13527
+ "pr-resolver.log",
13528
+ "manager.log"
12817
13529
  ];
12818
13530
  logFiles.forEach((logFile) => {
12819
- const logPath = path28.join(logDir, logFile);
12820
- if (fs27.existsSync(logPath)) {
12821
- fs27.unlinkSync(logPath);
13531
+ const logPath = path31.join(logDir, logFile);
13532
+ if (fs30.existsSync(logPath)) {
13533
+ fs30.unlinkSync(logPath);
12822
13534
  }
12823
13535
  });
12824
13536
  try {
12825
- const remainingFiles = fs27.readdirSync(logDir);
13537
+ const remainingFiles = fs30.readdirSync(logDir);
12826
13538
  if (remainingFiles.length === 0) {
12827
- fs27.rmdirSync(logDir);
13539
+ fs30.rmdirSync(logDir);
12828
13540
  }
12829
13541
  } catch {
12830
13542
  }
@@ -12857,27 +13569,28 @@ function uninstallCommand(program2) {
12857
13569
  existingEntries.forEach((entry) => dim(` ${entry}`));
12858
13570
  const removedCount = removeEntriesForProject(projectDir, marker);
12859
13571
  if (!options.keepLogs) {
12860
- const logDir = path28.join(projectDir, "logs");
12861
- if (fs27.existsSync(logDir)) {
13572
+ const logDir = path31.join(projectDir, "logs");
13573
+ if (fs30.existsSync(logDir)) {
12862
13574
  const logFiles = [
12863
13575
  "executor.log",
12864
13576
  "reviewer.log",
12865
13577
  "slicer.log",
12866
13578
  "audit.log",
12867
- "pr-resolver.log"
13579
+ "pr-resolver.log",
13580
+ "manager.log"
12868
13581
  ];
12869
13582
  let logsRemoved = 0;
12870
13583
  logFiles.forEach((logFile) => {
12871
- const logPath = path28.join(logDir, logFile);
12872
- if (fs27.existsSync(logPath)) {
12873
- fs27.unlinkSync(logPath);
13584
+ const logPath = path31.join(logDir, logFile);
13585
+ if (fs30.existsSync(logPath)) {
13586
+ fs30.unlinkSync(logPath);
12874
13587
  logsRemoved++;
12875
13588
  }
12876
13589
  });
12877
13590
  try {
12878
- const remainingFiles = fs27.readdirSync(logDir);
13591
+ const remainingFiles = fs30.readdirSync(logDir);
12879
13592
  if (remainingFiles.length === 0) {
12880
- fs27.rmdirSync(logDir);
13593
+ fs30.rmdirSync(logDir);
12881
13594
  }
12882
13595
  } catch {
12883
13596
  }
@@ -13163,14 +13876,14 @@ function statusCommand(program2) {
13163
13876
  // src/commands/logs.ts
13164
13877
  init_dist();
13165
13878
  import { spawn as spawn3 } from "child_process";
13166
- import * as path29 from "path";
13167
- import * as fs28 from "fs";
13879
+ import * as path32 from "path";
13880
+ import * as fs31 from "fs";
13168
13881
  function getLastLines(filePath, lineCount) {
13169
- if (!fs28.existsSync(filePath)) {
13882
+ if (!fs31.existsSync(filePath)) {
13170
13883
  return `Log file not found: ${filePath}`;
13171
13884
  }
13172
13885
  try {
13173
- const content = fs28.readFileSync(filePath, "utf-8");
13886
+ const content = fs31.readFileSync(filePath, "utf-8");
13174
13887
  const lines = content.trim().split("\n");
13175
13888
  return lines.slice(-lineCount).join("\n");
13176
13889
  } catch (error2) {
@@ -13178,7 +13891,7 @@ function getLastLines(filePath, lineCount) {
13178
13891
  }
13179
13892
  }
13180
13893
  function followLog(filePath) {
13181
- if (!fs28.existsSync(filePath)) {
13894
+ if (!fs31.existsSync(filePath)) {
13182
13895
  console.log(`Log file not found: ${filePath}`);
13183
13896
  console.log("The log file will be created when the first execution runs.");
13184
13897
  return;
@@ -13197,20 +13910,21 @@ function followLog(filePath) {
13197
13910
  function logsCommand(program2) {
13198
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(
13199
13912
  "-t, --type <type>",
13200
- "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)",
13201
13914
  "all"
13202
13915
  ).action(async (options) => {
13203
13916
  try {
13204
13917
  const projectDir = process.cwd();
13205
- const logDir = path29.join(projectDir, LOG_DIR);
13918
+ const logDir = path32.join(projectDir, LOG_DIR);
13206
13919
  const lineCount = parseInt(options.lines || "50", 10);
13207
- const executorLog = path29.join(logDir, EXECUTOR_LOG_FILE);
13208
- const reviewerLog = path29.join(logDir, REVIEWER_LOG_FILE);
13209
- const qaLog = path29.join(logDir, `${QA_LOG_NAME}.log`);
13210
- const auditLog = path29.join(logDir, `${AUDIT_LOG_NAME}.log`);
13211
- const plannerLog = path29.join(logDir, `${PLANNER_LOG_NAME}.log`);
13212
- const analyticsLog = path29.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
13213
- 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`);
13214
13928
  const logType = options.type?.toLowerCase() || "all";
13215
13929
  const showExecutor = logType === "all" || logType === "run" || logType === "executor";
13216
13930
  const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
@@ -13219,10 +13933,11 @@ function logsCommand(program2) {
13219
13933
  const showPlanner = logType === "all" || logType === "planner" || logType === "slice" || logType === "slicer";
13220
13934
  const showAnalytics = logType === "all" || logType === "analytics";
13221
13935
  const showMerger = logType === "all" || logType === "merge" || logType === "merger";
13936
+ const showManager = logType === "all" || logType === "manager";
13222
13937
  if (options.follow) {
13223
13938
  if (logType === "all") {
13224
13939
  dim("Note: Following all logs is not supported. Showing executor log.");
13225
- 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");
13226
13941
  }
13227
13942
  let targetLog = executorLog;
13228
13943
  if (showReviewer) targetLog = reviewerLog;
@@ -13231,6 +13946,7 @@ function logsCommand(program2) {
13231
13946
  else if (showPlanner) targetLog = plannerLog;
13232
13947
  else if (showAnalytics) targetLog = analyticsLog;
13233
13948
  else if (showMerger) targetLog = mergerLog;
13949
+ else if (showManager) targetLog = managerLog;
13234
13950
  followLog(targetLog);
13235
13951
  return;
13236
13952
  }
@@ -13277,11 +13993,17 @@ function logsCommand(program2) {
13277
13993
  console.log();
13278
13994
  console.log(getLastLines(mergerLog, lineCount));
13279
13995
  }
13996
+ if (showManager) {
13997
+ header("Manager Log");
13998
+ dim(`File: ${managerLog}`);
13999
+ console.log();
14000
+ console.log(getLastLines(managerLog, lineCount));
14001
+ }
13280
14002
  console.log();
13281
14003
  dim("---");
13282
14004
  dim("Tip: Use -f to follow logs in real-time");
13283
14005
  dim(
13284
- " 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"
13285
14007
  );
13286
14008
  } catch (err) {
13287
14009
  console.error(`Error reading logs: ${err instanceof Error ? err.message : String(err)}`);
@@ -13293,30 +14015,30 @@ function logsCommand(program2) {
13293
14015
  // src/commands/prd.ts
13294
14016
  init_dist();
13295
14017
  import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
13296
- import * as fs29 from "fs";
13297
- import * as path30 from "path";
14018
+ import * as fs32 from "fs";
14019
+ import * as path33 from "path";
13298
14020
  import { fileURLToPath as fileURLToPath4 } from "url";
13299
- import { dirname as dirname9 } from "path";
14021
+ import { dirname as dirname10 } from "path";
13300
14022
  var __filename3 = fileURLToPath4(import.meta.url);
13301
- var __dirname3 = dirname9(__filename3);
14023
+ var __dirname3 = dirname10(__filename3);
13302
14024
  function findTemplatesDir2(startDir) {
13303
14025
  let current = startDir;
13304
14026
  for (let i = 0; i < 8; i++) {
13305
- const candidate = path30.join(current, "templates");
13306
- if (fs29.existsSync(candidate) && fs29.statSync(candidate).isDirectory()) {
14027
+ const candidate = path33.join(current, "templates");
14028
+ if (fs32.existsSync(candidate) && fs32.statSync(candidate).isDirectory()) {
13307
14029
  return candidate;
13308
14030
  }
13309
- current = path30.dirname(current);
14031
+ current = path33.dirname(current);
13310
14032
  }
13311
- return path30.join(startDir, "templates");
14033
+ return path33.join(startDir, "templates");
13312
14034
  }
13313
14035
  var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
13314
14036
  function slugify2(name) {
13315
14037
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
13316
14038
  }
13317
14039
  function getNextPrdNumber2(prdDir) {
13318
- if (!fs29.existsSync(prdDir)) return 1;
13319
- 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"));
13320
14042
  const numbers = files.map((f) => {
13321
14043
  const match = f.match(/^(\d+)-/);
13322
14044
  return match ? parseInt(match[1], 10) : 0;
@@ -13397,13 +14119,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
13397
14119
  return null;
13398
14120
  }
13399
14121
  const ref = branch && branch !== "HEAD" ? branch : "main";
13400
- 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("/")}`;
13401
14123
  } catch {
13402
14124
  return null;
13403
14125
  }
13404
14126
  }
13405
14127
  function buildGithubIssueBody(prdPath, projectDir, prdContent) {
13406
- const relPath = path30.relative(projectDir, prdPath);
14128
+ const relPath = path33.relative(projectDir, prdPath);
13407
14129
  const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
13408
14130
  const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
13409
14131
  return `${fileLine}
@@ -13414,17 +14136,17 @@ ${prdContent}
13414
14136
  Created via \`night-watch prd create\`.`;
13415
14137
  }
13416
14138
  async function generatePrdWithClaude(description, projectDir, model) {
13417
- const bundledTemplatePath = path30.join(TEMPLATES_DIR2, "prd-creator.md");
13418
- const installedTemplatePath = path30.join(projectDir, "instructions", "prd-creator.md");
13419
- const templatePath = fs29.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
13420
- 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)) {
13421
14143
  return null;
13422
14144
  }
13423
- const planningPrinciples = fs29.readFileSync(templatePath, "utf-8");
14145
+ const planningPrinciples = fs32.readFileSync(templatePath, "utf-8");
13424
14146
  const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
13425
14147
  const modelId = model ?? CLAUDE_MODEL_IDS.opus;
13426
14148
  const env = buildNativeClaudeEnv(process.env);
13427
- return await new Promise((resolve9) => {
14149
+ return await new Promise((resolve11) => {
13428
14150
  const child = spawn4(
13429
14151
  "claude",
13430
14152
  [
@@ -13477,9 +14199,9 @@ async function generatePrdWithClaude(description, projectDir, model) {
13477
14199
  }
13478
14200
  }
13479
14201
  process.stdout.write("\n");
13480
- resolve9(code === 0 && finalResult ? extractPrdMarkdown(finalResult) : null);
14202
+ resolve11(code === 0 && finalResult ? extractPrdMarkdown(finalResult) : null);
13481
14203
  });
13482
- child.on("error", () => resolve9(null));
14204
+ child.on("error", () => resolve11(null));
13483
14205
  });
13484
14206
  }
13485
14207
  function runGh(args, cwd) {
@@ -13488,17 +14210,17 @@ function runGh(args, cwd) {
13488
14210
  return null;
13489
14211
  }
13490
14212
  function createGithubIssue(title, prdPath, projectDir, prdContent) {
13491
- const tmpFile = path30.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
14213
+ const tmpFile = path33.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
13492
14214
  try {
13493
14215
  const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
13494
- fs29.writeFileSync(tmpFile, body, "utf-8");
14216
+ fs32.writeFileSync(tmpFile, body, "utf-8");
13495
14217
  const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
13496
14218
  return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
13497
14219
  } catch {
13498
14220
  return null;
13499
14221
  } finally {
13500
14222
  try {
13501
- fs29.unlinkSync(tmpFile);
14223
+ fs32.unlinkSync(tmpFile);
13502
14224
  } catch {
13503
14225
  }
13504
14226
  }
@@ -13509,14 +14231,15 @@ function parseDependencies(content) {
13509
14231
  return match[1].split(",").map((d) => d.replace(/`/g, "").trim()).filter(Boolean);
13510
14232
  }
13511
14233
  function isClaimActive(claimPath, maxRuntime) {
14234
+ const claimStaleAfter = maxRuntime > 0 ? maxRuntime : 14400;
13512
14235
  try {
13513
- if (!fs29.existsSync(claimPath)) {
14236
+ if (!fs32.existsSync(claimPath)) {
13514
14237
  return { active: false };
13515
14238
  }
13516
- const content = fs29.readFileSync(claimPath, "utf-8");
14239
+ const content = fs32.readFileSync(claimPath, "utf-8");
13517
14240
  const claim = JSON.parse(content);
13518
14241
  const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
13519
- if (age < maxRuntime) {
14242
+ if (age < claimStaleAfter) {
13520
14243
  return { active: true, hostname: claim.hostname, pid: claim.pid };
13521
14244
  }
13522
14245
  return { active: false };
@@ -13528,14 +14251,16 @@ function prdCommand(program2) {
13528
14251
  const prd = program2.command("prd").description("Manage PRD files");
13529
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) => {
13530
14253
  const projectDir = process.cwd();
13531
- const prdDir = path30.join(projectDir, resolvePrdCreateDir());
13532
- if (!fs29.existsSync(prdDir)) {
13533
- fs29.mkdirSync(prdDir, { recursive: true });
14254
+ const prdDir = path33.join(projectDir, resolvePrdCreateDir());
14255
+ if (!fs32.existsSync(prdDir)) {
14256
+ fs32.mkdirSync(prdDir, { recursive: true });
13534
14257
  }
13535
14258
  const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
13536
14259
  const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
13537
- dim(`Calling Claude (${modelLabel}) to generate the PRD. It can take several minutes, please hang on!
13538
- `);
14260
+ dim(
14261
+ `Calling Claude (${modelLabel}) to generate the PRD. It can take several minutes, please hang on!
14262
+ `
14263
+ );
13539
14264
  const generated = await generatePrdWithClaude(name, projectDir, resolvedModel);
13540
14265
  if (!generated) {
13541
14266
  error("Claude generation failed. Is the provider configured and available?");
@@ -13544,13 +14269,13 @@ function prdCommand(program2) {
13544
14269
  const prdTitle = extractPrdTitle(generated) ?? name;
13545
14270
  const slug = slugify2(prdTitle);
13546
14271
  const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
13547
- const filePath = path30.join(prdDir, filename);
13548
- if (fs29.existsSync(filePath)) {
14272
+ const filePath = path33.join(prdDir, filename);
14273
+ if (fs32.existsSync(filePath)) {
13549
14274
  error(`File already exists: ${filePath}`);
13550
14275
  dim("Use a different name or remove the existing file.");
13551
14276
  process.exit(1);
13552
14277
  }
13553
- fs29.writeFileSync(filePath, generated, "utf-8");
14278
+ fs32.writeFileSync(filePath, generated, "utf-8");
13554
14279
  header("PRD Created");
13555
14280
  success(`Created: ${filePath}`);
13556
14281
  const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
@@ -13563,15 +14288,15 @@ function prdCommand(program2) {
13563
14288
  prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
13564
14289
  const projectDir = process.cwd();
13565
14290
  const config = loadConfig(projectDir);
13566
- const absolutePrdDir = path30.join(projectDir, config.prdDir);
13567
- const doneDir = path30.join(absolutePrdDir, "done");
14291
+ const absolutePrdDir = path33.join(projectDir, config.prdDir);
14292
+ const doneDir = path33.join(absolutePrdDir, "done");
13568
14293
  const pending = [];
13569
- if (fs29.existsSync(absolutePrdDir)) {
13570
- 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"));
13571
14296
  for (const file of files) {
13572
- const content = fs29.readFileSync(path30.join(absolutePrdDir, file), "utf-8");
14297
+ const content = fs32.readFileSync(path33.join(absolutePrdDir, file), "utf-8");
13573
14298
  const deps = parseDependencies(content);
13574
- const claimPath = path30.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
14299
+ const claimPath = path33.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
13575
14300
  const claimStatus = isClaimActive(claimPath, config.maxRuntime);
13576
14301
  pending.push({
13577
14302
  name: file,
@@ -13582,10 +14307,10 @@ function prdCommand(program2) {
13582
14307
  }
13583
14308
  }
13584
14309
  const done = [];
13585
- if (fs29.existsSync(doneDir)) {
13586
- 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"));
13587
14312
  for (const file of files) {
13588
- const content = fs29.readFileSync(path30.join(doneDir, file), "utf-8");
14313
+ const content = fs32.readFileSync(path33.join(doneDir, file), "utf-8");
13589
14314
  const deps = parseDependencies(content);
13590
14315
  done.push({ name: file, dependencies: deps });
13591
14316
  }
@@ -13622,7 +14347,7 @@ import blessed6 from "blessed";
13622
14347
  // src/commands/dashboard/tab-status.ts
13623
14348
  init_dist();
13624
14349
  import blessed from "blessed";
13625
- import * as fs30 from "fs";
14350
+ import * as fs33 from "fs";
13626
14351
  function sortPrdsByPriority(prds, priority) {
13627
14352
  if (priority.length === 0) return prds;
13628
14353
  const priorityMap = /* @__PURE__ */ new Map();
@@ -13718,7 +14443,7 @@ function renderLogPane(projectDir, logs) {
13718
14443
  let newestMtime = 0;
13719
14444
  for (const log of existingLogs) {
13720
14445
  try {
13721
- const stat = fs30.statSync(log.path);
14446
+ const stat = fs33.statSync(log.path);
13722
14447
  if (stat.mtimeMs > newestMtime) {
13723
14448
  newestMtime = stat.mtimeMs;
13724
14449
  newestLog = log;
@@ -14051,7 +14776,7 @@ var CONFIG_FIELDS = [
14051
14776
  type: "number",
14052
14777
  validate: (v) => {
14053
14778
  const n = parseInt(v, 10);
14054
- return isNaN(n) || n <= 0 ? "Must be a positive integer" : null;
14779
+ return isNaN(n) || n < 0 ? "Must be 0 (no timeout) or a positive integer" : null;
14055
14780
  }
14056
14781
  },
14057
14782
  {
@@ -14060,7 +14785,7 @@ var CONFIG_FIELDS = [
14060
14785
  type: "number",
14061
14786
  validate: (v) => {
14062
14787
  const n = parseInt(v, 10);
14063
- return isNaN(n) || n <= 0 ? "Must be a positive integer" : null;
14788
+ return isNaN(n) || n < 0 ? "Must be 0 (no timeout) or a positive integer" : null;
14064
14789
  }
14065
14790
  },
14066
14791
  {
@@ -14894,6 +15619,7 @@ function createSchedulesTab() {
14894
15619
  const { config } = ctx;
14895
15620
  const executorHuman = cronToHuman(config.cronSchedule);
14896
15621
  const reviewerHuman = cronToHuman(config.reviewerSchedule);
15622
+ const managerHuman = cronToHuman(config.manager.schedule);
14897
15623
  const lines = [
14898
15624
  `{bold}Executor Schedule:{/bold} ${config.cronSchedule}`,
14899
15625
  ` ${executorHuman}`,
@@ -14901,14 +15627,19 @@ function createSchedulesTab() {
14901
15627
  `{bold}Reviewer Schedule:{/bold} ${config.reviewerSchedule}`,
14902
15628
  ` ${reviewerHuman}`,
14903
15629
  "",
15630
+ `{bold}Manager Schedule:{/bold} ${config.manager.schedule}`,
15631
+ ` ${managerHuman}`,
15632
+ "",
14904
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}"}`,
14905
15635
  "",
14906
- "{#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}"
14907
15637
  ];
14908
15638
  scheduleBox.setContent(lines.join("\n"));
14909
15639
  }
14910
15640
  function applySchedule(ctx, field, cronExpr) {
14911
- 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);
14912
15643
  if (!result.success) {
14913
15644
  ctx.showMessage(`Save failed: ${result.error}`, "error");
14914
15645
  return;
@@ -14933,7 +15664,7 @@ function createSchedulesTab() {
14933
15664
  });
14934
15665
  }
14935
15666
  function showCustomCronInput(ctx, field, label2) {
14936
- const currentValue = ctx.config[field];
15667
+ const currentValue = field === "manager.schedule" ? ctx.config.manager.schedule : ctx.config[field];
14937
15668
  const inputBox = blessed3.textbox({
14938
15669
  top: "center",
14939
15670
  left: "center",
@@ -14987,7 +15718,7 @@ function createSchedulesTab() {
14987
15718
  interactive: true
14988
15719
  });
14989
15720
  selectorList.setItems(presetItems);
14990
- const currentCron = ctx.config[field];
15721
+ const currentCron = field === "manager.schedule" ? ctx.config.manager.schedule : ctx.config[field];
14991
15722
  const matchIdx = SCHEDULE_PRESETS.findIndex((p) => p.cron === currentCron);
14992
15723
  if (matchIdx >= 0) {
14993
15724
  selectorList.select(matchIdx);
@@ -15019,6 +15750,7 @@ function createSchedulesTab() {
15019
15750
  const handlers = [
15020
15751
  [["e"], () => editSchedule(ctx, "cronSchedule", "Executor Schedule")],
15021
15752
  [["v"], () => editSchedule(ctx, "reviewerSchedule", "Reviewer Schedule")],
15753
+ [["m"], () => editSchedule(ctx, "manager.schedule", "Manager Schedule")],
15022
15754
  [
15023
15755
  ["i"],
15024
15756
  () => {
@@ -15092,7 +15824,7 @@ function createSchedulesTab() {
15092
15824
  name: "Schedules",
15093
15825
  container: container2,
15094
15826
  activate(ctx) {
15095
- 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");
15096
15828
  renderCrontab(ctx);
15097
15829
  renderScheduleSettings(ctx);
15098
15830
  activeCtx = ctx;
@@ -15364,8 +16096,8 @@ function createActionsTab() {
15364
16096
  // src/commands/dashboard/tab-logs.ts
15365
16097
  init_dist();
15366
16098
  import blessed5 from "blessed";
15367
- import * as fs31 from "fs";
15368
- import * as path31 from "path";
16099
+ import * as fs34 from "fs";
16100
+ import * as path34 from "path";
15369
16101
  var LOG_NAMES = ["executor", "reviewer"];
15370
16102
  var LOG_LINES = 200;
15371
16103
  function createLogsTab() {
@@ -15406,7 +16138,7 @@ function createLogsTab() {
15406
16138
  let activeKeyHandlers = [];
15407
16139
  let activeCtx = null;
15408
16140
  function getLogPath(projectDir, logName) {
15409
- return path31.join(projectDir, "logs", `${logName}.log`);
16141
+ return path34.join(projectDir, "logs", `${logName}.log`);
15410
16142
  }
15411
16143
  function updateSelector() {
15412
16144
  const tabs = LOG_NAMES.map((name, idx) => {
@@ -15420,7 +16152,7 @@ function createLogsTab() {
15420
16152
  function loadLog(ctx) {
15421
16153
  const logName = LOG_NAMES[selectedLogIndex];
15422
16154
  const logPath = getLogPath(ctx.projectDir, logName);
15423
- if (!fs31.existsSync(logPath)) {
16155
+ if (!fs34.existsSync(logPath)) {
15424
16156
  logContent.setContent(
15425
16157
  `{yellow-fg}No ${logName}.log file found{/yellow-fg}
15426
16158
 
@@ -15430,7 +16162,7 @@ Log will appear here once the ${logName} runs.`
15430
16162
  return;
15431
16163
  }
15432
16164
  try {
15433
- const stat = fs31.statSync(logPath);
16165
+ const stat = fs34.statSync(logPath);
15434
16166
  const sizeKB = (stat.size / 1024).toFixed(1);
15435
16167
  logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
15436
16168
  } catch {
@@ -15880,13 +16612,13 @@ function doctorCommand(program2) {
15880
16612
 
15881
16613
  // src/commands/serve.ts
15882
16614
  init_dist();
15883
- import * as fs36 from "fs";
16615
+ import * as fs39 from "fs";
15884
16616
 
15885
16617
  // ../server/dist/index.js
15886
16618
  init_dist();
15887
- import * as fs35 from "fs";
15888
- import * as path37 from "path";
15889
- 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";
15890
16622
  import { fileURLToPath as fileURLToPath5 } from "url";
15891
16623
  import cors from "cors";
15892
16624
  import express from "express";
@@ -15970,8 +16702,8 @@ function setupGracefulShutdown(server, beforeClose) {
15970
16702
 
15971
16703
  // ../server/dist/middleware/project-resolver.middleware.js
15972
16704
  init_dist();
15973
- import * as fs32 from "fs";
15974
- import * as path32 from "path";
16705
+ import * as fs35 from "fs";
16706
+ import * as path35 from "path";
15975
16707
  function resolveProject(req, res, next) {
15976
16708
  const projectId = req.params.projectId;
15977
16709
  const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
@@ -15981,7 +16713,7 @@ function resolveProject(req, res, next) {
15981
16713
  res.status(404).json({ error: `Project not found: ${decodedId}` });
15982
16714
  return;
15983
16715
  }
15984
- 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))) {
15985
16717
  res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
15986
16718
  return;
15987
16719
  }
@@ -16026,8 +16758,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
16026
16758
 
16027
16759
  // ../server/dist/routes/action.routes.js
16028
16760
  init_dist();
16029
- import * as fs33 from "fs";
16030
- import * as path33 from "path";
16761
+ import * as fs36 from "fs";
16762
+ import * as path36 from "path";
16031
16763
  import { execSync as execSync6, spawn as spawn6 } from "child_process";
16032
16764
  import { Router } from "express";
16033
16765
 
@@ -16065,17 +16797,17 @@ function getBoardProvider(config, projectDir) {
16065
16797
  function cleanOrphanedClaims(dir) {
16066
16798
  let entries;
16067
16799
  try {
16068
- entries = fs33.readdirSync(dir, { withFileTypes: true });
16800
+ entries = fs36.readdirSync(dir, { withFileTypes: true });
16069
16801
  } catch {
16070
16802
  return;
16071
16803
  }
16072
16804
  for (const entry of entries) {
16073
- const fullPath = path33.join(dir, entry.name);
16805
+ const fullPath = path36.join(dir, entry.name);
16074
16806
  if (entry.isDirectory() && entry.name !== "done") {
16075
16807
  cleanOrphanedClaims(fullPath);
16076
16808
  } else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
16077
16809
  try {
16078
- fs33.unlinkSync(fullPath);
16810
+ fs36.unlinkSync(fullPath);
16079
16811
  } catch {
16080
16812
  }
16081
16813
  }
@@ -16230,19 +16962,19 @@ function createActionRouteHandlers(ctx) {
16230
16962
  res.status(400).json({ error: "Invalid PRD name" });
16231
16963
  return;
16232
16964
  }
16233
- const prdDir = path33.join(projectDir, config.prdDir);
16965
+ const prdDir = path36.join(projectDir, config.prdDir);
16234
16966
  const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
16235
- const pendingPath = path33.join(prdDir, normalized);
16236
- const donePath = path33.join(prdDir, "done", normalized);
16237
- if (fs33.existsSync(pendingPath)) {
16967
+ const pendingPath = path36.join(prdDir, normalized);
16968
+ const donePath = path36.join(prdDir, "done", normalized);
16969
+ if (fs36.existsSync(pendingPath)) {
16238
16970
  res.json({ message: `"${normalized}" is already pending` });
16239
16971
  return;
16240
16972
  }
16241
- if (!fs33.existsSync(donePath)) {
16973
+ if (!fs36.existsSync(donePath)) {
16242
16974
  res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
16243
16975
  return;
16244
16976
  }
16245
- fs33.renameSync(donePath, pendingPath);
16977
+ fs36.renameSync(donePath, pendingPath);
16246
16978
  res.json({ message: `Moved "${normalized}" back to pending` });
16247
16979
  } catch (error2) {
16248
16980
  res.status(500).json({
@@ -16260,11 +16992,11 @@ function createActionRouteHandlers(ctx) {
16260
16992
  res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
16261
16993
  return;
16262
16994
  }
16263
- if (fs33.existsSync(lockPath)) {
16264
- fs33.unlinkSync(lockPath);
16995
+ if (fs36.existsSync(lockPath)) {
16996
+ fs36.unlinkSync(lockPath);
16265
16997
  }
16266
- const prdDir = path33.join(projectDir, config.prdDir);
16267
- if (fs33.existsSync(prdDir)) {
16998
+ const prdDir = path36.join(projectDir, config.prdDir);
16999
+ if (fs36.existsSync(prdDir)) {
16268
17000
  cleanOrphanedClaims(prdDir);
16269
17001
  }
16270
17002
  broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
@@ -17032,8 +17764,8 @@ function createProjectConfigRoutes() {
17032
17764
 
17033
17765
  // ../server/dist/routes/doctor.routes.js
17034
17766
  init_dist();
17035
- import * as fs34 from "fs";
17036
- import * as path34 from "path";
17767
+ import * as fs37 from "fs";
17768
+ import * as path37 from "path";
17037
17769
  import { execSync as execSync7 } from "child_process";
17038
17770
  import { Router as Router4 } from "express";
17039
17771
  function runDoctorChecks(projectDir, config) {
@@ -17066,7 +17798,7 @@ function runDoctorChecks(projectDir, config) {
17066
17798
  });
17067
17799
  }
17068
17800
  try {
17069
- const projectName = path34.basename(projectDir);
17801
+ const projectName = path37.basename(projectDir);
17070
17802
  const marker = generateMarker(projectName);
17071
17803
  const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
17072
17804
  if (crontabEntries.length > 0) {
@@ -17089,8 +17821,8 @@ function runDoctorChecks(projectDir, config) {
17089
17821
  detail: "Failed to check crontab"
17090
17822
  });
17091
17823
  }
17092
- const configPath = path34.join(projectDir, CONFIG_FILE_NAME);
17093
- if (fs34.existsSync(configPath)) {
17824
+ const configPath = path37.join(projectDir, CONFIG_FILE_NAME);
17825
+ if (fs37.existsSync(configPath)) {
17094
17826
  checks.push({ name: "config", status: "pass", detail: "Config file exists" });
17095
17827
  } else {
17096
17828
  checks.push({
@@ -17099,9 +17831,9 @@ function runDoctorChecks(projectDir, config) {
17099
17831
  detail: "Config file not found (using defaults)"
17100
17832
  });
17101
17833
  }
17102
- const prdDir = path34.join(projectDir, config.prdDir);
17103
- if (fs34.existsSync(prdDir)) {
17104
- const prds = fs34.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
17834
+ const prdDir = path37.join(projectDir, config.prdDir);
17835
+ if (fs37.existsSync(prdDir)) {
17836
+ const prds = fs37.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
17105
17837
  checks.push({
17106
17838
  name: "prdDir",
17107
17839
  status: "pass",
@@ -17542,6 +18274,8 @@ function getLockPathForJob2(projectDir, jobId) {
17542
18274
  return prResolverLockPath(projectDir);
17543
18275
  case "merger":
17544
18276
  return mergerLockPath(projectDir);
18277
+ case "manager":
18278
+ return managerLockPath(projectDir);
17545
18279
  }
17546
18280
  }
17547
18281
  function createJobRouteHandlers(ctx) {
@@ -17668,7 +18402,7 @@ function createProjectJobRoutes() {
17668
18402
 
17669
18403
  // ../server/dist/routes/log.routes.js
17670
18404
  init_dist();
17671
- import * as path35 from "path";
18405
+ import * as path38 from "path";
17672
18406
  import { Router as Router7 } from "express";
17673
18407
  function createLogRoutes(deps) {
17674
18408
  const { projectDir } = deps;
@@ -17687,7 +18421,7 @@ function createLogRoutes(deps) {
17687
18421
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
17688
18422
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
17689
18423
  const fileName = LOG_FILE_NAMES[name] || name;
17690
- const logPath = path35.join(projectDir, LOG_DIR, `${fileName}.log`);
18424
+ const logPath = path38.join(projectDir, LOG_DIR, `${fileName}.log`);
17691
18425
  const logLines = getLastLogLines(logPath, linesToRead);
17692
18426
  res.json({ name, lines: logLines });
17693
18427
  } catch (error2) {
@@ -17713,7 +18447,7 @@ function createProjectLogRoutes() {
17713
18447
  const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
17714
18448
  const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
17715
18449
  const fileName = LOG_FILE_NAMES[name] || name;
17716
- const logPath = path35.join(projectDir, LOG_DIR, `${fileName}.log`);
18450
+ const logPath = path38.join(projectDir, LOG_DIR, `${fileName}.log`);
17717
18451
  const logLines = getLastLogLines(logPath, linesToRead);
17718
18452
  res.json({ name, lines: logLines });
17719
18453
  } catch (error2) {
@@ -17748,7 +18482,7 @@ function createProjectPrdRoutes() {
17748
18482
 
17749
18483
  // ../server/dist/routes/roadmap.routes.js
17750
18484
  init_dist();
17751
- import * as path36 from "path";
18485
+ import * as path39 from "path";
17752
18486
  import { Router as Router9 } from "express";
17753
18487
  function createRoadmapRouteHandlers(ctx) {
17754
18488
  const router = Router9({ mergeParams: true });
@@ -17758,7 +18492,7 @@ function createRoadmapRouteHandlers(ctx) {
17758
18492
  const config = ctx.getConfig(req);
17759
18493
  const projectDir = ctx.getProjectDir(req);
17760
18494
  const status = getRoadmapStatus(projectDir, config);
17761
- const prdDir = path36.join(projectDir, config.prdDir);
18495
+ const prdDir = path39.join(projectDir, config.prdDir);
17762
18496
  const state = loadRoadmapState(prdDir);
17763
18497
  res.json({
17764
18498
  ...status,
@@ -17885,6 +18619,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
17885
18619
  const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
17886
18620
  const prResolverPlan = getSchedulingPlan(projectDir, config, "pr-resolver");
17887
18621
  const mergerPlan = getSchedulingPlan(projectDir, config, "merger");
18622
+ const managerPlan = getSchedulingPlan(projectDir, config, "manager");
17888
18623
  const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
17889
18624
  const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
17890
18625
  const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
@@ -17893,6 +18628,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
17893
18628
  const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
17894
18629
  const prResolverInstalled = installed && (config.prResolver?.enabled ?? true) && hasScheduledCommand(entries, "resolve");
17895
18630
  const mergerInstalled = installed && (config.merger?.enabled ?? false) && hasScheduledCommand(entries, "merge");
18631
+ const managerInstalled = installed && (config.manager?.enabled ?? true) && hasScheduledCommand(entries, "manager");
17896
18632
  return {
17897
18633
  executor: {
17898
18634
  schedule: config.cronSchedule,
@@ -17958,6 +18694,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
17958
18694
  manualDelayMinutes: mergerPlan.manualDelayMinutes,
17959
18695
  balancedDelayMinutes: mergerPlan.balancedDelayMinutes
17960
18696
  },
18697
+ manager: {
18698
+ schedule: config.manager?.schedule ?? "15 7 * * *",
18699
+ installed: managerInstalled,
18700
+ nextRun: managerInstalled ? addDelayToIsoString(computeNextRun(config.manager?.schedule ?? "15 7 * * *"), managerPlan.totalDelayMinutes) : null,
18701
+ delayMinutes: managerPlan.totalDelayMinutes,
18702
+ manualDelayMinutes: managerPlan.manualDelayMinutes,
18703
+ balancedDelayMinutes: managerPlan.balancedDelayMinutes
18704
+ },
17961
18705
  paused: !installed,
17962
18706
  schedulingPriority: config.schedulingPriority,
17963
18707
  entries
@@ -18110,31 +18854,31 @@ function createQueueRoutes(deps) {
18110
18854
 
18111
18855
  // ../server/dist/index.js
18112
18856
  var __filename4 = fileURLToPath5(import.meta.url);
18113
- var __dirname4 = dirname11(__filename4);
18857
+ var __dirname4 = dirname12(__filename4);
18114
18858
  var JOB_RAW_BODY_LIMIT = "1mb";
18115
18859
  function setupJobRawBodyParsing(app) {
18116
18860
  app.use("/api/jobs", express.raw({ type: "*/*", limit: JOB_RAW_BODY_LIMIT }));
18117
18861
  app.use("/api/projects/:projectId/jobs", express.raw({ type: "*/*", limit: JOB_RAW_BODY_LIMIT }));
18118
18862
  }
18119
18863
  function resolveWebDistPath() {
18120
- const bundled = path37.join(__dirname4, "web");
18121
- if (fs35.existsSync(path37.join(bundled, "index.html")))
18864
+ const bundled = path40.join(__dirname4, "web");
18865
+ if (fs38.existsSync(path40.join(bundled, "index.html")))
18122
18866
  return bundled;
18123
18867
  let d = __dirname4;
18124
18868
  for (let i = 0; i < 8; i++) {
18125
- if (fs35.existsSync(path37.join(d, "turbo.json"))) {
18126
- const dev = path37.join(d, "web/dist");
18127
- if (fs35.existsSync(path37.join(dev, "index.html")))
18869
+ if (fs38.existsSync(path40.join(d, "turbo.json"))) {
18870
+ const dev = path40.join(d, "web/dist");
18871
+ if (fs38.existsSync(path40.join(dev, "index.html")))
18128
18872
  return dev;
18129
18873
  break;
18130
18874
  }
18131
- d = dirname11(d);
18875
+ d = dirname12(d);
18132
18876
  }
18133
18877
  return bundled;
18134
18878
  }
18135
18879
  function setupStaticFiles(app) {
18136
18880
  const webDistPath = resolveWebDistPath();
18137
- if (fs35.existsSync(webDistPath)) {
18881
+ if (fs38.existsSync(webDistPath)) {
18138
18882
  app.use(express.static(webDistPath));
18139
18883
  }
18140
18884
  app.use((req, res, next) => {
@@ -18142,8 +18886,8 @@ function setupStaticFiles(app) {
18142
18886
  next();
18143
18887
  return;
18144
18888
  }
18145
- const indexPath = path37.resolve(webDistPath, "index.html");
18146
- if (fs35.existsSync(indexPath)) {
18889
+ const indexPath = path40.resolve(webDistPath, "index.html");
18890
+ if (fs38.existsSync(indexPath)) {
18147
18891
  res.sendFile(indexPath, (err) => {
18148
18892
  if (err)
18149
18893
  next();
@@ -18282,7 +19026,7 @@ function createGlobalApp() {
18282
19026
  return app;
18283
19027
  }
18284
19028
  function bootContainer() {
18285
- initContainer(path37.dirname(getDbPath()));
19029
+ initContainer(path40.dirname(getDbPath()));
18286
19030
  }
18287
19031
  function startServer(projectDir, port) {
18288
19032
  bootContainer();
@@ -18335,8 +19079,8 @@ function isProcessRunning2(pid) {
18335
19079
  }
18336
19080
  function readPid(lockPath) {
18337
19081
  try {
18338
- if (!fs36.existsSync(lockPath)) return null;
18339
- const raw = fs36.readFileSync(lockPath, "utf-8").trim();
19082
+ if (!fs39.existsSync(lockPath)) return null;
19083
+ const raw = fs39.readFileSync(lockPath, "utf-8").trim();
18340
19084
  const pid = parseInt(raw, 10);
18341
19085
  return Number.isFinite(pid) ? pid : null;
18342
19086
  } catch {
@@ -18348,10 +19092,10 @@ function acquireServeLock(mode, port) {
18348
19092
  let stalePidCleaned;
18349
19093
  for (let attempt = 0; attempt < 2; attempt++) {
18350
19094
  try {
18351
- const fd = fs36.openSync(lockPath, "wx");
18352
- fs36.writeFileSync(fd, `${process.pid}
19095
+ const fd = fs39.openSync(lockPath, "wx");
19096
+ fs39.writeFileSync(fd, `${process.pid}
18353
19097
  `);
18354
- fs36.closeSync(fd);
19098
+ fs39.closeSync(fd);
18355
19099
  return { acquired: true, lockPath, stalePidCleaned };
18356
19100
  } catch (error2) {
18357
19101
  const err = error2;
@@ -18372,7 +19116,7 @@ function acquireServeLock(mode, port) {
18372
19116
  };
18373
19117
  }
18374
19118
  try {
18375
- fs36.unlinkSync(lockPath);
19119
+ fs39.unlinkSync(lockPath);
18376
19120
  if (existingPid) {
18377
19121
  stalePidCleaned = existingPid;
18378
19122
  }
@@ -18395,10 +19139,10 @@ function acquireServeLock(mode, port) {
18395
19139
  }
18396
19140
  function releaseServeLock(lockPath) {
18397
19141
  try {
18398
- if (!fs36.existsSync(lockPath)) return;
19142
+ if (!fs39.existsSync(lockPath)) return;
18399
19143
  const lockPid = readPid(lockPath);
18400
19144
  if (lockPid !== null && lockPid !== process.pid) return;
18401
- fs36.unlinkSync(lockPath);
19145
+ fs39.unlinkSync(lockPath);
18402
19146
  } catch {
18403
19147
  }
18404
19148
  }
@@ -18494,14 +19238,14 @@ function historyCommand(program2) {
18494
19238
  // src/commands/update.ts
18495
19239
  init_dist();
18496
19240
  import { spawnSync as spawnSync2 } from "child_process";
18497
- import * as fs37 from "fs";
18498
- import * as path38 from "path";
19241
+ import * as fs40 from "fs";
19242
+ import * as path41 from "path";
18499
19243
  var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
18500
19244
  function parseProjectDirs(projects, cwd) {
18501
19245
  if (!projects || projects.trim().length === 0) {
18502
19246
  return [cwd];
18503
19247
  }
18504
- const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path38.resolve(cwd, entry));
19248
+ const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path41.resolve(cwd, entry));
18505
19249
  return Array.from(new Set(dirs));
18506
19250
  }
18507
19251
  function shouldInstallGlobal(options) {
@@ -18543,7 +19287,7 @@ function updateCommand(program2) {
18543
19287
  }
18544
19288
  const nightWatchBin = resolveNightWatchBin();
18545
19289
  for (const projectDir of projectDirs) {
18546
- if (!fs37.existsSync(projectDir) || !fs37.statSync(projectDir).isDirectory()) {
19290
+ if (!fs40.existsSync(projectDir) || !fs40.statSync(projectDir).isDirectory()) {
18547
19291
  warn(`Skipping invalid project directory: ${projectDir}`);
18548
19292
  continue;
18549
19293
  }
@@ -18587,8 +19331,8 @@ function prdStateCommand(program2) {
18587
19331
 
18588
19332
  // src/commands/retry.ts
18589
19333
  init_dist();
18590
- import * as fs38 from "fs";
18591
- import * as path39 from "path";
19334
+ import * as fs41 from "fs";
19335
+ import * as path42 from "path";
18592
19336
  function normalizePrdName(name) {
18593
19337
  if (!name.endsWith(".md")) {
18594
19338
  return `${name}.md`;
@@ -18596,26 +19340,26 @@ function normalizePrdName(name) {
18596
19340
  return name;
18597
19341
  }
18598
19342
  function getDonePrds(doneDir) {
18599
- if (!fs38.existsSync(doneDir)) {
19343
+ if (!fs41.existsSync(doneDir)) {
18600
19344
  return [];
18601
19345
  }
18602
- return fs38.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
19346
+ return fs41.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
18603
19347
  }
18604
19348
  function retryCommand(program2) {
18605
19349
  program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
18606
19350
  const projectDir = process.cwd();
18607
19351
  const config = loadConfig(projectDir);
18608
- const prdDir = path39.join(projectDir, config.prdDir);
18609
- const doneDir = path39.join(prdDir, "done");
19352
+ const prdDir = path42.join(projectDir, config.prdDir);
19353
+ const doneDir = path42.join(prdDir, "done");
18610
19354
  const normalizedPrdName = normalizePrdName(prdName);
18611
- const pendingPath = path39.join(prdDir, normalizedPrdName);
18612
- if (fs38.existsSync(pendingPath)) {
19355
+ const pendingPath = path42.join(prdDir, normalizedPrdName);
19356
+ if (fs41.existsSync(pendingPath)) {
18613
19357
  info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
18614
19358
  return;
18615
19359
  }
18616
- const donePath = path39.join(doneDir, normalizedPrdName);
18617
- if (fs38.existsSync(donePath)) {
18618
- fs38.renameSync(donePath, pendingPath);
19360
+ const donePath = path42.join(doneDir, normalizedPrdName);
19361
+ if (fs41.existsSync(donePath)) {
19362
+ fs41.renameSync(donePath, pendingPath);
18619
19363
  success(`Moved "${normalizedPrdName}" back to pending.`);
18620
19364
  dim(`From: ${donePath}`);
18621
19365
  dim(`To: ${pendingPath}`);
@@ -18867,7 +19611,7 @@ function prdsCommand(program2) {
18867
19611
 
18868
19612
  // src/commands/cancel.ts
18869
19613
  init_dist();
18870
- import * as fs39 from "fs";
19614
+ import * as fs42 from "fs";
18871
19615
  import * as readline2 from "readline";
18872
19616
  function getLockFilePaths2(projectDir) {
18873
19617
  const runtimeKey = projectRuntimeKey(projectDir);
@@ -18884,16 +19628,16 @@ async function promptConfirmation(prompt) {
18884
19628
  input: process.stdin,
18885
19629
  output: process.stdout
18886
19630
  });
18887
- return new Promise((resolve9) => {
19631
+ return new Promise((resolve11) => {
18888
19632
  rl.question(`${prompt} `, (answer) => {
18889
19633
  rl.close();
18890
19634
  const normalized = answer.toLowerCase().trim();
18891
- resolve9(normalized === "y" || normalized === "yes");
19635
+ resolve11(normalized === "y" || normalized === "yes");
18892
19636
  });
18893
19637
  });
18894
19638
  }
18895
19639
  function sleep2(ms) {
18896
- return new Promise((resolve9) => setTimeout(resolve9, ms));
19640
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
18897
19641
  }
18898
19642
  function isProcessRunning3(pid) {
18899
19643
  try {
@@ -18914,7 +19658,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
18914
19658
  const pid = lockStatus.pid;
18915
19659
  if (!lockStatus.running) {
18916
19660
  try {
18917
- fs39.unlinkSync(lockPath);
19661
+ fs42.unlinkSync(lockPath);
18918
19662
  return {
18919
19663
  success: true,
18920
19664
  message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
@@ -18952,7 +19696,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
18952
19696
  await sleep2(3e3);
18953
19697
  if (!isProcessRunning3(pid)) {
18954
19698
  try {
18955
- fs39.unlinkSync(lockPath);
19699
+ fs42.unlinkSync(lockPath);
18956
19700
  } catch {
18957
19701
  }
18958
19702
  return {
@@ -18987,7 +19731,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
18987
19731
  await sleep2(500);
18988
19732
  if (!isProcessRunning3(pid)) {
18989
19733
  try {
18990
- fs39.unlinkSync(lockPath);
19734
+ fs42.unlinkSync(lockPath);
18991
19735
  } catch {
18992
19736
  }
18993
19737
  return {
@@ -19048,31 +19792,31 @@ function cancelCommand(program2) {
19048
19792
 
19049
19793
  // src/commands/slice.ts
19050
19794
  init_dist();
19051
- import * as fs40 from "fs";
19052
- import * as path40 from "path";
19795
+ import * as fs43 from "fs";
19796
+ import * as path43 from "path";
19053
19797
  function plannerLockPath2(projectDir) {
19054
19798
  return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
19055
19799
  }
19056
19800
  function acquirePlannerLock(projectDir) {
19057
19801
  const lockFile = plannerLockPath2(projectDir);
19058
- if (fs40.existsSync(lockFile)) {
19059
- const pidRaw = fs40.readFileSync(lockFile, "utf-8").trim();
19802
+ if (fs43.existsSync(lockFile)) {
19803
+ const pidRaw = fs43.readFileSync(lockFile, "utf-8").trim();
19060
19804
  const pid = parseInt(pidRaw, 10);
19061
19805
  if (!Number.isNaN(pid) && isProcessRunning(pid)) {
19062
19806
  return { acquired: false, lockFile, pid };
19063
19807
  }
19064
19808
  try {
19065
- fs40.unlinkSync(lockFile);
19809
+ fs43.unlinkSync(lockFile);
19066
19810
  } catch {
19067
19811
  }
19068
19812
  }
19069
- fs40.writeFileSync(lockFile, String(process.pid));
19813
+ fs43.writeFileSync(lockFile, String(process.pid));
19070
19814
  return { acquired: true, lockFile };
19071
19815
  }
19072
19816
  function releasePlannerLock(lockFile) {
19073
19817
  try {
19074
- if (fs40.existsSync(lockFile)) {
19075
- fs40.unlinkSync(lockFile);
19818
+ if (fs43.existsSync(lockFile)) {
19819
+ fs43.unlinkSync(lockFile);
19076
19820
  }
19077
19821
  } catch {
19078
19822
  }
@@ -19081,12 +19825,12 @@ function resolvePlannerIssueColumn(config) {
19081
19825
  return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
19082
19826
  }
19083
19827
  function buildPlannerIssueBody(projectDir, config, result) {
19084
- const relativePrdPath = path40.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
19085
- const absolutePrdPath = path40.join(projectDir, config.prdDir, result.file ?? "");
19828
+ const relativePrdPath = path43.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
19829
+ const absolutePrdPath = path43.join(projectDir, config.prdDir, result.file ?? "");
19086
19830
  const sourceItem = result.item;
19087
19831
  let prdContent;
19088
19832
  try {
19089
- prdContent = fs40.readFileSync(absolutePrdPath, "utf-8");
19833
+ prdContent = fs43.readFileSync(absolutePrdPath, "utf-8");
19090
19834
  } catch {
19091
19835
  prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
19092
19836
  }
@@ -19126,10 +19870,10 @@ async function createPlannerIssue(projectDir, config, result) {
19126
19870
  return { created: false, skippedReason: "board-not-configured" };
19127
19871
  }
19128
19872
  const issueTitle = `PRD: ${result.item.title}`;
19129
- const normalizeTitle = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
19873
+ const normalizeTitle2 = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
19130
19874
  const existingIssues = await provider.getAllIssues();
19131
19875
  const existing = existingIssues.find(
19132
- (issue2) => normalizeTitle(issue2.title) === normalizeTitle(result.item.title)
19876
+ (issue2) => normalizeTitle2(issue2.title) === normalizeTitle2(result.item.title)
19133
19877
  );
19134
19878
  if (existing) {
19135
19879
  return {
@@ -19316,7 +20060,7 @@ function sliceCommand(program2) {
19316
20060
  if (!options.dryRun && result.sliced) {
19317
20061
  await sendNotifications(config, {
19318
20062
  event: "run_succeeded",
19319
- projectName: path40.basename(projectDir),
20063
+ projectName: path43.basename(projectDir),
19320
20064
  exitCode,
19321
20065
  provider: config.provider,
19322
20066
  prTitle: result.item?.title
@@ -19324,7 +20068,7 @@ function sliceCommand(program2) {
19324
20068
  } else if (!options.dryRun && !nothingPending) {
19325
20069
  await sendNotifications(config, {
19326
20070
  event: "run_failed",
19327
- projectName: path40.basename(projectDir),
20071
+ projectName: path43.basename(projectDir),
19328
20072
  exitCode,
19329
20073
  provider: config.provider
19330
20074
  });
@@ -19357,20 +20101,20 @@ function sliceCommand(program2) {
19357
20101
  // src/commands/state.ts
19358
20102
  init_dist();
19359
20103
  import * as os9 from "os";
19360
- import * as path41 from "path";
20104
+ import * as path44 from "path";
19361
20105
  import chalk5 from "chalk";
19362
20106
  import { Command } from "commander";
19363
20107
  function createStateCommand() {
19364
20108
  const state = new Command("state");
19365
20109
  state.description("Manage Night Watch state");
19366
20110
  state.command("migrate").description("Migrate legacy JSON state files to SQLite").option("--dry-run", "Show what would be migrated without making changes").action((opts) => {
19367
- const nightWatchHome = process.env.NIGHT_WATCH_HOME || path41.join(os9.homedir(), GLOBAL_CONFIG_DIR);
20111
+ const nightWatchHome = process.env.NIGHT_WATCH_HOME || path44.join(os9.homedir(), GLOBAL_CONFIG_DIR);
19368
20112
  if (opts.dryRun) {
19369
20113
  console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
19370
20114
  console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
19371
- console.log(` ${path41.join(nightWatchHome, "projects.json")}`);
19372
- console.log(` ${path41.join(nightWatchHome, "history.json")}`);
19373
- console.log(` ${path41.join(nightWatchHome, "prd-states.json")}`);
20115
+ console.log(` ${path44.join(nightWatchHome, "projects.json")}`);
20116
+ console.log(` ${path44.join(nightWatchHome, "history.json")}`);
20117
+ console.log(` ${path44.join(nightWatchHome, "prd-states.json")}`);
19374
20118
  console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
19375
20119
  console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
19376
20120
  return;
@@ -19408,8 +20152,8 @@ function createStateCommand() {
19408
20152
  init_dist();
19409
20153
  init_dist();
19410
20154
  import { execFileSync as execFileSync6 } from "child_process";
19411
- import * as fs41 from "fs";
19412
- import * as path42 from "path";
20155
+ import * as fs44 from "fs";
20156
+ import * as path45 from "path";
19413
20157
  import * as readline3 from "readline";
19414
20158
  import chalk6 from "chalk";
19415
20159
  async function run(fn) {
@@ -19431,7 +20175,7 @@ function getProvider(config, cwd) {
19431
20175
  return createBoardProvider(bp, cwd);
19432
20176
  }
19433
20177
  function defaultBoardTitle(cwd) {
19434
- return `${path42.basename(cwd)} Night Watch`;
20178
+ return `${path45.basename(cwd)} Night Watch`;
19435
20179
  }
19436
20180
  async function ensureBoardConfigured(config, cwd, provider, options) {
19437
20181
  if (config.boardProvider?.projectNumber) {
@@ -19463,10 +20207,10 @@ async function confirmPrompt(question) {
19463
20207
  input: process.stdin,
19464
20208
  output: process.stdout
19465
20209
  });
19466
- return new Promise((resolve9) => {
20210
+ return new Promise((resolve11) => {
19467
20211
  rl.question(question, (answer) => {
19468
20212
  rl.close();
19469
- resolve9(answer.trim().toLowerCase() === "y");
20213
+ resolve11(answer.trim().toLowerCase() === "y");
19470
20214
  });
19471
20215
  });
19472
20216
  }
@@ -19632,11 +20376,11 @@ function boardCommand(program2) {
19632
20376
  let body = options.body ?? "";
19633
20377
  if (options.bodyFile) {
19634
20378
  const filePath = options.bodyFile;
19635
- if (!fs41.existsSync(filePath)) {
20379
+ if (!fs44.existsSync(filePath)) {
19636
20380
  console.error(`File not found: ${filePath}`);
19637
20381
  process.exit(1);
19638
20382
  }
19639
- body = fs41.readFileSync(filePath, "utf-8");
20383
+ body = fs44.readFileSync(filePath, "utf-8");
19640
20384
  }
19641
20385
  const labels = [];
19642
20386
  if (options.label) {
@@ -19877,12 +20621,12 @@ function boardCommand(program2) {
19877
20621
  const config = loadConfig(cwd);
19878
20622
  const provider = getProvider(config, cwd);
19879
20623
  await ensureBoardConfigured(config, cwd, provider);
19880
- const roadmapPath = options.roadmap ?? path42.join(cwd, "ROADMAP.md");
19881
- if (!fs41.existsSync(roadmapPath)) {
20624
+ const roadmapPath = options.roadmap ?? path45.join(cwd, "ROADMAP.md");
20625
+ if (!fs44.existsSync(roadmapPath)) {
19882
20626
  console.error(`Roadmap file not found: ${roadmapPath}`);
19883
20627
  process.exit(1);
19884
20628
  }
19885
- const roadmapContent = fs41.readFileSync(roadmapPath, "utf-8");
20629
+ const roadmapContent = fs44.readFileSync(roadmapPath, "utf-8");
19886
20630
  const items = parseRoadmap(roadmapContent);
19887
20631
  const uncheckedItems = getUncheckedItems(items);
19888
20632
  if (uncheckedItems.length === 0) {
@@ -20006,7 +20750,7 @@ function boardCommand(program2) {
20006
20750
  // src/commands/queue.ts
20007
20751
  init_dist();
20008
20752
  init_dist();
20009
- import * as path43 from "path";
20753
+ import * as path46 from "path";
20010
20754
  import { spawn as spawn8 } from "child_process";
20011
20755
  import chalk7 from "chalk";
20012
20756
  import { Command as Command2 } from "commander";
@@ -20019,7 +20763,8 @@ var VALID_JOB_TYPES2 = [
20019
20763
  "slicer",
20020
20764
  "planner",
20021
20765
  "pr-resolver",
20022
- "merger"
20766
+ "merger",
20767
+ "manager"
20023
20768
  ];
20024
20769
  function formatTimestamp(unixTs) {
20025
20770
  if (unixTs === null) return "-";
@@ -20142,7 +20887,7 @@ function createQueueCommand() {
20142
20887
  process.exit(1);
20143
20888
  }
20144
20889
  }
20145
- const projectName = path43.basename(projectDir);
20890
+ const projectName = path46.basename(projectDir);
20146
20891
  const queueConfig = loadConfig(projectDir).queue;
20147
20892
  if (isJobPaused(projectDir, jobType)) {
20148
20893
  logger6.info(`Skipping enqueue for paused job: ${jobType}`);
@@ -20160,7 +20905,7 @@ function createQueueCommand() {
20160
20905
  });
20161
20906
  queue.command("resolve-key").description("Resolve the provider bucket key for a given project and job type").requiredOption("--project <dir>", "Project directory").requiredOption(
20162
20907
  "--job-type <type>",
20163
- "Job type (executor, reviewer, qa, audit, slicer, planner, pr-resolver, merger)"
20908
+ "Job type (executor, reviewer, qa, audit, slicer, planner, pr-resolver, merger, manager)"
20164
20909
  ).action((opts) => {
20165
20910
  try {
20166
20911
  const config = loadConfig(opts.project);
@@ -20243,7 +20988,7 @@ function createQueueCommand() {
20243
20988
  if (isJobPaused(projectDir, jobType)) {
20244
20989
  process.exit(2);
20245
20990
  }
20246
- const projectName = path43.basename(projectDir);
20991
+ const projectName = path46.basename(projectDir);
20247
20992
  const callerPid = opts.pid ? parseInt(opts.pid, 10) : void 0;
20248
20993
  const result = claimJobSlot(
20249
20994
  projectDir,
@@ -20310,7 +21055,8 @@ var QUEUE_MARKER_KEYS = /* @__PURE__ */ new Set([
20310
21055
  "NW_AUTO_MERGE",
20311
21056
  "NW_AUTO_MERGE_METHOD",
20312
21057
  "NW_MAX_RUNTIME",
20313
- "NW_QA_MAX_RUNTIME"
21058
+ "NW_QA_MAX_RUNTIME",
21059
+ "NW_MANAGER_MAX_RUNTIME"
20314
21060
  ]);
20315
21061
  function filterQueueMarkers(envJson) {
20316
21062
  const result = {};
@@ -20339,6 +21085,8 @@ function getScriptNameForJobType(jobType) {
20339
21085
  return "night-watch-pr-resolver-cron.sh";
20340
21086
  case "merger":
20341
21087
  return "night-watch-merger-cron.sh";
21088
+ case "manager":
21089
+ return "night-watch-manager-cron.sh";
20342
21090
  default:
20343
21091
  return null;
20344
21092
  }
@@ -20376,7 +21124,7 @@ function notifyCommand(program2) {
20376
21124
 
20377
21125
  // src/commands/summary.ts
20378
21126
  init_dist();
20379
- import path44 from "path";
21127
+ import path47 from "path";
20380
21128
  import chalk8 from "chalk";
20381
21129
  function formatDuration2(seconds) {
20382
21130
  if (seconds === null) return "-";
@@ -20406,7 +21154,7 @@ function formatJobStatus(status) {
20406
21154
  return chalk8.dim(status);
20407
21155
  }
20408
21156
  function getProjectName2(projectPath) {
20409
- return path44.basename(projectPath) || projectPath;
21157
+ return path47.basename(projectPath) || projectPath;
20410
21158
  }
20411
21159
  function formatProvider(providerKey) {
20412
21160
  return providerKey.split(":")[0] || providerKey;
@@ -20525,7 +21273,7 @@ function summaryCommand(program2) {
20525
21273
  // src/commands/resolve.ts
20526
21274
  init_dist();
20527
21275
  import { execFileSync as execFileSync7 } from "child_process";
20528
- import * as path45 from "path";
21276
+ import * as path48 from "path";
20529
21277
  function buildEnvVars6(config, options) {
20530
21278
  const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
20531
21279
  env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
@@ -20678,7 +21426,7 @@ ${stderr}`);
20678
21426
  }
20679
21427
  await sendNotifications(config, {
20680
21428
  event: notificationEvent,
20681
- projectName: path45.basename(projectDir),
21429
+ projectName: path48.basename(projectDir),
20682
21430
  exitCode,
20683
21431
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
20684
21432
  });
@@ -20693,7 +21441,7 @@ ${stderr}`);
20693
21441
 
20694
21442
  // src/commands/merge.ts
20695
21443
  init_dist();
20696
- import * as path46 from "path";
21444
+ import * as path49 from "path";
20697
21445
  function buildEnvVars7(config, options) {
20698
21446
  const env = buildBaseEnvVars(config, "merger", options.dryRun);
20699
21447
  env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
@@ -20829,7 +21577,7 @@ ${stderr}`);
20829
21577
  if (notificationEvent) {
20830
21578
  await sendNotifications(config, {
20831
21579
  event: notificationEvent,
20832
- projectName: path46.basename(projectDir),
21580
+ projectName: path49.basename(projectDir),
20833
21581
  exitCode,
20834
21582
  provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
20835
21583
  });
@@ -20843,12 +21591,208 @@ ${stderr}`);
20843
21591
  });
20844
21592
  }
20845
21593
 
21594
+ // src/commands/manager.ts
21595
+ init_dist();
21596
+ import * as path50 from "path";
21597
+ function resolveRunManager() {
21598
+ const runManager2 = runManager;
21599
+ if (typeof runManager2 !== "function") {
21600
+ throw new Error(
21601
+ "Manager runner is not available in @night-watch/core. Update core to include runManager(projectDir, config, options)."
21602
+ );
21603
+ }
21604
+ return runManager2;
21605
+ }
21606
+ function writeJson(value) {
21607
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
21608
+ `);
21609
+ }
21610
+ function parseTimeout(timeout) {
21611
+ if (!timeout) return void 0;
21612
+ const parsed = parseInt(timeout, 10);
21613
+ return Number.isNaN(parsed) || parsed < 0 ? void 0 : parsed;
21614
+ }
21615
+ function buildManagerRunOptions(options) {
21616
+ const timeout = parseTimeout(options.timeout);
21617
+ return {
21618
+ dryRun: options.dryRun === true,
21619
+ ...timeout !== void 0 ? { timeout } : {},
21620
+ ...options.provider ? { provider: options.provider } : {}
21621
+ };
21622
+ }
21623
+ function applyManagerCliOverrides(config, options) {
21624
+ const timeout = parseTimeout(options.timeout);
21625
+ let overridden = config;
21626
+ if (timeout !== void 0) {
21627
+ overridden = {
21628
+ ...overridden,
21629
+ manager: {
21630
+ ...overridden.manager,
21631
+ maxRuntime: timeout
21632
+ }
21633
+ };
21634
+ }
21635
+ if (options.provider) {
21636
+ overridden = {
21637
+ ...overridden,
21638
+ _cliProviderOverride: options.provider
21639
+ };
21640
+ }
21641
+ return overridden;
21642
+ }
21643
+ function getManagerConfig(config) {
21644
+ return config.manager;
21645
+ }
21646
+ function buildJsonResult(result, options) {
21647
+ if (result && typeof result === "object" && !Array.isArray(result)) {
21648
+ return {
21649
+ ...result,
21650
+ dryRun: options.dryRun
21651
+ };
21652
+ }
21653
+ return {
21654
+ dryRun: options.dryRun,
21655
+ result
21656
+ };
21657
+ }
21658
+ function resultExitCode(result) {
21659
+ if (!result || typeof result !== "object") return 0;
21660
+ const record = result;
21661
+ if (record.ok === false || record.success === false || typeof record.error === "string") {
21662
+ return 1;
21663
+ }
21664
+ return 0;
21665
+ }
21666
+ async function sendManagerNotifications(config, projectDir, result) {
21667
+ if (!result || typeof result !== "object") return;
21668
+ const decisions = result.notificationDecisions;
21669
+ if (!Array.isArray(decisions)) return;
21670
+ for (const decision of decisions) {
21671
+ if (!decision || typeof decision !== "object") continue;
21672
+ const item = decision;
21673
+ if (!item.shouldNotify || !item.event) continue;
21674
+ await sendNotifications(config, {
21675
+ event: item.event,
21676
+ projectName: path50.basename(projectDir),
21677
+ provider: resolveJobProvider(config, "manager"),
21678
+ exitCode: 0,
21679
+ failureReason: item.title,
21680
+ failureDetail: item.body
21681
+ });
21682
+ }
21683
+ }
21684
+ function printHumanResult(result, options) {
21685
+ const payload = buildJsonResult(result, options);
21686
+ header(options.dryRun ? "Dry Run: Manager" : "Manager Result");
21687
+ const table = createTable({ head: ["Metric", "Value"] });
21688
+ for (const key of [
21689
+ "summary",
21690
+ "findings",
21691
+ "createdIssues",
21692
+ "createdDrafts",
21693
+ "skippedDuplicates",
21694
+ "blockedItems"
21695
+ ]) {
21696
+ const value = payload[key];
21697
+ if (value === void 0) continue;
21698
+ table.push([key, Array.isArray(value) ? String(value.length) : String(value)]);
21699
+ }
21700
+ console.log(table.length > 0 ? table.toString() : JSON.stringify(payload, null, 2));
21701
+ }
21702
+ function managerCommand(program2) {
21703
+ 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) => {
21704
+ const projectDir = process.cwd();
21705
+ let config = loadConfig(projectDir);
21706
+ config = applyManagerCliOverrides(config, options);
21707
+ const managerConfig = getManagerConfig(config);
21708
+ const runOptions = buildManagerRunOptions(options);
21709
+ if (!managerConfig.enabled && !runOptions.dryRun) {
21710
+ if (options.json) {
21711
+ writeJson({ dryRun: false, skipped: true, reason: "manager-disabled" });
21712
+ } else {
21713
+ info("Manager is disabled in config; skipping run.");
21714
+ }
21715
+ process.exit(0);
21716
+ }
21717
+ const startedAt = Date.now();
21718
+ let exitCode = 0;
21719
+ const run2 = async () => {
21720
+ if (!runOptions.dryRun) {
21721
+ await maybeApplyCronSchedulingDelay(config, "manager", projectDir);
21722
+ }
21723
+ const runner = resolveRunManager();
21724
+ return runner(projectDir, config, runOptions);
21725
+ };
21726
+ try {
21727
+ const spinner = options.json ? null : createSpinner("Running Manager...");
21728
+ spinner?.start();
21729
+ const result = await run2();
21730
+ exitCode = resultExitCode(result);
21731
+ if (!runOptions.dryRun) {
21732
+ await sendManagerNotifications(config, projectDir, result);
21733
+ try {
21734
+ recordJobOutcome({
21735
+ config,
21736
+ exitCode,
21737
+ finishedAt: Date.now(),
21738
+ jobType: "manager",
21739
+ metadata: buildJsonResult(result, runOptions),
21740
+ projectDir,
21741
+ providerKey: resolveJobProvider(config, "manager"),
21742
+ startedAt,
21743
+ stdout: typeof result === "string" ? result : JSON.stringify(result)
21744
+ });
21745
+ } catch {
21746
+ }
21747
+ }
21748
+ if (options.json) {
21749
+ writeJson(buildJsonResult(result, runOptions));
21750
+ } else {
21751
+ if (exitCode === 0) {
21752
+ spinner?.succeed("Manager completed successfully");
21753
+ } else {
21754
+ spinner?.fail("Manager completed with errors");
21755
+ }
21756
+ printHumanResult(result, runOptions);
21757
+ }
21758
+ } catch (err) {
21759
+ const message = err instanceof Error ? err.message : String(err);
21760
+ if (!runOptions.dryRun) {
21761
+ try {
21762
+ recordJobOutcome({
21763
+ config,
21764
+ exitCode: 1,
21765
+ finishedAt: Date.now(),
21766
+ jobType: "manager",
21767
+ metadata: { error: message },
21768
+ projectDir,
21769
+ providerKey: resolveJobProvider(config, "manager"),
21770
+ startedAt,
21771
+ stderr: message
21772
+ });
21773
+ } catch {
21774
+ }
21775
+ }
21776
+ if (options.json) {
21777
+ process.stderr.write(
21778
+ `${JSON.stringify({ dryRun: runOptions.dryRun, ok: false, error: message }, null, 2)}
21779
+ `
21780
+ );
21781
+ } else {
21782
+ error(`Manager failed: ${message}`);
21783
+ }
21784
+ process.exit(1);
21785
+ }
21786
+ process.exit(exitCode);
21787
+ });
21788
+ }
21789
+
20846
21790
  // src/commands/agent.ts
20847
21791
  init_dist();
20848
21792
  var SCHEMA_VERSION2 = 1;
20849
21793
  var JSON_OPTION = "--json";
20850
21794
  var JSON_OPTION_DESCRIPTION = "Output as JSON";
20851
- function writeJson(value) {
21795
+ function writeJson2(value) {
20852
21796
  process.stdout.write(`${JSON.stringify(value, null, 2)}
20853
21797
  `);
20854
21798
  }
@@ -21005,7 +21949,7 @@ function normalizeJobType(job) {
21005
21949
  function agentCommand(program2) {
21006
21950
  const agent = program2.command("agent").description("Machine-readable agent operations");
21007
21951
  agent.command("status").description("Print a stable machine-readable project snapshot").requiredOption(JSON_OPTION, "Output status as JSON").action(async () => {
21008
- writeJson(await buildAgentStatus(process.cwd()));
21952
+ writeJson2(await buildAgentStatus(process.cwd()));
21009
21953
  });
21010
21954
  }
21011
21955
  function configCommand(program2) {
@@ -21013,18 +21957,18 @@ function configCommand(program2) {
21013
21957
  config.command("list").description("Print resolved config").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((options) => {
21014
21958
  const value = loadConfig(process.cwd());
21015
21959
  if (options.json) {
21016
- writeJson({ schemaVersion: SCHEMA_VERSION2, config: value });
21960
+ writeJson2({ schemaVersion: SCHEMA_VERSION2, config: value });
21017
21961
  } else {
21018
- writeJson(value);
21962
+ writeJson2(value);
21019
21963
  }
21020
21964
  });
21021
21965
  config.command("get <path>").description("Read a resolved config value by dot path").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((dotPath, options) => {
21022
21966
  try {
21023
21967
  const result = getConfigValue(process.cwd(), dotPath);
21024
21968
  if (options.json) {
21025
- writeJson({ schemaVersion: SCHEMA_VERSION2, ...result });
21969
+ writeJson2({ schemaVersion: SCHEMA_VERSION2, ...result });
21026
21970
  } else {
21027
- writeJson(result.value);
21971
+ writeJson2(result.value);
21028
21972
  }
21029
21973
  } catch (error2) {
21030
21974
  fail(error2 instanceof Error ? error2.message : String(error2), options);
@@ -21034,7 +21978,7 @@ function configCommand(program2) {
21034
21978
  try {
21035
21979
  const result = setConfigValue(process.cwd(), dotPath, parseConfigValue(rawValue));
21036
21980
  if (options.json) {
21037
- writeJson({ schemaVersion: SCHEMA_VERSION2, ok: true, ...result });
21981
+ writeJson2({ schemaVersion: SCHEMA_VERSION2, ok: true, ...result });
21038
21982
  } else {
21039
21983
  process.stdout.write(`Updated ${result.path}
21040
21984
  `);
@@ -21050,7 +21994,7 @@ function healthCommand(program2) {
21050
21994
  const snapshot = await fetchStatusSnapshot(process.cwd(), config);
21051
21995
  const health = buildHealth(snapshot, config);
21052
21996
  if (options.json) {
21053
- writeJson(health);
21997
+ writeJson2(health);
21054
21998
  } else {
21055
21999
  for (const check of health.checks) {
21056
22000
  process.stdout.write(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}
@@ -21069,7 +22013,7 @@ function jobCommand(program2) {
21069
22013
  const jobType = normalizeJobType(jobName);
21070
22014
  const result = setConfigValue(process.cwd(), `pausedJobs.${jobType}`, true);
21071
22015
  if (options.json) {
21072
- writeJson({
22016
+ writeJson2({
21073
22017
  schemaVersion: SCHEMA_VERSION2,
21074
22018
  ok: true,
21075
22019
  job: jobType,
@@ -21088,7 +22032,7 @@ function jobCommand(program2) {
21088
22032
  const jobType = normalizeJobType(jobName);
21089
22033
  const result = setConfigValue(process.cwd(), `pausedJobs.${jobType}`, false);
21090
22034
  if (options.json) {
21091
- writeJson({
22035
+ writeJson2({
21092
22036
  schemaVersion: SCHEMA_VERSION2,
21093
22037
  ok: true,
21094
22038
  job: jobType,
@@ -21115,17 +22059,17 @@ function jobCommand(program2) {
21115
22059
 
21116
22060
  // src/cli.ts
21117
22061
  var __filename5 = fileURLToPath6(import.meta.url);
21118
- var __dirname5 = dirname12(__filename5);
22062
+ var __dirname5 = dirname13(__filename5);
21119
22063
  function findPackageRoot(dir) {
21120
22064
  let d = dir;
21121
22065
  for (let i = 0; i < 5; i++) {
21122
- if (existsSync34(join38(d, "package.json"))) return d;
21123
- d = dirname12(d);
22066
+ if (existsSync36(join39(d, "package.json"))) return d;
22067
+ d = dirname13(d);
21124
22068
  }
21125
22069
  return dir;
21126
22070
  }
21127
22071
  var packageRoot = findPackageRoot(__dirname5);
21128
- var packageJson = JSON.parse(readFileSync21(join38(packageRoot, "package.json"), "utf-8"));
22072
+ var packageJson = JSON.parse(readFileSync23(join39(packageRoot, "package.json"), "utf-8"));
21129
22073
  var program = new Command3();
21130
22074
  program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
21131
22075
  initCommand(program);
@@ -21157,6 +22101,7 @@ notifyCommand(program);
21157
22101
  summaryCommand(program);
21158
22102
  resolveCommand(program);
21159
22103
  mergeCommand(program);
22104
+ managerCommand(program);
21160
22105
  agentCommand(program);
21161
22106
  configCommand(program);
21162
22107
  healthCommand(program);