@jonit-dev/night-watch-cli 1.8.10-beta.0 → 1.8.10-beta.11

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.
Files changed (208) hide show
  1. package/dist/cli.js +2501 -1387
  2. package/dist/commands/dashboard/tab-config.d.ts.map +1 -1
  3. package/dist/commands/queue.d.ts.map +1 -1
  4. package/dist/commands/shared/env-builder.d.ts.map +1 -1
  5. package/dist/scripts/night-watch-cron.sh +344 -53
  6. package/dist/scripts/night-watch-helpers.sh +208 -7
  7. package/dist/scripts/night-watch-merger-cron.sh +8 -2
  8. package/dist/scripts/night-watch-plan-cron.sh +1 -1
  9. package/dist/scripts/night-watch-pr-resolver-cron.sh +7 -3
  10. package/dist/scripts/night-watch-pr-reviewer-cron.sh +11 -8
  11. package/dist/scripts/night-watch-qa-cron.sh +8 -3
  12. package/dist/scripts/night-watch-slicer-cron.sh +1 -1
  13. package/dist/web/assets/index-B6E6kOoR.js +406 -0
  14. package/dist/web/assets/index-DIMUXIP8.css +1 -0
  15. package/dist/web/assets/index-Ds8OqaCa.css +1 -0
  16. package/dist/web/assets/index-NR27JE3b.js +406 -0
  17. package/dist/web/index.html +2 -2
  18. package/package.json +1 -1
  19. package/dist/cli.d.ts +0 -3
  20. package/dist/cli.js.map +0 -1
  21. package/dist/commands/analytics.d.ts +0 -14
  22. package/dist/commands/analytics.js +0 -69
  23. package/dist/commands/analytics.js.map +0 -1
  24. package/dist/commands/audit.d.ts +0 -19
  25. package/dist/commands/audit.js +0 -144
  26. package/dist/commands/audit.js.map +0 -1
  27. package/dist/commands/board.d.ts +0 -9
  28. package/dist/commands/board.js +0 -702
  29. package/dist/commands/board.js.map +0 -1
  30. package/dist/commands/cancel.d.ts +0 -46
  31. package/dist/commands/cancel.js +0 -239
  32. package/dist/commands/cancel.js.map +0 -1
  33. package/dist/commands/cron.d.ts +0 -8
  34. package/dist/commands/cron.js +0 -134
  35. package/dist/commands/cron.js.map +0 -1
  36. package/dist/commands/dashboard/tab-actions.d.ts +0 -10
  37. package/dist/commands/dashboard/tab-actions.js +0 -247
  38. package/dist/commands/dashboard/tab-actions.js.map +0 -1
  39. package/dist/commands/dashboard/tab-config.d.ts +0 -21
  40. package/dist/commands/dashboard/tab-config.js +0 -873
  41. package/dist/commands/dashboard/tab-config.js.map +0 -1
  42. package/dist/commands/dashboard/tab-logs.d.ts +0 -10
  43. package/dist/commands/dashboard/tab-logs.js +0 -202
  44. package/dist/commands/dashboard/tab-logs.js.map +0 -1
  45. package/dist/commands/dashboard/tab-schedules.d.ts +0 -21
  46. package/dist/commands/dashboard/tab-schedules.js +0 -320
  47. package/dist/commands/dashboard/tab-schedules.js.map +0 -1
  48. package/dist/commands/dashboard/tab-status.d.ts +0 -32
  49. package/dist/commands/dashboard/tab-status.js +0 -424
  50. package/dist/commands/dashboard/tab-status.js.map +0 -1
  51. package/dist/commands/dashboard/types.d.ts +0 -42
  52. package/dist/commands/dashboard/types.js +0 -5
  53. package/dist/commands/dashboard/types.js.map +0 -1
  54. package/dist/commands/dashboard.d.ts +0 -11
  55. package/dist/commands/dashboard.js +0 -242
  56. package/dist/commands/dashboard.js.map +0 -1
  57. package/dist/commands/doctor.d.ts +0 -16
  58. package/dist/commands/doctor.js +0 -195
  59. package/dist/commands/doctor.js.map +0 -1
  60. package/dist/commands/history.d.ts +0 -7
  61. package/dist/commands/history.js +0 -49
  62. package/dist/commands/history.js.map +0 -1
  63. package/dist/commands/init.d.ts +0 -45
  64. package/dist/commands/init.js +0 -777
  65. package/dist/commands/init.js.map +0 -1
  66. package/dist/commands/install.d.ts +0 -65
  67. package/dist/commands/install.js +0 -405
  68. package/dist/commands/install.js.map +0 -1
  69. package/dist/commands/logs.d.ts +0 -15
  70. package/dist/commands/logs.js +0 -155
  71. package/dist/commands/logs.js.map +0 -1
  72. package/dist/commands/merge.d.ts +0 -26
  73. package/dist/commands/merge.js +0 -159
  74. package/dist/commands/merge.js.map +0 -1
  75. package/dist/commands/notify.d.ts +0 -7
  76. package/dist/commands/notify.js +0 -43
  77. package/dist/commands/notify.js.map +0 -1
  78. package/dist/commands/plan.d.ts +0 -19
  79. package/dist/commands/plan.js +0 -88
  80. package/dist/commands/plan.js.map +0 -1
  81. package/dist/commands/prd-state.d.ts +0 -12
  82. package/dist/commands/prd-state.js +0 -47
  83. package/dist/commands/prd-state.js.map +0 -1
  84. package/dist/commands/prd.d.ts +0 -18
  85. package/dist/commands/prd.js +0 -363
  86. package/dist/commands/prd.js.map +0 -1
  87. package/dist/commands/prds.d.ts +0 -13
  88. package/dist/commands/prds.js +0 -194
  89. package/dist/commands/prds.js.map +0 -1
  90. package/dist/commands/prs.d.ts +0 -14
  91. package/dist/commands/prs.js +0 -104
  92. package/dist/commands/prs.js.map +0 -1
  93. package/dist/commands/qa.d.ts +0 -34
  94. package/dist/commands/qa.js +0 -214
  95. package/dist/commands/qa.js.map +0 -1
  96. package/dist/commands/queue.d.ts +0 -8
  97. package/dist/commands/queue.js +0 -363
  98. package/dist/commands/queue.js.map +0 -1
  99. package/dist/commands/resolve.d.ts +0 -26
  100. package/dist/commands/resolve.js +0 -186
  101. package/dist/commands/resolve.js.map +0 -1
  102. package/dist/commands/retry.d.ts +0 -9
  103. package/dist/commands/retry.js +0 -71
  104. package/dist/commands/retry.js.map +0 -1
  105. package/dist/commands/review.d.ts +0 -82
  106. package/dist/commands/review.js +0 -479
  107. package/dist/commands/review.js.map +0 -1
  108. package/dist/commands/run.d.ts +0 -73
  109. package/dist/commands/run.js +0 -509
  110. package/dist/commands/run.js.map +0 -1
  111. package/dist/commands/serve.d.ts +0 -19
  112. package/dist/commands/serve.js +0 -142
  113. package/dist/commands/serve.js.map +0 -1
  114. package/dist/commands/shared/env-builder.d.ts +0 -49
  115. package/dist/commands/shared/env-builder.js +0 -150
  116. package/dist/commands/shared/env-builder.js.map +0 -1
  117. package/dist/commands/slice.d.ts +0 -35
  118. package/dist/commands/slice.js +0 -316
  119. package/dist/commands/slice.js.map +0 -1
  120. package/dist/commands/state.d.ts +0 -8
  121. package/dist/commands/state.js +0 -54
  122. package/dist/commands/state.js.map +0 -1
  123. package/dist/commands/status.d.ts +0 -14
  124. package/dist/commands/status.js +0 -297
  125. package/dist/commands/status.js.map +0 -1
  126. package/dist/commands/summary.d.ts +0 -14
  127. package/dist/commands/summary.js +0 -193
  128. package/dist/commands/summary.js.map +0 -1
  129. package/dist/commands/uninstall.d.ts +0 -25
  130. package/dist/commands/uninstall.js +0 -134
  131. package/dist/commands/uninstall.js.map +0 -1
  132. package/dist/commands/update.d.ts +0 -22
  133. package/dist/commands/update.js +0 -90
  134. package/dist/commands/update.js.map +0 -1
  135. package/dist/web/assets/index-2JY0x_Ij.js +0 -381
  136. package/dist/web/assets/index-3h8pgmqL.css +0 -1
  137. package/dist/web/assets/index-B-wbyZq7.js +0 -386
  138. package/dist/web/assets/index-B1BnOpiO.css +0 -1
  139. package/dist/web/assets/index-B3CnV08_.js +0 -365
  140. package/dist/web/assets/index-B5QjuFh9.css +0 -1
  141. package/dist/web/assets/index-B8FW2ecQ.js +0 -370
  142. package/dist/web/assets/index-BFxPiKyy.js +0 -381
  143. package/dist/web/assets/index-BGqNh_Da.js +0 -365
  144. package/dist/web/assets/index-BIONU0qz.css +0 -1
  145. package/dist/web/assets/index-B_l_3wnA.js +0 -370
  146. package/dist/web/assets/index-Ba-4YvTQ.js +0 -365
  147. package/dist/web/assets/index-Bbb4-39N.js +0 -370
  148. package/dist/web/assets/index-BdgdShEN.js +0 -365
  149. package/dist/web/assets/index-BhiC4Z-G.js +0 -381
  150. package/dist/web/assets/index-BjhCFjZi.js +0 -381
  151. package/dist/web/assets/index-BlRxmrnQ.css +0 -1
  152. package/dist/web/assets/index-BqwbXsHS.js +0 -365
  153. package/dist/web/assets/index-BsC7RT48.css +0 -1
  154. package/dist/web/assets/index-Bvh8XI8_.js +0 -370
  155. package/dist/web/assets/index-C01r2ymn.js +0 -381
  156. package/dist/web/assets/index-C51Rbsmk.js +0 -381
  157. package/dist/web/assets/index-C7lMNxRE.js +0 -370
  158. package/dist/web/assets/index-CJLObgsn.js +0 -386
  159. package/dist/web/assets/index-CLuRf7Zt.js +0 -381
  160. package/dist/web/assets/index-CM3xFd3e.css +0 -1
  161. package/dist/web/assets/index-CNkBtDK7.js +0 -370
  162. package/dist/web/assets/index-CPQbZ1BL.css +0 -1
  163. package/dist/web/assets/index-CTy5dUDU.css +0 -1
  164. package/dist/web/assets/index-CU15COKs.js +0 -370
  165. package/dist/web/assets/index-CiRJZI4z.js +0 -386
  166. package/dist/web/assets/index-Cp7RYjoy.css +0 -1
  167. package/dist/web/assets/index-CvPkZOWT.js +0 -381
  168. package/dist/web/assets/index-CvUk-33B.css +0 -1
  169. package/dist/web/assets/index-Cvmj-oF6.css +0 -1
  170. package/dist/web/assets/index-CxE5iQVO.js +0 -381
  171. package/dist/web/assets/index-D7lZQpFV.js +0 -365
  172. package/dist/web/assets/index-DAyP4GOi.css +0 -1
  173. package/dist/web/assets/index-DCG0n8Kg.js +0 -386
  174. package/dist/web/assets/index-DEEI8cyF.css +0 -1
  175. package/dist/web/assets/index-DF99BowV.js +0 -381
  176. package/dist/web/assets/index-DGWsvFj6.css +0 -1
  177. package/dist/web/assets/index-DGpU39Cp.css +0 -1
  178. package/dist/web/assets/index-DI4kFgOi.js +0 -370
  179. package/dist/web/assets/index-DIyTcPw5.css +0 -1
  180. package/dist/web/assets/index-DTsfDC7m.js +0 -381
  181. package/dist/web/assets/index-DcgNAi4A.js +0 -386
  182. package/dist/web/assets/index-DgOAgkZy.css +0 -1
  183. package/dist/web/assets/index-DnHkqbOa.js +0 -386
  184. package/dist/web/assets/index-DnR7Idcf.css +0 -1
  185. package/dist/web/assets/index-DpVirMEe.css +0 -1
  186. package/dist/web/assets/index-DsYIWZ86.css +0 -1
  187. package/dist/web/assets/index-DtrDkci5.js +0 -381
  188. package/dist/web/assets/index-DyjIth5M.js +0 -386
  189. package/dist/web/assets/index-FwIKfHPL.css +0 -1
  190. package/dist/web/assets/index-IKrZymWk.css +0 -1
  191. package/dist/web/assets/index-MA6fM0ab.js +0 -381
  192. package/dist/web/assets/index-N_QxaSEg.css +0 -1
  193. package/dist/web/assets/index-OcU-0TCQ.css +0 -1
  194. package/dist/web/assets/index-OyhrmG-L.js +0 -381
  195. package/dist/web/assets/index-SQlBKu_s.js +0 -386
  196. package/dist/web/assets/index-Sv2B60J4.js +0 -370
  197. package/dist/web/assets/index-Vgyivb5u.js +0 -365
  198. package/dist/web/assets/index-ZABWMEZR.js +0 -381
  199. package/dist/web/assets/index-ZE5lOeJp.js +0 -386
  200. package/dist/web/assets/index-aCHmkAcJ.css +0 -1
  201. package/dist/web/assets/index-bFijnpuU.js +0 -381
  202. package/dist/web/assets/index-bUPZgSoZ.css +0 -1
  203. package/dist/web/assets/index-mz1VIYsP.css +0 -1
  204. package/dist/web/assets/index-oOp_MFeE.js +0 -376
  205. package/dist/web/assets/index-rfU713Zm.js +0 -386
  206. package/dist/web/assets/index-tuNH9gmb.js +0 -448
  207. package/dist/web/assets/index-viSwHyDD.js +0 -365
  208. package/dist/web/assets/index-yKEQysks.js +0 -365
