@jonit-dev/night-watch-cli 1.8.3 → 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 +469 -160
- 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-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")
|
|
@@ -35,6 +312,7 @@ var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEW
|
|
|
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,14 +483,7 @@ 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";
|
|
@@ -231,14 +495,7 @@ If no issues are warranted, output an empty array: []`;
|
|
|
231
495
|
DEFAULT_QUEUE_MODE = "conservative";
|
|
232
496
|
DEFAULT_QUEUE_MAX_CONCURRENCY = 1;
|
|
233
497
|
DEFAULT_QUEUE_MAX_WAIT_TIME = 7200;
|
|
234
|
-
DEFAULT_QUEUE_PRIORITY =
|
|
235
|
-
executor: 50,
|
|
236
|
-
reviewer: 40,
|
|
237
|
-
slicer: 30,
|
|
238
|
-
qa: 20,
|
|
239
|
-
audit: 10,
|
|
240
|
-
analytics: 10
|
|
241
|
-
};
|
|
498
|
+
DEFAULT_QUEUE_PRIORITY = getDefaultQueuePriority();
|
|
242
499
|
DEFAULT_QUEUE = {
|
|
243
500
|
enabled: DEFAULT_QUEUE_ENABLED,
|
|
244
501
|
mode: DEFAULT_QUEUE_MODE,
|
|
@@ -252,15 +509,6 @@ If no issues are warranted, output an empty array: []`;
|
|
|
252
509
|
}
|
|
253
510
|
});
|
|
254
511
|
|
|
255
|
-
// ../core/dist/board/types.js
|
|
256
|
-
var BOARD_COLUMNS;
|
|
257
|
-
var init_types2 = __esm({
|
|
258
|
-
"../core/dist/board/types.js"() {
|
|
259
|
-
"use strict";
|
|
260
|
-
BOARD_COLUMNS = ["Draft", "Ready", "In Progress", "Review", "Done"];
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
512
|
// ../core/dist/config-normalize.js
|
|
265
513
|
function validateProvider(value) {
|
|
266
514
|
const trimmed = value.trim();
|
|
@@ -432,43 +680,14 @@ function normalizeConfig(rawConfig) {
|
|
|
432
680
|
if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
|
|
433
681
|
normalized.autoMergeMethod = mergeMethod;
|
|
434
682
|
}
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
branchPatterns: readStringArray(rawQa.branchPatterns) ?? DEFAULT_QA.branchPatterns,
|
|
444
|
-
artifacts,
|
|
445
|
-
skipLabel: readString(rawQa.skipLabel) ?? DEFAULT_QA.skipLabel,
|
|
446
|
-
autoInstallPlaywright: readBoolean(rawQa.autoInstallPlaywright) ?? DEFAULT_QA.autoInstallPlaywright
|
|
447
|
-
};
|
|
448
|
-
normalized.qa = qa;
|
|
449
|
-
}
|
|
450
|
-
const rawAudit = readObject(rawConfig.audit);
|
|
451
|
-
if (rawAudit) {
|
|
452
|
-
const audit = {
|
|
453
|
-
enabled: readBoolean(rawAudit.enabled) ?? DEFAULT_AUDIT.enabled,
|
|
454
|
-
schedule: readString(rawAudit.schedule) ?? DEFAULT_AUDIT.schedule,
|
|
455
|
-
maxRuntime: readNumber(rawAudit.maxRuntime) ?? DEFAULT_AUDIT.maxRuntime
|
|
456
|
-
};
|
|
457
|
-
normalized.audit = audit;
|
|
458
|
-
}
|
|
459
|
-
const rawAnalytics = readObject(rawConfig.analytics);
|
|
460
|
-
if (rawAnalytics) {
|
|
461
|
-
const targetColumnRaw = readString(rawAnalytics.targetColumn);
|
|
462
|
-
const targetColumn = targetColumnRaw && BOARD_COLUMNS.includes(targetColumnRaw) ? targetColumnRaw : DEFAULT_ANALYTICS.targetColumn;
|
|
463
|
-
const analytics = {
|
|
464
|
-
enabled: readBoolean(rawAnalytics.enabled) ?? DEFAULT_ANALYTICS.enabled,
|
|
465
|
-
schedule: readString(rawAnalytics.schedule) ?? DEFAULT_ANALYTICS.schedule,
|
|
466
|
-
maxRuntime: readNumber(rawAnalytics.maxRuntime) ?? DEFAULT_ANALYTICS.maxRuntime,
|
|
467
|
-
lookbackDays: readNumber(rawAnalytics.lookbackDays) ?? DEFAULT_ANALYTICS.lookbackDays,
|
|
468
|
-
targetColumn,
|
|
469
|
-
analysisPrompt: readString(rawAnalytics.analysisPrompt) ?? DEFAULT_ANALYTICS.analysisPrompt
|
|
470
|
-
};
|
|
471
|
-
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
|
+
}
|
|
472
691
|
}
|
|
473
692
|
const rawJobProviders = readObject(rawConfig.jobProviders);
|
|
474
693
|
if (rawJobProviders) {
|
|
@@ -529,8 +748,8 @@ function normalizeConfig(rawConfig) {
|
|
|
529
748
|
var init_config_normalize = __esm({
|
|
530
749
|
"../core/dist/config-normalize.js"() {
|
|
531
750
|
"use strict";
|
|
532
|
-
init_types2();
|
|
533
751
|
init_constants();
|
|
752
|
+
init_job_registry();
|
|
534
753
|
}
|
|
535
754
|
});
|
|
536
755
|
|
|
@@ -706,72 +925,16 @@ function buildEnvOverrideConfig(fileConfig) {
|
|
|
706
925
|
env.claudeModel = model;
|
|
707
926
|
}
|
|
708
927
|
}
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
if (process.env.NW_QA_MAX_RUNTIME) {
|
|
719
|
-
const v = parseInt(process.env.NW_QA_MAX_RUNTIME, 10);
|
|
720
|
-
if (!isNaN(v) && v > 0)
|
|
721
|
-
env.qa = { ...qaBase(), maxRuntime: v };
|
|
722
|
-
}
|
|
723
|
-
if (process.env.NW_QA_ARTIFACTS) {
|
|
724
|
-
const a = process.env.NW_QA_ARTIFACTS;
|
|
725
|
-
if (["screenshot", "video", "both"].includes(a)) {
|
|
726
|
-
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;
|
|
727
936
|
}
|
|
728
937
|
}
|
|
729
|
-
if (process.env.NW_QA_SKIP_LABEL) {
|
|
730
|
-
env.qa = { ...qaBase(), skipLabel: process.env.NW_QA_SKIP_LABEL };
|
|
731
|
-
}
|
|
732
|
-
if (process.env.NW_QA_AUTO_INSTALL_PLAYWRIGHT) {
|
|
733
|
-
const v = parseBoolean(process.env.NW_QA_AUTO_INSTALL_PLAYWRIGHT);
|
|
734
|
-
if (v !== null)
|
|
735
|
-
env.qa = { ...qaBase(), autoInstallPlaywright: v };
|
|
736
|
-
}
|
|
737
|
-
if (process.env.NW_QA_BRANCH_PATTERNS) {
|
|
738
|
-
const patterns = process.env.NW_QA_BRANCH_PATTERNS.split(",").map((s) => s.trim()).filter(Boolean);
|
|
739
|
-
if (patterns.length > 0)
|
|
740
|
-
env.qa = { ...qaBase(), branchPatterns: patterns };
|
|
741
|
-
}
|
|
742
|
-
const auditBase = () => env.audit ?? fileConfig?.audit ?? DEFAULT_AUDIT;
|
|
743
|
-
if (process.env.NW_AUDIT_ENABLED) {
|
|
744
|
-
const v = parseBoolean(process.env.NW_AUDIT_ENABLED);
|
|
745
|
-
if (v !== null)
|
|
746
|
-
env.audit = { ...auditBase(), enabled: v };
|
|
747
|
-
}
|
|
748
|
-
if (process.env.NW_AUDIT_SCHEDULE) {
|
|
749
|
-
env.audit = { ...auditBase(), schedule: process.env.NW_AUDIT_SCHEDULE };
|
|
750
|
-
}
|
|
751
|
-
if (process.env.NW_AUDIT_MAX_RUNTIME) {
|
|
752
|
-
const v = parseInt(process.env.NW_AUDIT_MAX_RUNTIME, 10);
|
|
753
|
-
if (!isNaN(v) && v > 0)
|
|
754
|
-
env.audit = { ...auditBase(), maxRuntime: v };
|
|
755
|
-
}
|
|
756
|
-
const analyticsBase = () => env.analytics ?? fileConfig?.analytics ?? DEFAULT_ANALYTICS;
|
|
757
|
-
if (process.env.NW_ANALYTICS_ENABLED) {
|
|
758
|
-
const v = parseBoolean(process.env.NW_ANALYTICS_ENABLED);
|
|
759
|
-
if (v !== null)
|
|
760
|
-
env.analytics = { ...analyticsBase(), enabled: v };
|
|
761
|
-
}
|
|
762
|
-
if (process.env.NW_ANALYTICS_SCHEDULE) {
|
|
763
|
-
env.analytics = { ...analyticsBase(), schedule: process.env.NW_ANALYTICS_SCHEDULE };
|
|
764
|
-
}
|
|
765
|
-
if (process.env.NW_ANALYTICS_MAX_RUNTIME) {
|
|
766
|
-
const v = parseInt(process.env.NW_ANALYTICS_MAX_RUNTIME, 10);
|
|
767
|
-
if (!isNaN(v) && v > 0)
|
|
768
|
-
env.analytics = { ...analyticsBase(), maxRuntime: v };
|
|
769
|
-
}
|
|
770
|
-
if (process.env.NW_ANALYTICS_LOOKBACK_DAYS) {
|
|
771
|
-
const v = parseInt(process.env.NW_ANALYTICS_LOOKBACK_DAYS, 10);
|
|
772
|
-
if (!isNaN(v) && v > 0)
|
|
773
|
-
env.analytics = { ...analyticsBase(), lookbackDays: v };
|
|
774
|
-
}
|
|
775
938
|
const jobProvidersEnv = {};
|
|
776
939
|
for (const jobType of VALID_JOB_TYPES) {
|
|
777
940
|
const val = process.env[`NW_JOB_PROVIDER_${jobType.toUpperCase()}`];
|
|
@@ -821,6 +984,7 @@ var init_config_env = __esm({
|
|
|
821
984
|
"use strict";
|
|
822
985
|
init_constants();
|
|
823
986
|
init_config_normalize();
|
|
987
|
+
init_job_registry();
|
|
824
988
|
}
|
|
825
989
|
});
|
|
826
990
|
|
|
@@ -1016,6 +1180,15 @@ var init_config = __esm({
|
|
|
1016
1180
|
}
|
|
1017
1181
|
});
|
|
1018
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
|
+
|
|
1019
1192
|
// ../core/dist/storage/repositories/sqlite/execution-history.repository.js
|
|
1020
1193
|
import Database from "better-sqlite3";
|
|
1021
1194
|
import { inject, injectable } from "tsyringe";
|
|
@@ -4921,6 +5094,26 @@ function validateRegistry() {
|
|
|
4921
5094
|
}
|
|
4922
5095
|
return { valid, invalid };
|
|
4923
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
|
+
}
|
|
4924
5117
|
var init_registry = __esm({
|
|
4925
5118
|
"../core/dist/utils/registry.js"() {
|
|
4926
5119
|
"use strict";
|
|
@@ -4928,6 +5121,7 @@ var init_registry = __esm({
|
|
|
4928
5121
|
init_repositories();
|
|
4929
5122
|
init_client();
|
|
4930
5123
|
init_client();
|
|
5124
|
+
init_crontab();
|
|
4931
5125
|
init_status_data();
|
|
4932
5126
|
}
|
|
4933
5127
|
});
|
|
@@ -6986,6 +7180,14 @@ sequenceDiagram
|
|
|
6986
7180
|
}
|
|
6987
7181
|
});
|
|
6988
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
|
+
|
|
6989
7191
|
// ../core/dist/index.js
|
|
6990
7192
|
var dist_exports = {};
|
|
6991
7193
|
__export(dist_exports, {
|
|
@@ -7067,6 +7269,7 @@ __export(dist_exports, {
|
|
|
7067
7269
|
HISTORY_FILE_NAME: () => HISTORY_FILE_NAME,
|
|
7068
7270
|
HORIZON_LABELS: () => HORIZON_LABELS,
|
|
7069
7271
|
HORIZON_LABEL_INFO: () => HORIZON_LABEL_INFO,
|
|
7272
|
+
JOB_REGISTRY: () => JOB_REGISTRY,
|
|
7070
7273
|
LOCK_FILE_PREFIX: () => LOCK_FILE_PREFIX,
|
|
7071
7274
|
LOG_DIR: () => LOG_DIR,
|
|
7072
7275
|
LOG_FILE_NAMES: () => LOG_FILE_NAMES,
|
|
@@ -7098,7 +7301,9 @@ __export(dist_exports, {
|
|
|
7098
7301
|
analyticsLockPath: () => analyticsLockPath,
|
|
7099
7302
|
auditLockPath: () => auditLockPath,
|
|
7100
7303
|
buildDescription: () => buildDescription,
|
|
7304
|
+
buildJobEnvOverrides: () => buildJobEnvOverrides,
|
|
7101
7305
|
calculateStringSimilarity: () => calculateStringSimilarity,
|
|
7306
|
+
camelToUpperSnake: () => camelToUpperSnake,
|
|
7102
7307
|
canStartJob: () => canStartJob,
|
|
7103
7308
|
cancelProcess: () => cancelProcess,
|
|
7104
7309
|
checkConfigFile: () => checkConfigFile,
|
|
@@ -7163,23 +7368,30 @@ __export(dist_exports, {
|
|
|
7163
7368
|
formatTelegramPayload: () => formatTelegramPayload,
|
|
7164
7369
|
generateItemHash: () => generateItemHash,
|
|
7165
7370
|
generateMarker: () => generateMarker,
|
|
7371
|
+
getAllJobDefs: () => getAllJobDefs,
|
|
7166
7372
|
getBranchTipTimestamp: () => getBranchTipTimestamp,
|
|
7167
7373
|
getCrontabInfo: () => getCrontabInfo,
|
|
7168
7374
|
getDb: () => getDb,
|
|
7169
7375
|
getDbPath: () => getDbPath,
|
|
7170
7376
|
getDefaultConfig: () => getDefaultConfig,
|
|
7377
|
+
getDefaultQueuePriority: () => getDefaultQueuePriority,
|
|
7171
7378
|
getEntries: () => getEntries,
|
|
7172
7379
|
getEventColor: () => getEventColor,
|
|
7173
7380
|
getEventEmoji: () => getEventEmoji,
|
|
7174
7381
|
getEventTitle: () => getEventTitle,
|
|
7175
7382
|
getHistoryPath: () => getHistoryPath,
|
|
7176
7383
|
getInFlightCount: () => getInFlightCount,
|
|
7384
|
+
getJobDef: () => getJobDef,
|
|
7385
|
+
getJobDefByCommand: () => getJobDefByCommand,
|
|
7386
|
+
getJobDefByLogName: () => getJobDefByLogName,
|
|
7177
7387
|
getJobPriority: () => getJobPriority,
|
|
7178
7388
|
getJobRunsAnalytics: () => getJobRunsAnalytics,
|
|
7179
7389
|
getLabelsForSection: () => getLabelsForSection,
|
|
7180
7390
|
getLastExecution: () => getLastExecution,
|
|
7181
7391
|
getLastLogLines: () => getLastLogLines,
|
|
7182
7392
|
getLockFilePaths: () => getLockFilePaths,
|
|
7393
|
+
getLockSuffix: () => getLockSuffix,
|
|
7394
|
+
getLogFileNames: () => getLogFileNames,
|
|
7183
7395
|
getLogInfo: () => getLogInfo,
|
|
7184
7396
|
getNextPendingJob: () => getNextPendingJob,
|
|
7185
7397
|
getNextPrdNumber: () => getNextPrdNumber,
|
|
@@ -7200,6 +7412,7 @@ __export(dist_exports, {
|
|
|
7200
7412
|
getStateFilePath: () => getStateFilePath,
|
|
7201
7413
|
getStateItem: () => getStateItem,
|
|
7202
7414
|
getUncheckedItems: () => getUncheckedItems,
|
|
7415
|
+
getValidJobTypes: () => getValidJobTypes,
|
|
7203
7416
|
groupBySection: () => groupBySection2,
|
|
7204
7417
|
hasEntry: () => hasEntry,
|
|
7205
7418
|
hasNewItems: () => hasNewItems,
|
|
@@ -7226,6 +7439,7 @@ __export(dist_exports, {
|
|
|
7226
7439
|
markJobRunning: () => markJobRunning,
|
|
7227
7440
|
markPrdDone: () => markPrdDone,
|
|
7228
7441
|
migrateJsonToSqlite: () => migrateJsonToSqlite,
|
|
7442
|
+
normalizeJobConfig: () => normalizeJobConfig,
|
|
7229
7443
|
normalizeSchedulingPriority: () => normalizeSchedulingPriority,
|
|
7230
7444
|
parsePrdDependencies: () => parsePrdDependencies,
|
|
7231
7445
|
parseRoadmap: () => parseRoadmap,
|
|
@@ -7235,6 +7449,7 @@ __export(dist_exports, {
|
|
|
7235
7449
|
prepareBranchWorktree: () => prepareBranchWorktree,
|
|
7236
7450
|
prepareDetachedWorktree: () => prepareDetachedWorktree,
|
|
7237
7451
|
projectRuntimeKey: () => projectRuntimeKey,
|
|
7452
|
+
pruneProjectData: () => pruneProjectData,
|
|
7238
7453
|
qaLockPath: () => qaLockPath,
|
|
7239
7454
|
readCrontab: () => readCrontab,
|
|
7240
7455
|
readPrdStates: () => readPrdStates,
|
|
@@ -7245,6 +7460,7 @@ __export(dist_exports, {
|
|
|
7245
7460
|
removeEntries: () => removeEntries,
|
|
7246
7461
|
removeEntriesForProject: () => removeEntriesForProject,
|
|
7247
7462
|
removeJob: () => removeJob,
|
|
7463
|
+
removeProject: () => removeProject,
|
|
7248
7464
|
renderPrdTemplate: () => renderPrdTemplate,
|
|
7249
7465
|
renderSlicerPrompt: () => renderSlicerPrompt,
|
|
7250
7466
|
resetRepositories: () => resetRepositories,
|
|
@@ -7329,6 +7545,7 @@ var init_dist = __esm({
|
|
|
7329
7545
|
init_analytics();
|
|
7330
7546
|
init_prd_template();
|
|
7331
7547
|
init_slicer_prompt();
|
|
7548
|
+
init_jobs();
|
|
7332
7549
|
}
|
|
7333
7550
|
});
|
|
7334
7551
|
|
|
@@ -7361,6 +7578,14 @@ function findTemplatesDir(startDir) {
|
|
|
7361
7578
|
return join18(startDir, "templates");
|
|
7362
7579
|
}
|
|
7363
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
|
+
];
|
|
7364
7589
|
function hasPlaywrightDependency(cwd) {
|
|
7365
7590
|
const packageJsonPath = path20.join(cwd, "package.json");
|
|
7366
7591
|
if (!fs20.existsSync(packageJsonPath)) {
|
|
@@ -7656,12 +7881,67 @@ function addToGitignore(cwd) {
|
|
|
7656
7881
|
fs20.writeFileSync(gitignorePath, newContent);
|
|
7657
7882
|
console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
|
|
7658
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
|
+
}
|
|
7659
7939
|
function initCommand(program2) {
|
|
7660
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) => {
|
|
7661
7941
|
const cwd = process.cwd();
|
|
7662
7942
|
const force = options.force || false;
|
|
7663
7943
|
const prdDir = options.prdDir || DEFAULT_PRD_DIR;
|
|
7664
|
-
const totalSteps =
|
|
7944
|
+
const totalSteps = 13;
|
|
7665
7945
|
const interactive = isInteractiveInitSession();
|
|
7666
7946
|
console.log();
|
|
7667
7947
|
header("Night Watch CLI - Initializing");
|
|
@@ -7912,7 +8192,19 @@ function initCommand(program2) {
|
|
|
7912
8192
|
` Warning: Could not register in global registry: ${regErr instanceof Error ? regErr.message : String(regErr)}`
|
|
7913
8193
|
);
|
|
7914
8194
|
}
|
|
7915
|
-
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!");
|
|
7916
8208
|
header("Initialization Complete");
|
|
7917
8209
|
const filesTable = createTable({ head: ["Created Files", ""] });
|
|
7918
8210
|
filesTable.push(["PRD Directory", `${prdDir}/done/`]);
|
|
@@ -7925,6 +8217,15 @@ function initCommand(program2) {
|
|
|
7925
8217
|
filesTable.push(["Config File", CONFIG_FILE_NAME]);
|
|
7926
8218
|
filesTable.push(["Board Setup", boardSetupStatus]);
|
|
7927
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]);
|
|
7928
8229
|
console.log(filesTable.toString());
|
|
7929
8230
|
header("Configuration");
|
|
7930
8231
|
label("Provider", selectedProvider);
|
|
@@ -7936,6 +8237,9 @@ function initCommand(program2) {
|
|
|
7936
8237
|
info("2. Run `night-watch install` to set up cron jobs");
|
|
7937
8238
|
info("3. Run `night-watch doctor` to verify the full setup");
|
|
7938
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
|
+
}
|
|
7939
8243
|
console.log();
|
|
7940
8244
|
});
|
|
7941
8245
|
}
|
|
@@ -12159,7 +12463,7 @@ function doctorCommand(program2) {
|
|
|
12159
12463
|
program2.command("doctor").description("Check Night Watch configuration and system health").option("--fix", "Automatically fix fixable issues").action(async (options) => {
|
|
12160
12464
|
const projectDir = process.cwd();
|
|
12161
12465
|
const config = loadConfig(projectDir);
|
|
12162
|
-
const totalChecks =
|
|
12466
|
+
const totalChecks = 7;
|
|
12163
12467
|
let checkNum = 1;
|
|
12164
12468
|
let passedChecks = 0;
|
|
12165
12469
|
let fixedChecks = 0;
|
|
@@ -12185,11 +12489,12 @@ function doctorCommand(program2) {
|
|
|
12185
12489
|
const ghResult = runCheck(checkNum++, totalChecks, "GitHub CLI", () => checkGhCli(), options);
|
|
12186
12490
|
if (ghResult.passed) passedChecks++;
|
|
12187
12491
|
if (ghResult.fixed) fixedChecks++;
|
|
12492
|
+
const resolvedProviderCli = BUILT_IN_PRESETS[config.provider]?.command ?? config.provider;
|
|
12188
12493
|
const providerResult = runCheck(
|
|
12189
12494
|
checkNum++,
|
|
12190
12495
|
totalChecks,
|
|
12191
12496
|
"provider CLI",
|
|
12192
|
-
() => checkProviderCli(
|
|
12497
|
+
() => checkProviderCli(resolvedProviderCli),
|
|
12193
12498
|
options
|
|
12194
12499
|
);
|
|
12195
12500
|
if (providerResult.passed) passedChecks++;
|
|
@@ -12203,15 +12508,6 @@ function doctorCommand(program2) {
|
|
|
12203
12508
|
);
|
|
12204
12509
|
if (configResult.passed) passedChecks++;
|
|
12205
12510
|
if (configResult.fixed) fixedChecks++;
|
|
12206
|
-
const prdResult = runCheck(
|
|
12207
|
-
checkNum++,
|
|
12208
|
-
totalChecks,
|
|
12209
|
-
"PRD directory",
|
|
12210
|
-
() => checkPrdDirectory(projectDir, config.prdDir),
|
|
12211
|
-
options
|
|
12212
|
-
);
|
|
12213
|
-
if (prdResult.passed) passedChecks++;
|
|
12214
|
-
if (prdResult.fixed) fixedChecks++;
|
|
12215
12511
|
const logsResult = runCheck(
|
|
12216
12512
|
checkNum++,
|
|
12217
12513
|
totalChecks,
|
|
@@ -12549,18 +12845,16 @@ function createActionRouteHandlers(ctx) {
|
|
|
12549
12845
|
router.post(`/${p}review`, (req, res) => {
|
|
12550
12846
|
spawnAction2(ctx.getProjectDir(req), ["review"], req, res);
|
|
12551
12847
|
});
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
spawnAction2(ctx.getProjectDir(req), ["planner"], req, res);
|
|
12563
|
-
});
|
|
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
|
+
}
|
|
12564
12858
|
router.post(`/${p}install-cron`, (req, res) => {
|
|
12565
12859
|
const projectDir = ctx.getProjectDir(req);
|
|
12566
12860
|
try {
|
|
@@ -14033,6 +14327,21 @@ function createGlobalApp() {
|
|
|
14033
14327
|
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
14034
14328
|
}
|
|
14035
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
|
+
});
|
|
14036
14345
|
app.use("/api/queue", createGlobalQueueRoutes());
|
|
14037
14346
|
app.use("/api/global-notifications", createGlobalNotificationsRoutes());
|
|
14038
14347
|
app.use("/api/projects/:projectId", resolveProject, createProjectRouter());
|