maistro 1.0.422 → 1.0.448

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;IAyG5B;;;OAGG;YACW,iBAAiB;IA8D/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAuC1B;;;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';
@@ -371,6 +371,8 @@ export class MaistroApp {
371
371
  restartAfterUpdate() {
372
372
  console.log(`\x1b[32m✓ Updated successfully!\x1b[0m`);
373
373
  console.log(`\x1b[90mRestarting maistro...\x1b[0m\n`);
374
+ // Ensure terminal is in a clean state before spawning
375
+ this.ui.disableRawMode();
374
376
  // Get the original command arguments (skip node and script path)
375
377
  const args = process.argv.slice(2);
376
378
  // Spawn new maistro process with same arguments
@@ -384,9 +386,13 @@ export class MaistroApp {
384
386
  MAISTRO_SKIP_UPDATE_CHECK: '1',
385
387
  },
386
388
  });
387
- // Exit current process once child starts
389
+ // Exit current process after a delay to let child fully attach to terminal
390
+ // The 'spawn' event fires early, before stdio is fully inherited.
391
+ // Exiting too quickly can cause EIO errors when the child tries to use stdin.
388
392
  child.on('spawn', () => {
389
- process.exit(0);
393
+ setTimeout(() => {
394
+ process.exit(0);
395
+ }, 200);
390
396
  });
391
397
  // If spawn fails, exit with error
