run method
Runs this command.
The return value is wrapped in a Future if necessary and returned by
CommandRunner.runCommand.
Implementation
@override
Future<void> run() async {
final planFile = File('.spectra/PLAN.md');
if (!planFile.existsSync()) {
logger.err('PLAN.md not found. Run `spectra plan` first.');
return;
}
final content = planFile.readAsStringSync();
final allTasks = _parseTasks(content);
if (allTasks.isEmpty) {
logger.warn('No tasks found in PLAN.md.');
return;
}
// Tasks already marked completed (by a previous run) are not re-executed,
// so `execute` is idempotent and `resume` continues where it left off.
final taskDocs = allTasks
.where((d) => d.rootElement.getAttribute('status') != 'completed')
.toList();
final alreadyDone = allTasks.length - taskDocs.length;
if (alreadyDone > 0) {
logger.detail('Skipping $alreadyDone already-completed task(s).');
}
if (taskDocs.isEmpty) {
logger.success(
'All ${allTasks.length} tasks in PLAN.md are already '
'completed. Nothing to execute.',
);
return;
}
// Determine execution mode
final config = await _configService.loadConfig();
final manualFlag = argResults?['manual'] as bool? ?? false;
final autoFlag = argResults?['auto'] as bool? ?? false;
final ExecutionMode mode;
if (manualFlag) {
mode = ExecutionMode.manual;
} else if (autoFlag) {
mode = ExecutionMode.automatic;
} else {
// Use config or default to automatic
final modeStr = config.executionMode ?? 'automatic';
mode = ExecutionMode.values.firstWhere(
(m) => m.name == modeStr,
orElse: () => ExecutionMode.automatic,
);
}
if (mode == ExecutionMode.manual) {
await _displayTasksForManualExecution(taskDocs);
return;
}
// Use coding provider for actual code generation
final provider = await _llmService.getProviderForUsage(LLMUsageType.coding);
if (provider == null) {
logger.err('No coding provider configured.');
return;
}
final interactive = mode == ExecutionMode.interactive;
logger.info(
'Executing ${taskDocs.length} tasks using ${provider.name} '
'(${interactive ? 'Interactive Review' : 'Code Generation'})...',
);
for (final taskDoc in taskDocs) {
final keepGoing = await _executeTask(
taskDoc,
provider,
interactive: interactive,
);
if (!keepGoing) {
logger.info('Execution stopped by user.');
return;
}
}
// Prune state if it gets too large
_stateManager.pruneState();
logger.success('All tasks executed successfully!');
}