maistro 1.0.402 → 1.0.410
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/README.md +90 -53
- package/dist/apiKeyValidator.d.ts +58 -0
- package/dist/apiKeyValidator.d.ts.map +1 -0
- package/dist/apiKeyValidator.js +235 -0
- package/dist/apiKeyValidator.js.map +1 -0
- package/dist/app.d.ts +13 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +191 -61
- package/dist/app.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/dependencyDetector.d.ts +3 -1
- package/dist/dependencyDetector.d.ts.map +1 -1
- package/dist/dependencyDetector.js +103 -4
- package/dist/dependencyDetector.js.map +1 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +13 -14
- package/dist/executor.js.map +1 -1
- package/dist/git.d.ts +7 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +34 -0
- package/dist/git.js.map +1 -1
- package/dist/logger.d.ts +7 -10
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +45 -24
- package/dist/logger.js.map +1 -1
- package/dist/logo.d.ts +20 -0
- package/dist/logo.d.ts.map +1 -0
- package/dist/logo.js +57 -0
- package/dist/logo.js.map +1 -0
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +38 -6
- package/dist/orchestrator.js.map +1 -1
- package/dist/planner.d.ts.map +1 -1
- package/dist/planner.js +0 -3
- package/dist/planner.js.map +1 -1
- package/dist/screen.d.ts +1 -1
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +3 -22
- package/dist/screen.js.map +1 -1
- package/dist/types.d.ts +17 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/validator.d.ts +6 -34
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +256 -344
- package/dist/validator.js.map +1 -1
- package/package.json +2 -2
package/dist/app.js
CHANGED
|
@@ -2,20 +2,22 @@ import { resolve, basename, extname, join } from 'node:path';
|
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import { exec } from 'node:child_process';
|
|
5
|
+
import { exec, spawn } from 'node:child_process';
|
|
6
6
|
import { Orchestrator } from './orchestrator.js';
|
|
7
7
|
import { InteractiveUI, formatTaskStatus, formatTaskStatusIcon, formatStatusName, formatBlockedStatus, progressBar } from './ui.js';
|
|
8
8
|
import { refinePlan, generateReadme, discoverRequirements, buildProjectContext, formatContextForDecomposition, detectDependencies } from './planner.js';
|
|
9
9
|
import { isTaskBlocked } from './taskQueue.js';
|
|
10
|
-
import { isClaudeCodeInstalled, isClaudeCodeAuthenticated, testClaudeInteraction, launchClaudeLogin, } from './config.js';
|
|
10
|
+
import { isClaudeCodeInstalled, isClaudeCodeAuthenticated, testClaudeInteraction, launchClaudeLogin, loadConfig, saveConfig, } from './config.js';
|
|
11
11
|
import { initLogger, getLogger } from './logger.js';
|
|
12
12
|
import { VERSION } from './version.js';
|
|
13
|
+
import { buildHeaderWithLogo } from './logo.js';
|
|
13
14
|
import { addImageFromFile, addImageFromUrl, addImageFromPaste, getImages, deleteImage, looksLikeImagePath, looksLikeImageUrl, formatImageSize, sanitizeImageId, generatePasteImageId, extractImageUrls, processDescriptionImageUrls, } from './imageManager.js';
|
|
14
15
|
import { InputBox, getDisplayWidth, charIndexAtDisplayColumn } from './inputBox.js';
|
|
15
16
|
import { startCaffeinate, stopCaffeinate, isCaffeinateAvailable } from './caffeinate.js';
|
|
16
17
|
import { getPreventSleep, setPreventSleep, getConfigPath, detectTerminal, getShiftEnterEnabled, setShiftEnterEnabled, configureTerminalShiftEnter, removeTerminalShiftEnter, getNewlineHint, getCmdVPasteEnabled, setCmdVPasteEnabled, configureTerminalCmdVPaste, removeTerminalCmdVPaste, getCmdVPasteInstructions, } from './config.js';
|
|
17
18
|
import { hasClipboardImage, saveClipboardImage, cleanupTempImage } from './clipboard.js';
|
|
18
|
-
import { isGitRepo } from './git.js';
|
|
19
|
+
import { isGitRepo, ensureEnvInGitignore } from './git.js';
|
|
20
|
+
import { validateApiKeys } from './apiKeyValidator.js';
|
|
19
21
|
import { UI, TipsManager } from './constants.js';
|
|
20
22
|
import { getUpdateNotice, checkForUpdates, performUpdate, isAutoUpdatePromptEnabled, isAutoUpdateEnabled, } from './versionCheck.js';
|
|
21
23
|
/**
|
|
@@ -226,8 +228,9 @@ export class MaistroApp {
|
|
|
226
228
|
await this.handleUpdateCheck();
|
|
227
229
|
}
|
|
228
230
|
// Run doctor check on every startup (skip if MAISTRO_SKIP_DOCTOR is set)
|
|
231
|
+
// Pass fullCheck=false to skip interaction test if it has already passed once
|
|
229
232
|
if (!process.env.MAISTRO_SKIP_DOCTOR) {
|
|
230
|
-
const configured = await this.handleDoctor();
|
|
233
|
+
const configured = await this.handleDoctor(false);
|
|
231
234
|
if (!configured) {
|
|
232
235
|
getLogger()?.info('Exit: not configured');
|
|
233
236
|
getLogger()?.session('end');
|
|
@@ -312,8 +315,8 @@ export class MaistroApp {
|
|
|
312
315
|
console.log('\x1b[90mAuto-updating...\x1b[0m');
|
|
313
316
|
const updateResult = await performUpdate();
|
|
314
317
|
if (updateResult.success) {
|
|
315
|
-
|
|
316
|
-
|
|
318
|
+
this.restartAfterUpdate();
|
|
319
|
+
return; // Won't reach here, but for clarity
|
|
317
320
|
}
|
|
318
321
|
else {
|
|
319
322
|
console.log(`\x1b[31m✗ Update failed: ${updateResult.error}\x1b[0m`);
|
|
@@ -333,8 +336,8 @@ export class MaistroApp {
|
|
|
333
336
|
}
|
|
334
337
|
// Prompt user to update
|
|
335
338
|
const choice = await this.ui.quickPick([
|
|
339
|
+
{ key: 'yes', label: 'Yes', description: 'Update and restart automatically' },
|
|
336
340
|
{ key: 'no', label: 'No', description: 'Continue with current version' },
|
|
337
|
-
{ key: 'yes', label: 'Yes', description: 'Run npm update -g maistro' },
|
|
338
341
|
], 'Update now?');
|
|
339
342
|
if (choice !== 'yes') {
|
|
340
343
|
console.log('\x1b[90mContinuing with current version...\x1b[0m\n');
|
|
@@ -343,18 +346,49 @@ export class MaistroApp {
|
|
|
343
346
|
console.log('\n\x1b[90mUpdating...\x1b[0m');
|
|
344
347
|
const updateResult = await performUpdate();
|
|
345
348
|
if (updateResult.success) {
|
|
346
|
-
|
|
347
|
-
|
|
349
|
+
this.restartAfterUpdate();
|
|
350
|
+
return; // Won't reach here, but for clarity
|
|
348
351
|
}
|
|
349
352
|
else {
|
|
350
353
|
console.log(`\x1b[31m✗ Update failed: ${updateResult.error}\x1b[0m`);
|
|
351
354
|
console.log(`\x1b[90mContinuing with current version...\x1b[0m\n`);
|
|
352
355
|
}
|
|
353
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Restart maistro after update by spawning a new process and exiting
|
|
359
|
+
*/
|
|
360
|
+
restartAfterUpdate() {
|
|
361
|
+
console.log(`\x1b[32m✓ Updated successfully!\x1b[0m`);
|
|
362
|
+
console.log(`\x1b[90mRestarting maistro...\x1b[0m\n`);
|
|
363
|
+
// Get the original command arguments (skip node and script path)
|
|
364
|
+
const args = process.argv.slice(2);
|
|
365
|
+
// Spawn new maistro process with same arguments
|
|
366
|
+
// Use stdio: 'inherit' so the new process takes over the terminal
|
|
367
|
+
const child = spawn(process.argv[0], [process.argv[1], ...args], {
|
|
368
|
+
stdio: 'inherit',
|
|
369
|
+
detached: false,
|
|
370
|
+
env: {
|
|
371
|
+
...process.env,
|
|
372
|
+
// Skip update check on restart to avoid infinite loop
|
|
373
|
+
MAISTRO_SKIP_UPDATE_CHECK: '1',
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
// Exit current process once child starts
|
|
377
|
+
child.on('spawn', () => {
|
|
378
|
+
process.exit(0);
|
|
379
|
+
});
|
|
380
|
+
// If spawn fails, exit with error
|
|
381
|
+
child.on('error', (err) => {
|
|
382
|
+
console.error(`\x1b[31m✗ Failed to restart: ${err.message}\x1b[0m`);
|
|
383
|
+
console.log(`\x1b[90mPlease restart maistro manually.\x1b[0m\n`);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
354
387
|
/**
|
|
355
388
|
* Run doctor check - verify Claude Code CLI installation and authentication
|
|
389
|
+
* @param fullCheck - If true, always run interaction test (step 3). If false, skip if already passed once.
|
|
356
390
|
*/
|
|
357
|
-
async handleDoctor() {
|
|
391
|
+
async handleDoctor(fullCheck = true) {
|
|
358
392
|
console.log('\n\x1b[1m── Doctor ──\x1b[0m\n');
|
|
359
393
|
// Step 1: Check Claude Code CLI
|
|
360
394
|
console.log('\x1b[36m1.\x1b[0m Checking Claude Code CLI...');
|
|
@@ -380,6 +414,14 @@ export class MaistroApp {
|
|
|
380
414
|
}
|
|
381
415
|
console.log(' \x1b[32m✓ Claude Code authenticated\x1b[0m');
|
|
382
416
|
// Step 3: Test Claude Code interaction (with tool use)
|
|
417
|
+
// Skip if: not a full check AND interaction test has passed before
|
|
418
|
+
const config = loadConfig();
|
|
419
|
+
const skipInteractionTest = !fullCheck && config.interactionTestPassed === true;
|
|
420
|
+
if (skipInteractionTest) {
|
|
421
|
+
console.log('\n\x1b[32m✓ All checks passed!\x1b[0m');
|
|
422
|
+
console.log('\x1b[90mMaistro uses Claude Code CLI with your subscription for all operations.\x1b[0m\n');
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
383
425
|
console.log('\n\x1b[36m3.\x1b[0m Testing Claude Code interaction...');
|
|
384
426
|
const interactionTest = await testClaudeInteraction();
|
|
385
427
|
if (!interactionTest.success) {
|
|
@@ -406,6 +448,11 @@ export class MaistroApp {
|
|
|
406
448
|
}
|
|
407
449
|
return false;
|
|
408
450
|
}
|
|
451
|
+
// Cache successful interaction test
|
|
452
|
+
saveConfig({
|
|
453
|
+
...config,
|
|
454
|
+
interactionTestPassed: true,
|
|
455
|
+
});
|
|
409
456
|
console.log(' \x1b[32m✓ Claude Code interaction working\x1b[0m');
|
|
410
457
|
console.log('\n\x1b[32m✓ All checks passed!\x1b[0m');
|
|
411
458
|
console.log('\x1b[90mMaistro uses Claude Code CLI with your subscription for all operations.\x1b[0m\n');
|
|
@@ -664,7 +711,7 @@ export class MaistroApp {
|
|
|
664
711
|
});
|
|
665
712
|
if (skipChoice === 's' || skipChoice === null) {
|
|
666
713
|
// Even when skipping discovery, still check for dependencies
|
|
667
|
-
await this.handleDependencyDetection();
|
|
714
|
+
await this.handleDependencyDetection(goal);
|
|
668
715
|
return null; // Skip discovery
|
|
669
716
|
}
|
|
670
717
|
// Show analyzing screen with spinner
|
|
@@ -681,7 +728,7 @@ export class MaistroApp {
|
|
|
681
728
|
console.log('\x1b[90mProceeding with direct planning...\x1b[0m\n');
|
|
682
729
|
await new Promise(r => setTimeout(r, 1500));
|
|
683
730
|
// Still check for dependencies even when discovery fails
|
|
684
|
-
await this.handleDependencyDetection();
|
|
731
|
+
await this.handleDependencyDetection(goal);
|
|
685
732
|
return null;
|
|
686
733
|
}
|
|
687
734
|
// Ask each question one at a time (full-screen for each)
|
|
@@ -698,7 +745,7 @@ export class MaistroApp {
|
|
|
698
745
|
answers[question.id] = answer;
|
|
699
746
|
}
|
|
700
747
|
// Run dependency detection after discovery questions
|
|
701
|
-
const depResult = await this.handleDependencyDetection();
|
|
748
|
+
const depResult = await this.handleDependencyDetection(goal);
|
|
702
749
|
if (depResult === null) {
|
|
703
750
|
// User cancelled during dependency collection
|
|
704
751
|
return null;
|
|
@@ -894,15 +941,16 @@ export class MaistroApp {
|
|
|
894
941
|
}
|
|
895
942
|
/**
|
|
896
943
|
* Detect and collect missing external dependencies
|
|
944
|
+
* @param goal - The user's goal text to detect mentioned dependencies
|
|
897
945
|
* Returns the collected values or null if user cancelled
|
|
898
946
|
*/
|
|
899
|
-
async handleDependencyDetection() {
|
|
947
|
+
async handleDependencyDetection(goal) {
|
|
900
948
|
// Show scanning screen with spinner
|
|
901
949
|
this.ui.clear();
|
|
902
950
|
this.showHeader();
|
|
903
951
|
console.log('\x1b[1m── Scanning for Dependencies ──\x1b[0m\n');
|
|
904
952
|
const spinner = this.ui.spinner('Scanning project for external dependencies...');
|
|
905
|
-
const result = await detectDependencies(this.projectPath);
|
|
953
|
+
const result = await detectDependencies(this.projectPath, goal);
|
|
906
954
|
spinner.stop();
|
|
907
955
|
if (result.dependencies.length === 0) {
|
|
908
956
|
console.log('\x1b[90mNo external dependencies detected.\x1b[0m\n');
|
|
@@ -986,12 +1034,102 @@ export class MaistroApp {
|
|
|
986
1034
|
collectedValues[dep.id] = value.trim();
|
|
987
1035
|
}
|
|
988
1036
|
}
|
|
989
|
-
//
|
|
1037
|
+
// Validate and save collected values
|
|
990
1038
|
if (Object.keys(collectedValues).length > 0) {
|
|
991
|
-
await this.
|
|
1039
|
+
const validationResult = await this.validateCollectedKeys(collectedValues, missing);
|
|
1040
|
+
if (validationResult === 'cancel') {
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
// validationResult contains the final values (possibly updated after retries)
|
|
1044
|
+
await this.saveDependenciesToEnv(validationResult);
|
|
1045
|
+
return validationResult;
|
|
992
1046
|
}
|
|
993
1047
|
return collectedValues;
|
|
994
1048
|
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Validate collected API keys against their respective services
|
|
1051
|
+
* Returns the validated values (possibly updated after retries), or 'cancel' if user cancelled
|
|
1052
|
+
*/
|
|
1053
|
+
async validateCollectedKeys(values, deps) {
|
|
1054
|
+
let currentValues = { ...values };
|
|
1055
|
+
let validationResults;
|
|
1056
|
+
// Validation loop - allows retrying failed keys
|
|
1057
|
+
while (true) {
|
|
1058
|
+
// Show validation spinner
|
|
1059
|
+
this.ui.clear();
|
|
1060
|
+
this.showHeader();
|
|
1061
|
+
console.log('\x1b[1m── Validating Credentials ──\x1b[0m\n');
|
|
1062
|
+
const spinner = this.ui.spinner('Validating API keys...');
|
|
1063
|
+
validationResults = await validateApiKeys(currentValues, { timeout: 5000 });
|
|
1064
|
+
spinner.stop();
|
|
1065
|
+
// Display results
|
|
1066
|
+
this.ui.clear();
|
|
1067
|
+
this.showHeader();
|
|
1068
|
+
console.log('\x1b[1m── Validation Results ──\x1b[0m\n');
|
|
1069
|
+
for (const [envVar, result] of Object.entries(validationResults.results)) {
|
|
1070
|
+
const dep = deps.find(d => d.id === envVar);
|
|
1071
|
+
const name = dep?.name || envVar;
|
|
1072
|
+
if (result.valid) {
|
|
1073
|
+
console.log(`\x1b[32m✓\x1b[0m ${name}`);
|
|
1074
|
+
if (result.skipped) {
|
|
1075
|
+
console.log(` \x1b[90m(${result.message})\x1b[0m`);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
console.log(`\x1b[31m✗\x1b[0m ${name}`);
|
|
1080
|
+
console.log(` \x1b[90m${result.error || result.message}\x1b[0m`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
console.log('');
|
|
1084
|
+
// If all passed, continue
|
|
1085
|
+
if (validationResults.allPassed) {
|
|
1086
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
1087
|
+
return currentValues;
|
|
1088
|
+
}
|
|
1089
|
+
// Some validations failed - offer choices
|
|
1090
|
+
const choice = await this.ui.quickPick([
|
|
1091
|
+
{ key: 'r', label: 'Retry failed', description: 'Re-enter values for failed keys' },
|
|
1092
|
+
{ key: 's', label: 'Save anyway', description: 'Save values despite validation failure' },
|
|
1093
|
+
{ key: 'c', label: 'Cancel', description: 'Discard all values' },
|
|
1094
|
+
], 'Some validations failed. What would you like to do?', undefined, { showInput: false });
|
|
1095
|
+
if (choice === 'c' || choice === null) {
|
|
1096
|
+
return 'cancel';
|
|
1097
|
+
}
|
|
1098
|
+
if (choice === 's') {
|
|
1099
|
+
return currentValues;
|
|
1100
|
+
}
|
|
1101
|
+
// choice === 'r' - retry failed keys
|
|
1102
|
+
const failedKeys = Object.entries(validationResults.results)
|
|
1103
|
+
.filter(([, result]) => !result.valid)
|
|
1104
|
+
.map(([key]) => key);
|
|
1105
|
+
for (const envVar of failedKeys) {
|
|
1106
|
+
const dep = deps.find(d => d.id === envVar);
|
|
1107
|
+
if (!dep)
|
|
1108
|
+
continue;
|
|
1109
|
+
// Build instructions string
|
|
1110
|
+
let instructions = '';
|
|
1111
|
+
if (dep.obtainInstructions) {
|
|
1112
|
+
instructions += `\n\n\x1b[90m${dep.obtainInstructions}\x1b[0m`;
|
|
1113
|
+
}
|
|
1114
|
+
if (dep.obtainUrl) {
|
|
1115
|
+
instructions += `\n\x1b[90m→ ${dep.obtainUrl}\x1b[0m`;
|
|
1116
|
+
}
|
|
1117
|
+
if (dep.valueFormat) {
|
|
1118
|
+
instructions += `\n\x1b[90mFormat: ${dep.valueFormat}\x1b[0m`;
|
|
1119
|
+
}
|
|
1120
|
+
const value = await this.fullScreenTextInput({
|
|
1121
|
+
title: 'Retry Failed Key',
|
|
1122
|
+
subtitle: `Re-enter value for \x1b[1m${dep.name}\x1b[0m${instructions}`,
|
|
1123
|
+
placeholder: dep.valueFormat || 'enter value...',
|
|
1124
|
+
hint: 'Enter to continue, Esc to skip',
|
|
1125
|
+
initialValue: currentValues[envVar],
|
|
1126
|
+
});
|
|
1127
|
+
if (value !== null && value.trim()) {
|
|
1128
|
+
currentValues[envVar] = value.trim();
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
995
1133
|
/**
|
|
996
1134
|
* Save collected dependency values to .env file
|
|
997
1135
|
*/
|
|
@@ -1029,6 +1167,11 @@ export class MaistroApp {
|
|
|
1029
1167
|
}
|
|
1030
1168
|
try {
|
|
1031
1169
|
writeFileSync(envPath, envContent);
|
|
1170
|
+
// Ensure .env is in .gitignore to prevent accidental commit of secrets
|
|
1171
|
+
const gitignoreResult = await ensureEnvInGitignore(this.projectPath);
|
|
1172
|
+
if (gitignoreResult.success && gitignoreResult.output.includes('Added')) {
|
|
1173
|
+
console.log(`\x1b[32m✓ Added .env to .gitignore\x1b[0m`);
|
|
1174
|
+
}
|
|
1032
1175
|
console.log(`\x1b[32m✓ Saved ${Object.keys(values).length} ${Object.keys(values).length === 1 ? 'value' : 'values'} to .env\x1b[0m\n`);
|
|
1033
1176
|
}
|
|
1034
1177
|
catch (err) {
|
|
@@ -1052,15 +1195,10 @@ export class MaistroApp {
|
|
|
1052
1195
|
}
|
|
1053
1196
|
showHeader() {
|
|
1054
1197
|
const versionStr = `v${VERSION}`;
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
console.log('\x1b[1m\x1b[36m│\x1b[0m \x1b[1mM A I S T R O\x1b[0m \x1b[1m\x1b[36m│\x1b[0m');
|
|
1060
|
-
console.log('\x1b[1m\x1b[36m│\x1b[0m \x1b[90mClaude Code Orchestration Maestro\x1b[0m \x1b[1m\x1b[36m│\x1b[0m');
|
|
1061
|
-
console.log(`\x1b[1m\x1b[36m│\x1b[0m \x1b[90m${paddedVersion}\x1b[0m \x1b[1m\x1b[36m│\x1b[0m`);
|
|
1062
|
-
console.log('\x1b[1m\x1b[36m╰─────────────────────────────────────╯\x1b[0m\n');
|
|
1063
|
-
console.log(`\x1b[90mProject: ${this.projectPath}\x1b[0m\n`);
|
|
1198
|
+
const headerLines = buildHeaderWithLogo(versionStr, this.projectPath);
|
|
1199
|
+
for (const line of headerLines) {
|
|
1200
|
+
console.log(line);
|
|
1201
|
+
}
|
|
1064
1202
|
}
|
|
1065
1203
|
/**
|
|
1066
1204
|
* Render plan view content (header, progress, tasks, commands)
|
|
@@ -2286,23 +2424,28 @@ export class MaistroApp {
|
|
|
2286
2424
|
console.log(` ${line}`);
|
|
2287
2425
|
});
|
|
2288
2426
|
console.log('');
|
|
2289
|
-
// Acceptance criteria with pass/fail status
|
|
2290
|
-
if (task.
|
|
2427
|
+
// Acceptance criteria with pass/fail status (includes code quality as implicit criterion)
|
|
2428
|
+
if (task.acceptanceCriteriaResults && task.acceptanceCriteriaResults.length > 0) {
|
|
2291
2429
|
console.log(`\x1b[90mAcceptance Criteria:\x1b[0m`);
|
|
2292
|
-
task.
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
if (result) {
|
|
2297
|
-
statusIcon = result.passed ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
2298
|
-
}
|
|
2299
|
-
console.log(` ${statusIcon} ${i + 1}. ${criterion}`);
|
|
2300
|
-
if (result && !result.passed && result.explanation) {
|
|
2430
|
+
task.acceptanceCriteriaResults.forEach((result, i) => {
|
|
2431
|
+
const statusIcon = result.passed ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
2432
|
+
console.log(` ${statusIcon} ${i + 1}. ${result.criterion}`);
|
|
2433
|
+
if (!result.passed && result.explanation) {
|
|
2301
2434
|
console.log(` \x1b[31m↳ ${result.explanation}\x1b[0m`);
|
|
2302
2435
|
}
|
|
2303
2436
|
});
|
|
2304
2437
|
console.log('');
|
|
2305
2438
|
}
|
|
2439
|
+
else if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
|
|
2440
|
+
// Show acceptance criteria without results (not yet validated)
|
|
2441
|
+
console.log(`\x1b[90mAcceptance Criteria:\x1b[0m`);
|
|
2442
|
+
task.acceptanceCriteria.forEach((criterion, i) => {
|
|
2443
|
+
console.log(` \x1b[90m○\x1b[0m ${i + 1}. ${criterion}`);
|
|
2444
|
+
});
|
|
2445
|
+
// Code quality is always checked as implicit criterion
|
|
2446
|
+
console.log(` \x1b[90m○\x1b[0m ${task.acceptanceCriteria.length + 1}. Code Quality: No debugging code, no copy/paste issues, no code smells`);
|
|
2447
|
+
console.log('');
|
|
2448
|
+
}
|
|
2306
2449
|
// Result summary for completed tasks
|
|
2307
2450
|
if (isCompleted && task.resultSummary) {
|
|
2308
2451
|
console.log(`\x1b[32m── Result Summary ──\x1b[0m\n`);
|
|
@@ -2784,7 +2927,7 @@ export class MaistroApp {
|
|
|
2784
2927
|
* Uses virtual screen approach: keeps state in memory, full clear+redraw on every change
|
|
2785
2928
|
*/
|
|
2786
2929
|
async fullScreenTextInput(options) {
|
|
2787
|
-
const { title, subtitle, placeholder = 'type your answer...', hint = 'Enter to continue, Esc to cancel' } = options;
|
|
2930
|
+
const { title, subtitle, placeholder = 'type your answer...', hint = 'Enter to continue, Esc to cancel', initialValue } = options;
|
|
2788
2931
|
// Build header lines
|
|
2789
2932
|
const { buildMaistroHeader, simpleTextInput } = await import('./screen.js');
|
|
2790
2933
|
const headerLines = buildMaistroHeader(`v${VERSION}`, this.projectPath);
|
|
@@ -2794,6 +2937,7 @@ export class MaistroApp {
|
|
|
2794
2937
|
subtitle,
|
|
2795
2938
|
placeholder,
|
|
2796
2939
|
hint,
|
|
2940
|
+
initialValue,
|
|
2797
2941
|
});
|
|
2798
2942
|
return result;
|
|
2799
2943
|
}
|
|
@@ -3924,7 +4068,7 @@ ${detection.projectContext}`;
|
|
|
3924
4068
|
});
|
|
3925
4069
|
}
|
|
3926
4070
|
/**
|
|
3927
|
-
* Handle clear plan command - clears goal
|
|
4071
|
+
* Handle clear plan command - clears goal, all tasks, and logs
|
|
3928
4072
|
* Returns true if plan was cleared and should return to init flow
|
|
3929
4073
|
*/
|
|
3930
4074
|
async handleClearPlan() {
|
|
@@ -3945,8 +4089,9 @@ ${detection.projectContext}`;
|
|
|
3945
4089
|
const truncatedGoal = goal.length > 60 ? goal.slice(0, 57) + '...' : goal;
|
|
3946
4090
|
console.log(` • Goal: \x1b[33m${truncatedGoal}\x1b[0m`);
|
|
3947
4091
|
}
|
|
3948
|
-
console.log(` • ${tasks.length} task${tasks.length !== 1 ? 's' : ''}
|
|
3949
|
-
console.log(
|
|
4092
|
+
console.log(` • ${tasks.length} task${tasks.length !== 1 ? 's' : ''}`);
|
|
4093
|
+
console.log(` • All task logs\n`);
|
|
4094
|
+
console.log('\x1b[90mImages will be preserved.\x1b[0m\n');
|
|
3950
4095
|
const choice = await this.ui.quickPick([
|
|
3951
4096
|
{ key: 'clear', label: 'Clear plan' },
|
|
3952
4097
|
{ key: 'cancel', label: 'Cancel' },
|
|
@@ -3954,7 +4099,7 @@ ${detection.projectContext}`;
|
|
|
3954
4099
|
if (choice === 'clear') {
|
|
3955
4100
|
const result = await orchestrator.clearPlan();
|
|
3956
4101
|
if (result.success) {
|
|
3957
|
-
console.log('\n\x1b[32m✓ Plan cleared\x1b[0m\n');
|
|
4102
|
+
console.log('\n\x1b[32m✓ Plan and logs cleared\x1b[0m\n');
|
|
3958
4103
|
await this.ui.waitForKey('Press any key to continue...');
|
|
3959
4104
|
return true;
|
|
3960
4105
|
}
|
|
@@ -4625,9 +4770,6 @@ ${detection.projectContext}`;
|
|
|
4625
4770
|
let editTaskFlag = false;
|
|
4626
4771
|
let editTaskIndex = -1;
|
|
4627
4772
|
let taskEditWarning = null;
|
|
4628
|
-
// Track task completion for notifications
|
|
4629
|
-
let lastCompletedTaskId = null;
|
|
4630
|
-
let taskCompletionNotification = null;
|
|
4631
4773
|
// Render function
|
|
4632
4774
|
const render = async () => {
|
|
4633
4775
|
needsRender = false;
|
|
@@ -4664,10 +4806,6 @@ ${detection.projectContext}`;
|
|
|
4664
4806
|
hint = taskEditWarning;
|
|
4665
4807
|
hintColor = '\x1b[33m';
|
|
4666
4808
|
}
|
|
4667
|
-
else if (taskCompletionNotification) {
|
|
4668
|
-
hint = taskCompletionNotification;
|
|
4669
|
-
hintColor = '\x1b[32m';
|
|
4670
|
-
}
|
|
4671
4809
|
else {
|
|
4672
4810
|
hint = 'Esc pause · ↑↓ navigate · Enter details · e edit';
|
|
4673
4811
|
hintColor = '\x1b[90m';
|
|
@@ -4837,19 +4975,11 @@ ${detection.projectContext}`;
|
|
|
4837
4975
|
this.executionState.lastCompletedTask = task;
|
|
4838
4976
|
}
|
|
4839
4977
|
}
|
|
4840
|
-
//
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
lastCompletedTaskId = task.id;
|
|
4978
|
+
// Always move cursor to the completed task so user sees the summary
|
|
4979
|
+
const completedTaskIndex = tasks.findIndex(t => t.id === task.id);
|
|
4980
|
+
if (completedTaskIndex >= 0) {
|
|
4981
|
+
this.selectedTaskIndex = completedTaskIndex;
|
|
4845
4982
|
needsRender = true;
|
|
4846
|
-
// Clear notification after 3 seconds
|
|
4847
|
-
setTimeout(() => {
|
|
4848
|
-
if (lastCompletedTaskId === task.id) {
|
|
4849
|
-
taskCompletionNotification = null;
|
|
4850
|
-
needsRender = true;
|
|
4851
|
-
}
|
|
4852
|
-
}, 3000);
|
|
4853
4983
|
}
|
|
4854
4984
|
},
|
|
4855
4985
|
onOutput: (line) => {
|