@proletariat/cli 0.3.90 → 0.3.92

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 (40) hide show
  1. package/dist/commands/work/index.js +4 -0
  2. package/dist/commands/work/index.js.map +1 -1
  3. package/dist/commands/work/ship.d.ts +43 -0
  4. package/dist/commands/work/ship.js +587 -0
  5. package/dist/commands/work/ship.js.map +1 -0
  6. package/dist/commands/work/start.d.ts +1 -0
  7. package/dist/commands/work/start.js +11 -1
  8. package/dist/commands/work/start.js.map +1 -1
  9. package/dist/lib/execution/context.d.ts +20 -0
  10. package/dist/lib/execution/context.js +88 -0
  11. package/dist/lib/execution/context.js.map +1 -1
  12. package/dist/lib/execution/devcontainer.d.ts +6 -0
  13. package/dist/lib/execution/devcontainer.js +48 -1
  14. package/dist/lib/execution/devcontainer.js.map +1 -1
  15. package/dist/lib/execution/runners/devcontainer.js +11 -0
  16. package/dist/lib/execution/runners/devcontainer.js.map +1 -1
  17. package/dist/lib/execution/runners/docker-management.js +1 -1
  18. package/dist/lib/execution/runners/docker-management.js.map +1 -1
  19. package/dist/lib/execution/runners/orchestrator.js +6 -3
  20. package/dist/lib/execution/runners/orchestrator.js.map +1 -1
  21. package/dist/lib/execution/runners/prompt-builder.js +31 -0
  22. package/dist/lib/execution/runners/prompt-builder.js.map +1 -1
  23. package/dist/lib/execution/runners/shared.d.ts +2 -2
  24. package/dist/lib/execution/runners/shared.js +2 -2
  25. package/dist/lib/execution/runners/shared.js.map +1 -1
  26. package/dist/lib/execution/types.d.ts +23 -0
  27. package/dist/lib/execution/types.js.map +1 -1
  28. package/dist/lib/providers/index.d.ts +2 -0
  29. package/dist/lib/providers/index.js +2 -0
  30. package/dist/lib/providers/index.js.map +1 -1
  31. package/dist/lib/providers/state-intents.d.ts +44 -0
  32. package/dist/lib/providers/state-intents.js +101 -0
  33. package/dist/lib/providers/state-intents.js.map +1 -0
  34. package/dist/lib/providers/state-resolution.d.ts +137 -0
  35. package/dist/lib/providers/state-resolution.js +302 -0
  36. package/dist/lib/providers/state-resolution.js.map +1 -0
  37. package/dist/lib/work-lifecycle/post-execution.js +47 -28
  38. package/dist/lib/work-lifecycle/post-execution.js.map +1 -1
  39. package/oclif.manifest.json +2317 -2203
  40. package/package.json +1 -1
