maistro 1.0.424 → 1.0.454

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/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAiOA,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAA+B;gBAEzC,WAAW,GAAE,MAAY;IAsBrC,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,qBAAqB;IA+CnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyG5B;;;OAGG;YACW,iBAAiB;IA8D/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAgC1B;;;OAGG;IACG,YAAY,CAAC,SAAS,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAqFtD;;;OAGG;YACW,iBAAiB;IAwD/B;;OAEG;YACW,sBAAsB;IAsCpC;;;OAGG;YACW,iBAAiB;IAoB/B;;;OAGG;YACW,oBAAoB;IAkClC;;OAEG;YACW,qBAAqB;IA4BnC;;OAEG;YACW,gBAAgB;IAoB9B;;;OAGG;YACW,qBAAqB;IAoEnC;;;OAGG;YACW,eAAe;IAyH7B;;;OAGG;YACW,uBAAuB;IAyErC;;OAEG;YACW,4BAA4B;IAiE1C;;OAEG;YACW,oBAAoB;IAgElC;;;;OAIG;YACW,yBAAyB;IAgIvC;;;OAGG;YACW,qBAAqB;IAmHnC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;YACW,mBAAmB;IA0BjC;;OAEG;YACW,qBAAqB;IAiDnC;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,UAAU;IAQlB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IA0J7B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;OAGG;YACW,YAAY;IAoS1B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA2CxB;;OAEG;YACW,aAAa;IA2lB3B;;;OAGG;YACW,iBAAiB;IAgI/B;;OAEG;YACW,cAAc;IAwU5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,gBAAgB;IAe9B;;;OAGG;YACW,WAAW;IAsEzB;;;OAGG;YACW,mBAAmB;IA8BjC;;;OAGG;YACW,cAAc;IAkB5B;;;;OAIG;YACW,6BAA6B;IAqI3C;;;OAGG;YACW,aAAa;IA+B3B;;;;OAIG;YACW,sBAAsB;IA4LpC;;;;OAIG;YACW,sBAAsB;YA0GtB,UAAU;IA4HxB,OAAO,CAAC,YAAY;IAiEpB;;;OAGG;YACW,eAAe;IAqD7B;;OAEG;YACW,cAAc;IAsO5B;;OAEG;YACW,oBAAoB;IAwBlC;;OAEG;YACW,eAAe;IAU7B;;OAEG;YACW,wBAAwB;YAsBxB,gBAAgB;IA4B9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsD;IAEpF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAQ/B;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,sBAAsB;YAoEhB,SAAS;YAiIT,sBAAsB;CAgbrC"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAiOA,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAA+B;gBAEzC,WAAW,GAAE,MAAY;IAsBrC,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,qBAAqB;IA+CnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiH5B;;;OAGG;YACW,iBAAiB;IA8D/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA4C1B;;;OAGG;IACG,YAAY,CAAC,SAAS,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAqFtD;;;OAGG;YACW,iBAAiB;IAwD/B;;OAEG;YACW,sBAAsB;IAsCpC;;;OAGG;YACW,iBAAiB;IAoB/B;;;OAGG;YACW,oBAAoB;IAkClC;;OAEG;YACW,qBAAqB;IA4BnC;;OAEG;YACW,gBAAgB;IAoB9B;;;OAGG;YACW,qBAAqB;IAoEnC;;;OAGG;YACW,eAAe;IAyH7B;;;OAGG;YACW,uBAAuB;IAyErC;;OAEG;YACW,4BAA4B;IAiE1C;;OAEG;YACW,oBAAoB;IAgElC;;;;OAIG;YACW,yBAAyB;IAgIvC;;;OAGG;YACW,qBAAqB;IAmHnC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;YACW,mBAAmB;IA0BjC;;OAEG;YACW,qBAAqB;IAiDnC;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,UAAU;IAQlB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IA2J7B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;OAGG;YACW,YAAY;IAqT1B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA2CxB;;OAEG;YACW,aAAa;IAsmB3B;;;OAGG;YACW,iBAAiB;IAgI/B;;OAEG;YACW,cAAc;IAwU5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,gBAAgB;IAe9B;;;OAGG;YACW,WAAW;IAsEzB;;;OAGG;YACW,mBAAmB;IA8BjC;;;OAGG;YACW,cAAc;IAkB5B;;;;OAIG;YACW,6BAA6B;IAqI3C;;;OAGG;YACW,aAAa;IA+B3B;;;;OAIG;YACW,sBAAsB;IAwPpC;;;;OAIG;YACW,sBAAsB;YA0GtB,UAAU;IA4HxB,OAAO,CAAC,YAAY;IAmFpB;;;OAGG;YACW,eAAe;IAqD7B;;OAEG;YACW,cAAc;IAsO5B;;OAEG;YACW,oBAAoB;IAwBlC;;OAEG;YACW,eAAe;IAU7B;;OAEG;YACW,wBAAwB;YAsBxB,gBAAgB;IA4B9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsD;IAEpF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAQ/B;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,sBAAsB;YAoEhB,SAAS;YAiIT,sBAAsB;CAkcrC"}
package/dist/app.js CHANGED
@@ -13,7 +13,7 @@ import { VERSION } from './version.js';
13
13
  import { buildHeaderWithLogo } from './logo.js';
