prjct-cli 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +169 -1
  2. package/CLAUDE.md +43 -28
  3. package/README.md +4 -4
  4. package/bin/prjct +78 -63
  5. package/core/agent-generator.js +19 -10
  6. package/core/ascii-graphics.js +433 -0
  7. package/core/command-registry.js +553 -0
  8. package/core/commands.js +274 -62
  9. package/core/task-schema.js +342 -0
  10. package/package.json +4 -3
  11. package/templates/agents/AGENTS.md +79 -101
  12. package/templates/agents/be.template.md +14 -29
  13. package/templates/agents/coordinator.template.md +34 -0
  14. package/templates/agents/data.template.md +14 -28
  15. package/templates/agents/devops.template.md +14 -28
  16. package/templates/agents/fe.template.md +14 -29
  17. package/templates/agents/mobile.template.md +14 -28
  18. package/templates/agents/qa.template.md +14 -41
  19. package/templates/agents/scribe.template.md +15 -81
  20. package/templates/agents/security.template.md +14 -28
  21. package/templates/agents/ux.template.md +14 -36
  22. package/templates/commands/analyze.md +36 -239
  23. package/templates/commands/build.md +41 -0
  24. package/templates/commands/cleanup.md +24 -87
  25. package/templates/commands/context.md +24 -93
  26. package/templates/commands/design.md +20 -98
  27. package/templates/commands/done.md +16 -181
  28. package/templates/commands/fix.md +27 -66
  29. package/templates/commands/git.md +33 -60
  30. package/templates/commands/help.md +18 -52
  31. package/templates/commands/idea.md +11 -36
  32. package/templates/commands/init.md +30 -277
  33. package/templates/commands/next.md +20 -62
  34. package/templates/commands/now.md +18 -22
  35. package/templates/commands/progress.md +23 -78
  36. package/templates/commands/recap.md +22 -74
  37. package/templates/commands/roadmap.md +21 -90
  38. package/templates/commands/ship.md +26 -161
  39. package/templates/commands/status.md +40 -0
  40. package/templates/commands/stuck.md +21 -33
  41. package/templates/commands/sync.md +19 -209
  42. package/templates/commands/task.md +18 -80
  43. package/templates/commands/test.md +23 -72
  44. package/templates/commands/workflow.md +20 -212
  45. package/templates/agents/pm.template.md +0 -84
