@proletariat/cli 0.3.24 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/action/create.js +3 -3
- package/dist/commands/action/index.js +2 -2
- package/dist/commands/action/update.js +3 -3
- package/dist/commands/agent/auth.js +1 -1
- package/dist/commands/agent/cleanup.js +6 -6
- package/dist/commands/agent/discover.js +1 -1
- package/dist/commands/agent/remove.js +4 -4
- package/dist/commands/autocomplete/setup.d.ts +2 -2
- package/dist/commands/autocomplete/setup.js +5 -5
- package/dist/commands/branch/create.js +31 -30
- package/dist/commands/category/create.js +4 -5
- package/dist/commands/category/delete.js +2 -3
- package/dist/commands/category/rename.js +2 -3
- package/dist/commands/claude.d.ts +2 -8
- package/dist/commands/claude.js +26 -26
- package/dist/commands/commit.d.ts +2 -8
- package/dist/commands/commit.js +4 -26
- package/dist/commands/config/index.d.ts +2 -10
- package/dist/commands/config/index.js +8 -34
- package/dist/commands/docker/index.d.ts +2 -2
- package/dist/commands/docker/index.js +8 -8
- package/dist/commands/epic/activate.js +9 -17
- package/dist/commands/epic/archive.js +13 -24
- package/dist/commands/epic/create.js +7 -6
- package/dist/commands/epic/delete.js +4 -5
- package/dist/commands/epic/move.js +28 -47
- package/dist/commands/epic/progress.js +10 -14
- package/dist/commands/epic/project.js +42 -59
- package/dist/commands/epic/reorder.js +25 -30
- package/dist/commands/epic/spec.d.ts +1 -0
- package/dist/commands/epic/spec.js +39 -40
- package/dist/commands/epic/ticket.d.ts +2 -0
- package/dist/commands/epic/ticket.js +63 -37
- package/dist/commands/feedback/index.d.ts +10 -0
- package/dist/commands/feedback/index.js +60 -0
- package/dist/commands/feedback/list.d.ts +12 -0
- package/dist/commands/feedback/list.js +126 -0
- package/dist/commands/feedback/submit.d.ts +16 -0
- package/dist/commands/feedback/submit.js +220 -0
- package/dist/commands/feedback/view.d.ts +15 -0
- package/dist/commands/feedback/view.js +109 -0
- package/dist/commands/gh/index.js +4 -0
- package/dist/commands/link/index.js +2 -2
- package/dist/commands/pmo/init.d.ts +2 -2
- package/dist/commands/pmo/init.js +7 -7
- package/dist/commands/project/spec.js +6 -6
- package/dist/commands/repo/create.d.ts +38 -0
- package/dist/commands/repo/create.js +283 -0
- package/dist/commands/repo/index.js +7 -0
- package/dist/commands/roadmap/add-project.js +9 -22
- package/dist/commands/roadmap/create.d.ts +0 -1
- package/dist/commands/roadmap/create.js +46 -40
- package/dist/commands/roadmap/delete.js +10 -24
- package/dist/commands/roadmap/generate.d.ts +1 -0
- package/dist/commands/roadmap/generate.js +21 -22
- package/dist/commands/roadmap/remove-project.js +14 -34
- package/dist/commands/roadmap/reorder.js +19 -26
- package/dist/commands/roadmap/update.js +27 -26
- package/dist/commands/roadmap/view.js +5 -12
- package/dist/commands/session/attach.d.ts +1 -8
- package/dist/commands/session/attach.js +93 -59
- package/dist/commands/session/health.d.ts +29 -0
- package/dist/commands/session/health.js +495 -0
- package/dist/commands/session/index.js +4 -0
- package/dist/commands/session/list.d.ts +0 -8
- package/dist/commands/session/list.js +130 -81
- package/dist/commands/spec/create.js +1 -1
- package/dist/commands/spec/edit.js +64 -35
- package/dist/commands/staff/add.d.ts +2 -2
- package/dist/commands/staff/add.js +15 -14
- package/dist/commands/staff/index.js +2 -2
- package/dist/commands/staff/remove.js +4 -4
- package/dist/commands/status/index.js +6 -7
- package/dist/commands/support/book.d.ts +10 -0
- package/dist/commands/support/book.js +54 -0
- package/dist/commands/support/discord.d.ts +10 -0
- package/dist/commands/support/discord.js +54 -0
- package/dist/commands/support/docs.d.ts +10 -0
- package/dist/commands/support/docs.js +54 -0
- package/dist/commands/support/index.d.ts +19 -0
- package/dist/commands/support/index.js +81 -0
- package/dist/commands/support/issues.d.ts +11 -0
- package/dist/commands/support/issues.js +77 -0
- package/dist/commands/support/logs.d.ts +18 -0
- package/dist/commands/support/logs.js +247 -0
- package/dist/commands/template/apply.js +10 -11
- package/dist/commands/template/create.js +18 -17
- package/dist/commands/template/index.d.ts +2 -2
- package/dist/commands/template/index.js +6 -6
- package/dist/commands/template/save.js +8 -7
- package/dist/commands/template/update.js +6 -7
- package/dist/commands/terminal/title.d.ts +2 -26
- package/dist/commands/terminal/title.js +4 -33
- package/dist/commands/theme/index.d.ts +2 -2
- package/dist/commands/theme/index.js +19 -18
- package/dist/commands/theme/set.d.ts +2 -2
- package/dist/commands/theme/set.js +5 -5
- package/dist/commands/ticket/create.js +52 -26
- package/dist/commands/ticket/delete.js +15 -13
- package/dist/commands/ticket/edit.js +59 -20
- package/dist/commands/ticket/epic.js +12 -10
- package/dist/commands/ticket/move.d.ts +7 -0
- package/dist/commands/ticket/move.js +132 -0
- package/dist/commands/ticket/project.js +11 -9
- package/dist/commands/ticket/reassign.js +23 -19
- package/dist/commands/ticket/spec.js +7 -5
- package/dist/commands/ticket/update.js +55 -53
- package/dist/commands/whoami.js +1 -0
- package/dist/commands/work/ready.js +7 -7
- package/dist/commands/work/revise.js +13 -11
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +225 -64
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +301 -173
- package/dist/hooks/init.js +4 -0
- package/dist/lib/execution/runners.js +21 -17
- package/dist/lib/execution/session-utils.d.ts +60 -0
- package/dist/lib/execution/session-utils.js +162 -0
- package/dist/lib/execution/spawner.d.ts +2 -0
- package/dist/lib/execution/spawner.js +42 -0
- package/dist/lib/flags/resolver.d.ts +2 -2
- package/dist/lib/flags/resolver.js +15 -0
- package/dist/lib/init/index.js +18 -0
- package/dist/lib/multiline-input.d.ts +63 -0
- package/dist/lib/multiline-input.js +360 -0
- package/dist/lib/pr/index.d.ts +4 -0
- package/dist/lib/pr/index.js +32 -14
- package/dist/lib/prompt-command.d.ts +3 -0
- package/dist/lib/prompt-json.d.ts +77 -6
- package/dist/lib/prompt-json.js +46 -0
- package/dist/lib/repos/git.d.ts +7 -0
- package/dist/lib/repos/git.js +20 -0
- package/oclif.manifest.json +2913 -2246
- package/package.json +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { autoExportToBoard, PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
|
+
// Note: inquirer import kept for inquirer.Separator usage in interactive mode
|
|
4
5
|
import { styles } from '../../lib/styles.js';
|
|
5
6
|
import { updateEpicTicketsSection } from '../../lib/pmo/epic-files.js';
|
|
6
7
|
import { PRIORITIES, PRIORITY_LABELS } from '../../lib/pmo/types.js';
|
|
7
8
|
import { shouldOutputJson, outputErrorAsJson, outputDryRunSuccessAsJson, outputDryRunErrorsAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
8
9
|
import { FlagResolver } from '../../lib/flags/index.js';
|
|
10
|
+
import { multiLineInput } from '../../lib/multiline-input.js';
|
|
9
11
|
export default class TicketCreate extends PMOCommand {
|
|
10
12
|
static description = 'Create a new ticket on the PMO board';
|
|
11
13
|
static examples = [
|
|
@@ -289,7 +291,7 @@ export default class TicketCreate extends PMOCommand {
|
|
|
289
291
|
if (!template && !flags.template) {
|
|
290
292
|
const templates = await storage.listTicketTemplates();
|
|
291
293
|
if (templates.length > 0) {
|
|
292
|
-
const { selectedTemplate } = await
|
|
294
|
+
const { selectedTemplate } = await this.prompt([
|
|
293
295
|
{
|
|
294
296
|
type: 'list',
|
|
295
297
|
name: 'selectedTemplate',
|
|
@@ -303,13 +305,14 @@ export default class TicketCreate extends PMOCommand {
|
|
|
303
305
|
})),
|
|
304
306
|
],
|
|
305
307
|
},
|
|
306
|
-
]);
|
|
308
|
+
], null);
|
|
307
309
|
if (selectedTemplate) {
|
|
308
310
|
template = templates.find(t => t.id === selectedTemplate) || null;
|
|
309
311
|
}
|
|
310
312
|
}
|
|
311
313
|
}
|
|
312
|
-
|
|
314
|
+
// Prompt for title
|
|
315
|
+
const { title: answerTitle } = await this.prompt([
|
|
313
316
|
{
|
|
314
317
|
type: 'input',
|
|
315
318
|
name: 'title',
|
|
@@ -317,13 +320,19 @@ export default class TicketCreate extends PMOCommand {
|
|
|
317
320
|
default: flags.title || template?.titlePattern,
|
|
318
321
|
validate: (input) => input.trim() ? true : 'Title cannot be empty',
|
|
319
322
|
},
|
|
323
|
+
], null);
|
|
324
|
+
// Prompt for column
|
|
325
|
+
const { column: answerColumn } = await this.prompt([
|
|
320
326
|
{
|
|
321
327
|
type: 'list',
|
|
322
328
|
name: 'column',
|
|
323
329
|
message: 'Column:',
|
|
324
|
-
choices: columns,
|
|
330
|
+
choices: columns.map(c => ({ name: c, value: c })),
|
|
325
331
|
default: flags.column || columns[0],
|
|
326
332
|
},
|
|
333
|
+
], null);
|
|
334
|
+
// Prompt for priority
|
|
335
|
+
const { priority: answerPriority } = await this.prompt([
|
|
327
336
|
{
|
|
328
337
|
type: 'list',
|
|
329
338
|
name: 'priority',
|
|
@@ -334,6 +343,9 @@ export default class TicketCreate extends PMOCommand {
|
|
|
334
343
|
],
|
|
335
344
|
default: flags.priority || template?.defaultPriority,
|
|
336
345
|
},
|
|
346
|
+
], null);
|
|
347
|
+
// Prompt for category
|
|
348
|
+
const { categoryChoice } = await this.prompt([
|
|
337
349
|
{
|
|
338
350
|
type: 'list',
|
|
339
351
|
name: 'categoryChoice',
|
|
@@ -365,14 +377,19 @@ export default class TicketCreate extends PMOCommand {
|
|
|
365
377
|
],
|
|
366
378
|
default: flags.category || template?.defaultCategory || '',
|
|
367
379
|
},
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
380
|
+
], null);
|
|
381
|
+
// Custom category prompt if needed
|
|
382
|
+
let customCategory;
|
|
383
|
+
if (categoryChoice === '__custom__') {
|
|
384
|
+
const result = await this.prompt([{
|
|
385
|
+
type: 'input',
|
|
386
|
+
name: 'customCategory',
|
|
387
|
+
message: 'Enter custom category:',
|
|
388
|
+
validate: (input) => input.trim() ? true : 'Category cannot be empty',
|
|
389
|
+
}], null);
|
|
390
|
+
customCategory = result.customCategory;
|
|
391
|
+
}
|
|
392
|
+
const answers = { title: answerTitle, column: answerColumn, priority: answerPriority, categoryChoice, customCategory };
|
|
376
393
|
// Resolve category from choice or custom input
|
|
377
394
|
const category = answers.categoryChoice === '__custom__'
|
|
378
395
|
? answers.customCategory
|
|
@@ -400,37 +417,46 @@ export default class TicketCreate extends PMOCommand {
|
|
|
400
417
|
return existingDescription;
|
|
401
418
|
}
|
|
402
419
|
this.log(styles.muted('\n─── Ticket Description (for agent execution) ───'));
|
|
403
|
-
|
|
420
|
+
// Prompt for "What" - the main outcome
|
|
421
|
+
const { what } = await this.prompt([
|
|
404
422
|
{
|
|
405
423
|
type: 'input',
|
|
406
424
|
name: 'what',
|
|
407
425
|
message: 'What is the concrete outcome? (one sentence):',
|
|
408
426
|
validate: (input) => input.trim() ? true : 'Outcome cannot be empty - what does success look like?',
|
|
409
427
|
},
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
428
|
+
], null);
|
|
429
|
+
// Prompt for acceptance criteria using multiline input
|
|
430
|
+
const doneWhenResult = await multiLineInput({
|
|
431
|
+
message: 'Done when (acceptance criteria):',
|
|
432
|
+
hint: 'Enter each criterion on a new line. Ctrl+D to finish, Ctrl+C to cancel',
|
|
433
|
+
});
|
|
434
|
+
if (doneWhenResult.cancelled) {
|
|
435
|
+
throw new Error('Ticket creation cancelled');
|
|
436
|
+
}
|
|
437
|
+
// Continue with remaining prompts
|
|
438
|
+
const { context } = await this.prompt([
|
|
415
439
|
{
|
|
416
440
|
type: 'input',
|
|
417
441
|
name: 'context',
|
|
418
442
|
message: 'Context (files, patterns, hints - optional):',
|
|
419
443
|
default: '',
|
|
420
444
|
},
|
|
445
|
+
], null);
|
|
446
|
+
const { notInScope } = await this.prompt([
|
|
421
447
|
{
|
|
422
448
|
type: 'input',
|
|
423
449
|
name: 'notInScope',
|
|
424
450
|
message: 'Not in scope (explicit exclusions - optional):',
|
|
425
451
|
default: '',
|
|
426
452
|
},
|
|
427
|
-
]);
|
|
453
|
+
], null);
|
|
428
454
|
// Build structured description
|
|
429
455
|
const parts = [];
|
|
430
|
-
parts.push(`## What\n${
|
|
431
|
-
if (
|
|
456
|
+
parts.push(`## What\n${what}`);
|
|
457
|
+
if (doneWhenResult.value.trim()) {
|
|
432
458
|
// Ensure each line in doneWhen starts with - [ ] if it doesn't already
|
|
433
|
-
const criteria =
|
|
459
|
+
const criteria = doneWhenResult.value
|
|
434
460
|
.split('\n')
|
|
435
461
|
.map(line => line.trim())
|
|
436
462
|
.filter(line => line.length > 0)
|
|
@@ -446,11 +472,11 @@ export default class TicketCreate extends PMOCommand {
|
|
|
446
472
|
.join('\n');
|
|
447
473
|
parts.push(`## Done when\n${criteria}`);
|
|
448
474
|
}
|
|
449
|
-
if (
|
|
450
|
-
parts.push(`## Context\n${
|
|
475
|
+
if (context.trim()) {
|
|
476
|
+
parts.push(`## Context\n${context}`);
|
|
451
477
|
}
|
|
452
|
-
if (
|
|
453
|
-
parts.push(`## Not in scope\n${
|
|
478
|
+
if (notInScope.trim()) {
|
|
479
|
+
parts.push(`## Not in scope\n${notInScope}`);
|
|
454
480
|
}
|
|
455
481
|
return parts.join('\n\n');
|
|
456
482
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { autoExportToBoard, PMOCommand, pmoBaseFlags, } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
4
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -57,7 +56,7 @@ export default class TicketDelete extends PMOCommand {
|
|
|
57
56
|
}
|
|
58
57
|
// Bulk mode
|
|
59
58
|
if (flags.bulk) {
|
|
60
|
-
await this.executeBulk(allTickets, flags.force);
|
|
59
|
+
await this.executeBulk(allTickets, flags.force, flags);
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
// Single ticket mode
|
|
@@ -90,16 +89,17 @@ export default class TicketDelete extends PMOCommand {
|
|
|
90
89
|
this.log(` Title: ${ticket.title}`);
|
|
91
90
|
this.log(` Project: ${board.name}`);
|
|
92
91
|
this.log(` Status: ${ticket.statusName}`);
|
|
93
|
-
const {
|
|
92
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'ticket delete' } : null;
|
|
93
|
+
const { confirmed } = await this.prompt([{
|
|
94
94
|
type: 'list',
|
|
95
95
|
name: 'confirmed',
|
|
96
96
|
message: 'Are you sure?',
|
|
97
97
|
choices: [
|
|
98
|
-
{ name: 'No, cancel', value: false },
|
|
99
|
-
{ name: 'Yes, delete', value: true },
|
|
98
|
+
{ name: 'No, cancel', value: false, command: '' },
|
|
99
|
+
{ name: 'Yes, delete', value: true, command: `prlt ticket delete ${ticketId} --force --json` },
|
|
100
100
|
],
|
|
101
101
|
default: 0,
|
|
102
|
-
}]);
|
|
102
|
+
}], jsonModeConfig);
|
|
103
103
|
if (!confirmed) {
|
|
104
104
|
this.log(styles.warning('Deletion cancelled.'));
|
|
105
105
|
return;
|
|
@@ -112,10 +112,12 @@ export default class TicketDelete extends PMOCommand {
|
|
|
112
112
|
this.log(styles.success(`\n✅ Ticket ${styles.emphasis(ticketId)} deleted`));
|
|
113
113
|
this.log(styles.muted(' Removed from database and board'));
|
|
114
114
|
}
|
|
115
|
-
async executeBulk(allTickets, force) {
|
|
115
|
+
async executeBulk(allTickets, force, flags) {
|
|
116
|
+
const jsonMode = flags ? shouldOutputJson(flags) : false;
|
|
117
|
+
const jsonModeConfig = jsonMode ? { flags: flags, commandName: 'ticket delete' } : null;
|
|
116
118
|
this.log(styles.emphasis('🗑️ Delete Multiple Tickets\n'));
|
|
117
119
|
// Select tickets to delete
|
|
118
|
-
const { selectedTickets } = await
|
|
120
|
+
const { selectedTickets } = await this.prompt([{
|
|
119
121
|
type: 'checkbox',
|
|
120
122
|
name: 'selectedTickets',
|
|
121
123
|
message: 'Select tickets to DELETE:',
|
|
@@ -123,7 +125,7 @@ export default class TicketDelete extends PMOCommand {
|
|
|
123
125
|
name: `${t.id} - ${t.title} (${t.statusName})`,
|
|
124
126
|
value: t.id,
|
|
125
127
|
})),
|
|
126
|
-
}]);
|
|
128
|
+
}], jsonModeConfig);
|
|
127
129
|
if (selectedTickets.length === 0) {
|
|
128
130
|
this.log(styles.muted('No tickets selected.'));
|
|
129
131
|
return;
|
|
@@ -136,16 +138,16 @@ export default class TicketDelete extends PMOCommand {
|
|
|
136
138
|
this.log(styles.primary(` • ${ticketId}: ${ticket?.title}`));
|
|
137
139
|
}
|
|
138
140
|
this.log('');
|
|
139
|
-
const { confirm } = await
|
|
141
|
+
const { confirm } = await this.prompt([{
|
|
140
142
|
type: 'list',
|
|
141
143
|
name: 'confirm',
|
|
142
144
|
message: 'Are you sure? This cannot be undone.',
|
|
143
145
|
choices: [
|
|
144
|
-
{ name: 'No, cancel', value: false },
|
|
145
|
-
{ name: 'Yes, DELETE tickets', value: true }
|
|
146
|
+
{ name: 'No, cancel', value: false, command: '' },
|
|
147
|
+
{ name: 'Yes, DELETE tickets', value: true, command: 'prlt ticket delete --bulk --force --json' }
|
|
146
148
|
],
|
|
147
149
|
default: 0
|
|
148
|
-
}]);
|
|
150
|
+
}], jsonModeConfig);
|
|
149
151
|
if (!confirm) {
|
|
150
152
|
this.log(styles.muted('Deletion cancelled.'));
|
|
151
153
|
return;
|
|
@@ -4,6 +4,7 @@ import { autoExportToBoard, PMOCommand, pmoBaseFlags } from '../../lib/pmo/index
|
|
|
4
4
|
import { PRIORITIES, PRIORITY_LABELS } from '../../lib/pmo/types.js';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
7
|
+
import { multiLineInput } from '../../lib/multiline-input.js';
|
|
7
8
|
export default class TicketEdit extends PMOCommand {
|
|
8
9
|
static description = 'Edit an existing ticket';
|
|
9
10
|
static examples = [
|
|
@@ -128,6 +129,29 @@ export default class TicketEdit extends PMOCommand {
|
|
|
128
129
|
flags.owner || flags.assignee || flags['add-subtask'] || flags['clear-subtasks'] ||
|
|
129
130
|
flags['add-label'] || flags['remove-label'] || flags['add-ac'] || flags['clear-ac'];
|
|
130
131
|
if (flags.interactive || !hasFlags) {
|
|
132
|
+
// In JSON mode without flags, output a form prompt instead of interactive prompts
|
|
133
|
+
if (jsonMode) {
|
|
134
|
+
const { outputPromptAsJson, buildFormPromptConfig } = await import('../../lib/prompt-json.js');
|
|
135
|
+
const formConfig = buildFormPromptConfig([
|
|
136
|
+
{ type: 'input', name: 'title', message: 'Title:', default: ticket.title },
|
|
137
|
+
{ type: 'multiline', name: 'description', message: 'Description:', default: ticket.description || '' },
|
|
138
|
+
{ type: 'list', name: 'priority', message: 'Priority:', choices: [
|
|
139
|
+
{ name: 'None', value: '' },
|
|
140
|
+
{ name: 'P0 - Critical', value: 'P0' },
|
|
141
|
+
{ name: 'P1 - High', value: 'P1' },
|
|
142
|
+
{ name: 'P2 - Medium', value: 'P2' },
|
|
143
|
+
{ name: 'P3 - Low', value: 'P3' },
|
|
144
|
+
], default: ticket.priority || '' },
|
|
145
|
+
{ type: 'input', name: 'category', message: 'Category:', default: ticket.category || '' },
|
|
146
|
+
]);
|
|
147
|
+
formConfig.context = {
|
|
148
|
+
hint: `Edit ticket with: prlt ticket edit ${ticketId} --title "..." --description "..." --priority P0 --json`,
|
|
149
|
+
ticketId,
|
|
150
|
+
currentValues: { title: ticket.title, description: ticket.description, priority: ticket.priority, category: ticket.category },
|
|
151
|
+
};
|
|
152
|
+
outputPromptAsJson(formConfig, createMetadata('ticket edit', flags));
|
|
153
|
+
return; // outputPromptAsJson exits, but TypeScript doesn't know
|
|
154
|
+
}
|
|
131
155
|
// Interactive mode - prompt for all editable fields
|
|
132
156
|
const board = await this.storage.getBoard(ticket.projectId);
|
|
133
157
|
const columns = board.columns.map(col => col.name);
|
|
@@ -244,7 +268,8 @@ export default class TicketEdit extends PMOCommand {
|
|
|
244
268
|
this.log('');
|
|
245
269
|
}
|
|
246
270
|
async promptForEdits(ticket, _columns) {
|
|
247
|
-
|
|
271
|
+
// First prompt for title
|
|
272
|
+
const { title } = await this.prompt([
|
|
248
273
|
{
|
|
249
274
|
type: 'input',
|
|
250
275
|
name: 'title',
|
|
@@ -252,13 +277,18 @@ export default class TicketEdit extends PMOCommand {
|
|
|
252
277
|
default: ticket.title,
|
|
253
278
|
validate: (input) => input.trim() ? true : 'Title cannot be empty',
|
|
254
279
|
},
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
280
|
+
], null);
|
|
281
|
+
// Prompt for description using inline multiline input
|
|
282
|
+
const descResult = await multiLineInput({
|
|
283
|
+
message: 'Description:',
|
|
284
|
+
default: ticket.description || '',
|
|
285
|
+
hint: 'Ctrl+D to finish, Ctrl+C to cancel',
|
|
286
|
+
});
|
|
287
|
+
if (descResult.cancelled) {
|
|
288
|
+
throw new Error('Edit cancelled');
|
|
289
|
+
}
|
|
290
|
+
// Continue with remaining prompts - priority first
|
|
291
|
+
const { priority } = await this.prompt([
|
|
262
292
|
{
|
|
263
293
|
type: 'list',
|
|
264
294
|
name: 'priority',
|
|
@@ -269,6 +299,9 @@ export default class TicketEdit extends PMOCommand {
|
|
|
269
299
|
],
|
|
270
300
|
default: ticket.priority || '',
|
|
271
301
|
},
|
|
302
|
+
], null);
|
|
303
|
+
// Then category
|
|
304
|
+
const { categoryChoice } = await this.prompt([
|
|
272
305
|
{
|
|
273
306
|
type: 'list',
|
|
274
307
|
name: 'categoryChoice',
|
|
@@ -300,21 +333,27 @@ export default class TicketEdit extends PMOCommand {
|
|
|
300
333
|
],
|
|
301
334
|
default: ticket.category || '',
|
|
302
335
|
},
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
336
|
+
], null);
|
|
337
|
+
// Custom category prompt if needed
|
|
338
|
+
let customCategory;
|
|
339
|
+
if (categoryChoice === '__custom__') {
|
|
340
|
+
const result = await this.prompt([{
|
|
341
|
+
type: 'input',
|
|
342
|
+
name: 'customCategory',
|
|
343
|
+
message: 'Enter custom category:',
|
|
344
|
+
validate: (input) => input.trim() ? true : 'Category cannot be empty',
|
|
345
|
+
}], null);
|
|
346
|
+
customCategory = result.customCategory;
|
|
347
|
+
}
|
|
348
|
+
const answers = { priority, categoryChoice, customCategory };
|
|
311
349
|
// Build updates object with only changed fields
|
|
312
350
|
const updates = {};
|
|
313
|
-
if (
|
|
314
|
-
updates.title =
|
|
351
|
+
if (title !== ticket.title) {
|
|
352
|
+
updates.title = title;
|
|
315
353
|
}
|
|
316
|
-
if (
|
|
317
|
-
|
|
354
|
+
if (descResult.value !== (ticket.description || '')) {
|
|
355
|
+
// Preserve empty string to allow clearing the description
|
|
356
|
+
updates.description = descResult.value;
|
|
318
357
|
}
|
|
319
358
|
if (answers.priority !== (ticket.priority || '')) {
|
|
320
359
|
updates.priority = answers.priority || undefined;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
4
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -198,6 +197,8 @@ export default class TicketEpic extends PMOCommand {
|
|
|
198
197
|
this.log(styles.muted(` Epic: ${epic.title}`));
|
|
199
198
|
}
|
|
200
199
|
async executeBulk(flags) {
|
|
200
|
+
const jsonMode = shouldOutputJson(flags);
|
|
201
|
+
const jsonModeConfig = jsonMode ? { flags: flags, commandName: 'ticket epic' } : null;
|
|
201
202
|
this.log(styles.emphasis('🔗 Link Tickets to Epic\n'));
|
|
202
203
|
// Get project first
|
|
203
204
|
const projectId = await this.requireProject();
|
|
@@ -238,7 +239,7 @@ export default class TicketEpic extends PMOCommand {
|
|
|
238
239
|
ticketEpics.set(ticket.id, row?.epic_id || null);
|
|
239
240
|
}
|
|
240
241
|
// Select tickets to link
|
|
241
|
-
const { selectedTickets } = await
|
|
242
|
+
const { selectedTickets } = await this.prompt([{
|
|
242
243
|
type: 'checkbox',
|
|
243
244
|
name: 'selectedTickets',
|
|
244
245
|
message: 'Select tickets to link:',
|
|
@@ -250,7 +251,7 @@ export default class TicketEpic extends PMOCommand {
|
|
|
250
251
|
value: t.id,
|
|
251
252
|
};
|
|
252
253
|
}),
|
|
253
|
-
}]);
|
|
254
|
+
}], jsonModeConfig);
|
|
254
255
|
if (selectedTickets.length === 0) {
|
|
255
256
|
this.log(styles.muted('No tickets selected.'));
|
|
256
257
|
return;
|
|
@@ -258,18 +259,19 @@ export default class TicketEpic extends PMOCommand {
|
|
|
258
259
|
// Select target epic
|
|
259
260
|
let targetEpic = flags['to-epic'];
|
|
260
261
|
if (targetEpic === undefined) {
|
|
261
|
-
const { epic } = await
|
|
262
|
+
const { epic } = await this.prompt([{
|
|
262
263
|
type: 'list',
|
|
263
264
|
name: 'epic',
|
|
264
265
|
message: 'Link to which epic?',
|
|
265
266
|
choices: [
|
|
266
|
-
{ name: 'None (remove epic link)', value: null },
|
|
267
|
+
{ name: 'None (remove epic link)', value: null, command: 'prlt ticket epic --bulk --unlink --json' },
|
|
267
268
|
...epics.map(e => ({
|
|
268
269
|
name: `${e.title} (${e.status})`,
|
|
269
270
|
value: e.id,
|
|
271
|
+
command: `prlt ticket epic --bulk --to-epic ${e.id} --json`,
|
|
270
272
|
})),
|
|
271
273
|
],
|
|
272
|
-
}]);
|
|
274
|
+
}], jsonModeConfig);
|
|
273
275
|
targetEpic = epic;
|
|
274
276
|
}
|
|
275
277
|
// Confirmation
|
|
@@ -285,16 +287,16 @@ export default class TicketEpic extends PMOCommand {
|
|
|
285
287
|
this.log(styles.primary(` • ${ticketId}: ${ticket?.title}`));
|
|
286
288
|
}
|
|
287
289
|
this.log('');
|
|
288
|
-
const { confirm } = await
|
|
290
|
+
const { confirm } = await this.prompt([{
|
|
289
291
|
type: 'list',
|
|
290
292
|
name: 'confirm',
|
|
291
293
|
message: 'Continue?',
|
|
292
294
|
choices: [
|
|
293
|
-
{ name: 'No, cancel', value: false },
|
|
294
|
-
{ name: 'Yes, link tickets', value: true }
|
|
295
|
+
{ name: 'No, cancel', value: false, command: '' },
|
|
296
|
+
{ name: 'Yes, link tickets', value: true, command: `prlt ticket epic --bulk --to-epic ${targetEpic || 'none'} --force --json` }
|
|
295
297
|
],
|
|
296
298
|
default: 0
|
|
297
|
-
}]);
|
|
299
|
+
}], jsonModeConfig);
|
|
298
300
|
if (!confirm) {
|
|
299
301
|
this.log(styles.muted('Operation cancelled.'));
|
|
300
302
|
return;
|
|
@@ -9,10 +9,17 @@ export default class TicketMove extends PMOCommand {
|
|
|
9
9
|
static flags: {
|
|
10
10
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
position: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'to-project': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
bulk: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
16
|
};
|
|
16
17
|
execute(): Promise<void>;
|
|
17
18
|
private executeBulk;
|
|
19
|
+
/**
|
|
20
|
+
* Move a ticket to a different project.
|
|
21
|
+
* If a target column is specified and exists in the target project, move to that column.
|
|
22
|
+
* Otherwise, use the default/backlog column.
|
|
23
|
+
*/
|
|
24
|
+
private executeCrossProjectMove;
|
|
18
25
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { autoExportToBoard, PMOCommand, pmoBaseFlags, } from '../../lib/pmo/index.js';
|
|
3
|
+
import { PMOError } from '../../lib/pmo/types.js';
|
|
3
4
|
import { styles } from '../../lib/styles.js';
|
|
4
5
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
5
6
|
export default class TicketMove extends PMOCommand {
|
|
@@ -8,6 +9,7 @@ export default class TicketMove extends PMOCommand {
|
|
|
8
9
|
'<%= config.bin %> <%= command.id %> my-ticket "In Progress"',
|
|
9
10
|
'<%= config.bin %> <%= command.id %> implement-auth Done',
|
|
10
11
|
'<%= config.bin %> <%= command.id %> fix-bug "In Review" --position 0',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> TKT-123 --to-project PROJ-002',
|
|
11
13
|
'<%= config.bin %> <%= command.id %> --bulk',
|
|
12
14
|
];
|
|
13
15
|
static args = {
|
|
@@ -31,6 +33,9 @@ export default class TicketMove extends PMOCommand {
|
|
|
31
33
|
position: Flags.integer({
|
|
32
34
|
description: 'Position within the column (0 = top)',
|
|
33
35
|
}),
|
|
36
|
+
'to-project': Flags.string({
|
|
37
|
+
description: 'Move ticket to a different project (uses Backlog/default column)',
|
|
38
|
+
}),
|
|
34
39
|
bulk: Flags.boolean({
|
|
35
40
|
char: 'b',
|
|
36
41
|
description: 'Enable bulk mode to move multiple tickets',
|
|
@@ -54,6 +59,16 @@ export default class TicketMove extends PMOCommand {
|
|
|
54
59
|
}
|
|
55
60
|
this.error(message);
|
|
56
61
|
};
|
|
62
|
+
// Cross-project move: if ticketId and --to-project are provided, skip project context
|
|
63
|
+
// The source project is determined from the ticket itself
|
|
64
|
+
if (args.ticketId && flags['to-project']) {
|
|
65
|
+
const ticket = await this.storage.getTicket(args.ticketId);
|
|
66
|
+
if (!ticket) {
|
|
67
|
+
return handleError('TICKET_NOT_FOUND', `Ticket "${args.ticketId}" not found.`);
|
|
68
|
+
}
|
|
69
|
+
await this.executeCrossProjectMove(ticket, flags['to-project'], args.column, jsonMode, flags);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
57
72
|
// This command requires project context - get projectId (with JSON mode support)
|
|
58
73
|
const projectId = await this.requireProject({
|
|
59
74
|
jsonMode: {
|
|
@@ -94,9 +109,69 @@ export default class TicketMove extends PMOCommand {
|
|
|
94
109
|
if (!ticket) {
|
|
95
110
|
this.error(`Ticket "${ticketId}" not found.`);
|
|
96
111
|
}
|
|
112
|
+
// Cross-project move (when --to-project flag is provided)
|
|
113
|
+
if (flags['to-project']) {
|
|
114
|
+
await this.executeCrossProjectMove(ticket, flags['to-project'], args.column, jsonMode, flags);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
97
117
|
// Get target column - prompt if not provided
|
|
98
118
|
let targetColumn = args.column;
|
|
99
119
|
if (!targetColumn) {
|
|
120
|
+
// Check if there are other projects to move to
|
|
121
|
+
const allProjects = await this.storage.listProjects();
|
|
122
|
+
const otherProjects = allProjects.filter(p => p.id !== projectId);
|
|
123
|
+
// If there are other projects, ask user what type of move they want
|
|
124
|
+
if (otherProjects.length > 0) {
|
|
125
|
+
const moveTypeChoices = [
|
|
126
|
+
{ id: 'column', name: 'Different column (same project)' },
|
|
127
|
+
{ id: 'project', name: 'Different project' },
|
|
128
|
+
];
|
|
129
|
+
const moveType = await this.selectFromList({
|
|
130
|
+
message: 'Move to:',
|
|
131
|
+
items: moveTypeChoices,
|
|
132
|
+
getName: (choice) => choice.name,
|
|
133
|
+
getValue: (choice) => choice.id,
|
|
134
|
+
getCommand: (choice) => choice.id === 'column'
|
|
135
|
+
? `prlt ticket move ${ticketId} -P ${projectId} --json`
|
|
136
|
+
: `prlt ticket project ${ticketId} -P ${projectId} --json`,
|
|
137
|
+
jsonMode: jsonMode ? { flags, commandName: 'ticket move' } : null,
|
|
138
|
+
});
|
|
139
|
+
if (!moveType) {
|
|
140
|
+
return; // Cancelled or JSON mode
|
|
141
|
+
}
|
|
142
|
+
// If user chose different project, handle cross-project move
|
|
143
|
+
if (moveType === 'project') {
|
|
144
|
+
const targetProjectId = await this.selectFromList({
|
|
145
|
+
message: 'Select target project:',
|
|
146
|
+
items: otherProjects,
|
|
147
|
+
getName: (p) => `${p.name} (${p.id})`,
|
|
148
|
+
getValue: (p) => p.id,
|
|
149
|
+
getCommand: (p) => `prlt ticket move ${ticketId} --to-project ${p.id} --json`,
|
|
150
|
+
jsonMode: jsonMode ? { flags, commandName: 'ticket move' } : null,
|
|
151
|
+
});
|
|
152
|
+
if (!targetProjectId) {
|
|
153
|
+
return; // Cancelled or JSON mode
|
|
154
|
+
}
|
|
155
|
+
// Get columns from target project and ask which column to move to
|
|
156
|
+
const targetProjectBoard = await this.storage.getProjectBoard(targetProjectId);
|
|
157
|
+
if (!targetProjectBoard) {
|
|
158
|
+
this.error('Target project not found.');
|
|
159
|
+
}
|
|
160
|
+
const targetColumnName = await this.selectFromList({
|
|
161
|
+
message: 'Move to column:',
|
|
162
|
+
items: targetProjectBoard.columns,
|
|
163
|
+
getName: (col) => col.name,
|
|
164
|
+
getValue: (col) => col.name,
|
|
165
|
+
getCommand: (col) => `prlt ticket move ${ticketId} "${col.name}" --to-project ${targetProjectId} --json`,
|
|
166
|
+
jsonMode: jsonMode ? { flags, commandName: 'ticket move' } : null,
|
|
167
|
+
});
|
|
168
|
+
if (!targetColumnName) {
|
|
169
|
+
return; // Cancelled or JSON mode
|
|
170
|
+
}
|
|
171
|
+
await this.executeCrossProjectMove(ticket, targetProjectId, targetColumnName, jsonMode, flags);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
100
175
|
// Get columns from the database (not config.json) to ensure accuracy
|
|
101
176
|
const project = await this.storage.getProjectBoard(projectId);
|
|
102
177
|
if (!project) {
|
|
@@ -221,4 +296,61 @@ export default class TicketMove extends PMOCommand {
|
|
|
221
296
|
this.log(styles.error(`Failed to move ${failCount} ticket(s)`));
|
|
222
297
|
}
|
|
223
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Move a ticket to a different project.
|
|
301
|
+
* If a target column is specified and exists in the target project, move to that column.
|
|
302
|
+
* Otherwise, use the default/backlog column.
|
|
303
|
+
*/
|
|
304
|
+
async executeCrossProjectMove(ticket, targetProjectId, targetColumn, jsonMode, flags) {
|
|
305
|
+
const ticketId = ticket.id;
|
|
306
|
+
const sourceProjectId = ticket.projectId;
|
|
307
|
+
// Check if target project exists
|
|
308
|
+
const projects = await this.storage.listProjects();
|
|
309
|
+
const targetProject = projects.find(p => p.id === targetProjectId ||
|
|
310
|
+
p.id.toLowerCase() === targetProjectId.toLowerCase() ||
|
|
311
|
+
p.name.toLowerCase() === targetProjectId.toLowerCase());
|
|
312
|
+
if (!targetProject) {
|
|
313
|
+
if (jsonMode) {
|
|
314
|
+
outputErrorAsJson('PROJECT_NOT_FOUND', `Project not found: ${targetProjectId}`, createMetadata('ticket move', flags));
|
|
315
|
+
this.exit(1);
|
|
316
|
+
}
|
|
317
|
+
this.error(`Project not found: ${targetProjectId}`);
|
|
318
|
+
}
|
|
319
|
+
// Check if moving to the same project
|
|
320
|
+
if (targetProject.id === sourceProjectId) {
|
|
321
|
+
this.log(styles.warning(`Ticket "${ticketId}" is already in project "${targetProject.id}".`));
|
|
322
|
+
this.log(styles.muted(`To move to a different column, use: prlt ticket move ${ticketId} <column>`));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
// Move ticket to the new project
|
|
326
|
+
const movedTicket = await this.storage.moveTicketToProject(ticketId, targetProject.id);
|
|
327
|
+
// If a target column was specified, try to move to that column in the new project
|
|
328
|
+
if (targetColumn) {
|
|
329
|
+
try {
|
|
330
|
+
await this.storage.moveTicket(targetProject.id, ticketId, targetColumn);
|
|
331
|
+
// Refresh ticket to get updated status
|
|
332
|
+
const updatedTicket = await this.storage.getTicket(ticketId);
|
|
333
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
334
|
+
this.log(styles.success(`\n✅ Moved ticket ${styles.emphasis(ticketId)} to project ${styles.emphasis(targetProject.id)}`));
|
|
335
|
+
this.log(styles.muted(` From project: ${sourceProjectId}`));
|
|
336
|
+
this.log(styles.muted(` To project: ${targetProject.id}`));
|
|
337
|
+
this.log(styles.muted(` Column: ${updatedTicket?.statusName || targetColumn}`));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
// Only catch "status not found" errors - re-throw unexpected errors
|
|
342
|
+
if (error instanceof PMOError && error.code === 'NOT_FOUND') {
|
|
343
|
+
this.log(styles.muted(`Note: Column "${targetColumn}" not found in target project, using default column.`));
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
351
|
+
this.log(styles.success(`\n✅ Moved ticket ${styles.emphasis(ticketId)} to project ${styles.emphasis(targetProject.id)}`));
|
|
352
|
+
this.log(styles.muted(` From project: ${sourceProjectId}`));
|
|
353
|
+
this.log(styles.muted(` To project: ${targetProject.id}`));
|
|
354
|
+
this.log(styles.muted(` Column: ${movedTicket.statusName || 'default'}`));
|
|
355
|
+
}
|
|
224
356
|
}
|