@jonit-dev/night-watch-cli 1.8.10-beta.1 → 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 (235) hide show
  1. package/dist/cli.js +2309 -1313
  2. package/dist/web/assets/index-BUgI2S1s.js +406 -0
  3. package/dist/web/assets/index-CkdLFBd7.js +406 -0
  4. package/dist/web/assets/index-RMfswANB.css +1 -0
  5. package/dist/web/index.html +2 -2
  6. package/package.json +1 -1
  7. package/dist/cli.d.ts +0 -3
  8. package/dist/cli.d.ts.map +0 -1
  9. package/dist/cli.js.map +0 -1
  10. package/dist/commands/analytics.d.ts +0 -14
  11. package/dist/commands/analytics.d.ts.map +0 -1
  12. package/dist/commands/analytics.js +0 -69
  13. package/dist/commands/analytics.js.map +0 -1
  14. package/dist/commands/audit.d.ts +0 -19
  15. package/dist/commands/audit.d.ts.map +0 -1
  16. package/dist/commands/audit.js +0 -144
  17. package/dist/commands/audit.js.map +0 -1
  18. package/dist/commands/board.d.ts +0 -9
  19. package/dist/commands/board.d.ts.map +0 -1
  20. package/dist/commands/board.js +0 -702
  21. package/dist/commands/board.js.map +0 -1
  22. package/dist/commands/cancel.d.ts +0 -46
  23. package/dist/commands/cancel.d.ts.map +0 -1
  24. package/dist/commands/cancel.js +0 -239
  25. package/dist/commands/cancel.js.map +0 -1
  26. package/dist/commands/cron.d.ts +0 -8
  27. package/dist/commands/cron.d.ts.map +0 -1
  28. package/dist/commands/cron.js +0 -134
  29. package/dist/commands/cron.js.map +0 -1
  30. package/dist/commands/dashboard/tab-actions.d.ts +0 -10
  31. package/dist/commands/dashboard/tab-actions.d.ts.map +0 -1
  32. package/dist/commands/dashboard/tab-actions.js +0 -247
  33. package/dist/commands/dashboard/tab-actions.js.map +0 -1
  34. package/dist/commands/dashboard/tab-config.d.ts +0 -21
  35. package/dist/commands/dashboard/tab-config.d.ts.map +0 -1
  36. package/dist/commands/dashboard/tab-config.js +0 -873
  37. package/dist/commands/dashboard/tab-config.js.map +0 -1
  38. package/dist/commands/dashboard/tab-logs.d.ts +0 -10
  39. package/dist/commands/dashboard/tab-logs.d.ts.map +0 -1
  40. package/dist/commands/dashboard/tab-logs.js +0 -202
  41. package/dist/commands/dashboard/tab-logs.js.map +0 -1
  42. package/dist/commands/dashboard/tab-schedules.d.ts +0 -21
  43. package/dist/commands/dashboard/tab-schedules.d.ts.map +0 -1
  44. package/dist/commands/dashboard/tab-schedules.js +0 -320
  45. package/dist/commands/dashboard/tab-schedules.js.map +0 -1
  46. package/dist/commands/dashboard/tab-status.d.ts +0 -32
  47. package/dist/commands/dashboard/tab-status.d.ts.map +0 -1
  48. package/dist/commands/dashboard/tab-status.js +0 -424
  49. package/dist/commands/dashboard/tab-status.js.map +0 -1
  50. package/dist/commands/dashboard/types.d.ts +0 -42
  51. package/dist/commands/dashboard/types.d.ts.map +0 -1
  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.d.ts.map +0 -1
  56. package/dist/commands/dashboard.js +0 -242
  57. package/dist/commands/dashboard.js.map +0 -1
  58. package/dist/commands/doctor.d.ts +0 -16
  59. package/dist/commands/doctor.d.ts.map +0 -1
  60. package/dist/commands/doctor.js +0 -195
  61. package/dist/commands/doctor.js.map +0 -1
  62. package/dist/commands/history.d.ts +0 -7
  63. package/dist/commands/history.d.ts.map +0 -1
  64. package/dist/commands/history.js +0 -49
  65. package/dist/commands/history.js.map +0 -1
  66. package/dist/commands/init.d.ts +0 -45
  67. package/dist/commands/init.d.ts.map +0 -1
  68. package/dist/commands/init.js +0 -777
  69. package/dist/commands/init.js.map +0 -1
  70. package/dist/commands/install.d.ts +0 -65
  71. package/dist/commands/install.d.ts.map +0 -1
  72. package/dist/commands/install.js +0 -405
  73. package/dist/commands/install.js.map +0 -1
  74. package/dist/commands/logs.d.ts +0 -15
  75. package/dist/commands/logs.d.ts.map +0 -1
  76. package/dist/commands/logs.js +0 -155
  77. package/dist/commands/logs.js.map +0 -1
  78. package/dist/commands/merge.d.ts +0 -26
  79. package/dist/commands/merge.d.ts.map +0 -1
  80. package/dist/commands/merge.js +0 -159
  81. package/dist/commands/merge.js.map +0 -1
  82. package/dist/commands/notify.d.ts +0 -7
  83. package/dist/commands/notify.d.ts.map +0 -1
  84. package/dist/commands/notify.js +0 -43
  85. package/dist/commands/notify.js.map +0 -1
  86. package/dist/commands/plan.d.ts +0 -19
  87. package/dist/commands/plan.d.ts.map +0 -1
  88. package/dist/commands/plan.js +0 -88
  89. package/dist/commands/plan.js.map +0 -1
  90. package/dist/commands/prd-state.d.ts +0 -12
  91. package/dist/commands/prd-state.d.ts.map +0 -1
  92. package/dist/commands/prd-state.js +0 -47
  93. package/dist/commands/prd-state.js.map +0 -1
  94. package/dist/commands/prd.d.ts +0 -18
  95. package/dist/commands/prd.d.ts.map +0 -1
  96. package/dist/commands/prd.js +0 -363
  97. package/dist/commands/prd.js.map +0 -1
  98. package/dist/commands/prds.d.ts +0 -13
  99. package/dist/commands/prds.d.ts.map +0 -1
  100. package/dist/commands/prds.js +0 -194
  101. package/dist/commands/prds.js.map +0 -1
  102. package/dist/commands/prs.d.ts +0 -14
  103. package/dist/commands/prs.d.ts.map +0 -1
  104. package/dist/commands/prs.js +0 -104
  105. package/dist/commands/prs.js.map +0 -1
  106. package/dist/commands/qa.d.ts +0 -34
  107. package/dist/commands/qa.d.ts.map +0 -1
  108. package/dist/commands/qa.js +0 -214
  109. package/dist/commands/qa.js.map +0 -1
  110. package/dist/commands/queue.d.ts +0 -8
  111. package/dist/commands/queue.d.ts.map +0 -1
  112. package/dist/commands/queue.js +0 -376
  113. package/dist/commands/queue.js.map +0 -1
  114. package/dist/commands/resolve.d.ts +0 -26
  115. package/dist/commands/resolve.d.ts.map +0 -1
  116. package/dist/commands/resolve.js +0 -186
  117. package/dist/commands/resolve.js.map +0 -1
  118. package/dist/commands/retry.d.ts +0 -9
  119. package/dist/commands/retry.d.ts.map +0 -1
  120. package/dist/commands/retry.js +0 -71
  121. package/dist/commands/retry.js.map +0 -1
  122. package/dist/commands/review.d.ts +0 -82
  123. package/dist/commands/review.d.ts.map +0 -1
  124. package/dist/commands/review.js +0 -479
  125. package/dist/commands/review.js.map +0 -1
  126. package/dist/commands/run.d.ts +0 -73
  127. package/dist/commands/run.d.ts.map +0 -1
  128. package/dist/commands/run.js +0 -509
  129. package/dist/commands/run.js.map +0 -1
  130. package/dist/commands/serve.d.ts +0 -19
  131. package/dist/commands/serve.d.ts.map +0 -1
  132. package/dist/commands/serve.js +0 -142
  133. package/dist/commands/serve.js.map +0 -1
  134. package/dist/commands/shared/env-builder.d.ts +0 -49
  135. package/dist/commands/shared/env-builder.d.ts.map +0 -1
  136. package/dist/commands/shared/env-builder.js +0 -150
  137. package/dist/commands/shared/env-builder.js.map +0 -1
  138. package/dist/commands/slice.d.ts +0 -35
  139. package/dist/commands/slice.d.ts.map +0 -1
  140. package/dist/commands/slice.js +0 -316
  141. package/dist/commands/slice.js.map +0 -1
  142. package/dist/commands/state.d.ts +0 -8
  143. package/dist/commands/state.d.ts.map +0 -1
  144. package/dist/commands/state.js +0 -54
  145. package/dist/commands/state.js.map +0 -1
  146. package/dist/commands/status.d.ts +0 -14
  147. package/dist/commands/status.d.ts.map +0 -1
  148. package/dist/commands/status.js +0 -297
  149. package/dist/commands/status.js.map +0 -1
  150. package/dist/commands/summary.d.ts +0 -14
  151. package/dist/commands/summary.d.ts.map +0 -1
  152. package/dist/commands/summary.js +0 -193
  153. package/dist/commands/summary.js.map +0 -1
  154. package/dist/commands/uninstall.d.ts +0 -25
  155. package/dist/commands/uninstall.d.ts.map +0 -1
  156. package/dist/commands/uninstall.js +0 -134
  157. package/dist/commands/uninstall.js.map +0 -1
  158. package/dist/commands/update.d.ts +0 -22
  159. package/dist/commands/update.d.ts.map +0 -1
  160. package/dist/commands/update.js +0 -90
  161. package/dist/commands/update.js.map +0 -1
  162. package/dist/web/assets/index-2JY0x_Ij.js +0 -381
  163. package/dist/web/assets/index-3h8pgmqL.css +0 -1
  164. package/dist/web/assets/index-B-wbyZq7.js +0 -386
  165. package/dist/web/assets/index-B1BnOpiO.css +0 -1
  166. package/dist/web/assets/index-B3CnV08_.js +0 -365
  167. package/dist/web/assets/index-B5QjuFh9.css +0 -1
  168. package/dist/web/assets/index-B8FW2ecQ.js +0 -370
  169. package/dist/web/assets/index-BFxPiKyy.js +0 -381
  170. package/dist/web/assets/index-BGqNh_Da.js +0 -365
  171. package/dist/web/assets/index-BIONU0qz.css +0 -1
  172. package/dist/web/assets/index-B_l_3wnA.js +0 -370
  173. package/dist/web/assets/index-Ba-4YvTQ.js +0 -365
  174. package/dist/web/assets/index-Bbb4-39N.js +0 -370
  175. package/dist/web/assets/index-BdgdShEN.js +0 -365
  176. package/dist/web/assets/index-BhiC4Z-G.js +0 -381
  177. package/dist/web/assets/index-BjhCFjZi.js +0 -381
  178. package/dist/web/assets/index-BlRxmrnQ.css +0 -1
  179. package/dist/web/assets/index-BqwbXsHS.js +0 -365
  180. package/dist/web/assets/index-BsC7RT48.css +0 -1
  181. package/dist/web/assets/index-Bvh8XI8_.js +0 -370
  182. package/dist/web/assets/index-C01r2ymn.js +0 -381
  183. package/dist/web/assets/index-C51Rbsmk.js +0 -381
  184. package/dist/web/assets/index-C7lMNxRE.js +0 -370
  185. package/dist/web/assets/index-CJLObgsn.js +0 -386
  186. package/dist/web/assets/index-CLuRf7Zt.js +0 -381
  187. package/dist/web/assets/index-CM3xFd3e.css +0 -1
  188. package/dist/web/assets/index-CNkBtDK7.js +0 -370
  189. package/dist/web/assets/index-CPQbZ1BL.css +0 -1
  190. package/dist/web/assets/index-CTy5dUDU.css +0 -1
  191. package/dist/web/assets/index-CU15COKs.js +0 -370
  192. package/dist/web/assets/index-CiRJZI4z.js +0 -386
  193. package/dist/web/assets/index-Cp7RYjoy.css +0 -1
  194. package/dist/web/assets/index-CvPkZOWT.js +0 -381
  195. package/dist/web/assets/index-CvUk-33B.css +0 -1
  196. package/dist/web/assets/index-Cvmj-oF6.css +0 -1
  197. package/dist/web/assets/index-CxE5iQVO.js +0 -381
  198. package/dist/web/assets/index-D7lZQpFV.js +0 -365
  199. package/dist/web/assets/index-DAyP4GOi.css +0 -1
  200. package/dist/web/assets/index-DCG0n8Kg.js +0 -386
  201. package/dist/web/assets/index-DEEI8cyF.css +0 -1
  202. package/dist/web/assets/index-DF99BowV.js +0 -381
  203. package/dist/web/assets/index-DGWsvFj6.css +0 -1
  204. package/dist/web/assets/index-DGpU39Cp.css +0 -1
  205. package/dist/web/assets/index-DI4kFgOi.js +0 -370
  206. package/dist/web/assets/index-DIyTcPw5.css +0 -1
  207. package/dist/web/assets/index-DTsfDC7m.js +0 -381
  208. package/dist/web/assets/index-DcgNAi4A.js +0 -386
  209. package/dist/web/assets/index-DgOAgkZy.css +0 -1
  210. package/dist/web/assets/index-DnHkqbOa.js +0 -386
  211. package/dist/web/assets/index-DnR7Idcf.css +0 -1
  212. package/dist/web/assets/index-DpVirMEe.css +0 -1
  213. package/dist/web/assets/index-DsYIWZ86.css +0 -1
  214. package/dist/web/assets/index-DtrDkci5.js +0 -381
  215. package/dist/web/assets/index-DyjIth5M.js +0 -386
  216. package/dist/web/assets/index-FwIKfHPL.css +0 -1
  217. package/dist/web/assets/index-IKrZymWk.css +0 -1
  218. package/dist/web/assets/index-MA6fM0ab.js +0 -381
  219. package/dist/web/assets/index-N_QxaSEg.css +0 -1
  220. package/dist/web/assets/index-OcU-0TCQ.css +0 -1
  221. package/dist/web/assets/index-OyhrmG-L.js +0 -381
  222. package/dist/web/assets/index-SQlBKu_s.js +0 -386
  223. package/dist/web/assets/index-Sv2B60J4.js +0 -370
  224. package/dist/web/assets/index-Vgyivb5u.js +0 -365
  225. package/dist/web/assets/index-ZABWMEZR.js +0 -381
  226. package/dist/web/assets/index-ZE5lOeJp.js +0 -386
  227. package/dist/web/assets/index-aCHmkAcJ.css +0 -1
  228. package/dist/web/assets/index-bFijnpuU.js +0 -381
  229. package/dist/web/assets/index-bUPZgSoZ.css +0 -1
  230. package/dist/web/assets/index-mz1VIYsP.css +0 -1
  231. package/dist/web/assets/index-oOp_MFeE.js +0 -376
  232. package/dist/web/assets/index-rfU713Zm.js +0 -386
  233. package/dist/web/assets/index-tuNH9gmb.js +0 -448
  234. package/dist/web/assets/index-viSwHyDD.js +0 -365
  235. 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