@jonit-dev/night-watch-cli 1.8.2 → 1.8.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.js +548 -393
- package/dist/commands/board.d.ts.map +1 -1
- package/dist/commands/board.js +20 -0
- package/dist/commands/board.js.map +1 -1
- package/dist/web/assets/index-BFxPiKyy.js +381 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
|
|
|
31
31
|
return `claude-proxy:${baseUrl}`;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, 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, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, 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, 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, QUEUE_LOCK_FILE_NAME;
|
|
34
|
+
var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, 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, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, 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, QUEUE_LOCK_FILE_NAME;
|
|
35
35
|
var init_constants = __esm({
|
|
36
36
|
"../core/dist/constants.js"() {
|
|
37
37
|
"use strict";
|
|
@@ -225,6 +225,7 @@ If no issues are warranted, output an empty array: []`;
|
|
|
225
225
|
HISTORY_FILE_NAME = "history.json";
|
|
226
226
|
PRD_STATES_FILE_NAME = "prd-states.json";
|
|
227
227
|
STATE_DB_FILE_NAME = "state.db";
|
|
228
|
+
GLOBAL_NOTIFICATIONS_FILE_NAME = "global-notifications.json";
|
|
228
229
|
MAX_HISTORY_RECORDS_PER_PRD = 10;
|
|
229
230
|
DEFAULT_QUEUE_ENABLED = true;
|
|
230
231
|
DEFAULT_QUEUE_MODE = "conservative";
|
|
@@ -2029,11 +2030,18 @@ var init_github_projects = __esm({
|
|
|
2029
2030
|
await this.ensureStatusColumns(existing.id);
|
|
2030
2031
|
return { id: existing.id, number: existing.number, title: existing.title, url: existing.url };
|
|
2031
2032
|
}
|
|
2032
|
-
const createData = await graphql(`
|
|
2033
|
-
|
|
2034
|
-
|
|
2033
|
+
const createData = await graphql(`
|
|
2034
|
+
mutation CreateProject($ownerId: ID!, $title: String!) {
|
|
2035
|
+
createProjectV2(input: { ownerId: $ownerId, title: $title }) {
|
|
2036
|
+
projectV2 {
|
|
2037
|
+
id
|
|
2038
|
+
number
|
|
2039
|
+
url
|
|
2040
|
+
title
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2035
2043
|
}
|
|
2036
|
-
|
|
2044
|
+
`, { ownerId: owner.id, title }, this.cwd);
|
|
2037
2045
|
const project = createData.createProjectV2.projectV2;
|
|
2038
2046
|
this.cachedProjectId = project.id;
|
|
2039
2047
|
await this.linkProjectToRepository(project.id);
|
|
@@ -2049,24 +2057,34 @@ var init_github_projects = __esm({
|
|
|
2049
2057
|
const message = err instanceof Error ? err.message : String(err);
|
|
2050
2058
|
if (!message.includes("Status field not found"))
|
|
2051
2059
|
throw err;
|
|
2052
|
-
const createFieldData = await graphql(`
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2060
|
+
const createFieldData = await graphql(`
|
|
2061
|
+
mutation CreateStatusField($projectId: ID!) {
|
|
2062
|
+
createProjectV2Field(
|
|
2063
|
+
input: {
|
|
2064
|
+
projectId: $projectId
|
|
2065
|
+
dataType: SINGLE_SELECT
|
|
2066
|
+
name: "Status"
|
|
2067
|
+
singleSelectOptions: [
|
|
2068
|
+
{ name: "Draft", color: GRAY, description: "" }
|
|
2069
|
+
{ name: "Ready", color: BLUE, description: "" }
|
|
2070
|
+
{ name: "In Progress", color: YELLOW, description: "" }
|
|
2071
|
+
{ name: "Review", color: ORANGE, description: "" }
|
|
2072
|
+
{ name: "Done", color: GREEN, description: "" }
|
|
2073
|
+
]
|
|
2074
|
+
}
|
|
2075
|
+
) {
|
|
2076
|
+
projectV2Field {
|
|
2077
|
+
... on ProjectV2SingleSelectField {
|
|
2078
|
+
id
|
|
2079
|
+
options {
|
|
2080
|
+
id
|
|
2081
|
+
name
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2067
2085
|
}
|
|
2068
2086
|
}
|
|
2069
|
-
|
|
2087
|
+
`, { projectId: project.id }, this.cwd);
|
|
2070
2088
|
const field = createFieldData.createProjectV2Field.projectV2Field;
|
|
2071
2089
|
this.cachedFieldId = field.id;
|
|
2072
2090
|
this.cachedOptionIds = new Map(field.options.map((o) => [o.name, o.id]));
|
|
@@ -2093,11 +2111,23 @@ var init_github_projects = __esm({
|
|
|
2093
2111
|
async createIssue(input) {
|
|
2094
2112
|
const repo = await this.getRepo();
|
|
2095
2113
|
const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
|
|
2096
|
-
const issueArgs = [
|
|
2114
|
+
const issueArgs = [
|
|
2115
|
+
"issue",
|
|
2116
|
+
"create",
|
|
2117
|
+
"--title",
|
|
2118
|
+
input.title,
|
|
2119
|
+
"--body",
|
|
2120
|
+
input.body,
|
|
2121
|
+
"--repo",
|
|
2122
|
+
repo
|
|
2123
|
+
];
|
|
2097
2124
|
if (input.labels && input.labels.length > 0) {
|
|
2098
2125
|
issueArgs.push("--label", input.labels.join(","));
|
|
2099
2126
|
}
|
|
2100
|
-
const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, {
|
|
2127
|
+
const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, {
|
|
2128
|
+
cwd: this.cwd,
|
|
2129
|
+
encoding: "utf-8"
|
|
2130
|
+
});
|
|
2101
2131
|
const issueUrl = issueUrlRaw.trim();
|
|
2102
2132
|
const issueNumber = parseInt(issueUrl.split("/").pop() ?? "", 10);
|
|
2103
2133
|
if (!issueNumber)
|
|
@@ -2105,11 +2135,15 @@ var init_github_projects = __esm({
|
|
|
2105
2135
|
const [owner, repoName] = repo.split("/");
|
|
2106
2136
|
const { stdout: nodeIdRaw } = await execFileAsync2("gh", ["api", `repos/${owner}/${repoName}/issues/${issueNumber}`, "--jq", ".node_id"], { cwd: this.cwd, encoding: "utf-8" });
|
|
2107
2137
|
const issueJson = { number: issueNumber, id: nodeIdRaw.trim(), url: issueUrl };
|
|
2108
|
-
const addData = await graphql(`
|
|
2109
|
-
|
|
2110
|
-
|
|
2138
|
+
const addData = await graphql(`
|
|
2139
|
+
mutation AddProjectItem($projectId: ID!, $contentId: ID!) {
|
|
2140
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
|
|
2141
|
+
item {
|
|
2142
|
+
id
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2111
2145
|
}
|
|
2112
|
-
|
|
2146
|
+
`, { projectId, contentId: issueJson.id }, this.cwd);
|
|
2113
2147
|
const itemId = addData.addProjectV2ItemById.item.id;
|
|
2114
2148
|
const targetColumn = input.column ?? "Draft";
|
|
2115
2149
|
const optionId = optionIds.get(targetColumn);
|
|
@@ -2129,11 +2163,45 @@ var init_github_projects = __esm({
|
|
|
2129
2163
|
assignees: []
|
|
2130
2164
|
};
|
|
2131
2165
|
}
|
|
2166
|
+
async addIssue(issueNumber, column = "Ready") {
|
|
2167
|
+
const repo = await this.getRepo();
|
|
2168
|
+
const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
|
|
2169
|
+
const [owner, repoName] = repo.split("/");
|
|
2170
|
+
const { stdout: nodeIdRaw } = await execFileAsync2("gh", ["api", `repos/${owner}/${repoName}/issues/${issueNumber}`, "--jq", ".node_id"], { cwd: this.cwd, encoding: "utf-8" });
|
|
2171
|
+
const nodeId = nodeIdRaw.trim();
|
|
2172
|
+
if (!nodeId)
|
|
2173
|
+
throw new Error(`Issue #${issueNumber} not found in ${repo}.`);
|
|
2174
|
+
const addData = await graphql(`
|
|
2175
|
+
mutation AddProjectItem($projectId: ID!, $contentId: ID!) {
|
|
2176
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
|
|
2177
|
+
item {
|
|
2178
|
+
id
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
`, { projectId, contentId: nodeId }, this.cwd);
|
|
2183
|
+
const itemId = addData.addProjectV2ItemById.item.id;
|
|
2184
|
+
const optionId = optionIds.get(column);
|
|
2185
|
+
if (optionId)
|
|
2186
|
+
await this.setItemStatus(projectId, itemId, fieldId, optionId);
|
|
2187
|
+
const full = await this.getIssue(issueNumber);
|
|
2188
|
+
if (full)
|
|
2189
|
+
return { ...full, column };
|
|
2190
|
+
throw new Error(`Added issue #${issueNumber} to project but failed to fetch it back.`);
|
|
2191
|
+
}
|
|
2132
2192
|
async getIssue(issueNumber) {
|
|
2133
2193
|
const repo = await this.getRepo();
|
|
2134
2194
|
let rawIssue;
|
|
2135
2195
|
try {
|
|
2136
|
-
const { stdout: output } = await execFileAsync2("gh", [
|
|
2196
|
+
const { stdout: output } = await execFileAsync2("gh", [
|
|
2197
|
+
"issue",
|
|
2198
|
+
"view",
|
|
2199
|
+
String(issueNumber),
|
|
2200
|
+
"--repo",
|
|
2201
|
+
repo,
|
|
2202
|
+
"--json",
|
|
2203
|
+
"number,title,body,url,id,labels,assignees"
|
|
2204
|
+
], { cwd: this.cwd, encoding: "utf-8" });
|
|
2137
2205
|
rawIssue = JSON.parse(output);
|
|
2138
2206
|
} catch {
|
|
2139
2207
|
return null;
|
|
@@ -2238,6 +2306,9 @@ var init_local_kanban = __esm({
|
|
|
2238
2306
|
});
|
|
2239
2307
|
return toIBoardIssue(row);
|
|
2240
2308
|
}
|
|
2309
|
+
async addIssue(_issueNumber, _column) {
|
|
2310
|
+
throw new Error("addIssue is not supported by the local Kanban provider.");
|
|
2311
|
+
}
|
|
2241
2312
|
async getIssue(issueNumber) {
|
|
2242
2313
|
const row = this.repo.getByNumber(issueNumber);
|
|
2243
2314
|
return row ? toIBoardIssue(row) : null;
|
|
@@ -4275,6 +4346,36 @@ var init_log_utils = __esm({
|
|
|
4275
4346
|
}
|
|
4276
4347
|
});
|
|
4277
4348
|
|
|
4349
|
+
// ../core/dist/utils/global-config.js
|
|
4350
|
+
import * as fs11 from "fs";
|
|
4351
|
+
import * as os5 from "os";
|
|
4352
|
+
import * as path10 from "path";
|
|
4353
|
+
function getGlobalNotificationsPath() {
|
|
4354
|
+
return path10.join(os5.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_NOTIFICATIONS_FILE_NAME);
|
|
4355
|
+
}
|
|
4356
|
+
function loadGlobalNotificationsConfig() {
|
|
4357
|
+
const filePath = getGlobalNotificationsPath();
|
|
4358
|
+
try {
|
|
4359
|
+
if (!fs11.existsSync(filePath))
|
|
4360
|
+
return { webhook: null };
|
|
4361
|
+
const raw = fs11.readFileSync(filePath, "utf-8");
|
|
4362
|
+
return JSON.parse(raw);
|
|
4363
|
+
} catch {
|
|
4364
|
+
return { webhook: null };
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
function saveGlobalNotificationsConfig(config) {
|
|
4368
|
+
const filePath = getGlobalNotificationsPath();
|
|
4369
|
+
fs11.mkdirSync(path10.dirname(filePath), { recursive: true });
|
|
4370
|
+
fs11.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4371
|
+
}
|
|
4372
|
+
var init_global_config = __esm({
|
|
4373
|
+
"../core/dist/utils/global-config.js"() {
|
|
4374
|
+
"use strict";
|
|
4375
|
+
init_constants();
|
|
4376
|
+
}
|
|
4377
|
+
});
|
|
4378
|
+
|
|
4278
4379
|
// ../core/dist/utils/ui.js
|
|
4279
4380
|
import chalk from "chalk";
|
|
4280
4381
|
import ora from "ora";
|
|
@@ -4631,24 +4732,33 @@ async function sendWebhook(webhook, ctx) {
|
|
|
4631
4732
|
warn(`Notification failed (${webhook.type}): ${message}`);
|
|
4632
4733
|
}
|
|
4633
4734
|
}
|
|
4735
|
+
function webhookIdentity(wh) {
|
|
4736
|
+
if (wh.type === "telegram")
|
|
4737
|
+
return `telegram:${wh.botToken}:${wh.chatId}`;
|
|
4738
|
+
return `${wh.type}:${wh.url}`;
|
|
4739
|
+
}
|
|
4634
4740
|
async function sendNotifications(config, ctx) {
|
|
4635
|
-
const
|
|
4636
|
-
const
|
|
4637
|
-
|
|
4638
|
-
|
|
4741
|
+
const projectWebhooks = config.notifications?.webhooks ?? [];
|
|
4742
|
+
const globalConfig = loadGlobalNotificationsConfig();
|
|
4743
|
+
const allWebhooks = [...projectWebhooks];
|
|
4744
|
+
if (globalConfig.webhook) {
|
|
4745
|
+
const projectIds = new Set(projectWebhooks.map(webhookIdentity));
|
|
4746
|
+
if (!projectIds.has(webhookIdentity(globalConfig.webhook))) {
|
|
4747
|
+
allWebhooks.push(globalConfig.webhook);
|
|
4748
|
+
}
|
|
4639
4749
|
}
|
|
4640
|
-
if (
|
|
4750
|
+
if (allWebhooks.length === 0) {
|
|
4641
4751
|
return;
|
|
4642
4752
|
}
|
|
4643
|
-
const results = await Promise.allSettled(
|
|
4753
|
+
const results = await Promise.allSettled(allWebhooks.map((wh) => sendWebhook(wh, ctx)));
|
|
4644
4754
|
const sent = results.filter((r) => r.status === "fulfilled").length;
|
|
4645
|
-
|
|
4646
|
-
info(`Sent ${sent}/${total} notifications`);
|
|
4755
|
+
info(`Sent ${sent}/${allWebhooks.length} notifications`);
|
|
4647
4756
|
}
|
|
4648
4757
|
var MAX_QA_SCREENSHOTS_IN_NOTIFICATION;
|
|
4649
4758
|
var init_notify = __esm({
|
|
4650
4759
|
"../core/dist/utils/notify.js"() {
|
|
4651
4760
|
"use strict";
|
|
4761
|
+
init_global_config();
|
|
4652
4762
|
init_ui();
|
|
4653
4763
|
init_github();
|
|
4654
4764
|
MAX_QA_SCREENSHOTS_IN_NOTIFICATION = 3;
|
|
@@ -4685,15 +4795,15 @@ var init_prd_discovery = __esm({
|
|
|
4685
4795
|
});
|
|
4686
4796
|
|
|
4687
4797
|
// ../core/dist/utils/prd-utils.js
|
|
4688
|
-
import * as
|
|
4689
|
-
import * as
|
|
4798
|
+
import * as fs12 from "fs";
|
|
4799
|
+
import * as path11 from "path";
|
|
4690
4800
|
function slugify(name) {
|
|
4691
4801
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4692
4802
|
}
|
|
4693
4803
|
function getNextPrdNumber(prdDir) {
|
|
4694
|
-
if (!
|
|
4804
|
+
if (!fs12.existsSync(prdDir))
|
|
4695
4805
|
return 1;
|
|
4696
|
-
const files =
|
|
4806
|
+
const files = fs12.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
4697
4807
|
const numbers = files.map((f) => {
|
|
4698
4808
|
const match = f.match(/^(\d+)-/);
|
|
4699
4809
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -4701,16 +4811,16 @@ function getNextPrdNumber(prdDir) {
|
|
|
4701
4811
|
return Math.max(0, ...numbers) + 1;
|
|
4702
4812
|
}
|
|
4703
4813
|
function markPrdDone(prdDir, prdFile) {
|
|
4704
|
-
const sourcePath =
|
|
4705
|
-
if (!
|
|
4814
|
+
const sourcePath = path11.join(prdDir, prdFile);
|
|
4815
|
+
if (!fs12.existsSync(sourcePath)) {
|
|
4706
4816
|
return false;
|
|
4707
4817
|
}
|
|
4708
|
-
const doneDir =
|
|
4709
|
-
if (!
|
|
4710
|
-
|
|
4818
|
+
const doneDir = path11.join(prdDir, "done");
|
|
4819
|
+
if (!fs12.existsSync(doneDir)) {
|
|
4820
|
+
fs12.mkdirSync(doneDir, { recursive: true });
|
|
4711
4821
|
}
|
|
4712
|
-
const destPath =
|
|
4713
|
-
|
|
4822
|
+
const destPath = path11.join(doneDir, prdFile);
|
|
4823
|
+
fs12.renameSync(sourcePath, destPath);
|
|
4714
4824
|
return true;
|
|
4715
4825
|
}
|
|
4716
4826
|
var init_prd_utils = __esm({
|
|
@@ -4720,16 +4830,16 @@ var init_prd_utils = __esm({
|
|
|
4720
4830
|
});
|
|
4721
4831
|
|
|
4722
4832
|
// ../core/dist/utils/registry.js
|
|
4723
|
-
import * as
|
|
4724
|
-
import * as
|
|
4725
|
-
import * as
|
|
4833
|
+
import * as fs13 from "fs";
|
|
4834
|
+
import * as os6 from "os";
|
|
4835
|
+
import * as path12 from "path";
|
|
4726
4836
|
function readLegacyRegistryEntries() {
|
|
4727
4837
|
const registryPath = getRegistryPath();
|
|
4728
|
-
if (!
|
|
4838
|
+
if (!fs13.existsSync(registryPath)) {
|
|
4729
4839
|
return [];
|
|
4730
4840
|
}
|
|
4731
4841
|
try {
|
|
4732
|
-
const raw =
|
|
4842
|
+
const raw = fs13.readFileSync(registryPath, "utf-8");
|
|
4733
4843
|
const parsed = JSON.parse(raw);
|
|
4734
4844
|
if (!Array.isArray(parsed)) {
|
|
4735
4845
|
return [];
|
|
@@ -4764,8 +4874,8 @@ function loadRegistryEntriesWithLegacyFallback() {
|
|
|
4764
4874
|
return projectRegistry.getAll();
|
|
4765
4875
|
}
|
|
4766
4876
|
function getRegistryPath() {
|
|
4767
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
4768
|
-
return
|
|
4877
|
+
const base = process.env.NIGHT_WATCH_HOME || path12.join(os6.homedir(), GLOBAL_CONFIG_DIR);
|
|
4878
|
+
return path12.join(base, REGISTRY_FILE_NAME);
|
|
4769
4879
|
}
|
|
4770
4880
|
function loadRegistry() {
|
|
4771
4881
|
return loadRegistryEntriesWithLegacyFallback();
|
|
@@ -4778,7 +4888,7 @@ function saveRegistry(entries) {
|
|
|
4778
4888
|
}
|
|
4779
4889
|
}
|
|
4780
4890
|
function registerProject(projectDir) {
|
|
4781
|
-
const resolvedPath =
|
|
4891
|
+
const resolvedPath = path12.resolve(projectDir);
|
|
4782
4892
|
const { projectRegistry } = getRepositories();
|
|
4783
4893
|
const entries = loadRegistryEntriesWithLegacyFallback();
|
|
4784
4894
|
const existing = entries.find((e) => e.path === resolvedPath);
|
|
@@ -4787,13 +4897,13 @@ function registerProject(projectDir) {
|
|
|
4787
4897
|
}
|
|
4788
4898
|
const name = getProjectName(resolvedPath);
|
|
4789
4899
|
const nameExists = entries.some((e) => e.name === name);
|
|
4790
|
-
const finalName = nameExists ? `${name}-${
|
|
4900
|
+
const finalName = nameExists ? `${name}-${path12.basename(resolvedPath)}` : name;
|
|
4791
4901
|
const entry = { name: finalName, path: resolvedPath };
|
|
4792
4902
|
projectRegistry.upsert(entry);
|
|
4793
4903
|
return entry;
|
|
4794
4904
|
}
|
|
4795
4905
|
function unregisterProject(projectDir) {
|
|
4796
|
-
const resolvedPath =
|
|
4906
|
+
const resolvedPath = path12.resolve(projectDir);
|
|
4797
4907
|
loadRegistryEntriesWithLegacyFallback();
|
|
4798
4908
|
const { projectRegistry } = getRepositories();
|
|
4799
4909
|
return projectRegistry.remove(resolvedPath);
|
|
@@ -4803,7 +4913,7 @@ function validateRegistry() {
|
|
|
4803
4913
|
const valid = [];
|
|
4804
4914
|
const invalid = [];
|
|
4805
4915
|
for (const entry of entries) {
|
|
4806
|
-
if (
|
|
4916
|
+
if (fs13.existsSync(entry.path) && fs13.existsSync(path12.join(entry.path, CONFIG_FILE_NAME))) {
|
|
4807
4917
|
valid.push(entry);
|
|
4808
4918
|
} else {
|
|
4809
4919
|
invalid.push(entry);
|
|
@@ -4991,18 +5101,18 @@ var init_roadmap_parser = __esm({
|
|
|
4991
5101
|
});
|
|
4992
5102
|
|
|
4993
5103
|
// ../core/dist/utils/roadmap-state.js
|
|
4994
|
-
import * as
|
|
4995
|
-
import * as
|
|
5104
|
+
import * as fs14 from "fs";
|
|
5105
|
+
import * as path13 from "path";
|
|
4996
5106
|
function getStateFilePath(prdDir) {
|
|
4997
|
-
return
|
|
5107
|
+
return path13.join(prdDir, STATE_FILE_NAME);
|
|
4998
5108
|
}
|
|
4999
5109
|
function readJsonState(prdDir) {
|
|
5000
5110
|
const statePath = getStateFilePath(prdDir);
|
|
5001
|
-
if (!
|
|
5111
|
+
if (!fs14.existsSync(statePath)) {
|
|
5002
5112
|
return null;
|
|
5003
5113
|
}
|
|
5004
5114
|
try {
|
|
5005
|
-
const content =
|
|
5115
|
+
const content = fs14.readFileSync(statePath, "utf-8");
|
|
5006
5116
|
const parsed = JSON.parse(content);
|
|
5007
5117
|
if (typeof parsed !== "object" || parsed === null) {
|
|
5008
5118
|
return null;
|
|
@@ -5040,11 +5150,11 @@ function saveRoadmapState(prdDir, state) {
|
|
|
5040
5150
|
const { roadmapState } = getRepositories();
|
|
5041
5151
|
roadmapState.save(prdDir, state);
|
|
5042
5152
|
const statePath = getStateFilePath(prdDir);
|
|
5043
|
-
const dir =
|
|
5044
|
-
if (!
|
|
5045
|
-
|
|
5153
|
+
const dir = path13.dirname(statePath);
|
|
5154
|
+
if (!fs14.existsSync(dir)) {
|
|
5155
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
5046
5156
|
}
|
|
5047
|
-
|
|
5157
|
+
fs14.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
5048
5158
|
}
|
|
5049
5159
|
function createEmptyState() {
|
|
5050
5160
|
return {
|
|
@@ -5084,15 +5194,15 @@ var init_roadmap_state = __esm({
|
|
|
5084
5194
|
});
|
|
5085
5195
|
|
|
5086
5196
|
// ../core/dist/templates/slicer-prompt.js
|
|
5087
|
-
import * as
|
|
5088
|
-
import * as
|
|
5197
|
+
import * as fs15 from "fs";
|
|
5198
|
+
import * as path14 from "path";
|
|
5089
5199
|
function loadSlicerTemplate(templateDir) {
|
|
5090
5200
|
if (cachedTemplate) {
|
|
5091
5201
|
return cachedTemplate;
|
|
5092
5202
|
}
|
|
5093
|
-
const templatePath = templateDir ?
|
|
5203
|
+
const templatePath = templateDir ? path14.join(templateDir, "slicer.md") : path14.resolve(__dirname, "..", "..", "templates", "slicer.md");
|
|
5094
5204
|
try {
|
|
5095
|
-
cachedTemplate =
|
|
5205
|
+
cachedTemplate = fs15.readFileSync(templatePath, "utf-8");
|
|
5096
5206
|
return cachedTemplate;
|
|
5097
5207
|
} catch (error2) {
|
|
5098
5208
|
console.warn(`Warning: Could not load slicer template from ${templatePath}, using default:`, error2 instanceof Error ? error2.message : String(error2));
|
|
@@ -5117,7 +5227,7 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
|
|
|
5117
5227
|
title,
|
|
5118
5228
|
section,
|
|
5119
5229
|
description: description || "(No description provided)",
|
|
5120
|
-
outputFilePath:
|
|
5230
|
+
outputFilePath: path14.join(prdDir, prdFilename),
|
|
5121
5231
|
prdDir
|
|
5122
5232
|
};
|
|
5123
5233
|
}
|
|
@@ -5211,8 +5321,8 @@ DO NOT forget to write the file.
|
|
|
5211
5321
|
});
|
|
5212
5322
|
|
|
5213
5323
|
// ../core/dist/utils/roadmap-scanner.js
|
|
5214
|
-
import * as
|
|
5215
|
-
import * as
|
|
5324
|
+
import * as fs16 from "fs";
|
|
5325
|
+
import * as path15 from "path";
|
|
5216
5326
|
import { spawn } from "child_process";
|
|
5217
5327
|
import { createHash as createHash3 } from "crypto";
|
|
5218
5328
|
function normalizeAuditSeverity(raw) {
|
|
@@ -5313,11 +5423,11 @@ function auditFindingToRoadmapItem(finding) {
|
|
|
5313
5423
|
};
|
|
5314
5424
|
}
|
|
5315
5425
|
function collectAuditPlannerItems(projectDir) {
|
|
5316
|
-
const reportPath =
|
|
5317
|
-
if (!
|
|
5426
|
+
const reportPath = path15.join(projectDir, "logs", "audit-report.md");
|
|
5427
|
+
if (!fs16.existsSync(reportPath)) {
|
|
5318
5428
|
return [];
|
|
5319
5429
|
}
|
|
5320
|
-
const reportContent =
|
|
5430
|
+
const reportContent = fs16.readFileSync(reportPath, "utf-8");
|
|
5321
5431
|
if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
|
|
5322
5432
|
return [];
|
|
5323
5433
|
}
|
|
@@ -5326,9 +5436,9 @@ function collectAuditPlannerItems(projectDir) {
|
|
|
5326
5436
|
return findings.map(auditFindingToRoadmapItem);
|
|
5327
5437
|
}
|
|
5328
5438
|
function getRoadmapStatus(projectDir, config) {
|
|
5329
|
-
const roadmapPath =
|
|
5439
|
+
const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
5330
5440
|
const scannerEnabled = config.roadmapScanner.enabled;
|
|
5331
|
-
if (!
|
|
5441
|
+
if (!fs16.existsSync(roadmapPath)) {
|
|
5332
5442
|
return {
|
|
5333
5443
|
found: false,
|
|
5334
5444
|
enabled: scannerEnabled,
|
|
@@ -5339,9 +5449,9 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
5339
5449
|
items: []
|
|
5340
5450
|
};
|
|
5341
5451
|
}
|
|
5342
|
-
const content =
|
|
5452
|
+
const content = fs16.readFileSync(roadmapPath, "utf-8");
|
|
5343
5453
|
const items = parseRoadmap(content);
|
|
5344
|
-
const prdDir =
|
|
5454
|
+
const prdDir = path15.join(projectDir, config.prdDir);
|
|
5345
5455
|
const state = loadRoadmapState(prdDir);
|
|
5346
5456
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
5347
5457
|
const statusItems = items.map((item) => {
|
|
@@ -5378,10 +5488,10 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
5378
5488
|
}
|
|
5379
5489
|
function scanExistingPrdSlugs(prdDir) {
|
|
5380
5490
|
const slugs = /* @__PURE__ */ new Set();
|
|
5381
|
-
if (!
|
|
5491
|
+
if (!fs16.existsSync(prdDir)) {
|
|
5382
5492
|
return slugs;
|
|
5383
5493
|
}
|
|
5384
|
-
const files =
|
|
5494
|
+
const files = fs16.readdirSync(prdDir);
|
|
5385
5495
|
for (const file of files) {
|
|
5386
5496
|
if (!file.endsWith(".md")) {
|
|
5387
5497
|
continue;
|
|
@@ -5417,20 +5527,20 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5417
5527
|
const nextNum = getNextPrdNumber(prdDir);
|
|
5418
5528
|
const padded = String(nextNum).padStart(2, "0");
|
|
5419
5529
|
const filename = `${padded}-${itemSlug}.md`;
|
|
5420
|
-
const filePath =
|
|
5421
|
-
if (!
|
|
5422
|
-
|
|
5530
|
+
const filePath = path15.join(prdDir, filename);
|
|
5531
|
+
if (!fs16.existsSync(prdDir)) {
|
|
5532
|
+
fs16.mkdirSync(prdDir, { recursive: true });
|
|
5423
5533
|
}
|
|
5424
5534
|
const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
|
|
5425
5535
|
const prompt2 = renderSlicerPrompt(promptVars);
|
|
5426
5536
|
const provider = resolveJobProvider(config, "slicer");
|
|
5427
5537
|
const providerArgs = buildProviderArgs(provider, prompt2, projectDir);
|
|
5428
|
-
const logDir =
|
|
5429
|
-
if (!
|
|
5430
|
-
|
|
5538
|
+
const logDir = path15.join(projectDir, "logs");
|
|
5539
|
+
if (!fs16.existsSync(logDir)) {
|
|
5540
|
+
fs16.mkdirSync(logDir, { recursive: true });
|
|
5431
5541
|
}
|
|
5432
|
-
const logFile =
|
|
5433
|
-
const logStream =
|
|
5542
|
+
const logFile = path15.join(logDir, `slicer-${itemSlug}.log`);
|
|
5543
|
+
const logStream = fs16.createWriteStream(logFile, { flags: "w" });
|
|
5434
5544
|
logStream.on("error", () => {
|
|
5435
5545
|
});
|
|
5436
5546
|
return new Promise((resolve10) => {
|
|
@@ -5467,7 +5577,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5467
5577
|
});
|
|
5468
5578
|
return;
|
|
5469
5579
|
}
|
|
5470
|
-
if (!
|
|
5580
|
+
if (!fs16.existsSync(filePath)) {
|
|
5471
5581
|
resolve10({
|
|
5472
5582
|
sliced: false,
|
|
5473
5583
|
error: `Provider did not create expected file: ${filePath}`,
|
|
@@ -5490,23 +5600,23 @@ async function sliceNextItem(projectDir, config) {
|
|
|
5490
5600
|
error: "Roadmap scanner is disabled"
|
|
5491
5601
|
};
|
|
5492
5602
|
}
|
|
5493
|
-
const roadmapPath =
|
|
5603
|
+
const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
5494
5604
|
const auditItems = collectAuditPlannerItems(projectDir);
|
|
5495
|
-
const roadmapExists =
|
|
5605
|
+
const roadmapExists = fs16.existsSync(roadmapPath);
|
|
5496
5606
|
if (!roadmapExists && auditItems.length === 0) {
|
|
5497
5607
|
return {
|
|
5498
5608
|
sliced: false,
|
|
5499
5609
|
error: "No pending items to process"
|
|
5500
5610
|
};
|
|
5501
5611
|
}
|
|
5502
|
-
const roadmapItems = roadmapExists ? parseRoadmap(
|
|
5612
|
+
const roadmapItems = roadmapExists ? parseRoadmap(fs16.readFileSync(roadmapPath, "utf-8")) : [];
|
|
5503
5613
|
if (roadmapExists && roadmapItems.length === 0 && auditItems.length === 0) {
|
|
5504
5614
|
return {
|
|
5505
5615
|
sliced: false,
|
|
5506
5616
|
error: "No items in roadmap"
|
|
5507
5617
|
};
|
|
5508
5618
|
}
|
|
5509
|
-
const prdDir =
|
|
5619
|
+
const prdDir = path15.join(projectDir, config.prdDir);
|
|
5510
5620
|
const state = loadRoadmapState(prdDir);
|
|
5511
5621
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
5512
5622
|
const pickEligibleItem = (items) => {
|
|
@@ -5677,8 +5787,8 @@ var init_shell = __esm({
|
|
|
5677
5787
|
});
|
|
5678
5788
|
|
|
5679
5789
|
// ../core/dist/utils/scheduling.js
|
|
5680
|
-
import * as
|
|
5681
|
-
import * as
|
|
5790
|
+
import * as fs17 from "fs";
|
|
5791
|
+
import * as path16 from "path";
|
|
5682
5792
|
function normalizeSchedulingPriority(priority) {
|
|
5683
5793
|
if (!Number.isFinite(priority)) {
|
|
5684
5794
|
return DEFAULT_SCHEDULING_PRIORITY;
|
|
@@ -5704,7 +5814,7 @@ function isJobTypeEnabled(config, jobType) {
|
|
|
5704
5814
|
}
|
|
5705
5815
|
}
|
|
5706
5816
|
function loadPeerConfig(projectPath) {
|
|
5707
|
-
if (!
|
|
5817
|
+
if (!fs17.existsSync(projectPath) || !fs17.existsSync(path16.join(projectPath, CONFIG_FILE_NAME))) {
|
|
5708
5818
|
return null;
|
|
5709
5819
|
}
|
|
5710
5820
|
try {
|
|
@@ -5715,9 +5825,9 @@ function loadPeerConfig(projectPath) {
|
|
|
5715
5825
|
}
|
|
5716
5826
|
function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
5717
5827
|
const peers = /* @__PURE__ */ new Map();
|
|
5718
|
-
const currentPath =
|
|
5828
|
+
const currentPath = path16.resolve(currentProjectDir);
|
|
5719
5829
|
const addPeer = (projectPath, config) => {
|
|
5720
|
-
const resolvedPath =
|
|
5830
|
+
const resolvedPath = path16.resolve(projectPath);
|
|
5721
5831
|
if (!isJobTypeEnabled(config, jobType)) {
|
|
5722
5832
|
return;
|
|
5723
5833
|
}
|
|
@@ -5725,12 +5835,12 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
5725
5835
|
path: resolvedPath,
|
|
5726
5836
|
config,
|
|
5727
5837
|
schedulingPriority: normalizeSchedulingPriority(config.schedulingPriority),
|
|
5728
|
-
sortKey: `${
|
|
5838
|
+
sortKey: `${path16.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
|
|
5729
5839
|
});
|
|
5730
5840
|
};
|
|
5731
5841
|
addPeer(currentPath, currentConfig);
|
|
5732
5842
|
for (const entry of loadRegistry()) {
|
|
5733
|
-
const resolvedPath =
|
|
5843
|
+
const resolvedPath = path16.resolve(entry.path);
|
|
5734
5844
|
if (resolvedPath === currentPath || peers.has(resolvedPath)) {
|
|
5735
5845
|
continue;
|
|
5736
5846
|
}
|
|
@@ -5748,7 +5858,7 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
5748
5858
|
}
|
|
5749
5859
|
function getSchedulingPlan(projectDir, config, jobType) {
|
|
5750
5860
|
const peers = collectSchedulingPeers(projectDir, config, jobType);
|
|
5751
|
-
const currentPath =
|
|
5861
|
+
const currentPath = path16.resolve(projectDir);
|
|
5752
5862
|
const slotIndex = Math.max(0, peers.findIndex((peer) => peer.path === currentPath));
|
|
5753
5863
|
const peerCount = Math.max(1, peers.length);
|
|
5754
5864
|
const balancedDelayMinutes = peerCount <= 1 ? 0 : Math.floor(slotIndex * 60 / peerCount);
|
|
@@ -5840,8 +5950,8 @@ var init_webhook_validator = __esm({
|
|
|
5840
5950
|
|
|
5841
5951
|
// ../core/dist/utils/worktree-manager.js
|
|
5842
5952
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
5843
|
-
import * as
|
|
5844
|
-
import * as
|
|
5953
|
+
import * as fs18 from "fs";
|
|
5954
|
+
import * as path17 from "path";
|
|
5845
5955
|
function gitExec(args, cwd, logFile) {
|
|
5846
5956
|
try {
|
|
5847
5957
|
const result = execFileSync4("git", args, {
|
|
@@ -5851,7 +5961,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
5851
5961
|
});
|
|
5852
5962
|
if (logFile && result) {
|
|
5853
5963
|
try {
|
|
5854
|
-
|
|
5964
|
+
fs18.appendFileSync(logFile, result);
|
|
5855
5965
|
} catch {
|
|
5856
5966
|
}
|
|
5857
5967
|
}
|
|
@@ -5860,7 +5970,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
5860
5970
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
5861
5971
|
if (logFile) {
|
|
5862
5972
|
try {
|
|
5863
|
-
|
|
5973
|
+
fs18.appendFileSync(logFile, errorMessage + "\n");
|
|
5864
5974
|
} catch {
|
|
5865
5975
|
}
|
|
5866
5976
|
}
|
|
@@ -5885,11 +5995,11 @@ function branchExistsRemotely(projectDir, branchName) {
|
|
|
5885
5995
|
}
|
|
5886
5996
|
function prepareBranchWorktree(options) {
|
|
5887
5997
|
const { projectDir, worktreeDir, branchName, defaultBranch, logFile } = options;
|
|
5888
|
-
if (
|
|
5998
|
+
if (fs18.existsSync(worktreeDir)) {
|
|
5889
5999
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
5890
6000
|
if (!isRegistered) {
|
|
5891
6001
|
try {
|
|
5892
|
-
|
|
6002
|
+
fs18.rmSync(worktreeDir, { recursive: true, force: true });
|
|
5893
6003
|
} catch {
|
|
5894
6004
|
}
|
|
5895
6005
|
}
|
|
@@ -5928,11 +6038,11 @@ function prepareBranchWorktree(options) {
|
|
|
5928
6038
|
}
|
|
5929
6039
|
function prepareDetachedWorktree(options) {
|
|
5930
6040
|
const { projectDir, worktreeDir, defaultBranch, logFile } = options;
|
|
5931
|
-
if (
|
|
6041
|
+
if (fs18.existsSync(worktreeDir)) {
|
|
5932
6042
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
5933
6043
|
if (!isRegistered) {
|
|
5934
6044
|
try {
|
|
5935
|
-
|
|
6045
|
+
fs18.rmSync(worktreeDir, { recursive: true, force: true });
|
|
5936
6046
|
} catch {
|
|
5937
6047
|
}
|
|
5938
6048
|
}
|
|
@@ -5974,7 +6084,7 @@ function isWorktreeRegistered(projectDir, worktreePath) {
|
|
|
5974
6084
|
}
|
|
5975
6085
|
}
|
|
5976
6086
|
function cleanupWorktrees(projectDir, scope) {
|
|
5977
|
-
const projectName =
|
|
6087
|
+
const projectName = path17.basename(projectDir);
|
|
5978
6088
|
const matchToken = scope ? scope : `${projectName}-nw`;
|
|
5979
6089
|
const removed = [];
|
|
5980
6090
|
try {
|
|
@@ -6009,16 +6119,16 @@ var init_worktree_manager = __esm({
|
|
|
6009
6119
|
});
|
|
6010
6120
|
|
|
6011
6121
|
// ../core/dist/utils/job-queue.js
|
|
6012
|
-
import * as
|
|
6013
|
-
import * as
|
|
6122
|
+
import * as os7 from "os";
|
|
6123
|
+
import * as path18 from "path";
|
|
6014
6124
|
import Database7 from "better-sqlite3";
|
|
6015
6125
|
function getStateDbPath() {
|
|
6016
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
6017
|
-
return
|
|
6126
|
+
const base = process.env.NIGHT_WATCH_HOME || path18.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
6127
|
+
return path18.join(base, STATE_DB_FILE_NAME);
|
|
6018
6128
|
}
|
|
6019
6129
|
function getQueueLockPath() {
|
|
6020
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
6021
|
-
return
|
|
6130
|
+
const base = process.env.NIGHT_WATCH_HOME || path18.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
6131
|
+
return path18.join(base, QUEUE_LOCK_FILE_NAME);
|
|
6022
6132
|
}
|
|
6023
6133
|
function openDb() {
|
|
6024
6134
|
const dbPath = getStateDbPath();
|
|
@@ -6586,9 +6696,9 @@ var init_amplitude_client = __esm({
|
|
|
6586
6696
|
});
|
|
6587
6697
|
|
|
6588
6698
|
// ../core/dist/analytics/analytics-runner.js
|
|
6589
|
-
import * as
|
|
6590
|
-
import * as
|
|
6591
|
-
import * as
|
|
6699
|
+
import * as fs19 from "fs";
|
|
6700
|
+
import * as os8 from "os";
|
|
6701
|
+
import * as path19 from "path";
|
|
6592
6702
|
function parseIssuesFromResponse(text) {
|
|
6593
6703
|
const start = text.indexOf("[");
|
|
6594
6704
|
const end = text.lastIndexOf("]");
|
|
@@ -6617,9 +6727,9 @@ async function runAnalytics(config, projectDir) {
|
|
|
6617
6727
|
|
|
6618
6728
|
--- AMPLITUDE DATA ---
|
|
6619
6729
|
${JSON.stringify(data, null, 2)}`;
|
|
6620
|
-
const tmpDir =
|
|
6621
|
-
const promptFile =
|
|
6622
|
-
|
|
6730
|
+
const tmpDir = fs19.mkdtempSync(path19.join(os8.tmpdir(), "nw-analytics-"));
|
|
6731
|
+
const promptFile = path19.join(tmpDir, "analytics-prompt.md");
|
|
6732
|
+
fs19.writeFileSync(promptFile, prompt2, "utf-8");
|
|
6623
6733
|
try {
|
|
6624
6734
|
const provider = resolveJobProvider(config, "analytics");
|
|
6625
6735
|
const providerCmd = PROVIDER_COMMANDS[provider];
|
|
@@ -6636,8 +6746,8 @@ set -euo pipefail
|
|
|
6636
6746
|
${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
|
|
6637
6747
|
`;
|
|
6638
6748
|
}
|
|
6639
|
-
const scriptFile =
|
|
6640
|
-
|
|
6749
|
+
const scriptFile = path19.join(tmpDir, "run-analytics.sh");
|
|
6750
|
+
fs19.writeFileSync(scriptFile, scriptContent, { mode: 493 });
|
|
6641
6751
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], config.providerEnv ?? {});
|
|
6642
6752
|
if (exitCode !== 0) {
|
|
6643
6753
|
throw new Error(`AI provider exited with code ${exitCode}: ${stderr || stdout}`);
|
|
@@ -6675,7 +6785,7 @@ ${stderr}`;
|
|
|
6675
6785
|
};
|
|
6676
6786
|
} finally {
|
|
6677
6787
|
try {
|
|
6678
|
-
|
|
6788
|
+
fs19.rmSync(tmpDir, { recursive: true, force: true });
|
|
6679
6789
|
} catch {
|
|
6680
6790
|
}
|
|
6681
6791
|
}
|
|
@@ -6953,6 +7063,7 @@ __export(dist_exports, {
|
|
|
6953
7063
|
EXECUTOR_LOG_FILE: () => EXECUTOR_LOG_FILE,
|
|
6954
7064
|
EXECUTOR_LOG_NAME: () => EXECUTOR_LOG_NAME,
|
|
6955
7065
|
GLOBAL_CONFIG_DIR: () => GLOBAL_CONFIG_DIR,
|
|
7066
|
+
GLOBAL_NOTIFICATIONS_FILE_NAME: () => GLOBAL_NOTIFICATIONS_FILE_NAME,
|
|
6956
7067
|
HISTORY_FILE_NAME: () => HISTORY_FILE_NAME,
|
|
6957
7068
|
HORIZON_LABELS: () => HORIZON_LABELS,
|
|
6958
7069
|
HORIZON_LABEL_INFO: () => HORIZON_LABEL_INFO,
|
|
@@ -7106,6 +7217,7 @@ __export(dist_exports, {
|
|
|
7106
7217
|
label: () => label,
|
|
7107
7218
|
listPrdStatesByStatus: () => listPrdStatesByStatus,
|
|
7108
7219
|
loadConfig: () => loadConfig,
|
|
7220
|
+
loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
|
|
7109
7221
|
loadHistory: () => loadHistory,
|
|
7110
7222
|
loadRegistry: () => loadRegistry,
|
|
7111
7223
|
loadRoadmapState: () => loadRoadmapState,
|
|
@@ -7146,6 +7258,7 @@ __export(dist_exports, {
|
|
|
7146
7258
|
runAnalytics: () => runAnalytics,
|
|
7147
7259
|
runMigrations: () => runMigrations,
|
|
7148
7260
|
saveConfig: () => saveConfig,
|
|
7261
|
+
saveGlobalNotificationsConfig: () => saveGlobalNotificationsConfig,
|
|
7149
7262
|
saveHistory: () => saveHistory,
|
|
7150
7263
|
saveRegistry: () => saveRegistry,
|
|
7151
7264
|
saveRoadmapState: () => saveRoadmapState,
|
|
@@ -7195,6 +7308,7 @@ var init_dist = __esm({
|
|
|
7195
7308
|
init_git_utils();
|
|
7196
7309
|
init_github();
|
|
7197
7310
|
init_log_utils();
|
|
7311
|
+
init_global_config();
|
|
7198
7312
|
init_notify();
|
|
7199
7313
|
init_prd_discovery();
|
|
7200
7314
|
init_prd_states();
|
|
@@ -7221,39 +7335,39 @@ var init_dist = __esm({
|
|
|
7221
7335
|
// src/cli.ts
|
|
7222
7336
|
import "reflect-metadata";
|
|
7223
7337
|
import { Command as Command3 } from "commander";
|
|
7224
|
-
import { existsSync as
|
|
7338
|
+
import { existsSync as existsSync30, readFileSync as readFileSync19 } from "fs";
|
|
7225
7339
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7226
|
-
import { dirname as
|
|
7340
|
+
import { dirname as dirname9, join as join36 } from "path";
|
|
7227
7341
|
|
|
7228
7342
|
// src/commands/init.ts
|
|
7229
7343
|
init_dist();
|
|
7230
|
-
import
|
|
7231
|
-
import
|
|
7344
|
+
import fs20 from "fs";
|
|
7345
|
+
import path20 from "path";
|
|
7232
7346
|
import { execSync as execSync3 } from "child_process";
|
|
7233
7347
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7234
|
-
import { dirname as
|
|
7348
|
+
import { dirname as dirname5, join as join18 } from "path";
|
|
7235
7349
|
import * as readline from "readline";
|
|
7236
7350
|
var __filename = fileURLToPath2(import.meta.url);
|
|
7237
|
-
var __dirname2 =
|
|
7351
|
+
var __dirname2 = dirname5(__filename);
|
|
7238
7352
|
function findTemplatesDir(startDir) {
|
|
7239
7353
|
let d = startDir;
|
|
7240
7354
|
for (let i = 0; i < 8; i++) {
|
|
7241
|
-
const candidate =
|
|
7242
|
-
if (
|
|
7355
|
+
const candidate = join18(d, "templates");
|
|
7356
|
+
if (fs20.existsSync(candidate) && fs20.statSync(candidate).isDirectory()) {
|
|
7243
7357
|
return candidate;
|
|
7244
7358
|
}
|
|
7245
|
-
d =
|
|
7359
|
+
d = dirname5(d);
|
|
7246
7360
|
}
|
|
7247
|
-
return
|
|
7361
|
+
return join18(startDir, "templates");
|
|
7248
7362
|
}
|
|
7249
7363
|
var TEMPLATES_DIR = findTemplatesDir(__dirname2);
|
|
7250
7364
|
function hasPlaywrightDependency(cwd) {
|
|
7251
|
-
const packageJsonPath =
|
|
7252
|
-
if (!
|
|
7365
|
+
const packageJsonPath = path20.join(cwd, "package.json");
|
|
7366
|
+
if (!fs20.existsSync(packageJsonPath)) {
|
|
7253
7367
|
return false;
|
|
7254
7368
|
}
|
|
7255
7369
|
try {
|
|
7256
|
-
const packageJson2 = JSON.parse(
|
|
7370
|
+
const packageJson2 = JSON.parse(fs20.readFileSync(packageJsonPath, "utf-8"));
|
|
7257
7371
|
return Boolean(
|
|
7258
7372
|
packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
|
|
7259
7373
|
);
|
|
@@ -7265,7 +7379,7 @@ function detectPlaywright(cwd) {
|
|
|
7265
7379
|
if (hasPlaywrightDependency(cwd)) {
|
|
7266
7380
|
return true;
|
|
7267
7381
|
}
|
|
7268
|
-
if (
|
|
7382
|
+
if (fs20.existsSync(path20.join(cwd, "node_modules", ".bin", "playwright"))) {
|
|
7269
7383
|
return true;
|
|
7270
7384
|
}
|
|
7271
7385
|
try {
|
|
@@ -7281,10 +7395,10 @@ function detectPlaywright(cwd) {
|
|
|
7281
7395
|
}
|
|
7282
7396
|
}
|
|
7283
7397
|
function resolvePlaywrightInstallCommand(cwd) {
|
|
7284
|
-
if (
|
|
7398
|
+
if (fs20.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
|
|
7285
7399
|
return "pnpm add -D @playwright/test";
|
|
7286
7400
|
}
|
|
7287
|
-
if (
|
|
7401
|
+
if (fs20.existsSync(path20.join(cwd, "yarn.lock"))) {
|
|
7288
7402
|
return "yarn add -D @playwright/test";
|
|
7289
7403
|
}
|
|
7290
7404
|
return "npm install -D @playwright/test";
|
|
@@ -7430,8 +7544,8 @@ function promptProviderSelection(providers) {
|
|
|
7430
7544
|
});
|
|
7431
7545
|
}
|
|
7432
7546
|
function ensureDir(dirPath) {
|
|
7433
|
-
if (!
|
|
7434
|
-
|
|
7547
|
+
if (!fs20.existsSync(dirPath)) {
|
|
7548
|
+
fs20.mkdirSync(dirPath, { recursive: true });
|
|
7435
7549
|
}
|
|
7436
7550
|
}
|
|
7437
7551
|
function buildInitConfig(params) {
|
|
@@ -7488,30 +7602,30 @@ function buildInitConfig(params) {
|
|
|
7488
7602
|
}
|
|
7489
7603
|
function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
|
|
7490
7604
|
if (customTemplatesDir !== null) {
|
|
7491
|
-
const customPath =
|
|
7492
|
-
if (
|
|
7605
|
+
const customPath = join18(customTemplatesDir, templateName);
|
|
7606
|
+
if (fs20.existsSync(customPath)) {
|
|
7493
7607
|
return { path: customPath, source: "custom" };
|
|
7494
7608
|
}
|
|
7495
7609
|
}
|
|
7496
|
-
return { path:
|
|
7610
|
+
return { path: join18(bundledTemplatesDir, templateName), source: "bundled" };
|
|
7497
7611
|
}
|
|
7498
7612
|
function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
|
|
7499
|
-
if (
|
|
7613
|
+
if (fs20.existsSync(targetPath) && !force) {
|
|
7500
7614
|
console.log(` Skipped (exists): ${targetPath}`);
|
|
7501
7615
|
return { created: false, source: source ?? "bundled" };
|
|
7502
7616
|
}
|
|
7503
|
-
const templatePath = sourcePath ??
|
|
7617
|
+
const templatePath = sourcePath ?? join18(TEMPLATES_DIR, templateName);
|
|
7504
7618
|
const resolvedSource = source ?? "bundled";
|
|
7505
|
-
let content =
|
|
7619
|
+
let content = fs20.readFileSync(templatePath, "utf-8");
|
|
7506
7620
|
for (const [key, value] of Object.entries(replacements)) {
|
|
7507
7621
|
content = content.replaceAll(key, value);
|
|
7508
7622
|
}
|
|
7509
|
-
|
|
7623
|
+
fs20.writeFileSync(targetPath, content);
|
|
7510
7624
|
console.log(` Created: ${targetPath} (${resolvedSource})`);
|
|
7511
7625
|
return { created: true, source: resolvedSource };
|
|
7512
7626
|
}
|
|
7513
7627
|
function addToGitignore(cwd) {
|
|
7514
|
-
const gitignorePath =
|
|
7628
|
+
const gitignorePath = path20.join(cwd, ".gitignore");
|
|
7515
7629
|
const entries = [
|
|
7516
7630
|
{
|
|
7517
7631
|
pattern: "/logs/",
|
|
@@ -7525,13 +7639,13 @@ function addToGitignore(cwd) {
|
|
|
7525
7639
|
},
|
|
7526
7640
|
{ pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
|
|
7527
7641
|
];
|
|
7528
|
-
if (!
|
|
7642
|
+
if (!fs20.existsSync(gitignorePath)) {
|
|
7529
7643
|
const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
|
|
7530
|
-
|
|
7644
|
+
fs20.writeFileSync(gitignorePath, lines.join("\n"));
|
|
7531
7645
|
console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
|
|
7532
7646
|
return;
|
|
7533
7647
|
}
|
|
7534
|
-
const content =
|
|
7648
|
+
const content = fs20.readFileSync(gitignorePath, "utf-8");
|
|
7535
7649
|
const missing = entries.filter((e) => !e.check(content));
|
|
7536
7650
|
if (missing.length === 0) {
|
|
7537
7651
|
console.log(` Skipped (exists): Night Watch entries in .gitignore`);
|
|
@@ -7539,7 +7653,7 @@ function addToGitignore(cwd) {
|
|
|
7539
7653
|
}
|
|
7540
7654
|
const additions = missing.map((e) => e.pattern).join("\n");
|
|
7541
7655
|
const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
|
|
7542
|
-
|
|
7656
|
+
fs20.writeFileSync(gitignorePath, newContent);
|
|
7543
7657
|
console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
|
|
7544
7658
|
}
|
|
7545
7659
|
function initCommand(program2) {
|
|
@@ -7660,28 +7774,28 @@ function initCommand(program2) {
|
|
|
7660
7774
|
"${DEFAULT_BRANCH}": defaultBranch
|
|
7661
7775
|
};
|
|
7662
7776
|
step(6, totalSteps, "Creating PRD directory structure...");
|
|
7663
|
-
const prdDirPath =
|
|
7664
|
-
const doneDirPath =
|
|
7777
|
+
const prdDirPath = path20.join(cwd, prdDir);
|
|
7778
|
+
const doneDirPath = path20.join(prdDirPath, "done");
|
|
7665
7779
|
ensureDir(doneDirPath);
|
|
7666
7780
|
success(`Created ${prdDirPath}/`);
|
|
7667
7781
|
success(`Created ${doneDirPath}/`);
|
|
7668
7782
|
step(7, totalSteps, "Creating logs directory...");
|
|
7669
|
-
const logsPath =
|
|
7783
|
+
const logsPath = path20.join(cwd, LOG_DIR);
|
|
7670
7784
|
ensureDir(logsPath);
|
|
7671
7785
|
success(`Created ${logsPath}/`);
|
|
7672
7786
|
addToGitignore(cwd);
|
|
7673
7787
|
step(8, totalSteps, "Creating instructions directory...");
|
|
7674
|
-
const instructionsDir =
|
|
7788
|
+
const instructionsDir = path20.join(cwd, "instructions");
|
|
7675
7789
|
ensureDir(instructionsDir);
|
|
7676
7790
|
success(`Created ${instructionsDir}/`);
|
|
7677
7791
|
const existingConfig = loadConfig(cwd);
|
|
7678
|
-
const customTemplatesDirPath =
|
|
7679
|
-
const customTemplatesDir =
|
|
7792
|
+
const customTemplatesDirPath = path20.join(cwd, existingConfig.templatesDir);
|
|
7793
|
+
const customTemplatesDir = fs20.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
|
|
7680
7794
|
const templateSources = [];
|
|
7681
7795
|
const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
|
|
7682
7796
|
const nwResult = processTemplate(
|
|
7683
7797
|
"executor.md",
|
|
7684
|
-
|
|
7798
|
+
path20.join(instructionsDir, "executor.md"),
|
|
7685
7799
|
replacements,
|
|
7686
7800
|
force,
|
|
7687
7801
|
nwResolution.path,
|
|
@@ -7695,7 +7809,7 @@ function initCommand(program2) {
|
|
|
7695
7809
|
);
|
|
7696
7810
|
const peResult = processTemplate(
|
|
7697
7811
|
"prd-executor.md",
|
|
7698
|
-
|
|
7812
|
+
path20.join(instructionsDir, "prd-executor.md"),
|
|
7699
7813
|
replacements,
|
|
7700
7814
|
force,
|
|
7701
7815
|
peResolution.path,
|
|
@@ -7705,7 +7819,7 @@ function initCommand(program2) {
|
|
|
7705
7819
|
const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
|
|
7706
7820
|
const prResult = processTemplate(
|
|
7707
7821
|
"pr-reviewer.md",
|
|
7708
|
-
|
|
7822
|
+
path20.join(instructionsDir, "pr-reviewer.md"),
|
|
7709
7823
|
replacements,
|
|
7710
7824
|
force,
|
|
7711
7825
|
prResolution.path,
|
|
@@ -7715,7 +7829,7 @@ function initCommand(program2) {
|
|
|
7715
7829
|
const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
|
|
7716
7830
|
const qaResult = processTemplate(
|
|
7717
7831
|
"qa.md",
|
|
7718
|
-
|
|
7832
|
+
path20.join(instructionsDir, "qa.md"),
|
|
7719
7833
|
replacements,
|
|
7720
7834
|
force,
|
|
7721
7835
|
qaResolution.path,
|
|
@@ -7725,7 +7839,7 @@ function initCommand(program2) {
|
|
|
7725
7839
|
const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
|
|
7726
7840
|
const auditResult = processTemplate(
|
|
7727
7841
|
"audit.md",
|
|
7728
|
-
|
|
7842
|
+
path20.join(instructionsDir, "audit.md"),
|
|
7729
7843
|
replacements,
|
|
7730
7844
|
force,
|
|
7731
7845
|
auditResolution.path,
|
|
@@ -7733,8 +7847,8 @@ function initCommand(program2) {
|
|
|
7733
7847
|
);
|
|
7734
7848
|
templateSources.push({ name: "audit.md", source: auditResult.source });
|
|
7735
7849
|
step(9, totalSteps, "Creating configuration file...");
|
|
7736
|
-
const configPath =
|
|
7737
|
-
if (
|
|
7850
|
+
const configPath = path20.join(cwd, CONFIG_FILE_NAME);
|
|
7851
|
+
if (fs20.existsSync(configPath) && !force) {
|
|
7738
7852
|
console.log(` Skipped (exists): ${configPath}`);
|
|
7739
7853
|
} else {
|
|
7740
7854
|
const config = buildInitConfig({
|
|
@@ -7744,11 +7858,11 @@ function initCommand(program2) {
|
|
|
7744
7858
|
reviewerEnabled,
|
|
7745
7859
|
prdDir
|
|
7746
7860
|
});
|
|
7747
|
-
|
|
7861
|
+
fs20.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
7748
7862
|
success(`Created ${configPath}`);
|
|
7749
7863
|
}
|
|
7750
7864
|
step(10, totalSteps, "Setting up GitHub Project board...");
|
|
7751
|
-
const existingRaw = JSON.parse(
|
|
7865
|
+
const existingRaw = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
|
|
7752
7866
|
const existingBoard = existingRaw.boardProvider;
|
|
7753
7867
|
let boardSetupStatus = "Skipped";
|
|
7754
7868
|
if (existingBoard?.projectNumber && !force) {
|
|
@@ -7770,13 +7884,13 @@ function initCommand(program2) {
|
|
|
7770
7884
|
const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
|
|
7771
7885
|
const boardTitle = `${projectName} Night Watch`;
|
|
7772
7886
|
const board = await provider.setupBoard(boardTitle);
|
|
7773
|
-
const rawConfig = JSON.parse(
|
|
7887
|
+
const rawConfig = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
|
|
7774
7888
|
rawConfig.boardProvider = {
|
|
7775
7889
|
enabled: true,
|
|
7776
7890
|
provider: "github",
|
|
7777
7891
|
projectNumber: board.number
|
|
7778
7892
|
};
|
|
7779
|
-
|
|
7893
|
+
fs20.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
|
|
7780
7894
|
boardSetupStatus = `Created (#${board.number})`;
|
|
7781
7895
|
success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
|
|
7782
7896
|
} catch (boardErr) {
|
|
@@ -7900,8 +8014,8 @@ function getTelegramStatusWebhooks(config) {
|
|
|
7900
8014
|
}
|
|
7901
8015
|
|
|
7902
8016
|
// src/commands/run.ts
|
|
7903
|
-
import * as
|
|
7904
|
-
import * as
|
|
8017
|
+
import * as fs21 from "fs";
|
|
8018
|
+
import * as path21 from "path";
|
|
7905
8019
|
function resolveRunNotificationEvent(exitCode, scriptStatus) {
|
|
7906
8020
|
if (exitCode === 124) {
|
|
7907
8021
|
return "run_timeout";
|
|
@@ -7933,12 +8047,12 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
|
|
|
7933
8047
|
return scriptStatus === "skip_no_eligible_prd";
|
|
7934
8048
|
}
|
|
7935
8049
|
function getCrossProjectFallbackCandidates(currentProjectDir) {
|
|
7936
|
-
const current =
|
|
8050
|
+
const current = path21.resolve(currentProjectDir);
|
|
7937
8051
|
const { valid, invalid } = validateRegistry();
|
|
7938
8052
|
for (const entry of invalid) {
|
|
7939
8053
|
warn(`Skipping invalid registry entry: ${entry.path}`);
|
|
7940
8054
|
}
|
|
7941
|
-
return valid.filter((entry) =>
|
|
8055
|
+
return valid.filter((entry) => path21.resolve(entry.path) !== current);
|
|
7942
8056
|
}
|
|
7943
8057
|
async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
|
|
7944
8058
|
if (isRateLimitFallbackTriggered(scriptResult?.data)) {
|
|
@@ -7948,7 +8062,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
7948
8062
|
if (nonTelegramWebhooks.length > 0) {
|
|
7949
8063
|
const _rateLimitCtx = {
|
|
7950
8064
|
event: "rate_limit_fallback",
|
|
7951
|
-
projectName:
|
|
8065
|
+
projectName: path21.basename(projectDir),
|
|
7952
8066
|
exitCode,
|
|
7953
8067
|
provider: config.provider
|
|
7954
8068
|
};
|
|
@@ -7976,7 +8090,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
7976
8090
|
const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
|
|
7977
8091
|
const _ctx = {
|
|
7978
8092
|
event,
|
|
7979
|
-
projectName:
|
|
8093
|
+
projectName: path21.basename(projectDir),
|
|
7980
8094
|
exitCode,
|
|
7981
8095
|
provider: config.provider,
|
|
7982
8096
|
prdName: scriptResult?.data.prd,
|
|
@@ -8138,20 +8252,20 @@ function applyCliOverrides(config, options) {
|
|
|
8138
8252
|
return overridden;
|
|
8139
8253
|
}
|
|
8140
8254
|
function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
8141
|
-
const absolutePrdDir =
|
|
8142
|
-
const doneDir =
|
|
8255
|
+
const absolutePrdDir = path21.join(projectDir, prdDir);
|
|
8256
|
+
const doneDir = path21.join(absolutePrdDir, "done");
|
|
8143
8257
|
const pending = [];
|
|
8144
8258
|
const completed = [];
|
|
8145
|
-
if (
|
|
8146
|
-
const entries =
|
|
8259
|
+
if (fs21.existsSync(absolutePrdDir)) {
|
|
8260
|
+
const entries = fs21.readdirSync(absolutePrdDir, { withFileTypes: true });
|
|
8147
8261
|
for (const entry of entries) {
|
|
8148
8262
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8149
|
-
const claimPath =
|
|
8263
|
+
const claimPath = path21.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
|
|
8150
8264
|
let claimed = false;
|
|
8151
8265
|
let claimInfo = null;
|
|
8152
|
-
if (
|
|
8266
|
+
if (fs21.existsSync(claimPath)) {
|
|
8153
8267
|
try {
|
|
8154
|
-
const content =
|
|
8268
|
+
const content = fs21.readFileSync(claimPath, "utf-8");
|
|
8155
8269
|
const data = JSON.parse(content);
|
|
8156
8270
|
const age = Math.floor(Date.now() / 1e3) - data.timestamp;
|
|
8157
8271
|
if (age < maxRuntime) {
|
|
@@ -8165,8 +8279,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
|
8165
8279
|
}
|
|
8166
8280
|
}
|
|
8167
8281
|
}
|
|
8168
|
-
if (
|
|
8169
|
-
const entries =
|
|
8282
|
+
if (fs21.existsSync(doneDir)) {
|
|
8283
|
+
const entries = fs21.readdirSync(doneDir, { withFileTypes: true });
|
|
8170
8284
|
for (const entry of entries) {
|
|
8171
8285
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8172
8286
|
completed.push(entry.name);
|
|
@@ -8326,7 +8440,7 @@ ${stderr}`);
|
|
|
8326
8440
|
// src/commands/review.ts
|
|
8327
8441
|
init_dist();
|
|
8328
8442
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
8329
|
-
import * as
|
|
8443
|
+
import * as path22 from "path";
|
|
8330
8444
|
function shouldSendReviewNotification(scriptStatus) {
|
|
8331
8445
|
if (!scriptStatus) {
|
|
8332
8446
|
return true;
|
|
@@ -8566,7 +8680,7 @@ ${stderr}`);
|
|
|
8566
8680
|
const finalScore = parseFinalReviewScore(scriptResult?.data.final_score);
|
|
8567
8681
|
const _reviewCtx = {
|
|
8568
8682
|
event: "review_completed",
|
|
8569
|
-
projectName:
|
|
8683
|
+
projectName: path22.basename(projectDir),
|
|
8570
8684
|
exitCode,
|
|
8571
8685
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
8572
8686
|
prUrl: prDetails?.url,
|
|
@@ -8587,7 +8701,7 @@ ${stderr}`);
|
|
|
8587
8701
|
const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
|
|
8588
8702
|
const _mergeCtx = {
|
|
8589
8703
|
event: "pr_auto_merged",
|
|
8590
|
-
projectName:
|
|
8704
|
+
projectName: path22.basename(projectDir),
|
|
8591
8705
|
exitCode,
|
|
8592
8706
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
8593
8707
|
prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
|
|
@@ -8612,7 +8726,7 @@ ${stderr}`);
|
|
|
8612
8726
|
|
|
8613
8727
|
// src/commands/qa.ts
|
|
8614
8728
|
init_dist();
|
|
8615
|
-
import * as
|
|
8729
|
+
import * as path23 from "path";
|
|
8616
8730
|
function shouldSendQaNotification(scriptStatus) {
|
|
8617
8731
|
if (!scriptStatus) {
|
|
8618
8732
|
return true;
|
|
@@ -8746,7 +8860,7 @@ ${stderr}`);
|
|
|
8746
8860
|
const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
|
|
8747
8861
|
const _qaCtx = {
|
|
8748
8862
|
event: "qa_completed",
|
|
8749
|
-
projectName:
|
|
8863
|
+
projectName: path23.basename(projectDir),
|
|
8750
8864
|
exitCode,
|
|
8751
8865
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
8752
8866
|
prNumber: prDetails?.number ?? primaryQaPr,
|
|
@@ -8772,8 +8886,8 @@ ${stderr}`);
|
|
|
8772
8886
|
|
|
8773
8887
|
// src/commands/audit.ts
|
|
8774
8888
|
init_dist();
|
|
8775
|
-
import * as
|
|
8776
|
-
import * as
|
|
8889
|
+
import * as fs22 from "fs";
|
|
8890
|
+
import * as path24 from "path";
|
|
8777
8891
|
function buildEnvVars4(config, options) {
|
|
8778
8892
|
const env = buildBaseEnvVars(config, "audit", options.dryRun);
|
|
8779
8893
|
env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
|
|
@@ -8816,7 +8930,7 @@ function auditCommand(program2) {
|
|
|
8816
8930
|
configTable.push(["Provider", auditProvider]);
|
|
8817
8931
|
configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
|
|
8818
8932
|
configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
|
|
8819
|
-
configTable.push(["Report File",
|
|
8933
|
+
configTable.push(["Report File", path24.join(projectDir, "logs", "audit-report.md")]);
|
|
8820
8934
|
console.log(configTable.toString());
|
|
8821
8935
|
header("Provider Invocation");
|
|
8822
8936
|
const providerCmd = PROVIDER_COMMANDS[auditProvider];
|
|
@@ -8851,8 +8965,8 @@ ${stderr}`);
|
|
|
8851
8965
|
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
8852
8966
|
spinner.succeed("Code audit skipped");
|
|
8853
8967
|
} else {
|
|
8854
|
-
const reportPath =
|
|
8855
|
-
if (!
|
|
8968
|
+
const reportPath = path24.join(projectDir, "logs", "audit-report.md");
|
|
8969
|
+
if (!fs22.existsSync(reportPath)) {
|
|
8856
8970
|
spinner.fail("Code audit finished without a report file");
|
|
8857
8971
|
process.exit(1);
|
|
8858
8972
|
}
|
|
@@ -8863,9 +8977,9 @@ ${stderr}`);
|
|
|
8863
8977
|
const providerExit = scriptResult?.data?.provider_exit;
|
|
8864
8978
|
const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
|
|
8865
8979
|
spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
|
|
8866
|
-
const logPath =
|
|
8867
|
-
if (
|
|
8868
|
-
const logLines =
|
|
8980
|
+
const logPath = path24.join(projectDir, "logs", "audit.log");
|
|
8981
|
+
if (fs22.existsSync(logPath)) {
|
|
8982
|
+
const logLines = fs22.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
|
|
8869
8983
|
if (logLines.length > 0) {
|
|
8870
8984
|
process.stderr.write(logLines.join("\n") + "\n");
|
|
8871
8985
|
}
|
|
@@ -8943,16 +9057,16 @@ function analyticsCommand(program2) {
|
|
|
8943
9057
|
// src/commands/install.ts
|
|
8944
9058
|
init_dist();
|
|
8945
9059
|
import { execSync as execSync4 } from "child_process";
|
|
8946
|
-
import * as
|
|
8947
|
-
import * as
|
|
9060
|
+
import * as path25 from "path";
|
|
9061
|
+
import * as fs23 from "fs";
|
|
8948
9062
|
function shellQuote(value) {
|
|
8949
9063
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
8950
9064
|
}
|
|
8951
9065
|
function getNightWatchBinPath() {
|
|
8952
9066
|
try {
|
|
8953
9067
|
const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
|
|
8954
|
-
const binPath =
|
|
8955
|
-
if (
|
|
9068
|
+
const binPath = path25.join(npmBin, "night-watch");
|
|
9069
|
+
if (fs23.existsSync(binPath)) {
|
|
8956
9070
|
return binPath;
|
|
8957
9071
|
}
|
|
8958
9072
|
} catch {
|
|
@@ -8965,17 +9079,17 @@ function getNightWatchBinPath() {
|
|
|
8965
9079
|
}
|
|
8966
9080
|
function getNodeBinDir() {
|
|
8967
9081
|
if (process.execPath && process.execPath !== "node") {
|
|
8968
|
-
return
|
|
9082
|
+
return path25.dirname(process.execPath);
|
|
8969
9083
|
}
|
|
8970
9084
|
try {
|
|
8971
9085
|
const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
|
|
8972
|
-
return
|
|
9086
|
+
return path25.dirname(nodePath);
|
|
8973
9087
|
} catch {
|
|
8974
9088
|
return "";
|
|
8975
9089
|
}
|
|
8976
9090
|
}
|
|
8977
9091
|
function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
|
|
8978
|
-
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ?
|
|
9092
|
+
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path25.dirname(nightWatchBin) : "";
|
|
8979
9093
|
const pathParts = Array.from(
|
|
8980
9094
|
new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
|
|
8981
9095
|
);
|
|
@@ -8991,12 +9105,12 @@ function performInstall(projectDir, config, options) {
|
|
|
8991
9105
|
const nightWatchBin = getNightWatchBinPath();
|
|
8992
9106
|
const projectName = getProjectName(projectDir);
|
|
8993
9107
|
const marker = generateMarker(projectName);
|
|
8994
|
-
const logDir =
|
|
8995
|
-
if (!
|
|
8996
|
-
|
|
9108
|
+
const logDir = path25.join(projectDir, LOG_DIR);
|
|
9109
|
+
if (!fs23.existsSync(logDir)) {
|
|
9110
|
+
fs23.mkdirSync(logDir, { recursive: true });
|
|
8997
9111
|
}
|
|
8998
|
-
const executorLog =
|
|
8999
|
-
const reviewerLog =
|
|
9112
|
+
const executorLog = path25.join(logDir, "executor.log");
|
|
9113
|
+
const reviewerLog = path25.join(logDir, "reviewer.log");
|
|
9000
9114
|
if (!options?.force) {
|
|
9001
9115
|
const existingEntries2 = Array.from(
|
|
9002
9116
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
@@ -9033,7 +9147,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9033
9147
|
const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
9034
9148
|
if (installSlicer) {
|
|
9035
9149
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
9036
|
-
const slicerLog =
|
|
9150
|
+
const slicerLog = path25.join(logDir, "slicer.log");
|
|
9037
9151
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
9038
9152
|
entries.push(slicerEntry);
|
|
9039
9153
|
}
|
|
@@ -9041,7 +9155,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9041
9155
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
9042
9156
|
if (installQa) {
|
|
9043
9157
|
const qaSchedule = config.qa.schedule;
|
|
9044
|
-
const qaLog =
|
|
9158
|
+
const qaLog = path25.join(logDir, "qa.log");
|
|
9045
9159
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
9046
9160
|
entries.push(qaEntry);
|
|
9047
9161
|
}
|
|
@@ -9049,7 +9163,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9049
9163
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
9050
9164
|
if (installAudit) {
|
|
9051
9165
|
const auditSchedule = config.audit.schedule;
|
|
9052
|
-
const auditLog =
|
|
9166
|
+
const auditLog = path25.join(logDir, "audit.log");
|
|
9053
9167
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
9054
9168
|
entries.push(auditEntry);
|
|
9055
9169
|
}
|
|
@@ -9057,7 +9171,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9057
9171
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
9058
9172
|
if (installAnalytics) {
|
|
9059
9173
|
const analyticsSchedule = config.analytics.schedule;
|
|
9060
|
-
const analyticsLog =
|
|
9174
|
+
const analyticsLog = path25.join(logDir, "analytics.log");
|
|
9061
9175
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
9062
9176
|
entries.push(analyticsEntry);
|
|
9063
9177
|
}
|
|
@@ -9088,12 +9202,12 @@ function installCommand(program2) {
|
|
|
9088
9202
|
const nightWatchBin = getNightWatchBinPath();
|
|
9089
9203
|
const projectName = getProjectName(projectDir);
|
|
9090
9204
|
const marker = generateMarker(projectName);
|
|
9091
|
-
const logDir =
|
|
9092
|
-
if (!
|
|
9093
|
-
|
|
9205
|
+
const logDir = path25.join(projectDir, LOG_DIR);
|
|
9206
|
+
if (!fs23.existsSync(logDir)) {
|
|
9207
|
+
fs23.mkdirSync(logDir, { recursive: true });
|
|
9094
9208
|
}
|
|
9095
|
-
const executorLog =
|
|
9096
|
-
const reviewerLog =
|
|
9209
|
+
const executorLog = path25.join(logDir, "executor.log");
|
|
9210
|
+
const reviewerLog = path25.join(logDir, "reviewer.log");
|
|
9097
9211
|
const existingEntries = Array.from(
|
|
9098
9212
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
9099
9213
|
);
|
|
@@ -9129,7 +9243,7 @@ function installCommand(program2) {
|
|
|
9129
9243
|
const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
9130
9244
|
let slicerLog;
|
|
9131
9245
|
if (installSlicer) {
|
|
9132
|
-
slicerLog =
|
|
9246
|
+
slicerLog = path25.join(logDir, "slicer.log");
|
|
9133
9247
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
9134
9248
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
9135
9249
|
entries.push(slicerEntry);
|
|
@@ -9138,7 +9252,7 @@ function installCommand(program2) {
|
|
|
9138
9252
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
9139
9253
|
let qaLog;
|
|
9140
9254
|
if (installQa) {
|
|
9141
|
-
qaLog =
|
|
9255
|
+
qaLog = path25.join(logDir, "qa.log");
|
|
9142
9256
|
const qaSchedule = config.qa.schedule;
|
|
9143
9257
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
9144
9258
|
entries.push(qaEntry);
|
|
@@ -9147,7 +9261,7 @@ function installCommand(program2) {
|
|
|
9147
9261
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
9148
9262
|
let auditLog;
|
|
9149
9263
|
if (installAudit) {
|
|
9150
|
-
auditLog =
|
|
9264
|
+
auditLog = path25.join(logDir, "audit.log");
|
|
9151
9265
|
const auditSchedule = config.audit.schedule;
|
|
9152
9266
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
9153
9267
|
entries.push(auditEntry);
|
|
@@ -9156,7 +9270,7 @@ function installCommand(program2) {
|
|
|
9156
9270
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
9157
9271
|
let analyticsLog;
|
|
9158
9272
|
if (installAnalytics) {
|
|
9159
|
-
analyticsLog =
|
|
9273
|
+
analyticsLog = path25.join(logDir, "analytics.log");
|
|
9160
9274
|
const analyticsSchedule = config.analytics.schedule;
|
|
9161
9275
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
9162
9276
|
entries.push(analyticsEntry);
|
|
@@ -9204,8 +9318,8 @@ function installCommand(program2) {
|
|
|
9204
9318
|
|
|
9205
9319
|
// src/commands/uninstall.ts
|
|
9206
9320
|
init_dist();
|
|
9207
|
-
import * as
|
|
9208
|
-
import * as
|
|
9321
|
+
import * as path26 from "path";
|
|
9322
|
+
import * as fs24 from "fs";
|
|
9209
9323
|
function performUninstall(projectDir, options) {
|
|
9210
9324
|
try {
|
|
9211
9325
|
const projectName = getProjectName(projectDir);
|
|
@@ -9220,19 +9334,19 @@ function performUninstall(projectDir, options) {
|
|
|
9220
9334
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
9221
9335
|
unregisterProject(projectDir);
|
|
9222
9336
|
if (!options?.keepLogs) {
|
|
9223
|
-
const logDir =
|
|
9224
|
-
if (
|
|
9337
|
+
const logDir = path26.join(projectDir, "logs");
|
|
9338
|
+
if (fs24.existsSync(logDir)) {
|
|
9225
9339
|
const logFiles = ["executor.log", "reviewer.log", "slicer.log", "audit.log"];
|
|
9226
9340
|
logFiles.forEach((logFile) => {
|
|
9227
|
-
const logPath =
|
|
9228
|
-
if (
|
|
9229
|
-
|
|
9341
|
+
const logPath = path26.join(logDir, logFile);
|
|
9342
|
+
if (fs24.existsSync(logPath)) {
|
|
9343
|
+
fs24.unlinkSync(logPath);
|
|
9230
9344
|
}
|
|
9231
9345
|
});
|
|
9232
9346
|
try {
|
|
9233
|
-
const remainingFiles =
|
|
9347
|
+
const remainingFiles = fs24.readdirSync(logDir);
|
|
9234
9348
|
if (remainingFiles.length === 0) {
|
|
9235
|
-
|
|
9349
|
+
fs24.rmdirSync(logDir);
|
|
9236
9350
|
}
|
|
9237
9351
|
} catch {
|
|
9238
9352
|
}
|
|
@@ -9265,21 +9379,21 @@ function uninstallCommand(program2) {
|
|
|
9265
9379
|
existingEntries.forEach((entry) => dim(` ${entry}`));
|
|
9266
9380
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
9267
9381
|
if (!options.keepLogs) {
|
|
9268
|
-
const logDir =
|
|
9269
|
-
if (
|
|
9382
|
+
const logDir = path26.join(projectDir, "logs");
|
|
9383
|
+
if (fs24.existsSync(logDir)) {
|
|
9270
9384
|
const logFiles = ["executor.log", "reviewer.log", "slicer.log", "audit.log"];
|
|
9271
9385
|
let logsRemoved = 0;
|
|
9272
9386
|
logFiles.forEach((logFile) => {
|
|
9273
|
-
const logPath =
|
|
9274
|
-
if (
|
|
9275
|
-
|
|
9387
|
+
const logPath = path26.join(logDir, logFile);
|
|
9388
|
+
if (fs24.existsSync(logPath)) {
|
|
9389
|
+
fs24.unlinkSync(logPath);
|
|
9276
9390
|
logsRemoved++;
|
|
9277
9391
|
}
|
|
9278
9392
|
});
|
|
9279
9393
|
try {
|
|
9280
|
-
const remainingFiles =
|
|
9394
|
+
const remainingFiles = fs24.readdirSync(logDir);
|
|
9281
9395
|
if (remainingFiles.length === 0) {
|
|
9282
|
-
|
|
9396
|
+
fs24.rmdirSync(logDir);
|
|
9283
9397
|
}
|
|
9284
9398
|
} catch {
|
|
9285
9399
|
}
|
|
@@ -9515,14 +9629,14 @@ function statusCommand(program2) {
|
|
|
9515
9629
|
// src/commands/logs.ts
|
|
9516
9630
|
init_dist();
|
|
9517
9631
|
import { spawn as spawn3 } from "child_process";
|
|
9518
|
-
import * as
|
|
9519
|
-
import * as
|
|
9632
|
+
import * as path27 from "path";
|
|
9633
|
+
import * as fs25 from "fs";
|
|
9520
9634
|
function getLastLines(filePath, lineCount) {
|
|
9521
|
-
if (!
|
|
9635
|
+
if (!fs25.existsSync(filePath)) {
|
|
9522
9636
|
return `Log file not found: ${filePath}`;
|
|
9523
9637
|
}
|
|
9524
9638
|
try {
|
|
9525
|
-
const content =
|
|
9639
|
+
const content = fs25.readFileSync(filePath, "utf-8");
|
|
9526
9640
|
const lines = content.trim().split("\n");
|
|
9527
9641
|
return lines.slice(-lineCount).join("\n");
|
|
9528
9642
|
} catch (error2) {
|
|
@@ -9530,7 +9644,7 @@ function getLastLines(filePath, lineCount) {
|
|
|
9530
9644
|
}
|
|
9531
9645
|
}
|
|
9532
9646
|
function followLog(filePath) {
|
|
9533
|
-
if (!
|
|
9647
|
+
if (!fs25.existsSync(filePath)) {
|
|
9534
9648
|
console.log(`Log file not found: ${filePath}`);
|
|
9535
9649
|
console.log("The log file will be created when the first execution runs.");
|
|
9536
9650
|
return;
|
|
@@ -9550,13 +9664,13 @@ function logsCommand(program2) {
|
|
|
9550
9664
|
program2.command("logs").description("View night-watch log output").option("-n, --lines <count>", "Number of lines to show", "50").option("-f, --follow", "Follow log output (tail -f)").option("-t, --type <type>", "Log type to view (executor|reviewer|qa|audit|planner|all)", "all").action(async (options) => {
|
|
9551
9665
|
try {
|
|
9552
9666
|
const projectDir = process.cwd();
|
|
9553
|
-
const logDir =
|
|
9667
|
+
const logDir = path27.join(projectDir, LOG_DIR);
|
|
9554
9668
|
const lineCount = parseInt(options.lines || "50", 10);
|
|
9555
|
-
const executorLog =
|
|
9556
|
-
const reviewerLog =
|
|
9557
|
-
const qaLog =
|
|
9558
|
-
const auditLog =
|
|
9559
|
-
const plannerLog =
|
|
9669
|
+
const executorLog = path27.join(logDir, EXECUTOR_LOG_FILE);
|
|
9670
|
+
const reviewerLog = path27.join(logDir, REVIEWER_LOG_FILE);
|
|
9671
|
+
const qaLog = path27.join(logDir, `${QA_LOG_NAME}.log`);
|
|
9672
|
+
const auditLog = path27.join(logDir, `${AUDIT_LOG_NAME}.log`);
|
|
9673
|
+
const plannerLog = path27.join(logDir, `${PLANNER_LOG_NAME}.log`);
|
|
9560
9674
|
const logType = options.type?.toLowerCase() || "all";
|
|
9561
9675
|
const showExecutor = logType === "all" || logType === "run" || logType === "executor";
|
|
9562
9676
|
const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
|
|
@@ -9620,15 +9734,15 @@ function logsCommand(program2) {
|
|
|
9620
9734
|
|
|
9621
9735
|
// src/commands/prd.ts
|
|
9622
9736
|
init_dist();
|
|
9623
|
-
import * as
|
|
9624
|
-
import * as
|
|
9737
|
+
import * as fs26 from "fs";
|
|
9738
|
+
import * as path28 from "path";
|
|
9625
9739
|
import * as readline2 from "readline";
|
|
9626
9740
|
function slugify2(name) {
|
|
9627
9741
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
9628
9742
|
}
|
|
9629
9743
|
function getNextPrdNumber2(prdDir) {
|
|
9630
|
-
if (!
|
|
9631
|
-
const files =
|
|
9744
|
+
if (!fs26.existsSync(prdDir)) return 1;
|
|
9745
|
+
const files = fs26.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
9632
9746
|
const numbers = files.map((f) => {
|
|
9633
9747
|
const match = f.match(/^(\d+)-/);
|
|
9634
9748
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -9649,10 +9763,10 @@ function parseDependencies(content) {
|
|
|
9649
9763
|
}
|
|
9650
9764
|
function isClaimActive(claimPath, maxRuntime) {
|
|
9651
9765
|
try {
|
|
9652
|
-
if (!
|
|
9766
|
+
if (!fs26.existsSync(claimPath)) {
|
|
9653
9767
|
return { active: false };
|
|
9654
9768
|
}
|
|
9655
|
-
const content =
|
|
9769
|
+
const content = fs26.readFileSync(claimPath, "utf-8");
|
|
9656
9770
|
const claim = JSON.parse(content);
|
|
9657
9771
|
const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
|
|
9658
9772
|
if (age < maxRuntime) {
|
|
@@ -9668,9 +9782,9 @@ function prdCommand(program2) {
|
|
|
9668
9782
|
prd.command("create").description("Generate a new PRD markdown file from template").argument("<name>", "PRD name (used for title and filename)").option("-i, --interactive", "Prompt for complexity, dependencies, and phase count", false).option("-t, --template <path>", "Path to a custom template file").option("--deps <files>", "Comma-separated dependency filenames").option("--phases <count>", "Number of execution phases", "3").option("--no-number", "Skip auto-numbering prefix").action(async (name, options) => {
|
|
9669
9783
|
const projectDir = process.cwd();
|
|
9670
9784
|
const config = loadConfig(projectDir);
|
|
9671
|
-
const prdDir =
|
|
9672
|
-
if (!
|
|
9673
|
-
|
|
9785
|
+
const prdDir = path28.join(projectDir, config.prdDir);
|
|
9786
|
+
if (!fs26.existsSync(prdDir)) {
|
|
9787
|
+
fs26.mkdirSync(prdDir, { recursive: true });
|
|
9674
9788
|
}
|
|
9675
9789
|
let complexityScore = 5;
|
|
9676
9790
|
let dependsOn = [];
|
|
@@ -9729,20 +9843,20 @@ function prdCommand(program2) {
|
|
|
9729
9843
|
} else {
|
|
9730
9844
|
filename = `${slug}.md`;
|
|
9731
9845
|
}
|
|
9732
|
-
const filePath =
|
|
9733
|
-
if (
|
|
9846
|
+
const filePath = path28.join(prdDir, filename);
|
|
9847
|
+
if (fs26.existsSync(filePath)) {
|
|
9734
9848
|
error(`File already exists: ${filePath}`);
|
|
9735
9849
|
dim("Use a different name or remove the existing file.");
|
|
9736
9850
|
process.exit(1);
|
|
9737
9851
|
}
|
|
9738
9852
|
let customTemplate;
|
|
9739
9853
|
if (options.template) {
|
|
9740
|
-
const templatePath =
|
|
9741
|
-
if (!
|
|
9854
|
+
const templatePath = path28.resolve(options.template);
|
|
9855
|
+
if (!fs26.existsSync(templatePath)) {
|
|
9742
9856
|
error(`Template file not found: ${templatePath}`);
|
|
9743
9857
|
process.exit(1);
|
|
9744
9858
|
}
|
|
9745
|
-
customTemplate =
|
|
9859
|
+
customTemplate = fs26.readFileSync(templatePath, "utf-8");
|
|
9746
9860
|
}
|
|
9747
9861
|
const vars = {
|
|
9748
9862
|
title: name,
|
|
@@ -9753,7 +9867,7 @@ function prdCommand(program2) {
|
|
|
9753
9867
|
phaseCount
|
|
9754
9868
|
};
|
|
9755
9869
|
const content = renderPrdTemplate(vars, customTemplate);
|
|
9756
|
-
|
|
9870
|
+
fs26.writeFileSync(filePath, content, "utf-8");
|
|
9757
9871
|
header("PRD Created");
|
|
9758
9872
|
success(`Created: ${filePath}`);
|
|
9759
9873
|
info(`Title: ${name}`);
|
|
@@ -9765,15 +9879,15 @@ function prdCommand(program2) {
|
|
|
9765
9879
|
prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
|
|
9766
9880
|
const projectDir = process.cwd();
|
|
9767
9881
|
const config = loadConfig(projectDir);
|
|
9768
|
-
const absolutePrdDir =
|
|
9769
|
-
const doneDir =
|
|
9882
|
+
const absolutePrdDir = path28.join(projectDir, config.prdDir);
|
|
9883
|
+
const doneDir = path28.join(absolutePrdDir, "done");
|
|
9770
9884
|
const pending = [];
|
|
9771
|
-
if (
|
|
9772
|
-
const files =
|
|
9885
|
+
if (fs26.existsSync(absolutePrdDir)) {
|
|
9886
|
+
const files = fs26.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
|
|
9773
9887
|
for (const file of files) {
|
|
9774
|
-
const content =
|
|
9888
|
+
const content = fs26.readFileSync(path28.join(absolutePrdDir, file), "utf-8");
|
|
9775
9889
|
const deps = parseDependencies(content);
|
|
9776
|
-
const claimPath =
|
|
9890
|
+
const claimPath = path28.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
|
|
9777
9891
|
const claimStatus = isClaimActive(claimPath, config.maxRuntime);
|
|
9778
9892
|
pending.push({
|
|
9779
9893
|
name: file,
|
|
@@ -9784,10 +9898,10 @@ function prdCommand(program2) {
|
|
|
9784
9898
|
}
|
|
9785
9899
|
}
|
|
9786
9900
|
const done = [];
|
|
9787
|
-
if (
|
|
9788
|
-
const files =
|
|
9901
|
+
if (fs26.existsSync(doneDir)) {
|
|
9902
|
+
const files = fs26.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
9789
9903
|
for (const file of files) {
|
|
9790
|
-
const content =
|
|
9904
|
+
const content = fs26.readFileSync(path28.join(doneDir, file), "utf-8");
|
|
9791
9905
|
const deps = parseDependencies(content);
|
|
9792
9906
|
done.push({ name: file, dependencies: deps });
|
|
9793
9907
|
}
|
|
@@ -9826,7 +9940,7 @@ import blessed6 from "blessed";
|
|
|
9826
9940
|
// src/commands/dashboard/tab-status.ts
|
|
9827
9941
|
init_dist();
|
|
9828
9942
|
import blessed from "blessed";
|
|
9829
|
-
import * as
|
|
9943
|
+
import * as fs27 from "fs";
|
|
9830
9944
|
function sortPrdsByPriority(prds, priority) {
|
|
9831
9945
|
if (priority.length === 0) return prds;
|
|
9832
9946
|
const priorityMap = /* @__PURE__ */ new Map();
|
|
@@ -9922,7 +10036,7 @@ function renderLogPane(projectDir, logs) {
|
|
|
9922
10036
|
let newestMtime = 0;
|
|
9923
10037
|
for (const log of existingLogs) {
|
|
9924
10038
|
try {
|
|
9925
|
-
const stat =
|
|
10039
|
+
const stat = fs27.statSync(log.path);
|
|
9926
10040
|
if (stat.mtimeMs > newestMtime) {
|
|
9927
10041
|
newestMtime = stat.mtimeMs;
|
|
9928
10042
|
newestLog = log;
|
|
@@ -11577,8 +11691,8 @@ function createActionsTab() {
|
|
|
11577
11691
|
// src/commands/dashboard/tab-logs.ts
|
|
11578
11692
|
init_dist();
|
|
11579
11693
|
import blessed5 from "blessed";
|
|
11580
|
-
import * as
|
|
11581
|
-
import * as
|
|
11694
|
+
import * as fs28 from "fs";
|
|
11695
|
+
import * as path29 from "path";
|
|
11582
11696
|
var LOG_NAMES = ["executor", "reviewer"];
|
|
11583
11697
|
var LOG_LINES = 200;
|
|
11584
11698
|
function createLogsTab() {
|
|
@@ -11619,7 +11733,7 @@ function createLogsTab() {
|
|
|
11619
11733
|
let activeKeyHandlers = [];
|
|
11620
11734
|
let activeCtx = null;
|
|
11621
11735
|
function getLogPath(projectDir, logName) {
|
|
11622
|
-
return
|
|
11736
|
+
return path29.join(projectDir, "logs", `${logName}.log`);
|
|
11623
11737
|
}
|
|
11624
11738
|
function updateSelector() {
|
|
11625
11739
|
const tabs = LOG_NAMES.map((name, idx) => {
|
|
@@ -11633,7 +11747,7 @@ function createLogsTab() {
|
|
|
11633
11747
|
function loadLog(ctx) {
|
|
11634
11748
|
const logName = LOG_NAMES[selectedLogIndex];
|
|
11635
11749
|
const logPath = getLogPath(ctx.projectDir, logName);
|
|
11636
|
-
if (!
|
|
11750
|
+
if (!fs28.existsSync(logPath)) {
|
|
11637
11751
|
logContent.setContent(
|
|
11638
11752
|
`{yellow-fg}No ${logName}.log file found{/yellow-fg}
|
|
11639
11753
|
|
|
@@ -11643,7 +11757,7 @@ Log will appear here once the ${logName} runs.`
|
|
|
11643
11757
|
return;
|
|
11644
11758
|
}
|
|
11645
11759
|
try {
|
|
11646
|
-
const stat =
|
|
11760
|
+
const stat = fs28.statSync(logPath);
|
|
11647
11761
|
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
11648
11762
|
logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
|
|
11649
11763
|
} catch {
|
|
@@ -12149,13 +12263,13 @@ function doctorCommand(program2) {
|
|
|
12149
12263
|
|
|
12150
12264
|
// src/commands/serve.ts
|
|
12151
12265
|
init_dist();
|
|
12152
|
-
import * as
|
|
12266
|
+
import * as fs33 from "fs";
|
|
12153
12267
|
|
|
12154
12268
|
// ../server/dist/index.js
|
|
12155
12269
|
init_dist();
|
|
12156
|
-
import * as
|
|
12157
|
-
import * as
|
|
12158
|
-
import { dirname as
|
|
12270
|
+
import * as fs32 from "fs";
|
|
12271
|
+
import * as path35 from "path";
|
|
12272
|
+
import { dirname as dirname8 } from "path";
|
|
12159
12273
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12160
12274
|
import cors from "cors";
|
|
12161
12275
|
import express from "express";
|
|
@@ -12239,8 +12353,8 @@ function setupGracefulShutdown(server, beforeClose) {
|
|
|
12239
12353
|
|
|
12240
12354
|
// ../server/dist/middleware/project-resolver.middleware.js
|
|
12241
12355
|
init_dist();
|
|
12242
|
-
import * as
|
|
12243
|
-
import * as
|
|
12356
|
+
import * as fs29 from "fs";
|
|
12357
|
+
import * as path30 from "path";
|
|
12244
12358
|
function resolveProject(req, res, next) {
|
|
12245
12359
|
const projectId = req.params.projectId;
|
|
12246
12360
|
const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
|
|
@@ -12250,7 +12364,7 @@ function resolveProject(req, res, next) {
|
|
|
12250
12364
|
res.status(404).json({ error: `Project not found: ${decodedId}` });
|
|
12251
12365
|
return;
|
|
12252
12366
|
}
|
|
12253
|
-
if (!
|
|
12367
|
+
if (!fs29.existsSync(entry.path) || !fs29.existsSync(path30.join(entry.path, CONFIG_FILE_NAME))) {
|
|
12254
12368
|
res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
|
|
12255
12369
|
return;
|
|
12256
12370
|
}
|
|
@@ -12295,8 +12409,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
|
|
|
12295
12409
|
|
|
12296
12410
|
// ../server/dist/routes/action.routes.js
|
|
12297
12411
|
init_dist();
|
|
12298
|
-
import * as
|
|
12299
|
-
import * as
|
|
12412
|
+
import * as fs30 from "fs";
|
|
12413
|
+
import * as path31 from "path";
|
|
12300
12414
|
import { execSync as execSync5, spawn as spawn5 } from "child_process";
|
|
12301
12415
|
import { Router } from "express";
|
|
12302
12416
|
|
|
@@ -12334,17 +12448,17 @@ function getBoardProvider(config, projectDir) {
|
|
|
12334
12448
|
function cleanOrphanedClaims(dir) {
|
|
12335
12449
|
let entries;
|
|
12336
12450
|
try {
|
|
12337
|
-
entries =
|
|
12451
|
+
entries = fs30.readdirSync(dir, { withFileTypes: true });
|
|
12338
12452
|
} catch {
|
|
12339
12453
|
return;
|
|
12340
12454
|
}
|
|
12341
12455
|
for (const entry of entries) {
|
|
12342
|
-
const fullPath =
|
|
12456
|
+
const fullPath = path31.join(dir, entry.name);
|
|
12343
12457
|
if (entry.isDirectory() && entry.name !== "done") {
|
|
12344
12458
|
cleanOrphanedClaims(fullPath);
|
|
12345
12459
|
} else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
|
|
12346
12460
|
try {
|
|
12347
|
-
|
|
12461
|
+
fs30.unlinkSync(fullPath);
|
|
12348
12462
|
} catch {
|
|
12349
12463
|
}
|
|
12350
12464
|
}
|
|
@@ -12501,19 +12615,19 @@ function createActionRouteHandlers(ctx) {
|
|
|
12501
12615
|
res.status(400).json({ error: "Invalid PRD name" });
|
|
12502
12616
|
return;
|
|
12503
12617
|
}
|
|
12504
|
-
const prdDir =
|
|
12618
|
+
const prdDir = path31.join(projectDir, config.prdDir);
|
|
12505
12619
|
const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
|
|
12506
|
-
const pendingPath =
|
|
12507
|
-
const donePath =
|
|
12508
|
-
if (
|
|
12620
|
+
const pendingPath = path31.join(prdDir, normalized);
|
|
12621
|
+
const donePath = path31.join(prdDir, "done", normalized);
|
|
12622
|
+
if (fs30.existsSync(pendingPath)) {
|
|
12509
12623
|
res.json({ message: `"${normalized}" is already pending` });
|
|
12510
12624
|
return;
|
|
12511
12625
|
}
|
|
12512
|
-
if (!
|
|
12626
|
+
if (!fs30.existsSync(donePath)) {
|
|
12513
12627
|
res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
|
|
12514
12628
|
return;
|
|
12515
12629
|
}
|
|
12516
|
-
|
|
12630
|
+
fs30.renameSync(donePath, pendingPath);
|
|
12517
12631
|
res.json({ message: `Moved "${normalized}" back to pending` });
|
|
12518
12632
|
} catch (error2) {
|
|
12519
12633
|
res.status(500).json({
|
|
@@ -12531,11 +12645,11 @@ function createActionRouteHandlers(ctx) {
|
|
|
12531
12645
|
res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
|
|
12532
12646
|
return;
|
|
12533
12647
|
}
|
|
12534
|
-
if (
|
|
12535
|
-
|
|
12648
|
+
if (fs30.existsSync(lockPath)) {
|
|
12649
|
+
fs30.unlinkSync(lockPath);
|
|
12536
12650
|
}
|
|
12537
|
-
const prdDir =
|
|
12538
|
-
if (
|
|
12651
|
+
const prdDir = path31.join(projectDir, config.prdDir);
|
|
12652
|
+
if (fs30.existsSync(prdDir)) {
|
|
12539
12653
|
cleanOrphanedClaims(prdDir);
|
|
12540
12654
|
}
|
|
12541
12655
|
broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
|
|
@@ -13194,6 +13308,26 @@ function createConfigRoutes(deps) {
|
|
|
13194
13308
|
});
|
|
13195
13309
|
return router;
|
|
13196
13310
|
}
|
|
13311
|
+
function createGlobalNotificationsRoutes() {
|
|
13312
|
+
const router = Router3();
|
|
13313
|
+
router.get("/", (_req, res) => {
|
|
13314
|
+
res.json(loadGlobalNotificationsConfig());
|
|
13315
|
+
});
|
|
13316
|
+
router.put("/", (req, res) => {
|
|
13317
|
+
const { webhook } = req.body;
|
|
13318
|
+
if (webhook !== null && webhook !== void 0) {
|
|
13319
|
+
const issues = validateWebhook(webhook);
|
|
13320
|
+
if (issues.length > 0) {
|
|
13321
|
+
res.status(400).json({ error: `Invalid webhook: ${issues.join(", ")}` });
|
|
13322
|
+
return;
|
|
13323
|
+
}
|
|
13324
|
+
}
|
|
13325
|
+
const config = { webhook: webhook ?? null };
|
|
13326
|
+
saveGlobalNotificationsConfig(config);
|
|
13327
|
+
res.json(loadGlobalNotificationsConfig());
|
|
13328
|
+
});
|
|
13329
|
+
return router;
|
|
13330
|
+
}
|
|
13197
13331
|
function createProjectConfigRoutes() {
|
|
13198
13332
|
const router = Router3({ mergeParams: true });
|
|
13199
13333
|
router.get("/config", (req, res) => {
|
|
@@ -13227,8 +13361,8 @@ function createProjectConfigRoutes() {
|
|
|
13227
13361
|
|
|
13228
13362
|
// ../server/dist/routes/doctor.routes.js
|
|
13229
13363
|
init_dist();
|
|
13230
|
-
import * as
|
|
13231
|
-
import * as
|
|
13364
|
+
import * as fs31 from "fs";
|
|
13365
|
+
import * as path32 from "path";
|
|
13232
13366
|
import { execSync as execSync6 } from "child_process";
|
|
13233
13367
|
import { Router as Router4 } from "express";
|
|
13234
13368
|
function runDoctorChecks(projectDir, config) {
|
|
@@ -13261,7 +13395,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
13261
13395
|
});
|
|
13262
13396
|
}
|
|
13263
13397
|
try {
|
|
13264
|
-
const projectName =
|
|
13398
|
+
const projectName = path32.basename(projectDir);
|
|
13265
13399
|
const marker = generateMarker(projectName);
|
|
13266
13400
|
const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
|
|
13267
13401
|
if (crontabEntries.length > 0) {
|
|
@@ -13284,8 +13418,8 @@ function runDoctorChecks(projectDir, config) {
|
|
|
13284
13418
|
detail: "Failed to check crontab"
|
|
13285
13419
|
});
|
|
13286
13420
|
}
|
|
13287
|
-
const configPath =
|
|
13288
|
-
if (
|
|
13421
|
+
const configPath = path32.join(projectDir, CONFIG_FILE_NAME);
|
|
13422
|
+
if (fs31.existsSync(configPath)) {
|
|
13289
13423
|
checks.push({ name: "config", status: "pass", detail: "Config file exists" });
|
|
13290
13424
|
} else {
|
|
13291
13425
|
checks.push({
|
|
@@ -13294,9 +13428,9 @@ function runDoctorChecks(projectDir, config) {
|
|
|
13294
13428
|
detail: "Config file not found (using defaults)"
|
|
13295
13429
|
});
|
|
13296
13430
|
}
|
|
13297
|
-
const prdDir =
|
|
13298
|
-
if (
|
|
13299
|
-
const prds =
|
|
13431
|
+
const prdDir = path32.join(projectDir, config.prdDir);
|
|
13432
|
+
if (fs31.existsSync(prdDir)) {
|
|
13433
|
+
const prds = fs31.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
13300
13434
|
checks.push({
|
|
13301
13435
|
name: "prdDir",
|
|
13302
13436
|
status: "pass",
|
|
@@ -13339,7 +13473,7 @@ function createProjectDoctorRoutes() {
|
|
|
13339
13473
|
|
|
13340
13474
|
// ../server/dist/routes/log.routes.js
|
|
13341
13475
|
init_dist();
|
|
13342
|
-
import * as
|
|
13476
|
+
import * as path33 from "path";
|
|
13343
13477
|
import { Router as Router5 } from "express";
|
|
13344
13478
|
function createLogRoutes(deps) {
|
|
13345
13479
|
const { projectDir } = deps;
|
|
@@ -13358,7 +13492,7 @@ function createLogRoutes(deps) {
|
|
|
13358
13492
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
13359
13493
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
13360
13494
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
13361
|
-
const logPath =
|
|
13495
|
+
const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
13362
13496
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
13363
13497
|
res.json({ name, lines: logLines });
|
|
13364
13498
|
} catch (error2) {
|
|
@@ -13384,7 +13518,7 @@ function createProjectLogRoutes() {
|
|
|
13384
13518
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
13385
13519
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
13386
13520
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
13387
|
-
const logPath =
|
|
13521
|
+
const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
13388
13522
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
13389
13523
|
res.json({ name, lines: logLines });
|
|
13390
13524
|
} catch (error2) {
|
|
@@ -13419,7 +13553,7 @@ function createProjectPrdRoutes() {
|
|
|
13419
13553
|
|
|
13420
13554
|
// ../server/dist/routes/roadmap.routes.js
|
|
13421
13555
|
init_dist();
|
|
13422
|
-
import * as
|
|
13556
|
+
import * as path34 from "path";
|
|
13423
13557
|
import { Router as Router7 } from "express";
|
|
13424
13558
|
function createRoadmapRouteHandlers(ctx) {
|
|
13425
13559
|
const router = Router7({ mergeParams: true });
|
|
@@ -13429,7 +13563,7 @@ function createRoadmapRouteHandlers(ctx) {
|
|
|
13429
13563
|
const config = ctx.getConfig(req);
|
|
13430
13564
|
const projectDir = ctx.getProjectDir(req);
|
|
13431
13565
|
const status = getRoadmapStatus(projectDir, config);
|
|
13432
|
-
const prdDir =
|
|
13566
|
+
const prdDir = path34.join(projectDir, config.prdDir);
|
|
13433
13567
|
const state = loadRoadmapState(prdDir);
|
|
13434
13568
|
res.json({
|
|
13435
13569
|
...status,
|
|
@@ -13761,26 +13895,26 @@ function createQueueRoutes(deps) {
|
|
|
13761
13895
|
|
|
13762
13896
|
// ../server/dist/index.js
|
|
13763
13897
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
13764
|
-
var __dirname3 =
|
|
13898
|
+
var __dirname3 = dirname8(__filename2);
|
|
13765
13899
|
function resolveWebDistPath() {
|
|
13766
|
-
const bundled =
|
|
13767
|
-
if (
|
|
13900
|
+
const bundled = path35.join(__dirname3, "web");
|
|
13901
|
+
if (fs32.existsSync(path35.join(bundled, "index.html")))
|
|
13768
13902
|
return bundled;
|
|
13769
13903
|
let d = __dirname3;
|
|
13770
13904
|
for (let i = 0; i < 8; i++) {
|
|
13771
|
-
if (
|
|
13772
|
-
const dev =
|
|
13773
|
-
if (
|
|
13905
|
+
if (fs32.existsSync(path35.join(d, "turbo.json"))) {
|
|
13906
|
+
const dev = path35.join(d, "web/dist");
|
|
13907
|
+
if (fs32.existsSync(path35.join(dev, "index.html")))
|
|
13774
13908
|
return dev;
|
|
13775
13909
|
break;
|
|
13776
13910
|
}
|
|
13777
|
-
d =
|
|
13911
|
+
d = dirname8(d);
|
|
13778
13912
|
}
|
|
13779
13913
|
return bundled;
|
|
13780
13914
|
}
|
|
13781
13915
|
function setupStaticFiles(app) {
|
|
13782
13916
|
const webDistPath = resolveWebDistPath();
|
|
13783
|
-
if (
|
|
13917
|
+
if (fs32.existsSync(webDistPath)) {
|
|
13784
13918
|
app.use(express.static(webDistPath));
|
|
13785
13919
|
}
|
|
13786
13920
|
app.use((req, res, next) => {
|
|
@@ -13788,8 +13922,8 @@ function setupStaticFiles(app) {
|
|
|
13788
13922
|
next();
|
|
13789
13923
|
return;
|
|
13790
13924
|
}
|
|
13791
|
-
const indexPath =
|
|
13792
|
-
if (
|
|
13925
|
+
const indexPath = path35.resolve(webDistPath, "index.html");
|
|
13926
|
+
if (fs32.existsSync(indexPath)) {
|
|
13793
13927
|
res.sendFile(indexPath, (err) => {
|
|
13794
13928
|
if (err)
|
|
13795
13929
|
next();
|
|
@@ -13827,6 +13961,7 @@ function createApp(projectDir) {
|
|
|
13827
13961
|
app.use("/api/logs", createLogRoutes({ projectDir }));
|
|
13828
13962
|
app.use("/api/doctor", createDoctorRoutes({ projectDir, getConfig: () => config }));
|
|
13829
13963
|
app.use("/api/queue", createQueueRoutes({ getConfig: () => config }));
|
|
13964
|
+
app.use("/api/global-notifications", createGlobalNotificationsRoutes());
|
|
13830
13965
|
app.get("/api/prs", async (_req, res) => {
|
|
13831
13966
|
try {
|
|
13832
13967
|
res.json(await collectPrInfo(projectDir, config.branchPatterns));
|
|
@@ -13899,13 +14034,14 @@ function createGlobalApp() {
|
|
|
13899
14034
|
}
|
|
13900
14035
|
});
|
|
13901
14036
|
app.use("/api/queue", createGlobalQueueRoutes());
|
|
14037
|
+
app.use("/api/global-notifications", createGlobalNotificationsRoutes());
|
|
13902
14038
|
app.use("/api/projects/:projectId", resolveProject, createProjectRouter());
|
|
13903
14039
|
setupStaticFiles(app);
|
|
13904
14040
|
app.use(errorHandler);
|
|
13905
14041
|
return app;
|
|
13906
14042
|
}
|
|
13907
14043
|
function bootContainer() {
|
|
13908
|
-
initContainer(
|
|
14044
|
+
initContainer(path35.dirname(getDbPath()));
|
|
13909
14045
|
}
|
|
13910
14046
|
function startServer(projectDir, port) {
|
|
13911
14047
|
bootContainer();
|
|
@@ -13958,8 +14094,8 @@ function isProcessRunning2(pid) {
|
|
|
13958
14094
|
}
|
|
13959
14095
|
function readPid(lockPath) {
|
|
13960
14096
|
try {
|
|
13961
|
-
if (!
|
|
13962
|
-
const raw =
|
|
14097
|
+
if (!fs33.existsSync(lockPath)) return null;
|
|
14098
|
+
const raw = fs33.readFileSync(lockPath, "utf-8").trim();
|
|
13963
14099
|
const pid = parseInt(raw, 10);
|
|
13964
14100
|
return Number.isFinite(pid) ? pid : null;
|
|
13965
14101
|
} catch {
|
|
@@ -13971,10 +14107,10 @@ function acquireServeLock(mode, port) {
|
|
|
13971
14107
|
let stalePidCleaned;
|
|
13972
14108
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13973
14109
|
try {
|
|
13974
|
-
const fd =
|
|
13975
|
-
|
|
14110
|
+
const fd = fs33.openSync(lockPath, "wx");
|
|
14111
|
+
fs33.writeFileSync(fd, `${process.pid}
|
|
13976
14112
|
`);
|
|
13977
|
-
|
|
14113
|
+
fs33.closeSync(fd);
|
|
13978
14114
|
return { acquired: true, lockPath, stalePidCleaned };
|
|
13979
14115
|
} catch (error2) {
|
|
13980
14116
|
const err = error2;
|
|
@@ -13995,7 +14131,7 @@ function acquireServeLock(mode, port) {
|
|
|
13995
14131
|
};
|
|
13996
14132
|
}
|
|
13997
14133
|
try {
|
|
13998
|
-
|
|
14134
|
+
fs33.unlinkSync(lockPath);
|
|
13999
14135
|
if (existingPid) {
|
|
14000
14136
|
stalePidCleaned = existingPid;
|
|
14001
14137
|
}
|
|
@@ -14018,10 +14154,10 @@ function acquireServeLock(mode, port) {
|
|
|
14018
14154
|
}
|
|
14019
14155
|
function releaseServeLock(lockPath) {
|
|
14020
14156
|
try {
|
|
14021
|
-
if (!
|
|
14157
|
+
if (!fs33.existsSync(lockPath)) return;
|
|
14022
14158
|
const lockPid = readPid(lockPath);
|
|
14023
14159
|
if (lockPid !== null && lockPid !== process.pid) return;
|
|
14024
|
-
|
|
14160
|
+
fs33.unlinkSync(lockPath);
|
|
14025
14161
|
} catch {
|
|
14026
14162
|
}
|
|
14027
14163
|
}
|
|
@@ -14117,14 +14253,14 @@ function historyCommand(program2) {
|
|
|
14117
14253
|
// src/commands/update.ts
|
|
14118
14254
|
init_dist();
|
|
14119
14255
|
import { spawnSync } from "child_process";
|
|
14120
|
-
import * as
|
|
14121
|
-
import * as
|
|
14256
|
+
import * as fs34 from "fs";
|
|
14257
|
+
import * as path36 from "path";
|
|
14122
14258
|
var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
|
|
14123
14259
|
function parseProjectDirs(projects, cwd) {
|
|
14124
14260
|
if (!projects || projects.trim().length === 0) {
|
|
14125
14261
|
return [cwd];
|
|
14126
14262
|
}
|
|
14127
|
-
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) =>
|
|
14263
|
+
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path36.resolve(cwd, entry));
|
|
14128
14264
|
return Array.from(new Set(dirs));
|
|
14129
14265
|
}
|
|
14130
14266
|
function shouldInstallGlobal(options) {
|
|
@@ -14166,7 +14302,7 @@ function updateCommand(program2) {
|
|
|
14166
14302
|
}
|
|
14167
14303
|
const nightWatchBin = resolveNightWatchBin();
|
|
14168
14304
|
for (const projectDir of projectDirs) {
|
|
14169
|
-
if (!
|
|
14305
|
+
if (!fs34.existsSync(projectDir) || !fs34.statSync(projectDir).isDirectory()) {
|
|
14170
14306
|
warn(`Skipping invalid project directory: ${projectDir}`);
|
|
14171
14307
|
continue;
|
|
14172
14308
|
}
|
|
@@ -14210,8 +14346,8 @@ function prdStateCommand(program2) {
|
|
|
14210
14346
|
|
|
14211
14347
|
// src/commands/retry.ts
|
|
14212
14348
|
init_dist();
|
|
14213
|
-
import * as
|
|
14214
|
-
import * as
|
|
14349
|
+
import * as fs35 from "fs";
|
|
14350
|
+
import * as path37 from "path";
|
|
14215
14351
|
function normalizePrdName(name) {
|
|
14216
14352
|
if (!name.endsWith(".md")) {
|
|
14217
14353
|
return `${name}.md`;
|
|
@@ -14219,26 +14355,26 @@ function normalizePrdName(name) {
|
|
|
14219
14355
|
return name;
|
|
14220
14356
|
}
|
|
14221
14357
|
function getDonePrds(doneDir) {
|
|
14222
|
-
if (!
|
|
14358
|
+
if (!fs35.existsSync(doneDir)) {
|
|
14223
14359
|
return [];
|
|
14224
14360
|
}
|
|
14225
|
-
return
|
|
14361
|
+
return fs35.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
14226
14362
|
}
|
|
14227
14363
|
function retryCommand(program2) {
|
|
14228
14364
|
program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
|
|
14229
14365
|
const projectDir = process.cwd();
|
|
14230
14366
|
const config = loadConfig(projectDir);
|
|
14231
|
-
const prdDir =
|
|
14232
|
-
const doneDir =
|
|
14367
|
+
const prdDir = path37.join(projectDir, config.prdDir);
|
|
14368
|
+
const doneDir = path37.join(prdDir, "done");
|
|
14233
14369
|
const normalizedPrdName = normalizePrdName(prdName);
|
|
14234
|
-
const pendingPath =
|
|
14235
|
-
if (
|
|
14370
|
+
const pendingPath = path37.join(prdDir, normalizedPrdName);
|
|
14371
|
+
if (fs35.existsSync(pendingPath)) {
|
|
14236
14372
|
info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
|
|
14237
14373
|
return;
|
|
14238
14374
|
}
|
|
14239
|
-
const donePath =
|
|
14240
|
-
if (
|
|
14241
|
-
|
|
14375
|
+
const donePath = path37.join(doneDir, normalizedPrdName);
|
|
14376
|
+
if (fs35.existsSync(donePath)) {
|
|
14377
|
+
fs35.renameSync(donePath, pendingPath);
|
|
14242
14378
|
success(`Moved "${normalizedPrdName}" back to pending.`);
|
|
14243
14379
|
dim(`From: ${donePath}`);
|
|
14244
14380
|
dim(`To: ${pendingPath}`);
|
|
@@ -14490,7 +14626,7 @@ function prdsCommand(program2) {
|
|
|
14490
14626
|
|
|
14491
14627
|
// src/commands/cancel.ts
|
|
14492
14628
|
init_dist();
|
|
14493
|
-
import * as
|
|
14629
|
+
import * as fs36 from "fs";
|
|
14494
14630
|
import * as readline3 from "readline";
|
|
14495
14631
|
function getLockFilePaths2(projectDir) {
|
|
14496
14632
|
const runtimeKey = projectRuntimeKey(projectDir);
|
|
@@ -14537,7 +14673,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
14537
14673
|
const pid = lockStatus.pid;
|
|
14538
14674
|
if (!lockStatus.running) {
|
|
14539
14675
|
try {
|
|
14540
|
-
|
|
14676
|
+
fs36.unlinkSync(lockPath);
|
|
14541
14677
|
return {
|
|
14542
14678
|
success: true,
|
|
14543
14679
|
message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
|
|
@@ -14575,7 +14711,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
14575
14711
|
await sleep2(3e3);
|
|
14576
14712
|
if (!isProcessRunning3(pid)) {
|
|
14577
14713
|
try {
|
|
14578
|
-
|
|
14714
|
+
fs36.unlinkSync(lockPath);
|
|
14579
14715
|
} catch {
|
|
14580
14716
|
}
|
|
14581
14717
|
return {
|
|
@@ -14610,7 +14746,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
14610
14746
|
await sleep2(500);
|
|
14611
14747
|
if (!isProcessRunning3(pid)) {
|
|
14612
14748
|
try {
|
|
14613
|
-
|
|
14749
|
+
fs36.unlinkSync(lockPath);
|
|
14614
14750
|
} catch {
|
|
14615
14751
|
}
|
|
14616
14752
|
return {
|
|
@@ -14671,31 +14807,31 @@ function cancelCommand(program2) {
|
|
|
14671
14807
|
|
|
14672
14808
|
// src/commands/slice.ts
|
|
14673
14809
|
init_dist();
|
|
14674
|
-
import * as
|
|
14675
|
-
import * as
|
|
14810
|
+
import * as fs37 from "fs";
|
|
14811
|
+
import * as path38 from "path";
|
|
14676
14812
|
function plannerLockPath2(projectDir) {
|
|
14677
14813
|
return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
|
|
14678
14814
|
}
|
|
14679
14815
|
function acquirePlannerLock(projectDir) {
|
|
14680
14816
|
const lockFile = plannerLockPath2(projectDir);
|
|
14681
|
-
if (
|
|
14682
|
-
const pidRaw =
|
|
14817
|
+
if (fs37.existsSync(lockFile)) {
|
|
14818
|
+
const pidRaw = fs37.readFileSync(lockFile, "utf-8").trim();
|
|
14683
14819
|
const pid = parseInt(pidRaw, 10);
|
|
14684
14820
|
if (!Number.isNaN(pid) && isProcessRunning(pid)) {
|
|
14685
14821
|
return { acquired: false, lockFile, pid };
|
|
14686
14822
|
}
|
|
14687
14823
|
try {
|
|
14688
|
-
|
|
14824
|
+
fs37.unlinkSync(lockFile);
|
|
14689
14825
|
} catch {
|
|
14690
14826
|
}
|
|
14691
14827
|
}
|
|
14692
|
-
|
|
14828
|
+
fs37.writeFileSync(lockFile, String(process.pid));
|
|
14693
14829
|
return { acquired: true, lockFile };
|
|
14694
14830
|
}
|
|
14695
14831
|
function releasePlannerLock(lockFile) {
|
|
14696
14832
|
try {
|
|
14697
|
-
if (
|
|
14698
|
-
|
|
14833
|
+
if (fs37.existsSync(lockFile)) {
|
|
14834
|
+
fs37.unlinkSync(lockFile);
|
|
14699
14835
|
}
|
|
14700
14836
|
} catch {
|
|
14701
14837
|
}
|
|
@@ -14704,12 +14840,12 @@ function resolvePlannerIssueColumn(config) {
|
|
|
14704
14840
|
return config.roadmapScanner.issueColumn === "Ready" ? "Ready" : "Draft";
|
|
14705
14841
|
}
|
|
14706
14842
|
function buildPlannerIssueBody(projectDir, config, result) {
|
|
14707
|
-
const relativePrdPath =
|
|
14708
|
-
const absolutePrdPath =
|
|
14843
|
+
const relativePrdPath = path38.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
|
|
14844
|
+
const absolutePrdPath = path38.join(projectDir, config.prdDir, result.file ?? "");
|
|
14709
14845
|
const sourceItem = result.item;
|
|
14710
14846
|
let prdContent;
|
|
14711
14847
|
try {
|
|
14712
|
-
prdContent =
|
|
14848
|
+
prdContent = fs37.readFileSync(absolutePrdPath, "utf-8");
|
|
14713
14849
|
} catch {
|
|
14714
14850
|
prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
|
|
14715
14851
|
}
|
|
@@ -14885,7 +15021,7 @@ function sliceCommand(program2) {
|
|
|
14885
15021
|
if (!options.dryRun) {
|
|
14886
15022
|
await sendNotifications(config, {
|
|
14887
15023
|
event: "run_started",
|
|
14888
|
-
projectName:
|
|
15024
|
+
projectName: path38.basename(projectDir),
|
|
14889
15025
|
exitCode: 0,
|
|
14890
15026
|
provider: config.provider
|
|
14891
15027
|
});
|
|
@@ -14920,7 +15056,7 @@ function sliceCommand(program2) {
|
|
|
14920
15056
|
if (!options.dryRun && result.sliced) {
|
|
14921
15057
|
await sendNotifications(config, {
|
|
14922
15058
|
event: "run_succeeded",
|
|
14923
|
-
projectName:
|
|
15059
|
+
projectName: path38.basename(projectDir),
|
|
14924
15060
|
exitCode,
|
|
14925
15061
|
provider: config.provider,
|
|
14926
15062
|
prTitle: result.item?.title
|
|
@@ -14928,7 +15064,7 @@ function sliceCommand(program2) {
|
|
|
14928
15064
|
} else if (!options.dryRun && !nothingPending) {
|
|
14929
15065
|
await sendNotifications(config, {
|
|
14930
15066
|
event: "run_failed",
|
|
14931
|
-
projectName:
|
|
15067
|
+
projectName: path38.basename(projectDir),
|
|
14932
15068
|
exitCode,
|
|
14933
15069
|
provider: config.provider
|
|
14934
15070
|
});
|
|
@@ -14944,21 +15080,21 @@ function sliceCommand(program2) {
|
|
|
14944
15080
|
|
|
14945
15081
|
// src/commands/state.ts
|
|
14946
15082
|
init_dist();
|
|
14947
|
-
import * as
|
|
14948
|
-
import * as
|
|
15083
|
+
import * as os9 from "os";
|
|
15084
|
+
import * as path39 from "path";
|
|
14949
15085
|
import chalk5 from "chalk";
|
|
14950
15086
|
import { Command } from "commander";
|
|
14951
15087
|
function createStateCommand() {
|
|
14952
15088
|
const state = new Command("state");
|
|
14953
15089
|
state.description("Manage Night Watch state");
|
|
14954
15090
|
state.command("migrate").description("Migrate legacy JSON state files to SQLite").option("--dry-run", "Show what would be migrated without making changes").action((opts) => {
|
|
14955
|
-
const nightWatchHome = process.env.NIGHT_WATCH_HOME ||
|
|
15091
|
+
const nightWatchHome = process.env.NIGHT_WATCH_HOME || path39.join(os9.homedir(), GLOBAL_CONFIG_DIR);
|
|
14956
15092
|
if (opts.dryRun) {
|
|
14957
15093
|
console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
|
|
14958
15094
|
console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
|
|
14959
|
-
console.log(` ${
|
|
14960
|
-
console.log(` ${
|
|
14961
|
-
console.log(` ${
|
|
15095
|
+
console.log(` ${path39.join(nightWatchHome, "projects.json")}`);
|
|
15096
|
+
console.log(` ${path39.join(nightWatchHome, "history.json")}`);
|
|
15097
|
+
console.log(` ${path39.join(nightWatchHome, "prd-states.json")}`);
|
|
14962
15098
|
console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
|
|
14963
15099
|
console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
|
|
14964
15100
|
return;
|
|
@@ -14996,8 +15132,8 @@ function createStateCommand() {
|
|
|
14996
15132
|
init_dist();
|
|
14997
15133
|
init_dist();
|
|
14998
15134
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
14999
|
-
import * as
|
|
15000
|
-
import * as
|
|
15135
|
+
import * as fs38 from "fs";
|
|
15136
|
+
import * as path40 from "path";
|
|
15001
15137
|
import * as readline4 from "readline";
|
|
15002
15138
|
import chalk6 from "chalk";
|
|
15003
15139
|
async function run(fn) {
|
|
@@ -15019,7 +15155,7 @@ function getProvider(config, cwd) {
|
|
|
15019
15155
|
return createBoardProvider(bp, cwd);
|
|
15020
15156
|
}
|
|
15021
15157
|
function defaultBoardTitle(cwd) {
|
|
15022
|
-
return `${
|
|
15158
|
+
return `${path40.basename(cwd)} Night Watch`;
|
|
15023
15159
|
}
|
|
15024
15160
|
async function ensureBoardConfigured(config, cwd, provider, options) {
|
|
15025
15161
|
if (config.boardProvider?.projectNumber) {
|
|
@@ -15218,11 +15354,11 @@ function boardCommand(program2) {
|
|
|
15218
15354
|
let body = options.body ?? "";
|
|
15219
15355
|
if (options.bodyFile) {
|
|
15220
15356
|
const filePath = options.bodyFile;
|
|
15221
|
-
if (!
|
|
15357
|
+
if (!fs38.existsSync(filePath)) {
|
|
15222
15358
|
console.error(`File not found: ${filePath}`);
|
|
15223
15359
|
process.exit(1);
|
|
15224
15360
|
}
|
|
15225
|
-
body =
|
|
15361
|
+
body = fs38.readFileSync(filePath, "utf-8");
|
|
15226
15362
|
}
|
|
15227
15363
|
const labels = [];
|
|
15228
15364
|
if (options.label) {
|
|
@@ -15250,6 +15386,25 @@ function boardCommand(program2) {
|
|
|
15250
15386
|
}
|
|
15251
15387
|
})
|
|
15252
15388
|
);
|
|
15389
|
+
board.command("add-issue").description("Add an existing GitHub issue to the board").argument("<number>", "Issue number").option("--column <name>", "Target column (default: Ready)", "Ready").action(
|
|
15390
|
+
async (number, options) => run(async () => {
|
|
15391
|
+
const cwd = process.cwd();
|
|
15392
|
+
const config = loadConfig(cwd);
|
|
15393
|
+
const provider = getProvider(config, cwd);
|
|
15394
|
+
await ensureBoardConfigured(config, cwd, provider);
|
|
15395
|
+
if (!BOARD_COLUMNS.includes(options.column)) {
|
|
15396
|
+
console.error(
|
|
15397
|
+
`Invalid column "${options.column}". Valid columns: ${BOARD_COLUMNS.join(", ")}`
|
|
15398
|
+
);
|
|
15399
|
+
process.exit(1);
|
|
15400
|
+
}
|
|
15401
|
+
const issue = await provider.addIssue(
|
|
15402
|
+
parseInt(number, 10),
|
|
15403
|
+
options.column
|
|
15404
|
+
);
|
|
15405
|
+
success(`Added issue #${issue.number} "${issue.title}" to ${options.column}`);
|
|
15406
|
+
})
|
|
15407
|
+
);
|
|
15253
15408
|
board.command("status").description("Show the current state of all issues grouped by column").option("--json", "Output raw JSON").option("--group-by <field>", "Group by: priority, category, or column (default: column)").action(
|
|
15254
15409
|
async (options) => run(async () => {
|
|
15255
15410
|
const cwd = process.cwd();
|
|
@@ -15444,12 +15599,12 @@ function boardCommand(program2) {
|
|
|
15444
15599
|
const config = loadConfig(cwd);
|
|
15445
15600
|
const provider = getProvider(config, cwd);
|
|
15446
15601
|
await ensureBoardConfigured(config, cwd, provider);
|
|
15447
|
-
const roadmapPath = options.roadmap ??
|
|
15448
|
-
if (!
|
|
15602
|
+
const roadmapPath = options.roadmap ?? path40.join(cwd, "ROADMAP.md");
|
|
15603
|
+
if (!fs38.existsSync(roadmapPath)) {
|
|
15449
15604
|
console.error(`Roadmap file not found: ${roadmapPath}`);
|
|
15450
15605
|
process.exit(1);
|
|
15451
15606
|
}
|
|
15452
|
-
const roadmapContent =
|
|
15607
|
+
const roadmapContent = fs38.readFileSync(roadmapPath, "utf-8");
|
|
15453
15608
|
const items = parseRoadmap(roadmapContent);
|
|
15454
15609
|
const uncheckedItems = getUncheckedItems(items);
|
|
15455
15610
|
if (uncheckedItems.length === 0) {
|
|
@@ -15573,7 +15728,7 @@ function boardCommand(program2) {
|
|
|
15573
15728
|
// src/commands/queue.ts
|
|
15574
15729
|
init_dist();
|
|
15575
15730
|
init_dist();
|
|
15576
|
-
import * as
|
|
15731
|
+
import * as path41 from "path";
|
|
15577
15732
|
import { spawn as spawn6 } from "child_process";
|
|
15578
15733
|
import chalk7 from "chalk";
|
|
15579
15734
|
import { Command as Command2 } from "commander";
|
|
@@ -15693,7 +15848,7 @@ function createQueueCommand() {
|
|
|
15693
15848
|
process.exit(1);
|
|
15694
15849
|
}
|
|
15695
15850
|
}
|
|
15696
|
-
const projectName =
|
|
15851
|
+
const projectName = path41.basename(projectDir);
|
|
15697
15852
|
const queueConfig = loadConfig(projectDir).queue;
|
|
15698
15853
|
const id = enqueueJob(projectDir, projectName, jobType, envVars, queueConfig);
|
|
15699
15854
|
console.log(chalk7.green(`Enqueued ${jobType} for ${projectName} (ID: ${id})`));
|
|
@@ -15849,17 +16004,17 @@ function notifyCommand(program2) {
|
|
|
15849
16004
|
|
|
15850
16005
|
// src/cli.ts
|
|
15851
16006
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
15852
|
-
var __dirname4 =
|
|
16007
|
+
var __dirname4 = dirname9(__filename3);
|
|
15853
16008
|
function findPackageRoot(dir) {
|
|
15854
16009
|
let d = dir;
|
|
15855
16010
|
for (let i = 0; i < 5; i++) {
|
|
15856
|
-
if (
|
|
15857
|
-
d =
|
|
16011
|
+
if (existsSync30(join36(d, "package.json"))) return d;
|
|
16012
|
+
d = dirname9(d);
|
|
15858
16013
|
}
|
|
15859
16014
|
return dir;
|
|
15860
16015
|
}
|
|
15861
16016
|
var packageRoot = findPackageRoot(__dirname4);
|
|
15862
|
-
var packageJson = JSON.parse(
|
|
16017
|
+
var packageJson = JSON.parse(readFileSync19(join36(packageRoot, "package.json"), "utf-8"));
|
|
15863
16018
|
var program = new Command3();
|
|
15864
16019
|
program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
|
|
15865
16020
|
initCommand(program);
|