392
398
  child.on('error', (err) => {
@@ -1308,7 +1314,7 @@ export class MaistroApp {
1308
1314
  if (this.executionState?.isExecuting) {
1309
1315
  this.renderExecutionSection(termWidth);
1310
1316
  }
1311
- // Show progress bar with total duration
1317
+ // Show progress bar with total duration and caffeinate indicator
1312
1318
  // Calculate total duration: completed tasks + current task elapsed (if executing)
1313
1319
  const completedDurationMs = tasks.reduce((sum, t) => sum + (t.durationMs || 0), 0);
1314
1320
  const currentTaskElapsedMs = this.executionState?.isExecuting
@@ -1316,7 +1322,8 @@ export class MaistroApp {
1316
1322
  : 0;
1317
1323
  const totalDurationMs = completedDurationMs + currentTaskElapsedMs;
1318
1324
  const durationStr = totalDurationMs > 0 ? ` • ${this.formatDuration(totalDurationMs)}` : '';
1319
- console.log(`${progressBar(status.tasks.completed, status.tasks.total, 30, status.tasks.skipped)}${durationStr}\n`);
1325
+ const mainCaffeinateIndicator = isCaffeinateRunning() ? ' \x1b[33m☕ caffeinating\x1b[0m' : '';
1326
+ console.log(`${progressBar(status.tasks.completed, status.tasks.total, 30, status.tasks.skipped)}${durationStr}${mainCaffeinateIndicator}\n`);
1320
1327
  // Calculate viewport for tasks
1321
1328
  const termRows = process.stdout.rows || 24;
1322
1329
  // Fixed overhead: header(~7) + progress(2) + position indicator(1) + failed section(variable) + commands(2) + input(4 base) + margins
@@ -1356,7 +1363,7 @@ export class MaistroApp {
1356
1363
  }
1357
1364
  // Get visible slice of tasks
1358
1365
  const visibleTasks = tasks.slice(this.taskScrollOffset, this.taskScrollOffset + maxVisibleTasks);
1359
- this.displayTasks(visibleTasks, this.taskScrollOffset, selectedIndex, tasks);
1366
+ this.displayTasks(visibleTasks, this.taskScrollOffset, selectedIndex, tasks, this.executionState?.phase);
1360
1367
  // Show scroll down indicator
1361
1368
  const belowCount = totalTasks - this.taskScrollOffset - visibleTasks.length;
1362
1369
  if (belowCount > 0) {
@@ -1485,12 +1492,31 @@ export class MaistroApp {
1485
1492
  const availableRows = Math.max(6, termRows - fixedOverhead);
1486
1493
  const maxVisibleTasks = Math.min(UI.MAX_VISIBLE_TASKS, Math.max(UI.MIN_VISIBLE_TASKS, Math.floor(availableRows / UI.LINES_PER_TASK)));
1487
1494
  // Auto-scroll to first relevant task if no manual selection yet
1488
- // Priority: executing task > first pending task > last task
1495
+ // Priority: last completed task (to show summary) > first pending task > last task
1489
1496
  const executingTaskIndex = tasks.findIndex(t => t.status === 'in_progress');
1490
1497
  const firstPendingIndex = tasks.findIndex(t => t.status === 'pending');
1491
1498
  if (this.selectedTaskIndex === -1) {
1492
- if (executingTaskIndex >= 0) {
1493
- this.selectedTaskIndex = executingTaskIndex;
1499
+ if (executingTaskIndex > 0) {
1500
+ // When a task is executing, select the last completed task before it
1501
+ // This allows users to see the achievement summary
1502
+ let lastCompletedIndex = -1;
1503
+ for (let i = executingTaskIndex - 1; i >= 0; i--) {
1504
+ if (tasks[i].status === 'completed') {
1505
+ lastCompletedIndex = i;
1506
+ break;
1507
+ }
1508
+ }
1509
+ if (lastCompletedIndex >= 0) {
1510
+ this.selectedTaskIndex = lastCompletedIndex;
1511
+ }
1512
+ else {
1513
+ // No completed task before executing one, select executing task
1514
+ this.selectedTaskIndex = executingTaskIndex;
1515
+ }
1516
+ }
1517
+ else if (executingTaskIndex === 0) {
1518
+ // First task is executing, select it
1519
+ this.selectedTaskIndex = 0;
1494
1520
  }
1495
1521
  else if (firstPendingIndex >= 0) {
1496
1522
  // Default to first pending task - shows what's next to be done
@@ -2010,6 +2036,16 @@ export class MaistroApp {
2010
2036
  }, 50);
2011
2037
  return;
2012
2038
  }
2039
+ // Partial CSI sequence (ESC [) - wait for final character(s)
2040
+ // This handles cases where escape sequences arrive character-by-character (e.g., in PTY tests)
2041
+ if (key === '\x1b[' || (key.startsWith('\x1b[') && key.length < 4 && !/[A-Za-z~]$/.test(key))) {
2042
+ escapeBuffer = key;
2043
+ escapeTimeout = setTimeout(() => {
2044
+ // Timeout - discard partial sequence
2045
+ escapeBuffer = '';
2046
+ }, 50);
2047
+ return;
2048
+ }
2013
2049
  // Clear escape pending states on any other input
2014
2050
  if ((escPendingClear || escPendingPause) && key !== '\x1b') {
2015
2051
  if (escTimeout) {
@@ -3156,6 +3192,30 @@ export class MaistroApp {
3156
3192
  if (!hasOutput && result.response) {
3157
3193
  console.log(`${result.response}\n`);
3158
3194
  }
3195
+ // Check for structured questions first (from new JSON format)
3196
+ if (result.questions && result.questions.length > 0) {
3197
+ // Use the existing discovery question UI to present questions
3198
+ const answers = {};
3199
+ const totalQuestions = result.questions.length;
3200
+ for (let i = 0; i < result.questions.length; i++) {
3201
+ const question = result.questions[i];
3202
+ const answer = await this.askDiscoveryQuestionRequired(question, i + 1, totalQuestions, '' // No summary needed for plan refinement
3203
+ );
3204
+ if (answer === null) {
3205
+ // User cancelled - return to main view
3206
+ return null;
3207
+ }
3208
+ answers[question.id] = answer;
3209
+ }
3210
+ // Format answers as full text and continue conversation
3211
+ const answerText = Object.entries(answers)
3212
+ .map(([id, value]) => {
3213
+ const q = result.questions.find(q => q.id === id);
3214
+ return `${q?.question || id}: ${value}`;
3215
+ })
3216
+ .join('\n');
3217
+ return this.handleInlineDiscussion(answerText, currentTasks, goal);
3218
+ }
3159
3219
  // Return updated tasks if any (or null if no changes)
3160
3220
  if (result.updatedTasks && result.updatedTasks.length > 0) {
3161
3221
  // Process any image URLs in the updated task descriptions
@@ -3172,8 +3232,28 @@ export class MaistroApp {
3172
3232
  await new Promise(r => setTimeout(r, 500));
3173
3233
  return tasksWithImages;
3174
3234
  }
3175
- // No task changes - just wait briefly and return
3176
- await new Promise(r => setTimeout(r, 300));
3235
+ // No task changes and no structured questions - allow text input for follow-up
3236
+ if (hasOutput && result.response) {
3237
+ // Fall back to text input
3238
+ console.log(''); // Add spacing
3239
+ const followUp = await this.ui.promptWithEscape('\x1b[90mReply to continue discussion (Enter to go back):\x1b[0m ', '');
3240
+ if (followUp && followUp.trim()) {
3241
+ // User wants to continue the conversation - recursively call with follow-up
3242
+ return this.handleInlineDiscussion(followUp.trim(), currentTasks, goal);
3243
+ }
3244
+ // Empty input or Escape - return to main view
3245
+ }
3246
+ else if (hasOutput) {
3247
+ // No result.response available - simple text input fallback
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
+ return this.handleInlineDiscussion(followUp.trim(), currentTasks, goal);
3252
+ }
3253
+ }
3254
+ else {
3255
+ await new Promise(r => setTimeout(r, 300));
3256
+ }
3177
3257
  return null;
3178
3258
  }
3179
3259
  /**
@@ -3377,7 +3457,7 @@ ${detection.projectContext}`;
3377
3457
  getLogger()?.error('handleInit error', { error: String(err), stack: err instanceof Error ? err.stack : undefined });
3378
3458
  }
3379
3459
  }
3380
- displayTasks(tasks, startIndex = 0, selectedIndex = -1, allTasks) {
3460
+ displayTasks(tasks, startIndex = 0, selectedIndex = -1, allTasks, executionPhase) {
3381
3461
  const termWidth = process.stdout.columns || 80;
3382
3462
  // Helper to get visible length (strip ANSI codes)
3383
3463
  const visibleLength = (s) => s.replace(/\x1b\[[0-9;]*m/g, '').length;
@@ -3389,15 +3469,27 @@ ${detection.projectContext}`;
3389
3469
  ? formatBlockedStatus()
3390
3470
  : formatTaskStatusIcon(task.status, task.failureType);
3391
3471
  // Format status name in brackets
3472
+ // For in_progress tasks, show the current phase (Implementation/Validation/Committing)
3473
+ // Default to 'executing' (Implementation) if no phase is provided but task is in_progress
3474
+ const phaseForStatus = task.status === 'in_progress' ? (executionPhase ?? 'executing') : undefined;
3392
3475
  const statusBracket = blocked
3393
3476
  ? ' \x1b[31m[Blocked]\x1b[0m'
3394
- : ` [${formatStatusName(task.status, task.failureType)}]`;
3477
+ : ` [${formatStatusName(task.status, task.failureType, phaseForStatus)}]`;
3478
+ // Format task duration for completed and in_progress tasks
3479
+ let durationStr = '';
3480
+ if (task.status === 'completed' && task.durationMs) {
3481
+ durationStr = ` \x1b[90m${this.formatDuration(task.durationMs)}\x1b[0m`;
3482
+ }
3483
+ else if (task.status === 'in_progress' && this.executionState?.taskStartTime) {
3484
+ const elapsed = Date.now() - this.executionState.taskStartTime;
3485
+ durationStr = ` \x1b[36m${this.formatDuration(elapsed)}\x1b[0m`;
3486
+ }
3395
3487
  const taskNum = startIndex + i + 1;
3396
3488
  const isSelected = (startIndex + i) === selectedIndex;
3397
3489
  const cursor = isSelected ? '\x1b[36m▶\x1b[0m ' : ' ';
3398
3490
  // Build line template and measure total visible length
3399
3491
  const prefix = `${cursor}${statusIcon} ${taskNum}. `;
3400
- const suffix = statusBracket;
3492
+ const suffix = statusBracket + durationStr;
3401
3493
  const prefixLen = visibleLength(prefix);
3402
3494
  const suffixLen = visibleLength(suffix);
3403
3495
  // Calculate max title length (leave 1 char margin for safety)
@@ -3410,10 +3502,10 @@ ${detection.projectContext}`;
3410
3502
  }
3411
3503
  // Output the line
3412
3504
  if (isSelected) {
3413
- console.log(`${cursor}${statusIcon} \x1b[1;36m${taskNum}.\x1b[0m \x1b[1;36m${title}\x1b[0m${statusBracket}`);
3505
+ console.log(`${cursor}${statusIcon} \x1b[1;36m${taskNum}.\x1b[0m \x1b[1;36m${title}\x1b[0m${statusBracket}${durationStr}`);
3414
3506
  }
3415
3507
  else {
3416
- console.log(`${cursor}${statusIcon} \x1b[1m${taskNum}.\x1b[0m ${title}${statusBracket}`);
3508
+ console.log(`${cursor}${statusIcon} \x1b[1m${taskNum}.\x1b[0m ${title}${statusBracket}${durationStr}`);
3417
3509
  }
3418
3510
  // Show 5-line result summary preview for selected completed tasks
3419
3511
  if (isSelected && task.status === 'completed' && task.resultSummary) {
@@ -3938,7 +4030,7 @@ ${detection.projectContext}`;
3938
4030
  validating: '\x1b[33mValidating\x1b[0m',
3939
4031
  committing: '\x1b[32mCommitting\x1b[0m',
3940
4032
  }[this.executionState.phase];
3941
- console.log(`${taskBar} 📋 \x1b[1mTask ${this.executionState.currentTaskIndex + 1}\x1b[0m ${phaseLabel} \x1b[90m(${taskElapsed})\x1b[0m`);
4033
+ console.log(`${taskBar} 📋 \x1b[1mTask ${this.executionState.currentTaskIndex + 1}\x1b[0m ${phaseLabel} \x1b[90m(${taskElapsed})\x1b[0m`);
3942
4034
  }
3943
4035
  }
3944
4036
  // ==================== END EXECUTION STATE MANAGEMENT ====================
@@ -4130,11 +4222,31 @@ ${detection.projectContext}`;
4130
4222
  return;
4131
4223
  const failed = tasks.filter(t => t.status === 'failed');
4132
4224
  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)
4225
+ // Update selected task to last completed task (only if user hasn't navigated)
4226
+ // This shows the achievement summary for the most recently completed task
4227
+ // while the next task is executing
4134
4228
  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);
4229
+ const executingTaskIndex = tasks.findIndex(t => t.status === 'in_progress');
4230
+ if (executingTaskIndex > 0) {
4231
+ // Find the last completed task before the executing one
4232
+ let lastCompletedIndex = -1;
4233
+ for (let i = executingTaskIndex - 1; i >= 0; i--) {
4234
+ if (tasks[i].status === 'completed') {
4235
+ lastCompletedIndex = i;
4236
+ break;
4237
+ }
4238
+ }
4239
+ if (lastCompletedIndex >= 0) {
4240
+ this.selectedTaskIndex = lastCompletedIndex;
4241
+ }
4242
+ else {
4243
+ // No completed task before executing one, select the executing task
4244
+ this.selectedTaskIndex = executingTaskIndex;
4245
+ }
4246
+ }
4247
+ else if (executingTaskIndex === 0) {
4248
+ // First task is executing, select it
4249
+ this.selectedTaskIndex = 0;
4138
4250
  }
4139
4251
  }
4140
4252
  // Clear and render