@proletariat/cli 0.3.18 → 0.3.20

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/agent/staff/remove.d.ts +1 -0
  4. package/dist/commands/agent/staff/remove.js +34 -26
  5. package/dist/commands/agent/temp/cleanup.js +10 -17
  6. package/dist/commands/board/view.d.ts +15 -0
  7. package/dist/commands/board/view.js +136 -0
  8. package/dist/commands/config/index.js +6 -3
  9. package/dist/commands/execution/config.d.ts +34 -0
  10. package/dist/commands/execution/config.js +411 -0
  11. package/dist/commands/execution/index.js +6 -1
  12. package/dist/commands/execution/kill.d.ts +9 -0
  13. package/dist/commands/execution/kill.js +16 -0
  14. package/dist/commands/execution/view.d.ts +17 -0
  15. package/dist/commands/execution/view.js +288 -0
  16. package/dist/commands/phase/template/create.js +67 -20
  17. package/dist/commands/pr/index.js +6 -2
  18. package/dist/commands/pr/list.d.ts +17 -0
  19. package/dist/commands/pr/list.js +163 -0
  20. package/dist/commands/project/update.d.ts +19 -0
  21. package/dist/commands/project/update.js +163 -0
  22. package/dist/commands/roadmap/create.js +5 -0
  23. package/dist/commands/spec/delete.d.ts +18 -0
  24. package/dist/commands/spec/delete.js +111 -0
  25. package/dist/commands/spec/edit.d.ts +23 -0
  26. package/dist/commands/spec/edit.js +232 -0
  27. package/dist/commands/spec/index.js +5 -0
  28. package/dist/commands/status/create.js +38 -34
  29. package/dist/commands/template/phase/create.d.ts +1 -0
  30. package/dist/commands/template/phase/create.js +10 -1
  31. package/dist/commands/template/ticket/create.d.ts +20 -0
  32. package/dist/commands/template/ticket/create.js +87 -0
  33. package/dist/commands/template/ticket/save.d.ts +2 -0
  34. package/dist/commands/template/ticket/save.js +11 -0
  35. package/dist/commands/ticket/create.js +7 -0
  36. package/dist/commands/ticket/template/create.d.ts +9 -1
  37. package/dist/commands/ticket/template/create.js +224 -52
  38. package/dist/commands/ticket/template/save.d.ts +2 -1
  39. package/dist/commands/ticket/template/save.js +58 -7
  40. package/dist/commands/work/ready.js +8 -8
  41. package/dist/lib/agents/index.js +14 -4
  42. package/dist/lib/branch/index.js +24 -0
  43. package/dist/lib/execution/config.d.ts +2 -0
  44. package/dist/lib/execution/config.js +12 -0
  45. package/dist/lib/pmo/utils.d.ts +4 -2
  46. package/dist/lib/pmo/utils.js +4 -2
  47. package/oclif.manifest.json +2555 -1781
  48. package/package.json +5 -6
@@ -7,6 +7,7 @@ export default class TemplatePhaseCreate extends Command {
7
7
  };
8
8
  static flags: {
9
9
  description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  };
12
13
  run(): Promise<void>;
