@link-assistant/hive-mind 2.15.0 → 2.15.2

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.
@@ -39,7 +39,7 @@ const { ensureAiToolScratchIgnored, filterAiToolScratchFromStatus } = await impo
39
39
  const { RESOURCE_PHASE_RESTART_AFTER, RESOURCE_PHASE_RESTART_BEFORE, recordResourceSnapshot } = await import('./solve.resource-diagnostics.lib.mjs');
40
40
  const { classifyFormalAiToolResult } = await import('./formal-ai.lib.mjs');
41
41
  // Issue #2123: shared draft/ready transitions for working sessions.
42
- const { ensurePullRequestIsDraft } = await import('./pr-draft-state.lib.mjs');
42
+ const { ensurePullRequestIsDraft, ensurePullRequestIsReady } = await import('./pr-draft-state.lib.mjs');
43
43
 
44
44
  // Import Sentry integration
45
45
  const sentryLib = await import('./sentry.lib.mjs');
@@ -219,251 +219,211 @@ export const executeToolIteration = async params => {
219
219
  const { cascadePlaywrightMcpDisable } = await import('./playwright-mcp.lib.mjs');
220
220
  await cascadePlaywrightMcpDisable(argv, log);
221
221
 
222
- let toolResult;
223
- if (argv.useAgentCommander) {
224
- const agentCommanderLib = await import('./agent-commander.lib.mjs');
225
- await agentCommanderLib.resolvePlaywrightMcpForAgentCommander({ argv, log, tool: argv.tool || 'claude' });
222
+ // Issue #2182: the ready conversion below lives in `finally` on purpose. When the AI
223
+ // tool throws (crash, API error, aborted process) the iteration is still over, and a
224
+ // pull request left in draft can never be merged by --auto-merge.
225
+ try {
226
+ let toolResult;
227
+ if (argv.useAgentCommander) {
228
+ const agentCommanderLib = await import('./agent-commander.lib.mjs');
229
+ await agentCommanderLib.resolvePlaywrightMcpForAgentCommander({ argv, log, tool: argv.tool || 'claude' });
230
+
231
+ toolResult = await agentCommanderLib.executeWithAgentCommander({
232
+ issueUrl,
233
+ issueNumber,
234
+ prNumber,
235
+ prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
236
+ branchName,
237
+ tempDir,
238
+ workspaceTmpDir: params.workspaceTmpDir,
239
+ isContinueMode: true,
240
+ mergeStateStatus,
241
+ forkedRepo: argv.fork,
242
+ feedbackLines,
243
+ forkActionsUrl: null,
244
+ owner,
245
+ repo,
246
+ argv,
247
+ log,
248
+ formatAligned,
249
+ getResourceSnapshot,
250
+ setLogFile,
251
+ getLogFile,
252
+ $,
253
+ });
254
+ } else if (argv.tool === 'opencode') {
255
+ // Use OpenCode
256
+ const opencodeExecLib = await import('./opencode.lib.mjs');
257
+ const { executeOpenCode, checkPlaywrightMcpAvailability } = opencodeExecLib;
258
+ const opencodePath = argv.opencodePath || 'opencode';
226
259
 
227
- toolResult = await agentCommanderLib.executeWithAgentCommander({
228
- issueUrl,
229
- issueNumber,
230
- prNumber,
231
- prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
232
- branchName,
233
- tempDir,
234
- workspaceTmpDir: params.workspaceTmpDir,
235
- isContinueMode: true,
236
- mergeStateStatus,
237
- forkedRepo: argv.fork,
238
- feedbackLines,
239
- forkActionsUrl: null,
240
- owner,
241
- repo,
242
- argv,
243
- log,
244
- formatAligned,
245
- getResourceSnapshot,
246
- setLogFile,
247
- getLogFile,
248
- $,
249
- });
250
- } else if (argv.tool === 'opencode') {
251
- // Use OpenCode
252
- const opencodeExecLib = await import('./opencode.lib.mjs');
253
- const { executeOpenCode, checkPlaywrightMcpAvailability } = opencodeExecLib;
254
- const opencodePath = argv.opencodePath || 'opencode';
255
-
256
- if (argv.promptPlaywrightMcp) {
257
- const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
258
- if (playwrightMcpAvailable) {
259
- await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
260
+ if (argv.promptPlaywrightMcp) {
261
+ const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
262
+ if (playwrightMcpAvailable) {
263
+ await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
264
+ } else {
265
+ await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
266
+ argv.promptPlaywrightMcp = false;
267
+ }
260
268
  } else {
261
- await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
262
- argv.promptPlaywrightMcp = false;
269
+ await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
263
270
  }
264
- } else {
265
- await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
266
- }
267
271
 
268
- toolResult = await executeOpenCode({
269
- issueUrl,
270
- issueNumber,
271
- prNumber,
272
- prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
273
- branchName,
274
- tempDir,
275
- workspaceTmpDir,
276
- isContinueMode: true,
277
- mergeStateStatus,
278
- forkedRepo: argv.fork,
279
- feedbackLines,
280
- owner,
281
- repo,
282
- argv,
283
- log,
284
- formatAligned,
285
- getResourceSnapshot,
286
- setLogFile,
287
- getLogFile,
288
- opencodePath,
289
- $,
290
- });
291
- } else if (argv.tool === 'codex') {
292
- // Use Codex
293
- const codexExecLib = await import('./codex.lib.mjs');
294
- const { executeCodex, checkPlaywrightMcpAvailability } = codexExecLib;
295
- const codexPath = argv.codexPath || 'codex';
296
-
297
- if (argv.promptPlaywrightMcp) {
298
- const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
299
- if (playwrightMcpAvailable) {
300
- await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
301
- } else {
302
- await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
303
- argv.promptPlaywrightMcp = false;
304
- }
305
- } else {
306
- await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
307
- }
272
+ toolResult = await executeOpenCode({
273
+ issueUrl,
274
+ issueNumber,
275
+ prNumber,
276
+ prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
277
+ branchName,
278
+ tempDir,
279
+ workspaceTmpDir,
280
+ isContinueMode: true,
281
+ mergeStateStatus,
282
+ forkedRepo: argv.fork,
283
+ feedbackLines,
284
+ owner,
285
+ repo,
286
+ argv,
287
+ log,
288
+ formatAligned,
289
+ getResourceSnapshot,
290
+ setLogFile,
291
+ getLogFile,
292
+ opencodePath,
293
+ $,
294
+ });
295
+ } else if (argv.tool === 'codex') {
296
+ // Use Codex
297
+ const codexExecLib = await import('./codex.lib.mjs');
298
+ const { executeCodex, checkPlaywrightMcpAvailability } = codexExecLib;
299
+ const codexPath = argv.codexPath || 'codex';
308
300
 
309
- toolResult = await executeCodex({
310
- issueUrl,
311
- issueNumber,
312
- prNumber,
313
- prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
314
- branchName,
315
- tempDir,
316
- workspaceTmpDir,
317
- isContinueMode: true,
318
- mergeStateStatus,
319
- forkedRepo: argv.fork,
320
- feedbackLines,
321
- forkActionsUrl: null,
322
- owner,
323
- repo,
324
- argv,
325
- log,
326
- setLogFile,
327
- getLogFile,
328
- formatAligned,
329
- getResourceSnapshot,
330
- codexPath,
331
- $,
332
- });
333
- } else if (argv.tool === 'agent') {
334
- // Use Agent
335
- const agentExecLib = await import('./agent.lib.mjs');
336
- const { executeAgent, checkPlaywrightMcpAvailability } = agentExecLib;
337
- const agentPath = argv.agentPath || 'agent';
338
-
339
- if (argv.promptPlaywrightMcp) {
340
- const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
341
- if (playwrightMcpAvailable) {
342
- await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
301
+ if (argv.promptPlaywrightMcp) {
302
+ const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
303
+ if (playwrightMcpAvailable) {
304
+ await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
305
+ } else {
306
+ await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
307
+ argv.promptPlaywrightMcp = false;
308
+ }
343
309
  } else {
344
- await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
345
- argv.promptPlaywrightMcp = false;
310
+ await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
346
311
  }
347
- } else {
348
- await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
349
- }
350
312
 
351
- toolResult = await executeAgent({
352
- issueUrl,
353
- issueNumber,
354
- prNumber,
355
- prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
356
- branchName,
357
- tempDir,
358
- workspaceTmpDir,
359
- isContinueMode: true,
360
- mergeStateStatus,
361
- forkedRepo: argv.fork,
362
- feedbackLines,
363
- forkActionsUrl: null,
364
- owner,
365
- repo,
366
- argv,
367
- log,
368
- formatAligned,
369
- getResourceSnapshot,
370
- setLogFile,
371
- getLogFile,
372
- agentPath,
373
- $,
374
- });
375
- } else if (argv.tool === 'gemini') {
376
- // Use Gemini
377
- const geminiExecLib = await import('./gemini.lib.mjs');
378
- const { executeGemini, checkPlaywrightMcpAvailability } = geminiExecLib;
379
- const geminiPath = argv.geminiPath || 'gemini';
380
-
381
- if (argv.promptPlaywrightMcp) {
382
- const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
383
- if (playwrightMcpAvailable) {
384
- await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
313
+ toolResult = await executeCodex({
314
+ issueUrl,
315
+ issueNumber,
316
+ prNumber,
317
+ prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
318
+ branchName,
319
+ tempDir,
320
+ workspaceTmpDir,
321
+ isContinueMode: true,
322
+ mergeStateStatus,
323
+ forkedRepo: argv.fork,
324
+ feedbackLines,
325
+ forkActionsUrl: null,
326
+ owner,
327
+ repo,
328
+ argv,
329
+ log,
330
+ setLogFile,
331
+ getLogFile,
332
+ formatAligned,
333
+ getResourceSnapshot,
334
+ codexPath,
335
+ $,
336
+ });
337
+ } else if (argv.tool === 'agent') {
338
+ // Use Agent
339
+ const agentExecLib = await import('./agent.lib.mjs');
340
+ const { executeAgent, checkPlaywrightMcpAvailability } = agentExecLib;
341
+ const agentPath = argv.agentPath || 'agent';
342
+
343
+ if (argv.promptPlaywrightMcp) {
344
+ const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
345
+ if (playwrightMcpAvailable) {
346
+ await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
347
+ } else {
348
+ await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
349
+ argv.promptPlaywrightMcp = false;
350
+ }
385
351
  } else {
386
- await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
387
- argv.promptPlaywrightMcp = false;
352
+ await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
388
353
  }
389
- } else {
390
- await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
391
- }
392
354
 
393
- toolResult = await executeGemini({
394
- issueUrl,
395
- issueNumber,
396
- prNumber,
397
- prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
398
- branchName,
399
- tempDir,
400
- workspaceTmpDir,
401
- isContinueMode: true,
402
- mergeStateStatus,
403
- forkedRepo: argv.fork,
404
- feedbackLines,
405
- forkActionsUrl: null,
406
- owner,
407
- repo,
408
- argv,
409
- log,
410
- setLogFile,
411
- getLogFile,
412
- formatAligned,
413
- getResourceSnapshot,
414
- geminiPath,
415
- $,
416
- });
417
- } else if (argv.tool === 'qwen') {
418
- // Use Qwen Code
419
- const qwenExecLib = await import('./qwen.lib.mjs');
420
- const { executeQwen, checkPlaywrightMcpAvailability } = qwenExecLib;
421
- const qwenPath = argv.qwenPath || 'qwen';
422
-
423
- if (argv.promptPlaywrightMcp) {
424
- const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
425
- if (playwrightMcpAvailable) {
426
- await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
355
+ toolResult = await executeAgent({
356
+ issueUrl,
357
+ issueNumber,
358
+ prNumber,
359
+ prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
360
+ branchName,
361
+ tempDir,
362
+ workspaceTmpDir,
363
+ isContinueMode: true,
364
+ mergeStateStatus,
365
+ forkedRepo: argv.fork,
366
+ feedbackLines,
367
+ forkActionsUrl: null,
368
+ owner,
369
+ repo,
370
+ argv,
371
+ log,
372
+ formatAligned,
373
+ getResourceSnapshot,
374
+ setLogFile,
375
+ getLogFile,
376
+ agentPath,
377
+ $,
378
+ });
379
+ } else if (argv.tool === 'gemini') {
380
+ // Use Gemini
381
+ const geminiExecLib = await import('./gemini.lib.mjs');
382
+ const { executeGemini, checkPlaywrightMcpAvailability } = geminiExecLib;
383
+ const geminiPath = argv.geminiPath || 'gemini';
384
+
385
+ if (argv.promptPlaywrightMcp) {
386
+ const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
387
+ if (playwrightMcpAvailable) {
388
+ await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
389
+ } else {
390
+ await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
391
+ argv.promptPlaywrightMcp = false;
392
+ }
427
393
  } else {
428
- await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
429
- argv.promptPlaywrightMcp = false;
394
+ await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
430
395
  }
431
- } else {
432
- await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
433
- }
434
396
 
435
- toolResult = await executeQwen({
436
- issueUrl,
437
- issueNumber,
438
- prNumber,
439
- prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
440
- branchName,
441
- tempDir,
442
- workspaceTmpDir,
443
- isContinueMode: true,
444
- mergeStateStatus,
445
- forkedRepo: argv.fork,
446
- feedbackLines,
447
- forkActionsUrl: null,
448
- owner,
449
- repo,
450
- argv,
451
- log,
452
- setLogFile,
453
- getLogFile,
454
- formatAligned,
455
- getResourceSnapshot,
456
- qwenPath,
457
- $,
458
- });
459
- } else {
460
- // Use Claude (default)
461
- const claudeExecLib = await import('./claude.lib.mjs');
462
- const { executeClaude, checkPlaywrightMcpAvailability } = claudeExecLib;
463
- const claudePath = argv.claudePath || 'claude';
464
-
465
- // Check for Playwright MCP availability if using Claude tool
466
- if (argv.tool === 'claude' || !argv.tool) {
397
+ toolResult = await executeGemini({
398
+ issueUrl,
399
+ issueNumber,
400
+ prNumber,
401
+ prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
402
+ branchName,
403
+ tempDir,
404
+ workspaceTmpDir,
405
+ isContinueMode: true,
406
+ mergeStateStatus,
407
+ forkedRepo: argv.fork,
408
+ feedbackLines,
409
+ forkActionsUrl: null,
410
+ owner,
411
+ repo,
412
+ argv,
413
+ log,
414
+ setLogFile,
415
+ getLogFile,
416
+ formatAligned,
417
+ getResourceSnapshot,
418
+ geminiPath,
419
+ $,
420
+ });
421
+ } else if (argv.tool === 'qwen') {
422
+ // Use Qwen Code
423
+ const qwenExecLib = await import('./qwen.lib.mjs');
424
+ const { executeQwen, checkPlaywrightMcpAvailability } = qwenExecLib;
425
+ const qwenPath = argv.qwenPath || 'qwen';
426
+
467
427
  if (argv.promptPlaywrightMcp) {
468
428
  const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
469
429
  if (playwrightMcpAvailable) {
@@ -475,58 +435,122 @@ export const executeToolIteration = async params => {
475
435
  } else {
476
436
  await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
477
437
  }
438
+
439
+ toolResult = await executeQwen({
440
+ issueUrl,
441
+ issueNumber,
442
+ prNumber,
443
+ prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
444
+ branchName,
445
+ tempDir,
446
+ workspaceTmpDir,
447
+ isContinueMode: true,
448
+ mergeStateStatus,
449
+ forkedRepo: argv.fork,
450
+ feedbackLines,
451
+ forkActionsUrl: null,
452
+ owner,
453
+ repo,
454
+ argv,
455
+ log,
456
+ setLogFile,
457
+ getLogFile,
458
+ formatAligned,
459
+ getResourceSnapshot,
460
+ qwenPath,
461
+ $,
462
+ });
463
+ } else {
464
+ // Use Claude (default)
465
+ const claudeExecLib = await import('./claude.lib.mjs');
466
+ const { executeClaude, checkPlaywrightMcpAvailability } = claudeExecLib;
467
+ const claudePath = argv.claudePath || 'claude';
468
+
469
+ // Check for Playwright MCP availability if using Claude tool
470
+ if (argv.tool === 'claude' || !argv.tool) {
471
+ if (argv.promptPlaywrightMcp) {
472
+ const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
473
+ if (playwrightMcpAvailable) {
474
+ await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
475
+ } else {
476
+ await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
477
+ argv.promptPlaywrightMcp = false;
478
+ }
479
+ } else {
480
+ await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
481
+ }
482
+ }
483
+
484
+ toolResult = await executeClaude({
485
+ issueUrl,
486
+ issueNumber,
487
+ prNumber,
488
+ prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
489
+ branchName,
490
+ tempDir,
491
+ workspaceTmpDir,
492
+ isContinueMode: true,
493
+ mergeStateStatus,
494
+ forkedRepo: argv.fork,
495
+ feedbackLines,
496
+ owner,
497
+ repo,
498
+ argv,
499
+ log,
500
+ formatAligned,
501
+ getResourceSnapshot,
502
+ setLogFile,
503
+ getLogFile,
504
+ claudePath,
505
+ $,
506
+ });
478
507
  }
479
508
 
480
- toolResult = await executeClaude({
481
- issueUrl,
482
- issueNumber,
483
- prNumber,
484
- prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
485
- branchName,
486
- tempDir,
487
- workspaceTmpDir,
488
- isContinueMode: true,
489
- mergeStateStatus,
490
- forkedRepo: argv.fork,
491
- feedbackLines,
492
- owner,
493
- repo,
494
- argv,
509
+ toolResult = classifyFormalAiToolResult({ model: argv.model, toolResult });
510
+ if (toolResult?.formalAiNonExecution) {
511
+ await log(`❌ ${toolResult.errorInfo.message}`, { level: 'error' });
512
+ }
513
+
514
+ await ensurePullRequestBaseBranch({ owner, repo, prNumber, argv, log, formatAligned, $ });
515
+ await recordResourceSnapshot({
516
+ phase: RESOURCE_PHASE_RESTART_AFTER,
495
517
  log,
496
- formatAligned,
497
- getResourceSnapshot,
498
- setLogFile,
499
- getLogFile,
500
- claudePath,
501
- $,
518
+ diskPath: '/',
519
+ label: 'after AI restart iteration',
502
520
  });
503
- }
504
521
 
505
- toolResult = classifyFormalAiToolResult({ model: argv.model, toolResult });
506
- if (toolResult?.formalAiNonExecution) {
507
- await log(`❌ ${toolResult.errorInfo.message}`, { level: 'error' });
522
+ // Issue #2090: every restart iteration starts a brand new tool session with
523
+ // its own session UUID. Collect its development log here — this is the single
524
+ // chokepoint shared by watch mode, auto-restart-until-mergeable,
525
+ // keep-working, escalation and auto-ensure — otherwise only the very first
526
+ // session of the process ever reached the pull request.
527
+ // When the tool did not report a session id there is nothing to key a new
528
+ // directory on, so extend the previously collected session instead of losing
529
+ // this iteration's part of the log.
530
+ const { finalizeActiveDevelopmentLog } = await import('./development-log.finalize.lib.mjs');
531
+ await (toolResult?.sessionId ? finalizeActiveDevelopmentLog({ sessionId: toolResult.sessionId }) : finalizeActiveDevelopmentLog({ force: true }));
532
+
533
+ return toolResult;
534
+ } finally {
535
+ // Issue #2182: the iteration drafted the pull request above, so it must also
536
+ // undo that when the AI session finishes — exactly like endWorkSession() does
537
+ // for the primary session. Without this the pull request stayed a draft after
538
+ // the last restart iteration and --auto-merge retried `gh pr merge` every
539
+ // 120 seconds for 4d 12h ("Pull Request is still a draft"), because a draft
540
+ // PR still reports mergeable=MERGEABLE / mergeStateStatus=CLEAN.
541
+ if (prNumber) {
542
+ await ensurePullRequestIsReady({
543
+ owner,
544
+ repo,
545
+ prNumber,
546
+ $,
547
+ log,
548
+ formatAligned,
549
+ reason: 'restart iteration finished',
550
+ reportError,
551
+ });
552
+ }
508
553
  }
509
-
510
- await ensurePullRequestBaseBranch({ owner, repo, prNumber, argv, log, formatAligned, $ });
511
- await recordResourceSnapshot({
512
- phase: RESOURCE_PHASE_RESTART_AFTER,
513
- log,
514
- diskPath: '/',
515
- label: 'after AI restart iteration',
516
- });
517
-
518
- // Issue #2090: every restart iteration starts a brand new tool session with
519
- // its own session UUID. Collect its development log here — this is the single
520
- // chokepoint shared by watch mode, auto-restart-until-mergeable,
521
- // keep-working, escalation and auto-ensure — otherwise only the very first
522
- // session of the process ever reached the pull request.
523
- // When the tool did not report a session id there is nothing to key a new
524
- // directory on, so extend the previously collected session instead of losing
525
- // this iteration's part of the log.
526
- const { finalizeActiveDevelopmentLog } = await import('./development-log.finalize.lib.mjs');
527
- await (toolResult?.sessionId ? finalizeActiveDevelopmentLog({ sessionId: toolResult.sessionId }) : finalizeActiveDevelopmentLog({ force: true }));
528
-
529
- return toolResult;
530
554
  };
531
555
 
532
556
  /**
@@ -802,18 +802,12 @@ Fixes ${issueRef}
802
802
  await log(` âš ī¸ Error updating PR description: ${descError.message}`);
803
803
  }
804
804
  }
805
- // Check if PR is ready for review (convert from draft if necessary)
806
- if (pr.isDraft) {
807
- await log(' 🔄 Converting PR from draft to ready for review...');
808
- const readyResult = await $`gh pr ready ${pr.number} --repo ${owner}/${repo}`;
809
- if (readyResult.code === 0) {
810
- await log(' ✅ PR converted to ready for review');
811
- } else {
812
- await log(` âš ī¸ Could not convert PR to ready (${readyResult.stderr ? readyResult.stderr.toString().trim() : 'unknown error'})`);
813
- }
814
- } else {
815
- await log(' ✅ PR is already ready for review', { verbose: true });
816
- }
805
+ // Check if PR is ready for review (convert from draft if necessary).
806
+ // Issue #2182: this used to be an inline `gh pr ready` that bypassed
807
+ // pr-draft-state.lib.mjs, so it neither skipped merged/closed PRs nor cleared the
808
+ // session draft registry that guarantees the ready transition on every exit path.
809
+ const { ensurePullRequestIsReady } = await import('./pr-draft-state.lib.mjs');
810
+ await ensurePullRequestIsReady({ owner, repo, prNumber: pr.number, $, log, reason: 'solution draft verified' });
817
811
  }
818
812
 
819
813
  // Upload log file to PR if requested