@jonit-dev/night-watch-cli 1.8.6-beta.3 → 1.8.8-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1941,6 +1941,114 @@ var init_container = __esm({
1941
1941
  }
1942
1942
  });
1943
1943
 
1944
+ // ../core/dist/utils/logger.js
1945
+ import fs3 from "fs";
1946
+ import os2 from "os";
1947
+ import path3 from "path";
1948
+ function colorize(color, text) {
1949
+ return NO_COLOR ? text : `${color}${text}${ANSI.reset}`;
1950
+ }
1951
+ function stripAnsi(text) {
1952
+ return text.replace(ANSI_STRIP_RE, "");
1953
+ }
1954
+ function serializeValue(v) {
1955
+ if (v instanceof Error)
1956
+ return JSON.stringify(v.message);
1957
+ if (typeof v === "string")
1958
+ return JSON.stringify(v);
1959
+ if (v === null || v === void 0)
1960
+ return String(v);
1961
+ return JSON.stringify(v);
1962
+ }
1963
+ function formatMeta(meta) {
1964
+ const parts = Object.entries(meta).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k}=${serializeValue(v)}`);
1965
+ return parts.length > 0 ? " " + parts.join(" ") : "";
1966
+ }
1967
+ function getLogStream() {
1968
+ if (_logStreamFailed)
1969
+ return null;
1970
+ if (_logStream)
1971
+ return _logStream;
1972
+ try {
1973
+ const logsDir = path3.join(process.env.NIGHT_WATCH_HOME ?? path3.join(os2.homedir(), GLOBAL_CONFIG_DIR), "logs");
1974
+ fs3.mkdirSync(logsDir, { recursive: true });
1975
+ const logFile = path3.join(logsDir, "night-watch.log");
1976
+ _logStream = fs3.createWriteStream(logFile, { flags: "a" });
1977
+ _logStream.on("error", () => {
1978
+ _logStreamFailed = true;
1979
+ _logStream = null;
1980
+ });
1981
+ } catch {
1982
+ _logStreamFailed = true;
1983
+ }
1984
+ return _logStream;
1985
+ }
1986
+ function createLogger(context) {
1987
+ return new Logger(context);
1988
+ }
1989
+ var ANSI, NO_COLOR, ANSI_STRIP_RE, LEVEL_STYLES, _logStream, _logStreamFailed, Logger;
1990
+ var init_logger = __esm({
1991
+ "../core/dist/utils/logger.js"() {
1992
+ "use strict";
1993
+ init_constants();
1994
+ ANSI = {
1995
+ reset: "\x1B[0m",
1996
+ dim: "\x1B[2m",
1997
+ cyan: "\x1B[36m",
1998
+ green: "\x1B[32m",
1999
+ yellow: "\x1B[33m",
2000
+ red: "\x1B[31m",
2001
+ magenta: "\x1B[35m",
2002
+ bold: "\x1B[1m"
2003
+ };
2004
+ NO_COLOR = process.env.NO_COLOR !== void 0 || process.env.TERM === "dumb";
2005
+ ANSI_STRIP_RE = /\x1b\[[0-9;]*m/g;
2006
+ LEVEL_STYLES = {
2007
+ debug: { label: "DEBUG", color: ANSI.magenta },
2008
+ info: { label: "INFO ", color: ANSI.green },
2009
+ warn: { label: "WARN ", color: ANSI.yellow },
2010
+ error: { label: "ERROR", color: ANSI.red }
2011
+ };
2012
+ _logStream = null;
2013
+ _logStreamFailed = false;
2014
+ Logger = class {
2015
+ context;
2016
+ constructor(context) {
2017
+ this.context = context;
2018
+ }
2019
+ write(level, message, meta) {
2020
+ const { label: label2, color } = LEVEL_STYLES[level];
2021
+ const ts = colorize(ANSI.dim, (/* @__PURE__ */ new Date()).toISOString());
2022
+ const lvl = colorize(`${ANSI.bold}${color}`, `[${label2}]`);
2023
+ const ctx = colorize(ANSI.cyan, `[${this.context}]`);
2024
+ const msg = level === "error" ? colorize(color, message) : message;
2025
+ const metaStr = meta ? formatMeta(meta) : "";
2026
+ const line = `${ts} ${lvl} ${ctx} ${msg}${metaStr}`;
2027
+ if (level === "error") {
2028
+ console.error(line);
2029
+ } else if (level === "warn") {
2030
+ console.warn(line);
2031
+ } else {
2032
+ console.log(line);
2033
+ }
2034
+ getLogStream()?.write(stripAnsi(line) + "\n");
2035
+ }
2036
+ debug(message, meta) {
2037
+ this.write("debug", message, meta);
2038
+ }
2039
+ info(message, meta) {
2040
+ this.write("info", message, meta);
2041
+ }
2042
+ warn(message, meta) {
2043
+ this.write("warn", message, meta);
2044
+ }
2045
+ error(message, meta) {
2046
+ this.write("error", message, meta);
2047
+ }
2048
+ };
2049
+ }
2050
+ });
2051
+
1944
2052
  // ../core/dist/board/providers/github-graphql.js
1945
2053
  import { execFile } from "child_process";
1946
2054
  import { promisify } from "util";
@@ -2309,15 +2417,55 @@ var init_github_projects_base = __esm({
2309
2417
  // ../core/dist/board/providers/github-projects.js
2310
2418
  import { execFile as execFile2 } from "child_process";
2311
2419
  import { promisify as promisify2 } from "util";
2312
- var execFileAsync2, GitHubProjectsProvider;
2420
+ function formatExecFileError(err) {
2421
+ return err instanceof Error ? err.message : String(err);
2422
+ }
2423
+ function isMissingLabelError(err) {
2424
+ const message = formatExecFileError(err);
2425
+ return /could not add label/i.test(message) || /label.*not found/i.test(message);
2426
+ }
2427
+ var execFileAsync2, logger, GitHubProjectsProvider;
2313
2428
  var init_github_projects = __esm({
2314
2429
  "../core/dist/board/providers/github-projects.js"() {
2315
2430
  "use strict";
2316
2431
  init_types2();
2432
+ init_logger();
2317
2433
  init_github_graphql();
2318
2434
  init_github_projects_base();
2319
2435
  execFileAsync2 = promisify2(execFile2);
2436
+ logger = createLogger("github-board");
2320
2437
  GitHubProjectsProvider = class extends GitHubProjectsBase {
2438
+ async createRepositoryIssue(repo, input) {
2439
+ const requestedLabels = input.labels ?? [];
2440
+ const buildIssueArgs = (labels) => {
2441
+ const args = ["issue", "create", "--title", input.title, "--body", input.body, "--repo", repo];
2442
+ if (labels.length > 0) {
2443
+ args.push("--label", labels.join(","));
2444
+ }
2445
+ return args;
2446
+ };
2447
+ try {
2448
+ const { stdout } = await execFileAsync2("gh", buildIssueArgs(requestedLabels), {
2449
+ cwd: this.cwd,
2450
+ encoding: "utf-8"
2451
+ });
2452
+ return { issueUrl: stdout.trim(), appliedLabels: requestedLabels };
2453
+ } catch (err) {
2454
+ if (requestedLabels.length > 0 && isMissingLabelError(err)) {
2455
+ logger.warn("Issue creation failed due to missing labels; retrying without labels", {
2456
+ repo,
2457
+ title: input.title,
2458
+ labels: requestedLabels
2459
+ });
2460
+ const { stdout } = await execFileAsync2("gh", buildIssueArgs([]), {
2461
+ cwd: this.cwd,
2462
+ encoding: "utf-8"
2463
+ });
2464
+ return { issueUrl: stdout.trim(), appliedLabels: [] };
2465
+ }
2466
+ throw err;
2467
+ }
2468
+ }
2321
2469
  async setupBoard(title) {
2322
2470
  const owner = await this.getRepoOwner();
2323
2471
  const existing = await this.findExistingProject(owner, title);
@@ -2408,24 +2556,7 @@ var init_github_projects = __esm({
2408
2556
  async createIssue(input) {
2409
2557
  const repo = await this.getRepo();
2410
2558
  const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
2411
- const issueArgs = [
2412
- "issue",
2413
- "create",
2414
- "--title",
2415
- input.title,
2416
- "--body",
2417
- input.body,
2418
- "--repo",
2419
- repo
2420
- ];
2421
- if (input.labels && input.labels.length > 0) {
2422
- issueArgs.push("--label", input.labels.join(","));
2423
- }
2424
- const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, {
2425
- cwd: this.cwd,
2426
- encoding: "utf-8"
2427
- });
2428
- const issueUrl = issueUrlRaw.trim();
2559
+ const { issueUrl, appliedLabels } = await this.createRepositoryIssue(repo, input);
2429
2560
  const issueNumber = parseInt(issueUrl.split("/").pop() ?? "", 10);
2430
2561
  if (!issueNumber)
2431
2562
  throw new Error(`Failed to parse issue number from URL: ${issueUrl}`);
@@ -2456,7 +2587,7 @@ var init_github_projects = __esm({
2456
2587
  body: input.body,
2457
2588
  url: issueJson.url,
2458
2589
  column: targetColumn,
2459
- labels: input.labels ?? [],
2590
+ labels: appliedLabels,
2460
2591
  assignees: []
2461
2592
  };
2462
2593
  }
@@ -2799,7 +2930,13 @@ var init_labels = __esm({
2799
2930
  name: HORIZON_LABEL_INFO[h].name,
2800
2931
  description: HORIZON_LABEL_INFO[h].description,
2801
2932
  color: "5319e7"
2802
- }))
2933
+ })),
2934
+ // Operational labels
2935
+ {
2936
+ name: "analytics",
2937
+ description: "Created by the analytics job for Amplitude findings",
2938
+ color: "1d76db"
2939
+ }
2803
2940
  ];
2804
2941
  }
2805
2942
  });
@@ -3003,40 +3140,40 @@ var init_repositories = __esm({
3003
3140
  });
3004
3141
 
3005
3142
  // ../core/dist/storage/json-state-migrator.js
3006
- import * as fs3 from "fs";
3007
- import * as path3 from "path";
3143
+ import * as fs4 from "fs";
3144
+ import * as path4 from "path";
3008
3145
  function tryReadJson(filePath) {
3009
- if (!fs3.existsSync(filePath)) {
3146
+ if (!fs4.existsSync(filePath)) {
3010
3147
  return null;
3011
3148
  }
3012
3149
  try {
3013
- const content = fs3.readFileSync(filePath, "utf-8");
3150
+ const content = fs4.readFileSync(filePath, "utf-8");
3014
3151
  return JSON.parse(content);
3015
3152
  } catch {
3016
3153
  return null;
3017
3154
  }
3018
3155
  }
3019
3156
  function backupFile(src, backupDir) {
3020
- if (fs3.existsSync(src)) {
3021
- fs3.copyFileSync(src, path3.join(backupDir, path3.basename(src)));
3157
+ if (fs4.existsSync(src)) {
3158
+ fs4.copyFileSync(src, path4.join(backupDir, path4.basename(src)));
3022
3159
  }
3023
3160
  }
3024
3161
  function collectPrdDirs(projectPaths) {
3025
3162
  const prdDirs = [];
3026
3163
  for (const projectPath of projectPaths) {
3027
- const configPath = path3.join(projectPath, CONFIG_FILE_NAME);
3164
+ const configPath = path4.join(projectPath, CONFIG_FILE_NAME);
3028
3165
  let prdDir = "docs/prds";
3029
- if (fs3.existsSync(configPath)) {
3166
+ if (fs4.existsSync(configPath)) {
3030
3167
  try {
3031
- const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
3168
+ const config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
3032
3169
  if (typeof config.prdDir === "string" && config.prdDir.length > 0) {
3033
3170
  prdDir = config.prdDir;
3034
3171
  }
3035
3172
  } catch {
3036
3173
  }
3037
3174
  }
3038
- const fullPrdDir = path3.join(projectPath, prdDir);
3039
- if (fs3.existsSync(fullPrdDir)) {
3175
+ const fullPrdDir = path4.join(projectPath, prdDir);
3176
+ if (fs4.existsSync(fullPrdDir)) {
3040
3177
  prdDirs.push(fullPrdDir);
3041
3178
  }
3042
3179
  }
@@ -3056,11 +3193,11 @@ function migrateJsonToSqlite(nightWatchHome) {
3056
3193
  alreadyMigrated: true
3057
3194
  };
3058
3195
  }
3059
- const backupDir = path3.join(nightWatchHome, "backups", `json-migration-${Date.now()}`);
3060
- fs3.mkdirSync(backupDir, { recursive: true });
3061
- const projectsJsonPath = path3.join(nightWatchHome, "projects.json");
3062
- const historyJsonPath = path3.join(nightWatchHome, "history.json");
3063
- const prdStatesJsonPath = path3.join(nightWatchHome, "prd-states.json");
3196
+ const backupDir = path4.join(nightWatchHome, "backups", `json-migration-${Date.now()}`);
3197
+ fs4.mkdirSync(backupDir, { recursive: true });
3198
+ const projectsJsonPath = path4.join(nightWatchHome, "projects.json");
3199
+ const historyJsonPath = path4.join(nightWatchHome, "history.json");
3200
+ const prdStatesJsonPath = path4.join(nightWatchHome, "prd-states.json");
3064
3201
  backupFile(projectsJsonPath, backupDir);
3065
3202
  backupFile(historyJsonPath, backupDir);
3066
3203
  backupFile(prdStatesJsonPath, backupDir);
@@ -3112,7 +3249,7 @@ function migrateJsonToSqlite(nightWatchHome) {
3112
3249
  const prdDirs = collectPrdDirs(projectPaths);
3113
3250
  const migrateRoadmapStates = db.transaction(() => {
3114
3251
  for (const prdDir of prdDirs) {
3115
- const stateFilePath = path3.join(prdDir, ".roadmap-state.json");
3252
+ const stateFilePath = path4.join(prdDir, ".roadmap-state.json");
3116
3253
  const state = tryReadJson(stateFilePath);
3117
3254
  if (state === null) {
3118
3255
  continue;
@@ -3122,7 +3259,7 @@ function migrateJsonToSqlite(nightWatchHome) {
3122
3259
  }
3123
3260
  const backupName = `roadmap-state-${Buffer.from(prdDir).toString("base64url").slice(0, 32)}.json`;
3124
3261
  try {
3125
- fs3.copyFileSync(stateFilePath, path3.join(backupDir, backupName));
3262
+ fs4.copyFileSync(stateFilePath, path4.join(backupDir, backupName));
3126
3263
  } catch {
3127
3264
  }
3128
3265
  roadmapState.save(prdDir, {
@@ -3154,114 +3291,6 @@ var init_json_state_migrator = __esm({
3154
3291
  }
3155
3292
  });
3156
3293
 
3157
- // ../core/dist/utils/logger.js
3158
- import fs4 from "fs";
3159
- import os2 from "os";
3160
- import path4 from "path";
3161
- function colorize(color, text) {
3162
- return NO_COLOR ? text : `${color}${text}${ANSI.reset}`;
3163
- }
3164
- function stripAnsi(text) {
3165
- return text.replace(ANSI_STRIP_RE, "");
3166
- }
3167
- function serializeValue(v) {
3168
- if (v instanceof Error)
3169
- return JSON.stringify(v.message);
3170
- if (typeof v === "string")
3171
- return JSON.stringify(v);
3172
- if (v === null || v === void 0)
3173
- return String(v);
3174
- return JSON.stringify(v);
3175
- }
3176
- function formatMeta(meta) {
3177
- const parts = Object.entries(meta).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k}=${serializeValue(v)}`);
3178
- return parts.length > 0 ? " " + parts.join(" ") : "";
3179
- }
3180
- function getLogStream() {
3181
- if (_logStreamFailed)
3182
- return null;
3183
- if (_logStream)
3184
- return _logStream;
3185
- try {
3186
- const logsDir = path4.join(process.env.NIGHT_WATCH_HOME ?? path4.join(os2.homedir(), GLOBAL_CONFIG_DIR), "logs");
3187
- fs4.mkdirSync(logsDir, { recursive: true });
3188
- const logFile = path4.join(logsDir, "night-watch.log");
3189
- _logStream = fs4.createWriteStream(logFile, { flags: "a" });
3190
- _logStream.on("error", () => {
3191
- _logStreamFailed = true;
3192
- _logStream = null;
3193
- });
3194
- } catch {
3195
- _logStreamFailed = true;
3196
- }
3197
- return _logStream;
3198
- }
3199
- function createLogger(context) {
3200
- return new Logger(context);
3201
- }
3202
- var ANSI, NO_COLOR, ANSI_STRIP_RE, LEVEL_STYLES, _logStream, _logStreamFailed, Logger;
3203
- var init_logger = __esm({
3204
- "../core/dist/utils/logger.js"() {
3205
- "use strict";
3206
- init_constants();
3207
- ANSI = {
3208
- reset: "\x1B[0m",
3209
- dim: "\x1B[2m",
3210
- cyan: "\x1B[36m",
3211
- green: "\x1B[32m",
3212
- yellow: "\x1B[33m",
3213
- red: "\x1B[31m",
3214
- magenta: "\x1B[35m",
3215
- bold: "\x1B[1m"
3216
- };
3217
- NO_COLOR = process.env.NO_COLOR !== void 0 || process.env.TERM === "dumb";
3218
- ANSI_STRIP_RE = /\x1b\[[0-9;]*m/g;
3219
- LEVEL_STYLES = {
3220
- debug: { label: "DEBUG", color: ANSI.magenta },
3221
- info: { label: "INFO ", color: ANSI.green },
3222
- warn: { label: "WARN ", color: ANSI.yellow },
3223
- error: { label: "ERROR", color: ANSI.red }
3224
- };
3225
- _logStream = null;
3226
- _logStreamFailed = false;
3227
- Logger = class {
3228
- context;
3229
- constructor(context) {
3230
- this.context = context;
3231
- }
3232
- write(level, message, meta) {
3233
- const { label: label2, color } = LEVEL_STYLES[level];
3234
- const ts = colorize(ANSI.dim, (/* @__PURE__ */ new Date()).toISOString());
3235
- const lvl = colorize(`${ANSI.bold}${color}`, `[${label2}]`);
3236
- const ctx = colorize(ANSI.cyan, `[${this.context}]`);
3237
- const msg = level === "error" ? colorize(color, message) : message;
3238
- const metaStr = meta ? formatMeta(meta) : "";
3239
- const line = `${ts} ${lvl} ${ctx} ${msg}${metaStr}`;
3240
- if (level === "error") {
3241
- console.error(line);
3242
- } else if (level === "warn") {
3243
- console.warn(line);
3244
- } else {
3245
- console.log(line);
3246
- }
3247
- getLogStream()?.write(stripAnsi(line) + "\n");
3248
- }
3249
- debug(message, meta) {
3250
- this.write("debug", message, meta);
3251
- }
3252
- info(message, meta) {
3253
- this.write("info", message, meta);
3254
- }
3255
- warn(message, meta) {
3256
- this.write("warn", message, meta);
3257
- }
3258
- error(message, meta) {
3259
- this.write("error", message, meta);
3260
- }
3261
- };
3262
- }
3263
- });
3264
-
3265
3294
  // ../core/dist/utils/prd-states.js
3266
3295
  function readPrdStates() {
3267
3296
  const { prdState } = getRepositories();
@@ -6517,7 +6546,7 @@ function reconcileStaleRunningJobs(db) {
6517
6546
  const lockInfo = checkLockFile(getLockPathForJob(entry.projectPath, entry.jobType));
6518
6547
  if (!lockInfo.running) {
6519
6548
  staleIds.push(entry.id);
6520
- logger.warn("Expiring stale running queue entry", {
6549
+ logger2.warn("Expiring stale running queue entry", {
6521
6550
  id: entry.id,
6522
6551
  jobType: entry.jobType,
6523
6552
  project: entry.projectName,
@@ -6600,7 +6629,7 @@ function enqueueJob(projectPath, projectName, jobType, envVars, config, provider
6600
6629
  provider_key)
6601
6630
  VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)`).run(projectPath, projectName, jobType, priority, envJson, now, providerKey ?? null);
6602
6631
  const id = result.lastInsertRowid;
6603
- logger.info("Job enqueued", {
6632
+ logger2.info("Job enqueued", {
6604
6633
  id,
6605
6634
  jobType,
6606
6635
  project: projectName,
@@ -6626,7 +6655,7 @@ function markJobRunning(queueId) {
6626
6655
  const db = openDb();
6627
6656
  try {
6628
6657
  db.prepare(`UPDATE job_queue SET status = 'running' WHERE id = ?`).run(queueId);
6629
- logger.debug("Job marked running", { id: queueId });
6658
+ logger2.debug("Job marked running", { id: queueId });
6630
6659
  } finally {
6631
6660
  db.close();
6632
6661
  }
@@ -6635,7 +6664,7 @@ function removeJob(queueId) {
6635
6664
  const db = openDb();
6636
6665
  try {
6637
6666
  db.prepare(`DELETE FROM job_queue WHERE id = ?`).run(queueId);
6638
- logger.debug("Job removed from queue", { id: queueId });
6667
+ logger2.debug("Job removed from queue", { id: queueId });
6639
6668
  } finally {
6640
6669
  db.close();
6641
6670
  }
@@ -6677,12 +6706,12 @@ function getInFlightCountByBucket(db) {
6677
6706
  function fitsProviderCapacity(candidate, config, inFlightByBucket) {
6678
6707
  const bucketKey = candidate.providerKey;
6679
6708
  if (!bucketKey) {
6680
- logger.debug("Capacity check skipped: no provider bucket assigned", { id: candidate.id });
6709
+ logger2.debug("Capacity check skipped: no provider bucket assigned", { id: candidate.id });
6681
6710
  return true;
6682
6711
  }
6683
6712
  const bucketConfig = config?.providerBuckets?.[bucketKey];
6684
6713
  if (!bucketConfig) {
6685
- logger.debug("Capacity check skipped: bucket not configured", {
6714
+ logger2.debug("Capacity check skipped: bucket not configured", {
6686
6715
  id: candidate.id,
6687
6716
  bucket: bucketKey
6688
6717
  });
@@ -6690,7 +6719,7 @@ function fitsProviderCapacity(candidate, config, inFlightByBucket) {
6690
6719
  }
6691
6720
  const inFlightCount = inFlightByBucket[bucketKey] ?? 0;
6692
6721
  if (inFlightCount >= bucketConfig.maxConcurrency) {
6693
- logger.debug("Capacity check failed: concurrency limit reached", {
6722
+ logger2.debug("Capacity check failed: concurrency limit reached", {
6694
6723
  id: candidate.id,
6695
6724
  bucket: bucketKey,
6696
6725
  inFlightCount,
@@ -6698,7 +6727,7 @@ function fitsProviderCapacity(candidate, config, inFlightByBucket) {
6698
6727
  });
6699
6728
  return false;
6700
6729
  }
6701
- logger.debug("Capacity check passed", {
6730
+ logger2.debug("Capacity check passed", {
6702
6731
  id: candidate.id,
6703
6732
  bucket: bucketKey,
6704
6733
  inFlightCount
@@ -6714,9 +6743,9 @@ function dispatchNextJob(config) {
6714
6743
  const mode = config?.mode ?? "conservative";
6715
6744
  const running = db.prepare(`SELECT COUNT(*) as count FROM job_queue WHERE status IN ('running', 'dispatched')`).get();
6716
6745
  const runningCount = running?.count ?? 0;
6717
- logger.debug("Dispatch attempt", { mode, runningCount, maxConcurrency });
6746
+ logger2.debug("Dispatch attempt", { mode, runningCount, maxConcurrency });
6718
6747
  if (runningCount >= maxConcurrency) {
6719
- logger.info("Dispatch skipped: global concurrency limit reached", {
6748
+ logger2.info("Dispatch skipped: global concurrency limit reached", {
6720
6749
  runningCount,
6721
6750
  maxConcurrency
6722
6751
  });
@@ -6726,11 +6755,11 @@ function dispatchNextJob(config) {
6726
6755
  if (mode === "conservative") {
6727
6756
  const [entry] = getPendingCandidates(db, 1);
6728
6757
  if (!entry) {
6729
- logger.debug("Dispatch skipped: no pending jobs");
6758
+ logger2.debug("Dispatch skipped: no pending jobs");
6730
6759
  return null;
6731
6760
  }
6732
6761
  db.prepare(`UPDATE job_queue SET status = 'dispatched', dispatched_at = ? WHERE id = ?`).run(now, entry.id);
6733
- logger.info("Job dispatched (conservative)", {
6762
+ logger2.info("Job dispatched (conservative)", {
6734
6763
  id: entry.id,
6735
6764
  jobType: entry.jobType,
6736
6765
  project: entry.projectName,
@@ -6743,16 +6772,16 @@ function dispatchNextJob(config) {
6743
6772
  if (mode === "auto") {
6744
6773
  const candidates2 = getPendingCandidates(db);
6745
6774
  if (candidates2.length === 0) {
6746
- logger.debug("Dispatch skipped: no pending jobs");
6775
+ logger2.debug("Dispatch skipped: no pending jobs");
6747
6776
  return null;
6748
6777
  }
6749
- logger.debug("Auto dispatch: evaluating candidates", { candidateCount: candidates2.length });
6778
+ logger2.debug("Auto dispatch: evaluating candidates", { candidateCount: candidates2.length });
6750
6779
  const inFlightByBucket2 = getInFlightCountByBucket(db);
6751
6780
  for (const candidate of candidates2) {
6752
6781
  const bucketKey = candidate.providerKey ?? "default";
6753
6782
  if ((inFlightByBucket2[bucketKey] ?? 0) === 0) {
6754
6783
  db.prepare(`UPDATE job_queue SET status = 'dispatched', dispatched_at = ? WHERE id = ?`).run(now, candidate.id);
6755
- logger.info("Job dispatched (auto)", {
6784
+ logger2.info("Job dispatched (auto)", {
6756
6785
  id: candidate.id,
6757
6786
  jobType: candidate.jobType,
6758
6787
  project: candidate.projectName,
@@ -6763,24 +6792,24 @@ function dispatchNextJob(config) {
6763
6792
  return { ...candidate, status: "dispatched", dispatchedAt: now };
6764
6793
  }
6765
6794
  }
6766
- logger.info("Dispatch skipped: all candidates blocked by provider concurrency (auto)", {
6795
+ logger2.info("Dispatch skipped: all candidates blocked by provider concurrency (auto)", {
6767
6796
  candidateCount: candidates2.length
6768
6797
  });
6769
6798
  return null;
6770
6799
  }
6771
6800
  const candidates = getPendingCandidates(db);
6772
6801
  if (candidates.length === 0) {
6773
- logger.debug("Dispatch skipped: no pending jobs");
6802
+ logger2.debug("Dispatch skipped: no pending jobs");
6774
6803
  return null;
6775
6804
  }
6776
- logger.debug("Provider-aware dispatch: evaluating candidates", {
6805
+ logger2.debug("Provider-aware dispatch: evaluating candidates", {
6777
6806
  candidateCount: candidates.length
6778
6807
  });
6779
6808
  const inFlightByBucket = getInFlightCountByBucket(db);
6780
6809
  for (const candidate of candidates) {
6781
6810
  if (fitsProviderCapacity(candidate, config, inFlightByBucket)) {
6782
6811
  db.prepare(`UPDATE job_queue SET status = 'dispatched', dispatched_at = ? WHERE id = ?`).run(now, candidate.id);
6783
- logger.info("Job dispatched (provider-aware)", {
6812
+ logger2.info("Job dispatched (provider-aware)", {
6784
6813
  id: candidate.id,
6785
6814
  jobType: candidate.jobType,
6786
6815
  project: candidate.projectName,
@@ -6791,7 +6820,7 @@ function dispatchNextJob(config) {
6791
6820
  return { ...candidate, status: "dispatched", dispatchedAt: now };
6792
6821
  }
6793
6822
  }
6794
- logger.info("Dispatch skipped: all candidates blocked by provider capacity", {
6823
+ logger2.info("Dispatch skipped: all candidates blocked by provider capacity", {
6795
6824
  candidateCount: candidates.length
6796
6825
  });
6797
6826
  return null;
@@ -6851,7 +6880,7 @@ function clearQueue(filter, force) {
6851
6880
  } else {
6852
6881
  result = db.prepare(`DELETE FROM job_queue WHERE status IN ${statuses}`).run();
6853
6882
  }
6854
- logger.info("Queue cleared", {
6883
+ logger2.info("Queue cleared", {
6855
6884
  count: result.changes,
6856
6885
  filter: filter ?? "all",
6857
6886
  force: force ?? false
@@ -6871,9 +6900,9 @@ function expireStaleJobs(maxWaitTime) {
6871
6900
  WHERE (status = 'pending' AND enqueued_at < ?)
6872
6901
  OR (status IN ('dispatched', 'running') AND dispatched_at < ?)`).run(now, cutoff, cutoff);
6873
6902
  if (result.changes > 0) {
6874
- logger.warn("Expired stale jobs", { count: result.changes, maxWaitTime });
6903
+ logger2.warn("Expired stale jobs", { count: result.changes, maxWaitTime });
6875
6904
  } else {
6876
- logger.debug("No stale jobs to expire", { maxWaitTime });
6905
+ logger2.debug("No stale jobs to expire", { maxWaitTime });
6877
6906
  }
6878
6907
  return result.changes;
6879
6908
  } finally {
@@ -6916,7 +6945,7 @@ function claimJobSlot(projectPath, projectName, jobType, providerKey, config) {
6916
6945
  const lockInfo = checkLockFile(getLockPathForJob(entry.projectPath, entry.jobType));
6917
6946
  if (!lockInfo.running) {
6918
6947
  db.prepare(`UPDATE job_queue SET status = 'expired', expired_at = ? WHERE id = ?`).run(now, entry.id);
6919
- logger.warn("claimJobSlot: expired stale running entry", {
6948
+ logger2.warn("claimJobSlot: expired stale running entry", {
6920
6949
  id: entry.id,
6921
6950
  jobType: entry.jobType,
6922
6951
  project: entry.projectName
@@ -6925,7 +6954,7 @@ function claimJobSlot(projectPath, projectName, jobType, providerKey, config) {
6925
6954
  }
6926
6955
  const globalCount = db.prepare(`SELECT COUNT(*) as count FROM job_queue WHERE status IN ('running', 'dispatched')`).get().count;
6927
6956
  if (globalCount >= maxConcurrency) {
6928
- logger.debug("claimJobSlot: global concurrency limit reached", {
6957
+ logger2.debug("claimJobSlot: global concurrency limit reached", {
6929
6958
  globalCount,
6930
6959
  maxConcurrency
6931
6960
  });
@@ -6937,7 +6966,7 @@ function claimJobSlot(projectPath, projectName, jobType, providerKey, config) {
6937
6966
  const bucketCount = db.prepare(`SELECT COUNT(*) as count FROM job_queue
6938
6967
  WHERE status IN ('running', 'dispatched') AND provider_key = ?`).get(providerKey).count;
6939
6968
  if (bucketCount >= bucketConfig.maxConcurrency) {
6940
- logger.debug("claimJobSlot: bucket concurrency limit reached", {
6969
+ logger2.debug("claimJobSlot: bucket concurrency limit reached", {
6941
6970
  providerKey,
6942
6971
  bucketCount,
6943
6972
  bucketMax: bucketConfig.maxConcurrency
@@ -6952,7 +6981,7 @@ function claimJobSlot(projectPath, projectName, jobType, providerKey, config) {
6952
6981
  dispatched_at, provider_key)
6953
6982
  VALUES (?, ?, ?, ?, 'running', '{}', ?, ?, ?)`).run(projectPath, projectName, jobType, priority, now, now, providerKey ?? null);
6954
6983
  const id = insertResult.lastInsertRowid;
6955
- logger.info("claimJobSlot: slot claimed", {
6984
+ logger2.info("claimJobSlot: slot claimed", {
6956
6985
  id,
6957
6986
  jobType,
6958
6987
  project: projectName,
@@ -7042,7 +7071,7 @@ function getJobRunsAnalytics(windowHours = 24) {
7042
7071
  db.close();
7043
7072
  }
7044
7073
  }
7045
- var logger, _migrationsApplied;
7074
+ var logger2, _migrationsApplied;
7046
7075
  var init_job_queue = __esm({
7047
7076
  "../core/dist/utils/job-queue.js"() {
7048
7077
  "use strict";
@@ -7052,7 +7081,7 @@ var init_job_queue = __esm({
7052
7081
  init_logger();
7053
7082
  init_scheduling();
7054
7083
  init_status_data();
7055
- logger = createLogger("job-queue");
7084
+ logger2 = createLogger("job-queue");
7056
7085
  _migrationsApplied = false;
7057
7086
  }
7058
7087
  });
@@ -7069,7 +7098,7 @@ function buildDateRange(lookbackDays) {
7069
7098
  return { start: fmt(start), end: fmt(end) };
7070
7099
  }
7071
7100
  async function amplitudeFetch(url, authHeader, label2) {
7072
- logger2.debug(`Fetching ${label2}`, { url });
7101
+ logger3.debug(`Fetching ${label2}`, { url });
7073
7102
  const response = await fetch(url, {
7074
7103
  headers: { Authorization: authHeader }
7075
7104
  });
@@ -7087,7 +7116,7 @@ async function amplitudeFetch(url, authHeader, label2) {
7087
7116
  async function fetchAmplitudeData(apiKey, secretKey, lookbackDays) {
7088
7117
  const authHeader = buildAuthHeader(apiKey, secretKey);
7089
7118
  const { start, end } = buildDateRange(lookbackDays);
7090
- logger2.info("Fetching Amplitude data", { lookbackDays, start, end });
7119
+ logger3.info("Fetching Amplitude data", { lookbackDays, start, end });
7091
7120
  const baseUrl = "https://amplitude.com/api/2";
7092
7121
  const allEventsParam = encodeURIComponent('{"event_type":"_all"}');
7093
7122
  const [activeUsers, eventSegmentation, retention, userSessions] = await Promise.allSettled([
@@ -7104,7 +7133,7 @@ async function fetchAmplitudeData(apiKey, secretKey, lookbackDays) {
7104
7133
  const extract = (result, label2) => {
7105
7134
  if (result.status === "fulfilled")
7106
7135
  return result.value;
7107
- logger2.warn(`Failed to fetch ${label2}`, { error: String(result.reason) });
7136
+ logger3.warn(`Failed to fetch ${label2}`, { error: String(result.reason) });
7108
7137
  return null;
7109
7138
  };
7110
7139
  return {
@@ -7116,12 +7145,12 @@ async function fetchAmplitudeData(apiKey, secretKey, lookbackDays) {
7116
7145
  lookbackDays
7117
7146
  };
7118
7147
  }
7119
- var logger2;
7148
+ var logger3;
7120
7149
  var init_amplitude_client = __esm({
7121
7150
  "../core/dist/analytics/amplitude-client.js"() {
7122
7151
  "use strict";
7123
7152
  init_logger();
7124
- logger2 = createLogger("amplitude-client");
7153
+ logger3 = createLogger("amplitude-client");
7125
7154
  }
7126
7155
  });
7127
7156
 
@@ -7140,7 +7169,7 @@ function parseIssuesFromResponse(text) {
7140
7169
  return [];
7141
7170
  return parsed.filter((item) => typeof item === "object" && item !== null && typeof item.title === "string" && typeof item.body === "string");
7142
7171
  } catch {
7143
- logger3.warn("Failed to parse AI response as JSON");
7172
+ logger4.warn("Failed to parse AI response as JSON");
7144
7173
  return [];
7145
7174
  }
7146
7175
  }
@@ -7150,7 +7179,7 @@ async function runAnalytics(config, projectDir) {
7150
7179
  if (!apiKey || !secretKey) {
7151
7180
  throw new Error("AMPLITUDE_API_KEY and AMPLITUDE_SECRET_KEY must be set in providerEnv to run analytics");
7152
7181
  }
7153
- logger3.info("Fetching Amplitude data", { lookbackDays: config.analytics.lookbackDays });
7182
+ logger4.info("Fetching Amplitude data", { lookbackDays: config.analytics.lookbackDays });
7154
7183
  const data = await fetchAmplitudeData(apiKey, secretKey, config.analytics.lookbackDays);
7155
7184
  const systemPrompt = config.analytics.analysisPrompt?.trim() || DEFAULT_ANALYTICS_PROMPT;
7156
7185
  const prompt = `${systemPrompt}
@@ -7190,7 +7219,7 @@ ${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
7190
7219
  ${stderr}`;
7191
7220
  const issues = parseIssuesFromResponse(fullOutput);
7192
7221
  if (issues.length === 0) {
7193
- logger3.info("No actionable insights found");
7222
+ logger4.info("No actionable insights found");
7194
7223
  return { issuesCreated: 0, summary: "No actionable insights found" };
7195
7224
  }
7196
7225
  const boardProvider = createBoardProvider(config.boardProvider, projectDir);
@@ -7205,17 +7234,26 @@ ${stderr}`;
7205
7234
  labels: issue.labels ?? ["analytics"]
7206
7235
  });
7207
7236
  created++;
7208
- logger3.info("Created board issue", { title: issue.title, column: targetColumn });
7237
+ logger4.info("Created board issue", { title: issue.title, column: targetColumn });
7209
7238
  } catch (err) {
7210
- logger3.error("Failed to create board issue", {
7239
+ logger4.error("Failed to create board issue", {
7211
7240
  title: issue.title,
7212
7241
  error: String(err)
7213
7242
  });
7214
7243
  }
7215
7244
  }
7245
+ const failed = issues.length - created;
7246
+ let summary;
7247
+ if (created === issues.length) {
7248
+ summary = `Created ${created} issue(s) from analytics insights`;
7249
+ } else if (created === 0) {
7250
+ summary = `Found ${issues.length} actionable insight(s), but failed to create board issue(s)`;
7251
+ } else {
7252
+ summary = `Created ${created} of ${issues.length} issue(s) from analytics insights (${failed} failed)`;
7253
+ }
7216
7254
  return {
7217
7255
  issuesCreated: created,
7218
- summary: `Created ${created} issue(s) from analytics insights`
7256
+ summary
7219
7257
  };
7220
7258
  } finally {
7221
7259
  try {
@@ -7224,7 +7262,7 @@ ${stderr}`;
7224
7262
  }
7225
7263
  }
7226
7264
  }
7227
- var logger3;
7265
+ var logger4;
7228
7266
  var init_analytics_runner = __esm({
7229
7267
  "../core/dist/analytics/analytics-runner.js"() {
7230
7268
  "use strict";
@@ -7234,7 +7272,7 @@ var init_analytics_runner = __esm({
7234
7272
  init_shell();
7235
7273
  init_logger();
7236
7274
  init_amplitude_client();
7237
- logger3 = createLogger("analytics");
7275
+ logger4 = createLogger("analytics");
7238
7276
  }
7239
7277
  });
7240
7278
 
@@ -9026,6 +9064,30 @@ function parseAutoMergedPrNumbers(raw) {
9026
9064
  }
9027
9065
  return raw.split(",").map((token) => parseInt(token.trim().replace(/^#/, ""), 10)).filter((value) => !Number.isNaN(value));
9028
9066
  }
9067
+ function parseReviewedPrNumbers(raw) {
9068
+ if (!raw || raw.trim().length === 0) {
9069
+ return [];
9070
+ }
9071
+ const seen = /* @__PURE__ */ new Set();
9072
+ return raw.split(",").map((token) => parseInt(token.trim().replace(/^#/, ""), 10)).filter((value) => !Number.isNaN(value)).filter((value) => {
9073
+ if (seen.has(value)) {
9074
+ return false;
9075
+ }
9076
+ seen.add(value);
9077
+ return true;
9078
+ });
9079
+ }
9080
+ function buildReviewNotificationTargets(reviewedPrNumbers, noChangesPrNumbers, legacyNoChangesNeeded = false) {
9081
+ const uniqueReviewedPrNumbers = Array.from(new Set(reviewedPrNumbers));
9082
+ const noChangesSet = new Set(noChangesPrNumbers);
9083
+ if (legacyNoChangesNeeded && uniqueReviewedPrNumbers.length === 1) {
9084
+ noChangesSet.add(uniqueReviewedPrNumbers[0]);
9085
+ }
9086
+ return uniqueReviewedPrNumbers.map((prNumber) => ({
9087
+ prNumber,
9088
+ noChangesNeeded: noChangesSet.has(prNumber)
9089
+ }));
9090
+ }
9029
9091
  function parseRetryAttempts(raw) {
9030
9092
  if (!raw) {
9031
9093
  return 1;
@@ -9258,44 +9320,70 @@ ${stderr}`);
9258
9320
  if (skipNotification) {
9259
9321
  info("Skipping review notification (no actionable review result)");
9260
9322
  }
9261
- let prDetails = null;
9323
+ let fallbackPrDetails = null;
9262
9324
  if (!skipNotification && exitCode === 0) {
9263
- const prsRaw = scriptResult?.data.prs;
9264
- const firstPrToken = prsRaw?.split(",")[0]?.trim();
9265
- if (firstPrToken) {
9266
- const parsedNumber = parseInt(firstPrToken.replace(/^#/, ""), 10);
9267
- if (!Number.isNaN(parsedNumber)) {
9268
- prDetails = fetchPrDetailsByNumber(parsedNumber, projectDir);
9269
- }
9325
+ const reviewedPrNumbers = parseReviewedPrNumbers(scriptResult?.data.prs);
9326
+ const firstReviewedPrNumber = reviewedPrNumbers[0];
9327
+ if (firstReviewedPrNumber !== void 0) {
9328
+ fallbackPrDetails = fetchPrDetailsByNumber(firstReviewedPrNumber, projectDir);
9270
9329
  }
9271
- if (!prDetails) {
9272
- prDetails = fetchReviewedPrDetails(config.branchPatterns, projectDir);
9330
+ if (!fallbackPrDetails) {
9331
+ fallbackPrDetails = fetchReviewedPrDetails(config.branchPatterns, projectDir);
9273
9332
  }
9274
9333
  }
9275
9334
  if (!skipNotification) {
9276
9335
  const attempts = parseRetryAttempts(scriptResult?.data.attempts);
9277
9336
  const finalScore = parseFinalReviewScore(scriptResult?.data.final_score);
9278
- const noChangesNeeded = scriptResult?.data.no_changes_needed === "1";
9279
- if (noChangesNeeded && prDetails?.number) {
9280
- postReadyForHumanReviewComment(prDetails.number, finalScore, projectDir);
9337
+ const legacyNoChangesNeeded = scriptResult?.data.no_changes_needed === "1";
9338
+ const reviewedPrNumbers = parseReviewedPrNumbers(scriptResult?.data.prs);
9339
+ const noChangesPrNumbers = parseReviewedPrNumbers(scriptResult?.data.no_changes_prs);
9340
+ const fallbackPrNumber = fallbackPrDetails?.number;
9341
+ const notificationTargets = buildReviewNotificationTargets(
9342
+ reviewedPrNumbers.length > 0 ? reviewedPrNumbers : fallbackPrNumber !== void 0 ? [fallbackPrNumber] : [],
9343
+ noChangesPrNumbers,
9344
+ legacyNoChangesNeeded
9345
+ );
9346
+ if (notificationTargets.length === 0) {
9347
+ const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
9348
+ await sendNotifications(config, {
9349
+ event: reviewEvent,
9350
+ projectName: path22.basename(projectDir),
9351
+ exitCode,
9352
+ provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9353
+ prUrl: fallbackPrDetails?.url,
9354
+ prTitle: fallbackPrDetails?.title,
9355
+ prBody: fallbackPrDetails?.body,
9356
+ prNumber: fallbackPrDetails?.number,
9357
+ filesChanged: fallbackPrDetails?.changedFiles,
9358
+ additions: fallbackPrDetails?.additions,
9359
+ deletions: fallbackPrDetails?.deletions,
9360
+ attempts,
9361
+ finalScore
9362
+ });
9363
+ } else {
9364
+ for (const target of notificationTargets) {
9365
+ const prDetails = fallbackPrDetails?.number === target.prNumber ? fallbackPrDetails : fetchPrDetailsByNumber(target.prNumber, projectDir);
9366
+ if (target.noChangesNeeded && prDetails?.number) {
9367
+ postReadyForHumanReviewComment(prDetails.number, finalScore, projectDir);
9368
+ }
9369
+ const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
9370
+ await sendNotifications(config, {
9371
+ event: reviewEvent,
9372
+ projectName: path22.basename(projectDir),
9373
+ exitCode,
9374
+ provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9375
+ prUrl: prDetails?.url,
9376
+ prTitle: prDetails?.title,
9377
+ prBody: prDetails?.body,
9378
+ prNumber: prDetails?.number ?? target.prNumber,
9379
+ filesChanged: prDetails?.changedFiles,
9380
+ additions: prDetails?.additions,
9381
+ deletions: prDetails?.deletions,
9382
+ attempts,
9383
+ finalScore
9384
+ });
9385
+ }
9281
9386
  }
9282
- const reviewEvent = noChangesNeeded ? "review_ready_for_human" : "review_completed";
9283
- const _reviewCtx = {
9284
- event: reviewEvent,
9285
- projectName: path22.basename(projectDir),
9286
- exitCode,
9287
- provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
9288
- prUrl: prDetails?.url,
9289
- prTitle: prDetails?.title,
9290
- prBody: prDetails?.body,
9291
- prNumber: prDetails?.number,
9292
- filesChanged: prDetails?.changedFiles,
9293
- additions: prDetails?.additions,
9294
- deletions: prDetails?.deletions,
9295
- attempts,
9296
- finalScore
9297
- };
9298
- await sendNotifications(config, _reviewCtx);
9299
9387
  }
9300
9388
  const autoMergedPrNumbers = parseAutoMergedPrNumbers(scriptResult?.data.auto_merged);
9301
9389
  if (autoMergedPrNumbers.length > 0) {
@@ -9647,11 +9735,7 @@ function analyticsCommand(program2) {
9647
9735
  try {
9648
9736
  await maybeApplyCronSchedulingDelay(config, "analytics", projectDir);
9649
9737
  const result = await runAnalytics(config, projectDir);
9650
- if (result.issuesCreated > 0) {
9651
- spinner.succeed(`Analytics complete \u2014 ${result.summary}`);
9652
- } else {
9653
- spinner.succeed("Analytics complete \u2014 no actionable insights found");
9654
- }
9738
+ spinner.succeed(`Analytics complete \u2014 ${result.summary}`);
9655
9739
  } catch (err) {
9656
9740
  spinner.fail(`Analytics failed: ${err instanceof Error ? err.message : String(err)}`);
9657
9741
  process.exit(1);
@@ -16063,7 +16147,7 @@ function boardCommand(program2) {
16063
16147
  }
16064
16148
  })
16065
16149
  );
16066
- board.command("setup-labels").description("Create Night Watch priority, category, and horizon labels in the GitHub repo").option("--dry-run", "Show what labels would be created without creating them").action(
16150
+ board.command("setup-labels").description("Create Night Watch labels in the GitHub repo").option("--dry-run", "Show what labels would be created without creating them").action(
16067
16151
  async (options) => run(async () => {
16068
16152
  const cwd = process.cwd();
16069
16153
  header("Night Watch Labels");
@@ -16511,7 +16595,7 @@ import * as path41 from "path";
16511
16595
  import { spawn as spawn7 } from "child_process";
16512
16596
  import chalk7 from "chalk";
16513
16597
  import { Command as Command2 } from "commander";
16514
- var logger4 = createLogger("queue");
16598
+ var logger5 = createLogger("queue");
16515
16599
  var VALID_JOB_TYPES2 = ["executor", "reviewer", "qa", "audit", "slicer", "planner"];
16516
16600
  function formatTimestamp(unixTs) {
16517
16601
  if (unixTs === null) return "-";
@@ -16659,13 +16743,13 @@ function createQueueCommand() {
16659
16743
  queue.command("dispatch").description("Dispatch the next pending job (used by cron scripts)").option("--log <file>", "Log file to write dispatch output").action((_opts) => {
16660
16744
  const entry = dispatchNextJob(loadConfig(process.cwd()).queue);
16661
16745
  if (!entry) {
16662
- logger4.info("No pending jobs to dispatch");
16746
+ logger5.info("No pending jobs to dispatch");
16663
16747
  return;
16664
16748
  }
16665
- logger4.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
16749
+ logger5.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
16666
16750
  const scriptName = getScriptNameForJobType(entry.jobType);
16667
16751
  if (!scriptName) {
16668
- logger4.error(`Unknown job type: ${entry.jobType}`);
16752
+ logger5.error(`Unknown job type: ${entry.jobType}`);
16669
16753
  return;
16670
16754
  }
16671
16755
  let projectEnv;
@@ -16684,7 +16768,7 @@ function createQueueCommand() {
16684
16768
  NW_QUEUE_ENTRY_ID: String(entry.id)
16685
16769
  };
16686
16770
  const scriptPath = getScriptPath(scriptName);
16687
- logger4.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
16771
+ logger5.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
16688
16772
  try {
16689
16773
  const child = spawn7("bash", [scriptPath, entry.projectPath], {
16690
16774
  detached: true,
@@ -16693,11 +16777,11 @@ function createQueueCommand() {
16693
16777
  cwd: entry.projectPath
16694
16778
  });
16695
16779
  child.unref();
16696
- logger4.info(`Spawned PID: ${child.pid}`);
16780
+ logger5.info(`Spawned PID: ${child.pid}`);
16697
16781
  markJobRunning(entry.id);
16698
16782
  } catch (error2) {
16699
16783
  updateJobStatus(entry.id, "pending");
16700
- logger4.error(
16784
+ logger5.error(
16701
16785
  `Failed to dispatch ${entry.jobType} for ${entry.projectName}: ${error2 instanceof Error ? error2.message : String(error2)}`
16702
16786
  );
16703
16787
  process.exit(1);