@@ -0,0 +1,433 @@
1
+ /**
2
+ * ASCII Graphics Utilities
3
+ *
4
+ * Creates visual dashboards and progress bars in terminal
5
+ * using ASCII characters with Catppuccin Mocha color scheme.
6
+ *
7
+ * @version 0.6.0
8
+ */
9
+
10
+ const chalk = require('chalk')
11
+
12
+ /**
13
+ * Catppuccin Mocha Color Palette
14
+ * https://github.com/catppuccin/catppuccin
15
+ */
16
+ const CATPPUCCIN = {
17
+ // Base colors
18
+ base: '#1e1e2e',
19
+ mantle: '#181825',
20
+ crust: '#11111b',
21
+
22
+ // Text colors
23
+ text: '#cdd6f4',
24
+ subtext1: '#bac2de',
25
+ subtext0: '#a6adc8',
26
+
27
+ // Overlay colors
28
+ overlay2: '#9399b2',
29
+ overlay1: '#7f849c',
30
+ overlay0: '#6c7086',
31
+ surface2: '#585b70',
32
+ surface1: '#45475a',
33
+ surface0: '#313244',
34
+
35
+ // Accent colors
36
+ rosewater: '#f5e0dc',
37
+ flamingo: '#f2cdcd',
38
+ pink: '#f5c2e7',
39
+ mauve: '#cba6f7',
40
+ red: '#f38ba8',
41
+ maroon: '#eba0ac',
42
+ peach: '#fab387',
43
+ yellow: '#f9e2af',
44
+ green: '#a6e3a1',
45
+ teal: '#94e2d5',
46
+ sky: '#89dceb',
47
+ sapphire: '#74c7ec',
48
+ blue: '#89b4fa',
49
+ lavender: '#b4befe',
50
+ }
51
+
52
+ /**
53
+ * Semantic color mapping
54
+ */
55
+ const COLORS = {
56
+ // Status colors
57
+ success: chalk.hex(CATPPUCCIN.green),
58
+ warning: chalk.hex(CATPPUCCIN.yellow),
59
+ error: chalk.hex(CATPPUCCIN.red),
60
+ info: chalk.hex(CATPPUCCIN.blue),
61
+
62
+ // UI colors
63
+ primary: chalk.hex(CATPPUCCIN.mauve),
64
+ secondary: chalk.hex(CATPPUCCIN.lavender),
65
+ accent: chalk.hex(CATPPUCCIN.peach),
66
+ muted: chalk.hex(CATPPUCCIN.overlay0),
67
+
68
+ // Progress colors
69
+ progress: chalk.hex(CATPPUCCIN.teal),
70
+ complete: chalk.hex(CATPPUCCIN.green),
71
+ pending: chalk.hex(CATPPUCCIN.overlay1),
72
+
73
+ // Text colors
74
+ bold: chalk.hex(CATPPUCCIN.text).bold,
75
+ dim: chalk.hex(CATPPUCCIN.overlay0),
76
+ highlight: chalk.hex(CATPPUCCIN.sapphire),
77
+ }
78
+
79
+ /**
80
+ * Box Drawing Characters
81
+ */
82
+ const BOX = {
83
+ topLeft: '┌',
84
+ topRight: '┐',
85
+ bottomLeft: '└',
86
+ bottomRight: '┘',
87
+ horizontal: '─',
88
+ vertical: '│',
89
+ verticalRight: '├',
90
+ verticalLeft: '┤',
91
+ cross: '┼',
92
+ }
93
+
94
+ /**
95
+ * Progress Bar Characters
96
+ */
97
+ const PROGRESS = {
98
+ filled: '█',
99
+ empty: '░',
100
+ partial: ['▏', '▎', '▍', '▌', '▋', '▊', '▉'],
101
+ }
102
+
103
+ /**
104
+ * ASCII Graphics Generator
105
+ */
106
+ class ASCIIGraphics {
107
+ /**
108
+ * Create a status dashboard
109
+ */
110
+ static createDashboard(data) {
111
+ const width = 50
112
+ const lines = []
113
+
114
+ // Top border
115
+ lines.push(
116
+ COLORS.primary(
117
+ `${BOX.topLeft}${BOX.horizontal} Project Status ${BOX.horizontal.repeat(width - 17)}${BOX.topRight}`,
118
+ ),
119
+ )
120
+
121
+ // Sprint Progress
122
+ const sprintProgress = data.sprintProgress || 0
123
+ lines.push(
124
+ `${COLORS.primary(BOX.vertical)} ${COLORS.bold('Sprint Progress')} ${this.createProgressBar(sprintProgress, 10)} ${COLORS.highlight(Math.round(sprintProgress) + '%')} ${' '.repeat(width - 38)}${COLORS.primary(BOX.vertical)}`,
125
+ )
126
+
127
+ // Tasks Complete
128
+ const tasksComplete = data.tasksComplete || 0
129
+ const tasksTotal = data.tasksTotal || 0
130
+ lines.push(
131
+ `${COLORS.primary(BOX.vertical)} ${COLORS.bold('Tasks Complete')} ${COLORS.complete(tasksComplete)}/${COLORS.info(tasksTotal)}${' '.repeat(width - 28)}${COLORS.primary(BOX.vertical)}`,
132
+ )
133
+
134
+ // Ideas in Backlog
135
+ const ideasCount = data.ideasCount || 0
136
+ lines.push(
137
+ `${COLORS.primary(BOX.vertical)} ${COLORS.bold('Ideas in Backlog')} ${COLORS.accent(ideasCount)}${' '.repeat(width - 28)}${COLORS.primary(BOX.vertical)}`,
138
+ )
139
+
140
+ // Days Since Ship
141
+ const daysSinceShip = data.daysSinceShip || 0
142
+ const shipColor =
143
+ daysSinceShip > 7 ? COLORS.error : daysSinceShip > 3 ? COLORS.warning : COLORS.success
144
+ lines.push(
145
+ `${COLORS.primary(BOX.vertical)} ${COLORS.bold('Days Since Ship')} ${shipColor(daysSinceShip)}${' '.repeat(width - 28)}${COLORS.primary(BOX.vertical)}`,
146
+ )
147
+
148
+ // Middle separator
149
+ lines.push(
150
+ COLORS.primary(
151
+ `${BOX.verticalRight}${BOX.horizontal} Current Focus ${BOX.horizontal.repeat(width - 17)}${BOX.verticalLeft}`,
152
+ ),
153
+ )
154
+
155
+ // Current Task
156
+ const currentTask = data.currentTask || 'No active task'
157
+ const taskTime = data.taskTime || ''
158
+ lines.push(
159
+ `${COLORS.primary(BOX.vertical)} ${COLORS.highlight('→')} ${COLORS.bold(this.truncate(currentTask, width - 5))}${' '.repeat(Math.max(0, width - currentTask.length - 4))}${COLORS.primary(BOX.vertical)}`,
160
+ )
161
+
162
+ if (taskTime) {
163
+ lines.push(
164
+ `${COLORS.primary(BOX.vertical)} ${COLORS.dim(`Started: ${taskTime}`)}${' '.repeat(Math.max(0, width - taskTime.length - 13))}${COLORS.primary(BOX.vertical)}`,
165
+ )
166
+ }
167
+
168
+ // Bottom border
169
+ lines.push(COLORS.primary(`${BOX.bottomLeft}${BOX.horizontal.repeat(width)}${BOX.bottomRight}`))
170
+
171
+ return lines.join('\n')
172
+ }
173
+
174
+ /**
175
+ * Create a progress bar
176
+ */
177
+ static createProgressBar(percentage, width = 20) {
178
+ const filled = Math.floor((percentage / 100) * width)
179
+ const empty = width - filled
180
+
181
+ return COLORS.progress(PROGRESS.filled.repeat(filled)) + COLORS.pending(PROGRESS.empty.repeat(empty))
182
+ }
183
+
184
+ /**
185
+ * Create a horizontal bar chart
186
+ */
187
+ static createBarChart(data, maxWidth = 30) {
188
+ const lines = []
189
+ const maxValue = Math.max(...data.map((d) => d.value))
190
+
191
+ for (const item of data) {
192
+ const barWidth = Math.round((item.value / maxValue) * maxWidth)
193
+ const bar = PROGRESS.filled.repeat(barWidth)
194
+ const label = item.label.padEnd(15)
195
+ const value = String(item.value).padStart(3)
196
+
197
+ lines.push(`${label} ${COLORS.progress(bar)} ${COLORS.bold(value)}`)
198
+ }
199
+
200
+ return lines.join('\n')
201
+ }
202
+
203
+ /**
204
+ * Create a vertical progress indicator
205
+ */
206
+ static createVerticalProgress(percentage, height = 10) {
207
+ const filled = Math.floor((percentage / 100) * height)
208
+ const empty = height - filled
209
+
210
+ const lines = []
211
+ lines.push(COLORS.primary('┌─┐'))
212
+
213
+ for (let i = 0; i < empty; i++) {
214
+ lines.push(`${COLORS.primary('│')}${COLORS.pending(PROGRESS.empty)}${COLORS.primary('│')}`)
215
+ }
216
+
217
+ for (let i = 0; i < filled; i++) {
218
+ lines.push(`${COLORS.primary('│')}${COLORS.progress(PROGRESS.filled)}${COLORS.primary('│')}`)
219
+ }
220
+
221
+ lines.push(COLORS.primary('└─┘'))
222
+ lines.push(` ${COLORS.highlight(percentage + '%')}`)
223
+
224
+ return lines.join('\n')
225
+ }
226
+
227
+ /**
228
+ * Create a timeline view
229
+ */
230
+ static createTimeline(events) {
231
+ const lines = []
232
+
233
+ for (let i = 0; i < events.length; i++) {
234
+ const event = events[i]
235
+ const isLast = i === events.length - 1
236
+
237
+ // Event marker
238
+ const marker = event.completed ? COLORS.complete('●') : COLORS.pending('○')
239
+ const connector = isLast ? ' ' : COLORS.muted('│')
240
+
241
+ lines.push(`${marker} ${COLORS.bold(event.title)}`)
242
+
243
+ if (event.description) {
244
+ lines.push(`${connector} ${COLORS.dim(event.description)}`)
245
+ }
246
+
247
+ if (event.time) {
248
+ lines.push(`${connector} ${COLORS.info(event.time)}`)
249
+ }
250
+
251
+ if (!isLast) {
252
+ lines.push(connector)
253
+ }
254
+ }
255
+
256
+ return lines.join('\n')
257
+ }
258
+
259
+ /**
260
+ * Create a table
261
+ */
262
+ static createTable(headers, rows) {
263
+ const columnWidths = headers.map((h, i) => {
264
+ const maxRowWidth = Math.max(...rows.map((r) => String(r[i] || '').length))
265
+ return Math.max(h.length, maxRowWidth) + 2
266
+ })
267
+
268
+ const lines = []
269
+
270
+ // Top border
271
+ const topBorder =
272
+ BOX.topLeft +
273
+ columnWidths.map((w) => BOX.horizontal.repeat(w)).join(BOX.cross) +
274
+ BOX.topRight
275
+ lines.push(topBorder)
276
+
277
+ // Headers
278
+ const headerRow =
279
+ BOX.vertical +
280
+ headers
281
+ .map((h, i) => chalk.bold(h.padEnd(columnWidths[i])))
282
+ .join(BOX.vertical) +
283
+ BOX.vertical
284
+ lines.push(headerRow)
285
+
286
+ // Header separator
287
+ const headerSep =
288
+ BOX.verticalRight +
289
+ columnWidths.map((w) => BOX.horizontal.repeat(w)).join(BOX.cross) +
290
+ BOX.verticalLeft
291
+ lines.push(headerSep)
292
+
293
+ // Rows
294
+ for (const row of rows) {
295
+ const rowStr =
296
+ BOX.vertical +
297
+ row
298
+ .map((cell, i) => String(cell || '').padEnd(columnWidths[i]))
299
+ .join(BOX.vertical) +
300
+ BOX.vertical
301
+ lines.push(rowStr)
302
+ }
303
+
304
+ // Bottom border
305
+ const bottomBorder =
306
+ BOX.bottomLeft +
307
+ columnWidths.map((w) => BOX.horizontal.repeat(w)).join(BOX.cross) +
308
+ BOX.bottomRight
309
+ lines.push(bottomBorder)
310
+
311
+ return lines.join('\n')
312
+ }
313
+
314
+ /**
315
+ * Create a sparkline (mini chart)
316
+ */
317
+ static createSparkline(values) {
318
+ const chars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']
319
+ const max = Math.max(...values)
320
+ const min = Math.min(...values)
321
+ const range = max - min
322
+
323
+ if (range === 0) {
324
+ return chars[0].repeat(values.length)
325
+ }
326
+
327
+ return values
328
+ .map((v) => {
329
+ const normalized = (v - min) / range
330
+ const index = Math.floor(normalized * (chars.length - 1))
331
+ return chars[index]
332
+ })
333
+ .join('')
334
+ }
335
+
336
+ /**
337
+ * Create a gauge/meter
338
+ */
339
+ static createGauge(value, max, label = '') {
340
+ const percentage = (value / max) * 100
341
+ const width = 30
342
+
343
+ const filled = Math.floor((percentage / 100) * width)
344
+ const empty = width - filled
345
+
346
+ let color = COLORS.success
347
+ if (percentage > 75) color = COLORS.error
348
+ else if (percentage > 50) color = COLORS.warning
349
+
350
+ const bar = color(PROGRESS.filled.repeat(filled)) + COLORS.pending(PROGRESS.empty.repeat(empty))
351
+
352
+ const labelStr = label ? `${COLORS.bold(label.padEnd(15))} ` : ''
353
+ const valueStr = `${COLORS.info(value)}/${COLORS.muted(max)} (${COLORS.highlight(Math.round(percentage) + '%')})`
354
+
355
+ return `${labelStr}[${bar}] ${valueStr}`
356
+ }
357
+
358
+ /**
359
+ * Create ASCII art number (for big stats)
360
+ */
361
+ static createBigNumber(num) {
362
+ const digits = {
363
+ 0: ['███', '█ █', '█ █', '█ █', '███'],
364
+ 1: [' █', ' █', ' █', ' █', ' █'],
365
+ 2: ['███', ' █', '███', '█ ', '███'],
366
+ 3: ['███', ' █', '███', ' █', '███'],
367
+ 4: ['█ █', '█ █', '███', ' █', ' █'],
368
+ 5: ['███', '█ ', '███', ' █', '███'],
369
+ 6: ['███', '█ ', '███', '█ █', '███'],
370
+ 7: ['███', ' █', ' █', ' █', ' █'],
371
+ 8: ['███', '█ █', '███', '█ █', '███'],
372
+ 9: ['███', '█ █', '███', ' █', '███'],
373
+ }
374
+
375
+ const numStr = String(num)
376
+ const lines = ['', '', '', '', '']
377
+
378
+ for (const char of numStr) {
379
+ if (digits[char]) {
380
+ for (let i = 0; i < 5; i++) {
381
+ lines[i] += digits[char][i] + ' '
382
+ }
383
+ }
384
+ }
385
+
386
+ return lines.join('\n')
387
+ }
388
+
389
+ /**
390
+ * Truncate text to fit width
391
+ */
392
+ static truncate(text, maxWidth) {
393
+ if (text.length <= maxWidth) {
394
+ return text
395
+ }
396
+ return text.substring(0, maxWidth - 3) + '...'
397
+ }
398
+
399
+ /**
400
+ * Create a loading spinner frame
401
+ */
402
+ static getSpinnerFrame(index) {
403
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
404
+ return frames[index % frames.length]
405
+ }
406
+
407
+ /**
408
+ * Create a status indicator
409
+ */
410
+ static statusIndicator(status) {
411
+ const indicators = {
412
+ success: COLORS.success('✓'),
413
+ error: COLORS.error('✗'),
414
+ warning: COLORS.warning('⚠'),
415
+ info: COLORS.info('ℹ'),
416
+ pending: COLORS.pending('○'),
417
+ active: COLORS.highlight('●'),
418
+ }
419
+
420
+ return indicators[status] || indicators.info
421
+ }
422
+
423
+ /**
424
+ * Create a divider line
425
+ */
426
+ static divider(width = 50, char = '─') {
427
+ return COLORS.muted(char.repeat(width))
428
+ }
429
+ }
430
+
431
+ module.exports = ASCIIGraphics
432
+ module.exports.COLORS = COLORS
433
+ module.exports.CATPPUCCIN = CATPPUCCIN