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

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 (238) hide show
  1. package/dist/cli.js +2399 -1364
  2. package/dist/scripts/night-watch-helpers.sh +26 -0
  3. package/dist/scripts/night-watch-pr-reviewer-cron.sh +6 -8
  4. package/dist/scripts/night-watch-qa-cron.sh +3 -3
  5. package/dist/web/assets/index-BUgI2S1s.js +406 -0
  6. package/dist/web/assets/index-CkdLFBd7.js +406 -0
  7. package/dist/web/assets/index-RMfswANB.css +1 -0
  8. package/dist/web/index.html +2 -2
  9. package/package.json +1 -1
  10. package/dist/cli.d.ts +0 -3
  11. package/dist/cli.d.ts.map +0 -1
  12. package/dist/cli.js.map +0 -1
  13. package/dist/commands/analytics.d.ts +0 -14
  14. package/dist/commands/analytics.d.ts.map +0 -1
  15. package/dist/commands/analytics.js +0 -69
  16. package/dist/commands/analytics.js.map +0 -1
  17. package/dist/commands/audit.d.ts +0 -19
  18. package/dist/commands/audit.d.ts.map +0 -1
  19. package/dist/commands/audit.js +0 -144
  20. package/dist/commands/audit.js.map +0 -1
  21. package/dist/commands/board.d.ts +0 -9
  22. package/dist/commands/board.d.ts.map +0 -1
  23. package/dist/commands/board.js +0 -702
  24. package/dist/commands/board.js.map +0 -1
  25. package/dist/commands/cancel.d.ts +0 -46
  26. package/dist/commands/cancel.d.ts.map +0 -1
  27. package/dist/commands/cancel.js +0 -239
  28. package/dist/commands/cancel.js.map +0 -1
  29. package/dist/commands/cron.d.ts +0 -8
  30. package/dist/commands/cron.d.ts.map +0 -1
  31. package/dist/commands/cron.js +0 -134
  32. package/dist/commands/cron.js.map +0 -1
  33. package/dist/commands/dashboard/tab-actions.d.ts +0 -10
  34. package/dist/commands/dashboard/tab-actions.d.ts.map +0 -1
  35. package/dist/commands/dashboard/tab-actions.js +0 -247
  36. package/dist/commands/dashboard/tab-actions.js.map +0 -1
  37. package/dist/commands/dashboard/tab-config.d.ts +0 -21
  38. package/dist/commands/dashboard/tab-config.d.ts.map +0 -1
  39. package/dist/commands/dashboard/tab-config.js +0 -873
  40. package/dist/commands/dashboard/tab-config.js.map +0 -1
  41. package/dist/commands/dashboard/tab-logs.d.ts +0 -10
  42. package/dist/commands/dashboard/tab-logs.d.ts.map +0 -1
  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.d.ts.map +0 -1
  47. package/dist/commands/dashboard/tab-schedules.js +0 -320
  48. package/dist/commands/dashboard/tab-schedules.js.map +0 -1
  49. package/dist/commands/dashboard/tab-status.d.ts +0 -32
  50. package/dist/commands/dashboard/tab-status.d.ts.map +0 -1
  51. package/dist/commands/dashboard/tab-status.js +0 -424
  52. package/dist/commands/dashboard/tab-status.js.map +0 -1
  53. package/dist/commands/dashboard/types.d.ts +0 -42
  54. package/dist/commands/dashboard/types.d.ts.map +0 -1
  55. package/dist/commands/dashboard/types.js +0 -5
  56. package/dist/commands/dashboard/types.js.map +0 -1
  57. package/dist/commands/dashboard.d.ts +0 -11
  58. package/dist/commands/dashboard.d.ts.map +0 -1
  59. package/dist/commands/dashboard.js +0 -242
  60. package/dist/commands/dashboard.js.map +0 -1
  61. package/dist/commands/doctor.d.ts +0 -16
  62. package/dist/commands/doctor.d.ts.map +0 -1
  63. package/dist/commands/doctor.js +0 -195
  64. package/dist/commands/doctor.js.map +0 -1
  65. package/dist/commands/history.d.ts +0 -7
  66. package/dist/commands/history.d.ts.map +0 -1
  67. package/dist/commands/history.js +0 -49
  68. package/dist/commands/history.js.map +0 -1
  69. package/dist/commands/init.d.ts +0 -45
  70. package/dist/commands/init.d.ts.map +0 -1
  71. package/dist/commands/init.js +0 -777
  72. package/dist/commands/init.js.map +0 -1
  73. package/dist/commands/install.d.ts +0 -65
  74. package/dist/commands/install.d.ts.map +0 -1
  75. package/dist/commands/install.js +0 -405
  76. package/dist/commands/install.js.map +0 -1
  77. package/dist/commands/logs.d.ts +0 -15
  78. package/dist/commands/logs.d.ts.map +0 -1
  79. package/dist/commands/logs.js +0 -155
  80. package/dist/commands/logs.js.map +0 -1
  81. package/dist/commands/merge.d.ts +0 -26
  82. package/dist/commands/merge.d.ts.map +0 -1
  83. package/dist/commands/merge.js +0 -159
  84. package/dist/commands/merge.js.map +0 -1
  85. package/dist/commands/notify.d.ts +0 -7
  86. package/dist/commands/notify.d.ts.map +0 -1
  87. package/dist/commands/notify.js +0 -43
  88. package/dist/commands/notify.js.map +0 -1
  89. package/dist/commands/plan.d.ts +0 -19
  90. package/dist/commands/plan.d.ts.map +0 -1
  91. package/dist/commands/plan.js +0 -88
  92. package/dist/commands/plan.js.map +0 -1
  93. package/dist/commands/prd-state.d.ts +0 -12
  94. package/dist/commands/prd-state.d.ts.map +0 -1
  95. package/dist/commands/prd-state.js +0 -47
  96. package/dist/commands/prd-state.js.map +0 -1
  97. package/dist/commands/prd.d.ts +0 -18
  98. package/dist/commands/prd.d.ts.map +0 -1
  99. package/dist/commands/prd.js +0 -363
  100. package/dist/commands/prd.js.map +0 -1
  101. package/dist/commands/prds.d.ts +0 -13
  102. package/dist/commands/prds.d.ts.map +0 -1
  103. package/dist/commands/prds.js +0 -194
  104. package/dist/commands/prds.js.map +0 -1
  105. package/dist/commands/prs.d.ts +0 -14
  106. package/dist/commands/prs.d.ts.map +0 -1
  107. package/dist/commands/prs.js +0 -104
  108. package/dist/commands/prs.js.map +0 -1
  109. package/dist/commands/qa.d.ts +0 -34
  110. package/dist/commands/qa.d.ts.map +0 -1
  111. package/dist/commands/qa.js +0 -214
  112. package/dist/commands/qa.js.map +0 -1
  113. package/dist/commands/queue.d.ts +0 -8
  114. package/dist/commands/queue.d.ts.map +0 -1
  115. package/dist/commands/queue.js +0 -363
  116. package/dist/commands/queue.js.map +0 -1
  117. package/dist/commands/resolve.d.ts +0 -26
  118. package/dist/commands/resolve.d.ts.map +0 -1
  119. package/dist/commands/resolve.js +0 -186
  120. package/dist/commands/resolve.js.map +0 -1
  121. package/dist/commands/retry.d.ts +0 -9
  122. package/dist/commands/retry.d.ts.map +0 -1
  123. package/dist/commands/retry.js +0 -71
  124. package/dist/commands/retry.js.map +0 -1
  125. package/dist/commands/review.d.ts +0 -82
  126. package/dist/commands/review.d.ts.map +0 -1
  127. package/dist/commands/review.js +0 -479
  128. package/dist/commands/review.js.map +0 -1
  129. package/dist/commands/run.d.ts +0 -73
  130. package/dist/commands/run.d.ts.map +0 -1
  131. package/dist/commands/run.js +0 -509
  132. package/dist/commands/run.js.map +0 -1
  133. package/dist/commands/serve.d.ts +0 -19
  134. package/dist/commands/serve.d.ts.map +0 -1
  135. package/dist/commands/serve.js +0 -142
  136. package/dist/commands/serve.js.map +0 -1
  137. package/dist/commands/shared/env-builder.d.ts +0 -49
  138. package/dist/commands/shared/env-builder.d.ts.map +0 -1
  139. package/dist/commands/shared/env-builder.js +0 -150
  140. package/dist/commands/shared/env-builder.js.map +0 -1
  141. package/dist/commands/slice.d.ts +0 -35
  142. package/dist/commands/slice.d.ts.map +0 -1
  143. package/dist/commands/slice.js +0 -316
  144. package/dist/commands/slice.js.map +0 -1
  145. package/dist/commands/state.d.ts +0 -8
  146. package/dist/commands/state.d.ts.map +0 -1
  147. package/dist/commands/state.js +0 -54
  148. package/dist/commands/state.js.map +0 -1
  149. package/dist/commands/status.d.ts +0 -14
  150. package/dist/commands/status.d.ts.map +0 -1
  151. package/dist/commands/status.js +0 -297
  152. package/dist/commands/status.js.map +0 -1
  153. package/dist/commands/summary.d.ts +0 -14
  154. package/dist/commands/summary.d.ts.map +0 -1
  155. package/dist/commands/summary.js +0 -193
  156. package/dist/commands/summary.js.map +0 -1
  157. package/dist/commands/uninstall.d.ts +0 -25
  158. package/dist/commands/uninstall.d.ts.map +0 -1
  159. package/dist/commands/uninstall.js +0 -134
  160. package/dist/commands/uninstall.js.map +0 -1
  161. package/dist/commands/update.d.ts +0 -22
  162. package/dist/commands/update.d.ts.map +0 -1
  163. package/dist/commands/update.js +0 -90
  164. package/dist/commands/update.js.map +0 -1
  165. package/dist/web/assets/index-2JY0x_Ij.js +0 -381
  166. package/dist/web/assets/index-3h8pgmqL.css +0 -1
  167. package/dist/web/assets/index-B-wbyZq7.js +0 -386
  168. package/dist/web/assets/index-B1BnOpiO.css +0 -1
  169. package/dist/web/assets/index-B3CnV08_.js +0 -365
  170. package/dist/web/assets/index-B5QjuFh9.css +0 -1
  171. package/dist/web/assets/index-B8FW2ecQ.js +0 -370
  172. package/dist/web/assets/index-BFxPiKyy.js +0 -381
  173. package/dist/web/assets/index-BGqNh_Da.js +0 -365
  174. package/dist/web/assets/index-BIONU0qz.css +0 -1
  175. package/dist/web/assets/index-B_l_3wnA.js +0 -370
  176. package/dist/web/assets/index-Ba-4YvTQ.js +0 -365
  177. package/dist/web/assets/index-Bbb4-39N.js +0 -370
  178. package/dist/web/assets/index-BdgdShEN.js +0 -365
  179. package/dist/web/assets/index-BhiC4Z-G.js +0 -381
  180. package/dist/web/assets/index-BjhCFjZi.js +0 -381
  181. package/dist/web/assets/index-BlRxmrnQ.css +0 -1
  182. package/dist/web/assets/index-BqwbXsHS.js +0 -365
  183. package/dist/web/assets/index-BsC7RT48.css +0 -1
  184. package/dist/web/assets/index-Bvh8XI8_.js +0 -370
  185. package/dist/web/assets/index-C01r2ymn.js +0 -381
  186. package/dist/web/assets/index-C51Rbsmk.js +0 -381
  187. package/dist/web/assets/index-C7lMNxRE.js +0 -370
  188. package/dist/web/assets/index-CJLObgsn.js +0 -386
  189. package/dist/web/assets/index-CLuRf7Zt.js +0 -381
  190. package/dist/web/assets/index-CM3xFd3e.css +0 -1
  191. package/dist/web/assets/index-CNkBtDK7.js +0 -370
  192. package/dist/web/assets/index-CPQbZ1BL.css +0 -1
  193. package/dist/web/assets/index-CTy5dUDU.css +0 -1
  194. package/dist/web/assets/index-CU15COKs.js +0 -370
  195. package/dist/web/assets/index-CiRJZI4z.js +0 -386
  196. package/dist/web/assets/index-Cp7RYjoy.css +0 -1
  197. package/dist/web/assets/index-CvPkZOWT.js +0 -381
  198. package/dist/web/assets/index-CvUk-33B.css +0 -1
  199. package/dist/web/assets/index-Cvmj-oF6.css +0 -1
  200. package/dist/web/assets/index-CxE5iQVO.js +0 -381
  201. package/dist/web/assets/index-D7lZQpFV.js +0 -365
  202. package/dist/web/assets/index-DAyP4GOi.css +0 -1
  203. package/dist/web/assets/index-DCG0n8Kg.js +0 -386
  204. package/dist/web/assets/index-DEEI8cyF.css +0 -1
  205. package/dist/web/assets/index-DF99BowV.js +0 -381
  206. package/dist/web/assets/index-DGWsvFj6.css +0 -1
  207. package/dist/web/assets/index-DGpU39Cp.css +0 -1
  208. package/dist/web/assets/index-DI4kFgOi.js +0 -370
  209. package/dist/web/assets/index-DIyTcPw5.css +0 -1
  210. package/dist/web/assets/index-DTsfDC7m.js +0 -381
  211. package/dist/web/assets/index-DcgNAi4A.js +0 -386
  212. package/dist/web/assets/index-DgOAgkZy.css +0 -1
  213. package/dist/web/assets/index-DnHkqbOa.js +0 -386
  214. package/dist/web/assets/index-DnR7Idcf.css +0 -1
  215. package/dist/web/assets/index-DpVirMEe.css +0 -1
  216. package/dist/web/assets/index-DsYIWZ86.css +0 -1
  217. package/dist/web/assets/index-DtrDkci5.js +0 -381
  218. package/dist/web/assets/index-DyjIth5M.js +0 -386
  219. package/dist/web/assets/index-FwIKfHPL.css +0 -1
  220. package/dist/web/assets/index-IKrZymWk.css +0 -1
  221. package/dist/web/assets/index-MA6fM0ab.js +0 -381
  222. package/dist/web/assets/index-N_QxaSEg.css +0 -1
  223. package/dist/web/assets/index-OcU-0TCQ.css +0 -1
  224. package/dist/web/assets/index-OyhrmG-L.js +0 -381
  225. package/dist/web/assets/index-SQlBKu_s.js +0 -386
  226. package/dist/web/assets/index-Sv2B60J4.js +0 -370
  227. package/dist/web/assets/index-Vgyivb5u.js +0 -365
  228. package/dist/web/assets/index-ZABWMEZR.js +0 -381
  229. package/dist/web/assets/index-ZE5lOeJp.js +0 -386
  230. package/dist/web/assets/index-aCHmkAcJ.css +0 -1
  231. package/dist/web/assets/index-bFijnpuU.js +0 -381
  232. package/dist/web/assets/index-bUPZgSoZ.css +0 -1
  233. package/dist/web/assets/index-mz1VIYsP.css +0 -1
  234. package/dist/web/assets/index-oOp_MFeE.js +0 -376
  235. package/dist/web/assets/index-rfU713Zm.js +0 -386
  236. package/dist/web/assets/index-tuNH9gmb.js +0 -448
  237. package/dist/web/assets/index-viSwHyDD.js +0 -365
  238. package/dist/web/assets/index-yKEQysks.js +0 -365
