tachibot-mcp 2.0.2
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/.env.example +260 -0
- package/CHANGELOG.md +54 -0
- package/CODE_OF_CONDUCT.md +56 -0
- package/CONTRIBUTING.md +54 -0
- package/Dockerfile +36 -0
- package/LICENSE +644 -0
- package/README.md +201 -0
- package/SECURITY.md +95 -0
- package/dist/personality/komaai-expressions.js +12 -0
- package/dist/profiles/balanced.json +33 -0
- package/dist/profiles/code_focus.json +33 -0
- package/dist/profiles/full.json +33 -0
- package/dist/profiles/minimal.json +33 -0
- package/dist/profiles/research_power.json +33 -0
- package/dist/scripts/build-profiles.js +46 -0
- package/dist/src/application/services/focus/FocusModeRegistry.js +46 -0
- package/dist/src/application/services/focus/FocusTool.service.js +109 -0
- package/dist/src/application/services/focus/ModeRegistry.js +46 -0
- package/dist/src/application/services/focus/modes/focus-deep.mode.js +27 -0
- package/dist/src/application/services/focus/modes/status.mode.js +50 -0
- package/dist/src/application/services/focus/modes/tachibot-status.mode.js +50 -0
- package/dist/src/collaborative-orchestrator.js +391 -0
- package/dist/src/config/model-constants.js +188 -0
- package/dist/src/config/model-defaults.js +57 -0
- package/dist/src/config/model-preferences.js +382 -0
- package/dist/src/config/timeout-config.js +130 -0
- package/dist/src/config.js +173 -0
- package/dist/src/domain/interfaces/IFocusMode.js +5 -0
- package/dist/src/domain/interfaces/IProvider.js +6 -0
- package/dist/src/domain/interfaces/ITool.js +5 -0
- package/dist/src/focus-deep.js +245 -0
- package/dist/src/infrastructure/ascii/art/robots.ascii.js +16 -0
- package/dist/src/mcp-client.js +90 -0
- package/dist/src/memory/index.js +17 -0
- package/dist/src/memory/memory-config.js +135 -0
- package/dist/src/memory/memory-interface.js +174 -0
- package/dist/src/memory/memory-manager.js +383 -0
- package/dist/src/memory/providers/devlog-provider.js +385 -0
- package/dist/src/memory/providers/hybrid-provider.js +399 -0
- package/dist/src/memory/providers/local-provider.js +388 -0
- package/dist/src/memory/providers/mem0-provider.js +337 -0
- package/dist/src/modes/architect.js +477 -0
- package/dist/src/modes/auditor.js +362 -0
- package/dist/src/modes/challenger.js +841 -0
- package/dist/src/modes/code-reviewer.js +382 -0
- package/dist/src/modes/commit-guardian.js +424 -0
- package/dist/src/modes/documentation-writer.js +572 -0
- package/dist/src/modes/scout.js +587 -0
- package/dist/src/modes/shared/helpers/challenger-helpers.js +454 -0
- package/dist/src/modes/shared/helpers/index.js +17 -0
- package/dist/src/modes/shared/helpers/scout-helpers.js +270 -0
- package/dist/src/modes/shared/helpers/verifier-helpers.js +332 -0
- package/dist/src/modes/test-architect.js +767 -0
- package/dist/src/modes/verifier.js +378 -0
- package/dist/src/monitoring/performance-monitor.js +435 -0
- package/dist/src/optimization/batch-executor.js +121 -0
- package/dist/src/optimization/context-pruner.js +196 -0
- package/dist/src/optimization/cost-monitor.js +338 -0
- package/dist/src/optimization/index.js +65 -0
- package/dist/src/optimization/model-router.js +264 -0
- package/dist/src/optimization/result-cache.js +114 -0
- package/dist/src/optimization/token-optimizer.js +257 -0
- package/dist/src/optimization/token-tracker.js +118 -0
- package/dist/src/orchestrator-instructions.js +128 -0
- package/dist/src/orchestrator-lite.js +139 -0
- package/dist/src/orchestrator.js +191 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionEngine.js +1 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionStrategy.js +5 -0
- package/dist/src/orchestrators/collaborative/interfaces/IVisualizationRenderer.js +1 -0
- package/dist/src/orchestrators/collaborative/registries/ModelProviderRegistry.js +95 -0
- package/dist/src/orchestrators/collaborative/registries/ToolAdapterRegistry.js +64 -0
- package/dist/src/orchestrators/collaborative/services/tool-execution/ToolExecutionService.js +502 -0
- package/dist/src/orchestrators/collaborative/services/visualization/VisualizationService.js +206 -0
- package/dist/src/orchestrators/collaborative/types/session-types.js +5 -0
- package/dist/src/profiles/balanced.js +37 -0
- package/dist/src/profiles/code_focus.js +37 -0
- package/dist/src/profiles/debug_intensive.js +59 -0
- package/dist/src/profiles/full.js +37 -0
- package/dist/src/profiles/minimal.js +37 -0
- package/dist/src/profiles/research_code.js +59 -0
- package/dist/src/profiles/research_power.js +37 -0
- package/dist/src/profiles/types.js +5 -0
- package/dist/src/profiles/workflow_builder.js +53 -0
- package/dist/src/prompt-engineer-lite.js +78 -0
- package/dist/src/prompt-engineer.js +399 -0
- package/dist/src/reasoning-chain.js +508 -0
- package/dist/src/sequential-thinking.js +291 -0
- package/dist/src/server-diagnostic.js +74 -0
- package/dist/src/server-raw.js +158 -0
- package/dist/src/server-simple.js +58 -0
- package/dist/src/server.js +514 -0
- package/dist/src/session/session-logger.js +617 -0
- package/dist/src/session/session-manager.js +571 -0
- package/dist/src/session/session-tools.js +400 -0
- package/dist/src/tools/advanced-modes.js +200 -0
- package/dist/src/tools/claude-integration.js +356 -0
- package/dist/src/tools/consolidated/ai-router.js +174 -0
- package/dist/src/tools/consolidated/ai-tool.js +48 -0
- package/dist/src/tools/consolidated/brainstorm-tool.js +87 -0
- package/dist/src/tools/consolidated/environment-detector.js +80 -0
- package/dist/src/tools/consolidated/index.js +50 -0
- package/dist/src/tools/consolidated/search-tool.js +110 -0
- package/dist/src/tools/consolidated/workflow-tool.js +238 -0
- package/dist/src/tools/gemini-tools.js +329 -0
- package/dist/src/tools/grok-enhanced.js +376 -0
- package/dist/src/tools/grok-tools.js +299 -0
- package/dist/src/tools/lmstudio-tools.js +223 -0
- package/dist/src/tools/openai-tools.js +498 -0
- package/dist/src/tools/openrouter-tools.js +317 -0
- package/dist/src/tools/optimized-wrapper.js +204 -0
- package/dist/src/tools/perplexity-tools.js +294 -0
- package/dist/src/tools/pingpong-tool.js +343 -0
- package/dist/src/tools/qwen-wrapper.js +74 -0
- package/dist/src/tools/tool-router.js +444 -0
- package/dist/src/tools/unified-ai-provider.js +260 -0
- package/dist/src/tools/workflow-runner.js +425 -0
- package/dist/src/tools/workflow-validator-tool.js +107 -0
- package/dist/src/types.js +23 -0
- package/dist/src/utils/input-validator.js +130 -0
- package/dist/src/utils/model-router.js +91 -0
- package/dist/src/utils/progress-stream.js +255 -0
- package/dist/src/utils/provider-router.js +88 -0
- package/dist/src/utils/smart-api-client.js +146 -0
- package/dist/src/utils/table-builder.js +218 -0
- package/dist/src/utils/timestamp-formatter.js +134 -0
- package/dist/src/utils/tool-compressor.js +257 -0
- package/dist/src/utils/tool-config.js +201 -0
- package/dist/src/validators/dependency-graph-validator.js +147 -0
- package/dist/src/validators/interpolation-validator.js +222 -0
- package/dist/src/validators/output-usage-validator.js +151 -0
- package/dist/src/validators/syntax-validator.js +102 -0
- package/dist/src/validators/tool-registry-validator.js +123 -0
- package/dist/src/validators/tool-types.js +97 -0
- package/dist/src/validators/types.js +8 -0
- package/dist/src/validators/workflow-validator.js +134 -0
- package/dist/src/visualizer-lite.js +42 -0
- package/dist/src/visualizer.js +179 -0
- package/dist/src/workflows/circuit-breaker.js +199 -0
- package/dist/src/workflows/custom-workflows.js +451 -0
- package/dist/src/workflows/engine/AutoSynthesizer.js +97 -0
- package/dist/src/workflows/engine/StepParameterResolver.js +74 -0
- package/dist/src/workflows/engine/VariableInterpolator.js +123 -0
- package/dist/src/workflows/engine/WorkflowDiscovery.js +125 -0
- package/dist/src/workflows/engine/WorkflowExecutionEngine.js +485 -0
- package/dist/src/workflows/engine/WorkflowExecutor.js +113 -0
- package/dist/src/workflows/engine/WorkflowFileManager.js +244 -0
- package/dist/src/workflows/engine/WorkflowHelpers.js +114 -0
- package/dist/src/workflows/engine/WorkflowOutputFormatter.js +83 -0
- package/dist/src/workflows/engine/events/WorkflowEventBus.js +132 -0
- package/dist/src/workflows/engine/events/interfaces/IEventBus.js +5 -0
- package/dist/src/workflows/engine/handlers/ErrorRecoveryHandler.js +162 -0
- package/dist/src/workflows/engine/handlers/PromptEnhancementHandler.js +115 -0
- package/dist/src/workflows/engine/handlers/SessionPersistenceHandler.js +167 -0
- package/dist/src/workflows/engine/handlers/StepExecutionHandler.js +231 -0
- package/dist/src/workflows/engine/handlers/ToolInvocationHandler.js +46 -0
- package/dist/src/workflows/engine/interfaces/IAutoSynthesizer.js +5 -0
- package/dist/src/workflows/engine/interfaces/IStepParameterResolver.js +5 -0
- package/dist/src/workflows/engine/interfaces/IVariableInterpolator.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowDiscovery.js +4 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowFileManager.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowOutputFormatter.js +5 -0
- package/dist/src/workflows/engine/state/WorkflowStateMachine.js +194 -0
- package/dist/src/workflows/engine/state/interfaces/IStateMachine.js +17 -0
- package/dist/src/workflows/fallback-strategies.js +373 -0
- package/dist/src/workflows/message-queue.js +455 -0
- package/dist/src/workflows/model-router.js +189 -0
- package/dist/src/workflows/orchestrator-examples.js +174 -0
- package/dist/src/workflows/orchestrator-integration.js +200 -0
- package/dist/src/workflows/self-healing.js +524 -0
- package/dist/src/workflows/tool-mapper.js +407 -0
- package/dist/src/workflows/tool-orchestrator.js +796 -0
- package/dist/src/workflows/workflow-engine.js +573 -0
- package/dist/src/workflows/workflow-parser.js +283 -0
- package/dist/src/workflows/workflow-types.js +95 -0
- package/dist/src/workflows.js +568 -0
- package/dist/test-workflow-file-output.js +93 -0
- package/docs/API_KEYS.md +570 -0
- package/docs/CLAUDE_CODE_SETUP.md +181 -0
- package/docs/CLAUDE_DESKTOP_MANUAL.md +127 -0
- package/docs/CONFIGURATION.md +745 -0
- package/docs/FOCUS_MODES.md +240 -0
- package/docs/INSTALLATION_BOTH.md +145 -0
- package/docs/TERMS.md +352 -0
- package/docs/TOOLS_REFERENCE.md +1622 -0
- package/docs/TOOL_PARAMETERS.md +496 -0
- package/docs/TOOL_PROFILES.md +236 -0
- package/docs/WORKFLOWS.md +987 -0
- package/docs/WORKFLOW_OUTPUT.md +198 -0
- package/docs/WORKFLOW_PROGRESS_TRACKING.md +305 -0
- package/docs/workflows/design-brainstorm.md +335 -0
- package/package.json +97 -0
- package/profiles/balanced.json +37 -0
- package/profiles/code_focus.json +37 -0
- package/profiles/debug_intensive.json +34 -0
- package/profiles/full.json +37 -0
- package/profiles/minimal.json +37 -0
- package/profiles/research_power.json +37 -0
- package/profiles/workflow_builder.json +37 -0
- package/smithery.yaml +66 -0
- package/start.sh +8 -0
- package/tools.config.json +81 -0
- package/tsconfig.json +18 -0
- package/workflows/accessibility-code-audit.yaml +92 -0
- package/workflows/code-architecture-review.yaml +202 -0
- package/workflows/code-review.yaml +142 -0
- package/workflows/core/iterative-problem-solver.yaml +283 -0
- package/workflows/creative-brainstorm-yaml.yaml +215 -0
- package/workflows/pingpong.yaml +141 -0
- package/workflows/system/README.md +412 -0
- package/workflows/system/challenger.yaml +175 -0
- package/workflows/system/scout.yaml +164 -0
- package/workflows/system/verifier.yaml +133 -0
- package/workflows/ultra-creative-brainstorm.yaml +318 -0
- package/workflows/ux-research-flow.yaml +92 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interpolation validator - validates ${...} references in workflow
|
|
3
|
+
*/
|
|
4
|
+
export class InterpolationValidator {
|
|
5
|
+
constructor() {
|
|
6
|
+
// Matches ${anything} patterns
|
|
7
|
+
this.interpolationRegex = /\$\{([^}]+)\}/g;
|
|
8
|
+
}
|
|
9
|
+
validate(context) {
|
|
10
|
+
const errors = [];
|
|
11
|
+
// Build step index for quick lookup
|
|
12
|
+
const stepNames = new Set(context.workflow.steps.map(s => s.name));
|
|
13
|
+
const stepOrder = new Map(context.workflow.steps.map((s, i) => [s.name, i]));
|
|
14
|
+
// Build list of step output variables
|
|
15
|
+
const stepOutputVars = new Set();
|
|
16
|
+
context.workflow.steps.forEach(step => {
|
|
17
|
+
if (step.output?.variable) {
|
|
18
|
+
stepOutputVars.add(step.output.variable);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
// Add runtime variables that are always available
|
|
22
|
+
const runtimeVars = ['input', 'timestamp', 'query'];
|
|
23
|
+
// Validate each step's interpolations
|
|
24
|
+
context.workflow.steps.forEach((step, index) => {
|
|
25
|
+
this.validateStepInterpolations(step, index, stepNames, stepOrder, context, errors, stepOutputVars, runtimeVars);
|
|
26
|
+
});
|
|
27
|
+
// Validate output interpolations if present
|
|
28
|
+
if (context.workflow.output?.saveToFile && typeof context.workflow.output.saveToFile === 'string') {
|
|
29
|
+
const allVars = [
|
|
30
|
+
...Array.from(stepOutputVars),
|
|
31
|
+
...runtimeVars,
|
|
32
|
+
...(context.workflow.variables ? Object.keys(context.workflow.variables) : [])
|
|
33
|
+
];
|
|
34
|
+
this.validateStringInterpolations(context.workflow.output.saveToFile, stepNames, allVars, '$.output.saveToFile', errors, stepOrder.size // All steps are valid for output
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return errors;
|
|
38
|
+
}
|
|
39
|
+
validateStepInterpolations(step, stepIndex, stepNames, stepOrder, context, errors, stepOutputVars, runtimeVars) {
|
|
40
|
+
// Collect all available variables at this point
|
|
41
|
+
const availableVars = [
|
|
42
|
+
...Array.from(stepOutputVars),
|
|
43
|
+
...runtimeVars,
|
|
44
|
+
...(context.workflow.variables ? Object.keys(context.workflow.variables) : [])
|
|
45
|
+
];
|
|
46
|
+
// Validate input field interpolations
|
|
47
|
+
if (step.input) {
|
|
48
|
+
const inputStr = JSON.stringify(step.input);
|
|
49
|
+
this.validateStringInterpolations(inputStr, stepNames, availableVars, `$.steps[${stepIndex}].input`, errors, stepIndex, stepOrder);
|
|
50
|
+
}
|
|
51
|
+
// Validate 'when' condition interpolations
|
|
52
|
+
if (step.when) {
|
|
53
|
+
this.validateStringInterpolations(step.when, stepNames, availableVars, `$.steps[${stepIndex}].when`, errors, stepIndex, stepOrder);
|
|
54
|
+
}
|
|
55
|
+
// Validate loadFiles references
|
|
56
|
+
if (step.loadFiles && Array.isArray(step.loadFiles)) {
|
|
57
|
+
step.loadFiles.forEach((fileRef, idx) => {
|
|
58
|
+
if (!stepNames.has(fileRef)) {
|
|
59
|
+
errors.push({
|
|
60
|
+
type: 'interpolation',
|
|
61
|
+
severity: 'error',
|
|
62
|
+
message: `loadFiles references unknown step '${fileRef}'`,
|
|
63
|
+
path: `$.steps[${stepIndex}].loadFiles[${idx}]`,
|
|
64
|
+
suggestion: `Ensure step '${fileRef}' is defined and has saveToFile: true`
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else if (stepOrder.get(fileRef) >= stepIndex) {
|
|
68
|
+
errors.push({
|
|
69
|
+
type: 'interpolation',
|
|
70
|
+
severity: 'error',
|
|
71
|
+
message: `loadFiles references step '${fileRef}' that comes after current step`,
|
|
72
|
+
path: `$.steps[${stepIndex}].loadFiles[${idx}]`,
|
|
73
|
+
suggestion: 'Can only load files from previous steps'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Check for redundancy: is this loadFiles unnecessary?
|
|
78
|
+
const referencedStep = context.workflow.steps.find(s => s.name === fileRef);
|
|
79
|
+
if (referencedStep?.output?.variable) {
|
|
80
|
+
const varName = referencedStep.output.variable;
|
|
81
|
+
const inputStr = step.input ? JSON.stringify(step.input) : '';
|
|
82
|
+
if (inputStr.includes(`\${${varName}}`)) {
|
|
83
|
+
errors.push({
|
|
84
|
+
type: 'redundancy',
|
|
85
|
+
severity: 'warning',
|
|
86
|
+
message: `loadFiles includes '${fileRef}' but its output variable '\${${varName}}' is already used in input`,
|
|
87
|
+
path: `$.steps[${stepIndex}].loadFiles[${idx}]`,
|
|
88
|
+
suggestion: `Remove '${fileRef}' from loadFiles - data is already in memory from previous step`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Validate dependsOn references
|
|
96
|
+
if (step.dependsOn && Array.isArray(step.dependsOn)) {
|
|
97
|
+
step.dependsOn.forEach((depName, idx) => {
|
|
98
|
+
if (!stepNames.has(depName)) {
|
|
99
|
+
errors.push({
|
|
100
|
+
type: 'interpolation',
|
|
101
|
+
severity: 'error',
|
|
102
|
+
message: `dependsOn references unknown step '${depName}'`,
|
|
103
|
+
path: `$.steps[${stepIndex}].dependsOn[${idx}]`,
|
|
104
|
+
suggestion: `Ensure step '${depName}' is defined`
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else if (stepOrder.get(depName) >= stepIndex) {
|
|
108
|
+
errors.push({
|
|
109
|
+
type: 'interpolation',
|
|
110
|
+
severity: 'error',
|
|
111
|
+
message: `dependsOn references step '${depName}' that comes after current step`,
|
|
112
|
+
path: `$.steps[${stepIndex}].dependsOn[${idx}]`,
|
|
113
|
+
suggestion: 'Dependencies must come before dependent steps'
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
validateStringInterpolations(content, stepNames, variables, path, errors, currentStepIndex, stepOrder) {
|
|
120
|
+
const matches = content.matchAll(this.interpolationRegex);
|
|
121
|
+
for (const match of matches) {
|
|
122
|
+
const fullMatch = match[0]; // e.g., "${step1.output}"
|
|
123
|
+
const reference = match[1]; // e.g., "step1.output"
|
|
124
|
+
this.validateSingleReference(reference, fullMatch, stepNames, variables, path, errors, currentStepIndex, stepOrder);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
validateSingleReference(reference, fullMatch, stepNames, variables, path, errors, currentStepIndex, stepOrder) {
|
|
128
|
+
// Check for step output reference: step_name.output or step_name.something
|
|
129
|
+
if (reference.includes('.')) {
|
|
130
|
+
const parts = reference.split('.');
|
|
131
|
+
const stepName = parts[0];
|
|
132
|
+
const property = parts.slice(1).join('.');
|
|
133
|
+
// Validate step exists
|
|
134
|
+
if (!stepNames.has(stepName)) {
|
|
135
|
+
errors.push({
|
|
136
|
+
type: 'interpolation',
|
|
137
|
+
severity: 'error',
|
|
138
|
+
message: `Reference '${fullMatch}' points to undefined step '${stepName}'`,
|
|
139
|
+
path,
|
|
140
|
+
suggestion: `Define step '${stepName}' or correct the reference`
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Validate step order (if applicable)
|
|
145
|
+
if (currentStepIndex !== undefined && stepOrder) {
|
|
146
|
+
const refStepIndex = stepOrder.get(stepName);
|
|
147
|
+
if (refStepIndex !== undefined && refStepIndex >= currentStepIndex) {
|
|
148
|
+
errors.push({
|
|
149
|
+
type: 'interpolation',
|
|
150
|
+
severity: 'error',
|
|
151
|
+
message: `Reference '${fullMatch}' points to step '${stepName}' which appears later in the workflow`,
|
|
152
|
+
path,
|
|
153
|
+
suggestion: `Move step '${stepName}' before the current step, or remove the reference`
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Validate property name - accept 'output' as valid
|
|
158
|
+
if (!property || property.trim() === '') {
|
|
159
|
+
errors.push({
|
|
160
|
+
type: 'interpolation',
|
|
161
|
+
severity: 'warning',
|
|
162
|
+
message: `Reference '${fullMatch}' is missing property after '${stepName}.'`,
|
|
163
|
+
path,
|
|
164
|
+
suggestion: 'Common properties: output, result, data'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else if (property !== 'output') {
|
|
168
|
+
// Warn if using non-standard property (not 'output')
|
|
169
|
+
errors.push({
|
|
170
|
+
type: 'interpolation',
|
|
171
|
+
severity: 'warning',
|
|
172
|
+
message: `Reference '${fullMatch}' uses property '${property}' - only '.output' is guaranteed to exist`,
|
|
173
|
+
path,
|
|
174
|
+
suggestion: `Use '\${${stepName}.output}' for step outputs, or '\${${stepName}}' for direct reference`
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// else property === 'output' → Valid, no error
|
|
178
|
+
}
|
|
179
|
+
// Variable reference (no dot)
|
|
180
|
+
else {
|
|
181
|
+
// Check if this is a direct step reference (valid - runtime supports this)
|
|
182
|
+
if (stepNames.has(reference)) {
|
|
183
|
+
// ✅ VALID: Direct step reference like ${step-name}
|
|
184
|
+
// Runtime will use the step's output
|
|
185
|
+
// Validate step order (if applicable)
|
|
186
|
+
if (currentStepIndex !== undefined && stepOrder) {
|
|
187
|
+
const refStepIndex = stepOrder.get(reference);
|
|
188
|
+
if (refStepIndex !== undefined && refStepIndex >= currentStepIndex) {
|
|
189
|
+
errors.push({
|
|
190
|
+
type: 'interpolation',
|
|
191
|
+
severity: 'error',
|
|
192
|
+
message: `Reference '${fullMatch}' points to step '${reference}' which appears later in the workflow`,
|
|
193
|
+
path,
|
|
194
|
+
suggestion: `Move step '${reference}' before the current step`
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return; // Valid step reference, no error
|
|
199
|
+
}
|
|
200
|
+
// Check variable name format
|
|
201
|
+
if (!/^[a-z][a-z0-9_]*$/.test(reference)) {
|
|
202
|
+
errors.push({
|
|
203
|
+
type: 'interpolation',
|
|
204
|
+
severity: 'warning',
|
|
205
|
+
message: `Variable reference '${fullMatch}' should use snake_case`,
|
|
206
|
+
path,
|
|
207
|
+
suggestion: 'Use lowercase with underscores, e.g., "${my_variable}"'
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// Check if variable is defined
|
|
211
|
+
if (!variables.includes(reference)) {
|
|
212
|
+
errors.push({
|
|
213
|
+
type: 'interpolation',
|
|
214
|
+
severity: 'error',
|
|
215
|
+
message: `Reference '${fullMatch}' points to undefined variable '${reference}'`,
|
|
216
|
+
path,
|
|
217
|
+
suggestion: 'Define the variable in the workflow variables section'
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output usage validator - detects unused step outputs
|
|
3
|
+
* Ensures every step's output is consumed (unless it's final, parallel, or explicitly saved)
|
|
4
|
+
*/
|
|
5
|
+
export class OutputUsageValidator {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.interpolationRegex = /\$\{([^}]+)\}/g;
|
|
8
|
+
}
|
|
9
|
+
validate(context) {
|
|
10
|
+
const errors = [];
|
|
11
|
+
const steps = context.workflow.steps;
|
|
12
|
+
if (steps.length === 0)
|
|
13
|
+
return errors;
|
|
14
|
+
// Build usage maps
|
|
15
|
+
const interpolationUsage = this.buildInterpolationUsageMap(steps);
|
|
16
|
+
const fileUsage = this.buildFileUsageMap(steps);
|
|
17
|
+
// Check each step (except last)
|
|
18
|
+
steps.forEach((step, index) => {
|
|
19
|
+
const isLast = index === steps.length - 1;
|
|
20
|
+
// Skip validation for special cases
|
|
21
|
+
if (isLast)
|
|
22
|
+
return; // Final step output is always "used"
|
|
23
|
+
if (step.parallel === true)
|
|
24
|
+
return; // Parallel steps are side effects
|
|
25
|
+
const stepName = step.name;
|
|
26
|
+
const usedViaInterpolation = interpolationUsage.has(stepName);
|
|
27
|
+
const usedViaFile = fileUsage.has(stepName);
|
|
28
|
+
const hasSaveToFile = step.saveToFile === true;
|
|
29
|
+
// Case 1: Not used at all → ERROR
|
|
30
|
+
if (!usedViaInterpolation && !usedViaFile) {
|
|
31
|
+
errors.push({
|
|
32
|
+
type: 'output-usage',
|
|
33
|
+
severity: 'error',
|
|
34
|
+
message: `Step '${stepName}' produces output but nothing uses it`,
|
|
35
|
+
path: `$.steps[${index}]`,
|
|
36
|
+
suggestion: hasSaveToFile
|
|
37
|
+
? `Add 'loadFiles: ["${stepName}"]' to a later step, or remove 'saveToFile'`
|
|
38
|
+
: `Either:\n - Remove this step to save cost\n - Reference it in a later step: \${${stepName}}\n - Add 'saveToFile: true' and load it later`
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Case 2: Saved to file but never loaded → WARNING
|
|
42
|
+
else if (hasSaveToFile && !usedViaFile) {
|
|
43
|
+
errors.push({
|
|
44
|
+
type: 'output-usage',
|
|
45
|
+
severity: 'warning',
|
|
46
|
+
message: `Step '${stepName}' saves output to file but no step loads it`,
|
|
47
|
+
path: `$.steps[${index}]`,
|
|
48
|
+
suggestion: usedViaInterpolation
|
|
49
|
+
? `File is saved but unused. Consider removing 'saveToFile: true' to save disk space`
|
|
50
|
+
: `Add 'loadFiles: ["${stepName}"]' to a later step, or remove 'saveToFile'`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Case 3: Only used via file (not interpolated) → INFO
|
|
54
|
+
else if (usedViaFile && !usedViaInterpolation && hasSaveToFile) {
|
|
55
|
+
// This is actually fine - file-based workflows are intentional
|
|
56
|
+
// Only warn if output is large (can't determine here, so skip)
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return errors;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build map of which steps are referenced via ${step-name} or ${variable} interpolation
|
|
63
|
+
*/
|
|
64
|
+
buildInterpolationUsageMap(steps) {
|
|
65
|
+
const used = new Set();
|
|
66
|
+
// Build map of variable names to step names
|
|
67
|
+
const varToStep = new Map();
|
|
68
|
+
steps.forEach(step => {
|
|
69
|
+
if (step.output?.variable) {
|
|
70
|
+
varToStep.set(step.output.variable, step.name);
|
|
71
|
+
console.error(`[DEBUG] Mapped variable '${step.output.variable}' → step '${step.name}'`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
console.error(`[DEBUG] varToStep has ${varToStep.size} entries`);
|
|
75
|
+
steps.forEach((step) => {
|
|
76
|
+
// Check input field
|
|
77
|
+
if (step.input) {
|
|
78
|
+
const inputStr = JSON.stringify(step.input);
|
|
79
|
+
const matches = inputStr.matchAll(this.interpolationRegex);
|
|
80
|
+
for (const match of matches) {
|
|
81
|
+
const reference = match[1]; // e.g., "step-name", "step-name.output", or "variable_name"
|
|
82
|
+
// Extract step name (remove .output suffix if present)
|
|
83
|
+
const stepName = reference.includes('.')
|
|
84
|
+
? reference.split('.')[0]
|
|
85
|
+
: reference;
|
|
86
|
+
// Check if it's a direct step reference
|
|
87
|
+
if (steps.some(s => s.name === stepName)) {
|
|
88
|
+
console.error(`[DEBUG] Found step reference: ${stepName}`);
|
|
89
|
+
used.add(stepName);
|
|
90
|
+
}
|
|
91
|
+
// Check if it's a variable name that maps to a step
|
|
92
|
+
else if (varToStep.has(reference)) {
|
|
93
|
+
const mappedStep = varToStep.get(reference);
|
|
94
|
+
console.error(`[DEBUG] Found variable reference: ${reference} → ${mappedStep}`);
|
|
95
|
+
used.add(mappedStep);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.error(`[DEBUG] Unknown reference: ${reference}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Check 'when' condition
|
|
103
|
+
if (step.when) {
|
|
104
|
+
const matches = step.when.matchAll(this.interpolationRegex);
|
|
105
|
+
for (const match of matches) {
|
|
106
|
+
const reference = match[1];
|
|
107
|
+
const stepName = reference.includes('.')
|
|
108
|
+
? reference.split('.')[0]
|
|
109
|
+
: reference;
|
|
110
|
+
if (steps.some(s => s.name === stepName)) {
|
|
111
|
+
used.add(stepName);
|
|
112
|
+
}
|
|
113
|
+
else if (varToStep.has(reference)) {
|
|
114
|
+
used.add(varToStep.get(reference));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Check condition.if field
|
|
119
|
+
if (step.condition?.if) {
|
|
120
|
+
const matches = step.condition.if.matchAll(this.interpolationRegex);
|
|
121
|
+
for (const match of matches) {
|
|
122
|
+
const reference = match[1];
|
|
123
|
+
const stepName = reference.includes('.')
|
|
124
|
+
? reference.split('.')[0]
|
|
125
|
+
: reference;
|
|
126
|
+
if (steps.some(s => s.name === stepName)) {
|
|
127
|
+
used.add(stepName);
|
|
128
|
+
}
|
|
129
|
+
else if (varToStep.has(reference)) {
|
|
130
|
+
used.add(varToStep.get(reference));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return used;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build map of which steps are loaded via loadFiles
|
|
139
|
+
*/
|
|
140
|
+
buildFileUsageMap(steps) {
|
|
141
|
+
const used = new Set();
|
|
142
|
+
steps.forEach((step) => {
|
|
143
|
+
if (step.loadFiles && Array.isArray(step.loadFiles)) {
|
|
144
|
+
step.loadFiles.forEach((fileRef) => {
|
|
145
|
+
used.add(fileRef);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return used;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax validator - validates YAML/JSON structure
|
|
3
|
+
*/
|
|
4
|
+
import { load as yamlLoad } from 'js-yaml';
|
|
5
|
+
export class SyntaxValidator {
|
|
6
|
+
validate(workflowContent, isJson = false) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
let workflow;
|
|
9
|
+
try {
|
|
10
|
+
// Parse YAML or JSON
|
|
11
|
+
workflow = isJson
|
|
12
|
+
? JSON.parse(workflowContent)
|
|
13
|
+
: yamlLoad(workflowContent);
|
|
14
|
+
// Ensure workflow is not undefined
|
|
15
|
+
if (!workflow) {
|
|
16
|
+
errors.push({
|
|
17
|
+
type: 'syntax',
|
|
18
|
+
severity: 'error',
|
|
19
|
+
message: 'Failed to parse workflow content',
|
|
20
|
+
path: '$'
|
|
21
|
+
});
|
|
22
|
+
return { valid: false, errors };
|
|
23
|
+
}
|
|
24
|
+
// Validate required fields
|
|
25
|
+
if (!workflow.name) {
|
|
26
|
+
errors.push({
|
|
27
|
+
type: 'syntax',
|
|
28
|
+
severity: 'error',
|
|
29
|
+
message: 'Missing required field: name',
|
|
30
|
+
path: '$.name'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (!workflow.steps || !Array.isArray(workflow.steps)) {
|
|
34
|
+
errors.push({
|
|
35
|
+
type: 'syntax',
|
|
36
|
+
severity: 'error',
|
|
37
|
+
message: 'Missing or invalid required field: steps (must be an array)',
|
|
38
|
+
path: '$.steps'
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Validate each step has required fields
|
|
42
|
+
if (workflow.steps && Array.isArray(workflow.steps)) {
|
|
43
|
+
workflow.steps.forEach((step, index) => {
|
|
44
|
+
if (!step.name) {
|
|
45
|
+
errors.push({
|
|
46
|
+
type: 'syntax',
|
|
47
|
+
severity: 'error',
|
|
48
|
+
message: 'Step is missing required field: name',
|
|
49
|
+
path: `$.steps[${index}].name`
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (!step.tool) {
|
|
53
|
+
errors.push({
|
|
54
|
+
type: 'syntax',
|
|
55
|
+
severity: 'error',
|
|
56
|
+
message: 'Step is missing required field: tool',
|
|
57
|
+
path: `$.steps[${index}].tool`
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Check for valid step name format (snake_case or kebab-case)
|
|
61
|
+
if (step.name && !/^[a-z][a-z0-9_-]*$/.test(step.name)) {
|
|
62
|
+
errors.push({
|
|
63
|
+
type: 'syntax',
|
|
64
|
+
severity: 'warning',
|
|
65
|
+
message: `Step name '${step.name}' should use snake_case or kebab-case`,
|
|
66
|
+
path: `$.steps[${index}].name`,
|
|
67
|
+
suggestion: 'Use lowercase with underscores or hyphens, e.g., "my_step" or "my-step"'
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Validate variable names if present
|
|
73
|
+
if (workflow.variables) {
|
|
74
|
+
for (const varName of Object.keys(workflow.variables)) {
|
|
75
|
+
if (!/^[a-z][a-z0-9_]*$/.test(varName)) {
|
|
76
|
+
errors.push({
|
|
77
|
+
type: 'syntax',
|
|
78
|
+
severity: 'warning',
|
|
79
|
+
message: `Variable name '${varName}' should use snake_case`,
|
|
80
|
+
path: `$.variables.${varName}`,
|
|
81
|
+
suggestion: 'Use lowercase with underscores, e.g., "my_variable"'
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
errors.push({
|
|
89
|
+
type: 'syntax',
|
|
90
|
+
severity: 'error',
|
|
91
|
+
message: `Invalid ${isJson ? 'JSON' : 'YAML'} syntax: ${e.message}`,
|
|
92
|
+
path: '$',
|
|
93
|
+
suggestion: 'Check for proper indentation, missing colons, or invalid characters'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
valid: errors.filter(e => e.severity === 'error').length === 0,
|
|
98
|
+
errors,
|
|
99
|
+
workflow
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool registry validator - validates tool names against enabled tools
|
|
3
|
+
*/
|
|
4
|
+
export class ToolRegistryValidator {
|
|
5
|
+
validate(context) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
context.workflow.steps.forEach((step, index) => {
|
|
8
|
+
// Check if tool exists in all known tools
|
|
9
|
+
const isKnownTool = context.allKnownTools.has(step.tool);
|
|
10
|
+
const isEnabled = context.enabledTools.has(step.tool);
|
|
11
|
+
if (!isKnownTool) {
|
|
12
|
+
// Unknown tool (misspelled or doesn't exist) → ERROR
|
|
13
|
+
errors.push({
|
|
14
|
+
type: 'tool',
|
|
15
|
+
severity: 'error',
|
|
16
|
+
message: `Tool '${step.tool}' does not exist`,
|
|
17
|
+
path: `$.steps[${index}].tool`,
|
|
18
|
+
suggestion: this.suggestSimilarTool(step.tool, context.allKnownTools)
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else if (!isEnabled) {
|
|
22
|
+
// Known tool but disabled → WARNING
|
|
23
|
+
errors.push({
|
|
24
|
+
type: 'tool',
|
|
25
|
+
severity: 'warning',
|
|
26
|
+
message: `Tool '${step.tool}' is disabled in tools.config.json`,
|
|
27
|
+
path: `$.steps[${index}].tool`,
|
|
28
|
+
suggestion: `Enable '${step.tool}' in your tools.config.json or active profile`
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// Validate tool name format
|
|
32
|
+
if (step.tool && !this.isValidToolName(step.tool)) {
|
|
33
|
+
errors.push({
|
|
34
|
+
type: 'tool',
|
|
35
|
+
severity: 'warning',
|
|
36
|
+
message: `Tool name '${step.tool}' has unusual format`,
|
|
37
|
+
path: `$.steps[${index}].tool`,
|
|
38
|
+
suggestion: 'Tool names should use snake_case or be known MCP tools'
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return errors;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if tool name follows valid conventions
|
|
46
|
+
*/
|
|
47
|
+
isValidToolName(toolName) {
|
|
48
|
+
// Valid formats:
|
|
49
|
+
// - snake_case: my_tool
|
|
50
|
+
// - Known tool patterns: focus, scout, verifier, etc.
|
|
51
|
+
return /^[a-z][a-z0-9_]*$/.test(toolName);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if this is a known tool name (might be disabled)
|
|
55
|
+
*/
|
|
56
|
+
isKnownToolName(toolName) {
|
|
57
|
+
const knownTools = [
|
|
58
|
+
// Core
|
|
59
|
+
'think', 'focus', 'nextThought',
|
|
60
|
+
// Perplexity
|
|
61
|
+
'perplexity_ask', 'perplexity_reason', 'perplexity_research',
|
|
62
|
+
// Grok
|
|
63
|
+
'grok_reason', 'grok_code', 'grok_debug', 'grok_architect', 'grok_brainstorm', 'grok_search',
|
|
64
|
+
// OpenAI
|
|
65
|
+
'openai_compare', 'openai_brainstorm', 'openai_gpt5_reason', 'openai_code_review', 'openai_explain',
|
|
66
|
+
// Gemini
|
|
67
|
+
'gemini_brainstorm', 'gemini_analyze_code', 'gemini_analyze_text',
|
|
68
|
+
// Qwen
|
|
69
|
+
'qwen_coder',
|
|
70
|
+
// Advanced modes
|
|
71
|
+
'verifier', 'scout', 'challenger', 'hunter',
|
|
72
|
+
// Workflow
|
|
73
|
+
'workflow', 'list_workflows', 'create_workflow', 'visualize_workflow',
|
|
74
|
+
// Collaborative
|
|
75
|
+
'pingpong', 'qwen_competitive'
|
|
76
|
+
];
|
|
77
|
+
return knownTools.includes(toolName);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Suggest a similar tool name using Levenshtein distance
|
|
81
|
+
*/
|
|
82
|
+
suggestSimilarTool(toolName, enabledTools) {
|
|
83
|
+
const suggestions = [];
|
|
84
|
+
for (const tool of enabledTools) {
|
|
85
|
+
const distance = this.levenshteinDistance(toolName, tool);
|
|
86
|
+
if (distance <= 3) { // Only suggest if reasonably close
|
|
87
|
+
suggestions.push({ tool, distance });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (suggestions.length === 0) {
|
|
91
|
+
return 'Check tools.config.json for available tools';
|
|
92
|
+
}
|
|
93
|
+
suggestions.sort((a, b) => a.distance - b.distance);
|
|
94
|
+
const topSuggestions = suggestions.slice(0, 3).map(s => s.tool);
|
|
95
|
+
return `Did you mean: ${topSuggestions.join(', ')}?`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Calculate Levenshtein distance between two strings
|
|
99
|
+
*/
|
|
100
|
+
levenshteinDistance(a, b) {
|
|
101
|
+
const matrix = [];
|
|
102
|
+
for (let i = 0; i <= b.length; i++) {
|
|
103
|
+
matrix[i] = [i];
|
|
104
|
+
}
|
|
105
|
+
for (let j = 0; j <= a.length; j++) {
|
|
106
|
+
matrix[0][j] = j;
|
|
107
|
+
}
|
|
108
|
+
for (let i = 1; i <= b.length; i++) {
|
|
109
|
+
for (let j = 1; j <= a.length; j++) {
|
|
110
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
111
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
|
|
115
|
+
matrix[i][j - 1] + 1, // insertion
|
|
116
|
+
matrix[i - 1][j] + 1 // deletion
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return matrix[b.length][a.length];
|
|
122
|
+
}
|
|
123
|
+
}
|