@hypercli/gen 0.1.1
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/LICENSE +21 -0
- package/README.md +24 -0
- package/dist/actions/communication.d.ts +201 -0
- package/dist/actions/communication.d.ts.map +1 -0
- package/dist/actions/communication.js +515 -0
- package/dist/actions/communication.js.map +1 -0
- package/dist/actions/decorator.d.ts +22 -0
- package/dist/actions/decorator.d.ts.map +1 -0
- package/dist/actions/decorator.js +110 -0
- package/dist/actions/decorator.js.map +1 -0
- package/dist/actions/executor.d.ts +85 -0
- package/dist/actions/executor.d.ts.map +1 -0
- package/dist/actions/executor.js +289 -0
- package/dist/actions/executor.js.map +1 -0
- package/dist/actions/index.d.ts +14 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +15 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/parameter-resolver.d.ts +54 -0
- package/dist/actions/parameter-resolver.d.ts.map +1 -0
- package/dist/actions/parameter-resolver.js +300 -0
- package/dist/actions/parameter-resolver.js.map +1 -0
- package/dist/actions/registry.d.ts +78 -0
- package/dist/actions/registry.d.ts.map +1 -0
- package/dist/actions/registry.js +221 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/actions/types.d.ts +109 -0
- package/dist/actions/types.d.ts.map +1 -0
- package/dist/actions/types.js +31 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/actions/utils.d.ts +42 -0
- package/dist/actions/utils.d.ts.map +1 -0
- package/dist/actions/utils.js +144 -0
- package/dist/actions/utils.js.map +1 -0
- package/dist/ai/ai-collector.d.ts +52 -0
- package/dist/ai/ai-collector.d.ts.map +1 -0
- package/dist/ai/ai-collector.js +64 -0
- package/dist/ai/ai-collector.js.map +1 -0
- package/dist/ai/ai-config.d.ts +230 -0
- package/dist/ai/ai-config.d.ts.map +1 -0
- package/dist/ai/ai-config.js +8 -0
- package/dist/ai/ai-config.js.map +1 -0
- package/dist/ai/ai-service.d.ts +66 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +198 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/ai-variable-resolver.d.ts +59 -0
- package/dist/ai/ai-variable-resolver.d.ts.map +1 -0
- package/dist/ai/ai-variable-resolver.js +219 -0
- package/dist/ai/ai-variable-resolver.js.map +1 -0
- package/dist/ai/context-collector.d.ts +30 -0
- package/dist/ai/context-collector.d.ts.map +1 -0
- package/dist/ai/context-collector.js +158 -0
- package/dist/ai/context-collector.js.map +1 -0
- package/dist/ai/cost-tracker.d.ts +41 -0
- package/dist/ai/cost-tracker.d.ts.map +1 -0
- package/dist/ai/cost-tracker.js +131 -0
- package/dist/ai/cost-tracker.js.map +1 -0
- package/dist/ai/env.d.ts +36 -0
- package/dist/ai/env.d.ts.map +1 -0
- package/dist/ai/env.js +100 -0
- package/dist/ai/env.js.map +1 -0
- package/dist/ai/index.d.ts +17 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +25 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/model-router.d.ts +32 -0
- package/dist/ai/model-router.d.ts.map +1 -0
- package/dist/ai/model-router.js +113 -0
- package/dist/ai/model-router.js.map +1 -0
- package/dist/ai/output-validator.d.ts +24 -0
- package/dist/ai/output-validator.d.ts.map +1 -0
- package/dist/ai/output-validator.js +279 -0
- package/dist/ai/output-validator.js.map +1 -0
- package/dist/ai/prompt-assembler.d.ts +30 -0
- package/dist/ai/prompt-assembler.d.ts.map +1 -0
- package/dist/ai/prompt-assembler.js +93 -0
- package/dist/ai/prompt-assembler.js.map +1 -0
- package/dist/ai/prompt-pipeline.d.ts +63 -0
- package/dist/ai/prompt-pipeline.d.ts.map +1 -0
- package/dist/ai/prompt-pipeline.js +119 -0
- package/dist/ai/prompt-pipeline.js.map +1 -0
- package/dist/ai/prompt-template.jig +88 -0
- package/dist/ai/transports/api-transport.d.ts +12 -0
- package/dist/ai/transports/api-transport.d.ts.map +1 -0
- package/dist/ai/transports/api-transport.js +86 -0
- package/dist/ai/transports/api-transport.js.map +1 -0
- package/dist/ai/transports/command-transport.d.ts +20 -0
- package/dist/ai/transports/command-transport.d.ts.map +1 -0
- package/dist/ai/transports/command-transport.js +203 -0
- package/dist/ai/transports/command-transport.js.map +1 -0
- package/dist/ai/transports/index.d.ts +11 -0
- package/dist/ai/transports/index.d.ts.map +1 -0
- package/dist/ai/transports/index.js +10 -0
- package/dist/ai/transports/index.js.map +1 -0
- package/dist/ai/transports/resolve-transport.d.ts +15 -0
- package/dist/ai/transports/resolve-transport.d.ts.map +1 -0
- package/dist/ai/transports/resolve-transport.js +96 -0
- package/dist/ai/transports/resolve-transport.js.map +1 -0
- package/dist/ai/transports/stdout-transport.d.ts +14 -0
- package/dist/ai/transports/stdout-transport.d.ts.map +1 -0
- package/dist/ai/transports/stdout-transport.js +27 -0
- package/dist/ai/transports/stdout-transport.js.map +1 -0
- package/dist/ai/transports/types.d.ts +77 -0
- package/dist/ai/transports/types.d.ts.map +1 -0
- package/dist/ai/transports/types.js +8 -0
- package/dist/ai/transports/types.js.map +1 -0
- package/dist/commands/cookbook/info.d.ts +22 -0
- package/dist/commands/cookbook/info.d.ts.map +1 -0
- package/dist/commands/cookbook/info.js +217 -0
- package/dist/commands/cookbook/info.js.map +1 -0
- package/dist/commands/cookbook/list.d.ts +20 -0
- package/dist/commands/cookbook/list.d.ts.map +1 -0
- package/dist/commands/cookbook/list.js +133 -0
- package/dist/commands/cookbook/list.js.map +1 -0
- package/dist/commands/gen.d.ts +65 -0
- package/dist/commands/gen.d.ts.map +1 -0
- package/dist/commands/gen.js +478 -0
- package/dist/commands/gen.js.map +1 -0
- package/dist/commands/recipe/info.d.ts +18 -0
- package/dist/commands/recipe/info.d.ts.map +1 -0
- package/dist/commands/recipe/info.js +89 -0
- package/dist/commands/recipe/info.js.map +1 -0
- package/dist/commands/recipe/list.d.ts +29 -0
- package/dist/commands/recipe/list.d.ts.map +1 -0
- package/dist/commands/recipe/list.js +215 -0
- package/dist/commands/recipe/list.js.map +1 -0
- package/dist/commands/recipe/run.d.ts +44 -0
- package/dist/commands/recipe/run.d.ts.map +1 -0
- package/dist/commands/recipe/run.js +239 -0
- package/dist/commands/recipe/run.js.map +1 -0
- package/dist/commands/recipe/validate.d.ts +19 -0
- package/dist/commands/recipe/validate.d.ts.map +1 -0
- package/dist/commands/recipe/validate.js +66 -0
- package/dist/commands/recipe/validate.js.map +1 -0
- package/dist/discovery/generator-discovery.d.ts +130 -0
- package/dist/discovery/generator-discovery.d.ts.map +1 -0
- package/dist/discovery/generator-discovery.js +674 -0
- package/dist/discovery/generator-discovery.js.map +1 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +7 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/hooks/command-not-found.d.ts +18 -0
- package/dist/hooks/command-not-found.d.ts.map +1 -0
- package/dist/hooks/command-not-found.js +182 -0
- package/dist/hooks/command-not-found.js.map +1 -0
- package/dist/hooks/suggest.d.ts +13 -0
- package/dist/hooks/suggest.d.ts.map +1 -0
- package/dist/hooks/suggest.js +28 -0
- package/dist/hooks/suggest.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/base-command.d.ts +26 -0
- package/dist/lib/base-command.d.ts.map +1 -0
- package/dist/lib/base-command.js +24 -0
- package/dist/lib/base-command.js.map +1 -0
- package/dist/lib/flags.d.ts +33 -0
- package/dist/lib/flags.d.ts.map +1 -0
- package/dist/lib/flags.js +64 -0
- package/dist/lib/flags.js.map +1 -0
- package/dist/ops/add.d.ts +4 -0
- package/dist/ops/add.d.ts.map +1 -0
- package/dist/ops/add.js +85 -0
- package/dist/ops/add.js.map +1 -0
- package/dist/ops/inject.d.ts +4 -0
- package/dist/ops/inject.d.ts.map +1 -0
- package/dist/ops/inject.js +28 -0
- package/dist/ops/inject.js.map +1 -0
- package/dist/ops/injector.d.ts +4 -0
- package/dist/ops/injector.d.ts.map +1 -0
- package/dist/ops/injector.js +68 -0
- package/dist/ops/injector.js.map +1 -0
- package/dist/ops/result.d.ts +3 -0
- package/dist/ops/result.d.ts.map +1 -0
- package/dist/ops/result.js +8 -0
- package/dist/ops/result.js.map +1 -0
- package/dist/prompts/interactive-prompts.d.ts +152 -0
- package/dist/prompts/interactive-prompts.d.ts.map +1 -0
- package/dist/prompts/interactive-prompts.js +574 -0
- package/dist/prompts/interactive-prompts.js.map +1 -0
- package/dist/recipe-engine/group-executor.d.ts +97 -0
- package/dist/recipe-engine/group-executor.d.ts.map +1 -0
- package/dist/recipe-engine/group-executor.js +293 -0
- package/dist/recipe-engine/group-executor.js.map +1 -0
- package/dist/recipe-engine/index.d.ts +112 -0
- package/dist/recipe-engine/index.d.ts.map +1 -0
- package/dist/recipe-engine/index.js +223 -0
- package/dist/recipe-engine/index.js.map +1 -0
- package/dist/recipe-engine/output-evaluator.d.ts +28 -0
- package/dist/recipe-engine/output-evaluator.d.ts.map +1 -0
- package/dist/recipe-engine/output-evaluator.js +78 -0
- package/dist/recipe-engine/output-evaluator.js.map +1 -0
- package/dist/recipe-engine/recipe-engine.d.ts +227 -0
- package/dist/recipe-engine/recipe-engine.d.ts.map +1 -0
- package/dist/recipe-engine/recipe-engine.js +1036 -0
- package/dist/recipe-engine/recipe-engine.js.map +1 -0
- package/dist/recipe-engine/step-executor.d.ts +172 -0
- package/dist/recipe-engine/step-executor.d.ts.map +1 -0
- package/dist/recipe-engine/step-executor.js +802 -0
- package/dist/recipe-engine/step-executor.js.map +1 -0
- package/dist/recipe-engine/tools/action-tool.d.ts +103 -0
- package/dist/recipe-engine/tools/action-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/action-tool.js +473 -0
- package/dist/recipe-engine/tools/action-tool.js.map +1 -0
- package/dist/recipe-engine/tools/ai-tool.d.ts +26 -0
- package/dist/recipe-engine/tools/ai-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/ai-tool.js +233 -0
- package/dist/recipe-engine/tools/ai-tool.js.map +1 -0
- package/dist/recipe-engine/tools/base.d.ts +214 -0
- package/dist/recipe-engine/tools/base.d.ts.map +1 -0
- package/dist/recipe-engine/tools/base.js +397 -0
- package/dist/recipe-engine/tools/base.js.map +1 -0
- package/dist/recipe-engine/tools/codemod-tool.d.ts +130 -0
- package/dist/recipe-engine/tools/codemod-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/codemod-tool.js +786 -0
- package/dist/recipe-engine/tools/codemod-tool.js.map +1 -0
- package/dist/recipe-engine/tools/ensure-dirs-tool.d.ts +21 -0
- package/dist/recipe-engine/tools/ensure-dirs-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/ensure-dirs-tool.js +130 -0
- package/dist/recipe-engine/tools/ensure-dirs-tool.js.map +1 -0
- package/dist/recipe-engine/tools/index.d.ts +126 -0
- package/dist/recipe-engine/tools/index.d.ts.map +1 -0
- package/dist/recipe-engine/tools/index.js +290 -0
- package/dist/recipe-engine/tools/index.js.map +1 -0
- package/dist/recipe-engine/tools/install-tool.d.ts +20 -0
- package/dist/recipe-engine/tools/install-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/install-tool.js +194 -0
- package/dist/recipe-engine/tools/install-tool.js.map +1 -0
- package/dist/recipe-engine/tools/parallel-tool.d.ts +21 -0
- package/dist/recipe-engine/tools/parallel-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/parallel-tool.js +134 -0
- package/dist/recipe-engine/tools/parallel-tool.js.map +1 -0
- package/dist/recipe-engine/tools/patch-tool.d.ts +21 -0
- package/dist/recipe-engine/tools/patch-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/patch-tool.js +248 -0
- package/dist/recipe-engine/tools/patch-tool.js.map +1 -0
- package/dist/recipe-engine/tools/prompt-tool.d.ts +25 -0
- package/dist/recipe-engine/tools/prompt-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/prompt-tool.js +162 -0
- package/dist/recipe-engine/tools/prompt-tool.js.map +1 -0
- package/dist/recipe-engine/tools/query-tool.d.ts +21 -0
- package/dist/recipe-engine/tools/query-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/query-tool.js +249 -0
- package/dist/recipe-engine/tools/query-tool.js.map +1 -0
- package/dist/recipe-engine/tools/recipe-tool.d.ts +103 -0
- package/dist/recipe-engine/tools/recipe-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/recipe-tool.js +617 -0
- package/dist/recipe-engine/tools/recipe-tool.js.map +1 -0
- package/dist/recipe-engine/tools/registry.d.ts +151 -0
- package/dist/recipe-engine/tools/registry.d.ts.map +1 -0
- package/dist/recipe-engine/tools/registry.js +244 -0
- package/dist/recipe-engine/tools/registry.js.map +1 -0
- package/dist/recipe-engine/tools/sequence-tool.d.ts +22 -0
- package/dist/recipe-engine/tools/sequence-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/sequence-tool.js +122 -0
- package/dist/recipe-engine/tools/sequence-tool.js.map +1 -0
- package/dist/recipe-engine/tools/shell-tool.d.ts +25 -0
- package/dist/recipe-engine/tools/shell-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/shell-tool.js +149 -0
- package/dist/recipe-engine/tools/shell-tool.js.map +1 -0
- package/dist/recipe-engine/tools/template-tool.d.ts +88 -0
- package/dist/recipe-engine/tools/template-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/template-tool.js +613 -0
- package/dist/recipe-engine/tools/template-tool.js.map +1 -0
- package/dist/recipe-engine/types.d.ts +963 -0
- package/dist/recipe-engine/types.d.ts.map +1 -0
- package/dist/recipe-engine/types.js +94 -0
- package/dist/recipe-engine/types.js.map +1 -0
- package/dist/template-engines/ai-tags.d.ts +26 -0
- package/dist/template-engines/ai-tags.d.ts.map +1 -0
- package/dist/template-engines/ai-tags.js +233 -0
- package/dist/template-engines/ai-tags.js.map +1 -0
- package/dist/template-engines/index.d.ts +8 -0
- package/dist/template-engines/index.d.ts.map +1 -0
- package/dist/template-engines/index.js +8 -0
- package/dist/template-engines/index.js.map +1 -0
- package/dist/template-engines/jig-engine.d.ts +47 -0
- package/dist/template-engines/jig-engine.d.ts.map +1 -0
- package/dist/template-engines/jig-engine.js +173 -0
- package/dist/template-engines/jig-engine.js.map +1 -0
- package/dist/utils/coerce-value.d.ts +7 -0
- package/dist/utils/coerce-value.d.ts.map +1 -0
- package/dist/utils/coerce-value.js +18 -0
- package/dist/utils/coerce-value.js.map +1 -0
- package/dist/utils/diff.d.ts +8 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +10 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/global-packages.d.ts +11 -0
- package/dist/utils/global-packages.d.ts.map +1 -0
- package/dist/utils/global-packages.js +88 -0
- package/dist/utils/global-packages.js.map +1 -0
- package/dist/utils/pager.d.ts +6 -0
- package/dist/utils/pager.d.ts.map +1 -0
- package/dist/utils/pager.js +41 -0
- package/dist/utils/pager.js.map +1 -0
- package/help/cookbook/info.md +35 -0
- package/help/cookbook/list.md +37 -0
- package/help/gen.md +26 -0
- package/help/recipe/run.md +52 -0
- package/help/recipe/validate.md +51 -0
- package/oclif.manifest.json +580 -0
- package/package.json +120 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step Executor for Recipe Step System
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the execution of recipe steps with proper dependency management,
|
|
5
|
+
* conditional logic, and parallel execution capabilities. This is the core
|
|
6
|
+
* coordination layer that brings together all recipe tools.
|
|
7
|
+
*/
|
|
8
|
+
import { EventEmitter } from "node:events";
|
|
9
|
+
import { ErrorCode, ErrorHandler, HypergenError } from "@hypercli/core";
|
|
10
|
+
import createDebug from "debug";
|
|
11
|
+
import { evaluateStepOutputs } from "./output-evaluator.js";
|
|
12
|
+
import { getToolRegistry } from "./tools/registry.js";
|
|
13
|
+
import { CircularDependencyError, StepExecutionError, isAIStep, isActionStep, isCodeModStep, isEnsureDirsStep, isInstallStep, isParallelStep, isPatchStep, isPromptStep, isQueryStep, isRecipeStep, isSequenceStep, isShellStep, isTemplateStep, } from "./types.js";
|
|
14
|
+
/**
|
|
15
|
+
* Default step executor configuration
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_CONFIG = {
|
|
18
|
+
maxConcurrency: 10,
|
|
19
|
+
defaultTimeout: 30000, // 30 seconds
|
|
20
|
+
defaultRetries: 3,
|
|
21
|
+
continueOnError: false,
|
|
22
|
+
enableParallelExecution: true,
|
|
23
|
+
collectMetrics: true,
|
|
24
|
+
enableProgressTracking: true,
|
|
25
|
+
memoryWarningThreshold: 1024, // 1GB
|
|
26
|
+
timeoutSafetyFactor: 1.2,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Step Executor for orchestrating recipe step execution
|
|
30
|
+
*
|
|
31
|
+
* The StepExecutor is responsible for:
|
|
32
|
+
* - Managing step dependencies and execution order
|
|
33
|
+
* - Coordinating parallel step execution
|
|
34
|
+
* - Evaluating conditional expressions
|
|
35
|
+
* - Routing steps to appropriate tools
|
|
36
|
+
* - Handling errors and retries
|
|
37
|
+
* - Tracking execution progress and metrics
|
|
38
|
+
*/
|
|
39
|
+
export class StepExecutor extends EventEmitter {
|
|
40
|
+
toolRegistry;
|
|
41
|
+
debug;
|
|
42
|
+
config;
|
|
43
|
+
// Execution state
|
|
44
|
+
activeExecutions = new Map();
|
|
45
|
+
runningSteps = new Map();
|
|
46
|
+
executionCounter = 0;
|
|
47
|
+
// Metrics and progress tracking
|
|
48
|
+
metrics;
|
|
49
|
+
progress;
|
|
50
|
+
constructor(toolRegistry, config = {}) {
|
|
51
|
+
super();
|
|
52
|
+
this.toolRegistry = toolRegistry || getToolRegistry();
|
|
53
|
+
this.debug = createDebug("hyper:recipe:step-executor");
|
|
54
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
55
|
+
this.debug("Step executor initialized with config: %o", this.config);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Execute a list of recipe steps with dependency management and parallel execution
|
|
59
|
+
*/
|
|
60
|
+
async executeSteps(steps, context, options = {}) {
|
|
61
|
+
const executionId = this.generateExecutionId();
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
this.debug("Starting step execution [%s] with %d steps", executionId, steps.length);
|
|
64
|
+
this.emit("execution:started", { executionId, steps: steps.length });
|
|
65
|
+
try {
|
|
66
|
+
// Initialize metrics and progress tracking
|
|
67
|
+
if (this.config.collectMetrics) {
|
|
68
|
+
this.initializeMetrics();
|
|
69
|
+
}
|
|
70
|
+
if (this.config.enableProgressTracking) {
|
|
71
|
+
this.initializeProgress(steps.length);
|
|
72
|
+
}
|
|
73
|
+
// Validate steps and context
|
|
74
|
+
this.validateSteps(steps);
|
|
75
|
+
this.validateContext(context);
|
|
76
|
+
// Create execution plan with dependency resolution
|
|
77
|
+
const executionPlan = await this.createExecutionPlan(steps, context);
|
|
78
|
+
this.debug("Execution plan created: %d phases", executionPlan.phases.length);
|
|
79
|
+
this.emit("execution:plan-created", { executionId, plan: executionPlan });
|
|
80
|
+
// Execute phases sequentially, steps within phases potentially in parallel
|
|
81
|
+
const results = await this.executeExecutionPlan(executionPlan, context, options, executionId);
|
|
82
|
+
// Finalize metrics
|
|
83
|
+
if (this.config.collectMetrics && this.metrics) {
|
|
84
|
+
this.metrics.totalExecutionTime = Date.now() - startTime;
|
|
85
|
+
this.finalizeMetrics();
|
|
86
|
+
}
|
|
87
|
+
this.debug("Step execution completed [%s] in %dms", executionId, Date.now() - startTime);
|
|
88
|
+
this.emit("execution:completed", {
|
|
89
|
+
executionId,
|
|
90
|
+
results,
|
|
91
|
+
duration: Date.now() - startTime,
|
|
92
|
+
metrics: this.metrics,
|
|
93
|
+
});
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
this.debug("Step execution failed [%s]: %s", executionId, error instanceof Error ? error.message : String(error));
|
|
98
|
+
this.emit("execution:failed", {
|
|
99
|
+
executionId,
|
|
100
|
+
error,
|
|
101
|
+
duration: Date.now() - startTime,
|
|
102
|
+
});
|
|
103
|
+
if (error instanceof HypergenError) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
throw ErrorHandler.createError(ErrorCode.INTERNAL_ERROR, `Step execution failed: ${error instanceof Error ? error.message : String(error)}`, { executionId, steps: steps.length });
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
this.activeExecutions.delete(executionId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Execute a single step with proper tool routing
|
|
114
|
+
*/
|
|
115
|
+
async executeStep(step, context, options = {}) {
|
|
116
|
+
const stepStartTime = Date.now();
|
|
117
|
+
const stepResult = {
|
|
118
|
+
status: "pending",
|
|
119
|
+
stepName: step.name,
|
|
120
|
+
toolType: step.tool,
|
|
121
|
+
startTime: new Date(),
|
|
122
|
+
retryCount: 0,
|
|
123
|
+
dependenciesSatisfied: true, // Single step execution assumes deps are satisfied
|
|
124
|
+
conditionResult: true,
|
|
125
|
+
};
|
|
126
|
+
this.debug("Executing single step: %s (%s)", step.name, step.tool);
|
|
127
|
+
this.emit("step:started", { step: step.name, toolType: step.tool });
|
|
128
|
+
try {
|
|
129
|
+
stepResult.status = "running";
|
|
130
|
+
// Evaluate condition if present
|
|
131
|
+
if (step.when) {
|
|
132
|
+
stepResult.conditionResult = context.evaluateCondition(step.when, context.variables);
|
|
133
|
+
if (!stepResult.conditionResult) {
|
|
134
|
+
this.debug("Step condition not met, skipping: %s", step.name);
|
|
135
|
+
stepResult.status = "skipped";
|
|
136
|
+
stepResult.endTime = new Date();
|
|
137
|
+
stepResult.duration = Date.now() - stepStartTime;
|
|
138
|
+
this.emit("step:skipped", { step: step.name, condition: step.when });
|
|
139
|
+
return stepResult;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// In collect mode (pass 1), only template steps need to run
|
|
143
|
+
// Sequence/parallel are structural containers that must execute so their
|
|
144
|
+
// nested template steps can run. Other tools (shell, action, prompt) are skipped.
|
|
145
|
+
if (context.collectMode &&
|
|
146
|
+
step.tool !== "template" &&
|
|
147
|
+
step.tool !== "sequence" &&
|
|
148
|
+
step.tool !== "parallel") {
|
|
149
|
+
this.debug("Skipping non-template step in collect mode: %s (%s)", step.name, step.tool);
|
|
150
|
+
stepResult.status = "skipped";
|
|
151
|
+
stepResult.endTime = new Date();
|
|
152
|
+
stepResult.duration = Date.now() - stepStartTime;
|
|
153
|
+
this.emit("step:skipped", { step: step.name, reason: "collect-mode" });
|
|
154
|
+
return stepResult;
|
|
155
|
+
}
|
|
156
|
+
// Execute step with retries
|
|
157
|
+
const maxRetries = options.retries ?? step.retries ?? this.config.defaultRetries;
|
|
158
|
+
let lastError;
|
|
159
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
160
|
+
try {
|
|
161
|
+
stepResult.retryCount = attempt;
|
|
162
|
+
if (attempt > 0) {
|
|
163
|
+
this.debug("Retrying step: %s (attempt %d/%d)", step.name, attempt + 1, maxRetries + 1);
|
|
164
|
+
await this.delay(this.calculateRetryDelay(attempt));
|
|
165
|
+
}
|
|
166
|
+
// Route to appropriate tool and execute
|
|
167
|
+
const toolResult = await this.routeAndExecuteStep(step, context, options);
|
|
168
|
+
stepResult.toolResult = toolResult;
|
|
169
|
+
// Respect failure status returned by the tool
|
|
170
|
+
if (toolResult.status === "failed") {
|
|
171
|
+
stepResult.status = "failed";
|
|
172
|
+
stepResult.error = toolResult.error;
|
|
173
|
+
stepResult.endTime = new Date();
|
|
174
|
+
stepResult.duration = Date.now() - stepStartTime;
|
|
175
|
+
throw new Error(toolResult.error?.message || `Step '${step.name}' failed`);
|
|
176
|
+
}
|
|
177
|
+
stepResult.status = "completed";
|
|
178
|
+
stepResult.endTime = new Date();
|
|
179
|
+
stepResult.duration = Date.now() - stepStartTime;
|
|
180
|
+
// Extract file changes from tool result
|
|
181
|
+
this.extractFileChanges(stepResult, toolResult);
|
|
182
|
+
// Evaluate export expressions and inject into context.variables
|
|
183
|
+
if (step.exports && Object.keys(step.exports).length > 0) {
|
|
184
|
+
const outputs = await evaluateStepOutputs(step.exports, toolResult, context);
|
|
185
|
+
stepResult.output = { ...stepResult.output, ...outputs };
|
|
186
|
+
Object.assign(context.variables, outputs);
|
|
187
|
+
}
|
|
188
|
+
this.debug("Step completed successfully: %s in %dms", step.name, stepResult.duration);
|
|
189
|
+
this.emit("step:completed", {
|
|
190
|
+
step: step.name,
|
|
191
|
+
toolType: step.tool,
|
|
192
|
+
duration: stepResult.duration,
|
|
193
|
+
retries: attempt,
|
|
194
|
+
});
|
|
195
|
+
return stepResult;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
199
|
+
if (attempt < maxRetries) {
|
|
200
|
+
this.debug("Step failed, will retry: %s - %s", step.name, lastError.message);
|
|
201
|
+
this.emit("step:retry", {
|
|
202
|
+
step: step.name,
|
|
203
|
+
attempt: attempt + 1,
|
|
204
|
+
error: lastError,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// All retries exhausted
|
|
210
|
+
stepResult.status = "failed";
|
|
211
|
+
stepResult.endTime = new Date();
|
|
212
|
+
stepResult.duration = Date.now() - stepStartTime;
|
|
213
|
+
stepResult.error = {
|
|
214
|
+
message: lastError?.message || "Step execution failed",
|
|
215
|
+
stack: lastError?.stack,
|
|
216
|
+
cause: lastError,
|
|
217
|
+
};
|
|
218
|
+
this.debug("Step failed permanently: %s after %d retries", step.name, maxRetries);
|
|
219
|
+
this.emit("step:failed", {
|
|
220
|
+
step: step.name,
|
|
221
|
+
toolType: step.tool,
|
|
222
|
+
error: lastError,
|
|
223
|
+
retries: maxRetries,
|
|
224
|
+
});
|
|
225
|
+
throw new StepExecutionError(`Step '${step.name}' failed after ${maxRetries} retries: ${lastError?.message}`, step.name, step.tool, lastError);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (stepResult.status !== "failed") {
|
|
229
|
+
stepResult.status = "failed";
|
|
230
|
+
stepResult.endTime = new Date();
|
|
231
|
+
stepResult.duration = Date.now() - stepStartTime;
|
|
232
|
+
stepResult.error = {
|
|
233
|
+
message: error instanceof Error ? error.message : String(error),
|
|
234
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
235
|
+
cause: error,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get current execution metrics
|
|
243
|
+
*/
|
|
244
|
+
getMetrics() {
|
|
245
|
+
return this.metrics;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get current execution progress
|
|
249
|
+
*/
|
|
250
|
+
getProgress() {
|
|
251
|
+
return this.progress;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Cancel all running executions
|
|
255
|
+
*/
|
|
256
|
+
async cancelAllExecutions() {
|
|
257
|
+
this.debug("Cancelling all running executions");
|
|
258
|
+
const promises = [];
|
|
259
|
+
for (const [executionId] of this.activeExecutions) {
|
|
260
|
+
promises.push(this.cancelExecution(executionId));
|
|
261
|
+
}
|
|
262
|
+
await Promise.allSettled(promises);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Cancel a specific execution
|
|
266
|
+
*/
|
|
267
|
+
async cancelExecution(executionId) {
|
|
268
|
+
this.debug("Cancelling execution: %s", executionId);
|
|
269
|
+
// Cancel all running steps for this execution
|
|
270
|
+
for (const [stepName, stepInfo] of this.runningSteps) {
|
|
271
|
+
if (stepName.startsWith(executionId)) {
|
|
272
|
+
try {
|
|
273
|
+
await stepInfo.tool.cleanup();
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
this.debug("Error cleaning up step during cancellation: %s - %s", stepName, error instanceof Error ? error.message : String(error));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
this.emit("execution:cancelled", { executionId });
|
|
281
|
+
}
|
|
282
|
+
// Private implementation methods
|
|
283
|
+
async createExecutionPlan(steps, context) {
|
|
284
|
+
const startTime = Date.now();
|
|
285
|
+
// Build dependency graph
|
|
286
|
+
const dependencyGraph = this.buildDependencyGraph(steps);
|
|
287
|
+
// Detect circular dependencies
|
|
288
|
+
this.detectCircularDependencies(dependencyGraph);
|
|
289
|
+
// Create execution phases using topological sort
|
|
290
|
+
const phases = this.createExecutionPhases(steps, dependencyGraph);
|
|
291
|
+
const plan = {
|
|
292
|
+
recipe: {
|
|
293
|
+
name: context.recipe.name,
|
|
294
|
+
description: "Recipe execution plan",
|
|
295
|
+
version: context.recipe.version,
|
|
296
|
+
author: undefined,
|
|
297
|
+
category: undefined,
|
|
298
|
+
tags: [],
|
|
299
|
+
variables: context.recipeVariables,
|
|
300
|
+
steps,
|
|
301
|
+
examples: [],
|
|
302
|
+
dependencies: [],
|
|
303
|
+
outputs: [],
|
|
304
|
+
},
|
|
305
|
+
phases,
|
|
306
|
+
dependencyGraph,
|
|
307
|
+
estimatedDuration: this.estimateExecutionDuration(steps),
|
|
308
|
+
};
|
|
309
|
+
if (this.config.collectMetrics && this.metrics) {
|
|
310
|
+
this.metrics.dependencies.resolutionTime = Date.now() - startTime;
|
|
311
|
+
}
|
|
312
|
+
return plan;
|
|
313
|
+
}
|
|
314
|
+
buildDependencyGraph(steps) {
|
|
315
|
+
const graph = new Map();
|
|
316
|
+
// If ANY step declares explicit dependsOn, assume the recipe author manages
|
|
317
|
+
// dependencies manually — don't add implicit sequential deps.
|
|
318
|
+
// Otherwise, add implicit sequential deps so YAML order = execution order.
|
|
319
|
+
// This ensures that exports from earlier steps are available when later
|
|
320
|
+
// step conditions evaluate.
|
|
321
|
+
const hasExplicitDeps = steps.some((s) => s.dependsOn && s.dependsOn.length > 0);
|
|
322
|
+
for (let i = 0; i < steps.length; i++) {
|
|
323
|
+
const step = steps[i];
|
|
324
|
+
let dependencies;
|
|
325
|
+
if (step.dependsOn && step.dependsOn.length > 0) {
|
|
326
|
+
// Explicit dependencies — use as-is
|
|
327
|
+
dependencies = [...step.dependsOn];
|
|
328
|
+
}
|
|
329
|
+
else if (!hasExplicitDeps && i > 0) {
|
|
330
|
+
// No explicit deps in the recipe — implicitly depend on previous step
|
|
331
|
+
dependencies = [steps[i - 1].name];
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
dependencies = [];
|
|
335
|
+
}
|
|
336
|
+
graph.set(step.name, {
|
|
337
|
+
stepName: step.name,
|
|
338
|
+
dependencies,
|
|
339
|
+
dependents: [],
|
|
340
|
+
priority: 0,
|
|
341
|
+
parallelizable: step.parallel ?? true,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// Build dependent relationships
|
|
345
|
+
for (const [stepName, node] of graph) {
|
|
346
|
+
for (const depName of node.dependencies) {
|
|
347
|
+
const depNode = graph.get(depName);
|
|
348
|
+
if (depNode) {
|
|
349
|
+
depNode.dependents.push(stepName);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Calculate priority based on dependency depth
|
|
354
|
+
this.calculatePriorities(graph);
|
|
355
|
+
return graph;
|
|
356
|
+
}
|
|
357
|
+
calculatePriorities(graph) {
|
|
358
|
+
const visited = new Set();
|
|
359
|
+
const calculatePriority = (stepName) => {
|
|
360
|
+
if (visited.has(stepName)) {
|
|
361
|
+
return graph.get(stepName)?.priority ?? 0;
|
|
362
|
+
}
|
|
363
|
+
visited.add(stepName);
|
|
364
|
+
const node = graph.get(stepName);
|
|
365
|
+
if (node.dependencies.length === 0) {
|
|
366
|
+
node.priority = 0;
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
let maxDepPriority = -1;
|
|
370
|
+
for (const depName of node.dependencies) {
|
|
371
|
+
const depPriority = calculatePriority(depName);
|
|
372
|
+
maxDepPriority = Math.max(maxDepPriority, depPriority);
|
|
373
|
+
}
|
|
374
|
+
node.priority = maxDepPriority + 1;
|
|
375
|
+
return node.priority;
|
|
376
|
+
};
|
|
377
|
+
for (const stepName of graph.keys()) {
|
|
378
|
+
calculatePriority(stepName);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
detectCircularDependencies(graph) {
|
|
382
|
+
const visited = new Set();
|
|
383
|
+
const visiting = new Set();
|
|
384
|
+
const visit = (stepName, path = []) => {
|
|
385
|
+
if (visiting.has(stepName)) {
|
|
386
|
+
const cycle = [...path, stepName];
|
|
387
|
+
throw new CircularDependencyError(`Circular dependency detected: ${cycle.join(" -> ")}`, cycle);
|
|
388
|
+
}
|
|
389
|
+
if (visited.has(stepName)) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
visiting.add(stepName);
|
|
393
|
+
const node = graph.get(stepName);
|
|
394
|
+
for (const depName of node.dependencies) {
|
|
395
|
+
visit(depName, [...path, stepName]);
|
|
396
|
+
}
|
|
397
|
+
visiting.delete(stepName);
|
|
398
|
+
visited.add(stepName);
|
|
399
|
+
};
|
|
400
|
+
for (const stepName of graph.keys()) {
|
|
401
|
+
visit(stepName);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
createExecutionPhases(steps, graph) {
|
|
405
|
+
const phases = [];
|
|
406
|
+
const assigned = new Set();
|
|
407
|
+
const stepMap = new Map(steps.map((step) => [step.name, step]));
|
|
408
|
+
while (assigned.size < steps.length) {
|
|
409
|
+
const readySteps = [];
|
|
410
|
+
// Find steps that can run in this phase
|
|
411
|
+
for (const [stepName, node] of graph) {
|
|
412
|
+
if (assigned.has(stepName))
|
|
413
|
+
continue;
|
|
414
|
+
// Check if all dependencies are satisfied
|
|
415
|
+
const dependenciesSatisfied = node.dependencies.every((dep) => assigned.has(dep));
|
|
416
|
+
if (dependenciesSatisfied) {
|
|
417
|
+
readySteps.push(stepName);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (readySteps.length === 0) {
|
|
421
|
+
throw ErrorHandler.createError(ErrorCode.INTERNAL_ERROR, "No ready steps found - possible circular dependency", { assigned: Array.from(assigned), total: steps.length });
|
|
422
|
+
}
|
|
423
|
+
// Determine if this phase can run in parallel
|
|
424
|
+
const canRunInParallel = this.config.enableParallelExecution &&
|
|
425
|
+
readySteps.length > 1 &&
|
|
426
|
+
readySteps.every((stepName) => {
|
|
427
|
+
const step = stepMap.get(stepName);
|
|
428
|
+
return step.parallel !== false;
|
|
429
|
+
});
|
|
430
|
+
phases.push({
|
|
431
|
+
phase: phases.length,
|
|
432
|
+
steps: readySteps,
|
|
433
|
+
parallel: canRunInParallel,
|
|
434
|
+
});
|
|
435
|
+
// Mark steps as assigned
|
|
436
|
+
readySteps.forEach((stepName) => assigned.add(stepName));
|
|
437
|
+
}
|
|
438
|
+
return phases;
|
|
439
|
+
}
|
|
440
|
+
async executeExecutionPlan(plan, context, options, executionId) {
|
|
441
|
+
const results = [];
|
|
442
|
+
const stepMap = new Map(plan.recipe.steps.map((step) => [step.name, step]));
|
|
443
|
+
if (this.config.enableProgressTracking && this.progress) {
|
|
444
|
+
this.progress.totalPhases = plan.phases.length;
|
|
445
|
+
}
|
|
446
|
+
for (let phaseIndex = 0; phaseIndex < plan.phases.length; phaseIndex++) {
|
|
447
|
+
const phase = plan.phases[phaseIndex];
|
|
448
|
+
this.debug("Executing phase %d with %d steps (parallel: %s)", phase.phase, phase.steps.length, phase.parallel);
|
|
449
|
+
if (this.config.enableProgressTracking && this.progress) {
|
|
450
|
+
this.progress.currentPhase = phaseIndex + 1;
|
|
451
|
+
this.progress.phaseDescription = `Phase ${phase.phase + 1}: ${phase.steps.join(", ")}`;
|
|
452
|
+
}
|
|
453
|
+
this.emit("phase:started", {
|
|
454
|
+
phase: phase.phase,
|
|
455
|
+
steps: phase.steps,
|
|
456
|
+
parallel: phase.parallel,
|
|
457
|
+
});
|
|
458
|
+
const phaseResults = await this.executePhase(phase.steps.map((stepName) => stepMap.get(stepName)), context, options, phase.parallel, executionId);
|
|
459
|
+
results.push(...phaseResults);
|
|
460
|
+
// Update step results in context for next phase
|
|
461
|
+
phaseResults.forEach((result) => {
|
|
462
|
+
context.stepResults.set(result.stepName, result);
|
|
463
|
+
});
|
|
464
|
+
this.emit("phase:completed", {
|
|
465
|
+
phase: phase.phase,
|
|
466
|
+
results: phaseResults,
|
|
467
|
+
});
|
|
468
|
+
// Check if we should continue execution
|
|
469
|
+
const failed = phaseResults.filter((r) => r.status === "failed");
|
|
470
|
+
if (failed.length > 0 && !options.continueOnError && !this.config.continueOnError) {
|
|
471
|
+
throw new StepExecutionError(`Phase ${phase.phase} had failures: ${failed.map((r) => r.stepName).join(", ")}`, failed[0].stepName, failed[0].toolType);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return results;
|
|
475
|
+
}
|
|
476
|
+
async executePhase(steps, context, options, parallel, executionId) {
|
|
477
|
+
if (!parallel) {
|
|
478
|
+
// Execute steps sequentially
|
|
479
|
+
const results = [];
|
|
480
|
+
for (const step of steps) {
|
|
481
|
+
const result = await this.executeStep(step, context, options);
|
|
482
|
+
results.push(result);
|
|
483
|
+
// Update progress
|
|
484
|
+
if (this.config.enableProgressTracking && this.progress) {
|
|
485
|
+
if (result.status === "completed") {
|
|
486
|
+
this.progress.completedSteps.push(step.name);
|
|
487
|
+
}
|
|
488
|
+
else if (result.status === "failed") {
|
|
489
|
+
this.progress.failedSteps.push(step.name);
|
|
490
|
+
}
|
|
491
|
+
else if (result.status === "skipped") {
|
|
492
|
+
this.progress.skippedSteps.push(step.name);
|
|
493
|
+
}
|
|
494
|
+
this.updateProgressPercentage();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return results;
|
|
498
|
+
}
|
|
499
|
+
// Execute steps in parallel with concurrency limit
|
|
500
|
+
const concurrency = Math.min(steps.length, this.config.maxConcurrency);
|
|
501
|
+
const results = [];
|
|
502
|
+
const executeWithConcurrency = async (stepList) => {
|
|
503
|
+
const promises = [];
|
|
504
|
+
let index = 0;
|
|
505
|
+
const executeNext = async () => {
|
|
506
|
+
if (index >= stepList.length)
|
|
507
|
+
return;
|
|
508
|
+
const step = stepList[index++];
|
|
509
|
+
const stepKey = `${executionId}:${step.name}`;
|
|
510
|
+
try {
|
|
511
|
+
// Track running step
|
|
512
|
+
const tool = await this.toolRegistry.resolve(step.tool, this.getToolName(step));
|
|
513
|
+
this.runningSteps.set(stepKey, { step, startTime: new Date(), tool });
|
|
514
|
+
if (this.config.enableProgressTracking && this.progress) {
|
|
515
|
+
this.progress.runningSteps.push(step.name);
|
|
516
|
+
}
|
|
517
|
+
const result = await this.executeStep(step, context, options);
|
|
518
|
+
results.push(result);
|
|
519
|
+
// Update progress
|
|
520
|
+
if (this.config.enableProgressTracking && this.progress) {
|
|
521
|
+
const runningIndex = this.progress.runningSteps.indexOf(step.name);
|
|
522
|
+
if (runningIndex !== -1) {
|
|
523
|
+
this.progress.runningSteps.splice(runningIndex, 1);
|
|
524
|
+
}
|
|
525
|
+
if (result.status === "completed") {
|
|
526
|
+
this.progress.completedSteps.push(step.name);
|
|
527
|
+
}
|
|
528
|
+
else if (result.status === "failed") {
|
|
529
|
+
this.progress.failedSteps.push(step.name);
|
|
530
|
+
}
|
|
531
|
+
else if (result.status === "skipped") {
|
|
532
|
+
this.progress.skippedSteps.push(step.name);
|
|
533
|
+
}
|
|
534
|
+
this.updateProgressPercentage();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
finally {
|
|
538
|
+
// Release tool before deleting from tracking map
|
|
539
|
+
const runningStep = this.runningSteps.get(stepKey);
|
|
540
|
+
if (runningStep) {
|
|
541
|
+
this.toolRegistry.release(step.tool, this.getToolName(step), runningStep.tool);
|
|
542
|
+
}
|
|
543
|
+
this.runningSteps.delete(stepKey);
|
|
544
|
+
}
|
|
545
|
+
// Execute next step
|
|
546
|
+
return executeNext();
|
|
547
|
+
};
|
|
548
|
+
// Start concurrent executions
|
|
549
|
+
for (let i = 0; i < concurrency && i < stepList.length; i++) {
|
|
550
|
+
promises.push(executeNext());
|
|
551
|
+
}
|
|
552
|
+
await Promise.all(promises);
|
|
553
|
+
};
|
|
554
|
+
await executeWithConcurrency(steps);
|
|
555
|
+
// Update parallelization metrics
|
|
556
|
+
if (this.config.collectMetrics && this.metrics) {
|
|
557
|
+
this.metrics.parallelization.maxConcurrentSteps = Math.max(this.metrics.parallelization.maxConcurrentSteps, Math.min(steps.length, concurrency));
|
|
558
|
+
this.metrics.parallelization.parallelPhases++;
|
|
559
|
+
}
|
|
560
|
+
return results;
|
|
561
|
+
}
|
|
562
|
+
async routeAndExecuteStep(step, context, options) {
|
|
563
|
+
const toolName = this.getToolName(step);
|
|
564
|
+
const tool = await this.toolRegistry.resolve(step.tool, toolName);
|
|
565
|
+
this.debug("Routing step to tool: %s -> %s:%s", step.name, step.tool, toolName);
|
|
566
|
+
try {
|
|
567
|
+
// Initialize tool if needed
|
|
568
|
+
if (!tool.isInitialized()) {
|
|
569
|
+
await tool.initialize();
|
|
570
|
+
}
|
|
571
|
+
// Validate step configuration
|
|
572
|
+
const validation = await tool.validate(step, context);
|
|
573
|
+
if (!validation.isValid) {
|
|
574
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, `Step validation failed: ${validation.errors.join(", ")}`, { step: step.name, tool: step.tool, errors: validation.errors });
|
|
575
|
+
}
|
|
576
|
+
// Execute step through tool
|
|
577
|
+
const result = await tool.execute(step, context);
|
|
578
|
+
this.debug("Tool execution completed: %s", step.name);
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
finally {
|
|
582
|
+
// Release tool back to registry
|
|
583
|
+
this.toolRegistry.release(step.tool, toolName, tool);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
getToolName(step) {
|
|
587
|
+
// Route to appropriate tool based on step type
|
|
588
|
+
// Most tools use 'default' as the tool name, but action and codemod use specific names
|
|
589
|
+
if (isTemplateStep(step)) {
|
|
590
|
+
return "default";
|
|
591
|
+
}
|
|
592
|
+
if (isActionStep(step)) {
|
|
593
|
+
return step.action;
|
|
594
|
+
}
|
|
595
|
+
if (isCodeModStep(step)) {
|
|
596
|
+
return step.codemod;
|
|
597
|
+
}
|
|
598
|
+
if (isRecipeStep(step)) {
|
|
599
|
+
return "default";
|
|
600
|
+
}
|
|
601
|
+
if (isShellStep(step)) {
|
|
602
|
+
return "default";
|
|
603
|
+
}
|
|
604
|
+
if (isPromptStep(step)) {
|
|
605
|
+
return "default";
|
|
606
|
+
}
|
|
607
|
+
if (isSequenceStep(step)) {
|
|
608
|
+
return "default";
|
|
609
|
+
}
|
|
610
|
+
if (isParallelStep(step)) {
|
|
611
|
+
return "default";
|
|
612
|
+
}
|
|
613
|
+
if (isAIStep(step)) {
|
|
614
|
+
return "default";
|
|
615
|
+
}
|
|
616
|
+
if (isInstallStep(step)) {
|
|
617
|
+
return "default";
|
|
618
|
+
}
|
|
619
|
+
if (isQueryStep(step)) {
|
|
620
|
+
return "default";
|
|
621
|
+
}
|
|
622
|
+
if (isPatchStep(step)) {
|
|
623
|
+
return "default";
|
|
624
|
+
}
|
|
625
|
+
if (isEnsureDirsStep(step)) {
|
|
626
|
+
return "default";
|
|
627
|
+
}
|
|
628
|
+
// Fallback: check if the tool type is registered
|
|
629
|
+
const toolType = step.tool;
|
|
630
|
+
if (typeof toolType === "string" && this.toolRegistry.isRegistered(toolType, "default")) {
|
|
631
|
+
return "default";
|
|
632
|
+
}
|
|
633
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, `Unknown step type: ${step.tool}`, { step: step.name });
|
|
634
|
+
}
|
|
635
|
+
extractFileChanges(result, toolResult) {
|
|
636
|
+
// Extract file changes from tool result based on result type
|
|
637
|
+
if (toolResult && typeof toolResult === "object") {
|
|
638
|
+
if (toolResult.filesGenerated) {
|
|
639
|
+
result.filesCreated = toolResult.filesGenerated;
|
|
640
|
+
}
|
|
641
|
+
if (toolResult.filesProcessed) {
|
|
642
|
+
result.filesModified = toolResult.filesProcessed;
|
|
643
|
+
}
|
|
644
|
+
if (toolResult.filesCreated) {
|
|
645
|
+
result.filesCreated = toolResult.filesCreated;
|
|
646
|
+
}
|
|
647
|
+
if (toolResult.filesModified) {
|
|
648
|
+
result.filesModified = toolResult.filesModified;
|
|
649
|
+
}
|
|
650
|
+
if (toolResult.filesDeleted) {
|
|
651
|
+
result.filesDeleted = toolResult.filesDeleted;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
validateSteps(steps) {
|
|
656
|
+
if (!Array.isArray(steps)) {
|
|
657
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, "Steps must be an array");
|
|
658
|
+
}
|
|
659
|
+
if (steps.length === 0) {
|
|
660
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, "At least one step is required");
|
|
661
|
+
}
|
|
662
|
+
// Validate step names are unique
|
|
663
|
+
const stepNames = new Set();
|
|
664
|
+
for (const step of steps) {
|
|
665
|
+
if (!step.name) {
|
|
666
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, "Step name is required");
|
|
667
|
+
}
|
|
668
|
+
if (stepNames.has(step.name)) {
|
|
669
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, `Duplicate step name: ${step.name}`);
|
|
670
|
+
}
|
|
671
|
+
stepNames.add(step.name);
|
|
672
|
+
if (!step.tool) {
|
|
673
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, `Step ${step.name} must specify a tool`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Validate dependencies reference existing steps
|
|
677
|
+
for (const step of steps) {
|
|
678
|
+
if (step.dependsOn) {
|
|
679
|
+
for (const depName of step.dependsOn) {
|
|
680
|
+
if (!stepNames.has(depName)) {
|
|
681
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, `Step ${step.name} depends on unknown step: ${depName}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
validateContext(context) {
|
|
688
|
+
if (!context) {
|
|
689
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, "Step context is required");
|
|
690
|
+
}
|
|
691
|
+
if (!context.evaluateCondition) {
|
|
692
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, "Context must provide evaluateCondition function");
|
|
693
|
+
}
|
|
694
|
+
if (!context.recipe) {
|
|
695
|
+
throw ErrorHandler.createError(ErrorCode.VALIDATION_ERROR, "Context must include recipe information");
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
calculateRetryDelay(attempt) {
|
|
699
|
+
// Exponential backoff with jitter
|
|
700
|
+
const baseDelay = 1000; // 1 second
|
|
701
|
+
const maxDelay = 30000; // 30 seconds
|
|
702
|
+
let delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
|
|
703
|
+
// Add jitter (±25%)
|
|
704
|
+
const jitter = delay * 0.25 * (Math.random() - 0.5);
|
|
705
|
+
delay += jitter;
|
|
706
|
+
return Math.max(delay, 100); // Minimum 100ms delay
|
|
707
|
+
}
|
|
708
|
+
estimateExecutionDuration(steps) {
|
|
709
|
+
// Simple estimation based on step types and historical data
|
|
710
|
+
const estimations = {
|
|
711
|
+
template: 5000, // 5 seconds average
|
|
712
|
+
action: 3000, // 3 seconds average
|
|
713
|
+
codemod: 10000, // 10 seconds average
|
|
714
|
+
recipe: 15000, // 15 seconds average (sub-recipes)
|
|
715
|
+
shell: 2000, // 2 seconds average
|
|
716
|
+
prompt: 30000, // 30 seconds average (interactive)
|
|
717
|
+
sequence: 0, // Sequence tool itself is instant
|
|
718
|
+
parallel: 0, // Parallel tool itself is instant
|
|
719
|
+
ai: 20000, // 20 seconds average (AI generation)
|
|
720
|
+
install: 15000, // 15 seconds average (package install)
|
|
721
|
+
query: 100, // 100ms average (file read + parse)
|
|
722
|
+
patch: 200, // 200ms average (read + merge + write)
|
|
723
|
+
"ensure-dirs": 50, // 50ms average (mkdir -p)
|
|
724
|
+
};
|
|
725
|
+
let totalEstimate = 0;
|
|
726
|
+
for (const step of steps) {
|
|
727
|
+
totalEstimate += estimations[step.tool] || 5000;
|
|
728
|
+
}
|
|
729
|
+
return totalEstimate;
|
|
730
|
+
}
|
|
731
|
+
delay(ms) {
|
|
732
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
733
|
+
}
|
|
734
|
+
generateExecutionId() {
|
|
735
|
+
return `exec_${Date.now()}_${++this.executionCounter}`;
|
|
736
|
+
}
|
|
737
|
+
initializeMetrics() {
|
|
738
|
+
this.metrics = {
|
|
739
|
+
totalExecutionTime: 0,
|
|
740
|
+
stepExecutionTimes: new Map(),
|
|
741
|
+
memoryUsage: {
|
|
742
|
+
peak: 0,
|
|
743
|
+
average: 0,
|
|
744
|
+
start: process.memoryUsage().heapUsed,
|
|
745
|
+
end: 0,
|
|
746
|
+
},
|
|
747
|
+
parallelization: {
|
|
748
|
+
maxConcurrentSteps: 0,
|
|
749
|
+
averageConcurrentSteps: 0,
|
|
750
|
+
parallelPhases: 0,
|
|
751
|
+
},
|
|
752
|
+
errors: {
|
|
753
|
+
totalFailures: 0,
|
|
754
|
+
totalRetries: 0,
|
|
755
|
+
permanentFailures: [],
|
|
756
|
+
recoveredAfterRetries: [],
|
|
757
|
+
},
|
|
758
|
+
dependencies: {
|
|
759
|
+
resolutionTime: 0,
|
|
760
|
+
cyclesDetected: 0,
|
|
761
|
+
maxDepth: 0,
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
initializeProgress(totalSteps) {
|
|
766
|
+
this.progress = {
|
|
767
|
+
currentPhase: 0,
|
|
768
|
+
totalPhases: 0,
|
|
769
|
+
runningSteps: [],
|
|
770
|
+
completedSteps: [],
|
|
771
|
+
failedSteps: [],
|
|
772
|
+
skippedSteps: [],
|
|
773
|
+
progressPercentage: 0,
|
|
774
|
+
phaseDescription: "Initializing...",
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
updateProgressPercentage() {
|
|
778
|
+
if (!this.progress)
|
|
779
|
+
return;
|
|
780
|
+
const totalSteps = this.progress.completedSteps.length +
|
|
781
|
+
this.progress.failedSteps.length +
|
|
782
|
+
this.progress.skippedSteps.length +
|
|
783
|
+
this.progress.runningSteps.length;
|
|
784
|
+
if (totalSteps === 0)
|
|
785
|
+
return;
|
|
786
|
+
const completed = this.progress.completedSteps.length +
|
|
787
|
+
this.progress.failedSteps.length +
|
|
788
|
+
this.progress.skippedSteps.length;
|
|
789
|
+
this.progress.progressPercentage = Math.round((completed / totalSteps) * 100);
|
|
790
|
+
}
|
|
791
|
+
finalizeMetrics() {
|
|
792
|
+
if (!this.metrics)
|
|
793
|
+
return;
|
|
794
|
+
this.metrics.memoryUsage.end = process.memoryUsage().heapUsed;
|
|
795
|
+
// Calculate average memory usage (simplified)
|
|
796
|
+
this.metrics.memoryUsage.average =
|
|
797
|
+
(this.metrics.memoryUsage.start + this.metrics.memoryUsage.end) / 2;
|
|
798
|
+
// Set peak to end for now (would need continuous monitoring for accuracy)
|
|
799
|
+
this.metrics.memoryUsage.peak = Math.max(this.metrics.memoryUsage.start, this.metrics.memoryUsage.end);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
//# sourceMappingURL=step-executor.js.map
|