@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.
- package/dist/commands/agent/staff/remove.d.ts +1 -0
- package/dist/commands/agent/staff/remove.js +34 -26
- package/dist/commands/agent/temp/cleanup.js +10 -17
- 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/execution/config.d.ts +34 -0
- package/dist/commands/execution/config.js +411 -0
- package/dist/commands/execution/index.js +6 -1
- package/dist/commands/execution/kill.d.ts +9 -0
- package/dist/commands/execution/kill.js +16 -0
- package/dist/commands/execution/view.d.ts +17 -0
- package/dist/commands/execution/view.js +288 -0
- package/dist/commands/phase/template/create.js +67 -20
- 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/template/phase/create.d.ts +1 -0
- package/dist/commands/template/phase/create.js +10 -1
- package/dist/commands/template/ticket/create.d.ts +20 -0
- package/dist/commands/template/ticket/create.js +87 -0
- 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 +7 -0
- 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/work/ready.js +8 -8
- 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/pmo/utils.d.ts +4 -2
- package/dist/lib/pmo/utils.js +4 -2
- package/oclif.manifest.json +3017 -2243
- package/package.json +2 -4
|
@@ -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) {
|
|
@@ -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
|
}
|
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
|
}
|
package/dist/lib/branch/index.js
CHANGED
|
@@ -129,6 +129,14 @@ export function validateBranchName(name) {
|
|
|
129
129
|
if (parts.length === 2) {
|
|
130
130
|
// {type}/{description}
|
|
131
131
|
const description = parts[1];
|
|
132
|
+
// Check if description looks like a ticket ID (user put ticket in wrong position)
|
|
133
|
+
if (isTicketId(description)) {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
error: `Segment "${description}" looks like a ticket ID, but ticket IDs must be the first segment. ` +
|
|
137
|
+
`Expected format: {ticketId}/{type}/{description}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
132
140
|
if (!isKebabCase(description)) {
|
|
133
141
|
return {
|
|
134
142
|
valid: false,
|
|
@@ -143,12 +151,28 @@ export function validateBranchName(name) {
|
|
|
143
151
|
// {type}/{owner}/{description}
|
|
144
152
|
const owner = parts[1];
|
|
145
153
|
const description = parts[2];
|
|
154
|
+
// Check if owner looks like a ticket ID (user put ticket in wrong position)
|
|
155
|
+
if (isTicketId(owner)) {
|
|
156
|
+
return {
|
|
157
|
+
valid: false,
|
|
158
|
+
error: `Segment "${owner}" looks like a ticket ID, but it's in the owner position (segment 2). ` +
|
|
159
|
+
`Ticket IDs must be the first segment. Expected format: {ticketId}/{type}/{owner}/{description}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
146
162
|
if (!isKebabCase(owner)) {
|
|
147
163
|
return {
|
|
148
164
|
valid: false,
|
|
149
165
|
error: `Owner name must be kebab-case: "${owner}"`,
|
|
150
166
|
};
|
|
151
167
|
}
|
|
168
|
+
// Check if description looks like a ticket ID (user put ticket in wrong position)
|
|
169
|
+
if (isTicketId(description)) {
|
|
170
|
+
return {
|
|
171
|
+
valid: false,
|
|
172
|
+
error: `Segment "${description}" looks like a ticket ID, but it's in the description position (segment 3). ` +
|
|
173
|
+
`Ticket IDs must be the first segment.`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
152
176
|
if (!isKebabCase(description)) {
|
|
153
177
|
return {
|
|
154
178
|
valid: false,
|
|
@@ -18,6 +18,8 @@ const CONFIG_KEYS = {
|
|
|
18
18
|
defaultMode: 'execution.default_mode',
|
|
19
19
|
defaultExecutor: 'execution.default_executor',
|
|
20
20
|
autoExecute: 'execution.auto_execute',
|
|
21
|
+
outputMode: 'execution.output_mode',
|
|
22
|
+
sandboxed: 'execution.sandboxed',
|
|
21
23
|
tmuxSession: 'execution.tmux.session',
|
|
22
24
|
tmuxLayout: 'execution.tmux.layout',
|
|
23
25
|
tmuxControlMode: 'execution.tmux.control_mode',
|
|
@@ -85,6 +87,16 @@ export function loadExecutionConfig(db) {
|
|
|
85
87
|
if (autoExecute !== null) {
|
|
86
88
|
config.autoExecute = autoExecute === 'true';
|
|
87
89
|
}
|
|
90
|
+
// Load output mode
|
|
91
|
+
const outputMode = getSetting(db, CONFIG_KEYS.outputMode);
|
|
92
|
+
if (outputMode) {
|
|
93
|
+
config.outputMode = outputMode;
|
|
94
|
+
}
|
|
95
|
+
// Load sandboxed preference
|
|
96
|
+
const sandboxed = getSetting(db, CONFIG_KEYS.sandboxed);
|
|
97
|
+
if (sandboxed !== null) {
|
|
98
|
+
config.sandboxed = sandboxed === 'true';
|
|
99
|
+
}
|
|
88
100
|
// Load tmux settings
|
|
89
101
|
const tmuxSession = getSetting(db, CONFIG_KEYS.tmuxSession);
|
|
90
102
|
if (tmuxSession) {
|
package/dist/lib/pmo/utils.d.ts
CHANGED
|
@@ -75,14 +75,16 @@ export declare function deepClone<T>(obj: T): T;
|
|
|
75
75
|
/**
|
|
76
76
|
* Default column names for work commands (Linear-style workflow)
|
|
77
77
|
*
|
|
78
|
-
* Linear-style: Backlog → Planned → In Progress → Done
|
|
78
|
+
* Linear-style: Backlog → Planned → In Progress → Review → Done
|
|
79
79
|
* - planned: Move tickets here when scheduled/assigned
|
|
80
80
|
* - in_progress: Move tickets here when work starts
|
|
81
|
-
* -
|
|
81
|
+
* - review: Move tickets here when work is ready for review
|
|
82
|
+
* - done: Move tickets here when work is complete (reviewed/merged)
|
|
82
83
|
*/
|
|
83
84
|
export declare const DEFAULT_WORK_COLUMNS: {
|
|
84
85
|
readonly planned: "Planned";
|
|
85
86
|
readonly in_progress: "In Progress";
|
|
87
|
+
readonly review: "Review";
|
|
86
88
|
readonly done: "Done";
|
|
87
89
|
};
|
|
88
90
|
export type WorkColumnType = keyof typeof DEFAULT_WORK_COLUMNS;
|
package/dist/lib/pmo/utils.js
CHANGED
|
@@ -122,14 +122,16 @@ export function deepClone(obj) {
|
|
|
122
122
|
/**
|
|
123
123
|
* Default column names for work commands (Linear-style workflow)
|
|
124
124
|
*
|
|
125
|
-
* Linear-style: Backlog → Planned → In Progress → Done
|
|
125
|
+
* Linear-style: Backlog → Planned → In Progress → Review → Done
|
|
126
126
|
* - planned: Move tickets here when scheduled/assigned
|
|
127
127
|
* - in_progress: Move tickets here when work starts
|
|
128
|
-
* -
|
|
128
|
+
* - review: Move tickets here when work is ready for review
|
|
129
|
+
* - done: Move tickets here when work is complete (reviewed/merged)
|
|
129
130
|
*/
|
|
130
131
|
export const DEFAULT_WORK_COLUMNS = {
|
|
131
132
|
planned: 'Planned',
|
|
132
133
|
in_progress: 'In Progress',
|
|
134
|
+
review: 'Review',
|
|
133
135
|
done: 'Done',
|
|
134
136
|
};
|
|
135
137
|
/**
|