@proletariat/cli 0.3.19 → 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 (46) hide show
  1. package/dist/commands/agent/staff/remove.d.ts +1 -0
  2. package/dist/commands/agent/staff/remove.js +34 -26
  3. package/dist/commands/agent/temp/cleanup.js +10 -17
  4. package/dist/commands/board/view.d.ts +15 -0
  5. package/dist/commands/board/view.js +136 -0
  6. package/dist/commands/config/index.js +6 -3
  7. package/dist/commands/execution/config.d.ts +34 -0
  8. package/dist/commands/execution/config.js +411 -0
  9. package/dist/commands/execution/index.js +6 -1
  10. package/dist/commands/execution/kill.d.ts +9 -0
  11. package/dist/commands/execution/kill.js +16 -0
  12. package/dist/commands/execution/view.d.ts +17 -0
  13. package/dist/commands/execution/view.js +288 -0
  14. package/dist/commands/phase/template/create.js +67 -20
  15. package/dist/commands/pr/index.js +6 -2
  16. package/dist/commands/pr/list.d.ts +17 -0
  17. package/dist/commands/pr/list.js +163 -0
  18. package/dist/commands/project/update.d.ts +19 -0
  19. package/dist/commands/project/update.js +163 -0
  20. package/dist/commands/roadmap/create.js +5 -0
  21. package/dist/commands/spec/delete.d.ts +18 -0
  22. package/dist/commands/spec/delete.js +111 -0
  23. package/dist/commands/spec/edit.d.ts +23 -0
  24. package/dist/commands/spec/edit.js +232 -0
  25. package/dist/commands/spec/index.js +5 -0
  26. package/dist/commands/status/create.js +38 -34
  27. package/dist/commands/template/phase/create.d.ts +1 -0
  28. package/dist/commands/template/phase/create.js +10 -1
  29. package/dist/commands/template/ticket/create.d.ts +20 -0
  30. package/dist/commands/template/ticket/create.js +87 -0
  31. package/dist/commands/template/ticket/save.d.ts +2 -0
  32. package/dist/commands/template/ticket/save.js +11 -0
  33. package/dist/commands/ticket/create.js +7 -0
  34. package/dist/commands/ticket/template/create.d.ts +9 -1
  35. package/dist/commands/ticket/template/create.js +224 -52
  36. package/dist/commands/ticket/template/save.d.ts +2 -1
  37. package/dist/commands/ticket/template/save.js +58 -7
  38. package/dist/commands/work/ready.js +8 -8
  39. package/dist/lib/agents/index.js +14 -4
  40. package/dist/lib/branch/index.js +24 -0
  41. package/dist/lib/execution/config.d.ts +2 -0
  42. package/dist/lib/execution/config.js +12 -0
  43. package/dist/lib/pmo/utils.d.ts +4 -2
  44. package/dist/lib/pmo/utils.js +4 -2
  45. package/oclif.manifest.json +3017 -2243
  46. package/package.json +2 -4
