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.
Files changed (214) hide show
  1. package/.env.example +260 -0
  2. package/CHANGELOG.md +54 -0
  3. package/CODE_OF_CONDUCT.md +56 -0
  4. package/CONTRIBUTING.md +54 -0
  5. package/Dockerfile +36 -0
  6. package/LICENSE +644 -0
  7. package/README.md +201 -0
  8. package/SECURITY.md +95 -0
  9. package/dist/personality/komaai-expressions.js +12 -0
  10. package/dist/profiles/balanced.json +33 -0
  11. package/dist/profiles/code_focus.json +33 -0
  12. package/dist/profiles/full.json +33 -0
  13. package/dist/profiles/minimal.json +33 -0
  14. package/dist/profiles/research_power.json +33 -0
  15. package/dist/scripts/build-profiles.js +46 -0
  16. package/dist/src/application/services/focus/FocusModeRegistry.js +46 -0
  17. package/dist/src/application/services/focus/FocusTool.service.js +109 -0
  18. package/dist/src/application/services/focus/ModeRegistry.js +46 -0
  19. package/dist/src/application/services/focus/modes/focus-deep.mode.js +27 -0
  20. package/dist/src/application/services/focus/modes/status.mode.js +50 -0
  21. package/dist/src/application/services/focus/modes/tachibot-status.mode.js +50 -0
  22. package/dist/src/collaborative-orchestrator.js +391 -0
  23. package/dist/src/config/model-constants.js +188 -0
  24. package/dist/src/config/model-defaults.js +57 -0
  25. package/dist/src/config/model-preferences.js +382 -0
  26. package/dist/src/config/timeout-config.js +130 -0
  27. package/dist/src/config.js +173 -0
  28. package/dist/src/domain/interfaces/IFocusMode.js +5 -0
  29. package/dist/src/domain/interfaces/IProvider.js +6 -0
  30. package/dist/src/domain/interfaces/ITool.js +5 -0
  31. package/dist/src/focus-deep.js +245 -0
  32. package/dist/src/infrastructure/ascii/art/robots.ascii.js +16 -0
  33. package/dist/src/mcp-client.js +90 -0
  34. package/dist/src/memory/index.js +17 -0
  35. package/dist/src/memory/memory-config.js +135 -0
  36. package/dist/src/memory/memory-interface.js +174 -0
  37. package/dist/src/memory/memory-manager.js +383 -0
  38. package/dist/src/memory/providers/devlog-provider.js +385 -0
  39. package/dist/src/memory/providers/hybrid-provider.js +399 -0
  40. package/dist/src/memory/providers/local-provider.js +388 -0
  41. package/dist/src/memory/providers/mem0-provider.js +337 -0
  42. package/dist/src/modes/architect.js +477 -0
  43. package/dist/src/modes/auditor.js +362 -0
  44. package/dist/src/modes/challenger.js +841 -0
  45. package/dist/src/modes/code-reviewer.js +382 -0
  46. package/dist/src/modes/commit-guardian.js +424 -0
  47. package/dist/src/modes/documentation-writer.js +572 -0
  48. package/dist/src/modes/scout.js +587 -0
  49. package/dist/src/modes/shared/helpers/challenger-helpers.js +454 -0
  50. package/dist/src/modes/shared/helpers/index.js +17 -0
  51. package/dist/src/modes/shared/helpers/scout-helpers.js +270 -0
  52. package/dist/src/modes/shared/helpers/verifier-helpers.js +332 -0
  53. package/dist/src/modes/test-architect.js +767 -0
  54. package/dist/src/modes/verifier.js +378 -0
  55. package/dist/src/monitoring/performance-monitor.js +435 -0
  56. package/dist/src/optimization/batch-executor.js +121 -0
  57. package/dist/src/optimization/context-pruner.js +196 -0
  58. package/dist/src/optimization/cost-monitor.js +338 -0
  59. package/dist/src/optimization/index.js +65 -0
  60. package/dist/src/optimization/model-router.js +264 -0
  61. package/dist/src/optimization/result-cache.js +114 -0
  62. package/dist/src/optimization/token-optimizer.js +257 -0
  63. package/dist/src/optimization/token-tracker.js +118 -0
  64. package/dist/src/orchestrator-instructions.js +128 -0
  65. package/dist/src/orchestrator-lite.js +139 -0
  66. package/dist/src/orchestrator.js +191 -0
  67. package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionEngine.js +1 -0
  68. package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionStrategy.js +5 -0
  69. package/dist/src/orchestrators/collaborative/interfaces/IVisualizationRenderer.js +1 -0
  70. package/dist/src/orchestrators/collaborative/registries/ModelProviderRegistry.js +95 -0
  71. package/dist/src/orchestrators/collaborative/registries/ToolAdapterRegistry.js +64 -0
  72. package/dist/src/orchestrators/collaborative/services/tool-execution/ToolExecutionService.js +502 -0
  73. package/dist/src/orchestrators/collaborative/services/visualization/VisualizationService.js +206 -0
  74. package/dist/src/orchestrators/collaborative/types/session-types.js +5 -0
  75. package/dist/src/profiles/balanced.js +37 -0
  76. package/dist/src/profiles/code_focus.js +37 -0
  77. package/dist/src/profiles/debug_intensive.js +59 -0
  78. package/dist/src/profiles/full.js +37 -0
  79. package/dist/src/profiles/minimal.js +37 -0
  80. package/dist/src/profiles/research_code.js +59 -0
  81. package/dist/src/profiles/research_power.js +37 -0
  82. package/dist/src/profiles/types.js +5 -0
  83. package/dist/src/profiles/workflow_builder.js +53 -0
  84. package/dist/src/prompt-engineer-lite.js +78 -0
  85. package/dist/src/prompt-engineer.js +399 -0
  86. package/dist/src/reasoning-chain.js +508 -0
  87. package/dist/src/sequential-thinking.js +291 -0
  88. package/dist/src/server-diagnostic.js +74 -0
  89. package/dist/src/server-raw.js +158 -0
  90. package/dist/src/server-simple.js +58 -0
  91. package/dist/src/server.js +514 -0
  92. package/dist/src/session/session-logger.js +617 -0
  93. package/dist/src/session/session-manager.js +571 -0
  94. package/dist/src/session/session-tools.js +400 -0
  95. package/dist/src/tools/advanced-modes.js +200 -0
  96. package/dist/src/tools/claude-integration.js +356 -0
  97. package/dist/src/tools/consolidated/ai-router.js +174 -0
  98. package/dist/src/tools/consolidated/ai-tool.js +48 -0
  99. package/dist/src/tools/consolidated/brainstorm-tool.js +87 -0
  100. package/dist/src/tools/consolidated/environment-detector.js +80 -0
  101. package/dist/src/tools/consolidated/index.js +50 -0
  102. package/dist/src/tools/consolidated/search-tool.js +110 -0
  103. package/dist/src/tools/consolidated/workflow-tool.js +238 -0
  104. package/dist/src/tools/gemini-tools.js +329 -0
  105. package/dist/src/tools/grok-enhanced.js +376 -0
  106. package/dist/src/tools/grok-tools.js +299 -0
  107. package/dist/src/tools/lmstudio-tools.js +223 -0
  108. package/dist/src/tools/openai-tools.js +498 -0
  109. package/dist/src/tools/openrouter-tools.js +317 -0
  110. package/dist/src/tools/optimized-wrapper.js +204 -0
  111. package/dist/src/tools/perplexity-tools.js +294 -0
  112. package/dist/src/tools/pingpong-tool.js +343 -0
  113. package/dist/src/tools/qwen-wrapper.js +74 -0
  114. package/dist/src/tools/tool-router.js +444 -0
  115. package/dist/src/tools/unified-ai-provider.js +260 -0
  116. package/dist/src/tools/workflow-runner.js +425 -0
  117. package/dist/src/tools/workflow-validator-tool.js +107 -0
  118. package/dist/src/types.js +23 -0
  119. package/dist/src/utils/input-validator.js +130 -0
  120. package/dist/src/utils/model-router.js +91 -0
  121. package/dist/src/utils/progress-stream.js +255 -0
  122. package/dist/src/utils/provider-router.js +88 -0
  123. package/dist/src/utils/smart-api-client.js +146 -0
  124. package/dist/src/utils/table-builder.js +218 -0
  125. package/dist/src/utils/timestamp-formatter.js +134 -0
  126. package/dist/src/utils/tool-compressor.js +257 -0
  127. package/dist/src/utils/tool-config.js +201 -0
  128. package/dist/src/validators/dependency-graph-validator.js +147 -0
  129. package/dist/src/validators/interpolation-validator.js +222 -0
  130. package/dist/src/validators/output-usage-validator.js +151 -0
  131. package/dist/src/validators/syntax-validator.js +102 -0
  132. package/dist/src/validators/tool-registry-validator.js +123 -0
  133. package/dist/src/validators/tool-types.js +97 -0
  134. package/dist/src/validators/types.js +8 -0
  135. package/dist/src/validators/workflow-validator.js +134 -0
  136. package/dist/src/visualizer-lite.js +42 -0
  137. package/dist/src/visualizer.js +179 -0
  138. package/dist/src/workflows/circuit-breaker.js +199 -0
  139. package/dist/src/workflows/custom-workflows.js +451 -0
  140. package/dist/src/workflows/engine/AutoSynthesizer.js +97 -0
  141. package/dist/src/workflows/engine/StepParameterResolver.js +74 -0
  142. package/dist/src/workflows/engine/VariableInterpolator.js +123 -0
  143. package/dist/src/workflows/engine/WorkflowDiscovery.js +125 -0
  144. package/dist/src/workflows/engine/WorkflowExecutionEngine.js +485 -0
  145. package/dist/src/workflows/engine/WorkflowExecutor.js +113 -0
  146. package/dist/src/workflows/engine/WorkflowFileManager.js +244 -0
  147. package/dist/src/workflows/engine/WorkflowHelpers.js +114 -0
  148. package/dist/src/workflows/engine/WorkflowOutputFormatter.js +83 -0
  149. package/dist/src/workflows/engine/events/WorkflowEventBus.js +132 -0
  150. package/dist/src/workflows/engine/events/interfaces/IEventBus.js +5 -0
  151. package/dist/src/workflows/engine/handlers/ErrorRecoveryHandler.js +162 -0
  152. package/dist/src/workflows/engine/handlers/PromptEnhancementHandler.js +115 -0
  153. package/dist/src/workflows/engine/handlers/SessionPersistenceHandler.js +167 -0
  154. package/dist/src/workflows/engine/handlers/StepExecutionHandler.js +231 -0
  155. package/dist/src/workflows/engine/handlers/ToolInvocationHandler.js +46 -0
  156. package/dist/src/workflows/engine/interfaces/IAutoSynthesizer.js +5 -0
  157. package/dist/src/workflows/engine/interfaces/IStepParameterResolver.js +5 -0
  158. package/dist/src/workflows/engine/interfaces/IVariableInterpolator.js +5 -0
  159. package/dist/src/workflows/engine/interfaces/IWorkflowDiscovery.js +4 -0
  160. package/dist/src/workflows/engine/interfaces/IWorkflowFileManager.js +5 -0
  161. package/dist/src/workflows/engine/interfaces/IWorkflowOutputFormatter.js +5 -0
  162. package/dist/src/workflows/engine/state/WorkflowStateMachine.js +194 -0
  163. package/dist/src/workflows/engine/state/interfaces/IStateMachine.js +17 -0
  164. package/dist/src/workflows/fallback-strategies.js +373 -0
  165. package/dist/src/workflows/message-queue.js +455 -0
  166. package/dist/src/workflows/model-router.js +189 -0
  167. package/dist/src/workflows/orchestrator-examples.js +174 -0
  168. package/dist/src/workflows/orchestrator-integration.js +200 -0
  169. package/dist/src/workflows/self-healing.js +524 -0
  170. package/dist/src/workflows/tool-mapper.js +407 -0
  171. package/dist/src/workflows/tool-orchestrator.js +796 -0
  172. package/dist/src/workflows/workflow-engine.js +573 -0
  173. package/dist/src/workflows/workflow-parser.js +283 -0
  174. package/dist/src/workflows/workflow-types.js +95 -0
  175. package/dist/src/workflows.js +568 -0
  176. package/dist/test-workflow-file-output.js +93 -0
  177. package/docs/API_KEYS.md +570 -0
  178. package/docs/CLAUDE_CODE_SETUP.md +181 -0
  179. package/docs/CLAUDE_DESKTOP_MANUAL.md +127 -0
  180. package/docs/CONFIGURATION.md +745 -0
  181. package/docs/FOCUS_MODES.md +240 -0
  182. package/docs/INSTALLATION_BOTH.md +145 -0
  183. package/docs/TERMS.md +352 -0
  184. package/docs/TOOLS_REFERENCE.md +1622 -0
  185. package/docs/TOOL_PARAMETERS.md +496 -0
  186. package/docs/TOOL_PROFILES.md +236 -0
  187. package/docs/WORKFLOWS.md +987 -0
  188. package/docs/WORKFLOW_OUTPUT.md +198 -0
  189. package/docs/WORKFLOW_PROGRESS_TRACKING.md +305 -0
  190. package/docs/workflows/design-brainstorm.md +335 -0
  191. package/package.json +97 -0
  192. package/profiles/balanced.json +37 -0
  193. package/profiles/code_focus.json +37 -0
  194. package/profiles/debug_intensive.json +34 -0
  195. package/profiles/full.json +37 -0
  196. package/profiles/minimal.json +37 -0
  197. package/profiles/research_power.json +37 -0
  198. package/profiles/workflow_builder.json +37 -0
  199. package/smithery.yaml +66 -0
  200. package/start.sh +8 -0
  201. package/tools.config.json +81 -0
  202. package/tsconfig.json +18 -0
  203. package/workflows/accessibility-code-audit.yaml +92 -0
  204. package/workflows/code-architecture-review.yaml +202 -0
  205. package/workflows/code-review.yaml +142 -0
  206. package/workflows/core/iterative-problem-solver.yaml +283 -0
  207. package/workflows/creative-brainstorm-yaml.yaml +215 -0
  208. package/workflows/pingpong.yaml +141 -0
  209. package/workflows/system/README.md +412 -0
  210. package/workflows/system/challenger.yaml +175 -0
  211. package/workflows/system/scout.yaml +164 -0
  212. package/workflows/system/verifier.yaml +133 -0
  213. package/workflows/ultra-creative-brainstorm.yaml +318 -0
  214. 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
+ }