@jonit-dev/night-watch-cli 1.8.8-beta.9 → 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 +1073 -519
- 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 +0 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +0 -13
- 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 +1 -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;
|
|
@@ -9399,10 +9732,6 @@ function buildEnvVars2(config, options) {
|
|
|
9399
9732
|
env.NW_BRANCH_PATTERNS = config.branchPatterns.join(",");
|
|
9400
9733
|
env.NW_PRD_DIR = config.prdDir;
|
|
9401
9734
|
env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? "sonnet"];
|
|
9402
|
-
if (config.autoMerge) {
|
|
9403
|
-
env.NW_AUTO_MERGE = "1";
|
|
9404
|
-
}
|
|
9405
|
-
env.NW_AUTO_MERGE_METHOD = config.autoMergeMethod;
|
|
9406
9735
|
return env;
|
|
9407
9736
|
}
|
|
9408
9737
|
function applyCliOverrides2(config, options) {
|
|
@@ -9416,9 +9745,6 @@ function applyCliOverrides2(config, options) {
|
|
|
9416
9745
|
if (options.provider) {
|
|
9417
9746
|
overridden._cliProviderOverride = options.provider;
|
|
9418
9747
|
}
|
|
9419
|
-
if (options.autoMerge !== void 0) {
|
|
9420
|
-
overridden.autoMerge = options.autoMerge;
|
|
9421
|
-
}
|
|
9422
9748
|
return overridden;
|
|
9423
9749
|
}
|
|
9424
9750
|
function isFailingCheck(check) {
|
|
@@ -9479,7 +9805,7 @@ function getOpenPrsNeedingWork(branchPatterns) {
|
|
|
9479
9805
|
}
|
|
9480
9806
|
}
|
|
9481
9807
|
function reviewCommand(program2) {
|
|
9482
|
-
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) => {
|
|
9483
9809
|
const projectDir = process.cwd();
|
|
9484
9810
|
let config = loadConfig(projectDir);
|
|
9485
9811
|
config = applyCliOverrides2(config, options);
|
|
@@ -9502,10 +9828,6 @@ function reviewCommand(program2) {
|
|
|
9502
9828
|
]);
|
|
9503
9829
|
configTable.push(["Min Review Score", `${config.minReviewScore}/100`]);
|
|
9504
9830
|
configTable.push(["Branch Patterns", config.branchPatterns.join(", ")]);
|
|
9505
|
-
configTable.push([
|
|
9506
|
-
"Auto-merge",
|
|
9507
|
-
config.autoMerge ? `Enabled (${config.autoMergeMethod})` : "Disabled"
|
|
9508
|
-
]);
|
|
9509
9831
|
configTable.push(["Max Retry Attempts", String(config.reviewerMaxRetries)]);
|
|
9510
9832
|
configTable.push(["Retry Delay", `${config.reviewerRetryDelay}s`]);
|
|
9511
9833
|
configTable.push([
|
|
@@ -9610,7 +9932,7 @@ ${stderr}`);
|
|
|
9610
9932
|
const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
9611
9933
|
await sendNotifications(config, {
|
|
9612
9934
|
event: reviewEvent,
|
|
9613
|
-
projectName:
|
|
9935
|
+
projectName: path23.basename(projectDir),
|
|
9614
9936
|
exitCode,
|
|
9615
9937
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9616
9938
|
prUrl: fallbackPrDetails?.url,
|
|
@@ -9632,7 +9954,7 @@ ${stderr}`);
|
|
|
9632
9954
|
const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
9633
9955
|
await sendNotifications(config, {
|
|
9634
9956
|
event: reviewEvent,
|
|
9635
|
-
projectName:
|
|
9957
|
+
projectName: path23.basename(projectDir),
|
|
9636
9958
|
exitCode,
|
|
9637
9959
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9638
9960
|
prUrl: prDetails?.url,
|
|
@@ -9654,7 +9976,7 @@ ${stderr}`);
|
|
|
9654
9976
|
const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
|
|
9655
9977
|
const _mergeCtx = {
|
|
9656
9978
|
event: "pr_auto_merged",
|
|
9657
|
-
projectName:
|
|
9979
|
+
projectName: path23.basename(projectDir),
|
|
9658
9980
|
exitCode,
|
|
9659
9981
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9660
9982
|
prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
|
|
@@ -9679,7 +10001,7 @@ ${stderr}`);
|
|
|
9679
10001
|
|
|
9680
10002
|
// src/commands/qa.ts
|
|
9681
10003
|
init_dist();
|
|
9682
|
-
import * as
|
|
10004
|
+
import * as path24 from "path";
|
|
9683
10005
|
function shouldSendQaNotification(scriptStatus) {
|
|
9684
10006
|
if (!scriptStatus) {
|
|
9685
10007
|
return true;
|
|
@@ -9820,7 +10142,7 @@ ${stderr}`);
|
|
|
9820
10142
|
const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
|
|
9821
10143
|
const _qaCtx = {
|
|
9822
10144
|
event: "qa_completed",
|
|
9823
|
-
projectName:
|
|
10145
|
+
projectName: path24.basename(projectDir),
|
|
9824
10146
|
exitCode,
|
|
9825
10147
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9826
10148
|
prNumber: prDetails?.number ?? primaryQaPr,
|
|
@@ -9846,8 +10168,8 @@ ${stderr}`);
|
|
|
9846
10168
|
|
|
9847
10169
|
// src/commands/audit.ts
|
|
9848
10170
|
init_dist();
|
|
9849
|
-
import * as
|
|
9850
|
-
import * as
|
|
10171
|
+
import * as fs23 from "fs";
|
|
10172
|
+
import * as path25 from "path";
|
|
9851
10173
|
function buildEnvVars4(config, options) {
|
|
9852
10174
|
const env = buildBaseEnvVars(config, "audit", options.dryRun);
|
|
9853
10175
|
env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
|
|
@@ -9890,7 +10212,8 @@ function auditCommand(program2) {
|
|
|
9890
10212
|
configTable.push(["Provider", auditProvider]);
|
|
9891
10213
|
configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
|
|
9892
10214
|
configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
|
|
9893
|
-
configTable.push(["
|
|
10215
|
+
configTable.push(["Target Column", config.audit.targetColumn]);
|
|
10216
|
+
configTable.push(["Report File", path25.join(projectDir, "logs", "audit-report.md")]);
|
|
9894
10217
|
console.log(configTable.toString());
|
|
9895
10218
|
header("Provider Invocation");
|
|
9896
10219
|
const providerCmd = PROVIDER_COMMANDS[auditProvider];
|
|
@@ -9925,21 +10248,27 @@ ${stderr}`);
|
|
|
9925
10248
|
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
9926
10249
|
spinner.succeed("Code audit skipped");
|
|
9927
10250
|
} else {
|
|
9928
|
-
const reportPath =
|
|
9929
|
-
if (!
|
|
10251
|
+
const reportPath = path25.join(projectDir, "logs", "audit-report.md");
|
|
10252
|
+
if (!fs23.existsSync(reportPath)) {
|
|
9930
10253
|
spinner.fail("Code audit finished without a report file");
|
|
9931
10254
|
process.exit(1);
|
|
9932
10255
|
}
|
|
9933
|
-
|
|
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
|
+
}
|
|
9934
10263
|
}
|
|
9935
10264
|
} else {
|
|
9936
10265
|
const statusSuffix = scriptResult?.status ? ` (${scriptResult.status})` : "";
|
|
9937
10266
|
const providerExit = scriptResult?.data?.provider_exit;
|
|
9938
10267
|
const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
|
|
9939
10268
|
spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
|
|
9940
|
-
const logPath =
|
|
9941
|
-
if (
|
|
9942
|
-
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);
|
|
9943
10272
|
if (logLines.length > 0) {
|
|
9944
10273
|
process.stderr.write(logLines.join("\n") + "\n");
|
|
9945
10274
|
}
|
|
@@ -10013,16 +10342,16 @@ function analyticsCommand(program2) {
|
|
|
10013
10342
|
// src/commands/install.ts
|
|
10014
10343
|
init_dist();
|
|
10015
10344
|
import { execSync as execSync4 } from "child_process";
|
|
10016
|
-
import * as
|
|
10017
|
-
import * as
|
|
10345
|
+
import * as path26 from "path";
|
|
10346
|
+
import * as fs24 from "fs";
|
|
10018
10347
|
function shellQuote(value) {
|
|
10019
10348
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
10020
10349
|
}
|
|
10021
10350
|
function getNightWatchBinPath() {
|
|
10022
10351
|
try {
|
|
10023
10352
|
const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
|
|
10024
|
-
const binPath =
|
|
10025
|
-
if (
|
|
10353
|
+
const binPath = path26.join(npmBin, "night-watch");
|
|
10354
|
+
if (fs24.existsSync(binPath)) {
|
|
10026
10355
|
return binPath;
|
|
10027
10356
|
}
|
|
10028
10357
|
} catch {
|
|
@@ -10035,17 +10364,17 @@ function getNightWatchBinPath() {
|
|
|
10035
10364
|
}
|
|
10036
10365
|
function getNodeBinDir() {
|
|
10037
10366
|
if (process.execPath && process.execPath !== "node") {
|
|
10038
|
-
return
|
|
10367
|
+
return path26.dirname(process.execPath);
|
|
10039
10368
|
}
|
|
10040
10369
|
try {
|
|
10041
10370
|
const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
|
|
10042
|
-
return
|
|
10371
|
+
return path26.dirname(nodePath);
|
|
10043
10372
|
} catch {
|
|
10044
10373
|
return "";
|
|
10045
10374
|
}
|
|
10046
10375
|
}
|
|
10047
10376
|
function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
|
|
10048
|
-
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ?
|
|
10377
|
+
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path26.dirname(nightWatchBin) : "";
|
|
10049
10378
|
const pathParts = Array.from(
|
|
10050
10379
|
new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
|
|
10051
10380
|
);
|
|
@@ -10061,12 +10390,12 @@ function performInstall(projectDir, config, options) {
|
|
|
10061
10390
|
const nightWatchBin = getNightWatchBinPath();
|
|
10062
10391
|
const projectName = getProjectName(projectDir);
|
|
10063
10392
|
const marker = generateMarker(projectName);
|
|
10064
|
-
const logDir =
|
|
10065
|
-
if (!
|
|
10066
|
-
|
|
10393
|
+
const logDir = path26.join(projectDir, LOG_DIR);
|
|
10394
|
+
if (!fs24.existsSync(logDir)) {
|
|
10395
|
+
fs24.mkdirSync(logDir, { recursive: true });
|
|
10067
10396
|
}
|
|
10068
|
-
const executorLog =
|
|
10069
|
-
const reviewerLog =
|
|
10397
|
+
const executorLog = path26.join(logDir, "executor.log");
|
|
10398
|
+
const reviewerLog = path26.join(logDir, "reviewer.log");
|
|
10070
10399
|
if (!options?.force) {
|
|
10071
10400
|
const existingEntries2 = Array.from(
|
|
10072
10401
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
@@ -10103,7 +10432,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10103
10432
|
const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
10104
10433
|
if (installSlicer) {
|
|
10105
10434
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
10106
|
-
const slicerLog =
|
|
10435
|
+
const slicerLog = path26.join(logDir, "slicer.log");
|
|
10107
10436
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
10108
10437
|
entries.push(slicerEntry);
|
|
10109
10438
|
}
|
|
@@ -10111,7 +10440,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10111
10440
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
10112
10441
|
if (installQa) {
|
|
10113
10442
|
const qaSchedule = config.qa.schedule;
|
|
10114
|
-
const qaLog =
|
|
10443
|
+
const qaLog = path26.join(logDir, "qa.log");
|
|
10115
10444
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
10116
10445
|
entries.push(qaEntry);
|
|
10117
10446
|
}
|
|
@@ -10119,7 +10448,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10119
10448
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
10120
10449
|
if (installAudit) {
|
|
10121
10450
|
const auditSchedule = config.audit.schedule;
|
|
10122
|
-
const auditLog =
|
|
10451
|
+
const auditLog = path26.join(logDir, "audit.log");
|
|
10123
10452
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
10124
10453
|
entries.push(auditEntry);
|
|
10125
10454
|
}
|
|
@@ -10127,7 +10456,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10127
10456
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
10128
10457
|
if (installAnalytics) {
|
|
10129
10458
|
const analyticsSchedule = config.analytics.schedule;
|
|
10130
|
-
const analyticsLog =
|
|
10459
|
+
const analyticsLog = path26.join(logDir, "analytics.log");
|
|
10131
10460
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
10132
10461
|
entries.push(analyticsEntry);
|
|
10133
10462
|
}
|
|
@@ -10135,10 +10464,18 @@ function performInstall(projectDir, config, options) {
|
|
|
10135
10464
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10136
10465
|
if (installPrResolver) {
|
|
10137
10466
|
const prResolverSchedule = config.prResolver.schedule;
|
|
10138
|
-
const prResolverLog =
|
|
10467
|
+
const prResolverLog = path26.join(logDir, "pr-resolver.log");
|
|
10139
10468
|
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10140
10469
|
entries.push(prResolverEntry);
|
|
10141
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
|
+
}
|
|
10142
10479
|
const existingEntries = new Set(
|
|
10143
10480
|
Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
|
|
10144
10481
|
);
|
|
@@ -10157,7 +10494,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10157
10494
|
}
|
|
10158
10495
|
}
|
|
10159
10496
|
function installCommand(program2) {
|
|
10160
|
-
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) => {
|
|
10161
10498
|
try {
|
|
10162
10499
|
const projectDir = process.cwd();
|
|
10163
10500
|
const config = loadConfig(projectDir);
|
|
@@ -10166,12 +10503,12 @@ function installCommand(program2) {
|
|
|
10166
10503
|
const nightWatchBin = getNightWatchBinPath();
|
|
10167
10504
|
const projectName = getProjectName(projectDir);
|
|
10168
10505
|
const marker = generateMarker(projectName);
|
|
10169
|
-
const logDir =
|
|
10170
|
-
if (!
|
|
10171
|
-
|
|
10506
|
+
const logDir = path26.join(projectDir, LOG_DIR);
|
|
10507
|
+
if (!fs24.existsSync(logDir)) {
|
|
10508
|
+
fs24.mkdirSync(logDir, { recursive: true });
|
|
10172
10509
|
}
|
|
10173
|
-
const executorLog =
|
|
10174
|
-
const reviewerLog =
|
|
10510
|
+
const executorLog = path26.join(logDir, "executor.log");
|
|
10511
|
+
const reviewerLog = path26.join(logDir, "reviewer.log");
|
|
10175
10512
|
const existingEntries = Array.from(
|
|
10176
10513
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
10177
10514
|
);
|
|
@@ -10207,7 +10544,7 @@ function installCommand(program2) {
|
|
|
10207
10544
|
const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
10208
10545
|
let slicerLog;
|
|
10209
10546
|
if (installSlicer) {
|
|
10210
|
-
slicerLog =
|
|
10547
|
+
slicerLog = path26.join(logDir, "slicer.log");
|
|
10211
10548
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
10212
10549
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
10213
10550
|
entries.push(slicerEntry);
|
|
@@ -10216,7 +10553,7 @@ function installCommand(program2) {
|
|
|
10216
10553
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
10217
10554
|
let qaLog;
|
|
10218
10555
|
if (installQa) {
|
|
10219
|
-
qaLog =
|
|
10556
|
+
qaLog = path26.join(logDir, "qa.log");
|
|
10220
10557
|
const qaSchedule = config.qa.schedule;
|
|
10221
10558
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
10222
10559
|
entries.push(qaEntry);
|
|
@@ -10225,7 +10562,7 @@ function installCommand(program2) {
|
|
|
10225
10562
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
10226
10563
|
let auditLog;
|
|
10227
10564
|
if (installAudit) {
|
|
10228
|
-
auditLog =
|
|
10565
|
+
auditLog = path26.join(logDir, "audit.log");
|
|
10229
10566
|
const auditSchedule = config.audit.schedule;
|
|
10230
10567
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
10231
10568
|
entries.push(auditEntry);
|
|
@@ -10234,7 +10571,7 @@ function installCommand(program2) {
|
|
|
10234
10571
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
10235
10572
|
let analyticsLog;
|
|
10236
10573
|
if (installAnalytics) {
|
|
10237
|
-
analyticsLog =
|
|
10574
|
+
analyticsLog = path26.join(logDir, "analytics.log");
|
|
10238
10575
|
const analyticsSchedule = config.analytics.schedule;
|
|
10239
10576
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
10240
10577
|
entries.push(analyticsEntry);
|
|
@@ -10243,11 +10580,20 @@ function installCommand(program2) {
|
|
|
10243
10580
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10244
10581
|
let prResolverLog;
|
|
10245
10582
|
if (installPrResolver) {
|
|
10246
|
-
prResolverLog =
|
|
10583
|
+
prResolverLog = path26.join(logDir, "pr-resolver.log");
|
|
10247
10584
|
const prResolverSchedule = config.prResolver.schedule;
|
|
10248
10585
|
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10249
10586
|
entries.push(prResolverEntry);
|
|
10250
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
|
+
}
|
|
10251
10597
|
const existingEntrySet = new Set(existingEntries);
|
|
10252
10598
|
const currentCrontab = readCrontab();
|
|
10253
10599
|
const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
|
|
@@ -10280,6 +10626,9 @@ function installCommand(program2) {
|
|
|
10280
10626
|
if (installPrResolver && prResolverLog) {
|
|
10281
10627
|
dim(` PR Resolver: ${prResolverLog}`);
|
|
10282
10628
|
}
|
|
10629
|
+
if (installMerger && mergerLog) {
|
|
10630
|
+
dim(` Merger: ${mergerLog}`);
|
|
10631
|
+
}
|
|
10283
10632
|
console.log();
|
|
10284
10633
|
dim("To uninstall, run: night-watch uninstall");
|
|
10285
10634
|
dim("To check status, run: night-watch status");
|
|
@@ -10294,8 +10643,8 @@ function installCommand(program2) {
|
|
|
10294
10643
|
|
|
10295
10644
|
// src/commands/uninstall.ts
|
|
10296
10645
|
init_dist();
|
|
10297
|
-
import * as
|
|
10298
|
-
import * as
|
|
10646
|
+
import * as path27 from "path";
|
|
10647
|
+
import * as fs25 from "fs";
|
|
10299
10648
|
function performUninstall(projectDir, options) {
|
|
10300
10649
|
try {
|
|
10301
10650
|
const projectName = getProjectName(projectDir);
|
|
@@ -10310,8 +10659,8 @@ function performUninstall(projectDir, options) {
|
|
|
10310
10659
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
10311
10660
|
unregisterProject(projectDir);
|
|
10312
10661
|
if (!options?.keepLogs) {
|
|
10313
|
-
const logDir =
|
|
10314
|
-
if (
|
|
10662
|
+
const logDir = path27.join(projectDir, "logs");
|
|
10663
|
+
if (fs25.existsSync(logDir)) {
|
|
10315
10664
|
const logFiles = [
|
|
10316
10665
|
"executor.log",
|
|
10317
10666
|
"reviewer.log",
|
|
@@ -10320,15 +10669,15 @@ function performUninstall(projectDir, options) {
|
|
|
10320
10669
|
"pr-resolver.log"
|
|
10321
10670
|
];
|
|
10322
10671
|
logFiles.forEach((logFile) => {
|
|
10323
|
-
const logPath =
|
|
10324
|
-
if (
|
|
10325
|
-
|
|
10672
|
+
const logPath = path27.join(logDir, logFile);
|
|
10673
|
+
if (fs25.existsSync(logPath)) {
|
|
10674
|
+
fs25.unlinkSync(logPath);
|
|
10326
10675
|
}
|
|
10327
10676
|
});
|
|
10328
10677
|
try {
|
|
10329
|
-
const remainingFiles =
|
|
10678
|
+
const remainingFiles = fs25.readdirSync(logDir);
|
|
10330
10679
|
if (remainingFiles.length === 0) {
|
|
10331
|
-
|
|
10680
|
+
fs25.rmdirSync(logDir);
|
|
10332
10681
|
}
|
|
10333
10682
|
} catch {
|
|
10334
10683
|
}
|
|
@@ -10361,8 +10710,8 @@ function uninstallCommand(program2) {
|
|
|
10361
10710
|
existingEntries.forEach((entry) => dim(` ${entry}`));
|
|
10362
10711
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
10363
10712
|
if (!options.keepLogs) {
|
|
10364
|
-
const logDir =
|
|
10365
|
-
if (
|
|
10713
|
+
const logDir = path27.join(projectDir, "logs");
|
|
10714
|
+
if (fs25.existsSync(logDir)) {
|
|
10366
10715
|
const logFiles = [
|
|
10367
10716
|
"executor.log",
|
|
10368
10717
|
"reviewer.log",
|
|
@@ -10372,16 +10721,16 @@ function uninstallCommand(program2) {
|
|
|
10372
10721
|
];
|
|
10373
10722
|
let logsRemoved = 0;
|
|
10374
10723
|
logFiles.forEach((logFile) => {
|
|
10375
|
-
const logPath =
|
|
10376
|
-
if (
|
|
10377
|
-
|
|
10724
|
+
const logPath = path27.join(logDir, logFile);
|
|
10725
|
+
if (fs25.existsSync(logPath)) {
|
|
10726
|
+
fs25.unlinkSync(logPath);
|
|
10378
10727
|
logsRemoved++;
|
|
10379
10728
|
}
|
|
10380
10729
|
});
|
|
10381
10730
|
try {
|
|
10382
|
-
const remainingFiles =
|
|
10731
|
+
const remainingFiles = fs25.readdirSync(logDir);
|
|
10383
10732
|
if (remainingFiles.length === 0) {
|
|
10384
|
-
|
|
10733
|
+
fs25.rmdirSync(logDir);
|
|
10385
10734
|
}
|
|
10386
10735
|
} catch {
|
|
10387
10736
|
}
|
|
@@ -10425,11 +10774,15 @@ function statusCommand(program2) {
|
|
|
10425
10774
|
const qaProc = snapshot.processes.find((p) => p.name === "qa");
|
|
10426
10775
|
const auditProc = snapshot.processes.find((p) => p.name === "audit");
|
|
10427
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");
|
|
10428
10779
|
const executorLog = snapshot.logs.find((l) => l.name === "executor");
|
|
10429
10780
|
const reviewerLog = snapshot.logs.find((l) => l.name === "reviewer");
|
|
10430
10781
|
const qaLog = snapshot.logs.find((l) => l.name === "qa");
|
|
10431
10782
|
const auditLog = snapshot.logs.find((l) => l.name === "audit");
|
|
10432
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");
|
|
10433
10786
|
const pendingPrds = snapshot.prds.filter(
|
|
10434
10787
|
(p) => p.status === "ready" || p.status === "blocked"
|
|
10435
10788
|
).length;
|
|
@@ -10447,6 +10800,8 @@ function statusCommand(program2) {
|
|
|
10447
10800
|
qa: { running: qaProc?.running ?? false, pid: qaProc?.pid ?? null },
|
|
10448
10801
|
audit: { running: auditProc?.running ?? false, pid: auditProc?.pid ?? null },
|
|
10449
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 },
|
|
10450
10805
|
prds: { pending: pendingPrds, claimed: claimedPrds, done: donePrds },
|
|
10451
10806
|
prs: { open: snapshot.prs.length },
|
|
10452
10807
|
crontab: snapshot.crontab,
|
|
@@ -10480,6 +10835,18 @@ function statusCommand(program2) {
|
|
|
10480
10835
|
lastLines: plannerLog.lastLines,
|
|
10481
10836
|
exists: plannerLog.exists,
|
|
10482
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
|
|
10483
10850
|
} : void 0
|
|
10484
10851
|
}
|
|
10485
10852
|
};
|
|
@@ -10517,6 +10884,14 @@ function statusCommand(program2) {
|
|
|
10517
10884
|
"Planner",
|
|
10518
10885
|
formatRunningStatus(status.planner.running, status.planner.pid)
|
|
10519
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
|
+
]);
|
|
10520
10895
|
console.log(processTable.toString());
|
|
10521
10896
|
header("PRD Status");
|
|
10522
10897
|
const prdTable = createTable({ head: ["Status", "Count"] });
|
|
@@ -10573,6 +10948,20 @@ function statusCommand(program2) {
|
|
|
10573
10948
|
status.logs.planner.exists ? "Exists" : "Not found"
|
|
10574
10949
|
]);
|
|
10575
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
|
+
}
|
|
10576
10965
|
console.log(logTable.toString());
|
|
10577
10966
|
if (options.verbose) {
|
|
10578
10967
|
if (status.logs.executor?.exists && status.logs.executor.lastLines.length > 0) {
|
|
@@ -10595,6 +10984,14 @@ function statusCommand(program2) {
|
|
|
10595
10984
|
dim(" Planner last 5 lines:");
|
|
10596
10985
|
status.logs.planner.lastLines.forEach((line) => dim(` ${line}`));
|
|
10597
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
|
+
}
|
|
10598
10995
|
}
|
|
10599
10996
|
header("Commands");
|
|
10600
10997
|
dim(" night-watch install - Install crontab entries");
|
|
@@ -10604,6 +11001,8 @@ function statusCommand(program2) {
|
|
|
10604
11001
|
dim(" night-watch qa - Run QA now");
|
|
10605
11002
|
dim(" night-watch audit - Run audit now");
|
|
10606
11003
|
dim(" night-watch planner - Run planner now");
|
|
11004
|
+
dim(" night-watch analytics - Run analytics now");
|
|
11005
|
+
dim(" night-watch merge - Run merger now");
|
|
10607
11006
|
console.log();
|
|
10608
11007
|
} catch (error2) {
|
|
10609
11008
|
console.error(
|
|
@@ -10617,14 +11016,14 @@ function statusCommand(program2) {
|
|
|
10617
11016
|
// src/commands/logs.ts
|
|
10618
11017
|
init_dist();
|
|
10619
11018
|
import { spawn as spawn3 } from "child_process";
|
|
10620
|
-
import * as
|
|
10621
|
-
import * as
|
|
11019
|
+
import * as path28 from "path";
|
|
11020
|
+
import * as fs26 from "fs";
|
|
10622
11021
|
function getLastLines(filePath, lineCount) {
|
|
10623
|
-
if (!
|
|
11022
|
+
if (!fs26.existsSync(filePath)) {
|
|
10624
11023
|
return `Log file not found: ${filePath}`;
|
|
10625
11024
|
}
|
|
10626
11025
|
try {
|
|
10627
|
-
const content =
|
|
11026
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
10628
11027
|
const lines = content.trim().split("\n");
|
|
10629
11028
|
return lines.slice(-lineCount).join("\n");
|
|
10630
11029
|
} catch (error2) {
|
|
@@ -10632,7 +11031,7 @@ function getLastLines(filePath, lineCount) {
|
|
|
10632
11031
|
}
|
|
10633
11032
|
}
|
|
10634
11033
|
function followLog(filePath) {
|
|
10635
|
-
if (!
|
|
11034
|
+
if (!fs26.existsSync(filePath)) {
|
|
10636
11035
|
console.log(`Log file not found: ${filePath}`);
|
|
10637
11036
|
console.log("The log file will be created when the first execution runs.");
|
|
10638
11037
|
return;
|
|
@@ -10649,32 +11048,42 @@ function followLog(filePath) {
|
|
|
10649
11048
|
});
|
|
10650
11049
|
}
|
|
10651
11050
|
function logsCommand(program2) {
|
|
10652
|
-
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) => {
|
|
10653
11056
|
try {
|
|
10654
11057
|
const projectDir = process.cwd();
|
|
10655
|
-
const logDir =
|
|
11058
|
+
const logDir = path28.join(projectDir, LOG_DIR);
|
|
10656
11059
|
const lineCount = parseInt(options.lines || "50", 10);
|
|
10657
|
-
const executorLog =
|
|
10658
|
-
const reviewerLog =
|
|
10659
|
-
const qaLog =
|
|
10660
|
-
const auditLog =
|
|
10661
|
-
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`);
|
|
10662
11067
|
const logType = options.type?.toLowerCase() || "all";
|
|
10663
11068
|
const showExecutor = logType === "all" || logType === "run" || logType === "executor";
|
|
10664
11069
|
const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
|
|
10665
11070
|
const showQa = logType === "all" || logType === "qa";
|
|
10666
11071
|
const showAudit = logType === "all" || logType === "audit";
|
|
10667
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";
|
|
10668
11075
|
if (options.follow) {
|
|
10669
11076
|
if (logType === "all") {
|
|
10670
11077
|
dim("Note: Following all logs is not supported. Showing executor log.");
|
|
10671
|
-
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");
|
|
10672
11079
|
}
|
|
10673
11080
|
let targetLog = executorLog;
|
|
10674
11081
|
if (showReviewer) targetLog = reviewerLog;
|
|
10675
11082
|
else if (showQa) targetLog = qaLog;
|
|
10676
11083
|
else if (showAudit) targetLog = auditLog;
|
|
10677
11084
|
else if (showPlanner) targetLog = plannerLog;
|
|
11085
|
+
else if (showAnalytics) targetLog = analyticsLog;
|
|
11086
|
+
else if (showMerger) targetLog = mergerLog;
|
|
10678
11087
|
followLog(targetLog);
|
|
10679
11088
|
return;
|
|
10680
11089
|
}
|
|
@@ -10709,10 +11118,24 @@ function logsCommand(program2) {
|
|
|
10709
11118
|
console.log();
|
|
10710
11119
|
console.log(getLastLines(plannerLog, lineCount));
|
|
10711
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
|
+
}
|
|
10712
11133
|
console.log();
|
|
10713
11134
|
dim("---");
|
|
10714
11135
|
dim("Tip: Use -f to follow logs in real-time");
|
|
10715
|
-
dim(
|
|
11136
|
+
dim(
|
|
11137
|
+
" Use --type executor|reviewer|qa|audit|planner|analytics|merger to view specific logs"
|
|
11138
|
+
);
|
|
10716
11139
|
} catch (err) {
|
|
10717
11140
|
console.error(`Error reading logs: ${err instanceof Error ? err.message : String(err)}`);
|
|
10718
11141
|
process.exit(1);
|
|
@@ -10723,8 +11146,8 @@ function logsCommand(program2) {
|
|
|
10723
11146
|
// src/commands/prd.ts
|
|
10724
11147
|
init_dist();
|
|
10725
11148
|
import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
|
|
10726
|
-
import * as
|
|
10727
|
-
import * as
|
|
11149
|
+
import * as fs27 from "fs";
|
|
11150
|
+
import * as path29 from "path";
|
|
10728
11151
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10729
11152
|
import { dirname as dirname9 } from "path";
|
|
10730
11153
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
@@ -10732,21 +11155,21 @@ var __dirname3 = dirname9(__filename3);
|
|
|
10732
11155
|
function findTemplatesDir2(startDir) {
|
|
10733
11156
|
let current = startDir;
|
|
10734
11157
|
for (let i = 0; i < 8; i++) {
|
|
10735
|
-
const candidate =
|
|
10736
|
-
if (
|
|
11158
|
+
const candidate = path29.join(current, "templates");
|
|
11159
|
+
if (fs27.existsSync(candidate) && fs27.statSync(candidate).isDirectory()) {
|
|
10737
11160
|
return candidate;
|
|
10738
11161
|
}
|
|
10739
|
-
current =
|
|
11162
|
+
current = path29.dirname(current);
|
|
10740
11163
|
}
|
|
10741
|
-
return
|
|
11164
|
+
return path29.join(startDir, "templates");
|
|
10742
11165
|
}
|
|
10743
11166
|
var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
|
|
10744
11167
|
function slugify2(name) {
|
|
10745
11168
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
10746
11169
|
}
|
|
10747
11170
|
function getNextPrdNumber2(prdDir) {
|
|
10748
|
-
if (!
|
|
10749
|
-
const files =
|
|
11171
|
+
if (!fs27.existsSync(prdDir)) return 1;
|
|
11172
|
+
const files = fs27.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
10750
11173
|
const numbers = files.map((f) => {
|
|
10751
11174
|
const match = f.match(/^(\d+)-/);
|
|
10752
11175
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -10827,13 +11250,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
|
|
|
10827
11250
|
return null;
|
|
10828
11251
|
}
|
|
10829
11252
|
const ref = branch && branch !== "HEAD" ? branch : "main";
|
|
10830
|
-
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("/")}`;
|
|
10831
11254
|
} catch {
|
|
10832
11255
|
return null;
|
|
10833
11256
|
}
|
|
10834
11257
|
}
|
|
10835
11258
|
function buildGithubIssueBody(prdPath, projectDir, prdContent) {
|
|
10836
|
-
const relPath =
|
|
11259
|
+
const relPath = path29.relative(projectDir, prdPath);
|
|
10837
11260
|
const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
|
|
10838
11261
|
const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
|
|
10839
11262
|
return `${fileLine}
|
|
@@ -10844,13 +11267,13 @@ ${prdContent}
|
|
|
10844
11267
|
Created via \`night-watch prd create\`.`;
|
|
10845
11268
|
}
|
|
10846
11269
|
async function generatePrdWithClaude(description, projectDir, model) {
|
|
10847
|
-
const bundledTemplatePath =
|
|
10848
|
-
const installedTemplatePath =
|
|
10849
|
-
const templatePath =
|
|
10850
|
-
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)) {
|
|
10851
11274
|
return null;
|
|
10852
11275
|
}
|
|
10853
|
-
const planningPrinciples =
|
|
11276
|
+
const planningPrinciples = fs27.readFileSync(templatePath, "utf-8");
|
|
10854
11277
|
const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
|
|
10855
11278
|
const modelId = model ?? CLAUDE_MODEL_IDS.opus;
|
|
10856
11279
|
const env = buildNativeClaudeEnv(process.env);
|
|
@@ -10918,17 +11341,17 @@ function runGh(args, cwd) {
|
|
|
10918
11341
|
return null;
|
|
10919
11342
|
}
|
|
10920
11343
|
function createGithubIssue(title, prdPath, projectDir, prdContent) {
|
|
10921
|
-
const tmpFile =
|
|
11344
|
+
const tmpFile = path29.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
|
|
10922
11345
|
try {
|
|
10923
11346
|
const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
|
|
10924
|
-
|
|
11347
|
+
fs27.writeFileSync(tmpFile, body, "utf-8");
|
|
10925
11348
|
const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
|
|
10926
11349
|
return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
|
|
10927
11350
|
} catch {
|
|
10928
11351
|
return null;
|
|
10929
11352
|
} finally {
|
|
10930
11353
|
try {
|
|
10931
|
-
|
|
11354
|
+
fs27.unlinkSync(tmpFile);
|
|
10932
11355
|
} catch {
|
|
10933
11356
|
}
|
|
10934
11357
|
}
|
|
@@ -10940,10 +11363,10 @@ function parseDependencies(content) {
|
|
|
10940
11363
|
}
|
|
10941
11364
|
function isClaimActive(claimPath, maxRuntime) {
|
|
10942
11365
|
try {
|
|
10943
|
-
if (!
|
|
11366
|
+
if (!fs27.existsSync(claimPath)) {
|
|
10944
11367
|
return { active: false };
|
|
10945
11368
|
}
|
|
10946
|
-
const content =
|
|
11369
|
+
const content = fs27.readFileSync(claimPath, "utf-8");
|
|
10947
11370
|
const claim = JSON.parse(content);
|
|
10948
11371
|
const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
|
|
10949
11372
|
if (age < maxRuntime) {
|
|
@@ -10958,9 +11381,9 @@ function prdCommand(program2) {
|
|
|
10958
11381
|
const prd = program2.command("prd").description("Manage PRD files");
|
|
10959
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) => {
|
|
10960
11383
|
const projectDir = process.cwd();
|
|
10961
|
-
const prdDir =
|
|
10962
|
-
if (!
|
|
10963
|
-
|
|
11384
|
+
const prdDir = path29.join(projectDir, resolvePrdCreateDir());
|
|
11385
|
+
if (!fs27.existsSync(prdDir)) {
|
|
11386
|
+
fs27.mkdirSync(prdDir, { recursive: true });
|
|
10964
11387
|
}
|
|
10965
11388
|
const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
|
|
10966
11389
|
const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
|
|
@@ -10974,13 +11397,13 @@ function prdCommand(program2) {
|
|
|
10974
11397
|
const prdTitle = extractPrdTitle(generated) ?? name;
|
|
10975
11398
|
const slug = slugify2(prdTitle);
|
|
10976
11399
|
const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
|
|
10977
|
-
const filePath =
|
|
10978
|
-
if (
|
|
11400
|
+
const filePath = path29.join(prdDir, filename);
|
|
11401
|
+
if (fs27.existsSync(filePath)) {
|
|
10979
11402
|
error(`File already exists: ${filePath}`);
|
|
10980
11403
|
dim("Use a different name or remove the existing file.");
|
|
10981
11404
|
process.exit(1);
|
|
10982
11405
|
}
|
|
10983
|
-
|
|
11406
|
+
fs27.writeFileSync(filePath, generated, "utf-8");
|
|
10984
11407
|
header("PRD Created");
|
|
10985
11408
|
success(`Created: ${filePath}`);
|
|
10986
11409
|
const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
|
|
@@ -10993,15 +11416,15 @@ function prdCommand(program2) {
|
|
|
10993
11416
|
prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
|
|
10994
11417
|
const projectDir = process.cwd();
|
|
10995
11418
|
const config = loadConfig(projectDir);
|
|
10996
|
-
const absolutePrdDir =
|
|
10997
|
-
const doneDir =
|
|
11419
|
+
const absolutePrdDir = path29.join(projectDir, config.prdDir);
|
|
11420
|
+
const doneDir = path29.join(absolutePrdDir, "done");
|
|
10998
11421
|
const pending = [];
|
|
10999
|
-
if (
|
|
11000
|
-
const files =
|
|
11422
|
+
if (fs27.existsSync(absolutePrdDir)) {
|
|
11423
|
+
const files = fs27.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
|
|
11001
11424
|
for (const file of files) {
|
|
11002
|
-
const content =
|
|
11425
|
+
const content = fs27.readFileSync(path29.join(absolutePrdDir, file), "utf-8");
|
|
11003
11426
|
const deps = parseDependencies(content);
|
|
11004
|
-
const claimPath =
|
|
11427
|
+
const claimPath = path29.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
|
|
11005
11428
|
const claimStatus = isClaimActive(claimPath, config.maxRuntime);
|
|
11006
11429
|
pending.push({
|
|
11007
11430
|
name: file,
|
|
@@ -11012,10 +11435,10 @@ function prdCommand(program2) {
|
|
|
11012
11435
|
}
|
|
11013
11436
|
}
|
|
11014
11437
|
const done = [];
|
|
11015
|
-
if (
|
|
11016
|
-
const files =
|
|
11438
|
+
if (fs27.existsSync(doneDir)) {
|
|
11439
|
+
const files = fs27.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
11017
11440
|
for (const file of files) {
|
|
11018
|
-
const content =
|
|
11441
|
+
const content = fs27.readFileSync(path29.join(doneDir, file), "utf-8");
|
|
11019
11442
|
const deps = parseDependencies(content);
|
|
11020
11443
|
done.push({ name: file, dependencies: deps });
|
|
11021
11444
|
}
|
|
@@ -11052,7 +11475,7 @@ import blessed6 from "blessed";
|
|
|
11052
11475
|
// src/commands/dashboard/tab-status.ts
|
|
11053
11476
|
init_dist();
|
|
11054
11477
|
import blessed from "blessed";
|
|
11055
|
-
import * as
|
|
11478
|
+
import * as fs28 from "fs";
|
|
11056
11479
|
function sortPrdsByPriority(prds, priority) {
|
|
11057
11480
|
if (priority.length === 0) return prds;
|
|
11058
11481
|
const priorityMap = /* @__PURE__ */ new Map();
|
|
@@ -11148,7 +11571,7 @@ function renderLogPane(projectDir, logs) {
|
|
|
11148
11571
|
let newestMtime = 0;
|
|
11149
11572
|
for (const log of existingLogs) {
|
|
11150
11573
|
try {
|
|
11151
|
-
const stat =
|
|
11574
|
+
const stat = fs28.statSync(log.path);
|
|
11152
11575
|
if (stat.mtimeMs > newestMtime) {
|
|
11153
11576
|
newestMtime = stat.mtimeMs;
|
|
11154
11577
|
newestLog = log;
|
|
@@ -12803,8 +13226,8 @@ function createActionsTab() {
|
|
|
12803
13226
|
// src/commands/dashboard/tab-logs.ts
|
|
12804
13227
|
init_dist();
|
|
12805
13228
|
import blessed5 from "blessed";
|
|
12806
|
-
import * as
|
|
12807
|
-
import * as
|
|
13229
|
+
import * as fs29 from "fs";
|
|
13230
|
+
import * as path30 from "path";
|
|
12808
13231
|
var LOG_NAMES = ["executor", "reviewer"];
|
|
12809
13232
|
var LOG_LINES = 200;
|
|
12810
13233
|
function createLogsTab() {
|
|
@@ -12845,7 +13268,7 @@ function createLogsTab() {
|
|
|
12845
13268
|
let activeKeyHandlers = [];
|
|
12846
13269
|
let activeCtx = null;
|
|
12847
13270
|
function getLogPath(projectDir, logName) {
|
|
12848
|
-
return
|
|
13271
|
+
return path30.join(projectDir, "logs", `${logName}.log`);
|
|
12849
13272
|
}
|
|
12850
13273
|
function updateSelector() {
|
|
12851
13274
|
const tabs = LOG_NAMES.map((name, idx) => {
|
|
@@ -12859,7 +13282,7 @@ function createLogsTab() {
|
|
|
12859
13282
|
function loadLog(ctx) {
|
|
12860
13283
|
const logName = LOG_NAMES[selectedLogIndex];
|
|
12861
13284
|
const logPath = getLogPath(ctx.projectDir, logName);
|
|
12862
|
-
if (!
|
|
13285
|
+
if (!fs29.existsSync(logPath)) {
|
|
12863
13286
|
logContent.setContent(
|
|
12864
13287
|
`{yellow-fg}No ${logName}.log file found{/yellow-fg}
|
|
12865
13288
|
|
|
@@ -12869,7 +13292,7 @@ Log will appear here once the ${logName} runs.`
|
|
|
12869
13292
|
return;
|
|
12870
13293
|
}
|
|
12871
13294
|
try {
|
|
12872
|
-
const stat =
|
|
13295
|
+
const stat = fs29.statSync(logPath);
|
|
12873
13296
|
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
12874
13297
|
logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
|
|
12875
13298
|
} catch {
|
|
@@ -13367,12 +13790,12 @@ function doctorCommand(program2) {
|
|
|
13367
13790
|
|
|
13368
13791
|
// src/commands/serve.ts
|
|
13369
13792
|
init_dist();
|
|
13370
|
-
import * as
|
|
13793
|
+
import * as fs34 from "fs";
|
|
13371
13794
|
|
|
13372
13795
|
// ../server/dist/index.js
|
|
13373
13796
|
init_dist();
|
|
13374
|
-
import * as
|
|
13375
|
-
import * as
|
|
13797
|
+
import * as fs33 from "fs";
|
|
13798
|
+
import * as path36 from "path";
|
|
13376
13799
|
import { dirname as dirname11 } from "path";
|
|
13377
13800
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
13378
13801
|
import cors from "cors";
|
|
@@ -13457,8 +13880,8 @@ function setupGracefulShutdown(server, beforeClose) {
|
|
|
13457
13880
|
|
|
13458
13881
|
// ../server/dist/middleware/project-resolver.middleware.js
|
|
13459
13882
|
init_dist();
|
|
13460
|
-
import * as
|
|
13461
|
-
import * as
|
|
13883
|
+
import * as fs30 from "fs";
|
|
13884
|
+
import * as path31 from "path";
|
|
13462
13885
|
function resolveProject(req, res, next) {
|
|
13463
13886
|
const projectId = req.params.projectId;
|
|
13464
13887
|
const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
|
|
@@ -13468,7 +13891,7 @@ function resolveProject(req, res, next) {
|
|
|
13468
13891
|
res.status(404).json({ error: `Project not found: ${decodedId}` });
|
|
13469
13892
|
return;
|
|
13470
13893
|
}
|
|
13471
|
-
if (!
|
|
13894
|
+
if (!fs30.existsSync(entry.path) || !fs30.existsSync(path31.join(entry.path, CONFIG_FILE_NAME))) {
|
|
13472
13895
|
res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
|
|
13473
13896
|
return;
|
|
13474
13897
|
}
|
|
@@ -13513,8 +13936,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
|
|
|
13513
13936
|
|
|
13514
13937
|
// ../server/dist/routes/action.routes.js
|
|
13515
13938
|
init_dist();
|
|
13516
|
-
import * as
|
|
13517
|
-
import * as
|
|
13939
|
+
import * as fs31 from "fs";
|
|
13940
|
+
import * as path32 from "path";
|
|
13518
13941
|
import { execSync as execSync6, spawn as spawn6 } from "child_process";
|
|
13519
13942
|
import { Router } from "express";
|
|
13520
13943
|
|
|
@@ -13552,17 +13975,17 @@ function getBoardProvider(config, projectDir) {
|
|
|
13552
13975
|
function cleanOrphanedClaims(dir) {
|
|
13553
13976
|
let entries;
|
|
13554
13977
|
try {
|
|
13555
|
-
entries =
|
|
13978
|
+
entries = fs31.readdirSync(dir, { withFileTypes: true });
|
|
13556
13979
|
} catch {
|
|
13557
13980
|
return;
|
|
13558
13981
|
}
|
|
13559
13982
|
for (const entry of entries) {
|
|
13560
|
-
const fullPath =
|
|
13983
|
+
const fullPath = path32.join(dir, entry.name);
|
|
13561
13984
|
if (entry.isDirectory() && entry.name !== "done") {
|
|
13562
13985
|
cleanOrphanedClaims(fullPath);
|
|
13563
13986
|
} else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
|
|
13564
13987
|
try {
|
|
13565
|
-
|
|
13988
|
+
fs31.unlinkSync(fullPath);
|
|
13566
13989
|
} catch {
|
|
13567
13990
|
}
|
|
13568
13991
|
}
|
|
@@ -13717,19 +14140,19 @@ function createActionRouteHandlers(ctx) {
|
|
|
13717
14140
|
res.status(400).json({ error: "Invalid PRD name" });
|
|
13718
14141
|
return;
|
|
13719
14142
|
}
|
|
13720
|
-
const prdDir =
|
|
14143
|
+
const prdDir = path32.join(projectDir, config.prdDir);
|
|
13721
14144
|
const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
|
|
13722
|
-
const pendingPath =
|
|
13723
|
-
const donePath =
|
|
13724
|
-
if (
|
|
14145
|
+
const pendingPath = path32.join(prdDir, normalized);
|
|
14146
|
+
const donePath = path32.join(prdDir, "done", normalized);
|
|
14147
|
+
if (fs31.existsSync(pendingPath)) {
|
|
13725
14148
|
res.json({ message: `"${normalized}" is already pending` });
|
|
13726
14149
|
return;
|
|
13727
14150
|
}
|
|
13728
|
-
if (!
|
|
14151
|
+
if (!fs31.existsSync(donePath)) {
|
|
13729
14152
|
res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
|
|
13730
14153
|
return;
|
|
13731
14154
|
}
|
|
13732
|
-
|
|
14155
|
+
fs31.renameSync(donePath, pendingPath);
|
|
13733
14156
|
res.json({ message: `Moved "${normalized}" back to pending` });
|
|
13734
14157
|
} catch (error2) {
|
|
13735
14158
|
res.status(500).json({
|
|
@@ -13747,11 +14170,11 @@ function createActionRouteHandlers(ctx) {
|
|
|
13747
14170
|
res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
|
|
13748
14171
|
return;
|
|
13749
14172
|
}
|
|
13750
|
-
if (
|
|
13751
|
-
|
|
14173
|
+
if (fs31.existsSync(lockPath)) {
|
|
14174
|
+
fs31.unlinkSync(lockPath);
|
|
13752
14175
|
}
|
|
13753
|
-
const prdDir =
|
|
13754
|
-
if (
|
|
14176
|
+
const prdDir = path32.join(projectDir, config.prdDir);
|
|
14177
|
+
if (fs31.existsSync(prdDir)) {
|
|
13755
14178
|
cleanOrphanedClaims(prdDir);
|
|
13756
14179
|
}
|
|
13757
14180
|
broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
|
|
@@ -14306,6 +14729,9 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14306
14729
|
if (audit.maxRuntime !== void 0 && (typeof audit.maxRuntime !== "number" || audit.maxRuntime < 60)) {
|
|
14307
14730
|
return "audit.maxRuntime must be a number >= 60";
|
|
14308
14731
|
}
|
|
14732
|
+
if (audit.targetColumn !== void 0 && !BOARD_COLUMNS.includes(audit.targetColumn)) {
|
|
14733
|
+
return `audit.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
|
|
14734
|
+
}
|
|
14309
14735
|
}
|
|
14310
14736
|
if (changes.analytics !== void 0) {
|
|
14311
14737
|
if (typeof changes.analytics !== "object" || changes.analytics === null) {
|
|
@@ -14326,9 +14752,8 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14326
14752
|
return "analytics.lookbackDays must be a number between 1 and 90";
|
|
14327
14753
|
}
|
|
14328
14754
|
if (analytics.targetColumn !== void 0) {
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
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(", ")}`;
|
|
14332
14757
|
}
|
|
14333
14758
|
}
|
|
14334
14759
|
if (analytics.analysisPrompt !== void 0 && typeof analytics.analysisPrompt !== "string") {
|
|
@@ -14353,16 +14778,8 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14353
14778
|
if (typeof queue.priority !== "object" || queue.priority === null) {
|
|
14354
14779
|
return "queue.priority must be an object";
|
|
14355
14780
|
}
|
|
14356
|
-
const validQueueJobs = [
|
|
14357
|
-
"executor",
|
|
14358
|
-
"reviewer",
|
|
14359
|
-
"qa",
|
|
14360
|
-
"audit",
|
|
14361
|
-
"slicer",
|
|
14362
|
-
"analytics"
|
|
14363
|
-
];
|
|
14364
14781
|
for (const [jobType, value] of Object.entries(queue.priority)) {
|
|
14365
|
-
if (!
|
|
14782
|
+
if (!VALID_JOB_TYPES.includes(jobType)) {
|
|
14366
14783
|
return `queue.priority contains invalid job type: ${jobType}`;
|
|
14367
14784
|
}
|
|
14368
14785
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
@@ -14516,8 +14933,8 @@ function createProjectConfigRoutes() {
|
|
|
14516
14933
|
|
|
14517
14934
|
// ../server/dist/routes/doctor.routes.js
|
|
14518
14935
|
init_dist();
|
|
14519
|
-
import * as
|
|
14520
|
-
import * as
|
|
14936
|
+
import * as fs32 from "fs";
|
|
14937
|
+
import * as path33 from "path";
|
|
14521
14938
|
import { execSync as execSync7 } from "child_process";
|
|
14522
14939
|
import { Router as Router4 } from "express";
|
|
14523
14940
|
function runDoctorChecks(projectDir, config) {
|
|
@@ -14550,7 +14967,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14550
14967
|
});
|
|
14551
14968
|
}
|
|
14552
14969
|
try {
|
|
14553
|
-
const projectName =
|
|
14970
|
+
const projectName = path33.basename(projectDir);
|
|
14554
14971
|
const marker = generateMarker(projectName);
|
|
14555
14972
|
const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
|
|
14556
14973
|
if (crontabEntries.length > 0) {
|
|
@@ -14573,8 +14990,8 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14573
14990
|
detail: "Failed to check crontab"
|
|
14574
14991
|
});
|
|
14575
14992
|
}
|
|
14576
|
-
const configPath =
|
|
14577
|
-
if (
|
|
14993
|
+
const configPath = path33.join(projectDir, CONFIG_FILE_NAME);
|
|
14994
|
+
if (fs32.existsSync(configPath)) {
|
|
14578
14995
|
checks.push({ name: "config", status: "pass", detail: "Config file exists" });
|
|
14579
14996
|
} else {
|
|
14580
14997
|
checks.push({
|
|
@@ -14583,9 +15000,9 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14583
15000
|
detail: "Config file not found (using defaults)"
|
|
14584
15001
|
});
|
|
14585
15002
|
}
|
|
14586
|
-
const prdDir =
|
|
14587
|
-
if (
|
|
14588
|
-
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"));
|
|
14589
15006
|
checks.push({
|
|
14590
15007
|
name: "prdDir",
|
|
14591
15008
|
status: "pass",
|
|
@@ -14628,7 +15045,7 @@ function createProjectDoctorRoutes() {
|
|
|
14628
15045
|
|
|
14629
15046
|
// ../server/dist/routes/log.routes.js
|
|
14630
15047
|
init_dist();
|
|
14631
|
-
import * as
|
|
15048
|
+
import * as path34 from "path";
|
|
14632
15049
|
import { Router as Router5 } from "express";
|
|
14633
15050
|
function createLogRoutes(deps) {
|
|
14634
15051
|
const { projectDir } = deps;
|
|
@@ -14636,7 +15053,7 @@ function createLogRoutes(deps) {
|
|
|
14636
15053
|
router.get("/:name", (req, res) => {
|
|
14637
15054
|
try {
|
|
14638
15055
|
const { name } = req.params;
|
|
14639
|
-
const validNames =
|
|
15056
|
+
const validNames = Object.keys(LOG_FILE_NAMES);
|
|
14640
15057
|
if (!validNames.includes(name)) {
|
|
14641
15058
|
res.status(400).json({
|
|
14642
15059
|
error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
|
|
@@ -14647,7 +15064,7 @@ function createLogRoutes(deps) {
|
|
|
14647
15064
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
14648
15065
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
14649
15066
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
14650
|
-
const logPath =
|
|
15067
|
+
const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
14651
15068
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
14652
15069
|
res.json({ name, lines: logLines });
|
|
14653
15070
|
} catch (error2) {
|
|
@@ -14662,7 +15079,7 @@ function createProjectLogRoutes() {
|
|
|
14662
15079
|
try {
|
|
14663
15080
|
const projectDir = req.projectDir;
|
|
14664
15081
|
const { name } = req.params;
|
|
14665
|
-
const validNames =
|
|
15082
|
+
const validNames = Object.keys(LOG_FILE_NAMES);
|
|
14666
15083
|
if (!validNames.includes(name)) {
|
|
14667
15084
|
res.status(400).json({
|
|
14668
15085
|
error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
|
|
@@ -14673,7 +15090,7 @@ function createProjectLogRoutes() {
|
|
|
14673
15090
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
14674
15091
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
14675
15092
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
14676
|
-
const logPath =
|
|
15093
|
+
const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
14677
15094
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
14678
15095
|
res.json({ name, lines: logLines });
|
|
14679
15096
|
} catch (error2) {
|
|
@@ -14708,7 +15125,7 @@ function createProjectPrdRoutes() {
|
|
|
14708
15125
|
|
|
14709
15126
|
// ../server/dist/routes/roadmap.routes.js
|
|
14710
15127
|
init_dist();
|
|
14711
|
-
import * as
|
|
15128
|
+
import * as path35 from "path";
|
|
14712
15129
|
import { Router as Router7 } from "express";
|
|
14713
15130
|
function createRoadmapRouteHandlers(ctx) {
|
|
14714
15131
|
const router = Router7({ mergeParams: true });
|
|
@@ -14718,7 +15135,7 @@ function createRoadmapRouteHandlers(ctx) {
|
|
|
14718
15135
|
const config = ctx.getConfig(req);
|
|
14719
15136
|
const projectDir = ctx.getProjectDir(req);
|
|
14720
15137
|
const status = getRoadmapStatus(projectDir, config);
|
|
14721
|
-
const prdDir =
|
|
15138
|
+
const prdDir = path35.join(projectDir, config.prdDir);
|
|
14722
15139
|
const state = loadRoadmapState(prdDir);
|
|
14723
15140
|
res.json({
|
|
14724
15141
|
...status,
|
|
@@ -14843,12 +15260,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
14843
15260
|
const auditPlan = getSchedulingPlan(projectDir, config, "audit");
|
|
14844
15261
|
const plannerPlan = getSchedulingPlan(projectDir, config, "slicer");
|
|
14845
15262
|
const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
|
|
15263
|
+
const mergerPlan = getSchedulingPlan(projectDir, config, "merger");
|
|
14846
15264
|
const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
|
|
14847
15265
|
const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
|
|
14848
15266
|
const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
|
|
14849
15267
|
const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
|
|
14850
15268
|
const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
|
|
14851
15269
|
const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
|
|
15270
|
+
const mergerInstalled = installed && (config.merger?.enabled ?? false) && hasScheduledCommand(entries, "merge");
|
|
14852
15271
|
return {
|
|
14853
15272
|
executor: {
|
|
14854
15273
|
schedule: config.cronSchedule,
|
|
@@ -14898,6 +15317,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
14898
15317
|
manualDelayMinutes: analyticsPlan.manualDelayMinutes,
|
|
14899
15318
|
balancedDelayMinutes: analyticsPlan.balancedDelayMinutes
|
|
14900
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
|
+
},
|
|
14901
15328
|
paused: !installed,
|
|
14902
15329
|
schedulingPriority: config.schedulingPriority,
|
|
14903
15330
|
entries
|
|
@@ -15052,14 +15479,14 @@ function createQueueRoutes(deps) {
|
|
|
15052
15479
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
15053
15480
|
var __dirname4 = dirname11(__filename4);
|
|
15054
15481
|
function resolveWebDistPath() {
|
|
15055
|
-
const bundled =
|
|
15056
|
-
if (
|
|
15482
|
+
const bundled = path36.join(__dirname4, "web");
|
|
15483
|
+
if (fs33.existsSync(path36.join(bundled, "index.html")))
|
|
15057
15484
|
return bundled;
|
|
15058
15485
|
let d = __dirname4;
|
|
15059
15486
|
for (let i = 0; i < 8; i++) {
|
|
15060
|
-
if (
|
|
15061
|
-
const dev =
|
|
15062
|
-
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")))
|
|
15063
15490
|
return dev;
|
|
15064
15491
|
break;
|
|
15065
15492
|
}
|
|
@@ -15069,7 +15496,7 @@ function resolveWebDistPath() {
|
|
|
15069
15496
|
}
|
|
15070
15497
|
function setupStaticFiles(app) {
|
|
15071
15498
|
const webDistPath = resolveWebDistPath();
|
|
15072
|
-
if (
|
|
15499
|
+
if (fs33.existsSync(webDistPath)) {
|
|
15073
15500
|
app.use(express.static(webDistPath));
|
|
15074
15501
|
}
|
|
15075
15502
|
app.use((req, res, next) => {
|
|
@@ -15077,8 +15504,8 @@ function setupStaticFiles(app) {
|
|
|
15077
15504
|
next();
|
|
15078
15505
|
return;
|
|
15079
15506
|
}
|
|
15080
|
-
const indexPath =
|
|
15081
|
-
if (
|
|
15507
|
+
const indexPath = path36.resolve(webDistPath, "index.html");
|
|
15508
|
+
if (fs33.existsSync(indexPath)) {
|
|
15082
15509
|
res.sendFile(indexPath, (err) => {
|
|
15083
15510
|
if (err)
|
|
15084
15511
|
next();
|
|
@@ -15211,7 +15638,7 @@ function createGlobalApp() {
|
|
|
15211
15638
|
return app;
|
|
15212
15639
|
}
|
|
15213
15640
|
function bootContainer() {
|
|
15214
|
-
initContainer(
|
|
15641
|
+
initContainer(path36.dirname(getDbPath()));
|
|
15215
15642
|
}
|
|
15216
15643
|
function startServer(projectDir, port) {
|
|
15217
15644
|
bootContainer();
|
|
@@ -15264,8 +15691,8 @@ function isProcessRunning2(pid) {
|
|
|
15264
15691
|
}
|
|
15265
15692
|
function readPid(lockPath) {
|
|
15266
15693
|
try {
|
|
15267
|
-
if (!
|
|
15268
|
-
const raw =
|
|
15694
|
+
if (!fs34.existsSync(lockPath)) return null;
|
|
15695
|
+
const raw = fs34.readFileSync(lockPath, "utf-8").trim();
|
|
15269
15696
|
const pid = parseInt(raw, 10);
|
|
15270
15697
|
return Number.isFinite(pid) ? pid : null;
|
|
15271
15698
|
} catch {
|
|
@@ -15277,10 +15704,10 @@ function acquireServeLock(mode, port) {
|
|
|
15277
15704
|
let stalePidCleaned;
|
|
15278
15705
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
15279
15706
|
try {
|
|
15280
|
-
const fd =
|
|
15281
|
-
|
|
15707
|
+
const fd = fs34.openSync(lockPath, "wx");
|
|
15708
|
+
fs34.writeFileSync(fd, `${process.pid}
|
|
15282
15709
|
`);
|
|
15283
|
-
|
|
15710
|
+
fs34.closeSync(fd);
|
|
15284
15711
|
return { acquired: true, lockPath, stalePidCleaned };
|
|
15285
15712
|
} catch (error2) {
|
|
15286
15713
|
const err = error2;
|
|
@@ -15301,7 +15728,7 @@ function acquireServeLock(mode, port) {
|
|
|
15301
15728
|
};
|
|
15302
15729
|
}
|
|
15303
15730
|
try {
|
|
15304
|
-
|
|
15731
|
+
fs34.unlinkSync(lockPath);
|
|
15305
15732
|
if (existingPid) {
|
|
15306
15733
|
stalePidCleaned = existingPid;
|
|
15307
15734
|
}
|
|
@@ -15324,10 +15751,10 @@ function acquireServeLock(mode, port) {
|
|
|
15324
15751
|
}
|
|
15325
15752
|
function releaseServeLock(lockPath) {
|
|
15326
15753
|
try {
|
|
15327
|
-
if (!
|
|
15754
|
+
if (!fs34.existsSync(lockPath)) return;
|
|
15328
15755
|
const lockPid = readPid(lockPath);
|
|
15329
15756
|
if (lockPid !== null && lockPid !== process.pid) return;
|
|
15330
|
-
|
|
15757
|
+
fs34.unlinkSync(lockPath);
|
|
15331
15758
|
} catch {
|
|
15332
15759
|
}
|
|
15333
15760
|
}
|
|
@@ -15423,14 +15850,14 @@ function historyCommand(program2) {
|
|
|
15423
15850
|
// src/commands/update.ts
|
|
15424
15851
|
init_dist();
|
|
15425
15852
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
15426
|
-
import * as
|
|
15427
|
-
import * as
|
|
15853
|
+
import * as fs35 from "fs";
|
|
15854
|
+
import * as path37 from "path";
|
|
15428
15855
|
var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
|
|
15429
15856
|
function parseProjectDirs(projects, cwd) {
|
|
15430
15857
|
if (!projects || projects.trim().length === 0) {
|
|
15431
15858
|
return [cwd];
|
|
15432
15859
|
}
|
|
15433
|
-
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));
|
|
15434
15861
|
return Array.from(new Set(dirs));
|
|
15435
15862
|
}
|
|
15436
15863
|
function shouldInstallGlobal(options) {
|
|
@@ -15472,7 +15899,7 @@ function updateCommand(program2) {
|
|
|
15472
15899
|
}
|
|
15473
15900
|
const nightWatchBin = resolveNightWatchBin();
|
|
15474
15901
|
for (const projectDir of projectDirs) {
|
|
15475
|
-
if (!
|
|
15902
|
+
if (!fs35.existsSync(projectDir) || !fs35.statSync(projectDir).isDirectory()) {
|
|
15476
15903
|
warn(`Skipping invalid project directory: ${projectDir}`);
|
|
15477
15904
|
continue;
|
|
15478
15905
|
}
|
|
@@ -15516,8 +15943,8 @@ function prdStateCommand(program2) {
|
|
|
15516
15943
|
|
|
15517
15944
|
// src/commands/retry.ts
|
|
15518
15945
|
init_dist();
|
|
15519
|
-
import * as
|
|
15520
|
-
import * as
|
|
15946
|
+
import * as fs36 from "fs";
|
|
15947
|
+
import * as path38 from "path";
|
|
15521
15948
|
function normalizePrdName(name) {
|
|
15522
15949
|
if (!name.endsWith(".md")) {
|
|
15523
15950
|
return `${name}.md`;
|
|
@@ -15525,26 +15952,26 @@ function normalizePrdName(name) {
|
|
|
15525
15952
|
return name;
|
|
15526
15953
|
}
|
|
15527
15954
|
function getDonePrds(doneDir) {
|
|
15528
|
-
if (!
|
|
15955
|
+
if (!fs36.existsSync(doneDir)) {
|
|
15529
15956
|
return [];
|
|
15530
15957
|
}
|
|
15531
|
-
return
|
|
15958
|
+
return fs36.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
15532
15959
|
}
|
|
15533
15960
|
function retryCommand(program2) {
|
|
15534
15961
|
program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
|
|
15535
15962
|
const projectDir = process.cwd();
|
|
15536
15963
|
const config = loadConfig(projectDir);
|
|
15537
|
-
const prdDir =
|
|
15538
|
-
const doneDir =
|
|
15964
|
+
const prdDir = path38.join(projectDir, config.prdDir);
|
|
15965
|
+
const doneDir = path38.join(prdDir, "done");
|
|
15539
15966
|
const normalizedPrdName = normalizePrdName(prdName);
|
|
15540
|
-
const pendingPath =
|
|
15541
|
-
if (
|
|
15967
|
+
const pendingPath = path38.join(prdDir, normalizedPrdName);
|
|
15968
|
+
if (fs36.existsSync(pendingPath)) {
|
|
15542
15969
|
info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
|
|
15543
15970
|
return;
|
|
15544
15971
|
}
|
|
15545
|
-
const donePath =
|
|
15546
|
-
if (
|
|
15547
|
-
|
|
15972
|
+
const donePath = path38.join(doneDir, normalizedPrdName);
|
|
15973
|
+
if (fs36.existsSync(donePath)) {
|
|
15974
|
+
fs36.renameSync(donePath, pendingPath);
|
|
15548
15975
|
success(`Moved "${normalizedPrdName}" back to pending.`);
|
|
15549
15976
|
dim(`From: ${donePath}`);
|
|
15550
15977
|
dim(`To: ${pendingPath}`);
|
|
@@ -15796,7 +16223,7 @@ function prdsCommand(program2) {
|
|
|
15796
16223
|
|
|
15797
16224
|
// src/commands/cancel.ts
|
|
15798
16225
|
init_dist();
|
|
15799
|
-
import * as
|
|
16226
|
+
import * as fs37 from "fs";
|
|
15800
16227
|
import * as readline2 from "readline";
|
|
15801
16228
|
function getLockFilePaths2(projectDir) {
|
|
15802
16229
|
const runtimeKey = projectRuntimeKey(projectDir);
|
|
@@ -15843,7 +16270,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15843
16270
|
const pid = lockStatus.pid;
|
|
15844
16271
|
if (!lockStatus.running) {
|
|
15845
16272
|
try {
|
|
15846
|
-
|
|
16273
|
+
fs37.unlinkSync(lockPath);
|
|
15847
16274
|
return {
|
|
15848
16275
|
success: true,
|
|
15849
16276
|
message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
|
|
@@ -15881,7 +16308,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15881
16308
|
await sleep2(3e3);
|
|
15882
16309
|
if (!isProcessRunning3(pid)) {
|
|
15883
16310
|
try {
|
|
15884
|
-
|
|
16311
|
+
fs37.unlinkSync(lockPath);
|
|
15885
16312
|
} catch {
|
|
15886
16313
|
}
|
|
15887
16314
|
return {
|
|
@@ -15916,7 +16343,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15916
16343
|
await sleep2(500);
|
|
15917
16344
|
if (!isProcessRunning3(pid)) {
|
|
15918
16345
|
try {
|
|
15919
|
-
|
|
16346
|
+
fs37.unlinkSync(lockPath);
|
|
15920
16347
|
} catch {
|
|
15921
16348
|
}
|
|
15922
16349
|
return {
|
|
@@ -15977,67 +16404,69 @@ function cancelCommand(program2) {
|
|
|
15977
16404
|
|
|
15978
16405
|
// src/commands/slice.ts
|
|
15979
16406
|
init_dist();
|
|
15980
|
-
import * as
|
|
15981
|
-
import * as
|
|
16407
|
+
import * as fs38 from "fs";
|
|
16408
|
+
import * as path39 from "path";
|
|
15982
16409
|
function plannerLockPath2(projectDir) {
|
|
15983
16410
|
return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
|
|
15984
16411
|
}
|
|
15985
16412
|
function acquirePlannerLock(projectDir) {
|
|
15986
16413
|
const lockFile = plannerLockPath2(projectDir);
|
|
15987
|
-
if (
|
|
15988
|
-
const pidRaw =
|
|
16414
|
+
if (fs38.existsSync(lockFile)) {
|
|
16415
|
+
const pidRaw = fs38.readFileSync(lockFile, "utf-8").trim();
|
|
15989
16416
|
const pid = parseInt(pidRaw, 10);
|
|
15990
16417
|
if (!Number.isNaN(pid) && isProcessRunning(pid)) {
|
|
15991
16418
|
return { acquired: false, lockFile, pid };
|
|
15992
16419
|
}
|
|
15993
16420
|
try {
|
|
15994
|
-
|
|
16421
|
+
fs38.unlinkSync(lockFile);
|
|
15995
16422
|
} catch {
|
|
15996
16423
|
}
|
|
15997
16424
|
}
|
|
15998
|
-
|
|
16425
|
+
fs38.writeFileSync(lockFile, String(process.pid));
|
|
15999
16426
|
return { acquired: true, lockFile };
|
|
16000
16427
|
}
|
|
16001
16428
|
function releasePlannerLock(lockFile) {
|
|
16002
16429
|
try {
|
|
16003
|
-
if (
|
|
16004
|
-
|
|
16430
|
+
if (fs38.existsSync(lockFile)) {
|
|
16431
|
+
fs38.unlinkSync(lockFile);
|
|
16005
16432
|
}
|
|
16006
16433
|
} catch {
|
|
16007
16434
|
}
|
|
16008
16435
|
}
|
|
16009
16436
|
function resolvePlannerIssueColumn(config) {
|
|
16010
|
-
return config.roadmapScanner.issueColumn === "
|
|
16437
|
+
return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
|
|
16011
16438
|
}
|
|
16012
16439
|
function buildPlannerIssueBody(projectDir, config, result) {
|
|
16013
|
-
const relativePrdPath =
|
|
16014
|
-
const absolutePrdPath =
|
|
16440
|
+
const relativePrdPath = path39.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
|
|
16441
|
+
const absolutePrdPath = path39.join(projectDir, config.prdDir, result.file ?? "");
|
|
16015
16442
|
const sourceItem = result.item;
|
|
16016
16443
|
let prdContent;
|
|
16017
16444
|
try {
|
|
16018
|
-
prdContent =
|
|
16445
|
+
prdContent = fs38.readFileSync(absolutePrdPath, "utf-8");
|
|
16019
16446
|
} catch {
|
|
16020
16447
|
prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
|
|
16021
16448
|
}
|
|
16022
16449
|
const maxBodyChars = 6e4;
|
|
16023
16450
|
const truncated = prdContent.length > maxBodyChars;
|
|
16024
|
-
const
|
|
16451
|
+
const prdBody = truncated ? `${prdContent.slice(0, maxBodyChars)}
|
|
16025
16452
|
|
|
16026
16453
|
...[truncated]` : prdContent;
|
|
16027
|
-
const
|
|
16028
|
-
|
|
16029
|
-
`- Source
|
|
16030
|
-
sourceItem.description
|
|
16031
|
-
|
|
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
|
+
}
|
|
16032
16461
|
return [
|
|
16033
|
-
|
|
16462
|
+
prdBody,
|
|
16034
16463
|
"",
|
|
16035
|
-
|
|
16036
|
-
|
|
16464
|
+
"<details>",
|
|
16465
|
+
"<summary>Source metadata</summary>",
|
|
16037
16466
|
"",
|
|
16038
|
-
|
|
16467
|
+
...metaLines,
|
|
16039
16468
|
"",
|
|
16040
|
-
|
|
16469
|
+
"</details>"
|
|
16041
16470
|
].join("\n");
|
|
16042
16471
|
}
|
|
16043
16472
|
async function createPlannerIssue(projectDir, config, result) {
|
|
@@ -16052,9 +16481,11 @@ async function createPlannerIssue(projectDir, config, result) {
|
|
|
16052
16481
|
if (!board) {
|
|
16053
16482
|
return { created: false, skippedReason: "board-not-configured" };
|
|
16054
16483
|
}
|
|
16484
|
+
const issueTitle = `PRD: ${result.item.title}`;
|
|
16485
|
+
const normalizeTitle = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
|
|
16055
16486
|
const existingIssues = await provider.getAllIssues();
|
|
16056
16487
|
const existing = existingIssues.find(
|
|
16057
|
-
(issue2) => issue2.title
|
|
16488
|
+
(issue2) => normalizeTitle(issue2.title) === normalizeTitle(result.item.title)
|
|
16058
16489
|
);
|
|
16059
16490
|
if (existing) {
|
|
16060
16491
|
return {
|
|
@@ -16065,7 +16496,7 @@ async function createPlannerIssue(projectDir, config, result) {
|
|
|
16065
16496
|
};
|
|
16066
16497
|
}
|
|
16067
16498
|
const issue = await provider.createIssue({
|
|
16068
|
-
title:
|
|
16499
|
+
title: issueTitle,
|
|
16069
16500
|
body: buildPlannerIssueBody(projectDir, config, result),
|
|
16070
16501
|
column: resolvePlannerIssueColumn(config)
|
|
16071
16502
|
});
|
|
@@ -16218,7 +16649,7 @@ function sliceCommand(program2) {
|
|
|
16218
16649
|
if (!options.dryRun && result.sliced) {
|
|
16219
16650
|
await sendNotifications(config, {
|
|
16220
16651
|
event: "run_succeeded",
|
|
16221
|
-
projectName:
|
|
16652
|
+
projectName: path39.basename(projectDir),
|
|
16222
16653
|
exitCode,
|
|
16223
16654
|
provider: config.provider,
|
|
16224
16655
|
prTitle: result.item?.title
|
|
@@ -16226,7 +16657,7 @@ function sliceCommand(program2) {
|
|
|
16226
16657
|
} else if (!options.dryRun && !nothingPending) {
|
|
16227
16658
|
await sendNotifications(config, {
|
|
16228
16659
|
event: "run_failed",
|
|
16229
|
-
projectName:
|
|
16660
|
+
projectName: path39.basename(projectDir),
|
|
16230
16661
|
exitCode,
|
|
16231
16662
|
provider: config.provider
|
|
16232
16663
|
});
|
|
@@ -16243,20 +16674,20 @@ function sliceCommand(program2) {
|
|
|
16243
16674
|
// src/commands/state.ts
|
|
16244
16675
|
init_dist();
|
|
16245
16676
|
import * as os9 from "os";
|
|
16246
|
-
import * as
|
|
16677
|
+
import * as path40 from "path";
|
|
16247
16678
|
import chalk5 from "chalk";
|
|
16248
16679
|
import { Command } from "commander";
|
|
16249
16680
|
function createStateCommand() {
|
|
16250
16681
|
const state = new Command("state");
|
|
16251
16682
|
state.description("Manage Night Watch state");
|
|
16252
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) => {
|
|
16253
|
-
const nightWatchHome = process.env.NIGHT_WATCH_HOME ||
|
|
16684
|
+
const nightWatchHome = process.env.NIGHT_WATCH_HOME || path40.join(os9.homedir(), GLOBAL_CONFIG_DIR);
|
|
16254
16685
|
if (opts.dryRun) {
|
|
16255
16686
|
console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
|
|
16256
16687
|
console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
|
|
16257
|
-
console.log(` ${
|
|
16258
|
-
console.log(` ${
|
|
16259
|
-
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")}`);
|
|
16260
16691
|
console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
|
|
16261
16692
|
console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
|
|
16262
16693
|
return;
|
|
@@ -16294,8 +16725,8 @@ function createStateCommand() {
|
|
|
16294
16725
|
init_dist();
|
|
16295
16726
|
init_dist();
|
|
16296
16727
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
16297
|
-
import * as
|
|
16298
|
-
import * as
|
|
16728
|
+
import * as fs39 from "fs";
|
|
16729
|
+
import * as path41 from "path";
|
|
16299
16730
|
import * as readline3 from "readline";
|
|
16300
16731
|
import chalk6 from "chalk";
|
|
16301
16732
|
async function run(fn) {
|
|
@@ -16317,7 +16748,7 @@ function getProvider(config, cwd) {
|
|
|
16317
16748
|
return createBoardProvider(bp, cwd);
|
|
16318
16749
|
}
|
|
16319
16750
|
function defaultBoardTitle(cwd) {
|
|
16320
|
-
return `${
|
|
16751
|
+
return `${path41.basename(cwd)} Night Watch`;
|
|
16321
16752
|
}
|
|
16322
16753
|
async function ensureBoardConfigured(config, cwd, provider, options) {
|
|
16323
16754
|
if (config.boardProvider?.projectNumber) {
|
|
@@ -16516,11 +16947,11 @@ function boardCommand(program2) {
|
|
|
16516
16947
|
let body = options.body ?? "";
|
|
16517
16948
|
if (options.bodyFile) {
|
|
16518
16949
|
const filePath = options.bodyFile;
|
|
16519
|
-
if (!
|
|
16950
|
+
if (!fs39.existsSync(filePath)) {
|
|
16520
16951
|
console.error(`File not found: ${filePath}`);
|
|
16521
16952
|
process.exit(1);
|
|
16522
16953
|
}
|
|
16523
|
-
body =
|
|
16954
|
+
body = fs39.readFileSync(filePath, "utf-8");
|
|
16524
16955
|
}
|
|
16525
16956
|
const labels = [];
|
|
16526
16957
|
if (options.label) {
|
|
@@ -16761,12 +17192,12 @@ function boardCommand(program2) {
|
|
|
16761
17192
|
const config = loadConfig(cwd);
|
|
16762
17193
|
const provider = getProvider(config, cwd);
|
|
16763
17194
|
await ensureBoardConfigured(config, cwd, provider);
|
|
16764
|
-
const roadmapPath = options.roadmap ??
|
|
16765
|
-
if (!
|
|
17195
|
+
const roadmapPath = options.roadmap ?? path41.join(cwd, "ROADMAP.md");
|
|
17196
|
+
if (!fs39.existsSync(roadmapPath)) {
|
|
16766
17197
|
console.error(`Roadmap file not found: ${roadmapPath}`);
|
|
16767
17198
|
process.exit(1);
|
|
16768
17199
|
}
|
|
16769
|
-
const roadmapContent =
|
|
17200
|
+
const roadmapContent = fs39.readFileSync(roadmapPath, "utf-8");
|
|
16770
17201
|
const items = parseRoadmap(roadmapContent);
|
|
16771
17202
|
const uncheckedItems = getUncheckedItems(items);
|
|
16772
17203
|
if (uncheckedItems.length === 0) {
|
|
@@ -16890,11 +17321,11 @@ function boardCommand(program2) {
|
|
|
16890
17321
|
// src/commands/queue.ts
|
|
16891
17322
|
init_dist();
|
|
16892
17323
|
init_dist();
|
|
16893
|
-
import * as
|
|
17324
|
+
import * as path42 from "path";
|
|
16894
17325
|
import { spawn as spawn7 } from "child_process";
|
|
16895
17326
|
import chalk7 from "chalk";
|
|
16896
17327
|
import { Command as Command2 } from "commander";
|
|
16897
|
-
var
|
|
17328
|
+
var logger6 = createLogger("queue");
|
|
16898
17329
|
var VALID_JOB_TYPES2 = ["executor", "reviewer", "qa", "audit", "slicer", "planner"];
|
|
16899
17330
|
function formatTimestamp(unixTs) {
|
|
16900
17331
|
if (unixTs === null) return "-";
|
|
@@ -17010,7 +17441,7 @@ function createQueueCommand() {
|
|
|
17010
17441
|
process.exit(1);
|
|
17011
17442
|
}
|
|
17012
17443
|
}
|
|
17013
|
-
const projectName =
|
|
17444
|
+
const projectName = path42.basename(projectDir);
|
|
17014
17445
|
const queueConfig = loadConfig(projectDir).queue;
|
|
17015
17446
|
const id = enqueueJob(
|
|
17016
17447
|
projectDir,
|
|
@@ -17043,13 +17474,13 @@ function createQueueCommand() {
|
|
|
17043
17474
|
const configDir = _opts.projectDir ?? process.cwd();
|
|
17044
17475
|
const entry = dispatchNextJob(loadConfig(configDir).queue);
|
|
17045
17476
|
if (!entry) {
|
|
17046
|
-
|
|
17477
|
+
logger6.info("No pending jobs to dispatch");
|
|
17047
17478
|
return;
|
|
17048
17479
|
}
|
|
17049
|
-
|
|
17480
|
+
logger6.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
|
|
17050
17481
|
const scriptName = getScriptNameForJobType(entry.jobType);
|
|
17051
17482
|
if (!scriptName) {
|
|
17052
|
-
|
|
17483
|
+
logger6.error(`Unknown job type: ${entry.jobType}`);
|
|
17053
17484
|
return;
|
|
17054
17485
|
}
|
|
17055
17486
|
let projectEnv;
|
|
@@ -17068,7 +17499,7 @@ function createQueueCommand() {
|
|
|
17068
17499
|
NW_QUEUE_ENTRY_ID: String(entry.id)
|
|
17069
17500
|
};
|
|
17070
17501
|
const scriptPath = getScriptPath(scriptName);
|
|
17071
|
-
|
|
17502
|
+
logger6.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
|
|
17072
17503
|
try {
|
|
17073
17504
|
const child = spawn7("bash", [scriptPath, entry.projectPath], {
|
|
17074
17505
|
detached: true,
|
|
@@ -17077,11 +17508,11 @@ function createQueueCommand() {
|
|
|
17077
17508
|
cwd: entry.projectPath
|
|
17078
17509
|
});
|
|
17079
17510
|
child.unref();
|
|
17080
|
-
|
|
17511
|
+
logger6.info(`Spawned PID: ${child.pid}`);
|
|
17081
17512
|
markJobRunning(entry.id);
|
|
17082
17513
|
} catch (error2) {
|
|
17083
17514
|
updateJobStatus(entry.id, "pending");
|
|
17084
|
-
|
|
17515
|
+
logger6.error(
|
|
17085
17516
|
`Failed to dispatch ${entry.jobType} for ${entry.projectName}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
17086
17517
|
);
|
|
17087
17518
|
process.exit(1);
|
|
@@ -17096,7 +17527,7 @@ function createQueueCommand() {
|
|
|
17096
17527
|
process.exit(1);
|
|
17097
17528
|
}
|
|
17098
17529
|
const queueConfig = loadConfig(projectDir).queue;
|
|
17099
|
-
const projectName =
|
|
17530
|
+
const projectName = path42.basename(projectDir);
|
|
17100
17531
|
const result = claimJobSlot(
|
|
17101
17532
|
projectDir,
|
|
17102
17533
|
projectName,
|
|
@@ -17233,7 +17664,7 @@ function notifyCommand(program2) {
|
|
|
17233
17664
|
|
|
17234
17665
|
// src/commands/summary.ts
|
|
17235
17666
|
init_dist();
|
|
17236
|
-
import
|
|
17667
|
+
import path43 from "path";
|
|
17237
17668
|
import chalk8 from "chalk";
|
|
17238
17669
|
function formatDuration2(seconds) {
|
|
17239
17670
|
if (seconds === null) return "-";
|
|
@@ -17263,7 +17694,7 @@ function formatJobStatus(status) {
|
|
|
17263
17694
|
return chalk8.dim(status);
|
|
17264
17695
|
}
|
|
17265
17696
|
function getProjectName2(projectPath) {
|
|
17266
|
-
return
|
|
17697
|
+
return path43.basename(projectPath) || projectPath;
|
|
17267
17698
|
}
|
|
17268
17699
|
function formatProvider(providerKey) {
|
|
17269
17700
|
return providerKey.split(":")[0] || providerKey;
|
|
@@ -17382,7 +17813,7 @@ function summaryCommand(program2) {
|
|
|
17382
17813
|
// src/commands/resolve.ts
|
|
17383
17814
|
init_dist();
|
|
17384
17815
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
17385
|
-
import * as
|
|
17816
|
+
import * as path44 from "path";
|
|
17386
17817
|
function buildEnvVars6(config, options) {
|
|
17387
17818
|
const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
|
|
17388
17819
|
env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
|
|
@@ -17512,7 +17943,7 @@ ${stderr}`);
|
|
|
17512
17943
|
const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
|
|
17513
17944
|
await sendNotifications(config, {
|
|
17514
17945
|
event: notificationEvent,
|
|
17515
|
-
projectName:
|
|
17946
|
+
projectName: path44.basename(projectDir),
|
|
17516
17947
|
exitCode,
|
|
17517
17948
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
17518
17949
|
});
|
|
@@ -17525,19 +17956,141 @@ ${stderr}`);
|
|
|
17525
17956
|
});
|
|
17526
17957
|
}
|
|
17527
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
|
+
|
|
17528
18081
|
// src/cli.ts
|
|
17529
18082
|
var __filename5 = fileURLToPath6(import.meta.url);
|
|
17530
18083
|
var __dirname5 = dirname12(__filename5);
|
|
17531
18084
|
function findPackageRoot(dir) {
|
|
17532
18085
|
let d = dir;
|
|
17533
18086
|
for (let i = 0; i < 5; i++) {
|
|
17534
|
-
if (
|
|
18087
|
+
if (existsSync31(join37(d, "package.json"))) return d;
|
|
17535
18088
|
d = dirname12(d);
|
|
17536
18089
|
}
|
|
17537
18090
|
return dir;
|
|
17538
18091
|
}
|
|
17539
18092
|
var packageRoot = findPackageRoot(__dirname5);
|
|
17540
|
-
var packageJson = JSON.parse(
|
|
18093
|
+
var packageJson = JSON.parse(readFileSync20(join37(packageRoot, "package.json"), "utf-8"));
|
|
17541
18094
|
var program = new Command3();
|
|
17542
18095
|
program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
|
|
17543
18096
|
initCommand(program);
|
|
@@ -17568,4 +18121,5 @@ queueCommand(program);
|
|
|
17568
18121
|
notifyCommand(program);
|
|
17569
18122
|
summaryCommand(program);
|
|
17570
18123
|
resolveCommand(program);
|
|
18124
|
+
mergeCommand(program);
|
|
17571
18125
|
program.parse();
|