@jonit-dev/night-watch-cli 1.8.8-beta.9 → 1.8.10-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1086 -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-DGpU39Cp.css +1 -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/assets/index-rfU713Zm.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,9 @@ 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: "pr-resolver", fileName: `${PR_RESOLVER_LOG_NAME}.log` },
|
|
4039
|
+
{ name: "merger", fileName: `${MERGER_LOG_NAME}.log` }
|
|
3954
4040
|
];
|
|
3955
4041
|
return logEntries.map(({ name, fileName }) => {
|
|
3956
4042
|
const logPath = path6.join(projectDir, LOG_DIR, fileName);
|
|
@@ -3980,13 +4066,17 @@ async function fetchStatusSnapshot(projectDir, config) {
|
|
|
3980
4066
|
const auditLock = checkLockFile(auditLockPath(projectDir));
|
|
3981
4067
|
const plannerLock = checkLockFile(plannerLockPath(projectDir));
|
|
3982
4068
|
const analyticsLock = checkLockFile(analyticsLockPath(projectDir));
|
|
4069
|
+
const prResolverLock = checkLockFile(prResolverLockPath(projectDir));
|
|
4070
|
+
const mergerLock = checkLockFile(mergerLockPath(projectDir));
|
|
3983
4071
|
const processes = [
|
|
3984
4072
|
{ name: "executor", running: executorLock.running, pid: executorLock.pid },
|
|
3985
4073
|
{ name: "reviewer", running: reviewerLock.running, pid: reviewerLock.pid },
|
|
3986
4074
|
{ name: "qa", running: qaLock.running, pid: qaLock.pid },
|
|
3987
4075
|
{ name: "audit", running: auditLock.running, pid: auditLock.pid },
|
|
3988
4076
|
{ name: "planner", running: plannerLock.running, pid: plannerLock.pid },
|
|
3989
|
-
{ name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid }
|
|
4077
|
+
{ name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid },
|
|
4078
|
+
{ name: "pr-resolver", running: prResolverLock.running, pid: prResolverLock.pid },
|
|
4079
|
+
{ name: "merger", running: mergerLock.running, pid: mergerLock.pid }
|
|
3990
4080
|
];
|
|
3991
4081
|
const prds = collectPrdInfo(projectDir, config.prdDir, config.maxRuntime);
|
|
3992
4082
|
const prs = await collectPrInfo(projectDir, config.branchPatterns);
|
|
@@ -4900,6 +4990,10 @@ function getEventEmoji(event) {
|
|
|
4900
4990
|
return "\u2705";
|
|
4901
4991
|
case "pr_resolver_failed":
|
|
4902
4992
|
return "\u274C";
|
|
4993
|
+
case "merge_completed":
|
|
4994
|
+
return "\u{1F500}";
|
|
4995
|
+
case "merge_failed":
|
|
4996
|
+
return "\u274C";
|
|
4903
4997
|
}
|
|
4904
4998
|
}
|
|
4905
4999
|
function getEventTitle(event) {
|
|
@@ -4930,6 +5024,10 @@ function getEventTitle(event) {
|
|
|
4930
5024
|
return "PR Conflict Resolved";
|
|
4931
5025
|
case "pr_resolver_failed":
|
|
4932
5026
|
return "PR Resolver Failed";
|
|
5027
|
+
case "merge_completed":
|
|
5028
|
+
return "PR Merged";
|
|
5029
|
+
case "merge_failed":
|
|
5030
|
+
return "Merge Failed";
|
|
4933
5031
|
}
|
|
4934
5032
|
}
|
|
4935
5033
|
function getEventColor(event) {
|
|
@@ -4960,6 +5058,10 @@ function getEventColor(event) {
|
|
|
4960
5058
|
return 65280;
|
|
4961
5059
|
case "pr_resolver_failed":
|
|
4962
5060
|
return 16711680;
|
|
5061
|
+
case "merge_completed":
|
|
5062
|
+
return 10181046;
|
|
5063
|
+
case "merge_failed":
|
|
5064
|
+
return 16711680;
|
|
4963
5065
|
}
|
|
4964
5066
|
}
|
|
4965
5067
|
function buildDescription(ctx) {
|
|
@@ -5562,19 +5664,96 @@ var init_roadmap_parser = __esm({
|
|
|
5562
5664
|
}
|
|
5563
5665
|
});
|
|
5564
5666
|
|
|
5565
|
-
// ../core/dist/
|
|
5667
|
+
// ../core/dist/audit/report.js
|
|
5566
5668
|
import * as fs14 from "fs";
|
|
5567
5669
|
import * as path13 from "path";
|
|
5670
|
+
function normalizeAuditSeverity(raw) {
|
|
5671
|
+
const normalized = raw.trim().toLowerCase();
|
|
5672
|
+
if (normalized === "critical")
|
|
5673
|
+
return "critical";
|
|
5674
|
+
if (normalized === "high")
|
|
5675
|
+
return "high";
|
|
5676
|
+
if (normalized === "low")
|
|
5677
|
+
return "low";
|
|
5678
|
+
return "medium";
|
|
5679
|
+
}
|
|
5680
|
+
function extractAuditField(block, field) {
|
|
5681
|
+
const pattern = new RegExp(`- \\*\\*${field}\\*\\*:\\s*([\\s\\S]*?)(?=\\n- \\*\\*|\\n###\\s+Finding\\s+\\d+|$)`, "i");
|
|
5682
|
+
const match = block.match(pattern);
|
|
5683
|
+
if (!match)
|
|
5684
|
+
return "";
|
|
5685
|
+
return match[1].replace(/`/g, "").replace(/\r/g, "").trim();
|
|
5686
|
+
}
|
|
5687
|
+
function parseAuditFindings(reportContent) {
|
|
5688
|
+
const headingRegex = /^###\s+Finding\s+(\d+)\s*$/gm;
|
|
5689
|
+
const headings = [];
|
|
5690
|
+
let match;
|
|
5691
|
+
while ((match = headingRegex.exec(reportContent)) !== null) {
|
|
5692
|
+
const number = parseInt(match[1], 10);
|
|
5693
|
+
if (!Number.isNaN(number)) {
|
|
5694
|
+
headings.push({
|
|
5695
|
+
number,
|
|
5696
|
+
bodyStart: headingRegex.lastIndex,
|
|
5697
|
+
headingStart: match.index
|
|
5698
|
+
});
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
if (headings.length === 0) {
|
|
5702
|
+
return [];
|
|
5703
|
+
}
|
|
5704
|
+
const findings = [];
|
|
5705
|
+
for (let i = 0; i < headings.length; i++) {
|
|
5706
|
+
const current = headings[i];
|
|
5707
|
+
const next = headings[i + 1];
|
|
5708
|
+
const block = reportContent.slice(current.bodyStart, next?.headingStart ?? reportContent.length);
|
|
5709
|
+
const severityRaw = extractAuditField(block, "Severity");
|
|
5710
|
+
const category = extractAuditField(block, "Category") || "uncategorized";
|
|
5711
|
+
const location = extractAuditField(block, "Location") || "unknown location";
|
|
5712
|
+
const description = extractAuditField(block, "Description") || "No description provided";
|
|
5713
|
+
const snippet = extractAuditField(block, "Snippet");
|
|
5714
|
+
const suggestedFix = extractAuditField(block, "Suggested Fix") || "No suggested fix provided";
|
|
5715
|
+
findings.push({
|
|
5716
|
+
number: current.number,
|
|
5717
|
+
severity: normalizeAuditSeverity(severityRaw),
|
|
5718
|
+
category,
|
|
5719
|
+
location,
|
|
5720
|
+
description,
|
|
5721
|
+
snippet,
|
|
5722
|
+
suggestedFix
|
|
5723
|
+
});
|
|
5724
|
+
}
|
|
5725
|
+
return findings;
|
|
5726
|
+
}
|
|
5727
|
+
function loadAuditFindings(projectDir) {
|
|
5728
|
+
const reportPath = path13.join(projectDir, "logs", "audit-report.md");
|
|
5729
|
+
if (!fs14.existsSync(reportPath)) {
|
|
5730
|
+
return [];
|
|
5731
|
+
}
|
|
5732
|
+
const reportContent = fs14.readFileSync(reportPath, "utf-8");
|
|
5733
|
+
if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
|
|
5734
|
+
return [];
|
|
5735
|
+
}
|
|
5736
|
+
return parseAuditFindings(reportContent);
|
|
5737
|
+
}
|
|
5738
|
+
var init_report = __esm({
|
|
5739
|
+
"../core/dist/audit/report.js"() {
|
|
5740
|
+
"use strict";
|
|
5741
|
+
}
|
|
5742
|
+
});
|
|
5743
|
+
|
|
5744
|
+
// ../core/dist/utils/roadmap-state.js
|
|
5745
|
+
import * as fs15 from "fs";
|
|
5746
|
+
import * as path14 from "path";
|
|
5568
5747
|
function getStateFilePath(prdDir) {
|
|
5569
|
-
return
|
|
5748
|
+
return path14.join(prdDir, STATE_FILE_NAME);
|
|
5570
5749
|
}
|
|
5571
5750
|
function readJsonState(prdDir) {
|
|
5572
5751
|
const statePath = getStateFilePath(prdDir);
|
|
5573
|
-
if (!
|
|
5752
|
+
if (!fs15.existsSync(statePath)) {
|
|
5574
5753
|
return null;
|
|
5575
5754
|
}
|
|
5576
5755
|
try {
|
|
5577
|
-
const content =
|
|
5756
|
+
const content = fs15.readFileSync(statePath, "utf-8");
|
|
5578
5757
|
const parsed = JSON.parse(content);
|
|
5579
5758
|
if (typeof parsed !== "object" || parsed === null) {
|
|
5580
5759
|
return null;
|
|
@@ -5612,11 +5791,11 @@ function saveRoadmapState(prdDir, state) {
|
|
|
5612
5791
|
const { roadmapState } = getRepositories();
|
|
5613
5792
|
roadmapState.save(prdDir, state);
|
|
5614
5793
|
const statePath = getStateFilePath(prdDir);
|
|
5615
|
-
const dir =
|
|
5616
|
-
if (!
|
|
5617
|
-
|
|
5794
|
+
const dir = path14.dirname(statePath);
|
|
5795
|
+
if (!fs15.existsSync(dir)) {
|
|
5796
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
5618
5797
|
}
|
|
5619
|
-
|
|
5798
|
+
fs15.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
5620
5799
|
}
|
|
5621
5800
|
function createEmptyState() {
|
|
5622
5801
|
return {
|
|
@@ -5656,16 +5835,16 @@ var init_roadmap_state = __esm({
|
|
|
5656
5835
|
});
|
|
5657
5836
|
|
|
5658
5837
|
// ../core/dist/templates/slicer-prompt.js
|
|
5659
|
-
import * as
|
|
5660
|
-
import * as
|
|
5838
|
+
import * as fs16 from "fs";
|
|
5839
|
+
import * as path15 from "path";
|
|
5661
5840
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5662
5841
|
function loadSlicerTemplate(templateDir) {
|
|
5663
5842
|
if (cachedTemplate) {
|
|
5664
5843
|
return cachedTemplate;
|
|
5665
5844
|
}
|
|
5666
|
-
const templatePath = templateDir ?
|
|
5845
|
+
const templatePath = templateDir ? path15.join(templateDir, "slicer.md") : path15.resolve(__dirname, "..", "..", "templates", "slicer.md");
|
|
5667
5846
|
try {
|
|
5668
|
-
cachedTemplate =
|
|
5847
|
+
cachedTemplate = fs16.readFileSync(templatePath, "utf-8");
|
|
5669
5848
|
return cachedTemplate;
|
|
5670
5849
|
} catch (error2) {
|
|
5671
5850
|
console.warn(`Warning: Could not load slicer template from ${templatePath}, using default:`, error2 instanceof Error ? error2.message : String(error2));
|
|
@@ -5690,7 +5869,7 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
|
|
|
5690
5869
|
title,
|
|
5691
5870
|
section,
|
|
5692
5871
|
description: description || "(No description provided)",
|
|
5693
|
-
outputFilePath:
|
|
5872
|
+
outputFilePath: path15.join(prdDir, prdFilename),
|
|
5694
5873
|
prdDir
|
|
5695
5874
|
};
|
|
5696
5875
|
}
|
|
@@ -5699,10 +5878,10 @@ var init_slicer_prompt = __esm({
|
|
|
5699
5878
|
"../core/dist/templates/slicer-prompt.js"() {
|
|
5700
5879
|
"use strict";
|
|
5701
5880
|
__filename = fileURLToPath2(import.meta.url);
|
|
5702
|
-
__dirname =
|
|
5703
|
-
DEFAULT_SLICER_TEMPLATE = `You are a **
|
|
5881
|
+
__dirname = path15.dirname(__filename);
|
|
5882
|
+
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
5883
|
|
|
5705
|
-
When this activates: \`
|
|
5884
|
+
When this activates: \`Planning Mode: Principal Architect\`
|
|
5706
5885
|
|
|
5707
5886
|
---
|
|
5708
5887
|
|
|
@@ -5723,22 +5902,16 @@ The PRD directory is: \`{{PRD_DIR}}\`
|
|
|
5723
5902
|
|
|
5724
5903
|
## Your Task
|
|
5725
5904
|
|
|
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}}\`.
|
|
5905
|
+
1. **Explore the Codebase** \u2014 Read relevant existing files to understand structure, patterns, and conventions.
|
|
5906
|
+
2. **Assess Complexity** \u2014 Score using the rubric below and determine LOW / MEDIUM / HIGH.
|
|
5907
|
+
3. **Write a Complete PRD** \u2014 Follow the exact template structure below. Every section must be filled with concrete information.
|
|
5908
|
+
4. **Write the PRD File** \u2014 Use the Write tool to create the PRD file at \`{{OUTPUT_FILE_PATH}}\`.
|
|
5735
5909
|
|
|
5736
5910
|
---
|
|
5737
5911
|
|
|
5738
5912
|
## Complexity Scoring
|
|
5739
5913
|
|
|
5740
5914
|
\`\`\`
|
|
5741
|
-
COMPLEXITY SCORE (sum all that apply):
|
|
5742
5915
|
+1 Touches 1-5 files
|
|
5743
5916
|
+2 Touches 6-10 files
|
|
5744
5917
|
+3 Touches 10+ files
|
|
@@ -5750,7 +5923,7 @@ COMPLEXITY SCORE (sum all that apply):
|
|
|
5750
5923
|
|
|
5751
5924
|
| Score | Level | Template Mode |
|
|
5752
5925
|
| ----- | ------ | ----------------------------------------------- |
|
|
5753
|
-
| 1-3 | LOW | Minimal (skip sections marked
|
|
5926
|
+
| 1-3 | LOW | Minimal (skip sections marked MEDIUM/HIGH) |
|
|
5754
5927
|
| 4-6 | MEDIUM | Standard (all sections) |
|
|
5755
5928
|
| 7+ | HIGH | Full + mandatory checkpoints every phase |
|
|
5756
5929
|
\`\`\`
|
|
@@ -5759,25 +5932,77 @@ COMPLEXITY SCORE (sum all that apply):
|
|
|
5759
5932
|
|
|
5760
5933
|
## PRD Template Structure
|
|
5761
5934
|
|
|
5762
|
-
Your PRD MUST
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5935
|
+
Your PRD MUST use this structure. Replace every [bracketed placeholder] with real content.
|
|
5936
|
+
|
|
5937
|
+
# PRD: [Title]
|
|
5938
|
+
|
|
5939
|
+
**Complexity: [SCORE] \u2192 [LEVEL] mode**
|
|
5940
|
+
|
|
5941
|
+
## 1. Context
|
|
5942
|
+
|
|
5943
|
+
**Problem:** [1-2 sentences]
|
|
5944
|
+
|
|
5945
|
+
**Files Analyzed:**
|
|
5946
|
+
- \`path/to/file.ts\` \u2014 [what you found]
|
|
5947
|
+
|
|
5948
|
+
**Current Behavior:**
|
|
5949
|
+
- [3-5 bullets]
|
|
5950
|
+
|
|
5951
|
+
### Integration Points
|
|
5952
|
+
- Entry point: [cron / CLI / event / route]
|
|
5953
|
+
- Caller file: [file invoking new code]
|
|
5954
|
+
- User flow: User does X \u2192 triggers Y \u2192 result Z
|
|
5955
|
+
|
|
5956
|
+
## 2. Solution
|
|
5957
|
+
|
|
5958
|
+
**Approach:**
|
|
5959
|
+
- [3-5 bullets]
|
|
5960
|
+
|
|
5961
|
+
**Key Decisions:** [library choices, error handling, reused utilities]
|
|
5962
|
+
|
|
5963
|
+
**Data Changes:** [schema changes, or "None"]
|
|
5964
|
+
|
|
5965
|
+
## 3. Sequence Flow (MEDIUM/HIGH only)
|
|
5966
|
+
|
|
5967
|
+
[mermaid sequenceDiagram]
|
|
5968
|
+
|
|
5969
|
+
## 4. Execution Phases
|
|
5970
|
+
|
|
5971
|
+
### Phase N: [Name] \u2014 [User-visible outcome]
|
|
5972
|
+
|
|
5973
|
+
**Files (max 5):**
|
|
5974
|
+
- \`src/path/file.ts\` \u2014 [what changes]
|
|
5975
|
+
|
|
5976
|
+
**Implementation:**
|
|
5977
|
+
- [ ] Step 1
|
|
5978
|
+
|
|
5979
|
+
**Tests Required:**
|
|
5980
|
+
| Test File | Test Name | Assertion |
|
|
5981
|
+
|-----------|-----------|-----------|
|
|
5982
|
+
| \`src/__tests__/feature.test.ts\` | \`should X when Y\` | \`expect(r).toBe(Z)\` |
|
|
5983
|
+
|
|
5984
|
+
**Checkpoint:** Run \`yarn verify\` and related tests after this phase.
|
|
5985
|
+
|
|
5986
|
+
## 5. Acceptance Criteria
|
|
5987
|
+
|
|
5988
|
+
- [ ] All phases complete
|
|
5989
|
+
- [ ] All tests pass
|
|
5990
|
+
- [ ] \`yarn verify\` passes
|
|
5991
|
+
- [ ] Feature is reachable (not orphaned code)
|
|
5768
5992
|
|
|
5769
5993
|
---
|
|
5770
5994
|
|
|
5771
5995
|
## Critical Instructions
|
|
5772
5996
|
|
|
5773
|
-
1.
|
|
5774
|
-
2.
|
|
5775
|
-
3.
|
|
5776
|
-
4.
|
|
5777
|
-
5.
|
|
5778
|
-
6.
|
|
5997
|
+
1. Read all relevant files BEFORE writing the PRD
|
|
5998
|
+
2. Follow existing patterns \u2014 use \`@/*\` path aliases, match naming conventions
|
|
5999
|
+
3. Include concrete file paths and implementation steps
|
|
6000
|
+
4. Include specific test names and assertions
|
|
6001
|
+
5. Use the Write tool to create the file at \`{{OUTPUT_FILE_PATH}}\`
|
|
6002
|
+
6. No placeholder text in the final PRD
|
|
6003
|
+
7. The PRD is the GitHub issue body \u2014 make it self-contained
|
|
5779
6004
|
|
|
5780
|
-
DO NOT leave placeholder text
|
|
6005
|
+
DO NOT leave [bracketed placeholder] text in the output.
|
|
5781
6006
|
DO NOT skip any sections.
|
|
5782
6007
|
DO NOT forget to write the file.
|
|
5783
6008
|
`;
|
|
@@ -5786,65 +6011,10 @@ DO NOT forget to write the file.
|
|
|
5786
6011
|
});
|
|
5787
6012
|
|
|
5788
6013
|
// ../core/dist/utils/roadmap-scanner.js
|
|
5789
|
-
import * as
|
|
5790
|
-
import * as
|
|
6014
|
+
import * as fs17 from "fs";
|
|
6015
|
+
import * as path16 from "path";
|
|
5791
6016
|
import { spawn } from "child_process";
|
|
5792
6017
|
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
6018
|
function auditSeverityRank(severity) {
|
|
5849
6019
|
switch (severity) {
|
|
5850
6020
|
case "critical":
|
|
@@ -5888,22 +6058,14 @@ function auditFindingToRoadmapItem(finding) {
|
|
|
5888
6058
|
};
|
|
5889
6059
|
}
|
|
5890
6060
|
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);
|
|
6061
|
+
const findings = loadAuditFindings(projectDir);
|
|
5900
6062
|
findings.sort((a, b) => auditSeverityRank(a.severity) - auditSeverityRank(b.severity) || a.number - b.number);
|
|
5901
6063
|
return findings.map(auditFindingToRoadmapItem);
|
|
5902
6064
|
}
|
|
5903
6065
|
function getRoadmapStatus(projectDir, config) {
|
|
5904
|
-
const roadmapPath =
|
|
6066
|
+
const roadmapPath = path16.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
5905
6067
|
const scannerEnabled = config.roadmapScanner.enabled;
|
|
5906
|
-
if (!
|
|
6068
|
+
if (!fs17.existsSync(roadmapPath)) {
|
|
5907
6069
|
return {
|
|
5908
6070
|
found: false,
|
|
5909
6071
|
enabled: scannerEnabled,
|
|
@@ -5914,9 +6076,9 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
5914
6076
|
items: []
|
|
5915
6077
|
};
|
|
5916
6078
|
}
|
|
5917
|
-
const content =
|
|
6079
|
+
const content = fs17.readFileSync(roadmapPath, "utf-8");
|
|
5918
6080
|
const items = parseRoadmap(content);
|
|
5919
|
-
const prdDir =
|
|
6081
|
+
const prdDir = path16.join(projectDir, config.prdDir);
|
|
5920
6082
|
const state = loadRoadmapState(prdDir);
|
|
5921
6083
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
5922
6084
|
const statusItems = items.map((item) => {
|
|
@@ -5953,10 +6115,10 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
5953
6115
|
}
|
|
5954
6116
|
function scanExistingPrdSlugs(prdDir) {
|
|
5955
6117
|
const slugs = /* @__PURE__ */ new Set();
|
|
5956
|
-
if (!
|
|
6118
|
+
if (!fs17.existsSync(prdDir)) {
|
|
5957
6119
|
return slugs;
|
|
5958
6120
|
}
|
|
5959
|
-
const files =
|
|
6121
|
+
const files = fs17.readdirSync(prdDir);
|
|
5960
6122
|
for (const file of files) {
|
|
5961
6123
|
if (!file.endsWith(".md")) {
|
|
5962
6124
|
continue;
|
|
@@ -5992,20 +6154,20 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5992
6154
|
const nextNum = getNextPrdNumber(prdDir);
|
|
5993
6155
|
const padded = String(nextNum).padStart(2, "0");
|
|
5994
6156
|
const filename = `${padded}-${itemSlug}.md`;
|
|
5995
|
-
const filePath =
|
|
5996
|
-
if (!
|
|
5997
|
-
|
|
6157
|
+
const filePath = path16.join(prdDir, filename);
|
|
6158
|
+
if (!fs17.existsSync(prdDir)) {
|
|
6159
|
+
fs17.mkdirSync(prdDir, { recursive: true });
|
|
5998
6160
|
}
|
|
5999
6161
|
const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
|
|
6000
6162
|
const prompt = renderSlicerPrompt(promptVars);
|
|
6001
6163
|
const provider = resolveJobProvider(config, "slicer");
|
|
6002
6164
|
const providerArgs = buildProviderArgs(provider, prompt, projectDir);
|
|
6003
|
-
const logDir =
|
|
6004
|
-
if (!
|
|
6005
|
-
|
|
6165
|
+
const logDir = path16.join(projectDir, "logs");
|
|
6166
|
+
if (!fs17.existsSync(logDir)) {
|
|
6167
|
+
fs17.mkdirSync(logDir, { recursive: true });
|
|
6006
6168
|
}
|
|
6007
|
-
const logFile =
|
|
6008
|
-
const logStream =
|
|
6169
|
+
const logFile = path16.join(logDir, `slicer-${itemSlug}.log`);
|
|
6170
|
+
const logStream = fs17.createWriteStream(logFile, { flags: "w" });
|
|
6009
6171
|
logStream.on("error", () => {
|
|
6010
6172
|
});
|
|
6011
6173
|
return new Promise((resolve9) => {
|
|
@@ -6042,7 +6204,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
6042
6204
|
});
|
|
6043
6205
|
return;
|
|
6044
6206
|
}
|
|
6045
|
-
if (!
|
|
6207
|
+
if (!fs17.existsSync(filePath)) {
|
|
6046
6208
|
resolve9({
|
|
6047
6209
|
sliced: false,
|
|
6048
6210
|
error: `Provider did not create expected file: ${filePath}`,
|
|
@@ -6065,23 +6227,23 @@ async function sliceNextItem(projectDir, config) {
|
|
|
6065
6227
|
error: "Roadmap scanner is disabled"
|
|
6066
6228
|
};
|
|
6067
6229
|
}
|
|
6068
|
-
const roadmapPath =
|
|
6230
|
+
const roadmapPath = path16.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
6069
6231
|
const auditItems = collectAuditPlannerItems(projectDir);
|
|
6070
|
-
const roadmapExists =
|
|
6232
|
+
const roadmapExists = fs17.existsSync(roadmapPath);
|
|
6071
6233
|
if (!roadmapExists && auditItems.length === 0) {
|
|
6072
6234
|
return {
|
|
6073
6235
|
sliced: false,
|
|
6074
6236
|
error: "No pending items to process"
|
|
6075
6237
|
};
|
|
6076
6238
|
}
|
|
6077
|
-
const roadmapItems = roadmapExists ? parseRoadmap(
|
|
6239
|
+
const roadmapItems = roadmapExists ? parseRoadmap(fs17.readFileSync(roadmapPath, "utf-8")) : [];
|
|
6078
6240
|
if (roadmapExists && roadmapItems.length === 0 && auditItems.length === 0) {
|
|
6079
6241
|
return {
|
|
6080
6242
|
sliced: false,
|
|
6081
6243
|
error: "No items in roadmap"
|
|
6082
6244
|
};
|
|
6083
6245
|
}
|
|
6084
|
-
const prdDir =
|
|
6246
|
+
const prdDir = path16.join(projectDir, config.prdDir);
|
|
6085
6247
|
const state = loadRoadmapState(prdDir);
|
|
6086
6248
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
6087
6249
|
const pickEligibleItem = (items) => {
|
|
@@ -6150,6 +6312,7 @@ function hasNewItems(projectDir, config) {
|
|
|
6150
6312
|
var init_roadmap_scanner = __esm({
|
|
6151
6313
|
"../core/dist/utils/roadmap-scanner.js"() {
|
|
6152
6314
|
"use strict";
|
|
6315
|
+
init_report();
|
|
6153
6316
|
init_config();
|
|
6154
6317
|
init_prd_utils();
|
|
6155
6318
|
init_roadmap_parser();
|
|
@@ -6252,8 +6415,8 @@ var init_shell = __esm({
|
|
|
6252
6415
|
});
|
|
6253
6416
|
|
|
6254
6417
|
// ../core/dist/utils/scheduling.js
|
|
6255
|
-
import * as
|
|
6256
|
-
import * as
|
|
6418
|
+
import * as fs18 from "fs";
|
|
6419
|
+
import * as path17 from "path";
|
|
6257
6420
|
function normalizeSchedulingPriority(priority) {
|
|
6258
6421
|
if (!Number.isFinite(priority)) {
|
|
6259
6422
|
return DEFAULT_SCHEDULING_PRIORITY;
|
|
@@ -6288,7 +6451,7 @@ function getJobSchedule(config, jobType) {
|
|
|
6288
6451
|
}
|
|
6289
6452
|
}
|
|
6290
6453
|
function loadPeerConfig(projectPath) {
|
|
6291
|
-
if (!
|
|
6454
|
+
if (!fs18.existsSync(projectPath) || !fs18.existsSync(path17.join(projectPath, CONFIG_FILE_NAME))) {
|
|
6292
6455
|
return null;
|
|
6293
6456
|
}
|
|
6294
6457
|
try {
|
|
@@ -6299,10 +6462,10 @@ function loadPeerConfig(projectPath) {
|
|
|
6299
6462
|
}
|
|
6300
6463
|
function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
6301
6464
|
const peers = /* @__PURE__ */ new Map();
|
|
6302
|
-
const currentPath =
|
|
6465
|
+
const currentPath = path17.resolve(currentProjectDir);
|
|
6303
6466
|
const currentSchedule = getJobSchedule(currentConfig, jobType);
|
|
6304
6467
|
const addPeer = (projectPath, config) => {
|
|
6305
|
-
const resolvedPath =
|
|
6468
|
+
const resolvedPath = path17.resolve(projectPath);
|
|
6306
6469
|
if (!isJobTypeEnabled(config, jobType)) {
|
|
6307
6470
|
return;
|
|
6308
6471
|
}
|
|
@@ -6313,12 +6476,12 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
6313
6476
|
path: resolvedPath,
|
|
6314
6477
|
config,
|
|
6315
6478
|
schedulingPriority: normalizeSchedulingPriority(config.schedulingPriority),
|
|
6316
|
-
sortKey: `${
|
|
6479
|
+
sortKey: `${path17.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
|
|
6317
6480
|
});
|
|
6318
6481
|
};
|
|
6319
6482
|
addPeer(currentPath, currentConfig);
|
|
6320
6483
|
for (const entry of loadRegistry()) {
|
|
6321
|
-
const resolvedPath =
|
|
6484
|
+
const resolvedPath = path17.resolve(entry.path);
|
|
6322
6485
|
if (resolvedPath === currentPath || peers.has(resolvedPath)) {
|
|
6323
6486
|
continue;
|
|
6324
6487
|
}
|
|
@@ -6336,7 +6499,7 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
6336
6499
|
}
|
|
6337
6500
|
function getSchedulingPlan(projectDir, config, jobType) {
|
|
6338
6501
|
const peers = collectSchedulingPeers(projectDir, config, jobType);
|
|
6339
|
-
const currentPath =
|
|
6502
|
+
const currentPath = path17.resolve(projectDir);
|
|
6340
6503
|
const slotIndex = Math.max(0, peers.findIndex((peer) => peer.path === currentPath));
|
|
6341
6504
|
const peerCount = Math.max(1, peers.length);
|
|
6342
6505
|
const balancedDelayMinutes = peerCount <= 1 ? 0 : Math.floor(slotIndex * 60 / peerCount);
|
|
@@ -6428,8 +6591,8 @@ var init_webhook_validator = __esm({
|
|
|
6428
6591
|
|
|
6429
6592
|
// ../core/dist/utils/worktree-manager.js
|
|
6430
6593
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
6431
|
-
import * as
|
|
6432
|
-
import * as
|
|
6594
|
+
import * as fs19 from "fs";
|
|
6595
|
+
import * as path18 from "path";
|
|
6433
6596
|
function gitExec(args, cwd, logFile) {
|
|
6434
6597
|
try {
|
|
6435
6598
|
const result = execFileSync4("git", args, {
|
|
@@ -6439,7 +6602,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
6439
6602
|
});
|
|
6440
6603
|
if (logFile && result) {
|
|
6441
6604
|
try {
|
|
6442
|
-
|
|
6605
|
+
fs19.appendFileSync(logFile, result);
|
|
6443
6606
|
} catch {
|
|
6444
6607
|
}
|
|
6445
6608
|
}
|
|
@@ -6448,7 +6611,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
6448
6611
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
6449
6612
|
if (logFile) {
|
|
6450
6613
|
try {
|
|
6451
|
-
|
|
6614
|
+
fs19.appendFileSync(logFile, errorMessage + "\n");
|
|
6452
6615
|
} catch {
|
|
6453
6616
|
}
|
|
6454
6617
|
}
|
|
@@ -6473,11 +6636,11 @@ function branchExistsRemotely(projectDir, branchName) {
|
|
|
6473
6636
|
}
|
|
6474
6637
|
function prepareBranchWorktree(options) {
|
|
6475
6638
|
const { projectDir, worktreeDir, branchName, defaultBranch, logFile } = options;
|
|
6476
|
-
if (
|
|
6639
|
+
if (fs19.existsSync(worktreeDir)) {
|
|
6477
6640
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
6478
6641
|
if (!isRegistered) {
|
|
6479
6642
|
try {
|
|
6480
|
-
|
|
6643
|
+
fs19.rmSync(worktreeDir, { recursive: true, force: true });
|
|
6481
6644
|
} catch {
|
|
6482
6645
|
}
|
|
6483
6646
|
}
|
|
@@ -6516,11 +6679,11 @@ function prepareBranchWorktree(options) {
|
|
|
6516
6679
|
}
|
|
6517
6680
|
function prepareDetachedWorktree(options) {
|
|
6518
6681
|
const { projectDir, worktreeDir, defaultBranch, logFile } = options;
|
|
6519
|
-
if (
|
|
6682
|
+
if (fs19.existsSync(worktreeDir)) {
|
|
6520
6683
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
6521
6684
|
if (!isRegistered) {
|
|
6522
6685
|
try {
|
|
6523
|
-
|
|
6686
|
+
fs19.rmSync(worktreeDir, { recursive: true, force: true });
|
|
6524
6687
|
} catch {
|
|
6525
6688
|
}
|
|
6526
6689
|
}
|
|
@@ -6562,7 +6725,7 @@ function isWorktreeRegistered(projectDir, worktreePath) {
|
|
|
6562
6725
|
}
|
|
6563
6726
|
}
|
|
6564
6727
|
function cleanupWorktrees(projectDir, scope) {
|
|
6565
|
-
const projectName =
|
|
6728
|
+
const projectName = path18.basename(projectDir);
|
|
6566
6729
|
const matchToken = scope ? scope : `${projectName}-nw`;
|
|
6567
6730
|
const removed = [];
|
|
6568
6731
|
try {
|
|
@@ -6598,11 +6761,11 @@ var init_worktree_manager = __esm({
|
|
|
6598
6761
|
|
|
6599
6762
|
// ../core/dist/utils/job-queue.js
|
|
6600
6763
|
import * as os7 from "os";
|
|
6601
|
-
import * as
|
|
6764
|
+
import * as path19 from "path";
|
|
6602
6765
|
import Database7 from "better-sqlite3";
|
|
6603
6766
|
function getStateDbPath() {
|
|
6604
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
6605
|
-
return
|
|
6767
|
+
const base = process.env.NIGHT_WATCH_HOME || path19.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
6768
|
+
return path19.join(base, STATE_DB_FILE_NAME);
|
|
6606
6769
|
}
|
|
6607
6770
|
function openDb() {
|
|
6608
6771
|
const dbPath = getStateDbPath();
|
|
@@ -6647,6 +6810,8 @@ function getLockPathForJob(projectPath, jobType) {
|
|
|
6647
6810
|
return analyticsLockPath(projectPath);
|
|
6648
6811
|
case "pr-resolver":
|
|
6649
6812
|
return prResolverLockPath(projectPath);
|
|
6813
|
+
case "merger":
|
|
6814
|
+
return mergerLockPath(projectPath);
|
|
6650
6815
|
}
|
|
6651
6816
|
}
|
|
6652
6817
|
function reconcileStaleRunningJobs(db) {
|
|
@@ -7361,9 +7526,9 @@ var init_amplitude_client = __esm({
|
|
|
7361
7526
|
});
|
|
7362
7527
|
|
|
7363
7528
|
// ../core/dist/analytics/analytics-runner.js
|
|
7364
|
-
import * as
|
|
7529
|
+
import * as fs20 from "fs";
|
|
7365
7530
|
import * as os8 from "os";
|
|
7366
|
-
import * as
|
|
7531
|
+
import * as path20 from "path";
|
|
7367
7532
|
function parseIssuesFromResponse(text) {
|
|
7368
7533
|
const start = text.indexOf("[");
|
|
7369
7534
|
const end = text.lastIndexOf("]");
|
|
@@ -7392,9 +7557,9 @@ async function runAnalytics(config, projectDir) {
|
|
|
7392
7557
|
|
|
7393
7558
|
--- AMPLITUDE DATA ---
|
|
7394
7559
|
${JSON.stringify(data, null, 2)}`;
|
|
7395
|
-
const tmpDir =
|
|
7396
|
-
const promptFile =
|
|
7397
|
-
|
|
7560
|
+
const tmpDir = fs20.mkdtempSync(path20.join(os8.tmpdir(), "nw-analytics-"));
|
|
7561
|
+
const promptFile = path20.join(tmpDir, "analytics-prompt.md");
|
|
7562
|
+
fs20.writeFileSync(promptFile, prompt, "utf-8");
|
|
7398
7563
|
try {
|
|
7399
7564
|
const provider = resolveJobProvider(config, "analytics");
|
|
7400
7565
|
let providerCmd = PROVIDER_COMMANDS[provider];
|
|
@@ -7415,8 +7580,8 @@ set -euo pipefail
|
|
|
7415
7580
|
${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
|
|
7416
7581
|
`;
|
|
7417
7582
|
}
|
|
7418
|
-
const scriptFile =
|
|
7419
|
-
|
|
7583
|
+
const scriptFile = path20.join(tmpDir, "run-analytics.sh");
|
|
7584
|
+
fs20.writeFileSync(scriptFile, scriptContent, { mode: 493 });
|
|
7420
7585
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], config.providerEnv ?? {});
|
|
7421
7586
|
if (exitCode !== 0) {
|
|
7422
7587
|
throw new Error(`AI provider exited with code ${exitCode}: ${stderr || stdout}`);
|
|
@@ -7463,7 +7628,7 @@ ${stderr}`;
|
|
|
7463
7628
|
};
|
|
7464
7629
|
} finally {
|
|
7465
7630
|
try {
|
|
7466
|
-
|
|
7631
|
+
fs20.rmSync(tmpDir, { recursive: true, force: true });
|
|
7467
7632
|
} catch {
|
|
7468
7633
|
}
|
|
7469
7634
|
}
|
|
@@ -7491,6 +7656,160 @@ var init_analytics = __esm({
|
|
|
7491
7656
|
}
|
|
7492
7657
|
});
|
|
7493
7658
|
|
|
7659
|
+
// ../core/dist/audit/board-sync.js
|
|
7660
|
+
function humanizeCategory(category) {
|
|
7661
|
+
return category.trim().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
7662
|
+
}
|
|
7663
|
+
function severityToPriorityLabel(severity) {
|
|
7664
|
+
switch (severity) {
|
|
7665
|
+
case "critical":
|
|
7666
|
+
return "P0";
|
|
7667
|
+
case "high":
|
|
7668
|
+
return "P1";
|
|
7669
|
+
default:
|
|
7670
|
+
return "P2";
|
|
7671
|
+
}
|
|
7672
|
+
}
|
|
7673
|
+
function truncateTitle(title, maxLength = 240) {
|
|
7674
|
+
const normalized = title.replace(/\s+/g, " ").trim();
|
|
7675
|
+
if (normalized.length <= maxLength) {
|
|
7676
|
+
return normalized;
|
|
7677
|
+
}
|
|
7678
|
+
return `${normalized.slice(0, maxLength - 1).trimEnd()}\u2026`;
|
|
7679
|
+
}
|
|
7680
|
+
function buildIssueTitle(finding) {
|
|
7681
|
+
return truncateTitle(`Audit: ${finding.severity} ${humanizeCategory(finding.category)} in ${finding.location}`);
|
|
7682
|
+
}
|
|
7683
|
+
function buildIssueBody(finding) {
|
|
7684
|
+
const lines = [
|
|
7685
|
+
"## Summary",
|
|
7686
|
+
"",
|
|
7687
|
+
`Night Watch audit detected a **${finding.severity}** finding in \`${finding.location}\`.`,
|
|
7688
|
+
"",
|
|
7689
|
+
"## Category",
|
|
7690
|
+
"",
|
|
7691
|
+
`\`${finding.category}\``,
|
|
7692
|
+
"",
|
|
7693
|
+
"## Description",
|
|
7694
|
+
"",
|
|
7695
|
+
finding.description,
|
|
7696
|
+
"",
|
|
7697
|
+
"## Suggested Fix",
|
|
7698
|
+
"",
|
|
7699
|
+
finding.suggestedFix
|
|
7700
|
+
];
|
|
7701
|
+
if (finding.snippet) {
|
|
7702
|
+
lines.push("", "## Snippet", "", "```", finding.snippet, "```");
|
|
7703
|
+
}
|
|
7704
|
+
lines.push("", "## Source", "", "- Report: `logs/audit-report.md`", `- Finding: ${finding.number}`);
|
|
7705
|
+
return lines.join("\n");
|
|
7706
|
+
}
|
|
7707
|
+
async function syncAuditFindingsToBoard(config, projectDir) {
|
|
7708
|
+
const findings = loadAuditFindings(projectDir);
|
|
7709
|
+
const targetColumn = config.audit.targetColumn;
|
|
7710
|
+
if (findings.length === 0) {
|
|
7711
|
+
return {
|
|
7712
|
+
status: "skipped",
|
|
7713
|
+
findingsCount: 0,
|
|
7714
|
+
issuesCreated: 0,
|
|
7715
|
+
issuesFailed: 0,
|
|
7716
|
+
targetColumn: null,
|
|
7717
|
+
summary: "no actionable audit findings to sync"
|
|
7718
|
+
};
|
|
7719
|
+
}
|
|
7720
|
+
if (!config.boardProvider.enabled) {
|
|
7721
|
+
return {
|
|
7722
|
+
status: "skipped",
|
|
7723
|
+
findingsCount: findings.length,
|
|
7724
|
+
issuesCreated: 0,
|
|
7725
|
+
issuesFailed: 0,
|
|
7726
|
+
targetColumn: null,
|
|
7727
|
+
summary: `found ${findings.length} actionable audit finding(s); board sync skipped because board provider is disabled`
|
|
7728
|
+
};
|
|
7729
|
+
}
|
|
7730
|
+
let boardProvider;
|
|
7731
|
+
try {
|
|
7732
|
+
boardProvider = createBoardProvider(config.boardProvider, projectDir);
|
|
7733
|
+
} catch (err) {
|
|
7734
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7735
|
+
logger5.error("Failed to create board provider for audit sync", { error: message });
|
|
7736
|
+
return {
|
|
7737
|
+
status: "failed",
|
|
7738
|
+
findingsCount: findings.length,
|
|
7739
|
+
issuesCreated: 0,
|
|
7740
|
+
issuesFailed: findings.length,
|
|
7741
|
+
targetColumn,
|
|
7742
|
+
summary: `found ${findings.length} actionable audit finding(s), but board sync failed: ${message}`
|
|
7743
|
+
};
|
|
7744
|
+
}
|
|
7745
|
+
let created = 0;
|
|
7746
|
+
let failed = 0;
|
|
7747
|
+
for (const finding of findings) {
|
|
7748
|
+
try {
|
|
7749
|
+
await boardProvider.createIssue({
|
|
7750
|
+
title: buildIssueTitle(finding),
|
|
7751
|
+
body: buildIssueBody(finding),
|
|
7752
|
+
column: targetColumn,
|
|
7753
|
+
labels: [severityToPriorityLabel(finding.severity)]
|
|
7754
|
+
});
|
|
7755
|
+
created++;
|
|
7756
|
+
} catch (err) {
|
|
7757
|
+
failed++;
|
|
7758
|
+
logger5.error("Failed to create board issue for audit finding", {
|
|
7759
|
+
finding: finding.number,
|
|
7760
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7761
|
+
});
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
if (failed === 0) {
|
|
7765
|
+
return {
|
|
7766
|
+
status: "success",
|
|
7767
|
+
findingsCount: findings.length,
|
|
7768
|
+
issuesCreated: created,
|
|
7769
|
+
issuesFailed: 0,
|
|
7770
|
+
targetColumn,
|
|
7771
|
+
summary: `created ${created} audit issue(s) in ${targetColumn}`
|
|
7772
|
+
};
|
|
7773
|
+
}
|
|
7774
|
+
if (created === 0) {
|
|
7775
|
+
return {
|
|
7776
|
+
status: "failed",
|
|
7777
|
+
findingsCount: findings.length,
|
|
7778
|
+
issuesCreated: 0,
|
|
7779
|
+
issuesFailed: failed,
|
|
7780
|
+
targetColumn,
|
|
7781
|
+
summary: `found ${findings.length} actionable audit finding(s), but failed to create board issue(s)`
|
|
7782
|
+
};
|
|
7783
|
+
}
|
|
7784
|
+
return {
|
|
7785
|
+
status: "partial",
|
|
7786
|
+
findingsCount: findings.length,
|
|
7787
|
+
issuesCreated: created,
|
|
7788
|
+
issuesFailed: failed,
|
|
7789
|
+
targetColumn,
|
|
7790
|
+
summary: `created ${created} of ${findings.length} audit issue(s) in ${targetColumn} (${failed} failed)`
|
|
7791
|
+
};
|
|
7792
|
+
}
|
|
7793
|
+
var logger5;
|
|
7794
|
+
var init_board_sync = __esm({
|
|
7795
|
+
"../core/dist/audit/board-sync.js"() {
|
|
7796
|
+
"use strict";
|
|
7797
|
+
init_factory();
|
|
7798
|
+
init_logger();
|
|
7799
|
+
init_report();
|
|
7800
|
+
logger5 = createLogger("audit-sync");
|
|
7801
|
+
}
|
|
7802
|
+
});
|
|
7803
|
+
|
|
7804
|
+
// ../core/dist/audit/index.js
|
|
7805
|
+
var init_audit = __esm({
|
|
7806
|
+
"../core/dist/audit/index.js"() {
|
|
7807
|
+
"use strict";
|
|
7808
|
+
init_report();
|
|
7809
|
+
init_board_sync();
|
|
7810
|
+
}
|
|
7811
|
+
});
|
|
7812
|
+
|
|
7494
7813
|
// ../core/dist/templates/prd-template.js
|
|
7495
7814
|
function renderDependsOn(deps) {
|
|
7496
7815
|
if (deps.length === 0) {
|
|
@@ -7698,6 +8017,7 @@ __export(dist_exports, {
|
|
|
7698
8017
|
DEFAULT_AUDIT_ENABLED: () => DEFAULT_AUDIT_ENABLED,
|
|
7699
8018
|
DEFAULT_AUDIT_MAX_RUNTIME: () => DEFAULT_AUDIT_MAX_RUNTIME,
|
|
7700
8019
|
DEFAULT_AUDIT_SCHEDULE: () => DEFAULT_AUDIT_SCHEDULE,
|
|
8020
|
+
DEFAULT_AUDIT_TARGET_COLUMN: () => DEFAULT_AUDIT_TARGET_COLUMN,
|
|
7701
8021
|
DEFAULT_AUTO_MERGE: () => DEFAULT_AUTO_MERGE,
|
|
7702
8022
|
DEFAULT_AUTO_MERGE_METHOD: () => DEFAULT_AUTO_MERGE_METHOD,
|
|
7703
8023
|
DEFAULT_BOARD_PROVIDER: () => DEFAULT_BOARD_PROVIDER,
|
|
@@ -7714,6 +8034,14 @@ __export(dist_exports, {
|
|
|
7714
8034
|
DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
|
|
7715
8035
|
DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
|
|
7716
8036
|
DEFAULT_MAX_RUNTIME: () => DEFAULT_MAX_RUNTIME,
|
|
8037
|
+
DEFAULT_MERGER: () => DEFAULT_MERGER,
|
|
8038
|
+
DEFAULT_MERGER_ENABLED: () => DEFAULT_MERGER_ENABLED,
|
|
8039
|
+
DEFAULT_MERGER_MAX_PRS_PER_RUN: () => DEFAULT_MERGER_MAX_PRS_PER_RUN,
|
|
8040
|
+
DEFAULT_MERGER_MAX_RUNTIME: () => DEFAULT_MERGER_MAX_RUNTIME,
|
|
8041
|
+
DEFAULT_MERGER_MERGE_METHOD: () => DEFAULT_MERGER_MERGE_METHOD,
|
|
8042
|
+
DEFAULT_MERGER_MIN_REVIEW_SCORE: () => DEFAULT_MERGER_MIN_REVIEW_SCORE,
|
|
8043
|
+
DEFAULT_MERGER_REBASE_BEFORE_MERGE: () => DEFAULT_MERGER_REBASE_BEFORE_MERGE,
|
|
8044
|
+
DEFAULT_MERGER_SCHEDULE: () => DEFAULT_MERGER_SCHEDULE,
|
|
7717
8045
|
DEFAULT_MIN_REVIEW_SCORE: () => DEFAULT_MIN_REVIEW_SCORE,
|
|
7718
8046
|
DEFAULT_NOTIFICATIONS: () => DEFAULT_NOTIFICATIONS,
|
|
7719
8047
|
DEFAULT_PRD_DIR: () => DEFAULT_PRD_DIR,
|
|
@@ -7772,6 +8100,7 @@ __export(dist_exports, {
|
|
|
7772
8100
|
LocalKanbanProvider: () => LocalKanbanProvider,
|
|
7773
8101
|
Logger: () => Logger,
|
|
7774
8102
|
MAX_HISTORY_RECORDS_PER_PRD: () => MAX_HISTORY_RECORDS_PER_PRD,
|
|
8103
|
+
MERGER_LOG_NAME: () => MERGER_LOG_NAME,
|
|
7775
8104
|
NIGHT_WATCH_LABELS: () => NIGHT_WATCH_LABELS,
|
|
7776
8105
|
PLANNER_LOG_NAME: () => PLANNER_LOG_NAME,
|
|
7777
8106
|
PRD_STATES_FILE_NAME: () => PRD_STATES_FILE_NAME,
|
|
@@ -7927,6 +8256,7 @@ __export(dist_exports, {
|
|
|
7927
8256
|
isValidPriority: () => isValidPriority,
|
|
7928
8257
|
label: () => label,
|
|
7929
8258
|
listPrdStatesByStatus: () => listPrdStatesByStatus,
|
|
8259
|
+
loadAuditFindings: () => loadAuditFindings,
|
|
7930
8260
|
loadConfig: () => loadConfig,
|
|
7931
8261
|
loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
|
|
7932
8262
|
loadHistory: () => loadHistory,
|
|
@@ -7936,9 +8266,12 @@ __export(dist_exports, {
|
|
|
7936
8266
|
markItemProcessed: () => markItemProcessed,
|
|
7937
8267
|
markJobRunning: () => markJobRunning,
|
|
7938
8268
|
markPrdDone: () => markPrdDone,
|
|
8269
|
+
mergerLockPath: () => mergerLockPath,
|
|
7939
8270
|
migrateJsonToSqlite: () => migrateJsonToSqlite,
|
|
8271
|
+
normalizeAuditSeverity: () => normalizeAuditSeverity,
|
|
7940
8272
|
normalizeJobConfig: () => normalizeJobConfig,
|
|
7941
8273
|
normalizeSchedulingPriority: () => normalizeSchedulingPriority,
|
|
8274
|
+
parseAuditFindings: () => parseAuditFindings,
|
|
7942
8275
|
parsePrdDependencies: () => parsePrdDependencies,
|
|
7943
8276
|
parseRoadmap: () => parseRoadmap,
|
|
7944
8277
|
parseScriptResult: () => parseScriptResult,
|
|
@@ -7988,6 +8321,7 @@ __export(dist_exports, {
|
|
|
7988
8321
|
sortByPriority: () => sortByPriority,
|
|
7989
8322
|
step: () => step,
|
|
7990
8323
|
success: () => success,
|
|
8324
|
+
syncAuditFindingsToBoard: () => syncAuditFindingsToBoard,
|
|
7991
8325
|
unmarkItemProcessed: () => unmarkItemProcessed,
|
|
7992
8326
|
unregisterProject: () => unregisterProject,
|
|
7993
8327
|
updateJobStatus: () => updateJobStatus,
|
|
@@ -8044,6 +8378,7 @@ var init_dist = __esm({
|
|
|
8044
8378
|
init_job_queue();
|
|
8045
8379
|
init_summary();
|
|
8046
8380
|
init_analytics();
|
|
8381
|
+
init_audit();
|
|
8047
8382
|
init_prd_template();
|
|
8048
8383
|
init_slicer_prompt();
|
|
8049
8384
|
init_jobs();
|
|
@@ -8053,30 +8388,30 @@ var init_dist = __esm({
|
|
|
8053
8388
|
// src/cli.ts
|
|
8054
8389
|
import "reflect-metadata";
|
|
8055
8390
|
import { Command as Command3 } from "commander";
|
|
8056
|
-
import { existsSync as
|
|
8391
|
+
import { existsSync as existsSync31, readFileSync as readFileSync20 } from "fs";
|
|
8057
8392
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
8058
|
-
import { dirname as dirname12, join as
|
|
8393
|
+
import { dirname as dirname12, join as join37 } from "path";
|
|
8059
8394
|
|
|
8060
8395
|
// src/commands/init.ts
|
|
8061
8396
|
init_dist();
|
|
8062
|
-
import
|
|
8063
|
-
import
|
|
8397
|
+
import fs21 from "fs";
|
|
8398
|
+
import path21 from "path";
|
|
8064
8399
|
import { execSync as execSync3 } from "child_process";
|
|
8065
8400
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8066
|
-
import { dirname as dirname6, join as
|
|
8401
|
+
import { dirname as dirname6, join as join19 } from "path";
|
|
8067
8402
|
import * as readline from "readline";
|
|
8068
8403
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
8069
8404
|
var __dirname2 = dirname6(__filename2);
|
|
8070
8405
|
function findTemplatesDir(startDir) {
|
|
8071
8406
|
let d = startDir;
|
|
8072
8407
|
for (let i = 0; i < 8; i++) {
|
|
8073
|
-
const candidate =
|
|
8074
|
-
if (
|
|
8408
|
+
const candidate = join19(d, "templates");
|
|
8409
|
+
if (fs21.existsSync(candidate) && fs21.statSync(candidate).isDirectory()) {
|
|
8075
8410
|
return candidate;
|
|
8076
8411
|
}
|
|
8077
8412
|
d = dirname6(d);
|
|
8078
8413
|
}
|
|
8079
|
-
return
|
|
8414
|
+
return join19(startDir, "templates");
|
|
8080
8415
|
}
|
|
8081
8416
|
var TEMPLATES_DIR = findTemplatesDir(__dirname2);
|
|
8082
8417
|
var NW_SKILLS = [
|
|
@@ -8088,12 +8423,12 @@ var NW_SKILLS = [
|
|
|
8088
8423
|
"nw-review"
|
|
8089
8424
|
];
|
|
8090
8425
|
function hasPlaywrightDependency(cwd) {
|
|
8091
|
-
const packageJsonPath =
|
|
8092
|
-
if (!
|
|
8426
|
+
const packageJsonPath = path21.join(cwd, "package.json");
|
|
8427
|
+
if (!fs21.existsSync(packageJsonPath)) {
|
|
8093
8428
|
return false;
|
|
8094
8429
|
}
|
|
8095
8430
|
try {
|
|
8096
|
-
const packageJson2 = JSON.parse(
|
|
8431
|
+
const packageJson2 = JSON.parse(fs21.readFileSync(packageJsonPath, "utf-8"));
|
|
8097
8432
|
return Boolean(
|
|
8098
8433
|
packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
|
|
8099
8434
|
);
|
|
@@ -8105,7 +8440,7 @@ function detectPlaywright(cwd) {
|
|
|
8105
8440
|
if (hasPlaywrightDependency(cwd)) {
|
|
8106
8441
|
return true;
|
|
8107
8442
|
}
|
|
8108
|
-
if (
|
|
8443
|
+
if (fs21.existsSync(path21.join(cwd, "node_modules", ".bin", "playwright"))) {
|
|
8109
8444
|
return true;
|
|
8110
8445
|
}
|
|
8111
8446
|
try {
|
|
@@ -8121,10 +8456,10 @@ function detectPlaywright(cwd) {
|
|
|
8121
8456
|
}
|
|
8122
8457
|
}
|
|
8123
8458
|
function resolvePlaywrightInstallCommand(cwd) {
|
|
8124
|
-
if (
|
|
8459
|
+
if (fs21.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) {
|
|
8125
8460
|
return "pnpm add -D @playwright/test";
|
|
8126
8461
|
}
|
|
8127
|
-
if (
|
|
8462
|
+
if (fs21.existsSync(path21.join(cwd, "yarn.lock"))) {
|
|
8128
8463
|
return "yarn add -D @playwright/test";
|
|
8129
8464
|
}
|
|
8130
8465
|
return "npm install -D @playwright/test";
|
|
@@ -8270,8 +8605,8 @@ function promptProviderSelection(providers) {
|
|
|
8270
8605
|
});
|
|
8271
8606
|
}
|
|
8272
8607
|
function ensureDir(dirPath) {
|
|
8273
|
-
if (!
|
|
8274
|
-
|
|
8608
|
+
if (!fs21.existsSync(dirPath)) {
|
|
8609
|
+
fs21.mkdirSync(dirPath, { recursive: true });
|
|
8275
8610
|
}
|
|
8276
8611
|
}
|
|
8277
8612
|
function buildInitConfig(params) {
|
|
@@ -8319,6 +8654,7 @@ function buildInitConfig(params) {
|
|
|
8319
8654
|
},
|
|
8320
8655
|
audit: { ...defaults.audit },
|
|
8321
8656
|
analytics: { ...defaults.analytics },
|
|
8657
|
+
merger: { ...defaults.merger },
|
|
8322
8658
|
prResolver: { ...defaults.prResolver },
|
|
8323
8659
|
jobProviders: { ...defaults.jobProviders },
|
|
8324
8660
|
queue: {
|
|
@@ -8329,30 +8665,30 @@ function buildInitConfig(params) {
|
|
|
8329
8665
|
}
|
|
8330
8666
|
function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
|
|
8331
8667
|
if (customTemplatesDir !== null) {
|
|
8332
|
-
const customPath =
|
|
8333
|
-
if (
|
|
8668
|
+
const customPath = join19(customTemplatesDir, templateName);
|
|
8669
|
+
if (fs21.existsSync(customPath)) {
|
|
8334
8670
|
return { path: customPath, source: "custom" };
|
|
8335
8671
|
}
|
|
8336
8672
|
}
|
|
8337
|
-
return { path:
|
|
8673
|
+
return { path: join19(bundledTemplatesDir, templateName), source: "bundled" };
|
|
8338
8674
|
}
|
|
8339
8675
|
function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
|
|
8340
|
-
if (
|
|
8676
|
+
if (fs21.existsSync(targetPath) && !force) {
|
|
8341
8677
|
console.log(` Skipped (exists): ${targetPath}`);
|
|
8342
8678
|
return { created: false, source: source ?? "bundled" };
|
|
8343
8679
|
}
|
|
8344
|
-
const templatePath = sourcePath ??
|
|
8680
|
+
const templatePath = sourcePath ?? join19(TEMPLATES_DIR, templateName);
|
|
8345
8681
|
const resolvedSource = source ?? "bundled";
|
|
8346
|
-
let content =
|
|
8682
|
+
let content = fs21.readFileSync(templatePath, "utf-8");
|
|
8347
8683
|
for (const [key, value] of Object.entries(replacements)) {
|
|
8348
8684
|
content = content.replaceAll(key, value);
|
|
8349
8685
|
}
|
|
8350
|
-
|
|
8686
|
+
fs21.writeFileSync(targetPath, content);
|
|
8351
8687
|
console.log(` Created: ${targetPath} (${resolvedSource})`);
|
|
8352
8688
|
return { created: true, source: resolvedSource };
|
|
8353
8689
|
}
|
|
8354
8690
|
function addToGitignore(cwd) {
|
|
8355
|
-
const gitignorePath =
|
|
8691
|
+
const gitignorePath = path21.join(cwd, ".gitignore");
|
|
8356
8692
|
const entries = [
|
|
8357
8693
|
{
|
|
8358
8694
|
pattern: "/logs/",
|
|
@@ -8366,13 +8702,13 @@ function addToGitignore(cwd) {
|
|
|
8366
8702
|
},
|
|
8367
8703
|
{ pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
|
|
8368
8704
|
];
|
|
8369
|
-
if (!
|
|
8705
|
+
if (!fs21.existsSync(gitignorePath)) {
|
|
8370
8706
|
const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
|
|
8371
|
-
|
|
8707
|
+
fs21.writeFileSync(gitignorePath, lines.join("\n"));
|
|
8372
8708
|
console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
|
|
8373
8709
|
return;
|
|
8374
8710
|
}
|
|
8375
|
-
const content =
|
|
8711
|
+
const content = fs21.readFileSync(gitignorePath, "utf-8");
|
|
8376
8712
|
const missing = entries.filter((e) => !e.check(content));
|
|
8377
8713
|
if (missing.length === 0) {
|
|
8378
8714
|
console.log(` Skipped (exists): Night Watch entries in .gitignore`);
|
|
@@ -8380,59 +8716,59 @@ function addToGitignore(cwd) {
|
|
|
8380
8716
|
}
|
|
8381
8717
|
const additions = missing.map((e) => e.pattern).join("\n");
|
|
8382
8718
|
const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
|
|
8383
|
-
|
|
8719
|
+
fs21.writeFileSync(gitignorePath, newContent);
|
|
8384
8720
|
console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
|
|
8385
8721
|
}
|
|
8386
8722
|
function installSkills(cwd, provider, force, templatesDir) {
|
|
8387
|
-
const skillsTemplatesDir =
|
|
8388
|
-
if (!
|
|
8723
|
+
const skillsTemplatesDir = path21.join(templatesDir, "skills");
|
|
8724
|
+
if (!fs21.existsSync(skillsTemplatesDir)) {
|
|
8389
8725
|
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
8390
8726
|
}
|
|
8391
8727
|
const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
|
|
8392
8728
|
const isCodexProvider = provider === "codex";
|
|
8393
|
-
const claudeDir =
|
|
8394
|
-
if (isClaudeProvider ||
|
|
8729
|
+
const claudeDir = path21.join(cwd, ".claude");
|
|
8730
|
+
if (isClaudeProvider || fs21.existsSync(claudeDir)) {
|
|
8395
8731
|
ensureDir(claudeDir);
|
|
8396
|
-
const skillsDir =
|
|
8732
|
+
const skillsDir = path21.join(claudeDir, "skills");
|
|
8397
8733
|
ensureDir(skillsDir);
|
|
8398
8734
|
let installed = 0;
|
|
8399
8735
|
let skipped = 0;
|
|
8400
8736
|
for (const skillName of NW_SKILLS) {
|
|
8401
|
-
const templateFile =
|
|
8402
|
-
if (!
|
|
8403
|
-
const skillDir =
|
|
8737
|
+
const templateFile = path21.join(skillsTemplatesDir, `${skillName}.md`);
|
|
8738
|
+
if (!fs21.existsSync(templateFile)) continue;
|
|
8739
|
+
const skillDir = path21.join(skillsDir, skillName);
|
|
8404
8740
|
ensureDir(skillDir);
|
|
8405
|
-
const target =
|
|
8406
|
-
if (
|
|
8741
|
+
const target = path21.join(skillDir, "SKILL.md");
|
|
8742
|
+
if (fs21.existsSync(target) && !force) {
|
|
8407
8743
|
skipped++;
|
|
8408
8744
|
continue;
|
|
8409
8745
|
}
|
|
8410
|
-
|
|
8746
|
+
fs21.copyFileSync(templateFile, target);
|
|
8411
8747
|
installed++;
|
|
8412
8748
|
}
|
|
8413
8749
|
return { location: ".claude/skills/", installed, skipped, type: "claude" };
|
|
8414
8750
|
}
|
|
8415
8751
|
if (isCodexProvider) {
|
|
8416
|
-
const agentsFile =
|
|
8417
|
-
const blockFile =
|
|
8418
|
-
if (!
|
|
8752
|
+
const agentsFile = path21.join(cwd, "AGENTS.md");
|
|
8753
|
+
const blockFile = path21.join(skillsTemplatesDir, "_codex-block.md");
|
|
8754
|
+
if (!fs21.existsSync(blockFile)) {
|
|
8419
8755
|
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
8420
8756
|
}
|
|
8421
|
-
const block =
|
|
8757
|
+
const block = fs21.readFileSync(blockFile, "utf-8");
|
|
8422
8758
|
const marker = "## Night Watch Skills";
|
|
8423
|
-
if (!
|
|
8424
|
-
|
|
8759
|
+
if (!fs21.existsSync(agentsFile)) {
|
|
8760
|
+
fs21.writeFileSync(agentsFile, block);
|
|
8425
8761
|
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
8426
8762
|
}
|
|
8427
|
-
const existing =
|
|
8763
|
+
const existing = fs21.readFileSync(agentsFile, "utf-8");
|
|
8428
8764
|
if (existing.includes(marker)) {
|
|
8429
8765
|
if (!force) {
|
|
8430
8766
|
return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
|
|
8431
8767
|
}
|
|
8432
8768
|
const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
|
|
8433
|
-
|
|
8769
|
+
fs21.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
|
|
8434
8770
|
} else {
|
|
8435
|
-
|
|
8771
|
+
fs21.appendFileSync(agentsFile, "\n\n" + block);
|
|
8436
8772
|
}
|
|
8437
8773
|
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
8438
8774
|
}
|
|
@@ -8556,28 +8892,28 @@ function initCommand(program2) {
|
|
|
8556
8892
|
"${DEFAULT_BRANCH}": defaultBranch
|
|
8557
8893
|
};
|
|
8558
8894
|
step(6, totalSteps, "Creating PRD directory structure...");
|
|
8559
|
-
const prdDirPath =
|
|
8560
|
-
const doneDirPath =
|
|
8895
|
+
const prdDirPath = path21.join(cwd, prdDir);
|
|
8896
|
+
const doneDirPath = path21.join(prdDirPath, "done");
|
|
8561
8897
|
ensureDir(doneDirPath);
|
|
8562
8898
|
success(`Created ${prdDirPath}/`);
|
|
8563
8899
|
success(`Created ${doneDirPath}/`);
|
|
8564
8900
|
step(7, totalSteps, "Creating logs directory...");
|
|
8565
|
-
const logsPath =
|
|
8901
|
+
const logsPath = path21.join(cwd, LOG_DIR);
|
|
8566
8902
|
ensureDir(logsPath);
|
|
8567
8903
|
success(`Created ${logsPath}/`);
|
|
8568
8904
|
addToGitignore(cwd);
|
|
8569
8905
|
step(8, totalSteps, "Creating instructions directory...");
|
|
8570
|
-
const instructionsDir =
|
|
8906
|
+
const instructionsDir = path21.join(cwd, "instructions");
|
|
8571
8907
|
ensureDir(instructionsDir);
|
|
8572
8908
|
success(`Created ${instructionsDir}/`);
|
|
8573
8909
|
const existingConfig = loadConfig(cwd);
|
|
8574
|
-
const customTemplatesDirPath =
|
|
8575
|
-
const customTemplatesDir =
|
|
8910
|
+
const customTemplatesDirPath = path21.join(cwd, existingConfig.templatesDir);
|
|
8911
|
+
const customTemplatesDir = fs21.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
|
|
8576
8912
|
const templateSources = [];
|
|
8577
8913
|
const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
|
|
8578
8914
|
const nwResult = processTemplate(
|
|
8579
8915
|
"executor.md",
|
|
8580
|
-
|
|
8916
|
+
path21.join(instructionsDir, "executor.md"),
|
|
8581
8917
|
replacements,
|
|
8582
8918
|
force,
|
|
8583
8919
|
nwResolution.path,
|
|
@@ -8591,7 +8927,7 @@ function initCommand(program2) {
|
|
|
8591
8927
|
);
|
|
8592
8928
|
const peResult = processTemplate(
|
|
8593
8929
|
"prd-executor.md",
|
|
8594
|
-
|
|
8930
|
+
path21.join(instructionsDir, "prd-executor.md"),
|
|
8595
8931
|
replacements,
|
|
8596
8932
|
force,
|
|
8597
8933
|
peResolution.path,
|
|
@@ -8601,7 +8937,7 @@ function initCommand(program2) {
|
|
|
8601
8937
|
const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
|
|
8602
8938
|
const prResult = processTemplate(
|
|
8603
8939
|
"pr-reviewer.md",
|
|
8604
|
-
|
|
8940
|
+
path21.join(instructionsDir, "pr-reviewer.md"),
|
|
8605
8941
|
replacements,
|
|
8606
8942
|
force,
|
|
8607
8943
|
prResolution.path,
|
|
@@ -8611,7 +8947,7 @@ function initCommand(program2) {
|
|
|
8611
8947
|
const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
|
|
8612
8948
|
const qaResult = processTemplate(
|
|
8613
8949
|
"qa.md",
|
|
8614
|
-
|
|
8950
|
+
path21.join(instructionsDir, "qa.md"),
|
|
8615
8951
|
replacements,
|
|
8616
8952
|
force,
|
|
8617
8953
|
qaResolution.path,
|
|
@@ -8621,7 +8957,7 @@ function initCommand(program2) {
|
|
|
8621
8957
|
const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
|
|
8622
8958
|
const auditResult = processTemplate(
|
|
8623
8959
|
"audit.md",
|
|
8624
|
-
|
|
8960
|
+
path21.join(instructionsDir, "audit.md"),
|
|
8625
8961
|
replacements,
|
|
8626
8962
|
force,
|
|
8627
8963
|
auditResolution.path,
|
|
@@ -8635,7 +8971,7 @@ function initCommand(program2) {
|
|
|
8635
8971
|
);
|
|
8636
8972
|
const plannerResult = processTemplate(
|
|
8637
8973
|
"prd-creator.md",
|
|
8638
|
-
|
|
8974
|
+
path21.join(instructionsDir, "prd-creator.md"),
|
|
8639
8975
|
replacements,
|
|
8640
8976
|
force,
|
|
8641
8977
|
plannerResolution.path,
|
|
@@ -8643,8 +8979,8 @@ function initCommand(program2) {
|
|
|
8643
8979
|
);
|
|
8644
8980
|
templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
|
|
8645
8981
|
step(9, totalSteps, "Creating configuration file...");
|
|
8646
|
-
const configPath =
|
|
8647
|
-
if (
|
|
8982
|
+
const configPath = path21.join(cwd, CONFIG_FILE_NAME);
|
|
8983
|
+
if (fs21.existsSync(configPath) && !force) {
|
|
8648
8984
|
console.log(` Skipped (exists): ${configPath}`);
|
|
8649
8985
|
} else {
|
|
8650
8986
|
const config = buildInitConfig({
|
|
@@ -8654,11 +8990,11 @@ function initCommand(program2) {
|
|
|
8654
8990
|
reviewerEnabled,
|
|
8655
8991
|
prdDir
|
|
8656
8992
|
});
|
|
8657
|
-
|
|
8993
|
+
fs21.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
8658
8994
|
success(`Created ${configPath}`);
|
|
8659
8995
|
}
|
|
8660
8996
|
step(10, totalSteps, "Setting up GitHub Project board...");
|
|
8661
|
-
const existingRaw = JSON.parse(
|
|
8997
|
+
const existingRaw = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
|
|
8662
8998
|
const existingBoard = existingRaw.boardProvider;
|
|
8663
8999
|
let boardSetupStatus = "Skipped";
|
|
8664
9000
|
if (existingBoard?.projectNumber && !force) {
|
|
@@ -8680,13 +9016,13 @@ function initCommand(program2) {
|
|
|
8680
9016
|
const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
|
|
8681
9017
|
const boardTitle = `${projectName} Night Watch`;
|
|
8682
9018
|
const board = await provider.setupBoard(boardTitle);
|
|
8683
|
-
const rawConfig = JSON.parse(
|
|
9019
|
+
const rawConfig = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
|
|
8684
9020
|
rawConfig.boardProvider = {
|
|
8685
9021
|
enabled: true,
|
|
8686
9022
|
provider: "github",
|
|
8687
9023
|
projectNumber: board.number
|
|
8688
9024
|
};
|
|
8689
|
-
|
|
9025
|
+
fs21.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
|
|
8690
9026
|
boardSetupStatus = `Created (#${board.number})`;
|
|
8691
9027
|
success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
|
|
8692
9028
|
} catch (boardErr) {
|
|
@@ -8869,8 +9205,8 @@ function getTelegramStatusWebhooks(config) {
|
|
|
8869
9205
|
}
|
|
8870
9206
|
|
|
8871
9207
|
// src/commands/run.ts
|
|
8872
|
-
import * as
|
|
8873
|
-
import * as
|
|
9208
|
+
import * as fs22 from "fs";
|
|
9209
|
+
import * as path22 from "path";
|
|
8874
9210
|
function resolveRunNotificationEvent(exitCode, scriptStatus) {
|
|
8875
9211
|
if (exitCode === 124) {
|
|
8876
9212
|
return "run_timeout";
|
|
@@ -8902,12 +9238,12 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
|
|
|
8902
9238
|
return scriptStatus === "skip_no_eligible_prd";
|
|
8903
9239
|
}
|
|
8904
9240
|
function getCrossProjectFallbackCandidates(currentProjectDir) {
|
|
8905
|
-
const current =
|
|
9241
|
+
const current = path22.resolve(currentProjectDir);
|
|
8906
9242
|
const { valid, invalid } = validateRegistry();
|
|
8907
9243
|
for (const entry of invalid) {
|
|
8908
9244
|
warn(`Skipping invalid registry entry: ${entry.path}`);
|
|
8909
9245
|
}
|
|
8910
|
-
return valid.filter((entry) =>
|
|
9246
|
+
return valid.filter((entry) => path22.resolve(entry.path) !== current);
|
|
8911
9247
|
}
|
|
8912
9248
|
async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
|
|
8913
9249
|
if (isRateLimitFallbackTriggered(scriptResult?.data)) {
|
|
@@ -8917,7 +9253,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
8917
9253
|
if (nonTelegramWebhooks.length > 0) {
|
|
8918
9254
|
const _rateLimitCtx = {
|
|
8919
9255
|
event: "rate_limit_fallback",
|
|
8920
|
-
projectName:
|
|
9256
|
+
projectName: path22.basename(projectDir),
|
|
8921
9257
|
exitCode,
|
|
8922
9258
|
provider: config.provider
|
|
8923
9259
|
};
|
|
@@ -8949,7 +9285,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
8949
9285
|
const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
|
|
8950
9286
|
const _ctx = {
|
|
8951
9287
|
event,
|
|
8952
|
-
projectName:
|
|
9288
|
+
projectName: path22.basename(projectDir),
|
|
8953
9289
|
exitCode,
|
|
8954
9290
|
provider: config.provider,
|
|
8955
9291
|
prdName: scriptResult?.data.prd,
|
|
@@ -9111,20 +9447,20 @@ function applyCliOverrides(config, options) {
|
|
|
9111
9447
|
return overridden;
|
|
9112
9448
|
}
|
|
9113
9449
|
function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
9114
|
-
const absolutePrdDir =
|
|
9115
|
-
const doneDir =
|
|
9450
|
+
const absolutePrdDir = path22.join(projectDir, prdDir);
|
|
9451
|
+
const doneDir = path22.join(absolutePrdDir, "done");
|
|
9116
9452
|
const pending = [];
|
|
9117
9453
|
const completed = [];
|
|
9118
|
-
if (
|
|
9119
|
-
const entries =
|
|
9454
|
+
if (fs22.existsSync(absolutePrdDir)) {
|
|
9455
|
+
const entries = fs22.readdirSync(absolutePrdDir, { withFileTypes: true });
|
|
9120
9456
|
for (const entry of entries) {
|
|
9121
9457
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9122
|
-
const claimPath =
|
|
9458
|
+
const claimPath = path22.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
|
|
9123
9459
|
let claimed = false;
|
|
9124
9460
|
let claimInfo = null;
|
|
9125
|
-
if (
|
|
9461
|
+
if (fs22.existsSync(claimPath)) {
|
|
9126
9462
|
try {
|
|
9127
|
-
const content =
|
|
9463
|
+
const content = fs22.readFileSync(claimPath, "utf-8");
|
|
9128
9464
|
const data = JSON.parse(content);
|
|
9129
9465
|
const age = Math.floor(Date.now() / 1e3) - data.timestamp;
|
|
9130
9466
|
if (age < maxRuntime) {
|
|
@@ -9138,8 +9474,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
|
9138
9474
|
}
|
|
9139
9475
|
}
|
|
9140
9476
|
}
|
|
9141
|
-
if (
|
|
9142
|
-
const entries =
|
|
9477
|
+
if (fs22.existsSync(doneDir)) {
|
|
9478
|
+
const entries = fs22.readdirSync(doneDir, { withFileTypes: true });
|
|
9143
9479
|
for (const entry of entries) {
|
|
9144
9480
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9145
9481
|
completed.push(entry.name);
|
|
@@ -9299,7 +9635,7 @@ ${stderr}`);
|
|
|
9299
9635
|
// src/commands/review.ts
|
|
9300
9636
|
init_dist();
|
|
9301
9637
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
9302
|
-
import * as
|
|
9638
|
+
import * as path23 from "path";
|
|
9303
9639
|
function shouldSendReviewNotification(scriptStatus) {
|
|
9304
9640
|
if (!scriptStatus) {
|
|
9305
9641
|
return true;
|
|
@@ -9399,10 +9735,6 @@ function buildEnvVars2(config, options) {
|
|
|
9399
9735
|
env.NW_BRANCH_PATTERNS = config.branchPatterns.join(",");
|
|
9400
9736
|
env.NW_PRD_DIR = config.prdDir;
|
|
9401
9737
|
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
9738
|
return env;
|
|
9407
9739
|
}
|
|
9408
9740
|
function applyCliOverrides2(config, options) {
|
|
@@ -9416,9 +9748,6 @@ function applyCliOverrides2(config, options) {
|
|
|
9416
9748
|
if (options.provider) {
|
|
9417
9749
|
overridden._cliProviderOverride = options.provider;
|
|
9418
9750
|
}
|
|
9419
|
-
if (options.autoMerge !== void 0) {
|
|
9420
|
-
overridden.autoMerge = options.autoMerge;
|
|
9421
|
-
}
|
|
9422
9751
|
return overridden;
|
|
9423
9752
|
}
|
|
9424
9753
|
function isFailingCheck(check) {
|
|
@@ -9479,7 +9808,7 @@ function getOpenPrsNeedingWork(branchPatterns) {
|
|
|
9479
9808
|
}
|
|
9480
9809
|
}
|
|
9481
9810
|
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)").
|
|
9811
|
+
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
9812
|
const projectDir = process.cwd();
|
|
9484
9813
|
let config = loadConfig(projectDir);
|
|
9485
9814
|
config = applyCliOverrides2(config, options);
|
|
@@ -9502,10 +9831,6 @@ function reviewCommand(program2) {
|
|
|
9502
9831
|
]);
|
|
9503
9832
|
configTable.push(["Min Review Score", `${config.minReviewScore}/100`]);
|
|
9504
9833
|
configTable.push(["Branch Patterns", config.branchPatterns.join(", ")]);
|
|
9505
|
-
configTable.push([
|
|
9506
|
-
"Auto-merge",
|
|
9507
|
-
config.autoMerge ? `Enabled (${config.autoMergeMethod})` : "Disabled"
|
|
9508
|
-
]);
|
|
9509
9834
|
configTable.push(["Max Retry Attempts", String(config.reviewerMaxRetries)]);
|
|
9510
9835
|
configTable.push(["Retry Delay", `${config.reviewerRetryDelay}s`]);
|
|
9511
9836
|
configTable.push([
|
|
@@ -9610,7 +9935,7 @@ ${stderr}`);
|
|
|
9610
9935
|
const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
9611
9936
|
await sendNotifications(config, {
|
|
9612
9937
|
event: reviewEvent,
|
|
9613
|
-
projectName:
|
|
9938
|
+
projectName: path23.basename(projectDir),
|
|
9614
9939
|
exitCode,
|
|
9615
9940
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9616
9941
|
prUrl: fallbackPrDetails?.url,
|
|
@@ -9632,7 +9957,7 @@ ${stderr}`);
|
|
|
9632
9957
|
const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
9633
9958
|
await sendNotifications(config, {
|
|
9634
9959
|
event: reviewEvent,
|
|
9635
|
-
projectName:
|
|
9960
|
+
projectName: path23.basename(projectDir),
|
|
9636
9961
|
exitCode,
|
|
9637
9962
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9638
9963
|
prUrl: prDetails?.url,
|
|
@@ -9654,7 +9979,7 @@ ${stderr}`);
|
|
|
9654
9979
|
const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
|
|
9655
9980
|
const _mergeCtx = {
|
|
9656
9981
|
event: "pr_auto_merged",
|
|
9657
|
-
projectName:
|
|
9982
|
+
projectName: path23.basename(projectDir),
|
|
9658
9983
|
exitCode,
|
|
9659
9984
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9660
9985
|
prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
|
|
@@ -9679,7 +10004,7 @@ ${stderr}`);
|
|
|
9679
10004
|
|
|
9680
10005
|
// src/commands/qa.ts
|
|
9681
10006
|
init_dist();
|
|
9682
|
-
import * as
|
|
10007
|
+
import * as path24 from "path";
|
|
9683
10008
|
function shouldSendQaNotification(scriptStatus) {
|
|
9684
10009
|
if (!scriptStatus) {
|
|
9685
10010
|
return true;
|
|
@@ -9820,7 +10145,7 @@ ${stderr}`);
|
|
|
9820
10145
|
const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
|
|
9821
10146
|
const _qaCtx = {
|
|
9822
10147
|
event: "qa_completed",
|
|
9823
|
-
projectName:
|
|
10148
|
+
projectName: path24.basename(projectDir),
|
|
9824
10149
|
exitCode,
|
|
9825
10150
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
9826
10151
|
prNumber: prDetails?.number ?? primaryQaPr,
|
|
@@ -9846,8 +10171,8 @@ ${stderr}`);
|
|
|
9846
10171
|
|
|
9847
10172
|
// src/commands/audit.ts
|
|
9848
10173
|
init_dist();
|
|
9849
|
-
import * as
|
|
9850
|
-
import * as
|
|
10174
|
+
import * as fs23 from "fs";
|
|
10175
|
+
import * as path25 from "path";
|
|
9851
10176
|
function buildEnvVars4(config, options) {
|
|
9852
10177
|
const env = buildBaseEnvVars(config, "audit", options.dryRun);
|
|
9853
10178
|
env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
|
|
@@ -9890,7 +10215,8 @@ function auditCommand(program2) {
|
|
|
9890
10215
|
configTable.push(["Provider", auditProvider]);
|
|
9891
10216
|
configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
|
|
9892
10217
|
configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
|
|
9893
|
-
configTable.push(["
|
|
10218
|
+
configTable.push(["Target Column", config.audit.targetColumn]);
|
|
10219
|
+
configTable.push(["Report File", path25.join(projectDir, "logs", "audit-report.md")]);
|
|
9894
10220
|
console.log(configTable.toString());
|
|
9895
10221
|
header("Provider Invocation");
|
|
9896
10222
|
const providerCmd = PROVIDER_COMMANDS[auditProvider];
|
|
@@ -9925,21 +10251,27 @@ ${stderr}`);
|
|
|
9925
10251
|
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
9926
10252
|
spinner.succeed("Code audit skipped");
|
|
9927
10253
|
} else {
|
|
9928
|
-
const reportPath =
|
|
9929
|
-
if (!
|
|
10254
|
+
const reportPath = path25.join(projectDir, "logs", "audit-report.md");
|
|
10255
|
+
if (!fs23.existsSync(reportPath)) {
|
|
9930
10256
|
spinner.fail("Code audit finished without a report file");
|
|
9931
10257
|
process.exit(1);
|
|
9932
10258
|
}
|
|
9933
|
-
|
|
10259
|
+
const syncResult = await syncAuditFindingsToBoard(config, projectDir);
|
|
10260
|
+
const message = `Code audit complete \u2014 report written to ${reportPath}; ${syncResult.summary}`;
|
|
10261
|
+
if (syncResult.status === "failed" || syncResult.status === "partial") {
|
|
10262
|
+
spinner.warn(message);
|
|
10263
|
+
} else {
|
|
10264
|
+
spinner.succeed(message);
|
|
10265
|
+
}
|
|
9934
10266
|
}
|
|
9935
10267
|
} else {
|
|
9936
10268
|
const statusSuffix = scriptResult?.status ? ` (${scriptResult.status})` : "";
|
|
9937
10269
|
const providerExit = scriptResult?.data?.provider_exit;
|
|
9938
10270
|
const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
|
|
9939
10271
|
spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
|
|
9940
|
-
const logPath =
|
|
9941
|
-
if (
|
|
9942
|
-
const logLines =
|
|
10272
|
+
const logPath = path25.join(projectDir, "logs", "audit.log");
|
|
10273
|
+
if (fs23.existsSync(logPath)) {
|
|
10274
|
+
const logLines = fs23.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
|
|
9943
10275
|
if (logLines.length > 0) {
|
|
9944
10276
|
process.stderr.write(logLines.join("\n") + "\n");
|
|
9945
10277
|
}
|
|
@@ -10013,16 +10345,16 @@ function analyticsCommand(program2) {
|
|
|
10013
10345
|
// src/commands/install.ts
|
|
10014
10346
|
init_dist();
|
|
10015
10347
|
import { execSync as execSync4 } from "child_process";
|
|
10016
|
-
import * as
|
|
10017
|
-
import * as
|
|
10348
|
+
import * as path26 from "path";
|
|
10349
|
+
import * as fs24 from "fs";
|
|
10018
10350
|
function shellQuote(value) {
|
|
10019
10351
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
10020
10352
|
}
|
|
10021
10353
|
function getNightWatchBinPath() {
|
|
10022
10354
|
try {
|
|
10023
10355
|
const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
|
|
10024
|
-
const binPath =
|
|
10025
|
-
if (
|
|
10356
|
+
const binPath = path26.join(npmBin, "night-watch");
|
|
10357
|
+
if (fs24.existsSync(binPath)) {
|
|
10026
10358
|
return binPath;
|
|
10027
10359
|
}
|
|
10028
10360
|
} catch {
|
|
@@ -10035,17 +10367,17 @@ function getNightWatchBinPath() {
|
|
|
10035
10367
|
}
|
|
10036
10368
|
function getNodeBinDir() {
|
|
10037
10369
|
if (process.execPath && process.execPath !== "node") {
|
|
10038
|
-
return
|
|
10370
|
+
return path26.dirname(process.execPath);
|
|
10039
10371
|
}
|
|
10040
10372
|
try {
|
|
10041
10373
|
const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
|
|
10042
|
-
return
|
|
10374
|
+
return path26.dirname(nodePath);
|
|
10043
10375
|
} catch {
|
|
10044
10376
|
return "";
|
|
10045
10377
|
}
|
|
10046
10378
|
}
|
|
10047
10379
|
function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
|
|
10048
|
-
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ?
|
|
10380
|
+
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path26.dirname(nightWatchBin) : "";
|
|
10049
10381
|
const pathParts = Array.from(
|
|
10050
10382
|
new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
|
|
10051
10383
|
);
|
|
@@ -10061,12 +10393,12 @@ function performInstall(projectDir, config, options) {
|
|
|
10061
10393
|
const nightWatchBin = getNightWatchBinPath();
|
|
10062
10394
|
const projectName = getProjectName(projectDir);
|
|
10063
10395
|
const marker = generateMarker(projectName);
|
|
10064
|
-
const logDir =
|
|
10065
|
-
if (!
|
|
10066
|
-
|
|
10396
|
+
const logDir = path26.join(projectDir, LOG_DIR);
|
|
10397
|
+
if (!fs24.existsSync(logDir)) {
|
|
10398
|
+
fs24.mkdirSync(logDir, { recursive: true });
|
|
10067
10399
|
}
|
|
10068
|
-
const executorLog =
|
|
10069
|
-
const reviewerLog =
|
|
10400
|
+
const executorLog = path26.join(logDir, "executor.log");
|
|
10401
|
+
const reviewerLog = path26.join(logDir, "reviewer.log");
|
|
10070
10402
|
if (!options?.force) {
|
|
10071
10403
|
const existingEntries2 = Array.from(
|
|
10072
10404
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
@@ -10103,7 +10435,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10103
10435
|
const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
10104
10436
|
if (installSlicer) {
|
|
10105
10437
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
10106
|
-
const slicerLog =
|
|
10438
|
+
const slicerLog = path26.join(logDir, "slicer.log");
|
|
10107
10439
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
10108
10440
|
entries.push(slicerEntry);
|
|
10109
10441
|
}
|
|
@@ -10111,7 +10443,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10111
10443
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
10112
10444
|
if (installQa) {
|
|
10113
10445
|
const qaSchedule = config.qa.schedule;
|
|
10114
|
-
const qaLog =
|
|
10446
|
+
const qaLog = path26.join(logDir, "qa.log");
|
|
10115
10447
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
10116
10448
|
entries.push(qaEntry);
|
|
10117
10449
|
}
|
|
@@ -10119,7 +10451,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10119
10451
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
10120
10452
|
if (installAudit) {
|
|
10121
10453
|
const auditSchedule = config.audit.schedule;
|
|
10122
|
-
const auditLog =
|
|
10454
|
+
const auditLog = path26.join(logDir, "audit.log");
|
|
10123
10455
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
10124
10456
|
entries.push(auditEntry);
|
|
10125
10457
|
}
|
|
@@ -10127,7 +10459,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10127
10459
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
10128
10460
|
if (installAnalytics) {
|
|
10129
10461
|
const analyticsSchedule = config.analytics.schedule;
|
|
10130
|
-
const analyticsLog =
|
|
10462
|
+
const analyticsLog = path26.join(logDir, "analytics.log");
|
|
10131
10463
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
10132
10464
|
entries.push(analyticsEntry);
|
|
10133
10465
|
}
|
|
@@ -10135,10 +10467,18 @@ function performInstall(projectDir, config, options) {
|
|
|
10135
10467
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10136
10468
|
if (installPrResolver) {
|
|
10137
10469
|
const prResolverSchedule = config.prResolver.schedule;
|
|
10138
|
-
const prResolverLog =
|
|
10470
|
+
const prResolverLog = path26.join(logDir, "pr-resolver.log");
|
|
10139
10471
|
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10140
10472
|
entries.push(prResolverEntry);
|
|
10141
10473
|
}
|
|
10474
|
+
const disableMerger = options?.noMerger === true || options?.merger === false;
|
|
10475
|
+
const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
|
|
10476
|
+
if (installMerger) {
|
|
10477
|
+
const mergerSchedule = config.merger.schedule;
|
|
10478
|
+
const mergerLog = path26.join(logDir, "merger.log");
|
|
10479
|
+
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
|
|
10480
|
+
entries.push(mergerEntry);
|
|
10481
|
+
}
|
|
10142
10482
|
const existingEntries = new Set(
|
|
10143
10483
|
Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]))
|
|
10144
10484
|
);
|
|
@@ -10157,7 +10497,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10157
10497
|
}
|
|
10158
10498
|
}
|
|
10159
10499
|
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) => {
|
|
10500
|
+
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
10501
|
try {
|
|
10162
10502
|
const projectDir = process.cwd();
|
|
10163
10503
|
const config = loadConfig(projectDir);
|
|
@@ -10166,12 +10506,12 @@ function installCommand(program2) {
|
|
|
10166
10506
|
const nightWatchBin = getNightWatchBinPath();
|
|
10167
10507
|
const projectName = getProjectName(projectDir);
|
|
10168
10508
|
const marker = generateMarker(projectName);
|
|
10169
|
-
const logDir =
|
|
10170
|
-
if (!
|
|
10171
|
-
|
|
10509
|
+
const logDir = path26.join(projectDir, LOG_DIR);
|
|
10510
|
+
if (!fs24.existsSync(logDir)) {
|
|
10511
|
+
fs24.mkdirSync(logDir, { recursive: true });
|
|
10172
10512
|
}
|
|
10173
|
-
const executorLog =
|
|
10174
|
-
const reviewerLog =
|
|
10513
|
+
const executorLog = path26.join(logDir, "executor.log");
|
|
10514
|
+
const reviewerLog = path26.join(logDir, "reviewer.log");
|
|
10175
10515
|
const existingEntries = Array.from(
|
|
10176
10516
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
10177
10517
|
);
|
|
@@ -10207,7 +10547,7 @@ function installCommand(program2) {
|
|
|
10207
10547
|
const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
10208
10548
|
let slicerLog;
|
|
10209
10549
|
if (installSlicer) {
|
|
10210
|
-
slicerLog =
|
|
10550
|
+
slicerLog = path26.join(logDir, "slicer.log");
|
|
10211
10551
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
10212
10552
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
10213
10553
|
entries.push(slicerEntry);
|
|
@@ -10216,7 +10556,7 @@ function installCommand(program2) {
|
|
|
10216
10556
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
10217
10557
|
let qaLog;
|
|
10218
10558
|
if (installQa) {
|
|
10219
|
-
qaLog =
|
|
10559
|
+
qaLog = path26.join(logDir, "qa.log");
|
|
10220
10560
|
const qaSchedule = config.qa.schedule;
|
|
10221
10561
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
10222
10562
|
entries.push(qaEntry);
|
|
@@ -10225,7 +10565,7 @@ function installCommand(program2) {
|
|
|
10225
10565
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
10226
10566
|
let auditLog;
|
|
10227
10567
|
if (installAudit) {
|
|
10228
|
-
auditLog =
|
|
10568
|
+
auditLog = path26.join(logDir, "audit.log");
|
|
10229
10569
|
const auditSchedule = config.audit.schedule;
|
|
10230
10570
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
10231
10571
|
entries.push(auditEntry);
|
|
@@ -10234,7 +10574,7 @@ function installCommand(program2) {
|
|
|
10234
10574
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
10235
10575
|
let analyticsLog;
|
|
10236
10576
|
if (installAnalytics) {
|
|
10237
|
-
analyticsLog =
|
|
10577
|
+
analyticsLog = path26.join(logDir, "analytics.log");
|
|
10238
10578
|
const analyticsSchedule = config.analytics.schedule;
|
|
10239
10579
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
10240
10580
|
entries.push(analyticsEntry);
|
|
@@ -10243,11 +10583,20 @@ function installCommand(program2) {
|
|
|
10243
10583
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10244
10584
|
let prResolverLog;
|
|
10245
10585
|
if (installPrResolver) {
|
|
10246
|
-
prResolverLog =
|
|
10586
|
+
prResolverLog = path26.join(logDir, "pr-resolver.log");
|
|
10247
10587
|
const prResolverSchedule = config.prResolver.schedule;
|
|
10248
10588
|
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10249
10589
|
entries.push(prResolverEntry);
|
|
10250
10590
|
}
|
|
10591
|
+
const disableMerger = options.noMerger === true || options.merger === false;
|
|
10592
|
+
const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
|
|
10593
|
+
let mergerLog;
|
|
10594
|
+
if (installMerger) {
|
|
10595
|
+
mergerLog = path26.join(logDir, "merger.log");
|
|
10596
|
+
const mergerSchedule = config.merger.schedule;
|
|
10597
|
+
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
|
|
10598
|
+
entries.push(mergerEntry);
|
|
10599
|
+
}
|
|
10251
10600
|
const existingEntrySet = new Set(existingEntries);
|
|
10252
10601
|
const currentCrontab = readCrontab();
|
|
10253
10602
|
const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
|
|
@@ -10280,6 +10629,9 @@ function installCommand(program2) {
|
|
|
10280
10629
|
if (installPrResolver && prResolverLog) {
|
|
10281
10630
|
dim(` PR Resolver: ${prResolverLog}`);
|
|
10282
10631
|
}
|
|
10632
|
+
if (installMerger && mergerLog) {
|
|
10633
|
+
dim(` Merger: ${mergerLog}`);
|
|
10634
|
+
}
|
|
10283
10635
|
console.log();
|
|
10284
10636
|
dim("To uninstall, run: night-watch uninstall");
|
|
10285
10637
|
dim("To check status, run: night-watch status");
|
|
@@ -10294,8 +10646,8 @@ function installCommand(program2) {
|
|
|
10294
10646
|
|
|
10295
10647
|
// src/commands/uninstall.ts
|
|
10296
10648
|
init_dist();
|
|
10297
|
-
import * as
|
|
10298
|
-
import * as
|
|
10649
|
+
import * as path27 from "path";
|
|
10650
|
+
import * as fs25 from "fs";
|
|
10299
10651
|
function performUninstall(projectDir, options) {
|
|
10300
10652
|
try {
|
|
10301
10653
|
const projectName = getProjectName(projectDir);
|
|
@@ -10310,8 +10662,8 @@ function performUninstall(projectDir, options) {
|
|
|
10310
10662
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
10311
10663
|
unregisterProject(projectDir);
|
|
10312
10664
|
if (!options?.keepLogs) {
|
|
10313
|
-
const logDir =
|
|
10314
|
-
if (
|
|
10665
|
+
const logDir = path27.join(projectDir, "logs");
|
|
10666
|
+
if (fs25.existsSync(logDir)) {
|
|
10315
10667
|
const logFiles = [
|
|
10316
10668
|
"executor.log",
|
|
10317
10669
|
"reviewer.log",
|
|
@@ -10320,15 +10672,15 @@ function performUninstall(projectDir, options) {
|
|
|
10320
10672
|
"pr-resolver.log"
|
|
10321
10673
|
];
|
|
10322
10674
|
logFiles.forEach((logFile) => {
|
|
10323
|
-
const logPath =
|
|
10324
|
-
if (
|
|
10325
|
-
|
|
10675
|
+
const logPath = path27.join(logDir, logFile);
|
|
10676
|
+
if (fs25.existsSync(logPath)) {
|
|
10677
|
+
fs25.unlinkSync(logPath);
|
|
10326
10678
|
}
|
|
10327
10679
|
});
|
|
10328
10680
|
try {
|
|
10329
|
-
const remainingFiles =
|
|
10681
|
+
const remainingFiles = fs25.readdirSync(logDir);
|
|
10330
10682
|
if (remainingFiles.length === 0) {
|
|
10331
|
-
|
|
10683
|
+
fs25.rmdirSync(logDir);
|
|
10332
10684
|
}
|
|
10333
10685
|
} catch {
|
|
10334
10686
|
}
|
|
@@ -10361,8 +10713,8 @@ function uninstallCommand(program2) {
|
|
|
10361
10713
|
existingEntries.forEach((entry) => dim(` ${entry}`));
|
|
10362
10714
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
10363
10715
|
if (!options.keepLogs) {
|
|
10364
|
-
const logDir =
|
|
10365
|
-
if (
|
|
10716
|
+
const logDir = path27.join(projectDir, "logs");
|
|
10717
|
+
if (fs25.existsSync(logDir)) {
|
|
10366
10718
|
const logFiles = [
|
|
10367
10719
|
"executor.log",
|
|
10368
10720
|
"reviewer.log",
|
|
@@ -10372,16 +10724,16 @@ function uninstallCommand(program2) {
|
|
|
10372
10724
|
];
|
|
10373
10725
|
let logsRemoved = 0;
|
|
10374
10726
|
logFiles.forEach((logFile) => {
|
|
10375
|
-
const logPath =
|
|
10376
|
-
if (
|
|
10377
|
-
|
|
10727
|
+
const logPath = path27.join(logDir, logFile);
|
|
10728
|
+
if (fs25.existsSync(logPath)) {
|
|
10729
|
+
fs25.unlinkSync(logPath);
|
|
10378
10730
|
logsRemoved++;
|
|
10379
10731
|
}
|
|
10380
10732
|
});
|
|
10381
10733
|
try {
|
|
10382
|
-
const remainingFiles =
|
|
10734
|
+
const remainingFiles = fs25.readdirSync(logDir);
|
|
10383
10735
|
if (remainingFiles.length === 0) {
|
|
10384
|
-
|
|
10736
|
+
fs25.rmdirSync(logDir);
|
|
10385
10737
|
}
|
|
10386
10738
|
} catch {
|
|
10387
10739
|
}
|
|
@@ -10425,11 +10777,15 @@ function statusCommand(program2) {
|
|
|
10425
10777
|
const qaProc = snapshot.processes.find((p) => p.name === "qa");
|
|
10426
10778
|
const auditProc = snapshot.processes.find((p) => p.name === "audit");
|
|
10427
10779
|
const plannerProc = snapshot.processes.find((p) => p.name === "planner");
|
|
10780
|
+
const analyticsProc = snapshot.processes.find((p) => p.name === "analytics");
|
|
10781
|
+
const mergerProc = snapshot.processes.find((p) => p.name === "merger");
|
|
10428
10782
|
const executorLog = snapshot.logs.find((l) => l.name === "executor");
|
|
10429
10783
|
const reviewerLog = snapshot.logs.find((l) => l.name === "reviewer");
|
|
10430
10784
|
const qaLog = snapshot.logs.find((l) => l.name === "qa");
|
|
10431
10785
|
const auditLog = snapshot.logs.find((l) => l.name === "audit");
|
|
10432
10786
|
const plannerLog = snapshot.logs.find((l) => l.name === "planner");
|
|
10787
|
+
const analyticsLog = snapshot.logs.find((l) => l.name === "analytics");
|
|
10788
|
+
const mergerLog = snapshot.logs.find((l) => l.name === "merger");
|
|
10433
10789
|
const pendingPrds = snapshot.prds.filter(
|
|
10434
10790
|
(p) => p.status === "ready" || p.status === "blocked"
|
|
10435
10791
|
).length;
|
|
@@ -10447,6 +10803,8 @@ function statusCommand(program2) {
|
|
|
10447
10803
|
qa: { running: qaProc?.running ?? false, pid: qaProc?.pid ?? null },
|
|
10448
10804
|
audit: { running: auditProc?.running ?? false, pid: auditProc?.pid ?? null },
|
|
10449
10805
|
planner: { running: plannerProc?.running ?? false, pid: plannerProc?.pid ?? null },
|
|
10806
|
+
analytics: { running: analyticsProc?.running ?? false, pid: analyticsProc?.pid ?? null },
|
|
10807
|
+
merger: { running: mergerProc?.running ?? false, pid: mergerProc?.pid ?? null },
|
|
10450
10808
|
prds: { pending: pendingPrds, claimed: claimedPrds, done: donePrds },
|
|
10451
10809
|
prs: { open: snapshot.prs.length },
|
|
10452
10810
|
crontab: snapshot.crontab,
|
|
@@ -10480,6 +10838,18 @@ function statusCommand(program2) {
|
|
|
10480
10838
|
lastLines: plannerLog.lastLines,
|
|
10481
10839
|
exists: plannerLog.exists,
|
|
10482
10840
|
size: plannerLog.size
|
|
10841
|
+
} : void 0,
|
|
10842
|
+
analytics: analyticsLog ? {
|
|
10843
|
+
path: analyticsLog.path,
|
|
10844
|
+
lastLines: analyticsLog.lastLines,
|
|
10845
|
+
exists: analyticsLog.exists,
|
|
10846
|
+
size: analyticsLog.size
|
|
10847
|
+
} : void 0,
|
|
10848
|
+
merger: mergerLog ? {
|
|
10849
|
+
path: mergerLog.path,
|
|
10850
|
+
lastLines: mergerLog.lastLines,
|
|
10851
|
+
exists: mergerLog.exists,
|
|
10852
|
+
size: mergerLog.size
|
|
10483
10853
|
} : void 0
|
|
10484
10854
|
}
|
|
10485
10855
|
};
|
|
@@ -10517,6 +10887,14 @@ function statusCommand(program2) {
|
|
|
10517
10887
|
"Planner",
|
|
10518
10888
|
formatRunningStatus(status.planner.running, status.planner.pid)
|
|
10519
10889
|
]);
|
|
10890
|
+
processTable.push([
|
|
10891
|
+
"Analytics",
|
|
10892
|
+
formatRunningStatus(status.analytics.running, status.analytics.pid)
|
|
10893
|
+
]);
|
|
10894
|
+
processTable.push([
|
|
10895
|
+
"Merger",
|
|
10896
|
+
formatRunningStatus(status.merger.running, status.merger.pid)
|
|
10897
|
+
]);
|
|
10520
10898
|
console.log(processTable.toString());
|
|
10521
10899
|
header("PRD Status");
|
|
10522
10900
|
const prdTable = createTable({ head: ["Status", "Count"] });
|
|
@@ -10573,6 +10951,20 @@ function statusCommand(program2) {
|
|
|
10573
10951
|
status.logs.planner.exists ? "Exists" : "Not found"
|
|
10574
10952
|
]);
|
|
10575
10953
|
}
|
|
10954
|
+
if (status.logs.analytics) {
|
|
10955
|
+
logTable.push([
|
|
10956
|
+
"Analytics",
|
|
10957
|
+
status.logs.analytics.exists ? formatBytes(status.logs.analytics.size) : "-",
|
|
10958
|
+
status.logs.analytics.exists ? "Exists" : "Not found"
|
|
10959
|
+
]);
|
|
10960
|
+
}
|
|
10961
|
+
if (status.logs.merger) {
|
|
10962
|
+
logTable.push([
|
|
10963
|
+
"Merger",
|
|
10964
|
+
status.logs.merger.exists ? formatBytes(status.logs.merger.size) : "-",
|
|
10965
|
+
status.logs.merger.exists ? "Exists" : "Not found"
|
|
10966
|
+
]);
|
|
10967
|
+
}
|
|
10576
10968
|
console.log(logTable.toString());
|
|
10577
10969
|
if (options.verbose) {
|
|
10578
10970
|
if (status.logs.executor?.exists && status.logs.executor.lastLines.length > 0) {
|
|
@@ -10595,6 +10987,14 @@ function statusCommand(program2) {
|
|
|
10595
10987
|
dim(" Planner last 5 lines:");
|
|
10596
10988
|
status.logs.planner.lastLines.forEach((line) => dim(` ${line}`));
|
|
10597
10989
|
}
|
|
10990
|
+
if (status.logs.analytics?.exists && status.logs.analytics.lastLines.length > 0) {
|
|
10991
|
+
dim(" Analytics last 5 lines:");
|
|
10992
|
+
status.logs.analytics.lastLines.forEach((line) => dim(` ${line}`));
|
|
10993
|
+
}
|
|
10994
|
+
if (status.logs.merger?.exists && status.logs.merger.lastLines.length > 0) {
|
|
10995
|
+
dim(" Merger last 5 lines:");
|
|
10996
|
+
status.logs.merger.lastLines.forEach((line) => dim(` ${line}`));
|
|
10997
|
+
}
|
|
10598
10998
|
}
|
|
10599
10999
|
header("Commands");
|
|
10600
11000
|
dim(" night-watch install - Install crontab entries");
|
|
@@ -10604,6 +11004,8 @@ function statusCommand(program2) {
|
|
|
10604
11004
|
dim(" night-watch qa - Run QA now");
|
|
10605
11005
|
dim(" night-watch audit - Run audit now");
|
|
10606
11006
|
dim(" night-watch planner - Run planner now");
|
|
11007
|
+
dim(" night-watch analytics - Run analytics now");
|
|
11008
|
+
dim(" night-watch merge - Run merger now");
|
|
10607
11009
|
console.log();
|
|
10608
11010
|
} catch (error2) {
|
|
10609
11011
|
console.error(
|
|
@@ -10617,14 +11019,14 @@ function statusCommand(program2) {
|
|
|
10617
11019
|
// src/commands/logs.ts
|
|
10618
11020
|
init_dist();
|
|
10619
11021
|
import { spawn as spawn3 } from "child_process";
|
|
10620
|
-
import * as
|
|
10621
|
-
import * as
|
|
11022
|
+
import * as path28 from "path";
|
|
11023
|
+
import * as fs26 from "fs";
|
|
10622
11024
|
function getLastLines(filePath, lineCount) {
|
|
10623
|
-
if (!
|
|
11025
|
+
if (!fs26.existsSync(filePath)) {
|
|
10624
11026
|
return `Log file not found: ${filePath}`;
|
|
10625
11027
|
}
|
|
10626
11028
|
try {
|
|
10627
|
-
const content =
|
|
11029
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
10628
11030
|
const lines = content.trim().split("\n");
|
|
10629
11031
|
return lines.slice(-lineCount).join("\n");
|
|
10630
11032
|
} catch (error2) {
|
|
@@ -10632,7 +11034,7 @@ function getLastLines(filePath, lineCount) {
|
|
|
10632
11034
|
}
|
|
10633
11035
|
}
|
|
10634
11036
|
function followLog(filePath) {
|
|
10635
|
-
if (!
|
|
11037
|
+
if (!fs26.existsSync(filePath)) {
|
|
10636
11038
|
console.log(`Log file not found: ${filePath}`);
|
|
10637
11039
|
console.log("The log file will be created when the first execution runs.");
|
|
10638
11040
|
return;
|
|
@@ -10649,32 +11051,42 @@ function followLog(filePath) {
|
|
|
10649
11051
|
});
|
|
10650
11052
|
}
|
|
10651
11053
|
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(
|
|
11054
|
+
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(
|
|
11055
|
+
"-t, --type <type>",
|
|
11056
|
+
"Log type to view (executor|reviewer|qa|audit|planner|analytics|merger|all)",
|
|
11057
|
+
"all"
|
|
11058
|
+
).action(async (options) => {
|
|
10653
11059
|
try {
|
|
10654
11060
|
const projectDir = process.cwd();
|
|
10655
|
-
const logDir =
|
|
11061
|
+
const logDir = path28.join(projectDir, LOG_DIR);
|
|
10656
11062
|
const lineCount = parseInt(options.lines || "50", 10);
|
|
10657
|
-
const executorLog =
|
|
10658
|
-
const reviewerLog =
|
|
10659
|
-
const qaLog =
|
|
10660
|
-
const auditLog =
|
|
10661
|
-
const plannerLog =
|
|
11063
|
+
const executorLog = path28.join(logDir, EXECUTOR_LOG_FILE);
|
|
11064
|
+
const reviewerLog = path28.join(logDir, REVIEWER_LOG_FILE);
|
|
11065
|
+
const qaLog = path28.join(logDir, `${QA_LOG_NAME}.log`);
|
|
11066
|
+
const auditLog = path28.join(logDir, `${AUDIT_LOG_NAME}.log`);
|
|
11067
|
+
const plannerLog = path28.join(logDir, `${PLANNER_LOG_NAME}.log`);
|
|
11068
|
+
const analyticsLog = path28.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
|
|
11069
|
+
const mergerLog = path28.join(logDir, `${MERGER_LOG_NAME}.log`);
|
|
10662
11070
|
const logType = options.type?.toLowerCase() || "all";
|
|
10663
11071
|
const showExecutor = logType === "all" || logType === "run" || logType === "executor";
|
|
10664
11072
|
const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
|
|
10665
11073
|
const showQa = logType === "all" || logType === "qa";
|
|
10666
11074
|
const showAudit = logType === "all" || logType === "audit";
|
|
10667
11075
|
const showPlanner = logType === "all" || logType === "planner" || logType === "slice" || logType === "slicer";
|
|
11076
|
+
const showAnalytics = logType === "all" || logType === "analytics";
|
|
11077
|
+
const showMerger = logType === "all" || logType === "merge" || logType === "merger";
|
|
10668
11078
|
if (options.follow) {
|
|
10669
11079
|
if (logType === "all") {
|
|
10670
11080
|
dim("Note: Following all logs is not supported. Showing executor log.");
|
|
10671
|
-
dim("Use --type reviewer|qa|audit|planner for other logs.\n");
|
|
11081
|
+
dim("Use --type reviewer|qa|audit|planner|analytics|merger for other logs.\n");
|
|
10672
11082
|
}
|
|
10673
11083
|
let targetLog = executorLog;
|
|
10674
11084
|
if (showReviewer) targetLog = reviewerLog;
|
|
10675
11085
|
else if (showQa) targetLog = qaLog;
|
|
10676
11086
|
else if (showAudit) targetLog = auditLog;
|
|
10677
11087
|
else if (showPlanner) targetLog = plannerLog;
|
|
11088
|
+
else if (showAnalytics) targetLog = analyticsLog;
|
|
11089
|
+
else if (showMerger) targetLog = mergerLog;
|
|
10678
11090
|
followLog(targetLog);
|
|
10679
11091
|
return;
|
|
10680
11092
|
}
|
|
@@ -10709,10 +11121,24 @@ function logsCommand(program2) {
|
|
|
10709
11121
|
console.log();
|
|
10710
11122
|
console.log(getLastLines(plannerLog, lineCount));
|
|
10711
11123
|
}
|
|
11124
|
+
if (showAnalytics) {
|
|
11125
|
+
header("Analytics Log");
|
|
11126
|
+
dim(`File: ${analyticsLog}`);
|
|
11127
|
+
console.log();
|
|
11128
|
+
console.log(getLastLines(analyticsLog, lineCount));
|
|
11129
|
+
}
|
|
11130
|
+
if (showMerger) {
|
|
11131
|
+
header("Merger Log");
|
|
11132
|
+
dim(`File: ${mergerLog}`);
|
|
11133
|
+
console.log();
|
|
11134
|
+
console.log(getLastLines(mergerLog, lineCount));
|
|
11135
|
+
}
|
|
10712
11136
|
console.log();
|
|
10713
11137
|
dim("---");
|
|
10714
11138
|
dim("Tip: Use -f to follow logs in real-time");
|
|
10715
|
-
dim(
|
|
11139
|
+
dim(
|
|
11140
|
+
" Use --type executor|reviewer|qa|audit|planner|analytics|merger to view specific logs"
|
|
11141
|
+
);
|
|
10716
11142
|
} catch (err) {
|
|
10717
11143
|
console.error(`Error reading logs: ${err instanceof Error ? err.message : String(err)}`);
|
|
10718
11144
|
process.exit(1);
|
|
@@ -10723,8 +11149,8 @@ function logsCommand(program2) {
|
|
|
10723
11149
|
// src/commands/prd.ts
|
|
10724
11150
|
init_dist();
|
|
10725
11151
|
import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
|
|
10726
|
-
import * as
|
|
10727
|
-
import * as
|
|
11152
|
+
import * as fs27 from "fs";
|
|
11153
|
+
import * as path29 from "path";
|
|
10728
11154
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10729
11155
|
import { dirname as dirname9 } from "path";
|
|
10730
11156
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
@@ -10732,21 +11158,21 @@ var __dirname3 = dirname9(__filename3);
|
|
|
10732
11158
|
function findTemplatesDir2(startDir) {
|
|
10733
11159
|
let current = startDir;
|
|
10734
11160
|
for (let i = 0; i < 8; i++) {
|
|
10735
|
-
const candidate =
|
|
10736
|
-
if (
|
|
11161
|
+
const candidate = path29.join(current, "templates");
|
|
11162
|
+
if (fs27.existsSync(candidate) && fs27.statSync(candidate).isDirectory()) {
|
|
10737
11163
|
return candidate;
|
|
10738
11164
|
}
|
|
10739
|
-
current =
|
|
11165
|
+
current = path29.dirname(current);
|
|
10740
11166
|
}
|
|
10741
|
-
return
|
|
11167
|
+
return path29.join(startDir, "templates");
|
|
10742
11168
|
}
|
|
10743
11169
|
var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
|
|
10744
11170
|
function slugify2(name) {
|
|
10745
11171
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
10746
11172
|
}
|
|
10747
11173
|
function getNextPrdNumber2(prdDir) {
|
|
10748
|
-
if (!
|
|
10749
|
-
const files =
|
|
11174
|
+
if (!fs27.existsSync(prdDir)) return 1;
|
|
11175
|
+
const files = fs27.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
10750
11176
|
const numbers = files.map((f) => {
|
|
10751
11177
|
const match = f.match(/^(\d+)-/);
|
|
10752
11178
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -10827,13 +11253,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
|
|
|
10827
11253
|
return null;
|
|
10828
11254
|
}
|
|
10829
11255
|
const ref = branch && branch !== "HEAD" ? branch : "main";
|
|
10830
|
-
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(
|
|
11256
|
+
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path29.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
10831
11257
|
} catch {
|
|
10832
11258
|
return null;
|
|
10833
11259
|
}
|
|
10834
11260
|
}
|
|
10835
11261
|
function buildGithubIssueBody(prdPath, projectDir, prdContent) {
|
|
10836
|
-
const relPath =
|
|
11262
|
+
const relPath = path29.relative(projectDir, prdPath);
|
|
10837
11263
|
const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
|
|
10838
11264
|
const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
|
|
10839
11265
|
return `${fileLine}
|
|
@@ -10844,13 +11270,13 @@ ${prdContent}
|
|
|
10844
11270
|
Created via \`night-watch prd create\`.`;
|
|
10845
11271
|
}
|
|
10846
11272
|
async function generatePrdWithClaude(description, projectDir, model) {
|
|
10847
|
-
const bundledTemplatePath =
|
|
10848
|
-
const installedTemplatePath =
|
|
10849
|
-
const templatePath =
|
|
10850
|
-
if (!
|
|
11273
|
+
const bundledTemplatePath = path29.join(TEMPLATES_DIR2, "prd-creator.md");
|
|
11274
|
+
const installedTemplatePath = path29.join(projectDir, "instructions", "prd-creator.md");
|
|
11275
|
+
const templatePath = fs27.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
|
|
11276
|
+
if (!fs27.existsSync(templatePath)) {
|
|
10851
11277
|
return null;
|
|
10852
11278
|
}
|
|
10853
|
-
const planningPrinciples =
|
|
11279
|
+
const planningPrinciples = fs27.readFileSync(templatePath, "utf-8");
|
|
10854
11280
|
const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
|
|
10855
11281
|
const modelId = model ?? CLAUDE_MODEL_IDS.opus;
|
|
10856
11282
|
const env = buildNativeClaudeEnv(process.env);
|
|
@@ -10918,17 +11344,17 @@ function runGh(args, cwd) {
|
|
|
10918
11344
|
return null;
|
|
10919
11345
|
}
|
|
10920
11346
|
function createGithubIssue(title, prdPath, projectDir, prdContent) {
|
|
10921
|
-
const tmpFile =
|
|
11347
|
+
const tmpFile = path29.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
|
|
10922
11348
|
try {
|
|
10923
11349
|
const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
|
|
10924
|
-
|
|
11350
|
+
fs27.writeFileSync(tmpFile, body, "utf-8");
|
|
10925
11351
|
const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
|
|
10926
11352
|
return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
|
|
10927
11353
|
} catch {
|
|
10928
11354
|
return null;
|
|
10929
11355
|
} finally {
|
|
10930
11356
|
try {
|
|
10931
|
-
|
|
11357
|
+
fs27.unlinkSync(tmpFile);
|
|
10932
11358
|
} catch {
|
|
10933
11359
|
}
|
|
10934
11360
|
}
|
|
@@ -10940,10 +11366,10 @@ function parseDependencies(content) {
|
|
|
10940
11366
|
}
|
|
10941
11367
|
function isClaimActive(claimPath, maxRuntime) {
|
|
10942
11368
|
try {
|
|
10943
|
-
if (!
|
|
11369
|
+
if (!fs27.existsSync(claimPath)) {
|
|
10944
11370
|
return { active: false };
|
|
10945
11371
|
}
|
|
10946
|
-
const content =
|
|
11372
|
+
const content = fs27.readFileSync(claimPath, "utf-8");
|
|
10947
11373
|
const claim = JSON.parse(content);
|
|
10948
11374
|
const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
|
|
10949
11375
|
if (age < maxRuntime) {
|
|
@@ -10958,9 +11384,9 @@ function prdCommand(program2) {
|
|
|
10958
11384
|
const prd = program2.command("prd").description("Manage PRD files");
|
|
10959
11385
|
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
11386
|
const projectDir = process.cwd();
|
|
10961
|
-
const prdDir =
|
|
10962
|
-
if (!
|
|
10963
|
-
|
|
11387
|
+
const prdDir = path29.join(projectDir, resolvePrdCreateDir());
|
|
11388
|
+
if (!fs27.existsSync(prdDir)) {
|
|
11389
|
+
fs27.mkdirSync(prdDir, { recursive: true });
|
|
10964
11390
|
}
|
|
10965
11391
|
const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
|
|
10966
11392
|
const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
|
|
@@ -10974,13 +11400,13 @@ function prdCommand(program2) {
|
|
|
10974
11400
|
const prdTitle = extractPrdTitle(generated) ?? name;
|
|
10975
11401
|
const slug = slugify2(prdTitle);
|
|
10976
11402
|
const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
|
|
10977
|
-
const filePath =
|
|
10978
|
-
if (
|
|
11403
|
+
const filePath = path29.join(prdDir, filename);
|
|
11404
|
+
if (fs27.existsSync(filePath)) {
|
|
10979
11405
|
error(`File already exists: ${filePath}`);
|
|
10980
11406
|
dim("Use a different name or remove the existing file.");
|
|
10981
11407
|
process.exit(1);
|
|
10982
11408
|
}
|
|
10983
|
-
|
|
11409
|
+
fs27.writeFileSync(filePath, generated, "utf-8");
|
|
10984
11410
|
header("PRD Created");
|
|
10985
11411
|
success(`Created: ${filePath}`);
|
|
10986
11412
|
const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
|
|
@@ -10993,15 +11419,15 @@ function prdCommand(program2) {
|
|
|
10993
11419
|
prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
|
|
10994
11420
|
const projectDir = process.cwd();
|
|
10995
11421
|
const config = loadConfig(projectDir);
|
|
10996
|
-
const absolutePrdDir =
|
|
10997
|
-
const doneDir =
|
|
11422
|
+
const absolutePrdDir = path29.join(projectDir, config.prdDir);
|
|
11423
|
+
const doneDir = path29.join(absolutePrdDir, "done");
|
|
10998
11424
|
const pending = [];
|
|
10999
|
-
if (
|
|
11000
|
-
const files =
|
|
11425
|
+
if (fs27.existsSync(absolutePrdDir)) {
|
|
11426
|
+
const files = fs27.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
|
|
11001
11427
|
for (const file of files) {
|
|
11002
|
-
const content =
|
|
11428
|
+
const content = fs27.readFileSync(path29.join(absolutePrdDir, file), "utf-8");
|
|
11003
11429
|
const deps = parseDependencies(content);
|
|
11004
|
-
const claimPath =
|
|
11430
|
+
const claimPath = path29.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
|
|
11005
11431
|
const claimStatus = isClaimActive(claimPath, config.maxRuntime);
|
|
11006
11432
|
pending.push({
|
|
11007
11433
|
name: file,
|
|
@@ -11012,10 +11438,10 @@ function prdCommand(program2) {
|
|
|
11012
11438
|
}
|
|
11013
11439
|
}
|
|
11014
11440
|
const done = [];
|
|
11015
|
-
if (
|
|
11016
|
-
const files =
|
|
11441
|
+
if (fs27.existsSync(doneDir)) {
|
|
11442
|
+
const files = fs27.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
11017
11443
|
for (const file of files) {
|
|
11018
|
-
const content =
|
|
11444
|
+
const content = fs27.readFileSync(path29.join(doneDir, file), "utf-8");
|
|
11019
11445
|
const deps = parseDependencies(content);
|
|
11020
11446
|
done.push({ name: file, dependencies: deps });
|
|
11021
11447
|
}
|
|
@@ -11052,7 +11478,7 @@ import blessed6 from "blessed";
|
|
|
11052
11478
|
// src/commands/dashboard/tab-status.ts
|
|
11053
11479
|
init_dist();
|
|
11054
11480
|
import blessed from "blessed";
|
|
11055
|
-
import * as
|
|
11481
|
+
import * as fs28 from "fs";
|
|
11056
11482
|
function sortPrdsByPriority(prds, priority) {
|
|
11057
11483
|
if (priority.length === 0) return prds;
|
|
11058
11484
|
const priorityMap = /* @__PURE__ */ new Map();
|
|
@@ -11148,7 +11574,7 @@ function renderLogPane(projectDir, logs) {
|
|
|
11148
11574
|
let newestMtime = 0;
|
|
11149
11575
|
for (const log of existingLogs) {
|
|
11150
11576
|
try {
|
|
11151
|
-
const stat =
|
|
11577
|
+
const stat = fs28.statSync(log.path);
|
|
11152
11578
|
if (stat.mtimeMs > newestMtime) {
|
|
11153
11579
|
newestMtime = stat.mtimeMs;
|
|
11154
11580
|
newestLog = log;
|
|
@@ -12803,8 +13229,8 @@ function createActionsTab() {
|
|
|
12803
13229
|
// src/commands/dashboard/tab-logs.ts
|
|
12804
13230
|
init_dist();
|
|
12805
13231
|
import blessed5 from "blessed";
|
|
12806
|
-
import * as
|
|
12807
|
-
import * as
|
|
13232
|
+
import * as fs29 from "fs";
|
|
13233
|
+
import * as path30 from "path";
|
|
12808
13234
|
var LOG_NAMES = ["executor", "reviewer"];
|
|
12809
13235
|
var LOG_LINES = 200;
|
|
12810
13236
|
function createLogsTab() {
|
|
@@ -12845,7 +13271,7 @@ function createLogsTab() {
|
|
|
12845
13271
|
let activeKeyHandlers = [];
|
|
12846
13272
|
let activeCtx = null;
|
|
12847
13273
|
function getLogPath(projectDir, logName) {
|
|
12848
|
-
return
|
|
13274
|
+
return path30.join(projectDir, "logs", `${logName}.log`);
|
|
12849
13275
|
}
|
|
12850
13276
|
function updateSelector() {
|
|
12851
13277
|
const tabs = LOG_NAMES.map((name, idx) => {
|
|
@@ -12859,7 +13285,7 @@ function createLogsTab() {
|
|
|
12859
13285
|
function loadLog(ctx) {
|
|
12860
13286
|
const logName = LOG_NAMES[selectedLogIndex];
|
|
12861
13287
|
const logPath = getLogPath(ctx.projectDir, logName);
|
|
12862
|
-
if (!
|
|
13288
|
+
if (!fs29.existsSync(logPath)) {
|
|
12863
13289
|
logContent.setContent(
|
|
12864
13290
|
`{yellow-fg}No ${logName}.log file found{/yellow-fg}
|
|
12865
13291
|
|
|
@@ -12869,7 +13295,7 @@ Log will appear here once the ${logName} runs.`
|
|
|
12869
13295
|
return;
|
|
12870
13296
|
}
|
|
12871
13297
|
try {
|
|
12872
|
-
const stat =
|
|
13298
|
+
const stat = fs29.statSync(logPath);
|
|
12873
13299
|
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
12874
13300
|
logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
|
|
12875
13301
|
} catch {
|
|
@@ -13367,12 +13793,12 @@ function doctorCommand(program2) {
|
|
|
13367
13793
|
|
|
13368
13794
|
// src/commands/serve.ts
|
|
13369
13795
|
init_dist();
|
|
13370
|
-
import * as
|
|
13796
|
+
import * as fs34 from "fs";
|
|
13371
13797
|
|
|
13372
13798
|
// ../server/dist/index.js
|
|
13373
13799
|
init_dist();
|
|
13374
|
-
import * as
|
|
13375
|
-
import * as
|
|
13800
|
+
import * as fs33 from "fs";
|
|
13801
|
+
import * as path36 from "path";
|
|
13376
13802
|
import { dirname as dirname11 } from "path";
|
|
13377
13803
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
13378
13804
|
import cors from "cors";
|
|
@@ -13457,8 +13883,8 @@ function setupGracefulShutdown(server, beforeClose) {
|
|
|
13457
13883
|
|
|
13458
13884
|
// ../server/dist/middleware/project-resolver.middleware.js
|
|
13459
13885
|
init_dist();
|
|
13460
|
-
import * as
|
|
13461
|
-
import * as
|
|
13886
|
+
import * as fs30 from "fs";
|
|
13887
|
+
import * as path31 from "path";
|
|
13462
13888
|
function resolveProject(req, res, next) {
|
|
13463
13889
|
const projectId = req.params.projectId;
|
|
13464
13890
|
const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
|
|
@@ -13468,7 +13894,7 @@ function resolveProject(req, res, next) {
|
|
|
13468
13894
|
res.status(404).json({ error: `Project not found: ${decodedId}` });
|
|
13469
13895
|
return;
|
|
13470
13896
|
}
|
|
13471
|
-
if (!
|
|
13897
|
+
if (!fs30.existsSync(entry.path) || !fs30.existsSync(path31.join(entry.path, CONFIG_FILE_NAME))) {
|
|
13472
13898
|
res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
|
|
13473
13899
|
return;
|
|
13474
13900
|
}
|
|
@@ -13513,8 +13939,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
|
|
|
13513
13939
|
|
|
13514
13940
|
// ../server/dist/routes/action.routes.js
|
|
13515
13941
|
init_dist();
|
|
13516
|
-
import * as
|
|
13517
|
-
import * as
|
|
13942
|
+
import * as fs31 from "fs";
|
|
13943
|
+
import * as path32 from "path";
|
|
13518
13944
|
import { execSync as execSync6, spawn as spawn6 } from "child_process";
|
|
13519
13945
|
import { Router } from "express";
|
|
13520
13946
|
|
|
@@ -13552,17 +13978,17 @@ function getBoardProvider(config, projectDir) {
|
|
|
13552
13978
|
function cleanOrphanedClaims(dir) {
|
|
13553
13979
|
let entries;
|
|
13554
13980
|
try {
|
|
13555
|
-
entries =
|
|
13981
|
+
entries = fs31.readdirSync(dir, { withFileTypes: true });
|
|
13556
13982
|
} catch {
|
|
13557
13983
|
return;
|
|
13558
13984
|
}
|
|
13559
13985
|
for (const entry of entries) {
|
|
13560
|
-
const fullPath =
|
|
13986
|
+
const fullPath = path32.join(dir, entry.name);
|
|
13561
13987
|
if (entry.isDirectory() && entry.name !== "done") {
|
|
13562
13988
|
cleanOrphanedClaims(fullPath);
|
|
13563
13989
|
} else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
|
|
13564
13990
|
try {
|
|
13565
|
-
|
|
13991
|
+
fs31.unlinkSync(fullPath);
|
|
13566
13992
|
} catch {
|
|
13567
13993
|
}
|
|
13568
13994
|
}
|
|
@@ -13717,19 +14143,19 @@ function createActionRouteHandlers(ctx) {
|
|
|
13717
14143
|
res.status(400).json({ error: "Invalid PRD name" });
|
|
13718
14144
|
return;
|
|
13719
14145
|
}
|
|
13720
|
-
const prdDir =
|
|
14146
|
+
const prdDir = path32.join(projectDir, config.prdDir);
|
|
13721
14147
|
const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
|
|
13722
|
-
const pendingPath =
|
|
13723
|
-
const donePath =
|
|
13724
|
-
if (
|
|
14148
|
+
const pendingPath = path32.join(prdDir, normalized);
|
|
14149
|
+
const donePath = path32.join(prdDir, "done", normalized);
|
|
14150
|
+
if (fs31.existsSync(pendingPath)) {
|
|
13725
14151
|
res.json({ message: `"${normalized}" is already pending` });
|
|
13726
14152
|
return;
|
|
13727
14153
|
}
|
|
13728
|
-
if (!
|
|
14154
|
+
if (!fs31.existsSync(donePath)) {
|
|
13729
14155
|
res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
|
|
13730
14156
|
return;
|
|
13731
14157
|
}
|
|
13732
|
-
|
|
14158
|
+
fs31.renameSync(donePath, pendingPath);
|
|
13733
14159
|
res.json({ message: `Moved "${normalized}" back to pending` });
|
|
13734
14160
|
} catch (error2) {
|
|
13735
14161
|
res.status(500).json({
|
|
@@ -13747,11 +14173,11 @@ function createActionRouteHandlers(ctx) {
|
|
|
13747
14173
|
res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
|
|
13748
14174
|
return;
|
|
13749
14175
|
}
|
|
13750
|
-
if (
|
|
13751
|
-
|
|
14176
|
+
if (fs31.existsSync(lockPath)) {
|
|
14177
|
+
fs31.unlinkSync(lockPath);
|
|
13752
14178
|
}
|
|
13753
|
-
const prdDir =
|
|
13754
|
-
if (
|
|
14179
|
+
const prdDir = path32.join(projectDir, config.prdDir);
|
|
14180
|
+
if (fs31.existsSync(prdDir)) {
|
|
13755
14181
|
cleanOrphanedClaims(prdDir);
|
|
13756
14182
|
}
|
|
13757
14183
|
broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
|
|
@@ -14306,6 +14732,9 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14306
14732
|
if (audit.maxRuntime !== void 0 && (typeof audit.maxRuntime !== "number" || audit.maxRuntime < 60)) {
|
|
14307
14733
|
return "audit.maxRuntime must be a number >= 60";
|
|
14308
14734
|
}
|
|
14735
|
+
if (audit.targetColumn !== void 0 && !BOARD_COLUMNS.includes(audit.targetColumn)) {
|
|
14736
|
+
return `audit.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
|
|
14737
|
+
}
|
|
14309
14738
|
}
|
|
14310
14739
|
if (changes.analytics !== void 0) {
|
|
14311
14740
|
if (typeof changes.analytics !== "object" || changes.analytics === null) {
|
|
@@ -14326,9 +14755,8 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14326
14755
|
return "analytics.lookbackDays must be a number between 1 and 90";
|
|
14327
14756
|
}
|
|
14328
14757
|
if (analytics.targetColumn !== void 0) {
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
return `analytics.targetColumn must be one of: ${validColumns.join(", ")}`;
|
|
14758
|
+
if (!BOARD_COLUMNS.includes(analytics.targetColumn)) {
|
|
14759
|
+
return `analytics.targetColumn must be one of: ${BOARD_COLUMNS.join(", ")}`;
|
|
14332
14760
|
}
|
|
14333
14761
|
}
|
|
14334
14762
|
if (analytics.analysisPrompt !== void 0 && typeof analytics.analysisPrompt !== "string") {
|
|
@@ -14353,16 +14781,8 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
14353
14781
|
if (typeof queue.priority !== "object" || queue.priority === null) {
|
|
14354
14782
|
return "queue.priority must be an object";
|
|
14355
14783
|
}
|
|
14356
|
-
const validQueueJobs = [
|
|
14357
|
-
"executor",
|
|
14358
|
-
"reviewer",
|
|
14359
|
-
"qa",
|
|
14360
|
-
"audit",
|
|
14361
|
-
"slicer",
|
|
14362
|
-
"analytics"
|
|
14363
|
-
];
|
|
14364
14784
|
for (const [jobType, value] of Object.entries(queue.priority)) {
|
|
14365
|
-
if (!
|
|
14785
|
+
if (!VALID_JOB_TYPES.includes(jobType)) {
|
|
14366
14786
|
return `queue.priority contains invalid job type: ${jobType}`;
|
|
14367
14787
|
}
|
|
14368
14788
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
@@ -14516,8 +14936,8 @@ function createProjectConfigRoutes() {
|
|
|
14516
14936
|
|
|
14517
14937
|
// ../server/dist/routes/doctor.routes.js
|
|
14518
14938
|
init_dist();
|
|
14519
|
-
import * as
|
|
14520
|
-
import * as
|
|
14939
|
+
import * as fs32 from "fs";
|
|
14940
|
+
import * as path33 from "path";
|
|
14521
14941
|
import { execSync as execSync7 } from "child_process";
|
|
14522
14942
|
import { Router as Router4 } from "express";
|
|
14523
14943
|
function runDoctorChecks(projectDir, config) {
|
|
@@ -14550,7 +14970,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14550
14970
|
});
|
|
14551
14971
|
}
|
|
14552
14972
|
try {
|
|
14553
|
-
const projectName =
|
|
14973
|
+
const projectName = path33.basename(projectDir);
|
|
14554
14974
|
const marker = generateMarker(projectName);
|
|
14555
14975
|
const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
|
|
14556
14976
|
if (crontabEntries.length > 0) {
|
|
@@ -14573,8 +14993,8 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14573
14993
|
detail: "Failed to check crontab"
|
|
14574
14994
|
});
|
|
14575
14995
|
}
|
|
14576
|
-
const configPath =
|
|
14577
|
-
if (
|
|
14996
|
+
const configPath = path33.join(projectDir, CONFIG_FILE_NAME);
|
|
14997
|
+
if (fs32.existsSync(configPath)) {
|
|
14578
14998
|
checks.push({ name: "config", status: "pass", detail: "Config file exists" });
|
|
14579
14999
|
} else {
|
|
14580
15000
|
checks.push({
|
|
@@ -14583,9 +15003,9 @@ function runDoctorChecks(projectDir, config) {
|
|
|
14583
15003
|
detail: "Config file not found (using defaults)"
|
|
14584
15004
|
});
|
|
14585
15005
|
}
|
|
14586
|
-
const prdDir =
|
|
14587
|
-
if (
|
|
14588
|
-
const prds =
|
|
15006
|
+
const prdDir = path33.join(projectDir, config.prdDir);
|
|
15007
|
+
if (fs32.existsSync(prdDir)) {
|
|
15008
|
+
const prds = fs32.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
14589
15009
|
checks.push({
|
|
14590
15010
|
name: "prdDir",
|
|
14591
15011
|
status: "pass",
|
|
@@ -14628,7 +15048,7 @@ function createProjectDoctorRoutes() {
|
|
|
14628
15048
|
|
|
14629
15049
|
// ../server/dist/routes/log.routes.js
|
|
14630
15050
|
init_dist();
|
|
14631
|
-
import * as
|
|
15051
|
+
import * as path34 from "path";
|
|
14632
15052
|
import { Router as Router5 } from "express";
|
|
14633
15053
|
function createLogRoutes(deps) {
|
|
14634
15054
|
const { projectDir } = deps;
|
|
@@ -14636,7 +15056,7 @@ function createLogRoutes(deps) {
|
|
|
14636
15056
|
router.get("/:name", (req, res) => {
|
|
14637
15057
|
try {
|
|
14638
15058
|
const { name } = req.params;
|
|
14639
|
-
const validNames =
|
|
15059
|
+
const validNames = Object.keys(LOG_FILE_NAMES);
|
|
14640
15060
|
if (!validNames.includes(name)) {
|
|
14641
15061
|
res.status(400).json({
|
|
14642
15062
|
error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
|
|
@@ -14647,7 +15067,7 @@ function createLogRoutes(deps) {
|
|
|
14647
15067
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
14648
15068
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
14649
15069
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
14650
|
-
const logPath =
|
|
15070
|
+
const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
14651
15071
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
14652
15072
|
res.json({ name, lines: logLines });
|
|
14653
15073
|
} catch (error2) {
|
|
@@ -14662,7 +15082,7 @@ function createProjectLogRoutes() {
|
|
|
14662
15082
|
try {
|
|
14663
15083
|
const projectDir = req.projectDir;
|
|
14664
15084
|
const { name } = req.params;
|
|
14665
|
-
const validNames =
|
|
15085
|
+
const validNames = Object.keys(LOG_FILE_NAMES);
|
|
14666
15086
|
if (!validNames.includes(name)) {
|
|
14667
15087
|
res.status(400).json({
|
|
14668
15088
|
error: `Invalid log name. Must be one of: ${validNames.join(", ")}`
|
|
@@ -14673,7 +15093,7 @@ function createProjectLogRoutes() {
|
|
|
14673
15093
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
14674
15094
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
14675
15095
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
14676
|
-
const logPath =
|
|
15096
|
+
const logPath = path34.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
14677
15097
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
14678
15098
|
res.json({ name, lines: logLines });
|
|
14679
15099
|
} catch (error2) {
|
|
@@ -14708,7 +15128,7 @@ function createProjectPrdRoutes() {
|
|
|
14708
15128
|
|
|
14709
15129
|
// ../server/dist/routes/roadmap.routes.js
|
|
14710
15130
|
init_dist();
|
|
14711
|
-
import * as
|
|
15131
|
+
import * as path35 from "path";
|
|
14712
15132
|
import { Router as Router7 } from "express";
|
|
14713
15133
|
function createRoadmapRouteHandlers(ctx) {
|
|
14714
15134
|
const router = Router7({ mergeParams: true });
|
|
@@ -14718,7 +15138,7 @@ function createRoadmapRouteHandlers(ctx) {
|
|
|
14718
15138
|
const config = ctx.getConfig(req);
|
|
14719
15139
|
const projectDir = ctx.getProjectDir(req);
|
|
14720
15140
|
const status = getRoadmapStatus(projectDir, config);
|
|
14721
|
-
const prdDir =
|
|
15141
|
+
const prdDir = path35.join(projectDir, config.prdDir);
|
|
14722
15142
|
const state = loadRoadmapState(prdDir);
|
|
14723
15143
|
res.json({
|
|
14724
15144
|
...status,
|
|
@@ -14843,12 +15263,16 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
14843
15263
|
const auditPlan = getSchedulingPlan(projectDir, config, "audit");
|
|
14844
15264
|
const plannerPlan = getSchedulingPlan(projectDir, config, "slicer");
|
|
14845
15265
|
const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
|
|
15266
|
+
const prResolverPlan = getSchedulingPlan(projectDir, config, "pr-resolver");
|
|
15267
|
+
const mergerPlan = getSchedulingPlan(projectDir, config, "merger");
|
|
14846
15268
|
const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
|
|
14847
15269
|
const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
|
|
14848
15270
|
const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
|
|
14849
15271
|
const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
|
|
14850
15272
|
const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
|
|
14851
15273
|
const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
|
|
15274
|
+
const prResolverInstalled = installed && (config.prResolver?.enabled ?? true) && hasScheduledCommand(entries, "resolve");
|
|
15275
|
+
const mergerInstalled = installed && (config.merger?.enabled ?? false) && hasScheduledCommand(entries, "merge");
|
|
14852
15276
|
return {
|
|
14853
15277
|
executor: {
|
|
14854
15278
|
schedule: config.cronSchedule,
|
|
@@ -14898,6 +15322,22 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
14898
15322
|
manualDelayMinutes: analyticsPlan.manualDelayMinutes,
|
|
14899
15323
|
balancedDelayMinutes: analyticsPlan.balancedDelayMinutes
|
|
14900
15324
|
},
|
|
15325
|
+
prResolver: {
|
|
15326
|
+
schedule: config.prResolver?.schedule ?? "15 6,14,22 * * *",
|
|
15327
|
+
installed: prResolverInstalled,
|
|
15328
|
+
nextRun: prResolverInstalled ? addDelayToIsoString(computeNextRun(config.prResolver?.schedule ?? "15 6,14,22 * * *"), prResolverPlan.totalDelayMinutes) : null,
|
|
15329
|
+
delayMinutes: prResolverPlan.totalDelayMinutes,
|
|
15330
|
+
manualDelayMinutes: prResolverPlan.manualDelayMinutes,
|
|
15331
|
+
balancedDelayMinutes: prResolverPlan.balancedDelayMinutes
|
|
15332
|
+
},
|
|
15333
|
+
merger: {
|
|
15334
|
+
schedule: config.merger?.schedule ?? "55 */4 * * *",
|
|
15335
|
+
installed: mergerInstalled,
|
|
15336
|
+
nextRun: mergerInstalled ? addDelayToIsoString(computeNextRun(config.merger?.schedule ?? "55 */4 * * *"), mergerPlan.totalDelayMinutes) : null,
|
|
15337
|
+
delayMinutes: mergerPlan.totalDelayMinutes,
|
|
15338
|
+
manualDelayMinutes: mergerPlan.manualDelayMinutes,
|
|
15339
|
+
balancedDelayMinutes: mergerPlan.balancedDelayMinutes
|
|
15340
|
+
},
|
|
14901
15341
|
paused: !installed,
|
|
14902
15342
|
schedulingPriority: config.schedulingPriority,
|
|
14903
15343
|
entries
|
|
@@ -15052,14 +15492,14 @@ function createQueueRoutes(deps) {
|
|
|
15052
15492
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
15053
15493
|
var __dirname4 = dirname11(__filename4);
|
|
15054
15494
|
function resolveWebDistPath() {
|
|
15055
|
-
const bundled =
|
|
15056
|
-
if (
|
|
15495
|
+
const bundled = path36.join(__dirname4, "web");
|
|
15496
|
+
if (fs33.existsSync(path36.join(bundled, "index.html")))
|
|
15057
15497
|
return bundled;
|
|
15058
15498
|
let d = __dirname4;
|
|
15059
15499
|
for (let i = 0; i < 8; i++) {
|
|
15060
|
-
if (
|
|
15061
|
-
const dev =
|
|
15062
|
-
if (
|
|
15500
|
+
if (fs33.existsSync(path36.join(d, "turbo.json"))) {
|
|
15501
|
+
const dev = path36.join(d, "web/dist");
|
|
15502
|
+
if (fs33.existsSync(path36.join(dev, "index.html")))
|
|
15063
15503
|
return dev;
|
|
15064
15504
|
break;
|
|
15065
15505
|
}
|
|
@@ -15069,7 +15509,7 @@ function resolveWebDistPath() {
|
|
|
15069
15509
|
}
|
|
15070
15510
|
function setupStaticFiles(app) {
|
|
15071
15511
|
const webDistPath = resolveWebDistPath();
|
|
15072
|
-
if (
|
|
15512
|
+
if (fs33.existsSync(webDistPath)) {
|
|
15073
15513
|
app.use(express.static(webDistPath));
|
|
15074
15514
|
}
|
|
15075
15515
|
app.use((req, res, next) => {
|
|
@@ -15077,8 +15517,8 @@ function setupStaticFiles(app) {
|
|
|
15077
15517
|
next();
|
|
15078
15518
|
return;
|
|
15079
15519
|
}
|
|
15080
|
-
const indexPath =
|
|
15081
|
-
if (
|
|
15520
|
+
const indexPath = path36.resolve(webDistPath, "index.html");
|
|
15521
|
+
if (fs33.existsSync(indexPath)) {
|
|
15082
15522
|
res.sendFile(indexPath, (err) => {
|
|
15083
15523
|
if (err)
|
|
15084
15524
|
next();
|
|
@@ -15211,7 +15651,7 @@ function createGlobalApp() {
|
|
|
15211
15651
|
return app;
|
|
15212
15652
|
}
|
|
15213
15653
|
function bootContainer() {
|
|
15214
|
-
initContainer(
|
|
15654
|
+
initContainer(path36.dirname(getDbPath()));
|
|
15215
15655
|
}
|
|
15216
15656
|
function startServer(projectDir, port) {
|
|
15217
15657
|
bootContainer();
|
|
@@ -15264,8 +15704,8 @@ function isProcessRunning2(pid) {
|
|
|
15264
15704
|
}
|
|
15265
15705
|
function readPid(lockPath) {
|
|
15266
15706
|
try {
|
|
15267
|
-
if (!
|
|
15268
|
-
const raw =
|
|
15707
|
+
if (!fs34.existsSync(lockPath)) return null;
|
|
15708
|
+
const raw = fs34.readFileSync(lockPath, "utf-8").trim();
|
|
15269
15709
|
const pid = parseInt(raw, 10);
|
|
15270
15710
|
return Number.isFinite(pid) ? pid : null;
|
|
15271
15711
|
} catch {
|
|
@@ -15277,10 +15717,10 @@ function acquireServeLock(mode, port) {
|
|
|
15277
15717
|
let stalePidCleaned;
|
|
15278
15718
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
15279
15719
|
try {
|
|
15280
|
-
const fd =
|
|
15281
|
-
|
|
15720
|
+
const fd = fs34.openSync(lockPath, "wx");
|
|
15721
|
+
fs34.writeFileSync(fd, `${process.pid}
|
|
15282
15722
|
`);
|
|
15283
|
-
|
|
15723
|
+
fs34.closeSync(fd);
|
|
15284
15724
|
return { acquired: true, lockPath, stalePidCleaned };
|
|
15285
15725
|
} catch (error2) {
|
|
15286
15726
|
const err = error2;
|
|
@@ -15301,7 +15741,7 @@ function acquireServeLock(mode, port) {
|
|
|
15301
15741
|
};
|
|
15302
15742
|
}
|
|
15303
15743
|
try {
|
|
15304
|
-
|
|
15744
|
+
fs34.unlinkSync(lockPath);
|
|
15305
15745
|
if (existingPid) {
|
|
15306
15746
|
stalePidCleaned = existingPid;
|
|
15307
15747
|
}
|
|
@@ -15324,10 +15764,10 @@ function acquireServeLock(mode, port) {
|
|
|
15324
15764
|
}
|
|
15325
15765
|
function releaseServeLock(lockPath) {
|
|
15326
15766
|
try {
|
|
15327
|
-
if (!
|
|
15767
|
+
if (!fs34.existsSync(lockPath)) return;
|
|
15328
15768
|
const lockPid = readPid(lockPath);
|
|
15329
15769
|
if (lockPid !== null && lockPid !== process.pid) return;
|
|
15330
|
-
|
|
15770
|
+
fs34.unlinkSync(lockPath);
|
|
15331
15771
|
} catch {
|
|
15332
15772
|
}
|
|
15333
15773
|
}
|
|
@@ -15423,14 +15863,14 @@ function historyCommand(program2) {
|
|
|
15423
15863
|
// src/commands/update.ts
|
|
15424
15864
|
init_dist();
|
|
15425
15865
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
15426
|
-
import * as
|
|
15427
|
-
import * as
|
|
15866
|
+
import * as fs35 from "fs";
|
|
15867
|
+
import * as path37 from "path";
|
|
15428
15868
|
var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
|
|
15429
15869
|
function parseProjectDirs(projects, cwd) {
|
|
15430
15870
|
if (!projects || projects.trim().length === 0) {
|
|
15431
15871
|
return [cwd];
|
|
15432
15872
|
}
|
|
15433
|
-
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) =>
|
|
15873
|
+
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path37.resolve(cwd, entry));
|
|
15434
15874
|
return Array.from(new Set(dirs));
|
|
15435
15875
|
}
|
|
15436
15876
|
function shouldInstallGlobal(options) {
|
|
@@ -15472,7 +15912,7 @@ function updateCommand(program2) {
|
|
|
15472
15912
|
}
|
|
15473
15913
|
const nightWatchBin = resolveNightWatchBin();
|
|
15474
15914
|
for (const projectDir of projectDirs) {
|
|
15475
|
-
if (!
|
|
15915
|
+
if (!fs35.existsSync(projectDir) || !fs35.statSync(projectDir).isDirectory()) {
|
|
15476
15916
|
warn(`Skipping invalid project directory: ${projectDir}`);
|
|
15477
15917
|
continue;
|
|
15478
15918
|
}
|
|
@@ -15516,8 +15956,8 @@ function prdStateCommand(program2) {
|
|
|
15516
15956
|
|
|
15517
15957
|
// src/commands/retry.ts
|
|
15518
15958
|
init_dist();
|
|
15519
|
-
import * as
|
|
15520
|
-
import * as
|
|
15959
|
+
import * as fs36 from "fs";
|
|
15960
|
+
import * as path38 from "path";
|
|
15521
15961
|
function normalizePrdName(name) {
|
|
15522
15962
|
if (!name.endsWith(".md")) {
|
|
15523
15963
|
return `${name}.md`;
|
|
@@ -15525,26 +15965,26 @@ function normalizePrdName(name) {
|
|
|
15525
15965
|
return name;
|
|
15526
15966
|
}
|
|
15527
15967
|
function getDonePrds(doneDir) {
|
|
15528
|
-
if (!
|
|
15968
|
+
if (!fs36.existsSync(doneDir)) {
|
|
15529
15969
|
return [];
|
|
15530
15970
|
}
|
|
15531
|
-
return
|
|
15971
|
+
return fs36.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
15532
15972
|
}
|
|
15533
15973
|
function retryCommand(program2) {
|
|
15534
15974
|
program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
|
|
15535
15975
|
const projectDir = process.cwd();
|
|
15536
15976
|
const config = loadConfig(projectDir);
|
|
15537
|
-
const prdDir =
|
|
15538
|
-
const doneDir =
|
|
15977
|
+
const prdDir = path38.join(projectDir, config.prdDir);
|
|
15978
|
+
const doneDir = path38.join(prdDir, "done");
|
|
15539
15979
|
const normalizedPrdName = normalizePrdName(prdName);
|
|
15540
|
-
const pendingPath =
|
|
15541
|
-
if (
|
|
15980
|
+
const pendingPath = path38.join(prdDir, normalizedPrdName);
|
|
15981
|
+
if (fs36.existsSync(pendingPath)) {
|
|
15542
15982
|
info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
|
|
15543
15983
|
return;
|
|
15544
15984
|
}
|
|
15545
|
-
const donePath =
|
|
15546
|
-
if (
|
|
15547
|
-
|
|
15985
|
+
const donePath = path38.join(doneDir, normalizedPrdName);
|
|
15986
|
+
if (fs36.existsSync(donePath)) {
|
|
15987
|
+
fs36.renameSync(donePath, pendingPath);
|
|
15548
15988
|
success(`Moved "${normalizedPrdName}" back to pending.`);
|
|
15549
15989
|
dim(`From: ${donePath}`);
|
|
15550
15990
|
dim(`To: ${pendingPath}`);
|
|
@@ -15796,7 +16236,7 @@ function prdsCommand(program2) {
|
|
|
15796
16236
|
|
|
15797
16237
|
// src/commands/cancel.ts
|
|
15798
16238
|
init_dist();
|
|
15799
|
-
import * as
|
|
16239
|
+
import * as fs37 from "fs";
|
|
15800
16240
|
import * as readline2 from "readline";
|
|
15801
16241
|
function getLockFilePaths2(projectDir) {
|
|
15802
16242
|
const runtimeKey = projectRuntimeKey(projectDir);
|
|
@@ -15843,7 +16283,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15843
16283
|
const pid = lockStatus.pid;
|
|
15844
16284
|
if (!lockStatus.running) {
|
|
15845
16285
|
try {
|
|
15846
|
-
|
|
16286
|
+
fs37.unlinkSync(lockPath);
|
|
15847
16287
|
return {
|
|
15848
16288
|
success: true,
|
|
15849
16289
|
message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
|
|
@@ -15881,7 +16321,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15881
16321
|
await sleep2(3e3);
|
|
15882
16322
|
if (!isProcessRunning3(pid)) {
|
|
15883
16323
|
try {
|
|
15884
|
-
|
|
16324
|
+
fs37.unlinkSync(lockPath);
|
|
15885
16325
|
} catch {
|
|
15886
16326
|
}
|
|
15887
16327
|
return {
|
|
@@ -15916,7 +16356,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
15916
16356
|
await sleep2(500);
|
|
15917
16357
|
if (!isProcessRunning3(pid)) {
|
|
15918
16358
|
try {
|
|
15919
|
-
|
|
16359
|
+
fs37.unlinkSync(lockPath);
|
|
15920
16360
|
} catch {
|
|
15921
16361
|
}
|
|
15922
16362
|
return {
|
|
@@ -15977,67 +16417,69 @@ function cancelCommand(program2) {
|
|
|
15977
16417
|
|
|
15978
16418
|
// src/commands/slice.ts
|
|
15979
16419
|
init_dist();
|
|
15980
|
-
import * as
|
|
15981
|
-
import * as
|
|
16420
|
+
import * as fs38 from "fs";
|
|
16421
|
+
import * as path39 from "path";
|
|
15982
16422
|
function plannerLockPath2(projectDir) {
|
|
15983
16423
|
return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
|
|
15984
16424
|
}
|
|
15985
16425
|
function acquirePlannerLock(projectDir) {
|
|
15986
16426
|
const lockFile = plannerLockPath2(projectDir);
|
|
15987
|
-
if (
|
|
15988
|
-
const pidRaw =
|
|
16427
|
+
if (fs38.existsSync(lockFile)) {
|
|
16428
|
+
const pidRaw = fs38.readFileSync(lockFile, "utf-8").trim();
|
|
15989
16429
|
const pid = parseInt(pidRaw, 10);
|
|
15990
16430
|
if (!Number.isNaN(pid) && isProcessRunning(pid)) {
|
|
15991
16431
|
return { acquired: false, lockFile, pid };
|
|
15992
16432
|
}
|
|
15993
16433
|
try {
|
|
15994
|
-
|
|
16434
|
+
fs38.unlinkSync(lockFile);
|
|
15995
16435
|
} catch {
|
|
15996
16436
|
}
|
|
15997
16437
|
}
|
|
15998
|
-
|
|
16438
|
+
fs38.writeFileSync(lockFile, String(process.pid));
|
|
15999
16439
|
return { acquired: true, lockFile };
|
|
16000
16440
|
}
|
|
16001
16441
|
function releasePlannerLock(lockFile) {
|
|
16002
16442
|
try {
|
|
16003
|
-
if (
|
|
16004
|
-
|
|
16443
|
+
if (fs38.existsSync(lockFile)) {
|
|
16444
|
+
fs38.unlinkSync(lockFile);
|
|
16005
16445
|
}
|
|
16006
16446
|
} catch {
|
|
16007
16447
|
}
|
|
16008
16448
|
}
|
|
16009
16449
|
function resolvePlannerIssueColumn(config) {
|
|
16010
|
-
return config.roadmapScanner.issueColumn === "
|
|
16450
|
+
return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
|
|
16011
16451
|
}
|
|
16012
16452
|
function buildPlannerIssueBody(projectDir, config, result) {
|
|
16013
|
-
const relativePrdPath =
|
|
16014
|
-
const absolutePrdPath =
|
|
16453
|
+
const relativePrdPath = path39.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
|
|
16454
|
+
const absolutePrdPath = path39.join(projectDir, config.prdDir, result.file ?? "");
|
|
16015
16455
|
const sourceItem = result.item;
|
|
16016
16456
|
let prdContent;
|
|
16017
16457
|
try {
|
|
16018
|
-
prdContent =
|
|
16458
|
+
prdContent = fs38.readFileSync(absolutePrdPath, "utf-8");
|
|
16019
16459
|
} catch {
|
|
16020
16460
|
prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
|
|
16021
16461
|
}
|
|
16022
16462
|
const maxBodyChars = 6e4;
|
|
16023
16463
|
const truncated = prdContent.length > maxBodyChars;
|
|
16024
|
-
const
|
|
16464
|
+
const prdBody = truncated ? `${prdContent.slice(0, maxBodyChars)}
|
|
16025
16465
|
|
|
16026
16466
|
...[truncated]` : prdContent;
|
|
16027
|
-
const
|
|
16028
|
-
|
|
16029
|
-
`- Source
|
|
16030
|
-
sourceItem.description
|
|
16031
|
-
|
|
16467
|
+
const metaLines = [`- PRD file: \`${relativePrdPath}\``];
|
|
16468
|
+
if (sourceItem) {
|
|
16469
|
+
metaLines.push(`- Source section: ${sourceItem.section}`);
|
|
16470
|
+
if (sourceItem.description) {
|
|
16471
|
+
metaLines.push(`- Source summary: ${sourceItem.description}`);
|
|
16472
|
+
}
|
|
16473
|
+
}
|
|
16032
16474
|
return [
|
|
16033
|
-
|
|
16475
|
+
prdBody,
|
|
16034
16476
|
"",
|
|
16035
|
-
|
|
16036
|
-
|
|
16477
|
+
"<details>",
|
|
16478
|
+
"<summary>Source metadata</summary>",
|
|
16037
16479
|
"",
|
|
16038
|
-
|
|
16480
|
+
...metaLines,
|
|
16039
16481
|
"",
|
|
16040
|
-
|
|
16482
|
+
"</details>"
|
|
16041
16483
|
].join("\n");
|
|
16042
16484
|
}
|
|
16043
16485
|
async function createPlannerIssue(projectDir, config, result) {
|
|
@@ -16052,9 +16494,11 @@ async function createPlannerIssue(projectDir, config, result) {
|
|
|
16052
16494
|
if (!board) {
|
|
16053
16495
|
return { created: false, skippedReason: "board-not-configured" };
|
|
16054
16496
|
}
|
|
16497
|
+
const issueTitle = `PRD: ${result.item.title}`;
|
|
16498
|
+
const normalizeTitle = (t) => t.replace(/^PRD:\s*/i, "").trim().toLowerCase();
|
|
16055
16499
|
const existingIssues = await provider.getAllIssues();
|
|
16056
16500
|
const existing = existingIssues.find(
|
|
16057
|
-
(issue2) => issue2.title
|
|
16501
|
+
(issue2) => normalizeTitle(issue2.title) === normalizeTitle(result.item.title)
|
|
16058
16502
|
);
|
|
16059
16503
|
if (existing) {
|
|
16060
16504
|
return {
|
|
@@ -16065,7 +16509,7 @@ async function createPlannerIssue(projectDir, config, result) {
|
|
|
16065
16509
|
};
|
|
16066
16510
|
}
|
|
16067
16511
|
const issue = await provider.createIssue({
|
|
16068
|
-
title:
|
|
16512
|
+
title: issueTitle,
|
|
16069
16513
|
body: buildPlannerIssueBody(projectDir, config, result),
|
|
16070
16514
|
column: resolvePlannerIssueColumn(config)
|
|
16071
16515
|
});
|
|
@@ -16218,7 +16662,7 @@ function sliceCommand(program2) {
|
|
|
16218
16662
|
if (!options.dryRun && result.sliced) {
|
|
16219
16663
|
await sendNotifications(config, {
|
|
16220
16664
|
event: "run_succeeded",
|
|
16221
|
-
projectName:
|
|
16665
|
+
projectName: path39.basename(projectDir),
|
|
16222
16666
|
exitCode,
|
|
16223
16667
|
provider: config.provider,
|
|
16224
16668
|
prTitle: result.item?.title
|
|
@@ -16226,7 +16670,7 @@ function sliceCommand(program2) {
|
|
|
16226
16670
|
} else if (!options.dryRun && !nothingPending) {
|
|
16227
16671
|
await sendNotifications(config, {
|
|
16228
16672
|
event: "run_failed",
|
|
16229
|
-
projectName:
|
|
16673
|
+
projectName: path39.basename(projectDir),
|
|
16230
16674
|
exitCode,
|
|
16231
16675
|
provider: config.provider
|
|
16232
16676
|
});
|
|
@@ -16243,20 +16687,20 @@ function sliceCommand(program2) {
|
|
|
16243
16687
|
// src/commands/state.ts
|
|
16244
16688
|
init_dist();
|
|
16245
16689
|
import * as os9 from "os";
|
|
16246
|
-
import * as
|
|
16690
|
+
import * as path40 from "path";
|
|
16247
16691
|
import chalk5 from "chalk";
|
|
16248
16692
|
import { Command } from "commander";
|
|
16249
16693
|
function createStateCommand() {
|
|
16250
16694
|
const state = new Command("state");
|
|
16251
16695
|
state.description("Manage Night Watch state");
|
|
16252
16696
|
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 ||
|
|
16697
|
+
const nightWatchHome = process.env.NIGHT_WATCH_HOME || path40.join(os9.homedir(), GLOBAL_CONFIG_DIR);
|
|
16254
16698
|
if (opts.dryRun) {
|
|
16255
16699
|
console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
|
|
16256
16700
|
console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
|
|
16257
|
-
console.log(` ${
|
|
16258
|
-
console.log(` ${
|
|
16259
|
-
console.log(` ${
|
|
16701
|
+
console.log(` ${path40.join(nightWatchHome, "projects.json")}`);
|
|
16702
|
+
console.log(` ${path40.join(nightWatchHome, "history.json")}`);
|
|
16703
|
+
console.log(` ${path40.join(nightWatchHome, "prd-states.json")}`);
|
|
16260
16704
|
console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
|
|
16261
16705
|
console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
|
|
16262
16706
|
return;
|
|
@@ -16294,8 +16738,8 @@ function createStateCommand() {
|
|
|
16294
16738
|
init_dist();
|
|
16295
16739
|
init_dist();
|
|
16296
16740
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
16297
|
-
import * as
|
|
16298
|
-
import * as
|
|
16741
|
+
import * as fs39 from "fs";
|
|
16742
|
+
import * as path41 from "path";
|
|
16299
16743
|
import * as readline3 from "readline";
|
|
16300
16744
|
import chalk6 from "chalk";
|
|
16301
16745
|
async function run(fn) {
|
|
@@ -16317,7 +16761,7 @@ function getProvider(config, cwd) {
|
|
|
16317
16761
|
return createBoardProvider(bp, cwd);
|
|
16318
16762
|
}
|
|
16319
16763
|
function defaultBoardTitle(cwd) {
|
|
16320
|
-
return `${
|
|
16764
|
+
return `${path41.basename(cwd)} Night Watch`;
|
|
16321
16765
|
}
|
|
16322
16766
|
async function ensureBoardConfigured(config, cwd, provider, options) {
|
|
16323
16767
|
if (config.boardProvider?.projectNumber) {
|
|
@@ -16516,11 +16960,11 @@ function boardCommand(program2) {
|
|
|
16516
16960
|
let body = options.body ?? "";
|
|
16517
16961
|
if (options.bodyFile) {
|
|
16518
16962
|
const filePath = options.bodyFile;
|
|
16519
|
-
if (!
|
|
16963
|
+
if (!fs39.existsSync(filePath)) {
|
|
16520
16964
|
console.error(`File not found: ${filePath}`);
|
|
16521
16965
|
process.exit(1);
|
|
16522
16966
|
}
|
|
16523
|
-
body =
|
|
16967
|
+
body = fs39.readFileSync(filePath, "utf-8");
|
|
16524
16968
|
}
|
|
16525
16969
|
const labels = [];
|
|
16526
16970
|
if (options.label) {
|
|
@@ -16761,12 +17205,12 @@ function boardCommand(program2) {
|
|
|
16761
17205
|
const config = loadConfig(cwd);
|
|
16762
17206
|
const provider = getProvider(config, cwd);
|
|
16763
17207
|
await ensureBoardConfigured(config, cwd, provider);
|
|
16764
|
-
const roadmapPath = options.roadmap ??
|
|
16765
|
-
if (!
|
|
17208
|
+
const roadmapPath = options.roadmap ?? path41.join(cwd, "ROADMAP.md");
|
|
17209
|
+
if (!fs39.existsSync(roadmapPath)) {
|
|
16766
17210
|
console.error(`Roadmap file not found: ${roadmapPath}`);
|
|
16767
17211
|
process.exit(1);
|
|
16768
17212
|
}
|
|
16769
|
-
const roadmapContent =
|
|
17213
|
+
const roadmapContent = fs39.readFileSync(roadmapPath, "utf-8");
|
|
16770
17214
|
const items = parseRoadmap(roadmapContent);
|
|
16771
17215
|
const uncheckedItems = getUncheckedItems(items);
|
|
16772
17216
|
if (uncheckedItems.length === 0) {
|
|
@@ -16890,11 +17334,11 @@ function boardCommand(program2) {
|
|
|
16890
17334
|
// src/commands/queue.ts
|
|
16891
17335
|
init_dist();
|
|
16892
17336
|
init_dist();
|
|
16893
|
-
import * as
|
|
17337
|
+
import * as path42 from "path";
|
|
16894
17338
|
import { spawn as spawn7 } from "child_process";
|
|
16895
17339
|
import chalk7 from "chalk";
|
|
16896
17340
|
import { Command as Command2 } from "commander";
|
|
16897
|
-
var
|
|
17341
|
+
var logger6 = createLogger("queue");
|
|
16898
17342
|
var VALID_JOB_TYPES2 = ["executor", "reviewer", "qa", "audit", "slicer", "planner"];
|
|
16899
17343
|
function formatTimestamp(unixTs) {
|
|
16900
17344
|
if (unixTs === null) return "-";
|
|
@@ -17010,7 +17454,7 @@ function createQueueCommand() {
|
|
|
17010
17454
|
process.exit(1);
|
|
17011
17455
|
}
|
|
17012
17456
|
}
|
|
17013
|
-
const projectName =
|
|
17457
|
+
const projectName = path42.basename(projectDir);
|
|
17014
17458
|
const queueConfig = loadConfig(projectDir).queue;
|
|
17015
17459
|
const id = enqueueJob(
|
|
17016
17460
|
projectDir,
|
|
@@ -17043,13 +17487,13 @@ function createQueueCommand() {
|
|
|
17043
17487
|
const configDir = _opts.projectDir ?? process.cwd();
|
|
17044
17488
|
const entry = dispatchNextJob(loadConfig(configDir).queue);
|
|
17045
17489
|
if (!entry) {
|
|
17046
|
-
|
|
17490
|
+
logger6.info("No pending jobs to dispatch");
|
|
17047
17491
|
return;
|
|
17048
17492
|
}
|
|
17049
|
-
|
|
17493
|
+
logger6.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
|
|
17050
17494
|
const scriptName = getScriptNameForJobType(entry.jobType);
|
|
17051
17495
|
if (!scriptName) {
|
|
17052
|
-
|
|
17496
|
+
logger6.error(`Unknown job type: ${entry.jobType}`);
|
|
17053
17497
|
return;
|
|
17054
17498
|
}
|
|
17055
17499
|
let projectEnv;
|
|
@@ -17068,7 +17512,7 @@ function createQueueCommand() {
|
|
|
17068
17512
|
NW_QUEUE_ENTRY_ID: String(entry.id)
|
|
17069
17513
|
};
|
|
17070
17514
|
const scriptPath = getScriptPath(scriptName);
|
|
17071
|
-
|
|
17515
|
+
logger6.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
|
|
17072
17516
|
try {
|
|
17073
17517
|
const child = spawn7("bash", [scriptPath, entry.projectPath], {
|
|
17074
17518
|
detached: true,
|
|
@@ -17077,11 +17521,11 @@ function createQueueCommand() {
|
|
|
17077
17521
|
cwd: entry.projectPath
|
|
17078
17522
|
});
|
|
17079
17523
|
child.unref();
|
|
17080
|
-
|
|
17524
|
+
logger6.info(`Spawned PID: ${child.pid}`);
|
|
17081
17525
|
markJobRunning(entry.id);
|
|
17082
17526
|
} catch (error2) {
|
|
17083
17527
|
updateJobStatus(entry.id, "pending");
|
|
17084
|
-
|
|
17528
|
+
logger6.error(
|
|
17085
17529
|
`Failed to dispatch ${entry.jobType} for ${entry.projectName}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
17086
17530
|
);
|
|
17087
17531
|
process.exit(1);
|
|
@@ -17096,7 +17540,7 @@ function createQueueCommand() {
|
|
|
17096
17540
|
process.exit(1);
|
|
17097
17541
|
}
|
|
17098
17542
|
const queueConfig = loadConfig(projectDir).queue;
|
|
17099
|
-
const projectName =
|
|
17543
|
+
const projectName = path42.basename(projectDir);
|
|
17100
17544
|
const result = claimJobSlot(
|
|
17101
17545
|
projectDir,
|
|
17102
17546
|
projectName,
|
|
@@ -17233,7 +17677,7 @@ function notifyCommand(program2) {
|
|
|
17233
17677
|
|
|
17234
17678
|
// src/commands/summary.ts
|
|
17235
17679
|
init_dist();
|
|
17236
|
-
import
|
|
17680
|
+
import path43 from "path";
|
|
17237
17681
|
import chalk8 from "chalk";
|
|
17238
17682
|
function formatDuration2(seconds) {
|
|
17239
17683
|
if (seconds === null) return "-";
|
|
@@ -17263,7 +17707,7 @@ function formatJobStatus(status) {
|
|
|
17263
17707
|
return chalk8.dim(status);
|
|
17264
17708
|
}
|
|
17265
17709
|
function getProjectName2(projectPath) {
|
|
17266
|
-
return
|
|
17710
|
+
return path43.basename(projectPath) || projectPath;
|
|
17267
17711
|
}
|
|
17268
17712
|
function formatProvider(providerKey) {
|
|
17269
17713
|
return providerKey.split(":")[0] || providerKey;
|
|
@@ -17382,7 +17826,7 @@ function summaryCommand(program2) {
|
|
|
17382
17826
|
// src/commands/resolve.ts
|
|
17383
17827
|
init_dist();
|
|
17384
17828
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
17385
|
-
import * as
|
|
17829
|
+
import * as path44 from "path";
|
|
17386
17830
|
function buildEnvVars6(config, options) {
|
|
17387
17831
|
const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
|
|
17388
17832
|
env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
|
|
@@ -17512,7 +17956,7 @@ ${stderr}`);
|
|
|
17512
17956
|
const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
|
|
17513
17957
|
await sendNotifications(config, {
|
|
17514
17958
|
event: notificationEvent,
|
|
17515
|
-
projectName:
|
|
17959
|
+
projectName: path44.basename(projectDir),
|
|
17516
17960
|
exitCode,
|
|
17517
17961
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
17518
17962
|
});
|
|
@@ -17525,19 +17969,141 @@ ${stderr}`);
|
|
|
17525
17969
|
});
|
|
17526
17970
|
}
|
|
17527
17971
|
|
|
17972
|
+
// src/commands/merge.ts
|
|
17973
|
+
init_dist();
|
|
17974
|
+
import * as path45 from "path";
|
|
17975
|
+
function buildEnvVars7(config, options) {
|
|
17976
|
+
const env = buildBaseEnvVars(config, "merger", options.dryRun);
|
|
17977
|
+
env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
|
|
17978
|
+
env.NW_MERGER_MERGE_METHOD = config.merger.mergeMethod;
|
|
17979
|
+
env.NW_MERGER_MIN_REVIEW_SCORE = String(config.merger.minReviewScore);
|
|
17980
|
+
env.NW_MERGER_BRANCH_PATTERNS = (config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns : config.branchPatterns).join(",");
|
|
17981
|
+
env.NW_MERGER_REBASE_BEFORE_MERGE = config.merger.rebaseBeforeMerge ? "1" : "0";
|
|
17982
|
+
env.NW_MERGER_MAX_PRS_PER_RUN = String(config.merger.maxPrsPerRun);
|
|
17983
|
+
return env;
|
|
17984
|
+
}
|
|
17985
|
+
function applyCliOverrides6(config, options) {
|
|
17986
|
+
const overridden = { ...config, merger: { ...config.merger } };
|
|
17987
|
+
if (options.timeout) {
|
|
17988
|
+
const timeout = parseInt(options.timeout, 10);
|
|
17989
|
+
if (!isNaN(timeout)) {
|
|
17990
|
+
overridden.merger.maxRuntime = timeout;
|
|
17991
|
+
}
|
|
17992
|
+
}
|
|
17993
|
+
if (options.provider) {
|
|
17994
|
+
overridden._cliProviderOverride = options.provider;
|
|
17995
|
+
}
|
|
17996
|
+
return overridden;
|
|
17997
|
+
}
|
|
17998
|
+
function resolveMergeNotificationEvent(exitCode, mergedCount, failedCount) {
|
|
17999
|
+
if (exitCode === 0 && mergedCount > 0) {
|
|
18000
|
+
return "merge_completed";
|
|
18001
|
+
}
|
|
18002
|
+
if (exitCode !== 0 || failedCount > 0) {
|
|
18003
|
+
return "merge_failed";
|
|
18004
|
+
}
|
|
18005
|
+
return null;
|
|
18006
|
+
}
|
|
18007
|
+
function printDryRun(config, envVars, scriptPath, projectDir) {
|
|
18008
|
+
header("Dry Run: Merge Orchestrator");
|
|
18009
|
+
const mergerProvider = resolveJobProvider(config, "merger");
|
|
18010
|
+
header("Configuration");
|
|
18011
|
+
const configTable = createTable({ head: ["Setting", "Value"] });
|
|
18012
|
+
configTable.push(["Provider", mergerProvider]);
|
|
18013
|
+
configTable.push([
|
|
18014
|
+
"Max Runtime",
|
|
18015
|
+
`${config.merger.maxRuntime}s (${Math.floor(config.merger.maxRuntime / 60)}min)`
|
|
18016
|
+
]);
|
|
18017
|
+
configTable.push(["Merge Method", config.merger.mergeMethod]);
|
|
18018
|
+
configTable.push(["Min Review Score", `${config.merger.minReviewScore}/100`]);
|
|
18019
|
+
configTable.push([
|
|
18020
|
+
"Branch Patterns",
|
|
18021
|
+
config.merger.branchPatterns.length > 0 ? config.merger.branchPatterns.join(", ") : "(top-level)"
|
|
18022
|
+
]);
|
|
18023
|
+
configTable.push(["Rebase Before Merge", config.merger.rebaseBeforeMerge ? "Yes" : "No"]);
|
|
18024
|
+
configTable.push([
|
|
18025
|
+
"Max PRs Per Run",
|
|
18026
|
+
config.merger.maxPrsPerRun === 0 ? "Unlimited" : String(config.merger.maxPrsPerRun)
|
|
18027
|
+
]);
|
|
18028
|
+
console.log(configTable.toString());
|
|
18029
|
+
header("Environment Variables");
|
|
18030
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
18031
|
+
dim(` ${key}=${value}`);
|
|
18032
|
+
}
|
|
18033
|
+
header("Command");
|
|
18034
|
+
dim(` bash ${scriptPath} ${projectDir}`);
|
|
18035
|
+
console.log();
|
|
18036
|
+
}
|
|
18037
|
+
function mergeCommand(program2) {
|
|
18038
|
+
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) => {
|
|
18039
|
+
const projectDir = process.cwd();
|
|
18040
|
+
let config = loadConfig(projectDir);
|
|
18041
|
+
config = applyCliOverrides6(config, options);
|
|
18042
|
+
if (!config.merger.enabled && !options.dryRun) {
|
|
18043
|
+
info("Merge orchestrator is disabled in config; skipping.");
|
|
18044
|
+
process.exit(0);
|
|
18045
|
+
}
|
|
18046
|
+
const envVars = buildEnvVars7(config, options);
|
|
18047
|
+
const scriptPath = getScriptPath("night-watch-merger-cron.sh");
|
|
18048
|
+
if (options.dryRun) {
|
|
18049
|
+
printDryRun(config, envVars, scriptPath, projectDir);
|
|
18050
|
+
process.exit(0);
|
|
18051
|
+
}
|
|
18052
|
+
const spinner = createSpinner("Running merge orchestrator...");
|
|
18053
|
+
spinner.start();
|
|
18054
|
+
try {
|
|
18055
|
+
await maybeApplyCronSchedulingDelay(config, "merger", projectDir);
|
|
18056
|
+
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
18057
|
+
scriptPath,
|
|
18058
|
+
[projectDir],
|
|
18059
|
+
envVars
|
|
18060
|
+
);
|
|
18061
|
+
const scriptResult = parseScriptResult(`${stdout}
|
|
18062
|
+
${stderr}`);
|
|
18063
|
+
if (exitCode === 0) {
|
|
18064
|
+
if (scriptResult?.status === "queued") {
|
|
18065
|
+
spinner.succeed("Merge orchestrator queued \u2014 another job is currently running");
|
|
18066
|
+
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
18067
|
+
spinner.succeed("Merge orchestrator completed (no eligible PRs)");
|
|
18068
|
+
} else {
|
|
18069
|
+
spinner.succeed("Merge orchestrator completed successfully");
|
|
18070
|
+
}
|
|
18071
|
+
} else {
|
|
18072
|
+
spinner.fail(`Merge orchestrator exited with code ${exitCode}`);
|
|
18073
|
+
}
|
|
18074
|
+
const mergedCount = parseInt(scriptResult?.data?.merged ?? "0", 10);
|
|
18075
|
+
const failedCount = parseInt(scriptResult?.data?.failed ?? "0", 10);
|
|
18076
|
+
const notificationEvent = resolveMergeNotificationEvent(exitCode, mergedCount, failedCount);
|
|
18077
|
+
if (notificationEvent) {
|
|
18078
|
+
await sendNotifications(config, {
|
|
18079
|
+
event: notificationEvent,
|
|
18080
|
+
projectName: path45.basename(projectDir),
|
|
18081
|
+
exitCode,
|
|
18082
|
+
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
18083
|
+
});
|
|
18084
|
+
}
|
|
18085
|
+
process.exit(exitCode);
|
|
18086
|
+
} catch (err) {
|
|
18087
|
+
spinner.fail("Failed to execute merge command");
|
|
18088
|
+
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
18089
|
+
process.exit(1);
|
|
18090
|
+
}
|
|
18091
|
+
});
|
|
18092
|
+
}
|
|
18093
|
+
|
|
17528
18094
|
// src/cli.ts
|
|
17529
18095
|
var __filename5 = fileURLToPath6(import.meta.url);
|
|
17530
18096
|
var __dirname5 = dirname12(__filename5);
|
|
17531
18097
|
function findPackageRoot(dir) {
|
|
17532
18098
|
let d = dir;
|
|
17533
18099
|
for (let i = 0; i < 5; i++) {
|
|
17534
|
-
if (
|
|
18100
|
+
if (existsSync31(join37(d, "package.json"))) return d;
|
|
17535
18101
|
d = dirname12(d);
|
|
17536
18102
|
}
|
|
17537
18103
|
return dir;
|
|
17538
18104
|
}
|
|
17539
18105
|
var packageRoot = findPackageRoot(__dirname5);
|
|
17540
|
-
var packageJson = JSON.parse(
|
|
18106
|
+
var packageJson = JSON.parse(readFileSync20(join37(packageRoot, "package.json"), "utf-8"));
|
|
17541
18107
|
var program = new Command3();
|
|
17542
18108
|
program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
|
|
17543
18109
|
initCommand(program);
|
|
@@ -17568,4 +18134,5 @@ queueCommand(program);
|
|
|
17568
18134
|
notifyCommand(program);
|
|
17569
18135
|
summaryCommand(program);
|
|
17570
18136
|
resolveCommand(program);
|
|
18137
|
+
mergeCommand(program);
|
|
17571
18138
|
program.parse();
|