@@ -33,6 +33,7 @@ export default class Work extends PMOCommand {
33
33
  { id: 'status', name: 'Status — view in-progress tickets', command: `prlt work status -P ${projectId} --json` },
34
34
  { id: 'spawn', name: 'Spawn — batch by column', command: `prlt work spawn -P ${projectId} --json` },
35
35
  { id: 'ready', name: 'Ready — mark work ready for review', command: `prlt work ready -P ${projectId} --json` },
36
+ { id: 'ship', name: 'Ship — rebase, merge PR, move to Done', command: `prlt work ship -P ${projectId} --json` },
36
37
  { id: 'complete', name: 'Complete — mark work done', command: `prlt work complete -P ${projectId} --json` },
37
38
  { id: 'hooks', name: 'Hooks — manage lifecycle hooks', command: 'prlt work hooks --json' },
38
39
  { id: 'cancel', name: 'Cancel', command: '' },
@@ -85,6 +86,9 @@ export default class Work extends PMOCommand {
85
86
  case 'ready':
86
87
  await this.config.runCommand('work:ready', projectArgs);
87
88
  break;
89
+ case 'ship':
90
+ await this.config.runCommand('work:ship', projectArgs);
91
+ break;
88
92
  case 'complete':
89
93
  await this.config.runCommand('work:complete', projectArgs);
90
94
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/work/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/work/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class WorkShip 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
+ pr: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ method: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ wait: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ 'no-rebase': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ 'delete-branch': import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ admin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ };
20
+ execute(): Promise<void>;
21
+ /**
22
+ * Resolve the linked ticket for a PR.
23
+ */
24
+ private resolveLinkedTicket;
25
+ /**
26
+ * Wait for CI checks to pass. Returns immediately if all passed.
27
+ * If --wait is set, polls until all complete. Otherwise warns on pending.
28
+ */
29
+ private waitForCI;
30
+ /**
31
+ * Check if the PR has merge conflicts with the base branch.
32
+ */
33
+ private checkMergeConflicts;
34
+ /**
35
+ * Rebase the PR branch onto the base branch and force-push.
36
+ */
37
+ private rebaseOntoBase;
38
+ /**
39
+ * Resolve the working directory for git operations.
40
+ * Tries: devcontainer repo dirs, execution worktree, agent dirs.
41
+ */
42
+ private resolveWorktreePath;
43
+ }
@@ -0,0 +1,587 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as path from 'node:path';
3
+ import * as fs from 'node:fs';
4
+ import Database from 'better-sqlite3';
5
+ import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
6
+ import { getWorkColumnSetting, findColumnByName } from '../../lib/pmo/utils.js';
7
+ import { styles } from '../../lib/styles.js';
8
+ import { getWorkspaceInfo } from '../../lib/agents/commands.js';
9
+ import { ExecutionStorage } from '../../lib/execution/storage.js';
10
+ import { isGHInstalled, isGHAuthenticated, getPRByNumber, getPRForBranch, getPRChecks, mergePR, getGitHubRepo, } from '../../lib/pr/index.js';
11
+ import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
12
+ import { getEventBus } from '../../lib/events/event-bus.js';
13
+ import { validateBranchName } from '../../lib/branch/index.js';
14
+ import { PMO_TABLES } from '../../lib/pmo/schema.js';
15
+ import { execSync } from 'node:child_process';
16
+ /**
17
+ * Sleep for the given number of milliseconds.
18
+ */
19
+ function sleep(ms) {
20
+ return new Promise(resolve => setTimeout(resolve, ms));
21
+ }
22
+ export default class WorkShip extends PMOCommand {
23
+ static description = 'Ship work: rebase, merge PR, and move ticket to Done';
24
+ static examples = [
25
+ '<%= config.bin %> <%= command.id %> TKT-001',
26
+ '<%= config.bin %> <%= command.id %> PRLT-1092',
27
+ '<%= config.bin %> <%= command.id %> --pr 929',
28
+ '<%= config.bin %> <%= command.id %> TKT-001 --dry-run',
29
+ '<%= config.bin %> <%= command.id %> TKT-001 --wait',
30
+ '<%= config.bin %> <%= command.id %> TKT-001 --no-rebase',
31
+ ];
32
+ static args = {
33
+ ticketId: Args.string({
34
+ description: 'Ticket ID - prompts with dropdown if not provided',
35
+ required: false,
36
+ }),
37
+ };
38
+ static flags = {
39
+ ...pmoBaseFlags,
40
+ pr: Flags.integer({
41
+ description: 'PR number to merge (alternative to ticket ID lookup)',
42
+ required: false,
43
+ }),
44
+ method: Flags.string({
45
+ description: 'Merge method',
46
+ options: ['merge', 'squash', 'rebase'],
47
+ default: 'squash',
48
+ }),
49
+ wait: Flags.boolean({
50
+ description: 'Wait for CI to go green before merging',
51
+ default: false,
52
+ }),
53
+ 'no-rebase': Flags.boolean({
54
+ description: 'Fail on conflicts instead of auto-rebasing',
55
+ default: false,
56
+ }),
57
+ 'dry-run': Flags.boolean({
58
+ description: 'Show what would happen without doing it',
59
+ default: false,
60
+ }),
61
+ 'delete-branch': Flags.boolean({
62
+ description: 'Delete remote branch after merging',
63
+ default: true,
64
+ allowNo: true,
65
+ }),
66
+ admin: Flags.boolean({
67
+ description: 'Use admin privileges to bypass branch protections',
68
+ default: false,
69
+ }),
70
+ };
71
+ async execute() {
72
+ const { args, flags } = await this.parse(WorkShip);
73
+ const projectId = flags.project;
74
+ const jsonMode = shouldOutputJson(flags);
75
+ const handleError = (code, message) => {
76
+ if (jsonMode) {
77
+ outputErrorAsJson(code, message, createMetadata('work ship', flags));
78
+ return;
79
+ }
80
+ this.error(message);
81
+ };
82
+ // Check gh CLI
83
+ if (!isGHInstalled()) {
84
+ return handleError('GH_NOT_INSTALLED', 'GitHub CLI (gh) is not installed. Install it from https://cli.github.com/');
85
+ }
86
+ if (!isGHAuthenticated()) {
87
+ return handleError('GH_NOT_AUTHENTICATED', 'GitHub CLI is not authenticated. Run "gh auth login" first.');
88
+ }
89
+ // Get workspace info
90
+ let workspaceInfo;
91
+ try {
92
+ workspaceInfo = getWorkspaceInfo();
93
+ }
94
+ catch {
95
+ return handleError('NOT_IN_WORKSPACE', 'Not in a workspace. Run "prlt new" first.');
96
+ }
97
+ const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
98
+ const db = new Database(dbPath);
99
+ const executionStorage = new ExecutionStorage(db);
100
+ try {
101
+ // --- Step 1: Resolve ticket and PR ---
102
+ let ticketId = args.ticketId;
103
+ let prNumber = flags.pr;
104
+ let ticket = null;
105
+ let prInfo = null;
106
+ if (prNumber) {
107
+ // PR number provided directly — find the PR, then resolve the ticket
108
+ prInfo = getPRByNumber(prNumber);
109
+ if (!prInfo) {
110
+ db.close();
111
+ return handleError('PR_NOT_FOUND', `PR #${prNumber} not found.`);
112
+ }
113
+ // Try to resolve the linked ticket
114
+ ticket = await this.resolveLinkedTicket(prNumber, prInfo.headBranch, projectId);
115
+ if (ticket) {
116
+ ticketId = ticket.id;
117
+ }
118
+ }
119
+ else {
120
+ // Resolve ticket first
121
+ if (!ticketId) {
122
+ // Prompt for ticket selection from shippable tickets (in review or in progress)
123
+ const allTickets = await this.storage.listTickets(projectId);
124
+ const shippableTickets = allTickets.filter(t => t.statusCategory === 'started' ||
125
+ (t.statusName && (t.statusName.toLowerCase().includes('progress') ||
126
+ t.statusName.toLowerCase().includes('review'))));
127
+ if (shippableTickets.length === 0) {
128
+ db.close();
129
+ if (jsonMode) {
130
+ outputErrorAsJson('NO_SHIPPABLE_WORK', 'No in-progress or in-review work found.', createMetadata('work ship', flags));
131
+ return;
132
+ }
133
+ this.log(styles.info('No in-progress or in-review work found.'));
134
+ return;
135
+ }
136
+ const selected = await this.selectFromList({
137
+ message: 'Select work to ship:',
138
+ items: shippableTickets,
139
+ getName: (t) => `${t.id} - ${t.title} (${t.statusName})`,
140
+ getValue: (t) => t.id,
141
+ getCommand: (t) => `prlt work ship ${t.id} --json`,
142
+ jsonMode: jsonMode ? { flags, commandName: 'work ship' } : null,
143
+ });
144
+ if (!selected) {
145
+ db.close();
146
+ return;
147
+ }
148
+ ticketId = selected;
149
+ }
150
+ // Get ticket
151
+ ticket = await this.storage.getTicket(ticketId);
152
+ if (!ticket) {
153
+ db.close();
154
+ return handleError('TICKET_NOT_FOUND', `Ticket "${ticketId}" not found.`);
155
+ }
156
+ ticketId = ticket.id;
157
+ // Find PR from ticket metadata or branch
158
+ prNumber = ticket.metadata?.pr_number ? parseInt(ticket.metadata.pr_number, 10) : undefined;
159
+ if (prNumber) {
160
+ prInfo = getPRByNumber(prNumber);
161
+ }
162
+ if (!prInfo) {
163
+ // Try to find PR from execution branch
164
+ const runningExecution = executionStorage.getRunningExecution(ticketId);
165
+ const branch = runningExecution?.branch || ticket.branch;
166
+ if (branch) {
167
+ prInfo = getPRForBranch(branch);
168
+ if (prInfo) {
169
+ prNumber = prInfo.number;
170
+ }
171
+ }
172
+ }
173
+ if (!prInfo || !prNumber) {
174
+ db.close();
175
+ return handleError('NO_PR_FOUND', `No pull request found for ticket "${ticketId}". Create one with "prlt work ready ${ticketId} --pr".`);
176
+ }
177
+ }
178
+ // Validate PR state
179
+ if (prInfo.state === 'MERGED') {
180
+ db.close();
181
+ if (jsonMode) {
182
+ outputSuccessAsJson({ alreadyMerged: true, prNumber, title: prInfo.title, ticketId: ticketId ?? null }, createMetadata('work ship', flags));
183
+ return;
184
+ }
185
+ this.log(styles.info(`PR #${prNumber} is already merged.`));
186
+ return;
187
+ }
188
+ if (prInfo.state === 'CLOSED') {
189
+ db.close();
190
+ return handleError('PR_CLOSED', `PR #${prNumber} is closed. Reopen it first.`);
191
+ }
192
+ // Resolve the working directory for git operations
193
+ const cwd = this.resolveWorktreePath(workspaceInfo, executionStorage, ticketId);
194
+ // --- Dry run summary ---
195
+ if (flags['dry-run']) {
196
+ this.log('');
197
+ this.log(styles.info('Dry run — no changes will be made:'));
198
+ this.log('');
199
+ this.log(styles.muted(` PR: #${prNumber} — ${prInfo.title}`));
200
+ this.log(styles.muted(` Branch: ${prInfo.headBranch} → ${prInfo.baseBranch}`));
201
+ this.log(styles.muted(` Method: ${flags.method}`));
202
+ if (ticketId) {
203
+ this.log(styles.muted(` Ticket: ${ticketId}${ticket ? ` — ${ticket.title}` : ''}`));
204
+ }
205
+ // Check CI
206
+ const checks = getPRChecks(prNumber, cwd);
207
+ if (checks.length > 0) {
208
+ const allPassed = checks.every(c => c.conclusion === 'SUCCESS' || c.conclusion === 'SKIPPED');
209
+ const pending = checks.filter(c => !c.conclusion || c.status === 'IN_PROGRESS' || c.status === 'QUEUED');
210
+ const failed = checks.filter(c => c.conclusion === 'FAILURE');
211
+ if (allPassed) {
212
+ this.log(styles.muted(` CI: All checks passed (${checks.length})`));
213
+ }
214
+ else if (pending.length > 0) {
215
+ this.log(styles.muted(` CI: ${pending.length} pending, ${failed.length} failed`));
216
+ }
217
+ else {
218
+ this.log(styles.muted(` CI: ${failed.length} failed`));
219
+ }
220
+ }
221
+ else {
222
+ this.log(styles.muted(` CI: No checks configured`));
223
+ }
224
+ // Check conflicts
225
+ const hasConflicts = this.checkMergeConflicts(prNumber, cwd);
226
+ this.log(styles.muted(` Conflicts: ${hasConflicts ? 'Yes — would rebase' : 'None'}`));
227
+ this.log('');
228
+ this.log(styles.muted(' Actions that would be taken:'));
229
+ if (hasConflicts && !flags['no-rebase']) {
230
+ this.log(styles.muted(' 1. Rebase onto base branch and force-push'));
231
+ this.log(styles.muted(' 2. Wait for CI to rerun'));
232
+ }
233
+ this.log(styles.muted(` ${hasConflicts && !flags['no-rebase'] ? '3' : '1'}. Squash merge PR #${prNumber}`));
234
+ if (ticketId) {
235
+ this.log(styles.muted(` ${hasConflicts && !flags['no-rebase'] ? '4' : '2'}. Move ticket ${ticketId} to Done`));
236
+ }
237
+ if (flags['delete-branch']) {
238
+ this.log(styles.muted(` ${hasConflicts && !flags['no-rebase'] ? '5' : '3'}. Delete remote branch ${prInfo.headBranch}`));
239
+ }
240
+ db.close();
241
+ return;
242
+ }
243
+ // --- Step 2: Check CI status ---
244
+ this.log('');
245
+ this.log(styles.info(`Shipping PR #${prNumber}: ${prInfo.title}`));
246
+ const ciResult = await this.waitForCI(prNumber, flags.wait, cwd);
247
+ if (!ciResult.passed) {
248
+ db.close();
249
+ return handleError('CI_FAILED', ciResult.message);
250
+ }
251
+ // --- Step 3: Check for merge conflicts and rebase if needed ---
252
+ const hasConflicts = this.checkMergeConflicts(prNumber, cwd);
253
+ if (hasConflicts) {
254
+ if (flags['no-rebase']) {
255
+ db.close();
256
+ return handleError('MERGE_CONFLICTS', `PR #${prNumber} has merge conflicts. Resolve them manually or run without --no-rebase to auto-rebase.`);
257
+ }
258
+ this.log(styles.muted(' Merge conflicts detected, rebasing...'));
259
+ const rebaseResult = this.rebaseOntoBase(prInfo.headBranch, prInfo.baseBranch, cwd);
260
+ if (!rebaseResult.success) {
261
+ db.close();
262
+ return handleError('REBASE_FAILED', `Rebase failed: ${rebaseResult.error}. Resolve conflicts manually.`);
263
+ }
264
+ this.log(styles.muted(' Rebased and force-pushed. Waiting for CI...'));
265
+ // Wait for CI to rerun after rebase
266
+ const postRebaseCI = await this.waitForCI(prNumber, true, cwd);
267
+ if (!postRebaseCI.passed) {
268
+ db.close();
269
+ return handleError('CI_FAILED_AFTER_REBASE', postRebaseCI.message);
270
+ }
271
+ }
272
+ // --- Step 4: Merge the PR ---
273
+ const method = flags.method;
274
+ this.log(styles.muted(` Merging PR #${prNumber} (${method})...`));
275
+ const mergeResult = mergePR(prNumber, {
276
+ method,
277
+ deleteBranch: flags['delete-branch'],
278
+ admin: flags.admin,
279
+ cwd,
280
+ });
281
+ if (!mergeResult.success) {
282
+ db.close();
283
+ return handleError('MERGE_FAILED', `Failed to merge PR #${prNumber}: ${mergeResult.error}`);
284
+ }
285
+ this.log(styles.success(` PR #${prNumber} merged`));
286
+ // --- Step 5: Move ticket to Done ---
287
+ let ticketMovedToDone = false;
288
+ let ticketTransitionProvider;
289
+ let previousColumn;
290
+ let doneColumn;
291
+ if (ticket && ticketId) {
292
+ // Update PR metadata
293
+ await this.storage.updateTicket(ticketId, {
294
+ metadata: {
295
+ ...ticket.metadata,
296
+ pr_state: 'MERGED',
297
+ merged_at: new Date().toISOString(),
298
+ },
299
+ });
300
+ try {
301
+ const targetColumnName = getWorkColumnSetting(db, 'done');
302
+ const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
303
+ const columnNames = board ? board.columns.map(col => col.name) : [];
304
+ doneColumn = findColumnByName(columnNames, targetColumnName);
305
+ if (doneColumn && ticket.projectId) {
306
+ previousColumn = ticket.statusName;
307
+ await this.storage.moveTicket(ticket.projectId, ticketId, doneColumn);
308
+ ticketMovedToDone = true;
309
+ // Sync to external provider
310
+ try {
311
+ const provider = await this.resolveTicketProvider(ticketId, ticket.projectId);
312
+ if (provider.name !== 'pmo') {
313
+ const moveResult = await provider.moveTicket(ticketId, doneColumn);
314
+ if (moveResult.success) {
315
+ ticketTransitionProvider = moveResult.provider;
316
+ this.log(styles.muted(` Synced to ${moveResult.provider}: ${doneColumn}`));
317
+ }
318
+ }
319
+ }
320
+ catch {
321
+ // Non-fatal
322
+ }
323
+ }
324
+ }
325
+ catch (err) {
326
+ this.warn(`Failed to move ticket ${ticketId} to Done: ${err instanceof Error ? err.message : err}`);
327
+ }
328
+ // Auto-export board
329
+ await autoExportToBoard(this.pmoPath, this.storage);
330
+ // Mark execution as completed
331
+ const runningExecution = executionStorage.getRunningExecution(ticketId);
332
+ if (runningExecution) {
333
+ executionStorage.updateStatus(runningExecution.id, 'completed');
334
+ this.log(styles.muted(` Execution ${runningExecution.id} marked as completed`));
335
+ }
336
+ }
337
+ // --- Step 6: Emit event ---
338
+ if (ticketId) {
339
+ try {
340
+ const repo = getGitHubRepo(cwd);
341
+ const prUrl = repo ? `https://github.com/${repo}/pull/${prNumber}` : null;
342
+ getEventBus().emit('work:pr_merged', {
343
+ workItemId: ticketId,
344
+ source: 'github',
345
+ prNumber,
346
+ prTitle: prInfo.title,
347
+ prUrl,
348
+ mergeMethod: method,
349
+ timestamp: new Date(),
350
+ });
351
+ }
352
+ catch {
353
+ // Non-critical
354
+ }
355
+ }
356
+ db.close();
357
+ // --- Output ---
358
+ if (jsonMode) {
359
+ outputSuccessAsJson({
360
+ shipped: true,
361
+ prNumber,
362
+ prTitle: prInfo.title,
363
+ method,
364
+ branchDeleted: flags['delete-branch'],
365
+ ticketId: ticketId ?? null,
366
+ ticketMovedToDone,
367
+ ticketTransitionProvider: ticketTransitionProvider ?? null,
368
+ }, createMetadata('work ship', flags));
369
+ return;
370
+ }
371
+ this.log('');
372
+ this.log(styles.success(`Shipped: ${ticketId ?? `PR #${prNumber}`}`));
373
+ this.log(styles.muted(` PR: #${prNumber} — ${prInfo.title}`));
374
+ this.log(styles.muted(` Method: ${method}`));
375
+ if (flags['delete-branch']) {
376
+ this.log(styles.muted(` Branch: ${prInfo.headBranch} deleted`));
377
+ }
378
+ if (ticketMovedToDone && ticketId) {
379
+ this.log(styles.muted(` Ticket: ${ticketId} → ${doneColumn}`));
380
+ if (previousColumn) {
381
+ this.log(styles.muted(` From: ${previousColumn}`));
382
+ }
383
+ }
384
+ }
385
+ catch (error) {
386
+ db.close();
387
+ throw error;
388
+ }
389
+ }
390
+ /**
391
+ * Resolve the linked ticket for a PR.
392
+ */
393
+ async resolveLinkedTicket(prNumber, headBranch, projectId) {
394
+ // 1. Find by PR metadata on tickets
395
+ const allTickets = await this.storage.listTickets(projectId);
396
+ const byMetadata = allTickets.find(t => t.metadata?.pr_number === String(prNumber) ||
397
+ t.metadata?.pr_url?.endsWith(`/pull/${prNumber}`) ||
398
+ t.metadata?.pr_url?.endsWith(`/${prNumber}`));
399
+ if (byMetadata)
400
+ return byMetadata;
401
+ // 2. Look up from agent_work table by branch name
402
+ try {
403
+ const db = this.storage.getDatabase();
404
+ const row = db.prepare(`SELECT ticket_id FROM ${PMO_TABLES.agent_work} WHERE branch = ? LIMIT 1`).get(headBranch);
405
+ if (row?.ticket_id) {
406
+ const ticket = await this.storage.getTicket(row.ticket_id);
407
+ if (ticket)
408
+ return ticket;
409
+ }
410
+ }
411
+ catch {
412
+ // agent_work lookup failed
413
+ }
414
+ // 3. Parse ticket ID from branch name
415
+ const branchResult = validateBranchName(headBranch);
416
+ if (branchResult.valid && branchResult.parts?.ticketId) {
417
+ const branchTicketId = branchResult.parts.ticketId;
418
+ const directTicket = await this.storage.getTicket(branchTicketId);
419
+ if (directTicket)
420
+ return directTicket;
421
+ const byExternalKey = allTickets.find(t => t.metadata?.external_key === branchTicketId);
422
+ if (byExternalKey)
423
+ return byExternalKey;
424
+ }
425
+ return null;
426
+ }
427
+ /**
428
+ * Wait for CI checks to pass. Returns immediately if all passed.
429
+ * If --wait is set, polls until all complete. Otherwise warns on pending.
430
+ */
431
+ async waitForCI(prNumber, shouldWait, cwd) {
432
+ const maxAttempts = 120; // 30 minutes max (15s intervals)
433
+ let attempts = 0;
434
+ while (attempts < maxAttempts) {
435
+ const checks = getPRChecks(prNumber, cwd);
436
+ // No checks configured — pass through
437
+ if (checks.length === 0) {
438
+ this.log(styles.muted(' No CI checks configured'));
439
+ return { passed: true, message: '' };
440
+ }
441
+ const failed = checks.filter(c => c.conclusion === 'FAILURE');
442
+ const pending = checks.filter(c => !c.conclusion || c.status === 'IN_PROGRESS' || c.status === 'QUEUED');
443
+ const passed = checks.filter(c => c.conclusion === 'SUCCESS' || c.conclusion === 'SKIPPED');
444
+ // All done
445
+ if (pending.length === 0) {
446
+ if (failed.length > 0) {
447
+ const failedNames = failed.map(c => c.name).join(', ');
448
+ return {
449
+ passed: false,
450
+ message: `CI checks failed: ${failedNames}. Fix the failures before shipping.`,
451
+ };
452
+ }
453
+ this.log(styles.muted(` CI: ${passed.length} checks passed`));
454
+ return { passed: true, message: '' };
455
+ }
456
+ // Checks still running
457
+ if (!shouldWait) {
458
+ const pendingNames = pending.map(c => c.name).join(', ');
459
+ return {
460
+ passed: false,
461
+ message: `CI checks still running: ${pendingNames}. Use --wait to wait for them, or re-run after they complete.`,
462
+ };
463
+ }
464
+ // Wait and retry
465
+ if (attempts === 0) {
466
+ this.log(styles.muted(` Waiting for CI (${pending.length} pending)...`));
467
+ }
468
+ attempts++;
469
+ await sleep(15_000);
470
+ }
471
+ return {
472
+ passed: false,
473
+ message: 'Timed out waiting for CI checks (30 minutes). Check manually.',
474
+ };
475
+ }
476
+ /**
477
+ * Check if the PR has merge conflicts with the base branch.
478
+ */
479
+ checkMergeConflicts(prNumber, cwd) {
480
+ try {
481
+ const result = execSync(`gh pr view ${prNumber} --json mergeable -q .mergeable`, {
482
+ cwd,
483
+ encoding: 'utf-8',
484
+ stdio: ['pipe', 'pipe', 'pipe'],
485
+ }).trim();
486
+ return result === 'CONFLICTING';
487
+ }
488
+ catch {
489
+ // If we can't determine, assume no conflicts
490
+ return false;
491
+ }
492
+ }
493
+ /**
494
+ * Rebase the PR branch onto the base branch and force-push.
495
+ */
496
+ rebaseOntoBase(headBranch, baseBranch, cwd) {
497
+ try {
498
+ // Fetch latest from origin
499
+ execSync('git fetch origin', {
500
+ cwd,
501
+ stdio: ['pipe', 'pipe', 'pipe'],
502
+ });
503
+ // Check out the head branch
504
+ execSync(`git checkout ${headBranch}`, {
505
+ cwd,
506
+ stdio: ['pipe', 'pipe', 'pipe'],
507
+ });
508
+ // Rebase onto base
509
+ execSync(`git rebase origin/${baseBranch}`, {
510
+ cwd,
511
+ stdio: ['pipe', 'pipe', 'pipe'],
512
+ });
513
+ // Force-push
514
+ execSync(`git push --force-with-lease origin ${headBranch}`, {
515
+ cwd,
516
+ stdio: ['pipe', 'pipe', 'pipe'],
517
+ });
518
+ return { success: true };
519
+ }
520
+ catch (error) {
521
+ // Abort rebase on failure
522
+ try {
523
+ execSync('git rebase --abort', {
524
+ cwd,
525
+ stdio: ['pipe', 'pipe', 'pipe'],
526
+ });
527
+ }
528
+ catch {
529
+ // Ignore abort failures
530
+ }
531
+ return {
532
+ success: false,
533
+ error: error instanceof Error ? error.message : 'Unknown rebase error',
534
+ };
535
+ }
536
+ }
537
+ /**
538
+ * Resolve the working directory for git operations.
539
+ * Tries: devcontainer repo dirs, execution worktree, agent dirs.
540
+ */
541
+ resolveWorktreePath(workspaceInfo, executionStorage, ticketId) {
542
+ // In devcontainer, find repo directories
543
+ if (process.env.DEVCONTAINER === 'true') {
544
+ try {
545
+ const entries = fs.readdirSync('/workspace', { withFileTypes: true });
546
+ const repoDirs = entries.filter(e => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules');
547
+ if (repoDirs.length > 0) {
548
+ return path.join('/workspace', repoDirs[0].name);
549
+ }
550
+ }
551
+ catch {
552
+ // Fall through
553
+ }
554
+ }
555
+ // Try execution record
556
+ if (ticketId) {
557
+ const execution = executionStorage.getRunningExecution(ticketId);
558
+ if (execution?.agentName) {
559
+ const agentRecord = workspaceInfo.agents.find(a => a.name === execution.agentName);
560
+ let agentDir;
561
+ if (agentRecord?.worktree_path) {
562
+ agentDir = path.join(workspaceInfo.path, agentRecord.worktree_path);
563
+ }
564
+ else if (agentRecord?.type === 'ephemeral') {
565
+ agentDir = path.join(workspaceInfo.path, 'agents', workspaceInfo.ephemeralAgentsDir, execution.agentName);
566
+ }
567
+ else if (agentRecord) {
568
+ agentDir = path.join(workspaceInfo.path, 'agents', workspaceInfo.persistentAgentsDir, execution.agentName);
569
+ }
570
+ if (agentDir && fs.existsSync(agentDir)) {
571
+ try {
572
+ const entries = fs.readdirSync(agentDir, { withFileTypes: true });
573
+ const repoDirs = entries.filter(e => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules');
574
+ if (repoDirs.length > 0) {
575
+ return path.join(agentDir, repoDirs[0].name);
576
+ }
577
+ }
578
+ catch {
579
+ // Fall through
580
+ }
581
+ }
582
+ }
583
+ }
584
+ return undefined;
585
+ }
586
+ }
587
+ //# sourceMappingURL=ship.js.map