@proletariat/cli 0.3.45 → 0.3.46
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/config/index.js +39 -1
- package/dist/commands/linear/auth.d.ts +14 -0
- package/dist/commands/linear/auth.js +211 -0
- package/dist/commands/linear/import.d.ts +21 -0
- package/dist/commands/linear/import.js +260 -0
- package/dist/commands/linear/status.d.ts +11 -0
- package/dist/commands/linear/status.js +88 -0
- package/dist/commands/linear/sync.d.ts +15 -0
- package/dist/commands/linear/sync.js +233 -0
- package/dist/commands/orchestrator/attach.d.ts +9 -1
- package/dist/commands/orchestrator/attach.js +67 -13
- package/dist/commands/orchestrator/index.js +22 -7
- package/dist/commands/ticket/link/duplicates.d.ts +15 -0
- package/dist/commands/ticket/link/duplicates.js +95 -0
- package/dist/commands/ticket/link/index.js +14 -0
- package/dist/commands/ticket/link/relates.d.ts +15 -0
- package/dist/commands/ticket/link/relates.js +95 -0
- package/dist/commands/work/revise.js +4 -3
- package/dist/commands/work/spawn.d.ts +5 -0
- package/dist/commands/work/spawn.js +195 -14
- package/dist/commands/work/start.js +75 -19
- package/dist/lib/execution/config.d.ts +15 -0
- package/dist/lib/execution/config.js +54 -0
- package/dist/lib/execution/devcontainer.d.ts +6 -3
- package/dist/lib/execution/devcontainer.js +39 -12
- package/dist/lib/execution/runners.d.ts +28 -32
- package/dist/lib/execution/runners.js +345 -275
- package/dist/lib/execution/spawner.js +62 -5
- package/dist/lib/execution/types.d.ts +4 -0
- package/dist/lib/execution/types.js +3 -0
- package/dist/lib/external-issues/adapters.d.ts +26 -0
- package/dist/lib/external-issues/adapters.js +251 -0
- package/dist/lib/external-issues/index.d.ts +10 -0
- package/dist/lib/external-issues/index.js +14 -0
- package/dist/lib/external-issues/mapper.d.ts +21 -0
- package/dist/lib/external-issues/mapper.js +86 -0
- package/dist/lib/external-issues/types.d.ts +144 -0
- package/dist/lib/external-issues/types.js +26 -0
- package/dist/lib/external-issues/validation.d.ts +34 -0
- package/dist/lib/external-issues/validation.js +219 -0
- package/dist/lib/linear/client.d.ts +55 -0
- package/dist/lib/linear/client.js +254 -0
- package/dist/lib/linear/config.d.ts +37 -0
- package/dist/lib/linear/config.js +100 -0
- package/dist/lib/linear/index.d.ts +11 -0
- package/dist/lib/linear/index.js +10 -0
- package/dist/lib/linear/mapper.d.ts +67 -0
- package/dist/lib/linear/mapper.js +219 -0
- package/dist/lib/linear/sync.d.ts +37 -0
- package/dist/lib/linear/sync.js +89 -0
- package/dist/lib/linear/types.d.ts +139 -0
- package/dist/lib/linear/types.js +34 -0
- package/dist/lib/mcp/helpers.d.ts +8 -0
- package/dist/lib/mcp/helpers.js +10 -0
- package/dist/lib/mcp/tools/board.js +63 -11
- package/dist/lib/pmo/schema.d.ts +2 -0
- package/dist/lib/pmo/schema.js +20 -0
- package/dist/lib/pmo/storage/base.js +92 -13
- package/dist/lib/pmo/storage/dependencies.js +15 -0
- package/dist/lib/prompt-json.d.ts +4 -0
- package/oclif.manifest.json +2867 -2380
- package/package.json +2 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PMOCommand } from '../../../lib/pmo/index.js';
|
|
2
|
+
export default class TicketLinkRelates extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
ticket: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
related: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
execute(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { autoExportToBoard, PMOCommand, pmoBaseFlags } from '../../../lib/pmo/index.js';
|
|
3
|
+
import { styles } from '../../../lib/styles.js';
|
|
4
|
+
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../../lib/prompt-json.js';
|
|
5
|
+
export default class TicketLinkRelates extends PMOCommand {
|
|
6
|
+
static description = 'Add a relates-to dependency between two tickets';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> TKT-001',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> TKT-001 --json',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
ticket: Args.string({
|
|
14
|
+
description: 'First ticket',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
related: Args.string({
|
|
18
|
+
description: 'Second ticket (related)',
|
|
19
|
+
required: false,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
static flags = {
|
|
23
|
+
...pmoBaseFlags,
|
|
24
|
+
};
|
|
25
|
+
async execute() {
|
|
26
|
+
const { args, flags } = await this.parse(TicketLinkRelates);
|
|
27
|
+
const jsonMode = shouldOutputJson(flags);
|
|
28
|
+
const projectId = await this.requireProject();
|
|
29
|
+
const handleError = (code, message) => {
|
|
30
|
+
if (jsonMode) {
|
|
31
|
+
outputErrorAsJson(code, message, createMetadata('ticket link relates', flags));
|
|
32
|
+
this.exit(1);
|
|
33
|
+
}
|
|
34
|
+
this.error(message);
|
|
35
|
+
};
|
|
36
|
+
// Verify the source ticket exists
|
|
37
|
+
const ticket = await this.storage.getTicket(args.ticket);
|
|
38
|
+
if (!ticket) {
|
|
39
|
+
return handleError('TICKET_NOT_FOUND', `Ticket not found: ${args.ticket}`);
|
|
40
|
+
}
|
|
41
|
+
// If related ticket not provided, prompt for selection
|
|
42
|
+
if (!args.related) {
|
|
43
|
+
const tickets = await this.storage.listTickets(projectId);
|
|
44
|
+
const otherTickets = tickets.filter(t => t.id !== args.ticket);
|
|
45
|
+
if (otherTickets.length === 0) {
|
|
46
|
+
return handleError('NO_TICKETS', 'No other tickets to select as related.');
|
|
47
|
+
}
|
|
48
|
+
const projectFlag = flags.project ? ` -P ${flags.project}` : '';
|
|
49
|
+
const choices = otherTickets.map(t => ({
|
|
50
|
+
name: `${t.id} - ${t.title}`,
|
|
51
|
+
value: t.id,
|
|
52
|
+
command: `prlt ticket link relates ${args.ticket} ${t.id}${projectFlag} --json`,
|
|
53
|
+
}));
|
|
54
|
+
const message = `Select ticket to relate to ${args.ticket}:`;
|
|
55
|
+
if (jsonMode) {
|
|
56
|
+
outputPromptAsJson(buildPromptConfig('list', 'related', message, choices), createMetadata('ticket link relates', flags));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const { selected } = await this.prompt([{
|
|
60
|
+
type: 'list',
|
|
61
|
+
name: 'selected',
|
|
62
|
+
message,
|
|
63
|
+
choices,
|
|
64
|
+
}], null);
|
|
65
|
+
args.related = selected;
|
|
66
|
+
}
|
|
67
|
+
// Verify related ticket exists
|
|
68
|
+
const relatedTicket = await this.storage.getTicket(args.related);
|
|
69
|
+
if (!relatedTicket) {
|
|
70
|
+
return handleError('RELATED_NOT_FOUND', `Related ticket not found: ${args.related}`);
|
|
71
|
+
}
|
|
72
|
+
// Create the relates_to dependency
|
|
73
|
+
try {
|
|
74
|
+
await this.storage.createTicketDependency(args.ticket, args.related, 'relates_to');
|
|
75
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
76
|
+
if (jsonMode) {
|
|
77
|
+
outputSuccessAsJson({
|
|
78
|
+
ticketId: args.ticket,
|
|
79
|
+
relatedTicketId: args.related,
|
|
80
|
+
type: 'relates_to',
|
|
81
|
+
}, createMetadata('ticket link relates', flags));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.log(styles.success(`\n${args.ticket} now relates to ${args.related}`));
|
|
85
|
+
this.log(styles.muted(` ${ticket.title}`));
|
|
86
|
+
this.log(styles.muted(` relates to: ${relatedTicket.title}`));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof Error && error.message.includes('already exists')) {
|
|
90
|
+
return handleError('ALREADY_EXISTS', 'Relates-to dependency already exists.');
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -8,7 +8,7 @@ import { getWorkColumnSetting, findColumnByName } from '../../lib/pmo/utils.js';
|
|
|
8
8
|
import { styles } from '../../lib/styles.js';
|
|
9
9
|
import { getWorkspaceInfo, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
10
10
|
import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
|
|
11
|
-
import { runExecution, isDockerRunning, isDevcontainerCliInstalled } from '../../lib/execution/runners.js';
|
|
11
|
+
import { runExecution, isDockerRunning, isDevcontainerCliInstalled, getExecutorDisplayName } from '../../lib/execution/runners.js';
|
|
12
12
|
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
13
13
|
import { loadExecutionConfig, getTerminalApp, getShell, hasTerminalPreference, hasShellPreference } from '../../lib/execution/config.js';
|
|
14
14
|
import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
|
|
@@ -242,12 +242,14 @@ export default class WorkRevise extends PMOCommand {
|
|
|
242
242
|
// Host environment: terminal/background are display modes
|
|
243
243
|
displayMode = flags.mode;
|
|
244
244
|
}
|
|
245
|
+
const executor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
|
|
246
|
+
const executorName = getExecutorDisplayName(executor);
|
|
245
247
|
// Permission mode
|
|
246
248
|
const { permissionMode } = await this.prompt([
|
|
247
249
|
{
|
|
248
250
|
type: 'list',
|
|
249
251
|
name: 'permissionMode',
|
|
250
|
-
message:
|
|
252
|
+
message: `Permission mode for ${executorName}:`,
|
|
251
253
|
choices: [
|
|
252
254
|
{ name: 'danger - Skip permission checks (faster for revisions)', value: 'danger', command: `prlt work revise ${ticketId} --json` },
|
|
253
255
|
{ name: 'safe - Requires approval for dangerous operations', value: 'safe', command: `prlt work revise ${ticketId} --json` },
|
|
@@ -256,7 +258,6 @@ export default class WorkRevise extends PMOCommand {
|
|
|
256
258
|
},
|
|
257
259
|
], reviseJsonModeConfig);
|
|
258
260
|
sandboxed = permissionMode === 'safe';
|
|
259
|
-
const executor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
|
|
260
261
|
// Show execution info
|
|
261
262
|
this.log('');
|
|
262
263
|
this.log(styles.header(`Revising: ${ticket.id}: ${ticket.title}`));
|
|
@@ -31,6 +31,11 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
31
31
|
priority: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
32
32
|
epic: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
33
33
|
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
34
|
+
'from-linear': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
35
|
+
'linear-team': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
36
|
+
'linear-state': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
37
|
+
'linear-label': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
38
|
+
'linear-limit': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
34
39
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
35
40
|
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
36
41
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -4,10 +4,12 @@ import Database from 'better-sqlite3';
|
|
|
4
4
|
import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo, getTicketTmuxSession, killTmuxSession } from '../../lib/agents/commands.js';
|
|
7
|
-
import { isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled } from '../../lib/execution/runners.js';
|
|
7
|
+
import { isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled, getExecutorDisplayName } from '../../lib/execution/runners.js';
|
|
8
|
+
import { getCreatePrDefault, loadExecutionConfig } from '../../lib/execution/config.js';
|
|
8
9
|
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, outputConfirmationNeededAsJson, outputExecutionResultAsJson, } from '../../lib/prompt-json.js';
|
|
9
10
|
import { FlagResolver } from '../../lib/flags/index.js';
|
|
10
11
|
import { loadDietConfig, formatDietConfig, } from '../../lib/pmo/diet.js';
|
|
12
|
+
import { LinearClient, LinearMapper, isLinearConfigured, loadLinearConfig, } from '../../lib/linear/index.js';
|
|
11
13
|
export default class WorkSpawn extends PMOCommand {
|
|
12
14
|
static description = 'Spawn work for multiple tickets by column (batch mode)';
|
|
13
15
|
static strict = false; // Allow multiple ticket ID args without defining them
|
|
@@ -24,6 +26,10 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
24
26
|
'<%= config.bin %> <%= command.id %> --count 10 --diet --action groom # Diet-balanced',
|
|
25
27
|
'<%= config.bin %> <%= command.id %> --count 5 --category ship --action implement # Filtered by category',
|
|
26
28
|
'<%= config.bin %> <%= command.id %> --count 5 --priority P0 --action implement # Filtered by priority',
|
|
29
|
+
'<%= config.bin %> <%= command.id %> --from-linear # Pull Linear issues → PMO → spawn',
|
|
30
|
+
'<%= config.bin %> <%= command.id %> --from-linear ENG-123 ENG-124 # Pull specific Linear issues → spawn',
|
|
31
|
+
'<%= config.bin %> <%= command.id %> --from-linear --linear-team ENG # Pull from specific team',
|
|
32
|
+
'<%= config.bin %> <%= command.id %> --from-linear --linear-state "In Progress" # Filter by state',
|
|
27
33
|
'<%= config.bin %> <%= command.id %> --count 10 --diet --category ship,grow --action groom # Combined',
|
|
28
34
|
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002 --create-pr # Create PR when work is ready',
|
|
29
35
|
];
|
|
@@ -141,6 +147,28 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
141
147
|
status: Flags.string({
|
|
142
148
|
description: 'Filter tickets by status name (e.g., Backlog, Ready)',
|
|
143
149
|
}),
|
|
150
|
+
// Linear integration flags
|
|
151
|
+
'from-linear': Flags.boolean({
|
|
152
|
+
description: 'Pull issues from Linear, mirror into PMO, then spawn agents',
|
|
153
|
+
default: false,
|
|
154
|
+
}),
|
|
155
|
+
'linear-team': Flags.string({
|
|
156
|
+
description: 'Linear team key to pull issues from (e.g., ENG)',
|
|
157
|
+
dependsOn: ['from-linear'],
|
|
158
|
+
}),
|
|
159
|
+
'linear-state': Flags.string({
|
|
160
|
+
description: 'Filter Linear issues by state name (e.g., "In Progress")',
|
|
161
|
+
dependsOn: ['from-linear'],
|
|
162
|
+
}),
|
|
163
|
+
'linear-label': Flags.string({
|
|
164
|
+
description: 'Filter Linear issues by label name',
|
|
165
|
+
dependsOn: ['from-linear'],
|
|
166
|
+
}),
|
|
167
|
+
'linear-limit': Flags.integer({
|
|
168
|
+
description: 'Maximum number of Linear issues to pull',
|
|
169
|
+
default: 20,
|
|
170
|
+
dependsOn: ['from-linear'],
|
|
171
|
+
}),
|
|
144
172
|
};
|
|
145
173
|
async execute() {
|
|
146
174
|
const { flags, argv } = await this.parse(WorkSpawn);
|
|
@@ -154,12 +182,121 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
154
182
|
}
|
|
155
183
|
this.error(message);
|
|
156
184
|
};
|
|
185
|
+
// Check for conflicting PR flags (before deprecation warning to fail fast)
|
|
186
|
+
if (flags['create-pr'] && flags['no-pr']) {
|
|
187
|
+
this.error('--create-pr and --no-pr are mutually exclusive');
|
|
188
|
+
}
|
|
157
189
|
// Deprecation guidance for --no-pr
|
|
158
190
|
if (flags['no-pr']) {
|
|
159
191
|
this.warn('--no-pr is deprecated. Omit --create-pr instead (PR creation is off by default). --no-pr will continue to work.');
|
|
160
192
|
}
|
|
161
193
|
// Parse ticket IDs from args (everything after flags)
|
|
162
|
-
|
|
194
|
+
let ticketIdArgs = argv;
|
|
195
|
+
// =========================================================================
|
|
196
|
+
// --from-linear: Pull Linear issues → mirror into PMO → spawn from PMO
|
|
197
|
+
// =========================================================================
|
|
198
|
+
if (flags['from-linear']) {
|
|
199
|
+
const db = this.storage.getDatabase();
|
|
200
|
+
if (!isLinearConfigured(db)) {
|
|
201
|
+
return handleError('LINEAR_NOT_CONFIGURED', 'Linear is not configured. Run "prlt linear auth" first.');
|
|
202
|
+
}
|
|
203
|
+
const linearConfig = loadLinearConfig(db);
|
|
204
|
+
const linearClient = new LinearClient(linearConfig.apiKey);
|
|
205
|
+
const mapper = new LinearMapper(db);
|
|
206
|
+
// Determine project first (needed for ticket creation)
|
|
207
|
+
const linearProjectId = await this.requireProject({
|
|
208
|
+
jsonMode: jsonMode ? {
|
|
209
|
+
flags,
|
|
210
|
+
commandName: 'work spawn',
|
|
211
|
+
baseCommand: 'prlt work spawn --from-linear',
|
|
212
|
+
} : undefined,
|
|
213
|
+
});
|
|
214
|
+
// Get project workflow statuses for mapping
|
|
215
|
+
const workflow = await this.storage.getProjectWorkflow(linearProjectId);
|
|
216
|
+
if (!workflow) {
|
|
217
|
+
return handleError('NO_WORKFLOW', 'Project has no workflow configured.');
|
|
218
|
+
}
|
|
219
|
+
const statuses = await this.storage.listStatuses(workflow.id);
|
|
220
|
+
// Check if args are Linear identifiers (e.g., ENG-123)
|
|
221
|
+
const linearIdentifiers = ticketIdArgs.filter((id) => /^[A-Z]+-\d+$/i.test(id));
|
|
222
|
+
const pmoTicketIds = [];
|
|
223
|
+
if (linearIdentifiers.length > 0) {
|
|
224
|
+
// Fetch and import specific Linear issues
|
|
225
|
+
if (!jsonMode) {
|
|
226
|
+
this.log(styles.muted(`Pulling ${linearIdentifiers.length} issue(s) from Linear...`));
|
|
227
|
+
}
|
|
228
|
+
for (const identifier of linearIdentifiers) {
|
|
229
|
+
// eslint-disable-next-line no-await-in-loop
|
|
230
|
+
const issue = await linearClient.getIssueByIdentifier(identifier);
|
|
231
|
+
if (!issue) {
|
|
232
|
+
if (!jsonMode)
|
|
233
|
+
this.warn(`Linear issue not found: ${identifier}`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// eslint-disable-next-line no-await-in-loop
|
|
237
|
+
const { ticketId, created } = await mapper.importIssue(issue, linearProjectId, this.storage, statuses);
|
|
238
|
+
pmoTicketIds.push(ticketId);
|
|
239
|
+
if (!jsonMode) {
|
|
240
|
+
const action = created ? 'Imported' : 'Already imported';
|
|
241
|
+
this.log(styles.muted(` ${action}: ${identifier} → ${ticketId}`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Fetch issues using filters
|
|
247
|
+
const filter = {
|
|
248
|
+
teamKey: flags['linear-team'] ?? linearConfig.defaultTeamKey,
|
|
249
|
+
stateName: flags['linear-state'],
|
|
250
|
+
labelName: flags['linear-label'],
|
|
251
|
+
limit: flags['linear-limit'],
|
|
252
|
+
};
|
|
253
|
+
if (!jsonMode) {
|
|
254
|
+
this.log(styles.muted(`Pulling issues from Linear (team: ${filter.teamKey ?? 'all'})...`));
|
|
255
|
+
}
|
|
256
|
+
const issues = await linearClient.listIssues(filter);
|
|
257
|
+
if (issues.length === 0) {
|
|
258
|
+
if (jsonMode) {
|
|
259
|
+
outputSuccessAsJson({
|
|
260
|
+
imported: 0,
|
|
261
|
+
spawned: 0,
|
|
262
|
+
message: 'No matching Linear issues found.',
|
|
263
|
+
}, createMetadata('work spawn', flags));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
this.log(styles.muted('No matching Linear issues found.'));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (!jsonMode) {
|
|
270
|
+
this.log(styles.muted(`Found ${issues.length} issue(s). Importing into PMO...`));
|
|
271
|
+
}
|
|
272
|
+
const result = await mapper.importIssues(issues, linearProjectId, this.storage, statuses);
|
|
273
|
+
if (!jsonMode) {
|
|
274
|
+
if (result.imported > 0)
|
|
275
|
+
this.log(styles.success(` Imported: ${result.imported}`));
|
|
276
|
+
if (result.skipped > 0)
|
|
277
|
+
this.log(styles.muted(` Already imported: ${result.skipped}`));
|
|
278
|
+
if (result.errors.length > 0)
|
|
279
|
+
this.log(styles.error(` Errors: ${result.errors.length}`));
|
|
280
|
+
}
|
|
281
|
+
// Collect all PMO ticket IDs (both new and existing)
|
|
282
|
+
for (const issue of issues) {
|
|
283
|
+
const existing = mapper.getByLinearId(issue.id);
|
|
284
|
+
if (existing) {
|
|
285
|
+
pmoTicketIds.push(existing.pmoTicketId);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (pmoTicketIds.length === 0) {
|
|
290
|
+
return handleError('NO_LINEAR_ISSUES', 'No Linear issues were imported. Nothing to spawn.');
|
|
291
|
+
}
|
|
292
|
+
if (!jsonMode) {
|
|
293
|
+
this.log('');
|
|
294
|
+
this.log(styles.header(`Spawning agents for ${pmoTicketIds.length} imported ticket(s)...`));
|
|
295
|
+
this.log('');
|
|
296
|
+
}
|
|
297
|
+
// Replace ticket args with the imported PMO ticket IDs
|
|
298
|
+
ticketIdArgs = pmoTicketIds;
|
|
299
|
+
}
|
|
163
300
|
// Try to infer project from ticket IDs if provided
|
|
164
301
|
let projectId;
|
|
165
302
|
if (ticketIdArgs.length > 0) {
|
|
@@ -889,12 +1026,16 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
889
1026
|
return;
|
|
890
1027
|
}
|
|
891
1028
|
// Batch mode settings - prompt once for all tickets
|
|
1029
|
+
const executionConfig = loadExecutionConfig(db);
|
|
1030
|
+
const effectiveExecutor = flags.executor || executionConfig.defaultExecutor;
|
|
1031
|
+
const executorName = getExecutorDisplayName(effectiveExecutor);
|
|
892
1032
|
let batchDisplay = flags.display;
|
|
893
1033
|
let batchOutput = flags.output;
|
|
894
1034
|
// Track permission mode - default to 'safe', check flag to determine if prompting needed
|
|
895
1035
|
let batchPermissionMode = flags['skip-permissions'] ? 'danger' : 'safe';
|
|
896
1036
|
let batchCreatePr = flags['create-pr'];
|
|
897
1037
|
let batchNoPr = flags['no-pr'];
|
|
1038
|
+
let batchPrModeSource = flags['create-pr'] ? 'flag --create-pr' : flags['no-pr'] ? 'flag --no-pr' : 'default';
|
|
898
1039
|
let batchRunOnHost = flags['run-on-host'];
|
|
899
1040
|
let batchAction = flags.action;
|
|
900
1041
|
// Track custom message for custom action (needs to be outside the if block)
|
|
@@ -1045,10 +1186,12 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1045
1186
|
if (modifiesCode) {
|
|
1046
1187
|
batchCreatePr = true;
|
|
1047
1188
|
batchNoPr = false;
|
|
1189
|
+
batchPrModeSource = 'batch defaults';
|
|
1048
1190
|
}
|
|
1049
1191
|
else {
|
|
1050
1192
|
batchCreatePr = false;
|
|
1051
1193
|
batchNoPr = true;
|
|
1194
|
+
batchPrModeSource = 'action (non-code-modifying)';
|
|
1052
1195
|
}
|
|
1053
1196
|
this.log('');
|
|
1054
1197
|
}
|
|
@@ -1225,7 +1368,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1225
1368
|
permissionResolver.addPrompt({
|
|
1226
1369
|
flagName: 'permissionMode',
|
|
1227
1370
|
type: 'list',
|
|
1228
|
-
message:
|
|
1371
|
+
message: `Permission mode for ${executorName}:`,
|
|
1229
1372
|
default: 'danger',
|
|
1230
1373
|
choices: () => [
|
|
1231
1374
|
{ name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger' },
|
|
@@ -1236,14 +1379,28 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1236
1379
|
batchPermissionMode = permissionResult.permissionMode;
|
|
1237
1380
|
}
|
|
1238
1381
|
// Prompt for PR creation if not provided AND action modifies code
|
|
1239
|
-
//
|
|
1382
|
+
// Resolution order: explicit flags > workspace config default > interactive prompt
|
|
1240
1383
|
const actionModifiesCode = selectedActionDetails?.modifiesCode ?? true;
|
|
1241
1384
|
if (!batchCreatePr && !batchNoPr) {
|
|
1242
|
-
if (actionModifiesCode) {
|
|
1243
|
-
//
|
|
1244
|
-
|
|
1385
|
+
if (!actionModifiesCode) {
|
|
1386
|
+
// Non-code-modifying action - no PR needed
|
|
1387
|
+
batchCreatePr = false;
|
|
1388
|
+
batchNoPr = true;
|
|
1389
|
+
batchPrModeSource = 'action (non-code-modifying)';
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
// Check workspace config default
|
|
1393
|
+
const configPrDefault = getCreatePrDefault(db);
|
|
1394
|
+
if (configPrDefault !== null) {
|
|
1395
|
+
batchCreatePr = configPrDefault;
|
|
1396
|
+
batchNoPr = !configPrDefault;
|
|
1397
|
+
batchPrModeSource = 'workspace config (execution.create_pr_default)';
|
|
1398
|
+
}
|
|
1399
|
+
else if (jsonMode && flags.yes) {
|
|
1400
|
+
// In JSON mode with --yes, default to creating PRs for code-modifying actions
|
|
1245
1401
|
batchCreatePr = true;
|
|
1246
1402
|
batchNoPr = false;
|
|
1403
|
+
batchPrModeSource = 'default (--json --yes)';
|
|
1247
1404
|
}
|
|
1248
1405
|
else {
|
|
1249
1406
|
// Use FlagResolver for PR choice
|
|
@@ -1266,12 +1423,24 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1266
1423
|
const prResult = await prResolver.resolve();
|
|
1267
1424
|
batchCreatePr = prResult.prChoice === 'yes';
|
|
1268
1425
|
batchNoPr = prResult.prChoice === 'no';
|
|
1426
|
+
batchPrModeSource = 'interactive prompt';
|
|
1269
1427
|
}
|
|
1270
1428
|
}
|
|
1429
|
+
}
|
|
1430
|
+
// Show effective PR mode in batch summary
|
|
1431
|
+
if (!jsonMode) {
|
|
1432
|
+
const prModeLabel = batchCreatePr ? 'create-pr' : 'no-pr';
|
|
1433
|
+
if (batchCreatePr) {
|
|
1434
|
+
this.log(styles.success(` PR mode: ${prModeLabel} (${batchPrModeSource})`));
|
|
1435
|
+
}
|
|
1271
1436
|
else {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1437
|
+
this.log(styles.warning(` PR mode: ${prModeLabel} (${batchPrModeSource})`));
|
|
1438
|
+
}
|
|
1439
|
+
// Strong warning when no-pr is active for code-modifying actions
|
|
1440
|
+
if (!batchCreatePr && actionModifiesCode) {
|
|
1441
|
+
this.log('');
|
|
1442
|
+
this.log(styles.warning(` ⚠️ WARNING: PR creation is DISABLED. Branches will be pushed but NO pull requests will be created.`));
|
|
1443
|
+
this.log(styles.warning(` To create PRs later: prlt pr create <ticket-id>`));
|
|
1275
1444
|
}
|
|
1276
1445
|
}
|
|
1277
1446
|
this.log('');
|
|
@@ -1400,17 +1569,29 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1400
1569
|
});
|
|
1401
1570
|
db.close();
|
|
1402
1571
|
// Output results
|
|
1572
|
+
const resolvedPRMode = batchCreatePr ? 'create-pr' : 'no-pr';
|
|
1403
1573
|
if (jsonMode) {
|
|
1404
|
-
// Output JSON execution results with resolved PR mode
|
|
1574
|
+
// Output JSON execution results with resolved PR mode and source
|
|
1405
1575
|
const spawnMetadata = createMetadata('work spawn', flags);
|
|
1406
|
-
spawnMetadata.resolvedPRMode =
|
|
1576
|
+
spawnMetadata.resolvedPRMode = resolvedPRMode;
|
|
1577
|
+
spawnMetadata.prModeSource = batchPrModeSource;
|
|
1578
|
+
if (!batchCreatePr && (selectedActionDetails?.modifiesCode ?? true)) {
|
|
1579
|
+
spawnMetadata.prWarning = `PR creation is DISABLED (${batchPrModeSource}). Branches will be pushed without PRs. To create later: prlt pr create <ticket-id>`;
|
|
1580
|
+
}
|
|
1407
1581
|
outputExecutionResultAsJson(executionResults, successCount, failCount, spawnMetadata);
|
|
1408
1582
|
}
|
|
1409
1583
|
else {
|
|
1410
|
-
const resolvedPRMode = flags['create-pr'] ? 'create-pr' : 'no-pr';
|
|
1411
1584
|
this.log('');
|
|
1412
1585
|
this.log(styles.success(`✓ Spawn results: ${successCount} started, ${failCount} failed`));
|
|
1413
|
-
this.log(styles.muted(` PR mode: ${resolvedPRMode}`));
|
|
1586
|
+
this.log(styles.muted(` PR mode: ${resolvedPRMode} (${batchPrModeSource})`));
|
|
1587
|
+
// Post-run reminder when branches pushed without PRs
|
|
1588
|
+
if (!batchCreatePr && (selectedActionDetails?.modifiesCode ?? true)) {
|
|
1589
|
+
this.log('');
|
|
1590
|
+
this.log(styles.warning(`Note: No PRs will be auto-created. To create PRs later:`));
|
|
1591
|
+
for (const result of executionResults.filter(r => r.status === 'running')) {
|
|
1592
|
+
this.log(styles.warning(` prlt pr create ${result.ticketId}`));
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1414
1595
|
}
|
|
1415
1596
|
}
|
|
1416
1597
|
catch (error) {
|