@@ -1,777 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { execSync } from 'child_process';
5
- import { fileURLToPath } from 'url';
6
- import { dirname, join } from 'path';
7
- import * as readline from 'readline';
8
- import { BUILT_IN_PRESET_IDS, CONFIG_FILE_NAME, DEFAULT_PRD_DIR, LOG_DIR, checkGhCli, checkGitRepo, checkNodeVersion, checkProviderCli, createBoardProvider, createTable, detectProviders, getDefaultConfig, getProjectName, header, info, label, loadConfig, step, success, error as uiError, warn, } from '@night-watch/core';
9
- // Get templates directory path.
10
- // Walk up from __dirname to find the package root (the directory that contains
11
- // a package.json AND a templates/ folder). This works whether the code runs
12
- // from the TypeScript source tree (src/commands/), the compiled dist tree
13
- // (dist/commands/), or as a single esbuild bundle (dist/).
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = dirname(__filename);
16
- function findTemplatesDir(startDir) {
17
- let d = startDir;
18
- for (let i = 0; i < 8; i++) {
19
- const candidate = join(d, 'templates');
20
- if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
21
- return candidate;
22
- }
23
- d = dirname(d);
24
- }
25
- return join(startDir, 'templates'); // fallback
26
- }
27
- const TEMPLATES_DIR = findTemplatesDir(__dirname);
28
- const NW_SKILLS = [
29
- 'nw-create-prd',
30
- 'nw-add-issue',
31
- 'nw-run',
32
- 'nw-slice',
33
- 'nw-board-sync',
34
- 'nw-review',
35
- ];
36
- function hasPlaywrightDependency(cwd) {
37
- const packageJsonPath = path.join(cwd, 'package.json');
38
- if (!fs.existsSync(packageJsonPath)) {
39
- return false;
40
- }
41
- try {
42
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
43
- return Boolean(packageJson.dependencies?.['@playwright/test'] ||
44
- packageJson.dependencies?.playwright ||
45
- packageJson.devDependencies?.['@playwright/test'] ||
46
- packageJson.devDependencies?.playwright);
47
- }
48
- catch {
49
- return false;
50
- }
51
- }
52
- function detectPlaywright(cwd) {
53
- if (hasPlaywrightDependency(cwd)) {
54
- return true;
55
- }
56
- if (fs.existsSync(path.join(cwd, 'node_modules', '.bin', 'playwright'))) {
57
- return true;
58
- }
59
- try {
60
- execSync('playwright --version', {
61
- cwd,
62
- encoding: 'utf-8',
63
- stdio: ['pipe', 'pipe', 'pipe'],
64
- timeout: 1500,
65
- });
66
- return true;
67
- }
68
- catch {
69
- return false;
70
- }
71
- }
72
- function resolvePlaywrightInstallCommand(cwd) {
73
- if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {
74
- return 'pnpm add -D @playwright/test';
75
- }
76
- if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {
77
- return 'yarn add -D @playwright/test';
78
- }
79
- return 'npm install -D @playwright/test';
80
- }
81
- function promptYesNo(question, defaultNo = true) {
82
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
83
- return Promise.resolve(false);
84
- }
85
- return new Promise((resolve) => {
86
- const rl = readline.createInterface({
87
- input: process.stdin,
88
- output: process.stdout,
89
- });
90
- const suffix = defaultNo ? ' [y/N]: ' : ' [Y/n]: ';
91
- rl.question(`${question}${suffix}`, (answer) => {
92
- rl.close();
93
- const normalized = answer.trim().toLowerCase();
94
- if (normalized === '') {
95
- resolve(!defaultNo);
96
- return;
97
- }
98
- resolve(normalized === 'y' || normalized === 'yes');
99
- });
100
- });
101
- }
102
- export function isInteractiveInitSession() {
103
- return Boolean(process.stdin.isTTY && process.stdout.isTTY);
104
- }
105
- export function chooseProviderForNonInteractive(providers) {
106
- if (providers.includes('claude')) {
107
- return 'claude';
108
- }
109
- return providers[0];
110
- }
111
- export function getGitHubRemoteStatus(cwd) {
112
- try {
113
- const remoteUrl = execSync('git remote get-url origin', {
114
- cwd,
115
- encoding: 'utf-8',
116
- stdio: ['pipe', 'pipe', 'pipe'],
117
- }).trim();
118
- return {
119
- hasGitHubRemote: remoteUrl.includes('github.com'),
120
- remoteUrl: remoteUrl || null,
121
- };
122
- }
123
- catch {
124
- return {
125
- hasGitHubRemote: false,
126
- remoteUrl: null,
127
- };
128
- }
129
- }
130
- function installPlaywrightForQa(cwd) {
131
- try {
132
- const installCmd = resolvePlaywrightInstallCommand(cwd);
133
- execSync(installCmd, {
134
- cwd,
135
- encoding: 'utf-8',
136
- stdio: ['pipe', 'pipe', 'pipe'],
137
- });
138
- execSync('npx playwright install chromium', {
139
- cwd,
140
- encoding: 'utf-8',
141
- stdio: ['pipe', 'pipe', 'pipe'],
142
- });
143
- return true;
144
- }
145
- catch {
146
- return false;
147
- }
148
- }
149
- /**
150
- * Get the default branch name for the repository
151
- */
152
- export function getDefaultBranch(cwd) {
153
- const getRefTimestamp = (ref) => {
154
- try {
155
- const timestamp = execSync(`git log -1 --format=%ct ${ref}`, {
156
- encoding: 'utf-8',
157
- cwd,
158
- stdio: ['pipe', 'pipe', 'pipe'],
159
- }).trim();
160
- const parsed = parseInt(timestamp, 10);
161
- return Number.isNaN(parsed) ? null : parsed;
162
- }
163
- catch {
164
- return null;
165
- }
166
- };
167
- const getBranchLatestTimestamp = (branch) => {
168
- const refs = [`refs/remotes/origin/${branch}`, `refs/heads/${branch}`];
169
- let latest = null;
170
- for (const ref of refs) {
171
- const timestamp = getRefTimestamp(ref);
172
- if (timestamp !== null && (latest === null || timestamp > latest)) {
173
- latest = timestamp;
174
- }
175
- }
176
- return latest;
177
- };
178
- try {
179
- // If both main and master exist, use whichever has the newest tip commit
180
- const mainTimestamp = getBranchLatestTimestamp('main');
181
- const masterTimestamp = getBranchLatestTimestamp('master');
182
- if (mainTimestamp !== null && masterTimestamp !== null) {
183
- return mainTimestamp >= masterTimestamp ? 'main' : 'master';
184
- }
185
- if (mainTimestamp !== null) {
186
- return 'main';
187
- }
188
- if (masterTimestamp !== null) {
189
- return 'master';
190
- }
191
- // Fallback to origin/HEAD when neither main nor master exists
192
- const remoteRef = execSync('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || echo ""', {
193
- encoding: 'utf-8',
194
- cwd,
195
- stdio: ['pipe', 'pipe', 'pipe'],
196
- }).trim();
197
- if (remoteRef) {
198
- // Extract branch name from refs/remotes/origin/HEAD -> refs/remotes/origin/main
199
- const match = remoteRef.match(/refs\/remotes\/origin\/(.+)/);
200
- if (match) {
201
- return match[1];
202
- }
203
- }
204
- // Default to main
205
- return 'main';
206
- }
207
- catch {
208
- return 'main';
209
- }
210
- }
211
- /**
212
- * Prompt user to select a provider from available options
213
- */
214
- function promptProviderSelection(providers) {
215
- return new Promise((resolve, reject) => {
216
- const rl = readline.createInterface({
217
- input: process.stdin,
218
- output: process.stdout,
219
- });
220
- console.log('\nMultiple AI providers detected:');
221
- providers.forEach((p, i) => {
222
- console.log(` ${i + 1}. ${p}`);
223
- });
224
- rl.question('\nSelect a provider (enter number): ', (answer) => {
225
- rl.close();
226
- const selection = parseInt(answer.trim(), 10);
227
- if (isNaN(selection) || selection < 1 || selection > providers.length) {
228
- reject(new Error('Invalid selection. Please run init again and select a valid number.'));
229
- return;
230
- }
231
- resolve(providers[selection - 1]);
232
- });
233
- });
234
- }
235
- /**
236
- * Create directory if it doesn't exist
237
- */
238
- function ensureDir(dirPath) {
239
- if (!fs.existsSync(dirPath)) {
240
- fs.mkdirSync(dirPath, { recursive: true });
241
- }
242
- }
243
- export function buildInitConfig(params) {
244
- const defaults = getDefaultConfig();
245
- return {
246
- $schema: 'https://json-schema.org/schema',
247
- projectName: params.projectName,
248
- defaultBranch: params.defaultBranch,
249
- prdDir: params.prdDir,
250
- maxRuntime: defaults.maxRuntime,
251
- reviewerMaxRuntime: defaults.reviewerMaxRuntime,
252
- branchPrefix: defaults.branchPrefix,
253
- branchPatterns: [...defaults.branchPatterns],
254
- minReviewScore: defaults.minReviewScore,
255
- maxLogSize: defaults.maxLogSize,
256
- cronSchedule: defaults.cronSchedule,
257
- reviewerSchedule: defaults.reviewerSchedule,
258
- scheduleBundleId: defaults.scheduleBundleId ?? 'always-on',
259
- cronScheduleOffset: defaults.cronScheduleOffset,
260
- schedulingPriority: defaults.schedulingPriority,
261
- maxRetries: defaults.maxRetries,
262
- reviewerMaxRetries: defaults.reviewerMaxRetries,
263
- reviewerMaxPrsPerRun: defaults.reviewerMaxPrsPerRun,
264
- reviewerRetryDelay: defaults.reviewerRetryDelay,
265
- provider: params.provider,
266
- providerLabel: '',
267
- executorEnabled: defaults.executorEnabled ?? true,
268
- reviewerEnabled: params.reviewerEnabled,
269
- providerEnv: { ...defaults.providerEnv },
270
- notifications: {
271
- ...defaults.notifications,
272
- webhooks: [...(defaults.notifications?.webhooks ?? [])],
273
- },
274
- prdPriority: [...defaults.prdPriority],
275
- roadmapScanner: { ...defaults.roadmapScanner },
276
- templatesDir: defaults.templatesDir,
277
- boardProvider: { ...defaults.boardProvider },
278
- autoMerge: defaults.autoMerge,
279
- autoMergeMethod: defaults.autoMergeMethod,
280
- fallbackOnRateLimit: defaults.fallbackOnRateLimit,
281
- claudeModel: defaults.claudeModel,
282
- qa: {
283
- ...defaults.qa,
284
- branchPatterns: [...defaults.qa.branchPatterns],
285
- },
286
- audit: { ...defaults.audit },
287
- analytics: { ...defaults.analytics },
288
- merger: { ...defaults.merger },
289
- prResolver: { ...defaults.prResolver },
290
- jobProviders: { ...defaults.jobProviders },
291
- queue: {
292
- ...defaults.queue,
293
- priority: { ...defaults.queue.priority },
294
- },
295
- };
296
- }
297
- /**
298
- * Resolve a template path with per-file fallback.
299
- * If customTemplatesDir is non-null and the file exists there, return custom path.
300
- * Otherwise return the bundled template path.
301
- */
302
- export function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
303
- if (customTemplatesDir !== null) {
304
- const customPath = join(customTemplatesDir, templateName);
305
- if (fs.existsSync(customPath)) {
306
- return { path: customPath, source: 'custom' };
307
- }
308
- }
309
- return { path: join(bundledTemplatesDir, templateName), source: 'bundled' };
310
- }
311
- /**
312
- * Copy and process template file
313
- */
314
- function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
315
- // Skip if exists and not forcing
316
- if (fs.existsSync(targetPath) && !force) {
317
- console.log(` Skipped (exists): ${targetPath}`);
318
- return { created: false, source: source ?? 'bundled' };
319
- }
320
- const templatePath = sourcePath ?? join(TEMPLATES_DIR, templateName);
321
- const resolvedSource = source ?? 'bundled';
322
- let content = fs.readFileSync(templatePath, 'utf-8');
323
- // Replace placeholders
324
- for (const [key, value] of Object.entries(replacements)) {
325
- content = content.replaceAll(key, value);
326
- }
327
- fs.writeFileSync(targetPath, content);
328
- console.log(` Created: ${targetPath} (${resolvedSource})`);
329
- return { created: true, source: resolvedSource };
330
- }
331
- /**
332
- * Ensure Night Watch entries are in .gitignore
333
- */
334
- function addToGitignore(cwd) {
335
- const gitignorePath = path.join(cwd, '.gitignore');
336
- const entries = [
337
- {
338
- pattern: '/logs/',
339
- label: '/logs/',
340
- check: (c) => c.includes('/logs/') || /^logs\//m.test(c),
341
- },
342
- {
343
- pattern: CONFIG_FILE_NAME,
344
- label: CONFIG_FILE_NAME,
345
- check: (c) => c.includes(CONFIG_FILE_NAME),
346
- },
347
- { pattern: '*.claim', label: '*.claim', check: (c) => c.includes('*.claim') },
348
- ];
349
- if (!fs.existsSync(gitignorePath)) {
350
- const lines = ['# Night Watch', ...entries.map((e) => e.pattern), ''];
351
- fs.writeFileSync(gitignorePath, lines.join('\n'));
352
- console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
353
- return;
354
- }
355
- const content = fs.readFileSync(gitignorePath, 'utf-8');
356
- const missing = entries.filter((e) => !e.check(content));
357
- if (missing.length === 0) {
358
- console.log(` Skipped (exists): Night Watch entries in .gitignore`);
359
- return;
360
- }
361
- const additions = missing.map((e) => e.pattern).join('\n');
362
- const newContent = content.trimEnd() + '\n\n# Night Watch\n' + additions + '\n';
363
- fs.writeFileSync(gitignorePath, newContent);
364
- console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(', ')})`);
365
- }
366
- function installSkills(cwd, provider, force, templatesDir) {
367
- const skillsTemplatesDir = path.join(templatesDir, 'skills');
368
- if (!fs.existsSync(skillsTemplatesDir)) {
369
- return { location: '', installed: 0, skipped: 0, type: 'none' };
370
- }
371
- const isClaudeProvider = provider === 'claude' || provider.startsWith('claude');
372
- const isCodexProvider = provider === 'codex';
373
- const claudeDir = path.join(cwd, '.claude');
374
- if (isClaudeProvider || fs.existsSync(claudeDir)) {
375
- ensureDir(claudeDir);
376
- const skillsDir = path.join(claudeDir, 'skills');
377
- ensureDir(skillsDir);
378
- let installed = 0;
379
- let skipped = 0;
380
- for (const skillName of NW_SKILLS) {
381
- const templateFile = path.join(skillsTemplatesDir, `${skillName}.md`);
382
- if (!fs.existsSync(templateFile))
383
- continue;
384
- const skillDir = path.join(skillsDir, skillName);
385
- ensureDir(skillDir);
386
- const target = path.join(skillDir, 'SKILL.md');
387
- if (fs.existsSync(target) && !force) {
388
- skipped++;
389
- continue;
390
- }
391
- fs.copyFileSync(templateFile, target);
392
- installed++;
393
- }
394
- return { location: '.claude/skills/', installed, skipped, type: 'claude' };
395
- }
396
- if (isCodexProvider) {
397
- const agentsFile = path.join(cwd, 'AGENTS.md');
398
- const blockFile = path.join(skillsTemplatesDir, '_codex-block.md');
399
- if (!fs.existsSync(blockFile)) {
400
- return { location: '', installed: 0, skipped: 0, type: 'none' };
401
- }
402
- const block = fs.readFileSync(blockFile, 'utf-8');
403
- const marker = '## Night Watch Skills';
404
- if (!fs.existsSync(agentsFile)) {
405
- fs.writeFileSync(agentsFile, block);
406
- return { location: 'AGENTS.md', installed: NW_SKILLS.length, skipped: 0, type: 'codex' };
407
- }
408
- const existing = fs.readFileSync(agentsFile, 'utf-8');
409
- if (existing.includes(marker)) {
410
- if (!force) {
411
- return { location: 'AGENTS.md', installed: 0, skipped: NW_SKILLS.length, type: 'codex' };
412
- }
413
- const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, '');
414
- fs.writeFileSync(agentsFile, withoutSection + '\n\n' + block);
415
- }
416
- else {
417
- fs.appendFileSync(agentsFile, '\n\n' + block);
418
- }
419
- return { location: 'AGENTS.md', installed: NW_SKILLS.length, skipped: 0, type: 'codex' };
420
- }
421
- return { location: '', installed: 0, skipped: 0, type: 'none' };
422
- }
423
- /**
424
- * Main init command implementation
425
- */
426
- export function initCommand(program) {
427
- program
428
- .command('init')
429
- .description('Initialize night-watch in the current project')
430
- .option('-f, --force', 'Overwrite existing configuration')
431
- .option('-d, --prd-dir <path>', 'Path to PRD directory')
432
- .option('-p, --provider <name>', 'AI provider to use (claude or codex)')
433
- .option('--no-reviewer', 'Disable reviewer cron job')
434
- .action(async (options) => {
435
- const cwd = process.cwd();
436
- const force = options.force || false;
437
- const prdDir = options.prdDir || DEFAULT_PRD_DIR;
438
- const totalSteps = 14;
439
- const interactive = isInteractiveInitSession();
440
- console.log();
441
- header('Night Watch CLI - Initializing');
442
- // Step 1: Verify Node.js version
443
- step(1, totalSteps, 'Checking Node.js version...');
444
- const nodeCheck = checkNodeVersion(22);
445
- if (!nodeCheck.passed) {
446
- uiError(nodeCheck.message);
447
- process.exit(1);
448
- }
449
- success(nodeCheck.message);
450
- // Step 2: Verify git repository
451
- step(2, totalSteps, 'Checking git repository...');
452
- const gitCheck = checkGitRepo(cwd);
453
- if (!gitCheck.passed) {
454
- uiError(gitCheck.message);
455
- console.log('Please run this command from the root of a git repository.');
456
- process.exit(1);
457
- }
458
- success(gitCheck.message);
459
- // Step 3: Detect AI providers
460
- step(3, totalSteps, 'Detecting AI providers...');
461
- let selectedProvider;
462
- if (options.provider) {
463
- // Validate provider flag
464
- if (!BUILT_IN_PRESET_IDS.includes(options.provider)) {
465
- uiError(`Invalid provider "${options.provider}".`);
466
- console.log(`Valid providers: ${BUILT_IN_PRESET_IDS.join(', ')}`);
467
- process.exit(1);
468
- }
469
- selectedProvider = options.provider;
470
- const providerCheck = checkProviderCli(selectedProvider);
471
- if (!providerCheck.passed) {
472
- uiError(providerCheck.message);
473
- console.log(`Install the ${selectedProvider} CLI or rerun with --provider ${detectProviders()[0] ?? 'claude'}.`);
474
- process.exit(1);
475
- }
476
- info(`Using provider from flag: ${selectedProvider}`);
477
- }
478
- else {
479
- // Auto-detect providers
480
- const detectedProviders = detectProviders();
481
- if (detectedProviders.length === 0) {
482
- uiError('No AI provider CLI found.');
483
- console.log('\nPlease install one of the following:');
484
- console.log(' - Claude CLI: https://docs.anthropic.com/en/docs/claude-cli');
485
- console.log(' - Codex CLI: https://github.com/openai/codex');
486
- process.exit(1);
487
- }
488
- else if (detectedProviders.length === 1) {
489
- selectedProvider = detectedProviders[0];
490
- info(`Auto-detected provider: ${selectedProvider}`);
491
- }
492
- else {
493
- if (!interactive) {
494
- selectedProvider = chooseProviderForNonInteractive(detectedProviders);
495
- info(`Multiple providers detected in a non-interactive shell; defaulting to ${selectedProvider}. Use --provider to override.`);
496
- }
497
- else {
498
- try {
499
- selectedProvider = await promptProviderSelection(detectedProviders);
500
- info(`Selected provider: ${selectedProvider}`);
501
- }
502
- catch (err) {
503
- uiError(`${err instanceof Error ? err.message : String(err)}`);
504
- process.exit(1);
505
- }
506
- }
507
- }
508
- }
509
- // Step 4: Check optional GitHub integration prerequisites
510
- step(4, totalSteps, 'Checking GitHub integration prerequisites...');
511
- const remoteStatus = getGitHubRemoteStatus(cwd);
512
- const ghCheck = checkGhCli();
513
- const ghAuthenticated = ghCheck.passed;
514
- if (!remoteStatus.hasGitHubRemote) {
515
- info('No GitHub remote detected. Board setup will be skipped for now.');
516
- }
517
- else if (!ghAuthenticated) {
518
- warn(`${ghCheck.message}. Board setup will be skipped during init.`);
519
- }
520
- else {
521
- success(ghCheck.message);
522
- }
523
- // Step 5: Detect test frameworks for QA bootstrap
524
- step(5, totalSteps, 'Detecting test frameworks...');
525
- const playwrightDetected = detectPlaywright(cwd);
526
- let playwrightStatus = playwrightDetected ? 'detected' : 'not installed';
527
- if (playwrightDetected) {
528
- info('Playwright: detected');
529
- }
530
- else {
531
- info('Playwright: not found');
532
- const installPlaywright = await promptYesNo('Install Playwright for QA now?', true);
533
- if (installPlaywright) {
534
- if (installPlaywrightForQa(cwd)) {
535
- playwrightStatus = 'installed during init';
536
- success('Installed Playwright test runner and Chromium browser.');
537
- }
538
- else {
539
- playwrightStatus = 'install failed';
540
- console.warn(' Warning: Failed to install Playwright automatically. You can install it later.');
541
- }
542
- }
543
- else {
544
- info('Skipping Playwright install. QA can auto-install during execution if enabled.');
545
- }
546
- }
547
- // Set reviewerEnabled from flag (default: true, --no-reviewer sets to false)
548
- const reviewerEnabled = options.reviewer !== false;
549
- // Gather project information
550
- const projectName = getProjectName(cwd);
551
- const defaultBranch = getDefaultBranch(cwd);
552
- // Display project configuration
553
- header('Project Configuration');
554
- label('Project', projectName);
555
- label('Default branch', defaultBranch);
556
- label('Provider', selectedProvider);
557
- label('Reviewer', reviewerEnabled ? 'Enabled' : 'Disabled');
558
- console.log();
559
- // Define replacements for templates
560
- const replacements = {
561
- '${PROJECT_DIR}': cwd,
562
- '${PROJECT_NAME}': projectName,
563
- '${DEFAULT_BRANCH}': defaultBranch,
564
- };
565
- // Step 6: Create PRD directory structure
566
- step(6, totalSteps, 'Creating PRD directory structure...');
567
- const prdDirPath = path.join(cwd, prdDir);
568
- const doneDirPath = path.join(prdDirPath, 'done');
569
- ensureDir(doneDirPath);
570
- success(`Created ${prdDirPath}/`);
571
- success(`Created ${doneDirPath}/`);
572
- // Step 7: Create logs directory
573
- step(7, totalSteps, 'Creating logs directory...');
574
- const logsPath = path.join(cwd, LOG_DIR);
575
- ensureDir(logsPath);
576
- success(`Created ${logsPath}/`);
577
- // Add /logs/ to .gitignore
578
- addToGitignore(cwd);
579
- // Step 8: Create instructions directory and copy templates
580
- step(8, totalSteps, 'Creating instructions directory...');
581
- const instructionsDir = path.join(cwd, 'instructions');
582
- ensureDir(instructionsDir);
583
- success(`Created ${instructionsDir}/`);
584
- // Load existing config (if present) to get templatesDir
585
- const existingConfig = loadConfig(cwd);
586
- const customTemplatesDirPath = path.join(cwd, existingConfig.templatesDir);
587
- const customTemplatesDir = fs.existsSync(customTemplatesDirPath)
588
- ? customTemplatesDirPath
589
- : null;
590
- // Track template sources for summary
591
- const templateSources = [];
592
- // Copy executor.md template
593
- const nwResolution = resolveTemplatePath('executor.md', customTemplatesDir, TEMPLATES_DIR);
594
- const nwResult = processTemplate('executor.md', path.join(instructionsDir, 'executor.md'), replacements, force, nwResolution.path, nwResolution.source);
595
- templateSources.push({ name: 'executor.md', source: nwResult.source });
596
- // Copy prd-executor.md template
597
- const peResolution = resolveTemplatePath('prd-executor.md', customTemplatesDir, TEMPLATES_DIR);
598
- const peResult = processTemplate('prd-executor.md', path.join(instructionsDir, 'prd-executor.md'), replacements, force, peResolution.path, peResolution.source);
599
- templateSources.push({ name: 'prd-executor.md', source: peResult.source });
600
- // Copy pr-reviewer.md template
601
- const prResolution = resolveTemplatePath('pr-reviewer.md', customTemplatesDir, TEMPLATES_DIR);
602
- const prResult = processTemplate('pr-reviewer.md', path.join(instructionsDir, 'pr-reviewer.md'), replacements, force, prResolution.path, prResolution.source);
603
- templateSources.push({ name: 'pr-reviewer.md', source: prResult.source });
604
- // Copy qa.md template
605
- const qaResolution = resolveTemplatePath('qa.md', customTemplatesDir, TEMPLATES_DIR);
606
- const qaResult = processTemplate('qa.md', path.join(instructionsDir, 'qa.md'), replacements, force, qaResolution.path, qaResolution.source);
607
- templateSources.push({ name: 'qa.md', source: qaResult.source });
608
- // Copy audit.md template
609
- const auditResolution = resolveTemplatePath('audit.md', customTemplatesDir, TEMPLATES_DIR);
610
- const auditResult = processTemplate('audit.md', path.join(instructionsDir, 'audit.md'), replacements, force, auditResolution.path, auditResolution.source);
611
- templateSources.push({ name: 'audit.md', source: auditResult.source });
612
- // Copy prd-creator.md template
613
- const plannerResolution = resolveTemplatePath('prd-creator.md', customTemplatesDir, TEMPLATES_DIR);
614
- const plannerResult = processTemplate('prd-creator.md', path.join(instructionsDir, 'prd-creator.md'), replacements, force, plannerResolution.path, plannerResolution.source);
615
- templateSources.push({ name: 'prd-creator.md', source: plannerResult.source });
616
- // Step 9: Create config file
617
- step(9, totalSteps, 'Creating configuration file...');
618
- const configPath = path.join(cwd, CONFIG_FILE_NAME);
619
- if (fs.existsSync(configPath) && !force) {
620
- console.log(` Skipped (exists): ${configPath}`);
621
- }
622
- else {
623
- const config = buildInitConfig({
624
- projectName,
625
- defaultBranch,
626
- provider: selectedProvider,
627
- reviewerEnabled,
628
- prdDir,
629
- });
630
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
631
- success(`Created ${configPath}`);
632
- }
633
- // Step 10: Create GitHub Project board (only when repo has a GitHub remote)
634
- step(10, totalSteps, 'Setting up GitHub Project board...');
635
- const existingRaw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
636
- const existingBoard = existingRaw.boardProvider;
637
- let boardSetupStatus = 'Skipped';
638
- if (existingBoard?.projectNumber && !force) {
639
- boardSetupStatus = `Already configured (#${existingBoard.projectNumber})`;
640
- info(`Board already configured (#${existingBoard.projectNumber}), skipping.`);
641
- }
642
- else {
643
- if (!remoteStatus.hasGitHubRemote) {
644
- boardSetupStatus = 'Skipped (no GitHub remote)';
645
- info('No GitHub remote detected — skipping board setup. Run `night-watch board setup` manually.');
646
- }
647
- else if (!ghAuthenticated) {
648
- boardSetupStatus = 'Skipped (gh auth required)';
649
- info('GitHub CLI is not authenticated — run `gh auth login`, then `night-watch board setup`.');
650
- }
651
- else {
652
- try {
653
- const provider = createBoardProvider({ enabled: true, provider: 'github' }, cwd);
654
- const boardTitle = `${projectName} Night Watch`;
655
- const board = await provider.setupBoard(boardTitle);
656
- // Update the config file with the projectNumber
657
- const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
658
- rawConfig.boardProvider = {
659
- enabled: true,
660
- provider: 'github',
661
- projectNumber: board.number,
662
- };
663
- fs.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + '\n');
664
- boardSetupStatus = `Created (#${board.number})`;
665
- success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
666
- }
667
- catch (boardErr) {
668
- boardSetupStatus = 'Failed (manual setup required)';
669
- console.warn(` Warning: Could not set up GitHub Project board: ${boardErr instanceof Error ? boardErr.message : String(boardErr)}`);
670
- info('Run `night-watch board setup` to create the board manually.');
671
- }
672
- }
673
- }
674
- // Step 11: Sync Night Watch labels to GitHub
675
- step(11, totalSteps, 'Syncing Night Watch labels to GitHub...');
676
- let labelSyncStatus = 'Skipped';
677
- if (!remoteStatus.hasGitHubRemote || !ghAuthenticated) {
678
- labelSyncStatus = !remoteStatus.hasGitHubRemote
679
- ? 'Skipped (no GitHub remote)'
680
- : 'Skipped (gh auth required)';
681
- info('Skipping label sync (no GitHub remote or gh not authenticated).');
682
- }
683
- else {
684
- try {
685
- const { NIGHT_WATCH_LABELS } = await import('@night-watch/core');
686
- let created = 0;
687
- for (const label of NIGHT_WATCH_LABELS) {
688
- try {
689
- execSync(`gh label create "${label.name}" --description "${label.description}" --color "${label.color}" --force`, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
690
- created++;
691
- }
692
- catch {
693
- // Label creation is best-effort
694
- }
695
- }
696
- labelSyncStatus = `Synced ${created}/${NIGHT_WATCH_LABELS.length} labels`;
697
- success(`Synced ${created}/${NIGHT_WATCH_LABELS.length} labels to GitHub`);
698
- }
699
- catch (labelErr) {
700
- labelSyncStatus = 'Failed';
701
- warn(`Could not sync labels: ${labelErr instanceof Error ? labelErr.message : String(labelErr)}`);
702
- }
703
- }
704
- // Step 12: Register in global registry
705
- step(12, totalSteps, 'Registering project in global registry...');
706
- try {
707
- const { registerProject } = await import('@night-watch/core');
708
- const entry = registerProject(cwd);
709
- success(`Registered as "${entry.name}" in global registry`);
710
- }
711
- catch (regErr) {
712
- console.warn(` Warning: Could not register in global registry: ${regErr instanceof Error ? regErr.message : String(regErr)}`);
713
- }
714
- // Step 13: Install AI skills
715
- step(13, totalSteps, 'Installing Night Watch skills...');
716
- const skillsResult = installSkills(cwd, selectedProvider, force, TEMPLATES_DIR);
717
- if (skillsResult.installed > 0) {
718
- success(`Installed ${skillsResult.installed} skills to ${skillsResult.location}`);
719
- for (const skillName of NW_SKILLS) {
720
- console.log(` /${skillName}`);
721
- }
722
- }
723
- else if (skillsResult.skipped > 0) {
724
- info(`Skills already installed (use --force to overwrite)`);
725
- }
726
- else if (skillsResult.type === 'none') {
727
- info('No compatible AI skills directory detected — skipping.');
728
- }
729
- // Print summary
730
- step(14, totalSteps, 'Initialization complete!');
731
- // Summary with table
732
- header('Initialization Complete');
733
- const filesTable = createTable({ head: ['Created Files', ''] });
734
- filesTable.push(['PRD Directory', `${prdDir}/done/`]);
735
- filesTable.push(['Logs Directory', `${LOG_DIR}/`]);
736
- filesTable.push(['Instructions', `instructions/executor.md (${templateSources[0].source})`]);
737
- filesTable.push(['', `instructions/prd-executor.md (${templateSources[1].source})`]);
738
- filesTable.push(['', `instructions/pr-reviewer.md (${templateSources[2].source})`]);
739
- filesTable.push(['', `instructions/qa.md (${templateSources[3].source})`]);
740
- filesTable.push(['', `instructions/audit.md (${templateSources[4].source})`]);
741
- filesTable.push(['', `instructions/prd-creator.md (${templateSources[5].source})`]);
742
- filesTable.push(['Config File', CONFIG_FILE_NAME]);
743
- filesTable.push(['Board Setup', boardSetupStatus]);
744
- filesTable.push(['Label Sync', labelSyncStatus]);
745
- filesTable.push(['Global Registry', '~/.night-watch/projects.json']);
746
- let skillsSummary;
747
- if (skillsResult.installed > 0) {
748
- skillsSummary = `${skillsResult.installed} skills → ${skillsResult.location}`;
749
- }
750
- else if (skillsResult.skipped > 0) {
751
- skillsSummary = `Already installed (${skillsResult.location})`;
752
- }
753
- else {
754
- skillsSummary = 'Skipped';
755
- }
756
- filesTable.push(['Skills', skillsSummary]);
757
- console.log(filesTable.toString());
758
- // Configuration summary
759
- header('Configuration');
760
- label('Provider', selectedProvider);
761
- label('Reviewer', reviewerEnabled ? 'Enabled' : 'Disabled');
762
- label('Playwright', playwrightStatus);
763
- console.log();
764
- // Next steps
765
- header('Next Steps');
766
- info(`1. Add your PRD files to ${prdDir}/`);
767
- info('2. Run `night-watch install` to set up cron jobs');
768
- info('3. Run `night-watch doctor` to verify the full setup');
769
- info('4. Or run `night-watch run` to execute PRDs manually');
770
- if (skillsResult.installed > 0) {
771
- info(`5. Use /nw-create-prd, /nw-run, /nw-add-issue and more in your AI assistant`);
772
- }
773
- console.log();
774
- });
775
- }
776
- export default initCommand;
777
- //# sourceMappingURL=init.js.map