@@ -0,0 +1,163 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
4
+ import { styles } from '../../lib/styles.js';
5
+ import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, buildFormPromptConfig, } from '../../lib/prompt-json.js';
6
+ export default class ProjectUpdate extends PMOCommand {
7
+ static description = 'Update project metadata (name, description)';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %> my-project --name "New Project Name"',
10
+ '<%= config.bin %> <%= command.id %> my-project --description "Updated description"',
11
+ '<%= config.bin %> <%= command.id %> my-project --name "New Name" --description "New description"',
12
+ '<%= config.bin %> <%= command.id %> my-project # Interactive mode',
13
+ ];
14
+ static args = {
15
+ id: Args.string({
16
+ description: 'Project ID or name',
17
+ required: false,
18
+ }),
19
+ };
20
+ static flags = {
21
+ ...pmoBaseFlags,
22
+ name: Flags.string({
23
+ char: 'n',
24
+ description: 'New project name',
25
+ }),
26
+ description: Flags.string({
27
+ char: 'd',
28
+ description: 'New project description',
29
+ }),
30
+ };
31
+ getPMOOptions() {
32
+ return { promptIfMultiple: false };
33
+ }
34
+ async execute() {
35
+ const { args, flags } = await this.parse(ProjectUpdate);
36
+ // Check if JSON output mode is active
37
+ const jsonMode = shouldOutputJson(flags);
38
+ // Helper to handle errors in JSON mode
39
+ const handleError = (code, message) => {
40
+ if (jsonMode) {
41
+ outputErrorAsJson(code, message, createMetadata('project update', flags));
42
+ this.exit(1);
43
+ }
44
+ this.error(message);
45
+ };
46
+ // Agent mode config for prompts
47
+ const agentConfig = jsonMode ? { flags, commandName: 'project update' } : null;
48
+ // Get project ID - from args or prompt
49
+ let projectId = args.id;
50
+ if (!projectId) {
51
+ // List available projects for selection
52
+ const projects = await this.storage.listProjects({ isArchived: false });
53
+ if (projects.length === 0) {
54
+ return handleError('NO_PROJECTS', 'No projects found. Create one with: prlt project create');
55
+ }
56
+ const projectChoices = projects.map(p => ({
57
+ name: `${p.id} - ${p.name}`,
58
+ value: p.id,
59
+ command: `prlt project update ${p.id} --json`,
60
+ }));
61
+ // Handle JSON mode for project selection
62
+ if (jsonMode) {
63
+ outputPromptAsJson({
64
+ type: 'list',
65
+ name: 'projectId',
66
+ message: 'Select project to update:',
67
+ choices: projectChoices,
68
+ }, createMetadata('project update', flags));
69
+ return;
70
+ }
71
+ const { selectedProjectId } = await inquirer.prompt([{
72
+ type: 'list',
73
+ name: 'selectedProjectId',
74
+ message: 'Select project to update:',
75
+ choices: projectChoices,
76
+ }]);
77
+ projectId = selectedProjectId;
78
+ }
79
+ // Get the project
80
+ const project = await this.storage.getProject(projectId);
81
+ if (!project) {
82
+ return handleError('PROJECT_NOT_FOUND', `Project "${projectId}" not found.`);
83
+ }
84
+ // Determine what to update
85
+ let newName = flags.name;
86
+ let newDescription = flags.description;
87
+ // If no flags provided, enter interactive mode
88
+ if (newName === undefined && newDescription === undefined) {
89
+ const fields = [
90
+ {
91
+ type: 'input',
92
+ name: 'name',
93
+ message: 'Project name:',
94
+ default: project.name,
95
+ },
96
+ {
97
+ type: 'input',
98
+ name: 'description',
99
+ message: 'Project description:',
100
+ default: project.description || '',
101
+ },
102
+ ];
103
+ // Handle JSON mode for field input
104
+ if (jsonMode) {
105
+ outputPromptAsJson(buildFormPromptConfig(fields), createMetadata('project update', flags));
106
+ return;
107
+ }
108
+ const answers = await inquirer.prompt(fields.map(field => ({
109
+ ...field,
110
+ validate: field.name === 'name'
111
+ ? ((input) => input.length > 0 || 'Name is required')
112
+ : undefined,
113
+ })));
114
+ newName = answers.name;
115
+ newDescription = answers.description;
116
+ }
117
+ // Build changes object - only include fields that are different
118
+ const changes = {};
119
+ if (newName !== undefined && newName !== project.name) {
120
+ changes.name = newName;
121
+ }
122
+ if (newDescription !== undefined && newDescription !== (project.description || '')) {
123
+ // Allow clearing description with empty string (storage layer converts '' to null)
124
+ changes.description = newDescription;
125
+ }
126
+ // Check if anything changed
127
+ if (Object.keys(changes).length === 0) {
128
+ if (jsonMode) {
129
+ outputSuccessAsJson({
130
+ projectId: project.id,
131
+ projectName: project.name,
132
+ description: project.description,
133
+ noChanges: true,
134
+ }, createMetadata('project update', flags));
135
+ return;
136
+ }
137
+ this.log(styles.muted('No changes made.'));
138
+ return;
139
+ }
140
+ // Update the project
141
+ const updated = await this.storage.updateProject(project.id, changes);
142
+ // Output success
143
+ if (jsonMode) {
144
+ outputSuccessAsJson({
145
+ projectId: updated.id,
146
+ projectName: updated.name,
147
+ description: updated.description,
148
+ changes: Object.keys(changes),
149
+ }, createMetadata('project update', flags));
150
+ return;
151
+ }
152
+ this.log(styles.success(`\nUpdated project "${styles.emphasis(updated.name)}"`));
153
+ this.log(styles.muted(` ID: ${updated.id}`));
154
+ if (changes.name) {
155
+ this.log(styles.muted(` Name: ${project.name} → ${updated.name}`));
156
+ }
157
+ if ('description' in changes) {
158
+ const oldDesc = project.description || '(none)';
159
+ const newDesc = updated.description || '(none)';
160
+ this.log(styles.muted(` Description: ${oldDesc} → ${newDesc}`));
161
+ }
162
+ }
163
+ }
@@ -94,6 +94,11 @@ export default class RoadmapCreate extends PMOCommand {
94
94
  description: roadmapData.description,
95
95
  isDefault: roadmapData.isDefault,
96
96
  });
