@proletariat/cli 0.3.19 → 0.3.21
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/agent/login.js +2 -2
- package/dist/commands/agent/remove.d.ts +1 -0
- package/dist/commands/agent/remove.js +36 -28
- package/dist/commands/agent/shell.js +2 -2
- package/dist/commands/agent/staff/remove.d.ts +1 -0
- package/dist/commands/agent/staff/remove.js +36 -28
- package/dist/commands/agent/status.js +2 -2
- package/dist/commands/agent/temp/cleanup.js +10 -17
- package/dist/commands/agent/themes/add-names.d.ts +1 -0
- package/dist/commands/agent/themes/add-names.js +5 -1
- package/dist/commands/agent/visit.js +2 -2
- package/dist/commands/board/view.d.ts +15 -0
- package/dist/commands/board/view.js +136 -0
- package/dist/commands/config/index.js +6 -3
- package/dist/commands/epic/link/index.js +17 -0
- package/dist/commands/execution/config.d.ts +34 -0
- package/dist/commands/execution/config.js +433 -0
- package/dist/commands/execution/index.js +6 -1
- package/dist/commands/execution/kill.d.ts +12 -0
- package/dist/commands/execution/kill.js +17 -0
- package/dist/commands/execution/list.js +5 -4
- package/dist/commands/execution/logs.js +1 -0
- package/dist/commands/execution/view.d.ts +17 -0
- package/dist/commands/execution/view.js +288 -0
- package/dist/commands/phase/move.js +8 -0
- package/dist/commands/phase/template/apply.js +2 -2
- package/dist/commands/phase/template/create.js +67 -20
- package/dist/commands/phase/template/list.js +1 -1
- package/dist/commands/pr/index.js +6 -2
- package/dist/commands/pr/list.d.ts +17 -0
- package/dist/commands/pr/list.js +163 -0
- package/dist/commands/project/update.d.ts +19 -0
- package/dist/commands/project/update.js +163 -0
- package/dist/commands/roadmap/create.js +5 -0
- package/dist/commands/spec/delete.d.ts +18 -0
- package/dist/commands/spec/delete.js +111 -0
- package/dist/commands/spec/edit.d.ts +23 -0
- package/dist/commands/spec/edit.js +232 -0
- package/dist/commands/spec/index.js +5 -0
- package/dist/commands/status/create.js +38 -34
- package/dist/commands/status/list.js +5 -3
- package/dist/commands/template/phase/create.d.ts +1 -0
- package/dist/commands/template/phase/create.js +10 -1
- package/dist/commands/template/phase/index.js +4 -4
- package/dist/commands/template/ticket/create.d.ts +20 -0
- package/dist/commands/template/ticket/create.js +87 -0
- package/dist/commands/template/ticket/delete.d.ts +1 -1
- package/dist/commands/template/ticket/delete.js +4 -2
- package/dist/commands/template/ticket/save.d.ts +2 -0
- package/dist/commands/template/ticket/save.js +11 -0
- package/dist/commands/ticket/create.js +8 -1
- package/dist/commands/ticket/edit.js +1 -1
- package/dist/commands/ticket/list.d.ts +2 -0
- package/dist/commands/ticket/list.js +39 -2
- package/dist/commands/ticket/template/create.d.ts +9 -1
- package/dist/commands/ticket/template/create.js +224 -52
- package/dist/commands/ticket/template/save.d.ts +2 -1
- package/dist/commands/ticket/template/save.js +58 -7
- package/dist/commands/ticket/update.js +2 -2
- package/dist/commands/work/ready.js +8 -8
- package/dist/commands/work/spawn.js +32 -8
- package/dist/commands/work/watch.js +2 -0
- package/dist/lib/agents/commands.d.ts +7 -0
- package/dist/lib/agents/commands.js +11 -0
- package/dist/lib/agents/index.js +14 -4
- package/dist/lib/branch/index.js +24 -0
- package/dist/lib/execution/config.d.ts +2 -0
- package/dist/lib/execution/config.js +12 -0
- package/dist/lib/execution/runners.js +1 -2
- package/dist/lib/pmo/storage/epics.js +20 -10
- package/dist/lib/pmo/storage/helpers.d.ts +10 -0
- package/dist/lib/pmo/storage/helpers.js +59 -1
- package/dist/lib/pmo/storage/projects.js +20 -8
- package/dist/lib/pmo/storage/specs.js +23 -13
- package/dist/lib/pmo/storage/statuses.js +39 -18
- package/dist/lib/pmo/storage/subtasks.js +19 -8
- package/dist/lib/pmo/storage/tickets.js +27 -15
- package/dist/lib/pmo/utils.d.ts +4 -2
- package/dist/lib/pmo/utils.js +4 -2
- package/oclif.manifest.json +4037 -3234
- package/package.json +2 -4
|
@@ -3,12 +3,15 @@ import inquirer from 'inquirer';
|
|
|
3
3
|
import { PMOCommand, pmoBaseFlags } from '../../../lib/pmo/index.js';
|
|
4
4
|
import { PRIORITIES, PRIORITY_LABELS, TICKET_CATEGORIES } from '../../../lib/pmo/types.js';
|
|
5
5
|
import { styles } from '../../../lib/styles.js';
|
|
6
|
+
import { shouldOutputJson, outputSuccessAsJson, outputPromptAsJson, buildFormPromptConfig, createMetadata, } from '../../../lib/prompt-json.js';
|
|
6
7
|
export default class TicketTemplateCreate extends PMOCommand {
|
|
7
8
|
static description = 'Create a new ticket template from scratch';
|
|
8
9
|
static examples = [
|
|
9
10
|
'<%= config.bin %> <%= command.id %> "Bug Report"',
|
|
10
11
|
'<%= config.bin %> <%= command.id %> "Feature Request" -d "Template for new features"',
|
|
11
12
|
'<%= config.bin %> <%= command.id %> "Task" --title-pattern "[TASK] " --priority P2',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> "Onboarding" --subtask "Setup environment" --subtask "Read docs"',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> "Bug" --ac "Bug is fixed" --ac "Tests pass" --category bug',
|
|
12
15
|
];
|
|
13
16
|
static args = {
|
|
14
17
|
name: Args.string({
|
|
@@ -25,6 +28,9 @@ export default class TicketTemplateCreate extends PMOCommand {
|
|
|
25
28
|
'title-pattern': Flags.string({
|
|
26
29
|
description: 'Default title prefix/pattern (e.g., "[BUG] ")',
|
|
27
30
|
}),
|
|
31
|
+
'description-template': Flags.string({
|
|
32
|
+
description: 'Default description template (markdown)',
|
|
33
|
+
}),
|
|
28
34
|
priority: Flags.string({
|
|
29
35
|
char: 'p',
|
|
30
36
|
description: 'Default priority',
|
|
@@ -35,12 +41,141 @@ export default class TicketTemplateCreate extends PMOCommand {
|
|
|
35
41
|
description: 'Default category',
|
|
36
42
|
options: [...TICKET_CATEGORIES],
|
|
37
43
|
}),
|
|
44
|
+
subtask: Flags.string({
|
|
45
|
+
description: 'Add a suggested subtask (can be used multiple times)',
|
|
46
|
+
multiple: true,
|
|
47
|
+
}),
|
|
48
|
+
ac: Flags.string({
|
|
49
|
+
description: 'Add an acceptance criterion pattern (can be used multiple times)',
|
|
50
|
+
multiple: true,
|
|
51
|
+
}),
|
|
52
|
+
label: Flags.string({
|
|
53
|
+
char: 'l',
|
|
54
|
+
description: 'Add a default label (can be used multiple times)',
|
|
55
|
+
multiple: true,
|
|
56
|
+
}),
|
|
57
|
+
json: Flags.boolean({
|
|
58
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
59
|
+
default: false,
|
|
60
|
+
}),
|
|
38
61
|
};
|
|
39
62
|
getPMOOptions() {
|
|
40
63
|
return { promptIfMultiple: false };
|
|
41
64
|
}
|
|
42
65
|
async execute() {
|
|
43
66
|
const { args, flags } = await this.parse(TicketTemplateCreate);
|
|
67
|
+
const jsonMode = shouldOutputJson(flags);
|
|
68
|
+
// Check if we have all required data via flags (non-interactive mode)
|
|
69
|
+
const hasName = Boolean(args.name);
|
|
70
|
+
const hasAllFlags = hasName; // Name is the only required field
|
|
71
|
+
// In JSON mode with missing required data, output form prompt
|
|
72
|
+
if (jsonMode && !hasAllFlags) {
|
|
73
|
+
const fields = [];
|
|
74
|
+
if (!hasName) {
|
|
75
|
+
fields.push({
|
|
76
|
+
type: 'input',
|
|
77
|
+
name: 'name',
|
|
78
|
+
message: 'Template name:',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Add optional fields that weren't provided
|
|
82
|
+
if (flags.description === undefined) {
|
|
83
|
+
fields.push({
|
|
84
|
+
type: 'input',
|
|
85
|
+
name: 'description',
|
|
86
|
+
message: 'Template description (optional):',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (flags['title-pattern'] === undefined) {
|
|
90
|
+
fields.push({
|
|
91
|
+
type: 'input',
|
|
92
|
+
name: 'titlePattern',
|
|
93
|
+
message: 'Title prefix/pattern (optional, e.g., "[BUG] "):',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (flags.priority === undefined) {
|
|
97
|
+
fields.push({
|
|
98
|
+
type: 'list',
|
|
99
|
+
name: 'priority',
|
|
100
|
+
message: 'Default priority:',
|
|
101
|
+
choices: [
|
|
102
|
+
{ name: 'None', value: '' },
|
|
103
|
+
...PRIORITIES.map(p => ({ name: PRIORITY_LABELS[p], value: p })),
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (flags.category === undefined) {
|
|
108
|
+
fields.push({
|
|
109
|
+
type: 'list',
|
|
110
|
+
name: 'category',
|
|
111
|
+
message: 'Default category:',
|
|
112
|
+
choices: [
|
|
113
|
+
{ name: 'None', value: '' },
|
|
114
|
+
...TICKET_CATEGORIES.map(c => ({ name: c, value: c })),
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
outputPromptAsJson(buildFormPromptConfig(fields), createMetadata('ticket template create', flags));
|
|
119
|
+
return; // outputPromptAsJson exits, but TypeScript needs this
|
|
120
|
+
}
|
|
121
|
+
// Non-interactive mode: use flag values directly
|
|
122
|
+
if (hasAllFlags && (jsonMode || this.hasNonDefaultFlags(flags))) {
|
|
123
|
+
const name = args.name;
|
|
124
|
+
const description = flags.description;
|
|
125
|
+
const titlePattern = flags['title-pattern'];
|
|
126
|
+
const priority = flags.priority;
|
|
127
|
+
const category = flags.category;
|
|
128
|
+
const subtasks = flags.subtask || [];
|
|
129
|
+
const acs = flags.ac || [];
|
|
130
|
+
const labels = flags.label || [];
|
|
131
|
+
// Build description template with ACs if provided
|
|
132
|
+
let descriptionTemplate = flags['description-template'];
|
|
133
|
+
if (acs.length > 0 && !descriptionTemplate) {
|
|
134
|
+
// Create a description template with acceptance criteria section
|
|
135
|
+
descriptionTemplate = '## Description\n\n## Acceptance Criteria\n' +
|
|
136
|
+
acs.map(ac => `- [ ] ${ac}`).join('\n') + '\n';
|
|
137
|
+
}
|
|
138
|
+
else if (acs.length > 0 && descriptionTemplate) {
|
|
139
|
+
// Append ACs to existing template if it doesn't have an AC section
|
|
140
|
+
if (!descriptionTemplate.toLowerCase().includes('acceptance criteria')) {
|
|
141
|
+
descriptionTemplate += '\n\n## Acceptance Criteria\n' +
|
|
142
|
+
acs.map(ac => `- [ ] ${ac}`).join('\n') + '\n';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Create the template
|
|
146
|
+
const template = await this.storage.createTicketTemplate({
|
|
147
|
+
name,
|
|
148
|
+
description,
|
|
149
|
+
titlePattern,
|
|
150
|
+
defaultPriority: priority,
|
|
151
|
+
defaultCategory: category,
|
|
152
|
+
descriptionTemplate,
|
|
153
|
+
suggestedSubtasks: subtasks.map(title => ({ title })),
|
|
154
|
+
defaultLabels: labels,
|
|
155
|
+
});
|
|
156
|
+
if (jsonMode) {
|
|
157
|
+
outputSuccessAsJson({
|
|
158
|
+
template: {
|
|
159
|
+
id: template.id,
|
|
160
|
+
name: template.name,
|
|
161
|
+
description: template.description,
|
|
162
|
+
titlePattern: template.titlePattern,
|
|
163
|
+
defaultPriority: template.defaultPriority,
|
|
164
|
+
defaultCategory: template.defaultCategory,
|
|
165
|
+
descriptionTemplate: template.descriptionTemplate,
|
|
166
|
+
suggestedSubtasks: template.suggestedSubtasks,
|
|
167
|
+
defaultLabels: template.defaultLabels,
|
|
168
|
+
},
|
|
169
|
+
}, createMetadata('ticket template create', flags));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.log(styles.success(`\nCreated template "${styles.emphasis(template.name)}"`));
|
|
173
|
+
this.log(styles.muted(` ID: ${template.id}`));
|
|
174
|
+
this.log('');
|
|
175
|
+
this.log(styles.muted(`Create ticket from template: prlt ticket template apply ${template.id}`));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Interactive mode
|
|
44
179
|
// Get template name
|
|
45
180
|
let name = args.name;
|
|
46
181
|
if (!name) {
|
|
@@ -100,61 +235,79 @@ export default class TicketTemplateCreate extends PMOCommand {
|
|
|
100
235
|
}]);
|
|
101
236
|
category = selectedCategory || undefined;
|
|
102
237
|
}
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (wantDescriptionTemplate) {
|
|
115
|
-
const { template } = await inquirer.prompt([{
|
|
116
|
-
type: 'editor',
|
|
117
|
-
name: 'template',
|
|
118
|
-
message: 'Description template (opens editor):',
|
|
119
|
-
default: `## Summary\n\n## Details\n\n## Acceptance Criteria\n- [ ] \n`,
|
|
238
|
+
// Handle description template - use flag value or prompt
|
|
239
|
+
let descriptionTemplate = flags['description-template'];
|
|
240
|
+
if (descriptionTemplate === undefined) {
|
|
241
|
+
const { wantDescriptionTemplate } = await inquirer.prompt([{
|
|
242
|
+
type: 'list',
|
|
243
|
+
name: 'wantDescriptionTemplate',
|
|
244
|
+
message: 'Add a description template?',
|
|
245
|
+
choices: [
|
|
246
|
+
{ name: 'No', value: false },
|
|
247
|
+
{ name: 'Yes', value: true },
|
|
248
|
+
],
|
|
120
249
|
}]);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
name: 'wantSubtasks',
|
|
128
|
-
message: 'Add default subtasks?',
|
|
129
|
-
choices: [
|
|
130
|
-
{ name: 'No', value: false },
|
|
131
|
-
{ name: 'Yes', value: true },
|
|
132
|
-
],
|
|
133
|
-
}]);
|
|
134
|
-
if (wantSubtasks) {
|
|
135
|
-
let addMore = true;
|
|
136
|
-
while (addMore) {
|
|
137
|
-
// eslint-disable-next-line no-await-in-loop -- Interactive loop for subtask creation
|
|
138
|
-
const { subtaskTitle } = await inquirer.prompt([{
|
|
139
|
-
type: 'input',
|
|
140
|
-
name: 'subtaskTitle',
|
|
141
|
-
message: 'Subtask title:',
|
|
142
|
-
validate: (input) => input.length > 0 || 'Title is required',
|
|
143
|
-
}]);
|
|
144
|
-
subtasks.push(subtaskTitle);
|
|
145
|
-
// eslint-disable-next-line no-await-in-loop -- Interactive loop continuation
|
|
146
|
-
const { another } = await inquirer.prompt([{
|
|
147
|
-
type: 'list',
|
|
148
|
-
name: 'another',
|
|
149
|
-
message: 'Add another subtask?',
|
|
150
|
-
choices: [
|
|
151
|
-
{ name: 'No', value: false },
|
|
152
|
-
{ name: 'Yes', value: true },
|
|
153
|
-
],
|
|
250
|
+
if (wantDescriptionTemplate) {
|
|
251
|
+
const { template } = await inquirer.prompt([{
|
|
252
|
+
type: 'editor',
|
|
253
|
+
name: 'template',
|
|
254
|
+
message: 'Description template (opens editor):',
|
|
255
|
+
default: `## Summary\n\n## Details\n\n## Acceptance Criteria\n- [ ] \n`,
|
|
154
256
|
}]);
|
|
155
|
-
|
|
257
|
+
descriptionTemplate = template || undefined;
|
|
156
258
|
}
|
|
157
259
|
}
|
|
260
|
+
// Handle subtasks - use flag values or prompt
|
|
261
|
+
const subtasks = flags.subtask ? [...flags.subtask] : [];
|
|
262
|
+
if (subtasks.length === 0) {
|
|
263
|
+
const { wantSubtasks } = await inquirer.prompt([{
|
|
264
|
+
type: 'list',
|
|
265
|
+
name: 'wantSubtasks',
|
|
266
|
+
message: 'Add default subtasks?',
|
|
267
|
+
choices: [
|
|
268
|
+
{ name: 'No', value: false },
|
|
269
|
+
{ name: 'Yes', value: true },
|
|
270
|
+
],
|
|
271
|
+
}]);
|
|
272
|
+
if (wantSubtasks) {
|
|
273
|
+
let addMore = true;
|
|
274
|
+
while (addMore) {
|
|
275
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive loop for subtask creation
|
|
276
|
+
const { subtaskTitle } = await inquirer.prompt([{
|
|
277
|
+
type: 'input',
|
|
278
|
+
name: 'subtaskTitle',
|
|
279
|
+
message: 'Subtask title:',
|
|
280
|
+
validate: (input) => input.length > 0 || 'Title is required',
|
|
281
|
+
}]);
|
|
282
|
+
subtasks.push(subtaskTitle);
|
|
283
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive loop continuation
|
|
284
|
+
const { another } = await inquirer.prompt([{
|
|
285
|
+
type: 'list',
|
|
286
|
+
name: 'another',
|
|
287
|
+
message: 'Add another subtask?',
|
|
288
|
+
choices: [
|
|
289
|
+
{ name: 'No', value: false },
|
|
290
|
+
{ name: 'Yes', value: true },
|
|
291
|
+
],
|
|
292
|
+
}]);
|
|
293
|
+
addMore = another;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Handle ACs - add to description template if provided via flag
|
|
298
|
+
const acs = flags.ac || [];
|
|
299
|
+
if (acs.length > 0) {
|
|
300
|
+
if (!descriptionTemplate) {
|
|
301
|
+
descriptionTemplate = '## Description\n\n## Acceptance Criteria\n' +
|
|
302
|
+
acs.map(ac => `- [ ] ${ac}`).join('\n') + '\n';
|
|
303
|
+
}
|
|
304
|
+
else if (!descriptionTemplate.toLowerCase().includes('acceptance criteria')) {
|
|
305
|
+
descriptionTemplate += '\n\n## Acceptance Criteria\n' +
|
|
306
|
+
acs.map(ac => `- [ ] ${ac}`).join('\n') + '\n';
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Handle labels
|
|
310
|
+
const labels = flags.label ? [...flags.label] : [];
|
|
158
311
|
// Show preview
|
|
159
312
|
this.log(`\n${styles.emphasis('Template Preview:')}`);
|
|
160
313
|
this.log(styles.muted(` Name: ${name}`));
|
|
@@ -179,6 +332,9 @@ export default class TicketTemplateCreate extends PMOCommand {
|
|
|
179
332
|
this.log(styles.muted(` - ${subtask}`));
|
|
180
333
|
}
|
|
181
334
|
}
|
|
335
|
+
if (labels.length > 0) {
|
|
336
|
+
this.log(styles.muted(` Default labels: ${labels.join(', ')}`));
|
|
337
|
+
}
|
|
182
338
|
// Confirm creation
|
|
183
339
|
const { confirm } = await inquirer.prompt([{
|
|
184
340
|
type: 'list',
|
|
@@ -202,11 +358,27 @@ export default class TicketTemplateCreate extends PMOCommand {
|
|
|
202
358
|
defaultCategory: category,
|
|
203
359
|
descriptionTemplate,
|
|
204
360
|
suggestedSubtasks: subtasks.map(title => ({ title })),
|
|
205
|
-
defaultLabels:
|
|
361
|
+
defaultLabels: labels,
|
|
206
362
|
});
|
|
207
363
|
this.log(styles.success(`\nCreated template "${styles.emphasis(template.name)}"`));
|
|
208
364
|
this.log(styles.muted(` ID: ${template.id}`));
|
|
209
365
|
this.log('');
|
|
210
366
|
this.log(styles.muted(`Create ticket from template: prlt ticket template apply ${template.id}`));
|
|
211
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Check if any non-default flags were provided (indicating non-interactive intent)
|
|
370
|
+
*/
|
|
371
|
+
hasNonDefaultFlags(flags) {
|
|
372
|
+
const nonDefaultFlags = [
|
|
373
|
+
'description',
|
|
374
|
+
'title-pattern',
|
|
375
|
+
'description-template',
|
|
376
|
+
'priority',
|
|
377
|
+
'category',
|
|
378
|
+
'subtask',
|
|
379
|
+
'ac',
|
|
380
|
+
'label',
|
|
381
|
+
];
|
|
382
|
+
return nonDefaultFlags.some(flag => flags[flag] !== undefined);
|
|
383
|
+
}
|
|
212
384
|
}
|
|
@@ -7,9 +7,10 @@ export default class TicketTemplateSave extends PMOCommand {
|
|
|
7
7
|
name: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
8
|
};
|
|
9
9
|
static flags: {
|
|
10
|
+
'template-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
12
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
};
|
|
15
16
|
execute(): Promise<void>;
|
|
@@ -2,6 +2,7 @@ import { Flags, Args } from '@oclif/core';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { PMOCommand, pmoBaseFlags } from '../../../lib/pmo/index.js';
|
|
4
4
|
import { styles } from '../../../lib/styles.js';
|
|
5
|
+
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../../lib/prompt-json.js';
|
|
5
6
|
export default class TicketTemplateSave extends PMOCommand {
|
|
6
7
|
static description = 'Create a template from an existing ticket';
|
|
7
8
|
static examples = [
|
|
@@ -20,20 +21,45 @@ export default class TicketTemplateSave extends PMOCommand {
|
|
|
20
21
|
};
|
|
21
22
|
static flags = {
|
|
22
23
|
...pmoBaseFlags,
|
|
24
|
+
'template-name': Flags.string({
|
|
25
|
+
char: 'n',
|
|
26
|
+
description: 'Template name (alternative to positional arg, required in non-TTY/JSON mode)',
|
|
27
|
+
}),
|
|
23
28
|
description: Flags.string({
|
|
24
29
|
char: 'd',
|
|
25
30
|
description: 'Template description',
|
|
26
31
|
}),
|
|
32
|
+
json: Flags.boolean({
|
|
33
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
34
|
+
default: false,
|
|
35
|
+
}),
|
|
27
36
|
};
|
|
28
37
|
async execute() {
|
|
29
38
|
const { args, flags } = await this.parse(TicketTemplateSave);
|
|
39
|
+
// Check if JSON output mode is active
|
|
40
|
+
const jsonMode = shouldOutputJson(flags);
|
|
41
|
+
// Helper to handle errors in JSON mode
|
|
42
|
+
const handleError = (code, message) => {
|
|
43
|
+
if (jsonMode) {
|
|
44
|
+
outputErrorAsJson(code, message, createMetadata('ticket template save', flags));
|
|
45
|
+
}
|
|
46
|
+
this.error(message);
|
|
47
|
+
};
|
|
30
48
|
// Get ticket ID - prompt with picker if not provided
|
|
31
49
|
let ticketId = args.ticket;
|
|
32
50
|
if (!ticketId) {
|
|
33
51
|
const projectId = await this.requireProject();
|
|
34
52
|
const tickets = await this.storage.listTickets(projectId);
|
|
35
53
|
if (tickets.length === 0) {
|
|
36
|
-
|
|
54
|
+
return handleError('NO_TICKETS', 'No tickets found in this project.\nCreate a ticket first: prlt ticket create');
|
|
55
|
+
}
|
|
56
|
+
// In JSON mode, output prompt config for ticket selection
|
|
57
|
+
if (jsonMode) {
|
|
58
|
+
const choices = tickets.slice(0, 20).map(t => ({
|
|
59
|
+
name: `${t.id} - ${t.title}`,
|
|
60
|
+
value: t.id,
|
|
61
|
+
}));
|
|
62
|
+
outputPromptAsJson(buildPromptConfig('list', 'ticket', 'Select a ticket to save as template:', choices), createMetadata('ticket template save', flags));
|
|
37
63
|
}
|
|
38
64
|
const { selectedTicket } = await inquirer.prompt([{
|
|
39
65
|
type: 'list',
|
|
@@ -49,23 +75,28 @@ export default class TicketTemplateSave extends PMOCommand {
|
|
|
49
75
|
// Verify ticket exists
|
|
50
76
|
const ticket = await this.storage.getTicket(ticketId);
|
|
51
77
|
if (!ticket) {
|
|
52
|
-
|
|
78
|
+
return handleError('TICKET_NOT_FOUND', `Ticket not found: ${ticketId}\nRun 'prlt ticket list' to see available tickets.`);
|
|
53
79
|
}
|
|
54
|
-
// Get template name -
|
|
55
|
-
let templateName = args.name;
|
|
80
|
+
// Get template name - prefer flag over positional arg
|
|
81
|
+
let templateName = flags['template-name'] || args.name;
|
|
56
82
|
if (!templateName) {
|
|
83
|
+
const defaultName = ticket.category || ticket.title.split(' ')[0];
|
|
84
|
+
// In JSON mode, output prompt config for template name
|
|
85
|
+
if (jsonMode) {
|
|
86
|
+
outputPromptAsJson(buildPromptConfig('input', 'name', 'Template name:', undefined, defaultName), createMetadata('ticket template save', flags));
|
|
87
|
+
}
|
|
57
88
|
const { name } = await inquirer.prompt([{
|
|
58
89
|
type: 'input',
|
|
59
90
|
name: 'name',
|
|
60
91
|
message: 'Template name:',
|
|
61
|
-
default:
|
|
92
|
+
default: defaultName,
|
|
62
93
|
validate: (input) => input.length > 0 || 'Name is required',
|
|
63
94
|
}]);
|
|
64
95
|
templateName = name;
|
|
65
96
|
}
|
|
66
|
-
// Get description if not provided
|
|
97
|
+
// Get description if not provided - only prompt interactively (not in JSON mode)
|
|
67
98
|
let description = flags.description;
|
|
68
|
-
if (description === undefined) {
|
|
99
|
+
if (description === undefined && !jsonMode) {
|
|
69
100
|
const { desc } = await inquirer.prompt([{
|
|
70
101
|
type: 'input',
|
|
71
102
|
name: 'desc',
|
|
@@ -75,6 +106,26 @@ export default class TicketTemplateSave extends PMOCommand {
|
|
|
75
106
|
}
|
|
76
107
|
// Create template from ticket
|
|
77
108
|
const template = await this.storage.createTicketTemplateFromTicket(ticketId, templateName, description);
|
|
109
|
+
// In JSON mode, output success as JSON
|
|
110
|
+
if (jsonMode) {
|
|
111
|
+
outputSuccessAsJson({
|
|
112
|
+
template: {
|
|
113
|
+
id: template.id,
|
|
114
|
+
name: template.name,
|
|
115
|
+
description: template.description,
|
|
116
|
+
titlePattern: template.titlePattern,
|
|
117
|
+
defaultPriority: template.defaultPriority,
|
|
118
|
+
defaultCategory: template.defaultCategory,
|
|
119
|
+
defaultStatusId: template.defaultStatusId,
|
|
120
|
+
defaultAssignee: template.defaultAssignee,
|
|
121
|
+
defaultOwner: template.defaultOwner,
|
|
122
|
+
defaultLabels: template.defaultLabels,
|
|
123
|
+
suggestedSubtasks: template.suggestedSubtasks,
|
|
124
|
+
},
|
|
125
|
+
sourceTicketId: ticketId,
|
|
126
|
+
}, createMetadata('ticket template save', flags));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
78
129
|
this.log(styles.success(`\nCreated template "${styles.emphasis(template.name)}" from ticket ${ticketId}`));
|
|
79
130
|
this.log(styles.muted(` ID: ${template.id}`));
|
|
80
131
|
if (template.description) {
|
|
@@ -7,10 +7,10 @@ import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/
|
|
|
7
7
|
export default class TicketUpdate extends PMOCommand {
|
|
8
8
|
static description = 'Update priority/category for ticket(s)';
|
|
9
9
|
static examples = [
|
|
10
|
-
'<%= config.bin %> <%= command.id %> TKT-001 --priority
|
|
10
|
+
'<%= config.bin %> <%= command.id %> TKT-001 --priority P1',
|
|
11
11
|
'<%= config.bin %> <%= command.id %> TKT-001 --category bug',
|
|
12
12
|
'<%= config.bin %> <%= command.id %> --bulk',
|
|
13
|
-
'<%= config.bin %> <%= command.id %> --bulk --priority
|
|
13
|
+
'<%= config.bin %> <%= command.id %> --bulk --priority P1',
|
|
14
14
|
'<%= config.bin %> <%= command.id %> --json # Output choices as JSON',
|
|
15
15
|
];
|
|
16
16
|
static args = {
|
|
@@ -106,18 +106,18 @@ export default class WorkReady extends PMOCommand {
|
|
|
106
106
|
this.error(`Ticket "${ticketId}" not found.`);
|
|
107
107
|
}
|
|
108
108
|
// Get configured column name (from pmo_settings or default)
|
|
109
|
-
//
|
|
110
|
-
const targetColumnName = getWorkColumnSetting(db, '
|
|
109
|
+
// "ready" moves ticket to Review column (work complete moves to Done)
|
|
110
|
+
const targetColumnName = getWorkColumnSetting(db, 'review');
|
|
111
111
|
const board = await this.storage.getBoard(ticket.projectId);
|
|
112
112
|
const columnNames = board.columns.map(col => col.name);
|
|
113
|
-
const
|
|
114
|
-
if (!
|
|
113
|
+
const reviewColumn = findColumnByName(columnNames, targetColumnName);
|
|
114
|
+
if (!reviewColumn) {
|
|
115
115
|
db.close();
|
|
116
|
-
this.error(`No "${targetColumnName}" column found in board configuration. Configure with: prlt config set
|
|
116
|
+
this.error(`No "${targetColumnName}" column found in board configuration. Configure with: prlt config set column_review <column-name>`);
|
|
117
117
|
}
|
|
118
118
|
const previousColumn = ticket.statusName;
|
|
119
|
-
// Move to
|
|
120
|
-
await this.storage.moveTicket(ticket.projectId, ticketId,
|
|
119
|
+
// Move to Review column (moveTicket also updates status_id)
|
|
120
|
+
await this.storage.moveTicket(ticket.projectId, ticketId, reviewColumn);
|
|
121
121
|
// Auto-export to board.md if configured
|
|
122
122
|
await autoExportToBoard(this.pmoPath, this.storage);
|
|
123
123
|
// Mark any running executions for this ticket as completed
|
|
@@ -172,7 +172,7 @@ export default class WorkReady extends PMOCommand {
|
|
|
172
172
|
this.log(styles.success(`Work ready: ${ticketId}`));
|
|
173
173
|
this.log(styles.muted(` Title: ${ticket.title}`));
|
|
174
174
|
this.log(styles.muted(` From: ${previousColumn}`));
|
|
175
|
-
this.log(styles.muted(` To: ${
|
|
175
|
+
this.log(styles.muted(` To: ${reviewColumn}`));
|
|
176
176
|
if (prUrl) {
|
|
177
177
|
this.log(styles.muted(` PR: ${prUrl}`));
|
|
178
178
|
}
|
|
@@ -119,14 +119,6 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
119
119
|
const { flags, argv } = await this.parse(WorkSpawn);
|
|
120
120
|
// Check if JSON output mode is active
|
|
121
121
|
const jsonMode = shouldOutputJson(flags);
|
|
122
|
-
// This command requires project context (pass JSON mode config for AI agents)
|
|
123
|
-
const projectId = await this.requireProject({
|
|
124
|
-
jsonMode: {
|
|
125
|
-
flags,
|
|
126
|
-
commandName: 'work spawn',
|
|
127
|
-
baseCommand: 'prlt work spawn',
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
122
|
// Helper to handle errors in JSON mode
|
|
131
123
|
const handleError = (code, message) => {
|
|
132
124
|
if (jsonMode) {
|
|
@@ -137,6 +129,38 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
137
129
|
};
|
|
138
130
|
// Parse ticket IDs from args (everything after flags)
|
|
139
131
|
const ticketIdArgs = argv;
|
|
132
|
+
// Try to infer project from ticket IDs if provided
|
|
133
|
+
let projectId;
|
|
134
|
+
if (ticketIdArgs.length > 0) {
|
|
135
|
+
// Look up tickets to get their project IDs
|
|
136
|
+
const projectIds = new Set();
|
|
137
|
+
for (const ticketId of ticketIdArgs) {
|
|
138
|
+
// eslint-disable-next-line no-await-in-loop
|
|
139
|
+
const ticket = await this.storage.getTicket(ticketId);
|
|
140
|
+
if (ticket?.projectId) {
|
|
141
|
+
projectIds.add(ticket.projectId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (projectIds.size === 1) {
|
|
145
|
+
// All tickets from same project - use that project
|
|
146
|
+
projectId = [...projectIds][0];
|
|
147
|
+
}
|
|
148
|
+
else if (projectIds.size > 1) {
|
|
149
|
+
// Tickets from multiple projects - warn and require prompt
|
|
150
|
+
this.warn('Tickets are from multiple projects. Please specify --project.');
|
|
151
|
+
}
|
|
152
|
+
// If size === 0, tickets not found - will be handled later in validation
|
|
153
|
+
}
|
|
154
|
+
// Only call requireProject() if we couldn't infer from tickets
|
|
155
|
+
if (!projectId) {
|
|
156
|
+
projectId = await this.requireProject({
|
|
157
|
+
jsonMode: {
|
|
158
|
+
flags,
|
|
159
|
+
commandName: 'work spawn',
|
|
160
|
+
baseCommand: 'prlt work spawn',
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
140
164
|
// Note: Docker check is handled by work:start command when spawning each ticket
|
|
141
165
|
// This allows for the interactive devcontainer/host selection with retry loop
|
|
142
166
|
// Get workspace info (for agent worktree paths)
|
|
@@ -42,11 +42,13 @@ export default class WorkWatch extends PMOCommand {
|
|
|
42
42
|
limit: Flags.integer({
|
|
43
43
|
char: 'l',
|
|
44
44
|
description: 'Maximum concurrent executions',
|
|
45
|
+
min: 1,
|
|
45
46
|
}),
|
|
46
47
|
interval: Flags.integer({
|
|
47
48
|
char: 'i',
|
|
48
49
|
description: 'Polling interval in seconds',
|
|
49
50
|
default: 5,
|
|
51
|
+
min: 1,
|
|
50
52
|
}),
|
|
51
53
|
once: Flags.boolean({
|
|
52
54
|
description: 'Check once and exit (no continuous watching)',
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { Agent, Repository, MountMode as DBMountMode } from '../database/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Format a list of agents for display in error messages.
|
|
4
|
+
* Truncates long lists to avoid overwhelming output.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatAgentList(agents: {
|
|
7
|
+
name: string;
|
|
8
|
+
}[], maxShow?: number): string;
|
|
2
9
|
export interface AgentStatus {
|
|
3
10
|
name: string;
|
|
4
11
|
exists: boolean;
|
|
@@ -9,6 +9,17 @@ import { getWorkspaceConfig, getWorkspaceAgents, getWorkspaceRepositories, getAg
|
|
|
9
9
|
import { isValidAgentName, getSuggestedAgentNames, generateEphemeralAgentName, getThemePersistentDir, getThemeEphemeralDir, extractBaseName, getAgentBaseName, } from '../themes.js';
|
|
10
10
|
import { createDevcontainerConfig } from '../execution/devcontainer.js';
|
|
11
11
|
import { getPMOContext } from '../pmo/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Format a list of agents for display in error messages.
|
|
14
|
+
* Truncates long lists to avoid overwhelming output.
|
|
15
|
+
*/
|
|
16
|
+
export function formatAgentList(agents, maxShow = 10) {
|
|
17
|
+
const names = agents.map(a => a.name);
|
|
18
|
+
if (names.length <= maxShow) {
|
|
19
|
+
return names.join(', ');
|
|
20
|
+
}
|
|
21
|
+
return `${names.slice(0, maxShow).join(', ')} ...and ${names.length - maxShow} more. Run 'prlt agent list' to see all.`;
|
|
22
|
+
}
|
|
12
23
|
/**
|
|
13
24
|
* Find workspace root and return workspace information.
|
|
14
25
|
*
|
package/dist/lib/agents/index.js
CHANGED
|
@@ -217,14 +217,15 @@ export async function createAgentWorktrees(workspacePath, agents, hqPath, option
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
-
// Create devcontainer config for sandboxed execution
|
|
220
|
+
// Create devcontainer config for sandboxed execution
|
|
221
221
|
// Note: Agent metadata is stored in SQLite (agents table), not in config files
|
|
222
|
-
|
|
222
|
+
// Always create devcontainer config (even if no repos were created) so agent rebuild works
|
|
223
|
+
if (!options?.skipDevcontainer) {
|
|
223
224
|
console.log(styles.muted(` Creating devcontainer config...`));
|
|
224
225
|
createDevcontainerConfig({
|
|
225
226
|
agentName: agent,
|
|
226
227
|
agentDir,
|
|
227
|
-
repoWorktrees: mountMode === 'worktree' ? createdRepos : undefined,
|
|
228
|
+
repoWorktrees: mountMode === 'worktree' && createdRepos.length > 0 ? createdRepos : undefined,
|
|
228
229
|
mountMode,
|
|
229
230
|
});
|
|
230
231
|
}
|
|
@@ -237,10 +238,19 @@ export async function createAgentWorktrees(workspacePath, agents, hqPath, option
|
|
|
237
238
|
}
|
|
238
239
|
else {
|
|
239
240
|
console.log(chalk.yellow('No repositories found in HQ. Creating placeholder agent directories.'));
|
|
240
|
-
// Create placeholder directories
|
|
241
|
+
// Create placeholder directories with devcontainer configs
|
|
241
242
|
for (const agent of agents) {
|
|
242
243
|
const agentDir = path.join(workspacePath, agent);
|
|
243
244
|
fs.mkdirSync(agentDir, { recursive: true });
|
|
245
|
+
// Create devcontainer config even without repos so agent rebuild works
|
|
246
|
+
if (!options?.skipDevcontainer) {
|
|
247
|
+
console.log(styles.muted(` Creating devcontainer config...`));
|
|
248
|
+
createDevcontainerConfig({
|
|
249
|
+
agentName: agent,
|
|
250
|
+
agentDir,
|
|
251
|
+
mountMode: mountMode,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
244
254
|
console.log(chalk.green(`✅ Placeholder agent ${agent} created`));
|
|
245
255
|
}
|
|
246
256
|
}
|