@@ -1,702 +0,0 @@
1
- /**
2
- * Board command group — manage the PRD tracking board
3
- */
4
- import { execFileSync } from 'child_process';
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import * as readline from 'readline';
8
- import { BOARD_COLUMNS, CATEGORY_LABELS, HORIZON_LABELS, NIGHT_WATCH_LABELS, PRIORITY_LABELS, createBoardProvider, createTable, dim, extractCategory, extractHorizon, extractPriority, getUncheckedItems, header, info, isValidCategory, isValidHorizon, isValidPriority, loadConfig, parseRoadmap, saveConfig, sortByPriority, success, warn, } from '@night-watch/core';
9
- import { findMatchingIssue, getLabelsForSection } from '@night-watch/core';
10
- import chalk from 'chalk';
11
- /** Wrap an async action body so provider errors surface as clean messages. */
12
- async function run(fn) {
13
- try {
14
- await fn();
15
- }
16
- catch (err) {
17
- console.error(chalk.red(err.message));
18
- process.exit(1);
19
- }
20
- }
21
- /**
22
- * Return a ready-to-use board provider, or exit with an error if not enabled.
23
- */
24
- function getProvider(config, cwd) {
25
- if (config.boardProvider?.enabled === false) {
26
- console.error('Board provider is disabled. Remove boardProvider.enabled: false from night-watch.config.json to re-enable.');
27
- process.exit(1);
28
- }
29
- const bp = config.boardProvider ?? { enabled: true, provider: 'github' };
30
- return createBoardProvider(bp, cwd);
31
- }
32
- function defaultBoardTitle(cwd) {
33
- return `${path.basename(cwd)} Night Watch`;
34
- }
35
- /**
36
- * Ensure the project has a configured board number.
37
- * If missing, auto-create a board and persist projectNumber to config.
38
- */
39
- async function ensureBoardConfigured(config, cwd, provider, options) {
40
- if (config.boardProvider?.projectNumber) {
41
- return;
42
- }
43
- const title = defaultBoardTitle(cwd);
44
- if (!options?.quiet) {
45
- info(`No board configured. Creating "${title}"…`);
46
- }
47
- const boardInfo = await provider.setupBoard(title);
48
- const result = saveConfig(cwd, {
49
- boardProvider: {
50
- ...config.boardProvider,
51
- enabled: config.boardProvider?.enabled ?? true,
52
- provider: config.boardProvider?.provider ?? 'github',
53
- projectNumber: boardInfo.number,
54
- },
55
- });
56
- if (!result.success) {
57
- throw new Error(`Failed to save config: ${result.error}`);
58
- }
59
- if (!options?.quiet) {
60
- success(`Board configured (#${boardInfo.number})`);
61
- }
62
- }
63
- /**
64
- * Prompt the user for a yes/no confirmation via readline.
65
- * Returns true when the user confirms.
66
- */
67
- async function confirmPrompt(question) {
68
- const rl = readline.createInterface({
69
- input: process.stdin,
70
- output: process.stdout,
71
- });
72
- return new Promise((resolve) => {
73
- rl.question(question, (answer) => {
74
- rl.close();
75
- resolve(answer.trim().toLowerCase() === 'y');
76
- });
77
- });
78
- }
79
- /**
80
- * Create GitHub labels via `gh label create` (idempotent).
81
- */
82
- async function createGitHubLabel(label, cwd) {
83
- try {
84
- execFileSync('gh', ['label', 'create', label.name, '--description', label.description, '--color', label.color], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
85
- return { created: true, skipped: false };
86
- }
87
- catch (err) {
88
- const output = err instanceof Error ? err.message : String(err);
89
- // Label already exists - treat as success
90
- if (output.includes('already exists') || output.includes('Label already exists')) {
91
- return { created: false, skipped: true };
92
- }
93
- return { created: false, skipped: false, error: output };
94
- }
95
- }
96
- /**
97
- * Group issues by priority for display.
98
- */
99
- function groupByPriority(issues) {
100
- const groups = new Map();
101
- // Initialize with ordered priority groups
102
- groups.set('P0 — Critical', []);
103
- groups.set('P1 — High', []);
104
- groups.set('P2 — Normal', []);
105
- groups.set('No Priority', []);
106
- for (const issue of issues) {
107
- const priority = extractPriority(issue);
108
- if (priority === 'P0') {
109
- groups.get('P0 — Critical').push(issue);
110
- }
111
- else if (priority === 'P1') {
112
- groups.get('P1 — High').push(issue);
113
- }
114
- else if (priority === 'P2') {
115
- groups.get('P2 — Normal').push(issue);
116
- }
117
- else {
118
- groups.get('No Priority').push(issue);
119
- }
120
- }
121
- return groups;
122
- }
123
- /**
124
- * Group issues by category for display.
125
- */
126
- function groupByCategory(issues) {
127
- const groups = new Map();
128
- // Initialize with all categories
129
- for (const cat of CATEGORY_LABELS) {
130
- groups.set(cat, []);
131
- }
132
- groups.set('No Category', []);
133
- for (const issue of issues) {
134
- const category = extractCategory(issue);
135
- if (category && groups.has(category)) {
136
- groups.get(category).push(issue);
137
- }
138
- else {
139
- groups.get('No Category').push(issue);
140
- }
141
- }
142
- return groups;
143
- }
144
- /**
145
- * Register the board command group with the program.
146
- */
147
- export function boardCommand(program) {
148
- const board = program.command('board').description('Manage the PRD tracking board');
149
- // ---------------------------------------------------------------------------
150
- // board setup
151
- // ---------------------------------------------------------------------------
152
- board
153
- .command('setup')
154
- .description('Create the Night Watch project board and persist its number to config')
155
- .option('--title <title>', 'Board title (default: <repo-folder> Night Watch)')
156
- .action(async (options) => run(async () => {
157
- const cwd = process.cwd();
158
- const config = loadConfig(cwd);
159
- const provider = getProvider(config, cwd);
160
- // Warn if already configured
161
- if (config.boardProvider?.projectNumber) {
162
- warn(`Board already set up (project #${config.boardProvider.projectNumber}).`);
163
- const confirmed = await confirmPrompt('Re-run setup? This will create a new board. [y/N] ');
164
- if (!confirmed) {
165
- dim('Aborted.');
166
- return;
167
- }
168
- }
169
- const boardTitle = options.title?.trim() || defaultBoardTitle(cwd);
170
- info(`Creating board "${boardTitle}"…`);
171
- const boardInfo = await provider.setupBoard(boardTitle);
172
- // Persist the project number
173
- const result = saveConfig(cwd, {
174
- boardProvider: {
175
- ...config.boardProvider,
176
- projectNumber: boardInfo.number,
177
- },
178
- });
179
- if (!result.success) {
180
- console.error(`Failed to save config: ${result.error}`);
181
- process.exit(1);
182
- }
183
- const columns = await provider.getColumns();
184
- header('Board Created');
185
- success(`URL: ${boardInfo.url}`);
186
- info('Columns:');
187
- for (const col of columns) {
188
- dim(` • ${col.name}`);
189
- }
190
- }));
191
- // ---------------------------------------------------------------------------
192
- // board setup-labels
193
- // ---------------------------------------------------------------------------
194
- board
195
- .command('setup-labels')
196
- .description('Create Night Watch labels in the GitHub repo')
197
- .option('--dry-run', 'Show what labels would be created without creating them')
198
- .action(async (options) => run(async () => {
199
- const cwd = process.cwd();
200
- header('Night Watch Labels');
201
- if (options.dryRun) {
202
- info('Dry run — showing labels that would be created:');
203
- for (const label of NIGHT_WATCH_LABELS) {
204
- console.log(` ${chalk.cyan(label.name)} (${label.color})`);
205
- dim(` ${label.description}`);
206
- }
207
- return;
208
- }
209
- let created = 0;
210
- let skipped = 0;
211
- let failed = 0;
212
- for (const label of NIGHT_WATCH_LABELS) {
213
- const result = await createGitHubLabel(label, cwd);
214
- if (result.created) {
215
- created++;
216
- success(`Created label: ${label.name}`);
217
- }
218
- else if (result.skipped) {
219
- skipped++;
220
- dim(`Label already exists: ${label.name}`);
221
- }
222
- else {
223
- failed++;
224
- console.error(chalk.red(`Failed to create label ${label.name}: ${result.error}`));
225
- }
226
- }
227
- console.log();
228
- info('Summary:');
229
- dim(` Created: ${created}`);
230
- dim(` Skipped (already existed): ${skipped}`);
231
- if (failed > 0) {
232
- console.error(chalk.red(` Failed: ${failed}`));
233
- }
234
- }));
235
- // ---------------------------------------------------------------------------
236
- // board create-prd <title>
237
- // ---------------------------------------------------------------------------
238
- board
239
- .command('create-prd')
240
- .description('Create a new issue on the board and add it in the Draft column')
241
- .argument('<title>', 'Issue title')
242
- .option('--body <text>', 'Issue body text')
243
- .option('--body-file <path>', 'Read issue body from a file')
244
- .option('--column <name>', 'Target column (default: Draft)', 'Draft')
245
- .option('--label <name>', 'Label to apply to the issue')
246
- .option('--priority <value>', 'Priority label (P0, P1, P2)')
247
- .option('--category <value>', 'Category label (reliability, quality, product, etc.)')
248
- .option('--horizon <value>', 'Horizon label (short-term, medium-term, long-term)')
249
- .action(async (title, options) => run(async () => {
250
- const cwd = process.cwd();
251
- const config = loadConfig(cwd);
252
- const provider = getProvider(config, cwd);
253
- await ensureBoardConfigured(config, cwd, provider);
254
- // Validate column name
255
- if (!BOARD_COLUMNS.includes(options.column)) {
256
- console.error(`Invalid column "${options.column}". Valid columns: ${BOARD_COLUMNS.join(', ')}`);
257
- process.exit(1);
258
- }
259
- // Validate priority
260
- if (options.priority && !isValidPriority(options.priority)) {
261
- console.error(`Invalid priority "${options.priority}". Valid values: ${PRIORITY_LABELS.join(', ')}`);
262
- process.exit(1);
263
- }
264
- // Validate category
265
- if (options.category && !isValidCategory(options.category)) {
266
- console.error(`Invalid category "${options.category}". Valid values: ${CATEGORY_LABELS.join(', ')}`);
267
- process.exit(1);
268
- }
269
- // Validate horizon
270
- if (options.horizon && !isValidHorizon(options.horizon)) {
271
- console.error(`Invalid horizon "${options.horizon}". Valid values: ${HORIZON_LABELS.join(', ')}`);
272
- process.exit(1);
273
- }
274
- let body = options.body ?? '';
275
- if (options.bodyFile) {
276
- const filePath = options.bodyFile;
277
- if (!fs.existsSync(filePath)) {
278
- console.error(`File not found: ${filePath}`);
279
- process.exit(1);
280
- }
281
- body = fs.readFileSync(filePath, 'utf-8');
282
- }
283
- // Build labels array
284
- const labels = [];
285
- if (options.label) {
286
- labels.push(options.label);
287
- }
288
- if (options.priority) {
289
- labels.push(options.priority);
290
- }
291
- if (options.category) {
292
- labels.push(options.category);
293
- }
294
- if (options.horizon) {
295
- labels.push(options.horizon);
296
- }
297
- const issue = await provider.createIssue({
298
- title,
299
- body,
300
- column: options.column,
301
- labels: labels.length > 0 ? labels : undefined,
302
- });
303
- console.log(chalk.green(`Created issue #${issue.number}: ${issue.title}`));
304
- console.log(chalk.green(`URL: ${issue.url}`));
305
- // Show applied labels
306
- if (labels.length > 0) {
307
- dim(`Labels: ${labels.join(', ')}`);
308
- }
309
- }));
310
- // ---------------------------------------------------------------------------
311
- // board add-issue <number>
312
- // ---------------------------------------------------------------------------
313
- board
314
- .command('add-issue')
315
- .description('Add an existing GitHub issue to the board')
316
- .argument('<number>', 'Issue number')
317
- .option('--column <name>', 'Target column (default: Ready)', 'Ready')
318
- .action(async (number, options) => run(async () => {
319
- const cwd = process.cwd();
320
- const config = loadConfig(cwd);
321
- const provider = getProvider(config, cwd);
322
- await ensureBoardConfigured(config, cwd, provider);
323
- if (!BOARD_COLUMNS.includes(options.column)) {
324
- console.error(`Invalid column "${options.column}". Valid columns: ${BOARD_COLUMNS.join(', ')}`);
325
- process.exit(1);
326
- }
327
- const issue = await provider.addIssue(parseInt(number, 10), options.column);
328
- success(`Added issue #${issue.number} "${issue.title}" to ${options.column}`);
329
- }));
330
- // ---------------------------------------------------------------------------
331
- // board status
332
- // ---------------------------------------------------------------------------
333
- board
334
- .command('status')
335
- .description('Show the current state of all issues grouped by column')
336
- .option('--json', 'Output raw JSON')
337
- .option('--group-by <field>', 'Group by: priority, category, or column (default: column)')
338
- .action(async (options) => run(async () => {
339
- const cwd = process.cwd();
340
- const config = loadConfig(cwd);
341
- const provider = getProvider(config, cwd);
342
- await ensureBoardConfigured(config, cwd, provider, { quiet: options.json });
343
- const issues = await provider.getAllIssues();
344
- if (options.json) {
345
- console.log(JSON.stringify(issues, null, 2));
346
- return;
347
- }
348
- header('Board Status');
349
- if (issues.length === 0) {
350
- dim('No issues found on the board.');
351
- return;
352
- }
353
- const groupBy = options.groupBy ?? 'column';
354
- if (groupBy === 'priority') {
355
- // Group by priority
356
- const grouped = groupByPriority(issues);
357
- const table = createTable({ head: ['Priority', 'Column', '#', 'Title', 'Category'] });
358
- for (const [priority, priorityIssues] of grouped) {
359
- if (priorityIssues.length === 0)
360
- continue;
361
- // Sort issues within priority by issue number
362
- const sorted = [...priorityIssues].sort((a, b) => a.number - b.number);
363
- for (const issue of sorted) {
364
- const category = extractCategory(issue) ?? '';
365
- table.push([
366
- priority,
367
- issue.column ?? '-',
368
- String(issue.number),
369
- issue.title,
370
- category,
371
- ]);
372
- }
373
- }
374
- console.log(table.toString());
375
- // Summary per priority
376
- info('Summary:');
377
- for (const [priority, priorityIssues] of grouped) {
378
- if (priorityIssues.length > 0) {
379
- dim(` ${priority}: ${priorityIssues.length} issue${priorityIssues.length === 1 ? '' : 's'}`);
380
- }
381
- }
382
- }
383
- else if (groupBy === 'category') {
384
- // Group by category
385
- const grouped = groupByCategory(issues);
386
- const table = createTable({ head: ['Category', 'Column', '#', 'Title', 'Priority'] });
387
- for (const [category, categoryIssues] of grouped) {
388
- if (categoryIssues.length === 0)
389
- continue;
390
- // Sort by priority within category
391
- const sorted = sortByPriority(categoryIssues);
392
- for (const issue of sorted) {
393
- const priority = extractPriority(issue) ?? '';
394
- table.push([
395
- category,
396
- issue.column ?? '-',
397
- String(issue.number),
398
- issue.title,
399
- priority,
400
- ]);
401
- }
402
- }
403
- console.log(table.toString());
404
- // Summary per category
405
- info('Summary:');
406
- for (const [category, categoryIssues] of grouped) {
407
- if (categoryIssues.length > 0) {
408
- dim(` ${category}: ${categoryIssues.length} issue${categoryIssues.length === 1 ? '' : 's'}`);
409
- }
410
- }
411
- }
412
- else {
413
- // Default: group by column (with priority and category columns)
414
- const grouped = {};
415
- for (const issue of issues) {
416
- const col = issue.column ?? 'Uncategorised';
417
- if (!grouped[col])
418
- grouped[col] = [];
419
- grouped[col].push(issue);
420
- }
421
- const table = createTable({ head: ['Column', '#', 'Title', 'Priority', 'Category'] });
422
- for (const [col, colIssues] of Object.entries(grouped)) {
423
- // Sort by priority within column
424
- const sorted = sortByPriority(colIssues);
425
- for (const issue of sorted) {
426
- const priority = extractPriority(issue) ?? '';
427
- const category = extractCategory(issue) ?? '';
428
- table.push([col, String(issue.number), issue.title, priority, category]);
429
- }
430
- }
431
- console.log(table.toString());
432
- // Summary per column
433
- info('Summary:');
434
- for (const [col, colIssues] of Object.entries(grouped)) {
435
- dim(` ${col}: ${colIssues.length} issue${colIssues.length === 1 ? '' : 's'}`);
436
- }
437
- }
438
- dim(` Total: ${issues.length}`);
439
- }));
440
- // ---------------------------------------------------------------------------
441
- // board next-issue
442
- // ---------------------------------------------------------------------------
443
- board
444
- .command('next-issue')
445
- .description('Return the next issue from a column (default: Ready), sorted by priority')
446
- .option('--column <name>', 'Column to fetch from', 'Ready')
447
- .option('--json', 'Output full issue JSON (for agent consumption)')
448
- .option('--all', 'Return all issues (as JSON array when combined with --json)')
449
- .action(async (options) => run(async () => {
450
- const cwd = process.cwd();
451
- const config = loadConfig(cwd);
452
- const provider = getProvider(config, cwd);
453
- await ensureBoardConfigured(config, cwd, provider, { quiet: options.json });
454
- const issues = await provider.getIssuesByColumn(options.column);
455
- if (issues.length === 0) {
456
- if (options.json) {
457
- if (options.all)
458
- console.log('[]');
459
- return;
460
- }
461
- console.log(`No issues found in ${options.column}`);
462
- return;
463
- }
464
- // Sort by priority (P0 > P1 > P2 > unlabeled), then by issue number
465
- const sorted = sortByPriority(issues).sort((a, b) => {
466
- const aPriority = extractPriority(a);
467
- const bPriority = extractPriority(b);
468
- const priorityOrder = { P0: 0, P1: 1, P2: 2 };
469
- const aOrder = aPriority ? priorityOrder[aPriority] : 99;
470
- const bOrder = bPriority ? priorityOrder[bPriority] : 99;
471
- if (aOrder !== bOrder)
472
- return aOrder - bOrder;
473
- return a.number - b.number; // Tie-breaker: issue number ascending
474
- });
475
- if (options.all) {
476
- if (options.json) {
477
- console.log(JSON.stringify(sorted, null, 2));
478
- return;
479
- }
480
- for (const issue of sorted) {
481
- const priority = extractPriority(issue);
482
- const category = extractCategory(issue);
483
- console.log(`#${issue.number} ${issue.title}`);
484
- if (priority || category) {
485
- dim(` Labels: ${[priority, category].filter(Boolean).join(', ')}`);
486
- }
487
- }
488
- return;
489
- }
490
- const issue = sorted[0];
491
- if (options.json) {
492
- console.log(JSON.stringify(issue, null, 2));
493
- return;
494
- }
495
- // Display with priority and category info
496
- const priority = extractPriority(issue);
497
- const category = extractCategory(issue);
498
- const horizon = extractHorizon(issue);
499
- console.log(`#${issue.number} ${issue.title}`);
500
- if (priority || category || horizon) {
501
- const labels = [priority, category, horizon].filter(Boolean);
502
- dim(`Labels: ${labels.join(', ')}`);
503
- }
504
- if (issue.body) {
505
- const preview = issue.body.slice(0, 200);
506
- const suffix = issue.body.length > 200 ? '…' : '';
507
- dim(preview + suffix);
508
- }
509
- }));
510
- // ---------------------------------------------------------------------------
511
- // board move-issue <number>
512
- // ---------------------------------------------------------------------------
513
- board
514
- .command('move-issue')
515
- .description('Move an issue to a different column')
516
- .argument('<number>', 'Issue number')
517
- .requiredOption('--column <name>', 'Target column name')
518
- .action(async (number, options) => run(async () => {
519
- const cwd = process.cwd();
520
- const config = loadConfig(cwd);
521
- const provider = getProvider(config, cwd);
522
- await ensureBoardConfigured(config, cwd, provider);
523
- await provider.moveIssue(parseInt(number, 10), options.column);
524
- success(`Moved issue #${number} to ${options.column}`);
525
- }));
526
- // ---------------------------------------------------------------------------
527
- // board comment <number>
528
- // ---------------------------------------------------------------------------
529
- board
530
- .command('comment')
531
- .description('Add a comment to an issue')
532
- .argument('<number>', 'Issue number')
533
- .requiredOption('--body <text>', 'Comment body text')
534
- .action(async (number, options) => run(async () => {
535
- const cwd = process.cwd();
536
- const config = loadConfig(cwd);
537
- const provider = getProvider(config, cwd);
538
- await ensureBoardConfigured(config, cwd, provider);
539
- await provider.commentOnIssue(parseInt(number, 10), options.body);
540
- success(`Comment added to issue #${number}`);
541
- }));
542
- // ---------------------------------------------------------------------------
543
- // board close-issue <number>
544
- // ---------------------------------------------------------------------------
545
- board
546
- .command('close-issue')
547
- .description('Close an issue and move it to Done')
548
- .argument('<number>', 'Issue number')
549
- .action(async (number) => run(async () => {
550
- const cwd = process.cwd();
551
- const config = loadConfig(cwd);
552
- const provider = getProvider(config, cwd);
553
- await ensureBoardConfigured(config, cwd, provider);
554
- const issueNumber = parseInt(number, 10);
555
- await provider.closeIssue(issueNumber);
556
- await provider.moveIssue(issueNumber, 'Done');
557
- success(`Closed issue #${number} and moved to Done`);
558
- }));
559
- // ---------------------------------------------------------------------------
560
- // board sync-roadmap
561
- // ---------------------------------------------------------------------------
562
- board
563
- .command('sync-roadmap')
564
- .description('Sync unchecked items from ROADMAP.md to the board as Draft issues')
565
- .option('--dry-run', 'Show what would be created without making API calls')
566
- .option('--update-labels', 'Update labels on existing matching issues')
567
- .option('--roadmap <path>', 'Path to ROADMAP.md file (default: ROADMAP.md in current directory)')
568
- .action(async (options) => run(async () => {
569
- const cwd = process.cwd();
570
- const config = loadConfig(cwd);
571
- const provider = getProvider(config, cwd);
572
- await ensureBoardConfigured(config, cwd, provider);
573
- // Find ROADMAP.md
574
- const roadmapPath = options.roadmap ?? path.join(cwd, 'ROADMAP.md');
575
- if (!fs.existsSync(roadmapPath)) {
576
- console.error(`Roadmap file not found: ${roadmapPath}`);
577
- process.exit(1);
578
- }
579
- const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
580
- const items = parseRoadmap(roadmapContent);
581
- const uncheckedItems = getUncheckedItems(items);
582
- if (uncheckedItems.length === 0) {
583
- info('No unchecked items found in ROADMAP.md');
584
- return;
585
- }
586
- // Get existing issues for matching
587
- const existingIssues = await provider.getAllIssues();
588
- header('Roadmap Sync');
589
- const toCreate = [];
590
- const toUpdate = [];
591
- const skipped = [];
592
- for (const item of uncheckedItems) {
593
- const labelMapping = getLabelsForSection(item.section);
594
- if (!labelMapping) {
595
- skipped.push({ item, reason: 'No section-to-label mapping found' });
596
- continue;
597
- }
598
- const { category, horizon } = labelMapping;
599
- const existingIssue = findMatchingIssue(item.title, existingIssues, 0.8);
600
- if (existingIssue) {
601
- if (options.updateLabels) {
602
- // Check if labels need updating
603
- const hasCategory = existingIssue.labels.includes(category);
604
- const hasHorizon = existingIssue.labels.includes(horizon);
605
- if (!hasCategory || !hasHorizon) {
606
- toUpdate.push({ item, issue: existingIssue, category, horizon });
607
- }
608
- else {
609
- skipped.push({
610
- item,
611
- reason: `Already exists with correct labels: #${existingIssue.number}`,
612
- });
613
- }
614
- }
615
- else {
616
- skipped.push({ item, reason: `Already exists: #${existingIssue.number}` });
617
- }
618
- }
619
- else {
620
- toCreate.push({ item, category, horizon });
621
- }
622
- }
623
- if (options.dryRun) {
624
- info('Dry run — showing what would happen:');
625
- console.log();
626
- if (toCreate.length > 0) {
627
- console.log(chalk.cyan('Would create:'));
628
- for (const { item, category, horizon } of toCreate) {
629
- dim(` • ${item.title}`);
630
- dim(` Section: ${item.section}`);
631
- dim(` Labels: ${category}, ${horizon}`);
632
- }
633
- }
634
- if (toUpdate.length > 0) {
635
- console.log();
636
- console.log(chalk.yellow('Would update labels:'));
637
- for (const { item, issue, category, horizon } of toUpdate) {
638
- dim(` • #${issue.number}: ${item.title}`);
639
- dim(` Labels to add: ${category}, ${horizon}`);
640
- }
641
- }
642
- if (skipped.length > 0) {
643
- console.log();
644
- console.log(chalk.gray('Skipped:'));
645
- for (const { item, reason } of skipped) {
646
- dim(` • ${item.title} — ${reason}`);
647
- }
648
- }
649
- console.log();
650
- info('Summary:');
651
- dim(` Would create: ${toCreate.length}`);
652
- dim(` Would update: ${toUpdate.length}`);
653
- dim(` Skipped: ${skipped.length}`);
654
- return;
655
- }
656
- // Create issues
657
- let created = 0;
658
- let updated = 0;
659
- let failed = 0;
660
- for (const { item, category, horizon } of toCreate) {
661
- try {
662
- const issue = await provider.createIssue({
663
- title: item.title,
664
- body: item.description || `Imported from ROADMAP.md section: ${item.section}`,
665
- column: 'Draft',
666
- labels: [category, horizon],
667
- });
668
- created++;
669
- success(`Created #${issue.number}: ${item.title}`);
670
- }
671
- catch (err) {
672
- failed++;
673
- console.error(chalk.red(`Failed to create "${item.title}": ${err.message}`));
674
- }
675
- }
676
- // Update labels on existing issues
677
- for (const { item, issue, category, horizon } of toUpdate) {
678
- try {
679
- // Add labels via gh CLI
680
- const labelsToAdd = [category, horizon].filter((l) => !issue.labels.includes(l));
681
- if (labelsToAdd.length > 0) {
682
- execFileSync('gh', ['issue', 'edit', String(issue.number), '--add-label', labelsToAdd.join(',')], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
683
- }
684
- updated++;
685
- success(`Updated labels on #${issue.number}: ${item.title}`);
686
- }
687
- catch (err) {
688
- failed++;
689
- console.error(chalk.red(`Failed to update #${issue.number}: ${err.message}`));
690
- }
691
- }
692
- console.log();
693
- info('Summary:');
694
- dim(` Created: ${created}`);
695
- dim(` Updated: ${updated}`);
696
- dim(` Skipped: ${skipped.length}`);
697
- if (failed > 0) {
698
- console.error(chalk.red(` Failed: ${failed}`));
699
- }
700
- }));
701
- }
702
- //# sourceMappingURL=board.js.map