@jonit-dev/night-watch-cli 1.8.8-beta.8 → 1.8.9
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 +1090 -524
- package/dist/cli.js.map +1 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +10 -2
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +25 -0
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/logs.d.ts +1 -1
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +25 -5
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/merge.d.ts +26 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/merge.js +159 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/review.d.ts +5 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +18 -18
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/slice.d.ts +1 -0
- package/dist/commands/slice.d.ts.map +1 -1
- package/dist/commands/slice.js +19 -19
- package/dist/commands/slice.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +54 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/scripts/night-watch-merger-cron.sh +321 -0
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +2 -137
- package/dist/templates/slicer.md +54 -64
- package/dist/web/assets/index-B1BnOpiO.css +1 -0
- package/dist/web/assets/index-CPQbZ1BL.css +1 -0
- package/dist/web/assets/index-CiRJZI4z.js +386 -0
- package/dist/web/assets/index-DcgNAi4A.js +386 -0
- package/dist/web/assets/index-SQlBKu_s.js +386 -0
- package/dist/web/assets/index-ZE5lOeJp.js +386 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,15 @@ var init_types = __esm({
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
// ../core/dist/board/types.js
|
|
21
|
+
var BOARD_COLUMNS;
|
|
22
|
+
var init_types2 = __esm({
|
|
23
|
+
"../core/dist/board/types.js"() {
|
|
24
|
+
"use strict";
|
|
25
|
+
BOARD_COLUMNS = ["Draft", "Ready", "In Progress", "Review", "Done"];
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
20
29
|
// ../core/dist/jobs/job-registry.js
|
|
21
30
|
function getJobDef(id) {
|
|
22
31
|
return JOB_MAP.get(id);
|
|
@@ -170,6 +179,7 @@ var JOB_REGISTRY, JOB_MAP;
|
|
|
170
179
|
var init_job_registry = __esm({
|
|
171
180
|
"../core/dist/jobs/job-registry.js"() {
|
|
172
181
|
"use strict";
|
|
182
|
+
init_types2();
|
|
173
183
|
JOB_REGISTRY = [
|
|
174
184
|
{
|
|
175
185
|
id: "executor",
|
|
@@ -286,10 +296,19 @@ var init_job_registry = __esm({
|
|
|
286
296
|
lockSuffix: "-audit.lock",
|
|
287
297
|
queuePriority: 10,
|
|
288
298
|
envPrefix: "NW_AUDIT",
|
|
299
|
+
extraFields: [
|
|
300
|
+
{
|
|
301
|
+
name: "targetColumn",
|
|
302
|
+
type: "enum",
|
|
303
|
+
enumValues: [...BOARD_COLUMNS],
|
|
304
|
+
defaultValue: "Draft"
|
|
305
|
+
}
|
|
306
|
+
],
|
|
289
307
|
defaultConfig: {
|
|
290
308
|
enabled: true,
|
|
291
309
|
schedule: "50 3 * * 1",
|
|
292
|
-
maxRuntime: 1800
|
|
310
|
+
maxRuntime: 1800,
|
|
311
|
+
targetColumn: "Draft"
|
|
293
312
|
}
|
|
294
313
|
},
|
|
295
314
|
{
|
|
@@ -306,7 +325,7 @@ var init_job_registry = __esm({
|
|
|
306
325
|
{
|
|
307
326
|
name: "targetColumn",
|
|
308
327
|
type: "enum",
|
|
309
|
-
enumValues: [
|
|
328
|
+
enumValues: [...BOARD_COLUMNS],
|
|
310
329
|
defaultValue: "Draft"
|
|
311
330
|
},
|
|
312
331
|
{ name: "analysisPrompt", type: "string", defaultValue: "" }
|
|
@@ -319,6 +338,38 @@ var init_job_registry = __esm({
|
|
|
319
338
|
targetColumn: "Draft",
|
|
320
339
|
analysisPrompt: ""
|
|
321
340
|
}
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: "merger",
|
|
344
|
+
name: "Merge Orchestrator",
|
|
345
|
+
description: "Repo-wide PR merge coordinator \u2014 scans, rebases, and merges in FIFO order",
|
|
346
|
+
cliCommand: "merge",
|
|
347
|
+
logName: "merger",
|
|
348
|
+
lockSuffix: "-merger.lock",
|
|
349
|
+
queuePriority: 45,
|
|
350
|
+
envPrefix: "NW_MERGER",
|
|
351
|
+
extraFields: [
|
|
352
|
+
{
|
|
353
|
+
name: "mergeMethod",
|
|
354
|
+
type: "enum",
|
|
355
|
+
enumValues: ["squash", "merge", "rebase"],
|
|
356
|
+
defaultValue: "squash"
|
|
357
|
+
},
|
|
358
|
+
{ name: "minReviewScore", type: "number", defaultValue: 80 },
|
|
359
|
+
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
360
|
+
{ name: "rebaseBeforeMerge", type: "boolean", defaultValue: true },
|
|
361
|
+
{ name: "maxPrsPerRun", type: "number", defaultValue: 0 }
|
|
362
|
+
],
|
|
363
|
+
defaultConfig: {
|
|
364
|
+
enabled: false,
|
|
365
|
+
schedule: "55 */4 * * *",
|
|
366
|
+
maxRuntime: 1800,
|
|
367
|
+
mergeMethod: "squash",
|
|
368
|
+
minReviewScore: 80,
|
|
369
|
+
branchPatterns: [],
|
|
370
|
+
rebaseBeforeMerge: true,
|
|
371
|
+
maxPrsPerRun: 0
|
|
372
|
+
}
|
|
322
373
|
}
|
|
323
374
|
];
|
|
324
375
|
JOB_MAP = new Map(JOB_REGISTRY.map((job) => [job.id, job]));
|
|
@@ -339,7 +390,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
|
|
|
339
390
|
return `claude-proxy:${baseUrl}`;
|
|
340
391
|
}
|
|
341
392
|
}
|
|
342
|
-
var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_SUMMARY_WINDOW_HOURS, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_REVIEWER_MAX_PRS_PER_RUN, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA_VALIDATED_LABEL, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, DEFAULT_PR_RESOLVER_ENABLED, DEFAULT_PR_RESOLVER_SCHEDULE, DEFAULT_PR_RESOLVER_MAX_RUNTIME, DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN, DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT, DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION, DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION, DEFAULT_PR_RESOLVER_READY_LABEL, DEFAULT_PR_RESOLVER, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, GLOBAL_NOTIFICATIONS_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY;
|
|
393
|
+
var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_SUMMARY_WINDOW_HOURS, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_REVIEWER_MAX_PRS_PER_RUN, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA_VALIDATED_LABEL, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT_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, MERGER_LOG_NAME, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, GLOBAL_NOTIFICATIONS_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY;
|
|
343
394
|
var init_constants = __esm({
|
|
344
395
|
"../core/dist/constants.js"() {
|
|
345
396
|
"use strict";
|
|
@@ -384,7 +435,7 @@ var init_constants = __esm({
|
|
|
384
435
|
slicerSchedule: DEFAULT_SLICER_SCHEDULE,
|
|
385
436
|
slicerMaxRuntime: DEFAULT_SLICER_MAX_RUNTIME,
|
|
386
437
|
priorityMode: "roadmap-first",
|
|
387
|
-
issueColumn: "
|
|
438
|
+
issueColumn: "Ready"
|
|
388
439
|
};
|
|
389
440
|
DEFAULT_TEMPLATES_DIR = ".night-watch/templates";
|
|
390
441
|
DEFAULT_BOARD_PROVIDER = {
|
|
@@ -416,10 +467,12 @@ var init_constants = __esm({
|
|
|
416
467
|
DEFAULT_AUDIT_ENABLED = true;
|
|
417
468
|
DEFAULT_AUDIT_SCHEDULE = "50 3 * * 1";
|
|
418
469
|
DEFAULT_AUDIT_MAX_RUNTIME = 1800;
|
|
470
|
+
DEFAULT_AUDIT_TARGET_COLUMN = "Draft";
|
|
419
471
|
DEFAULT_AUDIT = {
|
|
420
472
|
enabled: DEFAULT_AUDIT_ENABLED,
|
|
421
473
|
schedule: DEFAULT_AUDIT_SCHEDULE,
|
|
422
|
-
maxRuntime: DEFAULT_AUDIT_MAX_RUNTIME
|
|
474
|
+
maxRuntime: DEFAULT_AUDIT_MAX_RUNTIME,
|
|
475
|
+
targetColumn: DEFAULT_AUDIT_TARGET_COLUMN
|
|
423
476
|
};
|
|
424
477
|
DEFAULT_ANALYTICS_ENABLED = false;
|
|
425
478
|
DEFAULT_ANALYTICS_SCHEDULE = "0 6 * * 1";
|
|
@@ -458,6 +511,24 @@ If no issues are warranted, output an empty array: []`;
|
|
|
458
511
|
aiReviewResolution: DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION,
|
|
459
512
|
readyLabel: DEFAULT_PR_RESOLVER_READY_LABEL
|
|
460
513
|
};
|
|
514
|
+
DEFAULT_MERGER_ENABLED = false;
|
|
515
|
+
DEFAULT_MERGER_SCHEDULE = "55 */4 * * *";
|
|
516
|
+
DEFAULT_MERGER_MAX_RUNTIME = 1800;
|
|
517
|
+
DEFAULT_MERGER_MERGE_METHOD = "squash";
|
|
518
|
+
DEFAULT_MERGER_MIN_REVIEW_SCORE = 80;
|
|
519
|
+
DEFAULT_MERGER_REBASE_BEFORE_MERGE = true;
|
|
520
|
+
DEFAULT_MERGER_MAX_PRS_PER_RUN = 0;
|
|
521
|
+
DEFAULT_MERGER = {
|
|
522
|
+
enabled: DEFAULT_MERGER_ENABLED,
|
|
523
|
+
schedule: DEFAULT_MERGER_SCHEDULE,
|
|
524
|
+
maxRuntime: DEFAULT_MERGER_MAX_RUNTIME,
|
|
525
|
+
mergeMethod: DEFAULT_MERGER_MERGE_METHOD,
|
|
526
|
+
minReviewScore: DEFAULT_MERGER_MIN_REVIEW_SCORE,
|
|
527
|
+
branchPatterns: [],
|
|
528
|
+
rebaseBeforeMerge: DEFAULT_MERGER_REBASE_BEFORE_MERGE,
|
|
529
|
+
maxPrsPerRun: DEFAULT_MERGER_MAX_PRS_PER_RUN
|
|
530
|
+
};
|
|
531
|
+
MERGER_LOG_NAME = "merger";
|
|
461
532
|
AUDIT_LOG_NAME = "audit";
|
|
462
533
|
PLANNER_LOG_NAME = "slicer";
|
|
463
534
|
ANALYTICS_LOG_NAME = "analytics";
|
|
@@ -734,7 +805,7 @@ function normalizeConfig(rawConfig) {
|
|
|
734
805
|
if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
|
|
735
806
|
normalized.autoMergeMethod = mergeMethod;
|
|
736
807
|
}
|
|
737
|
-
for (const jobId of ["qa", "audit", "analytics"]) {
|
|
808
|
+
for (const jobId of ["qa", "audit", "analytics", "merger"]) {
|
|
738
809
|
const jobDef = getJobDef(jobId);
|
|
739
810
|
if (!jobDef)
|
|
740
811
|
continue;
|
|
@@ -1068,6 +1139,17 @@ function buildEnvOverrideConfig(fileConfig) {
|
|
|
1068
1139
|
env.prResolver = overrides;
|
|
1069
1140
|
}
|
|
1070
1141
|
}
|
|
1142
|
+
const mergerDef = getJobDef("merger");
|
|
1143
|
+
if (mergerDef) {
|
|
1144
|
+
const currentBase = (
|
|
1145
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1146
|
+
env.merger ?? fileConfig?.merger ?? mergerDef.defaultConfig
|
|
1147
|
+
);
|
|
1148
|
+
const overrides = buildJobEnvOverrides(mergerDef.envPrefix, currentBase, mergerDef.extraFields);
|
|
1149
|
+
if (overrides) {
|
|
1150
|
+
env.merger = overrides;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1071
1153
|
const jobProvidersEnv = {};
|
|
1072
1154
|
for (const jobType of VALID_JOB_TYPES) {
|
|
1073
1155
|
const val = process.env[`NW_JOB_PROVIDER_${jobType.toUpperCase()}`];
|
|
@@ -1163,6 +1245,7 @@ function getDefaultConfig() {
|
|
|
1163
1245
|
audit: { ...DEFAULT_AUDIT },
|
|
1164
1246
|
analytics: { ...DEFAULT_ANALYTICS },
|
|
1165
1247
|
prResolver: { ...DEFAULT_PR_RESOLVER },
|
|
1248
|
+
merger: { ...DEFAULT_MERGER },
|
|
1166
1249
|
jobProviders: { ...DEFAULT_JOB_PROVIDERS },
|
|
1167
1250
|
providerScheduleOverrides: [...DEFAULT_PROVIDER_SCHEDULE_OVERRIDES],
|
|
1168
1251
|
queue: { ...DEFAULT_QUEUE }
|
|
@@ -1230,7 +1313,7 @@ function mergeConfigLayer(base, layer) {
|
|
|
1230
1313
|
...layerQueue,
|
|
1231
1314
|
providerBuckets: { ...baseQueue.providerBuckets, ...layerQueue.providerBuckets }
|
|
1232
1315
|
};
|
|
1233
|
-
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver") {
|
|
1316
|
+
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver" || _key === "merger") {
|
|
1234
1317
|
base[_key] = {
|
|
1235
1318
|
...base[_key],
|
|
1236
1319
|
...value
|
|
@@ -1253,6 +1336,13 @@ function mergeConfigs(base, fileConfig, envConfig) {
|
|
|
1253
1336
|
if (fileConfig)
|
|
1254
1337
|
mergeConfigLayer(merged, fileConfig);
|
|
1255
1338
|
mergeConfigLayer(merged, envConfig);
|
|
1339
|
+
if (merged.autoMerge === true && !fileConfig?.merger) {
|
|
1340
|
+
merged.merger = {
|
|
1341
|
+
...merged.merger,
|
|
1342
|
+
enabled: true,
|
|
1343
|
+
mergeMethod: merged.autoMergeMethod ?? "squash"
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1256
1346
|
merged.maxRetries = sanitizeMaxRetries(merged.maxRetries, DEFAULT_MAX_RETRIES);
|
|
1257
1347
|
merged.reviewerMaxRetries = sanitizeReviewerMaxRetries(merged.reviewerMaxRetries, DEFAULT_REVIEWER_MAX_RETRIES);
|
|
1258
1348
|
merged.reviewerRetryDelay = sanitizeReviewerRetryDelay(merged.reviewerRetryDelay, DEFAULT_REVIEWER_RETRY_DELAY);
|
|
@@ -1377,15 +1467,6 @@ var init_config = __esm({
|
|
|
1377
1467
|
}
|
|
1378
1468
|
});
|
|
1379
1469
|
|
|
1380
|
-
// ../core/dist/board/types.js
|
|
1381
|
-
var BOARD_COLUMNS;
|
|
1382
|
-
var init_types2 = __esm({
|
|
1383
|
-
"../core/dist/board/types.js"() {
|
|
1384
|
-
"use strict";
|
|
1385
|
-
BOARD_COLUMNS = ["Draft", "Ready", "In Progress", "Review", "Done"];
|
|
1386
|
-
}
|
|
1387
|
-
});
|
|
1388
|
-
|
|
1389
1470
|
// ../core/dist/storage/repositories/sqlite/execution-history.repository.js
|
|
1390
1471
|
import Database from "better-sqlite3";
|
|
1391
1472
|
import { inject, injectable } from "tsyringe";
|
|
@@ -3590,6 +3671,9 @@ function analyticsLockPath(projectDir) {
|
|
|
3590
3671
|
function prResolverLockPath(projectDir) {
|
|
3591
3672
|
return `${LOCK_FILE_PREFIX}pr-resolver-${projectRuntimeKey(projectDir)}.lock`;
|
|
3592
3673
|
}
|
|
3674
|
+
function mergerLockPath(projectDir) {
|
|
3675
|
+
return `${LOCK_FILE_PREFIX}merger-${projectRuntimeKey(projectDir)}.lock`;
|
|
3676
|
+
}
|
|
3593
3677
|
function isProcessRunning(pid) {
|
|
3594
3678
|
try {
|
|
3595
3679
|
process.kill(pid, 0);
|
|
@@ -3950,7 +4034,8 @@ function collectLogInfo(projectDir) {
|
|
|
3950
4034
|
{ name: "qa", fileName: `${QA_LOG_NAME}.log` },
|
|
3951
4035
|
{ name: "audit", fileName: `${AUDIT_LOG_NAME}.log` },
|
|
3952
4036
|
{ name: "planner", fileName: `${PLANNER_LOG_NAME}.log` },
|
|
3953
|
-
{ name: "analytics", fileName: `${ANALYTICS_LOG_NAME}.log` }
|
|
4037
|
+
{ name: "analytics", fileName: `${ANALYTICS_LOG_NAME}.log` },
|
|
4038
|
+
{ name: "merger", fileName: `${MERGER_LOG_NAME}.log` }
|
|
3954
4039
|
];
|
|
3955
4040
|
return logEntries.map(({ name, fileName }) => {
|
|
3956
4041
|
const logPath = path6.join(projectDir, LOG_DIR, fileName);
|
|
@@ -3980,13 +4065,15 @@ async function fetchStatusSnapshot(projectDir, config) {
|
|
|
3980
4065
|
const auditLock = checkLockFile(auditLockPath(projectDir));
|
|
3981
4066
|
const plannerLock = checkLockFile(plannerLockPath(projectDir));
|
|
3982
4067
|
const analyticsLock = checkLockFile(analyticsLockPath(projectDir));
|
|
4068
|
+
const mergerLock = checkLockFile(mergerLockPath(projectDir));
|
|
3983
4069
|
const processes = [
|
|
3984
4070
|
{ name: "executor", running: executorLock.running, pid: executorLock.pid },
|
|
3985
4071
|
{ name: "reviewer", running: reviewerLock.running, pid: reviewerLock.pid },
|
|
3986
4072
|
{ name: "qa", running: qaLock.running, pid: qaLock.pid },
|
|
3987
4073
|
{ name: "audit", running: auditLock.running, pid: auditLock.pid },
|
|
3988
4074
|
{ name: "planner", running: plannerLock.running, pid: plannerLock.pid },
|
|
3989
|
-
{ name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid }
|
|
4075
|
+
{ name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid },
|
|
4076
|
+
{ name: "merger", running: mergerLock.running, pid: mergerLock.pid }
|
|
3990
4077
|
];
|
|
3991
4078
|
const prds = collectPrdInfo(projectDir, config.prdDir, config.maxRuntime);
|
|
3992
4079
|
const prs = await collectPrInfo(projectDir, config.branchPatterns);
|
|
@@ -4900,6 +4987,10 @@ function getEventEmoji(event) {
|
|
|
4900
4987
|
return "\u2705";
|
|
4901
4988
|
case "pr_resolver_failed":
|
|
4902
4989
|
return "\u274C";
|
|
4990
|
+
case "merge_completed":
|
|
4991
|
+
return "\u{1F500}";
|
|
4992
|
+
case "merge_failed":
|
|
4993
|
+
return "\u274C";
|
|
4903
4994
|
}
|
|
4904
4995
|
}
|
|
4905
4996
|
function getEventTitle(event) {
|
|
@@ -4930,6 +5021,10 @@ function getEventTitle(event) {
|
|
|
4930
5021
|
return "PR Conflict Resolved";
|
|
4931
5022
|
case "pr_resolver_failed":
|
|
4932
5023
|
return "PR Resolver Failed";
|
|
5024
|
+
case "merge_completed":
|
|
5025
|
+
return "PR Merged";
|
|
5026
|
+
case "merge_failed":
|
|
5027
|
+
return "Merge Failed";
|
|
4933
5028
|
}
|
|
4934
5029
|
}
|
|
4935
5030
|
function getEventColor(event) {
|
|
@@ -4960,6 +5055,10 @@ function getEventColor(event) {
|
|
|
4960
5055
|
return 65280;
|
|
4961
5056
|
case "pr_resolver_failed":
|
|
4962
5057
|
return 16711680;
|
|
5058
|
+
case "merge_completed":
|
|
5059
|
+
return 10181046;
|
|
5060
|
+
case "merge_failed":
|
|
5061
|
+
return 16711680;
|
|
4963
5062
|
}
|
|
4964
5063
|
}
|
|
4965
5064
|
function buildDescription(ctx) {
|
|
@@ -5562,19 +5661,96 @@ var init_roadmap_parser = __esm({
|
|
|
5562
5661
|
}
|
|
5563
5662
|
});
|
|
5564
5663
|
|
|
5565
|
-
// ../core/dist/
|
|
5664
|
+
// ../core/dist/audit/report.js
|
|
5566
5665
|
import * as fs14 from "fs";
|
|
5567
5666
|
import * as path13 from "path";
|
|
5667
|
+
function normalizeAuditSeverity(raw) {
|
|
5668
|
+
const normalized = raw.trim().toLowerCase();
|
|
5669
|
+
if (normalized === "critical")
|
|
5670
|
+
return "critical";
|
|
5671
|
+
if (normalized === "high")
|
|
5672
|
+
return "high";
|
|
5673
|
+
if (normalized === "low")
|
|
5674
|
+
return "low";
|
|
5675
|
+
return "medium";
|
|
5676
|
+
}
|
|
5677
|
+
function extractAuditField(block, field) {
|
|
5678
|
+
const pattern = new RegExp(`- \\*\\*${field}\\*\\*:\\s*([\\s\\S]*?)(?=\\n- \\*\\*|\\n###\\s+Finding\\s+\\d+|$)`, "i");
|
|
5679
|
+
const match = block.match(pattern);
|
|
5680
|
+
if (!match)
|
|
5681
|
+
return "";
|
|
5682
|
+
return match[1].replace(/`/g, "").replace(/\r/g, "").trim();
|
|
5683
|
+
}
|
|
5684
|
+
function parseAuditFindings(reportContent) {
|
|
5685
|
+
const headingRegex = /^###\s+Finding\s+(\d+)\s*$/gm;
|
|
5686
|
+
const headings = [];
|
|
5687
|
+
let match;
|
|
5688
|
+
while ((match = headingRegex.exec(reportContent)) !== null) {
|
|
5689
|
+
const number = parseInt(match[1], 10);
|
|
5690
|
+
if (!Number.isNaN(number)) {
|
|
5691
|
+
headings.push({
|
|
5692
|
+
number,
|
|
5693
|
+
bodyStart: headingRegex.lastIndex,
|
|
5694
|
+
headingStart: match.index
|
|
5695
|
+
});
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5698
|
+
if (headings.length === 0) {
|
|
5699
|
+
return [];
|
|
5700
|
+
}
|
|
5701
|
+
const findings = [];
|
|
5702
|
+
for (let i = 0; i < headings.length; i++) {
|
|
5703
|
+
const current = headings[i];
|
|
5704
|
+
const next = headings[i + 1];
|
|
5705
|
+
const block = reportContent.slice(current.bodyStart, next?.headingStart ?? reportContent.length);
|
|
5706
|
+
const severityRaw = extractAuditField(block, "Severity");
|
|
5707
|
+
const category = extractAuditField(block, "Category") || "uncategorized";
|
|
5708
|
+
const location = extractAuditField(block, "Location") || "unknown location";
|
|
5709
|
+
const description = extractAuditField(block, "Description") || "No description provided";
|
|
5710
|
+
const snippet = extractAuditField(block, "Snippet");
|
|
5711
|
+
const suggestedFix = extractAuditField(block, "Suggested Fix") || "No suggested fix provided";
|
|
5712
|
+
findings.push({
|
|
5713
|
+
number: current.number,
|
|
5714
|
+
severity: normalizeAuditSeverity(severityRaw),
|
|
5715
|
+
category,
|
|
5716
|
+
location,
|
|
5717
|
+
description,
|
|
5718
|
+
snippet,
|
|
5719
|
+
suggestedFix
|
|
5720
|
+
});
|
|
5721
|
+
}
|
|
5722
|
+
return findings;
|
|
5723
|
+
}
|
|
5724
|
+
function loadAuditFindings(projectDir) {
|
|
5725
|
+
const reportPath = path13.join(projectDir, "logs", "audit-report.md");
|
|
5726
|
+
if (!fs14.existsSync(reportPath)) {
|
|
5727
|
+
return [];
|
|
5728
|
+
}
|
|
5729
|
+
const reportContent = fs14.readFileSync(reportPath, "utf-8");
|
|
5730
|
+
if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
|
|
5731
|
+
return [];
|
|
5732
|
+
}
|
|
5733
|
+
return parseAuditFindings(reportContent);
|
|
5734
|
+
}
|
|
5735
|
+
var init_report = __esm({
|
|
5736
|
+
"../core/dist/audit/report.js"() {
|
|
5737
|
+
"use strict";
|
|
5738
|
+
}
|
|
5739
|
+
});
|
|
5740
|
+
|
|
5741
|
+
// ../core/dist/utils/roadmap-state.js
|
|
5742
|
+
import * as fs15 from "fs";
|
|
5743
|
+
import * as path14 from "path";
|
|
5568
5744
|
function getStateFilePath(prdDir) {
|
|
5569
|
-
return
|
|
5745
|
+
return path14.join(prdDir, STATE_FILE_NAME);
|
|
5570
5746
|
}
|
|
5571
5747
|
function readJsonState(prdDir) {
|
|
5572
5748
|
const statePath = getStateFilePath(prdDir);
|
|
5573
|
-
if (!
|
|
5749
|
+
if (!fs15.existsSync(statePath)) {
|
|
5574
5750
|
return null;
|
|
5575
5751
|
}
|
|
5576
5752
|
try {
|
|
5577
|
-
const content =
|
|
5753
|
+
const content = fs15.readFileSync(statePath, "utf-8");
|
|
5578
5754
|
const parsed = JSON.parse(content);
|
|
5579
5755
|
if (typeof parsed !== "object" || parsed === null) {
|
|
5580
5756
|
return null;
|
|
@@ -5612,11 +5788,11 @@ function saveRoadmapState(prdDir, state) {
|
|
|
5612
5788
|
const { roadmapState } = getRepositories();
|
|
5613
5789
|
roadmapState.save(prdDir, state);
|
|
5614
5790
|
const statePath = getStateFilePath(prdDir);
|
|
5615
|
-
const dir =
|
|
5616
|
-
if (!
|
|
5617
|
-
|
|
5791
|
+
const dir = path14.dirname(statePath);
|
|
5792
|
+
if (!fs15.existsSync(dir)) {
|
|
5793
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
5618
5794
|
}
|
|
5619
|
-
|
|
5795
|
+
fs15.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
5620
5796
|
}
|
|
5621
5797
|
function createEmptyState() {
|
|
5622
5798
|
return {
|
|
@@ -5656,16 +5832,16 @@ var init_roadmap_state = __esm({
|
|
|
5656
5832
|
});
|
|
5657
5833
|
|
|
5658
5834
|
// ../core/dist/templates/slicer-prompt.js
|
|
5659
|
-
import * as
|
|
5660
|
-
import * as
|
|
5835
|
+
import * as fs16 from "fs";
|
|
5836
|
+
import * as path15 from "path";
|
|
5661
5837
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5662
5838
|
function loadSlicerTemplate(templateDir) {
|
|
5663
5839
|
if (cachedTemplate) {
|
|
5664
5840
|
return cachedTemplate;
|
|
5665
5841
|
}
|
|
5666
|
-
const templatePath = templateDir ?
|
|
5842
|
+
const templatePath = templateDir ? path15.join(templateDir, "slicer.md") : path15.resolve(__dirname, "..", "..", "templates", "slicer.md");
|
|
5667
5843
|
try {
|
|
5668
|
-
cachedTemplate =
|
|
5844
|
+
cachedTemplate = fs16.readFileSync(templatePath, "utf-8");
|
|
5669
5845
|
return cachedTemplate;
|
|
5670
5846
|
} catch (error2) {
|
|
5671
5847
|
console.warn(`Warning: Could not load slicer template from ${templatePath}, using default:`, error2 instanceof Error ? error2.message : String(error2));
|
|
@@ -5690,7 +5866,7 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
|
|
|
5690
5866
|
title,
|
|
5691
5867
|
section,
|
|
5692
5868
|
description: description || "(No description provided)",
|
|
5693
|
-
outputFilePath:
|
|
5869
|
+
outputFilePath: path15.join(prdDir, prdFilename),
|
|
5694
5870
|
prdDir
|
|
5695
5871
|
};
|
|
5696
5872
|
}
|
|
@@ -5699,10 +5875,10 @@ var init_slicer_prompt = __esm({
|
|
|
5699
5875
|
"../core/dist/templates/slicer-prompt.js"() {
|
|
5700
5876
|
"use strict";
|
|
5701
5877
|
__filename = fileURLToPath2(import.meta.url);
|
|
5702
|
-
__dirname =
|
|
5703
|
-
DEFAULT_SLICER_TEMPLATE = `You are a **
|
|
5878
|
+
__dirname = path15.dirname(__filename);
|
|
5879
|
+
DEFAULT_SLICER_TEMPLATE = `You are a **Principal Software Architect**. Your job: analyze the codebase and write a complete Product Requirements Document (PRD) for a feature. The PRD will be used directly as a GitHub issue body, so it must be self-contained and immediately actionable by an engineer.
|
|
5704
5880
|
|
|
5705
|
-
When this activates: \`
|
|
5881
|
+
When this activates: \`Planning Mode: Principal Architect\`
|
|
5706
5882
|
|
|
5707
5883
|
---
|
|
5708
5884
|
|
|
@@ -5723,22 +5899,16 @@ The PRD directory is: \`{{PRD_DIR}}\`
|
|
|
5723
5899
|
|
|
5724
5900
|
## Your Task
|
|
5725
5901
|
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
2. **Assess Complexity** - Score the complexity using the rubric and determine whether this is LOW, MEDIUM, or HIGH complexity.
|
|
5731
|
-
|
|
5732
|
-
3. **Write a Complete PRD** - Create a full PRD following the prd-creator template structure with Context, Solution, Phases, Tests, and Acceptance Criteria.
|
|
5733
|
-
|
|
5734
|
-
4. **Write the PRD File** - Use the Write tool to create the PRD file at the exact path specified in \`{{OUTPUT_FILE_PATH}}\`.
|
|
5902
|
+
1. **Explore the Codebase** \u2014 Read relevant existing files to understand structure, patterns, and conventions.
|
|
5903
|
+
2. **Assess Complexity** \u2014 Score using the rubric below and determine LOW / MEDIUM / HIGH.
|
|
5904
|
+
3. **Write a Complete PRD** \u2014 Follow the exact template structure below. Every section must be filled with concrete information.
|
|
5905
|
+
4. **Write the PRD File** \u2014 Use the Write tool to create the PRD file at \`{{OUTPUT_FILE_PATH}}\`.
|
|
5735
5906
|
|
|
5736
5907
|
---
|
|
5737
5908
|
|
|
5738
5909
|
## Complexity Scoring
|
|
5739
5910
|
|
|
5740
5911
|
\`\`\`
|
|
5741
|
-
COMPLEXITY SCORE (sum all that apply):
|
|
5742
5912
|
+1 Touches 1-5 files
|
|
5743
5913
|
+2 Touches 6-10 files
|
|
5744
5914
|
+3 Touches 10+ files
|
|
@@ -5750,7 +5920,7 @@ COMPLEXITY SCORE (sum all that apply):
|
|
|
5750
5920
|
|
|
5751
5921
|
| Score | Level | Template Mode |
|
|
5752
5922
|
| ----- | ------ | ----------------------------------------------- |
|
|
5753
|
-
| 1-3 | LOW | Minimal (skip sections marked
|
|
5923
|
+
| 1-3 | LOW | Minimal (skip sections marked MEDIUM/HIGH) |
|
|
5754
5924
|
| 4-6 | MEDIUM | Standard (all sections) |
|
|
5755
5925
|
| 7+ | HIGH | Full + mandatory checkpoints every phase |
|
|
5756
5926
|
\`\`\`
|
|
@@ -5759,25 +5929,77 @@ COMPLEXITY SCORE (sum all that apply):
|
|
|
5759
5929
|
|
|
5760
5930
|
## PRD Template Structure
|
|
5761
5931
|
|
|
5762
|
-
Your PRD MUST
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5932
|
+
Your PRD MUST use this structure. Replace every [bracketed placeholder] with real content.
|
|
5933
|
+
|
|
5934
|
+
# PRD: [Title]
|
|
5935
|
+
|
|
5936
|
+
**Complexity: [SCORE] \u2192 [LEVEL] mode**
|
|
5937
|
+
|
|
5938
|
+
## 1. Context
|
|
5939
|
+
|
|
5940
|
+
**Problem:** [1-2 sentences]
|
|
5941
|
+
|
|
5942
|
+
**Files Analyzed:**
|
|
5943
|
+
- \`path/to/file.ts\` \u2014 [what you found]
|
|
5944
|
+
|
|
5945
|
+
**Current Behavior:**
|
|
5946
|
+
- [3-5 bullets]
|
|
5947
|
+
|
|
5948
|
+
### Integration Points
|
|
5949
|
+
- Entry point: [cron / CLI / event / route]
|
|
5950
|
+
- Caller file: [file invoking new code]
|
|
5951
|
+
- User flow: User does X \u2192 triggers Y \u2192 result Z
|
|
5952
|
+
|
|
5953
|
+
## 2. Solution
|
|
5954
|
+
|
|
5955
|
+
**Approach:**
|
|
5956
|
+
- [3-5 bullets]
|
|
5957
|
+
|
|
5958
|
+
**Key Decisions:** [library choices, error handling, reused utilities]
|
|
5959
|
+
|
|
5960
|
+
**Data Changes:** [schema changes, or "None"]
|
|
5961
|
+
|
|
5962
|
+
## 3. Sequence Flow (MEDIUM/HIGH only)
|
|
5963
|
+
|
|
5964
|
+
[mermaid sequenceDiagram]
|
|
5965
|
+
|
|
5966
|
+
## 4. Execution Phases
|
|
5967
|
+
|
|
5968
|
+
### Phase N: [Name] \u2014 [User-visible outcome]
|
|
5969
|
+
|
|
5970
|
+
**Files (max 5):**
|
|
5971
|
+
- \`src/path/file.ts\` \u2014 [what changes]
|
|
5972
|
+
|
|
5973
|
+
**Implementation:**
|
|
5974
|
+
- [ ] Step 1
|
|
5975
|
+
|
|
5976
|
+
**Tests Required:**
|
|
5977
|
+
| Test File | Test Name | Assertion |
|
|
5978
|
+
|-----------|-----------|-----------|
|
|
5979
|
+
| \`src/__tests__/feature.test.ts\` | \`should X when Y\` | \`expect(r).toBe(Z)\` |
|
|
5980
|
+
|
|
5981
|
+
**Checkpoint:** Run \`yarn verify\` and related tests after this phase.
|
|
5982
|
+
|
|
5983
|
+
## 5. Acceptance Criteria
|
|
5984
|
+
|
|
5985
|
+
- [ ] All phases complete
|
|
5986
|
+
- [ ] All tests pass
|
|
5987
|
+
- [ ] \`yarn verify\` passes
|
|
5988
|
+
- [ ] Feature is reachable (not orphaned code)
|
|
5768
5989
|
|
|
5769
5990
|
---
|
|
5770
5991
|
|
|
5771
5992
|
## Critical Instructions
|
|
5772
5993
|
|
|
5773
|
-
1.
|
|
5774
|
-
2.
|
|
5775
|
-
3.
|
|
5776
|
-
4.
|
|
5777
|
-
5.
|
|
5778
|
-
6.
|
|
5994
|
+
1. Read all relevant files BEFORE writing the PRD
|
|
5995
|
+
2. Follow existing patterns \u2014 use \`@/*\` path aliases, match naming conventions
|
|
5996
|
+
3. Include concrete file paths and implementation steps
|
|
5997
|
+
4. Include specific test names and assertions
|
|
5998
|
+
5. Use the Write tool to create the file at \`{{OUTPUT_FILE_PATH}}\`
|
|
5999
|
+
6. No placeholder text in the final PRD
|
|
6000
|
+
7. The PRD is the GitHub issue body \u2014 make it self-contained
|
|
5779
6001
|
|
|
5780
|
-
DO NOT leave placeholder text
|
|
6002
|
+
DO NOT leave [bracketed placeholder] text in the output.
|
|
5781
6003
|
DO NOT skip any sections.
|
|
5782
6004
|
DO NOT forget to write the file.
|
|
5783
6005
|
`;
|
|
@@ -5786,65 +6008,10 @@ DO NOT forget to write the file.
|
|
|
5786
6008
|
});
|
|
5787
6009
|
|
|
5788
6010
|
// ../core/dist/utils/roadmap-scanner.js
|
|
5789
|
-
import * as
|
|
5790
|
-
import * as
|
|
6011
|
+
import * as fs17 from "fs";
|
|
6012
|
+
import * as path16 from "path";
|
|
5791
6013
|
import { spawn } from "child_process";
|
|
5792
6014
|
import { createHash as createHash3 } from "crypto";
|
|
5793
|
-
function normalizeAuditSeverity(raw) {
|
|
5794
|
-
const normalized = raw.trim().toLowerCase();
|
|
5795
|
-
if (normalized === "critical")
|
|
5796
|
-
return "critical";
|
|
5797
|
-
if (normalized === "high")
|
|
5798
|
-
return "high";
|
|
5799
|
-
if (normalized === "low")
|
|
5800
|
-
return "low";
|
|
5801
|
-
return "medium";
|
|
5802
|
-
}
|
|
5803
|
-
function extractAuditField(block, field) {
|
|
5804
|
-
const pattern = new RegExp(`- \\*\\*${field}\\*\\*:\\s*([\\s\\S]*?)(?=\\n- \\*\\*|\\n###\\s+Finding\\s+\\d+|$)`, "i");
|
|
5805
|
-
const match = block.match(pattern);
|
|
5806
|
-
if (!match)
|
|
5807
|
-
return "";
|
|
5808
|
-
return match[1].replace(/`/g, "").replace(/\r/g, "").trim();
|
|
5809
|
-
}
|
|
5810
|
-
function parseAuditFindings(reportContent) {
|
|
5811
|
-
const headingRegex = /^###\s+Finding\s+(\d+)\s*$/gm;
|
|
5812
|
-
const headings = [];
|
|
5813
|
-
let match;
|
|
5814
|
-
while ((match = headingRegex.exec(reportContent)) !== null) {
|
|
5815
|
-
const number = parseInt(match[1], 10);
|
|
5816
|
-
if (!Number.isNaN(number)) {
|
|
5817
|
-
headings.push({
|
|
5818
|
-
number,
|
|
5819
|
-
bodyStart: headingRegex.lastIndex,
|
|
5820
|
-
headingStart: match.index
|
|
5821
|
-
});
|
|
5822
|
-
}
|
|
5823
|
-
}
|
|
5824
|
-
if (headings.length === 0) {
|
|
5825
|
-
return [];
|
|
5826
|
-
}
|
|
5827
|
-
const findings = [];
|
|
5828
|
-
for (let i = 0; i < headings.length; i++) {
|
|
5829
|
-
const current = headings[i];
|
|
5830
|
-
const next = headings[i + 1];
|
|
5831
|
-
const block = reportContent.slice(current.bodyStart, next?.headingStart ?? reportContent.length);
|
|
5832
|
-
const severityRaw = extractAuditField(block, "Severity");
|
|
5833
|
-
const category = extractAuditField(block, "Category") || "uncategorized";
|
|
5834
|
-
const location = extractAuditField(block, "Location") || "unknown location";
|
|
5835
|
-
const description = extractAuditField(block, "Description") || "No description provided";
|
|
5836
|
-
const suggestedFix = extractAuditField(block, "Suggested Fix") || "No suggested fix provided";
|
|
5837
|
-
findings.push({
|
|
5838
|
-
number: current.number,
|
|
5839
|
-
severity: normalizeAuditSeverity(severityRaw),
|
|
5840
|
-
category,
|
|
5841
|
-
location,
|
|
5842
|
-
description,
|
|
5843
|
-
suggestedFix
|
|
5844
|
-
});
|
|
5845
|
-
}
|
|
5846
|
-
return findings;
|
|
5847
|
-
}
|
|
5848
6015
|
function auditSeverityRank(severity) {
|
|
5849
6016
|
switch (severity) {
|
|
5850
6017
|
case "critical":
|
|
@@ -5888,22 +6055,14 @@ function auditFindingToRoadmapItem(finding) {
|
|
|
5888
6055
|
};
|
|
5889
6056
|
}
|
|
5890
6057
|
function collectAuditPlannerItems(projectDir) {
|
|
5891
|
-
const
|
|
5892
|
-
if (!fs16.existsSync(reportPath)) {
|
|
5893
|
-
return [];
|
|
5894
|
-
}
|
|
5895
|
-
const reportContent = fs16.readFileSync(reportPath, "utf-8");
|
|
5896
|
-
if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
|
|
5897
|
-
return [];
|
|
5898
|
-
}
|
|
5899
|
-
const findings = parseAuditFindings(reportContent);
|
|
6058
|
+
const findings = loadAuditFindings(projectDir);
|
|
5900
6059
|
findings.sort((a, b) => auditSeverityRank(a.severity) - auditSeverityRank(b.severity) || a.number - b.number);
|
|
5901
6060
|
return findings.map(auditFindingToRoadmapItem);
|
|
5902
6061
|
}
|
|
5903
6062
|
function getRoadmapStatus(projectDir, config) {
|
|
5904
|
-
const roadmapPath =
|
|
6063
|
+
const roadmapPath = path16.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
5905
6064
|
const scannerEnabled = config.roadmapScanner.enabled;
|
|
5906
|
-
if (!
|
|
6065
|
+
if (!fs17.existsSync(roadmapPath)) {
|
|
5907
6066
|
return {
|
|
5908
6067
|
found: false,
|
|
5909
6068
|
enabled: scannerEnabled,
|
|
@@ -5914,9 +6073,9 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
5914
6073
|
items: []
|
|
5915
6074
|
};
|
|
5916
6075
|
}
|
|
5917
|
-
const content =
|
|
6076
|
+
const content = fs17.readFileSync(roadmapPath, "utf-8");
|
|
5918
6077
|
const items = parseRoadmap(content);
|
|
5919
|
-
const prdDir =
|
|
6078
|
+
const prdDir = path16.join(projectDir, config.prdDir);
|
|
5920
6079
|
const state = loadRoadmapState(prdDir);
|
|
5921
6080
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
5922
6081
|
const statusItems = items.map((item) => {
|
|
@@ -5953,10 +6112,10 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
5953
6112
|
}
|
|
5954
6113
|
function scanExistingPrdSlugs(prdDir) {
|
|
5955
6114
|
const slugs = /* @__PURE__ */ new Set();
|
|
5956
|
-
if (!
|
|
6115
|
+
if (!fs17.existsSync(prdDir)) {
|
|
5957
6116
|
return slugs;
|
|
5958
6117
|
}
|
|
5959
|
-
const files =
|
|
6118
|
+
const files = fs17.readdirSync(prdDir);
|
|
5960
6119
|
for (const file of files) {
|
|
5961
6120
|
if (!file.endsWith(".md")) {
|
|
5962
6121
|
continue;
|
|
@@ -5992,20 +6151,20 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5992
6151
|
const nextNum = getNextPrdNumber(prdDir);
|
|
5993
6152
|
const padded = String(nextNum).padStart(2, "0");
|
|
5994
6153
|
const filename = `${padded}-${itemSlug}.md`;
|
|
5995
|
-
const filePath =
|
|
5996
|
-
if (!
|
|
5997
|
-
|
|
6154
|
+
const filePath = path16.join(prdDir, filename);
|
|
6155
|
+
if (!fs17.existsSync(prdDir)) {
|
|
6156
|
+
fs17.mkdirSync(prdDir, { recursive: true });
|
|
5998
6157
|
}
|
|
5999
6158
|
const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
|
|
6000
6159
|
const prompt = renderSlicerPrompt(promptVars);
|
|
6001
6160
|
const provider = resolveJobProvider(config, "slicer");
|
|
6002
6161
|
const providerArgs = buildProviderArgs(provider, prompt, projectDir);
|
|
6003
|
-
const logDir =
|
|
6004
|
-
if (!
|
|
6005
|
-
|
|
6162
|
+
const logDir = path16.join(projectDir, "logs");
|
|
6163
|
+
if (!fs17.existsSync(logDir)) {
|
|
6164
|
+
fs17.mkdirSync(logDir, { recursive: true });
|
|
6006
6165
|
}
|
|
6007
|
-
const logFile =
|
|
6008
|
-
const logStream =
|
|
6166
|
+
const logFile = path16.join(logDir, `slicer-${itemSlug}.log`);
|
|
6167
|
+
const logStream = fs17.createWriteStream(logFile, { flags: "w" });
|
|
6009
6168
|
logStream.on("error", () => {
|
|
6010
6169
|
});
|
|
6011
6170
|
return new Promise((resolve9) => {
|
|
@@ -6042,7 +6201,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
6042
6201
|
});
|
|
6043
6202
|
return;
|
|
6044
6203
|
}
|
|
6045
|
-
if (!
|
|
6204
|
+
if (!fs17.existsSync(filePath)) {
|
|
6046
6205
|
resolve9({
|
|
6047
6206
|
sliced: false,
|
|
6048
6207
|
error: `Provider did not create expected file: ${filePath}`,
|
|
@@ -6065,23 +6224,23 @@ async function sliceNextItem(projectDir, config) {
|
|
|
6065
6224
|
error: "Roadmap scanner is disabled"
|
|
6066
6225
|
};
|
|
6067
6226
|
}
|
|
6068
|
-
const roadmapPath =
|
|
6227
|
+
const roadmapPath = path16.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
6069
6228
|
const auditItems = collectAuditPlannerItems(projectDir);
|
|
6070
|
-
const roadmapExists =
|
|
6229
|
+
const roadmapExists = fs17.existsSync(roadmapPath);
|
|
6071
6230
|
if (!roadmapExists && auditItems.length === 0) {
|
|
6072
6231
|
return {
|
|
6073
6232
|
sliced: false,
|
|
6074
6233
|
error: "No pending items to process"
|
|
6075
6234
|
};
|
|
6076
6235
|
}
|
|
6077
|
-
const roadmapItems = roadmapExists ? parseRoadmap(
|
|
6236
|
+
const roadmapItems = roadmapExists ? parseRoadmap(fs17.readFileSync(roadmapPath, "utf-8")) : [];
|
|
6078
6237
|
if (roadmapExists && roadmapItems.length === 0 && auditItems.length === 0) {
|
|
6079
6238
|
return {
|
|
6080
6239
|
sliced: false,
|
|
6081
6240
|
error: "No items in roadmap"
|
|
6082
6241
|
};
|
|
6083
6242
|
}
|
|
6084
|
-
const prdDir =
|
|
6243
|
+
const prdDir = path16.join(projectDir, config.prdDir);
|
|
6085
6244
|
const state = loadRoadmapState(prdDir);
|
|
6086
6245
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
6087
6246
|
const pickEligibleItem = (items) => {
|
|
@@ -6150,6 +6309,7 @@ function hasNewItems(projectDir, config) {
|
|
|
6150
6309
|
var init_roadmap_scanner = __esm({
|
|
6151
6310
|
"../core/dist/utils/roadmap-scanner.js"() {
|
|
6152
6311
|
"use strict";
|
|
6312
|
+
init_report();
|
|
6153
6313
|
init_config();
|
|
6154
6314
|
init_prd_utils();
|
|
6155
6315
|
init_roadmap_parser();
|
|
@@ -6252,8 +6412,8 @@ var init_shell = __esm({
|
|
|
6252
6412
|
});
|
|
6253
6413
|
|
|
6254
6414
|
// ../core/dist/utils/scheduling.js
|
|
6255
|
-
import * as
|
|
6256
|
-
import * as
|
|
6415
|
+
import * as fs18 from "fs";
|
|
6416
|
+
import * as path17 from "path";
|
|
6257
6417
|
function normalizeSchedulingPriority(priority) {
|
|
6258
6418
|
if (!Number.isFinite(priority)) {
|
|
6259
6419
|
return DEFAULT_SCHEDULING_PRIORITY;
|
|
@@ -6288,7 +6448,7 @@ function getJobSchedule(config, jobType) {
|
|
|
6288
6448
|
}
|
|
6289
6449
|
}
|
|
6290
6450
|
function loadPeerConfig(projectPath) {
|
|
6291
|
-
if (!
|
|
6451
|
+
if (!fs18.existsSync(projectPath) || !fs18.existsSync(path17.join(projectPath, CONFIG_FILE_NAME))) {
|
|
6292
6452
|
return null;
|
|
6293
6453
|
}
|
|
6294
6454
|
try {
|
|
@@ -6299,10 +6459,10 @@ function loadPeerConfig(projectPath) {
|
|
|
6299
6459
|
}
|
|
6300
6460
|
function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
6301
6461
|
const peers = /* @__PURE__ */ new Map();
|
|
6302
|
-
const currentPath =
|
|
6462
|
+
const currentPath = path17.resolve(currentProjectDir);
|
|
6303
6463
|
const currentSchedule = getJobSchedule(currentConfig, jobType);
|
|
6304
6464
|
const addPeer = (projectPath, config) => {
|
|
6305
|
-
const resolvedPath =
|
|
6465
|
+
const resolvedPath = path17.resolve(projectPath);
|
|
6306
6466
|
if (!isJobTypeEnabled(config, jobType)) {
|
|
6307
6467
|
return;
|
|
6308
6468
|
}
|
|
@@ -6313,12 +6473,12 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
6313
6473
|
path: resolvedPath,
|
|
6314
6474
|
config,
|
|
6315
6475
|
schedulingPriority: normalizeSchedulingPriority(config.schedulingPriority),
|
|
6316
|
-
sortKey: `${
|
|
6476
|
+
sortKey: `${path17.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
|
|
6317
6477
|
});
|
|
6318
6478
|
};
|
|
6319
6479
|
addPeer(currentPath, currentConfig);
|
|
6320
6480
|
for (const entry of loadRegistry()) {
|
|
6321
|
-
const resolvedPath =
|
|
6481
|
+
const resolvedPath = path17.resolve(entry.path);
|
|
6322
6482
|
if (resolvedPath === currentPath || peers.has(resolvedPath)) {
|
|
6323
6483
|
continue;
|
|
6324
6484
|
}
|
|
@@ -6336,7 +6496,7 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
6336
6496
|
}
|
|
6337
6497
|
function getSchedulingPlan(projectDir, config, jobType) {
|
|
6338
6498
|
const peers = collectSchedulingPeers(projectDir, config, jobType);
|
|
6339
|
-
const currentPath =
|
|
6499
|
+
const currentPath = path17.resolve(projectDir);
|
|
6340
6500
|
const slotIndex = Math.max(0, peers.findIndex((peer) => peer.path === currentPath));
|
|
6341
6501
|
const peerCount = Math.max(1, peers.length);
|
|
6342
6502
|
const balancedDelayMinutes = peerCount <= 1 ? 0 : Math.floor(slotIndex * 60 / peerCount);
|
|
@@ -6428,8 +6588,8 @@ var init_webhook_validator = __esm({
|
|
|
6428
6588
|
|
|
6429
6589
|
// ../core/dist/utils/worktree-manager.js
|
|
6430
6590
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
6431
|
-
import * as
|
|
6432
|
-
import * as
|
|
6591
|
+
import * as fs19 from "fs";
|
|
6592
|
+
import * as path18 from "path";
|
|
6433
6593
|
function gitExec(args, cwd, logFile) {
|
|
6434
6594
|
try {
|
|
6435
6595
|
const result = execFileSync4("git", args, {
|
|
@@ -6439,7 +6599,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
6439
6599
|
});
|
|
6440
6600
|
if (logFile && result) {
|
|
6441
6601
|
try {
|
|
6442
|
-
|
|
6602
|
+
fs19.appendFileSync(logFile, result);
|
|
6443
6603
|
} catch {
|
|
6444
6604
|
}
|
|
6445
6605
|
}
|
|
@@ -6448,7 +6608,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
6448
6608
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
6449
6609
|
if (logFile) {
|
|
6450
6610
|
try {
|
|
6451
|
-
|
|
6611
|
+
fs19.appendFileSync(logFile, errorMessage + "\n");
|
|
6452
6612
|
} catch {
|
|
6453
6613
|
}
|
|
6454
6614
|
}
|
|
@@ -6473,11 +6633,11 @@ function branchExistsRemotely(projectDir, branchName) {
|
|
|
6473
6633
|
}
|
|
6474
6634
|
function prepareBranchWorktree(options) {
|
|
6475
6635
|
const { projectDir, worktreeDir, branchName, defaultBranch, logFile } = options;
|
|
6476
|
-
if (
|
|
6636
|
+
if (fs19.existsSync(worktreeDir)) {
|
|
6477
6637
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
6478
6638
|
if (!isRegistered) {
|
|
6479
6639
|
try {
|
|
6480
|
-
|
|
6640
|
+
fs19.rmSync(worktreeDir, { recursive: true, force: true });
|
|
6481
6641
|
} catch {
|
|
6482
6642
|
}
|
|
6483
6643
|
}
|
|
@@ -6516,11 +6676,11 @@ function prepareBranchWorktree(options) {
|
|
|
6516
6676
|
}
|
|
6517
6677
|
function prepareDetachedWorktree(options) {
|
|
6518
6678
|
const { projectDir, worktreeDir, defaultBranch, logFile } = options;
|
|
6519
|
-
if (
|
|
6679
|
+
if (fs19.existsSync(worktreeDir)) {
|
|
6520
6680
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
6521
6681
|
if (!isRegistered) {
|
|
6522
6682
|
try {
|
|
6523
|
-
|
|
6683
|
+
fs19.rmSync(worktreeDir, { recursive: true, force: true });
|
|
6524
6684
|
} catch {
|
|
6525
6685
|
}
|
|
6526
6686
|
}
|
|
@@ -6562,7 +6722,7 @@ function isWorktreeRegistered(projectDir, worktreePath) {
|
|
|
6562
6722
|
}
|
|
6563
6723
|
}
|
|
6564
6724
|
function cleanupWorktrees(projectDir, scope) {
|
|
6565
|
-
const projectName =
|
|
6725
|
+
const projectName = path18.basename(projectDir);
|
|
6566
6726
|
const matchToken = scope ? scope : `${projectName}-nw`;
|
|
6567
6727
|
const removed = [];
|
|
6568
6728
|
try {
|
|
@@ -6598,11 +6758,11 @@ var init_worktree_manager = __esm({
|
|
|
6598
6758
|
|
|
6599
6759
|
// ../core/dist/utils/job-queue.js
|
|
6600
6760
|
import * as os7 from "os";
|
|
6601
|
-
import * as
|
|
6761
|
+
import * as path19 from "path";
|
|
6602
6762
|
import Database7 from "better-sqlite3";
|
|
6603
6763
|
function getStateDbPath() {
|
|
6604
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
6605
|
-
return
|
|
6764
|
+
const base = process.env.NIGHT_WATCH_HOME || path19.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
6765
|
+
return path19.join(base, STATE_DB_FILE_NAME);
|
|
6606
6766
|
}
|
|
6607
6767
|
function openDb() {
|
|
6608
6768
|
const dbPath = getStateDbPath();
|
|
@@ -6647,6 +6807,8 @@ function getLockPathForJob(projectPath, jobType) {
|
|
|
6647
6807
|
return analyticsLockPath(projectPath);
|
|
6648
6808
|
case "pr-resolver":
|
|
6649
6809
|
return prResolverLockPath(projectPath);
|
|
6810
|
+
case "merger":
|
|
6811
|
+
return mergerLockPath(projectPath);
|
|
6650
6812
|
}
|
|
6651
6813
|
}
|
|
6652
6814
|
function reconcileStaleRunningJobs(db) {
|
|
@@ -7361,9 +7523,9 @@ var init_amplitude_client = __esm({
|
|
|
7361
7523
|
});
|
|
7362
7524
|
|
|
7363
7525
|
// ../core/dist/analytics/analytics-runner.js
|
|
7364
|
-
import * as
|
|
7526
|
+
import * as fs20 from "fs";
|
|
7365
7527
|
import * as os8 from "os";
|
|
7366
|
-
import * as
|
|
7528
|
+
import * as path20 from "path";
|
|
7367
7529
|
function parseIssuesFromResponse(text) {
|
|
7368
7530
|
const start = text.indexOf("[");
|
|
7369
7531
|
const end = text.lastIndexOf("]");
|
|
@@ -7392,9 +7554,9 @@ async function runAnalytics(config, projectDir) {
|
|
|
7392
7554
|
|
|
7393
7555
|
--- AMPLITUDE DATA ---
|
|
7394
7556
|
${JSON.stringify(data, null, 2)}`;
|
|
7395
|
-
const tmpDir =
|
|
7396
|
-
const promptFile =
|
|
7397
|
-
|
|
7557
|
+
const tmpDir = fs20.mkdtempSync(path20.join(os8.tmpdir(), "nw-analytics-"));
|
|
7558
|
+
const promptFile = path20.join(tmpDir, "analytics-prompt.md");
|
|
7559
|
+
fs20.writeFileSync(promptFile, prompt, "utf-8");
|
|
7398
7560
|
try {
|
|
7399
7561
|
const provider = resolveJobProvider(config, "analytics");
|
|
7400
7562
|
let providerCmd = PROVIDER_COMMANDS[provider];
|
|
@@ -7415,8 +7577,8 @@ set -euo pipefail
|
|
|
7415
7577
|
${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
|
|
7416
7578
|
`;
|
|
7417
7579
|
}
|
|
7418
|
-
const scriptFile =
|
|
7419
|
-
|
|
7580
|
+
const scriptFile = path20.join(tmpDir, "run-analytics.sh");
|
|
7581
|
+
fs20.writeFileSync(scriptFile, scriptContent, { mode: 493 });
|
|
7420
7582
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], config.providerEnv ?? {});
|
|
7421
7583
|
if (exitCode !== 0) {
|
|
7422
7584
|
throw new Error(`AI provider exited with code ${exitCode}: ${stderr || stdout}`);
|
|
@@ -7463,7 +7625,7 @@ ${stderr}`;
|
|
|
7463
7625
|
};
|
|
7464
7626
|
} finally {
|
|
7465
7627
|
try {
|
|
7466
|
-
|
|
7628
|
+
fs20.rmSync(tmpDir, { recursive: true, force: true });
|
|
7467
7629
|
} catch {
|
|
7468
7630
|
}
|
|
7469
7631
|
}
|
|
@@ -7491,6 +7653,160 @@ var init_analytics = __esm({
|
|
|
7491
7653
|
}
|
|
7492
7654
|
});
|
|
7493
7655
|
|
|
7656
|
+
// ../core/dist/audit/board-sync.js
|
|
7657
|
+
function humanizeCategory(category) {
|
|
7658
|
+
return category.trim().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
7659
|
+
}
|
|
7660
|
+
function severityToPriorityLabel(severity) {
|
|
7661
|
+
switch (severity) {
|
|
7662
|
+
case "critical":
|
|
7663
|
+
return "P0";
|
|
7664
|
+
case "high":
|
|
7665
|
+
return "P1";
|
|
7666
|
+
default:
|
|
7667
|
+
return "P2";
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
function truncateTitle(title, maxLength = 240) {
|
|
7671
|
+
const normalized = title.replace(/\s+/g, " ").trim();
|
|
7672
|
+
if (normalized.length <= maxLength) {
|
|
7673
|
+
return normalized;
|
|
7674
|
+
}
|
|
7675
|
+
return `${normalized.slice(0, maxLength - 1).trimEnd()}\u2026`;
|
|
7676
|
+
}
|
|
7677
|
+
function buildIssueTitle(finding) {
|
|
7678
|
+
return truncateTitle(`Audit: ${finding.severity} ${humanizeCategory(finding.category)} in ${finding.location}`);
|
|
7679
|
+
}
|
|
7680
|
+
function buildIssueBody(finding) {
|
|
7681
|
+
const lines = [
|
|
7682
|
+
"## Summary",
|
|
7683
|
+
"",
|
|
7684
|
+
`Night Watch audit detected a **${finding.severity}** finding in \`${finding.location}\`.`,
|
|
7685
|
+
"",
|
|
7686
|
+
"## Category",
|
|
7687
|
+
"",
|
|
7688
|
+
`\`${finding.category}\``,
|
|
7689
|
+
"",
|
|
7690
|
+
"## Description",
|
|
7691
|
+
"",
|
|
7692
|
+
finding.description,
|
|
7693
|
+
"",
|
|
7694
|
+
"## Suggested Fix",
|
|
7695
|
+
"",
|
|
7696
|
+
finding.suggestedFix
|
|
7697
|
+
];
|
|
7698
|
+
if (finding.snippet) {
|
|
7699
|
+
lines.push("", "## Snippet", "", "```", finding.snippet, "```");
|
|
7700
|
+
}
|
|
7701
|
+
lines.push("", "## Source", "", "- Report: `logs/audit-report.md`", `- Finding: ${finding.number}`);
|
|
7702
|
+
return lines.join("\n");
|
|
7703
|
+
}
|
|
7704
|
+
async function syncAuditFindingsToBoard(config, projectDir) {
|
|
7705
|
+
const findings = loadAuditFindings(projectDir);
|
|
7706
|
+
const targetColumn = config.audit.targetColumn;
|
|
7707
|
+
if (findings.length === 0) {
|
|
7708
|
+
return {
|
|
7709
|
+
status: "skipped",
|
|
7710
|
+
findingsCount: 0,
|
|
7711
|
+
issuesCreated: 0,
|
|
7712
|
+
issuesFailed: 0,
|
|
7713
|
+
targetColumn: null,
|
|
7714
|
+
summary: "no actionable audit findings to sync"
|
|
7715
|
+
};
|
|
7716
|
+
}
|
|
7717
|
+
if (!config.boardProvider.enabled) {
|
|
7718
|
+
return {
|
|
7719
|
+
status: "skipped",
|
|
7720
|
+
findingsCount: findings.length,
|
|
7721
|
+
issuesCreated: 0,
|
|
7722
|
+
issuesFailed: 0,
|
|
7723
|
+
targetColumn: null,
|
|
7724
|
+
summary: `found ${findings.length} actionable audit finding(s); board sync skipped because board provider is disabled`
|
|
7725
|
+
};
|
|
7726
|
+
}
|
|
7727
|
+
let boardProvider;
|
|
7728
|
+
try {
|
|
7729
|
+
boardProvider = createBoardProvider(config.boardProvider, projectDir);
|
|
7730
|
+
} catch (err) {
|
|
7731
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7732
|
+
logger5.error("Failed to create board provider for audit sync", { error: message });
|
|
7733
|
+
return {
|
|
7734
|
+
status: "failed",
|
|
7735
|
+
findingsCount: findings.length,
|
|
7736
|
+
issuesCreated: 0,
|
|
7737
|
+
issuesFailed: findings.length,
|
|
7738
|
+
targetColumn,
|
|
7739
|
+
summary: `found ${findings.length} actionable audit finding(s), but board sync failed: ${message}`
|
|
7740
|
+
};
|
|
7741
|
+
}
|
|
7742
|
+
let created = 0;
|
|
7743
|
+
let failed = 0;
|
|
7744
|
+
for (const finding of findings) {
|
|
7745
|
+
try {
|
|
7746
|
+
await boardProvider.createIssue({
|
|
7747
|
+
title: buildIssueTitle(finding),
|
|
7748
|
+
body: buildIssueBody(finding),
|
|
7749
|
+
column: targetColumn,
|
|
7750
|
+
labels: [severityToPriorityLabel(finding.severity)]
|
|
7751
|
+
});
|
|
7752
|
+
created++;
|
|
7753
|
+
} catch (err) {
|
|
7754
|
+
failed++;
|
|
7755
|
+
logger5.error("Failed to create board issue for audit finding", {
|
|
7756
|
+
finding: finding.number,
|
|
7757
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7758
|
+
});
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
7761
|
+
if (failed === 0) {
|
|
7762
|
+
return {
|
|
7763
|
+
status: "success",
|
|
7764
|
+
findingsCount: findings.length,
|
|
7765
|
+
issuesCreated: created,
|
|
7766
|
+
issuesFailed: 0,
|
|
7767
|
+
targetColumn,
|
|
7768
|
+
summary: `created ${created} audit issue(s) in ${targetColumn}`
|
|
7769
|
+
};
|
|
7770
|
+
}
|
|
7771
|
+
if (created === 0) {
|
|
7772
|
+
return {
|
|
7773
|
+
status: "failed",
|
|
7774
|
+
findingsCount: findings.length,
|
|
7775
|
+
issuesCreated: 0,
|
|
7776
|
+
issuesFailed: failed,
|
|
7777
|
+
targetColumn,
|
|
7778
|
+
summary: `found ${findings.length} actionable audit finding(s), but failed to create board issue(s)`
|
|
7779
|
+
};
|
|
7780
|
+
}
|
|
7781
|
+
return {
|
|
7782
|
+
status: "partial",
|
|
7783
|
+
findingsCount: findings.length,
|
|
7784
|
+
issuesCreated: created,
|
|
7785
|
+
issuesFailed: failed,
|
|
7786
|
+
targetColumn,
|
|
7787
|
+
summary: `created ${created} of ${findings.length} audit issue(s) in ${targetColumn} (${failed} failed)`
|
|
7788
|
+
};
|
|
7789
|
+
}
|
|
7790
|
+
var logger5;
|
|
7791
|
+
var init_board_sync = __esm({
|
|
7792
|
+
"../core/dist/audit/board-sync.js"() {
|
|
7793
|
+
"use strict";
|
|
7794
|
+
init_factory();
|
|
7795
|
+
init_logger();
|
|
7796
|
+
init_report();
|
|
7797
|
+
logger5 = createLogger("audit-sync");
|
|
7798
|
+
}
|
|
7799
|
+
});
|
|
7800
|
+
|
|
7801
|
+
// ../core/dist/audit/index.js
|
|
7802
|
+
var init_audit = __esm({
|
|
7803
|
+
"../core/dist/audit/index.js"() {
|
|
7804
|
+
"use strict";
|
|
7805
|
+
init_report();
|
|
7806
|
+
init_board_sync();
|
|
7807
|
+
}
|
|
7808
|
+
});
|
|
7809
|
+
|
|
7494
7810
|
// ../core/dist/templates/prd-template.js
|
|
7495
7811
|
function renderDependsOn(deps) {
|
|
7496
7812
|
if (deps.length === 0) {
|
|
@@ -7698,6 +8014,7 @@ __export(dist_exports, {
|
|
|
7698
8014
|
DEFAULT_AUDIT_ENABLED: () => DEFAULT_AUDIT_ENABLED,
|
|
7699
8015
|
DEFAULT_AUDIT_MAX_RUNTIME: () => DEFAULT_AUDIT_MAX_RUNTIME,
|
|
7700
8016
|
DEFAULT_AUDIT_SCHEDULE: () => DEFAULT_AUDIT_SCHEDULE,
|
|
8017
|
+
DEFAULT_AUDIT_TARGET_COLUMN: () => DEFAULT_AUDIT_TARGET_COLUMN,
|
|
7701
8018
|
DEFAULT_AUTO_MERGE: () => DEFAULT_AUTO_MERGE,
|
|
7702
8019
|
DEFAULT_AUTO_MERGE_METHOD: () => DEFAULT_AUTO_MERGE_METHOD,
|
|
7703
8020
|
DEFAULT_BOARD_PROVIDER: () => DEFAULT_BOARD_PROVIDER,
|
|
@@ -7714,6 +8031,14 @@ __export(dist_exports, {
|
|
|
7714
8031
|
DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
|
|
7715
8032
|
DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
|
|
7716
8033
|
DEFAULT_MAX_RUNTIME: () => DEFAULT_MAX_RUNTIME,
|
|
8034
|
+
DEFAULT_MERGER: () => DEFAULT_MERGER,
|
|
8035
|
+
DEFAULT_MERGER_ENABLED: () => DEFAULT_MERGER_ENABLED,
|
|
8036
|
+
DEFAULT_MERGER_MAX_PRS_PER_RUN: () => DEFAULT_MERGER_MAX_PRS_PER_RUN,
|
|
8037
|
+
DEFAULT_MERGER_MAX_RUNTIME: () => DEFAULT_MERGER_MAX_RUNTIME,
|
|
8038
|
+
DEFAULT_MERGER_MERGE_METHOD: () => DEFAULT_MERGER_MERGE_METHOD,
|
|
8039
|
+
DEFAULT_MERGER_MIN_REVIEW_SCORE: () => DEFAULT_MERGER_MIN_REVIEW_SCORE,
|
|
8040
|
+
DEFAULT_MERGER_REBASE_BEFORE_MERGE: () => DEFAULT_MERGER_REBASE_BEFORE_MERGE,
|
|
8041
|
+
DEFAULT_MERGER_SCHEDULE: () => DEFAULT_MERGER_SCHEDULE,
|
|
7717
8042
|
DEFAULT_MIN_REVIEW_SCORE: () => DEFAULT_MIN_REVIEW_SCORE,
|
|
7718
8043
|
DEFAULT_NOTIFICATIONS: () => DEFAULT_NOTIFICATIONS,
|
|
7719
8044
|
DEFAULT_PRD_DIR: () => DEFAULT_PRD_DIR,
|
|
@@ -7772,6 +8097,7 @@ __export(dist_exports, {
|
|
|
7772
8097
|
LocalKanbanProvider: () => LocalKanbanProvider,
|
|
7773
8098
|
Logger: () => Logger,
|
|
7774
8099
|
MAX_HISTORY_RECORDS_PER_PRD: () => MAX_HISTORY_RECORDS_PER_PRD,
|
|
8100
|
+
MERGER_LOG_NAME: () => MERGER_LOG_NAME,
|
|
7775
8101
|
NIGHT_WATCH_LABELS: () => NIGHT_WATCH_LABELS,
|
|
7776
8102
|
PLANNER_LOG_NAME: () => PLANNER_LOG_NAME,
|
|
7777
8103
|
PRD_STATES_FILE_NAME: () => PRD_STATES_FILE_NAME,
|
|
@@ -7927,6 +8253,7 @@ __export(dist_exports, {
|
|
|
7927
8253
|
isValidPriority: () => isValidPriority,
|
|
7928
8254
|
label: () => label,
|
|
7929
8255
|
listPrdStatesByStatus: () => listPrdStatesByStatus,
|
|
8256
|
+
loadAuditFindings: () => loadAuditFindings,
|
|
7930
8257
|
loadConfig: () => loadConfig,
|
|
7931
8258
|
loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
|
|
7932
8259
|
loadHistory: () => loadHistory,
|
|
@@ -7936,9 +8263,12 @@ __export(dist_exports, {
|
|
|
7936
8263
|
markItemProcessed: () => markItemProcessed,
|
|
7937
8264
|
markJobRunning: () => markJobRunning,
|
|
7938
8265
|
markPrdDone: () => markPrdDone,
|
|
8266
|
+
mergerLockPath: () => mergerLockPath,
|
|
7939
8267
|
migrateJsonToSqlite: () => migrateJsonToSqlite,
|
|
8268
|
+
normalizeAuditSeverity: () => normalizeAuditSeverity,
|
|
7940
8269
|
normalizeJobConfig: () => normalizeJobConfig,
|
|
7941
8270
|
normalizeSchedulingPriority: () => normalizeSchedulingPriority,
|
|
8271
|
+
parseAuditFindings: () => parseAuditFindings,
|
|
7942
8272
|
parsePrdDependencies: () => parsePrdDependencies,
|
|
7943
8273
|
parseRoadmap: () => parseRoadmap,
|
|
7944
8274
|
parseScriptResult: () => parseScriptResult,
|
|
@@ -7988,6 +8318,7 @@ __export(dist_exports, {
|
|
|
7988
8318
|
sortByPriority: () => sortByPriority,
|
|
7989
8319
|
step: () => step,
|
|
7990
8320
|
success: () => success,
|
|
8321
|
+
syncAuditFindingsToBoard: () => syncAuditFindingsToBoard,
|
|
7991
8322
|
unmarkItemProcessed: () => unmarkItemProcessed,
|
|
7992
8323
|
unregisterProject: () => unregisterProject,
|
|
7993
8324
|
updateJobStatus: () => updateJobStatus,
|
|
@@ -8044,6 +8375,7 @@ var init_dist = __esm({
|
|
|
8044
8375
|
init_job_queue();
|
|
8045
8376
|
init_summary();
|
|
8046
8377
|
init_analytics();
|
|
8378
|
+
init_audit();
|
|
8047
8379
|
init_prd_template();
|
|
8048
8380
|
init_slicer_prompt();
|
|
8049
8381
|
init_jobs();
|
|
@@ -8053,30 +8385,30 @@ var init_dist = __esm({
|
|
|
8053
8385
|
// src/cli.ts
|
|
8054
8386
|
import "reflect-metadata";
|
|
8055
8387
|
import { Command as Command3 } from "commander";
|
|
8056
|
-
import { existsSync as
|
|
8388
|
+
import { existsSync as existsSync31, readFileSync as readFileSync20 } from "fs";
|
|
8057
8389
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
8058
|
-
import { dirname as dirname12, join as
|
|
8390
|
+
import { dirname as dirname12, join as join37 } from "path";
|
|
8059
8391
|
|
|
8060
8392
|
// src/commands/init.ts
|
|
8061
8393
|
init_dist();
|
|
8062
|
-
import
|
|
8063
|
-
import
|
|
8394
|
+
import fs21 from "fs";
|
|
8395
|
+
import path21 from "path";
|
|
8064
8396
|
import { execSync as execSync3 } from "child_process";
|
|
8065
8397
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8066
|
-
import { dirname as dirname6, join as
|
|
8398
|
+
import { dirname as dirname6, join as join19 } from "path";
|
|
8067
8399
|
import * as readline from "readline";
|
|
8068
8400
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
8069
8401
|
var __dirname2 = dirname6(__filename2);
|
|
8070
8402
|
function findTemplatesDir(startDir) {
|
|
8071
8403
|
let d = startDir;
|
|
8072
8404
|
for (let i = 0; i < 8; i++) {
|
|
8073
|
-
const candidate =
|
|
8074
|
-
if (
|
|
8405
|
+
const candidate = join19(d, "templates");
|
|
8406
|
+
if (fs21.existsSync(candidate) && fs21.statSync(candidate).isDirectory()) {
|
|
8075
8407
|
return candidate;
|
|
8076
8408
|
}
|
|
8077
8409
|
d = dirname6(d);
|
|
8078
8410
|
}
|
|
8079
|
-
return
|
|
8411
|
+
return join19(startDir, "templates");
|
|
8080
8412
|
}
|
|
8081
8413
|
var TEMPLATES_DIR = findTemplatesDir(__dirname2);
|
|
8082
8414
|
var NW_SKILLS = [
|
|
@@ -8088,12 +8420,12 @@ var NW_SKILLS = [
|
|
|
8088
8420
|
"nw-review"
|
|
8089
8421
|
];
|
|
8090
8422
|
function hasPlaywrightDependency(cwd) {
|
|
8091
|
-
const packageJsonPath =
|
|
8092
|
-
if (!
|
|
8423
|
+
const packageJsonPath = path21.join(cwd, "package.json");
|
|
8424
|
+
if (!fs21.existsSync(packageJsonPath)) {
|
|
8093
8425
|
return false;
|
|
8094
8426
|
}
|
|
8095
8427
|
try {
|
|
8096
|
-
const packageJson2 = JSON.parse(
|
|
8428
|
+
const packageJson2 = JSON.parse(fs21.readFileSync(packageJsonPath, "utf-8"));
|
|
8097
8429
|
return Boolean(
|
|
8098
8430
|
packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
|
|
8099
8431
|
);
|
|
@@ -8105,7 +8437,7 @@ function detectPlaywright(cwd) {
|
|
|
8105
8437
|
if (hasPlaywrightDependency(cwd)) {
|
|
8106
8438
|
return true;
|
|
8107
8439
|
}
|
|
8108
|
-
if (
|
|
8440
|
+
if (fs21.existsSync(path21.join(cwd, "node_modules", ".bin", "playwright"))) {
|
|
8109
8441
|
return true;
|
|
8110
8442
|
}
|
|
8111
8443
|
try {
|
|
@@ -8121,10 +8453,10 @@ function detectPlaywright(cwd) {
|
|
|
8121
8453
|
}
|
|
8122
8454
|
}
|
|
8123
8455
|
function resolvePlaywrightInstallCommand(cwd) {
|
|
8124
|
-
if (
|
|
8456
|
+
if (fs21.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) {
|
|
8125
8457
|
return "pnpm add -D @playwright/test";
|
|
8126
8458
|
}
|
|
8127
|
-
if (
|
|
8459
|
+
if (fs21.existsSync(path21.join(cwd, "yarn.lock"))) {
|
|
8128
8460
|
return "yarn add -D @playwright/test";
|
|
8129
8461
|
}
|
|
8130
8462
|
return "npm install -D @playwright/test";
|
|
@@ -8270,8 +8602,8 @@ function promptProviderSelection(providers) {
|
|
|
8270
8602
|
});
|
|
8271
8603
|
}
|
|
8272
8604
|
function ensureDir(dirPath) {
|
|
8273
|
-
if (!
|
|
8274
|
-
|
|
8605
|
+
if (!fs21.existsSync(dirPath)) {
|
|
8606
|
+
fs21.mkdirSync(dirPath, { recursive: true });
|
|
8275
8607
|
}
|
|
8276
8608
|
}
|
|
8277
8609
|
function buildInitConfig(params) {
|
|
@@ -8319,6 +8651,7 @@ function buildInitConfig(params) {
|
|
|
8319
8651
|
},
|
|
8320
8652
|
audit: { ...defaults.audit },
|
|
8321
8653
|
analytics: { ...defaults.analytics },
|
|
8654
|
+
merger: { ...defaults.merger },
|
|
8322
8655
|
prResolver: { ...defaults.prResolver },
|
|
8323
8656
|
jobProviders: { ...defaults.jobProviders },
|
|
8324
8657
|
queue: {
|
|
@@ -8329,30 +8662,30 @@ function buildInitConfig(params) {
|
|
|
8329
8662
|
}
|
|
8330
8663
|
function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
|
|
8331
8664
|
if (customTemplatesDir !== null) {
|
|
8332
|
-
const customPath =
|
|
8333
|
-
if (
|
|
8665
|
+
const customPath = join19(customTemplatesDir, templateName);
|
|
8666
|
+
if (fs21.existsSync(customPath)) {
|
|
8334
8667
|
return { path: customPath, source: "custom" };
|
|
8335
8668
|
}
|
|
8336
8669
|
}
|
|
8337
|
-
return { path:
|
|
8670
|
+
return { path: join19(bundledTemplatesDir, templateName), source: "bundled" };
|
|
8338
8671
|
}
|
|
8339
8672
|
function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
|
|
8340
|
-
if (
|
|
8673
|
+
if (fs21.existsSync(targetPath) && !force) {
|
|
8341
8674
|
console.log(` Skipped (exists): ${targetPath}`);
|
|
8342
8675
|
return { created: false, source: source ?? "bundled" };
|
|
8343
8676
|
}
|
|
8344
|
-
const templatePath = sourcePath ??
|
|
8677
|
+
const templatePath = sourcePath ?? join19(TEMPLATES_DIR, templateName);
|
|
8345
8678
|
const resolvedSource = source ?? "bundled";
|
|
8346
|
-
let content =
|
|
8679
|
+
let content = fs21.readFileSync(templatePath, "utf-8");
|
|
8347
8680
|
for (const [key, value] of Object.entries(replacements)) {
|
|
8348
8681
|
content = content.replaceAll(key, value);
|
|
8349
8682
|
}
|
|
8350
|
-
|
|
8683
|
+
fs21.writeFileSync(targetPath, content);
|
|
8351
8684
|
console.log(` Created: ${targetPath} (${resolvedSource})`);
|
|
8352
8685
|
return { created: true, source: resolvedSource };
|
|
8353
8686
|
}
|
|
8354
8687
|
function addToGitignore(cwd) {
|
|
8355
|
-
const gitignorePath =
|
|
8688
|
+
const gitignorePath = path21.join(cwd, ".gitignore");
|
|
8356
8689
|
const entries = [
|
|
8357
8690
|
{
|
|
8358
8691
|
pattern: "/logs/",
|
|
@@ -8366,13 +8699,13 @@ function addToGitignore(cwd) {
|
|
|
8366
8699
|
},
|
|
8367
8700
|
{ pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
|
|
8368
8701
|
];
|
|
8369
|
-
if (!
|
|
8702
|
+
if (!fs21.existsSync(gitignorePath)) {
|
|
8370
8703
|
const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
|
|
8371
|
-
|
|
8704
|
+
fs21.writeFileSync(gitignorePath, lines.join("\n"));
|
|
8372
8705
|
console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
|
|
8373
8706
|
return;
|
|
8374
8707
|
}
|
|
8375
|
-
const content =
|
|
8708
|
+
const content = fs21.readFileSync(gitignorePath, "utf-8");
|
|
8376
8709
|
const missing = entries.filter((e) => !e.check(content));
|
|
8377
8710
|
if (missing.length === 0) {
|
|
8378
8711
|
console.log(` Skipped (exists): Night Watch entries in .gitignore`);
|
|
@@ -8380,59 +8713,59 @@ function addToGitignore(cwd) {
|
|
|
8380
8713
|
}
|
|
8381
8714
|
const additions = missing.map((e) => e.pattern).join("\n");
|
|
8382
8715
|
const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
|
|
8383
|
-
|
|
8716
|
+
fs21.writeFileSync(gitignorePath, newContent);
|
|
8384
8717
|
console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
|
|
8385
8718
|
}
|
|
8386
8719
|
function installSkills(cwd, provider, force, templatesDir) {
|
|
8387
|
-
const skillsTemplatesDir =
|
|
8388
|
-
if (!
|
|
8720
|
+
const skillsTemplatesDir = path21.join(templatesDir, "skills");
|
|
8721
|
+
if (!fs21.existsSync(skillsTemplatesDir)) {
|
|
8389
8722
|
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
8390
8723
|
}
|
|
8391
8724
|
const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
|
|
8392
8725
|
const isCodexProvider = provider === "codex";
|
|
8393
|
-
const claudeDir =
|
|
8394
|
-
if (isClaudeProvider ||
|
|
8726
|
+
const claudeDir = path21.join(cwd, ".claude");
|
|
8727
|
+
if (isClaudeProvider || fs21.existsSync(claudeDir)) {
|
|
8395
8728
|
ensureDir(claudeDir);
|
|
8396
|
-
const skillsDir =
|
|
8729
|
+
const skillsDir = path21.join(claudeDir, "skills");
|
|
8397
8730
|
ensureDir(skillsDir);
|
|
8398
8731
|
let installed = 0;
|
|
8399
8732
|
let skipped = 0;
|
|
8400
8733
|
for (const skillName of NW_SKILLS) {
|
|
8401
|
-
const templateFile =
|
|
8402
|
-
if (!
|
|
8403
|
-
const skillDir =
|
|
8734
|
+
const templateFile = path21.join(skillsTemplatesDir, `${skillName}.md`);
|
|
8735
|
+
if (!fs21.existsSync(templateFile)) continue;
|
|
8736
|
+
const skillDir = path21.join(skillsDir, skillName);
|
|
8404
8737
|
ensureDir(skillDir);
|
|
8405
|
-
const target =
|
|
8406
|
-
if (
|
|
8738
|
+
const target = path21.join(skillDir, "SKILL.md");
|
|
8739
|
+
if (fs21.existsSync(target) && !force) {
|
|
8407
8740
|
skipped++;
|
|
8408
8741
|
continue;
|
|
8409
8742
|
}
|
|
8410
|
-
|
|
8743
|
+
fs21.copyFileSync(templateFile, target);
|
|
8411
8744
|
installed++;
|
|
8412
8745
|
}
|
|
8413
8746
|
return { location: ".claude/skills/", installed, skipped, type: "claude" };
|
|
8414
8747
|
}
|
|
8415
8748
|
if (isCodexProvider) {
|
|
8416
|
-
const agentsFile =
|
|
8417
|
-
const blockFile =
|
|
8418
|
-
if (!
|
|
8749
|
+
const agentsFile = path21.join(cwd, "AGENTS.md");
|
|
8750
|
+
const blockFile = path21.join(skillsTemplatesDir, "_codex-block.md");
|
|
8751
|
+
if (!fs21.existsSync(blockFile)) {
|
|
8419
8752
|
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
8420
8753
|
}
|
|
8421
|
-
const block =
|
|
8754
|
+
const block = fs21.readFileSync(blockFile, "utf-8");
|
|
8422
8755
|
const marker = "## Night Watch Skills";
|
|
8423
|
-
if (!
|
|
8424
|
-
|
|
8756
|
+
if (!fs21.existsSync(agentsFile)) {
|
|
8757
|
+
fs21.writeFileSync(agentsFile, block);
|
|
8425
8758
|
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
8426
8759
|
}
|
|
8427
|
-
const existing =
|
|
8760
|
+
const existing = fs21.readFileSync(agentsFile, "utf-8");
|
|
8428
8761
|
if (existing.includes(marker)) {
|
|
8429
8762
|
if (!force) {
|
|
8430
8763
|
return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
|
|
8431
8764
|
}
|
|
8432
8765
|
const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
|
|
8433
|
-
|
|
8766
|
+
fs21.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
|
|
8434
8767
|
} else {
|
|
8435
|
-
|
|
8768
|
+
fs21.appendFileSync(agentsFile, "\n\n" + block);
|
|
8436
8769
|
}
|
|
8437
8770
|
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
8438
8771
|
}
|
|
@@ -8556,28 +8889,28 @@ function initCommand(program2) {
|
|
|
8556
8889
|
"${DEFAULT_BRANCH}": defaultBranch
|
|
8557
8890
|
};
|
|
8558
8891
|
step(6, totalSteps, "Creating PRD directory structure...");
|
|
8559
|
-
const prdDirPath =
|
|
8560
|
-
const doneDirPath =
|
|
8892
|
+
const prdDirPath = path21.join(cwd, prdDir);
|
|
8893
|
+
const doneDirPath = path21.join(prdDirPath, "done");
|
|
8561
8894
|
ensureDir(doneDirPath);
|
|
8562
8895
|
success(`Created ${prdDirPath}/`);
|
|
8563
8896
|
success(`Created ${doneDirPath}/`);
|
|
8564
8897
|
step(7, totalSteps, "Creating logs directory...");
|
|
8565
|
-
const logsPath =
|
|
8898
|
+
const logsPath = path21.join(cwd, LOG_DIR);
|
|
8566
8899
|
ensureDir(logsPath);
|
|
8567
8900
|
success(`Created ${logsPath}/`);
|
|
8568
8901
|
addToGitignore(cwd);
|
|
8569
8902
|
step(8, totalSteps, "Creating instructions directory...");
|
|
8570
|
-
const instructionsDir =
|
|
8903
|
+
const instructionsDir = path21.join(cwd, "instructions");
|
|
8571
8904
|
ensureDir(instructionsDir);
|
|
8572
8905
|
success(`Created ${instructionsDir}/`);
|
|
8573
8906
|
const existingConfig = loadConfig(cwd);
|
|
8574
|
-
const customTemplatesDirPath =
|
|
8575
|
-
const customTemplatesDir =
|
|
8907
|
+
const customTemplatesDirPath = path21.join(cwd, existingConfig.templatesDir);
|
|
8908
|
+
const customTemplatesDir = fs21.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
|
|
8576
8909
|
const templateSources = [];
|
|
8577
8910
|
const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
|
|
8578
8911
|
const nwResult = processTemplate(
|
|
8579
8912
|
"executor.md",
|
|
8580
|
-
|
|
8913
|
+
path21.join(instructionsDir, "executor.md"),
|
|
8581
8914
|
replacements,
|
|
8582
8915
|
force,
|
|
8583
8916
|
nwResolution.path,
|
|
@@ -8591,7 +8924,7 @@ function initCommand(program2) {
|
|
|
8591
8924
|
);
|
|
8592
8925
|
const peResult = processTemplate(
|
|
8593
8926
|
"prd-executor.md",
|
|
8594
|
-
|
|
8927
|
+
path21.join(instructionsDir, "prd-executor.md"),
|
|
8595
8928
|
replacements,
|
|
8596
8929
|
force,
|
|
8597
8930
|
peResolution.path,
|
|
@@ -8601,7 +8934,7 @@ function initCommand(program2) {
|
|
|
8601
8934
|
const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
|
|
8602
8935
|
const prResult = processTemplate(
|
|
8603
8936
|
"pr-reviewer.md",
|
|
8604
|
-
|
|
8937
|
+
path21.join(instructionsDir, "pr-reviewer.md"),
|
|
8605
8938
|
replacements,
|
|
8606
8939
|
force,
|
|
8607
8940
|
prResolution.path,
|
|
@@ -8611,7 +8944,7 @@ function initCommand(program2) {
|
|
|
8611
8944
|
const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
|
|
8612
8945
|
const qaResult = processTemplate(
|
|
8613
8946
|
"qa.md",
|
|
8614
|
-
|
|
8947
|
+
path21.join(instructionsDir, "qa.md"),
|
|
8615
8948
|
replacements,
|
|
8616
8949
|
force,
|
|
8617
8950
|
qaResolution.path,
|
|
@@ -8621,7 +8954,7 @@ function initCommand(program2) {
|
|
|
8621
8954
|
const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
|
|
8622
8955
|
const auditResult = processTemplate(
|
|
8623
8956
|
"audit.md",
|
|
8624
|
-
|
|
8957
|
+
path21.join(instructionsDir, "audit.md"),
|
|
8625
8958
|
replacements,
|
|
8626
8959
|
force,
|
|
8627
8960
|
auditResolution.path,
|
|
@@ -8635,7 +8968,7 @@ function initCommand(program2) {
|
|
|
8635
8968
|
);
|
|
8636
8969
|
const plannerResult = processTemplate(
|
|
8637
8970
|
"prd-creator.md",
|
|
8638
|
-
|
|
8971
|
+
path21.join(instructionsDir, "prd-creator.md"),
|
|
8639
8972
|
replacements,
|
|
8640
8973
|
force,
|
|
8641
8974
|
plannerResolution.path,
|
|
@@ -8643,8 +8976,8 @@ function initCommand(program2) {
|
|
|
8643
8976
|
);
|
|
8644
8977
|
templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
|
|
8645
8978
|
step(9, totalSteps, "Creating configuration file...");
|
|
8646
|
-
const configPath =
|
|
8647
|
-
if (
|
|
8979
|
+
const configPath = path21.join(cwd, CONFIG_FILE_NAME);
|
|
8980
|
+
if (fs21.existsSync(configPath) && !force) {
|
|
8648
8981
|
console.log(` Skipped (exists): ${configPath}`);
|
|
8649
8982
|
} else {
|
|
8650
8983
|
const config = buildInitConfig({
|
|
@@ -8654,11 +8987,11 @@ function initCommand(program2) {
|
|
|
8654
8987
|
reviewerEnabled,
|
|
8655
8988
|
prdDir
|
|
8656
8989
|
});
|
|
8657
|
-
|
|
8990
|
+
fs21.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
8658
8991
|
success(`Created ${configPath}`);
|
|
8659
8992
|
}
|
|
8660
8993
|
step(10, totalSteps, "Setting up GitHub Project board...");
|
|
8661
|
-
const existingRaw = JSON.parse(
|
|
8994
|
+
const existingRaw = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
|
|
8662
8995
|
const existingBoard = existingRaw.boardProvider;
|
|
8663
8996
|
let boardSetupStatus = "Skipped";
|
|
8664
8997
|
if (existingBoard?.projectNumber && !force) {
|
|
@@ -8680,13 +9013,13 @@ function initCommand(program2) {
|
|
|
8680
9013
|
const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
|
|
8681
9014
|
const boardTitle = `${projectName} Night Watch`;
|
|
8682
9015
|
const board = await provider.setupBoard(boardTitle);
|
|
8683
|
-
const rawConfig = JSON.parse(
|
|
9016
|
+
const rawConfig = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
|
|
8684
9017
|
rawConfig.boardProvider = {
|
|
8685
9018
|
enabled: true,
|
|
8686
9019
|
provider: "github",
|
|
8687
9020
|
projectNumber: board.number
|
|
8688
9021
|
};
|
|
8689
|
-
|
|
9022
|
+
fs21.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
|
|
8690
9023
|
boardSetupStatus = `Created (#${board.number})`;
|
|
8691
9024
|
success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
|
|
8692
9025
|
} catch (boardErr) {
|
|
@@ -8869,8 +9202,8 @@ function getTelegramStatusWebhooks(config) {
|
|
|
8869
9202
|
}
|
|
8870
9203
|
|
|
8871
9204
|
// src/commands/run.ts
|
|
8872
|
-
import * as
|
|
8873
|
-
import * as
|
|
9205
|
+
import * as fs22 from "fs";
|
|
9206
|
+
import * as path22 from "path";
|
|
8874
9207
|
function resolveRunNotificationEvent(exitCode, scriptStatus) {
|
|
8875
9208
|
if (exitCode === 124) {
|
|
8876
9209
|
return "run_timeout";
|
|
@@ -8902,12 +9235,12 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
|
|
|
8902
9235
|
return scriptStatus === "skip_no_eligible_prd";
|
|
8903
9236
|
}
|
|
8904
9237
|
function getCrossProjectFallbackCandidates(currentProjectDir) {
|
|
8905
|
-
const current =
|
|
9238
|
+
const current = path22.resolve(currentProjectDir);
|
|
8906
9239
|
const { valid, invalid } = validateRegistry();
|
|
8907
9240
|
for (const entry of invalid) {
|
|
8908
9241
|
warn(`Skipping invalid registry entry: ${entry.path}`);
|
|
8909
9242
|
}
|
|
8910
|
-
return valid.filter((entry) =>
|
|
9243
|
+
return valid.filter((entry) => path22.resolve(entry.path) !== current);
|
|
8911
9244
|
}
|
|
8912
9245
|
async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
|
|
8913
9246
|
if (isRateLimitFallbackTriggered(scriptResult?.data)) {
|
|
@@ -8917,7 +9250,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
8917
9250
|
if (nonTelegramWebhooks.length > 0) {
|
|
8918
9251
|
const _rateLimitCtx = {
|
|
8919
9252
|
event: "rate_limit_fallback",
|
|
8920
|
-
projectName:
|
|
9253
|
+
projectName: path22.basename(projectDir),
|
|
8921
9254
|
exitCode,
|
|
8922
9255
|
provider: config.provider
|
|
8923
9256
|
};
|
|
@@ -8949,7 +9282,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
8949
9282
|
const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
|
|
8950
9283
|
const _ctx = {
|
|
8951
9284
|
event,
|
|
8952
|
-
projectName:
|
|
9285
|
+
projectName: path22.basename(projectDir),
|
|
8953
9286
|
exitCode,
|
|
8954
9287
|
provider: config.provider,
|
|
8955
9288
|
prdName: scriptResult?.data.prd,
|
|
@@ -9111,20 +9444,20 @@ function applyCliOverrides(config, options) {
|
|
|
9111
9444
|
return overridden;
|
|
9112
9445
|
}
|
|
9113
9446
|
function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
9114
|
-
const absolutePrdDir =
|
|
9115
|
-
const doneDir =
|
|
9447
|
+
const absolutePrdDir = path22.join(projectDir, prdDir);
|
|
9448
|
+
const doneDir = path22.join(absolutePrdDir, "done");
|
|
9116
9449
|
const pending = [];
|
|
9117
9450
|
const completed = [];
|
|
9118
|
-
if (
|
|
9119
|
-
const entries =
|
|
9451
|
+
if (fs22.existsSync(absolutePrdDir)) {
|
|
9452
|
+
const entries = fs22.readdirSync(absolutePrdDir, { withFileTypes: true });
|
|
9120
9453
|
for (const entry of entries) {
|
|
9121
9454
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9122
|
-
const claimPath =
|
|
9455
|
+
const claimPath = path22.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
|
|
9123
9456
|
let claimed = false;
|
|
9124
9457
|
let claimInfo = null;
|
|
9125
|
-
if (
|
|
9458
|
+
if (fs22.existsSync(claimPath)) {
|
|
9126
9459
|
try {
|
|
9127
|
-
const content =
|
|
9460
|
+
const content = fs22.readFileSync(claimPath, "utf-8");
|
|
9128
9461
|
const data = JSON.parse(content);
|
|
9129
9462
|
const age = Math.floor(Date.now() / 1e3) - data.timestamp;
|
|
9130
9463
|
if (age < maxRuntime) {
|
|
@@ -9138,8 +9471,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
|
9138
9471
|
}
|
|
9139
9472
|
}
|
|
9140
9473
|
}
|
|
9141
|
-
if (
|
|
9142
|
-
const entries =
|
|
9474
|
+
if (fs22.existsSync(doneDir)) {
|
|
9475
|
+
const entries = fs22.readdirSync(doneDir, { withFileTypes: true });
|
|
9143
9476
|
for (const entry of entries) {
|
|
9144
9477
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9145
9478
|
completed.push(entry.name);
|
|
@@ -9299,7 +9632,7 @@ ${stderr}`);
|
|
|
9299
9632
|
// src/commands/review.ts
|
|
9300
9633
|
init_dist();
|
|
9301
9634
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
9302
|
-
import * as
|
|
9635
|
+
import * as path23 from "path";
|
|
9303
9636
|
function shouldSendReviewNotification(scriptStatus) {
|
|
9304
9637
|
if (!scriptStatus) {
|
|
9305
9638
|
return true;
|
|
@@ -9309,6 +9642,15 @@ function shouldSendReviewNotification(scriptStatus) {
|
|
|
9309
9642
|
}
|
|
9310
9643
|
return !scriptStatus.startsWith("skip_");
|
|
9311
9644
|
}
|
|
9645
|
+
function shouldSendReviewCompletionNotification(exitCode, scriptStatus) {
|
|
9646
|
+
if (exitCode !== 0) {
|
|
9647
|
+
return false;
|
|
9648
|
+
}
|
|
9649
|
+
if (scriptStatus === "failure" || scriptStatus === "timeout") {
|
|
9650
|
+
return false;
|
|
9651
|
+
}
|
|
9652
|
+
return shouldSendReviewNotification(scriptStatus);
|
|
9653
|
+
}
|
|
9312
9654
|
function parseAutoMergedPrNumbers(raw) {
|
|
9313
9655
|
if (!raw || raw.trim().length === 0) {
|
|
9314
9656
|
return [];
|
|
@@ -9390,10 +9732,6 @@ function buildEnvVars2(config, options) {
|
|
|
9390
9732
|
env.NW_BRANCH_PATTERNS = config.branchPatterns.join(",");
|
|
9391
9733
|
env.NW_PRD_DIR = config.prdDir;
|
|
9392
9734
|
env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? "sonnet"];
|
|
9393
|
-
if (config.autoMerge) {
|
|
9394
|
-
env.NW_AUTO_MERGE = "1";
|
|
9395
|
-
}
|
|
9396
|
-
env.NW_AUTO_MERGE_METHOD = config.autoMergeMethod;
|
|
9397
9735
|
return env;
|
|
9398
9736
|
}
|
|
9399
9737
|
function applyCliOverrides2(config, options) {
|
|
@@ -9407,9 +9745,6 @@ function applyCliOverrides2(config, options) {
|
|
|
9407
9745
|
if (options.provider) {
|
|
9408
9746
|
overridden._cliProviderOverride = options.provider;
|
|
9409
9747
|
}
|
|
9410
|
-
if (options.autoMerge !== void 0) {
|
|
9411
|
-
overridden.autoMerge = options.autoMerge;
|
|
9412
|
-
}
|
|
9413
9748
|
return overridden;
|
|
9414
9749
|
}
|
|
9415
9750
|
function isFailingCheck(check) {
|
|
@@ -9470,7 +9805,7 @@ function getOpenPrsNeedingWork(branchPatterns) {
|
|
|
9470
9805
|
}
|
|
9471
9806
|
}
|
|
9472
9807
|
function reviewCommand(program2) {
|
|
9473
|
-
program2.command("review").description("Run PR reviewer now").option("--dry-run", "Show what would be executed without running").option("--timeout <seconds>", "Override max runtime in seconds for reviewer").option("--provider <string>", "AI provider to use (claude or codex)").
|
|
9808
|
+
program2.command("review").description("Run PR reviewer now").option("--dry-run", "Show what would be executed without running").option("--timeout <seconds>", "Override max runtime in seconds for reviewer").option("--provider <string>", "AI provider to use (claude or codex)").action(async (options) => {
|
|
9474
9809
|
const projectDir = process.cwd();
|
|
9475
9810
|
let config = loadConfig(projectDir);
|
|
9476
9811
|
config = applyCliOverrides2(config, options);
|
|
@@ -9493,10 +9828,6 @@ function reviewCommand(program2) {
|
|
|
9493
9828
|
]);
|
|
9494
9829
|
configTable.push(["Min Review Score", `${config.minReviewScore}/100`]);
|
|
9495
9830
|
configTable.push(["Branch Patterns", config.branchPatterns.join(", ")]);
|
|
9496
|
-
configTable.push([
|
|
9497
|
-
"Auto-merge",
|
|
9498
|
-
config.autoMerge ? `Enabled (${config.autoMergeMethod})` : "Disabled"
|
|
9499
|
-
]);
|
|
9500
9831
|
configTable.push(["Max Retry Attempts", String(config.reviewerMaxRetries)]);
|
|
9501
9832
|
configTable.push(["Retry Delay", `${config.reviewerRetryDelay}s`]);
|
|
9502
9833
|
configTable.push([
|
|
@@ -9567,12 +9898,15 @@ ${stderr}`);
|
|
|
9567
9898
|
spinner.fail(`PR reviewer exited with code ${exitCode}`);
|
|
9568
9899
|
}
|
|
9569
9900
|
if (!options.dryRun) {
|
|
9570
|
-
const
|
|
9571
|
-
|
|
9572
|
-
|
|
9901
|
+
const shouldNotifyCompletion = shouldSendReviewCompletionNotification(
|
|
9902
|
+
exitCode,
|
|
9903
|
+
scriptResult?.status
|
|
9904
|
+
);
|
|
9905
|
+
if (!shouldNotifyCompletion) {
|
|
9906
|
+
info("Skipping review completion notification (review did not complete successfully)");
|
|
9573
9907
|
}
|
|
9574
9908
|
let fallbackPrDetails = null;
|
|
9575
|
-
if (
|
|
9909
|
+
if (shouldNotifyCompletion) {
|
|
9576
9910
|
const reviewedPrNumbers = parseReviewedPrNumbers(scriptResult?.data.prs);
|
|
9577
9911
|
const firstReviewedPrNumber = reviewedPrNumbers[0];
|
|
9578
9912
|
if (firstReviewedPrNumber !== void 0) {
|
|
@@ -9582,7 +9916,7 @@ ${stderr}`);
|
|
|
9582
9916
|
fallbackPrDetails = fetchReviewedPrDetails(config.branchPatterns, projectDir);
|
|
9583
9917
|
}
|
|
9584
9918
|
}
|
|
9585
|
-
if (
|
|
9919
|
+
if (shouldNotifyCompletion) {
|
|
9586
9920
|
const attempts = parseRetryAttempts(scriptResult?.data.attempts);
|
|
9587
9921
|
const finalScore = parseFinalReviewScore(scriptResult?.data.final_score);
|
|
9588
9922
|
const legacyNoChangesNeeded = scriptResult?.data.no_changes_needed === "1";
|
|
@@ -9598,7 +9932,7 @@ ${stderr}`);
|
|
|
9598
9932
|
const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
9599
9933
|
await sendNotifications(config, {
|
|
9600
9934
|
event: reviewEvent,
|
|
9601
|
-
projectName:
|
|
9935
|
+
projectName: path23.basename(projectDir),
|
|
9602
9936
|
exitCode,
|
|
9603
9937
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9604
9938
|
prUrl: fallbackPrDetails?.url,
|
|
@@ -9620,7 +9954,7 @@ ${stderr}`);
|
|
|
9620
9954
|
const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
9621
9955
|
await sendNotifications(config, {
|
|
9622
9956
|
event: reviewEvent,
|
|
9623
|
-
projectName:
|
|
9957
|
+
projectName: path23.basename(projectDir),
|
|
9624
9958
|
exitCode,
|
|
9625
9959
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9626
9960
|
prUrl: prDetails?.url,
|
|
@@ -9642,7 +9976,7 @@ ${stderr}`);
|
|
|
9642
9976
|
const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
|
|
9643
9977
|
const _mergeCtx = {
|
|
9644
9978
|
event: "pr_auto_merged",
|
|
9645
|
-
projectName:
|
|
9979
|
+
projectName: path23.basename(projectDir),
|
|
9646
9980
|
exitCode,
|
|
9647
9981
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9648
9982
|
prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
|
|
@@ -9667,7 +10001,7 @@ ${stderr}`);
|
|
|
9667
10001
|
|
|
9668
10002
|
// src/commands/qa.ts
|
|
9669
10003
|
init_dist();
|
|
9670
|
-
import * as
|
|
10004
|
+
import * as path24 from "path";
|
|
9671
10005
|
function shouldSendQaNotification(scriptStatus) {
|
|
9672
10006
|
if (!scriptStatus) {
|
|
9673
10007
|
return true;
|
|
@@ -9808,7 +10142,7 @@ ${stderr}`);
|
|
|
9808
10142
|
const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
|
|
9809
10143
|
const _qaCtx = {
|
|
9810
10144
|
event: "qa_completed",
|
|
9811
|
-
projectName:
|
|
10145
|
+
projectName: path24.basename(projectDir),
|
|
9812
10146
|
exitCode,
|
|
9813
10147
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9814
10148
|
prNumber: prDetails?.number ?? primaryQaPr,
|
|
@@ -9834,8 +10168,8 @@ ${stderr}`);
|
|
|
9834
10168
|
|
|
9835
10169
|
// src/commands/audit.ts
|
|
9836
10170
|
init_dist();
|
|
9837
|
-
import * as
|
|
9838
|
-
import * as
|
|
10171
|
+
import * as fs23 from "fs";
|
|
10172
|
+
import * as path25 from "path";
|
|
9839
10173
|
function buildEnvVars4(config, options) {
|
|
9840
10174
|
const env = buildBaseEnvVars(config, "audit", options.dryRun);
|
|
9841
10175
|
env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
|
|
@@ -9878,7 +10212,8 @@ function auditCommand(program2) {
|
|
|
9878
10212
|
configTable.push(["Provider", auditProvider]);
|
|
9879
10213
|
configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
|
|
9880
10214
|
configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
|
|
9881
|
-
configTable.push(["
|
|
10215
|
+
configTable.push(["Target Column", config.audit.targetColumn]);
|
|
10216
|
+
configTable.push(["Report File", path25.join(projectDir, "logs", "audit-report.md")]);
|
|
9882
10217
|
console.log(configTable.toString());
|
|
9883
10218
|
header("Provider Invocation");
|
|
9884
10219
|
const providerCmd = PROVIDER_COMMANDS[auditProvider];
|
|
@@ -9913,21 +10248,27 @@ ${stderr}`);
|
|
|
9913
10248
|
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
9914
10249
|
spinner.succeed("Code audit skipped");
|
|
9915
10250
|
} else {
|
|
9916
|
-
const reportPath =
|
|
9917
|
-
if (!
|
|
10251
|
+
const reportPath = path25.join(projectDir, "logs", "audit-report.md");
|
|
10252
|
+
if (!fs23.existsSync(reportPath)) {
|
|
9918
10253
|
spinner.fail("Code audit finished without a report file");
|
|
9919
10254
|
process.exit(1);
|
|
9920
10255
|
}
|
|
9921
|
-
|
|
10256
|
+
const syncResult = await syncAuditFindingsToBoard(config, projectDir);
|
|
10257
|
+
const message = `Code audit complete \u2014 report written to ${reportPath}; ${syncResult.summary}`;
|
|
10258
|
+
if (syncResult.status === "failed" || syncResult.status === "partial") {
|
|
10259
|
+
spinner.warn(message);
|
|
10260
|
+
} else {
|
|
10261
|
+
spinner.succeed(message);
|
|
10262
|
+
}
|
|
9922
10263
|
}
|
|
9923
10264
|
} else {
|
|
9924
10265
|
const statusSuffix = scriptResult?.status ? ` (${scriptResult.status})` : "";
|
|
9925
10266
|
const providerExit = scriptResult?.data?.provider_exit;
|
|
9926
10267
|
const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
|
|
9927
10268
|
spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
|
|
9928
|
-
const logPath =
|
|
9929
|
-
if (
|
|
9930
|
-
const logLines =
|
|
10269
|
+
const logPath = path25.join(projectDir, "logs", "audit.log");
|
|
10270
|
+
if (fs23.existsSync(logPath)) {
|
|
10271
|
+
const logLines = fs23.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
|
|
9931
10272
|
if (logLines.length > 0) {
|
|
9932
10273
|
process.stderr.write(logLines.join("\n") + "\n");
|
|
9933
10274
|
}
|
|
@@ -10001,16 +10342,16 @@ function analyticsCommand(program2) {
|
|
|
10001
10342
|
// src/commands/install.ts
|
|
10002
10343
|
init_dist();
|
|
10003
10344
|
import { execSync as execSync4 } from "child_process";
|
|
10004
|
-
import * as
|
|
10005
|
-
import * as
|
|
10345
|
+
import * as path26 from "path";
|
|
10346
|
+
import * as fs24 from "fs";
|
|
10006
10347
|
function shellQuote(value) {
|
|
10007
10348
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
10008
10349
|
}
|
|
10009
10350
|
function getNightWatchBinPath() {
|
|
10010
10351
|
try {
|
|
10011
10352
|
const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
|
|
10012
|
-
const binPath =
|
|
10013
|
-
if (
|
|
10353
|
+
const binPath = path26.join(npmBin, "night-watch");
|
|
10354
|
+
if (fs24.existsSync(binPath)) {
|
|
10014
10355
|
return binPath;
|
|
10015
10356
|
}
|
|
10016
10357
|
} catch {
|
|
@@ -10023,17 +10364,17 @@ function getNightWatchBinPath() {
|
|
|
10023
10364
|
}
|
|
10024
10365
|
function getNodeBinDir() {
|
|
10025
10366
|
if (process.execPath && process.execPath !== "node") {
|
|
10026
|
-
return
|
|
10367
|
+
return path26.dirname(process.execPath);
|
|
10027
10368
|
}
|
|
10028
10369
|
try {
|
|
10029
10370
|
const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
|
|
10030
|
-
return
|
|
10371
|
+
return path26.dirname(nodePath);
|
|
10031
10372
|
} catch {
|
|
10032
10373
|
return "";
|
|
10033
10374
|
}
|
|
10034
10375
|
}
|
|
10035
10376
|
function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
|
|
10036
|
-
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ?
|
|
10377
|
+
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path26.dirname(nightWatchBin) : "";
|
|
10037
10378
|
const pathParts = Array.from(
|
|
10038
10379
|
new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
|
|
10039
10380
|
);
|
|
@@ -10049,12 +10390,12 @@ function performInstall(projectDir, config, options) {
|
|
|
10049
10390
|
const nightWatchBin = getNightWatchBinPath();
|
|
10050
10391
|
const projectName = getProjectName(projectDir);
|
|
10051
10392
|
const marker = generateMarker(projectName);
|
|
10052
|
-
const logDir =
|
|
10053
|
-
if (!
|
|
10054
|
-
|
|
10393
|
+
const logDir = path26.join(projectDir, LOG_DIR);
|
|
10394
|
+
if (!fs24.existsSync(logDir)) {
|
|
10395
|
+
fs24.mkdirSync(logDir, { recursive: true });
|
|
10055
10396
|
}
|
|
10056
|
-
const executorLog =
|
|
10057
|
-
const reviewerLog =
|
|
10397
|
+
const executorLog = path26.join(logDir, "executor.log");
|
|
10398
|
+
const reviewerLog = path26.join(logDir, "reviewer.log");
|
|
10058
10399
|
if (!options?.force) {
|
|
10059
10400
|
const existingEntries2 = Array.from(
|
|
10060
10401
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
@@ -10091,7 +10432,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10091
10432
|
const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
10092
10433
|
if (installSlicer) {
|
|
10093
10434
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
10094
|
-
const slicerLog =
|
|
10435
|
+
const slicerLog = path26.join(logDir, "slicer.log");
|
|
10095
10436
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
10096
10437
|
entries.push(slicerEntry);
|
|
10097
10438
|
}
|
|
@@ -10099,7 +10440,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10099
10440
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
10100
10441
|
if (installQa) {
|
|
10101
10442
|
const qaSchedule = config.qa.schedule;
|
|
10102
|
-
const qaLog =
|
|
10443
|
+
const qaLog = path26.join(logDir, "qa.log");
|
|
10103
10444
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
10104
10445
|
entries.push(qaEntry);
|
|
10105
10446
|
}
|
|
@@ -10107,7 +10448,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10107
10448
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
10108
10449
|
if (installAudit) {
|
|
10109
10450
|
const auditSchedule = config.audit.schedule;
|
|
10110
|
-
const auditLog =
|
|
10451
|
+
const auditLog = path26.join(logDir, "audit.log");
|
|
10111
10452
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
10112
10453
|
entries.push(auditEntry);
|
|
10113
10454
|
}
|
|
@@ -10115,7 +10456,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10115
10456
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
10116
10457
|
if (installAnalytics) {
|
|
10117
10458
|
const analyticsSchedule = config.analytics.schedule;
|
|
10118
|
-
const analyticsLog =
|
|
10459
|
+
const analyticsLog = path26.join(logDir, "analytics.log");
|
|
10119
10460
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
10120
10461
|
entries.push(analyticsEntry);
|
|
10121
10462
|
}
|
|
@@ -10123,10 +10464,18 @@ function performInstall(projectDir, config, options) {
|
|
|
10123
10464
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10124
10465
|
if (installPrResolver) {
|
|
10125
10466
|
const prResolverSchedule = config.prResolver.schedule;
|
|
10126
|
-
const prResolverLog =
|
|
10467
|
+
const prResolverLog = path26.join(logDir, "pr-resolver.log");
|
|
10127
10468
|
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10128
10469
|
entries.push(prResolverEntry);
|
|
10129
10470
|
}
|
|
10471
|
+
const disableMerger = options?.noMerger === true || options?.merger === false;
|
|
10472
|
+
const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
|
|
10473
|
+
if (installMerger) {
|
|
10474
|
+
const mergerSchedule = config.merger.schedule;
|
|
10475
|
+
const mergerLog = path26.join(logDir, "merger.log");
|
|
10476
|
+
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
|
|
10477
|
+
entries.push(mergerEntry);
|
|
10478
|
+
}
|
|
10130
10479
|
const existingEntries = new Set(
|
|
10131
10480
|
Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
|
|
10132
10481
|
);
|
|
@@ -10145,7 +10494,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10145
10494
|
}
|
|
10146
10495
|
}
|
|
10147
10496
|
function installCommand(program2) {
|
|
10148
|
-
program2.command("install").description("Add crontab entries for automated execution").option("-s, --schedule <cron>", "Cron schedule for PRD executor").option("--reviewer-schedule <cron>", "Cron schedule for reviewer").option("--no-reviewer", "Skip installing reviewer cron").option("--no-slicer", "Skip installing slicer cron").option("--no-qa", "Skip installing QA cron").option("--no-audit", "Skip installing audit cron").option("--no-analytics", "Skip installing analytics cron").option("--no-pr-resolver", "Skip installing PR resolver cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
|
|
10497
|
+
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) => {
|
|
10149
10498
|
try {
|
|
10150
10499
|
const projectDir = process.cwd();
|
|
10151
10500
|
const config = loadConfig(projectDir);
|
|
@@ -10154,12 +10503,12 @@ function installCommand(program2) {
|
|
|
10154
10503
|
const nightWatchBin = getNightWatchBinPath();
|
|
10155
10504
|
const projectName = getProjectName(projectDir);
|
|
10156
10505
|
const marker = generateMarker(projectName);
|
|
10157
|
-
const logDir =
|
|
10158
|
-
if (!
|
|
10159
|
-
|
|
10506
|
+
const logDir = path26.join(projectDir, LOG_DIR);
|
|
10507
|
+
if (!fs24.existsSync(logDir)) {
|
|
10508
|
+
fs24.mkdirSync(logDir, { recursive: true });
|
|
10160
10509
|
}
|
|
10161
|
-
const executorLog =
|
|
10162
|
-
const reviewerLog =
|
|
10510
|
+
const executorLog = path26.join(logDir, "executor.log");
|
|
10511
|
+
const reviewerLog = path26.join(logDir, "reviewer.log");
|
|
10163
10512
|
const existingEntries = Array.from(
|
|
10164
10513
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
10165
10514
|
);
|
|
@@ -10195,7 +10544,7 @@ function installCommand(program2) {
|
|
|
10195
10544
|
const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
10196
10545
|
let slicerLog;
|
|
10197
10546
|
if (installSlicer) {
|
|
10198
|
-
slicerLog =
|
|
10547
|
+
slicerLog = path26.join(logDir, "slicer.log");
|
|
10199
10548
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
10200
10549
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
10201
10550
|
entries.push(slicerEntry);
|
|
@@ -10204,7 +10553,7 @@ function installCommand(program2) {
|
|
|
10204
10553
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
10205
10554
|
let qaLog;
|
|
10206
10555
|
if (installQa) {
|
|
10207
|
-
qaLog =
|
|
10556
|
+
qaLog = path26.join(logDir, "qa.log");
|
|
10208
10557
|
const qaSchedule = config.qa.schedule;
|
|
10209
10558
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
10210
10559
|
entries.push(qaEntry);
|
|
@@ -10213,7 +10562,7 @@ function installCommand(program2) {
|
|
|
10213
10562
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
10214
10563
|
let auditLog;
|
|
10215
10564
|
if (installAudit) {
|
|
10216
|
-
auditLog =
|
|
10565
|
+
auditLog = path26.join(logDir, "audit.log");
|
|
10217
10566
|
const auditSchedule = config.audit.schedule;
|
|
10218
10567
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
10219
10568
|
entries.push(auditEntry);
|
|
@@ -10222,7 +10571,7 @@ function installCommand(program2) {
|
|
|
10222
10571
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
10223
10572
|
let analyticsLog;
|
|
10224
10573
|
if (installAnalytics) {
|
|
10225
|
-
analyticsLog =
|
|
10574
|
+
analyticsLog = path26.join(logDir, "analytics.log");
|
|
10226
10575
|
const analyticsSchedule = config.analytics.schedule;
|
|
10227
10576
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
10228
10577
|
entries.push(analyticsEntry);
|
|
@@ -10231,11 +10580,20 @@ function installCommand(program2) {
|
|
|
10231
10580
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10232
10581
|
let prResolverLog;
|
|
10233
10582
|
if (installPrResolver) {
|
|
10234
|
-
prResolverLog =
|
|
10583
|
+
prResolverLog = path26.join(logDir, "pr-resolver.log");
|
|
10235
10584
|
const prResolverSchedule = config.prResolver.schedule;
|
|
10236
10585
|
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10237
10586
|
entries.push(prResolverEntry);
|
|
10238
10587
|
}
|
|
10588
|
+
const disableMerger = options.noMerger === true || options.merger === false;
|
|
10589
|
+
const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
|
|
10590
|
+
let mergerLog;
|
|
10591
|
+
if (installMerger) {
|
|
10592
|
+
mergerLog = path26.join(logDir, "merger.log");
|
|
10593
|
+
const mergerSchedule = config.merger.schedule;
|
|
10594
|
+
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
|
|
10595
|
+
entries.push(mergerEntry);
|
|
10596
|
+
}
|
|
10239
10597
|
const existingEntrySet = new Set(existingEntries);
|
|
10240
10598
|
const currentCrontab = readCrontab();
|
|
10241
10599
|
const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
|
|
@@ -10268,6 +10626,9 @@ function installCommand(program2) {
|
|
|
10268
10626
|
if (installPrResolver && prResolverLog) {
|
|
10269
10627
|
dim(` PR Resolver: ${prResolverLog}`);
|
|
10270
10628
|
}
|
|
10629
|
+
if (installMerger && mergerLog) {
|
|
10630
|
+
dim(` Merger: ${mergerLog}`);
|
|
10631
|
+
}
|
|
10271
10632
|
console.log();
|
|
10272
10633
|
dim("To uninstall, run: night-watch uninstall");
|
|
10273
10634
|
dim("To check status, run: night-watch status");
|
|
@@ -10282,8 +10643,8 @@ function installCommand(program2) {
|
|
|
10282
10643
|
|
|
10283
10644
|
// src/commands/uninstall.ts
|
|
10284
10645
|
init_dist();
|
|
10285
|
-
import * as
|
|
10286
|
-
import * as
|
|
10646
|
+
import * as path27 from "path";
|
|
10647
|
+
import * as fs25 from "fs";
|
|
10287
10648
|
function performUninstall(projectDir, options) {
|
|
10288
10649
|
try {
|
|
10289
10650
|
const projectName = getProjectName(projectDir);
|
|
@@ -10298,8 +10659,8 @@ function performUninstall(projectDir, options) {
|
|
|
10298
10659
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
10299
10660
|
unregisterProject(projectDir);
|
|
10300
10661
|
if (!options?.keepLogs) {
|
|
10301
|
-
const logDir =
|
|
10302
|
-
if (
|
|
10662
|
+
const logDir = path27.join(projectDir, "logs");
|
|
10663
|
+
if (fs25.existsSync(logDir)) {
|
|
10303
10664
|
const logFiles = [
|
|
10304
10665
|
"executor.log",
|
|
10305
10666
|
"reviewer.log",
|
|
@@ -10308,15 +10669,15 @@ function performUninstall(projectDir, options) {
|
|
|
10308
10669
|
"pr-resolver.log"
|
|
10309
10670
|
];
|
|
10310
10671
|
logFiles.forEach((logFile) => {
|
|
10311
|
-
const logPath =
|
|
10312
|
-
if (
|
|
10313
|
-
|
|
10672
|
+
const logPath = path27.join(logDir, logFile);
|
|
10673
|
+
if (fs25.existsSync(logPath)) {
|
|
10674
|
+
fs25.unlinkSync(logPath);
|
|
10314
10675
|
}
|
|
10315
10676
|
});
|
|
10316
10677
|
try {
|
|
10317
|
-
const remainingFiles =
|
|
10678
|
+
const remainingFiles = fs25.readdirSync(logDir);
|
|
10318
10679
|
if (remainingFiles.length === 0) {
|
|
10319
|
-
|
|
10680
|
+
fs25.rmdirSync(logDir);
|
|
10320
10681
|
}
|
|
10321
10682
|
} catch {
|
|
10322
10683
|
}
|
|
@@ -10349,8 +10710,8 @@ function uninstallCommand(program2) {
|
|
|
10349
10710
|
existingEntries.forEach((entry) => dim(` ${entry}`));
|
|
10350
10711
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
10351
10712
|
if (!options.keepLogs) {
|
|
10352
|
-
const logDir =
|
|
10353
|
-
if (
|
|
10713
|
+
const logDir = path27.join(projectDir, "logs");
|
|
10714
|
+
if (fs25.existsSync(logDir)) {
|
|
10354
10715
|
const logFiles = [
|
|
10355
10716
|
"executor.log",
|
|
10356
10717
|
"reviewer.log",
|
|
@@ -10360,16 +10721,16 @@ function uninstallCommand(program2) {
|
|
|
10360
10721
|
];
|
|
10361
10722
|
let logsRemoved = 0;
|
|
10362
10723
|
logFiles.forEach((logFile) => {
|
|
10363
|
-
const logPath =
|
|
10364
|
-
if (
|
|
10365
|
-
|
|
10724
|
+
const logPath = path27.join(logDir, logFile);
|
|
10725
|
+
if (fs25.existsSync(logPath)) {
|
|
10726
|
+
fs25.unlinkSync(logPath);
|
|
10366
10727
|
logsRemoved++;
|
|
10367
10728
|
}
|
|
10368
10729
|
});
|
|
10369
10730
|
try {
|
|
10370
|
-
const remainingFiles =
|
|
10731
|
+
const remainingFiles = fs25.readdirSync(logDir);
|
|
10371
10732
|
if (remainingFiles.length === 0) {
|
|
10372
|
-
|
|
10733
|
+
fs25.rmdirSync(logDir);
|
|
10373
10734
|
}
|
|
10374
10735
|
} catch {
|
|
10375
10736
|
}
|
|
@@ -10413,11 +10774,15 @@ function statusCommand(program2) {
|
|
|
10413
10774
|
const qaProc = snapshot.processes.find((p) => p.name === "qa");
|
|
10414
10775
|
const auditProc = snapshot.processes.find((p) => p.name === "audit");
|
|
10415
10776
|
const plannerProc = snapshot.processes.find((p) => p.name === "planner");
|
|
10777
|
+
const analyticsProc = snapshot.processes.find((p) => p.name === "analytics");
|
|
10778
|
+
const mergerProc = snapshot.processes.find((p) => p.name === "merger");
|
|
10416
10779
|
const executorLog = snapshot.logs.find((l) => l.name === "executor");
|
|
10417
10780
|
const reviewerLog = snapshot.logs.find((l) => l.name === "reviewer");
|
|
10418
10781
|
const qaLog = snapshot.logs.find((l) => l.name === "qa");
|
|
10419
10782
|
const auditLog = snapshot.logs.find((l) => l.name === "audit");
|
|
10420
10783
|
const plannerLog = snapshot.logs.find((l) => l.name === "planner");
|
|
10784
|
+
const analyticsLog = snapshot.logs.find((l) => l.name === "analytics");
|
|
10785
|
+
const mergerLog = snapshot.logs.find((l) => l.name === "merger");
|
|
10421
10786
|
const pendingPrds = snapshot.prds.filter(
|
|
10422
10787
|
(p) => p.status === "ready" || p.status === "blocked"
|
|
10423
10788
|
).length;
|
|
@@ -10435,6 +10800,8 @@ function statusCommand(program2) {
|
|
|
10435
10800
|
qa: { running: qaProc?.running ?? false, pid: qaProc?.pid ?? null },
|
|
10436
10801
|
audit: { running: auditProc?.running ?? false, pid: auditProc?.pid ?? null },
|
|
10437
10802
|
planner: { running: plannerProc?.running ?? false, pid: plannerProc?.pid ?? null },
|
|
10803
|
+
analytics: { running: analyticsProc?.running ?? false, pid: analyticsProc?.pid ?? null },
|
|
10804
|
+
merger: { running: mergerProc?.running ?? false, pid: mergerProc?.pid ?? null },
|
|
10438
10805
|
prds: { pending: pendingPrds, claimed: claimedPrds, done: donePrds },
|
|
10439
10806
|
prs: { open: snapshot.prs.length },
|
|
10440
10807
|
crontab: snapshot.crontab,
|
|
@@ -10468,6 +10835,18 @@ function statusCommand(program2) {
|
|
|
10468
10835
|
lastLines: plannerLog.lastLines,
|
|
10469
10836
|
exists: plannerLog.exists,
|
|
10470
10837
|
size: plannerLog.size
|
|
10838
|
+
} : void 0,
|
|
10839
|
+
analytics: analyticsLog ? {
|
|
10840
|
+
path: analyticsLog.path,
|
|
10841
|
+
lastLines: analyticsLog.lastLines,
|
|
10842
|
+
exists: analyticsLog.exists,
|
|
10843
|
+
size: analyticsLog.size
|
|
10844
|
+
} : void 0,
|
|
10845
|
+
merger: mergerLog ? {
|
|
10846
|
+
path: mergerLog.path,
|
|
10847
|
+
lastLines: mergerLog.lastLines,
|
|
10848
|
+
exists: mergerLog.exists,
|
|
10849
|
+
size: mergerLog.size
|
|
10471
10850
|
} : void 0
|
|
10472
10851
|
}
|
|
10473
10852
|
};
|
|
@@ -10505,6 +10884,14 @@ function statusCommand(program2) {
|
|
|
10505
10884
|
"Planner",
|
|
10506
10885
|
formatRunningStatus(status.planner.running, status.planner.pid)
|
|
10507
10886
|
]);
|
|
10887
|
+
processTable.push([
|
|
10888
|
+
"Analytics",
|
|
10889
|
+
formatRunningStatus(status.analytics.running, status.analytics.pid)
|
|
10890
|
+
]);
|
|
10891
|
+
processTable.push([
|
|
10892
|
+
"Merger",
|
|
10893
|
+
formatRunningStatus(status.merger.running, status.merger.pid)
|
|
10894
|
+
]);
|
|
10508
10895
|
console.log(processTable.toString());
|
|
10509
10896
|
header("PRD Status");
|
|
10510
10897
|
const prdTable = createTable({ head: ["Status", "Count"] });
|
|
@@ -10561,6 +10948,20 @@ function statusCommand(program2) {
|
|
|
10561
10948
|
status.logs.planner.exists ? "Exists" : "Not found"
|
|
10562
10949
|
]);
|
|
10563
10950
|
}
|
|
10951
|
+
if (status.logs.analytics) {
|
|
10952
|
+
logTable.push([
|
|
10953
|
+
"Analytics",
|
|
10954
|
+
status.logs.analytics.exists ? formatBytes(status.logs.analytics.size) : "-",
|
|
10955
|
+
status.logs.analytics.exists ? "Exists" : "Not found"
|
|
10956
|
+
]);
|
|
10957
|
+
}
|
|
10958
|
+
if (status.logs.merger) {
|
|
10959
|
+
logTable.push([
|
|
10960
|
+
"Merger",
|
|
10961
|
+
status.logs.merger.exists ? formatBytes(status.logs.merger.size) : "-",
|
|
10962
|
+
status.logs.merger.exists ? "Exists" : "Not found"
|
|
10963
|
+
]);
|
|
10964
|
+
}
|
|
10564
10965
|
console.log(logTable.toString());
|
|
10565
10966
|
if (options.verbose) {
|
|
10566
10967
|
if (status.logs.executor?.exists && status.logs.executor.lastLines.length > 0) {
|
|
@@ -10583,6 +10984,14 @@ function statusCommand(program2) {
|
|
|
10583
10984
|
dim(" Planner last 5 lines:");
|
|
10584
10985
|
status.logs.planner.lastLines.forEach((line) => dim(` ${line}`));
|
|
10585
10986
|
}
|
|
10987
|
+
if (status.logs.analytics?.exists && status.logs.analytics.lastLines.length > 0) {
|
|
10988
|
+
dim(" Analytics last 5 lines:");
|
|
10989
|
+
status.logs.analytics.lastLines.forEach((line) => dim(` ${line}`));
|
|
10990
|
+
}
|
|
10991
|
+
if (status.logs.merger?.exists && status.logs.merger.lastLines.length > 0) {
|
|
10992
|
+
dim(" Merger last 5 lines:");
|
|
10993
|
+
status.logs.merger.lastLines.forEach((line) => dim(` ${line}`));
|
|
10994
|
+
}
|
|
10586
10995
|
}
|
|
10587
10996
|
header("Commands");
|
|
10588
10997
|
dim(" night-watch install - Install crontab entries");
|
|
@@ -10592,6 +11001,8 @@ function statusCommand(program2) {
|
|
|
10592
11001
|
dim(" night-watch qa - Run QA now");
|
|
10593
11002
|
dim(" night-watch audit - Run audit now");
|
|
10594
11003
|
dim(" night-watch planner - Run planner now");
|
|
11004
|
+
dim(" night-watch analytics - Run analytics now");
|
|
11005
|
+
dim(" night-watch merge - Run merger now");
|
|
10595
11006
|
console.log();
|
|
10596
11007
|
} catch (error2) {
|
|
10597
11008
|
console.error(
|
|
@@ -10605,14 +11016,14 @@ function statusCommand(program2) {
|
|
|
10605
11016
|
// src/commands/logs.ts
|
|
10606
11017
|
init_dist();
|
|
10607
11018
|
import { spawn as spawn3 } from "child_process";
|
|
10608
|
-
import * as
|
|
10609
|
-
import * as
|
|
11019
|
+
import * as path28 from "path";
|
|
11020
|
+
import * as fs26 from "fs";
|
|
10610
11021
|
function getLastLines(filePath, lineCount) {
|
|
10611
|
-
if (!
|
|
11022
|
+
if (!fs26.existsSync(filePath)) {
|
|
10612
11023
|
return `Log file not found: ${filePath}`;
|
|
10613
11024
|
}
|
|
10614
11025
|
try {
|
|
10615
|
-
const content =
|
|
11026
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
10616
11027
|
const lines = content.trim().split("\n");
|
|
10617
11028
|
return lines.slice(-lineCount).join("\n");
|
|
10618
11029
|
} catch (error2) {
|
|
@@ -10620,7 +11031,7 @@ function getLastLines(filePath, lineCount) {
|
|
|
10620
11031
|
}
|
|
10621
11032
|
}
|
|
10622
11033
|
function followLog(filePath) {
|
|
10623
|
-
if (!
|
|
11034
|
+
if (!fs26.existsSync(filePath)) {
|
|
10624
11035
|
console.log(`Log file not found: ${filePath}`);
|
|
10625
11036
|
console.log("The log file will be created when the first execution runs.");
|
|
10626
11037
|
return;
|
|
@@ -10637,32 +11048,42 @@ function followLog(filePath) {
|
|
|
10637
11048
|
});
|
|
10638
11049
|
}
|
|
10639
11050
|
function logsCommand(program2) {
|
|
10640
|
-
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(
|
|
11051
|
+
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(
|
|
11052
|
+
"-t, --type <type>",
|
|
11053
|
+
"Log type to view (executor|reviewer|qa|audit|planner|analytics|merger|all)",
|
|
11054
|
+
"all"
|
|
11055
|
+
).action(async (options) => {
|
|
10641
11056
|
try {
|
|
10642
11057
|
const projectDir = process.cwd();
|
|
10643
|
-
const logDir =
|
|
11058
|
+
const logDir = path28.join(projectDir, LOG_DIR);
|
|
10644
11059
|
const lineCount = parseInt(options.lines || "50", 10);
|
|
10645
|
-
const executorLog =
|
|
10646
|
-
const reviewerLog =
|
|
10647
|
-
const qaLog =
|
|
10648
|
-
const auditLog =
|
|
10649
|
-
const plannerLog =
|
|
11060
|
+
const executorLog = path28.join(logDir, EXECUTOR_LOG_FILE);
|
|
11061
|
+
const reviewerLog = path28.join(logDir, REVIEWER_LOG_FILE);
|
|
11062
|
+
const qaLog = path28.join(logDir, `${QA_LOG_NAME}.log`);
|
|
11063
|
+
const auditLog = path28.join(logDir, `${AUDIT_LOG_NAME}.log`);
|
|
11064
|
+
const plannerLog = path28.join(logDir, `${PLANNER_LOG_NAME}.log`);
|
|
11065
|
+
const analyticsLog = path28.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
|
|
11066
|
+
const mergerLog = path28.join(logDir, `${MERGER_LOG_NAME}.log`);
|
|
10650
11067
|
const logType = options.type?.toLowerCase() || "all";
|
|
10651
11068
|
const showExecutor = logType === "all" || logType === "run" || logType === "executor";
|
|
10652
11069
|
const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
|
|
10653
11070
|
const showQa = logType === "all" || logType === "qa";
|
|
10654
11071
|
const showAudit = logType === "all" || logType === "audit";
|
|
10655
11072
|
const showPlanner = logType === "all" || logType === "planner" || logType === "slice" || logType === "slicer";
|
|
11073
|
+
const showAnalytics = logType === "all" || logType === "analytics";
|
|
11074
|
+
const showMerger = logType === "all" || logType === "merge" || logType === "merger";
|
|
10656
11075
|
if (options.follow) {
|
|
10657
11076
|
if (logType === "all") {
|
|
10658
11077
|
dim("Note: Following all logs is not supported. Showing executor log.");
|
|
10659
|
-
dim("Use --type reviewer|qa|audit|planner for other logs.\n");
|
|
11078
|
+
dim("Use --type reviewer|qa|audit|planner|analytics|merger for other logs.\n");
|
|
10660
11079
|
}
|
|
10661
11080
|
let targetLog = executorLog;
|
|
10662
11081
|
if (showReviewer) targetLog = reviewerLog;
|
|
10663
11082
|
else if (showQa) targetLog = qaLog;
|
|
10664
11083
|
else if (showAudit) targetLog = auditLog;
|
|
10665
11084
|
else if (showPlanner) targetLog = plannerLog;
|
|
11085
|
+
else if (showAnalytics) targetLog = analyticsLog;
|
|
11086
|
+
else if (showMerger) targetLog = mergerLog;
|
|
10666
11087
|
followLog(targetLog);
|
|
10667
11088
|
return;
|
|
10668
11089
|
}
|
|
@@ -10697,10 +11118,24 @@ function logsCommand(program2) {
|
|
|
10697
11118
|
console.log();
|
|
10698
11119
|
console.log(getLastLines(plannerLog, lineCount));
|
|
10699
11120
|
}
|
|
11121
|
+
if (showAnalytics) {
|
|
11122
|
+
header("Analytics Log");
|
|
11123
|
+
dim(`File: ${analyticsLog}`);
|
|
11124
|
+
console.log();
|
|
11125
|
+
console.log(getLastLines(analyticsLog, lineCount));
|
|
11126
|
+
}
|
|
11127
|
+
if (showMerger) {
|
|
11128
|
+
header("Merger Log");
|
|
11129
|
+
dim(`File: ${mergerLog}`);
|
|
11130
|
+
console.log();
|
|
11131
|
+
console.log(getLastLines(mergerLog, lineCount));
|
|
11132
|
+
}
|
|
10700
11133
|
console.log();
|
|
10701
11134
|
dim("---");
|
|
10702
11135
|
dim("Tip: Use -f to follow logs in real-time");
|
|
10703
|
-
dim(
|
|
11136
|
+
dim(
|
|
11137
|
+
" Use --type executor|reviewer|qa|audit|planner|analytics|merger to view specific logs"
|
|
11138
|
+
);
|
|
10704
11139
|
} catch (err) {
|
|
10705
11140
|
console.error(`Error reading logs: ${err instanceof Error ? err.message : String(err)}`);
|
|
10706
11141
|
process.exit(1);
|
|
@@ -10711,8 +11146,8 @@ function logsCommand(program2) {
|
|
|
10711
11146
|
// src/commands/prd.ts
|
|
10712
11147
|
init_dist();
|
|
10713
11148
|
import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
|
|
10714
|
-
import * as
|
|
10715
|
-
import * as
|
|
11149
|
+
import * as fs27 from "fs";
|
|
11150
|
+
import * as path29 from "path";
|
|
10716
11151
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10717
11152
|
import { dirname as dirname9 } from "path";
|
|
10718
11153
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
@@ -10720,21 +11155,21 @@ var __dirname3 = dirname9(__filename3);
|
|
|
10720
11155
|
function findTemplatesDir2(startDir) {
|
|
10721
11156
|
let current = startDir;
|
|
10722
11157
|
for (let i = 0; i < 8; i++) {
|
|
10723
|
-
const candidate =
|
|
10724
|
-
if (
|
|
11158
|
+
const candidate = path29.join(current, "templates");
|
|
11159
|
+
if (fs27.existsSync(candidate) && fs27.statSync(candidate).isDirectory()) {
|
|
10725
11160
|
return candidate;
|
|
10726
11161
|
}
|
|
10727
|
-
current =
|
|
11162
|
+
current = path29.dirname(current);
|
|
10728
11163
|
}
|
|
10729
|
-
return
|
|
11164
|
+
return path29.join(startDir, "templates");
|
|
10730
11165
|
}
|
|
10731
11166
|
var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
|
|
10732
11167
|
function slugify2(name) {
|
|
10733
11168
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
10734
11169
|
}
|
|
10735
11170
|
function getNextPrdNumber2(prdDir) {
|
|
10736
|
-
if (!
|
|
10737
|
-
const files =
|
|
11171
|
+
if (!fs27.existsSync(prdDir)) return 1;
|
|
11172
|
+
const files = fs27.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
10738
11173
|
const numbers = files.map((f) => {
|
|
10739
11174
|
const match = f.match(/^(\d+)-/);
|
|
10740
11175
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -10815,13 +11250,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
|
|
|
10815
11250
|
return null;
|
|
10816
11251
|
}
|
|
10817
11252
|
const ref = branch && branch !== "HEAD" ? branch : "main";
|
|
10818
|
-
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(
|
|
11253
|
+
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path29.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
10819
11254
|
} catch {
|
|
10820
11255
|
return null;
|
|
10821
11256
|
}
|
|
10822
11257
|
}
|
|
10823
11258
|
function buildGithubIssueBody(prdPath, projectDir, prdContent) {
|
|
10824
|
-
const relPath =
|
|
11259
|
+
const relPath = path29.relative(projectDir, prdPath);
|
|
10825
11260
|
const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
|
|
10826
11261
|
const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
|
|
10827
11262
|
return `${fileLine}
|
|
@@ -10832,13 +11267,13 @@ ${prdContent}
|
|
|
10832
11267
|
Created via \`night-watch prd create\`.`;
|
|
10833
11268
|
}
|
|
10834
11269
|
async function generatePrdWithClaude(description, projectDir, model) {
|
|
10835
|
-
const bundledTemplatePath =
|
|
10836
|
-
const installedTemplatePath =
|
|
10837
|
-
const templatePath =
|
|
10838
|
-
if (!
|
|
11270
|
+
const bundledTemplatePath = path29.join(TEMPLATES_DIR2, "prd-creator.md");
|
|
11271
|
+
const installedTemplatePath = path29.join(projectDir, "instructions", "prd-creator.md");
|
|
11272
|
+
const templatePath = fs27.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
|
|
11273
|
+
if (!fs27.existsSync(templatePath)) {
|
|
10839
11274
|
return null;
|
|
10840
11275
|
}
|
|
10841
|
-
const planningPrinciples =
|
|
11276
|
+
const planningPrinciples = fs27.readFileSync(templatePath, "utf-8");
|
|
10842
11277
|
const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
|
|
10843
11278
|
const modelId = model ?? CLAUDE_MODEL_IDS.opus;
|
|
10844
11279
|
const env = buildNativeClaudeEnv(process.env);
|
|
@@ -10906,17 +11341,17 @@ function runGh(args, cwd) {
|
|
|
10906
11341
|
return null;
|
|
10907
11342
|
}
|
|
10908
11343
|
function createGithubIssue(title, prdPath, projectDir, prdContent) {
|
|
10909
|
-
const tmpFile =
|
|
11344
|
+
const tmpFile = path29.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
|
|
10910
11345
|
try {
|
|
10911
11346
|
const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
|
|
10912
|
-
|
|
11347
|
+
fs27.writeFileSync(tmpFile, body, "utf-8");
|
|
10913
11348
|
const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
|
|
10914
11349
|
return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
|
|
10915
11350
|
} catch {
|
|
10916
11351
|
return null;
|
|
10917
11352
|
} finally {
|
|
10918
11353
|
try {
|
|
10919
|
-
|
|
11354
|
+
fs27.unlinkSync(tmpFile);
|
|
10920
11355
|
} catch {
|
|
10921
11356
|
}
|
|
10922
11357
|
}
|
|
@@ -10928,10 +11363,10 @@ function parseDependencies(content) {
|
|
|
10928
11363
|
}
|
|
10929
11364
|
function isClaimActive(claimPath, maxRuntime) {
|
|
10930
11365
|
try {
|
|
10931
|
-
if (!
|
|
11366
|
+
if (!fs27.existsSync(claimPath)) {
|
|
10932
11367
|
return { active: false };
|
|
10933
11368
|
}
|
|
10934
|
-
const content =
|
|
11369
|
+
const content = fs27.readFileSync(claimPath, "utf-8");
|
|
10935
11370
|
const claim = JSON.parse(content);
|
|
10936
11371
|
const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
|
|
10937
11372
|
if (age < maxRuntime) {
|
|
@@ -10946,9 +11381,9 @@ function prdCommand(program2) {
|
|
|
10946
11381
|
const prd = program2.command("prd").description("Manage PRD files");
|
|
10947
11382
|
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) => {
|
|
10948
11383
|
const projectDir = process.cwd();
|
|
10949
|
-
const prdDir =
|
|
10950
|
-
if (!
|
|
10951
|
-
|
|
11384
|
+
const prdDir = path29.join(projectDir, resolvePrdCreateDir());
|
|
11385
|
+
if (!fs27.existsSync(prdDir)) {
|
|
11386
|
+
fs27.mkdirSync(prdDir, { recursive: true });
|
|
10952
11387
|
}
|
|
10953
11388
|
const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
|
|
10954
11389
|
const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
|
|
@@ -10962,13 +11397,13 @@ function prdCommand(program2) {
|
|
|
10962
11397
|
const prdTitle = extractPrdTitle(generated) ?? name;
|
|
10963
11398
|
const slug = slugify2(prdTitle);
|
|
10964
11399
|
const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
|
|
10965
|
-
const filePath =
|
|
10966
|
-
if (
|
|
11400
|
+
const filePath = path29.join(prdDir, filename);
|
|
11401
|
+
if (fs27.existsSync(filePath)) {
|
|
10967
11402
|
error(`File already exists: ${filePath}`);
|
|
10968
11403
|
dim("Use a different name or remove the existing file.");
|
|
10969
11404
|
process.exit(1);
|
|
10970
11405
|
}
|
|
10971
|
-
|
|
11406
|
+
fs27.writeFileSync(filePath, generated, "utf-8");
|
|
10972
11407
|
header("PRD Created");
|
|
10973
11408
|
success(`Created: ${filePath}`);
|
|
10974
11409
|
const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
|
|
@@ -10981,15 +11416,15 @@ function prdCommand(program2) {
|
|
|
10981
11416
|
prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
|
|
10982
11417
|
const projectDir = process.cwd();
|
|
10983
11418
|
const config = loadConfig(projectDir);
|
|
10984
|
-
const absolutePrdDir =
|
|
10985
|
-
const doneDir =
|
|
11419
|
+
const absolutePrdDir = path29.join(projectDir, config.prdDir);
|
|
11420
|
+
const doneDir = path29.join(absolutePrdDir, "done");
|
|
10986
11421
|
const pending = [];
|
|
10987
|
-
if (
|
|
10988
|
-
const files =
|
|
11422
|
+
if (fs27.existsSync(absolutePrdDir)) {
|
|
11423
|
+
const files = fs27.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
|
|
10989
11424
|
for (const file of files) {
|
|
10990
|
-
const content =
|
|
11425
|
+
const content = fs27.readFileSync(path29.join(absolutePrdDir, file), "utf-8");
|
|
10991
11426
|
const deps = parseDependencies(content);
|
|
10992
|
-
const claimPath =
|
|
11427
|
+
const claimPath = path29.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
|
|
10993
11428
|
const claimStatus = isClaimActive(claimPath, config.maxRuntime);
|
|
10994
11429
|
pending.push({
|
|
10995
11430
|
name: file,
|
|
@@ -11000,10 +11435,10 @@ function prdCommand(program2) {
|
|
|
11000
11435
|
}
|
|
11001
11436
|
}
|
|
11002
11437
|
const done = [];
|
|
11003
|
-
if (
|
|
11004
|
-
const files =
|
|
11438
|
+
if (fs27.existsSync(doneDir)) {
|
|
11439
|
+
const files = fs27.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
11005
11440
|
for (const file of files) {
|
|
11006
|
-
const content =
|
|
11441
|
+
const content = fs27.readFileSync(path29.join(doneDir, file), "utf-8");
|
|
11007
11442
|
const deps = parseDependencies(content);
|
|
11008
11443
|
done.push({ name: file, dependencies: deps });
|
|
11009
11444
|
}
|
|
@@ -11040,7 +11475,7 @@ import blessed6 from "blessed";
|
|
|
11040
11475
|
// src/commands/dashboard/tab-status.ts
|
|
11041
11476
|
init_dist();
|
|
11042
11477
|
import blessed from "blessed";
|
|
11043
|
-
import * as
|
|
11478
|
+
import * as fs28 from "fs";
|
|
11044
11479
|
function sortPrdsByPriority(prds, priority) {
|
|
11045
11480
|
if (priority.length === 0) return prds;
|
|
11046
11481
|
const priorityMap = /* @__PURE__ */ new Map();
|
|
@@ -11136,7 +11571,7 @@ function renderLogPane(projectDir, logs) {
|
|
|
11136
11571
|
let newestMtime = 0;
|
|
11137
11572
|
for (const log of existingLogs) {
|
|
11138
11573
|
try {
|
|
11139
|
-
const stat =
|
|
11574
|
+
const stat = fs28.statSync(log.path);
|
|
11140
11575
|
if (stat.mtimeMs > newestMtime) {
|
|
11141
11576
|
newestMtime = stat.mtimeMs;
|
|
11142
11577
|
newestLog = log;
|
|
@@ -12791,8 +13226,8 @@ function createActionsTab() {
|
|
|
12791
13226
|
// src/commands/dashboard/tab-logs.ts
|
|
12792
13227
|
init_dist();
|
|
12793
13228
|
import blessed5 from "blessed";
|
|
12794
|
-
import * as
|
|
12795
|
-
import * as
|
|
13229
|
+
import * as fs29 from "fs";
|
|
13230
|
+
import * as path30 from "path";
|
|
12796
13231
|
var LOG_NAMES = ["executor", "reviewer"];
|
|
12797
13232
|
var LOG_LINES = 200;
|
|
12798
13233
|
function createLogsTab() {
|
|
@@ -12833,7 +13268,7 @@ function createLogsTab() {
|
|
|
12833
13268
|
let activeKeyHandlers = [];
|
|
12834
13269
|
let activeCtx = null;
|
|
12835
13270
|
function getLogPath(projectDir, logName) {
|
|
12836
|
-
return
|
|
13271
|
+
return path30.join(projectDir, "logs", `${logName}.log`);
|
|
12837
13272
|
}
|
|
12838
13273
|
function updateSelector() {
|
|
12839
13274
|
const tabs = LOG_NAMES.map((name, idx) => {
|
|
@@ -12847,7 +13282,7 @@ function createLogsTab() {
|
|
|
12847
13282
|
function loadLog(ctx) {
|
|
12848
13283
|
const logName = LOG_NAMES[selectedLogIndex];
|
|
12849
13284
|
const logPath = getLogPath(ctx.projectDir, logName);
|
|
12850
|
-
if (!
|
|
13285
|
+
if (!fs29.existsSync(logPath)) {
|
|
12851
13286
|
logContent.setContent(
|
|
12852
13287
|
`{yellow-fg}No ${logName}.log file found{/yellow-fg}
|
|
12853
13288
|
|
|
@@ -12857,7 +13292,7 @@ Log will appear here once the ${logName} runs.`
|
|
|
12857
13292
|
return;
|
|
12858
13293
|
}
|
|
12859
13294
|
try {
|
|
12860
|
-
const stat =
|
|
13295
|
+
const stat = fs29.statSync(logPath);
|
|
12861
13296
|
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
12862
13297
|
logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
|
|
12863
13298
|
} catch {
|
|
@@ -13355,12 +13790,12 @@ function doctorCommand(program2) {
|
|
|
13355
13790
|
|
|
13356
13791
|
// src/commands/serve.ts
|
|
13357
13792
|
init_dist();
|
|
13358
|
-
import * as
|
|
13793
|
+
import * as fs34 from "fs";
|
|
13359
13794
|
|
|
13360
13795
|
// ../server/dist/index.js
|
|
13361
13796
|
init_dist();
|
|
13362
|
-
import * as
|
|
13363
|
-
import * as
|
|
13797
|
+
import * as fs33 from "fs";
|
|
13798
|
+
import * as path36 from "path";
|
|
13364
13799
|
import { dirname as dirname11 } from "path";
|
|
13365
13800
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
13366
13801
|
import cors from "cors";
|
|
@@ -13445,8 +13880,8 @@ function setupGracefulShutdown(server, beforeClose) {
|
|
|
13445
13880
|
|
|
13446
13881
|
// ../server/dist/middleware/project-resolver.middleware.js
|
|
13447
13882
|
init_dist();
|
|
13448
|
-
import * as
|
|
13449
|
-
import * as
|
|
13883
|
+
import * as fs30 from "fs";
|
|
13884
|
+
import * as path31 from "path";
|
|
13450
13885
|
function resolveProject(req, res, next) {
|
|
13451
13886
|
const projectId = req.params.projectId;
|
|
13452
13887
|
const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
|
|
@@ -13456,7 +13891,7 @@ function resolveProject(req, res, next) {
|
|
|
13456
13891
|
res.status(404).json({ error: `Project not found: ${decodedId}` });
|
|
13457
13892
|
return;
|
|
13458
13893
|
}
|
|
13459
|
-
if (!
|
|
13894
|
+
if (!fs30.existsSync(entry.path) || !fs30.existsSync(path31.join(entry.path, CONFIG_FILE_NAME))) {
|
|
13460
13895
|
res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
|
|
13461
13896
|
return;
|
|
13462
13897
|
}
|
|
@@ -13501,8 +13936,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
|
|
|
13501
13936
|
|
|
13502
13937
|
// ../server/dist/routes/action.routes.js
|
|
13503
13938
|
init_dist();
|
|
13504
|
-
import * as
|
|
13505
|
-
import * as
|
|
13939
|
+
import * as fs31 from "fs";
|
|
13940
|
+
import * as path32 from "path";
|
|
13506
13941
|
import { execSync as execSync6, spawn as spawn6 } from "child_process";
|
|
13507
13942
|
import { Router } from "express";
|
|
13508
13943
|
|
|
@@ -13540,17 +13975,17 @@ function getBoardProvider(config, projectDir) {
|
|
|
13540
13975
|
function cleanOrphanedClaims(dir) {
|
|
13541
13976
|
let entries;
|
|
13542
13977
|
try {
|
|
13543
|
-
entries =
|
|
13978
|
+
entries = fs31.readdirSync(dir, { withFileTypes: true });
|
|
13544
13979
|
} catch {
|
|
13545
13980
|
return;
|
|
13546
13981
|
}
|
|
13547
13982
|
for (const entry of entries) {
|
|
13548
|
-
const fullPath =
|
|
13983
|
+
const fullPath = path32.join(dir, entry.name);
|
|
13549
13984
|
if (entry.isDirectory() && entry.name !== "done") {
|
|
13550
13985
|
cleanOrphanedClaims(fullPath);
|
|
13551
13986
|
} else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
|
|
13552
13987
|
try {
|
|
13553
|
-
|
|
13988
|
+
fs31.unlinkSync(fullPath);
|
|
13554
13989
|
} catch {
|
|
13555
13990
|
}
|
|
13556
13991
|
}
|
|
@@ -13705,19 +14140,19 @@ function createActionRouteHandlers(ctx) {
|
|
|
13705
14140
|
res.status(400).json({ error: "Invalid PRD name" });
|
|
13706
14141
|
return;
|
|
13707
14142
|
}
|
|
13708
|
-
const prdDir =
|
|
14143
|
+
const prdDir = path32.join(projectDir, config.prdDir);
|
|
13709
14144
|
const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
|
|
13710
|
-
const pendingPath =
|
|
13711
|
-
const donePath =
|
|
13712
|
-
if (
|
|
14145
|
+
const pendingPath = path32.join(prdDir, normalized);
|
|
14146
|
+
const donePath = path32.join(prdDir, "done", normalized);
|
|
14147
|
+
if (fs31.existsSync(pendingPath)) {
|
|
13713
14148
|
res.json({ message: `"${normalized}" is already pending` });
|
|
13714
14149
|
return;
|
|
13715
14150
|
}
|
|
13716
|
-
if (!
|
|
14151
|
+
if (!fs31.existsSync(donePath)) {
|
|
13717
14152
|
res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
|
|
13718
14153
|
return;
|
|
13719
14154
|
}
|
|
13720
|
-
|
|
14155
|
+
fs31.renameSync(donePath, pendingPath);
|
|
13721
14156
|
res.json({ message: `Moved "${normalized}" back to pending` });
|
|
13722
14157
|
} catch (error2) {
|
|
13723
14158
|
res.status(500).json({
|
|
@@ -13735,11 +14170,11 @@ function createActionRouteHandlers(ctx) {
|
|
|
13735
14170
|
res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
|
|
13736
14171
|
return;
|
|
13737
14172
|
}
|
|
13738
|
-
if (
|
|
13739
|
-
|
|
14173
|
+
if (fs31.existsSync(lockPath)) {
|
|
14174
|
+
fs31.unlinkSync(lockPath);
|
|
13740
14175
|
}
|
|
13741
|
-
const prdDir =
|
|
13742
|
-
if (
|
|
14176
|
+
const prdDir = path32.join(projectDir, config.prdDir);
|
|
14177
|
+
if (fs31.existsSync(prdDir)) {
|
|
13743
14178
|
cleanOrphanedClaims(prdDir);
|
|
13744
14179
|
}
|
|
13745
14180
|
broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
|
|
@@ -14294,6 +14729,9 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14294
14729
|
if (audit.maxRuntime !== void 0 && (typeof audit.maxRuntime !== "number" || audit.maxRuntime < 60)) {
|
|
14295
14730
|
return "audit.maxRuntime must be a number >= 60";
|
|
14296
14731
|
}
|
|
14732
|
+
if (audit.targetColumn !== void 0 && !BOARD_COLUMNS.includes(audit.targetColumn)) {
|
|
14733
|
+
return `audit.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
|
|
14734
|
+
}
|
|
14297
14735
|
}
|
|
14298
14736
|
if (changes.analytics !== void 0) {
|
|
14299
14737
|
if (typeof changes.analytics !== "object" || changes.analytics === null) {
|
|
@@ -14314,9 +14752,8 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14314
14752
|
return "analytics.lookbackDays must be a number between 1 and 90";
|
|
14315
14753
|
}
|
|
14316
14754
|
if (analytics.targetColumn !== void 0) {
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
return `analytics.targetColumn must be one of: ${validColumns.join(", ")}`;
|
|
14755
|
+
if (!BOARD_COLUMNS.includes(analytics.targetColumn)) {
|
|
14756
|
+
return `analytics.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
|
|
14320
14757
|
}
|
|
14321
14758
|
}
|
|
14322
14759
|
if (analytics.analysisPrompt !== void 0 && typeof analytics.analysisPrompt !== "string") {
|
|
@@ -14341,16 +14778,8 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14341
14778
|
if (typeof queue.priority !== "object" || queue.priority === null) {
|
|
14342
14779
|
return "queue.priority must be an object";
|
|
14343
14780
|
}
|
|
14344
|
-
const validQueueJobs = [
|
|
14345
|
-
"executor",
|
|
14346
|
-
"reviewer",
|
|
14347
|
-
"qa",
|
|
14348
|
-
"audit",
|
|
14349
|
-
"slicer",
|
|
14350
|
-
"analytics"
|
|
14351
|
-
];
|
|
14352
14781
|
for (const [jobType, value] of Object.entries(queue.priority)) {
|
|
14353
|
-
if (!
|
|
14782
|
+
if (!VALID_JOB_TYPES.includes(jobType)) {
|
|
14354
14783
|
return `queue.priority contains invalid job type: ${jobType}`;
|
|
14355
14784
|
}
|
|
14356
14785
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
@@ -14504,8 +14933,8 @@ function createProjectConfigRoutes() {
|
|
|
14504
14933
|
|
|
14505
14934
|
// ../server/dist/routes/doctor.routes.js
|
|
14506
14935
|
init_dist();
|
|
14507
|
-
import * as
|
|
14508
|
-
import * as
|
|
14936
|
+
import * as fs32 from "fs";
|
|
14937
|
+
import * as path33 from "path";
|
|
14509
14938
|
import { execSync as execSync7 } from "child_process";
|
|
14510
14939
|
import { Router as Router4 } from "express";
|
|
14511
14940
|
function runDoctorChecks(projectDir, config) {
|
|
@@ -14538,7 +14967,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14538
14967
|
});
|
|
14539
14968
|
}
|
|
14540
14969
|
try {
|
|
14541
|
-
const projectName =
|
|
14970
|
+
const projectName = path33.basename(projectDir);
|
|
14542
14971
|
const marker = generateMarker(projectName);
|
|
14543
14972
|
const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
|
|
14544
14973
|
if (crontabEntries.length > 0) {
|
|
@@ -14561,8 +14990,8 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14561
14990
|
detail: "Failed to check crontab"
|
|
14562
14991
|
});
|
|
14563
14992
|
}
|
|
14564
|
-
const configPath =
|
|
14565
|
-
if (
|
|
14993
|
+
const configPath = path33.join(projectDir, CONFIG_FILE_NAME);
|
|
14994
|
+
if (fs32.existsSync(configPath)) {
|
|
14566
14995
|
checks.push({ name: "config", status: "pass", detail: "Config file exists" });
|
|
14567
14996
|
} else {
|
|
14568
14997
|
checks.push({
|
|
@@ -14571,9 +15000,9 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14571
15000
|
detail: "Config file not found (using defaults)"
|
|
14572
15001
|
});
|
|
14573
15002
|
}
|
|
14574
|
-
const prdDir =
|
|
14575
|
-
if (
|
|
14576
|
-
const prds =
|
|
15003
|
+
const prdDir = path33.join(projectDir, config.prdDir);
|
|
15004
|
+
if (fs32.existsSync(prdDir)) {
|
|
15005
|
+
const prds = fs32.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
14577
15006
|
checks.push({
|
|
14578
15007
|
name: "prdDir",
|
|
14579
15008
|
status: "pass",
|
|
@@ -14616,7 +15045,7 @@ function createProjectDoctorRoutes() {
|
|
|
14616
15045
|
|
|
14617
15046
|
// ../server/dist/routes/log.routes.js
|
|
14618
15047
|
init_dist();
|
|
14619
|
-
import * as
|
|
15048
|
+
import * as path34 from "path";
|
|
14620
15049
|
import { Router as Router5 } from "express";
|
|
14621
15050
|
function createLogRoutes(deps) {
|
|
14622
15051
|
const { projectDir } = deps;
|
|
@@ -14624,7 +15053,7 @@ function createLogRoutes(deps) {
|
|
|
14624
15053
|
router.get("/:name", (req, res) => {
|
|
14625
15054
|
try {
|
|
14626
15055
|
const { name } = req.params;
|
|
14627
|
-
const validNames =
|
|
15056
|
+
const validNames = Object.keys(LOG_FILE_NAMES);
|
|
14628
15057
|
if (!validNames.includes(name)) {
|
|
14629
15058
|
res.status(400).json({
|
|
14630
15059
|
error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
|
|
@@ -14635,7 +15064,7 @@ function createLogRoutes(deps) {
|
|
|
14635
15064
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
14636
15065
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
14637
15066
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
14638
|
-
const logPath =
|
|
15067
|
+
const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
14639
15068
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
14640
15069
|
res.json({ name, lines: logLines });
|
|
14641
15070
|
} catch (error2) {
|
|
@@ -14650,7 +15079,7 @@ function createProjectLogRoutes() {
|
|
|
14650
15079
|
try {
|
|
14651
15080
|
const projectDir = req.projectDir;
|
|
14652
15081
|
const { name } = req.params;
|
|
14653
|
-
const validNames =
|
|
15082
|
+
const validNames = Object.keys(LOG_FILE_NAMES);
|
|
14654
15083
|
if (!validNames.includes(name)) {
|
|
14655
15084
|
res.status(400).json({
|
|
14656
15085
|
error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
|
|
@@ -14661,7 +15090,7 @@ function createProjectLogRoutes() {
|
|
|
14661
15090
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
14662
15091
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
14663
15092
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
14664
|
-
const logPath =
|
|
15093
|
+
const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
14665
15094
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
14666
15095
|
res.json({ name, lines: logLines });
|
|
14667
15096
|
} catch (error2) {
|
|
@@ -14696,7 +15125,7 @@ function createProjectPrdRoutes() {
|
|
|
14696
15125
|
|
|
14697
15126
|
// ../server/dist/routes/roadmap.routes.js
|
|
14698
15127
|
init_dist();
|
|
14699
|
-
import * as
|
|
15128
|
+
import * as path35 from "path";
|
|
14700
15129
|
import { Router as Router7 } from "express";
|
|
14701
15130
|
function createRoadmapRouteHandlers(ctx) {
|
|
14702
15131
|
const router = Router7({ mergeParams: true });
|
|
@@ -14706,7 +15135,7 @@ function createRoadmapRouteHandlers(ctx) {
|
|
|
14706
15135
|
const config = ctx.getConfig(req);
|
|
14707
15136
|
const projectDir = ctx.getProjectDir(req);
|
|
14708
15137
|
const status = getRoadmapStatus(projectDir, config);
|
|
14709
|
-
const prdDir =
|
|
15138
|
+
const prdDir = path35.join(projectDir, config.prdDir);
|
|
14710
15139
|
const state = loadRoadmapState(prdDir);
|
|
14711
15140
|
res.json({
|
|
14712
15141
|
...status,
|
|
@@ -14831,12 +15260,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
14831
15260
|
const auditPlan = getSchedulingPlan(projectDir, config, "audit");
|
|
14832
15261
|
const plannerPlan = getSchedulingPlan(projectDir, config, "slicer");
|
|
14833
15262
|
const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
|
|
15263
|
+
const mergerPlan = getSchedulingPlan(projectDir, config, "merger");
|
|
14834
15264
|
const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
|
|
14835
15265
|
const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
|
|
14836
15266
|
const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
|
|
14837
15267
|
const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
|
|
14838
15268
|
const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
|
|
14839
15269
|
const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
|
|
15270
|
+
const mergerInstalled = installed && (config.merger?.enabled ?? false) && hasScheduledCommand(entries, "merge");
|
|
14840
15271
|
return {
|
|
14841
15272
|
executor: {
|
|
14842
15273
|
schedule: config.cronSchedule,
|
|
@@ -14886,6 +15317,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
14886
15317
|
manualDelayMinutes: analyticsPlan.manualDelayMinutes,
|
|
14887
15318
|
balancedDelayMinutes: analyticsPlan.balancedDelayMinutes
|
|
14888
15319
|
},
|
|
15320
|
+
merger: {
|
|
15321
|
+
schedule: config.merger?.schedule ?? "55 */4 * * *",
|
|
15322
|
+
installed: mergerInstalled,
|
|
15323
|
+
nextRun: mergerInstalled ? addDelayToIsoString(computeNextRun(config.merger?.schedule ?? "55 */4 * * *"), mergerPlan.totalDelayMinutes) : null,
|
|
15324
|
+
delayMinutes: mergerPlan.totalDelayMinutes,
|
|
15325
|
+
manualDelayMinutes: mergerPlan.manualDelayMinutes,
|
|
15326
|
+
balancedDelayMinutes: mergerPlan.balancedDelayMinutes
|
|
15327
|
+
},
|
|
14889
15328
|
paused: !installed,
|
|
14890
15329
|
schedulingPriority: config.schedulingPriority,
|
|
14891
15330
|
entries
|
|
@@ -15040,14 +15479,14 @@ function createQueueRoutes(deps) {
|
|
|
15040
15479
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
15041
15480
|
var __dirname4 = dirname11(__filename4);
|
|
15042
15481
|
function resolveWebDistPath() {
|
|
15043
|
-
const bundled =
|
|
15044
|
-
if (
|
|
15482
|
+
const bundled = path36.join(__dirname4, "web");
|
|
15483
|
+
if (fs33.existsSync(path36.join(bundled, "index.html")))
|
|
15045
15484
|
return bundled;
|
|
15046
15485
|
let d = __dirname4;
|
|
15047
15486
|
for (let i = 0; i < 8; i++) {
|
|
15048
|
-
if (
|
|
15049
|
-
const dev =
|
|
15050
|
-
if (
|
|
15487
|
+
if (fs33.existsSync(path36.join(d, "turbo.json"))) {
|
|
15488
|
+
const dev = path36.join(d, "web/dist");
|
|
15489
|
+
if (fs33.existsSync(path36.join(dev, "index.html")))
|
|
15051
15490
|
return dev;
|
|
15052
15491
|
break;
|
|
15053
15492
|
}
|
|
@@ -15057,7 +15496,7 @@ function resolveWebDistPath() {
|
|
|
15057
15496
|
}
|
|
15058
15497
|
function setupStaticFiles(app) {
|
|
15059
15498
|
const webDistPath = resolveWebDistPath();
|
|
15060
|
-
if (
|
|
15499
|
+
if (fs33.existsSync(webDistPath)) {
|
|
15061
15500
|
app.use(express.static(webDistPath));
|
|
15062
15501
|
}
|
|
15063
15502
|
app.use((req, res, next) => {
|
|
@@ -15065,8 +15504,8 @@ function setupStaticFiles(app) {
|
|
|
15065
15504
|
next();
|
|
15066
15505
|
return;
|
|
15067
15506
|
}
|
|
15068
|
-
const indexPath =
|
|
15069
|
-
if (
|
|
15507
|
+
const indexPath = path36.resolve(webDistPath, "index.html");
|
|
15508
|
+
if (fs33.existsSync(indexPath)) {
|
|
15070
15509
|
res.sendFile(indexPath, (err) => {
|
|
15071
15510
|
if (err)
|
|
15072
15511
|
next();
|
|
@@ -15199,7 +15638,7 @@ function createGlobalApp() {
|
|
|
15199
15638
|
return app;
|
|
15200
15639
|
}
|
|
15201
15640
|
function bootContainer() {
|
|
15202
|
-
initContainer(
|
|
15641
|
+
initContainer(path36.dirname(getDbPath()));
|
|
15203
15642
|
}
|
|
15204
15643
|
function startServer(projectDir, port) {
|
|
15205
15644
|
bootContainer();
|
|
@@ -15252,8 +15691,8 @@ function isProcessRunning2(pid) {
|
|
|
15252
15691
|
}
|
|
15253
15692
|
function readPid(lockPath) {
|
|
15254
15693
|
try {
|
|
15255
|
-
if (!
|
|
15256
|
-
const raw =
|
|
15694
|
+
if (!fs34.existsSync(lockPath)) return null;
|
|
15695
|
+
const raw = fs34.readFileSync(lockPath, "utf-8").trim();
|
|
15257
15696
|
const pid = parseInt(raw, 10);
|
|
15258
15697
|
return Number.isFinite(pid) ? pid : null;
|
|
15259
15698
|
} catch {
|
|
@@ -15265,10 +15704,10 @@ function acquireServeLock(mode, port) {
|
|
|
15265
15704
|
let stalePidCleaned;
|
|
15266
15705
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
15267
15706
|
try {
|
|
15268
|
-
const fd =
|
|
15269
|
-
|
|
15707
|
+
const fd = fs34.openSync(lockPath, "wx");
|
|
15708
|
+
fs34.writeFileSync(fd, `${process.pid}
|
|
15270
15709
|
`);
|
|
15271
|
-
|
|
15710
|
+
fs34.closeSync(fd);
|
|
15272
15711
|
return { acquired: true, lockPath, stalePidCleaned };
|
|
15273
15712
|
} catch (error2) {
|
|
15274
15713
|
const err = error2;
|
|
@@ -15289,7 +15728,7 @@ function acquireServeLock(mode, port) {
|
|
|
15289
15728
|
};
|
|
15290
15729
|
}
|
|
15291
15730
|
try {
|
|
15292
|
-
|
|
15731
|
+
fs34.unlinkSync(lockPath);
|
|
15293
15732
|
if (existingPid) {
|
|
15294
15733
|
stalePidCleaned = existingPid;
|
|
15295
15734
|
}
|
|
@@ -15312,10 +15751,10 @@ function acquireServeLock(mode, port) {
|
|
|
15312
15751
|
}
|
|
15313
15752
|
function releaseServeLock(lockPath) {
|
|
15314
15753
|
try {
|
|
15315
|
-
if (!
|
|
15754
|
+
if (!fs34.existsSync(lockPath)) return;
|
|
15316
15755
|
const lockPid = readPid(lockPath);
|
|
15317
15756
|
if (lockPid !== null && lockPid !== process.pid) return;
|
|
15318
|
-
|
|
15757
|
+
fs34.unlinkSync(lockPath);
|
|
15319
15758
|
} catch {
|
|
15320
15759
|
}
|
|
15321
15760
|
}
|
|
@@ -15411,14 +15850,14 @@ function historyCommand(program2) {
|
|
|
15411
15850
|
// src/commands/update.ts
|
|
15412
15851
|
init_dist();
|
|
15413
15852
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
15414
|
-
import * as
|
|
15415
|
-
import * as
|
|
15853
|
+
import * as fs35 from "fs";
|
|
15854
|
+
import * as path37 from "path";
|
|
15416
15855
|
var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
|
|
15417
15856
|
function parseProjectDirs(projects, cwd) {
|
|
15418
15857
|
if (!projects || projects.trim().length === 0) {
|
|
15419
15858
|
return [cwd];
|
|
15420
15859
|
}
|
|
15421
|
-
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) =>
|
|
15860
|
+
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path37.resolve(cwd, entry));
|
|
15422
15861
|
return Array.from(new Set(dirs));
|
|
15423
15862
|
}
|
|
15424
15863
|
function shouldInstallGlobal(options) {
|
|
@@ -15460,7 +15899,7 @@ function updateCommand(program2) {
|
|
|
15460
15899
|
}
|
|
15461
15900
|
const nightWatchBin = resolveNightWatchBin();
|
|
15462
15901
|
for (const projectDir of projectDirs) {
|
|
15463
|
-
if (!
|
|
15902
|
+
if (!fs35.existsSync(projectDir) || !fs35.statSync(projectDir).isDirectory()) {
|
|
15464
15903
|
warn(`Skipping invalid project directory: ${projectDir}`);
|
|
15465
15904
|
continue;
|
|
15466
15905
|
}
|
|
@@ -15504,8 +15943,8 @@ function prdStateCommand(program2) {
|
|
|
15504
15943
|
|
|
15505
15944
|
// src/commands/retry.ts
|
|
15506
15945
|
init_dist();
|
|
15507
|
-
import * as
|
|
15508
|
-
import * as
|
|
15946
|
+
import * as fs36 from "fs";
|
|
15947
|
+
import * as path38 from "path";
|
|
15509
15948
|
function normalizePrdName(name) {
|
|
15510
15949
|
if (!name.endsWith(".md")) {
|
|
15511
15950
|
return `${name}.md`;
|
|
@@ -15513,26 +15952,26 @@ function normalizePrdName(name) {
|
|
|
15513
15952
|
return name;
|
|
15514
15953
|
}
|
|
15515
15954
|
function getDonePrds(doneDir) {
|
|
15516
|
-
if (!
|
|
15955
|
+
if (!fs36.existsSync(doneDir)) {
|
|
15517
15956
|
return [];
|
|
15518
15957
|
}
|
|
15519
|
-
return
|
|
15958
|
+
return fs36.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
15520
15959
|
}
|
|
15521
15960
|
function retryCommand(program2) {
|
|
15522
15961
|
program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
|
|
15523
15962
|
const projectDir = process.cwd();
|
|
15524
15963
|
const config = loadConfig(projectDir);
|
|
15525
|
-
const prdDir =
|
|
15526
|
-
const doneDir =
|
|
15964
|
+
const prdDir = path38.join(projectDir, config.prdDir);
|
|
15965
|
+
const doneDir = path38.join(prdDir, "done");
|
|
15527
15966
|
const normalizedPrdName = normalizePrdName(prdName);
|
|
15528
|
-
const pendingPath =
|
|
15529
|
-
if (
|
|
15967
|
+
const pendingPath = path38.join(prdDir, normalizedPrdName);
|
|
15968
|
+
if (fs36.existsSync(pendingPath)) {
|
|
15530
15969
|
info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
|
|
15531
15970
|
return;
|
|
15532
15971
|
}
|
|
15533
|
-
const donePath =
|
|
15534
|
-
if (
|
|
15535
|
-
|
|
15972
|
+
const donePath = path38.join(doneDir, normalizedPrdName);
|
|
15973
|
+
if (fs36.existsSync(donePath)) {
|
|
15974
|
+
fs36.renameSync(donePath, pendingPath);
|
|
15536
15975
|
success(`Moved "${normalizedPrdName}" back to pending.`);
|
|
15537
15976
|
dim(`From: ${donePath}`);
|
|
15538
15977
|
dim(`To: ${pendingPath}`);
|
|
@@ -15784,7 +16223,7 @@ function prdsCommand(program2) {
|
|
|
15784
16223
|
|
|
15785
16224
|
// src/commands/cancel.ts
|
|
15786
16225
|
init_dist();
|
|
15787
|
-
import * as
|
|
16226
|
+
import * as fs37 from "fs";
|
|
15788
16227
|
import * as readline2 from "readline";
|
|
15789
16228
|
function getLockFilePaths2(projectDir) {
|
|
15790
16229
|
const runtimeKey = projectRuntimeKey(projectDir);
|
|
@@ -15831,7 +16270,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15831
16270
|
const pid = lockStatus.pid;
|
|
15832
16271
|
if (!lockStatus.running) {
|
|
15833
16272
|
try {
|
|
15834
|
-
|
|
16273
|
+
fs37.unlinkSync(lockPath);
|
|
15835
16274
|
return {
|
|
15836
16275
|
success: true,
|
|
15837
16276
|
message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
|
|
@@ -15869,7 +16308,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15869
16308
|
await sleep2(3e3);
|
|
15870
16309
|
if (!isProcessRunning3(pid)) {
|
|
15871
16310
|
try {
|
|
15872
|
-
|
|
16311
|
+
fs37.unlinkSync(lockPath);
|
|
15873
16312
|
} catch {
|
|
15874
16313
|
}
|
|
15875
16314
|
return {
|
|
@@ -15904,7 +16343,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15904
16343
|
await sleep2(500);
|
|
15905
16344
|
if (!isProcessRunning3(pid)) {
|
|
15906
16345
|
try {
|
|
15907
|
-
|
|
16346
|
+
fs37.unlinkSync(lockPath);
|
|
15908
16347
|
} catch {
|
|
15909
16348
|
}
|
|
15910
16349
|
return {
|
|
@@ -15965,67 +16404,69 @@ function cancelCommand(program2) {
|
|
|
15965
16404
|
|
|
15966
16405
|
// src/commands/slice.ts
|
|
15967
16406
|
init_dist();
|
|
15968
|
-
import * as
|
|
15969
|
-
import * as
|
|
16407
|
+
import * as fs38 from "fs";
|
|
16408
|
+
import * as path39 from "path";
|
|
15970
16409
|
function plannerLockPath2(projectDir) {
|
|
15971
16410
|
return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
|
|
15972
16411
|
}
|
|
15973
16412
|
function acquirePlannerLock(projectDir) {
|
|
15974
16413
|
const lockFile = plannerLockPath2(projectDir);
|
|
15975
|
-
if (
|
|
15976
|
-
const pidRaw =
|
|
16414
|
+
if (fs38.existsSync(lockFile)) {
|
|
16415
|
+
const pidRaw = fs38.readFileSync(lockFile, "utf-8").trim();
|
|
15977
16416
|
const pid = parseInt(pidRaw, 10);
|
|
15978
16417
|
if (!Number.isNaN(pid) && isProcessRunning(pid)) {
|
|
15979
16418
|
return { acquired: false, lockFile, pid };
|
|
15980
16419
|
}
|
|
15981
16420
|
try {
|
|
15982
|
-
|
|
16421
|
+
fs38.unlinkSync(lockFile);
|
|
15983
16422
|
} catch {
|
|
15984
16423
|
}
|
|
15985
16424
|
}
|
|
15986
|
-
|
|
16425
|
+
fs38.writeFileSync(lockFile, String(process.pid));
|
|
15987
16426
|
return { acquired: true, lockFile };
|
|
15988
16427
|
}
|
|
15989
16428
|
function releasePlannerLock(lockFile) {
|
|
15990
16429
|
try {
|
|
15991
|
-
if (
|
|
15992
|
-
|
|
16430
|
+
if (fs38.existsSync(lockFile)) {
|
|
16431
|
+
fs38.unlinkSync(lockFile);
|
|
15993
16432
|
}
|
|
15994
16433
|
} catch {
|
|
15995
16434
|
}
|
|
15996
16435
|
}
|
|
15997
16436
|
function resolvePlannerIssueColumn(config) {
|
|
15998
|
-
return config.roadmapScanner.issueColumn === "
|
|
16437
|
+
return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
|
|
15999
16438
|
}
|
|
16000
16439
|
function buildPlannerIssueBody(projectDir, config, result) {
|
|
16001
|
-
const relativePrdPath =
|
|
16002
|
-
const absolutePrdPath =
|
|
16440
|
+
const relativePrdPath = path39.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
|
|
16441
|
+
const absolutePrdPath = path39.join(projectDir, config.prdDir, result.file ?? "");
|
|
16003
16442
|
const sourceItem = result.item;
|
|
16004
16443
|
let prdContent;
|
|
16005
16444
|
try {
|
|
16006
|
-
prdContent =
|
|
16445
|
+
prdContent = fs38.readFileSync(absolutePrdPath, "utf-8");
|
|
16007
16446
|
} catch {
|
|
16008
16447
|
prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
|
|
16009
16448
|
}
|
|
16010
16449
|
const maxBodyChars = 6e4;
|
|
16011
16450
|
const truncated = prdContent.length > maxBodyChars;
|
|
16012
|
-
const
|
|
16451
|
+
const prdBody = truncated ? `${prdContent.slice(0, maxBodyChars)}
|
|
16013
16452
|
|
|
16014
16453
|
...[truncated]` : prdContent;
|
|
16015
|
-
const
|
|
16016
|
-
|
|
16017
|
-
`- Source
|
|
16018
|
-
sourceItem.description
|
|
16019
|
-
|
|
16454
|
+
const metaLines = [`- PRD file: \`${relativePrdPath}\``];
|
|
16455
|
+
if (sourceItem) {
|
|
16456
|
+
metaLines.push(`- Source section: ${sourceItem.section}`);
|
|
16457
|
+
if (sourceItem.description) {
|
|
16458
|
+
metaLines.push(`- Source summary: ${sourceItem.description}`);
|
|
16459
|
+
}
|
|
16460
|
+
}
|
|
16020
16461
|
return [
|
|
16021
|
-
|
|
16462
|
+
prdBody,
|
|
16022
16463
|
"",
|
|
16023
|
-
|
|
16024
|
-
|
|
16464
|
+
"<details>",
|
|
16465
|
+
"<summary>Source metadata</summary>",
|
|
16025
16466
|
"",
|
|
16026
|
-
|
|
16467
|
+
...metaLines,
|
|
16027
16468
|
"",
|
|
16028
|
-
|
|
16469
|
+
"</details>"
|
|
16029
16470
|
].join("\n");
|
|
16030
16471
|
}
|
|
16031
16472
|
async function createPlannerIssue(projectDir, config, result) {
|
|
@@ -16040,9 +16481,11 @@ async function createPlannerIssue(projectDir, config, result) {
|
|
|
16040
16481
|
if (!board) {
|
|
16041
16482
|
return { created: false, skippedReason: "board-not-configured" };
|
|
16042
16483
|
}
|
|
16484
|
+
const issueTitle = `PRD: ${result.item.title}`;
|
|
16485
|
+
const normalizeTitle = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
|
|
16043
16486
|
const existingIssues = await provider.getAllIssues();
|
|
16044
16487
|
const existing = existingIssues.find(
|
|
16045
|
-
(issue2) => issue2.title
|
|
16488
|
+
(issue2) => normalizeTitle(issue2.title) === normalizeTitle(result.item.title)
|
|
16046
16489
|
);
|
|
16047
16490
|
if (existing) {
|
|
16048
16491
|
return {
|
|
@@ -16053,7 +16496,7 @@ async function createPlannerIssue(projectDir, config, result) {
|
|
|
16053
16496
|
};
|
|
16054
16497
|
}
|
|
16055
16498
|
const issue = await provider.createIssue({
|
|
16056
|
-
title:
|
|
16499
|
+
title: issueTitle,
|
|
16057
16500
|
body: buildPlannerIssueBody(projectDir, config, result),
|
|
16058
16501
|
column: resolvePlannerIssueColumn(config)
|
|
16059
16502
|
});
|
|
@@ -16206,7 +16649,7 @@ function sliceCommand(program2) {
|
|
|
16206
16649
|
if (!options.dryRun && result.sliced) {
|
|
16207
16650
|
await sendNotifications(config, {
|
|
16208
16651
|
event: "run_succeeded",
|
|
16209
|
-
projectName:
|
|
16652
|
+
projectName: path39.basename(projectDir),
|
|
16210
16653
|
exitCode,
|
|
16211
16654
|
provider: config.provider,
|
|
16212
16655
|
prTitle: result.item?.title
|
|
@@ -16214,7 +16657,7 @@ function sliceCommand(program2) {
|
|
|
16214
16657
|
} else if (!options.dryRun && !nothingPending) {
|
|
16215
16658
|
await sendNotifications(config, {
|
|
16216
16659
|
event: "run_failed",
|
|
16217
|
-
projectName:
|
|
16660
|
+
projectName: path39.basename(projectDir),
|
|
16218
16661
|
exitCode,
|
|
16219
16662
|
provider: config.provider
|
|
16220
16663
|
});
|
|
@@ -16231,20 +16674,20 @@ function sliceCommand(program2) {
|
|
|
16231
16674
|
// src/commands/state.ts
|
|
16232
16675
|
init_dist();
|
|
16233
16676
|
import * as os9 from "os";
|
|
16234
|
-
import * as
|
|
16677
|
+
import * as path40 from "path";
|
|
16235
16678
|
import chalk5 from "chalk";
|
|
16236
16679
|
import { Command } from "commander";
|
|
16237
16680
|
function createStateCommand() {
|
|
16238
16681
|
const state = new Command("state");
|
|
16239
16682
|
state.description("Manage Night Watch state");
|
|
16240
16683
|
state.command("migrate").description("Migrate legacy JSON state files to SQLite").option("--dry-run", "Show what would be migrated without making changes").action((opts) => {
|
|
16241
|
-
const nightWatchHome = process.env.NIGHT_WATCH_HOME ||
|
|
16684
|
+
const nightWatchHome = process.env.NIGHT_WATCH_HOME || path40.join(os9.homedir(), GLOBAL_CONFIG_DIR);
|
|
16242
16685
|
if (opts.dryRun) {
|
|
16243
16686
|
console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
|
|
16244
16687
|
console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
|
|
16245
|
-
console.log(` ${
|
|
16246
|
-
console.log(` ${
|
|
16247
|
-
console.log(` ${
|
|
16688
|
+
console.log(` ${path40.join(nightWatchHome, "projects.json")}`);
|
|
16689
|
+
console.log(` ${path40.join(nightWatchHome, "history.json")}`);
|
|
16690
|
+
console.log(` ${path40.join(nightWatchHome, "prd-states.json")}`);
|
|
16248
16691
|
console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
|
|
16249
16692
|
console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
|
|
16250
16693
|
return;
|
|
@@ -16282,8 +16725,8 @@ function createStateCommand() {
|
|
|
16282
16725
|
init_dist();
|
|
16283
16726
|
init_dist();
|
|
16284
16727
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
16285
|
-
import * as
|
|
16286
|
-
import * as
|
|
16728
|
+
import * as fs39 from "fs";
|
|
16729
|
+
import * as path41 from "path";
|
|
16287
16730
|
import * as readline3 from "readline";
|
|
16288
16731
|
import chalk6 from "chalk";
|
|
16289
16732
|
async function run(fn) {
|
|
@@ -16305,7 +16748,7 @@ function getProvider(config, cwd) {
|
|
|
16305
16748
|
return createBoardProvider(bp, cwd);
|
|
16306
16749
|
}
|
|
16307
16750
|
function defaultBoardTitle(cwd) {
|
|
16308
|
-
return `${
|
|
16751
|
+
return `${path41.basename(cwd)} Night Watch`;
|
|
16309
16752
|
}
|
|
16310
16753
|
async function ensureBoardConfigured(config, cwd, provider, options) {
|
|
16311
16754
|
if (config.boardProvider?.projectNumber) {
|
|
@@ -16504,11 +16947,11 @@ function boardCommand(program2) {
|
|
|
16504
16947
|
let body = options.body ?? "";
|
|
16505
16948
|
if (options.bodyFile) {
|
|
16506
16949
|
const filePath = options.bodyFile;
|
|
16507
|
-
if (!
|
|
16950
|
+
if (!fs39.existsSync(filePath)) {
|
|
16508
16951
|
console.error(`File not found: ${filePath}`);
|
|
16509
16952
|
process.exit(1);
|
|
16510
16953
|
}
|
|
16511
|
-
body =
|
|
16954
|
+
body = fs39.readFileSync(filePath, "utf-8");
|
|
16512
16955
|
}
|
|
16513
16956
|
const labels = [];
|
|
16514
16957
|
if (options.label) {
|
|
@@ -16749,12 +17192,12 @@ function boardCommand(program2) {
|
|
|
16749
17192
|
const config = loadConfig(cwd);
|
|
16750
17193
|
const provider = getProvider(config, cwd);
|
|
16751
17194
|
await ensureBoardConfigured(config, cwd, provider);
|
|
16752
|
-
const roadmapPath = options.roadmap ??
|
|
16753
|
-
if (!
|
|
17195
|
+
const roadmapPath = options.roadmap ?? path41.join(cwd, "ROADMAP.md");
|
|
17196
|
+
if (!fs39.existsSync(roadmapPath)) {
|
|
16754
17197
|
console.error(`Roadmap file not found: ${roadmapPath}`);
|
|
16755
17198
|
process.exit(1);
|
|
16756
17199
|
}
|
|
16757
|
-
const roadmapContent =
|
|
17200
|
+
const roadmapContent = fs39.readFileSync(roadmapPath, "utf-8");
|
|
16758
17201
|
const items = parseRoadmap(roadmapContent);
|
|
16759
17202
|
const uncheckedItems = getUncheckedItems(items);
|
|
16760
17203
|
if (uncheckedItems.length === 0) {
|
|
@@ -16878,11 +17321,11 @@ function boardCommand(program2) {
|
|
|
16878
17321
|
// src/commands/queue.ts
|
|
16879
17322
|
init_dist();
|
|
16880
17323
|
init_dist();
|
|
16881
|
-
import * as
|
|
17324
|
+
import * as path42 from "path";
|
|
16882
17325
|
import { spawn as spawn7 } from "child_process";
|
|
16883
17326
|
import chalk7 from "chalk";
|
|
16884
17327
|
import { Command as Command2 } from "commander";
|
|
16885
|
-
var
|
|
17328
|
+
var logger6 = createLogger("queue");
|
|
16886
17329
|
var VALID_JOB_TYPES2 = ["executor", "reviewer", "qa", "audit", "slicer", "planner"];
|
|
16887
17330
|
function formatTimestamp(unixTs) {
|
|
16888
17331
|
if (unixTs === null) return "-";
|
|
@@ -16998,7 +17441,7 @@ function createQueueCommand() {
|
|
|
16998
17441
|
process.exit(1);
|
|
16999
17442
|
}
|
|
17000
17443
|
}
|
|
17001
|
-
const projectName =
|
|
17444
|
+
const projectName = path42.basename(projectDir);
|
|
17002
17445
|
const queueConfig = loadConfig(projectDir).queue;
|
|
17003
17446
|
const id = enqueueJob(
|
|
17004
17447
|
projectDir,
|
|
@@ -17031,13 +17474,13 @@ function createQueueCommand() {
|
|
|
17031
17474
|
const configDir = _opts.projectDir ?? process.cwd();
|
|
17032
17475
|
const entry = dispatchNextJob(loadConfig(configDir).queue);
|
|
17033
17476
|
if (!entry) {
|
|
17034
|
-
|
|
17477
|
+
logger6.info("No pending jobs to dispatch");
|
|
17035
17478
|
return;
|
|
17036
17479
|
}
|
|
17037
|
-
|
|
17480
|
+
logger6.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
|
|
17038
17481
|
const scriptName = getScriptNameForJobType(entry.jobType);
|
|
17039
17482
|
if (!scriptName) {
|
|
17040
|
-
|
|
17483
|
+
logger6.error(`Unknown job type: ${entry.jobType}`);
|
|
17041
17484
|
return;
|
|
17042
17485
|
}
|
|
17043
17486
|
let projectEnv;
|
|
@@ -17056,7 +17499,7 @@ function createQueueCommand() {
|
|
|
17056
17499
|
NW_QUEUE_ENTRY_ID: String(entry.id)
|
|
17057
17500
|
};
|
|
17058
17501
|
const scriptPath = getScriptPath(scriptName);
|
|
17059
|
-
|
|
17502
|
+
logger6.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
|
|
17060
17503
|
try {
|
|
17061
17504
|
const child = spawn7("bash", [scriptPath, entry.projectPath], {
|
|
17062
17505
|
detached: true,
|
|
@@ -17065,11 +17508,11 @@ function createQueueCommand() {
|
|
|
17065
17508
|
cwd: entry.projectPath
|
|
17066
17509
|
});
|
|
17067
17510
|
child.unref();
|
|
17068
|
-
|
|
17511
|
+
logger6.info(`Spawned PID: ${child.pid}`);
|
|
17069
17512
|
markJobRunning(entry.id);
|
|
17070
17513
|
} catch (error2) {
|
|
17071
17514
|
updateJobStatus(entry.id, "pending");
|
|
17072
|
-
|
|
17515
|
+
logger6.error(
|
|
17073
17516
|
`Failed to dispatch ${entry.jobType} for ${entry.projectName}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
17074
17517
|
);
|
|
17075
17518
|
process.exit(1);
|
|
@@ -17084,7 +17527,7 @@ function createQueueCommand() {
|
|
|
17084
17527
|
process.exit(1);
|
|
17085
17528
|
}
|
|
17086
17529
|
const queueConfig = loadConfig(projectDir).queue;
|
|
17087
|
-
const projectName =
|
|
17530
|
+
const projectName = path42.basename(projectDir);
|
|
17088
17531
|
const result = claimJobSlot(
|
|
17089
17532
|
projectDir,
|
|
17090
17533
|
projectName,
|
|
@@ -17221,7 +17664,7 @@ function notifyCommand(program2) {
|
|
|
17221
17664
|
|
|
17222
17665
|
// src/commands/summary.ts
|
|
17223
17666
|
init_dist();
|
|
17224
|
-
import
|
|
17667
|
+
import path43 from "path";
|
|
17225
17668
|
import chalk8 from "chalk";
|
|
17226
17669
|
function formatDuration2(seconds) {
|
|
17227
17670
|
if (seconds === null) return "-";
|
|
@@ -17251,7 +17694,7 @@ function formatJobStatus(status) {
|
|
|
17251
17694
|
return chalk8.dim(status);
|
|
17252
17695
|
}
|
|
17253
17696
|
function getProjectName2(projectPath) {
|
|
17254
|
-
return
|
|
17697
|
+
return path43.basename(projectPath) || projectPath;
|
|
17255
17698
|
}
|
|
17256
17699
|
function formatProvider(providerKey) {
|
|
17257
17700
|
return providerKey.split(":")[0] || providerKey;
|
|
@@ -17370,7 +17813,7 @@ function summaryCommand(program2) {
|
|
|
17370
17813
|
// src/commands/resolve.ts
|
|
17371
17814
|
init_dist();
|
|
17372
17815
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
17373
|
-
import * as
|
|
17816
|
+
import * as path44 from "path";
|
|
17374
17817
|
function buildEnvVars6(config, options) {
|
|
17375
17818
|
const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
|
|
17376
17819
|
env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
|
|
@@ -17500,7 +17943,7 @@ ${stderr}`);
|
|
|
17500
17943
|
const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
|
|
17501
17944
|
await sendNotifications(config, {
|
|
17502
17945
|
event: notificationEvent,
|
|
17503
|
-
projectName:
|
|
17946
|
+
projectName: path44.basename(projectDir),
|
|
17504
17947
|
exitCode,
|
|
17505
17948
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
17506
17949
|
});
|
|
@@ -17513,19 +17956,141 @@ ${stderr}`);
|
|
|
17513
17956
|
});
|
|
17514
17957
|
}
|
|
17515
17958
|
|
|
17959
|
+
// src/commands/merge.ts
|
|
17960
|
+
init_dist();
|
|
17961
|
+
import * as path45 from "path";
|
|
17962
|
+
function buildEnvVars7(config, options) {
|
|
17963
|
+
const env = buildBaseEnvVars(config, "merger", options.dryRun);
|
|
17964
|
+
env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
|
|
17965
|
+
env.NW_MERGER_MERGE_METHOD = config.merger.mergeMethod;
|
|
17966
|
+
env.NW_MERGER_MIN_REVIEW_SCORE = String(config.merger.minReviewScore);
|
|
17967
|
+
env.NW_MERGER_BRANCH_PATTERNS = (config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns : config.branchPatterns).join(",");
|
|
17968
|
+
env.NW_MERGER_REBASE_BEFORE_MERGE = config.merger.rebaseBeforeMerge ? "1" : "0";
|
|
17969
|
+
env.NW_MERGER_MAX_PRS_PER_RUN = String(config.merger.maxPrsPerRun);
|
|
17970
|
+
return env;
|
|
17971
|
+
}
|
|
17972
|
+
function applyCliOverrides6(config, options) {
|
|
17973
|
+
const overridden = { ...config, merger: { ...config.merger } };
|
|
17974
|
+
if (options.timeout) {
|
|
17975
|
+
const timeout = parseInt(options.timeout, 10);
|
|
17976
|
+
if (!isNaN(timeout)) {
|
|
17977
|
+
overridden.merger.maxRuntime = timeout;
|
|
17978
|
+
}
|
|
17979
|
+
}
|
|
17980
|
+
if (options.provider) {
|
|
17981
|
+
overridden._cliProviderOverride = options.provider;
|
|
17982
|
+
}
|
|
17983
|
+
return overridden;
|
|
17984
|
+
}
|
|
17985
|
+
function resolveMergeNotificationEvent(exitCode, mergedCount, failedCount) {
|
|
17986
|
+
if (exitCode === 0 && mergedCount > 0) {
|
|
17987
|
+
return "merge_completed";
|
|
17988
|
+
}
|
|
17989
|
+
if (exitCode !== 0 || failedCount > 0) {
|
|
17990
|
+
return "merge_failed";
|
|
17991
|
+
}
|
|
17992
|
+
return null;
|
|
17993
|
+
}
|
|
17994
|
+
function printDryRun(config, envVars, scriptPath, projectDir) {
|
|
17995
|
+
header("Dry Run: Merge Orchestrator");
|
|
17996
|
+
const mergerProvider = resolveJobProvider(config, "merger");
|
|
17997
|
+
header("Configuration");
|
|
17998
|
+
const configTable = createTable({ head: ["Setting", "Value"] });
|
|
17999
|
+
configTable.push(["Provider", mergerProvider]);
|
|
18000
|
+
configTable.push([
|
|
18001
|
+
"Max Runtime",
|
|
18002
|
+
`${config.merger.maxRuntime}s (${Math.floor(config.merger.maxRuntime / 60)}min)`
|
|
18003
|
+
]);
|
|
18004
|
+
configTable.push(["Merge Method", config.merger.mergeMethod]);
|
|
18005
|
+
configTable.push(["Min Review Score", `${config.merger.minReviewScore}/100`]);
|
|
18006
|
+
configTable.push([
|
|
18007
|
+
"Branch Patterns",
|
|
18008
|
+
config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns.join(", ") : "(top-level)"
|
|
18009
|
+
]);
|
|
18010
|
+
configTable.push(["Rebase Before Merge", config.merger.rebaseBeforeMerge ? "Yes" : "No"]);
|
|
18011
|
+
configTable.push([
|
|
18012
|
+
"Max PRs Per Run",
|
|
18013
|
+
config.merger.maxPrsPerRun === 0 ? "Unlimited" : String(config.merger.maxPrsPerRun)
|
|
18014
|
+
]);
|
|
18015
|
+
console.log(configTable.toString());
|
|
18016
|
+
header("Environment Variables");
|
|
18017
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
18018
|
+
dim(` ${key}=${value}`);
|
|
18019
|
+
}
|
|
18020
|
+
header("Command");
|
|
18021
|
+
dim(` bash ${scriptPath} ${projectDir}`);
|
|
18022
|
+
console.log();
|
|
18023
|
+
}
|
|
18024
|
+
function mergeCommand(program2) {
|
|
18025
|
+
program2.command("merge").description("Merge eligible PRs in FIFO order").option("--dry-run", "Show what would be executed without running").option("--timeout <seconds>", "Override max runtime").option("--provider <string>", "AI provider to use").action(async (options) => {
|
|
18026
|
+
const projectDir = process.cwd();
|
|
18027
|
+
let config = loadConfig(projectDir);
|
|
18028
|
+
config = applyCliOverrides6(config, options);
|
|
18029
|
+
if (!config.merger.enabled && !options.dryRun) {
|
|
18030
|
+
info("Merge orchestrator is disabled in config; skipping.");
|
|
18031
|
+
process.exit(0);
|
|
18032
|
+
}
|
|
18033
|
+
const envVars = buildEnvVars7(config, options);
|
|
18034
|
+
const scriptPath = getScriptPath("night-watch-merger-cron.sh");
|
|
18035
|
+
if (options.dryRun) {
|
|
18036
|
+
printDryRun(config, envVars, scriptPath, projectDir);
|
|
18037
|
+
process.exit(0);
|
|
18038
|
+
}
|
|
18039
|
+
const spinner = createSpinner("Running merge orchestrator...");
|
|
18040
|
+
spinner.start();
|
|
18041
|
+
try {
|
|
18042
|
+
await maybeApplyCronSchedulingDelay(config, "merger", projectDir);
|
|
18043
|
+
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
18044
|
+
scriptPath,
|
|
18045
|
+
[projectDir],
|
|
18046
|
+
envVars
|
|
18047
|
+
);
|
|
18048
|
+
const scriptResult = parseScriptResult(`${stdout}
|
|
18049
|
+
${stderr}`);
|
|
18050
|
+
if (exitCode === 0) {
|
|
18051
|
+
if (scriptResult?.status === "queued") {
|
|
18052
|
+
spinner.succeed("Merge orchestrator queued \u2014 another job is currently running");
|
|
18053
|
+
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
18054
|
+
spinner.succeed("Merge orchestrator completed (no eligible PRs)");
|
|
18055
|
+
} else {
|
|
18056
|
+
spinner.succeed("Merge orchestrator completed successfully");
|
|
18057
|
+
}
|
|
18058
|
+
} else {
|
|
18059
|
+
spinner.fail(`Merge orchestrator exited with code ${exitCode}`);
|
|
18060
|
+
}
|
|
18061
|
+
const mergedCount = parseInt(scriptResult?.data?.merged ?? "0", 10);
|
|
18062
|
+
const failedCount = parseInt(scriptResult?.data?.failed ?? "0", 10);
|
|
18063
|
+
const notificationEvent = resolveMergeNotificationEvent(exitCode, mergedCount, failedCount);
|
|
18064
|
+
if (notificationEvent) {
|
|
18065
|
+
await sendNotifications(config, {
|
|
18066
|
+
event: notificationEvent,
|
|
18067
|
+
projectName: path45.basename(projectDir),
|
|
18068
|
+
exitCode,
|
|
18069
|
+
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
18070
|
+
});
|
|
18071
|
+
}
|
|
18072
|
+
process.exit(exitCode);
|
|
18073
|
+
} catch (err) {
|
|
18074
|
+
spinner.fail("Failed to execute merge command");
|
|
18075
|
+
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
18076
|
+
process.exit(1);
|
|
18077
|
+
}
|
|
18078
|
+
});
|
|
18079
|
+
}
|
|
18080
|
+
|
|
17516
18081
|
// src/cli.ts
|
|
17517
18082
|
var __filename5 = fileURLToPath6(import.meta.url);
|
|
17518
18083
|
var __dirname5 = dirname12(__filename5);
|
|
17519
18084
|
function findPackageRoot(dir) {
|
|
17520
18085
|
let d = dir;
|
|
17521
18086
|
for (let i = 0; i < 5; i++) {
|
|
17522
|
-
if (
|
|
18087
|
+
if (existsSync31(join37(d, "package.json"))) return d;
|
|
17523
18088
|
d = dirname12(d);
|
|
17524
18089
|
}
|
|
17525
18090
|
return dir;
|
|
17526
18091
|
}
|
|
17527
18092
|
var packageRoot = findPackageRoot(__dirname5);
|
|
17528
|
-
var packageJson = JSON.parse(
|
|
18093
|
+
var packageJson = JSON.parse(readFileSync20(join37(packageRoot, "package.json"), "utf-8"));
|
|
17529
18094
|
var program = new Command3();
|
|
17530
18095
|
program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
|
|
17531
18096
|
initCommand(program);
|
|
@@ -17556,4 +18121,5 @@ queueCommand(program);
|
|
|
17556
18121
|
notifyCommand(program);
|
|
17557
18122
|
summaryCommand(program);
|
|
17558
18123
|
resolveCommand(program);
|
|
18124
|
+
mergeCommand(program);
|
|
17559
18125
|
program.parse();
|