@jonit-dev/night-watch-cli 1.8.12-beta.0 → 1.8.12-beta.3
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.d.ts +3 -0
- package/dist/cli.js +1812 -631
- package/dist/cli.js.map +1 -0
- package/dist/commands/agent.d.ts +12 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +307 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/analytics.d.ts +14 -0
- package/dist/commands/analytics.js +69 -0
- package/dist/commands/analytics.js.map +1 -0
- package/dist/commands/audit.d.ts +19 -0
- package/dist/commands/audit.js +144 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/board.d.ts +9 -0
- package/dist/commands/board.js +702 -0
- package/dist/commands/board.js.map +1 -0
- package/dist/commands/cancel.d.ts +46 -0
- package/dist/commands/cancel.js +239 -0
- package/dist/commands/cancel.js.map +1 -0
- package/dist/commands/cron.d.ts +8 -0
- package/dist/commands/cron.js +134 -0
- package/dist/commands/cron.js.map +1 -0
- package/dist/commands/dashboard/tab-actions.d.ts +10 -0
- package/dist/commands/dashboard/tab-actions.js +247 -0
- package/dist/commands/dashboard/tab-actions.js.map +1 -0
- package/dist/commands/dashboard/tab-config.d.ts +21 -0
- package/dist/commands/dashboard/tab-config.js +874 -0
- package/dist/commands/dashboard/tab-config.js.map +1 -0
- package/dist/commands/dashboard/tab-logs.d.ts +10 -0
- package/dist/commands/dashboard/tab-logs.js +202 -0
- package/dist/commands/dashboard/tab-logs.js.map +1 -0
- package/dist/commands/dashboard/tab-schedules.d.ts +21 -0
- package/dist/commands/dashboard/tab-schedules.js +320 -0
- package/dist/commands/dashboard/tab-schedules.js.map +1 -0
- package/dist/commands/dashboard/tab-status.d.ts +32 -0
- package/dist/commands/dashboard/tab-status.js +424 -0
- package/dist/commands/dashboard/tab-status.js.map +1 -0
- package/dist/commands/dashboard/types.d.ts +42 -0
- package/dist/commands/dashboard/types.js +5 -0
- package/dist/commands/dashboard/types.js.map +1 -0
- package/dist/commands/dashboard.d.ts +11 -0
- package/dist/commands/dashboard.js +242 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/doctor.d.ts +16 -0
- package/dist/commands/doctor.js +195 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/history.d.ts +7 -0
- package/dist/commands/history.js +49 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/init.d.ts +45 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.d.ts +65 -0
- package/dist/commands/install.js +405 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/logs.d.ts +15 -0
- package/dist/commands/logs.js +155 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/merge.d.ts +26 -0
- package/dist/commands/merge.js +159 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/notify.d.ts +7 -0
- package/dist/commands/notify.js +43 -0
- package/dist/commands/notify.js.map +1 -0
- package/dist/commands/plan.d.ts +19 -0
- package/dist/commands/plan.js +88 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/prd-state.d.ts +12 -0
- package/dist/commands/prd-state.js +47 -0
- package/dist/commands/prd-state.js.map +1 -0
- package/dist/commands/prd.d.ts +18 -0
- package/dist/commands/prd.js +363 -0
- package/dist/commands/prd.js.map +1 -0
- package/dist/commands/prds.d.ts +13 -0
- package/dist/commands/prds.js +194 -0
- package/dist/commands/prds.js.map +1 -0
- package/dist/commands/prs.d.ts +14 -0
- package/dist/commands/prs.js +104 -0
- package/dist/commands/prs.js.map +1 -0
- package/dist/commands/qa.d.ts +34 -0
- package/dist/commands/qa.js +214 -0
- package/dist/commands/qa.js.map +1 -0
- package/dist/commands/queue.d.ts +8 -0
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/queue.js +398 -0
- package/dist/commands/queue.js.map +1 -0
- package/dist/commands/resolve.d.ts +26 -0
- package/dist/commands/resolve.js +186 -0
- package/dist/commands/resolve.js.map +1 -0
- package/dist/commands/retry.d.ts +9 -0
- package/dist/commands/retry.js +71 -0
- package/dist/commands/retry.js.map +1 -0
- package/dist/commands/review.d.ts +77 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +1 -74
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts +73 -0
- package/dist/commands/serve.d.ts +19 -0
- package/dist/commands/serve.js +142 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/shared/env-builder.d.ts +49 -0
- package/dist/commands/shared/env-builder.d.ts.map +1 -1
- package/dist/commands/shared/env-builder.js +152 -0
- package/dist/commands/shared/env-builder.js.map +1 -0
- package/dist/commands/slice.d.ts +35 -0
- package/dist/commands/slice.js +316 -0
- package/dist/commands/slice.js.map +1 -0
- package/dist/commands/state.d.ts +8 -0
- package/dist/commands/state.js +54 -0
- package/dist/commands/state.js.map +1 -0
- package/dist/commands/status.d.ts +14 -0
- package/dist/commands/status.js +297 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/summary.d.ts +14 -0
- package/dist/commands/summary.js +193 -0
- package/dist/commands/summary.js.map +1 -0
- package/dist/commands/uninstall.d.ts +25 -0
- package/dist/commands/uninstall.js +134 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +22 -0
- package/dist/commands/update.js +90 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/scripts/night-watch-audit-cron.sh +1 -0
- package/dist/scripts/night-watch-cron.sh +1 -0
- package/dist/scripts/night-watch-helpers.sh +22 -1
- package/dist/scripts/night-watch-merger-cron.sh +32 -3
- package/dist/scripts/night-watch-plan-cron.sh +2 -0
- package/dist/scripts/night-watch-pr-resolver-cron.sh +6 -0
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +18 -5
- package/dist/scripts/night-watch-qa-cron.sh +8 -1
- package/dist/scripts/night-watch-slicer-cron.sh +3 -0
- package/dist/templates/night-watch.config.json +21 -20
- package/dist/web/assets/index-C-xpWpS8.css +1 -0
- package/dist/web/assets/index-CEYe-290.js +412 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -64,13 +64,13 @@ function getLockSuffix(jobId) {
|
|
|
64
64
|
}
|
|
65
65
|
function normalizeJobConfig(raw, jobDef) {
|
|
66
66
|
const readBoolean = (v) => typeof v === "boolean" ? v : void 0;
|
|
67
|
-
const
|
|
67
|
+
const readString2 = (v) => typeof v === "string" ? v : void 0;
|
|
68
68
|
const readNumber = (v) => typeof v === "number" && !Number.isNaN(v) ? v : void 0;
|
|
69
|
-
const
|
|
69
|
+
const readStringArray2 = (v) => Array.isArray(v) && v.every((s) => typeof s === "string") ? v : void 0;
|
|
70
70
|
const defaults = jobDef.defaultConfig;
|
|
71
71
|
const result = {
|
|
72
72
|
enabled: readBoolean(raw.enabled) ?? defaults.enabled,
|
|
73
|
-
schedule:
|
|
73
|
+
schedule: readString2(raw.schedule) ?? defaults.schedule,
|
|
74
74
|
maxRuntime: readNumber(raw.maxRuntime) ?? defaults.maxRuntime
|
|
75
75
|
};
|
|
76
76
|
for (const field of jobDef.extraFields ?? []) {
|
|
@@ -79,16 +79,16 @@ function normalizeJobConfig(raw, jobDef) {
|
|
|
79
79
|
result[field.name] = readBoolean(raw[field.name]) ?? field.defaultValue;
|
|
80
80
|
break;
|
|
81
81
|
case "string":
|
|
82
|
-
result[field.name] =
|
|
82
|
+
result[field.name] = readString2(raw[field.name]) ?? field.defaultValue;
|
|
83
83
|
break;
|
|
84
84
|
case "number":
|
|
85
85
|
result[field.name] = readNumber(raw[field.name]) ?? field.defaultValue;
|
|
86
86
|
break;
|
|
87
87
|
case "string[]":
|
|
88
|
-
result[field.name] =
|
|
88
|
+
result[field.name] = readStringArray2(raw[field.name]) ?? field.defaultValue;
|
|
89
89
|
break;
|
|
90
90
|
case "enum": {
|
|
91
|
-
const val =
|
|
91
|
+
const val = readString2(raw[field.name]);
|
|
92
92
|
result[field.name] = val && field.enumValues?.includes(val) ? val : field.defaultValue;
|
|
93
93
|
break;
|
|
94
94
|
}
|
|
@@ -390,7 +390,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
|
|
|
390
390
|
return `claude-proxy:${baseUrl}`;
|
|
391
391
|
}
|
|
392
392
|
}
|
|
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;
|
|
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, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS, DEFAULT_WEBHOOK_TRIGGERS, 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;
|
|
394
394
|
var init_constants = __esm({
|
|
395
395
|
"../core/dist/constants.js"() {
|
|
396
396
|
"use strict";
|
|
@@ -537,6 +537,20 @@ If no issues are warranted, output an empty array: []`;
|
|
|
537
537
|
VALID_JOB_TYPES = getValidJobTypes();
|
|
538
538
|
DEFAULT_JOB_PROVIDERS = {};
|
|
539
539
|
DEFAULT_PROVIDER_SCHEDULE_OVERRIDES = [];
|
|
540
|
+
DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV = "NIGHT_WATCH_WEBHOOK_SECRET";
|
|
541
|
+
DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS = 300;
|
|
542
|
+
DEFAULT_WEBHOOK_TRIGGERS = {
|
|
543
|
+
enabled: false,
|
|
544
|
+
secretEnv: DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV,
|
|
545
|
+
allowedJobIds: getValidJobTypes(),
|
|
546
|
+
requireTimestamp: false,
|
|
547
|
+
maxSkewSeconds: DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS,
|
|
548
|
+
github: {
|
|
549
|
+
enabled: false,
|
|
550
|
+
events: [],
|
|
551
|
+
rules: []
|
|
552
|
+
}
|
|
553
|
+
};
|
|
540
554
|
BUILT_IN_PRESETS = {
|
|
541
555
|
claude: {
|
|
542
556
|
name: "Claude",
|
|
@@ -645,26 +659,26 @@ function validateProvider(value) {
|
|
|
645
659
|
function normalizeConfig(rawConfig) {
|
|
646
660
|
const normalized = {};
|
|
647
661
|
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(rawConfig, key);
|
|
648
|
-
const
|
|
662
|
+
const readString2 = (value) => typeof value === "string" ? value : void 0;
|
|
649
663
|
const readNumber = (value) => typeof value === "number" && !Number.isNaN(value) ? value : void 0;
|
|
650
664
|
const readBoolean = (value) => typeof value === "boolean" ? value : void 0;
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
const cron =
|
|
654
|
-
const review =
|
|
655
|
-
const logging =
|
|
656
|
-
normalized.defaultBranch =
|
|
657
|
-
normalized.prdDir =
|
|
665
|
+
const readStringArray2 = (value) => Array.isArray(value) && value.every((v) => typeof v === "string") ? value : void 0;
|
|
666
|
+
const readObject2 = (value) => value !== null && typeof value === "object" ? value : void 0;
|
|
667
|
+
const cron = readObject2(rawConfig.cron);
|
|
668
|
+
const review = readObject2(rawConfig.review);
|
|
669
|
+
const logging = readObject2(rawConfig.logging);
|
|
670
|
+
normalized.defaultBranch = readString2(rawConfig.defaultBranch);
|
|
671
|
+
normalized.prdDir = readString2(rawConfig.prdDir) ?? readString2(rawConfig.prdDirectory);
|
|
658
672
|
normalized.maxRuntime = readNumber(rawConfig.maxRuntime);
|
|
659
673
|
normalized.sessionMaxRuntime = readNumber(rawConfig.sessionMaxRuntime);
|
|
660
674
|
normalized.reviewerMaxRuntime = readNumber(rawConfig.reviewerMaxRuntime);
|
|
661
|
-
normalized.branchPrefix =
|
|
662
|
-
normalized.branchPatterns =
|
|
675
|
+
normalized.branchPrefix = readString2(rawConfig.branchPrefix);
|
|
676
|
+
normalized.branchPatterns = readStringArray2(rawConfig.branchPatterns) ?? readStringArray2(review?.branchPatterns);
|
|
663
677
|
normalized.minReviewScore = readNumber(rawConfig.minReviewScore) ?? readNumber(review?.minScore);
|
|
664
678
|
normalized.maxLogSize = readNumber(rawConfig.maxLogSize) ?? readNumber(logging?.maxLogSize);
|
|
665
679
|
normalized.gitPushNoVerify = readBoolean(rawConfig.gitPushNoVerify);
|
|
666
|
-
normalized.cronSchedule =
|
|
667
|
-
normalized.reviewerSchedule =
|
|
680
|
+
normalized.cronSchedule = readString2(rawConfig.cronSchedule) ?? readString2(cron?.executorSchedule);
|
|
681
|
+
normalized.reviewerSchedule = readString2(rawConfig.reviewerSchedule) ?? readString2(cron?.reviewerSchedule);
|
|
668
682
|
const rawScheduleBundleId = rawConfig.scheduleBundleId;
|
|
669
683
|
if (typeof rawScheduleBundleId === "string") {
|
|
670
684
|
const trimmed = rawScheduleBundleId.trim();
|
|
@@ -681,11 +695,11 @@ function normalizeConfig(rawConfig) {
|
|
|
681
695
|
normalized.provider = validateProvider(String(rawConfig.provider ?? "")) ?? void 0;
|
|
682
696
|
normalized.executorEnabled = readBoolean(rawConfig.executorEnabled);
|
|
683
697
|
normalized.reviewerEnabled = readBoolean(rawConfig.reviewerEnabled);
|
|
684
|
-
const providerLabelVal =
|
|
698
|
+
const providerLabelVal = readString2(rawConfig.providerLabel);
|
|
685
699
|
if (providerLabelVal) {
|
|
686
700
|
normalized.providerLabel = providerLabelVal;
|
|
687
701
|
}
|
|
688
|
-
const rawProviderEnv =
|
|
702
|
+
const rawProviderEnv = readObject2(rawConfig.providerEnv);
|
|
689
703
|
if (rawProviderEnv) {
|
|
690
704
|
const env = {};
|
|
691
705
|
for (const [key, value] of Object.entries(rawProviderEnv)) {
|
|
@@ -697,26 +711,26 @@ function normalizeConfig(rawConfig) {
|
|
|
697
711
|
normalized.providerEnv = env;
|
|
698
712
|
}
|
|
699
713
|
}
|
|
700
|
-
const rawProviderPresets =
|
|
714
|
+
const rawProviderPresets = readObject2(rawConfig.providerPresets);
|
|
701
715
|
if (rawProviderPresets) {
|
|
702
716
|
const presets = {};
|
|
703
717
|
for (const [presetId, presetVal] of Object.entries(rawProviderPresets)) {
|
|
704
|
-
const rawPreset =
|
|
718
|
+
const rawPreset = readObject2(presetVal);
|
|
705
719
|
if (rawPreset) {
|
|
706
|
-
const name =
|
|
707
|
-
const command =
|
|
720
|
+
const name = readString2(rawPreset.name);
|
|
721
|
+
const command = readString2(rawPreset.command);
|
|
708
722
|
if (name && command) {
|
|
709
723
|
const preset = {
|
|
710
724
|
name,
|
|
711
725
|
command,
|
|
712
|
-
subcommand:
|
|
713
|
-
promptFlag:
|
|
714
|
-
autoApproveFlag:
|
|
715
|
-
workdirFlag:
|
|
716
|
-
modelFlag:
|
|
717
|
-
model:
|
|
726
|
+
subcommand: readString2(rawPreset.subcommand),
|
|
727
|
+
promptFlag: readString2(rawPreset.promptFlag),
|
|
728
|
+
autoApproveFlag: readString2(rawPreset.autoApproveFlag),
|
|
729
|
+
workdirFlag: readString2(rawPreset.workdirFlag),
|
|
730
|
+
modelFlag: readString2(rawPreset.modelFlag),
|
|
731
|
+
model: readString2(rawPreset.model)
|
|
718
732
|
};
|
|
719
|
-
const rawEnvVars =
|
|
733
|
+
const rawEnvVars = readObject2(rawPreset.envVars);
|
|
720
734
|
if (rawEnvVars) {
|
|
721
735
|
const envVars = {};
|
|
722
736
|
for (const [envKey, envVal] of Object.entries(rawEnvVars)) {
|
|
@@ -736,7 +750,7 @@ function normalizeConfig(rawConfig) {
|
|
|
736
750
|
normalized.providerPresets = presets;
|
|
737
751
|
}
|
|
738
752
|
}
|
|
739
|
-
const rawNotifications =
|
|
753
|
+
const rawNotifications = readObject2(rawConfig.notifications);
|
|
740
754
|
if (rawNotifications) {
|
|
741
755
|
const rawWebhooks = Array.isArray(rawNotifications.webhooks) ? rawNotifications.webhooks : [];
|
|
742
756
|
const webhooks = [];
|
|
@@ -754,18 +768,18 @@ function normalizeConfig(rawConfig) {
|
|
|
754
768
|
}
|
|
755
769
|
normalized.notifications = { webhooks };
|
|
756
770
|
}
|
|
757
|
-
normalized.prdPriority =
|
|
758
|
-
const rawRoadmapScanner =
|
|
771
|
+
normalized.prdPriority = readStringArray2(rawConfig.prdPriority);
|
|
772
|
+
const rawRoadmapScanner = readObject2(rawConfig.roadmapScanner);
|
|
759
773
|
if (rawRoadmapScanner) {
|
|
760
|
-
const priorityModeRaw =
|
|
774
|
+
const priorityModeRaw = readString2(rawRoadmapScanner.priorityMode);
|
|
761
775
|
const priorityMode = priorityModeRaw === "roadmap-first" || priorityModeRaw === "audit-first" ? priorityModeRaw : DEFAULT_ROADMAP_SCANNER.priorityMode;
|
|
762
|
-
const issueColumnRaw =
|
|
776
|
+
const issueColumnRaw = readString2(rawRoadmapScanner.issueColumn);
|
|
763
777
|
const issueColumn = issueColumnRaw === "Draft" || issueColumnRaw === "Ready" ? issueColumnRaw : DEFAULT_ROADMAP_SCANNER.issueColumn;
|
|
764
778
|
const roadmapScanner = {
|
|
765
779
|
enabled: readBoolean(rawRoadmapScanner.enabled) ?? DEFAULT_ROADMAP_SCANNER.enabled,
|
|
766
|
-
roadmapPath:
|
|
780
|
+
roadmapPath: readString2(rawRoadmapScanner.roadmapPath) ?? DEFAULT_ROADMAP_SCANNER.roadmapPath,
|
|
767
781
|
autoScanInterval: readNumber(rawRoadmapScanner.autoScanInterval) ?? DEFAULT_ROADMAP_SCANNER.autoScanInterval,
|
|
768
|
-
slicerSchedule:
|
|
782
|
+
slicerSchedule: readString2(rawRoadmapScanner.slicerSchedule) ?? DEFAULT_ROADMAP_SCANNER.slicerSchedule,
|
|
769
783
|
slicerMaxRuntime: readNumber(rawRoadmapScanner.slicerMaxRuntime) ?? DEFAULT_ROADMAP_SCANNER.slicerMaxRuntime,
|
|
770
784
|
priorityMode,
|
|
771
785
|
issueColumn
|
|
@@ -775,12 +789,12 @@ function normalizeConfig(rawConfig) {
|
|
|
775
789
|
}
|
|
776
790
|
normalized.roadmapScanner = roadmapScanner;
|
|
777
791
|
}
|
|
778
|
-
normalized.templatesDir =
|
|
779
|
-
const rawBoardProvider =
|
|
792
|
+
normalized.templatesDir = readString2(rawConfig.templatesDir);
|
|
793
|
+
const rawBoardProvider = readObject2(rawConfig.boardProvider);
|
|
780
794
|
if (rawBoardProvider) {
|
|
781
795
|
const bp = {
|
|
782
796
|
enabled: readBoolean(rawBoardProvider.enabled) ?? DEFAULT_BOARD_PROVIDER.enabled,
|
|
783
|
-
provider:
|
|
797
|
+
provider: readString2(rawBoardProvider.provider) ?? DEFAULT_BOARD_PROVIDER.provider
|
|
784
798
|
};
|
|
785
799
|
if (typeof rawBoardProvider.projectNumber === "number") {
|
|
786
800
|
bp.projectNumber = rawBoardProvider.projectNumber;
|
|
@@ -796,7 +810,7 @@ function normalizeConfig(rawConfig) {
|
|
|
796
810
|
normalized.primaryFallbackModel = null;
|
|
797
811
|
normalized.claudeModel = null;
|
|
798
812
|
} else {
|
|
799
|
-
const primaryFallbackModelRaw =
|
|
813
|
+
const primaryFallbackModelRaw = readString2(rawConfig.primaryFallbackModel);
|
|
800
814
|
if (primaryFallbackModelRaw && VALID_CLAUDE_MODELS.includes(primaryFallbackModelRaw)) {
|
|
801
815
|
normalized.primaryFallbackModel = primaryFallbackModelRaw;
|
|
802
816
|
normalized.claudeModel = primaryFallbackModelRaw;
|
|
@@ -806,7 +820,7 @@ function normalizeConfig(rawConfig) {
|
|
|
806
820
|
if (rawConfig.claudeModel === null) {
|
|
807
821
|
normalized.claudeModel = null;
|
|
808
822
|
} else {
|
|
809
|
-
const claudeModelRaw =
|
|
823
|
+
const claudeModelRaw = readString2(rawConfig.claudeModel);
|
|
810
824
|
if (claudeModelRaw && VALID_CLAUDE_MODELS.includes(claudeModelRaw)) {
|
|
811
825
|
normalized.primaryFallbackModel = claudeModelRaw;
|
|
812
826
|
normalized.claudeModel = claudeModelRaw;
|
|
@@ -817,16 +831,16 @@ function normalizeConfig(rawConfig) {
|
|
|
817
831
|
if (rawConfig.secondaryFallbackModel === null) {
|
|
818
832
|
normalized.secondaryFallbackModel = null;
|
|
819
833
|
} else {
|
|
820
|
-
const secondaryFallbackModelRaw =
|
|
834
|
+
const secondaryFallbackModelRaw = readString2(rawConfig.secondaryFallbackModel);
|
|
821
835
|
if (secondaryFallbackModelRaw && VALID_CLAUDE_MODELS.includes(secondaryFallbackModelRaw)) {
|
|
822
836
|
normalized.secondaryFallbackModel = secondaryFallbackModelRaw;
|
|
823
837
|
}
|
|
824
838
|
}
|
|
825
839
|
}
|
|
826
|
-
normalized.primaryFallbackPreset =
|
|
827
|
-
normalized.secondaryFallbackPreset =
|
|
840
|
+
normalized.primaryFallbackPreset = readString2(rawConfig.primaryFallbackPreset);
|
|
841
|
+
normalized.secondaryFallbackPreset = readString2(rawConfig.secondaryFallbackPreset);
|
|
828
842
|
normalized.autoMerge = readBoolean(rawConfig.autoMerge);
|
|
829
|
-
const mergeMethod =
|
|
843
|
+
const mergeMethod = readString2(rawConfig.autoMergeMethod);
|
|
830
844
|
if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
|
|
831
845
|
normalized.autoMergeMethod = mergeMethod;
|
|
832
846
|
}
|
|
@@ -834,23 +848,23 @@ function normalizeConfig(rawConfig) {
|
|
|
834
848
|
const jobDef = getJobDef(jobId);
|
|
835
849
|
if (!jobDef)
|
|
836
850
|
continue;
|
|
837
|
-
const rawJob =
|
|
851
|
+
const rawJob = readObject2(rawConfig[jobId]);
|
|
838
852
|
if (rawJob) {
|
|
839
853
|
normalized[jobId] = normalizeJobConfig(rawJob, jobDef);
|
|
840
854
|
}
|
|
841
855
|
}
|
|
842
856
|
const prResolverDef = getJobDef("pr-resolver");
|
|
843
857
|
if (prResolverDef) {
|
|
844
|
-
const rawJob =
|
|
858
|
+
const rawJob = readObject2(rawConfig.prResolver);
|
|
845
859
|
if (rawJob) {
|
|
846
860
|
normalized.prResolver = normalizeJobConfig(rawJob, prResolverDef);
|
|
847
861
|
}
|
|
848
862
|
}
|
|
849
|
-
const rawJobProviders =
|
|
863
|
+
const rawJobProviders = readObject2(rawConfig.jobProviders);
|
|
850
864
|
if (rawJobProviders) {
|
|
851
865
|
const jobProviders = {};
|
|
852
866
|
for (const jobType of VALID_JOB_TYPES) {
|
|
853
|
-
const providerValue =
|
|
867
|
+
const providerValue = readString2(rawJobProviders[jobType]);
|
|
854
868
|
if (providerValue && providerValue.trim().length > 0) {
|
|
855
869
|
jobProviders[jobType] = providerValue.trim();
|
|
856
870
|
}
|
|
@@ -859,7 +873,20 @@ function normalizeConfig(rawConfig) {
|
|
|
859
873
|
normalized.jobProviders = jobProviders;
|
|
860
874
|
}
|
|
861
875
|
}
|
|
862
|
-
const
|
|
876
|
+
const rawPausedJobs = readObject2(rawConfig.pausedJobs);
|
|
877
|
+
if (rawPausedJobs) {
|
|
878
|
+
const pausedJobs = {};
|
|
879
|
+
for (const jobType of VALID_JOB_TYPES) {
|
|
880
|
+
const paused = readBoolean(rawPausedJobs[jobType]);
|
|
881
|
+
if (paused !== void 0) {
|
|
882
|
+
pausedJobs[jobType] = paused;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (Object.keys(pausedJobs).length > 0) {
|
|
886
|
+
normalized.pausedJobs = pausedJobs;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const rawScheduleOverrides = readObject2(rawConfig.providerScheduleOverrides);
|
|
863
890
|
if (rawScheduleOverrides) {
|
|
864
891
|
if (Array.isArray(rawScheduleOverrides)) {
|
|
865
892
|
const overrides = [];
|
|
@@ -867,10 +894,10 @@ function normalizeConfig(rawConfig) {
|
|
|
867
894
|
for (const rawOverride of rawScheduleOverrides) {
|
|
868
895
|
if (rawOverride && typeof rawOverride === "object") {
|
|
869
896
|
const overrideObj = rawOverride;
|
|
870
|
-
const label2 =
|
|
871
|
-
const presetId =
|
|
872
|
-
const startTime =
|
|
873
|
-
const endTime =
|
|
897
|
+
const label2 = readString2(overrideObj.label);
|
|
898
|
+
const presetId = readString2(overrideObj.presetId);
|
|
899
|
+
const startTime = readString2(overrideObj.startTime);
|
|
900
|
+
const endTime = readString2(overrideObj.endTime);
|
|
874
901
|
const rawDays = overrideObj.days;
|
|
875
902
|
const rawJobTypes = overrideObj.jobTypes;
|
|
876
903
|
const enabled = readBoolean(overrideObj.enabled);
|
|
@@ -920,9 +947,9 @@ function normalizeConfig(rawConfig) {
|
|
|
920
947
|
}
|
|
921
948
|
}
|
|
922
949
|
}
|
|
923
|
-
const rawQueue =
|
|
950
|
+
const rawQueue = readObject2(rawConfig.queue);
|
|
924
951
|
if (rawQueue) {
|
|
925
|
-
const rawMode =
|
|
952
|
+
const rawMode = readString2(rawQueue.mode);
|
|
926
953
|
const mode = rawMode === "conservative" || rawMode === "provider-aware" || rawMode === "auto" ? rawMode : DEFAULT_QUEUE.mode;
|
|
927
954
|
const queue = {
|
|
928
955
|
enabled: readBoolean(rawQueue.enabled) ?? DEFAULT_QUEUE.enabled,
|
|
@@ -932,7 +959,7 @@ function normalizeConfig(rawConfig) {
|
|
|
932
959
|
priority: { ...DEFAULT_QUEUE.priority },
|
|
933
960
|
providerBuckets: {}
|
|
934
961
|
};
|
|
935
|
-
const rawPriority =
|
|
962
|
+
const rawPriority = readObject2(rawQueue.priority);
|
|
936
963
|
if (rawPriority) {
|
|
937
964
|
for (const jobType of VALID_JOB_TYPES) {
|
|
938
965
|
const prio = readNumber(rawPriority[jobType]);
|
|
@@ -941,10 +968,10 @@ function normalizeConfig(rawConfig) {
|
|
|
941
968
|
}
|
|
942
969
|
}
|
|
943
970
|
}
|
|
944
|
-
const rawProviderBuckets =
|
|
971
|
+
const rawProviderBuckets = readObject2(rawQueue.providerBuckets);
|
|
945
972
|
if (rawProviderBuckets) {
|
|
946
973
|
for (const [bucketKey, bucketVal] of Object.entries(rawProviderBuckets)) {
|
|
947
|
-
const rawBucket =
|
|
974
|
+
const rawBucket = readObject2(bucketVal);
|
|
948
975
|
if (rawBucket) {
|
|
949
976
|
const maxConcurrency = readNumber(rawBucket.maxConcurrency);
|
|
950
977
|
if (maxConcurrency !== void 0) {
|
|
@@ -1274,9 +1301,113 @@ function getDefaultConfig() {
|
|
|
1274
1301
|
merger: { ...DEFAULT_MERGER },
|
|
1275
1302
|
jobProviders: { ...DEFAULT_JOB_PROVIDERS },
|
|
1276
1303
|
providerScheduleOverrides: [...DEFAULT_PROVIDER_SCHEDULE_OVERRIDES],
|
|
1277
|
-
queue: { ...DEFAULT_QUEUE }
|
|
1304
|
+
queue: { ...DEFAULT_QUEUE },
|
|
1305
|
+
pausedJobs: {},
|
|
1306
|
+
webhookTriggers: cloneWebhookTriggers(DEFAULT_WEBHOOK_TRIGGERS)
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
function cloneWebhookTriggers(config) {
|
|
1310
|
+
return {
|
|
1311
|
+
...config,
|
|
1312
|
+
allowedJobIds: [...config.allowedJobIds],
|
|
1313
|
+
github: {
|
|
1314
|
+
...config.github,
|
|
1315
|
+
events: [...config.github.events],
|
|
1316
|
+
rules: config.github.rules.map((rule) => {
|
|
1317
|
+
const cloned = { ...rule };
|
|
1318
|
+
if (rule.branchPatterns) {
|
|
1319
|
+
cloned.branchPatterns = [...rule.branchPatterns];
|
|
1320
|
+
}
|
|
1321
|
+
return cloned;
|
|
1322
|
+
})
|
|
1323
|
+
}
|
|
1278
1324
|
};
|
|
1279
1325
|
}
|
|
1326
|
+
function isPlainObject(value) {
|
|
1327
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1328
|
+
}
|
|
1329
|
+
function isValidJobType(value) {
|
|
1330
|
+
return typeof value === "string" && getJobDef(value) !== void 0;
|
|
1331
|
+
}
|
|
1332
|
+
function readStringArray(value) {
|
|
1333
|
+
if (!Array.isArray(value))
|
|
1334
|
+
return void 0;
|
|
1335
|
+
const strings = value.filter((item) => typeof item === "string");
|
|
1336
|
+
return strings.length > 0 ? strings : [];
|
|
1337
|
+
}
|
|
1338
|
+
function normalizeWebhookGithubRules(value) {
|
|
1339
|
+
if (!Array.isArray(value))
|
|
1340
|
+
return void 0;
|
|
1341
|
+
const rules = [];
|
|
1342
|
+
for (const item of value) {
|
|
1343
|
+
if (!isPlainObject(item))
|
|
1344
|
+
continue;
|
|
1345
|
+
const event = typeof item.event === "string" ? item.event.trim() : "";
|
|
1346
|
+
if (!event || !isValidJobType(item.jobId))
|
|
1347
|
+
continue;
|
|
1348
|
+
const rule = {
|
|
1349
|
+
event,
|
|
1350
|
+
jobId: item.jobId
|
|
1351
|
+
};
|
|
1352
|
+
if (typeof item.action === "string" && item.action.trim()) {
|
|
1353
|
+
rule.action = item.action.trim();
|
|
1354
|
+
}
|
|
1355
|
+
const branchPatterns = readStringArray(item.branchPatterns);
|
|
1356
|
+
if (branchPatterns !== void 0) {
|
|
1357
|
+
rule.branchPatterns = branchPatterns;
|
|
1358
|
+
}
|
|
1359
|
+
if (typeof item.onlyOnFailure === "boolean") {
|
|
1360
|
+
rule.onlyOnFailure = item.onlyOnFailure;
|
|
1361
|
+
}
|
|
1362
|
+
rules.push(rule);
|
|
1363
|
+
}
|
|
1364
|
+
return rules;
|
|
1365
|
+
}
|
|
1366
|
+
function normalizeWebhookTriggersConfig(value) {
|
|
1367
|
+
if (!isPlainObject(value))
|
|
1368
|
+
return void 0;
|
|
1369
|
+
const config = cloneWebhookTriggers(DEFAULT_WEBHOOK_TRIGGERS);
|
|
1370
|
+
if (typeof value.enabled === "boolean") {
|
|
1371
|
+
config.enabled = value.enabled;
|
|
1372
|
+
}
|
|
1373
|
+
if (typeof value.secretEnv === "string") {
|
|
1374
|
+
config.secretEnv = value.secretEnv.trim();
|
|
1375
|
+
}
|
|
1376
|
+
const allowedJobIds = readStringArray(value.allowedJobIds);
|
|
1377
|
+
if (allowedJobIds !== void 0) {
|
|
1378
|
+
config.allowedJobIds = allowedJobIds.filter(isValidJobType);
|
|
1379
|
+
}
|
|
1380
|
+
if (typeof value.requireTimestamp === "boolean") {
|
|
1381
|
+
config.requireTimestamp = value.requireTimestamp;
|
|
1382
|
+
}
|
|
1383
|
+
if (typeof value.maxSkewSeconds === "number" && Number.isFinite(value.maxSkewSeconds)) {
|
|
1384
|
+
const n = Math.floor(value.maxSkewSeconds);
|
|
1385
|
+
config.maxSkewSeconds = n > 0 ? n : DEFAULT_WEBHOOK_TRIGGERS.maxSkewSeconds;
|
|
1386
|
+
}
|
|
1387
|
+
if (isPlainObject(value.github)) {
|
|
1388
|
+
if (typeof value.github.enabled === "boolean") {
|
|
1389
|
+
config.github.enabled = value.github.enabled;
|
|
1390
|
+
}
|
|
1391
|
+
const events = readStringArray(value.github.events);
|
|
1392
|
+
if (events !== void 0) {
|
|
1393
|
+
config.github.events = events.map((event) => event.trim()).filter(Boolean);
|
|
1394
|
+
}
|
|
1395
|
+
const rules = normalizeWebhookGithubRules(value.github.rules);
|
|
1396
|
+
if (rules !== void 0) {
|
|
1397
|
+
config.github.rules = rules;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return config;
|
|
1401
|
+
}
|
|
1402
|
+
function validateWebhookTriggers(config) {
|
|
1403
|
+
const validated = cloneWebhookTriggers(config);
|
|
1404
|
+
validated.allowedJobIds = validated.allowedJobIds.filter(isValidJobType);
|
|
1405
|
+
validated.github.rules = validated.github.rules.filter((rule) => isValidJobType(rule.jobId));
|
|
1406
|
+
if (validated.enabled && validated.secretEnv.trim().length === 0) {
|
|
1407
|
+
throw new Error("webhookTriggers.secretEnv must be non-empty when webhook triggers are enabled");
|
|
1408
|
+
}
|
|
1409
|
+
return validated;
|
|
1410
|
+
}
|
|
1280
1411
|
function loadConfigFile(configPath) {
|
|
1281
1412
|
try {
|
|
1282
1413
|
if (!fs.existsSync(configPath)) {
|
|
@@ -1284,7 +1415,12 @@ function loadConfigFile(configPath) {
|
|
|
1284
1415
|
}
|
|
1285
1416
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
1286
1417
|
const rawConfig = JSON.parse(content);
|
|
1287
|
-
|
|
1418
|
+
const normalized = normalizeConfig(rawConfig);
|
|
1419
|
+
const webhookTriggers = normalizeWebhookTriggersConfig(rawConfig.webhookTriggers);
|
|
1420
|
+
if (webhookTriggers) {
|
|
1421
|
+
normalized.webhookTriggers = webhookTriggers;
|
|
1422
|
+
}
|
|
1423
|
+
return normalized;
|
|
1288
1424
|
} catch (error2) {
|
|
1289
1425
|
console.warn(`Warning: Could not parse config file at ${configPath}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
1290
1426
|
return null;
|
|
@@ -1339,6 +1475,26 @@ function mergeConfigLayer(base, layer) {
|
|
|
1339
1475
|
...layerQueue,
|
|
1340
1476
|
providerBuckets: { ...baseQueue.providerBuckets, ...layerQueue.providerBuckets }
|
|
1341
1477
|
};
|
|
1478
|
+
} else if (_key === "webhookTriggers") {
|
|
1479
|
+
const baseWebhook = base[_key];
|
|
1480
|
+
const layerWebhook = value;
|
|
1481
|
+
base[_key] = {
|
|
1482
|
+
...baseWebhook,
|
|
1483
|
+
...layerWebhook,
|
|
1484
|
+
allowedJobIds: [...layerWebhook.allowedJobIds],
|
|
1485
|
+
github: {
|
|
1486
|
+
...baseWebhook.github,
|
|
1487
|
+
...layerWebhook.github,
|
|
1488
|
+
events: [...layerWebhook.github.events],
|
|
1489
|
+
rules: layerWebhook.github.rules.map((rule) => {
|
|
1490
|
+
const cloned = { ...rule };
|
|
1491
|
+
if (rule.branchPatterns) {
|
|
1492
|
+
cloned.branchPatterns = [...rule.branchPatterns];
|
|
1493
|
+
}
|
|
1494
|
+
return cloned;
|
|
1495
|
+
})
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1342
1498
|
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver" || _key === "merger") {
|
|
1343
1499
|
base[_key] = {
|
|
1344
1500
|
...base[_key],
|
|
@@ -1379,6 +1535,7 @@ function mergeConfigs(base, fileConfig, envConfig) {
|
|
|
1379
1535
|
if (merged.claudeModel === void 0) {
|
|
1380
1536
|
merged.claudeModel = merged.primaryFallbackModel === void 0 ? DEFAULT_CLAUDE_MODEL : merged.primaryFallbackModel;
|
|
1381
1537
|
}
|
|
1538
|
+
merged.webhookTriggers = validateWebhookTriggers(merged.webhookTriggers);
|
|
1382
1539
|
return merged;
|
|
1383
1540
|
}
|
|
1384
1541
|
function loadConfig(projectDir) {
|
|
@@ -1492,6 +1649,7 @@ var init_config = __esm({
|
|
|
1492
1649
|
init_constants();
|
|
1493
1650
|
init_config_normalize();
|
|
1494
1651
|
init_config_env();
|
|
1652
|
+
init_job_registry();
|
|
1495
1653
|
init_config_normalize();
|
|
1496
1654
|
}
|
|
1497
1655
|
});
|
|
@@ -1895,14 +2053,350 @@ var init_roadmap_state_repository = __esm({
|
|
|
1895
2053
|
}
|
|
1896
2054
|
});
|
|
1897
2055
|
|
|
2056
|
+
// ../core/src/board/types.ts
|
|
2057
|
+
var BOARD_COLUMNS2;
|
|
2058
|
+
var init_types3 = __esm({
|
|
2059
|
+
"../core/src/board/types.ts"() {
|
|
2060
|
+
"use strict";
|
|
2061
|
+
BOARD_COLUMNS2 = ["Draft", "Ready", "In Progress", "Review", "Done"];
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
2064
|
+
|
|
2065
|
+
// ../core/src/jobs/job-registry.ts
|
|
2066
|
+
function getValidJobTypes2() {
|
|
2067
|
+
return JOB_REGISTRY2.map((job) => job.id);
|
|
2068
|
+
}
|
|
2069
|
+
function getDefaultQueuePriority2() {
|
|
2070
|
+
const result = {};
|
|
2071
|
+
for (const job of JOB_REGISTRY2) {
|
|
2072
|
+
result[job.id] = job.queuePriority;
|
|
2073
|
+
}
|
|
2074
|
+
return result;
|
|
2075
|
+
}
|
|
2076
|
+
function getLogFileNames2() {
|
|
2077
|
+
const result = {};
|
|
2078
|
+
for (const job of JOB_REGISTRY2) {
|
|
2079
|
+
result[job.id] = job.logName;
|
|
2080
|
+
if (job.cliCommand !== job.id) {
|
|
2081
|
+
result[job.cliCommand] = job.logName;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
return result;
|
|
2085
|
+
}
|
|
2086
|
+
var JOB_REGISTRY2, JOB_MAP2;
|
|
2087
|
+
var init_job_registry2 = __esm({
|
|
2088
|
+
"../core/src/jobs/job-registry.ts"() {
|
|
2089
|
+
"use strict";
|
|
2090
|
+
init_types3();
|
|
2091
|
+
JOB_REGISTRY2 = [
|
|
2092
|
+
{
|
|
2093
|
+
id: "executor",
|
|
2094
|
+
name: "Executor",
|
|
2095
|
+
description: "Creates implementation PRs from PRDs",
|
|
2096
|
+
cliCommand: "run",
|
|
2097
|
+
logName: "executor",
|
|
2098
|
+
lockSuffix: ".lock",
|
|
2099
|
+
queuePriority: 50,
|
|
2100
|
+
envPrefix: "NW_EXECUTOR",
|
|
2101
|
+
defaultConfig: {
|
|
2102
|
+
enabled: true,
|
|
2103
|
+
schedule: "5 */2 * * *",
|
|
2104
|
+
maxRuntime: 7200
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
id: "reviewer",
|
|
2109
|
+
name: "Reviewer",
|
|
2110
|
+
description: "Reviews and improves PRs on night-watch branches",
|
|
2111
|
+
cliCommand: "review",
|
|
2112
|
+
logName: "reviewer",
|
|
2113
|
+
lockSuffix: "-r.lock",
|
|
2114
|
+
queuePriority: 40,
|
|
2115
|
+
envPrefix: "NW_REVIEWER",
|
|
2116
|
+
defaultConfig: {
|
|
2117
|
+
enabled: true,
|
|
2118
|
+
schedule: "25 */3 * * *",
|
|
2119
|
+
maxRuntime: 3600
|
|
2120
|
+
}
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
id: "pr-resolver",
|
|
2124
|
+
name: "PR Conflict Solver",
|
|
2125
|
+
description: "Resolves merge conflicts via AI rebase; optionally addresses review comments and labels PRs ready-to-merge",
|
|
2126
|
+
cliCommand: "resolve",
|
|
2127
|
+
logName: "pr-resolver",
|
|
2128
|
+
lockSuffix: "-pr-resolver.lock",
|
|
2129
|
+
queuePriority: 35,
|
|
2130
|
+
envPrefix: "NW_PR_RESOLVER",
|
|
2131
|
+
extraFields: [
|
|
2132
|
+
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
2133
|
+
{ name: "maxPrsPerRun", type: "number", defaultValue: 0 },
|
|
2134
|
+
{ name: "perPrTimeout", type: "number", defaultValue: 600 },
|
|
2135
|
+
{ name: "aiConflictResolution", type: "boolean", defaultValue: true },
|
|
2136
|
+
{ name: "aiReviewResolution", type: "boolean", defaultValue: false },
|
|
2137
|
+
{ name: "readyLabel", type: "string", defaultValue: "ready-to-merge" }
|
|
2138
|
+
],
|
|
2139
|
+
defaultConfig: {
|
|
2140
|
+
enabled: true,
|
|
2141
|
+
schedule: "15 6,14,22 * * *",
|
|
2142
|
+
maxRuntime: 3600,
|
|
2143
|
+
branchPatterns: [],
|
|
2144
|
+
maxPrsPerRun: 0,
|
|
2145
|
+
perPrTimeout: 600,
|
|
2146
|
+
aiConflictResolution: true,
|
|
2147
|
+
aiReviewResolution: false,
|
|
2148
|
+
readyLabel: "ready-to-merge"
|
|
2149
|
+
}
|
|
2150
|
+
},
|
|
2151
|
+
{
|
|
2152
|
+
id: "slicer",
|
|
2153
|
+
name: "Slicer",
|
|
2154
|
+
description: "Generates PRDs from roadmap items",
|
|
2155
|
+
cliCommand: "planner",
|
|
2156
|
+
logName: "slicer",
|
|
2157
|
+
lockSuffix: "-slicer.lock",
|
|
2158
|
+
queuePriority: 30,
|
|
2159
|
+
envPrefix: "NW_SLICER",
|
|
2160
|
+
defaultConfig: {
|
|
2161
|
+
enabled: true,
|
|
2162
|
+
schedule: "35 */6 * * *",
|
|
2163
|
+
maxRuntime: 600
|
|
2164
|
+
}
|
|
2165
|
+
},
|
|
2166
|
+
{
|
|
2167
|
+
id: "qa",
|
|
2168
|
+
name: "QA",
|
|
2169
|
+
description: "Runs end-to-end tests on PRs",
|
|
2170
|
+
cliCommand: "qa",
|
|
2171
|
+
logName: "night-watch-qa",
|
|
2172
|
+
lockSuffix: "-qa.lock",
|
|
2173
|
+
queuePriority: 20,
|
|
2174
|
+
envPrefix: "NW_QA",
|
|
2175
|
+
extraFields: [
|
|
2176
|
+
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
2177
|
+
{
|
|
2178
|
+
name: "artifacts",
|
|
2179
|
+
type: "enum",
|
|
2180
|
+
enumValues: ["screenshot", "video", "both"],
|
|
2181
|
+
defaultValue: "both"
|
|
2182
|
+
},
|
|
2183
|
+
{ name: "skipLabel", type: "string", defaultValue: "skip-qa" },
|
|
2184
|
+
{ name: "autoInstallPlaywright", type: "boolean", defaultValue: true },
|
|
2185
|
+
{ name: "validatedLabel", type: "string", defaultValue: "e2e-validated" }
|
|
2186
|
+
],
|
|
2187
|
+
defaultConfig: {
|
|
2188
|
+
enabled: true,
|
|
2189
|
+
schedule: "45 2,10,18 * * *",
|
|
2190
|
+
maxRuntime: 3600,
|
|
2191
|
+
branchPatterns: [],
|
|
2192
|
+
artifacts: "both",
|
|
2193
|
+
skipLabel: "skip-qa",
|
|
2194
|
+
autoInstallPlaywright: true,
|
|
2195
|
+
validatedLabel: "e2e-validated"
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
id: "audit",
|
|
2200
|
+
name: "Auditor",
|
|
2201
|
+
description: "Performs code audits and creates issues for findings",
|
|
2202
|
+
cliCommand: "audit",
|
|
2203
|
+
logName: "audit",
|
|
2204
|
+
lockSuffix: "-audit.lock",
|
|
2205
|
+
queuePriority: 10,
|
|
2206
|
+
envPrefix: "NW_AUDIT",
|
|
2207
|
+
extraFields: [
|
|
2208
|
+
{
|
|
2209
|
+
name: "targetColumn",
|
|
2210
|
+
type: "enum",
|
|
2211
|
+
enumValues: [...BOARD_COLUMNS2],
|
|
2212
|
+
defaultValue: "Draft"
|
|
2213
|
+
}
|
|
2214
|
+
],
|
|
2215
|
+
defaultConfig: {
|
|
2216
|
+
enabled: true,
|
|
2217
|
+
schedule: "50 3 * * 1",
|
|
2218
|
+
maxRuntime: 1800,
|
|
2219
|
+
targetColumn: "Draft"
|
|
2220
|
+
}
|
|
2221
|
+
},
|
|
2222
|
+
{
|
|
2223
|
+
id: "analytics",
|
|
2224
|
+
name: "Analytics",
|
|
2225
|
+
description: "Analyzes product analytics and creates issues for trends",
|
|
2226
|
+
cliCommand: "analytics",
|
|
2227
|
+
logName: "analytics",
|
|
2228
|
+
lockSuffix: "-analytics.lock",
|
|
2229
|
+
queuePriority: 10,
|
|
2230
|
+
envPrefix: "NW_ANALYTICS",
|
|
2231
|
+
extraFields: [
|
|
2232
|
+
{ name: "lookbackDays", type: "number", defaultValue: 7 },
|
|
2233
|
+
{
|
|
2234
|
+
name: "targetColumn",
|
|
2235
|
+
type: "enum",
|
|
2236
|
+
enumValues: [...BOARD_COLUMNS2],
|
|
2237
|
+
defaultValue: "Draft"
|
|
2238
|
+
},
|
|
2239
|
+
{ name: "analysisPrompt", type: "string", defaultValue: "" }
|
|
2240
|
+
],
|
|
2241
|
+
defaultConfig: {
|
|
2242
|
+
enabled: false,
|
|
2243
|
+
schedule: "0 6 * * 1",
|
|
2244
|
+
maxRuntime: 900,
|
|
2245
|
+
lookbackDays: 7,
|
|
2246
|
+
targetColumn: "Draft",
|
|
2247
|
+
analysisPrompt: ""
|
|
2248
|
+
}
|
|
2249
|
+
},
|
|
2250
|
+
{
|
|
2251
|
+
id: "merger",
|
|
2252
|
+
name: "Merge Orchestrator",
|
|
2253
|
+
description: "Repo-wide PR merge coordinator \u2014 scans, rebases, and merges in FIFO order",
|
|
2254
|
+
cliCommand: "merge",
|
|
2255
|
+
logName: "merger",
|
|
2256
|
+
lockSuffix: "-merger.lock",
|
|
2257
|
+
queuePriority: 45,
|
|
2258
|
+
envPrefix: "NW_MERGER",
|
|
2259
|
+
extraFields: [
|
|
2260
|
+
{
|
|
2261
|
+
name: "mergeMethod",
|
|
2262
|
+
type: "enum",
|
|
2263
|
+
enumValues: ["squash", "merge", "rebase"],
|
|
2264
|
+
defaultValue: "squash"
|
|
2265
|
+
},
|
|
2266
|
+
{ name: "minReviewScore", type: "number", defaultValue: 80 },
|
|
2267
|
+
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
2268
|
+
{ name: "rebaseBeforeMerge", type: "boolean", defaultValue: true },
|
|
2269
|
+
{ name: "maxPrsPerRun", type: "number", defaultValue: 0 }
|
|
2270
|
+
],
|
|
2271
|
+
defaultConfig: {
|
|
2272
|
+
enabled: false,
|
|
2273
|
+
schedule: "55 */4 * * *",
|
|
2274
|
+
maxRuntime: 1800,
|
|
2275
|
+
mergeMethod: "squash",
|
|
2276
|
+
minReviewScore: 80,
|
|
2277
|
+
branchPatterns: [],
|
|
2278
|
+
rebaseBeforeMerge: true,
|
|
2279
|
+
maxPrsPerRun: 0
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
];
|
|
2283
|
+
JOB_MAP2 = new Map(JOB_REGISTRY2.map((job) => [job.id, job]));
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
|
|
2287
|
+
// ../core/src/constants.ts
|
|
2288
|
+
var DEFAULT_LOCAL_BOARD_INFO2, VALID_JOB_TYPES2, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV2, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS2, DEFAULT_WEBHOOK_TRIGGERS2, BUILT_IN_PRESETS2, BUILT_IN_PRESET_IDS2, PROVIDER_COMMANDS2, LOG_FILE_NAMES2, GLOBAL_CONFIG_DIR2, STATE_DB_FILE_NAME2, DEFAULT_QUEUE_ENABLED2, DEFAULT_QUEUE_MODE2, DEFAULT_QUEUE_MAX_CONCURRENCY2, DEFAULT_QUEUE_MAX_WAIT_TIME2, DEFAULT_QUEUE_PRIORITY2, DEFAULT_QUEUE2;
|
|
2289
|
+
var init_constants2 = __esm({
|
|
2290
|
+
"../core/src/constants.ts"() {
|
|
2291
|
+
"use strict";
|
|
2292
|
+
init_job_registry2();
|
|
2293
|
+
DEFAULT_LOCAL_BOARD_INFO2 = { id: "local", number: 0, title: "Local Kanban", url: "" };
|
|
2294
|
+
VALID_JOB_TYPES2 = getValidJobTypes2();
|
|
2295
|
+
DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV2 = "NIGHT_WATCH_WEBHOOK_SECRET";
|
|
2296
|
+
DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS2 = 300;
|
|
2297
|
+
DEFAULT_WEBHOOK_TRIGGERS2 = {
|
|
2298
|
+
enabled: false,
|
|
2299
|
+
secretEnv: DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV2,
|
|
2300
|
+
allowedJobIds: getValidJobTypes2(),
|
|
2301
|
+
requireTimestamp: false,
|
|
2302
|
+
maxSkewSeconds: DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS2,
|
|
2303
|
+
github: {
|
|
2304
|
+
enabled: false,
|
|
2305
|
+
events: [],
|
|
2306
|
+
rules: []
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
BUILT_IN_PRESETS2 = {
|
|
2310
|
+
claude: {
|
|
2311
|
+
name: "Claude",
|
|
2312
|
+
command: "claude",
|
|
2313
|
+
promptFlag: "-p",
|
|
2314
|
+
autoApproveFlag: "--dangerously-skip-permissions"
|
|
2315
|
+
},
|
|
2316
|
+
"claude-sonnet-4-6": {
|
|
2317
|
+
name: "Claude Sonnet 4.6",
|
|
2318
|
+
command: "claude",
|
|
2319
|
+
promptFlag: "-p",
|
|
2320
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2321
|
+
modelFlag: "--model",
|
|
2322
|
+
model: "claude-sonnet-4-6"
|
|
2323
|
+
},
|
|
2324
|
+
"claude-opus-4-6": {
|
|
2325
|
+
name: "Claude Opus 4.6",
|
|
2326
|
+
command: "claude",
|
|
2327
|
+
promptFlag: "-p",
|
|
2328
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2329
|
+
modelFlag: "--model",
|
|
2330
|
+
model: "claude-opus-4-6"
|
|
2331
|
+
},
|
|
2332
|
+
codex: {
|
|
2333
|
+
name: "Codex",
|
|
2334
|
+
command: "codex",
|
|
2335
|
+
subcommand: "exec",
|
|
2336
|
+
autoApproveFlag: "--yolo",
|
|
2337
|
+
workdirFlag: "-C"
|
|
2338
|
+
},
|
|
2339
|
+
"glm-47": {
|
|
2340
|
+
name: "GLM-4.7",
|
|
2341
|
+
command: "claude",
|
|
2342
|
+
promptFlag: "-p",
|
|
2343
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2344
|
+
modelFlag: "--model",
|
|
2345
|
+
model: "glm-4.7",
|
|
2346
|
+
envVars: {
|
|
2347
|
+
ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
|
|
2348
|
+
API_TIMEOUT_MS: "3000000",
|
|
2349
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-4.7",
|
|
2350
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.7"
|
|
2351
|
+
}
|
|
2352
|
+
},
|
|
2353
|
+
"glm-5": {
|
|
2354
|
+
name: "GLM-5",
|
|
2355
|
+
command: "claude",
|
|
2356
|
+
promptFlag: "-p",
|
|
2357
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2358
|
+
modelFlag: "--model",
|
|
2359
|
+
model: "glm-5",
|
|
2360
|
+
envVars: {
|
|
2361
|
+
ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
|
|
2362
|
+
API_TIMEOUT_MS: "3000000",
|
|
2363
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
|
|
2364
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5"
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
BUILT_IN_PRESET_IDS2 = Object.keys(BUILT_IN_PRESETS2);
|
|
2369
|
+
PROVIDER_COMMANDS2 = {
|
|
2370
|
+
claude: BUILT_IN_PRESETS2.claude.command,
|
|
2371
|
+
codex: BUILT_IN_PRESETS2.codex.command
|
|
2372
|
+
};
|
|
2373
|
+
LOG_FILE_NAMES2 = getLogFileNames2();
|
|
2374
|
+
GLOBAL_CONFIG_DIR2 = ".night-watch";
|
|
2375
|
+
STATE_DB_FILE_NAME2 = "state.db";
|
|
2376
|
+
DEFAULT_QUEUE_ENABLED2 = true;
|
|
2377
|
+
DEFAULT_QUEUE_MODE2 = "auto";
|
|
2378
|
+
DEFAULT_QUEUE_MAX_CONCURRENCY2 = 3;
|
|
2379
|
+
DEFAULT_QUEUE_MAX_WAIT_TIME2 = 7200;
|
|
2380
|
+
DEFAULT_QUEUE_PRIORITY2 = getDefaultQueuePriority2();
|
|
2381
|
+
DEFAULT_QUEUE2 = {
|
|
2382
|
+
enabled: DEFAULT_QUEUE_ENABLED2,
|
|
2383
|
+
mode: DEFAULT_QUEUE_MODE2,
|
|
2384
|
+
maxConcurrency: DEFAULT_QUEUE_MAX_CONCURRENCY2,
|
|
2385
|
+
maxWaitTime: DEFAULT_QUEUE_MAX_WAIT_TIME2,
|
|
2386
|
+
priority: { ...DEFAULT_QUEUE_PRIORITY2 },
|
|
2387
|
+
providerBuckets: {}
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
});
|
|
2391
|
+
|
|
1898
2392
|
// ../core/dist/storage/sqlite/client.js
|
|
1899
2393
|
import * as fs2 from "fs";
|
|
1900
2394
|
import * as os from "os";
|
|
1901
2395
|
import * as path2 from "path";
|
|
1902
2396
|
import Database6 from "better-sqlite3";
|
|
1903
2397
|
function getDbPath() {
|
|
1904
|
-
const base = process.env.NIGHT_WATCH_HOME || path2.join(os.homedir(),
|
|
1905
|
-
return path2.join(base,
|
|
2398
|
+
const base = process.env.NIGHT_WATCH_HOME || path2.join(os.homedir(), GLOBAL_CONFIG_DIR2);
|
|
2399
|
+
return path2.join(base, STATE_DB_FILE_NAME2);
|
|
1906
2400
|
}
|
|
1907
2401
|
function getDb() {
|
|
1908
2402
|
if (_db) {
|
|
@@ -1924,7 +2418,7 @@ function closeDb() {
|
|
|
1924
2418
|
}
|
|
1925
2419
|
function createDbForDir(projectDir) {
|
|
1926
2420
|
fs2.mkdirSync(projectDir, { recursive: true });
|
|
1927
|
-
const dbPath = path2.join(projectDir,
|
|
2421
|
+
const dbPath = path2.join(projectDir, STATE_DB_FILE_NAME2);
|
|
1928
2422
|
const db = new Database6(dbPath);
|
|
1929
2423
|
db.pragma("journal_mode = WAL");
|
|
1930
2424
|
db.pragma("busy_timeout = 5000");
|
|
@@ -1934,7 +2428,7 @@ var _db;
|
|
|
1934
2428
|
var init_client = __esm({
|
|
1935
2429
|
"../core/dist/storage/sqlite/client.js"() {
|
|
1936
2430
|
"use strict";
|
|
1937
|
-
|
|
2431
|
+
init_constants2();
|
|
1938
2432
|
_db = null;
|
|
1939
2433
|
}
|
|
1940
2434
|
});
|
|
@@ -2896,21 +3390,21 @@ var LocalKanbanProvider;
|
|
|
2896
3390
|
var init_local_kanban = __esm({
|
|
2897
3391
|
"../core/dist/board/providers/local-kanban.js"() {
|
|
2898
3392
|
"use strict";
|
|
2899
|
-
|
|
2900
|
-
|
|
3393
|
+
init_types3();
|
|
3394
|
+
init_constants2();
|
|
2901
3395
|
LocalKanbanProvider = class {
|
|
2902
3396
|
repo;
|
|
2903
3397
|
constructor(repo) {
|
|
2904
3398
|
this.repo = repo;
|
|
2905
3399
|
}
|
|
2906
3400
|
async setupBoard(title) {
|
|
2907
|
-
return { ...
|
|
3401
|
+
return { ...DEFAULT_LOCAL_BOARD_INFO2, title };
|
|
2908
3402
|
}
|
|
2909
3403
|
async getBoard() {
|
|
2910
|
-
return
|
|
3404
|
+
return DEFAULT_LOCAL_BOARD_INFO2;
|
|
2911
3405
|
}
|
|
2912
3406
|
async getColumns() {
|
|
2913
|
-
return
|
|
3407
|
+
return BOARD_COLUMNS2.map((name, i) => ({ id: String(i), name }));
|
|
2914
3408
|
}
|
|
2915
3409
|
async createIssue(input) {
|
|
2916
3410
|
const row = this.repo.create({
|
|
@@ -4491,11 +4985,11 @@ var init_checks = __esm({
|
|
|
4491
4985
|
// ../core/dist/utils/config-writer.js
|
|
4492
4986
|
import * as fs9 from "fs";
|
|
4493
4987
|
import * as path8 from "path";
|
|
4494
|
-
function
|
|
4988
|
+
function isPlainObject2(value) {
|
|
4495
4989
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4496
4990
|
}
|
|
4497
4991
|
function cleanJobProviders(value) {
|
|
4498
|
-
if (!
|
|
4992
|
+
if (!isPlainObject2(value)) {
|
|
4499
4993
|
return {};
|
|
4500
4994
|
}
|
|
4501
4995
|
const entries = [];
|
|
@@ -4519,7 +5013,7 @@ function saveConfig(projectDir, changes) {
|
|
|
4519
5013
|
if (value !== void 0) {
|
|
4520
5014
|
if (key === "jobProviders") {
|
|
4521
5015
|
merged[key] = cleanJobProviders(value);
|
|
4522
|
-
} else if (PARTIAL_MERGE_KEYS.has(key) &&
|
|
5016
|
+
} else if (PARTIAL_MERGE_KEYS.has(key) && isPlainObject2(existing[key]) && isPlainObject2(value)) {
|
|
4523
5017
|
merged[key] = { ...existing[key], ...value };
|
|
4524
5018
|
} else {
|
|
4525
5019
|
merged[key] = value;
|
|
@@ -4535,28 +5029,147 @@ function saveConfig(projectDir, changes) {
|
|
|
4535
5029
|
};
|
|
4536
5030
|
}
|
|
4537
5031
|
}
|
|
4538
|
-
var PARTIAL_MERGE_KEYS;
|
|
4539
|
-
var init_config_writer = __esm({
|
|
4540
|
-
"../core/dist/utils/config-writer.js"() {
|
|
5032
|
+
var PARTIAL_MERGE_KEYS;
|
|
5033
|
+
var init_config_writer = __esm({
|
|
5034
|
+
"../core/dist/utils/config-writer.js"() {
|
|
5035
|
+
"use strict";
|
|
5036
|
+
init_constants();
|
|
5037
|
+
PARTIAL_MERGE_KEYS = /* @__PURE__ */ new Set([
|
|
5038
|
+
"notifications",
|
|
5039
|
+
"qa",
|
|
5040
|
+
"audit",
|
|
5041
|
+
"roadmapScanner",
|
|
5042
|
+
"queue",
|
|
5043
|
+
"providerPresets",
|
|
5044
|
+
"pausedJobs"
|
|
5045
|
+
]);
|
|
5046
|
+
}
|
|
5047
|
+
});
|
|
5048
|
+
|
|
5049
|
+
// ../core/dist/utils/config-path.js
|
|
5050
|
+
import * as fs10 from "fs";
|
|
5051
|
+
import * as path9 from "path";
|
|
5052
|
+
function isPlainObject3(value) {
|
|
5053
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5054
|
+
}
|
|
5055
|
+
function splitConfigPath(dotPath) {
|
|
5056
|
+
const parts = dotPath.split(".").map((part) => part.trim()).filter(Boolean);
|
|
5057
|
+
if (parts.length === 0 || parts.some((part) => part === "__proto__" || part === "constructor")) {
|
|
5058
|
+
throw new Error(`Invalid config path: ${dotPath}`);
|
|
5059
|
+
}
|
|
5060
|
+
return parts;
|
|
5061
|
+
}
|
|
5062
|
+
function readRawConfig(projectDir) {
|
|
5063
|
+
const configPath = path9.join(projectDir, CONFIG_FILE_NAME);
|
|
5064
|
+
if (!fs10.existsSync(configPath)) {
|
|
5065
|
+
return {};
|
|
5066
|
+
}
|
|
5067
|
+
return JSON.parse(fs10.readFileSync(configPath, "utf-8"));
|
|
5068
|
+
}
|
|
5069
|
+
function writeRawConfig(projectDir, rawConfig) {
|
|
5070
|
+
const configPath = path9.join(projectDir, CONFIG_FILE_NAME);
|
|
5071
|
+
fs10.writeFileSync(configPath, `${JSON.stringify(rawConfig, null, 2)}
|
|
5072
|
+
`);
|
|
5073
|
+
}
|
|
5074
|
+
function getValueAtPath(source, parts) {
|
|
5075
|
+
let current = source;
|
|
5076
|
+
for (const part of parts) {
|
|
5077
|
+
if (!isPlainObject3(current) || !(part in current)) {
|
|
5078
|
+
return void 0;
|
|
5079
|
+
}
|
|
5080
|
+
current = current[part];
|
|
5081
|
+
}
|
|
5082
|
+
return current;
|
|
5083
|
+
}
|
|
5084
|
+
function setValueAtPath(target, parts, value) {
|
|
5085
|
+
let current = target;
|
|
5086
|
+
for (const part of parts.slice(0, -1)) {
|
|
5087
|
+
const next = current[part];
|
|
5088
|
+
if (next === void 0) {
|
|
5089
|
+
current[part] = {};
|
|
5090
|
+
} else if (!isPlainObject3(next)) {
|
|
5091
|
+
throw new Error(`Cannot set ${parts.join(".")}: ${part} is not an object`);
|
|
5092
|
+
}
|
|
5093
|
+
current = current[part];
|
|
5094
|
+
}
|
|
5095
|
+
current[parts[parts.length - 1]] = value;
|
|
5096
|
+
}
|
|
5097
|
+
function hasKnownConfigPath(parts) {
|
|
5098
|
+
if (parts[0] === "pausedJobs" && parts.length === 2) {
|
|
5099
|
+
return getValidJobTypes().includes(parts[1]);
|
|
5100
|
+
}
|
|
5101
|
+
const defaults = getDefaultConfig();
|
|
5102
|
+
let current = defaults;
|
|
5103
|
+
for (const part of parts) {
|
|
5104
|
+
if (!isPlainObject3(current) || !(part in current)) {
|
|
5105
|
+
return false;
|
|
5106
|
+
}
|
|
5107
|
+
current = current[part];
|
|
5108
|
+
}
|
|
5109
|
+
return true;
|
|
5110
|
+
}
|
|
5111
|
+
function parseConfigValue(rawValue) {
|
|
5112
|
+
const trimmed = rawValue.trim();
|
|
5113
|
+
if (trimmed === "true")
|
|
5114
|
+
return true;
|
|
5115
|
+
if (trimmed === "false")
|
|
5116
|
+
return false;
|
|
5117
|
+
if (trimmed === "null")
|
|
5118
|
+
return null;
|
|
5119
|
+
if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(trimmed)) {
|
|
5120
|
+
return Number(trimmed);
|
|
5121
|
+
}
|
|
5122
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]") || trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
5123
|
+
return JSON.parse(trimmed);
|
|
5124
|
+
}
|
|
5125
|
+
return rawValue;
|
|
5126
|
+
}
|
|
5127
|
+
function getConfigValue(projectDir, dotPath) {
|
|
5128
|
+
const parts = splitConfigPath(dotPath);
|
|
5129
|
+
if (!hasKnownConfigPath(parts)) {
|
|
5130
|
+
throw new Error(`Unknown config path: ${dotPath}`);
|
|
5131
|
+
}
|
|
5132
|
+
const config = loadConfig(projectDir);
|
|
5133
|
+
return { path: parts.join("."), value: getValueAtPath(config, parts) };
|
|
5134
|
+
}
|
|
5135
|
+
function setConfigValue(projectDir, dotPath, value) {
|
|
5136
|
+
const parts = splitConfigPath(dotPath);
|
|
5137
|
+
if (!hasKnownConfigPath(parts)) {
|
|
5138
|
+
throw new Error(`Unknown config path: ${dotPath}`);
|
|
5139
|
+
}
|
|
5140
|
+
const rawConfig = readRawConfig(projectDir);
|
|
5141
|
+
const originalRawConfig = JSON.parse(JSON.stringify(rawConfig));
|
|
5142
|
+
const currentConfig = loadConfig(projectDir);
|
|
5143
|
+
const previousValue = getValueAtPath(currentConfig, parts);
|
|
5144
|
+
setValueAtPath(rawConfig, parts, value);
|
|
5145
|
+
const result = saveConfig(projectDir, rawConfig);
|
|
5146
|
+
if (!result.success) {
|
|
5147
|
+
throw new Error(`Failed to save config: ${result.error}`);
|
|
5148
|
+
}
|
|
5149
|
+
const reloaded = loadConfig(projectDir);
|
|
5150
|
+
const reloadedValue = getValueAtPath(reloaded, parts);
|
|
5151
|
+
if (JSON.stringify(reloadedValue) !== JSON.stringify(value)) {
|
|
5152
|
+
writeRawConfig(projectDir, originalRawConfig);
|
|
5153
|
+
throw new Error(`Invalid value for config path: ${parts.join(".")}`);
|
|
5154
|
+
}
|
|
5155
|
+
return { path: parts.join("."), previousValue, value: reloadedValue };
|
|
5156
|
+
}
|
|
5157
|
+
var init_config_path = __esm({
|
|
5158
|
+
"../core/dist/utils/config-path.js"() {
|
|
4541
5159
|
"use strict";
|
|
4542
5160
|
init_constants();
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
"audit",
|
|
4547
|
-
"roadmapScanner",
|
|
4548
|
-
"queue",
|
|
4549
|
-
"providerPresets"
|
|
4550
|
-
]);
|
|
5161
|
+
init_config();
|
|
5162
|
+
init_job_registry();
|
|
5163
|
+
init_config_writer();
|
|
4551
5164
|
}
|
|
4552
5165
|
});
|
|
4553
5166
|
|
|
4554
5167
|
// ../core/dist/utils/execution-history.js
|
|
4555
5168
|
import * as os4 from "os";
|
|
4556
|
-
import * as
|
|
5169
|
+
import * as path10 from "path";
|
|
4557
5170
|
function getHistoryPath() {
|
|
4558
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
4559
|
-
return
|
|
5171
|
+
const base = process.env.NIGHT_WATCH_HOME || path10.join(os4.homedir(), GLOBAL_CONFIG_DIR);
|
|
5172
|
+
return path10.join(base, HISTORY_FILE_NAME);
|
|
4560
5173
|
}
|
|
4561
5174
|
function loadHistory() {
|
|
4562
5175
|
const { executionHistory } = getRepositories();
|
|
@@ -4567,7 +5180,7 @@ function saveHistory(history) {
|
|
|
4567
5180
|
executionHistory.replaceAll(history);
|
|
4568
5181
|
}
|
|
4569
5182
|
function recordExecution(projectDir, prdFile, outcome, exitCode, attempt = 1) {
|
|
4570
|
-
const resolved =
|
|
5183
|
+
const resolved = path10.resolve(projectDir);
|
|
4571
5184
|
const { executionHistory } = getRepositories();
|
|
4572
5185
|
const record = {
|
|
4573
5186
|
timestamp: Math.floor(Date.now() / 1e3),
|
|
@@ -4579,7 +5192,7 @@ function recordExecution(projectDir, prdFile, outcome, exitCode, attempt = 1) {
|
|
|
4579
5192
|
executionHistory.trimRecords(resolved, prdFile, MAX_HISTORY_RECORDS_PER_PRD);
|
|
4580
5193
|
}
|
|
4581
5194
|
function getLastExecution(projectDir, prdFile) {
|
|
4582
|
-
const resolved =
|
|
5195
|
+
const resolved = path10.resolve(projectDir);
|
|
4583
5196
|
const { executionHistory } = getRepositories();
|
|
4584
5197
|
const records = executionHistory.getRecords(resolved, prdFile);
|
|
4585
5198
|
return records.length > 0 ? records[0] : null;
|
|
@@ -4880,16 +5493,16 @@ var init_github = __esm({
|
|
|
4880
5493
|
});
|
|
4881
5494
|
|
|
4882
5495
|
// ../core/dist/utils/log-utils.js
|
|
4883
|
-
import * as
|
|
5496
|
+
import * as fs11 from "fs";
|
|
4884
5497
|
function rotateLog(logFile, maxSize = DEFAULT_MAX_LOG_SIZE) {
|
|
4885
|
-
if (!
|
|
5498
|
+
if (!fs11.existsSync(logFile)) {
|
|
4886
5499
|
return false;
|
|
4887
5500
|
}
|
|
4888
5501
|
try {
|
|
4889
|
-
const stats =
|
|
5502
|
+
const stats = fs11.statSync(logFile);
|
|
4890
5503
|
if (stats.size > maxSize) {
|
|
4891
5504
|
const oldPath = `${logFile}.old`;
|
|
4892
|
-
|
|
5505
|
+
fs11.renameSync(logFile, oldPath);
|
|
4893
5506
|
return true;
|
|
4894
5507
|
}
|
|
4895
5508
|
} catch {
|
|
@@ -4897,11 +5510,11 @@ function rotateLog(logFile, maxSize = DEFAULT_MAX_LOG_SIZE) {
|
|
|
4897
5510
|
return false;
|
|
4898
5511
|
}
|
|
4899
5512
|
function checkRateLimited(logFile, startLine) {
|
|
4900
|
-
if (!
|
|
5513
|
+
if (!fs11.existsSync(logFile)) {
|
|
4901
5514
|
return false;
|
|
4902
5515
|
}
|
|
4903
5516
|
try {
|
|
4904
|
-
const content =
|
|
5517
|
+
const content = fs11.readFileSync(logFile, "utf-8");
|
|
4905
5518
|
const lines = content.split("\n");
|
|
4906
5519
|
let linesToCheck;
|
|
4907
5520
|
if (startLine !== void 0 && startLine > 0) {
|
|
@@ -4922,18 +5535,18 @@ var init_log_utils = __esm({
|
|
|
4922
5535
|
});
|
|
4923
5536
|
|
|
4924
5537
|
// ../core/dist/utils/global-config.js
|
|
4925
|
-
import * as
|
|
5538
|
+
import * as fs12 from "fs";
|
|
4926
5539
|
import * as os5 from "os";
|
|
4927
|
-
import * as
|
|
5540
|
+
import * as path11 from "path";
|
|
4928
5541
|
function getGlobalNotificationsPath() {
|
|
4929
|
-
return
|
|
5542
|
+
return path11.join(os5.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_NOTIFICATIONS_FILE_NAME);
|
|
4930
5543
|
}
|
|
4931
5544
|
function loadGlobalNotificationsConfig() {
|
|
4932
5545
|
const filePath = getGlobalNotificationsPath();
|
|
4933
5546
|
try {
|
|
4934
|
-
if (!
|
|
5547
|
+
if (!fs12.existsSync(filePath))
|
|
4935
5548
|
return { webhook: null };
|
|
4936
|
-
const raw =
|
|
5549
|
+
const raw = fs12.readFileSync(filePath, "utf-8");
|
|
4937
5550
|
return JSON.parse(raw);
|
|
4938
5551
|
} catch {
|
|
4939
5552
|
return { webhook: null };
|
|
@@ -4941,8 +5554,8 @@ function loadGlobalNotificationsConfig() {
|
|
|
4941
5554
|
}
|
|
4942
5555
|
function saveGlobalNotificationsConfig(config) {
|
|
4943
5556
|
const filePath = getGlobalNotificationsPath();
|
|
4944
|
-
|
|
4945
|
-
|
|
5557
|
+
fs12.mkdirSync(path11.dirname(filePath), { recursive: true });
|
|
5558
|
+
fs12.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4946
5559
|
}
|
|
4947
5560
|
var init_global_config = __esm({
|
|
4948
5561
|
"../core/dist/utils/global-config.js"() {
|
|
@@ -5415,15 +6028,15 @@ var init_prd_discovery = __esm({
|
|
|
5415
6028
|
});
|
|
5416
6029
|
|
|
5417
6030
|
// ../core/dist/utils/prd-utils.js
|
|
5418
|
-
import * as
|
|
5419
|
-
import * as
|
|
6031
|
+
import * as fs13 from "fs";
|
|
6032
|
+
import * as path12 from "path";
|
|
5420
6033
|
function slugify(name) {
|
|
5421
6034
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
5422
6035
|
}
|
|
5423
6036
|
function getNextPrdNumber(prdDir) {
|
|
5424
|
-
if (!
|
|
6037
|
+
if (!fs13.existsSync(prdDir))
|
|
5425
6038
|
return 1;
|
|
5426
|
-
const files =
|
|
6039
|
+
const files = fs13.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
5427
6040
|
const numbers = files.map((f) => {
|
|
5428
6041
|
const match = f.match(/^(\d+)-/);
|
|
5429
6042
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -5431,16 +6044,16 @@ function getNextPrdNumber(prdDir) {
|
|
|
5431
6044
|
return Math.max(0, ...numbers) + 1;
|
|
5432
6045
|
}
|
|
5433
6046
|
function markPrdDone(prdDir, prdFile) {
|
|
5434
|
-
const sourcePath =
|
|
5435
|
-
if (!
|
|
6047
|
+
const sourcePath = path12.join(prdDir, prdFile);
|
|
6048
|
+
if (!fs13.existsSync(sourcePath)) {
|
|
5436
6049
|
return false;
|
|
5437
6050
|
}
|
|
5438
|
-
const doneDir =
|
|
5439
|
-
if (!
|
|
5440
|
-
|
|
6051
|
+
const doneDir = path12.join(prdDir, "done");
|
|
6052
|
+
if (!fs13.existsSync(doneDir)) {
|
|
6053
|
+
fs13.mkdirSync(doneDir, { recursive: true });
|
|
5441
6054
|
}
|
|
5442
|
-
const destPath =
|
|
5443
|
-
|
|
6055
|
+
const destPath = path12.join(doneDir, prdFile);
|
|
6056
|
+
fs13.renameSync(sourcePath, destPath);
|
|
5444
6057
|
return true;
|
|
5445
6058
|
}
|
|
5446
6059
|
var init_prd_utils = __esm({
|
|
@@ -5450,16 +6063,16 @@ var init_prd_utils = __esm({
|
|
|
5450
6063
|
});
|
|
5451
6064
|
|
|
5452
6065
|
// ../core/dist/utils/registry.js
|
|
5453
|
-
import * as
|
|
6066
|
+
import * as fs14 from "fs";
|
|
5454
6067
|
import * as os6 from "os";
|
|
5455
|
-
import * as
|
|
6068
|
+
import * as path13 from "path";
|
|
5456
6069
|
function readLegacyRegistryEntries() {
|
|
5457
6070
|
const registryPath = getRegistryPath();
|
|
5458
|
-
if (!
|
|
6071
|
+
if (!fs14.existsSync(registryPath)) {
|
|
5459
6072
|
return [];
|
|
5460
6073
|
}
|
|
5461
6074
|
try {
|
|
5462
|
-
const raw =
|
|
6075
|
+
const raw = fs14.readFileSync(registryPath, "utf-8");
|
|
5463
6076
|
const parsed = JSON.parse(raw);
|
|
5464
6077
|
if (!Array.isArray(parsed)) {
|
|
5465
6078
|
return [];
|
|
@@ -5494,8 +6107,8 @@ function loadRegistryEntriesWithLegacyFallback() {
|
|
|
5494
6107
|
return projectRegistry.getAll();
|
|
5495
6108
|
}
|
|
5496
6109
|
function getRegistryPath() {
|
|
5497
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
5498
|
-
return
|
|
6110
|
+
const base = process.env.NIGHT_WATCH_HOME || path13.join(os6.homedir(), GLOBAL_CONFIG_DIR);
|
|
6111
|
+
return path13.join(base, REGISTRY_FILE_NAME);
|
|
5499
6112
|
}
|
|
5500
6113
|
function loadRegistry() {
|
|
5501
6114
|
return loadRegistryEntriesWithLegacyFallback();
|
|
@@ -5508,7 +6121,7 @@ function saveRegistry(entries) {
|
|
|
5508
6121
|
}
|
|
5509
6122
|
}
|
|
5510
6123
|
function registerProject(projectDir) {
|
|
5511
|
-
const resolvedPath =
|
|
6124
|
+
const resolvedPath = path13.resolve(projectDir);
|
|
5512
6125
|
const { projectRegistry } = getRepositories();
|
|
5513
6126
|
const entries = loadRegistryEntriesWithLegacyFallback();
|
|
5514
6127
|
const existing = entries.find((e) => e.path === resolvedPath);
|
|
@@ -5517,13 +6130,13 @@ function registerProject(projectDir) {
|
|
|
5517
6130
|
}
|
|
5518
6131
|
const name = getProjectName(resolvedPath);
|
|
5519
6132
|
const nameExists = entries.some((e) => e.name === name);
|
|
5520
|
-
const finalName = nameExists ? `${name}-${
|
|
6133
|
+
const finalName = nameExists ? `${name}-${path13.basename(resolvedPath)}` : name;
|
|
5521
6134
|
const entry = { name: finalName, path: resolvedPath };
|
|
5522
6135
|
projectRegistry.upsert(entry);
|
|
5523
6136
|
return entry;
|
|
5524
6137
|
}
|
|
5525
6138
|
function unregisterProject(projectDir) {
|
|
5526
|
-
const resolvedPath =
|
|
6139
|
+
const resolvedPath = path13.resolve(projectDir);
|
|
5527
6140
|
loadRegistryEntriesWithLegacyFallback();
|
|
5528
6141
|
const { projectRegistry } = getRepositories();
|
|
5529
6142
|
return projectRegistry.remove(resolvedPath);
|
|
@@ -5533,7 +6146,7 @@ function validateRegistry() {
|
|
|
5533
6146
|
const valid = [];
|
|
5534
6147
|
const invalid = [];
|
|
5535
6148
|
for (const entry of entries) {
|
|
5536
|
-
if (
|
|
6149
|
+
if (fs14.existsSync(entry.path) && fs14.existsSync(path13.join(entry.path, CONFIG_FILE_NAME))) {
|
|
5537
6150
|
valid.push(entry);
|
|
5538
6151
|
} else {
|
|
5539
6152
|
invalid.push(entry);
|
|
@@ -5542,7 +6155,7 @@ function validateRegistry() {
|
|
|
5542
6155
|
return { valid, invalid };
|
|
5543
6156
|
}
|
|
5544
6157
|
function pruneProjectData(projectDir) {
|
|
5545
|
-
const resolvedPath =
|
|
6158
|
+
const resolvedPath = path13.resolve(projectDir);
|
|
5546
6159
|
const db = getDb();
|
|
5547
6160
|
db.transaction(() => {
|
|
5548
6161
|
db.prepare("DELETE FROM execution_history WHERE project_path = ?").run(resolvedPath);
|
|
@@ -5553,7 +6166,7 @@ function pruneProjectData(projectDir) {
|
|
|
5553
6166
|
})();
|
|
5554
6167
|
}
|
|
5555
6168
|
function removeProject(projectDir) {
|
|
5556
|
-
const resolvedPath =
|
|
6169
|
+
const resolvedPath = path13.resolve(projectDir);
|
|
5557
6170
|
const projectName = getProjectName(resolvedPath);
|
|
5558
6171
|
const marker = generateMarker(projectName);
|
|
5559
6172
|
const cronEntriesRemoved = removeEntriesForProject(resolvedPath, marker);
|
|
@@ -5742,8 +6355,8 @@ var init_roadmap_parser = __esm({
|
|
|
5742
6355
|
});
|
|
5743
6356
|
|
|
5744
6357
|
// ../core/dist/audit/report.js
|
|
5745
|
-
import * as
|
|
5746
|
-
import * as
|
|
6358
|
+
import * as fs15 from "fs";
|
|
6359
|
+
import * as path14 from "path";
|
|
5747
6360
|
function normalizeAuditSeverity(raw) {
|
|
5748
6361
|
const normalized = raw.trim().toLowerCase();
|
|
5749
6362
|
if (normalized === "critical")
|
|
@@ -5802,11 +6415,11 @@ function parseAuditFindings(reportContent) {
|
|
|
5802
6415
|
return findings;
|
|
5803
6416
|
}
|
|
5804
6417
|
function loadAuditFindings(projectDir) {
|
|
5805
|
-
const reportPath =
|
|
5806
|
-
if (!
|
|
6418
|
+
const reportPath = path14.join(projectDir, "logs", "audit-report.md");
|
|
6419
|
+
if (!fs15.existsSync(reportPath)) {
|
|
5807
6420
|
return [];
|
|
5808
6421
|
}
|
|
5809
|
-
const reportContent =
|
|
6422
|
+
const reportContent = fs15.readFileSync(reportPath, "utf-8");
|
|
5810
6423
|
if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
|
|
5811
6424
|
return [];
|
|
5812
6425
|
}
|
|
@@ -5819,18 +6432,18 @@ var init_report = __esm({
|
|
|
5819
6432
|
});
|
|
5820
6433
|
|
|
5821
6434
|
// ../core/dist/utils/roadmap-state.js
|
|
5822
|
-
import * as
|
|
5823
|
-
import * as
|
|
6435
|
+
import * as fs16 from "fs";
|
|
6436
|
+
import * as path15 from "path";
|
|
5824
6437
|
function getStateFilePath(prdDir) {
|
|
5825
|
-
return
|
|
6438
|
+
return path15.join(prdDir, STATE_FILE_NAME);
|
|
5826
6439
|
}
|
|
5827
6440
|
function readJsonState(prdDir) {
|
|
5828
6441
|
const statePath = getStateFilePath(prdDir);
|
|
5829
|
-
if (!
|
|
6442
|
+
if (!fs16.existsSync(statePath)) {
|
|
5830
6443
|
return null;
|
|
5831
6444
|
}
|
|
5832
6445
|
try {
|
|
5833
|
-
const content =
|
|
6446
|
+
const content = fs16.readFileSync(statePath, "utf-8");
|
|
5834
6447
|
const parsed = JSON.parse(content);
|
|
5835
6448
|
if (typeof parsed !== "object" || parsed === null) {
|
|
5836
6449
|
return null;
|
|
@@ -5868,11 +6481,11 @@ function saveRoadmapState(prdDir, state) {
|
|
|
5868
6481
|
const { roadmapState } = getRepositories();
|
|
5869
6482
|
roadmapState.save(prdDir, state);
|
|
5870
6483
|
const statePath = getStateFilePath(prdDir);
|
|
5871
|
-
const dir =
|
|
5872
|
-
if (!
|
|
5873
|
-
|
|
6484
|
+
const dir = path15.dirname(statePath);
|
|
6485
|
+
if (!fs16.existsSync(dir)) {
|
|
6486
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
5874
6487
|
}
|
|
5875
|
-
|
|
6488
|
+
fs16.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
5876
6489
|
}
|
|
5877
6490
|
function createEmptyState() {
|
|
5878
6491
|
return {
|
|
@@ -5912,21 +6525,21 @@ var init_roadmap_state = __esm({
|
|
|
5912
6525
|
});
|
|
5913
6526
|
|
|
5914
6527
|
// ../core/dist/templates/slicer-prompt.js
|
|
5915
|
-
import * as
|
|
5916
|
-
import * as
|
|
6528
|
+
import * as fs17 from "fs";
|
|
6529
|
+
import * as path16 from "path";
|
|
5917
6530
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5918
6531
|
function loadSlicerTemplate(templateDir) {
|
|
5919
6532
|
if (cachedTemplate) {
|
|
5920
6533
|
return cachedTemplate;
|
|
5921
6534
|
}
|
|
5922
|
-
const candidatePaths = templateDir ? [
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
6535
|
+
const candidatePaths = templateDir ? [path16.join(templateDir, "slicer.md")] : [
|
|
6536
|
+
path16.resolve(__dirname, "slicer.md"),
|
|
6537
|
+
path16.resolve(__dirname, "..", "templates", "slicer.md"),
|
|
6538
|
+
path16.resolve(__dirname, "..", "..", "..", "..", "templates", "slicer.md")
|
|
5926
6539
|
];
|
|
5927
|
-
const templatePath = candidatePaths.find((candidate) =>
|
|
6540
|
+
const templatePath = candidatePaths.find((candidate) => fs17.existsSync(candidate)) ?? candidatePaths[0];
|
|
5928
6541
|
try {
|
|
5929
|
-
cachedTemplate =
|
|
6542
|
+
cachedTemplate = fs17.readFileSync(templatePath, "utf-8");
|
|
5930
6543
|
return cachedTemplate;
|
|
5931
6544
|
} catch (error2) {
|
|
5932
6545
|
console.warn(`Warning: Could not load slicer template from ${templatePath}, using default:`, error2 instanceof Error ? error2.message : String(error2));
|
|
@@ -5951,7 +6564,7 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
|
|
|
5951
6564
|
title,
|
|
5952
6565
|
section,
|
|
5953
6566
|
description: description || "(No description provided)",
|
|
5954
|
-
outputFilePath:
|
|
6567
|
+
outputFilePath: path16.join(prdDir, prdFilename),
|
|
5955
6568
|
prdDir
|
|
5956
6569
|
};
|
|
5957
6570
|
}
|
|
@@ -5960,7 +6573,7 @@ var init_slicer_prompt = __esm({
|
|
|
5960
6573
|
"../core/dist/templates/slicer-prompt.js"() {
|
|
5961
6574
|
"use strict";
|
|
5962
6575
|
__filename = fileURLToPath2(import.meta.url);
|
|
5963
|
-
__dirname =
|
|
6576
|
+
__dirname = path16.dirname(__filename);
|
|
5964
6577
|
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.
|
|
5965
6578
|
|
|
5966
6579
|
When this activates: \`Planning Mode: Principal Architect\`
|
|
@@ -6093,8 +6706,8 @@ DO NOT forget to write the file.
|
|
|
6093
6706
|
});
|
|
6094
6707
|
|
|
6095
6708
|
// ../core/dist/utils/roadmap-scanner.js
|
|
6096
|
-
import * as
|
|
6097
|
-
import * as
|
|
6709
|
+
import * as fs18 from "fs";
|
|
6710
|
+
import * as path17 from "path";
|
|
6098
6711
|
import { spawn } from "child_process";
|
|
6099
6712
|
import { createHash as createHash3 } from "crypto";
|
|
6100
6713
|
function auditSeverityRank(severity) {
|
|
@@ -6145,9 +6758,9 @@ function collectAuditPlannerItems(projectDir) {
|
|
|
6145
6758
|
return findings.map(auditFindingToRoadmapItem);
|
|
6146
6759
|
}
|
|
6147
6760
|
function getRoadmapStatus(projectDir, config) {
|
|
6148
|
-
const roadmapPath =
|
|
6761
|
+
const roadmapPath = path17.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
6149
6762
|
const scannerEnabled = config.roadmapScanner.enabled;
|
|
6150
|
-
if (!
|
|
6763
|
+
if (!fs18.existsSync(roadmapPath)) {
|
|
6151
6764
|
return {
|
|
6152
6765
|
found: false,
|
|
6153
6766
|
enabled: scannerEnabled,
|
|
@@ -6158,9 +6771,9 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
6158
6771
|
items: []
|
|
6159
6772
|
};
|
|
6160
6773
|
}
|
|
6161
|
-
const content =
|
|
6774
|
+
const content = fs18.readFileSync(roadmapPath, "utf-8");
|
|
6162
6775
|
const items = parseRoadmap(content);
|
|
6163
|
-
const prdDir =
|
|
6776
|
+
const prdDir = path17.join(projectDir, config.prdDir);
|
|
6164
6777
|
const state = loadRoadmapState(prdDir);
|
|
6165
6778
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
6166
6779
|
const statusItems = items.map((item) => {
|
|
@@ -6197,10 +6810,10 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
6197
6810
|
}
|
|
6198
6811
|
function scanExistingPrdSlugs(prdDir) {
|
|
6199
6812
|
const slugs = /* @__PURE__ */ new Set();
|
|
6200
|
-
if (!
|
|
6813
|
+
if (!fs18.existsSync(prdDir)) {
|
|
6201
6814
|
return slugs;
|
|
6202
6815
|
}
|
|
6203
|
-
const files =
|
|
6816
|
+
const files = fs18.readdirSync(prdDir);
|
|
6204
6817
|
for (const file of files) {
|
|
6205
6818
|
if (!file.endsWith(".md")) {
|
|
6206
6819
|
continue;
|
|
@@ -6252,21 +6865,21 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
6252
6865
|
const nextNum = getNextPrdNumber(prdDir);
|
|
6253
6866
|
const padded = String(nextNum).padStart(2, "0");
|
|
6254
6867
|
const filename = `${padded}-${itemSlug}.md`;
|
|
6255
|
-
const filePath =
|
|
6256
|
-
if (!
|
|
6257
|
-
|
|
6868
|
+
const filePath = path17.join(prdDir, filename);
|
|
6869
|
+
if (!fs18.existsSync(prdDir)) {
|
|
6870
|
+
fs18.mkdirSync(prdDir, { recursive: true });
|
|
6258
6871
|
}
|
|
6259
6872
|
const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
|
|
6260
6873
|
const prompt = renderSlicerPrompt(promptVars);
|
|
6261
6874
|
const providerId = resolveJobProvider(config, "slicer");
|
|
6262
6875
|
const preset = resolvePreset(config, providerId);
|
|
6263
6876
|
const providerArgs = buildProviderArgs(preset, prompt, projectDir);
|
|
6264
|
-
const logDir =
|
|
6265
|
-
if (!
|
|
6266
|
-
|
|
6877
|
+
const logDir = path17.join(projectDir, "logs");
|
|
6878
|
+
if (!fs18.existsSync(logDir)) {
|
|
6879
|
+
fs18.mkdirSync(logDir, { recursive: true });
|
|
6267
6880
|
}
|
|
6268
|
-
const logFile =
|
|
6269
|
-
const logStream =
|
|
6881
|
+
const logFile = path17.join(logDir, `slicer-${itemSlug}.log`);
|
|
6882
|
+
const logStream = fs18.createWriteStream(logFile, { flags: "w" });
|
|
6270
6883
|
logStream.on("error", () => {
|
|
6271
6884
|
});
|
|
6272
6885
|
return new Promise((resolve9) => {
|
|
@@ -6304,7 +6917,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
6304
6917
|
});
|
|
6305
6918
|
return;
|
|
6306
6919
|
}
|
|
6307
|
-
if (!
|
|
6920
|
+
if (!fs18.existsSync(filePath)) {
|
|
6308
6921
|
resolve9({
|
|
6309
6922
|
sliced: false,
|
|
6310
6923
|
error: `Provider did not create expected file: ${filePath}`,
|
|
@@ -6327,23 +6940,23 @@ async function sliceNextItem(projectDir, config) {
|
|
|
6327
6940
|
error: "Roadmap scanner is disabled"
|
|
6328
6941
|
};
|
|
6329
6942
|
}
|
|
6330
|
-
const roadmapPath =
|
|
6943
|
+
const roadmapPath = path17.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
6331
6944
|
const auditItems = collectAuditPlannerItems(projectDir);
|
|
6332
|
-
const roadmapExists =
|
|
6945
|
+
const roadmapExists = fs18.existsSync(roadmapPath);
|
|
6333
6946
|
if (!roadmapExists && auditItems.length === 0) {
|
|
6334
6947
|
return {
|
|
6335
6948
|
sliced: false,
|
|
6336
6949
|
error: "No pending items to process"
|
|
6337
6950
|
};
|
|
6338
6951
|
}
|
|
6339
|
-
const roadmapItems = roadmapExists ? parseRoadmap(
|
|
6952
|
+
const roadmapItems = roadmapExists ? parseRoadmap(fs18.readFileSync(roadmapPath, "utf-8")) : [];
|
|
6340
6953
|
if (roadmapExists && roadmapItems.length === 0 && auditItems.length === 0) {
|
|
6341
6954
|
return {
|
|
6342
6955
|
sliced: false,
|
|
6343
6956
|
error: "No items in roadmap"
|
|
6344
6957
|
};
|
|
6345
6958
|
}
|
|
6346
|
-
const prdDir =
|
|
6959
|
+
const prdDir = path17.join(projectDir, config.prdDir);
|
|
6347
6960
|
const state = loadRoadmapState(prdDir);
|
|
6348
6961
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
6349
6962
|
const pickEligibleItem = (items) => {
|
|
@@ -6515,8 +7128,8 @@ var init_shell = __esm({
|
|
|
6515
7128
|
});
|
|
6516
7129
|
|
|
6517
7130
|
// ../core/dist/utils/scheduling.js
|
|
6518
|
-
import * as
|
|
6519
|
-
import * as
|
|
7131
|
+
import * as fs19 from "fs";
|
|
7132
|
+
import * as path18 from "path";
|
|
6520
7133
|
function normalizeSchedulingPriority(priority) {
|
|
6521
7134
|
if (!Number.isFinite(priority)) {
|
|
6522
7135
|
return DEFAULT_SCHEDULING_PRIORITY;
|
|
@@ -6551,7 +7164,7 @@ function getJobSchedule(config, jobType) {
|
|
|
6551
7164
|
}
|
|
6552
7165
|
}
|
|
6553
7166
|
function loadPeerConfig(projectPath) {
|
|
6554
|
-
if (!
|
|
7167
|
+
if (!fs19.existsSync(projectPath) || !fs19.existsSync(path18.join(projectPath, CONFIG_FILE_NAME))) {
|
|
6555
7168
|
return null;
|
|
6556
7169
|
}
|
|
6557
7170
|
try {
|
|
@@ -6562,10 +7175,10 @@ function loadPeerConfig(projectPath) {
|
|
|
6562
7175
|
}
|
|
6563
7176
|
function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
6564
7177
|
const peers = /* @__PURE__ */ new Map();
|
|
6565
|
-
const currentPath =
|
|
7178
|
+
const currentPath = path18.resolve(currentProjectDir);
|
|
6566
7179
|
const currentSchedule = getJobSchedule(currentConfig, jobType);
|
|
6567
7180
|
const addPeer = (projectPath, config) => {
|
|
6568
|
-
const resolvedPath =
|
|
7181
|
+
const resolvedPath = path18.resolve(projectPath);
|
|
6569
7182
|
if (!isJobTypeEnabled(config, jobType)) {
|
|
6570
7183
|
return;
|
|
6571
7184
|
}
|
|
@@ -6576,12 +7189,12 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
6576
7189
|
path: resolvedPath,
|
|
6577
7190
|
config,
|
|
6578
7191
|
schedulingPriority: normalizeSchedulingPriority(config.schedulingPriority),
|
|
6579
|
-
sortKey: `${
|
|
7192
|
+
sortKey: `${path18.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
|
|
6580
7193
|
});
|
|
6581
7194
|
};
|
|
6582
7195
|
addPeer(currentPath, currentConfig);
|
|
6583
7196
|
for (const entry of loadRegistry()) {
|
|
6584
|
-
const resolvedPath =
|
|
7197
|
+
const resolvedPath = path18.resolve(entry.path);
|
|
6585
7198
|
if (resolvedPath === currentPath || peers.has(resolvedPath)) {
|
|
6586
7199
|
continue;
|
|
6587
7200
|
}
|
|
@@ -6599,7 +7212,7 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
6599
7212
|
}
|
|
6600
7213
|
function getSchedulingPlan(projectDir, config, jobType) {
|
|
6601
7214
|
const peers = collectSchedulingPeers(projectDir, config, jobType);
|
|
6602
|
-
const currentPath =
|
|
7215
|
+
const currentPath = path18.resolve(projectDir);
|
|
6603
7216
|
const slotIndex = Math.max(0, peers.findIndex((peer) => peer.path === currentPath));
|
|
6604
7217
|
const peerCount = Math.max(1, peers.length);
|
|
6605
7218
|
const balancedDelayMinutes = peerCount <= 1 ? 0 : Math.floor(slotIndex * 60 / peerCount);
|
|
@@ -6691,8 +7304,8 @@ var init_webhook_validator = __esm({
|
|
|
6691
7304
|
|
|
6692
7305
|
// ../core/dist/utils/worktree-manager.js
|
|
6693
7306
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
6694
|
-
import * as
|
|
6695
|
-
import * as
|
|
7307
|
+
import * as fs20 from "fs";
|
|
7308
|
+
import * as path19 from "path";
|
|
6696
7309
|
function gitExec(args, cwd, logFile) {
|
|
6697
7310
|
try {
|
|
6698
7311
|
const result = execFileSync4("git", args, {
|
|
@@ -6702,7 +7315,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
6702
7315
|
});
|
|
6703
7316
|
if (logFile && result) {
|
|
6704
7317
|
try {
|
|
6705
|
-
|
|
7318
|
+
fs20.appendFileSync(logFile, result);
|
|
6706
7319
|
} catch {
|
|
6707
7320
|
}
|
|
6708
7321
|
}
|
|
@@ -6711,7 +7324,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
6711
7324
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
6712
7325
|
if (logFile) {
|
|
6713
7326
|
try {
|
|
6714
|
-
|
|
7327
|
+
fs20.appendFileSync(logFile, errorMessage + "\n");
|
|
6715
7328
|
} catch {
|
|
6716
7329
|
}
|
|
6717
7330
|
}
|
|
@@ -6736,11 +7349,11 @@ function branchExistsRemotely(projectDir, branchName) {
|
|
|
6736
7349
|
}
|
|
6737
7350
|
function prepareBranchWorktree(options) {
|
|
6738
7351
|
const { projectDir, worktreeDir, branchName, defaultBranch, logFile } = options;
|
|
6739
|
-
if (
|
|
7352
|
+
if (fs20.existsSync(worktreeDir)) {
|
|
6740
7353
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
6741
7354
|
if (!isRegistered) {
|
|
6742
7355
|
try {
|
|
6743
|
-
|
|
7356
|
+
fs20.rmSync(worktreeDir, { recursive: true, force: true });
|
|
6744
7357
|
} catch {
|
|
6745
7358
|
}
|
|
6746
7359
|
}
|
|
@@ -6779,11 +7392,11 @@ function prepareBranchWorktree(options) {
|
|
|
6779
7392
|
}
|
|
6780
7393
|
function prepareDetachedWorktree(options) {
|
|
6781
7394
|
const { projectDir, worktreeDir, defaultBranch, logFile } = options;
|
|
6782
|
-
if (
|
|
7395
|
+
if (fs20.existsSync(worktreeDir)) {
|
|
6783
7396
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
6784
7397
|
if (!isRegistered) {
|
|
6785
7398
|
try {
|
|
6786
|
-
|
|
7399
|
+
fs20.rmSync(worktreeDir, { recursive: true, force: true });
|
|
6787
7400
|
} catch {
|
|
6788
7401
|
}
|
|
6789
7402
|
}
|
|
@@ -6825,7 +7438,7 @@ function isWorktreeRegistered(projectDir, worktreePath) {
|
|
|
6825
7438
|
}
|
|
6826
7439
|
}
|
|
6827
7440
|
function cleanupWorktrees(projectDir, scope) {
|
|
6828
|
-
const projectName =
|
|
7441
|
+
const projectName = path19.basename(projectDir);
|
|
6829
7442
|
const matchToken = scope ? scope : `${projectName}-nw`;
|
|
6830
7443
|
const removed = [];
|
|
6831
7444
|
try {
|
|
@@ -6860,13 +7473,13 @@ var init_worktree_manager = __esm({
|
|
|
6860
7473
|
});
|
|
6861
7474
|
|
|
6862
7475
|
// ../core/dist/utils/job-queue.js
|
|
6863
|
-
import * as
|
|
7476
|
+
import * as fs21 from "fs";
|
|
6864
7477
|
import * as os7 from "os";
|
|
6865
|
-
import * as
|
|
7478
|
+
import * as path20 from "path";
|
|
6866
7479
|
import Database7 from "better-sqlite3";
|
|
6867
7480
|
function getStateDbPath() {
|
|
6868
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
6869
|
-
return
|
|
7481
|
+
const base = process.env.NIGHT_WATCH_HOME || path20.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
7482
|
+
return path20.join(base, STATE_DB_FILE_NAME);
|
|
6870
7483
|
}
|
|
6871
7484
|
function openDb() {
|
|
6872
7485
|
const dbPath = getStateDbPath();
|
|
@@ -6918,7 +7531,7 @@ function getLockPathForJob(projectPath, jobType) {
|
|
|
6918
7531
|
}
|
|
6919
7532
|
function isRunningEntryStale(entry) {
|
|
6920
7533
|
const lockPath = getLockPathForJob(entry.projectPath, entry.jobType);
|
|
6921
|
-
if (
|
|
7534
|
+
if (fs21.existsSync(lockPath)) {
|
|
6922
7535
|
const lockInfo = checkLockFile(lockPath);
|
|
6923
7536
|
return { isStale: !lockInfo.running, lockPid: lockInfo.pid };
|
|
6924
7537
|
}
|
|
@@ -7640,9 +8253,9 @@ var init_amplitude_client = __esm({
|
|
|
7640
8253
|
});
|
|
7641
8254
|
|
|
7642
8255
|
// ../core/dist/analytics/analytics-runner.js
|
|
7643
|
-
import * as
|
|
8256
|
+
import * as fs22 from "fs";
|
|
7644
8257
|
import * as os8 from "os";
|
|
7645
|
-
import * as
|
|
8258
|
+
import * as path21 from "path";
|
|
7646
8259
|
function parseIssuesFromResponse(text) {
|
|
7647
8260
|
const start = text.indexOf("[");
|
|
7648
8261
|
const end = text.lastIndexOf("]");
|
|
@@ -7671,9 +8284,9 @@ async function runAnalytics(config, projectDir) {
|
|
|
7671
8284
|
|
|
7672
8285
|
--- AMPLITUDE DATA ---
|
|
7673
8286
|
${JSON.stringify(data, null, 2)}`;
|
|
7674
|
-
const tmpDir =
|
|
7675
|
-
const promptFile =
|
|
7676
|
-
|
|
8287
|
+
const tmpDir = fs22.mkdtempSync(path21.join(os8.tmpdir(), "nw-analytics-"));
|
|
8288
|
+
const promptFile = path21.join(tmpDir, "analytics-prompt.md");
|
|
8289
|
+
fs22.writeFileSync(promptFile, prompt, "utf-8");
|
|
7677
8290
|
try {
|
|
7678
8291
|
const provider = resolveJobProvider(config, "analytics");
|
|
7679
8292
|
let providerCmd = PROVIDER_COMMANDS[provider];
|
|
@@ -7694,8 +8307,8 @@ set -euo pipefail
|
|
|
7694
8307
|
${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
|
|
7695
8308
|
`;
|
|
7696
8309
|
}
|
|
7697
|
-
const scriptFile =
|
|
7698
|
-
|
|
8310
|
+
const scriptFile = path21.join(tmpDir, "run-analytics.sh");
|
|
8311
|
+
fs22.writeFileSync(scriptFile, scriptContent, { mode: 493 });
|
|
7699
8312
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], config.providerEnv ?? {});
|
|
7700
8313
|
if (exitCode !== 0) {
|
|
7701
8314
|
throw new Error(`AI provider exited with code ${exitCode}: ${stderr || stdout}`);
|
|
@@ -7742,7 +8355,7 @@ ${stderr}`;
|
|
|
7742
8355
|
};
|
|
7743
8356
|
} finally {
|
|
7744
8357
|
try {
|
|
7745
|
-
|
|
8358
|
+
fs22.rmSync(tmpDir, { recursive: true, force: true });
|
|
7746
8359
|
} catch {
|
|
7747
8360
|
}
|
|
7748
8361
|
}
|
|
@@ -8200,6 +8813,9 @@ __export(dist_exports, {
|
|
|
8200
8813
|
DEFAULT_SLICER_SCHEDULE: () => DEFAULT_SLICER_SCHEDULE,
|
|
8201
8814
|
DEFAULT_SUMMARY_WINDOW_HOURS: () => DEFAULT_SUMMARY_WINDOW_HOURS,
|
|
8202
8815
|
DEFAULT_TEMPLATES_DIR: () => DEFAULT_TEMPLATES_DIR,
|
|
8816
|
+
DEFAULT_WEBHOOK_TRIGGERS: () => DEFAULT_WEBHOOK_TRIGGERS,
|
|
8817
|
+
DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS: () => DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS,
|
|
8818
|
+
DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV: () => DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV,
|
|
8203
8819
|
EXECUTOR_LOG_FILE: () => EXECUTOR_LOG_FILE,
|
|
8204
8820
|
EXECUTOR_LOG_NAME: () => EXECUTOR_LOG_NAME,
|
|
8205
8821
|
EXECUTOR_PARTIAL_LABEL: () => EXECUTOR_PARTIAL_LABEL,
|
|
@@ -8314,6 +8930,7 @@ __export(dist_exports, {
|
|
|
8314
8930
|
generateMarker: () => generateMarker,
|
|
8315
8931
|
getAllJobDefs: () => getAllJobDefs,
|
|
8316
8932
|
getBranchTipTimestamp: () => getBranchTipTimestamp,
|
|
8933
|
+
getConfigValue: () => getConfigValue,
|
|
8317
8934
|
getCrontabInfo: () => getCrontabInfo,
|
|
8318
8935
|
getDb: () => getDb,
|
|
8319
8936
|
getDbPath: () => getDbPath,
|
|
@@ -8390,6 +9007,7 @@ __export(dist_exports, {
|
|
|
8390
9007
|
normalizeJobConfig: () => normalizeJobConfig,
|
|
8391
9008
|
normalizeSchedulingPriority: () => normalizeSchedulingPriority,
|
|
8392
9009
|
parseAuditFindings: () => parseAuditFindings,
|
|
9010
|
+
parseConfigValue: () => parseConfigValue,
|
|
8393
9011
|
parsePrdDependencies: () => parsePrdDependencies,
|
|
8394
9012
|
parseRoadmap: () => parseRoadmap,
|
|
8395
9013
|
parseScriptResult: () => parseScriptResult,
|
|
@@ -8432,6 +9050,7 @@ __export(dist_exports, {
|
|
|
8432
9050
|
scanRoadmap: () => scanRoadmap,
|
|
8433
9051
|
sendNotifications: () => sendNotifications,
|
|
8434
9052
|
sendWebhook: () => sendWebhook,
|
|
9053
|
+
setConfigValue: () => setConfigValue,
|
|
8435
9054
|
sleep: () => sleep,
|
|
8436
9055
|
sliceNextItem: () => sliceNextItem,
|
|
8437
9056
|
sliceRoadmapItem: () => sliceRoadmapItem,
|
|
@@ -8471,6 +9090,7 @@ var init_dist = __esm({
|
|
|
8471
9090
|
init_cancel();
|
|
8472
9091
|
init_checks();
|
|
8473
9092
|
init_config_writer();
|
|
9093
|
+
init_config_path();
|
|
8474
9094
|
init_crontab();
|
|
8475
9095
|
init_execution_history();
|
|
8476
9096
|
init_git_utils();
|
|
@@ -8506,30 +9126,30 @@ var init_dist = __esm({
|
|
|
8506
9126
|
// src/cli.ts
|
|
8507
9127
|
import "reflect-metadata";
|
|
8508
9128
|
import { Command as Command3 } from "commander";
|
|
8509
|
-
import { existsSync as
|
|
9129
|
+
import { existsSync as existsSync34, readFileSync as readFileSync21 } from "fs";
|
|
8510
9130
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
8511
|
-
import { dirname as dirname12, join as
|
|
9131
|
+
import { dirname as dirname12, join as join38 } from "path";
|
|
8512
9132
|
|
|
8513
9133
|
// src/commands/init.ts
|
|
8514
9134
|
init_dist();
|
|
8515
|
-
import
|
|
8516
|
-
import
|
|
9135
|
+
import fs23 from "fs";
|
|
9136
|
+
import path22 from "path";
|
|
8517
9137
|
import { execSync as execSync3 } from "child_process";
|
|
8518
9138
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8519
|
-
import { dirname as dirname6, join as
|
|
9139
|
+
import { dirname as dirname6, join as join20 } from "path";
|
|
8520
9140
|
import * as readline from "readline";
|
|
8521
9141
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
8522
9142
|
var __dirname2 = dirname6(__filename2);
|
|
8523
9143
|
function findTemplatesDir(startDir) {
|
|
8524
9144
|
let d = startDir;
|
|
8525
9145
|
for (let i = 0; i < 8; i++) {
|
|
8526
|
-
const candidate =
|
|
8527
|
-
if (
|
|
9146
|
+
const candidate = join20(d, "templates");
|
|
9147
|
+
if (fs23.existsSync(candidate) && fs23.statSync(candidate).isDirectory()) {
|
|
8528
9148
|
return candidate;
|
|
8529
9149
|
}
|
|
8530
9150
|
d = dirname6(d);
|
|
8531
9151
|
}
|
|
8532
|
-
return
|
|
9152
|
+
return join20(startDir, "templates");
|
|
8533
9153
|
}
|
|
8534
9154
|
var TEMPLATES_DIR = findTemplatesDir(__dirname2);
|
|
8535
9155
|
var NW_SKILLS = [
|
|
@@ -8541,12 +9161,12 @@ var NW_SKILLS = [
|
|
|
8541
9161
|
"nw-review"
|
|
8542
9162
|
];
|
|
8543
9163
|
function hasPlaywrightDependency(cwd) {
|
|
8544
|
-
const packageJsonPath =
|
|
8545
|
-
if (!
|
|
9164
|
+
const packageJsonPath = path22.join(cwd, "package.json");
|
|
9165
|
+
if (!fs23.existsSync(packageJsonPath)) {
|
|
8546
9166
|
return false;
|
|
8547
9167
|
}
|
|
8548
9168
|
try {
|
|
8549
|
-
const packageJson2 = JSON.parse(
|
|
9169
|
+
const packageJson2 = JSON.parse(fs23.readFileSync(packageJsonPath, "utf-8"));
|
|
8550
9170
|
return Boolean(
|
|
8551
9171
|
packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
|
|
8552
9172
|
);
|
|
@@ -8558,7 +9178,7 @@ function detectPlaywright(cwd) {
|
|
|
8558
9178
|
if (hasPlaywrightDependency(cwd)) {
|
|
8559
9179
|
return true;
|
|
8560
9180
|
}
|
|
8561
|
-
if (
|
|
9181
|
+
if (fs23.existsSync(path22.join(cwd, "node_modules", ".bin", "playwright"))) {
|
|
8562
9182
|
return true;
|
|
8563
9183
|
}
|
|
8564
9184
|
try {
|
|
@@ -8574,10 +9194,10 @@ function detectPlaywright(cwd) {
|
|
|
8574
9194
|
}
|
|
8575
9195
|
}
|
|
8576
9196
|
function resolvePlaywrightInstallCommand(cwd) {
|
|
8577
|
-
if (
|
|
9197
|
+
if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) {
|
|
8578
9198
|
return "pnpm add -D @playwright/test";
|
|
8579
9199
|
}
|
|
8580
|
-
if (
|
|
9200
|
+
if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) {
|
|
8581
9201
|
return "yarn add -D @playwright/test";
|
|
8582
9202
|
}
|
|
8583
9203
|
return "npm install -D @playwright/test";
|
|
@@ -8723,8 +9343,8 @@ function promptProviderSelection(providers) {
|
|
|
8723
9343
|
});
|
|
8724
9344
|
}
|
|
8725
9345
|
function ensureDir(dirPath) {
|
|
8726
|
-
if (!
|
|
8727
|
-
|
|
9346
|
+
if (!fs23.existsSync(dirPath)) {
|
|
9347
|
+
fs23.mkdirSync(dirPath, { recursive: true });
|
|
8728
9348
|
}
|
|
8729
9349
|
}
|
|
8730
9350
|
function buildInitConfig(params) {
|
|
@@ -8780,35 +9400,47 @@ function buildInitConfig(params) {
|
|
|
8780
9400
|
queue: {
|
|
8781
9401
|
...defaults.queue,
|
|
8782
9402
|
priority: { ...defaults.queue.priority }
|
|
9403
|
+
},
|
|
9404
|
+
webhookTriggers: {
|
|
9405
|
+
...defaults.webhookTriggers,
|
|
9406
|
+
allowedJobIds: [...defaults.webhookTriggers.allowedJobIds],
|
|
9407
|
+
github: {
|
|
9408
|
+
...defaults.webhookTriggers.github,
|
|
9409
|
+
events: [...defaults.webhookTriggers.github.events],
|
|
9410
|
+
rules: defaults.webhookTriggers.github.rules.map((rule) => ({
|
|
9411
|
+
...rule,
|
|
9412
|
+
branchPatterns: rule.branchPatterns ? [...rule.branchPatterns] : void 0
|
|
9413
|
+
}))
|
|
9414
|
+
}
|
|
8783
9415
|
}
|
|
8784
9416
|
};
|
|
8785
9417
|
}
|
|
8786
9418
|
function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
|
|
8787
9419
|
if (customTemplatesDir !== null) {
|
|
8788
|
-
const customPath =
|
|
8789
|
-
if (
|
|
9420
|
+
const customPath = join20(customTemplatesDir, templateName);
|
|
9421
|
+
if (fs23.existsSync(customPath)) {
|
|
8790
9422
|
return { path: customPath, source: "custom" };
|
|
8791
9423
|
}
|
|
8792
9424
|
}
|
|
8793
|
-
return { path:
|
|
9425
|
+
return { path: join20(bundledTemplatesDir, templateName), source: "bundled" };
|
|
8794
9426
|
}
|
|
8795
9427
|
function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
|
|
8796
|
-
if (
|
|
9428
|
+
if (fs23.existsSync(targetPath) && !force) {
|
|
8797
9429
|
console.log(` Skipped (exists): ${targetPath}`);
|
|
8798
9430
|
return { created: false, source: source ?? "bundled" };
|
|
8799
9431
|
}
|
|
8800
|
-
const templatePath = sourcePath ??
|
|
9432
|
+
const templatePath = sourcePath ?? join20(TEMPLATES_DIR, templateName);
|
|
8801
9433
|
const resolvedSource = source ?? "bundled";
|
|
8802
|
-
let content =
|
|
9434
|
+
let content = fs23.readFileSync(templatePath, "utf-8");
|
|
8803
9435
|
for (const [key, value] of Object.entries(replacements)) {
|
|
8804
9436
|
content = content.replaceAll(key, value);
|
|
8805
9437
|
}
|
|
8806
|
-
|
|
9438
|
+
fs23.writeFileSync(targetPath, content);
|
|
8807
9439
|
console.log(` Created: ${targetPath} (${resolvedSource})`);
|
|
8808
9440
|
return { created: true, source: resolvedSource };
|
|
8809
9441
|
}
|
|
8810
9442
|
function addToGitignore(cwd) {
|
|
8811
|
-
const gitignorePath =
|
|
9443
|
+
const gitignorePath = path22.join(cwd, ".gitignore");
|
|
8812
9444
|
const entries = [
|
|
8813
9445
|
{
|
|
8814
9446
|
pattern: "/logs/",
|
|
@@ -8822,13 +9454,13 @@ function addToGitignore(cwd) {
|
|
|
8822
9454
|
},
|
|
8823
9455
|
{ pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
|
|
8824
9456
|
];
|
|
8825
|
-
if (!
|
|
9457
|
+
if (!fs23.existsSync(gitignorePath)) {
|
|
8826
9458
|
const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
|
|
8827
|
-
|
|
9459
|
+
fs23.writeFileSync(gitignorePath, lines.join("\n"));
|
|
8828
9460
|
console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
|
|
8829
9461
|
return;
|
|
8830
9462
|
}
|
|
8831
|
-
const content =
|
|
9463
|
+
const content = fs23.readFileSync(gitignorePath, "utf-8");
|
|
8832
9464
|
const missing = entries.filter((e) => !e.check(content));
|
|
8833
9465
|
if (missing.length === 0) {
|
|
8834
9466
|
console.log(` Skipped (exists): Night Watch entries in .gitignore`);
|
|
@@ -8836,59 +9468,59 @@ function addToGitignore(cwd) {
|
|
|
8836
9468
|
}
|
|
8837
9469
|
const additions = missing.map((e) => e.pattern).join("\n");
|
|
8838
9470
|
const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
|
|
8839
|
-
|
|
9471
|
+
fs23.writeFileSync(gitignorePath, newContent);
|
|
8840
9472
|
console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
|
|
8841
9473
|
}
|
|
8842
9474
|
function installSkills(cwd, provider, force, templatesDir) {
|
|
8843
|
-
const skillsTemplatesDir =
|
|
8844
|
-
if (!
|
|
9475
|
+
const skillsTemplatesDir = path22.join(templatesDir, "skills");
|
|
9476
|
+
if (!fs23.existsSync(skillsTemplatesDir)) {
|
|
8845
9477
|
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
8846
9478
|
}
|
|
8847
9479
|
const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
|
|
8848
9480
|
const isCodexProvider = provider === "codex";
|
|
8849
|
-
const claudeDir =
|
|
8850
|
-
if (isClaudeProvider ||
|
|
9481
|
+
const claudeDir = path22.join(cwd, ".claude");
|
|
9482
|
+
if (isClaudeProvider || fs23.existsSync(claudeDir)) {
|
|
8851
9483
|
ensureDir(claudeDir);
|
|
8852
|
-
const skillsDir =
|
|
9484
|
+
const skillsDir = path22.join(claudeDir, "skills");
|
|
8853
9485
|
ensureDir(skillsDir);
|
|
8854
9486
|
let installed = 0;
|
|
8855
9487
|
let skipped = 0;
|
|
8856
9488
|
for (const skillName of NW_SKILLS) {
|
|
8857
|
-
const templateFile =
|
|
8858
|
-
if (!
|
|
8859
|
-
const skillDir =
|
|
9489
|
+
const templateFile = path22.join(skillsTemplatesDir, `${skillName}.md`);
|
|
9490
|
+
if (!fs23.existsSync(templateFile)) continue;
|
|
9491
|
+
const skillDir = path22.join(skillsDir, skillName);
|
|
8860
9492
|
ensureDir(skillDir);
|
|
8861
|
-
const target =
|
|
8862
|
-
if (
|
|
9493
|
+
const target = path22.join(skillDir, "SKILL.md");
|
|
9494
|
+
if (fs23.existsSync(target) && !force) {
|
|
8863
9495
|
skipped++;
|
|
8864
9496
|
continue;
|
|
8865
9497
|
}
|
|
8866
|
-
|
|
9498
|
+
fs23.copyFileSync(templateFile, target);
|
|
8867
9499
|
installed++;
|
|
8868
9500
|
}
|
|
8869
9501
|
return { location: ".claude/skills/", installed, skipped, type: "claude" };
|
|
8870
9502
|
}
|
|
8871
9503
|
if (isCodexProvider) {
|
|
8872
|
-
const agentsFile =
|
|
8873
|
-
const blockFile =
|
|
8874
|
-
if (!
|
|
9504
|
+
const agentsFile = path22.join(cwd, "AGENTS.md");
|
|
9505
|
+
const blockFile = path22.join(skillsTemplatesDir, "_codex-block.md");
|
|
9506
|
+
if (!fs23.existsSync(blockFile)) {
|
|
8875
9507
|
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
8876
9508
|
}
|
|
8877
|
-
const block =
|
|
9509
|
+
const block = fs23.readFileSync(blockFile, "utf-8");
|
|
8878
9510
|
const marker = "## Night Watch Skills";
|
|
8879
|
-
if (!
|
|
8880
|
-
|
|
9511
|
+
if (!fs23.existsSync(agentsFile)) {
|
|
9512
|
+
fs23.writeFileSync(agentsFile, block);
|
|
8881
9513
|
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
8882
9514
|
}
|
|
8883
|
-
const existing =
|
|
9515
|
+
const existing = fs23.readFileSync(agentsFile, "utf-8");
|
|
8884
9516
|
if (existing.includes(marker)) {
|
|
8885
9517
|
if (!force) {
|
|
8886
9518
|
return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
|
|
8887
9519
|
}
|
|
8888
9520
|
const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
|
|
8889
|
-
|
|
9521
|
+
fs23.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
|
|
8890
9522
|
} else {
|
|
8891
|
-
|
|
9523
|
+
fs23.appendFileSync(agentsFile, "\n\n" + block);
|
|
8892
9524
|
}
|
|
8893
9525
|
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
8894
9526
|
}
|
|
@@ -9012,28 +9644,28 @@ function initCommand(program2) {
|
|
|
9012
9644
|
"${DEFAULT_BRANCH}": defaultBranch
|
|
9013
9645
|
};
|
|
9014
9646
|
step(6, totalSteps, "Creating PRD directory structure...");
|
|
9015
|
-
const prdDirPath =
|
|
9016
|
-
const doneDirPath =
|
|
9647
|
+
const prdDirPath = path22.join(cwd, prdDir);
|
|
9648
|
+
const doneDirPath = path22.join(prdDirPath, "done");
|
|
9017
9649
|
ensureDir(doneDirPath);
|
|
9018
9650
|
success(`Created ${prdDirPath}/`);
|
|
9019
9651
|
success(`Created ${doneDirPath}/`);
|
|
9020
9652
|
step(7, totalSteps, "Creating logs directory...");
|
|
9021
|
-
const logsPath =
|
|
9653
|
+
const logsPath = path22.join(cwd, LOG_DIR);
|
|
9022
9654
|
ensureDir(logsPath);
|
|
9023
9655
|
success(`Created ${logsPath}/`);
|
|
9024
9656
|
addToGitignore(cwd);
|
|
9025
9657
|
step(8, totalSteps, "Creating instructions directory...");
|
|
9026
|
-
const instructionsDir =
|
|
9658
|
+
const instructionsDir = path22.join(cwd, "instructions");
|
|
9027
9659
|
ensureDir(instructionsDir);
|
|
9028
9660
|
success(`Created ${instructionsDir}/`);
|
|
9029
9661
|
const existingConfig = loadConfig(cwd);
|
|
9030
|
-
const customTemplatesDirPath =
|
|
9031
|
-
const customTemplatesDir =
|
|
9662
|
+
const customTemplatesDirPath = path22.join(cwd, existingConfig.templatesDir);
|
|
9663
|
+
const customTemplatesDir = fs23.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
|
|
9032
9664
|
const templateSources = [];
|
|
9033
9665
|
const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
|
|
9034
9666
|
const nwResult = processTemplate(
|
|
9035
9667
|
"executor.md",
|
|
9036
|
-
|
|
9668
|
+
path22.join(instructionsDir, "executor.md"),
|
|
9037
9669
|
replacements,
|
|
9038
9670
|
force,
|
|
9039
9671
|
nwResolution.path,
|
|
@@ -9047,7 +9679,7 @@ function initCommand(program2) {
|
|
|
9047
9679
|
);
|
|
9048
9680
|
const peResult = processTemplate(
|
|
9049
9681
|
"prd-executor.md",
|
|
9050
|
-
|
|
9682
|
+
path22.join(instructionsDir, "prd-executor.md"),
|
|
9051
9683
|
replacements,
|
|
9052
9684
|
force,
|
|
9053
9685
|
peResolution.path,
|
|
@@ -9057,7 +9689,7 @@ function initCommand(program2) {
|
|
|
9057
9689
|
const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
|
|
9058
9690
|
const prResult = processTemplate(
|
|
9059
9691
|
"pr-reviewer.md",
|
|
9060
|
-
|
|
9692
|
+
path22.join(instructionsDir, "pr-reviewer.md"),
|
|
9061
9693
|
replacements,
|
|
9062
9694
|
force,
|
|
9063
9695
|
prResolution.path,
|
|
@@ -9067,7 +9699,7 @@ function initCommand(program2) {
|
|
|
9067
9699
|
const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
|
|
9068
9700
|
const qaResult = processTemplate(
|
|
9069
9701
|
"qa.md",
|
|
9070
|
-
|
|
9702
|
+
path22.join(instructionsDir, "qa.md"),
|
|
9071
9703
|
replacements,
|
|
9072
9704
|
force,
|
|
9073
9705
|
qaResolution.path,
|
|
@@ -9077,7 +9709,7 @@ function initCommand(program2) {
|
|
|
9077
9709
|
const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
|
|
9078
9710
|
const auditResult = processTemplate(
|
|
9079
9711
|
"audit.md",
|
|
9080
|
-
|
|
9712
|
+
path22.join(instructionsDir, "audit.md"),
|
|
9081
9713
|
replacements,
|
|
9082
9714
|
force,
|
|
9083
9715
|
auditResolution.path,
|
|
@@ -9091,7 +9723,7 @@ function initCommand(program2) {
|
|
|
9091
9723
|
);
|
|
9092
9724
|
const plannerResult = processTemplate(
|
|
9093
9725
|
"prd-creator.md",
|
|
9094
|
-
|
|
9726
|
+
path22.join(instructionsDir, "prd-creator.md"),
|
|
9095
9727
|
replacements,
|
|
9096
9728
|
force,
|
|
9097
9729
|
plannerResolution.path,
|
|
@@ -9099,8 +9731,8 @@ function initCommand(program2) {
|
|
|
9099
9731
|
);
|
|
9100
9732
|
templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
|
|
9101
9733
|
step(9, totalSteps, "Creating configuration file...");
|
|
9102
|
-
const configPath =
|
|
9103
|
-
if (
|
|
9734
|
+
const configPath = path22.join(cwd, CONFIG_FILE_NAME);
|
|
9735
|
+
if (fs23.existsSync(configPath) && !force) {
|
|
9104
9736
|
console.log(` Skipped (exists): ${configPath}`);
|
|
9105
9737
|
} else {
|
|
9106
9738
|
const config = buildInitConfig({
|
|
@@ -9110,11 +9742,11 @@ function initCommand(program2) {
|
|
|
9110
9742
|
reviewerEnabled,
|
|
9111
9743
|
prdDir
|
|
9112
9744
|
});
|
|
9113
|
-
|
|
9745
|
+
fs23.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
9114
9746
|
success(`Created ${configPath}`);
|
|
9115
9747
|
}
|
|
9116
9748
|
step(10, totalSteps, "Setting up GitHub Project board...");
|
|
9117
|
-
const existingRaw = JSON.parse(
|
|
9749
|
+
const existingRaw = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
|
|
9118
9750
|
const existingBoard = existingRaw.boardProvider;
|
|
9119
9751
|
let boardSetupStatus = "Skipped";
|
|
9120
9752
|
if (existingBoard?.projectNumber && !force) {
|
|
@@ -9136,13 +9768,13 @@ function initCommand(program2) {
|
|
|
9136
9768
|
const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
|
|
9137
9769
|
const boardTitle = `${projectName} Night Watch`;
|
|
9138
9770
|
const board = await provider.setupBoard(boardTitle);
|
|
9139
|
-
const rawConfig = JSON.parse(
|
|
9771
|
+
const rawConfig = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
|
|
9140
9772
|
rawConfig.boardProvider = {
|
|
9141
9773
|
enabled: true,
|
|
9142
9774
|
provider: "github",
|
|
9143
9775
|
projectNumber: board.number
|
|
9144
9776
|
};
|
|
9145
|
-
|
|
9777
|
+
fs23.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
|
|
9146
9778
|
boardSetupStatus = `Created (#${board.number})`;
|
|
9147
9779
|
success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
|
|
9148
9780
|
} catch (boardErr) {
|
|
@@ -9278,6 +9910,7 @@ function buildBaseEnvVars(config, jobType, isDryRun) {
|
|
|
9278
9910
|
if (config.defaultBranch) {
|
|
9279
9911
|
env.NW_DEFAULT_BRANCH = config.defaultBranch;
|
|
9280
9912
|
}
|
|
9913
|
+
env.NW_PR_RESOLVER_READY_LABEL = config.prResolver?.readyLabel ?? "ready-to-merge";
|
|
9281
9914
|
env.NW_GIT_PUSH_NO_VERIFY = config.gitPushNoVerify ? "1" : "0";
|
|
9282
9915
|
if (config.providerEnv) {
|
|
9283
9916
|
Object.assign(env, config.providerEnv);
|
|
@@ -9326,8 +9959,8 @@ function getTelegramStatusWebhooks(config) {
|
|
|
9326
9959
|
}
|
|
9327
9960
|
|
|
9328
9961
|
// src/commands/run.ts
|
|
9329
|
-
import * as
|
|
9330
|
-
import * as
|
|
9962
|
+
import * as fs24 from "fs";
|
|
9963
|
+
import * as path23 from "path";
|
|
9331
9964
|
function resolveRunNotificationEvent(exitCode, scriptStatus) {
|
|
9332
9965
|
if (exitCode === 124) {
|
|
9333
9966
|
return "run_timeout";
|
|
@@ -9359,12 +9992,12 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
|
|
|
9359
9992
|
return scriptStatus === "skip_no_eligible_prd";
|
|
9360
9993
|
}
|
|
9361
9994
|
function getCrossProjectFallbackCandidates(currentProjectDir) {
|
|
9362
|
-
const current =
|
|
9995
|
+
const current = path23.resolve(currentProjectDir);
|
|
9363
9996
|
const { valid, invalid } = validateRegistry();
|
|
9364
9997
|
for (const entry of invalid) {
|
|
9365
9998
|
warn(`Skipping invalid registry entry: ${entry.path}`);
|
|
9366
9999
|
}
|
|
9367
|
-
return valid.filter((entry) =>
|
|
10000
|
+
return valid.filter((entry) => path23.resolve(entry.path) !== current);
|
|
9368
10001
|
}
|
|
9369
10002
|
async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
|
|
9370
10003
|
if (isRateLimitFallbackTriggered(scriptResult?.data)) {
|
|
@@ -9374,7 +10007,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
9374
10007
|
if (nonTelegramWebhooks.length > 0) {
|
|
9375
10008
|
const _rateLimitCtx = {
|
|
9376
10009
|
event: "rate_limit_fallback",
|
|
9377
|
-
projectName:
|
|
10010
|
+
projectName: path23.basename(projectDir),
|
|
9378
10011
|
exitCode,
|
|
9379
10012
|
provider: config.provider
|
|
9380
10013
|
};
|
|
@@ -9406,7 +10039,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
9406
10039
|
const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
|
|
9407
10040
|
const _ctx = {
|
|
9408
10041
|
event,
|
|
9409
|
-
projectName:
|
|
10042
|
+
projectName: path23.basename(projectDir),
|
|
9410
10043
|
exitCode,
|
|
9411
10044
|
provider: config.provider,
|
|
9412
10045
|
prdName: scriptResult?.data.prd,
|
|
@@ -9570,20 +10203,20 @@ function applyCliOverrides(config, options) {
|
|
|
9570
10203
|
return overridden;
|
|
9571
10204
|
}
|
|
9572
10205
|
function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
9573
|
-
const absolutePrdDir =
|
|
9574
|
-
const doneDir =
|
|
10206
|
+
const absolutePrdDir = path23.join(projectDir, prdDir);
|
|
10207
|
+
const doneDir = path23.join(absolutePrdDir, "done");
|
|
9575
10208
|
const pending = [];
|
|
9576
10209
|
const completed = [];
|
|
9577
|
-
if (
|
|
9578
|
-
const entries =
|
|
10210
|
+
if (fs24.existsSync(absolutePrdDir)) {
|
|
10211
|
+
const entries = fs24.readdirSync(absolutePrdDir, { withFileTypes: true });
|
|
9579
10212
|
for (const entry of entries) {
|
|
9580
10213
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9581
|
-
const claimPath =
|
|
10214
|
+
const claimPath = path23.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
|
|
9582
10215
|
let claimed = false;
|
|
9583
10216
|
let claimInfo = null;
|
|
9584
|
-
if (
|
|
10217
|
+
if (fs24.existsSync(claimPath)) {
|
|
9585
10218
|
try {
|
|
9586
|
-
const content =
|
|
10219
|
+
const content = fs24.readFileSync(claimPath, "utf-8");
|
|
9587
10220
|
const data = JSON.parse(content);
|
|
9588
10221
|
const age = Math.floor(Date.now() / 1e3) - data.timestamp;
|
|
9589
10222
|
if (age < maxRuntime) {
|
|
@@ -9597,8 +10230,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
|
9597
10230
|
}
|
|
9598
10231
|
}
|
|
9599
10232
|
}
|
|
9600
|
-
if (
|
|
9601
|
-
const entries =
|
|
10233
|
+
if (fs24.existsSync(doneDir)) {
|
|
10234
|
+
const entries = fs24.readdirSync(doneDir, { withFileTypes: true });
|
|
9602
10235
|
for (const entry of entries) {
|
|
9603
10236
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9604
10237
|
completed.push(entry.name);
|
|
@@ -9758,7 +10391,7 @@ ${stderr}`);
|
|
|
9758
10391
|
// src/commands/review.ts
|
|
9759
10392
|
init_dist();
|
|
9760
10393
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
9761
|
-
import * as
|
|
10394
|
+
import * as path24 from "path";
|
|
9762
10395
|
function shouldSendReviewNotification(scriptStatus) {
|
|
9763
10396
|
if (!scriptStatus) {
|
|
9764
10397
|
return true;
|
|
@@ -9824,76 +10457,6 @@ function parseFinalReviewScore(raw) {
|
|
|
9824
10457
|
}
|
|
9825
10458
|
return parsed;
|
|
9826
10459
|
}
|
|
9827
|
-
function postReadyForHumanReviewComment(prNumber, finalScore, cwd) {
|
|
9828
|
-
const markerName = "night-watch-ready-for-review";
|
|
9829
|
-
let headRefOid = "";
|
|
9830
|
-
try {
|
|
9831
|
-
headRefOid = execFileSync5(
|
|
9832
|
-
"gh",
|
|
9833
|
-
["pr", "view", String(prNumber), "--json", "headRefOid", "--jq", ".headRefOid"],
|
|
9834
|
-
{
|
|
9835
|
-
cwd,
|
|
9836
|
-
encoding: "utf-8",
|
|
9837
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
9838
|
-
}
|
|
9839
|
-
).trim();
|
|
9840
|
-
} catch {
|
|
9841
|
-
headRefOid = "";
|
|
9842
|
-
}
|
|
9843
|
-
if (headRefOid) {
|
|
9844
|
-
try {
|
|
9845
|
-
const existingComments = execFileSync5(
|
|
9846
|
-
"gh",
|
|
9847
|
-
["api", `repos/{owner}/{repo}/issues/${prNumber}/comments`, "--jq", ".[].body"],
|
|
9848
|
-
{
|
|
9849
|
-
cwd,
|
|
9850
|
-
encoding: "utf-8",
|
|
9851
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
9852
|
-
}
|
|
9853
|
-
);
|
|
9854
|
-
const marker2 = `<!-- ${markerName} headRefOid:${headRefOid} -->`;
|
|
9855
|
-
if (existingComments.includes(marker2)) {
|
|
9856
|
-
try {
|
|
9857
|
-
execFileSync5("gh", ["pr", "edit", String(prNumber), "--add-label", "ready-for-review"], {
|
|
9858
|
-
cwd,
|
|
9859
|
-
encoding: "utf-8",
|
|
9860
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
9861
|
-
});
|
|
9862
|
-
} catch {
|
|
9863
|
-
}
|
|
9864
|
-
return;
|
|
9865
|
-
}
|
|
9866
|
-
} catch {
|
|
9867
|
-
}
|
|
9868
|
-
}
|
|
9869
|
-
const scoreNote = finalScore !== void 0 ? ` (score: ${finalScore}/100)` : "";
|
|
9870
|
-
const shortSha = headRefOid ? headRefOid.slice(0, 12) : "";
|
|
9871
|
-
const marker = headRefOid ? `<!-- ${markerName} headRefOid:${headRefOid} -->
|
|
9872
|
-
|
|
9873
|
-
` : "";
|
|
9874
|
-
const shaNote = shortSha ? ` at commit \`${shortSha}\`` : "";
|
|
9875
|
-
const body = `${marker}## \u2705 Ready for Human Review
|
|
9876
|
-
|
|
9877
|
-
Night Watch has reviewed this PR${scoreNote}${shaNote} and found no issues requiring automated fixes for the current head.
|
|
9878
|
-
|
|
9879
|
-
This PR is ready for human code review and merge.`;
|
|
9880
|
-
try {
|
|
9881
|
-
execFileSync5("gh", ["pr", "comment", String(prNumber), "--body", body], {
|
|
9882
|
-
cwd,
|
|
9883
|
-
encoding: "utf-8",
|
|
9884
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
9885
|
-
});
|
|
9886
|
-
} catch {
|
|
9887
|
-
}
|
|
9888
|
-
try {
|
|
9889
|
-
execFileSync5("gh", ["pr", "edit", String(prNumber), "--add-label", "ready-for-review"], {
|
|
9890
|
-
cwd,
|
|
9891
|
-
encoding: "utf-8",
|
|
9892
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
9893
|
-
});
|
|
9894
|
-
} catch {
|
|
9895
|
-
}
|
|
9896
|
-
}
|
|
9897
10460
|
function buildEnvVars2(config, options) {
|
|
9898
10461
|
const env = buildBaseEnvVars(config, "reviewer", options.dryRun);
|
|
9899
10462
|
env.NW_REVIEWER_MAX_RUNTIME = String(config.reviewerMaxRuntime);
|
|
@@ -9903,6 +10466,7 @@ function buildEnvVars2(config, options) {
|
|
|
9903
10466
|
env.NW_MIN_REVIEW_SCORE = String(config.minReviewScore);
|
|
9904
10467
|
env.NW_BRANCH_PATTERNS = config.branchPatterns.join(",");
|
|
9905
10468
|
env.NW_PRD_DIR = config.prdDir;
|
|
10469
|
+
env.NW_PR_RESOLVER_READY_LABEL = config.prResolver.readyLabel;
|
|
9906
10470
|
env.NW_CLAUDE_MODEL_ID = CLAUDE_MODEL_IDS[config.primaryFallbackModel ?? config.claudeModel ?? "sonnet"];
|
|
9907
10471
|
return env;
|
|
9908
10472
|
}
|
|
@@ -10108,7 +10672,7 @@ ${stderr}`);
|
|
|
10108
10672
|
const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
10109
10673
|
await sendNotifications(config, {
|
|
10110
10674
|
event: reviewEvent,
|
|
10111
|
-
projectName:
|
|
10675
|
+
projectName: path24.basename(projectDir),
|
|
10112
10676
|
exitCode,
|
|
10113
10677
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
10114
10678
|
prUrl: fallbackPrDetails?.url,
|
|
@@ -10124,13 +10688,10 @@ ${stderr}`);
|
|
|
10124
10688
|
} else {
|
|
10125
10689
|
for (const target of notificationTargets) {
|
|
10126
10690
|
const prDetails = fallbackPrDetails?.number === target.prNumber ? fallbackPrDetails : fetchPrDetailsByNumber(target.prNumber, projectDir);
|
|
10127
|
-
if (target.noChangesNeeded && prDetails?.number) {
|
|
10128
|
-
postReadyForHumanReviewComment(prDetails.number, finalScore, projectDir);
|
|
10129
|
-
}
|
|
10130
10691
|
const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
10131
10692
|
await sendNotifications(config, {
|
|
10132
10693
|
event: reviewEvent,
|
|
10133
|
-
projectName:
|
|
10694
|
+
projectName: path24.basename(projectDir),
|
|
10134
10695
|
exitCode,
|
|
10135
10696
|
provider: formatProviderDisplay(
|
|
10136
10697
|
envVars.NW_PROVIDER_CMD,
|
|
@@ -10155,7 +10716,7 @@ ${stderr}`);
|
|
|
10155
10716
|
const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
|
|
10156
10717
|
const _mergeCtx = {
|
|
10157
10718
|
event: "pr_auto_merged",
|
|
10158
|
-
projectName:
|
|
10719
|
+
projectName: path24.basename(projectDir),
|
|
10159
10720
|
exitCode,
|
|
10160
10721
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
10161
10722
|
prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
|
|
@@ -10180,7 +10741,7 @@ ${stderr}`);
|
|
|
10180
10741
|
|
|
10181
10742
|
// src/commands/qa.ts
|
|
10182
10743
|
init_dist();
|
|
10183
|
-
import * as
|
|
10744
|
+
import * as path25 from "path";
|
|
10184
10745
|
function shouldSendQaNotification(scriptStatus) {
|
|
10185
10746
|
if (!scriptStatus) {
|
|
10186
10747
|
return true;
|
|
@@ -10321,7 +10882,7 @@ ${stderr}`);
|
|
|
10321
10882
|
const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
|
|
10322
10883
|
const _qaCtx = {
|
|
10323
10884
|
event: "qa_completed",
|
|
10324
|
-
projectName:
|
|
10885
|
+
projectName: path25.basename(projectDir),
|
|
10325
10886
|
exitCode,
|
|
10326
10887
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
10327
10888
|
prNumber: prDetails?.number ?? primaryQaPr,
|
|
@@ -10347,8 +10908,8 @@ ${stderr}`);
|
|
|
10347
10908
|
|
|
10348
10909
|
// src/commands/audit.ts
|
|
10349
10910
|
init_dist();
|
|
10350
|
-
import * as
|
|
10351
|
-
import * as
|
|
10911
|
+
import * as fs25 from "fs";
|
|
10912
|
+
import * as path26 from "path";
|
|
10352
10913
|
function buildEnvVars4(config, options) {
|
|
10353
10914
|
const env = buildBaseEnvVars(config, "audit", options.dryRun);
|
|
10354
10915
|
env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
|
|
@@ -10392,7 +10953,7 @@ function auditCommand(program2) {
|
|
|
10392
10953
|
configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
|
|
10393
10954
|
configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
|
|
10394
10955
|
configTable.push(["Target Column", config.audit.targetColumn]);
|
|
10395
|
-
configTable.push(["Report File",
|
|
10956
|
+
configTable.push(["Report File", path26.join(projectDir, "logs", "audit-report.md")]);
|
|
10396
10957
|
console.log(configTable.toString());
|
|
10397
10958
|
header("Provider Invocation");
|
|
10398
10959
|
const providerCmd = PROVIDER_COMMANDS[auditProvider];
|
|
@@ -10427,8 +10988,8 @@ ${stderr}`);
|
|
|
10427
10988
|
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
10428
10989
|
spinner.succeed("Code audit skipped");
|
|
10429
10990
|
} else {
|
|
10430
|
-
const reportPath =
|
|
10431
|
-
if (!
|
|
10991
|
+
const reportPath = path26.join(projectDir, "logs", "audit-report.md");
|
|
10992
|
+
if (!fs25.existsSync(reportPath)) {
|
|
10432
10993
|
spinner.fail("Code audit finished without a report file");
|
|
10433
10994
|
process.exit(1);
|
|
10434
10995
|
}
|
|
@@ -10445,9 +11006,9 @@ ${stderr}`);
|
|
|
10445
11006
|
const providerExit = scriptResult?.data?.provider_exit;
|
|
10446
11007
|
const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
|
|
10447
11008
|
spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
|
|
10448
|
-
const logPath =
|
|
10449
|
-
if (
|
|
10450
|
-
const logLines =
|
|
11009
|
+
const logPath = path26.join(projectDir, "logs", "audit.log");
|
|
11010
|
+
if (fs25.existsSync(logPath)) {
|
|
11011
|
+
const logLines = fs25.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
|
|
10451
11012
|
if (logLines.length > 0) {
|
|
10452
11013
|
process.stderr.write(logLines.join("\n") + "\n");
|
|
10453
11014
|
}
|
|
@@ -10521,16 +11082,16 @@ function analyticsCommand(program2) {
|
|
|
10521
11082
|
// src/commands/install.ts
|
|
10522
11083
|
init_dist();
|
|
10523
11084
|
import { execSync as execSync4 } from "child_process";
|
|
10524
|
-
import * as
|
|
10525
|
-
import * as
|
|
11085
|
+
import * as path27 from "path";
|
|
11086
|
+
import * as fs26 from "fs";
|
|
10526
11087
|
function shellQuote(value) {
|
|
10527
11088
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
10528
11089
|
}
|
|
10529
11090
|
function getNightWatchBinPath() {
|
|
10530
11091
|
try {
|
|
10531
11092
|
const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
|
|
10532
|
-
const binPath =
|
|
10533
|
-
if (
|
|
11093
|
+
const binPath = path27.join(npmBin, "night-watch");
|
|
11094
|
+
if (fs26.existsSync(binPath)) {
|
|
10534
11095
|
return binPath;
|
|
10535
11096
|
}
|
|
10536
11097
|
} catch {
|
|
@@ -10543,17 +11104,17 @@ function getNightWatchBinPath() {
|
|
|
10543
11104
|
}
|
|
10544
11105
|
function getNodeBinDir() {
|
|
10545
11106
|
if (process.execPath && process.execPath !== "node") {
|
|
10546
|
-
return
|
|
11107
|
+
return path27.dirname(process.execPath);
|
|
10547
11108
|
}
|
|
10548
11109
|
try {
|
|
10549
11110
|
const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
|
|
10550
|
-
return
|
|
11111
|
+
return path27.dirname(nodePath);
|
|
10551
11112
|
} catch {
|
|
10552
11113
|
return "";
|
|
10553
11114
|
}
|
|
10554
11115
|
}
|
|
10555
11116
|
function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
|
|
10556
|
-
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ?
|
|
11117
|
+
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path27.dirname(nightWatchBin) : "";
|
|
10557
11118
|
const pathParts = Array.from(
|
|
10558
11119
|
new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
|
|
10559
11120
|
);
|
|
@@ -10569,12 +11130,12 @@ function performInstall(projectDir, config, options) {
|
|
|
10569
11130
|
const nightWatchBin = getNightWatchBinPath();
|
|
10570
11131
|
const projectName = getProjectName(projectDir);
|
|
10571
11132
|
const marker = generateMarker(projectName);
|
|
10572
|
-
const logDir =
|
|
10573
|
-
if (!
|
|
10574
|
-
|
|
11133
|
+
const logDir = path27.join(projectDir, LOG_DIR);
|
|
11134
|
+
if (!fs26.existsSync(logDir)) {
|
|
11135
|
+
fs26.mkdirSync(logDir, { recursive: true });
|
|
10575
11136
|
}
|
|
10576
|
-
const executorLog =
|
|
10577
|
-
const reviewerLog =
|
|
11137
|
+
const executorLog = path27.join(logDir, "executor.log");
|
|
11138
|
+
const reviewerLog = path27.join(logDir, "reviewer.log");
|
|
10578
11139
|
if (!options?.force) {
|
|
10579
11140
|
const existingEntries2 = Array.from(
|
|
10580
11141
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
@@ -10611,7 +11172,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10611
11172
|
const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
10612
11173
|
if (installSlicer) {
|
|
10613
11174
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
10614
|
-
const slicerLog =
|
|
11175
|
+
const slicerLog = path27.join(logDir, "slicer.log");
|
|
10615
11176
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
10616
11177
|
entries.push(slicerEntry);
|
|
10617
11178
|
}
|
|
@@ -10619,7 +11180,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10619
11180
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
10620
11181
|
if (installQa) {
|
|
10621
11182
|
const qaSchedule = config.qa.schedule;
|
|
10622
|
-
const qaLog =
|
|
11183
|
+
const qaLog = path27.join(logDir, "qa.log");
|
|
10623
11184
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
10624
11185
|
entries.push(qaEntry);
|
|
10625
11186
|
}
|
|
@@ -10627,7 +11188,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10627
11188
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
10628
11189
|
if (installAudit) {
|
|
10629
11190
|
const auditSchedule = config.audit.schedule;
|
|
10630
|
-
const auditLog =
|
|
11191
|
+
const auditLog = path27.join(logDir, "audit.log");
|
|
10631
11192
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
10632
11193
|
entries.push(auditEntry);
|
|
10633
11194
|
}
|
|
@@ -10635,7 +11196,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10635
11196
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
10636
11197
|
if (installAnalytics) {
|
|
10637
11198
|
const analyticsSchedule = config.analytics.schedule;
|
|
10638
|
-
const analyticsLog =
|
|
11199
|
+
const analyticsLog = path27.join(logDir, "analytics.log");
|
|
10639
11200
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
10640
11201
|
entries.push(analyticsEntry);
|
|
10641
11202
|
}
|
|
@@ -10643,7 +11204,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10643
11204
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10644
11205
|
if (installPrResolver) {
|
|
10645
11206
|
const prResolverSchedule = config.prResolver.schedule;
|
|
10646
|
-
const prResolverLog =
|
|
11207
|
+
const prResolverLog = path27.join(logDir, "pr-resolver.log");
|
|
10647
11208
|
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10648
11209
|
entries.push(prResolverEntry);
|
|
10649
11210
|
}
|
|
@@ -10651,7 +11212,7 @@ function performInstall(projectDir, config, options) {
|
|
|
10651
11212
|
const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
|
|
10652
11213
|
if (installMerger) {
|
|
10653
11214
|
const mergerSchedule = config.merger.schedule;
|
|
10654
|
-
const mergerLog =
|
|
11215
|
+
const mergerLog = path27.join(logDir, "merger.log");
|
|
10655
11216
|
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
|
|
10656
11217
|
entries.push(mergerEntry);
|
|
10657
11218
|
}
|
|
@@ -10682,12 +11243,12 @@ function installCommand(program2) {
|
|
|
10682
11243
|
const nightWatchBin = getNightWatchBinPath();
|
|
10683
11244
|
const projectName = getProjectName(projectDir);
|
|
10684
11245
|
const marker = generateMarker(projectName);
|
|
10685
|
-
const logDir =
|
|
10686
|
-
if (!
|
|
10687
|
-
|
|
11246
|
+
const logDir = path27.join(projectDir, LOG_DIR);
|
|
11247
|
+
if (!fs26.existsSync(logDir)) {
|
|
11248
|
+
fs26.mkdirSync(logDir, { recursive: true });
|
|
10688
11249
|
}
|
|
10689
|
-
const executorLog =
|
|
10690
|
-
const reviewerLog =
|
|
11250
|
+
const executorLog = path27.join(logDir, "executor.log");
|
|
11251
|
+
const reviewerLog = path27.join(logDir, "reviewer.log");
|
|
10691
11252
|
const existingEntries = Array.from(
|
|
10692
11253
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
10693
11254
|
);
|
|
@@ -10723,7 +11284,7 @@ function installCommand(program2) {
|
|
|
10723
11284
|
const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
10724
11285
|
let slicerLog;
|
|
10725
11286
|
if (installSlicer) {
|
|
10726
|
-
slicerLog =
|
|
11287
|
+
slicerLog = path27.join(logDir, "slicer.log");
|
|
10727
11288
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
10728
11289
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
10729
11290
|
entries.push(slicerEntry);
|
|
@@ -10732,7 +11293,7 @@ function installCommand(program2) {
|
|
|
10732
11293
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
10733
11294
|
let qaLog;
|
|
10734
11295
|
if (installQa) {
|
|
10735
|
-
qaLog =
|
|
11296
|
+
qaLog = path27.join(logDir, "qa.log");
|
|
10736
11297
|
const qaSchedule = config.qa.schedule;
|
|
10737
11298
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
10738
11299
|
entries.push(qaEntry);
|
|
@@ -10741,7 +11302,7 @@ function installCommand(program2) {
|
|
|
10741
11302
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
10742
11303
|
let auditLog;
|
|
10743
11304
|
if (installAudit) {
|
|
10744
|
-
auditLog =
|
|
11305
|
+
auditLog = path27.join(logDir, "audit.log");
|
|
10745
11306
|
const auditSchedule = config.audit.schedule;
|
|
10746
11307
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
10747
11308
|
entries.push(auditEntry);
|
|
@@ -10750,7 +11311,7 @@ function installCommand(program2) {
|
|
|
10750
11311
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
10751
11312
|
let analyticsLog;
|
|
10752
11313
|
if (installAnalytics) {
|
|
10753
|
-
analyticsLog =
|
|
11314
|
+
analyticsLog = path27.join(logDir, "analytics.log");
|
|
10754
11315
|
const analyticsSchedule = config.analytics.schedule;
|
|
10755
11316
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
10756
11317
|
entries.push(analyticsEntry);
|
|
@@ -10759,7 +11320,7 @@ function installCommand(program2) {
|
|
|
10759
11320
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
10760
11321
|
let prResolverLog;
|
|
10761
11322
|
if (installPrResolver) {
|
|
10762
|
-
prResolverLog =
|
|
11323
|
+
prResolverLog = path27.join(logDir, "pr-resolver.log");
|
|
10763
11324
|
const prResolverSchedule = config.prResolver.schedule;
|
|
10764
11325
|
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} resolve >> ${shellQuote(prResolverLog)} 2>&1 ${marker}`;
|
|
10765
11326
|
entries.push(prResolverEntry);
|
|
@@ -10768,7 +11329,7 @@ function installCommand(program2) {
|
|
|
10768
11329
|
const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
|
|
10769
11330
|
let mergerLog;
|
|
10770
11331
|
if (installMerger) {
|
|
10771
|
-
mergerLog =
|
|
11332
|
+
mergerLog = path27.join(logDir, "merger.log");
|
|
10772
11333
|
const mergerSchedule = config.merger.schedule;
|
|
10773
11334
|
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} merge >> ${shellQuote(mergerLog)} 2>&1 ${marker}`;
|
|
10774
11335
|
entries.push(mergerEntry);
|
|
@@ -10822,8 +11383,8 @@ function installCommand(program2) {
|
|
|
10822
11383
|
|
|
10823
11384
|
// src/commands/uninstall.ts
|
|
10824
11385
|
init_dist();
|
|
10825
|
-
import * as
|
|
10826
|
-
import * as
|
|
11386
|
+
import * as path28 from "path";
|
|
11387
|
+
import * as fs27 from "fs";
|
|
10827
11388
|
function performUninstall(projectDir, options) {
|
|
10828
11389
|
try {
|
|
10829
11390
|
const projectName = getProjectName(projectDir);
|
|
@@ -10838,8 +11399,8 @@ function performUninstall(projectDir, options) {
|
|
|
10838
11399
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
10839
11400
|
unregisterProject(projectDir);
|
|
10840
11401
|
if (!options?.keepLogs) {
|
|
10841
|
-
const logDir =
|
|
10842
|
-
if (
|
|
11402
|
+
const logDir = path28.join(projectDir, "logs");
|
|
11403
|
+
if (fs27.existsSync(logDir)) {
|
|
10843
11404
|
const logFiles = [
|
|
10844
11405
|
"executor.log",
|
|
10845
11406
|
"reviewer.log",
|
|
@@ -10848,15 +11409,15 @@ function performUninstall(projectDir, options) {
|
|
|
10848
11409
|
"pr-resolver.log"
|
|
10849
11410
|
];
|
|
10850
11411
|
logFiles.forEach((logFile) => {
|
|
10851
|
-
const logPath =
|
|
10852
|
-
if (
|
|
10853
|
-
|
|
11412
|
+
const logPath = path28.join(logDir, logFile);
|
|
11413
|
+
if (fs27.existsSync(logPath)) {
|
|
11414
|
+
fs27.unlinkSync(logPath);
|
|
10854
11415
|
}
|
|
10855
11416
|
});
|
|
10856
11417
|
try {
|
|
10857
|
-
const remainingFiles =
|
|
11418
|
+
const remainingFiles = fs27.readdirSync(logDir);
|
|
10858
11419
|
if (remainingFiles.length === 0) {
|
|
10859
|
-
|
|
11420
|
+
fs27.rmdirSync(logDir);
|
|
10860
11421
|
}
|
|
10861
11422
|
} catch {
|
|
10862
11423
|
}
|
|
@@ -10889,8 +11450,8 @@ function uninstallCommand(program2) {
|
|
|
10889
11450
|
existingEntries.forEach((entry) => dim(` ${entry}`));
|
|
10890
11451
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
10891
11452
|
if (!options.keepLogs) {
|
|
10892
|
-
const logDir =
|
|
10893
|
-
if (
|
|
11453
|
+
const logDir = path28.join(projectDir, "logs");
|
|
11454
|
+
if (fs27.existsSync(logDir)) {
|
|
10894
11455
|
const logFiles = [
|
|
10895
11456
|
"executor.log",
|
|
10896
11457
|
"reviewer.log",
|
|
@@ -10900,16 +11461,16 @@ function uninstallCommand(program2) {
|
|
|
10900
11461
|
];
|
|
10901
11462
|
let logsRemoved = 0;
|
|
10902
11463
|
logFiles.forEach((logFile) => {
|
|
10903
|
-
const logPath =
|
|
10904
|
-
if (
|
|
10905
|
-
|
|
11464
|
+
const logPath = path28.join(logDir, logFile);
|
|
11465
|
+
if (fs27.existsSync(logPath)) {
|
|
11466
|
+
fs27.unlinkSync(logPath);
|
|
10906
11467
|
logsRemoved++;
|
|
10907
11468
|
}
|
|
10908
11469
|
});
|
|
10909
11470
|
try {
|
|
10910
|
-
const remainingFiles =
|
|
11471
|
+
const remainingFiles = fs27.readdirSync(logDir);
|
|
10911
11472
|
if (remainingFiles.length === 0) {
|
|
10912
|
-
|
|
11473
|
+
fs27.rmdirSync(logDir);
|
|
10913
11474
|
}
|
|
10914
11475
|
} catch {
|
|
10915
11476
|
}
|
|
@@ -11195,14 +11756,14 @@ function statusCommand(program2) {
|
|
|
11195
11756
|
// src/commands/logs.ts
|
|
11196
11757
|
init_dist();
|
|
11197
11758
|
import { spawn as spawn3 } from "child_process";
|
|
11198
|
-
import * as
|
|
11199
|
-
import * as
|
|
11759
|
+
import * as path29 from "path";
|
|
11760
|
+
import * as fs28 from "fs";
|
|
11200
11761
|
function getLastLines(filePath, lineCount) {
|
|
11201
|
-
if (!
|
|
11762
|
+
if (!fs28.existsSync(filePath)) {
|
|
11202
11763
|
return `Log file not found: ${filePath}`;
|
|
11203
11764
|
}
|
|
11204
11765
|
try {
|
|
11205
|
-
const content =
|
|
11766
|
+
const content = fs28.readFileSync(filePath, "utf-8");
|
|
11206
11767
|
const lines = content.trim().split("\n");
|
|
11207
11768
|
return lines.slice(-lineCount).join("\n");
|
|
11208
11769
|
} catch (error2) {
|
|
@@ -11210,7 +11771,7 @@ function getLastLines(filePath, lineCount) {
|
|
|
11210
11771
|
}
|
|
11211
11772
|
}
|
|
11212
11773
|
function followLog(filePath) {
|
|
11213
|
-
if (!
|
|
11774
|
+
if (!fs28.existsSync(filePath)) {
|
|
11214
11775
|
console.log(`Log file not found: ${filePath}`);
|
|
11215
11776
|
console.log("The log file will be created when the first execution runs.");
|
|
11216
11777
|
return;
|
|
@@ -11234,15 +11795,15 @@ function logsCommand(program2) {
|
|
|
11234
11795
|
).action(async (options) => {
|
|
11235
11796
|
try {
|
|
11236
11797
|
const projectDir = process.cwd();
|
|
11237
|
-
const logDir =
|
|
11798
|
+
const logDir = path29.join(projectDir, LOG_DIR);
|
|
11238
11799
|
const lineCount = parseInt(options.lines || "50", 10);
|
|
11239
|
-
const executorLog =
|
|
11240
|
-
const reviewerLog =
|
|
11241
|
-
const qaLog =
|
|
11242
|
-
const auditLog =
|
|
11243
|
-
const plannerLog =
|
|
11244
|
-
const analyticsLog =
|
|
11245
|
-
const mergerLog =
|
|
11800
|
+
const executorLog = path29.join(logDir, EXECUTOR_LOG_FILE);
|
|
11801
|
+
const reviewerLog = path29.join(logDir, REVIEWER_LOG_FILE);
|
|
11802
|
+
const qaLog = path29.join(logDir, `${QA_LOG_NAME}.log`);
|
|
11803
|
+
const auditLog = path29.join(logDir, `${AUDIT_LOG_NAME}.log`);
|
|
11804
|
+
const plannerLog = path29.join(logDir, `${PLANNER_LOG_NAME}.log`);
|
|
11805
|
+
const analyticsLog = path29.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
|
|
11806
|
+
const mergerLog = path29.join(logDir, `${MERGER_LOG_NAME}.log`);
|
|
11246
11807
|
const logType = options.type?.toLowerCase() || "all";
|
|
11247
11808
|
const showExecutor = logType === "all" || logType === "run" || logType === "executor";
|
|
11248
11809
|
const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
|
|
@@ -11325,8 +11886,8 @@ function logsCommand(program2) {
|
|
|
11325
11886
|
// src/commands/prd.ts
|
|
11326
11887
|
init_dist();
|
|
11327
11888
|
import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
|
|
11328
|
-
import * as
|
|
11329
|
-
import * as
|
|
11889
|
+
import * as fs29 from "fs";
|
|
11890
|
+
import * as path30 from "path";
|
|
11330
11891
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11331
11892
|
import { dirname as dirname9 } from "path";
|
|
11332
11893
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
@@ -11334,21 +11895,21 @@ var __dirname3 = dirname9(__filename3);
|
|
|
11334
11895
|
function findTemplatesDir2(startDir) {
|
|
11335
11896
|
let current = startDir;
|
|
11336
11897
|
for (let i = 0; i < 8; i++) {
|
|
11337
|
-
const candidate =
|
|
11338
|
-
if (
|
|
11898
|
+
const candidate = path30.join(current, "templates");
|
|
11899
|
+
if (fs29.existsSync(candidate) && fs29.statSync(candidate).isDirectory()) {
|
|
11339
11900
|
return candidate;
|
|
11340
11901
|
}
|
|
11341
|
-
current =
|
|
11902
|
+
current = path30.dirname(current);
|
|
11342
11903
|
}
|
|
11343
|
-
return
|
|
11904
|
+
return path30.join(startDir, "templates");
|
|
11344
11905
|
}
|
|
11345
11906
|
var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
|
|
11346
11907
|
function slugify2(name) {
|
|
11347
11908
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
11348
11909
|
}
|
|
11349
11910
|
function getNextPrdNumber2(prdDir) {
|
|
11350
|
-
if (!
|
|
11351
|
-
const files =
|
|
11911
|
+
if (!fs29.existsSync(prdDir)) return 1;
|
|
11912
|
+
const files = fs29.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
11352
11913
|
const numbers = files.map((f) => {
|
|
11353
11914
|
const match = f.match(/^(\d+)-/);
|
|
11354
11915
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -11429,13 +11990,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
|
|
|
11429
11990
|
return null;
|
|
11430
11991
|
}
|
|
11431
11992
|
const ref = branch && branch !== "HEAD" ? branch : "main";
|
|
11432
|
-
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(
|
|
11993
|
+
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path30.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
11433
11994
|
} catch {
|
|
11434
11995
|
return null;
|
|
11435
11996
|
}
|
|
11436
11997
|
}
|
|
11437
11998
|
function buildGithubIssueBody(prdPath, projectDir, prdContent) {
|
|
11438
|
-
const relPath =
|
|
11999
|
+
const relPath = path30.relative(projectDir, prdPath);
|
|
11439
12000
|
const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
|
|
11440
12001
|
const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
|
|
11441
12002
|
return `${fileLine}
|
|
@@ -11446,13 +12007,13 @@ ${prdContent}
|
|
|
11446
12007
|
Created via \`night-watch prd create\`.`;
|
|
11447
12008
|
}
|
|
11448
12009
|
async function generatePrdWithClaude(description, projectDir, model) {
|
|
11449
|
-
const bundledTemplatePath =
|
|
11450
|
-
const installedTemplatePath =
|
|
11451
|
-
const templatePath =
|
|
11452
|
-
if (!
|
|
12010
|
+
const bundledTemplatePath = path30.join(TEMPLATES_DIR2, "prd-creator.md");
|
|
12011
|
+
const installedTemplatePath = path30.join(projectDir, "instructions", "prd-creator.md");
|
|
12012
|
+
const templatePath = fs29.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
|
|
12013
|
+
if (!fs29.existsSync(templatePath)) {
|
|
11453
12014
|
return null;
|
|
11454
12015
|
}
|
|
11455
|
-
const planningPrinciples =
|
|
12016
|
+
const planningPrinciples = fs29.readFileSync(templatePath, "utf-8");
|
|
11456
12017
|
const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
|
|
11457
12018
|
const modelId = model ?? CLAUDE_MODEL_IDS.opus;
|
|
11458
12019
|
const env = buildNativeClaudeEnv(process.env);
|
|
@@ -11520,17 +12081,17 @@ function runGh(args, cwd) {
|
|
|
11520
12081
|
return null;
|
|
11521
12082
|
}
|
|
11522
12083
|
function createGithubIssue(title, prdPath, projectDir, prdContent) {
|
|
11523
|
-
const tmpFile =
|
|
12084
|
+
const tmpFile = path30.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
|
|
11524
12085
|
try {
|
|
11525
12086
|
const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
|
|
11526
|
-
|
|
12087
|
+
fs29.writeFileSync(tmpFile, body, "utf-8");
|
|
11527
12088
|
const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
|
|
11528
12089
|
return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
|
|
11529
12090
|
} catch {
|
|
11530
12091
|
return null;
|
|
11531
12092
|
} finally {
|
|
11532
12093
|
try {
|
|
11533
|
-
|
|
12094
|
+
fs29.unlinkSync(tmpFile);
|
|
11534
12095
|
} catch {
|
|
11535
12096
|
}
|
|
11536
12097
|
}
|
|
@@ -11542,10 +12103,10 @@ function parseDependencies(content) {
|
|
|
11542
12103
|
}
|
|
11543
12104
|
function isClaimActive(claimPath, maxRuntime) {
|
|
11544
12105
|
try {
|
|
11545
|
-
if (!
|
|
12106
|
+
if (!fs29.existsSync(claimPath)) {
|
|
11546
12107
|
return { active: false };
|
|
11547
12108
|
}
|
|
11548
|
-
const content =
|
|
12109
|
+
const content = fs29.readFileSync(claimPath, "utf-8");
|
|
11549
12110
|
const claim = JSON.parse(content);
|
|
11550
12111
|
const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
|
|
11551
12112
|
if (age < maxRuntime) {
|
|
@@ -11560,9 +12121,9 @@ function prdCommand(program2) {
|
|
|
11560
12121
|
const prd = program2.command("prd").description("Manage PRD files");
|
|
11561
12122
|
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) => {
|
|
11562
12123
|
const projectDir = process.cwd();
|
|
11563
|
-
const prdDir =
|
|
11564
|
-
if (!
|
|
11565
|
-
|
|
12124
|
+
const prdDir = path30.join(projectDir, resolvePrdCreateDir());
|
|
12125
|
+
if (!fs29.existsSync(prdDir)) {
|
|
12126
|
+
fs29.mkdirSync(prdDir, { recursive: true });
|
|
11566
12127
|
}
|
|
11567
12128
|
const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
|
|
11568
12129
|
const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
|
|
@@ -11576,13 +12137,13 @@ function prdCommand(program2) {
|
|
|
11576
12137
|
const prdTitle = extractPrdTitle(generated) ?? name;
|
|
11577
12138
|
const slug = slugify2(prdTitle);
|
|
11578
12139
|
const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
|
|
11579
|
-
const filePath =
|
|
11580
|
-
if (
|
|
12140
|
+
const filePath = path30.join(prdDir, filename);
|
|
12141
|
+
if (fs29.existsSync(filePath)) {
|
|
11581
12142
|
error(`File already exists: ${filePath}`);
|
|
11582
12143
|
dim("Use a different name or remove the existing file.");
|
|
11583
12144
|
process.exit(1);
|
|
11584
12145
|
}
|
|
11585
|
-
|
|
12146
|
+
fs29.writeFileSync(filePath, generated, "utf-8");
|
|
11586
12147
|
header("PRD Created");
|
|
11587
12148
|
success(`Created: ${filePath}`);
|
|
11588
12149
|
const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
|
|
@@ -11595,15 +12156,15 @@ function prdCommand(program2) {
|
|
|
11595
12156
|
prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
|
|
11596
12157
|
const projectDir = process.cwd();
|
|
11597
12158
|
const config = loadConfig(projectDir);
|
|
11598
|
-
const absolutePrdDir =
|
|
11599
|
-
const doneDir =
|
|
12159
|
+
const absolutePrdDir = path30.join(projectDir, config.prdDir);
|
|
12160
|
+
const doneDir = path30.join(absolutePrdDir, "done");
|
|
11600
12161
|
const pending = [];
|
|
11601
|
-
if (
|
|
11602
|
-
const files =
|
|
12162
|
+
if (fs29.existsSync(absolutePrdDir)) {
|
|
12163
|
+
const files = fs29.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
|
|
11603
12164
|
for (const file of files) {
|
|
11604
|
-
const content =
|
|
12165
|
+
const content = fs29.readFileSync(path30.join(absolutePrdDir, file), "utf-8");
|
|
11605
12166
|
const deps = parseDependencies(content);
|
|
11606
|
-
const claimPath =
|
|
12167
|
+
const claimPath = path30.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
|
|
11607
12168
|
const claimStatus = isClaimActive(claimPath, config.maxRuntime);
|
|
11608
12169
|
pending.push({
|
|
11609
12170
|
name: file,
|
|
@@ -11614,10 +12175,10 @@ function prdCommand(program2) {
|
|
|
11614
12175
|
}
|
|
11615
12176
|
}
|
|
11616
12177
|
const done = [];
|
|
11617
|
-
if (
|
|
11618
|
-
const files =
|
|
12178
|
+
if (fs29.existsSync(doneDir)) {
|
|
12179
|
+
const files = fs29.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
11619
12180
|
for (const file of files) {
|
|
11620
|
-
const content =
|
|
12181
|
+
const content = fs29.readFileSync(path30.join(doneDir, file), "utf-8");
|
|
11621
12182
|
const deps = parseDependencies(content);
|
|
11622
12183
|
done.push({ name: file, dependencies: deps });
|
|
11623
12184
|
}
|
|
@@ -11654,7 +12215,7 @@ import blessed6 from "blessed";
|
|
|
11654
12215
|
// src/commands/dashboard/tab-status.ts
|
|
11655
12216
|
init_dist();
|
|
11656
12217
|
import blessed from "blessed";
|
|
11657
|
-
import * as
|
|
12218
|
+
import * as fs30 from "fs";
|
|
11658
12219
|
function sortPrdsByPriority(prds, priority) {
|
|
11659
12220
|
if (priority.length === 0) return prds;
|
|
11660
12221
|
const priorityMap = /* @__PURE__ */ new Map();
|
|
@@ -11750,7 +12311,7 @@ function renderLogPane(projectDir, logs) {
|
|
|
11750
12311
|
let newestMtime = 0;
|
|
11751
12312
|
for (const log of existingLogs) {
|
|
11752
12313
|
try {
|
|
11753
|
-
const stat =
|
|
12314
|
+
const stat = fs30.statSync(log.path);
|
|
11754
12315
|
if (stat.mtimeMs > newestMtime) {
|
|
11755
12316
|
newestMtime = stat.mtimeMs;
|
|
11756
12317
|
newestLog = log;
|
|
@@ -13406,8 +13967,8 @@ function createActionsTab() {
|
|
|
13406
13967
|
// src/commands/dashboard/tab-logs.ts
|
|
13407
13968
|
init_dist();
|
|
13408
13969
|
import blessed5 from "blessed";
|
|
13409
|
-
import * as
|
|
13410
|
-
import * as
|
|
13970
|
+
import * as fs31 from "fs";
|
|
13971
|
+
import * as path31 from "path";
|
|
13411
13972
|
var LOG_NAMES = ["executor", "reviewer"];
|
|
13412
13973
|
var LOG_LINES = 200;
|
|
13413
13974
|
function createLogsTab() {
|
|
@@ -13448,7 +14009,7 @@ function createLogsTab() {
|
|
|
13448
14009
|
let activeKeyHandlers = [];
|
|
13449
14010
|
let activeCtx = null;
|
|
13450
14011
|
function getLogPath(projectDir, logName) {
|
|
13451
|
-
return
|
|
14012
|
+
return path31.join(projectDir, "logs", `${logName}.log`);
|
|
13452
14013
|
}
|
|
13453
14014
|
function updateSelector() {
|
|
13454
14015
|
const tabs = LOG_NAMES.map((name, idx) => {
|
|
@@ -13462,7 +14023,7 @@ function createLogsTab() {
|
|
|
13462
14023
|
function loadLog(ctx) {
|
|
13463
14024
|
const logName = LOG_NAMES[selectedLogIndex];
|
|
13464
14025
|
const logPath = getLogPath(ctx.projectDir, logName);
|
|
13465
|
-
if (!
|
|
14026
|
+
if (!fs31.existsSync(logPath)) {
|
|
13466
14027
|
logContent.setContent(
|
|
13467
14028
|
`{yellow-fg}No ${logName}.log file found{/yellow-fg}
|
|
13468
14029
|
|
|
@@ -13472,7 +14033,7 @@ Log will appear here once the ${logName} runs.`
|
|
|
13472
14033
|
return;
|
|
13473
14034
|
}
|
|
13474
14035
|
try {
|
|
13475
|
-
const stat =
|
|
14036
|
+
const stat = fs31.statSync(logPath);
|
|
13476
14037
|
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
13477
14038
|
logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
|
|
13478
14039
|
} catch {
|
|
@@ -13970,12 +14531,12 @@ function doctorCommand(program2) {
|
|
|
13970
14531
|
|
|
13971
14532
|
// src/commands/serve.ts
|
|
13972
14533
|
init_dist();
|
|
13973
|
-
import * as
|
|
14534
|
+
import * as fs36 from "fs";
|
|
13974
14535
|
|
|
13975
14536
|
// ../server/dist/index.js
|
|
13976
14537
|
init_dist();
|
|
13977
|
-
import * as
|
|
13978
|
-
import * as
|
|
14538
|
+
import * as fs35 from "fs";
|
|
14539
|
+
import * as path37 from "path";
|
|
13979
14540
|
import { dirname as dirname11 } from "path";
|
|
13980
14541
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
13981
14542
|
import cors from "cors";
|
|
@@ -14060,8 +14621,8 @@ function setupGracefulShutdown(server, beforeClose) {
|
|
|
14060
14621
|
|
|
14061
14622
|
// ../server/dist/middleware/project-resolver.middleware.js
|
|
14062
14623
|
init_dist();
|
|
14063
|
-
import * as
|
|
14064
|
-
import * as
|
|
14624
|
+
import * as fs32 from "fs";
|
|
14625
|
+
import * as path32 from "path";
|
|
14065
14626
|
function resolveProject(req, res, next) {
|
|
14066
14627
|
const projectId = req.params.projectId;
|
|
14067
14628
|
const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
|
|
@@ -14071,7 +14632,7 @@ function resolveProject(req, res, next) {
|
|
|
14071
14632
|
res.status(404).json({ error: `Project not found: ${decodedId}` });
|
|
14072
14633
|
return;
|
|
14073
14634
|
}
|
|
14074
|
-
if (!
|
|
14635
|
+
if (!fs32.existsSync(entry.path) || !fs32.existsSync(path32.join(entry.path, CONFIG_FILE_NAME))) {
|
|
14075
14636
|
res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
|
|
14076
14637
|
return;
|
|
14077
14638
|
}
|
|
@@ -14116,8 +14677,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
|
|
|
14116
14677
|
|
|
14117
14678
|
// ../server/dist/routes/action.routes.js
|
|
14118
14679
|
init_dist();
|
|
14119
|
-
import * as
|
|
14120
|
-
import * as
|
|
14680
|
+
import * as fs33 from "fs";
|
|
14681
|
+
import * as path33 from "path";
|
|
14121
14682
|
import { execSync as execSync6, spawn as spawn6 } from "child_process";
|
|
14122
14683
|
import { Router } from "express";
|
|
14123
14684
|
|
|
@@ -14155,17 +14716,17 @@ function getBoardProvider(config, projectDir) {
|
|
|
14155
14716
|
function cleanOrphanedClaims(dir) {
|
|
14156
14717
|
let entries;
|
|
14157
14718
|
try {
|
|
14158
|
-
entries =
|
|
14719
|
+
entries = fs33.readdirSync(dir, { withFileTypes: true });
|
|
14159
14720
|
} catch {
|
|
14160
14721
|
return;
|
|
14161
14722
|
}
|
|
14162
14723
|
for (const entry of entries) {
|
|
14163
|
-
const fullPath =
|
|
14724
|
+
const fullPath = path33.join(dir, entry.name);
|
|
14164
14725
|
if (entry.isDirectory() && entry.name !== "done") {
|
|
14165
14726
|
cleanOrphanedClaims(fullPath);
|
|
14166
14727
|
} else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
|
|
14167
14728
|
try {
|
|
14168
|
-
|
|
14729
|
+
fs33.unlinkSync(fullPath);
|
|
14169
14730
|
} catch {
|
|
14170
14731
|
}
|
|
14171
14732
|
}
|
|
@@ -14320,19 +14881,19 @@ function createActionRouteHandlers(ctx) {
|
|
|
14320
14881
|
res.status(400).json({ error: "Invalid PRD name" });
|
|
14321
14882
|
return;
|
|
14322
14883
|
}
|
|
14323
|
-
const prdDir =
|
|
14884
|
+
const prdDir = path33.join(projectDir, config.prdDir);
|
|
14324
14885
|
const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
|
|
14325
|
-
const pendingPath =
|
|
14326
|
-
const donePath =
|
|
14327
|
-
if (
|
|
14886
|
+
const pendingPath = path33.join(prdDir, normalized);
|
|
14887
|
+
const donePath = path33.join(prdDir, "done", normalized);
|
|
14888
|
+
if (fs33.existsSync(pendingPath)) {
|
|
14328
14889
|
res.json({ message: `"${normalized}" is already pending` });
|
|
14329
14890
|
return;
|
|
14330
14891
|
}
|
|
14331
|
-
if (!
|
|
14892
|
+
if (!fs33.existsSync(donePath)) {
|
|
14332
14893
|
res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
|
|
14333
14894
|
return;
|
|
14334
14895
|
}
|
|
14335
|
-
|
|
14896
|
+
fs33.renameSync(donePath, pendingPath);
|
|
14336
14897
|
res.json({ message: `Moved "${normalized}" back to pending` });
|
|
14337
14898
|
} catch (error2) {
|
|
14338
14899
|
res.status(500).json({
|
|
@@ -14350,11 +14911,11 @@ function createActionRouteHandlers(ctx) {
|
|
|
14350
14911
|
res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
|
|
14351
14912
|
return;
|
|
14352
14913
|
}
|
|
14353
|
-
if (
|
|
14354
|
-
|
|
14914
|
+
if (fs33.existsSync(lockPath)) {
|
|
14915
|
+
fs33.unlinkSync(lockPath);
|
|
14355
14916
|
}
|
|
14356
|
-
const prdDir =
|
|
14357
|
-
if (
|
|
14917
|
+
const prdDir = path33.join(projectDir, config.prdDir);
|
|
14918
|
+
if (fs33.existsSync(prdDir)) {
|
|
14358
14919
|
cleanOrphanedClaims(prdDir);
|
|
14359
14920
|
}
|
|
14360
14921
|
broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
|
|
@@ -15116,8 +15677,8 @@ function createProjectConfigRoutes() {
|
|
|
15116
15677
|
|
|
15117
15678
|
// ../server/dist/routes/doctor.routes.js
|
|
15118
15679
|
init_dist();
|
|
15119
|
-
import * as
|
|
15120
|
-
import * as
|
|
15680
|
+
import * as fs34 from "fs";
|
|
15681
|
+
import * as path34 from "path";
|
|
15121
15682
|
import { execSync as execSync7 } from "child_process";
|
|
15122
15683
|
import { Router as Router4 } from "express";
|
|
15123
15684
|
function runDoctorChecks(projectDir, config) {
|
|
@@ -15150,7 +15711,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
15150
15711
|
});
|
|
15151
15712
|
}
|
|
15152
15713
|
try {
|
|
15153
|
-
const projectName =
|
|
15714
|
+
const projectName = path34.basename(projectDir);
|
|
15154
15715
|
const marker = generateMarker(projectName);
|
|
15155
15716
|
const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
|
|
15156
15717
|
if (crontabEntries.length > 0) {
|
|
@@ -15173,8 +15734,8 @@ function runDoctorChecks(projectDir, config) {
|
|
|
15173
15734
|
detail: "Failed to check crontab"
|
|
15174
15735
|
});
|
|
15175
15736
|
}
|
|
15176
|
-
const configPath =
|
|
15177
|
-
if (
|
|
15737
|
+
const configPath = path34.join(projectDir, CONFIG_FILE_NAME);
|
|
15738
|
+
if (fs34.existsSync(configPath)) {
|
|
15178
15739
|
checks.push({ name: "config", status: "pass", detail: "Config file exists" });
|
|
15179
15740
|
} else {
|
|
15180
15741
|
checks.push({
|
|
@@ -15183,56 +15744,374 @@ function runDoctorChecks(projectDir, config) {
|
|
|
15183
15744
|
detail: "Config file not found (using defaults)"
|
|
15184
15745
|
});
|
|
15185
15746
|
}
|
|
15186
|
-
const prdDir =
|
|
15187
|
-
if (
|
|
15188
|
-
const prds =
|
|
15189
|
-
checks.push({
|
|
15190
|
-
name: "prdDir",
|
|
15191
|
-
status: "pass",
|
|
15192
|
-
detail: `PRD directory exists (${prds.length} PRDs)`
|
|
15193
|
-
});
|
|
15194
|
-
} else {
|
|
15195
|
-
checks.push({
|
|
15196
|
-
name: "prdDir",
|
|
15197
|
-
status: "warn",
|
|
15198
|
-
detail: `PRD directory not found: ${config.prdDir}`
|
|
15199
|
-
});
|
|
15747
|
+
const prdDir = path34.join(projectDir, config.prdDir);
|
|
15748
|
+
if (fs34.existsSync(prdDir)) {
|
|
15749
|
+
const prds = fs34.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
15750
|
+
checks.push({
|
|
15751
|
+
name: "prdDir",
|
|
15752
|
+
status: "pass",
|
|
15753
|
+
detail: `PRD directory exists (${prds.length} PRDs)`
|
|
15754
|
+
});
|
|
15755
|
+
} else {
|
|
15756
|
+
checks.push({
|
|
15757
|
+
name: "prdDir",
|
|
15758
|
+
status: "warn",
|
|
15759
|
+
detail: `PRD directory not found: ${config.prdDir}`
|
|
15760
|
+
});
|
|
15761
|
+
}
|
|
15762
|
+
return checks;
|
|
15763
|
+
}
|
|
15764
|
+
function createDoctorRoutes(deps) {
|
|
15765
|
+
const { projectDir, getConfig } = deps;
|
|
15766
|
+
const router = Router4();
|
|
15767
|
+
router.get("/", (_req, res) => {
|
|
15768
|
+
try {
|
|
15769
|
+
const checks = runDoctorChecks(projectDir, getConfig());
|
|
15770
|
+
res.json(checks);
|
|
15771
|
+
} catch (error2) {
|
|
15772
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
15773
|
+
}
|
|
15774
|
+
});
|
|
15775
|
+
return router;
|
|
15776
|
+
}
|
|
15777
|
+
function createProjectDoctorRoutes() {
|
|
15778
|
+
const router = Router4({ mergeParams: true });
|
|
15779
|
+
router.get("/doctor", (req, res) => {
|
|
15780
|
+
try {
|
|
15781
|
+
const checks = runDoctorChecks(req.projectDir, req.projectConfig);
|
|
15782
|
+
res.json(checks);
|
|
15783
|
+
} catch (error2) {
|
|
15784
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
15785
|
+
}
|
|
15786
|
+
});
|
|
15787
|
+
return router;
|
|
15788
|
+
}
|
|
15789
|
+
|
|
15790
|
+
// ../server/dist/routes/job.routes.js
|
|
15791
|
+
init_dist();
|
|
15792
|
+
import { spawn as spawn7 } from "child_process";
|
|
15793
|
+
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
15794
|
+
import { Router as Router5 } from "express";
|
|
15795
|
+
var SUPPORTED_GITHUB_EVENTS = [
|
|
15796
|
+
"workflow_run",
|
|
15797
|
+
"check_suite",
|
|
15798
|
+
"pull_request",
|
|
15799
|
+
"repository_dispatch"
|
|
15800
|
+
];
|
|
15801
|
+
function isSupportedGithubEvent(value) {
|
|
15802
|
+
return SUPPORTED_GITHUB_EVENTS.some((event) => event === value);
|
|
15803
|
+
}
|
|
15804
|
+
function getRawBody(req) {
|
|
15805
|
+
if (Buffer.isBuffer(req.body)) {
|
|
15806
|
+
return req.body;
|
|
15807
|
+
}
|
|
15808
|
+
if (typeof req.body === "string") {
|
|
15809
|
+
return Buffer.from(req.body, "utf-8");
|
|
15810
|
+
}
|
|
15811
|
+
return Buffer.alloc(0);
|
|
15812
|
+
}
|
|
15813
|
+
function verifyHmacSignature(rawBody, header2, secret) {
|
|
15814
|
+
const match = header2?.match(/^sha256=([a-f0-9]{64})$/i);
|
|
15815
|
+
if (!match) {
|
|
15816
|
+
return false;
|
|
15817
|
+
}
|
|
15818
|
+
const expected = createHmac("sha256", secret).update(rawBody).digest();
|
|
15819
|
+
const actual = Buffer.from(match[1], "hex");
|
|
15820
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
15821
|
+
}
|
|
15822
|
+
function getSignatureHeaders(req) {
|
|
15823
|
+
return [req.get("X-Night-Watch-Signature"), req.get("X-Hub-Signature-256")];
|
|
15824
|
+
}
|
|
15825
|
+
function getGithubWebhookHeaders(req) {
|
|
15826
|
+
return {
|
|
15827
|
+
event: req.get("X-GitHub-Event"),
|
|
15828
|
+
delivery: req.get("X-GitHub-Delivery"),
|
|
15829
|
+
signature: req.get("X-Hub-Signature-256")
|
|
15830
|
+
};
|
|
15831
|
+
}
|
|
15832
|
+
function hasGithubHeaders(headers) {
|
|
15833
|
+
return Boolean(headers.event && headers.signature);
|
|
15834
|
+
}
|
|
15835
|
+
function isObjectRecord(value) {
|
|
15836
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15837
|
+
}
|
|
15838
|
+
function readObject(value) {
|
|
15839
|
+
return isObjectRecord(value) ? value : void 0;
|
|
15840
|
+
}
|
|
15841
|
+
function readString(value) {
|
|
15842
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
15843
|
+
}
|
|
15844
|
+
function readNumberString(value) {
|
|
15845
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
15846
|
+
return String(value);
|
|
15847
|
+
return readString(value);
|
|
15848
|
+
}
|
|
15849
|
+
function readFirstPullRequestNumber(value) {
|
|
15850
|
+
if (!Array.isArray(value))
|
|
15851
|
+
return void 0;
|
|
15852
|
+
for (const item of value) {
|
|
15853
|
+
const pr = readObject(item);
|
|
15854
|
+
const number = readNumberString(pr?.number);
|
|
15855
|
+
if (number)
|
|
15856
|
+
return number;
|
|
15857
|
+
}
|
|
15858
|
+
return void 0;
|
|
15859
|
+
}
|
|
15860
|
+
function isFailureConclusion(value) {
|
|
15861
|
+
const conclusion = readString(value)?.toLowerCase();
|
|
15862
|
+
if (!conclusion)
|
|
15863
|
+
return false;
|
|
15864
|
+
return !["success", "neutral", "skipped"].includes(conclusion);
|
|
15865
|
+
}
|
|
15866
|
+
function parseJsonBody(rawBody) {
|
|
15867
|
+
if (rawBody.length === 0) {
|
|
15868
|
+
return {};
|
|
15869
|
+
}
|
|
15870
|
+
return JSON.parse(rawBody.toString("utf-8"));
|
|
15871
|
+
}
|
|
15872
|
+
function buildGithubContext(headers, payload) {
|
|
15873
|
+
if (!headers.event || !isSupportedGithubEvent(headers.event)) {
|
|
15874
|
+
return void 0;
|
|
15875
|
+
}
|
|
15876
|
+
const context = {
|
|
15877
|
+
event: headers.event,
|
|
15878
|
+
delivery: headers.delivery,
|
|
15879
|
+
action: readString(payload.action),
|
|
15880
|
+
failed: false
|
|
15881
|
+
};
|
|
15882
|
+
switch (headers.event) {
|
|
15883
|
+
case "workflow_run": {
|
|
15884
|
+
const workflowRun = readObject(payload.workflow_run);
|
|
15885
|
+
context.branch = readString(workflowRun?.head_branch);
|
|
15886
|
+
context.prNumber = readFirstPullRequestNumber(workflowRun?.pull_requests);
|
|
15887
|
+
context.failed = isFailureConclusion(workflowRun?.conclusion);
|
|
15888
|
+
break;
|
|
15889
|
+
}
|
|
15890
|
+
case "check_suite": {
|
|
15891
|
+
const checkSuite = readObject(payload.check_suite);
|
|
15892
|
+
context.branch = readString(checkSuite?.head_branch);
|
|
15893
|
+
context.prNumber = readFirstPullRequestNumber(checkSuite?.pull_requests);
|
|
15894
|
+
context.failed = isFailureConclusion(checkSuite?.conclusion);
|
|
15895
|
+
break;
|
|
15896
|
+
}
|
|
15897
|
+
case "pull_request": {
|
|
15898
|
+
const pullRequest = readObject(payload.pull_request);
|
|
15899
|
+
const head = readObject(pullRequest?.head);
|
|
15900
|
+
context.branch = readString(head?.ref);
|
|
15901
|
+
context.prNumber = readNumberString(payload.number) ?? readNumberString(pullRequest?.number);
|
|
15902
|
+
break;
|
|
15903
|
+
}
|
|
15904
|
+
case "repository_dispatch": {
|
|
15905
|
+
const clientPayload = readObject(payload.client_payload);
|
|
15906
|
+
context.action = readString(payload.action) ?? readString(payload.event_type);
|
|
15907
|
+
context.branch = readString(clientPayload?.branch) ?? readString(clientPayload?.ref) ?? readString(payload.ref) ?? readString(payload.branch);
|
|
15908
|
+
context.prNumber = readNumberString(clientPayload?.pr_number) ?? readNumberString(clientPayload?.pull_request_number) ?? readNumberString(payload.pr_number);
|
|
15909
|
+
context.failed = readString(clientPayload?.status)?.toLowerCase() === "failure" || readString(clientPayload?.conclusion)?.toLowerCase() === "failure";
|
|
15910
|
+
break;
|
|
15911
|
+
}
|
|
15912
|
+
}
|
|
15913
|
+
return context;
|
|
15914
|
+
}
|
|
15915
|
+
function globToRegExp(pattern) {
|
|
15916
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
15917
|
+
const wildcarded = escaped.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
15918
|
+
return new RegExp(`^${wildcarded}$`);
|
|
15919
|
+
}
|
|
15920
|
+
function matchesBranchPattern(branch, branchPatterns) {
|
|
15921
|
+
if (!branchPatterns || branchPatterns.length === 0)
|
|
15922
|
+
return true;
|
|
15923
|
+
if (!branch)
|
|
15924
|
+
return false;
|
|
15925
|
+
return branchPatterns.some((pattern) => globToRegExp(pattern).test(branch));
|
|
15926
|
+
}
|
|
15927
|
+
function findGithubWebhookMatch(config, headers, payload) {
|
|
15928
|
+
const context = buildGithubContext(headers, payload);
|
|
15929
|
+
if (!context)
|
|
15930
|
+
return void 0;
|
|
15931
|
+
if (config.webhookTriggers.github.events.length > 0) {
|
|
15932
|
+
const eventAllowed = config.webhookTriggers.github.events.includes(context.event);
|
|
15933
|
+
if (!eventAllowed)
|
|
15934
|
+
return void 0;
|
|
15935
|
+
}
|
|
15936
|
+
const rule = config.webhookTriggers.github.rules.find((candidate) => {
|
|
15937
|
+
if (candidate.event !== context.event)
|
|
15938
|
+
return false;
|
|
15939
|
+
if (candidate.action && candidate.action !== context.action)
|
|
15940
|
+
return false;
|
|
15941
|
+
if (candidate.onlyOnFailure && !context.failed)
|
|
15942
|
+
return false;
|
|
15943
|
+
return matchesBranchPattern(context.branch, candidate.branchPatterns);
|
|
15944
|
+
});
|
|
15945
|
+
if (!rule)
|
|
15946
|
+
return void 0;
|
|
15947
|
+
return { rule, context };
|
|
15948
|
+
}
|
|
15949
|
+
function buildGithubWebhookEnv(context) {
|
|
15950
|
+
const env = {
|
|
15951
|
+
NW_WEBHOOK_SOURCE: "github",
|
|
15952
|
+
NW_WEBHOOK_EVENT: context.event
|
|
15953
|
+
};
|
|
15954
|
+
if (context.delivery) {
|
|
15955
|
+
env.NW_WEBHOOK_DELIVERY = context.delivery;
|
|
15956
|
+
}
|
|
15957
|
+
if (context.prNumber) {
|
|
15958
|
+
env.NW_WEBHOOK_PR_NUMBER = context.prNumber;
|
|
15200
15959
|
}
|
|
15201
|
-
|
|
15960
|
+
if (context.branch) {
|
|
15961
|
+
env.NW_WEBHOOK_BRANCH = context.branch;
|
|
15962
|
+
}
|
|
15963
|
+
return env;
|
|
15202
15964
|
}
|
|
15203
|
-
function
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15965
|
+
function getLockPathForJob2(projectDir, jobId) {
|
|
15966
|
+
switch (jobId) {
|
|
15967
|
+
case "executor":
|
|
15968
|
+
return executorLockPath(projectDir);
|
|
15969
|
+
case "reviewer":
|
|
15970
|
+
return reviewerLockPath(projectDir);
|
|
15971
|
+
case "qa":
|
|
15972
|
+
return qaLockPath(projectDir);
|
|
15973
|
+
case "audit":
|
|
15974
|
+
return auditLockPath(projectDir);
|
|
15975
|
+
case "slicer":
|
|
15976
|
+
case "planner":
|
|
15977
|
+
return plannerLockPath(projectDir);
|
|
15978
|
+
case "analytics":
|
|
15979
|
+
return analyticsLockPath(projectDir);
|
|
15980
|
+
case "pr-resolver":
|
|
15981
|
+
return prResolverLockPath(projectDir);
|
|
15982
|
+
case "merger":
|
|
15983
|
+
return mergerLockPath(projectDir);
|
|
15984
|
+
}
|
|
15985
|
+
}
|
|
15986
|
+
function createJobRouteHandlers(ctx) {
|
|
15987
|
+
const router = Router5({ mergeParams: true });
|
|
15988
|
+
const p = ctx.pathPrefix;
|
|
15989
|
+
router.post(`/${p}:id/run`, (req, res) => {
|
|
15207
15990
|
try {
|
|
15208
|
-
const
|
|
15209
|
-
|
|
15991
|
+
const config = ctx.getConfig(req);
|
|
15992
|
+
const webhookConfig = config.webhookTriggers;
|
|
15993
|
+
if (!webhookConfig.enabled) {
|
|
15994
|
+
res.status(403).json({ error: "Webhook triggers are disabled" });
|
|
15995
|
+
return;
|
|
15996
|
+
}
|
|
15997
|
+
const secret = process.env[webhookConfig.secretEnv];
|
|
15998
|
+
if (!secret) {
|
|
15999
|
+
res.status(403).json({ error: "Webhook signing secret is not configured" });
|
|
16000
|
+
return;
|
|
16001
|
+
}
|
|
16002
|
+
const rawBody = getRawBody(req);
|
|
16003
|
+
const hasValidSignature = getSignatureHeaders(req).some((header2) => verifyHmacSignature(rawBody, header2, secret));
|
|
16004
|
+
if (!hasValidSignature) {
|
|
16005
|
+
res.status(401).json({ error: "Invalid signature" });
|
|
16006
|
+
return;
|
|
16007
|
+
}
|
|
16008
|
+
const requestedJobId = req.params.id;
|
|
16009
|
+
const requestedJobDef = JOB_REGISTRY.find((job) => job.id === requestedJobId);
|
|
16010
|
+
if (!requestedJobDef) {
|
|
16011
|
+
res.status(404).json({ error: "Unknown job id" });
|
|
16012
|
+
return;
|
|
16013
|
+
}
|
|
16014
|
+
if (!webhookConfig.allowedJobIds.includes(requestedJobDef.id)) {
|
|
16015
|
+
res.status(403).json({ error: "Job is not allowed for webhook dispatch" });
|
|
16016
|
+
return;
|
|
16017
|
+
}
|
|
16018
|
+
const githubHeaders = getGithubWebhookHeaders(req);
|
|
16019
|
+
let githubEnv = {};
|
|
16020
|
+
let matchedGithubJobId;
|
|
16021
|
+
if (hasGithubHeaders(githubHeaders) && webhookConfig.github.enabled) {
|
|
16022
|
+
let payload;
|
|
16023
|
+
try {
|
|
16024
|
+
payload = parseJsonBody(rawBody);
|
|
16025
|
+
} catch {
|
|
16026
|
+
res.status(400).json({ error: "Malformed JSON payload" });
|
|
16027
|
+
return;
|
|
16028
|
+
}
|
|
16029
|
+
const match = findGithubWebhookMatch(config, githubHeaders, readObject(payload) ?? {});
|
|
16030
|
+
if (!match) {
|
|
16031
|
+
const response2 = {
|
|
16032
|
+
accepted: false,
|
|
16033
|
+
ignored: true,
|
|
16034
|
+
reason: "No matching GitHub webhook rule"
|
|
16035
|
+
};
|
|
16036
|
+
res.status(202).json(response2);
|
|
16037
|
+
return;
|
|
16038
|
+
}
|
|
16039
|
+
matchedGithubJobId = match.rule.jobId;
|
|
16040
|
+
githubEnv = buildGithubWebhookEnv(match.context);
|
|
16041
|
+
}
|
|
16042
|
+
const dispatchJobId = matchedGithubJobId ?? requestedJobDef.id;
|
|
16043
|
+
const jobDef = JOB_REGISTRY.find((job) => job.id === dispatchJobId);
|
|
16044
|
+
if (!jobDef) {
|
|
16045
|
+
res.status(404).json({ error: "Unknown job id" });
|
|
16046
|
+
return;
|
|
16047
|
+
}
|
|
16048
|
+
if (!webhookConfig.allowedJobIds.includes(jobDef.id)) {
|
|
16049
|
+
res.status(403).json({ error: "Job is not allowed for webhook dispatch" });
|
|
16050
|
+
return;
|
|
16051
|
+
}
|
|
16052
|
+
const projectDir = ctx.getProjectDir(req);
|
|
16053
|
+
const lock = checkLockFile(getLockPathForJob2(projectDir, jobDef.id));
|
|
16054
|
+
if (lock.running) {
|
|
16055
|
+
res.status(409).json({
|
|
16056
|
+
error: `${jobDef.name} is already running (PID ${lock.pid})`,
|
|
16057
|
+
pid: lock.pid
|
|
16058
|
+
});
|
|
16059
|
+
return;
|
|
16060
|
+
}
|
|
16061
|
+
const dispatchId = randomUUID();
|
|
16062
|
+
const child = spawn7("night-watch", [jobDef.cliCommand], {
|
|
16063
|
+
detached: true,
|
|
16064
|
+
stdio: "ignore",
|
|
16065
|
+
cwd: projectDir,
|
|
16066
|
+
env: {
|
|
16067
|
+
...process.env,
|
|
16068
|
+
NW_WEBHOOK_DISPATCH_ID: dispatchId,
|
|
16069
|
+
NW_WEBHOOK_JOB_ID: jobDef.id,
|
|
16070
|
+
...githubEnv
|
|
16071
|
+
}
|
|
16072
|
+
});
|
|
16073
|
+
child.unref();
|
|
16074
|
+
if (child.pid === void 0) {
|
|
16075
|
+
res.status(500).json({ error: "Failed to spawn process: no PID assigned" });
|
|
16076
|
+
return;
|
|
16077
|
+
}
|
|
16078
|
+
const response = {
|
|
16079
|
+
accepted: true,
|
|
16080
|
+
jobId: jobDef.id,
|
|
16081
|
+
pid: child.pid,
|
|
16082
|
+
dispatchId
|
|
16083
|
+
};
|
|
16084
|
+
res.status(202).json(response);
|
|
15210
16085
|
} catch (error2) {
|
|
15211
|
-
res.status(500).json({
|
|
16086
|
+
res.status(500).json({
|
|
16087
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
16088
|
+
});
|
|
15212
16089
|
}
|
|
15213
16090
|
});
|
|
15214
16091
|
return router;
|
|
15215
16092
|
}
|
|
15216
|
-
function
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
16093
|
+
function createJobRoutes(deps) {
|
|
16094
|
+
return createJobRouteHandlers({
|
|
16095
|
+
getConfig: () => deps.getConfig(),
|
|
16096
|
+
getProjectDir: () => deps.projectDir,
|
|
16097
|
+
pathPrefix: ""
|
|
16098
|
+
});
|
|
16099
|
+
}
|
|
16100
|
+
function createProjectJobRoutes() {
|
|
16101
|
+
return createJobRouteHandlers({
|
|
16102
|
+
getConfig: (req) => req.projectConfig,
|
|
16103
|
+
getProjectDir: (req) => req.projectDir,
|
|
16104
|
+
pathPrefix: "jobs/"
|
|
15225
16105
|
});
|
|
15226
|
-
return router;
|
|
15227
16106
|
}
|
|
15228
16107
|
|
|
15229
16108
|
// ../server/dist/routes/log.routes.js
|
|
15230
16109
|
init_dist();
|
|
15231
|
-
import * as
|
|
15232
|
-
import { Router as
|
|
16110
|
+
import * as path35 from "path";
|
|
16111
|
+
import { Router as Router6 } from "express";
|
|
15233
16112
|
function createLogRoutes(deps) {
|
|
15234
16113
|
const { projectDir } = deps;
|
|
15235
|
-
const router =
|
|
16114
|
+
const router = Router6();
|
|
15236
16115
|
router.get("/:name", (req, res) => {
|
|
15237
16116
|
try {
|
|
15238
16117
|
const { name } = req.params;
|
|
@@ -15247,7 +16126,7 @@ function createLogRoutes(deps) {
|
|
|
15247
16126
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
15248
16127
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
15249
16128
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
15250
|
-
const logPath =
|
|
16129
|
+
const logPath = path35.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
15251
16130
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
15252
16131
|
res.json({ name, lines: logLines });
|
|
15253
16132
|
} catch (error2) {
|
|
@@ -15257,7 +16136,7 @@ function createLogRoutes(deps) {
|
|
|
15257
16136
|
return router;
|
|
15258
16137
|
}
|
|
15259
16138
|
function createProjectLogRoutes() {
|
|
15260
|
-
const router =
|
|
16139
|
+
const router = Router6({ mergeParams: true });
|
|
15261
16140
|
router.get("/logs/:name", (req, res) => {
|
|
15262
16141
|
try {
|
|
15263
16142
|
const projectDir = req.projectDir;
|
|
@@ -15273,7 +16152,7 @@ function createProjectLogRoutes() {
|
|
|
15273
16152
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
15274
16153
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
15275
16154
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
15276
|
-
const logPath =
|
|
16155
|
+
const logPath = path35.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
15277
16156
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
15278
16157
|
res.json({ name, lines: logLines });
|
|
15279
16158
|
} catch (error2) {
|
|
@@ -15284,9 +16163,9 @@ function createProjectLogRoutes() {
|
|
|
15284
16163
|
}
|
|
15285
16164
|
|
|
15286
16165
|
// ../server/dist/routes/prd.routes.js
|
|
15287
|
-
import { Router as
|
|
16166
|
+
import { Router as Router7 } from "express";
|
|
15288
16167
|
function createPrdRoutes(_deps) {
|
|
15289
|
-
const router =
|
|
16168
|
+
const router = Router7();
|
|
15290
16169
|
router.get("/", (_req, res) => {
|
|
15291
16170
|
res.status(410).json({ error: "PRDs endpoint deprecated - use GitHub Board instead" });
|
|
15292
16171
|
});
|
|
@@ -15296,7 +16175,7 @@ function createPrdRoutes(_deps) {
|
|
|
15296
16175
|
return router;
|
|
15297
16176
|
}
|
|
15298
16177
|
function createProjectPrdRoutes() {
|
|
15299
|
-
const router =
|
|
16178
|
+
const router = Router7({ mergeParams: true });
|
|
15300
16179
|
router.get("/prds", (_req, res) => {
|
|
15301
16180
|
res.status(410).json({ error: "PRDs endpoint deprecated - use GitHub Board instead" });
|
|
15302
16181
|
});
|
|
@@ -15308,17 +16187,17 @@ function createProjectPrdRoutes() {
|
|
|
15308
16187
|
|
|
15309
16188
|
// ../server/dist/routes/roadmap.routes.js
|
|
15310
16189
|
init_dist();
|
|
15311
|
-
import * as
|
|
15312
|
-
import { Router as
|
|
16190
|
+
import * as path36 from "path";
|
|
16191
|
+
import { Router as Router8 } from "express";
|
|
15313
16192
|
function createRoadmapRouteHandlers(ctx) {
|
|
15314
|
-
const router =
|
|
16193
|
+
const router = Router8({ mergeParams: true });
|
|
15315
16194
|
const p = ctx.pathPrefix;
|
|
15316
16195
|
router.get(`/${p}`, (req, res) => {
|
|
15317
16196
|
try {
|
|
15318
16197
|
const config = ctx.getConfig(req);
|
|
15319
16198
|
const projectDir = ctx.getProjectDir(req);
|
|
15320
16199
|
const status = getRoadmapStatus(projectDir, config);
|
|
15321
|
-
const prdDir =
|
|
16200
|
+
const prdDir = path36.join(projectDir, config.prdDir);
|
|
15322
16201
|
const state = loadRoadmapState(prdDir);
|
|
15323
16202
|
res.json({
|
|
15324
16203
|
...status,
|
|
@@ -15392,11 +16271,11 @@ function createProjectRoadmapRoutes() {
|
|
|
15392
16271
|
|
|
15393
16272
|
// ../server/dist/routes/status.routes.js
|
|
15394
16273
|
init_dist();
|
|
15395
|
-
import { Router as
|
|
16274
|
+
import { Router as Router9 } from "express";
|
|
15396
16275
|
import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
|
|
15397
16276
|
function createStatusRoutes(deps) {
|
|
15398
16277
|
const { projectDir, getConfig, sseClients } = deps;
|
|
15399
|
-
const router =
|
|
16278
|
+
const router = Router9();
|
|
15400
16279
|
router.get("/events", (req, res) => {
|
|
15401
16280
|
res.setHeader("Content-Type", "text/event-stream");
|
|
15402
16281
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -15525,7 +16404,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
15525
16404
|
}
|
|
15526
16405
|
function createScheduleInfoRoutes(deps) {
|
|
15527
16406
|
const { projectDir, getConfig } = deps;
|
|
15528
|
-
const router =
|
|
16407
|
+
const router = Router9();
|
|
15529
16408
|
router.get("/", async (_req, res) => {
|
|
15530
16409
|
try {
|
|
15531
16410
|
const config = getConfig();
|
|
@@ -15539,7 +16418,7 @@ function createScheduleInfoRoutes(deps) {
|
|
|
15539
16418
|
}
|
|
15540
16419
|
function createProjectSseRoutes(deps) {
|
|
15541
16420
|
const { projectSseClients, projectSseWatchers } = deps;
|
|
15542
|
-
const router =
|
|
16421
|
+
const router = Router9({ mergeParams: true });
|
|
15543
16422
|
router.get("/status/events", (req, res) => {
|
|
15544
16423
|
const projectDir = req.projectDir;
|
|
15545
16424
|
if (!projectSseClients.has(projectDir)) {
|
|
@@ -15596,9 +16475,9 @@ data: ${JSON.stringify(snapshot)}
|
|
|
15596
16475
|
|
|
15597
16476
|
// ../server/dist/routes/queue.routes.js
|
|
15598
16477
|
init_dist();
|
|
15599
|
-
import { Router as
|
|
16478
|
+
import { Router as Router10 } from "express";
|
|
15600
16479
|
function createGlobalQueueRoutes() {
|
|
15601
|
-
const router =
|
|
16480
|
+
const router = Router10();
|
|
15602
16481
|
router.get("/status", async (_req, res) => {
|
|
15603
16482
|
try {
|
|
15604
16483
|
const status = getQueueStatus();
|
|
@@ -15633,7 +16512,7 @@ function createGlobalQueueRoutes() {
|
|
|
15633
16512
|
}
|
|
15634
16513
|
function createQueueRoutes(deps) {
|
|
15635
16514
|
const { getConfig } = deps;
|
|
15636
|
-
const router =
|
|
16515
|
+
const router = Router10();
|
|
15637
16516
|
router.get("/status", async (_req, res) => {
|
|
15638
16517
|
try {
|
|
15639
16518
|
const config = getConfig();
|
|
@@ -15671,15 +16550,20 @@ function createQueueRoutes(deps) {
|
|
|
15671
16550
|
// ../server/dist/index.js
|
|
15672
16551
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
15673
16552
|
var __dirname4 = dirname11(__filename4);
|
|
16553
|
+
var JOB_RAW_BODY_LIMIT = "1mb";
|
|
16554
|
+
function setupJobRawBodyParsing(app) {
|
|
16555
|
+
app.use("/api/jobs", express.raw({ type: "*/*", limit: JOB_RAW_BODY_LIMIT }));
|
|
16556
|
+
app.use("/api/projects/:projectId/jobs", express.raw({ type: "*/*", limit: JOB_RAW_BODY_LIMIT }));
|
|
16557
|
+
}
|
|
15674
16558
|
function resolveWebDistPath() {
|
|
15675
|
-
const bundled =
|
|
15676
|
-
if (
|
|
16559
|
+
const bundled = path37.join(__dirname4, "web");
|
|
16560
|
+
if (fs35.existsSync(path37.join(bundled, "index.html")))
|
|
15677
16561
|
return bundled;
|
|
15678
16562
|
let d = __dirname4;
|
|
15679
16563
|
for (let i = 0; i < 8; i++) {
|
|
15680
|
-
if (
|
|
15681
|
-
const dev =
|
|
15682
|
-
if (
|
|
16564
|
+
if (fs35.existsSync(path37.join(d, "turbo.json"))) {
|
|
16565
|
+
const dev = path37.join(d, "web/dist");
|
|
16566
|
+
if (fs35.existsSync(path37.join(dev, "index.html")))
|
|
15683
16567
|
return dev;
|
|
15684
16568
|
break;
|
|
15685
16569
|
}
|
|
@@ -15689,7 +16573,7 @@ function resolveWebDistPath() {
|
|
|
15689
16573
|
}
|
|
15690
16574
|
function setupStaticFiles(app) {
|
|
15691
16575
|
const webDistPath = resolveWebDistPath();
|
|
15692
|
-
if (
|
|
16576
|
+
if (fs35.existsSync(webDistPath)) {
|
|
15693
16577
|
app.use(express.static(webDistPath));
|
|
15694
16578
|
}
|
|
15695
16579
|
app.use((req, res, next) => {
|
|
@@ -15697,8 +16581,8 @@ function setupStaticFiles(app) {
|
|
|
15697
16581
|
next();
|
|
15698
16582
|
return;
|
|
15699
16583
|
}
|
|
15700
|
-
const indexPath =
|
|
15701
|
-
if (
|
|
16584
|
+
const indexPath = path37.resolve(webDistPath, "index.html");
|
|
16585
|
+
if (fs35.existsSync(indexPath)) {
|
|
15702
16586
|
res.sendFile(indexPath, (err) => {
|
|
15703
16587
|
if (err)
|
|
15704
16588
|
next();
|
|
@@ -15716,6 +16600,7 @@ function setupStaticFiles(app) {
|
|
|
15716
16600
|
function createApp(projectDir) {
|
|
15717
16601
|
const app = express();
|
|
15718
16602
|
app.use(cors());
|
|
16603
|
+
setupJobRawBodyParsing(app);
|
|
15719
16604
|
app.use(express.json());
|
|
15720
16605
|
let config = loadConfig(projectDir);
|
|
15721
16606
|
const reloadConfig = () => {
|
|
@@ -15732,6 +16617,7 @@ function createApp(projectDir) {
|
|
|
15732
16617
|
app.use("/api/config", createConfigRoutes({ projectDir, getConfig: () => config, reloadConfig }));
|
|
15733
16618
|
app.use("/api/board", createBoardRoutes({ projectDir, getConfig: () => config }));
|
|
15734
16619
|
app.use("/api/actions", createActionRoutes({ projectDir, getConfig: () => config, sseClients }));
|
|
16620
|
+
app.use("/api/jobs", createJobRoutes({ projectDir, getConfig: () => config }));
|
|
15735
16621
|
app.use("/api/roadmap", createRoadmapRoutes({ projectDir, getConfig: () => config, reloadConfig }));
|
|
15736
16622
|
app.use("/api/logs", createLogRoutes({ projectDir }));
|
|
15737
16623
|
app.use("/api/doctor", createDoctorRoutes({ projectDir, getConfig: () => config }));
|
|
@@ -15781,6 +16667,7 @@ function createProjectRouter() {
|
|
|
15781
16667
|
router.use(createProjectLogRoutes());
|
|
15782
16668
|
router.use(createProjectBoardRoutes());
|
|
15783
16669
|
router.use(createProjectActionRoutes({ projectSseClients }));
|
|
16670
|
+
router.use(createProjectJobRoutes());
|
|
15784
16671
|
router.use(createProjectRoadmapRoutes());
|
|
15785
16672
|
router.get("/prs", async (req, res) => {
|
|
15786
16673
|
try {
|
|
@@ -15794,6 +16681,7 @@ function createProjectRouter() {
|
|
|
15794
16681
|
function createGlobalApp() {
|
|
15795
16682
|
const app = express();
|
|
15796
16683
|
app.use(cors());
|
|
16684
|
+
setupJobRawBodyParsing(app);
|
|
15797
16685
|
app.use(express.json());
|
|
15798
16686
|
app.get("/api/mode", (_req, res) => {
|
|
15799
16687
|
res.json({ globalMode: true });
|
|
@@ -15831,7 +16719,7 @@ function createGlobalApp() {
|
|
|
15831
16719
|
return app;
|
|
15832
16720
|
}
|
|
15833
16721
|
function bootContainer() {
|
|
15834
|
-
initContainer(
|
|
16722
|
+
initContainer(path37.dirname(getDbPath()));
|
|
15835
16723
|
}
|
|
15836
16724
|
function startServer(projectDir, port) {
|
|
15837
16725
|
bootContainer();
|
|
@@ -15884,8 +16772,8 @@ function isProcessRunning2(pid) {
|
|
|
15884
16772
|
}
|
|
15885
16773
|
function readPid(lockPath) {
|
|
15886
16774
|
try {
|
|
15887
|
-
if (!
|
|
15888
|
-
const raw =
|
|
16775
|
+
if (!fs36.existsSync(lockPath)) return null;
|
|
16776
|
+
const raw = fs36.readFileSync(lockPath, "utf-8").trim();
|
|
15889
16777
|
const pid = parseInt(raw, 10);
|
|
15890
16778
|
return Number.isFinite(pid) ? pid : null;
|
|
15891
16779
|
} catch {
|
|
@@ -15897,10 +16785,10 @@ function acquireServeLock(mode, port) {
|
|
|
15897
16785
|
let stalePidCleaned;
|
|
15898
16786
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
15899
16787
|
try {
|
|
15900
|
-
const fd =
|
|
15901
|
-
|
|
16788
|
+
const fd = fs36.openSync(lockPath, "wx");
|
|
16789
|
+
fs36.writeFileSync(fd, `${process.pid}
|
|
15902
16790
|
`);
|
|
15903
|
-
|
|
16791
|
+
fs36.closeSync(fd);
|
|
15904
16792
|
return { acquired: true, lockPath, stalePidCleaned };
|
|
15905
16793
|
} catch (error2) {
|
|
15906
16794
|
const err = error2;
|
|
@@ -15921,7 +16809,7 @@ function acquireServeLock(mode, port) {
|
|
|
15921
16809
|
};
|
|
15922
16810
|
}
|
|
15923
16811
|
try {
|
|
15924
|
-
|
|
16812
|
+
fs36.unlinkSync(lockPath);
|
|
15925
16813
|
if (existingPid) {
|
|
15926
16814
|
stalePidCleaned = existingPid;
|
|
15927
16815
|
}
|
|
@@ -15944,10 +16832,10 @@ function acquireServeLock(mode, port) {
|
|
|
15944
16832
|
}
|
|
15945
16833
|
function releaseServeLock(lockPath) {
|
|
15946
16834
|
try {
|
|
15947
|
-
if (!
|
|
16835
|
+
if (!fs36.existsSync(lockPath)) return;
|
|
15948
16836
|
const lockPid = readPid(lockPath);
|
|
15949
16837
|
if (lockPid !== null && lockPid !== process.pid) return;
|
|
15950
|
-
|
|
16838
|
+
fs36.unlinkSync(lockPath);
|
|
15951
16839
|
} catch {
|
|
15952
16840
|
}
|
|
15953
16841
|
}
|
|
@@ -16043,14 +16931,14 @@ function historyCommand(program2) {
|
|
|
16043
16931
|
// src/commands/update.ts
|
|
16044
16932
|
init_dist();
|
|
16045
16933
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
16046
|
-
import * as
|
|
16047
|
-
import * as
|
|
16934
|
+
import * as fs37 from "fs";
|
|
16935
|
+
import * as path38 from "path";
|
|
16048
16936
|
var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
|
|
16049
16937
|
function parseProjectDirs(projects, cwd) {
|
|
16050
16938
|
if (!projects || projects.trim().length === 0) {
|
|
16051
16939
|
return [cwd];
|
|
16052
16940
|
}
|
|
16053
|
-
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) =>
|
|
16941
|
+
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path38.resolve(cwd, entry));
|
|
16054
16942
|
return Array.from(new Set(dirs));
|
|
16055
16943
|
}
|
|
16056
16944
|
function shouldInstallGlobal(options) {
|
|
@@ -16092,7 +16980,7 @@ function updateCommand(program2) {
|
|
|
16092
16980
|
}
|
|
16093
16981
|
const nightWatchBin = resolveNightWatchBin();
|
|
16094
16982
|
for (const projectDir of projectDirs) {
|
|
16095
|
-
if (!
|
|
16983
|
+
if (!fs37.existsSync(projectDir) || !fs37.statSync(projectDir).isDirectory()) {
|
|
16096
16984
|
warn(`Skipping invalid project directory: ${projectDir}`);
|
|
16097
16985
|
continue;
|
|
16098
16986
|
}
|
|
@@ -16136,8 +17024,8 @@ function prdStateCommand(program2) {
|
|
|
16136
17024
|
|
|
16137
17025
|
// src/commands/retry.ts
|
|
16138
17026
|
init_dist();
|
|
16139
|
-
import * as
|
|
16140
|
-
import * as
|
|
17027
|
+
import * as fs38 from "fs";
|
|
17028
|
+
import * as path39 from "path";
|
|
16141
17029
|
function normalizePrdName(name) {
|
|
16142
17030
|
if (!name.endsWith(".md")) {
|
|
16143
17031
|
return `${name}.md`;
|
|
@@ -16145,26 +17033,26 @@ function normalizePrdName(name) {
|
|
|
16145
17033
|
return name;
|
|
16146
17034
|
}
|
|
16147
17035
|
function getDonePrds(doneDir) {
|
|
16148
|
-
if (!
|
|
17036
|
+
if (!fs38.existsSync(doneDir)) {
|
|
16149
17037
|
return [];
|
|
16150
17038
|
}
|
|
16151
|
-
return
|
|
17039
|
+
return fs38.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
16152
17040
|
}
|
|
16153
17041
|
function retryCommand(program2) {
|
|
16154
17042
|
program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
|
|
16155
17043
|
const projectDir = process.cwd();
|
|
16156
17044
|
const config = loadConfig(projectDir);
|
|
16157
|
-
const prdDir =
|
|
16158
|
-
const doneDir =
|
|
17045
|
+
const prdDir = path39.join(projectDir, config.prdDir);
|
|
17046
|
+
const doneDir = path39.join(prdDir, "done");
|
|
16159
17047
|
const normalizedPrdName = normalizePrdName(prdName);
|
|
16160
|
-
const pendingPath =
|
|
16161
|
-
if (
|
|
17048
|
+
const pendingPath = path39.join(prdDir, normalizedPrdName);
|
|
17049
|
+
if (fs38.existsSync(pendingPath)) {
|
|
16162
17050
|
info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
|
|
16163
17051
|
return;
|
|
16164
17052
|
}
|
|
16165
|
-
const donePath =
|
|
16166
|
-
if (
|
|
16167
|
-
|
|
17053
|
+
const donePath = path39.join(doneDir, normalizedPrdName);
|
|
17054
|
+
if (fs38.existsSync(donePath)) {
|
|
17055
|
+
fs38.renameSync(donePath, pendingPath);
|
|
16168
17056
|
success(`Moved "${normalizedPrdName}" back to pending.`);
|
|
16169
17057
|
dim(`From: ${donePath}`);
|
|
16170
17058
|
dim(`To: ${pendingPath}`);
|
|
@@ -16416,7 +17304,7 @@ function prdsCommand(program2) {
|
|
|
16416
17304
|
|
|
16417
17305
|
// src/commands/cancel.ts
|
|
16418
17306
|
init_dist();
|
|
16419
|
-
import * as
|
|
17307
|
+
import * as fs39 from "fs";
|
|
16420
17308
|
import * as readline2 from "readline";
|
|
16421
17309
|
function getLockFilePaths2(projectDir) {
|
|
16422
17310
|
const runtimeKey = projectRuntimeKey(projectDir);
|
|
@@ -16463,7 +17351,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
16463
17351
|
const pid = lockStatus.pid;
|
|
16464
17352
|
if (!lockStatus.running) {
|
|
16465
17353
|
try {
|
|
16466
|
-
|
|
17354
|
+
fs39.unlinkSync(lockPath);
|
|
16467
17355
|
return {
|
|
16468
17356
|
success: true,
|
|
16469
17357
|
message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
|
|
@@ -16501,7 +17389,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
16501
17389
|
await sleep2(3e3);
|
|
16502
17390
|
if (!isProcessRunning3(pid)) {
|
|
16503
17391
|
try {
|
|
16504
|
-
|
|
17392
|
+
fs39.unlinkSync(lockPath);
|
|
16505
17393
|
} catch {
|
|
16506
17394
|
}
|
|
16507
17395
|
return {
|
|
@@ -16536,7 +17424,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
16536
17424
|
await sleep2(500);
|
|
16537
17425
|
if (!isProcessRunning3(pid)) {
|
|
16538
17426
|
try {
|
|
16539
|
-
|
|
17427
|
+
fs39.unlinkSync(lockPath);
|
|
16540
17428
|
} catch {
|
|
16541
17429
|
}
|
|
16542
17430
|
return {
|
|
@@ -16597,31 +17485,31 @@ function cancelCommand(program2) {
|
|
|
16597
17485
|
|
|
16598
17486
|
// src/commands/slice.ts
|
|
16599
17487
|
init_dist();
|
|
16600
|
-
import * as
|
|
16601
|
-
import * as
|
|
17488
|
+
import * as fs40 from "fs";
|
|
17489
|
+
import * as path40 from "path";
|
|
16602
17490
|
function plannerLockPath2(projectDir) {
|
|
16603
17491
|
return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
|
|
16604
17492
|
}
|
|
16605
17493
|
function acquirePlannerLock(projectDir) {
|
|
16606
17494
|
const lockFile = plannerLockPath2(projectDir);
|
|
16607
|
-
if (
|
|
16608
|
-
const pidRaw =
|
|
17495
|
+
if (fs40.existsSync(lockFile)) {
|
|
17496
|
+
const pidRaw = fs40.readFileSync(lockFile, "utf-8").trim();
|
|
16609
17497
|
const pid = parseInt(pidRaw, 10);
|
|
16610
17498
|
if (!Number.isNaN(pid) && isProcessRunning(pid)) {
|
|
16611
17499
|
return { acquired: false, lockFile, pid };
|
|
16612
17500
|
}
|
|
16613
17501
|
try {
|
|
16614
|
-
|
|
17502
|
+
fs40.unlinkSync(lockFile);
|
|
16615
17503
|
} catch {
|
|
16616
17504
|
}
|
|
16617
17505
|
}
|
|
16618
|
-
|
|
17506
|
+
fs40.writeFileSync(lockFile, String(process.pid));
|
|
16619
17507
|
return { acquired: true, lockFile };
|
|
16620
17508
|
}
|
|
16621
17509
|
function releasePlannerLock(lockFile) {
|
|
16622
17510
|
try {
|
|
16623
|
-
if (
|
|
16624
|
-
|
|
17511
|
+
if (fs40.existsSync(lockFile)) {
|
|
17512
|
+
fs40.unlinkSync(lockFile);
|
|
16625
17513
|
}
|
|
16626
17514
|
} catch {
|
|
16627
17515
|
}
|
|
@@ -16630,12 +17518,12 @@ function resolvePlannerIssueColumn(config) {
|
|
|
16630
17518
|
return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
|
|
16631
17519
|
}
|
|
16632
17520
|
function buildPlannerIssueBody(projectDir, config, result) {
|
|
16633
|
-
const relativePrdPath =
|
|
16634
|
-
const absolutePrdPath =
|
|
17521
|
+
const relativePrdPath = path40.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
|
|
17522
|
+
const absolutePrdPath = path40.join(projectDir, config.prdDir, result.file ?? "");
|
|
16635
17523
|
const sourceItem = result.item;
|
|
16636
17524
|
let prdContent;
|
|
16637
17525
|
try {
|
|
16638
|
-
prdContent =
|
|
17526
|
+
prdContent = fs40.readFileSync(absolutePrdPath, "utf-8");
|
|
16639
17527
|
} catch {
|
|
16640
17528
|
prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
|
|
16641
17529
|
}
|
|
@@ -16842,7 +17730,7 @@ function sliceCommand(program2) {
|
|
|
16842
17730
|
if (!options.dryRun && result.sliced) {
|
|
16843
17731
|
await sendNotifications(config, {
|
|
16844
17732
|
event: "run_succeeded",
|
|
16845
|
-
projectName:
|
|
17733
|
+
projectName: path40.basename(projectDir),
|
|
16846
17734
|
exitCode,
|
|
16847
17735
|
provider: config.provider,
|
|
16848
17736
|
prTitle: result.item?.title
|
|
@@ -16850,7 +17738,7 @@ function sliceCommand(program2) {
|
|
|
16850
17738
|
} else if (!options.dryRun && !nothingPending) {
|
|
16851
17739
|
await sendNotifications(config, {
|
|
16852
17740
|
event: "run_failed",
|
|
16853
|
-
projectName:
|
|
17741
|
+
projectName: path40.basename(projectDir),
|
|
16854
17742
|
exitCode,
|
|
16855
17743
|
provider: config.provider
|
|
16856
17744
|
});
|
|
@@ -16867,20 +17755,20 @@ function sliceCommand(program2) {
|
|
|
16867
17755
|
// src/commands/state.ts
|
|
16868
17756
|
init_dist();
|
|
16869
17757
|
import * as os9 from "os";
|
|
16870
|
-
import * as
|
|
17758
|
+
import * as path41 from "path";
|
|
16871
17759
|
import chalk5 from "chalk";
|
|
16872
17760
|
import { Command } from "commander";
|
|
16873
17761
|
function createStateCommand() {
|
|
16874
17762
|
const state = new Command("state");
|
|
16875
17763
|
state.description("Manage Night Watch state");
|
|
16876
17764
|
state.command("migrate").description("Migrate legacy JSON state files to SQLite").option("--dry-run", "Show what would be migrated without making changes").action((opts) => {
|
|
16877
|
-
const nightWatchHome = process.env.NIGHT_WATCH_HOME ||
|
|
17765
|
+
const nightWatchHome = process.env.NIGHT_WATCH_HOME || path41.join(os9.homedir(), GLOBAL_CONFIG_DIR);
|
|
16878
17766
|
if (opts.dryRun) {
|
|
16879
17767
|
console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
|
|
16880
17768
|
console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
|
|
16881
|
-
console.log(` ${
|
|
16882
|
-
console.log(` ${
|
|
16883
|
-
console.log(` ${
|
|
17769
|
+
console.log(` ${path41.join(nightWatchHome, "projects.json")}`);
|
|
17770
|
+
console.log(` ${path41.join(nightWatchHome, "history.json")}`);
|
|
17771
|
+
console.log(` ${path41.join(nightWatchHome, "prd-states.json")}`);
|
|
16884
17772
|
console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
|
|
16885
17773
|
console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
|
|
16886
17774
|
return;
|
|
@@ -16918,8 +17806,8 @@ function createStateCommand() {
|
|
|
16918
17806
|
init_dist();
|
|
16919
17807
|
init_dist();
|
|
16920
17808
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
16921
|
-
import * as
|
|
16922
|
-
import * as
|
|
17809
|
+
import * as fs41 from "fs";
|
|
17810
|
+
import * as path42 from "path";
|
|
16923
17811
|
import * as readline3 from "readline";
|
|
16924
17812
|
import chalk6 from "chalk";
|
|
16925
17813
|
async function run(fn) {
|
|
@@ -16941,7 +17829,7 @@ function getProvider(config, cwd) {
|
|
|
16941
17829
|
return createBoardProvider(bp, cwd);
|
|
16942
17830
|
}
|
|
16943
17831
|
function defaultBoardTitle(cwd) {
|
|
16944
|
-
return `${
|
|
17832
|
+
return `${path42.basename(cwd)} Night Watch`;
|
|
16945
17833
|
}
|
|
16946
17834
|
async function ensureBoardConfigured(config, cwd, provider, options) {
|
|
16947
17835
|
if (config.boardProvider?.projectNumber) {
|
|
@@ -17140,11 +18028,11 @@ function boardCommand(program2) {
|
|
|
17140
18028
|
let body = options.body ?? "";
|
|
17141
18029
|
if (options.bodyFile) {
|
|
17142
18030
|
const filePath = options.bodyFile;
|
|
17143
|
-
if (!
|
|
18031
|
+
if (!fs41.existsSync(filePath)) {
|
|
17144
18032
|
console.error(`File not found: ${filePath}`);
|
|
17145
18033
|
process.exit(1);
|
|
17146
18034
|
}
|
|
17147
|
-
body =
|
|
18035
|
+
body = fs41.readFileSync(filePath, "utf-8");
|
|
17148
18036
|
}
|
|
17149
18037
|
const labels = [];
|
|
17150
18038
|
if (options.label) {
|
|
@@ -17385,12 +18273,12 @@ function boardCommand(program2) {
|
|
|
17385
18273
|
const config = loadConfig(cwd);
|
|
17386
18274
|
const provider = getProvider(config, cwd);
|
|
17387
18275
|
await ensureBoardConfigured(config, cwd, provider);
|
|
17388
|
-
const roadmapPath = options.roadmap ??
|
|
17389
|
-
if (!
|
|
18276
|
+
const roadmapPath = options.roadmap ?? path42.join(cwd, "ROADMAP.md");
|
|
18277
|
+
if (!fs41.existsSync(roadmapPath)) {
|
|
17390
18278
|
console.error(`Roadmap file not found: ${roadmapPath}`);
|
|
17391
18279
|
process.exit(1);
|
|
17392
18280
|
}
|
|
17393
|
-
const roadmapContent =
|
|
18281
|
+
const roadmapContent = fs41.readFileSync(roadmapPath, "utf-8");
|
|
17394
18282
|
const items = parseRoadmap(roadmapContent);
|
|
17395
18283
|
const uncheckedItems = getUncheckedItems(items);
|
|
17396
18284
|
if (uncheckedItems.length === 0) {
|
|
@@ -17514,12 +18402,12 @@ function boardCommand(program2) {
|
|
|
17514
18402
|
// src/commands/queue.ts
|
|
17515
18403
|
init_dist();
|
|
17516
18404
|
init_dist();
|
|
17517
|
-
import * as
|
|
17518
|
-
import { spawn as
|
|
18405
|
+
import * as path43 from "path";
|
|
18406
|
+
import { spawn as spawn8 } from "child_process";
|
|
17519
18407
|
import chalk7 from "chalk";
|
|
17520
18408
|
import { Command as Command2 } from "commander";
|
|
17521
18409
|
var logger6 = createLogger("queue");
|
|
17522
|
-
var
|
|
18410
|
+
var VALID_JOB_TYPES3 = [
|
|
17523
18411
|
"executor",
|
|
17524
18412
|
"reviewer",
|
|
17525
18413
|
"qa",
|
|
@@ -17552,6 +18440,13 @@ function printQueueEntry(entry, indent = "") {
|
|
|
17552
18440
|
console.log(`${indent} Dispatched: ${formatTimestamp(entry.dispatchedAt)}`);
|
|
17553
18441
|
}
|
|
17554
18442
|
}
|
|
18443
|
+
function isJobPaused(projectDir, jobType) {
|
|
18444
|
+
try {
|
|
18445
|
+
return loadConfig(projectDir).pausedJobs?.[jobType] === true;
|
|
18446
|
+
} catch {
|
|
18447
|
+
return false;
|
|
18448
|
+
}
|
|
18449
|
+
}
|
|
17555
18450
|
function createQueueCommand() {
|
|
17556
18451
|
const queue = new Command2("queue");
|
|
17557
18452
|
queue.description("Manage the global job queue");
|
|
@@ -17620,18 +18515,18 @@ function createQueueCommand() {
|
|
|
17620
18515
|
}
|
|
17621
18516
|
});
|
|
17622
18517
|
queue.command("clear").description("Clear pending jobs from the queue").option("--type <type>", "Only clear jobs of this type").option("--all", "Clear all entries including running (dangerous)").action((opts) => {
|
|
17623
|
-
if (opts.type && !
|
|
18518
|
+
if (opts.type && !VALID_JOB_TYPES3.includes(opts.type)) {
|
|
17624
18519
|
console.error(chalk7.red(`Invalid job type: ${opts.type}`));
|
|
17625
|
-
console.error(chalk7.dim(`Valid types: ${
|
|
18520
|
+
console.error(chalk7.dim(`Valid types: ${VALID_JOB_TYPES3.join(", ")}`));
|
|
17626
18521
|
process.exit(1);
|
|
17627
18522
|
}
|
|
17628
18523
|
const count = clearQueue(opts.type);
|
|
17629
18524
|
console.log(chalk7.green(`Cleared ${count} pending job(s) from the queue.`));
|
|
17630
18525
|
});
|
|
17631
18526
|
queue.command("enqueue <job-type> <project-dir>").description("Manually enqueue a job").option("--env <json>", "JSON object of environment variables to store", "{}").option("--provider-key <key>", "Provider bucket key (e.g. claude-native, codex)").action((jobType, projectDir, opts) => {
|
|
17632
|
-
if (!
|
|
18527
|
+
if (!VALID_JOB_TYPES3.includes(jobType)) {
|
|
17633
18528
|
console.error(chalk7.red(`Invalid job type: ${jobType}`));
|
|
17634
|
-
console.error(chalk7.dim(`Valid types: ${
|
|
18529
|
+
console.error(chalk7.dim(`Valid types: ${VALID_JOB_TYPES3.join(", ")}`));
|
|
17635
18530
|
process.exit(1);
|
|
17636
18531
|
}
|
|
17637
18532
|
let envVars = {};
|
|
@@ -17643,8 +18538,12 @@ function createQueueCommand() {
|
|
|
17643
18538
|
process.exit(1);
|
|
17644
18539
|
}
|
|
17645
18540
|
}
|
|
17646
|
-
const projectName =
|
|
18541
|
+
const projectName = path43.basename(projectDir);
|
|
17647
18542
|
const queueConfig = loadConfig(projectDir).queue;
|
|
18543
|
+
if (isJobPaused(projectDir, jobType)) {
|
|
18544
|
+
logger6.info(`Skipping enqueue for paused job: ${jobType}`);
|
|
18545
|
+
return;
|
|
18546
|
+
}
|
|
17648
18547
|
const id = enqueueJob(
|
|
17649
18548
|
projectDir,
|
|
17650
18549
|
projectName,
|
|
@@ -17682,6 +18581,11 @@ function createQueueCommand() {
|
|
|
17682
18581
|
logger6.info("No pending jobs to dispatch");
|
|
17683
18582
|
return;
|
|
17684
18583
|
}
|
|
18584
|
+
if (isJobPaused(entry.projectPath, entry.jobType)) {
|
|
18585
|
+
logger6.info(`Skipping paused queued job: ${entry.jobType} for ${entry.projectName}`);
|
|
18586
|
+
removeJob(entry.id);
|
|
18587
|
+
return;
|
|
18588
|
+
}
|
|
17685
18589
|
logger6.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
|
|
17686
18590
|
const scriptName = getScriptNameForJobType(entry.jobType);
|
|
17687
18591
|
if (!scriptName) {
|
|
@@ -17706,7 +18610,7 @@ function createQueueCommand() {
|
|
|
17706
18610
|
const scriptPath = getScriptPath(scriptName);
|
|
17707
18611
|
logger6.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
|
|
17708
18612
|
try {
|
|
17709
|
-
const child =
|
|
18613
|
+
const child = spawn8("bash", [scriptPath, entry.projectPath], {
|
|
17710
18614
|
detached: true,
|
|
17711
18615
|
stdio: "ignore",
|
|
17712
18616
|
env,
|
|
@@ -17726,13 +18630,16 @@ function createQueueCommand() {
|
|
|
17726
18630
|
queue.command("claim <job-type> <project-dir>").description(
|
|
17727
18631
|
"Atomically claim a concurrency slot and insert a running entry (used by cron scripts)"
|
|
17728
18632
|
).option("--provider-key <key>", "Provider bucket key (e.g. claude-native, codex)").option("--pid <pid>", "PID of the calling process (stored for stale-job detection)").action((jobType, projectDir, opts) => {
|
|
17729
|
-
if (!
|
|
18633
|
+
if (!VALID_JOB_TYPES3.includes(jobType)) {
|
|
17730
18634
|
console.error(`Invalid job type: ${jobType}`);
|
|
17731
|
-
console.error(`Valid types: ${
|
|
18635
|
+
console.error(`Valid types: ${VALID_JOB_TYPES3.join(", ")}`);
|
|
17732
18636
|
process.exit(1);
|
|
17733
18637
|
}
|
|
17734
18638
|
const queueConfig = loadConfig(projectDir).queue;
|
|
17735
|
-
|
|
18639
|
+
if (isJobPaused(projectDir, jobType)) {
|
|
18640
|
+
process.exit(2);
|
|
18641
|
+
}
|
|
18642
|
+
const projectName = path43.basename(projectDir);
|
|
17736
18643
|
const callerPid = opts.pid ? parseInt(opts.pid, 10) : void 0;
|
|
17737
18644
|
const result = claimJobSlot(
|
|
17738
18645
|
projectDir,
|
|
@@ -17875,7 +18782,7 @@ function notifyCommand(program2) {
|
|
|
17875
18782
|
|
|
17876
18783
|
// src/commands/summary.ts
|
|
17877
18784
|
init_dist();
|
|
17878
|
-
import
|
|
18785
|
+
import path44 from "path";
|
|
17879
18786
|
import chalk8 from "chalk";
|
|
17880
18787
|
function formatDuration2(seconds) {
|
|
17881
18788
|
if (seconds === null) return "-";
|
|
@@ -17905,7 +18812,7 @@ function formatJobStatus(status) {
|
|
|
17905
18812
|
return chalk8.dim(status);
|
|
17906
18813
|
}
|
|
17907
18814
|
function getProjectName2(projectPath) {
|
|
17908
|
-
return
|
|
18815
|
+
return path44.basename(projectPath) || projectPath;
|
|
17909
18816
|
}
|
|
17910
18817
|
function formatProvider(providerKey) {
|
|
17911
18818
|
return providerKey.split(":")[0] || providerKey;
|
|
@@ -18024,7 +18931,7 @@ function summaryCommand(program2) {
|
|
|
18024
18931
|
// src/commands/resolve.ts
|
|
18025
18932
|
init_dist();
|
|
18026
18933
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
18027
|
-
import * as
|
|
18934
|
+
import * as path45 from "path";
|
|
18028
18935
|
function buildEnvVars6(config, options) {
|
|
18029
18936
|
const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
|
|
18030
18937
|
env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
|
|
@@ -18154,7 +19061,7 @@ ${stderr}`);
|
|
|
18154
19061
|
const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
|
|
18155
19062
|
await sendNotifications(config, {
|
|
18156
19063
|
event: notificationEvent,
|
|
18157
|
-
projectName:
|
|
19064
|
+
projectName: path45.basename(projectDir),
|
|
18158
19065
|
exitCode,
|
|
18159
19066
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
18160
19067
|
});
|
|
@@ -18169,7 +19076,7 @@ ${stderr}`);
|
|
|
18169
19076
|
|
|
18170
19077
|
// src/commands/merge.ts
|
|
18171
19078
|
init_dist();
|
|
18172
|
-
import * as
|
|
19079
|
+
import * as path46 from "path";
|
|
18173
19080
|
function buildEnvVars7(config, options) {
|
|
18174
19081
|
const env = buildBaseEnvVars(config, "merger", options.dryRun);
|
|
18175
19082
|
env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
|
|
@@ -18275,7 +19182,7 @@ ${stderr}`);
|
|
|
18275
19182
|
if (notificationEvent) {
|
|
18276
19183
|
await sendNotifications(config, {
|
|
18277
19184
|
event: notificationEvent,
|
|
18278
|
-
projectName:
|
|
19185
|
+
projectName: path46.basename(projectDir),
|
|
18279
19186
|
exitCode,
|
|
18280
19187
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
18281
19188
|
});
|
|
@@ -18289,19 +19196,289 @@ ${stderr}`);
|
|
|
18289
19196
|
});
|
|
18290
19197
|
}
|
|
18291
19198
|
|
|
19199
|
+
// src/commands/agent.ts
|
|
19200
|
+
init_dist();
|
|
19201
|
+
var SCHEMA_VERSION2 = 1;
|
|
19202
|
+
var JSON_OPTION = "--json";
|
|
19203
|
+
var JSON_OPTION_DESCRIPTION = "Output as JSON";
|
|
19204
|
+
function writeJson(value) {
|
|
19205
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
19206
|
+
`);
|
|
19207
|
+
}
|
|
19208
|
+
function fail(message, options) {
|
|
19209
|
+
if (options?.json) {
|
|
19210
|
+
const payload = {
|
|
19211
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
19212
|
+
ok: false,
|
|
19213
|
+
error: message
|
|
19214
|
+
};
|
|
19215
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}
|
|
19216
|
+
`);
|
|
19217
|
+
} else {
|
|
19218
|
+
process.stderr.write(`${message}
|
|
19219
|
+
`);
|
|
19220
|
+
}
|
|
19221
|
+
process.exit(1);
|
|
19222
|
+
}
|
|
19223
|
+
function getProcess(snapshot, name) {
|
|
19224
|
+
const processInfo = snapshot.processes.find((processEntry) => processEntry.name === name);
|
|
19225
|
+
return { running: processInfo?.running ?? false, pid: processInfo?.pid ?? null };
|
|
19226
|
+
}
|
|
19227
|
+
function buildLegacyStatus(snapshot, config) {
|
|
19228
|
+
const pendingPrds = snapshot.prds.filter(
|
|
19229
|
+
(prd) => prd.status === "ready" || prd.status === "blocked"
|
|
19230
|
+
).length;
|
|
19231
|
+
const claimedPrds = snapshot.prds.filter((prd) => prd.status === "in-progress").length;
|
|
19232
|
+
const donePrds = snapshot.prds.filter((prd) => prd.status === "done").length;
|
|
19233
|
+
const logs = Object.fromEntries(
|
|
19234
|
+
snapshot.logs.map((log) => [
|
|
19235
|
+
log.name,
|
|
19236
|
+
{ path: log.path, lastLines: log.lastLines, exists: log.exists, size: log.size }
|
|
19237
|
+
])
|
|
19238
|
+
);
|
|
19239
|
+
return {
|
|
19240
|
+
projectName: snapshot.projectName,
|
|
19241
|
+
projectDir: snapshot.projectDir,
|
|
19242
|
+
provider: config.provider,
|
|
19243
|
+
reviewerEnabled: config.reviewerEnabled,
|
|
19244
|
+
autoMerge: config.autoMerge,
|
|
19245
|
+
autoMergeMethod: config.autoMergeMethod,
|
|
19246
|
+
executor: getProcess(snapshot, "executor"),
|
|
19247
|
+
reviewer: getProcess(snapshot, "reviewer"),
|
|
19248
|
+
qa: getProcess(snapshot, "qa"),
|
|
19249
|
+
audit: getProcess(snapshot, "audit"),
|
|
19250
|
+
planner: getProcess(snapshot, "planner"),
|
|
19251
|
+
analytics: getProcess(snapshot, "analytics"),
|
|
19252
|
+
merger: getProcess(snapshot, "merger"),
|
|
19253
|
+
prds: { pending: pendingPrds, claimed: claimedPrds, done: donePrds },
|
|
19254
|
+
prs: { open: snapshot.prs.length },
|
|
19255
|
+
crontab: snapshot.crontab,
|
|
19256
|
+
logs
|
|
19257
|
+
};
|
|
19258
|
+
}
|
|
19259
|
+
function buildPausedState(config) {
|
|
19260
|
+
return Object.fromEntries(
|
|
19261
|
+
getValidJobTypes().map((jobType) => [jobType, config.pausedJobs?.[jobType] === true])
|
|
19262
|
+
);
|
|
19263
|
+
}
|
|
19264
|
+
function buildLastRuns(analytics) {
|
|
19265
|
+
const lastRuns = Object.fromEntries(
|
|
19266
|
+
getValidJobTypes().map((jobType) => [
|
|
19267
|
+
jobType,
|
|
19268
|
+
{ lastSuccessAt: null, lastFailureAt: null, lastExitCode: null }
|
|
19269
|
+
])
|
|
19270
|
+
);
|
|
19271
|
+
for (const run2 of analytics.recentRuns) {
|
|
19272
|
+
const item = lastRuns[run2.jobType];
|
|
19273
|
+
if (!item) continue;
|
|
19274
|
+
const finishedAt = run2.finishedAt ? new Date(run2.finishedAt * 1e3).toISOString() : null;
|
|
19275
|
+
if (run2.status === "success" && item.lastSuccessAt === null) {
|
|
19276
|
+
item.lastSuccessAt = finishedAt;
|
|
19277
|
+
item.lastExitCode = 0;
|
|
19278
|
+
} else if (run2.status !== "success" && item.lastFailureAt === null) {
|
|
19279
|
+
item.lastFailureAt = finishedAt;
|
|
19280
|
+
item.lastExitCode = 1;
|
|
19281
|
+
}
|
|
19282
|
+
}
|
|
19283
|
+
return lastRuns;
|
|
19284
|
+
}
|
|
19285
|
+
async function getBoardSnapshot(projectDir, config) {
|
|
19286
|
+
if (config.boardProvider?.enabled === false || !config.boardProvider?.projectNumber) {
|
|
19287
|
+
return { configured: false, columns: [], items: [], error: null };
|
|
19288
|
+
}
|
|
19289
|
+
try {
|
|
19290
|
+
const provider = createBoardProvider(config.boardProvider, projectDir);
|
|
19291
|
+
const [columns, items] = await Promise.all([provider.getColumns(), provider.getAllIssues()]);
|
|
19292
|
+
return { configured: true, columns, items, error: null };
|
|
19293
|
+
} catch (error2) {
|
|
19294
|
+
return {
|
|
19295
|
+
configured: true,
|
|
19296
|
+
columns: [],
|
|
19297
|
+
items: [],
|
|
19298
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
19299
|
+
};
|
|
19300
|
+
}
|
|
19301
|
+
}
|
|
19302
|
+
function buildHealth(snapshot, config) {
|
|
19303
|
+
const checks = [
|
|
19304
|
+
{
|
|
19305
|
+
name: "config",
|
|
19306
|
+
ok: true,
|
|
19307
|
+
message: "Configuration loaded"
|
|
19308
|
+
},
|
|
19309
|
+
{
|
|
19310
|
+
name: "cron",
|
|
19311
|
+
ok: snapshot.crontab.installed,
|
|
19312
|
+
message: snapshot.crontab.installed ? "Cron entries installed" : "No Night Watch cron entries found"
|
|
19313
|
+
},
|
|
19314
|
+
{
|
|
19315
|
+
name: "queue",
|
|
19316
|
+
ok: true,
|
|
19317
|
+
message: config.queue.enabled ? "Global queue enabled" : "Global queue disabled"
|
|
19318
|
+
},
|
|
19319
|
+
{
|
|
19320
|
+
name: "provider",
|
|
19321
|
+
ok: Boolean(config.provider),
|
|
19322
|
+
message: config.provider ? `Provider configured: ${config.provider}` : "No provider configured"
|
|
19323
|
+
}
|
|
19324
|
+
];
|
|
19325
|
+
const staleLocks = snapshot.processes.filter(
|
|
19326
|
+
(processInfo) => !processInfo.running && processInfo.pid !== null
|
|
19327
|
+
);
|
|
19328
|
+
checks.push({
|
|
19329
|
+
name: "locks",
|
|
19330
|
+
ok: staleLocks.length === 0,
|
|
19331
|
+
message: staleLocks.length === 0 ? "No stale lock files detected" : `Stale lock files detected for ${staleLocks.map((lock) => lock.name).join(", ")}`
|
|
19332
|
+
});
|
|
19333
|
+
return { schemaVersion: SCHEMA_VERSION2, ok: checks.every((check) => check.ok), checks };
|
|
19334
|
+
}
|
|
19335
|
+
async function buildAgentStatus(projectDir) {
|
|
19336
|
+
const config = loadConfig(projectDir);
|
|
19337
|
+
const snapshot = await fetchStatusSnapshot(projectDir, config);
|
|
19338
|
+
const analytics = getJobRunsAnalytics(24 * 30);
|
|
19339
|
+
const health = buildHealth(snapshot, config);
|
|
19340
|
+
return {
|
|
19341
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
19342
|
+
generatedAt: snapshot.timestamp.toISOString(),
|
|
19343
|
+
project: { name: snapshot.projectName, dir: snapshot.projectDir, provider: config.provider },
|
|
19344
|
+
status: buildLegacyStatus(snapshot, config),
|
|
19345
|
+
paused: buildPausedState(config),
|
|
19346
|
+
queue: getQueueStatus(),
|
|
19347
|
+
board: await getBoardSnapshot(projectDir, config),
|
|
19348
|
+
health,
|
|
19349
|
+
lastRuns: buildLastRuns(analytics)
|
|
19350
|
+
};
|
|
19351
|
+
}
|
|
19352
|
+
function normalizeJobType(job) {
|
|
19353
|
+
if (getValidJobTypes().includes(job)) {
|
|
19354
|
+
return job;
|
|
19355
|
+
}
|
|
19356
|
+
throw new Error(`Invalid job: ${job}. Valid jobs: ${getValidJobTypes().join(", ")}`);
|
|
19357
|
+
}
|
|
19358
|
+
function agentCommand(program2) {
|
|
19359
|
+
const agent = program2.command("agent").description("Machine-readable agent operations");
|
|
19360
|
+
agent.command("status").description("Print a stable machine-readable project snapshot").requiredOption(JSON_OPTION, "Output status as JSON").action(async () => {
|
|
19361
|
+
writeJson(await buildAgentStatus(process.cwd()));
|
|
19362
|
+
});
|
|
19363
|
+
}
|
|
19364
|
+
function configCommand(program2) {
|
|
19365
|
+
const config = program2.command("config").description("Inspect and edit Night Watch config");
|
|
19366
|
+
config.command("list").description("Print resolved config").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((options) => {
|
|
19367
|
+
const value = loadConfig(process.cwd());
|
|
19368
|
+
if (options.json) {
|
|
19369
|
+
writeJson({ schemaVersion: SCHEMA_VERSION2, config: value });
|
|
19370
|
+
} else {
|
|
19371
|
+
writeJson(value);
|
|
19372
|
+
}
|
|
19373
|
+
});
|
|
19374
|
+
config.command("get <path>").description("Read a resolved config value by dot path").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((dotPath, options) => {
|
|
19375
|
+
try {
|
|
19376
|
+
const result = getConfigValue(process.cwd(), dotPath);
|
|
19377
|
+
if (options.json) {
|
|
19378
|
+
writeJson({ schemaVersion: SCHEMA_VERSION2, ...result });
|
|
19379
|
+
} else {
|
|
19380
|
+
writeJson(result.value);
|
|
19381
|
+
}
|
|
19382
|
+
} catch (error2) {
|
|
19383
|
+
fail(error2 instanceof Error ? error2.message : String(error2), options);
|
|
19384
|
+
}
|
|
19385
|
+
});
|
|
19386
|
+
config.command("set <path> <value>").description("Write a config value by dot path").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((dotPath, rawValue, options) => {
|
|
19387
|
+
try {
|
|
19388
|
+
const result = setConfigValue(process.cwd(), dotPath, parseConfigValue(rawValue));
|
|
19389
|
+
if (options.json) {
|
|
19390
|
+
writeJson({ schemaVersion: SCHEMA_VERSION2, ok: true, ...result });
|
|
19391
|
+
} else {
|
|
19392
|
+
process.stdout.write(`Updated ${result.path}
|
|
19393
|
+
`);
|
|
19394
|
+
}
|
|
19395
|
+
} catch (error2) {
|
|
19396
|
+
fail(error2 instanceof Error ? error2.message : String(error2), options);
|
|
19397
|
+
}
|
|
19398
|
+
});
|
|
19399
|
+
}
|
|
19400
|
+
function healthCommand(program2) {
|
|
19401
|
+
program2.command("health").description("Check automation readiness").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action(async (options) => {
|
|
19402
|
+
const config = loadConfig(process.cwd());
|
|
19403
|
+
const snapshot = await fetchStatusSnapshot(process.cwd(), config);
|
|
19404
|
+
const health = buildHealth(snapshot, config);
|
|
19405
|
+
if (options.json) {
|
|
19406
|
+
writeJson(health);
|
|
19407
|
+
} else {
|
|
19408
|
+
for (const check of health.checks) {
|
|
19409
|
+
process.stdout.write(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}
|
|
19410
|
+
`);
|
|
19411
|
+
}
|
|
19412
|
+
}
|
|
19413
|
+
if (!health.ok) {
|
|
19414
|
+
process.exitCode = 1;
|
|
19415
|
+
}
|
|
19416
|
+
});
|
|
19417
|
+
}
|
|
19418
|
+
function jobCommand(program2) {
|
|
19419
|
+
const job = program2.command("job").description("Manage Night Watch jobs");
|
|
19420
|
+
job.command("pause <job>").description("Pause a cron/queue-dispatched job").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((jobName, options) => {
|
|
19421
|
+
try {
|
|
19422
|
+
const jobType = normalizeJobType(jobName);
|
|
19423
|
+
const result = setConfigValue(process.cwd(), `pausedJobs.${jobType}`, true);
|
|
19424
|
+
if (options.json) {
|
|
19425
|
+
writeJson({
|
|
19426
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
19427
|
+
ok: true,
|
|
19428
|
+
job: jobType,
|
|
19429
|
+
paused: result.value
|
|
19430
|
+
});
|
|
19431
|
+
} else {
|
|
19432
|
+
process.stdout.write(`Paused ${jobType}
|
|
19433
|
+
`);
|
|
19434
|
+
}
|
|
19435
|
+
} catch (error2) {
|
|
19436
|
+
fail(error2 instanceof Error ? error2.message : String(error2), options);
|
|
19437
|
+
}
|
|
19438
|
+
});
|
|
19439
|
+
job.command("resume <job>").description("Resume a cron/queue-dispatched job").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((jobName, options) => {
|
|
19440
|
+
try {
|
|
19441
|
+
const jobType = normalizeJobType(jobName);
|
|
19442
|
+
const result = setConfigValue(process.cwd(), `pausedJobs.${jobType}`, false);
|
|
19443
|
+
if (options.json) {
|
|
19444
|
+
writeJson({
|
|
19445
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
19446
|
+
ok: true,
|
|
19447
|
+
job: jobType,
|
|
19448
|
+
paused: result.value
|
|
19449
|
+
});
|
|
19450
|
+
} else {
|
|
19451
|
+
process.stdout.write(`Resumed ${jobType}
|
|
19452
|
+
`);
|
|
19453
|
+
}
|
|
19454
|
+
} catch (error2) {
|
|
19455
|
+
fail(error2 instanceof Error ? error2.message : String(error2), options);
|
|
19456
|
+
}
|
|
19457
|
+
});
|
|
19458
|
+
job.command("is-paused <job>").description("Return zero when a job is paused").action((jobName) => {
|
|
19459
|
+
try {
|
|
19460
|
+
const jobType = normalizeJobType(jobName);
|
|
19461
|
+
const paused = loadConfig(process.cwd()).pausedJobs?.[jobType] === true;
|
|
19462
|
+
process.exit(paused ? 0 : 1);
|
|
19463
|
+
} catch {
|
|
19464
|
+
process.exit(1);
|
|
19465
|
+
}
|
|
19466
|
+
});
|
|
19467
|
+
}
|
|
19468
|
+
|
|
18292
19469
|
// src/cli.ts
|
|
18293
19470
|
var __filename5 = fileURLToPath6(import.meta.url);
|
|
18294
19471
|
var __dirname5 = dirname12(__filename5);
|
|
18295
19472
|
function findPackageRoot(dir) {
|
|
18296
19473
|
let d = dir;
|
|
18297
19474
|
for (let i = 0; i < 5; i++) {
|
|
18298
|
-
if (
|
|
19475
|
+
if (existsSync34(join38(d, "package.json"))) return d;
|
|
18299
19476
|
d = dirname12(d);
|
|
18300
19477
|
}
|
|
18301
19478
|
return dir;
|
|
18302
19479
|
}
|
|
18303
19480
|
var packageRoot = findPackageRoot(__dirname5);
|
|
18304
|
-
var packageJson = JSON.parse(
|
|
19481
|
+
var packageJson = JSON.parse(readFileSync21(join38(packageRoot, "package.json"), "utf-8"));
|
|
18305
19482
|
var program = new Command3();
|
|
18306
19483
|
program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
|
|
18307
19484
|
initCommand(program);
|
|
@@ -18333,4 +19510,8 @@ notifyCommand(program);
|
|
|
18333
19510
|
summaryCommand(program);
|
|
18334
19511
|
resolveCommand(program);
|
|
18335
19512
|
mergeCommand(program);
|
|
19513
|
+
agentCommand(program);
|
|
19514
|
+
configCommand(program);
|
|
19515
|
+
healthCommand(program);
|
|
19516
|
+
jobCommand(program);
|
|
18336
19517
|
program.parse();
|