@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 +316 -232
- package/dist/commands/analytics.d.ts.map +1 -1
- package/dist/commands/analytics.js +1 -6
- package/dist/commands/analytics.js.map +1 -1
- package/dist/commands/board.js +1 -1
- package/dist/commands/board.js.map +1 -1
- package/dist/commands/review.d.ts +8 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +78 -32
- package/dist/commands/review.js.map +1 -1
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +15 -2
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
3007
|
-
import * as
|
|
3143
|
+
import * as fs4 from "fs";
|
|
3144
|
+
import * as path4 from "path";
|
|
3008
3145
|
function tryReadJson(filePath) {
|
|
3009
|
-
if (!
|
|
3146
|
+
if (!fs4.existsSync(filePath)) {
|
|
3010
3147
|
return null;
|
|
3011
3148
|
}
|
|
3012
3149
|
try {
|
|
3013
|
-
const content =
|
|
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 (
|
|
3021
|
-
|
|
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 =
|
|
3164
|
+
const configPath = path4.join(projectPath, CONFIG_FILE_NAME);
|
|
3028
3165
|
let prdDir = "docs/prds";
|
|
3029
|
-
if (
|
|
3166
|
+
if (fs4.existsSync(configPath)) {
|
|
3030
3167
|
try {
|
|
3031
|
-
const config = JSON.parse(
|
|
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 =
|
|
3039
|
-
if (
|
|
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 =
|
|
3060
|
-
|
|
3061
|
-
const projectsJsonPath =
|
|
3062
|
-
const historyJsonPath =
|
|
3063
|
-
const prdStatesJsonPath =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6746
|
+
logger2.debug("Dispatch attempt", { mode, runningCount, maxConcurrency });
|
|
6718
6747
|
if (runningCount >= maxConcurrency) {
|
|
6719
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6775
|
+
logger2.debug("Dispatch skipped: no pending jobs");
|
|
6747
6776
|
return null;
|
|
6748
6777
|
}
|
|
6749
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6802
|
+
logger2.debug("Dispatch skipped: no pending jobs");
|
|
6774
6803
|
return null;
|
|
6775
6804
|
}
|
|
6776
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6903
|
+
logger2.warn("Expired stale jobs", { count: result.changes, maxWaitTime });
|
|
6875
6904
|
} else {
|
|
6876
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7237
|
+
logger4.info("Created board issue", { title: issue.title, column: targetColumn });
|
|
7209
7238
|
} catch (err) {
|
|
7210
|
-
|
|
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
|
|
7256
|
+
summary
|
|
7219
7257
|
};
|
|
7220
7258
|
} finally {
|
|
7221
7259
|
try {
|
|
@@ -7224,7 +7262,7 @@ ${stderr}`;
|
|
|
7224
7262
|
}
|
|
7225
7263
|
}
|
|
7226
7264
|
}
|
|
7227
|
-
var
|
|
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
|
-
|
|
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
|
|
9323
|
+
let fallbackPrDetails = null;
|
|
9262
9324
|
if (!skipNotification && exitCode === 0) {
|
|
9263
|
-
const
|
|
9264
|
-
const
|
|
9265
|
-
if (
|
|
9266
|
-
|
|
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 (!
|
|
9272
|
-
|
|
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
|
|
9279
|
-
|
|
9280
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
16746
|
+
logger5.info("No pending jobs to dispatch");
|
|
16663
16747
|
return;
|
|
16664
16748
|
}
|
|
16665
|
-
|
|
16749
|
+
logger5.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
|
|
16666
16750
|
const scriptName = getScriptNameForJobType(entry.jobType);
|
|
16667
16751
|
if (!scriptName) {
|
|
16668
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16780
|
+
logger5.info(`Spawned PID: ${child.pid}`);
|
|
16697
16781
|
markJobRunning(entry.id);
|
|
16698
16782
|
} catch (error2) {
|
|
16699
16783
|
updateJobStatus(entry.id, "pending");
|
|
16700
|
-
|
|
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);
|