@jonit-dev/night-watch-cli 1.8.3 → 1.8.4-beta.1
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 +776 -324
- 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 +101 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/plan.d.ts +19 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +88 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/prd.d.ts +8 -14
- package/dist/commands/prd.d.ts.map +1 -1
- package/dist/commands/prd.js +238 -145
- package/dist/commands/prd.js.map +1 -1
- package/dist/scripts/night-watch-plan-cron.sh +130 -0
- package/dist/scripts/publish.sh +94 -26
- package/dist/templates/pr-reviewer.md +1 -1
- package/dist/templates/prd-creator.md +330 -0
- package/dist/templates/prd-executor.md +1 -0
- package/dist/templates/qa.md +8 -4
- 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";
|
|
@@ -3619,7 +3792,7 @@ function getLockFilePaths(projectDir) {
|
|
|
3619
3792
|
};
|
|
3620
3793
|
}
|
|
3621
3794
|
function sleep(ms) {
|
|
3622
|
-
return new Promise((
|
|
3795
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
3623
3796
|
}
|
|
3624
3797
|
async function cancelProcess(processType, lockPath, force = false) {
|
|
3625
3798
|
const lockStatus = checkLockFile(lockPath);
|
|
@@ -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
|
});
|
|
@@ -5196,6 +5390,7 @@ var init_roadmap_state = __esm({
|
|
|
5196
5390
|
// ../core/dist/templates/slicer-prompt.js
|
|
5197
5391
|
import * as fs15 from "fs";
|
|
5198
5392
|
import * as path14 from "path";
|
|
5393
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5199
5394
|
function loadSlicerTemplate(templateDir) {
|
|
5200
5395
|
if (cachedTemplate) {
|
|
5201
5396
|
return cachedTemplate;
|
|
@@ -5231,10 +5426,12 @@ function createSlicerPromptVars(title, section, description, prdDir, prdFilename
|
|
|
5231
5426
|
prdDir
|
|
5232
5427
|
};
|
|
5233
5428
|
}
|
|
5234
|
-
var DEFAULT_SLICER_TEMPLATE, cachedTemplate;
|
|
5429
|
+
var __filename, __dirname, DEFAULT_SLICER_TEMPLATE, cachedTemplate;
|
|
5235
5430
|
var init_slicer_prompt = __esm({
|
|
5236
5431
|
"../core/dist/templates/slicer-prompt.js"() {
|
|
5237
5432
|
"use strict";
|
|
5433
|
+
__filename = fileURLToPath2(import.meta.url);
|
|
5434
|
+
__dirname = path14.dirname(__filename);
|
|
5238
5435
|
DEFAULT_SLICER_TEMPLATE = `You are a **PRD Creator Agent**. Your job: analyze the codebase and write a complete Product Requirements Document (PRD) for a feature.
|
|
5239
5436
|
|
|
5240
5437
|
When this activates: \`PRD Creator: Initializing\`
|
|
@@ -5508,11 +5705,11 @@ function scanExistingPrdSlugs(prdDir) {
|
|
|
5508
5705
|
}
|
|
5509
5706
|
return slugs;
|
|
5510
5707
|
}
|
|
5511
|
-
function buildProviderArgs(provider,
|
|
5708
|
+
function buildProviderArgs(provider, prompt, workingDir) {
|
|
5512
5709
|
if (provider === "codex") {
|
|
5513
|
-
return ["exec", "-C", workingDir, "--yolo",
|
|
5710
|
+
return ["exec", "-C", workingDir, "--yolo", prompt];
|
|
5514
5711
|
}
|
|
5515
|
-
return ["-p",
|
|
5712
|
+
return ["-p", prompt, "--dangerously-skip-permissions"];
|
|
5516
5713
|
}
|
|
5517
5714
|
async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
5518
5715
|
const itemSlug = slugify(item.title);
|
|
@@ -5532,9 +5729,9 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5532
5729
|
fs16.mkdirSync(prdDir, { recursive: true });
|
|
5533
5730
|
}
|
|
5534
5731
|
const promptVars = createSlicerPromptVars(item.title, item.section, item.description, prdDir, filename);
|
|
5535
|
-
const
|
|
5732
|
+
const prompt = renderSlicerPrompt(promptVars);
|
|
5536
5733
|
const provider = resolveJobProvider(config, "slicer");
|
|
5537
|
-
const providerArgs = buildProviderArgs(provider,
|
|
5734
|
+
const providerArgs = buildProviderArgs(provider, prompt, projectDir);
|
|
5538
5735
|
const logDir = path15.join(projectDir, "logs");
|
|
5539
5736
|
if (!fs16.existsSync(logDir)) {
|
|
5540
5737
|
fs16.mkdirSync(logDir, { recursive: true });
|
|
@@ -5543,7 +5740,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5543
5740
|
const logStream = fs16.createWriteStream(logFile, { flags: "w" });
|
|
5544
5741
|
logStream.on("error", () => {
|
|
5545
5742
|
});
|
|
5546
|
-
return new Promise((
|
|
5743
|
+
return new Promise((resolve9) => {
|
|
5547
5744
|
const childEnv = {
|
|
5548
5745
|
...process.env,
|
|
5549
5746
|
...config.providerEnv
|
|
@@ -5561,7 +5758,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5561
5758
|
});
|
|
5562
5759
|
child.on("error", (error2) => {
|
|
5563
5760
|
logStream.end();
|
|
5564
|
-
|
|
5761
|
+
resolve9({
|
|
5565
5762
|
sliced: false,
|
|
5566
5763
|
error: `Failed to spawn provider: ${error2.message}`,
|
|
5567
5764
|
item
|
|
@@ -5570,7 +5767,7 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5570
5767
|
child.on("close", (code) => {
|
|
5571
5768
|
logStream.end();
|
|
5572
5769
|
if (code !== 0) {
|
|
5573
|
-
|
|
5770
|
+
resolve9({
|
|
5574
5771
|
sliced: false,
|
|
5575
5772
|
error: `Provider exited with code ${code ?? 1}`,
|
|
5576
5773
|
item
|
|
@@ -5578,14 +5775,14 @@ async function sliceRoadmapItem(projectDir, prdDir, item, config) {
|
|
|
5578
5775
|
return;
|
|
5579
5776
|
}
|
|
5580
5777
|
if (!fs16.existsSync(filePath)) {
|
|
5581
|
-
|
|
5778
|
+
resolve9({
|
|
5582
5779
|
sliced: false,
|
|
5583
5780
|
error: `Provider did not create expected file: ${filePath}`,
|
|
5584
5781
|
item
|
|
5585
5782
|
});
|
|
5586
5783
|
return;
|
|
5587
5784
|
}
|
|
5588
|
-
|
|
5785
|
+
resolve9({
|
|
5589
5786
|
sliced: true,
|
|
5590
5787
|
file: filename,
|
|
5591
5788
|
item
|
|
@@ -5746,7 +5943,7 @@ async function executeScript(scriptPath, args = [], env = {}, options = {}) {
|
|
|
5746
5943
|
return result.exitCode;
|
|
5747
5944
|
}
|
|
5748
5945
|
async function executeScriptWithOutput(scriptPath, args = [], env = {}, options = {}) {
|
|
5749
|
-
return new Promise((
|
|
5946
|
+
return new Promise((resolve9, reject) => {
|
|
5750
5947
|
const childEnv = {
|
|
5751
5948
|
...process.env,
|
|
5752
5949
|
...env
|
|
@@ -5772,7 +5969,7 @@ async function executeScriptWithOutput(scriptPath, args = [], env = {}, options
|
|
|
5772
5969
|
reject(error2);
|
|
5773
5970
|
});
|
|
5774
5971
|
child.on("close", (code) => {
|
|
5775
|
-
|
|
5972
|
+
resolve9({
|
|
5776
5973
|
exitCode: code ?? 1,
|
|
5777
5974
|
stdout: stdoutChunks.join(""),
|
|
5778
5975
|
stderr: stderrChunks.join("")
|
|
@@ -6167,6 +6364,7 @@ function getLockPathForJob(projectPath, jobType) {
|
|
|
6167
6364
|
case "audit":
|
|
6168
6365
|
return auditLockPath(projectPath);
|
|
6169
6366
|
case "slicer":
|
|
6367
|
+
case "planner":
|
|
6170
6368
|
return plannerLockPath(projectPath);
|
|
6171
6369
|
case "analytics":
|
|
6172
6370
|
return analyticsLockPath(projectPath);
|
|
@@ -6723,13 +6921,13 @@ async function runAnalytics(config, projectDir) {
|
|
|
6723
6921
|
logger3.info("Fetching Amplitude data", { lookbackDays: config.analytics.lookbackDays });
|
|
6724
6922
|
const data = await fetchAmplitudeData(apiKey, secretKey, config.analytics.lookbackDays);
|
|
6725
6923
|
const systemPrompt = config.analytics.analysisPrompt?.trim() || DEFAULT_ANALYTICS_PROMPT;
|
|
6726
|
-
const
|
|
6924
|
+
const prompt = `${systemPrompt}
|
|
6727
6925
|
|
|
6728
6926
|
--- AMPLITUDE DATA ---
|
|
6729
6927
|
${JSON.stringify(data, null, 2)}`;
|
|
6730
6928
|
const tmpDir = fs19.mkdtempSync(path19.join(os8.tmpdir(), "nw-analytics-"));
|
|
6731
6929
|
const promptFile = path19.join(tmpDir, "analytics-prompt.md");
|
|
6732
|
-
fs19.writeFileSync(promptFile,
|
|
6930
|
+
fs19.writeFileSync(promptFile, prompt, "utf-8");
|
|
6733
6931
|
try {
|
|
6734
6932
|
const provider = resolveJobProvider(config, "analytics");
|
|
6735
6933
|
const providerCmd = PROVIDER_COMMANDS[provider];
|
|
@@ -6986,6 +7184,14 @@ sequenceDiagram
|
|
|
6986
7184
|
}
|
|
6987
7185
|
});
|
|
6988
7186
|
|
|
7187
|
+
// ../core/dist/jobs/index.js
|
|
7188
|
+
var init_jobs = __esm({
|
|
7189
|
+
"../core/dist/jobs/index.js"() {
|
|
7190
|
+
"use strict";
|
|
7191
|
+
init_job_registry();
|
|
7192
|
+
}
|
|
7193
|
+
});
|
|
7194
|
+
|
|
6989
7195
|
// ../core/dist/index.js
|
|
6990
7196
|
var dist_exports = {};
|
|
6991
7197
|
__export(dist_exports, {
|
|
@@ -7067,6 +7273,7 @@ __export(dist_exports, {
|
|
|
7067
7273
|
HISTORY_FILE_NAME: () => HISTORY_FILE_NAME,
|
|
7068
7274
|
HORIZON_LABELS: () => HORIZON_LABELS,
|
|
7069
7275
|
HORIZON_LABEL_INFO: () => HORIZON_LABEL_INFO,
|
|
7276
|
+
JOB_REGISTRY: () => JOB_REGISTRY,
|
|
7070
7277
|
LOCK_FILE_PREFIX: () => LOCK_FILE_PREFIX,
|
|
7071
7278
|
LOG_DIR: () => LOG_DIR,
|
|
7072
7279
|
LOG_FILE_NAMES: () => LOG_FILE_NAMES,
|
|
@@ -7098,7 +7305,9 @@ __export(dist_exports, {
|
|
|
7098
7305
|
analyticsLockPath: () => analyticsLockPath,
|
|
7099
7306
|
auditLockPath: () => auditLockPath,
|
|
7100
7307
|
buildDescription: () => buildDescription,
|
|
7308
|
+
buildJobEnvOverrides: () => buildJobEnvOverrides,
|
|
7101
7309
|
calculateStringSimilarity: () => calculateStringSimilarity,
|
|
7310
|
+
camelToUpperSnake: () => camelToUpperSnake,
|
|
7102
7311
|
canStartJob: () => canStartJob,
|
|
7103
7312
|
cancelProcess: () => cancelProcess,
|
|
7104
7313
|
checkConfigFile: () => checkConfigFile,
|
|
@@ -7163,23 +7372,30 @@ __export(dist_exports, {
|
|
|
7163
7372
|
formatTelegramPayload: () => formatTelegramPayload,
|
|
7164
7373
|
generateItemHash: () => generateItemHash,
|
|
7165
7374
|
generateMarker: () => generateMarker,
|
|
7375
|
+
getAllJobDefs: () => getAllJobDefs,
|
|
7166
7376
|
getBranchTipTimestamp: () => getBranchTipTimestamp,
|
|
7167
7377
|
getCrontabInfo: () => getCrontabInfo,
|
|
7168
7378
|
getDb: () => getDb,
|
|
7169
7379
|
getDbPath: () => getDbPath,
|
|
7170
7380
|
getDefaultConfig: () => getDefaultConfig,
|
|
7381
|
+
getDefaultQueuePriority: () => getDefaultQueuePriority,
|
|
7171
7382
|
getEntries: () => getEntries,
|
|
7172
7383
|
getEventColor: () => getEventColor,
|
|
7173
7384
|
getEventEmoji: () => getEventEmoji,
|
|
7174
7385
|
getEventTitle: () => getEventTitle,
|
|
7175
7386
|
getHistoryPath: () => getHistoryPath,
|
|
7176
7387
|
getInFlightCount: () => getInFlightCount,
|
|
7388
|
+
getJobDef: () => getJobDef,
|
|
7389
|
+
getJobDefByCommand: () => getJobDefByCommand,
|
|
7390
|
+
getJobDefByLogName: () => getJobDefByLogName,
|
|
7177
7391
|
getJobPriority: () => getJobPriority,
|
|
7178
7392
|
getJobRunsAnalytics: () => getJobRunsAnalytics,
|
|
7179
7393
|
getLabelsForSection: () => getLabelsForSection,
|
|
7180
7394
|
getLastExecution: () => getLastExecution,
|
|
7181
7395
|
getLastLogLines: () => getLastLogLines,
|
|
7182
7396
|
getLockFilePaths: () => getLockFilePaths,
|
|
7397
|
+
getLockSuffix: () => getLockSuffix,
|
|
7398
|
+
getLogFileNames: () => getLogFileNames,
|
|
7183
7399
|
getLogInfo: () => getLogInfo,
|
|
7184
7400
|
getNextPendingJob: () => getNextPendingJob,
|
|
7185
7401
|
getNextPrdNumber: () => getNextPrdNumber,
|
|
@@ -7200,6 +7416,7 @@ __export(dist_exports, {
|
|
|
7200
7416
|
getStateFilePath: () => getStateFilePath,
|
|
7201
7417
|
getStateItem: () => getStateItem,
|
|
7202
7418
|
getUncheckedItems: () => getUncheckedItems,
|
|
7419
|
+
getValidJobTypes: () => getValidJobTypes,
|
|
7203
7420
|
groupBySection: () => groupBySection2,
|
|
7204
7421
|
hasEntry: () => hasEntry,
|
|
7205
7422
|
hasNewItems: () => hasNewItems,
|
|
@@ -7226,6 +7443,7 @@ __export(dist_exports, {
|
|
|
7226
7443
|
markJobRunning: () => markJobRunning,
|
|
7227
7444
|
markPrdDone: () => markPrdDone,
|
|
7228
7445
|
migrateJsonToSqlite: () => migrateJsonToSqlite,
|
|
7446
|
+
normalizeJobConfig: () => normalizeJobConfig,
|
|
7229
7447
|
normalizeSchedulingPriority: () => normalizeSchedulingPriority,
|
|
7230
7448
|
parsePrdDependencies: () => parsePrdDependencies,
|
|
7231
7449
|
parseRoadmap: () => parseRoadmap,
|
|
@@ -7235,6 +7453,7 @@ __export(dist_exports, {
|
|
|
7235
7453
|
prepareBranchWorktree: () => prepareBranchWorktree,
|
|
7236
7454
|
prepareDetachedWorktree: () => prepareDetachedWorktree,
|
|
7237
7455
|
projectRuntimeKey: () => projectRuntimeKey,
|
|
7456
|
+
pruneProjectData: () => pruneProjectData,
|
|
7238
7457
|
qaLockPath: () => qaLockPath,
|
|
7239
7458
|
readCrontab: () => readCrontab,
|
|
7240
7459
|
readPrdStates: () => readPrdStates,
|
|
@@ -7245,6 +7464,7 @@ __export(dist_exports, {
|
|
|
7245
7464
|
removeEntries: () => removeEntries,
|
|
7246
7465
|
removeEntriesForProject: () => removeEntriesForProject,
|
|
7247
7466
|
removeJob: () => removeJob,
|
|
7467
|
+
removeProject: () => removeProject,
|
|
7248
7468
|
renderPrdTemplate: () => renderPrdTemplate,
|
|
7249
7469
|
renderSlicerPrompt: () => renderSlicerPrompt,
|
|
7250
7470
|
resetRepositories: () => resetRepositories,
|
|
@@ -7329,6 +7549,7 @@ var init_dist = __esm({
|
|
|
7329
7549
|
init_analytics();
|
|
7330
7550
|
init_prd_template();
|
|
7331
7551
|
init_slicer_prompt();
|
|
7552
|
+
init_jobs();
|
|
7332
7553
|
}
|
|
7333
7554
|
});
|
|
7334
7555
|
|
|
@@ -7336,19 +7557,19 @@ var init_dist = __esm({
|
|
|
7336
7557
|
import "reflect-metadata";
|
|
7337
7558
|
import { Command as Command3 } from "commander";
|
|
7338
7559
|
import { existsSync as existsSync30, readFileSync as readFileSync19 } from "fs";
|
|
7339
|
-
import { fileURLToPath as
|
|
7340
|
-
import { dirname as
|
|
7560
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
7561
|
+
import { dirname as dirname12, join as join36 } from "path";
|
|
7341
7562
|
|
|
7342
7563
|
// src/commands/init.ts
|
|
7343
7564
|
init_dist();
|
|
7344
7565
|
import fs20 from "fs";
|
|
7345
7566
|
import path20 from "path";
|
|
7346
7567
|
import { execSync as execSync3 } from "child_process";
|
|
7347
|
-
import { fileURLToPath as
|
|
7348
|
-
import { dirname as
|
|
7568
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7569
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
7349
7570
|
import * as readline from "readline";
|
|
7350
|
-
var
|
|
7351
|
-
var __dirname2 =
|
|
7571
|
+
var __filename2 = fileURLToPath3(import.meta.url);
|
|
7572
|
+
var __dirname2 = dirname6(__filename2);
|
|
7352
7573
|
function findTemplatesDir(startDir) {
|
|
7353
7574
|
let d = startDir;
|
|
7354
7575
|
for (let i = 0; i < 8; i++) {
|
|
@@ -7356,11 +7577,19 @@ function findTemplatesDir(startDir) {
|
|
|
7356
7577
|
if (fs20.existsSync(candidate) && fs20.statSync(candidate).isDirectory()) {
|
|
7357
7578
|
return candidate;
|
|
7358
7579
|
}
|
|
7359
|
-
d =
|
|
7580
|
+
d = dirname6(d);
|
|
7360
7581
|
}
|
|
7361
7582
|
return join18(startDir, "templates");
|
|
7362
7583
|
}
|
|
7363
7584
|
var TEMPLATES_DIR = findTemplatesDir(__dirname2);
|
|
7585
|
+
var NW_SKILLS = [
|
|
7586
|
+
"nw-create-prd",
|
|
7587
|
+
"nw-add-issue",
|
|
7588
|
+
"nw-run",
|
|
7589
|
+
"nw-slice",
|
|
7590
|
+
"nw-board-sync",
|
|
7591
|
+
"nw-review"
|
|
7592
|
+
];
|
|
7364
7593
|
function hasPlaywrightDependency(cwd) {
|
|
7365
7594
|
const packageJsonPath = path20.join(cwd, "package.json");
|
|
7366
7595
|
if (!fs20.existsSync(packageJsonPath)) {
|
|
@@ -7407,7 +7636,7 @@ function promptYesNo(question, defaultNo = true) {
|
|
|
7407
7636
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
7408
7637
|
return Promise.resolve(false);
|
|
7409
7638
|
}
|
|
7410
|
-
return new Promise((
|
|
7639
|
+
return new Promise((resolve9) => {
|
|
7411
7640
|
const rl = readline.createInterface({
|
|
7412
7641
|
input: process.stdin,
|
|
7413
7642
|
output: process.stdout
|
|
@@ -7417,10 +7646,10 @@ function promptYesNo(question, defaultNo = true) {
|
|
|
7417
7646
|
rl.close();
|
|
7418
7647
|
const normalized = answer.trim().toLowerCase();
|
|
7419
7648
|
if (normalized === "") {
|
|
7420
|
-
|
|
7649
|
+
resolve9(!defaultNo);
|
|
7421
7650
|
return;
|
|
7422
7651
|
}
|
|
7423
|
-
|
|
7652
|
+
resolve9(normalized === "y" || normalized === "yes");
|
|
7424
7653
|
});
|
|
7425
7654
|
});
|
|
7426
7655
|
}
|
|
@@ -7523,7 +7752,7 @@ function getDefaultBranch(cwd) {
|
|
|
7523
7752
|
}
|
|
7524
7753
|
}
|
|
7525
7754
|
function promptProviderSelection(providers) {
|
|
7526
|
-
return new Promise((
|
|
7755
|
+
return new Promise((resolve9, reject) => {
|
|
7527
7756
|
const rl = readline.createInterface({
|
|
7528
7757
|
input: process.stdin,
|
|
7529
7758
|
output: process.stdout
|
|
@@ -7539,7 +7768,7 @@ function promptProviderSelection(providers) {
|
|
|
7539
7768
|
reject(new Error("Invalid selection. Please run init again and select a valid number."));
|
|
7540
7769
|
return;
|
|
7541
7770
|
}
|
|
7542
|
-
|
|
7771
|
+
resolve9(providers[selection - 1]);
|
|
7543
7772
|
});
|
|
7544
7773
|
});
|
|
7545
7774
|
}
|
|
@@ -7656,12 +7885,67 @@ function addToGitignore(cwd) {
|
|
|
7656
7885
|
fs20.writeFileSync(gitignorePath, newContent);
|
|
7657
7886
|
console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
|
|
7658
7887
|
}
|
|
7888
|
+
function installSkills(cwd, provider, force, templatesDir) {
|
|
7889
|
+
const skillsTemplatesDir = path20.join(templatesDir, "skills");
|
|
7890
|
+
if (!fs20.existsSync(skillsTemplatesDir)) {
|
|
7891
|
+
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
7892
|
+
}
|
|
7893
|
+
const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
|
|
7894
|
+
const isCodexProvider = provider === "codex";
|
|
7895
|
+
const claudeDir = path20.join(cwd, ".claude");
|
|
7896
|
+
if (isClaudeProvider || fs20.existsSync(claudeDir)) {
|
|
7897
|
+
ensureDir(claudeDir);
|
|
7898
|
+
const skillsDir = path20.join(claudeDir, "skills");
|
|
7899
|
+
ensureDir(skillsDir);
|
|
7900
|
+
let installed = 0;
|
|
7901
|
+
let skipped = 0;
|
|
7902
|
+
for (const skillName of NW_SKILLS) {
|
|
7903
|
+
const templateFile = path20.join(skillsTemplatesDir, `${skillName}.md`);
|
|
7904
|
+
if (!fs20.existsSync(templateFile)) continue;
|
|
7905
|
+
const skillDir = path20.join(skillsDir, skillName);
|
|
7906
|
+
ensureDir(skillDir);
|
|
7907
|
+
const target = path20.join(skillDir, "SKILL.md");
|
|
7908
|
+
if (fs20.existsSync(target) && !force) {
|
|
7909
|
+
skipped++;
|
|
7910
|
+
continue;
|
|
7911
|
+
}
|
|
7912
|
+
fs20.copyFileSync(templateFile, target);
|
|
7913
|
+
installed++;
|
|
7914
|
+
}
|
|
7915
|
+
return { location: ".claude/skills/", installed, skipped, type: "claude" };
|
|
7916
|
+
}
|
|
7917
|
+
if (isCodexProvider) {
|
|
7918
|
+
const agentsFile = path20.join(cwd, "AGENTS.md");
|
|
7919
|
+
const blockFile = path20.join(skillsTemplatesDir, "_codex-block.md");
|
|
7920
|
+
if (!fs20.existsSync(blockFile)) {
|
|
7921
|
+
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
7922
|
+
}
|
|
7923
|
+
const block = fs20.readFileSync(blockFile, "utf-8");
|
|
7924
|
+
const marker = "## Night Watch Skills";
|
|
7925
|
+
if (!fs20.existsSync(agentsFile)) {
|
|
7926
|
+
fs20.writeFileSync(agentsFile, block);
|
|
7927
|
+
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
7928
|
+
}
|
|
7929
|
+
const existing = fs20.readFileSync(agentsFile, "utf-8");
|
|
7930
|
+
if (existing.includes(marker)) {
|
|
7931
|
+
if (!force) {
|
|
7932
|
+
return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
|
|
7933
|
+
}
|
|
7934
|
+
const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
|
|
7935
|
+
fs20.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
|
|
7936
|
+
} else {
|
|
7937
|
+
fs20.appendFileSync(agentsFile, "\n\n" + block);
|
|
7938
|
+
}
|
|
7939
|
+
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
7940
|
+
}
|
|
7941
|
+
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
7942
|
+
}
|
|
7659
7943
|
function initCommand(program2) {
|
|
7660
7944
|
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
7945
|
const cwd = process.cwd();
|
|
7662
7946
|
const force = options.force || false;
|
|
7663
7947
|
const prdDir = options.prdDir || DEFAULT_PRD_DIR;
|
|
7664
|
-
const totalSteps =
|
|
7948
|
+
const totalSteps = 13;
|
|
7665
7949
|
const interactive = isInteractiveInitSession();
|
|
7666
7950
|
console.log();
|
|
7667
7951
|
header("Night Watch CLI - Initializing");
|
|
@@ -7846,6 +8130,20 @@ function initCommand(program2) {
|
|
|
7846
8130
|
auditResolution.source
|
|
7847
8131
|
);
|
|
7848
8132
|
templateSources.push({ name: "audit.md", source: auditResult.source });
|
|
8133
|
+
const plannerResolution = resolveTemplatePath(
|
|
8134
|
+
"prd-creator.md",
|
|
8135
|
+
customTemplatesDir,
|
|
8136
|
+
TEMPLATES_DIR
|
|
8137
|
+
);
|
|
8138
|
+
const plannerResult = processTemplate(
|
|
8139
|
+
"prd-creator.md",
|
|
8140
|
+
path20.join(instructionsDir, "prd-creator.md"),
|
|
8141
|
+
replacements,
|
|
8142
|
+
force,
|
|
8143
|
+
plannerResolution.path,
|
|
8144
|
+
plannerResolution.source
|
|
8145
|
+
);
|
|
8146
|
+
templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
|
|
7849
8147
|
step(9, totalSteps, "Creating configuration file...");
|
|
7850
8148
|
const configPath = path20.join(cwd, CONFIG_FILE_NAME);
|
|
7851
8149
|
if (fs20.existsSync(configPath) && !force) {
|
|
@@ -7912,7 +8210,19 @@ function initCommand(program2) {
|
|
|
7912
8210
|
` Warning: Could not register in global registry: ${regErr instanceof Error ? regErr.message : String(regErr)}`
|
|
7913
8211
|
);
|
|
7914
8212
|
}
|
|
7915
|
-
step(12, totalSteps, "
|
|
8213
|
+
step(12, totalSteps, "Installing Night Watch skills...");
|
|
8214
|
+
const skillsResult = installSkills(cwd, selectedProvider, force, TEMPLATES_DIR);
|
|
8215
|
+
if (skillsResult.installed > 0) {
|
|
8216
|
+
success(`Installed ${skillsResult.installed} skills to ${skillsResult.location}`);
|
|
8217
|
+
for (const skillName of NW_SKILLS) {
|
|
8218
|
+
console.log(` /${skillName}`);
|
|
8219
|
+
}
|
|
8220
|
+
} else if (skillsResult.skipped > 0) {
|
|
8221
|
+
info(`Skills already installed (use --force to overwrite)`);
|
|
8222
|
+
} else if (skillsResult.type === "none") {
|
|
8223
|
+
info("No compatible AI skills directory detected \u2014 skipping.");
|
|
8224
|
+
}
|
|
8225
|
+
step(13, totalSteps, "Initialization complete!");
|
|
7916
8226
|
header("Initialization Complete");
|
|
7917
8227
|
const filesTable = createTable({ head: ["Created Files", ""] });
|
|
7918
8228
|
filesTable.push(["PRD Directory", `${prdDir}/done/`]);
|
|
@@ -7922,9 +8232,19 @@ function initCommand(program2) {
|
|
|
7922
8232
|
filesTable.push(["", `instructions/pr-reviewer.md (${templateSources[2].source})`]);
|
|
7923
8233
|
filesTable.push(["", `instructions/qa.md (${templateSources[3].source})`]);
|
|
7924
8234
|
filesTable.push(["", `instructions/audit.md (${templateSources[4].source})`]);
|
|
8235
|
+
filesTable.push(["", `instructions/prd-creator.md (${templateSources[5].source})`]);
|
|
7925
8236
|
filesTable.push(["Config File", CONFIG_FILE_NAME]);
|
|
7926
8237
|
filesTable.push(["Board Setup", boardSetupStatus]);
|
|
7927
8238
|
filesTable.push(["Global Registry", "~/.night-watch/projects.json"]);
|
|
8239
|
+
let skillsSummary;
|
|
8240
|
+
if (skillsResult.installed > 0) {
|
|
8241
|
+
skillsSummary = `${skillsResult.installed} skills \u2192 ${skillsResult.location}`;
|
|
8242
|
+
} else if (skillsResult.skipped > 0) {
|
|
8243
|
+
skillsSummary = `Already installed (${skillsResult.location})`;
|
|
8244
|
+
} else {
|
|
8245
|
+
skillsSummary = "Skipped";
|
|
8246
|
+
}
|
|
8247
|
+
filesTable.push(["Skills", skillsSummary]);
|
|
7928
8248
|
console.log(filesTable.toString());
|
|
7929
8249
|
header("Configuration");
|
|
7930
8250
|
label("Provider", selectedProvider);
|
|
@@ -7936,6 +8256,9 @@ function initCommand(program2) {
|
|
|
7936
8256
|
info("2. Run `night-watch install` to set up cron jobs");
|
|
7937
8257
|
info("3. Run `night-watch doctor` to verify the full setup");
|
|
7938
8258
|
info("4. Or run `night-watch run` to execute PRDs manually");
|
|
8259
|
+
if (skillsResult.installed > 0) {
|
|
8260
|
+
info(`5. Use /nw-create-prd, /nw-run, /nw-add-issue and more in your AI assistant`);
|
|
8261
|
+
}
|
|
7939
8262
|
console.log();
|
|
7940
8263
|
});
|
|
7941
8264
|
}
|
|
@@ -7991,7 +8314,7 @@ async function maybeApplyCronSchedulingDelay(config, jobType, projectDir) {
|
|
|
7991
8314
|
return plan;
|
|
7992
8315
|
}
|
|
7993
8316
|
if (plan.totalDelayMinutes > 0) {
|
|
7994
|
-
await new Promise((
|
|
8317
|
+
await new Promise((resolve9) => setTimeout(resolve9, plan.totalDelayMinutes * 6e4));
|
|
7995
8318
|
}
|
|
7996
8319
|
return getSchedulingPlan(projectDir, config, jobType);
|
|
7997
8320
|
}
|
|
@@ -9734,9 +10057,25 @@ function logsCommand(program2) {
|
|
|
9734
10057
|
|
|
9735
10058
|
// src/commands/prd.ts
|
|
9736
10059
|
init_dist();
|
|
10060
|
+
import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
|
|
9737
10061
|
import * as fs26 from "fs";
|
|
9738
10062
|
import * as path28 from "path";
|
|
9739
|
-
import
|
|
10063
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10064
|
+
import { dirname as dirname9 } from "path";
|
|
10065
|
+
var __filename3 = fileURLToPath4(import.meta.url);
|
|
10066
|
+
var __dirname3 = dirname9(__filename3);
|
|
10067
|
+
function findTemplatesDir2(startDir) {
|
|
10068
|
+
let current = startDir;
|
|
10069
|
+
for (let i = 0; i < 8; i++) {
|
|
10070
|
+
const candidate = path28.join(current, "templates");
|
|
10071
|
+
if (fs26.existsSync(candidate) && fs26.statSync(candidate).isDirectory()) {
|
|
10072
|
+
return candidate;
|
|
10073
|
+
}
|
|
10074
|
+
current = path28.dirname(current);
|
|
10075
|
+
}
|
|
10076
|
+
return path28.join(startDir, "templates");
|
|
10077
|
+
}
|
|
10078
|
+
var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
|
|
9740
10079
|
function slugify2(name) {
|
|
9741
10080
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
9742
10081
|
}
|
|
@@ -9749,13 +10088,186 @@ function getNextPrdNumber2(prdDir) {
|
|
|
9749
10088
|
});
|
|
9750
10089
|
return Math.max(0, ...numbers) + 1;
|
|
9751
10090
|
}
|
|
9752
|
-
function
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
10091
|
+
function extractPrdMarkdown(response) {
|
|
10092
|
+
const match = response.match(/(^#\s+[\s\S]*)/m);
|
|
10093
|
+
return match ? match[1].trim() : response.trim();
|
|
10094
|
+
}
|
|
10095
|
+
function extractPrdTitle(markdown) {
|
|
10096
|
+
const match = markdown.match(/^#\s+PRD:\s*(.+)/m);
|
|
10097
|
+
return match ? match[1].trim() : null;
|
|
10098
|
+
}
|
|
10099
|
+
function buildPrdPrompt(description, projectDir, planningPrinciples) {
|
|
10100
|
+
return `You are generating a PRD markdown file for Night Watch.
|
|
10101
|
+
|
|
10102
|
+
Return only the final PRD markdown.
|
|
10103
|
+
|
|
10104
|
+
Hard requirements:
|
|
10105
|
+
- Start with: # PRD: <title>
|
|
10106
|
+
- Do not ask follow-up questions
|
|
10107
|
+
- Do not add any preamble, commentary, or code fences
|
|
10108
|
+
- Do not describe what you are going to do
|
|
10109
|
+
- Do not mention these instructions
|
|
10110
|
+
- Treat the planning guide below as mandatory instructions, not background context
|
|
10111
|
+
|
|
10112
|
+
Project directory: ${projectDir}
|
|
10113
|
+
|
|
10114
|
+
Planning guide:
|
|
10115
|
+
${planningPrinciples}
|
|
10116
|
+
|
|
10117
|
+
User request:
|
|
10118
|
+
${description}
|
|
10119
|
+
|
|
10120
|
+
Now write the complete PRD markdown file.`;
|
|
10121
|
+
}
|
|
10122
|
+
function buildNativeClaudeEnv(baseEnv = process.env) {
|
|
10123
|
+
const env = { ...baseEnv };
|
|
10124
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
10125
|
+
delete env.ANTHROPIC_API_KEY;
|
|
10126
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
10127
|
+
delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
10128
|
+
delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
10129
|
+
delete env.API_TIMEOUT_MS;
|
|
10130
|
+
delete env.CLAUDE_CODE_SSE_PORT;
|
|
10131
|
+
delete env.CLAUDE_NIGHTS_WATCH_DIR;
|
|
10132
|
+
delete env.NW_CLAUDE_MODEL_ID;
|
|
10133
|
+
delete env.NW_CLAUDE_PRIMARY_MODEL_ID;
|
|
10134
|
+
delete env.NW_CLAUDE_SECONDARY_MODEL_ID;
|
|
10135
|
+
delete env.NW_PROVIDER_CMD;
|
|
10136
|
+
delete env.NW_PROVIDER_SUBCOMMAND;
|
|
10137
|
+
delete env.NW_PROVIDER_PROMPT_FLAG;
|
|
10138
|
+
delete env.NW_PROVIDER_APPROVE_FLAG;
|
|
10139
|
+
delete env.NW_PROVIDER_WORKDIR_FLAG;
|
|
10140
|
+
delete env.NW_PROVIDER_MODEL_FLAG;
|
|
10141
|
+
delete env.NW_PROVIDER_MODEL;
|
|
10142
|
+
delete env.NW_PROVIDER_LABEL;
|
|
10143
|
+
return env;
|
|
10144
|
+
}
|
|
10145
|
+
function resolvePrdCreateDir() {
|
|
10146
|
+
return "docs/PRDs";
|
|
10147
|
+
}
|
|
10148
|
+
function resolveGitHubBlobUrl(projectDir, relPath) {
|
|
10149
|
+
try {
|
|
10150
|
+
const remoteUrl = execSync5("git remote get-url origin", {
|
|
10151
|
+
cwd: projectDir,
|
|
10152
|
+
encoding: "utf-8",
|
|
10153
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
10154
|
+
}).trim();
|
|
10155
|
+
const branch = execSync5("git rev-parse --abbrev-ref HEAD", {
|
|
10156
|
+
cwd: projectDir,
|
|
10157
|
+
encoding: "utf-8",
|
|
10158
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
10159
|
+
}).trim();
|
|
10160
|
+
const httpsBase = remoteUrl.replace(/^git@github\.com:/, "https://github.com/").replace(/^ssh:\/\/git@github\.com\//, "https://github.com/").replace(/\.git$/, "");
|
|
10161
|
+
if (!httpsBase.startsWith("https://github.com/")) {
|
|
10162
|
+
return null;
|
|
10163
|
+
}
|
|
10164
|
+
const ref = branch && branch !== "HEAD" ? branch : "main";
|
|
10165
|
+
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path28.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
10166
|
+
} catch {
|
|
10167
|
+
return null;
|
|
10168
|
+
}
|
|
10169
|
+
}
|
|
10170
|
+
function buildGithubIssueBody(prdPath, projectDir, prdContent) {
|
|
10171
|
+
const relPath = path28.relative(projectDir, prdPath);
|
|
10172
|
+
const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
|
|
10173
|
+
const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
|
|
10174
|
+
return `${fileLine}
|
|
10175
|
+
|
|
10176
|
+
${prdContent}
|
|
10177
|
+
|
|
10178
|
+
---
|
|
10179
|
+
Created via \`night-watch prd create\`.`;
|
|
10180
|
+
}
|
|
10181
|
+
async function generatePrdWithClaude(description, projectDir, model) {
|
|
10182
|
+
const bundledTemplatePath = path28.join(TEMPLATES_DIR2, "prd-creator.md");
|
|
10183
|
+
const installedTemplatePath = path28.join(projectDir, "instructions", "prd-creator.md");
|
|
10184
|
+
const templatePath = fs26.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
|
|
10185
|
+
if (!fs26.existsSync(templatePath)) {
|
|
10186
|
+
return null;
|
|
10187
|
+
}
|
|
10188
|
+
const planningPrinciples = fs26.readFileSync(templatePath, "utf-8");
|
|
10189
|
+
const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
|
|
10190
|
+
const modelId = model ?? CLAUDE_MODEL_IDS.opus;
|
|
10191
|
+
const env = buildNativeClaudeEnv(process.env);
|
|
10192
|
+
return await new Promise((resolve9) => {
|
|
10193
|
+
const child = spawn4(
|
|
10194
|
+
"claude",
|
|
10195
|
+
[
|
|
10196
|
+
"-p",
|
|
10197
|
+
"--verbose",
|
|
10198
|
+
"--output-format",
|
|
10199
|
+
"stream-json",
|
|
10200
|
+
"--include-partial-messages",
|
|
10201
|
+
"--model",
|
|
10202
|
+
modelId,
|
|
10203
|
+
prompt
|
|
10204
|
+
],
|
|
10205
|
+
{ env, stdio: ["ignore", "pipe", "pipe"] }
|
|
10206
|
+
);
|
|
10207
|
+
let stdoutBuffer = "";
|
|
10208
|
+
let finalResult = "";
|
|
10209
|
+
child.stdout.on("data", (chunk) => {
|
|
10210
|
+
stdoutBuffer += chunk.toString("utf-8");
|
|
10211
|
+
while (stdoutBuffer.includes("\n")) {
|
|
10212
|
+
const newlineIndex = stdoutBuffer.indexOf("\n");
|
|
10213
|
+
const line = stdoutBuffer.slice(0, newlineIndex).trim();
|
|
10214
|
+
stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
|
|
10215
|
+
if (!line) continue;
|
|
10216
|
+
try {
|
|
10217
|
+
const payload = JSON.parse(line);
|
|
10218
|
+
if (payload.type === "stream_event") {
|
|
10219
|
+
const event = payload.event;
|
|
10220
|
+
const delta = event?.delta;
|
|
10221
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
10222
|
+
process.stdout.write(delta.text);
|
|
10223
|
+
}
|
|
10224
|
+
continue;
|
|
10225
|
+
}
|
|
10226
|
+
if (payload.type === "result" && typeof payload.result === "string") {
|
|
10227
|
+
finalResult = payload.result;
|
|
10228
|
+
}
|
|
10229
|
+
} catch {
|
|
10230
|
+
}
|
|
10231
|
+
}
|
|
9756
10232
|
});
|
|
10233
|
+
child.stderr.on("data", (chunk) => process.stderr.write(chunk));
|
|
10234
|
+
child.on("close", (code) => {
|
|
10235
|
+
if (stdoutBuffer.trim().length > 0) {
|
|
10236
|
+
try {
|
|
10237
|
+
const payload = JSON.parse(stdoutBuffer.trim());
|
|
10238
|
+
if (payload.type === "result" && typeof payload.result === "string") {
|
|
10239
|
+
finalResult = payload.result;
|
|
10240
|
+
}
|
|
10241
|
+
} catch {
|
|
10242
|
+
}
|
|
10243
|
+
}
|
|
10244
|
+
process.stdout.write("\n");
|
|
10245
|
+
resolve9(code === 0 && finalResult ? extractPrdMarkdown(finalResult) : null);
|
|
10246
|
+
});
|
|
10247
|
+
child.on("error", () => resolve9(null));
|
|
9757
10248
|
});
|
|
9758
10249
|
}
|
|
10250
|
+
function runGh(args, cwd) {
|
|
10251
|
+
const result = spawnSync("gh", args, { cwd, encoding: "utf-8" });
|
|
10252
|
+
if (result.status === 0) return (result.stdout ?? "").trim();
|
|
10253
|
+
return null;
|
|
10254
|
+
}
|
|
10255
|
+
function createGithubIssue(title, prdPath, projectDir, prdContent) {
|
|
10256
|
+
const tmpFile = path28.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
|
|
10257
|
+
try {
|
|
10258
|
+
const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
|
|
10259
|
+
fs26.writeFileSync(tmpFile, body, "utf-8");
|
|
10260
|
+
const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
|
|
10261
|
+
return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
|
|
10262
|
+
} catch {
|
|
10263
|
+
return null;
|
|
10264
|
+
} finally {
|
|
10265
|
+
try {
|
|
10266
|
+
fs26.unlinkSync(tmpFile);
|
|
10267
|
+
} catch {
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
}
|
|
9759
10271
|
function parseDependencies(content) {
|
|
9760
10272
|
const match = content.match(/\*\*Depends on:\*\*\s*(.+)/i) || content.match(/Depends on:\s*(.+)/i);
|
|
9761
10273
|
if (!match) return [];
|
|
@@ -9779,101 +10291,38 @@ function isClaimActive(claimPath, maxRuntime) {
|
|
|
9779
10291
|
}
|
|
9780
10292
|
function prdCommand(program2) {
|
|
9781
10293
|
const prd = program2.command("prd").description("Manage PRD files");
|
|
9782
|
-
prd.command("create").description("Generate a new PRD markdown file
|
|
10294
|
+
prd.command("create").description("Generate a new PRD markdown file using Claude").argument("<name>", "PRD description").option("--number", "Add auto-numbering prefix to the filename", false).option("--model <model>", "Claude model to use (e.g. sonnet, opus, or a full model ID)").action(async (name, options) => {
|
|
9783
10295
|
const projectDir = process.cwd();
|
|
9784
|
-
const
|
|
9785
|
-
const prdDir = path28.join(projectDir, config.prdDir);
|
|
10296
|
+
const prdDir = path28.join(projectDir, resolvePrdCreateDir());
|
|
9786
10297
|
if (!fs26.existsSync(prdDir)) {
|
|
9787
10298
|
fs26.mkdirSync(prdDir, { recursive: true });
|
|
9788
10299
|
}
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
}
|
|
9798
|
-
if (options.interactive) {
|
|
9799
|
-
const rl = readline2.createInterface({
|
|
9800
|
-
input: process.stdin,
|
|
9801
|
-
output: process.stdout
|
|
9802
|
-
});
|
|
9803
|
-
try {
|
|
9804
|
-
const complexityInput = await prompt(rl, "Complexity score (1-10, default 5): ");
|
|
9805
|
-
if (complexityInput) {
|
|
9806
|
-
const parsed = parseInt(complexityInput, 10);
|
|
9807
|
-
if (!isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
9808
|
-
complexityScore = parsed;
|
|
9809
|
-
}
|
|
9810
|
-
}
|
|
9811
|
-
const depsInput = await prompt(
|
|
9812
|
-
rl,
|
|
9813
|
-
"Dependencies (comma-separated filenames, or empty): "
|
|
9814
|
-
);
|
|
9815
|
-
if (depsInput) {
|
|
9816
|
-
dependsOn = depsInput.split(",").map((d) => d.trim()).filter((d) => d.length > 0);
|
|
9817
|
-
}
|
|
9818
|
-
const phasesInput = await prompt(rl, `Number of phases (default ${phaseCount}): `);
|
|
9819
|
-
if (phasesInput) {
|
|
9820
|
-
const parsed = parseInt(phasesInput, 10);
|
|
9821
|
-
if (!isNaN(parsed) && parsed >= 1) {
|
|
9822
|
-
phaseCount = parsed;
|
|
9823
|
-
}
|
|
9824
|
-
}
|
|
9825
|
-
} finally {
|
|
9826
|
-
rl.close();
|
|
9827
|
-
}
|
|
9828
|
-
}
|
|
9829
|
-
let complexityLevel;
|
|
9830
|
-
if (complexityScore <= 3) {
|
|
9831
|
-
complexityLevel = "LOW";
|
|
9832
|
-
} else if (complexityScore <= 7) {
|
|
9833
|
-
complexityLevel = "MEDIUM";
|
|
9834
|
-
} else {
|
|
9835
|
-
complexityLevel = "HIGH";
|
|
9836
|
-
}
|
|
9837
|
-
const slug = slugify2(name);
|
|
9838
|
-
let filename;
|
|
9839
|
-
if (options.number) {
|
|
9840
|
-
const nextNum = getNextPrdNumber2(prdDir);
|
|
9841
|
-
const padded = String(nextNum).padStart(2, "0");
|
|
9842
|
-
filename = `${padded}-${slug}.md`;
|
|
9843
|
-
} else {
|
|
9844
|
-
filename = `${slug}.md`;
|
|
10300
|
+
const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
|
|
10301
|
+
const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
|
|
10302
|
+
dim(`Calling Claude (${modelLabel}) to generate the PRD. It can take several minutes, please hang on!
|
|
10303
|
+
`);
|
|
10304
|
+
const generated = await generatePrdWithClaude(name, projectDir, resolvedModel);
|
|
10305
|
+
if (!generated) {
|
|
10306
|
+
error("Claude generation failed. Is the provider configured and available?");
|
|
10307
|
+
process.exit(1);
|
|
9845
10308
|
}
|
|
10309
|
+
const prdTitle = extractPrdTitle(generated) ?? name;
|
|
10310
|
+
const slug = slugify2(prdTitle);
|
|
10311
|
+
const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
|
|
9846
10312
|
const filePath = path28.join(prdDir, filename);
|
|
9847
10313
|
if (fs26.existsSync(filePath)) {
|
|
9848
10314
|
error(`File already exists: ${filePath}`);
|
|
9849
10315
|
dim("Use a different name or remove the existing file.");
|
|
9850
10316
|
process.exit(1);
|
|
9851
10317
|
}
|
|
9852
|
-
|
|
9853
|
-
if (options.template) {
|
|
9854
|
-
const templatePath = path28.resolve(options.template);
|
|
9855
|
-
if (!fs26.existsSync(templatePath)) {
|
|
9856
|
-
error(`Template file not found: ${templatePath}`);
|
|
9857
|
-
process.exit(1);
|
|
9858
|
-
}
|
|
9859
|
-
customTemplate = fs26.readFileSync(templatePath, "utf-8");
|
|
9860
|
-
}
|
|
9861
|
-
const vars = {
|
|
9862
|
-
title: name,
|
|
9863
|
-
dependsOn,
|
|
9864
|
-
complexityScore,
|
|
9865
|
-
complexityLevel,
|
|
9866
|
-
complexityBreakdown: [],
|
|
9867
|
-
phaseCount
|
|
9868
|
-
};
|
|
9869
|
-
const content = renderPrdTemplate(vars, customTemplate);
|
|
9870
|
-
fs26.writeFileSync(filePath, content, "utf-8");
|
|
10318
|
+
fs26.writeFileSync(filePath, generated, "utf-8");
|
|
9871
10319
|
header("PRD Created");
|
|
9872
10320
|
success(`Created: ${filePath}`);
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
10321
|
+
const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
|
|
10322
|
+
if (issueUrl) {
|
|
10323
|
+
info(`Issue: ${issueUrl}`);
|
|
10324
|
+
} else {
|
|
10325
|
+
dim("GitHub issue creation skipped (gh not available or not in a GitHub repo).");
|
|
9877
10326
|
}
|
|
9878
10327
|
});
|
|
9879
10328
|
prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
|
|
@@ -9915,16 +10364,14 @@ function prdCommand(program2) {
|
|
|
9915
10364
|
dim(" No PRDs found.");
|
|
9916
10365
|
return;
|
|
9917
10366
|
}
|
|
9918
|
-
const table = createTable({
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
const statusDisplay = prd2.claimed && prd2.claimInfo ? `claimed (${prd2.claimInfo.hostname}:${prd2.claimInfo.pid})` : status;
|
|
9924
|
-
table.push([prd2.name, statusDisplay, prd2.dependencies.join(", ") || "-"]);
|
|
10367
|
+
const table = createTable({ head: ["Name", "Status", "Dependencies"] });
|
|
10368
|
+
for (const prdEntry of pending) {
|
|
10369
|
+
const status = prdEntry.claimed ? "claimed" : "pending";
|
|
10370
|
+
const statusDisplay = prdEntry.claimed && prdEntry.claimInfo ? `claimed (${prdEntry.claimInfo.hostname}:${prdEntry.claimInfo.pid})` : status;
|
|
10371
|
+
table.push([prdEntry.name, statusDisplay, prdEntry.dependencies.join(", ") || "-"]);
|
|
9925
10372
|
}
|
|
9926
|
-
for (const
|
|
9927
|
-
table.push([
|
|
10373
|
+
for (const prdEntry of done) {
|
|
10374
|
+
table.push([prdEntry.name, "done", prdEntry.dependencies.join(", ") || "-"]);
|
|
9928
10375
|
}
|
|
9929
10376
|
console.log(table.toString());
|
|
9930
10377
|
const claimedCount = pending.filter((p) => p.claimed).length;
|
|
@@ -11444,11 +11891,11 @@ function createSchedulesTab() {
|
|
|
11444
11891
|
|
|
11445
11892
|
// src/commands/dashboard/tab-actions.ts
|
|
11446
11893
|
import blessed4 from "blessed";
|
|
11447
|
-
import { spawn as
|
|
11894
|
+
import { spawn as spawn5 } from "child_process";
|
|
11448
11895
|
function spawnAction(args, ctx, outputBox, onDone) {
|
|
11449
11896
|
outputBox.setContent("{cyan-fg}Starting...{/cyan-fg}\n");
|
|
11450
11897
|
ctx.screen.render();
|
|
11451
|
-
const child =
|
|
11898
|
+
const child = spawn5("npx", ["tsx", "src/cli.ts", ...args], {
|
|
11452
11899
|
cwd: ctx.projectDir,
|
|
11453
11900
|
stdio: ["ignore", "pipe", "pipe"],
|
|
11454
11901
|
env: { ...process.env }
|
|
@@ -12159,7 +12606,7 @@ function doctorCommand(program2) {
|
|
|
12159
12606
|
program2.command("doctor").description("Check Night Watch configuration and system health").option("--fix", "Automatically fix fixable issues").action(async (options) => {
|
|
12160
12607
|
const projectDir = process.cwd();
|
|
12161
12608
|
const config = loadConfig(projectDir);
|
|
12162
|
-
const totalChecks =
|
|
12609
|
+
const totalChecks = 7;
|
|
12163
12610
|
let checkNum = 1;
|
|
12164
12611
|
let passedChecks = 0;
|
|
12165
12612
|
let fixedChecks = 0;
|
|
@@ -12185,11 +12632,12 @@ function doctorCommand(program2) {
|
|
|
12185
12632
|
const ghResult = runCheck(checkNum++, totalChecks, "GitHub CLI", () => checkGhCli(), options);
|
|
12186
12633
|
if (ghResult.passed) passedChecks++;
|
|
12187
12634
|
if (ghResult.fixed) fixedChecks++;
|
|
12635
|
+
const resolvedProviderCli = BUILT_IN_PRESETS[config.provider]?.command ?? config.provider;
|
|
12188
12636
|
const providerResult = runCheck(
|
|
12189
12637
|
checkNum++,
|
|
12190
12638
|
totalChecks,
|
|
12191
12639
|
"provider CLI",
|
|
12192
|
-
() => checkProviderCli(
|
|
12640
|
+
() => checkProviderCli(resolvedProviderCli),
|
|
12193
12641
|
options
|
|
12194
12642
|
);
|
|
12195
12643
|
if (providerResult.passed) passedChecks++;
|
|
@@ -12203,15 +12651,6 @@ function doctorCommand(program2) {
|
|
|
12203
12651
|
);
|
|
12204
12652
|
if (configResult.passed) passedChecks++;
|
|
12205
12653
|
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
12654
|
const logsResult = runCheck(
|
|
12216
12655
|
checkNum++,
|
|
12217
12656
|
totalChecks,
|
|
@@ -12269,8 +12708,8 @@ import * as fs33 from "fs";
|
|
|
12269
12708
|
init_dist();
|
|
12270
12709
|
import * as fs32 from "fs";
|
|
12271
12710
|
import * as path35 from "path";
|
|
12272
|
-
import { dirname as
|
|
12273
|
-
import { fileURLToPath as
|
|
12711
|
+
import { dirname as dirname11 } from "path";
|
|
12712
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
12274
12713
|
import cors from "cors";
|
|
12275
12714
|
import express from "express";
|
|
12276
12715
|
|
|
@@ -12411,7 +12850,7 @@ function startSseStatusWatcher(clients, projectDir, getConfig) {
|
|
|
12411
12850
|
init_dist();
|
|
12412
12851
|
import * as fs30 from "fs";
|
|
12413
12852
|
import * as path31 from "path";
|
|
12414
|
-
import { execSync as
|
|
12853
|
+
import { execSync as execSync6, spawn as spawn6 } from "child_process";
|
|
12415
12854
|
import { Router } from "express";
|
|
12416
12855
|
|
|
12417
12856
|
// ../server/dist/helpers.js
|
|
@@ -12498,7 +12937,7 @@ function spawnAction2(projectDir, command, req, res, onSpawned) {
|
|
|
12498
12937
|
if (prdName) {
|
|
12499
12938
|
extraEnv.NW_PRD_PRIORITY = prdName;
|
|
12500
12939
|
}
|
|
12501
|
-
const child =
|
|
12940
|
+
const child = spawn6("night-watch", command, {
|
|
12502
12941
|
detached: true,
|
|
12503
12942
|
stdio: "ignore",
|
|
12504
12943
|
cwd: projectDir,
|
|
@@ -12530,7 +12969,7 @@ function formatCommandError(error2) {
|
|
|
12530
12969
|
return output || error2.message;
|
|
12531
12970
|
}
|
|
12532
12971
|
function runCliCommand(projectDir, args) {
|
|
12533
|
-
|
|
12972
|
+
execSync6(`night-watch ${args.join(" ")}`, {
|
|
12534
12973
|
cwd: projectDir,
|
|
12535
12974
|
encoding: "utf-8",
|
|
12536
12975
|
stdio: "pipe",
|
|
@@ -12549,18 +12988,16 @@ function createActionRouteHandlers(ctx) {
|
|
|
12549
12988
|
router.post(`/${p}review`, (req, res) => {
|
|
12550
12989
|
spawnAction2(ctx.getProjectDir(req), ["review"], req, res);
|
|
12551
12990
|
});
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
spawnAction2(ctx.getProjectDir(req), ["planner"], req, res);
|
|
12563
|
-
});
|
|
12991
|
+
for (const jobDef of JOB_REGISTRY) {
|
|
12992
|
+
if (jobDef.id === "executor")
|
|
12993
|
+
continue;
|
|
12994
|
+
if (jobDef.id === "reviewer")
|
|
12995
|
+
continue;
|
|
12996
|
+
const cmd = jobDef.cliCommand;
|
|
12997
|
+
router.post(`/${p}${cmd}`, (req, res) => {
|
|
12998
|
+
spawnAction2(ctx.getProjectDir(req), [cmd], req, res);
|
|
12999
|
+
});
|
|
13000
|
+
}
|
|
12564
13001
|
router.post(`/${p}install-cron`, (req, res) => {
|
|
12565
13002
|
const projectDir = ctx.getProjectDir(req);
|
|
12566
13003
|
try {
|
|
@@ -13363,12 +13800,12 @@ function createProjectConfigRoutes() {
|
|
|
13363
13800
|
init_dist();
|
|
13364
13801
|
import * as fs31 from "fs";
|
|
13365
13802
|
import * as path32 from "path";
|
|
13366
|
-
import { execSync as
|
|
13803
|
+
import { execSync as execSync7 } from "child_process";
|
|
13367
13804
|
import { Router as Router4 } from "express";
|
|
13368
13805
|
function runDoctorChecks(projectDir, config) {
|
|
13369
13806
|
const checks = [];
|
|
13370
13807
|
try {
|
|
13371
|
-
|
|
13808
|
+
execSync7("git rev-parse --is-inside-work-tree", {
|
|
13372
13809
|
cwd: projectDir,
|
|
13373
13810
|
stdio: "pipe"
|
|
13374
13811
|
});
|
|
@@ -13379,7 +13816,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
13379
13816
|
try {
|
|
13380
13817
|
const preset = resolvePreset(config, config.provider);
|
|
13381
13818
|
const command = preset?.command ?? config.provider;
|
|
13382
|
-
|
|
13819
|
+
execSync7(`which ${command}`, { stdio: "pipe" });
|
|
13383
13820
|
checks.push({
|
|
13384
13821
|
name: "provider",
|
|
13385
13822
|
status: "pass",
|
|
@@ -13894,13 +14331,13 @@ function createQueueRoutes(deps) {
|
|
|
13894
14331
|
}
|
|
13895
14332
|
|
|
13896
14333
|
// ../server/dist/index.js
|
|
13897
|
-
var
|
|
13898
|
-
var
|
|
14334
|
+
var __filename4 = fileURLToPath5(import.meta.url);
|
|
14335
|
+
var __dirname4 = dirname11(__filename4);
|
|
13899
14336
|
function resolveWebDistPath() {
|
|
13900
|
-
const bundled = path35.join(
|
|
14337
|
+
const bundled = path35.join(__dirname4, "web");
|
|
13901
14338
|
if (fs32.existsSync(path35.join(bundled, "index.html")))
|
|
13902
14339
|
return bundled;
|
|
13903
|
-
let d =
|
|
14340
|
+
let d = __dirname4;
|
|
13904
14341
|
for (let i = 0; i < 8; i++) {
|
|
13905
14342
|
if (fs32.existsSync(path35.join(d, "turbo.json"))) {
|
|
13906
14343
|
const dev = path35.join(d, "web/dist");
|
|
@@ -13908,7 +14345,7 @@ function resolveWebDistPath() {
|
|
|
13908
14345
|
return dev;
|
|
13909
14346
|
break;
|
|
13910
14347
|
}
|
|
13911
|
-
d =
|
|
14348
|
+
d = dirname11(d);
|
|
13912
14349
|
}
|
|
13913
14350
|
return bundled;
|
|
13914
14351
|
}
|
|
@@ -14033,6 +14470,21 @@ function createGlobalApp() {
|
|
|
14033
14470
|
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
14034
14471
|
}
|
|
14035
14472
|
});
|
|
14473
|
+
app.delete("/api/projects/:projectId", (req, res) => {
|
|
14474
|
+
try {
|
|
14475
|
+
const rawId = decodeURIComponent(String(req.params.projectId)).replace(/~/g, "/");
|
|
14476
|
+
const entries = loadRegistry();
|
|
14477
|
+
const entry = entries.find((e) => e.name === rawId);
|
|
14478
|
+
if (!entry) {
|
|
14479
|
+
res.status(404).json({ error: "Project not found" });
|
|
14480
|
+
return;
|
|
14481
|
+
}
|
|
14482
|
+
const result = removeProject(entry.path);
|
|
14483
|
+
res.json(result);
|
|
14484
|
+
} catch (error2) {
|
|
14485
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
14486
|
+
}
|
|
14487
|
+
});
|
|
14036
14488
|
app.use("/api/queue", createGlobalQueueRoutes());
|
|
14037
14489
|
app.use("/api/global-notifications", createGlobalNotificationsRoutes());
|
|
14038
14490
|
app.use("/api/projects/:projectId", resolveProject, createProjectRouter());
|
|
@@ -14252,7 +14704,7 @@ function historyCommand(program2) {
|
|
|
14252
14704
|
|
|
14253
14705
|
// src/commands/update.ts
|
|
14254
14706
|
init_dist();
|
|
14255
|
-
import { spawnSync } from "child_process";
|
|
14707
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
14256
14708
|
import * as fs34 from "fs";
|
|
14257
14709
|
import * as path36 from "path";
|
|
14258
14710
|
var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
|
|
@@ -14267,7 +14719,7 @@ function shouldInstallGlobal(options) {
|
|
|
14267
14719
|
return options.global !== false;
|
|
14268
14720
|
}
|
|
14269
14721
|
function runCommand2(command, args, cwd) {
|
|
14270
|
-
const result =
|
|
14722
|
+
const result = spawnSync2(command, args, {
|
|
14271
14723
|
cwd,
|
|
14272
14724
|
env: process.env,
|
|
14273
14725
|
stdio: "inherit"
|
|
@@ -14281,7 +14733,7 @@ function runCommand2(command, args, cwd) {
|
|
|
14281
14733
|
}
|
|
14282
14734
|
}
|
|
14283
14735
|
function resolveNightWatchBin() {
|
|
14284
|
-
const result =
|
|
14736
|
+
const result = spawnSync2("which", ["night-watch"], {
|
|
14285
14737
|
encoding: "utf-8",
|
|
14286
14738
|
env: process.env
|
|
14287
14739
|
});
|
|
@@ -14479,10 +14931,10 @@ function prsCommand(program2) {
|
|
|
14479
14931
|
// src/commands/prds.ts
|
|
14480
14932
|
init_dist();
|
|
14481
14933
|
import chalk4 from "chalk";
|
|
14482
|
-
import { execSync as
|
|
14934
|
+
import { execSync as execSync8 } from "child_process";
|
|
14483
14935
|
function getOpenPrBranches(projectDir) {
|
|
14484
14936
|
try {
|
|
14485
|
-
|
|
14937
|
+
execSync8("git rev-parse --git-dir", {
|
|
14486
14938
|
cwd: projectDir,
|
|
14487
14939
|
encoding: "utf-8",
|
|
14488
14940
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -14491,12 +14943,12 @@ function getOpenPrBranches(projectDir) {
|
|
|
14491
14943
|
return /* @__PURE__ */ new Set();
|
|
14492
14944
|
}
|
|
14493
14945
|
try {
|
|
14494
|
-
|
|
14946
|
+
execSync8("which gh", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
14495
14947
|
} catch {
|
|
14496
14948
|
return /* @__PURE__ */ new Set();
|
|
14497
14949
|
}
|
|
14498
14950
|
try {
|
|
14499
|
-
const output =
|
|
14951
|
+
const output = execSync8("gh pr list --state open --json headRefName", {
|
|
14500
14952
|
cwd: projectDir,
|
|
14501
14953
|
encoding: "utf-8",
|
|
14502
14954
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -14627,7 +15079,7 @@ function prdsCommand(program2) {
|
|
|
14627
15079
|
// src/commands/cancel.ts
|
|
14628
15080
|
init_dist();
|
|
14629
15081
|
import * as fs36 from "fs";
|
|
14630
|
-
import * as
|
|
15082
|
+
import * as readline2 from "readline";
|
|
14631
15083
|
function getLockFilePaths2(projectDir) {
|
|
14632
15084
|
const runtimeKey = projectRuntimeKey(projectDir);
|
|
14633
15085
|
return {
|
|
@@ -14635,24 +15087,24 @@ function getLockFilePaths2(projectDir) {
|
|
|
14635
15087
|
reviewer: `${LOCK_FILE_PREFIX}pr-reviewer-${runtimeKey}.lock`
|
|
14636
15088
|
};
|
|
14637
15089
|
}
|
|
14638
|
-
async function promptConfirmation(
|
|
15090
|
+
async function promptConfirmation(prompt) {
|
|
14639
15091
|
if (!process.stdin.isTTY) {
|
|
14640
15092
|
return false;
|
|
14641
15093
|
}
|
|
14642
|
-
const rl =
|
|
15094
|
+
const rl = readline2.createInterface({
|
|
14643
15095
|
input: process.stdin,
|
|
14644
15096
|
output: process.stdout
|
|
14645
15097
|
});
|
|
14646
|
-
return new Promise((
|
|
14647
|
-
rl.question(`${
|
|
15098
|
+
return new Promise((resolve9) => {
|
|
15099
|
+
rl.question(`${prompt} `, (answer) => {
|
|
14648
15100
|
rl.close();
|
|
14649
15101
|
const normalized = answer.toLowerCase().trim();
|
|
14650
|
-
|
|
15102
|
+
resolve9(normalized === "y" || normalized === "yes");
|
|
14651
15103
|
});
|
|
14652
15104
|
});
|
|
14653
15105
|
}
|
|
14654
15106
|
function sleep2(ms) {
|
|
14655
|
-
return new Promise((
|
|
15107
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
14656
15108
|
}
|
|
14657
15109
|
function isProcessRunning3(pid) {
|
|
14658
15110
|
try {
|
|
@@ -15134,7 +15586,7 @@ init_dist();
|
|
|
15134
15586
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
15135
15587
|
import * as fs38 from "fs";
|
|
15136
15588
|
import * as path40 from "path";
|
|
15137
|
-
import * as
|
|
15589
|
+
import * as readline3 from "readline";
|
|
15138
15590
|
import chalk6 from "chalk";
|
|
15139
15591
|
async function run(fn) {
|
|
15140
15592
|
try {
|
|
@@ -15182,14 +15634,14 @@ async function ensureBoardConfigured(config, cwd, provider, options) {
|
|
|
15182
15634
|
}
|
|
15183
15635
|
}
|
|
15184
15636
|
async function confirmPrompt(question) {
|
|
15185
|
-
const rl =
|
|
15637
|
+
const rl = readline3.createInterface({
|
|
15186
15638
|
input: process.stdin,
|
|
15187
15639
|
output: process.stdout
|
|
15188
15640
|
});
|
|
15189
|
-
return new Promise((
|
|
15641
|
+
return new Promise((resolve9) => {
|
|
15190
15642
|
rl.question(question, (answer) => {
|
|
15191
15643
|
rl.close();
|
|
15192
|
-
|
|
15644
|
+
resolve9(answer.trim().toLowerCase() === "y");
|
|
15193
15645
|
});
|
|
15194
15646
|
});
|
|
15195
15647
|
}
|
|
@@ -15729,7 +16181,7 @@ function boardCommand(program2) {
|
|
|
15729
16181
|
init_dist();
|
|
15730
16182
|
init_dist();
|
|
15731
16183
|
import * as path41 from "path";
|
|
15732
|
-
import { spawn as
|
|
16184
|
+
import { spawn as spawn7 } from "child_process";
|
|
15733
16185
|
import chalk7 from "chalk";
|
|
15734
16186
|
import { Command as Command2 } from "commander";
|
|
15735
16187
|
var logger4 = createLogger("queue");
|
|
@@ -15883,7 +16335,7 @@ function createQueueCommand() {
|
|
|
15883
16335
|
const scriptPath = getScriptPath(scriptName);
|
|
15884
16336
|
logger4.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
|
|
15885
16337
|
try {
|
|
15886
|
-
const child =
|
|
16338
|
+
const child = spawn7("bash", [scriptPath, entry.projectPath], {
|
|
15887
16339
|
detached: true,
|
|
15888
16340
|
stdio: "ignore",
|
|
15889
16341
|
env,
|
|
@@ -16003,17 +16455,17 @@ function notifyCommand(program2) {
|
|
|
16003
16455
|
}
|
|
16004
16456
|
|
|
16005
16457
|
// src/cli.ts
|
|
16006
|
-
var
|
|
16007
|
-
var
|
|
16458
|
+
var __filename5 = fileURLToPath6(import.meta.url);
|
|
16459
|
+
var __dirname5 = dirname12(__filename5);
|
|
16008
16460
|
function findPackageRoot(dir) {
|
|
16009
16461
|
let d = dir;
|
|
16010
16462
|
for (let i = 0; i < 5; i++) {
|
|
16011
16463
|
if (existsSync30(join36(d, "package.json"))) return d;
|
|
16012
|
-
d =
|
|
16464
|
+
d = dirname12(d);
|
|
16013
16465
|
}
|
|
16014
16466
|
return dir;
|
|
16015
16467
|
}
|
|
16016
|
-
var packageRoot = findPackageRoot(
|
|
16468
|
+
var packageRoot = findPackageRoot(__dirname5);
|
|
16017
16469
|
var packageJson = JSON.parse(readFileSync19(join36(packageRoot, "package.json"), "utf-8"));
|
|
16018
16470
|
var program = new Command3();
|
|
16019
16471
|
program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
|