@proletariat/cli 0.3.46 → 0.3.48
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/bin/validate-better-sqlite3.cjs +55 -0
- package/dist/commands/caffeinate/index.d.ts +10 -0
- package/dist/commands/caffeinate/index.js +64 -0
- package/dist/commands/caffeinate/start.d.ts +14 -0
- package/dist/commands/caffeinate/start.js +86 -0
- package/dist/commands/caffeinate/status.d.ts +10 -0
- package/dist/commands/caffeinate/status.js +55 -0
- package/dist/commands/caffeinate/stop.d.ts +10 -0
- package/dist/commands/caffeinate/stop.js +47 -0
- package/dist/commands/commit.js +10 -8
- package/dist/commands/config/index.js +2 -3
- package/dist/commands/init.js +9 -1
- package/dist/commands/orchestrator/attach.d.ts +1 -0
- package/dist/commands/orchestrator/attach.js +104 -24
- package/dist/commands/orchestrator/index.js +2 -2
- package/dist/commands/orchestrator/start.d.ts +13 -1
- package/dist/commands/orchestrator/start.js +115 -34
- package/dist/commands/orchestrator/status.d.ts +1 -0
- package/dist/commands/orchestrator/status.js +68 -22
- package/dist/commands/orchestrator/stop.d.ts +1 -0
- package/dist/commands/orchestrator/stop.js +50 -13
- package/dist/commands/session/attach.js +55 -9
- package/dist/commands/session/poke.js +1 -1
- package/dist/commands/work/index.js +8 -0
- package/dist/commands/work/linear.d.ts +24 -0
- package/dist/commands/work/linear.js +195 -0
- package/dist/commands/work/review.d.ts +45 -0
- package/dist/commands/work/review.js +401 -0
- package/dist/commands/work/spawn.js +28 -19
- package/dist/commands/work/start.js +12 -2
- package/dist/hooks/init.js +26 -5
- package/dist/lib/caffeinate.d.ts +64 -0
- package/dist/lib/caffeinate.js +146 -0
- package/dist/lib/database/native-validation.d.ts +21 -0
- package/dist/lib/database/native-validation.js +49 -0
- package/dist/lib/execution/codex-adapter.d.ts +96 -0
- package/dist/lib/execution/codex-adapter.js +148 -0
- package/dist/lib/execution/index.d.ts +1 -0
- package/dist/lib/execution/index.js +1 -0
- package/dist/lib/execution/runners.js +56 -6
- package/dist/lib/external-issues/index.d.ts +1 -1
- package/dist/lib/external-issues/index.js +1 -1
- package/dist/lib/external-issues/linear.d.ts +37 -0
- package/dist/lib/external-issues/linear.js +198 -0
- package/dist/lib/external-issues/types.d.ts +67 -0
- package/dist/lib/external-issues/types.js +41 -0
- package/dist/lib/init/index.d.ts +4 -0
- package/dist/lib/init/index.js +11 -1
- package/dist/lib/machine-config.d.ts +1 -0
- package/dist/lib/machine-config.js +6 -3
- package/dist/lib/mcp/tools/work.js +36 -0
- package/dist/lib/pmo/storage/actions.js +3 -3
- package/dist/lib/pmo/storage/base.js +85 -6
- package/dist/lib/pmo/storage/epics.js +1 -1
- package/dist/lib/pmo/storage/tickets.js +2 -2
- package/dist/lib/pmo/storage/types.d.ts +2 -1
- package/oclif.manifest.json +4158 -3651
- package/package.json +2 -2
|
@@ -6,7 +6,8 @@ import * as os from 'node:os';
|
|
|
6
6
|
import Database from 'better-sqlite3';
|
|
7
7
|
import { styles } from '../../lib/styles.js';
|
|
8
8
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
9
|
-
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
9
|
+
import { ExecutionStorage, loadExecutionConfig, shouldUseControlMode, buildTmuxAttachCommand } from '../../lib/execution/index.js';
|
|
10
|
+
import { detectTerminalApp } from '../orchestrator/attach.js';
|
|
10
11
|
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findContainerSessionsByPrefix, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
11
12
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
12
13
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -106,12 +107,32 @@ export default class SessionAttach extends PMOCommand {
|
|
|
106
107
|
// Attach to the session
|
|
107
108
|
this.log('');
|
|
108
109
|
this.log(styles.info(`Attaching to session: ${selectedSession.sessionId}`));
|
|
110
|
+
// Determine if we should use tmux control mode (-u -CC) for iTerm
|
|
111
|
+
let useControlMode = false;
|
|
112
|
+
try {
|
|
113
|
+
const workspaceInfo = getWorkspaceInfo();
|
|
114
|
+
const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
|
|
115
|
+
const db = new Database(dbPath);
|
|
116
|
+
try {
|
|
117
|
+
const config = loadExecutionConfig(db);
|
|
118
|
+
const termApp = detectTerminalApp();
|
|
119
|
+
if (termApp === 'iTerm') {
|
|
120
|
+
useControlMode = shouldUseControlMode('iTerm', config.tmux.controlMode);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
db.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Not in a workspace or DB not available - fall back to no control mode
|
|
129
|
+
}
|
|
109
130
|
// Default to new tab unless --current-terminal is specified
|
|
110
131
|
if (flags['current-terminal']) {
|
|
111
|
-
await this.attachInCurrentTerminal(selectedSession);
|
|
132
|
+
await this.attachInCurrentTerminal(selectedSession, useControlMode);
|
|
112
133
|
}
|
|
113
134
|
else {
|
|
114
|
-
await this.attachInNewTab(selectedSession, flags.terminal);
|
|
135
|
+
await this.attachInNewTab(selectedSession, flags.terminal, useControlMode);
|
|
115
136
|
}
|
|
116
137
|
}
|
|
117
138
|
/**
|
|
@@ -247,13 +268,29 @@ export default class SessionAttach extends PMOCommand {
|
|
|
247
268
|
/**
|
|
248
269
|
* Attach to session in current terminal
|
|
249
270
|
*/
|
|
250
|
-
async attachInCurrentTerminal(session) {
|
|
271
|
+
async attachInCurrentTerminal(session, useControlMode) {
|
|
251
272
|
try {
|
|
273
|
+
// Set mouse mode based on attach type:
|
|
274
|
+
// - Plain terminal: mouse on (enables scroll in tmux; hold Shift/Option to bypass)
|
|
275
|
+
// - iTerm -CC: mouse off (iTerm handles scrolling natively)
|
|
276
|
+
const mouseMode = useControlMode ? 'off' : 'on';
|
|
277
|
+
try {
|
|
278
|
+
if (session.type === 'container' && session.containerId) {
|
|
279
|
+
execSync(`docker exec ${session.containerId} tmux set-option -t "${session.sessionId}" mouse ${mouseMode}`, { stdio: 'pipe' });
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
execSync(`tmux set-option -t "${session.sessionId}" mouse ${mouseMode}`, { stdio: 'pipe' });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// Non-fatal: mouse mode is a convenience, don't block attach
|
|
287
|
+
}
|
|
288
|
+
const tmuxAttach = buildTmuxAttachCommand(useControlMode, session.type === 'container');
|
|
252
289
|
if (session.type === 'container' && session.containerId) {
|
|
253
|
-
execSync(`docker exec -it ${session.containerId}
|
|
290
|
+
execSync(`docker exec -it ${session.containerId} ${tmuxAttach} -t "${session.sessionId}"`, { stdio: 'inherit' });
|
|
254
291
|
}
|
|
255
292
|
else {
|
|
256
|
-
execSync(
|
|
293
|
+
execSync(`${tmuxAttach} -t "${session.sessionId}"`, { stdio: 'inherit' });
|
|
257
294
|
}
|
|
258
295
|
}
|
|
259
296
|
catch {
|
|
@@ -263,7 +300,7 @@ export default class SessionAttach extends PMOCommand {
|
|
|
263
300
|
/**
|
|
264
301
|
* Attach to session in a new terminal tab
|
|
265
302
|
*/
|
|
266
|
-
async attachInNewTab(session, terminalApp) {
|
|
303
|
+
async attachInNewTab(session, terminalApp, useControlMode) {
|
|
267
304
|
// Build a readable title for the tab
|
|
268
305
|
const title = `${session.ticketId} (${session.agentName})`;
|
|
269
306
|
// Create a script that sets tab title and attaches to tmux
|
|
@@ -271,14 +308,23 @@ export default class SessionAttach extends PMOCommand {
|
|
|
271
308
|
fs.mkdirSync(baseDir, { recursive: true });
|
|
272
309
|
const scriptPath = path.join(baseDir, `attach-${Date.now()}.sh`);
|
|
273
310
|
// Different attach command for container vs host sessions
|
|
311
|
+
const tmuxAttach = buildTmuxAttachCommand(useControlMode, session.type === 'container');
|
|
274
312
|
const attachCmd = session.type === 'container' && session.containerId
|
|
275
|
-
? `docker exec -it ${session.containerId}
|
|
276
|
-
:
|
|
313
|
+
? `docker exec -it ${session.containerId} ${tmuxAttach} -t "${session.sessionId}"`
|
|
314
|
+
: `${tmuxAttach} -t "${session.sessionId}"`;
|
|
315
|
+
// Set mouse mode based on attach type
|
|
316
|
+
const mouseMode = useControlMode ? 'off' : 'on';
|
|
317
|
+
const mouseCmd = session.type === 'container' && session.containerId
|
|
318
|
+
? `docker exec ${session.containerId} tmux set-option -t "${session.sessionId}" mouse ${mouseMode} 2>/dev/null || true`
|
|
319
|
+
: `tmux set-option -t "${session.sessionId}" mouse ${mouseMode} 2>/dev/null || true`;
|
|
277
320
|
const script = `#!/bin/bash
|
|
278
321
|
# Set terminal tab title
|
|
279
322
|
echo -ne "\\033]0;${title}\\007"
|
|
280
323
|
echo -ne "\\033]1;${title}\\007"
|
|
281
324
|
|
|
325
|
+
# Set mouse mode before attaching
|
|
326
|
+
${mouseCmd}
|
|
327
|
+
|
|
282
328
|
echo "Attaching to: ${session.sessionId} (${session.type})"
|
|
283
329
|
${attachCmd}
|
|
284
330
|
|
|
@@ -176,7 +176,7 @@ export default class SessionPoke extends PMOCommand {
|
|
|
176
176
|
* Resolve the tmux session for a specific execution record.
|
|
177
177
|
*/
|
|
178
178
|
resolveSessionForExecution(exec, jsonMode, flags) {
|
|
179
|
-
const isContainer = exec.
|
|
179
|
+
const isContainer = !!exec.containerId;
|
|
180
180
|
let actualSessionId = exec.sessionId;
|
|
181
181
|
let containerId = isContainer ? exec.containerId : undefined;
|
|
182
182
|
// If sessionId is NULL, try to discover it from tmux
|
|
@@ -24,9 +24,11 @@ export default class Work extends PMOCommand {
|
|
|
24
24
|
const menuChoices = [
|
|
25
25
|
{ id: 'status', name: 'View work status (in-progress tickets)', command: `prlt work status -P ${projectId} --json` },
|
|
26
26
|
{ id: 'start', name: 'Start work (launch single agent)', command: `prlt work start -P ${projectId} --json` },
|
|
27
|
+
{ id: 'linear', name: 'Spawn from Linear issue', command: `prlt work linear -P ${projectId} --json` },
|
|
27
28
|
{ id: 'resolve', name: 'Resolve questions (agent-assisted)', command: `prlt work resolve -P ${projectId} --json` },
|
|
28
29
|
{ id: 'spawn', name: 'Spawn work (batch by column)', command: `prlt work spawn -P ${projectId} --json` },
|
|
29
30
|
{ id: 'watch', name: 'Watch column (auto-spawn)', command: `prlt work watch -P ${projectId} --json` },
|
|
31
|
+
{ id: 'review', name: 'Review pipeline (review → fix → re-review)', command: `prlt work review -P ${projectId} --json` },
|
|
30
32
|
{ id: 'ready', name: 'Mark work ready for review', command: `prlt work ready -P ${projectId} --json` },
|
|
31
33
|
{ id: 'complete', name: 'Mark work complete', command: `prlt work complete -P ${projectId} --json` },
|
|
32
34
|
{ id: 'cancel', name: 'Cancel', command: '' },
|
|
@@ -53,6 +55,9 @@ export default class Work extends PMOCommand {
|
|
|
53
55
|
case 'start':
|
|
54
56
|
await this.config.runCommand('work:start', projectArgs);
|
|
55
57
|
break;
|
|
58
|
+
case 'linear':
|
|
59
|
+
await this.config.runCommand('work:linear', projectArgs);
|
|
60
|
+
break;
|
|
56
61
|
case 'resolve':
|
|
57
62
|
await this.config.runCommand('work:resolve', projectArgs);
|
|
58
63
|
break;
|
|
@@ -62,6 +67,9 @@ export default class Work extends PMOCommand {
|
|
|
62
67
|
case 'watch':
|
|
63
68
|
await this.config.runCommand('work:watch', projectArgs);
|
|
64
69
|
break;
|
|
70
|
+
case 'review':
|
|
71
|
+
await this.config.runCommand('work:review', projectArgs);
|
|
72
|
+
break;
|
|
65
73
|
case 'ready':
|
|
66
74
|
await this.config.runCommand('work:ready', projectArgs);
|
|
67
75
|
break;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class WorkLinear extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
issue: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
display: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
action: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
message: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'run-on-host': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
'skip-permissions': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
'create-pr': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
};
|
|
21
|
+
private findLinkedTicket;
|
|
22
|
+
private createOrUpdateLinkedTicket;
|
|
23
|
+
execute(): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { PMOCommand, pmoBaseFlags, autoExportToBoard, } from '../../lib/pmo/index.js';
|
|
3
|
+
import { shouldOutputJson, } from '../../lib/prompt-json.js';
|
|
4
|
+
import { ExternalIssueAdapterError, } from '../../lib/external-issues/types.js';
|
|
5
|
+
import { listLinearIssues, buildLinearIssueChoiceCommand, buildLinearTicketDescription, buildLinearMetadata, buildLinearSpawnContextMessage, } from '../../lib/external-issues/linear.js';
|
|
6
|
+
function buildWorkStartArgs(options) {
|
|
7
|
+
const args = [options.ticketId, '--project', options.projectId, '--ephemeral'];
|
|
8
|
+
if (options.executor)
|
|
9
|
+
args.push('--executor', options.executor);
|
|
10
|
+
if (options.display)
|
|
11
|
+
args.push('--display', options.display);
|
|
12
|
+
if (options.runOnHost)
|
|
13
|
+
args.push('--run-on-host');
|
|
14
|
+
if (options.skipPermissions)
|
|
15
|
+
args.push('--skip-permissions');
|
|
16
|
+
if (options.createPr)
|
|
17
|
+
args.push('--create-pr');
|
|
18
|
+
if (options.action)
|
|
19
|
+
args.push('--action', options.action);
|
|
20
|
+
if (options.message)
|
|
21
|
+
args.push('--message', options.message);
|
|
22
|
+
if (options.json)
|
|
23
|
+
args.push('--json');
|
|
24
|
+
if (options.machine)
|
|
25
|
+
args.push('--machine');
|
|
26
|
+
if (options.yes)
|
|
27
|
+
args.push('--yes');
|
|
28
|
+
return args;
|
|
29
|
+
}
|
|
30
|
+
export default class WorkLinear extends PMOCommand {
|
|
31
|
+
static description = 'List/select Linear issues and spawn work using the existing work-start flow';
|
|
32
|
+
static examples = [
|
|
33
|
+
'<%= config.bin %> <%= command.id %> --team ENG',
|
|
34
|
+
'<%= config.bin %> <%= command.id %> --team ENG --issue ENG-123',
|
|
35
|
+
'<%= config.bin %> <%= command.id %> --team ENG --issue ENG-123 --yes --skip-permissions --display terminal',
|
|
36
|
+
];
|
|
37
|
+
static flags = {
|
|
38
|
+
...pmoBaseFlags,
|
|
39
|
+
team: Flags.string({
|
|
40
|
+
description: 'Linear team key (fallback: PRLT_LINEAR_TEAM)',
|
|
41
|
+
}),
|
|
42
|
+
issue: Flags.string({
|
|
43
|
+
description: 'Linear issue identifier (for example: ENG-123)',
|
|
44
|
+
}),
|
|
45
|
+
limit: Flags.integer({
|
|
46
|
+
char: 'l',
|
|
47
|
+
description: 'Maximum number of Linear issues to fetch',
|
|
48
|
+
default: 20,
|
|
49
|
+
min: 1,
|
|
50
|
+
max: 100,
|
|
51
|
+
}),
|
|
52
|
+
executor: Flags.string({
|
|
53
|
+
char: 'e',
|
|
54
|
+
description: 'Override executor',
|
|
55
|
+
options: ['claude-code', 'codex', 'aider', 'custom'],
|
|
56
|
+
}),
|
|
57
|
+
display: Flags.string({
|
|
58
|
+
char: 'd',
|
|
59
|
+
description: 'Display mode',
|
|
60
|
+
options: ['terminal', 'background', 'foreground'],
|
|
61
|
+
}),
|
|
62
|
+
action: Flags.string({
|
|
63
|
+
char: 'A',
|
|
64
|
+
description: 'Action to run in work start (default: implement)',
|
|
65
|
+
default: 'implement',
|
|
66
|
+
}),
|
|
67
|
+
message: Flags.string({
|
|
68
|
+
description: 'Additional instructions appended to spawn context',
|
|
69
|
+
}),
|
|
70
|
+
'run-on-host': Flags.boolean({
|
|
71
|
+
description: 'Run on host even if devcontainer exists',
|
|
72
|
+
default: false,
|
|
73
|
+
}),
|
|
74
|
+
'skip-permissions': Flags.boolean({
|
|
75
|
+
description: 'Skip permission prompts (danger mode)',
|
|
76
|
+
default: false,
|
|
77
|
+
}),
|
|
78
|
+
'create-pr': Flags.boolean({
|
|
79
|
+
description: 'Create PR when work is ready',
|
|
80
|
+
default: false,
|
|
81
|
+
}),
|
|
82
|
+
yes: Flags.boolean({
|
|
83
|
+
char: 'y',
|
|
84
|
+
description: 'Skip confirmation prompts in downstream work start',
|
|
85
|
+
default: false,
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
async findLinkedTicket(projectId, envelope) {
|
|
89
|
+
const tickets = await this.storage.listTickets(projectId);
|
|
90
|
+
return tickets.find((ticket) => {
|
|
91
|
+
const source = ticket.metadata?.external_source;
|
|
92
|
+
const key = ticket.metadata?.external_key;
|
|
93
|
+
const id = ticket.metadata?.external_id;
|
|
94
|
+
return source === 'linear'
|
|
95
|
+
&& (key === envelope.source.externalKey || id === envelope.source.externalId);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async createOrUpdateLinkedTicket(projectId, envelope) {
|
|
99
|
+
const existing = await this.findLinkedTicket(projectId, envelope);
|
|
100
|
+
const description = buildLinearTicketDescription(envelope);
|
|
101
|
+
const metadata = buildLinearMetadata(envelope);
|
|
102
|
+
if (existing) {
|
|
103
|
+
const updated = await this.storage.updateTicket(existing.id, {
|
|
104
|
+
title: envelope.title,
|
|
105
|
+
description,
|
|
106
|
+
priority: envelope.priority ?? undefined,
|
|
107
|
+
category: envelope.category ?? undefined,
|
|
108
|
+
labels: envelope.labels,
|
|
109
|
+
metadata: {
|
|
110
|
+
...existing.metadata,
|
|
111
|
+
...metadata,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
return updated;
|
|
115
|
+
}
|
|
116
|
+
return this.storage.createTicket(projectId, {
|
|
117
|
+
title: envelope.title,
|
|
118
|
+
description,
|
|
119
|
+
priority: envelope.priority ?? undefined,
|
|
120
|
+
category: envelope.category ?? undefined,
|
|
121
|
+
labels: envelope.labels,
|
|
122
|
+
metadata,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async execute() {
|
|
126
|
+
const { flags } = await this.parse(WorkLinear);
|
|
127
|
+
const jsonMode = shouldOutputJson(flags);
|
|
128
|
+
const projectId = await this.requireProject({
|
|
129
|
+
jsonMode: {
|
|
130
|
+
flags,
|
|
131
|
+
commandName: 'work linear',
|
|
132
|
+
baseCommand: 'prlt work linear',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
const team = flags.team || process.env.PRLT_LINEAR_TEAM;
|
|
136
|
+
let issues;
|
|
137
|
+
try {
|
|
138
|
+
issues = await listLinearIssues({
|
|
139
|
+
team,
|
|
140
|
+
}, { limit: flags.limit });
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error instanceof ExternalIssueAdapterError) {
|
|
144
|
+
return this.handleError(error.code, error.message, { jsonMode, commandName: 'work linear', flags });
|
|
145
|
+
}
|
|
146
|
+
const msg = error instanceof Error ? error.message : 'Failed to fetch Linear issues.';
|
|
147
|
+
return this.handleError('LINEAR_REQUEST_FAILED', msg, { jsonMode, commandName: 'work linear', flags });
|
|
148
|
+
}
|
|
149
|
+
if (issues.length === 0) {
|
|
150
|
+
return this.handleError('NO_LINEAR_ISSUES', 'No active Linear issues found for the configured team.', { jsonMode, commandName: 'work linear', flags });
|
|
151
|
+
}
|
|
152
|
+
let selectedIssue = issues.find(issue => issue.source.externalKey === flags.issue);
|
|
153
|
+
if (!selectedIssue) {
|
|
154
|
+
if (flags.issue) {
|
|
155
|
+
return this.handleError('LINEAR_ISSUE_NOT_FOUND', `Linear issue "${flags.issue}" was not found.`, { jsonMode, commandName: 'work linear', flags });
|
|
156
|
+
}
|
|
157
|
+
const selectedKey = await this.selectFromList({
|
|
158
|
+
message: 'Select Linear issue to spawn:',
|
|
159
|
+
items: issues,
|
|
160
|
+
getName: (issue) => {
|
|
161
|
+
const priority = issue.priority || 'None';
|
|
162
|
+
return `[${priority}] ${issue.source.externalKey} - ${issue.title}`;
|
|
163
|
+
},
|
|
164
|
+
getValue: issue => issue.source.externalKey,
|
|
165
|
+
getCommand: issue => buildLinearIssueChoiceCommand(issue.source.externalKey, projectId),
|
|
166
|
+
jsonMode: jsonMode ? { flags, commandName: 'work linear' } : null,
|
|
167
|
+
});
|
|
168
|
+
if (!selectedKey) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
selectedIssue = issues.find(issue => issue.source.externalKey === selectedKey);
|
|
172
|
+
if (!selectedIssue) {
|
|
173
|
+
return this.handleError('LINEAR_ISSUE_NOT_FOUND', `Linear issue "${selectedKey}" was not found.`, { jsonMode, commandName: 'work linear', flags });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const ticket = await this.createOrUpdateLinkedTicket(projectId, selectedIssue);
|
|
177
|
+
await autoExportToBoard(this.pmoPath, this.storage);
|
|
178
|
+
const contextMessage = buildLinearSpawnContextMessage(selectedIssue, flags.message);
|
|
179
|
+
const args = buildWorkStartArgs({
|
|
180
|
+
ticketId: ticket.id,
|
|
181
|
+
projectId,
|
|
182
|
+
executor: flags.executor,
|
|
183
|
+
display: flags.display,
|
|
184
|
+
runOnHost: flags['run-on-host'],
|
|
185
|
+
skipPermissions: flags['skip-permissions'],
|
|
186
|
+
createPr: flags['create-pr'],
|
|
187
|
+
action: flags.action,
|
|
188
|
+
message: contextMessage,
|
|
189
|
+
json: flags.json,
|
|
190
|
+
machine: flags.machine,
|
|
191
|
+
yes: flags.yes,
|
|
192
|
+
});
|
|
193
|
+
await this.config.runCommand('work:start', args);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class WorkReview extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
ticketId: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
'max-cycles': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
auto: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'run-on-host': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'skip-permissions': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
display: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
session: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
'poll-interval': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
|
+
};
|
|
22
|
+
execute(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Build args for `work:start` command.
|
|
25
|
+
*/
|
|
26
|
+
private buildStartArgs;
|
|
27
|
+
/**
|
|
28
|
+
* Run the fix phase: spawn review-fix agent and wait for completion.
|
|
29
|
+
*/
|
|
30
|
+
private runFixPhase;
|
|
31
|
+
/**
|
|
32
|
+
* Wait for the most recent execution on a ticket to complete.
|
|
33
|
+
* Polls the execution storage and checks tmux session existence.
|
|
34
|
+
*/
|
|
35
|
+
private waitForAgentCompletion;
|
|
36
|
+
/**
|
|
37
|
+
* Check if a tmux session exists.
|
|
38
|
+
*/
|
|
39
|
+
private checkTmuxSession;
|
|
40
|
+
/**
|
|
41
|
+
* Get the review verdict from PR feedback.
|
|
42
|
+
*/
|
|
43
|
+
private getReviewVerdict;
|
|
44
|
+
private sleep;
|
|
45
|
+
}
|