14
14
  import { addImageFromFile, addImageFromUrl, addImageFromPaste, getImages, deleteImage, looksLikeImagePath, looksLikeImageUrl, formatImageSize, sanitizeImageId, generatePasteImageId, extractImageUrls, processDescriptionImageUrls, } from './imageManager.js';
15
15
  import { InputBox, getDisplayWidth, charIndexAtDisplayColumn } from './inputBox.js';
16
- import { startCaffeinate, stopCaffeinate, isCaffeinateAvailable } from './caffeinate.js';
16
+ import { startCaffeinate, stopCaffeinate, isCaffeinateAvailable, isCaffeinateRunning } from './caffeinate.js';
17
17
  import { getPreventSleep, setPreventSleep, getConfigPath, detectTerminal, getShiftEnterEnabled, setShiftEnterEnabled, configureTerminalShiftEnter, removeTerminalShiftEnter, getNewlineHint, getCmdVPasteEnabled, setCmdVPasteEnabled, configureTerminalCmdVPaste, removeTerminalCmdVPaste, getCmdVPasteInstructions, } from './config.js';
18
18
  import { hasClipboardImage, saveClipboardImage, cleanupTempImage } from './clipboard.js';
19
19
  import { isGitRepo, ensureEnvInGitignore } from './git.js';