@@ -4,6 +4,7 @@ export default class TemplatePhaseCreate extends Command {
4
4
  static examples = [
5
5
  '<%= config.bin %> <%= command.id %> "My Phases"',
6
6
  '<%= config.bin %> <%= command.id %> "Sprint Phases" --description "Agile sprint phases"',
7
+ '<%= config.bin %> <%= command.id %> "My Phases" --description "Custom phases" --json',
7
8
  ];
8
9
  static args = {
9
10
  name: Args.string({
@@ -16,9 +17,15 @@ export default class TemplatePhaseCreate extends Command {
16
17
  char: 'd',
17
18
  description: 'Template description',
18
19
  }),
20
+ machine: Flags.boolean({
21
+ char: 'm',
22
+ description: 'Output as JSON for AI agents/scripts (machine-readable mode)',
23
+ default: false,
24
+ }),
19
25
  json: Flags.boolean({
20
- description: 'Output prompt configuration as JSON (for AI agents/scripts)',
26
+ description: 'Output as JSON (deprecated, use --machine)',
21
27
  default: false,
28
+ hidden: true,
22
29
  }),
23
30
  };
24
31
  async run() {
@@ -28,6 +35,8 @@ export default class TemplatePhaseCreate extends Command {
28
35
  cmdArgs.push(args.name);
29
36
  if (flags.description)
30
37
  cmdArgs.push('--description', flags.description);
38
+ if (flags.machine)
39
+ cmdArgs.push('--machine');
31
40
  if (flags.json)
32
41
  cmdArgs.push('--json');
33
42
  await this.config.runCommand('phase:template:create', cmdArgs);
@@ -0,0 +1,20 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class TemplateTicketCreate extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ name: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'title-pattern': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'description-template': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ priority: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ category: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ subtask: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ ac: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ label: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ };
19
+ run(): Promise<void>;
20
+ }
@@ -0,0 +1,87 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ export default class TemplateTicketCreate extends Command {
3
+ static description = 'Create a new ticket template from scratch';
4
+ static examples = [
5
+ '<%= config.bin %> <%= command.id %> "Bug Report"',
6
+ '<%= config.bin %> <%= command.id %> "Feature Request" -d "Template for new features"',
7
+ '<%= config.bin %> <%= command.id %> "Task" --title-pattern "[TASK] " --priority P2',
8
+ '<%= config.bin %> <%= command.id %> "Onboarding" --subtask "Setup environment" --subtask "Read docs" --ac "Complete all subtasks"',
9
+ ];
10
+ static args = {
11
+ name: Args.string({
12
+ description: 'Template name',
13
+ required: false,
14
+ }),
15
+ };
16
+ static flags = {
17
+ description: Flags.string({
18
+ char: 'd',
19
+ description: 'Template description',
20
+ }),
21
+ 'title-pattern': Flags.string({
22
+ description: 'Default title prefix/pattern (e.g., "[BUG] ")',
23
+ }),
24
+ 'description-template': Flags.string({
25
+ description: 'Default description template (markdown)',
26
+ }),
27
+ priority: Flags.string({
28
+ char: 'p',
29
+ description: 'Default priority (P0, P1, P2, P3)',
30
+ }),
31
+ category: Flags.string({
32
+ char: 'c',
33
+ description: 'Default category (bug, feature, etc.)',
34
+ }),
35
+ subtask: Flags.string({
36
+ description: 'Add a suggested subtask (can be used multiple times)',
37
+ multiple: true,
38
+ }),
39
+ ac: Flags.string({
40
+ description: 'Add an acceptance criterion pattern (can be used multiple times)',
41
+ multiple: true,
42
+ }),
43
+ label: Flags.string({
44
+ char: 'l',
45
+ description: 'Add a default label (can be used multiple times)',
46
+ multiple: true,
47
+ }),
48
+ json: Flags.boolean({
49
+ description: 'Output prompt configuration as JSON (for AI agents/scripts)',
50
+ default: false,
51
+ }),
52
+ };
53
+ async run() {
54
+ const { args, flags } = await this.parse(TemplateTicketCreate);
55
+ const cmdArgs = [];
56
+ if (args.name)
57
+ cmdArgs.push(args.name);
58
+ if (flags.description)
59
+ cmdArgs.push('--description', flags.description);
60
+ if (flags['title-pattern'])
61
+ cmdArgs.push('--title-pattern', flags['title-pattern']);
62
+ if (flags['description-template'])
63
+ cmdArgs.push('--description-template', flags['description-template']);
64
+ if (flags.priority)
65
+ cmdArgs.push('--priority', flags.priority);
66
+ if (flags.category)
67
+ cmdArgs.push('--category', flags.category);
68
+ if (flags.subtask) {
69
+ for (const subtask of flags.subtask) {
70
+ cmdArgs.push('--subtask', subtask);
71
+ }
72
+ }
73
+ if (flags.ac) {
74
+ for (const ac of flags.ac) {
75
+ cmdArgs.push('--ac', ac);
76
+ }
77
+ }
78
+ if (flags.label) {
79
+ for (const label of flags.label) {
80
+ cmdArgs.push('--label', label);
81
+ }
82
+ }
83
+ if (flags.json)
84
+ cmdArgs.push('--json');
85
+ await this.config.runCommand('ticket:template:create', cmdArgs);
86
+ }
87
+ }
@@ -7,8 +7,10 @@ export default class TemplateTicketSave extends Command {
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
12
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
14
  };
13
15
  run(): Promise<void>;
14
16
  }
@@ -1,9 +1,11 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
+ import { machineOutputFlags } from '../../../lib/pmo/base-command.js';
2
3
  export default class TemplateTicketSave extends Command {
3
4
  static description = 'Create a template from an existing ticket';
4
5
  static examples = [
5
6
  '<%= config.bin %> <%= command.id %> TKT-001 "Bug Report Template"',
6
7
  '<%= config.bin %> <%= command.id %> TKT-042 "Feature Request" --description "Standard feature request template"',
8
+ '<%= config.bin %> <%= command.id %> TKT-001 --template-name "My Template" --json',
7
9
  ];
8
10
  static args = {
9
11
  ticket: Args.string({
@@ -16,6 +18,11 @@ export default class TemplateTicketSave extends Command {
16
18
  }),
17
19
  };
18
20
  static flags = {
21
+ ...machineOutputFlags,
22
+ 'template-name': Flags.string({
23
+ char: 'n',
24
+ description: 'Template name (alternative to positional arg, required in non-TTY/JSON mode)',
25
+ }),
19
26
  description: Flags.string({
20
27
  char: 'd',
21
28
  description: 'Template description',
@@ -32,10 +39,14 @@ export default class TemplateTicketSave extends Command {
32
39
  cmdArgs.push(args.ticket);
33
40
  if (args.name)
34
41
  cmdArgs.push(args.name);
42
+ if (flags['template-name'])
43
+ cmdArgs.push('--template-name', flags['template-name']);
35
44
  if (flags.description)
36
45
  cmdArgs.push('--description', flags.description);
37
46
  if (flags.json)
38
47
  cmdArgs.push('--json');
48
+ if (flags.machine)
49
+ cmdArgs.push('--machine');
39
50
  await this.config.runCommand('ticket:template:save', cmdArgs);
40
51
  }
41
52
  }
@@ -110,6 +110,13 @@ export default class TicketCreate extends PMOCommand {
110
110
  // Use FlagResolver to handle both JSON mode and interactive prompts
111
111
  // This unifies the two code paths into one pattern
112
112
  if (!flags.interactive) {
113
+ // In JSON mode, default column to first backlog status if not provided
114
+ // This prevents prompting for column in non-interactive mode
115
+ if (jsonMode && !flags.column) {
116
+ // Prefer "Backlog" column, fall back to first column
117
+ const backlogColumn = columns.find(c => c.toLowerCase() === 'backlog') || columns[0];
118
+ flags.column = backlogColumn;
119
+ }
113
120
  const resolver = new FlagResolver({
114
121
  commandName: 'ticket create',
115
122
  baseCommand: 'prlt ticket create',
@@ -8,14 +8,22 @@ export default class TicketTemplateCreate extends PMOCommand {
8
8
  static flags: {
9
9
  description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  'title-pattern': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'description-template': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  priority: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
13
  category: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
- machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ subtask: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ ac: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ label: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
17
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
19
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
20
  };
17
21
  protected getPMOOptions(): {
18
22
  promptIfMultiple: boolean;
19
23
  };
20
24
  execute(): Promise<void>;
25
+ /**
26
+ * Check if any non-default flags were provided (indicating non-interactive intent)
27
+ */
28
+ private hasNonDefaultFlags;
21
29
  }
@@ -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
- // Ask about description template
104
- const { wantDescriptionTemplate } = await inquirer.prompt([{
105
- type: 'list',
106
- name: 'wantDescriptionTemplate',
107
- message: 'Add a description template?',
108
- choices: [
109
- { name: 'No', value: false },
110
- { name: 'Yes', value: true },
111
- ],
112
- }]);
113
- let descriptionTemplate;
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
- descriptionTemplate = template || undefined;
122
- }
123
- // Ask about default subtasks
124
- const subtasks = [];
125
- const { wantSubtasks } = await inquirer.prompt([{
126
- type: 'list',
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
- addMore = another;
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>;