@jonit-dev/night-watch-cli 1.8.2 → 1.8.4-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1017 -553
- 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/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +8 -12
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +96 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/scripts/publish.sh +94 -26
- package/dist/templates/prd-executor.md +1 -0
- package/dist/templates/skills/_codex-block.md +58 -0
- package/dist/templates/skills/nw-add-issue.md +39 -0
- package/dist/templates/skills/nw-board-sync.md +47 -0
- package/dist/templates/skills/nw-create-prd.md +61 -0
- package/dist/templates/skills/nw-review.md +39 -0
- package/dist/templates/skills/nw-run.md +39 -0
- package/dist/templates/skills/nw-slice.md +33 -0
- package/dist/templates/skills/skills/_codex-block.md +58 -0
- package/dist/templates/skills/skills/nw-add-issue.md +39 -0
- package/dist/templates/skills/skills/nw-board-sync.md +47 -0
- package/dist/templates/skills/skills/nw-create-prd.md +61 -0
- package/dist/templates/skills/skills/nw-review.md +39 -0
- package/dist/templates/skills/skills/nw-run.md +39 -0
- package/dist/templates/skills/skills/nw-slice.md +33 -0
- package/dist/web/assets/index-BFxPiKyy.js +381 -0
- package/dist/web/assets/index-Cp7RYjoy.css +1 -0
- package/dist/web/assets/index-DTsfDC7m.js +381 -0
- package/dist/web/assets/index-ZABWMEZR.js +381 -0
- package/dist/web/index.html +2 -2
- package/package.json +12 -3
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,283 @@ var init_types = __esm({
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
// ../core/dist/jobs/job-registry.js
|
|
21
|
+
function getJobDef(id) {
|
|
22
|
+
return JOB_MAP.get(id);
|
|
23
|
+
}
|
|
24
|
+
function getAllJobDefs() {
|
|
25
|
+
return [...JOB_REGISTRY];
|
|
26
|
+
}
|
|
27
|
+
function getJobDefByCommand(command) {
|
|
28
|
+
return JOB_REGISTRY.find((job) => job.cliCommand === command);
|
|
29
|
+
}
|
|
30
|
+
function getJobDefByLogName(logName) {
|
|
31
|
+
return JOB_REGISTRY.find((job) => job.logName === logName);
|
|
32
|
+
}
|
|
33
|
+
function getValidJobTypes() {
|
|
34
|
+
return JOB_REGISTRY.map((job) => job.id);
|
|
35
|
+
}
|
|
36
|
+
function getDefaultQueuePriority() {
|
|
37
|
+
const result = {};
|
|
38
|
+
for (const job of JOB_REGISTRY) {
|
|
39
|
+
result[job.id] = job.queuePriority;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function getLogFileNames() {
|
|
44
|
+
const result = {};
|
|
45
|
+
for (const job of JOB_REGISTRY) {
|
|
46
|
+
result[job.id] = job.logName;
|
|
47
|
+
if (job.cliCommand !== job.id) {
|
|
48
|
+
result[job.cliCommand] = job.logName;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function getLockSuffix(jobId) {
|
|
54
|
+
return getJobDef(jobId)?.lockSuffix ?? ".lock";
|
|
55
|
+
}
|
|
56
|
+
function normalizeJobConfig(raw, jobDef) {
|
|
57
|
+
const readBoolean = (v) => typeof v === "boolean" ? v : void 0;
|
|
58
|
+
const readString = (v) => typeof v === "string" ? v : void 0;
|
|
59
|
+
const readNumber = (v) => typeof v === "number" && !Number.isNaN(v) ? v : void 0;
|
|
60
|
+
const readStringArray = (v) => Array.isArray(v) && v.every((s) => typeof s === "string") ? v : void 0;
|
|
61
|
+
const defaults = jobDef.defaultConfig;
|
|
62
|
+
const result = {
|
|
63
|
+
enabled: readBoolean(raw.enabled) ?? defaults.enabled,
|
|
64
|
+
schedule: readString(raw.schedule) ?? defaults.schedule,
|
|
65
|
+
maxRuntime: readNumber(raw.maxRuntime) ?? defaults.maxRuntime
|
|
66
|
+
};
|
|
67
|
+
for (const field of jobDef.extraFields ?? []) {
|
|
68
|
+
switch (field.type) {
|
|
69
|
+
case "boolean":
|
|
70
|
+
result[field.name] = readBoolean(raw[field.name]) ?? field.defaultValue;
|
|
71
|
+
break;
|
|
72
|
+
case "string":
|
|
73
|
+
result[field.name] = readString(raw[field.name]) ?? field.defaultValue;
|
|
74
|
+
break;
|
|
75
|
+
case "number":
|
|
76
|
+
result[field.name] = readNumber(raw[field.name]) ?? field.defaultValue;
|
|
77
|
+
break;
|
|
78
|
+
case "string[]":
|
|
79
|
+
result[field.name] = readStringArray(raw[field.name]) ?? field.defaultValue;
|
|
80
|
+
break;
|
|
81
|
+
case "enum": {
|
|
82
|
+
const val = readString(raw[field.name]);
|
|
83
|
+
result[field.name] = val && field.enumValues?.includes(val) ? val : field.defaultValue;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function camelToUpperSnake(name) {
|
|
91
|
+
return name.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
92
|
+
}
|
|
93
|
+
function buildJobEnvOverrides(envPrefix, currentBase, extraFields) {
|
|
94
|
+
const parseBoolean2 = (value) => {
|
|
95
|
+
const v = value.toLowerCase().trim();
|
|
96
|
+
if (v === "true" || v === "1")
|
|
97
|
+
return true;
|
|
98
|
+
if (v === "false" || v === "0")
|
|
99
|
+
return false;
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
const result = { ...currentBase };
|
|
103
|
+
let changed = false;
|
|
104
|
+
const enabledVal = process.env[`${envPrefix}_ENABLED`];
|
|
105
|
+
if (enabledVal) {
|
|
106
|
+
const v = parseBoolean2(enabledVal);
|
|
107
|
+
if (v !== null) {
|
|
108
|
+
result.enabled = v;
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const scheduleVal = process.env[`${envPrefix}_SCHEDULE`];
|
|
113
|
+
if (scheduleVal) {
|
|
114
|
+
result.schedule = scheduleVal;
|
|
115
|
+
changed = true;
|
|
116
|
+
}
|
|
117
|
+
const maxRuntimeVal = process.env[`${envPrefix}_MAX_RUNTIME`];
|
|
118
|
+
if (maxRuntimeVal) {
|
|
119
|
+
const v = parseInt(maxRuntimeVal, 10);
|
|
120
|
+
if (!isNaN(v) && v > 0) {
|
|
121
|
+
result.maxRuntime = v;
|
|
122
|
+
changed = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const field of extraFields ?? []) {
|
|
126
|
+
const envKey = `${envPrefix}_${camelToUpperSnake(field.name)}`;
|
|
127
|
+
const envVal = process.env[envKey];
|
|
128
|
+
if (!envVal)
|
|
129
|
+
continue;
|
|
130
|
+
switch (field.type) {
|
|
131
|
+
case "boolean": {
|
|
132
|
+
const v = parseBoolean2(envVal);
|
|
133
|
+
if (v !== null) {
|
|
134
|
+
result[field.name] = v;
|
|
135
|
+
changed = true;
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case "string":
|
|
140
|
+
result[field.name] = envVal;
|
|
141
|
+
changed = true;
|
|
142
|
+
break;
|
|
143
|
+
case "number": {
|
|
144
|
+
const v = parseInt(envVal, 10);
|
|
145
|
+
if (!isNaN(v) && v > 0) {
|
|
146
|
+
result[field.name] = v;
|
|
147
|
+
changed = true;
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "string[]": {
|
|
152
|
+
const patterns = envVal.split(",").map((s) => s.trim()).filter(Boolean);
|
|
153
|
+
if (patterns.length > 0) {
|
|
154
|
+
result[field.name] = patterns;
|
|
155
|
+
changed = true;
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "enum":
|
|
160
|
+
if (field.enumValues?.includes(envVal)) {
|
|
161
|
+
result[field.name] = envVal;
|
|
162
|
+
changed = true;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return changed ? result : null;
|
|
168
|
+
}
|
|
169
|
+
var JOB_REGISTRY, JOB_MAP;
|
|
170
|
+
var init_job_registry = __esm({
|
|
171
|
+
"../core/dist/jobs/job-registry.js"() {
|
|
172
|
+
"use strict";
|
|
173
|
+
JOB_REGISTRY = [
|
|
174
|
+
{
|
|
175
|
+
id: "executor",
|
|
176
|
+
name: "Executor",
|
|
177
|
+
description: "Creates implementation PRs from PRDs",
|
|
178
|
+
cliCommand: "run",
|
|
179
|
+
logName: "executor",
|
|
180
|
+
lockSuffix: ".lock",
|
|
181
|
+
queuePriority: 50,
|
|
182
|
+
envPrefix: "NW_EXECUTOR",
|
|
183
|
+
defaultConfig: {
|
|
184
|
+
enabled: true,
|
|
185
|
+
schedule: "5 */2 * * *",
|
|
186
|
+
maxRuntime: 7200
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "reviewer",
|
|
191
|
+
name: "Reviewer",
|
|
192
|
+
description: "Reviews and improves PRs on night-watch branches",
|
|
193
|
+
cliCommand: "review",
|
|
194
|
+
logName: "reviewer",
|
|
195
|
+
lockSuffix: "-r.lock",
|
|
196
|
+
queuePriority: 40,
|
|
197
|
+
envPrefix: "NW_REVIEWER",
|
|
198
|
+
defaultConfig: {
|
|
199
|
+
enabled: true,
|
|
200
|
+
schedule: "25 */3 * * *",
|
|
201
|
+
maxRuntime: 3600
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "slicer",
|
|
206
|
+
name: "Slicer",
|
|
207
|
+
description: "Generates PRDs from roadmap items",
|
|
208
|
+
cliCommand: "planner",
|
|
209
|
+
logName: "slicer",
|
|
210
|
+
lockSuffix: "-slicer.lock",
|
|
211
|
+
queuePriority: 30,
|
|
212
|
+
envPrefix: "NW_SLICER",
|
|
213
|
+
defaultConfig: {
|
|
214
|
+
enabled: true,
|
|
215
|
+
schedule: "35 */6 * * *",
|
|
216
|
+
maxRuntime: 600
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "qa",
|
|
221
|
+
name: "QA",
|
|
222
|
+
description: "Runs end-to-end tests on PRs",
|
|
223
|
+
cliCommand: "qa",
|
|
224
|
+
logName: "night-watch-qa",
|
|
225
|
+
lockSuffix: "-qa.lock",
|
|
226
|
+
queuePriority: 20,
|
|
227
|
+
envPrefix: "NW_QA",
|
|
228
|
+
extraFields: [
|
|
229
|
+
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
230
|
+
{
|
|
231
|
+
name: "artifacts",
|
|
232
|
+
type: "enum",
|
|
233
|
+
enumValues: ["screenshot", "video", "both"],
|
|
234
|
+
defaultValue: "both"
|
|
235
|
+
},
|
|
236
|
+
{ name: "skipLabel", type: "string", defaultValue: "skip-qa" },
|
|
237
|
+
{ name: "autoInstallPlaywright", type: "boolean", defaultValue: true }
|
|
238
|
+
],
|
|
239
|
+
defaultConfig: {
|
|
240
|
+
enabled: true,
|
|
241
|
+
schedule: "45 2,10,18 * * *",
|
|
242
|
+
maxRuntime: 3600,
|
|
243
|
+
branchPatterns: [],
|
|
244
|
+
artifacts: "both",
|
|
245
|
+
skipLabel: "skip-qa",
|
|
246
|
+
autoInstallPlaywright: true
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "audit",
|
|
251
|
+
name: "Auditor",
|
|
252
|
+
description: "Performs code audits and creates issues for findings",
|
|
253
|
+
cliCommand: "audit",
|
|
254
|
+
logName: "audit",
|
|
255
|
+
lockSuffix: "-audit.lock",
|
|
256
|
+
queuePriority: 10,
|
|
257
|
+
envPrefix: "NW_AUDIT",
|
|
258
|
+
defaultConfig: {
|
|
259
|
+
enabled: true,
|
|
260
|
+
schedule: "50 3 * * 1",
|
|
261
|
+
maxRuntime: 1800
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "analytics",
|
|
266
|
+
name: "Analytics",
|
|
267
|
+
description: "Analyzes product analytics and creates issues for trends",
|
|
268
|
+
cliCommand: "analytics",
|
|
269
|
+
logName: "analytics",
|
|
270
|
+
lockSuffix: "-analytics.lock",
|
|
271
|
+
queuePriority: 10,
|
|
272
|
+
envPrefix: "NW_ANALYTICS",
|
|
273
|
+
extraFields: [
|
|
274
|
+
{ name: "lookbackDays", type: "number", defaultValue: 7 },
|
|
275
|
+
{
|
|
276
|
+
name: "targetColumn",
|
|
277
|
+
type: "enum",
|
|
278
|
+
enumValues: ["Draft", "Ready", "In Progress", "Done", "Closed"],
|
|
279
|
+
defaultValue: "Draft"
|
|
280
|
+
},
|
|
281
|
+
{ name: "analysisPrompt", type: "string", defaultValue: "" }
|
|
282
|
+
],
|
|
283
|
+
defaultConfig: {
|
|
284
|
+
enabled: false,
|
|
285
|
+
schedule: "0 6 * * 1",
|
|
286
|
+
maxRuntime: 900,
|
|
287
|
+
lookbackDays: 7,
|
|
288
|
+
targetColumn: "Draft",
|
|
289
|
+
analysisPrompt: ""
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
];
|
|
293
|
+
JOB_MAP = new Map(JOB_REGISTRY.map((job) => [job.id, job]));
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
20
297
|
// ../core/dist/constants.js
|
|
21
298
|
function resolveProviderBucketKey(provider, providerEnv) {
|
|
22
299
|
if (provider === "codex")
|
|
@@ -31,10 +308,11 @@ function resolveProviderBucketKey(provider, providerEnv) {
|
|
|
31
308
|
return `claude-proxy:${baseUrl}`;
|
|
32
309
|
}
|
|
33
310
|
}
|
|
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;
|
|
311
|
+
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
312
|
var init_constants = __esm({
|
|
36
313
|
"../core/dist/constants.js"() {
|
|
37
314
|
"use strict";
|
|
315
|
+
init_job_registry();
|
|
38
316
|
DEFAULT_DEFAULT_BRANCH = "";
|
|
39
317
|
DEFAULT_PRD_DIR = "docs/prds";
|
|
40
318
|
DEFAULT_MAX_RUNTIME = 7200;
|
|
@@ -131,14 +409,7 @@ If no issues are warranted, output an empty array: []`;
|
|
|
131
409
|
PLANNER_LOG_NAME = "slicer";
|
|
132
410
|
ANALYTICS_LOG_NAME = "analytics";
|
|
133
411
|
VALID_PROVIDERS = ["claude", "codex"];
|
|
134
|
-
VALID_JOB_TYPES =
|
|
135
|
-
"executor",
|
|
136
|
-
"reviewer",
|
|
137
|
-
"qa",
|
|
138
|
-
"audit",
|
|
139
|
-
"slicer",
|
|
140
|
-
"analytics"
|
|
141
|
-
];
|
|
412
|
+
VALID_JOB_TYPES = getValidJobTypes();
|
|
142
413
|
DEFAULT_JOB_PROVIDERS = {};
|
|
143
414
|
BUILT_IN_PRESETS = {
|
|
144
415
|
claude: {
|
|
@@ -212,32 +483,19 @@ If no issues are warranted, output an empty array: []`;
|
|
|
212
483
|
REVIEWER_LOG_NAME = "reviewer";
|
|
213
484
|
EXECUTOR_LOG_FILE = "executor.log";
|
|
214
485
|
REVIEWER_LOG_FILE = "reviewer.log";
|
|
215
|
-
LOG_FILE_NAMES =
|
|
216
|
-
executor: EXECUTOR_LOG_NAME,
|
|
217
|
-
reviewer: REVIEWER_LOG_NAME,
|
|
218
|
-
qa: QA_LOG_NAME,
|
|
219
|
-
audit: AUDIT_LOG_NAME,
|
|
220
|
-
planner: PLANNER_LOG_NAME,
|
|
221
|
-
analytics: ANALYTICS_LOG_NAME
|
|
222
|
-
};
|
|
486
|
+
LOG_FILE_NAMES = getLogFileNames();
|
|
223
487
|
GLOBAL_CONFIG_DIR = ".night-watch";
|
|
224
488
|
REGISTRY_FILE_NAME = "projects.json";
|
|
225
489
|
HISTORY_FILE_NAME = "history.json";
|
|
226
490
|
PRD_STATES_FILE_NAME = "prd-states.json";
|
|
227
491
|
STATE_DB_FILE_NAME = "state.db";
|
|
492
|
+
GLOBAL_NOTIFICATIONS_FILE_NAME = "global-notifications.json";
|
|
228
493
|
MAX_HISTORY_RECORDS_PER_PRD = 10;
|
|
229
494
|
DEFAULT_QUEUE_ENABLED = true;
|
|
230
495
|
DEFAULT_QUEUE_MODE = "conservative";
|
|
231
496
|
DEFAULT_QUEUE_MAX_CONCURRENCY = 1;
|
|
232
497
|
DEFAULT_QUEUE_MAX_WAIT_TIME = 7200;
|
|
233
|
-
DEFAULT_QUEUE_PRIORITY =
|
|
234
|
-
executor: 50,
|
|
235
|
-
reviewer: 40,
|
|
236
|
-
slicer: 30,
|
|
237
|
-
qa: 20,
|
|
238
|
-
audit: 10,
|
|
239
|
-
analytics: 10
|
|
240
|
-
};
|
|
498
|
+
DEFAULT_QUEUE_PRIORITY = getDefaultQueuePriority();
|
|
241
499
|
DEFAULT_QUEUE = {
|
|
242
500
|
enabled: DEFAULT_QUEUE_ENABLED,
|
|
243
501
|
mode: DEFAULT_QUEUE_MODE,
|
|
@@ -251,15 +509,6 @@ If no issues are warranted, output an empty array: []`;
|
|
|
251
509
|
}
|
|
252
510
|
});
|
|
253
511
|
|
|
254
|
-
// ../core/dist/board/types.js
|
|
255
|
-
var BOARD_COLUMNS;
|
|
256
|
-
var init_types2 = __esm({
|
|
257
|
-
"../core/dist/board/types.js"() {
|
|
258
|
-
"use strict";
|
|
259
|
-
BOARD_COLUMNS = ["Draft", "Ready", "In Progress", "Review", "Done"];
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
|
|
263
512
|
// ../core/dist/config-normalize.js
|
|
264
513
|
function validateProvider(value) {
|
|
265
514
|
const trimmed = value.trim();
|
|
@@ -431,43 +680,14 @@ function normalizeConfig(rawConfig) {
|
|
|
431
680
|
if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
|
|
432
681
|
normalized.autoMergeMethod = mergeMethod;
|
|
433
682
|
}
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
branchPatterns: readStringArray(rawQa.branchPatterns) ?? DEFAULT_QA.branchPatterns,
|
|
443
|
-
artifacts,
|
|
444
|
-
skipLabel: readString(rawQa.skipLabel) ?? DEFAULT_QA.skipLabel,
|
|
445
|
-
autoInstallPlaywright: readBoolean(rawQa.autoInstallPlaywright) ?? DEFAULT_QA.autoInstallPlaywright
|
|
446
|
-
};
|
|
447
|
-
normalized.qa = qa;
|
|
448
|
-
}
|
|
449
|
-
const rawAudit = readObject(rawConfig.audit);
|
|
450
|
-
if (rawAudit) {
|
|
451
|
-
const audit = {
|
|
452
|
-
enabled: readBoolean(rawAudit.enabled) ?? DEFAULT_AUDIT.enabled,
|
|
453
|
-
schedule: readString(rawAudit.schedule) ?? DEFAULT_AUDIT.schedule,
|
|
454
|
-
maxRuntime: readNumber(rawAudit.maxRuntime) ?? DEFAULT_AUDIT.maxRuntime
|
|
455
|
-
};
|
|
456
|
-
normalized.audit = audit;
|
|
457
|
-
}
|
|
458
|
-
const rawAnalytics = readObject(rawConfig.analytics);
|
|
459
|
-
if (rawAnalytics) {
|
|
460
|
-
const targetColumnRaw = readString(rawAnalytics.targetColumn);
|
|
461
|
-
const targetColumn = targetColumnRaw && BOARD_COLUMNS.includes(targetColumnRaw) ? targetColumnRaw : DEFAULT_ANALYTICS.targetColumn;
|
|
462
|
-
const analytics = {
|
|
463
|
-
enabled: readBoolean(rawAnalytics.enabled) ?? DEFAULT_ANALYTICS.enabled,
|
|
464
|
-
schedule: readString(rawAnalytics.schedule) ?? DEFAULT_ANALYTICS.schedule,
|
|
465
|
-
maxRuntime: readNumber(rawAnalytics.maxRuntime) ?? DEFAULT_ANALYTICS.maxRuntime,
|
|
466
|
-
lookbackDays: readNumber(rawAnalytics.lookbackDays) ?? DEFAULT_ANALYTICS.lookbackDays,
|
|
467
|
-
targetColumn,
|
|
468
|
-
analysisPrompt: readString(rawAnalytics.analysisPrompt) ?? DEFAULT_ANALYTICS.analysisPrompt
|
|
469
|
-
};
|
|
470
|
-
normalized.analytics = analytics;
|
|
683
|
+
for (const jobId of ["qa", "audit", "analytics"]) {
|
|
684
|
+
const jobDef = getJobDef(jobId);
|
|
685
|
+
if (!jobDef)
|
|
686
|
+
continue;
|
|
687
|
+
const rawJob = readObject(rawConfig[jobId]);
|
|
688
|
+
if (rawJob) {
|
|
689
|
+
normalized[jobId] = normalizeJobConfig(rawJob, jobDef);
|
|
690
|
+
}
|
|
471
691
|
}
|
|
472
692
|
const rawJobProviders = readObject(rawConfig.jobProviders);
|
|
473
693
|
if (rawJobProviders) {
|
|
@@ -528,8 +748,8 @@ function normalizeConfig(rawConfig) {
|
|
|
528
748
|
var init_config_normalize = __esm({
|
|
529
749
|
"../core/dist/config-normalize.js"() {
|
|
530
750
|
"use strict";
|
|
531
|
-
init_types2();
|
|
532
751
|
init_constants();
|
|
752
|
+
init_job_registry();
|
|
533
753
|
}
|
|
534
754
|
});
|
|
535
755
|
|
|
@@ -705,72 +925,16 @@ function buildEnvOverrideConfig(fileConfig) {
|
|
|
705
925
|
env.claudeModel = model;
|
|
706
926
|
}
|
|
707
927
|
}
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
if (process.env.NW_QA_MAX_RUNTIME) {
|
|
718
|
-
const v = parseInt(process.env.NW_QA_MAX_RUNTIME, 10);
|
|
719
|
-
if (!isNaN(v) && v > 0)
|
|
720
|
-
env.qa = { ...qaBase(), maxRuntime: v };
|
|
721
|
-
}
|
|
722
|
-
if (process.env.NW_QA_ARTIFACTS) {
|
|
723
|
-
const a = process.env.NW_QA_ARTIFACTS;
|
|
724
|
-
if (["screenshot", "video", "both"].includes(a)) {
|
|
725
|
-
env.qa = { ...qaBase(), artifacts: a };
|
|
928
|
+
for (const jobId of ["qa", "audit", "analytics"]) {
|
|
929
|
+
const jobDef = getJobDef(jobId);
|
|
930
|
+
if (!jobDef)
|
|
931
|
+
continue;
|
|
932
|
+
const currentBase = env[jobId] ?? fileConfig?.[jobId] ?? jobDef.defaultConfig;
|
|
933
|
+
const overrides = buildJobEnvOverrides(jobDef.envPrefix, currentBase, jobDef.extraFields);
|
|
934
|
+
if (overrides) {
|
|
935
|
+
env[jobId] = overrides;
|
|
726
936
|
}
|
|
727
937
|
}
|
|
728
|
-
if (process.env.NW_QA_SKIP_LABEL) {
|
|
729
|
-
env.qa = { ...qaBase(), skipLabel: process.env.NW_QA_SKIP_LABEL };
|
|
730
|
-
}
|
|
731
|
-
if (process.env.NW_QA_AUTO_INSTALL_PLAYWRIGHT) {
|
|
732
|
-
const v = parseBoolean(process.env.NW_QA_AUTO_INSTALL_PLAYWRIGHT);
|
|
733
|
-
if (v !== null)
|
|
734
|
-
env.qa = { ...qaBase(), autoInstallPlaywright: v };
|
|
735
|
-
}
|
|
736
|
-
if (process.env.NW_QA_BRANCH_PATTERNS) {
|
|
737
|
-
const patterns = process.env.NW_QA_BRANCH_PATTERNS.split(",").map((s) => s.trim()).filter(Boolean);
|
|
738
|
-
if (patterns.length > 0)
|
|
739
|
-
env.qa = { ...qaBase(), branchPatterns: patterns };
|
|
740
|
-
}
|
|
741
|
-
const auditBase = () => env.audit ?? fileConfig?.audit ?? DEFAULT_AUDIT;
|
|
742
|
-
if (process.env.NW_AUDIT_ENABLED) {
|
|
743
|
-
const v = parseBoolean(process.env.NW_AUDIT_ENABLED);
|
|
744
|
-
if (v !== null)
|
|
745
|
-
env.audit = { ...auditBase(), enabled: v };
|
|
746
|
-
}
|
|
747
|
-
if (process.env.NW_AUDIT_SCHEDULE) {
|
|
748
|
-
env.audit = { ...auditBase(), schedule: process.env.NW_AUDIT_SCHEDULE };
|
|
749
|
-
}
|
|
750
|
-
if (process.env.NW_AUDIT_MAX_RUNTIME) {
|
|
751
|
-
const v = parseInt(process.env.NW_AUDIT_MAX_RUNTIME, 10);
|
|
752
|
-
if (!isNaN(v) && v > 0)
|
|
753
|
-
env.audit = { ...auditBase(), maxRuntime: v };
|
|
754
|
-
}
|
|
755
|
-
const analyticsBase = () => env.analytics ?? fileConfig?.analytics ?? DEFAULT_ANALYTICS;
|
|
756
|
-
if (process.env.NW_ANALYTICS_ENABLED) {
|
|
757
|
-
const v = parseBoolean(process.env.NW_ANALYTICS_ENABLED);
|
|
758
|
-
if (v !== null)
|
|
759
|
-
env.analytics = { ...analyticsBase(), enabled: v };
|
|
760
|
-
}
|
|
761
|
-
if (process.env.NW_ANALYTICS_SCHEDULE) {
|
|
762
|
-
env.analytics = { ...analyticsBase(), schedule: process.env.NW_ANALYTICS_SCHEDULE };
|
|
763
|
-
}
|
|
764
|
-
if (process.env.NW_ANALYTICS_MAX_RUNTIME) {
|
|
765
|
-
const v = parseInt(process.env.NW_ANALYTICS_MAX_RUNTIME, 10);
|
|
766
|
-
if (!isNaN(v) && v > 0)
|
|
767
|
-
env.analytics = { ...analyticsBase(), maxRuntime: v };
|
|
768
|
-
}
|
|
769
|
-
if (process.env.NW_ANALYTICS_LOOKBACK_DAYS) {
|
|
770
|
-
const v = parseInt(process.env.NW_ANALYTICS_LOOKBACK_DAYS, 10);
|
|
771
|
-
if (!isNaN(v) && v > 0)
|
|
772
|
-
env.analytics = { ...analyticsBase(), lookbackDays: v };
|
|
773
|
-
}
|
|
774
938
|
const jobProvidersEnv = {};
|
|
775
939
|
for (const jobType of VALID_JOB_TYPES) {
|
|
776
940
|
const val = process.env[`NW_JOB_PROVIDER_${jobType.toUpperCase()}`];
|
|
@@ -820,6 +984,7 @@ var init_config_env = __esm({
|
|
|
820
984
|
"use strict";
|
|
821
985
|
init_constants();
|
|
822
986
|
init_config_normalize();
|
|
987
|
+
init_job_registry();
|
|
823
988
|
}
|
|
824
989
|
});
|
|
825
990
|
|
|
@@ -1015,6 +1180,15 @@ var init_config = __esm({
|
|
|
1015
1180
|
}
|
|
1016
1181
|
});
|
|
1017
1182
|
|
|
1183
|
+
// ../core/dist/board/types.js
|
|
1184
|
+
var BOARD_COLUMNS;
|
|
1185
|
+
var init_types2 = __esm({
|
|
1186
|
+
"../core/dist/board/types.js"() {
|
|
1187
|
+
"use strict";
|
|
1188
|
+
BOARD_COLUMNS = ["Draft", "Ready", "In Progress", "Review", "Done"];
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1018
1192
|
// ../core/dist/storage/repositories/sqlite/execution-history.repository.js
|
|
1019
1193
|
import Database from "better-sqlite3";
|
|
1020
1194
|
import { inject, injectable } from "tsyringe";
|
|
@@ -2029,11 +2203,18 @@ var init_github_projects = __esm({
|
|
|
2029
2203
|
await this.ensureStatusColumns(existing.id);
|
|
2030
2204
|
return { id: existing.id, number: existing.number, title: existing.title, url: existing.url };
|
|
2031
2205
|
}
|
|
2032
|
-
const createData = await graphql(`
|
|
2033
|
-
|
|
2034
|
-
|
|
2206
|
+
const createData = await graphql(`
|
|
2207
|
+
mutation CreateProject($ownerId: ID!, $title: String!) {
|
|
2208
|
+
createProjectV2(input: { ownerId: $ownerId, title: $title }) {
|
|
2209
|
+
projectV2 {
|
|
2210
|
+
id
|
|
2211
|
+
number
|
|
2212
|
+
url
|
|
2213
|
+
title
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2035
2216
|
}
|
|
2036
|
-
|
|
2217
|
+
`, { ownerId: owner.id, title }, this.cwd);
|
|
2037
2218
|
const project = createData.createProjectV2.projectV2;
|
|
2038
2219
|
this.cachedProjectId = project.id;
|
|
2039
2220
|
await this.linkProjectToRepository(project.id);
|
|
@@ -2049,24 +2230,34 @@ var init_github_projects = __esm({
|
|
|
2049
2230
|
const message = err instanceof Error ? err.message : String(err);
|
|
2050
2231
|
if (!message.includes("Status field not found"))
|
|
2051
2232
|
throw err;
|
|
2052
|
-
const createFieldData = await graphql(`
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2233
|
+
const createFieldData = await graphql(`
|
|
2234
|
+
mutation CreateStatusField($projectId: ID!) {
|
|
2235
|
+
createProjectV2Field(
|
|
2236
|
+
input: {
|
|
2237
|
+
projectId: $projectId
|
|
2238
|
+
dataType: SINGLE_SELECT
|
|
2239
|
+
name: "Status"
|
|
2240
|
+
singleSelectOptions: [
|
|
2241
|
+
{ name: "Draft", color: GRAY, description: "" }
|
|
2242
|
+
{ name: "Ready", color: BLUE, description: "" }
|
|
2243
|
+
{ name: "In Progress", color: YELLOW, description: "" }
|
|
2244
|
+
{ name: "Review", color: ORANGE, description: "" }
|
|
2245
|
+
{ name: "Done", color: GREEN, description: "" }
|
|
2246
|
+
]
|
|
2247
|
+
}
|
|
2248
|
+
) {
|
|
2249
|
+
projectV2Field {
|
|
2250
|
+
... on ProjectV2SingleSelectField {
|
|
2251
|
+
id
|
|
2252
|
+
options {
|
|
2253
|
+
id
|
|
2254
|
+
name
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2067
2258
|
}
|
|
2068
2259
|
}
|
|
2069
|
-
|
|
2260
|
+
`, { projectId: project.id }, this.cwd);
|
|
2070
2261
|
const field = createFieldData.createProjectV2Field.projectV2Field;
|
|
2071
2262
|
this.cachedFieldId = field.id;
|
|
2072
2263
|
this.cachedOptionIds = new Map(field.options.map((o) => [o.name, o.id]));
|
|
@@ -2093,11 +2284,23 @@ var init_github_projects = __esm({
|
|
|
2093
2284
|
async createIssue(input) {
|
|
2094
2285
|
const repo = await this.getRepo();
|
|
2095
2286
|
const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
|
|
2096
|
-
const issueArgs = [
|
|
2287
|
+
const issueArgs = [
|
|
2288
|
+
"issue",
|
|
2289
|
+
"create",
|
|
2290
|
+
"--title",
|
|
2291
|
+
input.title,
|
|
2292
|
+
"--body",
|
|
2293
|
+
input.body,
|
|
2294
|
+
"--repo",
|
|
2295
|
+
repo
|
|
2296
|
+
];
|
|
2097
2297
|
if (input.labels && input.labels.length > 0) {
|
|
2098
2298
|
issueArgs.push("--label", input.labels.join(","));
|
|
2099
2299
|
}
|
|
2100
|
-
const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, {
|
|
2300
|
+
const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, {
|
|
2301
|
+
cwd: this.cwd,
|
|
2302
|
+
encoding: "utf-8"
|
|
2303
|
+
});
|
|
2101
2304
|
const issueUrl = issueUrlRaw.trim();
|
|
2102
2305
|
const issueNumber = parseInt(issueUrl.split("/").pop() ?? "", 10);
|
|
2103
2306
|
if (!issueNumber)
|
|
@@ -2105,11 +2308,15 @@ var init_github_projects = __esm({
|
|
|
2105
2308
|
const [owner, repoName] = repo.split("/");
|
|
2106
2309
|
const { stdout: nodeIdRaw } = await execFileAsync2("gh", ["api", `repos/${owner}/${repoName}/issues/${issueNumber}`, "--jq", ".node_id"], { cwd: this.cwd, encoding: "utf-8" });
|
|
2107
2310
|
const issueJson = { number: issueNumber, id: nodeIdRaw.trim(), url: issueUrl };
|
|
2108
|
-
const addData = await graphql(`
|
|
2109
|
-
|
|
2110
|
-
|
|
2311
|
+
const addData = await graphql(`
|
|
2312
|
+
mutation AddProjectItem($projectId: ID!, $contentId: ID!) {
|
|
2313
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
|
|
2314
|
+
item {
|
|
2315
|
+
id
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2111
2318
|
}
|
|
2112
|
-
|
|
2319
|
+
`, { projectId, contentId: issueJson.id }, this.cwd);
|
|
2113
2320
|
const itemId = addData.addProjectV2ItemById.item.id;
|
|
2114
2321
|
const targetColumn = input.column ?? "Draft";
|
|
2115
2322
|
const optionId = optionIds.get(targetColumn);
|
|
@@ -2129,11 +2336,45 @@ var init_github_projects = __esm({
|
|
|
2129
2336
|
assignees: []
|
|
2130
2337
|
};
|
|
2131
2338
|
}
|
|
2339
|
+
async addIssue(issueNumber, column = "Ready") {
|
|
2340
|
+
const repo = await this.getRepo();
|
|
2341
|
+
const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
|
|
2342
|
+
const [owner, repoName] = repo.split("/");
|
|
2343
|
+
const { stdout: nodeIdRaw } = await execFileAsync2("gh", ["api", `repos/${owner}/${repoName}/issues/${issueNumber}`, "--jq", ".node_id"], { cwd: this.cwd, encoding: "utf-8" });
|
|
2344
|
+
const nodeId = nodeIdRaw.trim();
|
|
2345
|
+
if (!nodeId)
|
|
2346
|
+
throw new Error(`Issue #${issueNumber} not found in ${repo}.`);
|
|
2347
|
+
const addData = await graphql(`
|
|
2348
|
+
mutation AddProjectItem($projectId: ID!, $contentId: ID!) {
|
|
2349
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
|
|
2350
|
+
item {
|
|
2351
|
+
id
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
`, { projectId, contentId: nodeId }, this.cwd);
|
|
2356
|
+
const itemId = addData.addProjectV2ItemById.item.id;
|
|
2357
|
+
const optionId = optionIds.get(column);
|
|
2358
|
+
if (optionId)
|
|
2359
|
+
await this.setItemStatus(projectId, itemId, fieldId, optionId);
|
|
2360
|
+
const full = await this.getIssue(issueNumber);
|
|
2361
|
+
if (full)
|
|
2362
|
+
return { ...full, column };
|
|
2363
|
+
throw new Error(`Added issue #${issueNumber} to project but failed to fetch it back.`);
|
|
2364
|
+
}
|
|
2132
2365
|
async getIssue(issueNumber) {
|
|
2133
2366
|
const repo = await this.getRepo();
|
|
2134
2367
|
let rawIssue;
|
|
2135
2368
|
try {
|
|
2136
|
-
const { stdout: output } = await execFileAsync2("gh", [
|
|
2369
|
+
const { stdout: output } = await execFileAsync2("gh", [
|
|
2370
|
+
"issue",
|
|
2371
|
+
"view",
|
|
2372
|
+
String(issueNumber),
|
|
2373
|
+
"--repo",
|
|
2374
|
+
repo,
|
|
2375
|
+
"--json",
|
|
2376
|
+
"number,title,body,url,id,labels,assignees"
|
|
2377
|
+
], { cwd: this.cwd, encoding: "utf-8" });
|
|
2137
2378
|
rawIssue = JSON.parse(output);
|
|
2138
2379
|
} catch {
|
|
2139
2380
|
return null;
|
|
@@ -2238,6 +2479,9 @@ var init_local_kanban = __esm({
|
|
|
2238
2479
|
});
|
|
2239
2480
|
return toIBoardIssue(row);
|
|
2240
2481
|
}
|
|
2482
|
+
async addIssue(_issueNumber, _column) {
|
|
2483
|
+
throw new Error("addIssue is not supported by the local Kanban provider.");
|
|
2484
|
+
}
|
|
2241
2485
|
async getIssue(issueNumber) {
|
|
2242
2486
|
const row = this.repo.getByNumber(issueNumber);
|
|
2243
2487
|
return row ? toIBoardIssue(row) : null;
|
|
@@ -4275,6 +4519,36 @@ var init_log_utils = __esm({
|
|
|
4275
4519
|
}
|
|
4276
4520
|
});
|
|
4277
4521
|
|
|
4522
|
+
// ../core/dist/utils/global-config.js
|
|
4523
|
+
import * as fs11 from "fs";
|
|
4524
|
+
import * as os5 from "os";
|
|
4525
|
+
import * as path10 from "path";
|
|
4526
|
+
function getGlobalNotificationsPath() {
|
|
4527
|
+
return path10.join(os5.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_NOTIFICATIONS_FILE_NAME);
|
|
4528
|
+
}
|
|
4529
|
+
function loadGlobalNotificationsConfig() {
|
|
4530
|
+
const filePath = getGlobalNotificationsPath();
|
|
4531
|
+
try {
|
|
4532
|
+
if (!fs11.existsSync(filePath))
|
|
4533
|
+
return { webhook: null };
|
|
4534
|
+
const raw = fs11.readFileSync(filePath, "utf-8");
|
|
4535
|
+
return JSON.parse(raw);
|
|
4536
|
+
} catch {
|
|
4537
|
+
return { webhook: null };
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
function saveGlobalNotificationsConfig(config) {
|
|
4541
|
+
const filePath = getGlobalNotificationsPath();
|
|
4542
|
+
fs11.mkdirSync(path10.dirname(filePath), { recursive: true });
|
|
4543
|
+
fs11.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4544
|
+
}
|
|
4545
|
+
var init_global_config = __esm({
|
|
4546
|
+
"../core/dist/utils/global-config.js"() {
|
|
4547
|
+
"use strict";
|
|
4548
|
+
init_constants();
|
|
4549
|
+
}
|
|
4550
|
+
});
|
|
4551
|
+
|
|
4278
4552
|
// ../core/dist/utils/ui.js
|
|
4279
4553
|
import chalk from "chalk";
|
|
4280
4554
|
import ora from "ora";
|
|
@@ -4631,24 +4905,33 @@ async function sendWebhook(webhook, ctx) {
|
|
|
4631
4905
|
warn(`Notification failed (${webhook.type}): ${message}`);
|
|
4632
4906
|
}
|
|
4633
4907
|
}
|
|
4908
|
+
function webhookIdentity(wh) {
|
|
4909
|
+
if (wh.type === "telegram")
|
|
4910
|
+
return `telegram:${wh.botToken}:${wh.chatId}`;
|
|
4911
|
+
return `${wh.type}:${wh.url}`;
|
|
4912
|
+
}
|
|
4634
4913
|
async function sendNotifications(config, ctx) {
|
|
4635
|
-
const
|
|
4636
|
-
const
|
|
4637
|
-
|
|
4638
|
-
|
|
4914
|
+
const projectWebhooks = config.notifications?.webhooks ?? [];
|
|
4915
|
+
const globalConfig = loadGlobalNotificationsConfig();
|
|
4916
|
+
const allWebhooks = [...projectWebhooks];
|
|
4917
|
+
if (globalConfig.webhook) {
|
|
4918
|
+
const projectIds = new Set(projectWebhooks.map(webhookIdentity));
|
|
4919
|
+
if (!projectIds.has(webhookIdentity(globalConfig.webhook))) {
|
|
4920
|
+
allWebhooks.push(globalConfig.webhook);
|
|
4921
|
+
}
|
|
4639
4922
|
}
|
|
4640
|
-
if (
|
|
4923
|
+
if (allWebhooks.length === 0) {
|
|
4641
4924
|
return;
|
|
4642
4925
|
}
|
|
4643
|
-
const results = await Promise.allSettled(
|
|
4926
|
+
const results = await Promise.allSettled(allWebhooks.map((wh) => sendWebhook(wh, ctx)));
|
|
4644
4927
|
const sent = results.filter((r) => r.status === "fulfilled").length;
|
|
4645
|
-
|
|
4646
|
-
info(`Sent ${sent}/${total} notifications`);
|
|
4928
|
+
info(`Sent ${sent}/${allWebhooks.length} notifications`);
|
|
4647
4929
|
}
|
|
4648
4930
|
var MAX_QA_SCREENSHOTS_IN_NOTIFICATION;
|
|
4649
4931
|
var init_notify = __esm({
|
|
4650
4932
|
"../core/dist/utils/notify.js"() {
|
|
4651
4933
|
"use strict";
|
|
4934
|
+
init_global_config();
|
|
4652
4935
|
init_ui();
|
|
4653
4936
|
init_github();
|
|
4654
4937
|
MAX_QA_SCREENSHOTS_IN_NOTIFICATION = 3;
|
|
@@ -4685,15 +4968,15 @@ var init_prd_discovery = __esm({
|
|
|
4685
4968
|
});
|
|
4686
4969
|
|
|
4687
4970
|
// ../core/dist/utils/prd-utils.js
|
|
4688
|
-
import * as
|
|
4689
|
-
import * as
|
|
4971
|
+
import * as fs12 from "fs";
|
|
4972
|
+
import * as path11 from "path";
|
|
4690
4973
|
function slugify(name) {
|
|
4691
4974
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4692
4975
|
}
|
|
4693
4976
|
function getNextPrdNumber(prdDir) {
|
|
4694
|
-
if (!
|
|
4977
|
+
if (!fs12.existsSync(prdDir))
|
|
4695
4978
|
return 1;
|
|
4696
|
-
const files =
|
|
4979
|
+
const files = fs12.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
4697
4980
|
const numbers = files.map((f) => {
|
|
4698
4981
|
const match = f.match(/^(\d+)-/);
|
|
4699
4982
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -4701,16 +4984,16 @@ function getNextPrdNumber(prdDir) {
|
|
|
4701
4984
|
return Math.max(0, ...numbers) + 1;
|
|
4702
4985
|
}
|
|
4703
4986
|
function markPrdDone(prdDir, prdFile) {
|
|
4704
|
-
const sourcePath =
|
|
4705
|
-
if (!
|
|
4987
|
+
const sourcePath = path11.join(prdDir, prdFile);
|
|
4988
|
+
if (!fs12.existsSync(sourcePath)) {
|
|
4706
4989
|
return false;
|
|
4707
4990
|
}
|
|
4708
|
-
const doneDir =
|
|
4709
|
-
if (!
|
|
4710
|
-
|
|
4991
|
+
const doneDir = path11.join(prdDir, "done");
|
|
4992
|
+
if (!fs12.existsSync(doneDir)) {
|
|
4993
|
+
fs12.mkdirSync(doneDir, { recursive: true });
|
|
4711
4994
|
}
|
|
4712
|
-
const destPath =
|
|
4713
|
-
|
|
4995
|
+
const destPath = path11.join(doneDir, prdFile);
|
|
4996
|
+
fs12.renameSync(sourcePath, destPath);
|
|
4714
4997
|
return true;
|
|
4715
4998
|
}
|
|
4716
4999
|
var init_prd_utils = __esm({
|
|
@@ -4720,16 +5003,16 @@ var init_prd_utils = __esm({
|
|
|
4720
5003
|
});
|
|
4721
5004
|
|
|
4722
5005
|
// ../core/dist/utils/registry.js
|
|
4723
|
-
import * as
|
|
4724
|
-
import * as
|
|
4725
|
-
import * as
|
|
5006
|
+
import * as fs13 from "fs";
|
|
5007
|
+
import * as os6 from "os";
|
|
5008
|
+
import * as path12 from "path";
|
|
4726
5009
|
function readLegacyRegistryEntries() {
|
|
4727
5010
|
const registryPath = getRegistryPath();
|
|
4728
|
-
if (!
|
|
5011
|
+
if (!fs13.existsSync(registryPath)) {
|
|
4729
5012
|
return [];
|
|
4730
5013
|
}
|
|
4731
5014
|
try {
|
|
4732
|
-
const raw =
|
|
5015
|
+
const raw = fs13.readFileSync(registryPath, "utf-8");
|
|
4733
5016
|
const parsed = JSON.parse(raw);
|
|
4734
5017
|
if (!Array.isArray(parsed)) {
|
|
4735
5018
|
return [];
|
|
@@ -4764,8 +5047,8 @@ function loadRegistryEntriesWithLegacyFallback() {
|
|
|
4764
5047
|
return projectRegistry.getAll();
|
|
4765
5048
|
}
|
|
4766
5049
|
function getRegistryPath() {
|
|
4767
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
4768
|
-
return
|
|
5050
|
+
const base = process.env.NIGHT_WATCH_HOME || path12.join(os6.homedir(), GLOBAL_CONFIG_DIR);
|
|
5051
|
+
return path12.join(base, REGISTRY_FILE_NAME);
|
|
4769
5052
|
}
|
|
4770
5053
|
function loadRegistry() {
|
|
4771
5054
|
return loadRegistryEntriesWithLegacyFallback();
|
|
@@ -4778,7 +5061,7 @@ function saveRegistry(entries) {
|
|
|
4778
5061
|
}
|
|
4779
5062
|
}
|
|
4780
5063
|
function registerProject(projectDir) {
|
|
4781
|
-
const resolvedPath =
|
|
5064
|
+
const resolvedPath = path12.resolve(projectDir);
|
|
4782
5065
|
const { projectRegistry } = getRepositories();
|
|
4783
5066
|
const entries = loadRegistryEntriesWithLegacyFallback();
|
|
4784
5067
|
const existing = entries.find((e) => e.path === resolvedPath);
|
|
@@ -4787,13 +5070,13 @@ function registerProject(projectDir) {
|
|
|
4787
5070
|
}
|
|
4788
5071
|
const name = getProjectName(resolvedPath);
|
|
4789
5072
|
const nameExists = entries.some((e) => e.name === name);
|
|
4790
|
-
const finalName = nameExists ? `${name}-${
|
|
5073
|
+
const finalName = nameExists ? `${name}-${path12.basename(resolvedPath)}` : name;
|
|
4791
5074
|
const entry = { name: finalName, path: resolvedPath };
|
|
4792
5075
|
projectRegistry.upsert(entry);
|
|
4793
5076
|
return entry;
|
|
4794
5077
|
}
|
|
4795
5078
|
function unregisterProject(projectDir) {
|
|
4796
|
-
const resolvedPath =
|
|
5079
|
+
const resolvedPath = path12.resolve(projectDir);
|
|
4797
5080
|
loadRegistryEntriesWithLegacyFallback();
|
|
4798
5081
|
const { projectRegistry } = getRepositories();
|
|
4799
5082
|
return projectRegistry.remove(resolvedPath);
|
|
@@ -4803,7 +5086,7 @@ function validateRegistry() {
|
|
|
4803
5086
|
const valid = [];
|
|
4804
5087
|
const invalid = [];
|
|
4805
5088
|
for (const entry of entries) {
|
|
4806
|
-
if (
|
|
5089
|
+
if (fs13.existsSync(entry.path) && fs13.existsSync(path12.join(entry.path, CONFIG_FILE_NAME))) {
|
|
4807
5090
|
valid.push(entry);
|
|
4808
5091
|
} else {
|
|
4809
5092
|
invalid.push(entry);
|
|
@@ -4811,6 +5094,26 @@ function validateRegistry() {
|
|
|
4811
5094
|
}
|
|
4812
5095
|
return { valid, invalid };
|
|
4813
5096
|
}
|
|
5097
|
+
function pruneProjectData(projectDir) {
|
|
5098
|
+
const resolvedPath = path12.resolve(projectDir);
|
|
5099
|
+
const db = getDb();
|
|
5100
|
+
db.transaction(() => {
|
|
5101
|
+
db.prepare("DELETE FROM execution_history WHERE project_path = ?").run(resolvedPath);
|
|
5102
|
+
db.prepare("DELETE FROM prd_states WHERE project_path = ?").run(resolvedPath);
|
|
5103
|
+
db.prepare("DELETE FROM job_queue WHERE project_path = ?").run(resolvedPath);
|
|
5104
|
+
db.prepare("DELETE FROM job_runs WHERE project_path = ?").run(resolvedPath);
|
|
5105
|
+
db.prepare("DELETE FROM roadmap_states WHERE prd_dir LIKE ?").run(`${resolvedPath}%`);
|
|
5106
|
+
})();
|
|
5107
|
+
}
|
|
5108
|
+
function removeProject(projectDir) {
|
|
5109
|
+
const resolvedPath = path12.resolve(projectDir);
|
|
5110
|
+
const projectName = getProjectName(resolvedPath);
|
|
5111
|
+
const marker = generateMarker(projectName);
|
|
5112
|
+
const cronEntriesRemoved = removeEntriesForProject(resolvedPath, marker);
|
|
5113
|
+
pruneProjectData(resolvedPath);
|
|
5114
|
+
const unregistered = unregisterProject(resolvedPath);
|
|
5115
|
+
return { cronEntriesRemoved, unregistered, dataPruned: true };
|
|
5116
|
+
}
|
|
4814
5117
|
var init_registry = __esm({
|
|
4815
5118
|
"../core/dist/utils/registry.js"() {
|
|
4816
5119
|
"use strict";
|
|
@@ -4818,6 +5121,7 @@ var init_registry = __esm({
|
|
|
4818
5121
|
init_repositories();
|
|
4819
5122
|
init_client();
|
|
4820
5123
|
init_client();
|
|
5124
|
+
init_crontab();
|
|
4821
5125
|
init_status_data();
|
|
4822
5126
|
}
|
|
4823
5127
|
});
|
|
@@ -4991,18 +5295,18 @@ var init_roadmap_parser = __esm({
|
|
|
4991
5295
|
});
|
|
4992
5296
|
|
|
4993
5297
|
// ../core/dist/utils/roadmap-state.js
|
|
4994
|
-
import * as
|
|
4995
|
-
import * as
|
|
5298
|
+
import * as fs14 from "fs";
|
|
5299
|
+
import * as path13 from "path";
|
|
4996
5300
|
function getStateFilePath(prdDir) {
|
|
4997
|
-
return
|
|
5301
|
+
return path13.join(prdDir, STATE_FILE_NAME);
|
|
4998
5302
|
}
|
|
4999
5303
|
function readJsonState(prdDir) {
|
|
5000
5304
|
const statePath = getStateFilePath(prdDir);
|
|
5001
|
-
if (!
|
|
5305
|
+
if (!fs14.existsSync(statePath)) {
|
|
5002
5306
|
return null;
|
|
5003
5307
|
}
|
|
5004
5308
|
try {
|
|
5005
|
-
const content =
|
|
5309
|
+
const content = fs14.readFileSync(statePath, "utf-8");
|
|
5006
5310
|
const parsed = JSON.parse(content);
|
|
5007
5311
|
if (typeof parsed !== "object" || parsed === null) {
|
|
5008
5312
|
return null;
|
|
@@ -5040,11 +5344,11 @@ function saveRoadmapState(prdDir, state) {
|
|
|
5040
5344
|
const { roadmapState } = getRepositories();
|
|
5041
5345
|
roadmapState.save(prdDir, state);
|
|
5042
5346
|
const statePath = getStateFilePath(prdDir);
|
|
5043
|
-
const dir =
|
|
5044
|
-
if (!
|
|
5045
|
-
|
|
5347
|
+
const dir = path13.dirname(statePath);
|
|
5348
|
+
if (!fs14.existsSync(dir)) {
|
|
5349
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
5046
5350
|
}
|
|
5047
|
-
|
|
5351
|
+
fs14.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
5048
5352
|
}
|
|
5049
5353
|
function createEmptyState() {
|
|
5050
5354
|
return {
|
|
@@ -5084,15 +5388,15 @@ var init_roadmap_state = __esm({
|
|
|
5084
5388
|
});
|
|
5085
5389
|
|
|
5086
5390
|
// ../core/dist/templates/slicer-prompt.js
|
|
5087
|
-
import * as
|
|
5088
|
-
import * as
|
|
5391
|
+
import * as fs15 from "fs";
|
|
5392
|
+
import * as path14 from "path";
|
|
5089
5393
|
function loadSlicerTemplate(templateDir) {
|
|
5090
5394
|
if (cachedTemplate) {
|
|
5091
5395
|
return cachedTemplate;
|
|
5092
5396
|
}
|
|
5093
|
-
const templatePath = templateDir ?
|
|
5397
|
+
const templatePath = templateDir ? path14.join(templateDir, "slicer.md") : path14.resolve(__dirname, "..", "..", "templates", "slicer.md");
|
|
5094
5398
|
try {
|
|
5095
|
-
cachedTemplate =
|
|
5399
|
+
cachedTemplate = fs15.readFileSync(templatePath, "utf-8");
|
|
5096
5400
|
return cachedTemplate;
|
|
5097
5401
|
} catch (error2) {
|
|
5098
5402
|
console.warn(`Warning: Could not load slicer template from ${templatePath}, using default:`, error2 instanceof Error ? error2.message : String(error2));
|
|
@@ -5117,7 +5421,7 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
|
|
|
5117
5421
|
title,
|
|
5118
5422
|
section,
|
|
5119
5423
|
description: description || "(No description provided)",
|
|
5120
|
-
outputFilePath:
|
|
5424
|
+
outputFilePath: path14.join(prdDir, prdFilename),
|
|
5121
5425
|
prdDir
|
|
5122
5426
|
};
|
|
5123
5427
|
}
|
|
@@ -5211,8 +5515,8 @@ DO NOT forget to write the file.
|
|
|
5211
5515
|
});
|
|
5212
5516
|
|
|
5213
5517
|
// ../core/dist/utils/roadmap-scanner.js
|
|
5214
|
-
import * as
|
|
5215
|
-
import * as
|
|
5518
|
+
import * as fs16 from "fs";
|
|
5519
|
+
import * as path15 from "path";
|
|
5216
5520
|
import { spawn } from "child_process";
|
|
5217
5521
|
import { createHash as createHash3 } from "crypto";
|
|
5218
5522
|
function normalizeAuditSeverity(raw) {
|
|
@@ -5313,11 +5617,11 @@ function auditFindingToRoadmapItem(finding) {
|
|
|
5313
5617
|
};
|
|
5314
5618
|
}
|
|
5315
5619
|
function collectAuditPlannerItems(projectDir) {
|
|
5316
|
-
const reportPath =
|
|
5317
|
-
if (!
|
|
5620
|
+
const reportPath = path15.join(projectDir, "logs", "audit-report.md");
|
|
5621
|
+
if (!fs16.existsSync(reportPath)) {
|
|
5318
5622
|
return [];
|
|
5319
5623
|
}
|
|
5320
|
-
const reportContent =
|
|
5624
|
+
const reportContent = fs16.readFileSync(reportPath, "utf-8");
|
|
5321
5625
|
if (!reportContent.trim() || /\bNO_ISSUES_FOUND\b/.test(reportContent)) {
|
|
5322
5626
|
return [];
|
|
5323
5627
|
}
|
|
@@ -5326,9 +5630,9 @@ function collectAuditPlannerItems(projectDir) {
|
|
|
5326
5630
|
return findings.map(auditFindingToRoadmapItem);
|
|
5327
5631
|
}
|
|
5328
5632
|
function getRoadmapStatus(projectDir, config) {
|
|
5329
|
-
const roadmapPath =
|
|
5633
|
+
const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
5330
5634
|
const scannerEnabled = config.roadmapScanner.enabled;
|
|
5331
|
-
if (!
|
|
5635
|
+
if (!fs16.existsSync(roadmapPath)) {
|
|
5332
5636
|
return {
|
|
5333
5637
|
found: false,
|
|
5334
5638
|
enabled: scannerEnabled,
|
|
@@ -5339,9 +5643,9 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
5339
5643
|
items: []
|
|
5340
5644
|
};
|
|
5341
5645
|
}
|
|
5342
|
-
const content =
|
|
5646
|
+
const content = fs16.readFileSync(roadmapPath, "utf-8");
|
|
5343
5647
|
const items = parseRoadmap(content);
|
|
5344
|
-
const prdDir =
|
|
5648
|
+
const prdDir = path15.join(projectDir, config.prdDir);
|
|
5345
5649
|
const state = loadRoadmapState(prdDir);
|
|
5346
5650
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
5347
5651
|
const statusItems = items.map((item) => {
|
|
@@ -5378,10 +5682,10 @@ function getRoadmapStatus(projectDir, config) {
|
|
|
5378
5682
|
}
|
|
5379
5683
|
function scanExistingPrdSlugs(prdDir) {
|
|
5380
5684
|
const slugs = /* @__PURE__ */ new Set();
|
|
5381
|
-
if (!
|
|
5685
|
+
if (!fs16.existsSync(prdDir)) {
|
|
5382
5686
|
return slugs;
|
|
5383
5687
|
}
|
|
5384
|
-
const files =
|
|
5688
|
+
const files = fs16.readdirSync(prdDir);
|
|
5385
5689
|
for (const file of files) {
|
|
5386
5690
|
if (!file.endsWith(".md")) {
|
|
5387
5691
|
continue;
|
|
@@ -5417,20 +5721,20 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5417
5721
|
const nextNum = getNextPrdNumber(prdDir);
|
|
5418
5722
|
const padded = String(nextNum).padStart(2, "0");
|
|
5419
5723
|
const filename = `${padded}-${itemSlug}.md`;
|
|
5420
|
-
const filePath =
|
|
5421
|
-
if (!
|
|
5422
|
-
|
|
5724
|
+
const filePath = path15.join(prdDir, filename);
|
|
5725
|
+
if (!fs16.existsSync(prdDir)) {
|
|
5726
|
+
fs16.mkdirSync(prdDir, { recursive: true });
|
|
5423
5727
|
}
|
|
5424
5728
|
const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
|
|
5425
5729
|
const prompt2 = renderSlicerPrompt(promptVars);
|
|
5426
5730
|
const provider = resolveJobProvider(config, "slicer");
|
|
5427
5731
|
const providerArgs = buildProviderArgs(provider, prompt2, projectDir);
|
|
5428
|
-
const logDir =
|
|
5429
|
-
if (!
|
|
5430
|
-
|
|
5732
|
+
const logDir = path15.join(projectDir, "logs");
|
|
5733
|
+
if (!fs16.existsSync(logDir)) {
|
|
5734
|
+
fs16.mkdirSync(logDir, { recursive: true });
|
|
5431
5735
|
}
|
|
5432
|
-
const logFile =
|
|
5433
|
-
const logStream =
|
|
5736
|
+
const logFile = path15.join(logDir, `slicer-${itemSlug}.log`);
|
|
5737
|
+
const logStream = fs16.createWriteStream(logFile, { flags: "w" });
|
|
5434
5738
|
logStream.on("error", () => {
|
|
5435
5739
|
});
|
|
5436
5740
|
return new Promise((resolve10) => {
|
|
@@ -5467,7 +5771,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5467
5771
|
});
|
|
5468
5772
|
return;
|
|
5469
5773
|
}
|
|
5470
|
-
if (!
|
|
5774
|
+
if (!fs16.existsSync(filePath)) {
|
|
5471
5775
|
resolve10({
|
|
5472
5776
|
sliced: false,
|
|
5473
5777
|
error: `Provider did not create expected file: ${filePath}`,
|
|
@@ -5490,23 +5794,23 @@ async function sliceNextItem(projectDir, config) {
|
|
|
5490
5794
|
error: "Roadmap scanner is disabled"
|
|
5491
5795
|
};
|
|
5492
5796
|
}
|
|
5493
|
-
const roadmapPath =
|
|
5797
|
+
const roadmapPath = path15.join(projectDir, config.roadmapScanner.roadmapPath);
|
|
5494
5798
|
const auditItems = collectAuditPlannerItems(projectDir);
|
|
5495
|
-
const roadmapExists =
|
|
5799
|
+
const roadmapExists = fs16.existsSync(roadmapPath);
|
|
5496
5800
|
if (!roadmapExists && auditItems.length === 0) {
|
|
5497
5801
|
return {
|
|
5498
5802
|
sliced: false,
|
|
5499
5803
|
error: "No pending items to process"
|
|
5500
5804
|
};
|
|
5501
5805
|
}
|
|
5502
|
-
const roadmapItems = roadmapExists ? parseRoadmap(
|
|
5806
|
+
const roadmapItems = roadmapExists ? parseRoadmap(fs16.readFileSync(roadmapPath, "utf-8")) : [];
|
|
5503
5807
|
if (roadmapExists && roadmapItems.length === 0 && auditItems.length === 0) {
|
|
5504
5808
|
return {
|
|
5505
5809
|
sliced: false,
|
|
5506
5810
|
error: "No items in roadmap"
|
|
5507
5811
|
};
|
|
5508
5812
|
}
|
|
5509
|
-
const prdDir =
|
|
5813
|
+
const prdDir = path15.join(projectDir, config.prdDir);
|
|
5510
5814
|
const state = loadRoadmapState(prdDir);
|
|
5511
5815
|
const existingPrdSlugs = scanExistingPrdSlugs(prdDir);
|
|
5512
5816
|
const pickEligibleItem = (items) => {
|
|
@@ -5677,8 +5981,8 @@ var init_shell = __esm({
|
|
|
5677
5981
|
});
|
|
5678
5982
|
|
|
5679
5983
|
// ../core/dist/utils/scheduling.js
|
|
5680
|
-
import * as
|
|
5681
|
-
import * as
|
|
5984
|
+
import * as fs17 from "fs";
|
|
5985
|
+
import * as path16 from "path";
|
|
5682
5986
|
function normalizeSchedulingPriority(priority) {
|
|
5683
5987
|
if (!Number.isFinite(priority)) {
|
|
5684
5988
|
return DEFAULT_SCHEDULING_PRIORITY;
|
|
@@ -5704,7 +6008,7 @@ function isJobTypeEnabled(config, jobType) {
|
|
|
5704
6008
|
}
|
|
5705
6009
|
}
|
|
5706
6010
|
function loadPeerConfig(projectPath) {
|
|
5707
|
-
if (!
|
|
6011
|
+
if (!fs17.existsSync(projectPath) || !fs17.existsSync(path16.join(projectPath, CONFIG_FILE_NAME))) {
|
|
5708
6012
|
return null;
|
|
5709
6013
|
}
|
|
5710
6014
|
try {
|
|
@@ -5715,9 +6019,9 @@ function loadPeerConfig(projectPath) {
|
|
|
5715
6019
|
}
|
|
5716
6020
|
function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
5717
6021
|
const peers = /* @__PURE__ */ new Map();
|
|
5718
|
-
const currentPath =
|
|
6022
|
+
const currentPath = path16.resolve(currentProjectDir);
|
|
5719
6023
|
const addPeer = (projectPath, config) => {
|
|
5720
|
-
const resolvedPath =
|
|
6024
|
+
const resolvedPath = path16.resolve(projectPath);
|
|
5721
6025
|
if (!isJobTypeEnabled(config, jobType)) {
|
|
5722
6026
|
return;
|
|
5723
6027
|
}
|
|
@@ -5725,12 +6029,12 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
5725
6029
|
path: resolvedPath,
|
|
5726
6030
|
config,
|
|
5727
6031
|
schedulingPriority: normalizeSchedulingPriority(config.schedulingPriority),
|
|
5728
|
-
sortKey: `${
|
|
6032
|
+
sortKey: `${path16.basename(resolvedPath).toLowerCase()}::${resolvedPath.toLowerCase()}`
|
|
5729
6033
|
});
|
|
5730
6034
|
};
|
|
5731
6035
|
addPeer(currentPath, currentConfig);
|
|
5732
6036
|
for (const entry of loadRegistry()) {
|
|
5733
|
-
const resolvedPath =
|
|
6037
|
+
const resolvedPath = path16.resolve(entry.path);
|
|
5734
6038
|
if (resolvedPath === currentPath || peers.has(resolvedPath)) {
|
|
5735
6039
|
continue;
|
|
5736
6040
|
}
|
|
@@ -5748,7 +6052,7 @@ function collectSchedulingPeers(currentProjectDir, currentConfig, jobType) {
|
|
|
5748
6052
|
}
|
|
5749
6053
|
function getSchedulingPlan(projectDir, config, jobType) {
|
|
5750
6054
|
const peers = collectSchedulingPeers(projectDir, config, jobType);
|
|
5751
|
-
const currentPath =
|
|
6055
|
+
const currentPath = path16.resolve(projectDir);
|
|
5752
6056
|
const slotIndex = Math.max(0, peers.findIndex((peer) => peer.path === currentPath));
|
|
5753
6057
|
const peerCount = Math.max(1, peers.length);
|
|
5754
6058
|
const balancedDelayMinutes = peerCount <= 1 ? 0 : Math.floor(slotIndex * 60 / peerCount);
|
|
@@ -5840,8 +6144,8 @@ var init_webhook_validator = __esm({
|
|
|
5840
6144
|
|
|
5841
6145
|
// ../core/dist/utils/worktree-manager.js
|
|
5842
6146
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
5843
|
-
import * as
|
|
5844
|
-
import * as
|
|
6147
|
+
import * as fs18 from "fs";
|
|
6148
|
+
import * as path17 from "path";
|
|
5845
6149
|
function gitExec(args, cwd, logFile) {
|
|
5846
6150
|
try {
|
|
5847
6151
|
const result = execFileSync4("git", args, {
|
|
@@ -5851,7 +6155,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
5851
6155
|
});
|
|
5852
6156
|
if (logFile && result) {
|
|
5853
6157
|
try {
|
|
5854
|
-
|
|
6158
|
+
fs18.appendFileSync(logFile, result);
|
|
5855
6159
|
} catch {
|
|
5856
6160
|
}
|
|
5857
6161
|
}
|
|
@@ -5860,7 +6164,7 @@ function gitExec(args, cwd, logFile) {
|
|
|
5860
6164
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
5861
6165
|
if (logFile) {
|
|
5862
6166
|
try {
|
|
5863
|
-
|
|
6167
|
+
fs18.appendFileSync(logFile, errorMessage + "\n");
|
|
5864
6168
|
} catch {
|
|
5865
6169
|
}
|
|
5866
6170
|
}
|
|
@@ -5885,11 +6189,11 @@ function branchExistsRemotely(projectDir, branchName) {
|
|
|
5885
6189
|
}
|
|
5886
6190
|
function prepareBranchWorktree(options) {
|
|
5887
6191
|
const { projectDir, worktreeDir, branchName, defaultBranch, logFile } = options;
|
|
5888
|
-
if (
|
|
6192
|
+
if (fs18.existsSync(worktreeDir)) {
|
|
5889
6193
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
5890
6194
|
if (!isRegistered) {
|
|
5891
6195
|
try {
|
|
5892
|
-
|
|
6196
|
+
fs18.rmSync(worktreeDir, { recursive: true, force: true });
|
|
5893
6197
|
} catch {
|
|
5894
6198
|
}
|
|
5895
6199
|
}
|
|
@@ -5928,11 +6232,11 @@ function prepareBranchWorktree(options) {
|
|
|
5928
6232
|
}
|
|
5929
6233
|
function prepareDetachedWorktree(options) {
|
|
5930
6234
|
const { projectDir, worktreeDir, defaultBranch, logFile } = options;
|
|
5931
|
-
if (
|
|
6235
|
+
if (fs18.existsSync(worktreeDir)) {
|
|
5932
6236
|
const isRegistered = isWorktreeRegistered(projectDir, worktreeDir);
|
|
5933
6237
|
if (!isRegistered) {
|
|
5934
6238
|
try {
|
|
5935
|
-
|
|
6239
|
+
fs18.rmSync(worktreeDir, { recursive: true, force: true });
|
|
5936
6240
|
} catch {
|
|
5937
6241
|
}
|
|
5938
6242
|
}
|
|
@@ -5974,7 +6278,7 @@ function isWorktreeRegistered(projectDir, worktreePath) {
|
|
|
5974
6278
|
}
|
|
5975
6279
|
}
|
|
5976
6280
|
function cleanupWorktrees(projectDir, scope) {
|
|
5977
|
-
const projectName =
|
|
6281
|
+
const projectName = path17.basename(projectDir);
|
|
5978
6282
|
const matchToken = scope ? scope : `${projectName}-nw`;
|
|
5979
6283
|
const removed = [];
|
|
5980
6284
|
try {
|
|
@@ -6009,16 +6313,16 @@ var init_worktree_manager = __esm({
|
|
|
6009
6313
|
});
|
|
6010
6314
|
|
|
6011
6315
|
// ../core/dist/utils/job-queue.js
|
|
6012
|
-
import * as
|
|
6013
|
-
import * as
|
|
6316
|
+
import * as os7 from "os";
|
|
6317
|
+
import * as path18 from "path";
|
|
6014
6318
|
import Database7 from "better-sqlite3";
|
|
6015
6319
|
function getStateDbPath() {
|
|
6016
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
6017
|
-
return
|
|
6320
|
+
const base = process.env.NIGHT_WATCH_HOME || path18.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
6321
|
+
return path18.join(base, STATE_DB_FILE_NAME);
|
|
6018
6322
|
}
|
|
6019
6323
|
function getQueueLockPath() {
|
|
6020
|
-
const base = process.env.NIGHT_WATCH_HOME ||
|
|
6021
|
-
return
|
|
6324
|
+
const base = process.env.NIGHT_WATCH_HOME || path18.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
6325
|
+
return path18.join(base, QUEUE_LOCK_FILE_NAME);
|
|
6022
6326
|
}
|
|
6023
6327
|
function openDb() {
|
|
6024
6328
|
const dbPath = getStateDbPath();
|
|
@@ -6586,9 +6890,9 @@ var init_amplitude_client = __esm({
|
|
|
6586
6890
|
});
|
|
6587
6891
|
|
|
6588
6892
|
// ../core/dist/analytics/analytics-runner.js
|
|
6589
|
-
import * as
|
|
6590
|
-
import * as
|
|
6591
|
-
import * as
|
|
6893
|
+
import * as fs19 from "fs";
|
|
6894
|
+
import * as os8 from "os";
|
|
6895
|
+
import * as path19 from "path";
|
|
6592
6896
|
function parseIssuesFromResponse(text) {
|
|
6593
6897
|
const start = text.indexOf("[");
|
|
6594
6898
|
const end = text.lastIndexOf("]");
|
|
@@ -6617,9 +6921,9 @@ async function runAnalytics(config, projectDir) {
|
|
|
6617
6921
|
|
|
6618
6922
|
--- AMPLITUDE DATA ---
|
|
6619
6923
|
${JSON.stringify(data, null, 2)}`;
|
|
6620
|
-
const tmpDir =
|
|
6621
|
-
const promptFile =
|
|
6622
|
-
|
|
6924
|
+
const tmpDir = fs19.mkdtempSync(path19.join(os8.tmpdir(), "nw-analytics-"));
|
|
6925
|
+
const promptFile = path19.join(tmpDir, "analytics-prompt.md");
|
|
6926
|
+
fs19.writeFileSync(promptFile, prompt2, "utf-8");
|
|
6623
6927
|
try {
|
|
6624
6928
|
const provider = resolveJobProvider(config, "analytics");
|
|
6625
6929
|
const providerCmd = PROVIDER_COMMANDS[provider];
|
|
@@ -6636,8 +6940,8 @@ set -euo pipefail
|
|
|
6636
6940
|
${providerCmd} exec --yolo "$(cat ${promptFile})" 2>&1
|
|
6637
6941
|
`;
|
|
6638
6942
|
}
|
|
6639
|
-
const scriptFile =
|
|
6640
|
-
|
|
6943
|
+
const scriptFile = path19.join(tmpDir, "run-analytics.sh");
|
|
6944
|
+
fs19.writeFileSync(scriptFile, scriptContent, { mode: 493 });
|
|
6641
6945
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], config.providerEnv ?? {});
|
|
6642
6946
|
if (exitCode !== 0) {
|
|
6643
6947
|
throw new Error(`AI provider exited with code ${exitCode}: ${stderr || stdout}`);
|
|
@@ -6675,7 +6979,7 @@ ${stderr}`;
|
|
|
6675
6979
|
};
|
|
6676
6980
|
} finally {
|
|
6677
6981
|
try {
|
|
6678
|
-
|
|
6982
|
+
fs19.rmSync(tmpDir, { recursive: true, force: true });
|
|
6679
6983
|
} catch {
|
|
6680
6984
|
}
|
|
6681
6985
|
}
|
|
@@ -6876,6 +7180,14 @@ sequenceDiagram
|
|
|
6876
7180
|
}
|
|
6877
7181
|
});
|
|
6878
7182
|
|
|
7183
|
+
// ../core/dist/jobs/index.js
|
|
7184
|
+
var init_jobs = __esm({
|
|
7185
|
+
"../core/dist/jobs/index.js"() {
|
|
7186
|
+
"use strict";
|
|
7187
|
+
init_job_registry();
|
|
7188
|
+
}
|
|
7189
|
+
});
|
|
7190
|
+
|
|
6879
7191
|
// ../core/dist/index.js
|
|
6880
7192
|
var dist_exports = {};
|
|
6881
7193
|
__export(dist_exports, {
|
|
@@ -6953,9 +7265,11 @@ __export(dist_exports, {
|
|
|
6953
7265
|
EXECUTOR_LOG_FILE: () => EXECUTOR_LOG_FILE,
|
|
6954
7266
|
EXECUTOR_LOG_NAME: () => EXECUTOR_LOG_NAME,
|
|
6955
7267
|
GLOBAL_CONFIG_DIR: () => GLOBAL_CONFIG_DIR,
|
|
7268
|
+
GLOBAL_NOTIFICATIONS_FILE_NAME: () => GLOBAL_NOTIFICATIONS_FILE_NAME,
|
|
6956
7269
|
HISTORY_FILE_NAME: () => HISTORY_FILE_NAME,
|
|
6957
7270
|
HORIZON_LABELS: () => HORIZON_LABELS,
|
|
6958
7271
|
HORIZON_LABEL_INFO: () => HORIZON_LABEL_INFO,
|
|
7272
|
+
JOB_REGISTRY: () => JOB_REGISTRY,
|
|
6959
7273
|
LOCK_FILE_PREFIX: () => LOCK_FILE_PREFIX,
|
|
6960
7274
|
LOG_DIR: () => LOG_DIR,
|
|
6961
7275
|
LOG_FILE_NAMES: () => LOG_FILE_NAMES,
|
|
@@ -6987,7 +7301,9 @@ __export(dist_exports, {
|
|
|
6987
7301
|
analyticsLockPath: () => analyticsLockPath,
|
|
6988
7302
|
auditLockPath: () => auditLockPath,
|
|
6989
7303
|
buildDescription: () => buildDescription,
|
|
7304
|
+
buildJobEnvOverrides: () => buildJobEnvOverrides,
|
|
6990
7305
|
calculateStringSimilarity: () => calculateStringSimilarity,
|
|
7306
|
+
camelToUpperSnake: () => camelToUpperSnake,
|
|
6991
7307
|
canStartJob: () => canStartJob,
|
|
6992
7308
|
cancelProcess: () => cancelProcess,
|
|
6993
7309
|
checkConfigFile: () => checkConfigFile,
|
|
@@ -7052,23 +7368,30 @@ __export(dist_exports, {
|
|
|
7052
7368
|
formatTelegramPayload: () => formatTelegramPayload,
|
|
7053
7369
|
generateItemHash: () => generateItemHash,
|
|
7054
7370
|
generateMarker: () => generateMarker,
|
|
7371
|
+
getAllJobDefs: () => getAllJobDefs,
|
|
7055
7372
|
getBranchTipTimestamp: () => getBranchTipTimestamp,
|
|
7056
7373
|
getCrontabInfo: () => getCrontabInfo,
|
|
7057
7374
|
getDb: () => getDb,
|
|
7058
7375
|
getDbPath: () => getDbPath,
|
|
7059
7376
|
getDefaultConfig: () => getDefaultConfig,
|
|
7377
|
+
getDefaultQueuePriority: () => getDefaultQueuePriority,
|
|
7060
7378
|
getEntries: () => getEntries,
|
|
7061
7379
|
getEventColor: () => getEventColor,
|
|
7062
7380
|
getEventEmoji: () => getEventEmoji,
|
|
7063
7381
|
getEventTitle: () => getEventTitle,
|
|
7064
7382
|
getHistoryPath: () => getHistoryPath,
|
|
7065
7383
|
getInFlightCount: () => getInFlightCount,
|
|
7384
|
+
getJobDef: () => getJobDef,
|
|
7385
|
+
getJobDefByCommand: () => getJobDefByCommand,
|
|
7386
|
+
getJobDefByLogName: () => getJobDefByLogName,
|
|
7066
7387
|
getJobPriority: () => getJobPriority,
|
|
7067
7388
|
getJobRunsAnalytics: () => getJobRunsAnalytics,
|
|
7068
7389
|
getLabelsForSection: () => getLabelsForSection,
|
|
7069
7390
|
getLastExecution: () => getLastExecution,
|
|
7070
7391
|
getLastLogLines: () => getLastLogLines,
|
|
7071
7392
|
getLockFilePaths: () => getLockFilePaths,
|
|
7393
|
+
getLockSuffix: () => getLockSuffix,
|
|
7394
|
+
getLogFileNames: () => getLogFileNames,
|
|
7072
7395
|
getLogInfo: () => getLogInfo,
|
|
7073
7396
|
getNextPendingJob: () => getNextPendingJob,
|
|
7074
7397
|
getNextPrdNumber: () => getNextPrdNumber,
|
|
@@ -7089,6 +7412,7 @@ __export(dist_exports, {
|
|
|
7089
7412
|
getStateFilePath: () => getStateFilePath,
|
|
7090
7413
|
getStateItem: () => getStateItem,
|
|
7091
7414
|
getUncheckedItems: () => getUncheckedItems,
|
|
7415
|
+
getValidJobTypes: () => getValidJobTypes,
|
|
7092
7416
|
groupBySection: () => groupBySection2,
|
|
7093
7417
|
hasEntry: () => hasEntry,
|
|
7094
7418
|
hasNewItems: () => hasNewItems,
|
|
@@ -7106,6 +7430,7 @@ __export(dist_exports, {
|
|
|
7106
7430
|
label: () => label,
|
|
7107
7431
|
listPrdStatesByStatus: () => listPrdStatesByStatus,
|
|
7108
7432
|
loadConfig: () => loadConfig,
|
|
7433
|
+
loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
|
|
7109
7434
|
loadHistory: () => loadHistory,
|
|
7110
7435
|
loadRegistry: () => loadRegistry,
|
|
7111
7436
|
loadRoadmapState: () => loadRoadmapState,
|
|
@@ -7114,6 +7439,7 @@ __export(dist_exports, {
|
|
|
7114
7439
|
markJobRunning: () => markJobRunning,
|
|
7115
7440
|
markPrdDone: () => markPrdDone,
|
|
7116
7441
|
migrateJsonToSqlite: () => migrateJsonToSqlite,
|
|
7442
|
+
normalizeJobConfig: () => normalizeJobConfig,
|
|
7117
7443
|
normalizeSchedulingPriority: () => normalizeSchedulingPriority,
|
|
7118
7444
|
parsePrdDependencies: () => parsePrdDependencies,
|
|
7119
7445
|
parseRoadmap: () => parseRoadmap,
|
|
@@ -7123,6 +7449,7 @@ __export(dist_exports, {
|
|
|
7123
7449
|
prepareBranchWorktree: () => prepareBranchWorktree,
|
|
7124
7450
|
prepareDetachedWorktree: () => prepareDetachedWorktree,
|
|
7125
7451
|
projectRuntimeKey: () => projectRuntimeKey,
|
|
7452
|
+
pruneProjectData: () => pruneProjectData,
|
|
7126
7453
|
qaLockPath: () => qaLockPath,
|
|
7127
7454
|
readCrontab: () => readCrontab,
|
|
7128
7455
|
readPrdStates: () => readPrdStates,
|
|
@@ -7133,6 +7460,7 @@ __export(dist_exports, {
|
|
|
7133
7460
|
removeEntries: () => removeEntries,
|
|
7134
7461
|
removeEntriesForProject: () => removeEntriesForProject,
|
|
7135
7462
|
removeJob: () => removeJob,
|
|
7463
|
+
removeProject: () => removeProject,
|
|
7136
7464
|
renderPrdTemplate: () => renderPrdTemplate,
|
|
7137
7465
|
renderSlicerPrompt: () => renderSlicerPrompt,
|
|
7138
7466
|
resetRepositories: () => resetRepositories,
|
|
@@ -7146,6 +7474,7 @@ __export(dist_exports, {
|
|
|
7146
7474
|
runAnalytics: () => runAnalytics,
|
|
7147
7475
|
runMigrations: () => runMigrations,
|
|
7148
7476
|
saveConfig: () => saveConfig,
|
|
7477
|
+
saveGlobalNotificationsConfig: () => saveGlobalNotificationsConfig,
|
|
7149
7478
|
saveHistory: () => saveHistory,
|
|
7150
7479
|
saveRegistry: () => saveRegistry,
|
|
7151
7480
|
saveRoadmapState: () => saveRoadmapState,
|
|
@@ -7195,6 +7524,7 @@ var init_dist = __esm({
|
|
|
7195
7524
|
init_git_utils();
|
|
7196
7525
|
init_github();
|
|
7197
7526
|
init_log_utils();
|
|
7527
|
+
init_global_config();
|
|
7198
7528
|
init_notify();
|
|
7199
7529
|
init_prd_discovery();
|
|
7200
7530
|
init_prd_states();
|
|
@@ -7215,45 +7545,54 @@ var init_dist = __esm({
|
|
|
7215
7545
|
init_analytics();
|
|
7216
7546
|
init_prd_template();
|
|
7217
7547
|
init_slicer_prompt();
|
|
7548
|
+
init_jobs();
|
|
7218
7549
|
}
|
|
7219
7550
|
});
|
|
7220
7551
|
|
|
7221
7552
|
// src/cli.ts
|
|
7222
7553
|
import "reflect-metadata";
|
|
7223
7554
|
import { Command as Command3 } from "commander";
|
|
7224
|
-
import { existsSync as
|
|
7555
|
+
import { existsSync as existsSync30, readFileSync as readFileSync19 } from "fs";
|
|
7225
7556
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7226
|
-
import { dirname as
|
|
7557
|
+
import { dirname as dirname9, join as join36 } from "path";
|
|
7227
7558
|
|
|
7228
7559
|
// src/commands/init.ts
|
|
7229
7560
|
init_dist();
|
|
7230
|
-
import
|
|
7231
|
-
import
|
|
7561
|
+
import fs20 from "fs";
|
|
7562
|
+
import path20 from "path";
|
|
7232
7563
|
import { execSync as execSync3 } from "child_process";
|
|
7233
7564
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7234
|
-
import { dirname as
|
|
7565
|
+
import { dirname as dirname5, join as join18 } from "path";
|
|
7235
7566
|
import * as readline from "readline";
|
|
7236
7567
|
var __filename = fileURLToPath2(import.meta.url);
|
|
7237
|
-
var __dirname2 =
|
|
7568
|
+
var __dirname2 = dirname5(__filename);
|
|
7238
7569
|
function findTemplatesDir(startDir) {
|
|
7239
7570
|
let d = startDir;
|
|
7240
7571
|
for (let i = 0; i < 8; i++) {
|
|
7241
|
-
const candidate =
|
|
7242
|
-
if (
|
|
7572
|
+
const candidate = join18(d, "templates");
|
|
7573
|
+
if (fs20.existsSync(candidate) && fs20.statSync(candidate).isDirectory()) {
|
|
7243
7574
|
return candidate;
|
|
7244
7575
|
}
|
|
7245
|
-
d =
|
|
7576
|
+
d = dirname5(d);
|
|
7246
7577
|
}
|
|
7247
|
-
return
|
|
7578
|
+
return join18(startDir, "templates");
|
|
7248
7579
|
}
|
|
7249
7580
|
var TEMPLATES_DIR = findTemplatesDir(__dirname2);
|
|
7581
|
+
var NW_SKILLS = [
|
|
7582
|
+
"nw-create-prd",
|
|
7583
|
+
"nw-add-issue",
|
|
7584
|
+
"nw-run",
|
|
7585
|
+
"nw-slice",
|
|
7586
|
+
"nw-board-sync",
|
|
7587
|
+
"nw-review"
|
|
7588
|
+
];
|
|
7250
7589
|
function hasPlaywrightDependency(cwd) {
|
|
7251
|
-
const packageJsonPath =
|
|
7252
|
-
if (!
|
|
7590
|
+
const packageJsonPath = path20.join(cwd, "package.json");
|
|
7591
|
+
if (!fs20.existsSync(packageJsonPath)) {
|
|
7253
7592
|
return false;
|
|
7254
7593
|
}
|
|
7255
7594
|
try {
|
|
7256
|
-
const packageJson2 = JSON.parse(
|
|
7595
|
+
const packageJson2 = JSON.parse(fs20.readFileSync(packageJsonPath, "utf-8"));
|
|
7257
7596
|
return Boolean(
|
|
7258
7597
|
packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
|
|
7259
7598
|
);
|
|
@@ -7265,7 +7604,7 @@ function detectPlaywright(cwd) {
|
|
|
7265
7604
|
if (hasPlaywrightDependency(cwd)) {
|
|
7266
7605
|
return true;
|
|
7267
7606
|
}
|
|
7268
|
-
if (
|
|
7607
|
+
if (fs20.existsSync(path20.join(cwd, "node_modules", ".bin", "playwright"))) {
|
|
7269
7608
|
return true;
|
|
7270
7609
|
}
|
|
7271
7610
|
try {
|
|
@@ -7281,10 +7620,10 @@ function detectPlaywright(cwd) {
|
|
|
7281
7620
|
}
|
|
7282
7621
|
}
|
|
7283
7622
|
function resolvePlaywrightInstallCommand(cwd) {
|
|
7284
|
-
if (
|
|
7623
|
+
if (fs20.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
|
|
7285
7624
|
return "pnpm add -D @playwright/test";
|
|
7286
7625
|
}
|
|
7287
|
-
if (
|
|
7626
|
+
if (fs20.existsSync(path20.join(cwd, "yarn.lock"))) {
|
|
7288
7627
|
return "yarn add -D @playwright/test";
|
|
7289
7628
|
}
|
|
7290
7629
|
return "npm install -D @playwright/test";
|
|
@@ -7430,8 +7769,8 @@ function promptProviderSelection(providers) {
|
|
|
7430
7769
|
});
|
|
7431
7770
|
}
|
|
7432
7771
|
function ensureDir(dirPath) {
|
|
7433
|
-
if (!
|
|
7434
|
-
|
|
7772
|
+
if (!fs20.existsSync(dirPath)) {
|
|
7773
|
+
fs20.mkdirSync(dirPath, { recursive: true });
|
|
7435
7774
|
}
|
|
7436
7775
|
}
|
|
7437
7776
|
function buildInitConfig(params) {
|
|
@@ -7488,30 +7827,30 @@ function buildInitConfig(params) {
|
|
|
7488
7827
|
}
|
|
7489
7828
|
function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
|
|
7490
7829
|
if (customTemplatesDir !== null) {
|
|
7491
|
-
const customPath =
|
|
7492
|
-
if (
|
|
7830
|
+
const customPath = join18(customTemplatesDir, templateName);
|
|
7831
|
+
if (fs20.existsSync(customPath)) {
|
|
7493
7832
|
return { path: customPath, source: "custom" };
|
|
7494
7833
|
}
|
|
7495
7834
|
}
|
|
7496
|
-
return { path:
|
|
7835
|
+
return { path: join18(bundledTemplatesDir, templateName), source: "bundled" };
|
|
7497
7836
|
}
|
|
7498
7837
|
function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
|
|
7499
|
-
if (
|
|
7838
|
+
if (fs20.existsSync(targetPath) && !force) {
|
|
7500
7839
|
console.log(` Skipped (exists): ${targetPath}`);
|
|
7501
7840
|
return { created: false, source: source ?? "bundled" };
|
|
7502
7841
|
}
|
|
7503
|
-
const templatePath = sourcePath ??
|
|
7842
|
+
const templatePath = sourcePath ?? join18(TEMPLATES_DIR, templateName);
|
|
7504
7843
|
const resolvedSource = source ?? "bundled";
|
|
7505
|
-
let content =
|
|
7844
|
+
let content = fs20.readFileSync(templatePath, "utf-8");
|
|
7506
7845
|
for (const [key, value] of Object.entries(replacements)) {
|
|
7507
7846
|
content = content.replaceAll(key, value);
|
|
7508
7847
|
}
|
|
7509
|
-
|
|
7848
|
+
fs20.writeFileSync(targetPath, content);
|
|
7510
7849
|
console.log(` Created: ${targetPath} (${resolvedSource})`);
|
|
7511
7850
|
return { created: true, source: resolvedSource };
|
|
7512
7851
|
}
|
|
7513
7852
|
function addToGitignore(cwd) {
|
|
7514
|
-
const gitignorePath =
|
|
7853
|
+
const gitignorePath = path20.join(cwd, ".gitignore");
|
|
7515
7854
|
const entries = [
|
|
7516
7855
|
{
|
|
7517
7856
|
pattern: "/logs/",
|
|
@@ -7525,13 +7864,13 @@ function addToGitignore(cwd) {
|
|
|
7525
7864
|
},
|
|
7526
7865
|
{ pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
|
|
7527
7866
|
];
|
|
7528
|
-
if (!
|
|
7867
|
+
if (!fs20.existsSync(gitignorePath)) {
|
|
7529
7868
|
const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
|
|
7530
|
-
|
|
7869
|
+
fs20.writeFileSync(gitignorePath, lines.join("\n"));
|
|
7531
7870
|
console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
|
|
7532
7871
|
return;
|
|
7533
7872
|
}
|
|
7534
|
-
const content =
|
|
7873
|
+
const content = fs20.readFileSync(gitignorePath, "utf-8");
|
|
7535
7874
|
const missing = entries.filter((e) => !e.check(content));
|
|
7536
7875
|
if (missing.length === 0) {
|
|
7537
7876
|
console.log(` Skipped (exists): Night Watch entries in .gitignore`);
|
|
@@ -7539,15 +7878,70 @@ function addToGitignore(cwd) {
|
|
|
7539
7878
|
}
|
|
7540
7879
|
const additions = missing.map((e) => e.pattern).join("\n");
|
|
7541
7880
|
const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
|
|
7542
|
-
|
|
7881
|
+
fs20.writeFileSync(gitignorePath, newContent);
|
|
7543
7882
|
console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
|
|
7544
7883
|
}
|
|
7884
|
+
function installSkills(cwd, provider, force, templatesDir) {
|
|
7885
|
+
const skillsTemplatesDir = path20.join(templatesDir, "skills");
|
|
7886
|
+
if (!fs20.existsSync(skillsTemplatesDir)) {
|
|
7887
|
+
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
7888
|
+
}
|
|
7889
|
+
const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
|
|
7890
|
+
const isCodexProvider = provider === "codex";
|
|
7891
|
+
const claudeDir = path20.join(cwd, ".claude");
|
|
7892
|
+
if (isClaudeProvider || fs20.existsSync(claudeDir)) {
|
|
7893
|
+
ensureDir(claudeDir);
|
|
7894
|
+
const skillsDir = path20.join(claudeDir, "skills");
|
|
7895
|
+
ensureDir(skillsDir);
|
|
7896
|
+
let installed = 0;
|
|
7897
|
+
let skipped = 0;
|
|
7898
|
+
for (const skillName of NW_SKILLS) {
|
|
7899
|
+
const templateFile = path20.join(skillsTemplatesDir, `${skillName}.md`);
|
|
7900
|
+
if (!fs20.existsSync(templateFile)) continue;
|
|
7901
|
+
const skillDir = path20.join(skillsDir, skillName);
|
|
7902
|
+
ensureDir(skillDir);
|
|
7903
|
+
const target = path20.join(skillDir, "SKILL.md");
|
|
7904
|
+
if (fs20.existsSync(target) && !force) {
|
|
7905
|
+
skipped++;
|
|
7906
|
+
continue;
|
|
7907
|
+
}
|
|
7908
|
+
fs20.copyFileSync(templateFile, target);
|
|
7909
|
+
installed++;
|
|
7910
|
+
}
|
|
7911
|
+
return { location: ".claude/skills/", installed, skipped, type: "claude" };
|
|
7912
|
+
}
|
|
7913
|
+
if (isCodexProvider) {
|
|
7914
|
+
const agentsFile = path20.join(cwd, "AGENTS.md");
|
|
7915
|
+
const blockFile = path20.join(skillsTemplatesDir, "_codex-block.md");
|
|
7916
|
+
if (!fs20.existsSync(blockFile)) {
|
|
7917
|
+
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
7918
|
+
}
|
|
7919
|
+
const block = fs20.readFileSync(blockFile, "utf-8");
|
|
7920
|
+
const marker = "## Night Watch Skills";
|
|
7921
|
+
if (!fs20.existsSync(agentsFile)) {
|
|
7922
|
+
fs20.writeFileSync(agentsFile, block);
|
|
7923
|
+
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
7924
|
+
}
|
|
7925
|
+
const existing = fs20.readFileSync(agentsFile, "utf-8");
|
|
7926
|
+
if (existing.includes(marker)) {
|
|
7927
|
+
if (!force) {
|
|
7928
|
+
return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
|
|
7929
|
+
}
|
|
7930
|
+
const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
|
|
7931
|
+
fs20.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
|
|
7932
|
+
} else {
|
|
7933
|
+
fs20.appendFileSync(agentsFile, "\n\n" + block);
|
|
7934
|
+
}
|
|
7935
|
+
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
7936
|
+
}
|
|
7937
|
+
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
7938
|
+
}
|
|
7545
7939
|
function initCommand(program2) {
|
|
7546
7940
|
program2.command("init").description("Initialize night-watch in the current project").option("-f, --force", "Overwrite existing configuration").option("-d, --prd-dir <path>", "Path to PRD directory").option("-p, --provider <name>", "AI provider to use (claude or codex)").option("--no-reviewer", "Disable reviewer cron job").action(async (options) => {
|
|
7547
7941
|
const cwd = process.cwd();
|
|
7548
7942
|
const force = options.force || false;
|
|
7549
7943
|
const prdDir = options.prdDir || DEFAULT_PRD_DIR;
|
|
7550
|
-
const totalSteps =
|
|
7944
|
+
const totalSteps = 13;
|
|
7551
7945
|
const interactive = isInteractiveInitSession();
|
|
7552
7946
|
console.log();
|
|
7553
7947
|
header("Night Watch CLI - Initializing");
|
|
@@ -7660,28 +8054,28 @@ function initCommand(program2) {
|
|
|
7660
8054
|
"${DEFAULT_BRANCH}": defaultBranch
|
|
7661
8055
|
};
|
|
7662
8056
|
step(6, totalSteps, "Creating PRD directory structure...");
|
|
7663
|
-
const prdDirPath =
|
|
7664
|
-
const doneDirPath =
|
|
8057
|
+
const prdDirPath = path20.join(cwd, prdDir);
|
|
8058
|
+
const doneDirPath = path20.join(prdDirPath, "done");
|
|
7665
8059
|
ensureDir(doneDirPath);
|
|
7666
8060
|
success(`Created ${prdDirPath}/`);
|
|
7667
8061
|
success(`Created ${doneDirPath}/`);
|
|
7668
8062
|
step(7, totalSteps, "Creating logs directory...");
|
|
7669
|
-
const logsPath =
|
|
8063
|
+
const logsPath = path20.join(cwd, LOG_DIR);
|
|
7670
8064
|
ensureDir(logsPath);
|
|
7671
8065
|
success(`Created ${logsPath}/`);
|
|
7672
8066
|
addToGitignore(cwd);
|
|
7673
8067
|
step(8, totalSteps, "Creating instructions directory...");
|
|
7674
|
-
const instructionsDir =
|
|
8068
|
+
const instructionsDir = path20.join(cwd, "instructions");
|
|
7675
8069
|
ensureDir(instructionsDir);
|
|
7676
8070
|
success(`Created ${instructionsDir}/`);
|
|
7677
8071
|
const existingConfig = loadConfig(cwd);
|
|
7678
|
-
const customTemplatesDirPath =
|
|
7679
|
-
const customTemplatesDir =
|
|
8072
|
+
const customTemplatesDirPath = path20.join(cwd, existingConfig.templatesDir);
|
|
8073
|
+
const customTemplatesDir = fs20.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
|
|
7680
8074
|
const templateSources = [];
|
|
7681
8075
|
const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
|
|
7682
8076
|
const nwResult = processTemplate(
|
|
7683
8077
|
"executor.md",
|
|
7684
|
-
|
|
8078
|
+
path20.join(instructionsDir, "executor.md"),
|
|
7685
8079
|
replacements,
|
|
7686
8080
|
force,
|
|
7687
8081
|
nwResolution.path,
|
|
@@ -7695,7 +8089,7 @@ function initCommand(program2) {
|
|
|
7695
8089
|
);
|
|
7696
8090
|
const peResult = processTemplate(
|
|
7697
8091
|
"prd-executor.md",
|
|
7698
|
-
|
|
8092
|
+
path20.join(instructionsDir, "prd-executor.md"),
|
|
7699
8093
|
replacements,
|
|
7700
8094
|
force,
|
|
7701
8095
|
peResolution.path,
|
|
@@ -7705,7 +8099,7 @@ function initCommand(program2) {
|
|
|
7705
8099
|
const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
|
|
7706
8100
|
const prResult = processTemplate(
|
|
7707
8101
|
"pr-reviewer.md",
|
|
7708
|
-
|
|
8102
|
+
path20.join(instructionsDir, "pr-reviewer.md"),
|
|
7709
8103
|
replacements,
|
|
7710
8104
|
force,
|
|
7711
8105
|
prResolution.path,
|
|
@@ -7715,7 +8109,7 @@ function initCommand(program2) {
|
|
|
7715
8109
|
const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
|
|
7716
8110
|
const qaResult = processTemplate(
|
|
7717
8111
|
"qa.md",
|
|
7718
|
-
|
|
8112
|
+
path20.join(instructionsDir, "qa.md"),
|
|
7719
8113
|
replacements,
|
|
7720
8114
|
force,
|
|
7721
8115
|
qaResolution.path,
|
|
@@ -7725,7 +8119,7 @@ function initCommand(program2) {
|
|
|
7725
8119
|
const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
|
|
7726
8120
|
const auditResult = processTemplate(
|
|
7727
8121
|
"audit.md",
|
|
7728
|
-
|
|
8122
|
+
path20.join(instructionsDir, "audit.md"),
|
|
7729
8123
|
replacements,
|
|
7730
8124
|
force,
|
|
7731
8125
|
auditResolution.path,
|
|
@@ -7733,8 +8127,8 @@ function initCommand(program2) {
|
|
|
7733
8127
|
);
|
|
7734
8128
|
templateSources.push({ name: "audit.md", source: auditResult.source });
|
|
7735
8129
|
step(9, totalSteps, "Creating configuration file...");
|
|
7736
|
-
const configPath =
|
|
7737
|
-
if (
|
|
8130
|
+
const configPath = path20.join(cwd, CONFIG_FILE_NAME);
|
|
8131
|
+
if (fs20.existsSync(configPath) && !force) {
|
|
7738
8132
|
console.log(` Skipped (exists): ${configPath}`);
|
|
7739
8133
|
} else {
|
|
7740
8134
|
const config = buildInitConfig({
|
|
@@ -7744,11 +8138,11 @@ function initCommand(program2) {
|
|
|
7744
8138
|
reviewerEnabled,
|
|
7745
8139
|
prdDir
|
|
7746
8140
|
});
|
|
7747
|
-
|
|
8141
|
+
fs20.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
7748
8142
|
success(`Created ${configPath}`);
|
|
7749
8143
|
}
|
|
7750
8144
|
step(10, totalSteps, "Setting up GitHub Project board...");
|
|
7751
|
-
const existingRaw = JSON.parse(
|
|
8145
|
+
const existingRaw = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
|
|
7752
8146
|
const existingBoard = existingRaw.boardProvider;
|
|
7753
8147
|
let boardSetupStatus = "Skipped";
|
|
7754
8148
|
if (existingBoard?.projectNumber && !force) {
|
|
@@ -7770,13 +8164,13 @@ function initCommand(program2) {
|
|
|
7770
8164
|
const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
|
|
7771
8165
|
const boardTitle = `${projectName} Night Watch`;
|
|
7772
8166
|
const board = await provider.setupBoard(boardTitle);
|
|
7773
|
-
const rawConfig = JSON.parse(
|
|
8167
|
+
const rawConfig = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
|
|
7774
8168
|
rawConfig.boardProvider = {
|
|
7775
8169
|
enabled: true,
|
|
7776
8170
|
provider: "github",
|
|
7777
8171
|
projectNumber: board.number
|
|
7778
8172
|
};
|
|
7779
|
-
|
|
8173
|
+
fs20.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
|
|
7780
8174
|
boardSetupStatus = `Created (#${board.number})`;
|
|
7781
8175
|
success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
|
|
7782
8176
|
} catch (boardErr) {
|
|
@@ -7798,7 +8192,19 @@ function initCommand(program2) {
|
|
|
7798
8192
|
` Warning: Could not register in global registry: ${regErr instanceof Error ? regErr.message : String(regErr)}`
|
|
7799
8193
|
);
|
|
7800
8194
|
}
|
|
7801
|
-
step(12, totalSteps, "
|
|
8195
|
+
step(12, totalSteps, "Installing Night Watch skills...");
|
|
8196
|
+
const skillsResult = installSkills(cwd, selectedProvider, force, TEMPLATES_DIR);
|
|
8197
|
+
if (skillsResult.installed > 0) {
|
|
8198
|
+
success(`Installed ${skillsResult.installed} skills to ${skillsResult.location}`);
|
|
8199
|
+
for (const skillName of NW_SKILLS) {
|
|
8200
|
+
console.log(` /${skillName}`);
|
|
8201
|
+
}
|
|
8202
|
+
} else if (skillsResult.skipped > 0) {
|
|
8203
|
+
info(`Skills already installed (use --force to overwrite)`);
|
|
8204
|
+
} else if (skillsResult.type === "none") {
|
|
8205
|
+
info("No compatible AI skills directory detected \u2014 skipping.");
|
|
8206
|
+
}
|
|
8207
|
+
step(13, totalSteps, "Initialization complete!");
|
|
7802
8208
|
header("Initialization Complete");
|
|
7803
8209
|
const filesTable = createTable({ head: ["Created Files", ""] });
|
|
7804
8210
|
filesTable.push(["PRD Directory", `${prdDir}/done/`]);
|
|
@@ -7811,6 +8217,15 @@ function initCommand(program2) {
|
|
|
7811
8217
|
filesTable.push(["Config File", CONFIG_FILE_NAME]);
|
|
7812
8218
|
filesTable.push(["Board Setup", boardSetupStatus]);
|
|
7813
8219
|
filesTable.push(["Global Registry", "~/.night-watch/projects.json"]);
|
|
8220
|
+
let skillsSummary;
|
|
8221
|
+
if (skillsResult.installed > 0) {
|
|
8222
|
+
skillsSummary = `${skillsResult.installed} skills \u2192 ${skillsResult.location}`;
|
|
8223
|
+
} else if (skillsResult.skipped > 0) {
|
|
8224
|
+
skillsSummary = `Already installed (${skillsResult.location})`;
|
|
8225
|
+
} else {
|
|
8226
|
+
skillsSummary = "Skipped";
|
|
8227
|
+
}
|
|
8228
|
+
filesTable.push(["Skills", skillsSummary]);
|
|
7814
8229
|
console.log(filesTable.toString());
|
|
7815
8230
|
header("Configuration");
|
|
7816
8231
|
label("Provider", selectedProvider);
|
|
@@ -7822,6 +8237,9 @@ function initCommand(program2) {
|
|
|
7822
8237
|
info("2. Run `night-watch install` to set up cron jobs");
|
|
7823
8238
|
info("3. Run `night-watch doctor` to verify the full setup");
|
|
7824
8239
|
info("4. Or run `night-watch run` to execute PRDs manually");
|
|
8240
|
+
if (skillsResult.installed > 0) {
|
|
8241
|
+
info(`5. Use /nw-create-prd, /nw-run, /nw-add-issue and more in your AI assistant`);
|
|
8242
|
+
}
|
|
7825
8243
|
console.log();
|
|
7826
8244
|
});
|
|
7827
8245
|
}
|
|
@@ -7900,8 +8318,8 @@ function getTelegramStatusWebhooks(config) {
|
|
|
7900
8318
|
}
|
|
7901
8319
|
|
|
7902
8320
|
// src/commands/run.ts
|
|
7903
|
-
import * as
|
|
7904
|
-
import * as
|
|
8321
|
+
import * as fs21 from "fs";
|
|
8322
|
+
import * as path21 from "path";
|
|
7905
8323
|
function resolveRunNotificationEvent(exitCode, scriptStatus) {
|
|
7906
8324
|
if (exitCode === 124) {
|
|
7907
8325
|
return "run_timeout";
|
|
@@ -7933,12 +8351,12 @@ function shouldAttemptCrossProjectFallback(options, scriptStatus) {
|
|
|
7933
8351
|
return scriptStatus === "skip_no_eligible_prd";
|
|
7934
8352
|
}
|
|
7935
8353
|
function getCrossProjectFallbackCandidates(currentProjectDir) {
|
|
7936
|
-
const current =
|
|
8354
|
+
const current = path21.resolve(currentProjectDir);
|
|
7937
8355
|
const { valid, invalid } = validateRegistry();
|
|
7938
8356
|
for (const entry of invalid) {
|
|
7939
8357
|
warn(`Skipping invalid registry entry: ${entry.path}`);
|
|
7940
8358
|
}
|
|
7941
|
-
return valid.filter((entry) =>
|
|
8359
|
+
return valid.filter((entry) => path21.resolve(entry.path) !== current);
|
|
7942
8360
|
}
|
|
7943
8361
|
async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult) {
|
|
7944
8362
|
if (isRateLimitFallbackTriggered(scriptResult?.data)) {
|
|
@@ -7948,7 +8366,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
7948
8366
|
if (nonTelegramWebhooks.length > 0) {
|
|
7949
8367
|
const _rateLimitCtx = {
|
|
7950
8368
|
event: "rate_limit_fallback",
|
|
7951
|
-
projectName:
|
|
8369
|
+
projectName: path21.basename(projectDir),
|
|
7952
8370
|
exitCode,
|
|
7953
8371
|
provider: config.provider
|
|
7954
8372
|
};
|
|
@@ -7976,7 +8394,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
7976
8394
|
const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
|
|
7977
8395
|
const _ctx = {
|
|
7978
8396
|
event,
|
|
7979
|
-
projectName:
|
|
8397
|
+
projectName: path21.basename(projectDir),
|
|
7980
8398
|
exitCode,
|
|
7981
8399
|
provider: config.provider,
|
|
7982
8400
|
prdName: scriptResult?.data.prd,
|
|
@@ -8138,20 +8556,20 @@ function applyCliOverrides(config, options) {
|
|
|
8138
8556
|
return overridden;
|
|
8139
8557
|
}
|
|
8140
8558
|
function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
8141
|
-
const absolutePrdDir =
|
|
8142
|
-
const doneDir =
|
|
8559
|
+
const absolutePrdDir = path21.join(projectDir, prdDir);
|
|
8560
|
+
const doneDir = path21.join(absolutePrdDir, "done");
|
|
8143
8561
|
const pending = [];
|
|
8144
8562
|
const completed = [];
|
|
8145
|
-
if (
|
|
8146
|
-
const entries =
|
|
8563
|
+
if (fs21.existsSync(absolutePrdDir)) {
|
|
8564
|
+
const entries = fs21.readdirSync(absolutePrdDir, { withFileTypes: true });
|
|
8147
8565
|
for (const entry of entries) {
|
|
8148
8566
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8149
|
-
const claimPath =
|
|
8567
|
+
const claimPath = path21.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
|
|
8150
8568
|
let claimed = false;
|
|
8151
8569
|
let claimInfo = null;
|
|
8152
|
-
if (
|
|
8570
|
+
if (fs21.existsSync(claimPath)) {
|
|
8153
8571
|
try {
|
|
8154
|
-
const content =
|
|
8572
|
+
const content = fs21.readFileSync(claimPath, "utf-8");
|
|
8155
8573
|
const data = JSON.parse(content);
|
|
8156
8574
|
const age = Math.floor(Date.now() / 1e3) - data.timestamp;
|
|
8157
8575
|
if (age < maxRuntime) {
|
|
@@ -8165,8 +8583,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
|
8165
8583
|
}
|
|
8166
8584
|
}
|
|
8167
8585
|
}
|
|
8168
|
-
if (
|
|
8169
|
-
const entries =
|
|
8586
|
+
if (fs21.existsSync(doneDir)) {
|
|
8587
|
+
const entries = fs21.readdirSync(doneDir, { withFileTypes: true });
|
|
8170
8588
|
for (const entry of entries) {
|
|
8171
8589
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8172
8590
|
completed.push(entry.name);
|
|
@@ -8326,7 +8744,7 @@ ${stderr}`);
|
|
|
8326
8744
|
// src/commands/review.ts
|
|
8327
8745
|
init_dist();
|
|
8328
8746
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
8329
|
-
import * as
|
|
8747
|
+
import * as path22 from "path";
|
|
8330
8748
|
function shouldSendReviewNotification(scriptStatus) {
|
|
8331
8749
|
if (!scriptStatus) {
|
|
8332
8750
|
return true;
|
|
@@ -8566,7 +8984,7 @@ ${stderr}`);
|
|
|
8566
8984
|
const finalScore = parseFinalReviewScore(scriptResult?.data.final_score);
|
|
8567
8985
|
const _reviewCtx = {
|
|
8568
8986
|
event: "review_completed",
|
|
8569
|
-
projectName:
|
|
8987
|
+
projectName: path22.basename(projectDir),
|
|
8570
8988
|
exitCode,
|
|
8571
8989
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
8572
8990
|
prUrl: prDetails?.url,
|
|
@@ -8587,7 +9005,7 @@ ${stderr}`);
|
|
|
8587
9005
|
const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
|
|
8588
9006
|
const _mergeCtx = {
|
|
8589
9007
|
event: "pr_auto_merged",
|
|
8590
|
-
projectName:
|
|
9008
|
+
projectName: path22.basename(projectDir),
|
|
8591
9009
|
exitCode,
|
|
8592
9010
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
8593
9011
|
prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
|
|
@@ -8612,7 +9030,7 @@ ${stderr}`);
|
|
|
8612
9030
|
|
|
8613
9031
|
// src/commands/qa.ts
|
|
8614
9032
|
init_dist();
|
|
8615
|
-
import * as
|
|
9033
|
+
import * as path23 from "path";
|
|
8616
9034
|
function shouldSendQaNotification(scriptStatus) {
|
|
8617
9035
|
if (!scriptStatus) {
|
|
8618
9036
|
return true;
|
|
@@ -8746,7 +9164,7 @@ ${stderr}`);
|
|
|
8746
9164
|
const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
|
|
8747
9165
|
const _qaCtx = {
|
|
8748
9166
|
event: "qa_completed",
|
|
8749
|
-
projectName:
|
|
9167
|
+
projectName: path23.basename(projectDir),
|
|
8750
9168
|
exitCode,
|
|
8751
9169
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
8752
9170
|
prNumber: prDetails?.number ?? primaryQaPr,
|
|
@@ -8772,8 +9190,8 @@ ${stderr}`);
|
|
|
8772
9190
|
|
|
8773
9191
|
// src/commands/audit.ts
|
|
8774
9192
|
init_dist();
|
|
8775
|
-
import * as
|
|
8776
|
-
import * as
|
|
9193
|
+
import * as fs22 from "fs";
|
|
9194
|
+
import * as path24 from "path";
|
|
8777
9195
|
function buildEnvVars4(config, options) {
|
|
8778
9196
|
const env = buildBaseEnvVars(config, "audit", options.dryRun);
|
|
8779
9197
|
env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
|
|
@@ -8816,7 +9234,7 @@ function auditCommand(program2) {
|
|
|
8816
9234
|
configTable.push(["Provider", auditProvider]);
|
|
8817
9235
|
configTable.push(["Provider CLI", PROVIDER_COMMANDS[auditProvider]]);
|
|
8818
9236
|
configTable.push(["Max Runtime", `${config.audit.maxRuntime}s`]);
|
|
8819
|
-
configTable.push(["Report File",
|
|
9237
|
+
configTable.push(["Report File", path24.join(projectDir, "logs", "audit-report.md")]);
|
|
8820
9238
|
console.log(configTable.toString());
|
|
8821
9239
|
header("Provider Invocation");
|
|
8822
9240
|
const providerCmd = PROVIDER_COMMANDS[auditProvider];
|
|
@@ -8851,8 +9269,8 @@ ${stderr}`);
|
|
|
8851
9269
|
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
8852
9270
|
spinner.succeed("Code audit skipped");
|
|
8853
9271
|
} else {
|
|
8854
|
-
const reportPath =
|
|
8855
|
-
if (!
|
|
9272
|
+
const reportPath = path24.join(projectDir, "logs", "audit-report.md");
|
|
9273
|
+
if (!fs22.existsSync(reportPath)) {
|
|
8856
9274
|
spinner.fail("Code audit finished without a report file");
|
|
8857
9275
|
process.exit(1);
|
|
8858
9276
|
}
|
|
@@ -8863,9 +9281,9 @@ ${stderr}`);
|
|
|
8863
9281
|
const providerExit = scriptResult?.data?.provider_exit;
|
|
8864
9282
|
const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
|
|
8865
9283
|
spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
|
|
8866
|
-
const logPath =
|
|
8867
|
-
if (
|
|
8868
|
-
const logLines =
|
|
9284
|
+
const logPath = path24.join(projectDir, "logs", "audit.log");
|
|
9285
|
+
if (fs22.existsSync(logPath)) {
|
|
9286
|
+
const logLines = fs22.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
|
|
8869
9287
|
if (logLines.length > 0) {
|
|
8870
9288
|
process.stderr.write(logLines.join("\n") + "\n");
|
|
8871
9289
|
}
|
|
@@ -8943,16 +9361,16 @@ function analyticsCommand(program2) {
|
|
|
8943
9361
|
// src/commands/install.ts
|
|
8944
9362
|
init_dist();
|
|
8945
9363
|
import { execSync as execSync4 } from "child_process";
|
|
8946
|
-
import * as
|
|
8947
|
-
import * as
|
|
9364
|
+
import * as path25 from "path";
|
|
9365
|
+
import * as fs23 from "fs";
|
|
8948
9366
|
function shellQuote(value) {
|
|
8949
9367
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
8950
9368
|
}
|
|
8951
9369
|
function getNightWatchBinPath() {
|
|
8952
9370
|
try {
|
|
8953
9371
|
const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
|
|
8954
|
-
const binPath =
|
|
8955
|
-
if (
|
|
9372
|
+
const binPath = path25.join(npmBin, "night-watch");
|
|
9373
|
+
if (fs23.existsSync(binPath)) {
|
|
8956
9374
|
return binPath;
|
|
8957
9375
|
}
|
|
8958
9376
|
} catch {
|
|
@@ -8965,17 +9383,17 @@ function getNightWatchBinPath() {
|
|
|
8965
9383
|
}
|
|
8966
9384
|
function getNodeBinDir() {
|
|
8967
9385
|
if (process.execPath && process.execPath !== "node") {
|
|
8968
|
-
return
|
|
9386
|
+
return path25.dirname(process.execPath);
|
|
8969
9387
|
}
|
|
8970
9388
|
try {
|
|
8971
9389
|
const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
|
|
8972
|
-
return
|
|
9390
|
+
return path25.dirname(nodePath);
|
|
8973
9391
|
} catch {
|
|
8974
9392
|
return "";
|
|
8975
9393
|
}
|
|
8976
9394
|
}
|
|
8977
9395
|
function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
|
|
8978
|
-
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ?
|
|
9396
|
+
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path25.dirname(nightWatchBin) : "";
|
|
8979
9397
|
const pathParts = Array.from(
|
|
8980
9398
|
new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
|
|
8981
9399
|
);
|
|
@@ -8991,12 +9409,12 @@ function performInstall(projectDir, config, options) {
|
|
|
8991
9409
|
const nightWatchBin = getNightWatchBinPath();
|
|
8992
9410
|
const projectName = getProjectName(projectDir);
|
|
8993
9411
|
const marker = generateMarker(projectName);
|
|
8994
|
-
const logDir =
|
|
8995
|
-
if (!
|
|
8996
|
-
|
|
9412
|
+
const logDir = path25.join(projectDir, LOG_DIR);
|
|
9413
|
+
if (!fs23.existsSync(logDir)) {
|
|
9414
|
+
fs23.mkdirSync(logDir, { recursive: true });
|
|
8997
9415
|
}
|
|
8998
|
-
const executorLog =
|
|
8999
|
-
const reviewerLog =
|
|
9416
|
+
const executorLog = path25.join(logDir, "executor.log");
|
|
9417
|
+
const reviewerLog = path25.join(logDir, "reviewer.log");
|
|
9000
9418
|
if (!options?.force) {
|
|
9001
9419
|
const existingEntries2 = Array.from(
|
|
9002
9420
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
@@ -9033,7 +9451,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9033
9451
|
const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
9034
9452
|
if (installSlicer) {
|
|
9035
9453
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
9036
|
-
const slicerLog =
|
|
9454
|
+
const slicerLog = path25.join(logDir, "slicer.log");
|
|
9037
9455
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
9038
9456
|
entries.push(slicerEntry);
|
|
9039
9457
|
}
|
|
@@ -9041,7 +9459,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9041
9459
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
9042
9460
|
if (installQa) {
|
|
9043
9461
|
const qaSchedule = config.qa.schedule;
|
|
9044
|
-
const qaLog =
|
|
9462
|
+
const qaLog = path25.join(logDir, "qa.log");
|
|
9045
9463
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
9046
9464
|
entries.push(qaEntry);
|
|
9047
9465
|
}
|
|
@@ -9049,7 +9467,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9049
9467
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
9050
9468
|
if (installAudit) {
|
|
9051
9469
|
const auditSchedule = config.audit.schedule;
|
|
9052
|
-
const auditLog =
|
|
9470
|
+
const auditLog = path25.join(logDir, "audit.log");
|
|
9053
9471
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
9054
9472
|
entries.push(auditEntry);
|
|
9055
9473
|
}
|
|
@@ -9057,7 +9475,7 @@ function performInstall(projectDir, config, options) {
|
|
|
9057
9475
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
9058
9476
|
if (installAnalytics) {
|
|
9059
9477
|
const analyticsSchedule = config.analytics.schedule;
|
|
9060
|
-
const analyticsLog =
|
|
9478
|
+
const analyticsLog = path25.join(logDir, "analytics.log");
|
|
9061
9479
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
9062
9480
|
entries.push(analyticsEntry);
|
|
9063
9481
|
}
|
|
@@ -9088,12 +9506,12 @@ function installCommand(program2) {
|
|
|
9088
9506
|
const nightWatchBin = getNightWatchBinPath();
|
|
9089
9507
|
const projectName = getProjectName(projectDir);
|
|
9090
9508
|
const marker = generateMarker(projectName);
|
|
9091
|
-
const logDir =
|
|
9092
|
-
if (!
|
|
9093
|
-
|
|
9509
|
+
const logDir = path25.join(projectDir, LOG_DIR);
|
|
9510
|
+
if (!fs23.existsSync(logDir)) {
|
|
9511
|
+
fs23.mkdirSync(logDir, { recursive: true });
|
|
9094
9512
|
}
|
|
9095
|
-
const executorLog =
|
|
9096
|
-
const reviewerLog =
|
|
9513
|
+
const executorLog = path25.join(logDir, "executor.log");
|
|
9514
|
+
const reviewerLog = path25.join(logDir, "reviewer.log");
|
|
9097
9515
|
const existingEntries = Array.from(
|
|
9098
9516
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
9099
9517
|
);
|
|
@@ -9129,7 +9547,7 @@ function installCommand(program2) {
|
|
|
9129
9547
|
const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
9130
9548
|
let slicerLog;
|
|
9131
9549
|
if (installSlicer) {
|
|
9132
|
-
slicerLog =
|
|
9550
|
+
slicerLog = path25.join(logDir, "slicer.log");
|
|
9133
9551
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
9134
9552
|
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} planner >> ${shellQuote(slicerLog)} 2>&1 ${marker}`;
|
|
9135
9553
|
entries.push(slicerEntry);
|
|
@@ -9138,7 +9556,7 @@ function installCommand(program2) {
|
|
|
9138
9556
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
9139
9557
|
let qaLog;
|
|
9140
9558
|
if (installQa) {
|
|
9141
|
-
qaLog =
|
|
9559
|
+
qaLog = path25.join(logDir, "qa.log");
|
|
9142
9560
|
const qaSchedule = config.qa.schedule;
|
|
9143
9561
|
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} qa >> ${shellQuote(qaLog)} 2>&1 ${marker}`;
|
|
9144
9562
|
entries.push(qaEntry);
|
|
@@ -9147,7 +9565,7 @@ function installCommand(program2) {
|
|
|
9147
9565
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
9148
9566
|
let auditLog;
|
|
9149
9567
|
if (installAudit) {
|
|
9150
|
-
auditLog =
|
|
9568
|
+
auditLog = path25.join(logDir, "audit.log");
|
|
9151
9569
|
const auditSchedule = config.audit.schedule;
|
|
9152
9570
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
9153
9571
|
entries.push(auditEntry);
|
|
@@ -9156,7 +9574,7 @@ function installCommand(program2) {
|
|
|
9156
9574
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
9157
9575
|
let analyticsLog;
|
|
9158
9576
|
if (installAnalytics) {
|
|
9159
|
-
analyticsLog =
|
|
9577
|
+
analyticsLog = path25.join(logDir, "analytics.log");
|
|
9160
9578
|
const analyticsSchedule = config.analytics.schedule;
|
|
9161
9579
|
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} analytics >> ${shellQuote(analyticsLog)} 2>&1 ${marker}`;
|
|
9162
9580
|
entries.push(analyticsEntry);
|
|
@@ -9204,8 +9622,8 @@ function installCommand(program2) {
|
|
|
9204
9622
|
|
|
9205
9623
|
// src/commands/uninstall.ts
|
|
9206
9624
|
init_dist();
|
|
9207
|
-
import * as
|
|
9208
|
-
import * as
|
|
9625
|
+
import * as path26 from "path";
|
|
9626
|
+
import * as fs24 from "fs";
|
|
9209
9627
|
function performUninstall(projectDir, options) {
|
|
9210
9628
|
try {
|
|
9211
9629
|
const projectName = getProjectName(projectDir);
|
|
@@ -9220,19 +9638,19 @@ function performUninstall(projectDir, options) {
|
|
|
9220
9638
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
9221
9639
|
unregisterProject(projectDir);
|
|
9222
9640
|
if (!options?.keepLogs) {
|
|
9223
|
-
const logDir =
|
|
9224
|
-
if (
|
|
9641
|
+
const logDir = path26.join(projectDir, "logs");
|
|
9642
|
+
if (fs24.existsSync(logDir)) {
|
|
9225
9643
|
const logFiles = ["executor.log", "reviewer.log", "slicer.log", "audit.log"];
|
|
9226
9644
|
logFiles.forEach((logFile) => {
|
|
9227
|
-
const logPath =
|
|
9228
|
-
if (
|
|
9229
|
-
|
|
9645
|
+
const logPath = path26.join(logDir, logFile);
|
|
9646
|
+
if (fs24.existsSync(logPath)) {
|
|
9647
|
+
fs24.unlinkSync(logPath);
|
|
9230
9648
|
}
|
|
9231
9649
|
});
|
|
9232
9650
|
try {
|
|
9233
|
-
const remainingFiles =
|
|
9651
|
+
const remainingFiles = fs24.readdirSync(logDir);
|
|
9234
9652
|
if (remainingFiles.length === 0) {
|
|
9235
|
-
|
|
9653
|
+
fs24.rmdirSync(logDir);
|
|
9236
9654
|
}
|
|
9237
9655
|
} catch {
|
|
9238
9656
|
}
|
|
@@ -9265,21 +9683,21 @@ function uninstallCommand(program2) {
|
|
|
9265
9683
|
existingEntries.forEach((entry) => dim(` ${entry}`));
|
|
9266
9684
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
9267
9685
|
if (!options.keepLogs) {
|
|
9268
|
-
const logDir =
|
|
9269
|
-
if (
|
|
9686
|
+
const logDir = path26.join(projectDir, "logs");
|
|
9687
|
+
if (fs24.existsSync(logDir)) {
|
|
9270
9688
|
const logFiles = ["executor.log", "reviewer.log", "slicer.log", "audit.log"];
|
|
9271
9689
|
let logsRemoved = 0;
|
|
9272
9690
|
logFiles.forEach((logFile) => {
|
|
9273
|
-
const logPath =
|
|
9274
|
-
if (
|
|
9275
|
-
|
|
9691
|
+
const logPath = path26.join(logDir, logFile);
|
|
9692
|
+
if (fs24.existsSync(logPath)) {
|
|
9693
|
+
fs24.unlinkSync(logPath);
|
|
9276
9694
|
logsRemoved++;
|
|
9277
9695
|
}
|
|
9278
9696
|
});
|
|
9279
9697
|
try {
|
|
9280
|
-
const remainingFiles =
|
|
9698
|
+
const remainingFiles = fs24.readdirSync(logDir);
|
|
9281
9699
|
if (remainingFiles.length === 0) {
|
|
9282
|
-
|
|
9700
|
+
fs24.rmdirSync(logDir);
|
|
9283
9701
|
}
|
|
9284
9702
|
} catch {
|
|
9285
9703
|
}
|
|
@@ -9515,14 +9933,14 @@ function statusCommand(program2) {
|
|
|
9515
9933
|
// src/commands/logs.ts
|
|
9516
9934
|
init_dist();
|
|
9517
9935
|
import { spawn as spawn3 } from "child_process";
|
|
9518
|
-
import * as
|
|
9519
|
-
import * as
|
|
9936
|
+
import * as path27 from "path";
|
|
9937
|
+
import * as fs25 from "fs";
|
|
9520
9938
|
function getLastLines(filePath, lineCount) {
|
|
9521
|
-
if (!
|
|
9939
|
+
if (!fs25.existsSync(filePath)) {
|
|
9522
9940
|
return `Log file not found: ${filePath}`;
|
|
9523
9941
|
}
|
|
9524
9942
|
try {
|
|
9525
|
-
const content =
|
|
9943
|
+
const content = fs25.readFileSync(filePath, "utf-8");
|
|
9526
9944
|
const lines = content.trim().split("\n");
|
|
9527
9945
|
return lines.slice(-lineCount).join("\n");
|
|
9528
9946
|
} catch (error2) {
|
|
@@ -9530,7 +9948,7 @@ function getLastLines(filePath, lineCount) {
|
|
|
9530
9948
|
}
|
|
9531
9949
|
}
|
|
9532
9950
|
function followLog(filePath) {
|
|
9533
|
-
if (!
|
|
9951
|
+
if (!fs25.existsSync(filePath)) {
|
|
9534
9952
|
console.log(`Log file not found: ${filePath}`);
|
|
9535
9953
|
console.log("The log file will be created when the first execution runs.");
|
|
9536
9954
|
return;
|
|
@@ -9550,13 +9968,13 @@ function logsCommand(program2) {
|
|
|
9550
9968
|
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
9969
|
try {
|
|
9552
9970
|
const projectDir = process.cwd();
|
|
9553
|
-
const logDir =
|
|
9971
|
+
const logDir = path27.join(projectDir, LOG_DIR);
|
|
9554
9972
|
const lineCount = parseInt(options.lines || "50", 10);
|
|
9555
|
-
const executorLog =
|
|
9556
|
-
const reviewerLog =
|
|
9557
|
-
const qaLog =
|
|
9558
|
-
const auditLog =
|
|
9559
|
-
const plannerLog =
|
|
9973
|
+
const executorLog = path27.join(logDir, EXECUTOR_LOG_FILE);
|
|
9974
|
+
const reviewerLog = path27.join(logDir, REVIEWER_LOG_FILE);
|
|
9975
|
+
const qaLog = path27.join(logDir, `${QA_LOG_NAME}.log`);
|
|
9976
|
+
const auditLog = path27.join(logDir, `${AUDIT_LOG_NAME}.log`);
|
|
9977
|
+
const plannerLog = path27.join(logDir, `${PLANNER_LOG_NAME}.log`);
|
|
9560
9978
|
const logType = options.type?.toLowerCase() || "all";
|
|
9561
9979
|
const showExecutor = logType === "all" || logType === "run" || logType === "executor";
|
|
9562
9980
|
const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
|
|
@@ -9620,15 +10038,15 @@ function logsCommand(program2) {
|
|
|
9620
10038
|
|
|
9621
10039
|
// src/commands/prd.ts
|
|
9622
10040
|
init_dist();
|
|
9623
|
-
import * as
|
|
9624
|
-
import * as
|
|
10041
|
+
import * as fs26 from "fs";
|
|
10042
|
+
import * as path28 from "path";
|
|
9625
10043
|
import * as readline2 from "readline";
|
|
9626
10044
|
function slugify2(name) {
|
|
9627
10045
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
9628
10046
|
}
|
|
9629
10047
|
function getNextPrdNumber2(prdDir) {
|
|
9630
|
-
if (!
|
|
9631
|
-
const files =
|
|
10048
|
+
if (!fs26.existsSync(prdDir)) return 1;
|
|
10049
|
+
const files = fs26.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
9632
10050
|
const numbers = files.map((f) => {
|
|
9633
10051
|
const match = f.match(/^(\d+)-/);
|
|
9634
10052
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -9649,10 +10067,10 @@ function parseDependencies(content) {
|
|
|
9649
10067
|
}
|
|
9650
10068
|
function isClaimActive(claimPath, maxRuntime) {
|
|
9651
10069
|
try {
|
|
9652
|
-
if (!
|
|
10070
|
+
if (!fs26.existsSync(claimPath)) {
|
|
9653
10071
|
return { active: false };
|
|
9654
10072
|
}
|
|
9655
|
-
const content =
|
|
10073
|
+
const content = fs26.readFileSync(claimPath, "utf-8");
|
|
9656
10074
|
const claim = JSON.parse(content);
|
|
9657
10075
|
const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
|
|
9658
10076
|
if (age < maxRuntime) {
|
|
@@ -9668,9 +10086,9 @@ function prdCommand(program2) {
|
|
|
9668
10086
|
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
10087
|
const projectDir = process.cwd();
|
|
9670
10088
|
const config = loadConfig(projectDir);
|
|
9671
|
-
const prdDir =
|
|
9672
|
-
if (!
|
|
9673
|
-
|
|
10089
|
+
const prdDir = path28.join(projectDir, config.prdDir);
|
|
10090
|
+
if (!fs26.existsSync(prdDir)) {
|
|
10091
|
+
fs26.mkdirSync(prdDir, { recursive: true });
|
|
9674
10092
|
}
|
|
9675
10093
|
let complexityScore = 5;
|
|
9676
10094
|
let dependsOn = [];
|
|
@@ -9729,20 +10147,20 @@ function prdCommand(program2) {
|
|
|
9729
10147
|
} else {
|
|
9730
10148
|
filename = `${slug}.md`;
|
|
9731
10149
|
}
|
|
9732
|
-
const filePath =
|
|
9733
|
-
if (
|
|
10150
|
+
const filePath = path28.join(prdDir, filename);
|
|
10151
|
+
if (fs26.existsSync(filePath)) {
|
|
9734
10152
|
error(`File already exists: ${filePath}`);
|
|
9735
10153
|
dim("Use a different name or remove the existing file.");
|
|
9736
10154
|
process.exit(1);
|
|
9737
10155
|
}
|
|
9738
10156
|
let customTemplate;
|
|
9739
10157
|
if (options.template) {
|
|
9740
|
-
const templatePath =
|
|
9741
|
-
if (!
|
|
10158
|
+
const templatePath = path28.resolve(options.template);
|
|
10159
|
+
if (!fs26.existsSync(templatePath)) {
|
|
9742
10160
|
error(`Template file not found: ${templatePath}`);
|
|
9743
10161
|
process.exit(1);
|
|
9744
10162
|
}
|
|
9745
|
-
customTemplate =
|
|
10163
|
+
customTemplate = fs26.readFileSync(templatePath, "utf-8");
|
|
9746
10164
|
}
|
|
9747
10165
|
const vars = {
|
|
9748
10166
|
title: name,
|
|
@@ -9753,7 +10171,7 @@ function prdCommand(program2) {
|
|
|
9753
10171
|
phaseCount
|
|
9754
10172
|
};
|
|
9755
10173
|
const content = renderPrdTemplate(vars, customTemplate);
|
|
9756
|
-
|
|
10174
|
+
fs26.writeFileSync(filePath, content, "utf-8");
|
|
9757
10175
|
header("PRD Created");
|
|
9758
10176
|
success(`Created: ${filePath}`);
|
|
9759
10177
|
info(`Title: ${name}`);
|
|
@@ -9765,15 +10183,15 @@ function prdCommand(program2) {
|
|
|
9765
10183
|
prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
|
|
9766
10184
|
const projectDir = process.cwd();
|
|
9767
10185
|
const config = loadConfig(projectDir);
|
|
9768
|
-
const absolutePrdDir =
|
|
9769
|
-
const doneDir =
|
|
10186
|
+
const absolutePrdDir = path28.join(projectDir, config.prdDir);
|
|
10187
|
+
const doneDir = path28.join(absolutePrdDir, "done");
|
|
9770
10188
|
const pending = [];
|
|
9771
|
-
if (
|
|
9772
|
-
const files =
|
|
10189
|
+
if (fs26.existsSync(absolutePrdDir)) {
|
|
10190
|
+
const files = fs26.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
|
|
9773
10191
|
for (const file of files) {
|
|
9774
|
-
const content =
|
|
10192
|
+
const content = fs26.readFileSync(path28.join(absolutePrdDir, file), "utf-8");
|
|
9775
10193
|
const deps = parseDependencies(content);
|
|
9776
|
-
const claimPath =
|
|
10194
|
+
const claimPath = path28.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
|
|
9777
10195
|
const claimStatus = isClaimActive(claimPath, config.maxRuntime);
|
|
9778
10196
|
pending.push({
|
|
9779
10197
|
name: file,
|
|
@@ -9784,10 +10202,10 @@ function prdCommand(program2) {
|
|
|
9784
10202
|
}
|
|
9785
10203
|
}
|
|
9786
10204
|
const done = [];
|
|
9787
|
-
if (
|
|
9788
|
-
const files =
|
|
10205
|
+
if (fs26.existsSync(doneDir)) {
|
|
10206
|
+
const files = fs26.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
9789
10207
|
for (const file of files) {
|
|
9790
|
-
const content =
|
|
10208
|
+
const content = fs26.readFileSync(path28.join(doneDir, file), "utf-8");
|
|
9791
10209
|
const deps = parseDependencies(content);
|
|
9792
10210
|
done.push({ name: file, dependencies: deps });
|
|
9793
10211
|
}
|
|
@@ -9826,7 +10244,7 @@ import blessed6 from "blessed";
|
|
|
9826
10244
|
// src/commands/dashboard/tab-status.ts
|
|
9827
10245
|
init_dist();
|
|
9828
10246
|
import blessed from "blessed";
|
|
9829
|
-
import * as
|
|
10247
|
+
import * as fs27 from "fs";
|
|
9830
10248
|
function sortPrdsByPriority(prds, priority) {
|
|
9831
10249
|
if (priority.length === 0) return prds;
|
|
9832
10250
|
const priorityMap = /* @__PURE__ */ new Map();
|
|
@@ -9922,7 +10340,7 @@ function renderLogPane(projectDir, logs) {
|
|
|
9922
10340
|
let newestMtime = 0;
|
|
9923
10341
|
for (const log of existingLogs) {
|
|
9924
10342
|
try {
|
|
9925
|
-
const stat =
|
|
10343
|
+
const stat = fs27.statSync(log.path);
|
|
9926
10344
|
if (stat.mtimeMs > newestMtime) {
|
|
9927
10345
|
newestMtime = stat.mtimeMs;
|
|
9928
10346
|
newestLog = log;
|
|
@@ -11577,8 +11995,8 @@ function createActionsTab() {
|
|
|
11577
11995
|
// src/commands/dashboard/tab-logs.ts
|
|
11578
11996
|
init_dist();
|
|
11579
11997
|
import blessed5 from "blessed";
|
|
11580
|
-
import * as
|
|
11581
|
-
import * as
|
|
11998
|
+
import * as fs28 from "fs";
|
|
11999
|
+
import * as path29 from "path";
|
|
11582
12000
|
var LOG_NAMES = ["executor", "reviewer"];
|
|
11583
12001
|
var LOG_LINES = 200;
|
|
11584
12002
|
function createLogsTab() {
|
|
@@ -11619,7 +12037,7 @@ function createLogsTab() {
|
|
|
11619
12037
|
let activeKeyHandlers = [];
|
|
11620
12038
|
let activeCtx = null;
|
|
11621
12039
|
function getLogPath(projectDir, logName) {
|
|
11622
|
-
return
|
|
12040
|
+
return path29.join(projectDir, "logs", `${logName}.log`);
|
|
11623
12041
|
}
|
|
11624
12042
|
function updateSelector() {
|
|
11625
12043
|
const tabs = LOG_NAMES.map((name, idx) => {
|
|
@@ -11633,7 +12051,7 @@ function createLogsTab() {
|
|
|
11633
12051
|
function loadLog(ctx) {
|
|
11634
12052
|
const logName = LOG_NAMES[selectedLogIndex];
|
|
11635
12053
|
const logPath = getLogPath(ctx.projectDir, logName);
|
|
11636
|
-
if (!
|
|
12054
|
+
if (!fs28.existsSync(logPath)) {
|
|
11637
12055
|
logContent.setContent(
|
|
11638
12056
|
`{yellow-fg}No ${logName}.log file found{/yellow-fg}
|
|
11639
12057
|
|
|
@@ -11643,7 +12061,7 @@ Log will appear here once the ${logName} runs.`
|
|
|
11643
12061
|
return;
|
|
11644
12062
|
}
|
|
11645
12063
|
try {
|
|
11646
|
-
const stat =
|
|
12064
|
+
const stat = fs28.statSync(logPath);
|
|
11647
12065
|
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
11648
12066
|
logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
|
|
11649
12067
|
} catch {
|
|
@@ -12045,7 +12463,7 @@ function doctorCommand(program2) {
|
|
|
12045
12463
|
program2.command("doctor").description("Check Night Watch configuration and system health").option("--fix", "Automatically fix fixable issues").action(async (options) => {
|
|
12046
12464
|
const projectDir = process.cwd();
|
|
12047
12465
|
const config = loadConfig(projectDir);
|
|
12048
|
-
const totalChecks =
|
|
12466
|
+
const totalChecks = 7;
|
|
12049
12467
|
let checkNum = 1;
|
|
12050
12468
|
let passedChecks = 0;
|
|
12051
12469
|
let fixedChecks = 0;
|
|
@@ -12071,11 +12489,12 @@ function doctorCommand(program2) {
|
|
|
12071
12489
|
const ghResult = runCheck(checkNum++, totalChecks, "GitHub CLI", () => checkGhCli(), options);
|
|
12072
12490
|
if (ghResult.passed) passedChecks++;
|
|
12073
12491
|
if (ghResult.fixed) fixedChecks++;
|
|
12492
|
+
const resolvedProviderCli = BUILT_IN_PRESETS[config.provider]?.command ?? config.provider;
|
|
12074
12493
|
const providerResult = runCheck(
|
|
12075
12494
|
checkNum++,
|
|
12076
12495
|
totalChecks,
|
|
12077
12496
|
"provider CLI",
|
|
12078
|
-
() => checkProviderCli(
|
|
12497
|
+
() => checkProviderCli(resolvedProviderCli),
|
|
12079
12498
|
options
|
|
12080
12499
|
);
|
|
12081
12500
|
if (providerResult.passed) passedChecks++;
|
|
@@ -12089,15 +12508,6 @@ function doctorCommand(program2) {
|
|
|
12089
12508
|
);
|
|
12090
12509
|
if (configResult.passed) passedChecks++;
|
|
12091
12510
|
if (configResult.fixed) fixedChecks++;
|
|
12092
|
-
const prdResult = runCheck(
|
|
12093
|
-
checkNum++,
|
|
12094
|
-
totalChecks,
|
|
12095
|
-
"PRD directory",
|
|
12096
|
-
() => checkPrdDirectory(projectDir, config.prdDir),
|
|
12097
|
-
options
|
|
12098
|
-
);
|
|
12099
|
-
if (prdResult.passed) passedChecks++;
|
|
12100
|
-
if (prdResult.fixed) fixedChecks++;
|
|
12101
12511
|
const logsResult = runCheck(
|
|
12102
12512
|
checkNum++,
|
|
12103
12513
|
totalChecks,
|
|
@@ -12149,13 +12559,13 @@ function doctorCommand(program2) {
|
|
|
12149
12559
|
|
|
12150
12560
|
// src/commands/serve.ts
|
|
12151
12561
|
init_dist();
|
|
12152
|
-
import * as
|
|
12562
|
+
import * as fs33 from "fs";
|
|
12153
12563
|
|
|
12154
12564
|
// ../server/dist/index.js
|
|
12155
12565
|
init_dist();
|
|
12156
|
-
import * as
|
|
12157
|
-
import * as
|
|
12158
|
-
import { dirname as
|
|
12566
|
+
import * as fs32 from "fs";
|
|
12567
|
+
import * as path35 from "path";
|
|
12568
|
+
import { dirname as dirname8 } from "path";
|
|
12159
12569
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12160
12570
|
import cors from "cors";
|
|
12161
12571
|
import express from "express";
|
|
@@ -12239,8 +12649,8 @@ function setupGracefulShutdown(server, beforeClose) {
|
|
|
12239
12649
|
|
|
12240
12650
|
// ../server/dist/middleware/project-resolver.middleware.js
|
|
12241
12651
|
init_dist();
|
|
12242
|
-
import * as
|
|
12243
|
-
import * as
|
|
12652
|
+
import * as fs29 from "fs";
|
|
12653
|
+
import * as path30 from "path";
|
|
12244
12654
|
function resolveProject(req, res, next) {
|
|
12245
12655
|
const projectId = req.params.projectId;
|
|
12246
12656
|
const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
|
|
@@ -12250,7 +12660,7 @@ function resolveProject(req, res, next) {
|
|
|
12250
12660
|
res.status(404).json({ error: `Project not found: ${decodedId}` });
|
|
12251
12661
|
return;
|
|
12252
12662
|
}
|
|
12253
|
-
if (!
|
|
12663
|
+
if (!fs29.existsSync(entry.path) || !fs29.existsSync(path30.join(entry.path, CONFIG_FILE_NAME))) {
|
|
12254
12664
|
res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
|
|
12255
12665
|
return;
|
|
12256
12666
|
}
|
|
@@ -12295,8 +12705,8 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
|
|
|
12295
12705
|
|
|
12296
12706
|
// ../server/dist/routes/action.routes.js
|
|
12297
12707
|
init_dist();
|
|
12298
|
-
import * as
|
|
12299
|
-
import * as
|
|
12708
|
+
import * as fs30 from "fs";
|
|
12709
|
+
import * as path31 from "path";
|
|
12300
12710
|
import { execSync as execSync5, spawn as spawn5 } from "child_process";
|
|
12301
12711
|
import { Router } from "express";
|
|
12302
12712
|
|
|
@@ -12334,17 +12744,17 @@ function getBoardProvider(config, projectDir) {
|
|
|
12334
12744
|
function cleanOrphanedClaims(dir) {
|
|
12335
12745
|
let entries;
|
|
12336
12746
|
try {
|
|
12337
|
-
entries =
|
|
12747
|
+
entries = fs30.readdirSync(dir, { withFileTypes: true });
|
|
12338
12748
|
} catch {
|
|
12339
12749
|
return;
|
|
12340
12750
|
}
|
|
12341
12751
|
for (const entry of entries) {
|
|
12342
|
-
const fullPath =
|
|
12752
|
+
const fullPath = path31.join(dir, entry.name);
|
|
12343
12753
|
if (entry.isDirectory() && entry.name !== "done") {
|
|
12344
12754
|
cleanOrphanedClaims(fullPath);
|
|
12345
12755
|
} else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
|
|
12346
12756
|
try {
|
|
12347
|
-
|
|
12757
|
+
fs30.unlinkSync(fullPath);
|
|
12348
12758
|
} catch {
|
|
12349
12759
|
}
|
|
12350
12760
|
}
|
|
@@ -12435,18 +12845,16 @@ function createActionRouteHandlers(ctx) {
|
|
|
12435
12845
|
router.post(`/${p}review`, (req, res) => {
|
|
12436
12846
|
spawnAction2(ctx.getProjectDir(req), ["review"], req, res);
|
|
12437
12847
|
});
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
spawnAction2(ctx.getProjectDir(req), ["planner"], req, res);
|
|
12449
|
-
});
|
|
12848
|
+
for (const jobDef of JOB_REGISTRY) {
|
|
12849
|
+
if (jobDef.id === "executor")
|
|
12850
|
+
continue;
|
|
12851
|
+
if (jobDef.id === "reviewer")
|
|
12852
|
+
continue;
|
|
12853
|
+
const cmd = jobDef.cliCommand;
|
|
12854
|
+
router.post(`/${p}${cmd}`, (req, res) => {
|
|
12855
|
+
spawnAction2(ctx.getProjectDir(req), [cmd], req, res);
|
|
12856
|
+
});
|
|
12857
|
+
}
|
|
12450
12858
|
router.post(`/${p}install-cron`, (req, res) => {
|
|
12451
12859
|
const projectDir = ctx.getProjectDir(req);
|
|
12452
12860
|
try {
|
|
@@ -12501,19 +12909,19 @@ function createActionRouteHandlers(ctx) {
|
|
|
12501
12909
|
res.status(400).json({ error: "Invalid PRD name" });
|
|
12502
12910
|
return;
|
|
12503
12911
|
}
|
|
12504
|
-
const prdDir =
|
|
12912
|
+
const prdDir = path31.join(projectDir, config.prdDir);
|
|
12505
12913
|
const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
|
|
12506
|
-
const pendingPath =
|
|
12507
|
-
const donePath =
|
|
12508
|
-
if (
|
|
12914
|
+
const pendingPath = path31.join(prdDir, normalized);
|
|
12915
|
+
const donePath = path31.join(prdDir, "done", normalized);
|
|
12916
|
+
if (fs30.existsSync(pendingPath)) {
|
|
12509
12917
|
res.json({ message: `"${normalized}" is already pending` });
|
|
12510
12918
|
return;
|
|
12511
12919
|
}
|
|
12512
|
-
if (!
|
|
12920
|
+
if (!fs30.existsSync(donePath)) {
|
|
12513
12921
|
res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
|
|
12514
12922
|
return;
|
|
12515
12923
|
}
|
|
12516
|
-
|
|
12924
|
+
fs30.renameSync(donePath, pendingPath);
|
|
12517
12925
|
res.json({ message: `Moved "${normalized}" back to pending` });
|
|
12518
12926
|
} catch (error2) {
|
|
12519
12927
|
res.status(500).json({
|
|
@@ -12531,11 +12939,11 @@ function createActionRouteHandlers(ctx) {
|
|
|
12531
12939
|
res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
|
|
12532
12940
|
return;
|
|
12533
12941
|
}
|
|
12534
|
-
if (
|
|
12535
|
-
|
|
12942
|
+
if (fs30.existsSync(lockPath)) {
|
|
12943
|
+
fs30.unlinkSync(lockPath);
|
|
12536
12944
|
}
|
|
12537
|
-
const prdDir =
|
|
12538
|
-
if (
|
|
12945
|
+
const prdDir = path31.join(projectDir, config.prdDir);
|
|
12946
|
+
if (fs30.existsSync(prdDir)) {
|
|
12539
12947
|
cleanOrphanedClaims(prdDir);
|
|
12540
12948
|
}
|
|
12541
12949
|
broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
|
|
@@ -13194,6 +13602,26 @@ function createConfigRoutes(deps) {
|
|
|
13194
13602
|
});
|
|
13195
13603
|
return router;
|
|
13196
13604
|
}
|
|
13605
|
+
function createGlobalNotificationsRoutes() {
|
|
13606
|
+
const router = Router3();
|
|
13607
|
+
router.get("/", (_req, res) => {
|
|
13608
|
+
res.json(loadGlobalNotificationsConfig());
|
|
13609
|
+
});
|
|
13610
|
+
router.put("/", (req, res) => {
|
|
13611
|
+
const { webhook } = req.body;
|
|
13612
|
+
if (webhook !== null && webhook !== void 0) {
|
|
13613
|
+
const issues = validateWebhook(webhook);
|
|
13614
|
+
if (issues.length > 0) {
|
|
13615
|
+
res.status(400).json({ error: `Invalid webhook: ${issues.join(", ")}` });
|
|
13616
|
+
return;
|
|
13617
|
+
}
|
|
13618
|
+
}
|
|
13619
|
+
const config = { webhook: webhook ?? null };
|
|
13620
|
+
saveGlobalNotificationsConfig(config);
|
|
13621
|
+
res.json(loadGlobalNotificationsConfig());
|
|
13622
|
+
});
|
|
13623
|
+
return router;
|
|
13624
|
+
}
|
|
13197
13625
|
function createProjectConfigRoutes() {
|
|
13198
13626
|
const router = Router3({ mergeParams: true });
|
|
13199
13627
|
router.get("/config", (req, res) => {
|
|
@@ -13227,8 +13655,8 @@ function createProjectConfigRoutes() {
|
|
|
13227
13655
|
|
|
13228
13656
|
// ../server/dist/routes/doctor.routes.js
|
|
13229
13657
|
init_dist();
|
|
13230
|
-
import * as
|
|
13231
|
-
import * as
|
|
13658
|
+
import * as fs31 from "fs";
|
|
13659
|
+
import * as path32 from "path";
|
|
13232
13660
|
import { execSync as execSync6 } from "child_process";
|
|
13233
13661
|
import { Router as Router4 } from "express";
|
|
13234
13662
|
function runDoctorChecks(projectDir, config) {
|
|
@@ -13261,7 +13689,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
13261
13689
|
});
|
|
13262
13690
|
}
|
|
13263
13691
|
try {
|
|
13264
|
-
const projectName =
|
|
13692
|
+
const projectName = path32.basename(projectDir);
|
|
13265
13693
|
const marker = generateMarker(projectName);
|
|
13266
13694
|
const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
|
|
13267
13695
|
if (crontabEntries.length > 0) {
|
|
@@ -13284,8 +13712,8 @@ function runDoctorChecks(projectDir, config) {
|
|
|
13284
13712
|
detail: "Failed to check crontab"
|
|
13285
13713
|
});
|
|
13286
13714
|
}
|
|
13287
|
-
const configPath =
|
|
13288
|
-
if (
|
|
13715
|
+
const configPath = path32.join(projectDir, CONFIG_FILE_NAME);
|
|
13716
|
+
if (fs31.existsSync(configPath)) {
|
|
13289
13717
|
checks.push({ name: "config", status: "pass", detail: "Config file exists" });
|
|
13290
13718
|
} else {
|
|
13291
13719
|
checks.push({
|
|
@@ -13294,9 +13722,9 @@ function runDoctorChecks(projectDir, config) {
|
|
|
13294
13722
|
detail: "Config file not found (using defaults)"
|
|
13295
13723
|
});
|
|
13296
13724
|
}
|
|
13297
|
-
const prdDir =
|
|
13298
|
-
if (
|
|
13299
|
-
const prds =
|
|
13725
|
+
const prdDir = path32.join(projectDir, config.prdDir);
|
|
13726
|
+
if (fs31.existsSync(prdDir)) {
|
|
13727
|
+
const prds = fs31.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
13300
13728
|
checks.push({
|
|
13301
13729
|
name: "prdDir",
|
|
13302
13730
|
status: "pass",
|
|
@@ -13339,7 +13767,7 @@ function createProjectDoctorRoutes() {
|
|
|
13339
13767
|
|
|
13340
13768
|
// ../server/dist/routes/log.routes.js
|
|
13341
13769
|
init_dist();
|
|
13342
|
-
import * as
|
|
13770
|
+
import * as path33 from "path";
|
|
13343
13771
|
import { Router as Router5 } from "express";
|
|
13344
13772
|
function createLogRoutes(deps) {
|
|
13345
13773
|
const { projectDir } = deps;
|
|
@@ -13358,7 +13786,7 @@ function createLogRoutes(deps) {
|
|
|
13358
13786
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
13359
13787
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
13360
13788
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
13361
|
-
const logPath =
|
|
13789
|
+
const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
13362
13790
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
13363
13791
|
res.json({ name, lines: logLines });
|
|
13364
13792
|
} catch (error2) {
|
|
@@ -13384,7 +13812,7 @@ function createProjectLogRoutes() {
|
|
|
13384
13812
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
13385
13813
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
13386
13814
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
13387
|
-
const logPath =
|
|
13815
|
+
const logPath = path33.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
13388
13816
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
13389
13817
|
res.json({ name, lines: logLines });
|
|
13390
13818
|
} catch (error2) {
|
|
@@ -13419,7 +13847,7 @@ function createProjectPrdRoutes() {
|
|
|
13419
13847
|
|
|
13420
13848
|
// ../server/dist/routes/roadmap.routes.js
|
|
13421
13849
|
init_dist();
|
|
13422
|
-
import * as
|
|
13850
|
+
import * as path34 from "path";
|
|
13423
13851
|
import { Router as Router7 } from "express";
|
|
13424
13852
|
function createRoadmapRouteHandlers(ctx) {
|
|
13425
13853
|
const router = Router7({ mergeParams: true });
|
|
@@ -13429,7 +13857,7 @@ function createRoadmapRouteHandlers(ctx) {
|
|
|
13429
13857
|
const config = ctx.getConfig(req);
|
|
13430
13858
|
const projectDir = ctx.getProjectDir(req);
|
|
13431
13859
|
const status = getRoadmapStatus(projectDir, config);
|
|
13432
|
-
const prdDir =
|
|
13860
|
+
const prdDir = path34.join(projectDir, config.prdDir);
|
|
13433
13861
|
const state = loadRoadmapState(prdDir);
|
|
13434
13862
|
res.json({
|
|
13435
13863
|
...status,
|
|
@@ -13761,26 +14189,26 @@ function createQueueRoutes(deps) {
|
|
|
13761
14189
|
|
|
13762
14190
|
// ../server/dist/index.js
|
|
13763
14191
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
13764
|
-
var __dirname3 =
|
|
14192
|
+
var __dirname3 = dirname8(__filename2);
|
|
13765
14193
|
function resolveWebDistPath() {
|
|
13766
|
-
const bundled =
|
|
13767
|
-
if (
|
|
14194
|
+
const bundled = path35.join(__dirname3, "web");
|
|
14195
|
+
if (fs32.existsSync(path35.join(bundled, "index.html")))
|
|
13768
14196
|
return bundled;
|
|
13769
14197
|
let d = __dirname3;
|
|
13770
14198
|
for (let i = 0; i < 8; i++) {
|
|
13771
|
-
if (
|
|
13772
|
-
const dev =
|
|
13773
|
-
if (
|
|
14199
|
+
if (fs32.existsSync(path35.join(d, "turbo.json"))) {
|
|
14200
|
+
const dev = path35.join(d, "web/dist");
|
|
14201
|
+
if (fs32.existsSync(path35.join(dev, "index.html")))
|
|
13774
14202
|
return dev;
|
|
13775
14203
|
break;
|
|
13776
14204
|
}
|
|
13777
|
-
d =
|
|
14205
|
+
d = dirname8(d);
|
|
13778
14206
|
}
|
|
13779
14207
|
return bundled;
|
|
13780
14208
|
}
|
|
13781
14209
|
function setupStaticFiles(app) {
|
|
13782
14210
|
const webDistPath = resolveWebDistPath();
|
|
13783
|
-
if (
|
|
14211
|
+
if (fs32.existsSync(webDistPath)) {
|
|
13784
14212
|
app.use(express.static(webDistPath));
|
|
13785
14213
|
}
|
|
13786
14214
|
app.use((req, res, next) => {
|
|
@@ -13788,8 +14216,8 @@ function setupStaticFiles(app) {
|
|
|
13788
14216
|
next();
|
|
13789
14217
|
return;
|
|
13790
14218
|
}
|
|
13791
|
-
const indexPath =
|
|
13792
|
-
if (
|
|
14219
|
+
const indexPath = path35.resolve(webDistPath, "index.html");
|
|
14220
|
+
if (fs32.existsSync(indexPath)) {
|
|
13793
14221
|
res.sendFile(indexPath, (err) => {
|
|
13794
14222
|
if (err)
|
|
13795
14223
|
next();
|
|
@@ -13827,6 +14255,7 @@ function createApp(projectDir) {
|
|
|
13827
14255
|
app.use("/api/logs", createLogRoutes({ projectDir }));
|
|
13828
14256
|
app.use("/api/doctor", createDoctorRoutes({ projectDir, getConfig: () => config }));
|
|
13829
14257
|
app.use("/api/queue", createQueueRoutes({ getConfig: () => config }));
|
|
14258
|
+
app.use("/api/global-notifications", createGlobalNotificationsRoutes());
|
|
13830
14259
|
app.get("/api/prs", async (_req, res) => {
|
|
13831
14260
|
try {
|
|
13832
14261
|
res.json(await collectPrInfo(projectDir, config.branchPatterns));
|
|
@@ -13898,14 +14327,30 @@ function createGlobalApp() {
|
|
|
13898
14327
|
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
13899
14328
|
}
|
|
13900
14329
|
});
|
|
14330
|
+
app.delete("/api/projects/:projectId", (req, res) => {
|
|
14331
|
+
try {
|
|
14332
|
+
const rawId = decodeURIComponent(String(req.params.projectId)).replace(/~/g, "/");
|
|
14333
|
+
const entries = loadRegistry();
|
|
14334
|
+
const entry = entries.find((e) => e.name === rawId);
|
|
14335
|
+
if (!entry) {
|
|
14336
|
+
res.status(404).json({ error: "Project not found" });
|
|
14337
|
+
return;
|
|
14338
|
+
}
|
|
14339
|
+
const result = removeProject(entry.path);
|
|
14340
|
+
res.json(result);
|
|
14341
|
+
} catch (error2) {
|
|
14342
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
14343
|
+
}
|
|
14344
|
+
});
|
|
13901
14345
|
app.use("/api/queue", createGlobalQueueRoutes());
|
|
14346
|
+
app.use("/api/global-notifications", createGlobalNotificationsRoutes());
|
|
13902
14347
|
app.use("/api/projects/:projectId", resolveProject, createProjectRouter());
|
|
13903
14348
|
setupStaticFiles(app);
|
|
13904
14349
|
app.use(errorHandler);
|
|
13905
14350
|
return app;
|
|
13906
14351
|
}
|
|
13907
14352
|
function bootContainer() {
|
|
13908
|
-
initContainer(
|
|
14353
|
+
initContainer(path35.dirname(getDbPath()));
|
|
13909
14354
|
}
|
|
13910
14355
|
function startServer(projectDir, port) {
|
|
13911
14356
|
bootContainer();
|
|
@@ -13958,8 +14403,8 @@ function isProcessRunning2(pid) {
|
|
|
13958
14403
|
}
|
|
13959
14404
|
function readPid(lockPath) {
|
|
13960
14405
|
try {
|
|
13961
|
-
if (!
|
|
13962
|
-
const raw =
|
|
14406
|
+
if (!fs33.existsSync(lockPath)) return null;
|
|
14407
|
+
const raw = fs33.readFileSync(lockPath, "utf-8").trim();
|
|
13963
14408
|
const pid = parseInt(raw, 10);
|
|
13964
14409
|
return Number.isFinite(pid) ? pid : null;
|
|
13965
14410
|
} catch {
|
|
@@ -13971,10 +14416,10 @@ function acquireServeLock(mode, port) {
|
|
|
13971
14416
|
let stalePidCleaned;
|
|
13972
14417
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13973
14418
|
try {
|
|
13974
|
-
const fd =
|
|
13975
|
-
|
|
14419
|
+
const fd = fs33.openSync(lockPath, "wx");
|
|
14420
|
+
fs33.writeFileSync(fd, `${process.pid}
|
|
13976
14421
|
`);
|
|
13977
|
-
|
|
14422
|
+
fs33.closeSync(fd);
|
|
13978
14423
|
return { acquired: true, lockPath, stalePidCleaned };
|
|
13979
14424
|
} catch (error2) {
|
|
13980
14425
|
const err = error2;
|
|
@@ -13995,7 +14440,7 @@ function acquireServeLock(mode, port) {
|
|
|
13995
14440
|
};
|
|
13996
14441
|
}
|
|
13997
14442
|
try {
|
|
13998
|
-
|
|
14443
|
+
fs33.unlinkSync(lockPath);
|
|
13999
14444
|
if (existingPid) {
|
|
14000
14445
|
stalePidCleaned = existingPid;
|
|
14001
14446
|
}
|
|
@@ -14018,10 +14463,10 @@ function acquireServeLock(mode, port) {
|
|
|
14018
14463
|
}
|
|
14019
14464
|
function releaseServeLock(lockPath) {
|
|
14020
14465
|
try {
|
|
14021
|
-
if (!
|
|
14466
|
+
if (!fs33.existsSync(lockPath)) return;
|
|
14022
14467
|
const lockPid = readPid(lockPath);
|
|
14023
14468
|
if (lockPid !== null && lockPid !== process.pid) return;
|
|
14024
|
-
|
|
14469
|
+
fs33.unlinkSync(lockPath);
|
|
14025
14470
|
} catch {
|
|
14026
14471
|
}
|
|
14027
14472
|
}
|
|
@@ -14117,14 +14562,14 @@ function historyCommand(program2) {
|
|
|
14117
14562
|
// src/commands/update.ts
|
|
14118
14563
|
init_dist();
|
|
14119
14564
|
import { spawnSync } from "child_process";
|
|
14120
|
-
import * as
|
|
14121
|
-
import * as
|
|
14565
|
+
import * as fs34 from "fs";
|
|
14566
|
+
import * as path36 from "path";
|
|
14122
14567
|
var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
|
|
14123
14568
|
function parseProjectDirs(projects, cwd) {
|
|
14124
14569
|
if (!projects || projects.trim().length === 0) {
|
|
14125
14570
|
return [cwd];
|
|
14126
14571
|
}
|
|
14127
|
-
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) =>
|
|
14572
|
+
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path36.resolve(cwd, entry));
|
|
14128
14573
|
return Array.from(new Set(dirs));
|
|
14129
14574
|
}
|
|
14130
14575
|
function shouldInstallGlobal(options) {
|
|
@@ -14166,7 +14611,7 @@ function updateCommand(program2) {
|
|
|
14166
14611
|
}
|
|
14167
14612
|
const nightWatchBin = resolveNightWatchBin();
|
|
14168
14613
|
for (const projectDir of projectDirs) {
|
|
14169
|
-
if (!
|
|
14614
|
+
if (!fs34.existsSync(projectDir) || !fs34.statSync(projectDir).isDirectory()) {
|
|
14170
14615
|
warn(`Skipping invalid project directory: ${projectDir}`);
|
|
14171
14616
|
continue;
|
|
14172
14617
|
}
|
|
@@ -14210,8 +14655,8 @@ function prdStateCommand(program2) {
|
|
|
14210
14655
|
|
|
14211
14656
|
// src/commands/retry.ts
|
|
14212
14657
|
init_dist();
|
|
14213
|
-
import * as
|
|
14214
|
-
import * as
|
|
14658
|
+
import * as fs35 from "fs";
|
|
14659
|
+
import * as path37 from "path";
|
|
14215
14660
|
function normalizePrdName(name) {
|
|
14216
14661
|
if (!name.endsWith(".md")) {
|
|
14217
14662
|
return `${name}.md`;
|
|
@@ -14219,26 +14664,26 @@ function normalizePrdName(name) {
|
|
|
14219
14664
|
return name;
|
|
14220
14665
|
}
|
|
14221
14666
|
function getDonePrds(doneDir) {
|
|
14222
|
-
if (!
|
|
14667
|
+
if (!fs35.existsSync(doneDir)) {
|
|
14223
14668
|
return [];
|
|
14224
14669
|
}
|
|
14225
|
-
return
|
|
14670
|
+
return fs35.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
14226
14671
|
}
|
|
14227
14672
|
function retryCommand(program2) {
|
|
14228
14673
|
program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
|
|
14229
14674
|
const projectDir = process.cwd();
|
|
14230
14675
|
const config = loadConfig(projectDir);
|
|
14231
|
-
const prdDir =
|
|
14232
|
-
const doneDir =
|
|
14676
|
+
const prdDir = path37.join(projectDir, config.prdDir);
|
|
14677
|
+
const doneDir = path37.join(prdDir, "done");
|
|
14233
14678
|
const normalizedPrdName = normalizePrdName(prdName);
|
|
14234
|
-
const pendingPath =
|
|
14235
|
-
if (
|
|
14679
|
+
const pendingPath = path37.join(prdDir, normalizedPrdName);
|
|
14680
|
+
if (fs35.existsSync(pendingPath)) {
|
|
14236
14681
|
info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
|
|
14237
14682
|
return;
|
|
14238
14683
|
}
|
|
14239
|
-
const donePath =
|
|
14240
|
-
if (
|
|
14241
|
-
|
|
14684
|
+
const donePath = path37.join(doneDir, normalizedPrdName);
|
|
14685
|
+
if (fs35.existsSync(donePath)) {
|
|
14686
|
+
fs35.renameSync(donePath, pendingPath);
|
|
14242
14687
|
success(`Moved "${normalizedPrdName}" back to pending.`);
|
|
14243
14688
|
dim(`From: ${donePath}`);
|
|
14244
14689
|
dim(`To: ${pendingPath}`);
|
|
@@ -14490,7 +14935,7 @@ function prdsCommand(program2) {
|
|
|
14490
14935
|
|
|
14491
14936
|
// src/commands/cancel.ts
|
|
14492
14937
|
init_dist();
|
|
14493
|
-
import * as
|
|
14938
|
+
import * as fs36 from "fs";
|
|
14494
14939
|
import * as readline3 from "readline";
|
|
14495
14940
|
function getLockFilePaths2(projectDir) {
|
|
14496
14941
|
const runtimeKey = projectRuntimeKey(projectDir);
|
|
@@ -14537,7 +14982,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
14537
14982
|
const pid = lockStatus.pid;
|
|
14538
14983
|
if (!lockStatus.running) {
|
|
14539
14984
|
try {
|
|
14540
|
-
|
|
14985
|
+
fs36.unlinkSync(lockPath);
|
|
14541
14986
|
return {
|
|
14542
14987
|
success: true,
|
|
14543
14988
|
message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
|
|
@@ -14575,7 +15020,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
14575
15020
|
await sleep2(3e3);
|
|
14576
15021
|
if (!isProcessRunning3(pid)) {
|
|
14577
15022
|
try {
|
|
14578
|
-
|
|
15023
|
+
fs36.unlinkSync(lockPath);
|
|
14579
15024
|
} catch {
|
|
14580
15025
|
}
|
|
14581
15026
|
return {
|
|
@@ -14610,7 +15055,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
14610
15055
|
await sleep2(500);
|
|
14611
15056
|
if (!isProcessRunning3(pid)) {
|
|
14612
15057
|
try {
|
|
14613
|
-
|
|
15058
|
+
fs36.unlinkSync(lockPath);
|
|
14614
15059
|
} catch {
|
|
14615
15060
|
}
|
|
14616
15061
|
return {
|
|
@@ -14671,31 +15116,31 @@ function cancelCommand(program2) {
|
|
|
14671
15116
|
|
|
14672
15117
|
// src/commands/slice.ts
|
|
14673
15118
|
init_dist();
|
|
14674
|
-
import * as
|
|
14675
|
-
import * as
|
|
15119
|
+
import * as fs37 from "fs";
|
|
15120
|
+
import * as path38 from "path";
|
|
14676
15121
|
function plannerLockPath2(projectDir) {
|
|
14677
15122
|
return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
|
|
14678
15123
|
}
|
|
14679
15124
|
function acquirePlannerLock(projectDir) {
|
|
14680
15125
|
const lockFile = plannerLockPath2(projectDir);
|
|
14681
|
-
if (
|
|
14682
|
-
const pidRaw =
|
|
15126
|
+
if (fs37.existsSync(lockFile)) {
|
|
15127
|
+
const pidRaw = fs37.readFileSync(lockFile, "utf-8").trim();
|
|
14683
15128
|
const pid = parseInt(pidRaw, 10);
|
|
14684
15129
|
if (!Number.isNaN(pid) && isProcessRunning(pid)) {
|
|
14685
15130
|
return { acquired: false, lockFile, pid };
|
|
14686
15131
|
}
|
|
14687
15132
|
try {
|
|
14688
|
-
|
|
15133
|
+
fs37.unlinkSync(lockFile);
|
|
14689
15134
|
} catch {
|
|
14690
15135
|
}
|
|
14691
15136
|
}
|
|
14692
|
-
|
|
15137
|
+
fs37.writeFileSync(lockFile, String(process.pid));
|
|
14693
15138
|
return { acquired: true, lockFile };
|
|
14694
15139
|
}
|
|
14695
15140
|
function releasePlannerLock(lockFile) {
|
|
14696
15141
|
try {
|
|
14697
|
-
if (
|
|
14698
|
-
|
|
15142
|
+
if (fs37.existsSync(lockFile)) {
|
|
15143
|
+
fs37.unlinkSync(lockFile);
|
|
14699
15144
|
}
|
|
14700
15145
|
} catch {
|
|
14701
15146
|
}
|
|
@@ -14704,12 +15149,12 @@ function resolvePlannerIssueColumn(config) {
|
|
|
14704
15149
|
return config.roadmapScanner.issueColumn === "Ready" ? "Ready" : "Draft";
|
|
14705
15150
|
}
|
|
14706
15151
|
function buildPlannerIssueBody(projectDir, config, result) {
|
|
14707
|
-
const relativePrdPath =
|
|
14708
|
-
const absolutePrdPath =
|
|
15152
|
+
const relativePrdPath = path38.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
|
|
15153
|
+
const absolutePrdPath = path38.join(projectDir, config.prdDir, result.file ?? "");
|
|
14709
15154
|
const sourceItem = result.item;
|
|
14710
15155
|
let prdContent;
|
|
14711
15156
|
try {
|
|
14712
|
-
prdContent =
|
|
15157
|
+
prdContent = fs37.readFileSync(absolutePrdPath, "utf-8");
|
|
14713
15158
|
} catch {
|
|
14714
15159
|
prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
|
|
14715
15160
|
}
|
|
@@ -14885,7 +15330,7 @@ function sliceCommand(program2) {
|
|
|
14885
15330
|
if (!options.dryRun) {
|
|
14886
15331
|
await sendNotifications(config, {
|
|
14887
15332
|
event: "run_started",
|
|
14888
|
-
projectName:
|
|
15333
|
+
projectName: path38.basename(projectDir),
|
|
14889
15334
|
exitCode: 0,
|
|
14890
15335
|
provider: config.provider
|
|
14891
15336
|
});
|
|
@@ -14920,7 +15365,7 @@ function sliceCommand(program2) {
|
|
|
14920
15365
|
if (!options.dryRun && result.sliced) {
|
|
14921
15366
|
await sendNotifications(config, {
|
|
14922
15367
|
event: "run_succeeded",
|
|
14923
|
-
projectName:
|
|
15368
|
+
projectName: path38.basename(projectDir),
|
|
14924
15369
|
exitCode,
|
|
14925
15370
|
provider: config.provider,
|
|
14926
15371
|
prTitle: result.item?.title
|
|
@@ -14928,7 +15373,7 @@ function sliceCommand(program2) {
|
|
|
14928
15373
|
} else if (!options.dryRun && !nothingPending) {
|
|
14929
15374
|
await sendNotifications(config, {
|
|
14930
15375
|
event: "run_failed",
|
|
14931
|
-
projectName:
|
|
15376
|
+
projectName: path38.basename(projectDir),
|
|
14932
15377
|
exitCode,
|
|
14933
15378
|
provider: config.provider
|
|
14934
15379
|
});
|
|
@@ -14944,21 +15389,21 @@ function sliceCommand(program2) {
|
|
|
14944
15389
|
|
|
14945
15390
|
// src/commands/state.ts
|
|
14946
15391
|
init_dist();
|
|
14947
|
-
import * as
|
|
14948
|
-
import * as
|
|
15392
|
+
import * as os9 from "os";
|
|
15393
|
+
import * as path39 from "path";
|
|
14949
15394
|
import chalk5 from "chalk";
|
|
14950
15395
|
import { Command } from "commander";
|
|
14951
15396
|
function createStateCommand() {
|
|
14952
15397
|
const state = new Command("state");
|
|
14953
15398
|
state.description("Manage Night Watch state");
|
|
14954
15399
|
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 ||
|
|
15400
|
+
const nightWatchHome = process.env.NIGHT_WATCH_HOME || path39.join(os9.homedir(), GLOBAL_CONFIG_DIR);
|
|
14956
15401
|
if (opts.dryRun) {
|
|
14957
15402
|
console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
|
|
14958
15403
|
console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
|
|
14959
|
-
console.log(` ${
|
|
14960
|
-
console.log(` ${
|
|
14961
|
-
console.log(` ${
|
|
15404
|
+
console.log(` ${path39.join(nightWatchHome, "projects.json")}`);
|
|
15405
|
+
console.log(` ${path39.join(nightWatchHome, "history.json")}`);
|
|
15406
|
+
console.log(` ${path39.join(nightWatchHome, "prd-states.json")}`);
|
|
14962
15407
|
console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
|
|
14963
15408
|
console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
|
|
14964
15409
|
return;
|
|
@@ -14996,8 +15441,8 @@ function createStateCommand() {
|
|
|
14996
15441
|
init_dist();
|
|
14997
15442
|
init_dist();
|
|
14998
15443
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
14999
|
-
import * as
|
|
15000
|
-
import * as
|
|
15444
|
+
import * as fs38 from "fs";
|
|
15445
|
+
import * as path40 from "path";
|
|
15001
15446
|
import * as readline4 from "readline";
|
|
15002
15447
|
import chalk6 from "chalk";
|
|
15003
15448
|
async function run(fn) {
|
|
@@ -15019,7 +15464,7 @@ function getProvider(config, cwd) {
|
|
|
15019
15464
|
return createBoardProvider(bp, cwd);
|
|
15020
15465
|
}
|
|
15021
15466
|
function defaultBoardTitle(cwd) {
|
|
15022
|
-
return `${
|
|
15467
|
+
return `${path40.basename(cwd)} Night Watch`;
|
|
15023
15468
|
}
|
|
15024
15469
|
async function ensureBoardConfigured(config, cwd, provider, options) {
|
|
15025
15470
|
if (config.boardProvider?.projectNumber) {
|
|
@@ -15218,11 +15663,11 @@ function boardCommand(program2) {
|
|
|
15218
15663
|
let body = options.body ?? "";
|
|
15219
15664
|
if (options.bodyFile) {
|
|
15220
15665
|
const filePath = options.bodyFile;
|
|
15221
|
-
if (!
|
|
15666
|
+
if (!fs38.existsSync(filePath)) {
|
|
15222
15667
|
console.error(`File not found: ${filePath}`);
|
|
15223
15668
|
process.exit(1);
|
|
15224
15669
|
}
|
|
15225
|
-
body =
|
|
15670
|
+
body = fs38.readFileSync(filePath, "utf-8");
|
|
15226
15671
|
}
|
|
15227
15672
|
const labels = [];
|
|
15228
15673
|
if (options.label) {
|
|
@@ -15250,6 +15695,25 @@ function boardCommand(program2) {
|
|
|
15250
15695
|
}
|
|
15251
15696
|
})
|
|
15252
15697
|
);
|
|
15698
|
+
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(
|
|
15699
|
+
async (number, options) => run(async () => {
|
|
15700
|
+
const cwd = process.cwd();
|
|
15701
|
+
const config = loadConfig(cwd);
|
|
15702
|
+
const provider = getProvider(config, cwd);
|
|
15703
|
+
await ensureBoardConfigured(config, cwd, provider);
|
|
15704
|
+
if (!BOARD_COLUMNS.includes(options.column)) {
|
|
15705
|
+
console.error(
|
|
15706
|
+
`Invalid column "${options.column}". Valid columns: ${BOARD_COLUMNS.join(", ")}`
|
|
15707
|
+
);
|
|
15708
|
+
process.exit(1);
|
|
15709
|
+
}
|
|
15710
|
+
const issue = await provider.addIssue(
|
|
15711
|
+
parseInt(number, 10),
|
|
15712
|
+
options.column
|
|
15713
|
+
);
|
|
15714
|
+
success(`Added issue #${issue.number} "${issue.title}" to ${options.column}`);
|
|
15715
|
+
})
|
|
15716
|
+
);
|
|
15253
15717
|
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
15718
|
async (options) => run(async () => {
|
|
15255
15719
|
const cwd = process.cwd();
|
|
@@ -15444,12 +15908,12 @@ function boardCommand(program2) {
|
|
|
15444
15908
|
const config = loadConfig(cwd);
|
|
15445
15909
|
const provider = getProvider(config, cwd);
|
|
15446
15910
|
await ensureBoardConfigured(config, cwd, provider);
|
|
15447
|
-
const roadmapPath = options.roadmap ??
|
|
15448
|
-
if (!
|
|
15911
|
+
const roadmapPath = options.roadmap ?? path40.join(cwd, "ROADMAP.md");
|
|
15912
|
+
if (!fs38.existsSync(roadmapPath)) {
|
|
15449
15913
|
console.error(`Roadmap file not found: ${roadmapPath}`);
|
|
15450
15914
|
process.exit(1);
|
|
15451
15915
|
}
|
|
15452
|
-
const roadmapContent =
|
|
15916
|
+
const roadmapContent = fs38.readFileSync(roadmapPath, "utf-8");
|
|
15453
15917
|
const items = parseRoadmap(roadmapContent);
|
|
15454
15918
|
const uncheckedItems = getUncheckedItems(items);
|
|
15455
15919
|
if (uncheckedItems.length === 0) {
|
|
@@ -15573,7 +16037,7 @@ function boardCommand(program2) {
|
|
|
15573
16037
|
// src/commands/queue.ts
|
|
15574
16038
|
init_dist();
|
|
15575
16039
|
init_dist();
|
|
15576
|
-
import * as
|
|
16040
|
+
import * as path41 from "path";
|
|
15577
16041
|
import { spawn as spawn6 } from "child_process";
|
|
15578
16042
|
import chalk7 from "chalk";
|
|
15579
16043
|
import { Command as Command2 } from "commander";
|
|
@@ -15693,7 +16157,7 @@ function createQueueCommand() {
|
|
|
15693
16157
|
process.exit(1);
|
|
15694
16158
|
}
|
|
15695
16159
|
}
|
|
15696
|
-
const projectName =
|
|
16160
|
+
const projectName = path41.basename(projectDir);
|
|
15697
16161
|
const queueConfig = loadConfig(projectDir).queue;
|
|
15698
16162
|
const id = enqueueJob(projectDir, projectName, jobType, envVars, queueConfig);
|
|
15699
16163
|
console.log(chalk7.green(`Enqueued ${jobType} for ${projectName} (ID: ${id})`));
|
|
@@ -15849,17 +16313,17 @@ function notifyCommand(program2) {
|
|
|
15849
16313
|
|
|
15850
16314
|
// src/cli.ts
|
|
15851
16315
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
15852
|
-
var __dirname4 =
|
|
16316
|
+
var __dirname4 = dirname9(__filename3);
|
|
15853
16317
|
function findPackageRoot(dir) {
|
|
15854
16318
|
let d = dir;
|
|
15855
16319
|
for (let i = 0; i < 5; i++) {
|
|
15856
|
-
if (
|
|
15857
|
-
d =
|
|
16320
|
+
if (existsSync30(join36(d, "package.json"))) return d;
|
|
16321
|
+
d = dirname9(d);
|
|
15858
16322
|
}
|
|
15859
16323
|
return dir;
|
|
15860
16324
|
}
|
|
15861
16325
|
var packageRoot = findPackageRoot(__dirname4);
|
|
15862
|
-
var packageJson = JSON.parse(
|
|
16326
|
+
var packageJson = JSON.parse(readFileSync19(join36(packageRoot, "package.json"), "utf-8"));
|
|
15863
16327
|
var program = new Command3();
|
|
15864
16328
|
program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
|
|
15865
16329
|
initCommand(program);
|