@@ -220,6 +220,13 @@ export class MaistroApp {
220
220
  * Start the interactive application
221
221
  */
222
222
  async start() {
223
+ // If this is a restart after update, wait for terminal to settle
224
+ // This prevents EIO errors when the parent process just exited
225
+ if (process.env.MAISTRO_IS_RESTART) {
226
+ await new Promise(resolve => setTimeout(resolve, 300));
227
+ // Clear the flag so nested restarts don't accumulate delays
228
+ delete process.env.MAISTRO_IS_RESTART;
229
+ }
223
230
  this.ui.init();
224
231
  // Simple startup check (no header yet - showPlanView will show full UI)
225
232
  console.log(`\n\x1b[90mStarting maistro v${VERSION}\x1b[0m`);
@@ -371,24 +378,33 @@ export class MaistroApp {
371
378
  restartAfterUpdate() {
372
379
  console.log(`\x1b[32m✓ Updated successfully!\x1b[0m`);
373
380
  console.log(`\x1b[90mRestarting maistro...\x1b[0m\n`);
381
+ // Ensure terminal is in a clean state before spawning
382
+ this.ui.disableRawMode();
383
+ // Close the readline interface to release stdin
384
+ this.ui.close();
374
385
  // Get the original command arguments (skip node and script path)
375
386
  const args = process.argv.slice(2);
376
- // Spawn new maistro process with same arguments
377
- // Use stdio: 'inherit' so the new process takes over the terminal
387
+ // Spawn new maistro process in detached mode so it becomes the session leader
388
+ // This ensures proper terminal control handoff
378
389
  const child = spawn(process.argv[0], [process.argv[1], ...args], {
379
390
  stdio: 'inherit',
380
- detached: false,
391
+ detached: true,
381
392
  env: {
382
393
  ...process.env,
383
394
  // Skip update check on restart to avoid infinite loop
384
395
  MAISTRO_SKIP_UPDATE_CHECK: '1',
396
+ // Signal that this is a restart so child can wait for terminal to settle
397
+ MAISTRO_IS_RESTART: '1',
385
398
  },
386
399
  });
387
- // Exit current process once child starts
388
- child.on('spawn', () => {
400
+ // Unreference the child so parent can exit independently
401
+ child.unref();
402
+ // Exit immediately - the detached child will take over
403
+ // Using setImmediate to ensure the spawn completes first
404
+ setImmediate(() => {
389
405
  process.exit(0);
390
406
  });
391
- // If spawn fails, exit with error
407
+ // If spawn fails before we exit, show error
392
408
  child.on('error', (err) => {
393
409
  console.error(`\x1b[31m✗ Failed to restart: ${err.message}\x1b[0m`);
394
410
  console.log(`\x1b[90mPlease restart maistro manually.\x1b[0m\n`);
@@ -1308,7 +1324,7 @@ export class MaistroApp {
1308
1324
  if (this.executionState?.isExecuting) {
1309
1325
  this.renderExecutionSection(termWidth);
1310
1326
  }
1311
- // Show progress bar with total duration
1327
+ // Show progress bar with total duration and caffeinate indicator
1312
1328
  // Calculate total duration: completed tasks + current task elapsed (if executing)
1313
1329
  const completedDurationMs = tasks.reduce((sum, t) => sum + (t.durationMs || 0), 0);
1314
1330
  const currentTaskElapsedMs = this.executionState?.isExecuting
@@ -1316,7 +1332,8 @@ export class MaistroApp {
1316
1332
  : 0;
1317
1333
  const totalDurationMs = completedDurationMs + currentTaskElapsedMs;
1318
1334
  const durationStr = totalDurationMs > 0 ? ` • ${this.formatDuration(totalDurationMs)}` : '';
1319
- console.log(`${progressBar(status.tasks.completed, status.tasks.total, 30, status.tasks.skipped)}${durationStr}\n`);
1335
+ const mainCaffeinateIndicator = isCaffeinateRunning() ? ' \x1b[33m☕ caffeinating\x1b[0m' : '';
1336
+ console.log(`${progressBar(status.tasks.completed, status.tasks.total, 30, status.tasks.skipped)}${durationStr}${mainCaffeinateIndicator}\n`);
1320
1337
  // Calculate viewport for tasks
1321
1338
  const termRows = process.stdout.rows || 24;
1322
1339
  // Fixed overhead: header(~7) + progress(2) + position indicator(1) + failed section(variable) + commands(2) + input(4 base) + margins
@@ -1356,7 +1373,7 @@ export class MaistroApp {
1356
1373
  }
1357
1374
  // Get visible slice of tasks
1358
1375
  const visibleTasks = tasks.slice(this.taskScrollOffset, this.taskScrollOffset + maxVisibleTasks);
1359
- this.displayTasks(visibleTasks, this.taskScrollOffset, selectedIndex, tasks);
1376
+ this.displayTasks(visibleTasks, this.taskScrollOffset, selectedIndex, tasks, this.executionState?.phase);
1360
1377
  // Show scroll down indicator
1361
1378
  const belowCount = totalTasks - this.taskScrollOffset - visibleTasks.length;
1362
1379
  if (belowCount > 0) {
@@ -1485,12 +1502,31 @@ export class MaistroApp {
1485
1502
  const availableRows = Math.max(6, termRows - fixedOverhead);
1486
1503
  const maxVisibleTasks = Math.min(UI.MAX_VISIBLE_TASKS, Math.max(UI.MIN_VISIBLE_TASKS, Math.floor(availableRows / UI.LINES_PER_TASK)));
1487
1504
  // Auto-scroll to first relevant task if no manual selection yet
1488
- // Priority: executing task > first pending task > last task
1505
+ // Priority: last completed task (to show summary) > first pending task > last task
1489
1506
  const executingTaskIndex = tasks.findIndex(t => t.status === 'in_progress');
1490
1507
  const firstPendingIndex = tasks.findIndex(t => t.status === 'pending');
1491
1508
  if (this.selectedTaskIndex === -1) {
1492
- if (executingTaskIndex >= 0) {
1493
- this.selectedTaskIndex = executingTaskIndex;
1509
+ if (executingTaskIndex > 0) {
1510
+ // When a task is executing, select the last completed task before it
1511
+ // This allows users to see the achievement summary
1512
+ let lastCompletedIndex = -1;
1513
+ for (let i = executingTaskIndex - 1; i >= 0; i--) {
1514
+ if (tasks[i].status === 'completed') {
1515
+ lastCompletedIndex = i;
1516
+ break;
1517
+ }
1518
+ }
1519
+ if (lastCompletedIndex >= 0) {
1520
+ this.selectedTaskIndex = lastCompletedIndex;
1521
+ }
1522
+ else {
1523
+ // No completed task before executing one, select executing task
1524
+ this.selectedTaskIndex = executingTaskIndex;
1525
+ }
1526
+ }
1527
+ else if (executingTaskIndex === 0) {
1528
+ // First task is executing, select it
1529
+ this.selectedTaskIndex = 0;
1494
1530
  }
1495
1531
  else if (firstPendingIndex >= 0) {
1496
1532
  // Default to first pending task - shows what's next to be done
@@ -2010,6 +2046,16 @@ export class MaistroApp {
2010
2046
  }, 50);
2011
2047
  return;
2012
2048
  }
2049
+ // Partial CSI sequence (ESC [) - wait for final character(s)
2050
+ // This handles cases where escape sequences arrive character-by-character (e.g., in PTY tests)
2051
+ if (key === '\x1b[' || (key.startsWith('\x1b[') && key.length < 4 && !/[A-Za-z~]$/.test(key))) {
2052
+ escapeBuffer = key;
2053
+ escapeTimeout = setTimeout(() => {
2054
+ // Timeout - discard partial sequence
2055
+ escapeBuffer = '';
2056
+ }, 50);
2057
+ return;
2058
+ }
2013
2059
  // Clear escape pending states on any other input
2014
2060
  if ((escPendingClear || escPendingPause) && key !== '\x1b') {
2015
2061
  if (escTimeout) {
@@ -3156,6 +3202,30 @@ export class MaistroApp {
3156
3202
  if (!hasOutput && result.response) {
3157
3203
  console.log(`${result.response}\n`);
3158
3204
  }
3205
+ // Check for structured questions first (from new JSON format)
3206
+ if (result.questions && result.questions.length > 0) {
3207
+ // Use the existing discovery question UI to present questions
3208
+ const answers = {};
3209
+ const totalQuestions = result.questions.length;
3210
+ for (let i = 0; i < result.questions.length; i++) {
3211
+ const question = result.questions[i];
3212
+ const answer = await this.askDiscoveryQuestionRequired(question, i + 1, totalQuestions, '' // No summary needed for plan refinement
3213
+ );
3214
+ if (answer === null) {
3215
+ // User cancelled - return to main view
3216
+ return null;
3217
+ }
3218
+ answers[question.id] = answer;
3219
+ }
3220
+ // Format answers as full text and continue conversation
3221
+ const answerText = Object.entries(answers)
3222
+ .map(([id, value]) => {
3223
+ const q = result.questions.find(q => q.id === id);
3224
+ return `${q?.question || id}: ${value}`;
3225
+ })
3226
+ .join('\n');
3227
+ return this.handleInlineDiscussion(answerText, currentTasks, goal);
3228
+ }
3159
3229
  // Return updated tasks if any (or null if no changes)
3160
3230
  if (result.updatedTasks && result.updatedTasks.length > 0) {
3161
3231
  // Process any image URLs in the updated task descriptions
@@ -3172,8 +3242,28 @@ export class MaistroApp {
3172
3242
  await new Promise(r => setTimeout(r, 500));
3173
3243
  return tasksWithImages;
3174
3244
  }
3175
- // No task changes - just wait briefly and return
3176
- await new Promise(r => setTimeout(r, 300));
3245
+ // No task changes and no structured questions - allow text input for follow-up
3246
+ if (hasOutput && result.response) {
3247
+ // Fall back to text input
3248
+ console.log(''); // Add spacing
3249
+ const followUp = await this.ui.promptWithEscape('\x1b[90mReply to continue discussion (Enter to go back):\x1b[0m ', '');
3250
+ if (followUp && followUp.trim()) {
3251
+ // User wants to continue the conversation - recursively call with follow-up
3252
+ return this.handleInlineDiscussion(followUp.trim(), currentTasks, goal);
3253
+ }
3254
+ // Empty input or Escape - return to main view
3255
+ }
3256
+ else if (hasOutput) {
3257
+ // No result.response available - simple text input fallback
3258
+ console.log(''); // Add spacing
3259
+ const followUp = await this.ui.promptWithEscape('\x1b[90mReply to continue discussion (Enter to go back):\x1b[0m ', '');
3260
+ if (followUp && followUp.trim()) {
3261
+ return this.handleInlineDiscussion(followUp.trim(), currentTasks, goal);
3262
+ }
3263
+ }
3264
+ else {
3265
+ await new Promise(r => setTimeout(r, 300));
3266
+ }
3177
3267
  return null;
3178
3268
  }
3179
3269
  /**
@@ -3377,7 +3467,7 @@ ${detection.projectContext}`;
3377
3467
  getLogger()?.error('handleInit error', { error: String(err), stack: err instanceof Error ? err.stack : undefined });
3378
3468
  }
3379
3469
  }
3380
- displayTasks(tasks, startIndex = 0, selectedIndex = -1, allTasks) {
3470
+ displayTasks(tasks, startIndex = 0, selectedIndex = -1, allTasks, executionPhase) {
3381
3471
  const termWidth = process.stdout.columns || 80;
3382
3472
  // Helper to get visible length (strip ANSI codes)
3383
3473
  const visibleLength = (s) => s.replace(/\x1b\[[0-9;]*m/g, '').length;
@@ -3389,15 +3479,27 @@ ${detection.projectContext}`;
3389
3479
  ? formatBlockedStatus()
3390
3480
  : formatTaskStatusIcon(task.status, task.failureType);
3391
3481
  // Format status name in brackets
3482
+ // For in_progress tasks, show the current phase (Implementation/Validation/Committing)
3483
+ // Default to 'executing' (Implementation) if no phase is provided but task is in_progress
3484
+ const phaseForStatus = task.status === 'in_progress' ? (executionPhase ?? 'executing') : undefined;
3392
3485
  const statusBracket = blocked
3393
3486
  ? ' \x1b[31m[Blocked]\x1b[0m'
3394
- : ` [${formatStatusName(task.status, task.failureType)}]`;
3487
+ : ` [${formatStatusName(task.status, task.failureType, phaseForStatus)}]`;
3488
+ // Format task duration for completed and in_progress tasks
3489
+ let durationStr = '';
3490
+ if (task.status === 'completed' && task.durationMs) {
3491
+ durationStr = ` \x1b[90m${this.formatDuration(task.durationMs)}\x1b[0m`;
3492
+ }
3493
+ else if (task.status === 'in_progress' && this.executionState?.taskStartTime) {
3494
+ const elapsed = Date.now() - this.executionState.taskStartTime;
3495
+ durationStr = ` \x1b[36m${this.formatDuration(elapsed)}\x1b[0m`;
3496
+ }
3395
3497
  const taskNum = startIndex + i + 1;
3396
3498
  const isSelected = (startIndex + i) === selectedIndex;
3397
3499
  const cursor = isSelected ? '\x1b[36m▶\x1b[0m ' : ' ';
3398
3500
  // Build line template and measure total visible length
3399
3501
  const prefix = `${cursor}${statusIcon} ${taskNum}. `;
3400
- const suffix = statusBracket;
3502
+ const suffix = statusBracket + durationStr;
3401
3503
  const prefixLen = visibleLength(prefix);
3402
3504
  const suffixLen = visibleLength(suffix);
3403
3505
  // Calculate max title length (leave 1 char margin for safety)
@@ -3410,10 +3512,10 @@ ${detection.projectContext}`;
3410
3512
  }
3411
3513
  // Output the line
3412
3514
  if (isSelected) {
3413
- console.log(`${cursor}${statusIcon} \x1b[1;36m${taskNum}.\x1b[0m \x1b[1;36m${title}\x1b[0m${statusBracket}`);
3515
+ console.log(`${cursor}${statusIcon} \x1b[1;36m${taskNum}.\x1b[0m \x1b[1;36m${title}\x1b[0m${statusBracket}${durationStr}`);
3414
3516
  }
3415
3517
  else {
3416
- console.log(`${cursor}${statusIcon} \x1b[1m${taskNum}.\x1b[0m ${title}${statusBracket}`);
3518
+ console.log(`${cursor}${statusIcon} \x1b[1m${taskNum}.\x1b[0m ${title}${statusBracket}${durationStr}`);
3417
3519
  }
3418
3520
  // Show 5-line result summary preview for selected completed tasks
3419
3521
  if (isSelected && task.status === 'completed' && task.resultSummary) {
@@ -3938,7 +4040,7 @@ ${detection.projectContext}`;
3938
4040
  validating: '\x1b[33mValidating\x1b[0m',
3939
4041
  committing: '\x1b[32mCommitting\x1b[0m',
3940
4042
  }[this.executionState.phase];
3941
- console.log(`${taskBar} 📋 \x1b[1mTask ${this.executionState.currentTaskIndex + 1}\x1b[0m ${phaseLabel} \x1b[90m(${taskElapsed})\x1b[0m`);
4043
+ console.log(`${taskBar} 📋 \x1b[1mTask ${this.executionState.currentTaskIndex + 1}\x1b[0m ${phaseLabel} \x1b[90m(${taskElapsed})\x1b[0m`);
3942
4044
  }
3943
4045
  }
3944
4046
  // ==================== END EXECUTION STATE MANAGEMENT ====================
@@ -4130,11 +4232,31 @@ ${detection.projectContext}`;
4130
4232
  return;
4131
4233
  const failed = tasks.filter(t => t.status === 'failed');
4132
4234
  const allDone = tasks.every(t => t.status === 'completed' || t.status === 'skipped');
4133
- // Update selected task to current executing task (only if user hasn't navigated)
4235
+ // Update selected task to last completed task (only if user hasn't navigated)
4236
+ // This shows the achievement summary for the most recently completed task
4237
+ // while the next task is executing
4134
4238
  if (!userNavigated) {
4135
- const executingTask = tasks.find(t => t.status === 'in_progress');
4136
- if (executingTask) {
4137
- this.selectedTaskIndex = tasks.findIndex(t => t.id === executingTask.id);
4239
+ const executingTaskIndex = tasks.findIndex(t => t.status === 'in_progress');
4240
+ if (executingTaskIndex > 0) {
4241
+ // Find the last completed task before the executing one
4242
+ let lastCompletedIndex = -1;
4243
+ for (let i = executingTaskIndex - 1; i >= 0; i--) {
4244
+ if (tasks[i].status === 'completed') {
4245
+ lastCompletedIndex = i;
4246
+ break;
4247
+ }
4248
+ }
4249
+ if (lastCompletedIndex >= 0) {
4250
+ this.selectedTaskIndex = lastCompletedIndex;
4251
+ }
4252
+ else {
4253
+ // No completed task before executing one, select the executing task
4254
+ this.selectedTaskIndex = executingTaskIndex;
4255
+ }
4256
+ }
4257
+ else if (executingTaskIndex === 0) {
4258
+ // First task is executing, select it
4259
+ this.selectedTaskIndex = 0;
4138
4260
  }
4139
4261
  }
4140
4262
  // Clear and render