97
+ // In JSON mode, output roadmap data and return (skip interactive prompts)
98
+ if (jsonMode) {
99
+ this.log(JSON.stringify(roadmap, null, 2));
100
+ return;
101
+ }
97
102
  this.log(styles.success(`\nCreated roadmap "${styles.emphasis(roadmap.name)}"`));
98
103
  this.log(styles.muted(` ID: ${roadmap.id}`));
99
104
  if (roadmap.description) {
@@ -0,0 +1,18 @@
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class SpecDelete extends PMOCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ protected getPMOOptions(): {
15
+ promptIfMultiple: boolean;
16
+ };
17
+ execute(): Promise<void>;
18
+ }
@@ -0,0 +1,111 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
3
+ import { styles } from '../../lib/styles.js';
4
+ import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
5
+ import { FlagResolver } from '../../lib/flags/index.js';
6
+ export default class SpecDelete extends PMOCommand {
7
+ static description = 'Delete a spec';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %> my-spec',
10
+ '<%= config.bin %> <%= command.id %> my-spec --force',
11
+ '<%= config.bin %> <%= command.id %> # Interactive selection',
12
+ ];
13
+ static args = {
14
+ id: Args.string({
15
+ description: 'Spec ID to delete',
16
+ required: false,
17
+ }),
18
+ };
19
+ static flags = {
20
+ ...pmoBaseFlags,
21
+ force: Flags.boolean({
22
+ char: 'f',
23
+ description: 'Skip confirmation prompt',
24
+ default: false,
25
+ }),
26
+ };
27
+ getPMOOptions() {
28
+ return { promptIfMultiple: false };
29
+ }
30
+ async execute() {
31
+ const { args, flags } = await this.parse(SpecDelete);
32
+ const jsonMode = shouldOutputJson(flags);
33
+ const handleError = (code, message) => {
34
+ if (jsonMode) {
35
+ outputErrorAsJson(code, message, createMetadata('spec delete', flags));
36
+ this.exit(1);
37
+ }
38
+ this.error(message);
39
+ };
40
+ let specId = args.id;
41
+ // Interactive selection if no ID provided
42
+ if (!specId) {
43
+ const specs = await this.storage.listSpecs();
44
+ if (specs.length === 0) {
45
+ return handleError('NO_SPECS', 'No specs found');
46
+ }
47
+ const resolver = new FlagResolver({
48
+ commandName: 'spec delete',
49
+ baseCommand: 'prlt spec delete',
50
+ jsonMode,
51
+ flags: {},
52
+ });
53
+ resolver.addPrompt({
54
+ flagName: 'spec',
55
+ type: 'list',
56
+ message: 'Select spec to delete:',
57
+ choices: () => specs.map(s => ({
58
+ name: `${s.title} [${s.status}]${s.type ? ` (${s.type})` : ''}`,
59
+ value: s.id,
60
+ })),
61
+ getCommand: (value) => `prlt spec delete ${value} --json`,
62
+ });
63
+ const resolved = await resolver.resolve();
64
+ specId = resolved.spec;
65
+ }
66
+ // Get spec details
67
+ const spec = await this.storage.getSpec(specId);
68
+ if (!spec) {
69
+ return handleError('SPEC_NOT_FOUND', `Spec not found: ${specId}`);
70
+ }
71
+ // Confirmation prompt
72
+ if (!flags.force) {
73
+ const resolver = new FlagResolver({
74
+ commandName: 'spec delete',
75
+ baseCommand: `prlt spec delete ${specId}`,
76
+ jsonMode,
77
+ flags: {},
78
+ });
79
+ resolver.addPrompt({
80
+ flagName: 'confirmed',
81
+ type: 'list',
82
+ message: `Delete spec "${spec.title}"?`,
83
+ choices: () => [
84
+ { name: 'No', value: false },
85
+ { name: 'Yes', value: true },
86
+ ],
87
+ getCommand: (value) => value
88
+ ? `prlt spec delete ${specId} --force --json`
89
+ : '',
90
+ });
91
+ const resolved = await resolver.resolve();
92
+ if (!resolved.confirmed) {
93
+ this.log(styles.muted('Cancelled'));
94
+ return;
95
+ }
96
+ }
97
+ // Delete the spec
98
+ await this.storage.deleteSpec(specId);
99
+ // JSON response for agents
100
+ if (jsonMode) {
101
+ outputSuccessAsJson({
102
+ specId: spec.id,
103
+ title: spec.title,
104
+ deleted: true,
105
+ }, createMetadata('spec delete', flags));
106
+ return;
107
+ }
108
+ // Human-readable response
109
+ this.log(styles.success(`\nDeleted spec "${styles.emphasis(spec.title)}"`));
110
+ }
111
+ }
@@ -0,0 +1,23 @@
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class SpecEdit extends PMOCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ spec: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ spec: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ title: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ problem: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ solution: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ decisions: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ interactive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ };
21
+ execute(): Promise<void>;
22
+ private promptForEdits;
23
+ }
@@ -0,0 +1,232 @@
1
+ import { Flags, Args } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
4
+ import { styles } from '../../lib/styles.js';
5
+ import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
6
+ import { FlagResolver } from '../../lib/flags/index.js';
7
+ export default class SpecEdit extends PMOCommand {
8
+ static description = 'Edit an existing spec';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %> user-authentication',
11
+ '<%= config.bin %> <%= command.id %> --spec api-design --title "New API Design"',
12
+ '<%= config.bin %> <%= command.id %> user-auth --status active',
13
+ '<%= config.bin %> <%= command.id %> user-auth --type product --problem "Need better auth"',
14
+ '<%= config.bin %> <%= command.id %> -i # Interactive mode',
15
+ ];
16
+ static args = {
17
+ spec: Args.string({
18
+ description: 'Spec ID to edit',
19
+ required: false,
20
+ }),
21
+ };
22
+ static flags = {
23
+ ...pmoBaseFlags,
24
+ spec: Flags.string({
25
+ char: 's',
26
+ description: 'Spec ID to edit',
27
+ }),
28
+ title: Flags.string({
29
+ char: 't',
30
+ description: 'New spec title',
31
+ }),
32
+ status: Flags.string({
33
+ description: 'Spec status',
34
+ options: ['draft', 'active', 'implemented'],
35
+ }),
36
+ type: Flags.string({
37
+ description: 'Spec type',
38
+ options: ['product', 'platform', 'infra', 'integration', 'none'],
39
+ }),
40
+ problem: Flags.string({
41
+ description: 'Problem statement',
42
+ }),
43
+ solution: Flags.string({
44
+ description: 'Solution description',
45
+ }),
46
+ decisions: Flags.string({
47
+ description: 'Design decisions',
48
+ }),
49
+ interactive: Flags.boolean({
50
+ char: 'i',
51
+ description: 'Interactive mode - prompts for all fields',
52
+ default: false,
53
+ }),
54
+ };
55
+ async execute() {
56
+ const { args, flags } = await this.parse(SpecEdit);
57
+ // Check if JSON output mode is active
58
+ const jsonMode = shouldOutputJson(flags);
59
+ // Helper to handle errors in JSON mode
60
+ const handleError = (code, message) => {
61
+ if (jsonMode) {
62
+ outputErrorAsJson(code, message, createMetadata('spec edit', flags));
63
+ this.exit(1);
64
+ }
65
+ this.error(message);
66
+ };
67
+ // Get spec ID from args or flags
68
+ let specId = args.spec || flags.spec;
69
+ if (!specId) {
70
+ // List specs for selection
71
+ const specs = await this.storage.listSpecs();
72
+ if (specs.length === 0) {
73
+ return handleError('NO_SPECS', 'No specs found. Create one first with: prlt spec create');
74
+ }
75
+ // Use FlagResolver for spec selection
76
+ const resolver = new FlagResolver({
77
+ commandName: 'spec edit',
78
+ baseCommand: 'prlt spec edit',
79
+ jsonMode,
80
+ flags: { spec: flags.spec },
81
+ });
82
+ resolver.addPrompt({
83
+ flagName: 'spec',
84
+ type: 'list',
85
+ message: 'Select spec to edit:',
86
+ choices: () => specs.map(s => ({
87
+ name: `${s.title} [${s.status}]${s.type ? ` (${s.type})` : ''}`,
88
+ value: s.id,
89
+ })),
90
+ });
91
+ const resolved = await resolver.resolve();
92
+ specId = resolved.spec;
93
+ }
94
+ // Get current spec
95
+ const spec = await this.storage.getSpec(specId);
96
+ if (!spec) {
97
+ return handleError('NOT_FOUND', `Spec "${specId}" not found.`);
98
+ }
99
+ // Build choices for prompts
100
+ const typeChoices = [
101
+ { name: 'Product (user-facing feature)', value: 'product' },
102
+ { name: 'Platform (internal tooling)', value: 'platform' },
103
+ { name: 'Infra (technical infrastructure)', value: 'infra' },
104
+ { name: 'Integration (external service)', value: 'integration' },
105
+ { name: 'None', value: '' },
106
+ ];
107
+ const statusChoices = [
108
+ { name: 'Draft (planning)', value: 'draft' },
109
+ { name: 'Active (in progress)', value: 'active' },
110
+ { name: 'Implemented (complete)', value: 'implemented' },
111
+ ];
112
+ // Determine what to update
113
+ let updates = {};
114
+ const hasFlags = flags.title || flags.status || flags.type || flags.problem ||
115
+ flags.solution || flags.decisions;
116
+ if (flags.interactive || !hasFlags) {
117
+ // Interactive mode - prompt for editable fields
118
+ updates = await this.promptForEdits(spec, typeChoices, statusChoices);
119
+ }
120
+ else {
121
+ // Use flag values
122
+ if (flags.title)
123
+ updates.title = flags.title;
124
+ if (flags.status)
125
+ updates.status = flags.status;
126
+ if (flags.type) {
127
+ updates.type = flags.type === 'none' ? undefined : flags.type;
128
+ }
129
+ if (flags.problem)
130
+ updates.problem = flags.problem;
131
+ if (flags.solution)
132
+ updates.solution = flags.solution;
133
+ if (flags.decisions)
134
+ updates.decisions = flags.decisions;
135
+ }
136
+ // Check if anything changed
137
+ if (Object.keys(updates).length === 0) {
138
+ this.log(styles.muted('\nNo changes made.'));
139
+ return;
140
+ }
141
+ // Update the spec
142
+ const updatedSpec = await this.storage.updateSpec(specId, updates);
143
+ // Display updated spec
144
+ this.log(styles.success(`\n✅ Updated spec "${styles.emphasis(updatedSpec.title)}"`));
145
+ const changedFields = [];
146
+ if (updates.title)
147
+ changedFields.push(`Title: ${updatedSpec.title}`);
148
+ if (updates.status)
149
+ changedFields.push(`Status: ${updatedSpec.status}`);
150
+ if (updates.type !== undefined)
151
+ changedFields.push(`Type: ${updatedSpec.type || 'none'}`);
152
+ if (updates.problem !== undefined)
153
+ changedFields.push(`Problem: ${updates.problem ? 'updated' : '(cleared)'}`);
154
+ if (updates.solution !== undefined)
155
+ changedFields.push(`Solution: ${updates.solution ? 'updated' : '(cleared)'}`);
156
+ if (updates.decisions !== undefined)
157
+ changedFields.push(`Decisions: ${updates.decisions ? 'updated' : '(cleared)'}`);
158
+ for (const field of changedFields) {
159
+ this.log(styles.muted(` ${field}`));
160
+ }
161
+ this.log('');
162
+ this.log(styles.muted(`View spec: prlt spec view ${updatedSpec.id}`));
163
+ }
164
+ async promptForEdits(spec, typeChoices, statusChoices) {
165
+ const answers = await inquirer.prompt([
166
+ {
167
+ type: 'input',
168
+ name: 'title',
169
+ message: 'Title:',
170
+ default: spec.title,
171
+ validate: (input) => input.length > 0 || 'Title is required',
172
+ },
173
+ {
174
+ type: 'list',
175
+ name: 'status',
176
+ message: 'Status:',
177
+ choices: statusChoices,
178
+ default: spec.status,
179
+ },
180
+ {
181
+ type: 'list',
182
+ name: 'type',
183
+ message: 'Type:',
184
+ choices: typeChoices,
185
+ default: spec.type || '',
186
+ },
187
+ {
188
+ type: 'editor',
189
+ name: 'problem',
190
+ message: 'Problem statement (opens $EDITOR):',
191
+ default: spec.problem || '',
192
+ waitForUseInput: false,
193
+ },
194
+ {
195
+ type: 'editor',
196
+ name: 'solution',
197
+ message: 'Solution (opens $EDITOR):',
198
+ default: spec.solution || '',
199
+ waitForUseInput: false,
200
+ },
201
+ {
202
+ type: 'editor',
203
+ name: 'decisions',
204
+ message: 'Design decisions (opens $EDITOR):',
205
+ default: spec.decisions || '',
206
+ waitForUseInput: false,
207
+ },
208
+ ]);
209
+ // Build updates object with only changed fields
210
+ const updates = {};
211
+ if (answers.title !== spec.title) {
212
+ updates.title = answers.title;
213
+ }
214
+ if (answers.status !== spec.status) {
215
+ updates.status = answers.status;
216
+ }
217
+ const newType = answers.type === '' ? undefined : answers.type;
218
+ if (newType !== spec.type) {
219
+ updates.type = newType;
220
+ }
221
+ if (answers.problem !== (spec.problem || '')) {
222
+ updates.problem = answers.problem || undefined;
223
+ }
224
+ if (answers.solution !== (spec.solution || '')) {
225
+ updates.solution = answers.solution || undefined;
226
+ }
227
+ if (answers.decisions !== (spec.decisions || '')) {
228
+ updates.decisions = answers.decisions || undefined;
229
+ }
230
+ return updates;
231
+ }
232
+ }
@@ -21,6 +21,7 @@ export default class Spec extends PMOCommand {
21
21
  { name: 'Create new spec', value: 'create' },
22
22
  { name: 'List all specs', value: 'list' },
23
23
  { name: 'View spec', value: 'view' },
24
+ { name: 'Delete spec', value: 'delete' },
24
25
  { name: 'Generate tickets from spec', value: 'generate' },
25
26
  { name: 'Assign ticket to spec', value: 'ticket' },
26
27
  { name: 'Manage dependencies', value: 'link' },
@@ -44,6 +45,7 @@ export default class Spec extends PMOCommand {
44
45
  create: 'prlt spec create --json',
45
46
  list: 'prlt spec list --json',
46
47
  view: 'prlt spec view --json',
48
+ delete: 'prlt spec delete --json',
47
49
  generate: 'prlt spec plan --json',
48
50
  ticket: 'prlt spec ticket --json',
49
51
  link: 'prlt spec link --json',
@@ -68,6 +70,9 @@ export default class Spec extends PMOCommand {
68
70
  case 'view':
69
71
  await this.config.runCommand('spec:view', []);
70
72
  break;
73
+ case 'delete':
74
+ await this.config.runCommand('spec:delete', []);
75
+ break;
71
76
  case 'generate':
72
77
  await this.config.runCommand('spec:generate-tickets', []);
73
78
  break;
@@ -88,40 +88,44 @@ export default class StatusCreate extends PMOCommand {
88
88
  default: flags.category || 'backlog',
89
89
  when: (ctx) => !ctx.flags.category || flags.interactive,
90
90
  });
91
- // Add color prompt
92
- resolver.addPrompt({
93
- flagName: 'color',
94
- type: 'input',
95
- message: 'Color (hex, optional):',
96
- default: flags.color,
97
- validate: (value) => {
98
- const v = value;
99
- if (!v)
100
- return true;
101
- return /^#[0-9A-Fa-f]{6}$/.test(v) || 'Invalid hex color (e.g., #FF0000)';
102
- },
103
- when: (ctx) => ctx.flags.color === undefined || flags.interactive,
104
- });
105
- // Add description prompt
106
- resolver.addPrompt({
107
- flagName: 'description',
108
- type: 'input',
109
- message: 'Description (optional):',
110
- default: flags.description,
111
- when: (ctx) => ctx.flags.description === undefined || flags.interactive,
112
- });
113
- // Add default prompt
114
- resolver.addPrompt({
115
- flagName: 'default',
116
- type: 'list',
117
- message: 'Set as default status for new tickets?',
118
- choices: () => [
119
- { name: 'No', value: false, command: 'prlt status create --json' },
120
- { name: 'Yes', value: true, command: 'prlt status create --default --json' },
121
- ],
122
- default: flags.default || false,
123
- when: (ctx) => ctx.flags.default === undefined || flags.interactive,
124
- });
91
+ // Optional prompts - only shown in interactive mode
92
+ // In JSON/machine mode, optional fields should be passed as flags or omitted
93
+ if (flags.interactive) {
94
+ // Add color prompt
95
+ resolver.addPrompt({
96
+ flagName: 'color',
97
+ type: 'input',
98
+ message: 'Color (hex, optional):',
99
+ default: flags.color,
100
+ validate: (value) => {
101
+ const v = value;
102
+ if (!v)
103
+ return true;
104
+ return /^#[0-9A-Fa-f]{6}$/.test(v) || 'Invalid hex color (e.g., #FF0000)';
105
+ },
106
+ when: (ctx) => ctx.flags.color === undefined,
107
+ });
108
+ // Add description prompt
109
+ resolver.addPrompt({
110
+ flagName: 'description',
111
+ type: 'input',
112
+ message: 'Description (optional):',
113
+ default: flags.description,
114
+ when: (ctx) => ctx.flags.description === undefined,
115
+ });
116
+ // Add default prompt
117
+ resolver.addPrompt({
118
+ flagName: 'default',
119
+ type: 'list',
120
+ message: 'Set as default status for new tickets?',
121
+ choices: () => [
122
+ { name: 'No', value: false, command: 'prlt status create --json' },
123
+ { name: 'Yes', value: true, command: 'prlt status create --default --json' },
124
+ ],
125
+ default: flags.default || false,
126
+ when: (ctx) => ctx.flags.default === undefined,
127
+ });
128
+ }
125
129
  // Resolve all flags
126
130
  const resolved = await resolver.resolve();
127
131
  const status = await this.storage.createStatus(projectId, {