voyageai-cli 1.30.0 → 1.30.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/README.md +4 -4
- package/package.json +1 -1
- package/src/cli.js +8 -0
- package/src/commands/about.js +3 -3
- package/src/commands/chat.js +32 -11
- package/src/commands/code-search.js +751 -0
- package/src/commands/doctor.js +1 -1
- package/src/commands/export.js +124 -0
- package/src/commands/import.js +195 -0
- package/src/commands/index-workspace.js +243 -0
- package/src/commands/mcp-server.js +113 -3
- package/src/commands/playground.js +120 -4
- package/src/commands/quickstart.js +4 -4
- package/src/commands/workflow.js +132 -65
- package/src/lib/catalog.js +4 -2
- package/src/lib/code-search.js +315 -0
- package/src/lib/codegen.js +1 -1
- package/src/lib/explanations.js +3 -3
- package/src/lib/export/contexts/benchmark-export.js +27 -0
- package/src/lib/export/contexts/chat-export.js +41 -0
- package/src/lib/export/contexts/explore-export.js +22 -0
- package/src/lib/export/contexts/search-export.js +54 -0
- package/src/lib/export/contexts/workflow-export.js +80 -0
- package/src/lib/export/formats/clipboard-export.js +29 -0
- package/src/lib/export/formats/csv-export.js +45 -0
- package/src/lib/export/formats/json-export.js +50 -0
- package/src/lib/export/formats/markdown-export.js +189 -0
- package/src/lib/export/formats/mermaid-export.js +274 -0
- package/src/lib/export/formats/pdf-export.js +117 -0
- package/src/lib/export/formats/png-export.js +96 -0
- package/src/lib/export/formats/svg-export.js +116 -0
- package/src/lib/export/index.js +175 -0
- package/src/lib/github.js +226 -0
- package/src/lib/template-engine.js +154 -20
- package/src/lib/workflow-builder.js +753 -0
- package/src/lib/workflow-formatters.js +454 -0
- package/src/lib/workflow-input-cache.js +111 -0
- package/src/lib/workflow-scaffold.js +1 -1
- package/src/lib/workflow.js +297 -28
- package/src/mcp/install.js +280 -7
- package/src/mcp/schemas/index.js +170 -0
- package/src/mcp/server.js +19 -4
- package/src/mcp/tools/authoring.js +662 -0
- package/src/mcp/tools/code-search.js +620 -0
- package/src/mcp/tools/ingest.js +2 -5
- package/src/mcp/tools/retrieval.js +2 -15
- package/src/mcp/tools/workspace.js +452 -0
- package/src/mcp/utils.js +20 -0
- package/src/playground/announcements.md +52 -5
- package/src/playground/help/workflow-nodes.js +127 -2
- package/src/playground/index.html +17109 -12438
- package/src/playground/vendor/mermaid.min.js +2811 -0
- package/src/workflows/code-review.json +110 -0
- package/src/workflows/cost-analysis.json +5 -0
- package/src/workflows/rag-chat.json +165 -0
- package/src/workflows/tests/code-review.fresh-index.test.json +83 -0
- package/src/workflows/tests/code-review.happy-path.test.json +121 -0
- package/src/workflows/tests/code-review.no-question.test.json +70 -0
- package/src/workflows/tests/consistency-check.happy-path.test.json +28 -0
- package/src/workflows/tests/consistency-check.missing-source.test.json +26 -0
- package/src/workflows/tests/cost-analysis.happy-path.test.json +28 -0
- package/src/workflows/tests/enrich-and-ingest.happy-path.test.json +38 -0
- package/src/workflows/tests/enrich-and-ingest.notify-fails.test.json +38 -0
- package/src/workflows/tests/intelligent-ingest.all-filtered.test.json +26 -0
- package/src/workflows/tests/intelligent-ingest.happy-path.test.json +28 -0
- package/src/workflows/tests/kb-health-report.custom-queries.test.json +24 -0
- package/src/workflows/tests/kb-health-report.happy-path.test.json +26 -0
- package/src/workflows/tests/multi-collection-search.happy-path.test.json +40 -0
- package/src/workflows/tests/multi-collection-search.one-empty.test.json +28 -0
- package/src/workflows/tests/rag-chat.happy-path.test.json +26 -0
- package/src/workflows/tests/rag-chat.no-relevant-results.test.json +25 -0
- package/src/workflows/tests/research-and-summarize.happy-path.test.json +33 -0
- package/src/workflows/tests/research-and-summarize.no-results.test.json +29 -0
- package/src/workflows/tests/search-with-fallback.empty-both.test.json +24 -0
- package/src/workflows/tests/search-with-fallback.fallback-branch.test.json +24 -0
- package/src/workflows/tests/search-with-fallback.happy-path.test.json +27 -0
- package/src/workflows/tests/smart-ingest.duplicate-detected.test.json +34 -0
- package/src/workflows/tests/smart-ingest.happy-path.test.json +31 -0
- package/src/playground/assets/announcements/appstore.jpg +0 -0
- package/src/playground/assets/announcements/circuits.jpg +0 -0
- package/src/playground/assets/announcements/csvingest.jpg +0 -0
- package/src/playground/assets/announcements/green-wave.jpg +0 -0
package/src/lib/workflow.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
const VAI_TOOLS = new Set([
|
|
17
17
|
'query', 'search', 'rerank', 'embed', 'similarity',
|
|
18
18
|
'ingest', 'collections', 'models', 'explain', 'estimate',
|
|
19
|
+
'code_index', 'code_search', 'code_query', 'code_find_similar', 'code_status',
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
const CONTROL_FLOW_TOOLS = new Set(['merge', 'filter', 'transform', 'generate', 'conditional', 'loop', 'template']);
|
|
@@ -45,32 +46,74 @@ const SCHEMA_LIMITS = {
|
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* Validate a workflow definition object.
|
|
48
|
-
*
|
|
49
|
+
* Two modes: strict (default) returns error strings for backward compatibility,
|
|
50
|
+
* draft mode returns structured issue objects with severity levels.
|
|
49
51
|
*
|
|
50
52
|
* @param {object} definition - Parsed workflow JSON
|
|
51
|
-
* @
|
|
53
|
+
* @param {object} [options]
|
|
54
|
+
* @param {'strict'|'draft'} [options.mode='strict'] - Validation mode
|
|
55
|
+
* @returns {string[]|object} In strict: string[] errors. In draft: { valid, mode, issues, stats }
|
|
52
56
|
*/
|
|
53
|
-
function validateWorkflow(definition) {
|
|
54
|
-
const
|
|
57
|
+
function validateWorkflow(definition, { mode = 'strict' } = {}) {
|
|
58
|
+
const issues = [];
|
|
59
|
+
|
|
60
|
+
// Helper to add issue
|
|
61
|
+
function addIssue(severity, stepId, code, message, field = null, referencedStep = null) {
|
|
62
|
+
issues.push({
|
|
63
|
+
severity,
|
|
64
|
+
stepId,
|
|
65
|
+
code,
|
|
66
|
+
message,
|
|
67
|
+
...(field && { field }),
|
|
68
|
+
...(referencedStep && { referencedStep })
|
|
69
|
+
});
|
|
70
|
+
}
|
|
55
71
|
|
|
56
72
|
// Top-level required fields
|
|
57
73
|
if (!definition || typeof definition !== 'object') {
|
|
58
|
-
|
|
74
|
+
addIssue('error', null, 'INVALID_DEFINITION', 'Workflow definition must be a JSON object');
|
|
75
|
+
return mode === 'strict' ? ['Workflow definition must be a JSON object'] : { valid: false, mode, issues, stats: { totalSteps: 0, errors: 1, warnings: 0, info: 0 } };
|
|
59
76
|
}
|
|
77
|
+
|
|
60
78
|
if (!definition.name || typeof definition.name !== 'string') {
|
|
61
|
-
|
|
79
|
+
const severity = mode === 'draft' ? 'info' : 'error';
|
|
80
|
+
addIssue(severity, null, 'MISSING_WORKFLOW_NAME', 'Workflow must have a "name" string');
|
|
62
81
|
}
|
|
82
|
+
|
|
63
83
|
if (!Array.isArray(definition.steps) || definition.steps.length === 0) {
|
|
64
|
-
|
|
84
|
+
addIssue('error', null, 'MISSING_STEPS', 'Workflow must have a non-empty "steps" array');
|
|
65
85
|
}
|
|
66
86
|
|
|
67
|
-
|
|
87
|
+
const structuralErrors = issues.filter(i => i.severity === 'error');
|
|
88
|
+
if (structuralErrors.length > 0 && !Array.isArray(definition.steps)) {
|
|
89
|
+
// Can't validate steps without them
|
|
90
|
+
return formatResponse(mode, issues, 0);
|
|
91
|
+
}
|
|
68
92
|
|
|
69
93
|
// Validate inputs schema
|
|
70
94
|
if (definition.inputs) {
|
|
71
95
|
for (const [key, schema] of Object.entries(definition.inputs)) {
|
|
72
96
|
if (schema.type && !['string', 'number', 'boolean', 'array'].includes(schema.type)) {
|
|
73
|
-
|
|
97
|
+
addIssue('error', null, 'INVALID_INPUT_TYPE', `Input "${key}" has invalid type "${schema.type}" (must be string, number, boolean, or array)`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Validate formatters section (optional)
|
|
103
|
+
if (definition.formatters) {
|
|
104
|
+
if (typeof definition.formatters !== 'object' || Array.isArray(definition.formatters)) {
|
|
105
|
+
addIssue('error', null, 'INVALID_FORMATTERS', '"formatters" must be a plain object');
|
|
106
|
+
} else {
|
|
107
|
+
const f = definition.formatters;
|
|
108
|
+
const validFormats = ['json', 'table', 'markdown', 'text', 'csv'];
|
|
109
|
+
if (f.default && !validFormats.includes(f.default)) {
|
|
110
|
+
addIssue('warning', null, 'INVALID_FORMATTER_DEFAULT', `formatters.default "${f.default}" is not a recognized format (${validFormats.join(', ')})`);
|
|
111
|
+
}
|
|
112
|
+
if (f.columns && !Array.isArray(f.columns)) {
|
|
113
|
+
addIssue('error', null, 'INVALID_FORMATTER_COLUMNS', 'formatters.columns must be an array of strings');
|
|
114
|
+
}
|
|
115
|
+
if (f.title && typeof f.title !== 'string') {
|
|
116
|
+
addIssue('error', null, 'INVALID_FORMATTER_TITLE', 'formatters.title must be a string');
|
|
74
117
|
}
|
|
75
118
|
}
|
|
76
119
|
}
|
|
@@ -84,7 +127,7 @@ function validateWorkflow(definition) {
|
|
|
84
127
|
const prefix = `Step ${i}`;
|
|
85
128
|
|
|
86
129
|
if (!step.id || typeof step.id !== 'string') {
|
|
87
|
-
|
|
130
|
+
addIssue('error', step.id || `step${i}`, 'MISSING_STEP_ID', `${prefix}: must have a string "id"`);
|
|
88
131
|
continue;
|
|
89
132
|
}
|
|
90
133
|
|
|
@@ -98,14 +141,20 @@ function validateWorkflow(definition) {
|
|
|
98
141
|
|
|
99
142
|
// Tool validation
|
|
100
143
|
if (!step.tool || typeof step.tool !== 'string') {
|
|
101
|
-
|
|
144
|
+
addIssue('error', step.id, 'MISSING_TOOL', `${stepPrefix}: must have a string "tool"`);
|
|
102
145
|
} else if (!ALL_TOOLS.has(step.tool)) {
|
|
103
|
-
|
|
146
|
+
addIssue('error', step.id, 'INVALID_TOOL', `${stepPrefix}: unknown tool "${step.tool}" (available: ${[...ALL_TOOLS].join(', ')})`);
|
|
104
147
|
}
|
|
105
148
|
|
|
106
149
|
// Inputs validation
|
|
107
150
|
if (step.tool !== 'generate' && (!step.inputs || typeof step.inputs !== 'object')) {
|
|
108
|
-
|
|
151
|
+
const severity = mode === 'draft' ? 'info' : 'error';
|
|
152
|
+
addIssue(severity, step.id, 'MISSING_INPUTS', `${stepPrefix}: must have an "inputs" object`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Step name validation (only in draft mode for better UX)
|
|
156
|
+
if (!step.name && mode === 'draft') {
|
|
157
|
+
addIssue('info', step.id, 'MISSING_STEP_NAME', `${stepPrefix}: missing "name" field`);
|
|
109
158
|
}
|
|
110
159
|
|
|
111
160
|
// Check template references point to known step IDs or reserved prefixes
|
|
@@ -127,25 +176,32 @@ function validateWorkflow(definition) {
|
|
|
127
176
|
const deps = extractDependencies(inputsToCheck);
|
|
128
177
|
for (const dep of deps) {
|
|
129
178
|
if (!forEachVars.has(dep) && !loopVars.has(dep) && !stepIds.has(dep) && !definition.steps.some(s => s.id === dep)) {
|
|
130
|
-
|
|
179
|
+
const severity = mode === 'draft' ? 'warning' : 'error';
|
|
180
|
+
addIssue(severity, step.id, 'UNKNOWN_STEP_REF', `${stepPrefix}: references unknown step "${dep}"`, null, dep);
|
|
131
181
|
}
|
|
132
182
|
}
|
|
133
183
|
}
|
|
134
184
|
|
|
135
185
|
// Condition validation (if present, should be a string)
|
|
136
186
|
if (step.condition !== undefined && typeof step.condition !== 'string') {
|
|
137
|
-
|
|
187
|
+
addIssue('error', step.id, 'INVALID_CONDITION', `${stepPrefix}: "condition" must be a string`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Empty condition validation
|
|
191
|
+
if (step.condition !== undefined && typeof step.condition === 'string' && step.condition.trim() === '') {
|
|
192
|
+
const severity = mode === 'draft' ? 'info' : 'error';
|
|
193
|
+
addIssue(severity, step.id, 'EMPTY_CONDITION', `${stepPrefix}: "condition" is empty`);
|
|
138
194
|
}
|
|
139
195
|
|
|
140
196
|
// forEach validation (if present, should be a template string)
|
|
141
197
|
if (step.forEach !== undefined && typeof step.forEach !== 'string') {
|
|
142
|
-
|
|
198
|
+
addIssue('error', step.id, 'INVALID_FOREACH', `${stepPrefix}: "forEach" must be a string`);
|
|
143
199
|
}
|
|
144
200
|
}
|
|
145
201
|
|
|
146
202
|
// Report duplicates
|
|
147
203
|
for (const id of duplicateIds) {
|
|
148
|
-
|
|
204
|
+
addIssue('error', id, 'DUPLICATE_ID', `Duplicate step id: "${id}"`);
|
|
149
205
|
}
|
|
150
206
|
|
|
151
207
|
// Validate conditional branch references
|
|
@@ -157,40 +213,183 @@ function validateWorkflow(definition) {
|
|
|
157
213
|
if (refs && Array.isArray(refs)) {
|
|
158
214
|
for (const ref of refs) {
|
|
159
215
|
if (!stepIds.has(ref)) {
|
|
160
|
-
|
|
216
|
+
const severity = mode === 'draft' ? 'warning' : 'error';
|
|
217
|
+
addIssue(severity, step.id, 'UNKNOWN_STEP_REF', `Step "${step.id}": conditional ${branch} references unknown step "${ref}"`, null, ref);
|
|
161
218
|
}
|
|
162
219
|
}
|
|
163
220
|
}
|
|
164
221
|
}
|
|
165
222
|
if (!step.inputs.condition) {
|
|
166
|
-
|
|
223
|
+
const severity = mode === 'draft' ? 'info' : 'error';
|
|
224
|
+
addIssue(severity, step.id, 'MISSING_REQUIRED_INPUT', `Step "${step.id}": conditional must have a "condition" input`, 'inputs.condition');
|
|
167
225
|
}
|
|
168
226
|
if (!step.inputs.then || !Array.isArray(step.inputs.then)) {
|
|
169
|
-
|
|
227
|
+
const severity = mode === 'draft' ? 'info' : 'error';
|
|
228
|
+
addIssue(severity, step.id, 'MISSING_REQUIRED_INPUT', `Step "${step.id}": conditional must have a "then" array`, 'inputs.then');
|
|
170
229
|
}
|
|
171
230
|
}
|
|
172
231
|
|
|
173
232
|
// Validate loop inline step
|
|
174
233
|
if (step.tool === 'loop' && step.inputs) {
|
|
175
234
|
if (!step.inputs.items) {
|
|
176
|
-
|
|
235
|
+
const severity = mode === 'draft' ? 'info' : 'error';
|
|
236
|
+
addIssue(severity, step.id, 'MISSING_REQUIRED_INPUT', `Step "${step.id}": loop must have an "items" input`, 'inputs.items');
|
|
177
237
|
}
|
|
178
238
|
if (!step.inputs.as || typeof step.inputs.as !== 'string') {
|
|
179
|
-
|
|
239
|
+
const severity = mode === 'draft' ? 'info' : 'error';
|
|
240
|
+
addIssue(severity, step.id, 'MISSING_REQUIRED_INPUT', `Step "${step.id}": loop must have a string "as" input`, 'inputs.as');
|
|
180
241
|
}
|
|
181
242
|
if (!step.inputs.step || typeof step.inputs.step !== 'object') {
|
|
182
|
-
|
|
243
|
+
const severity = mode === 'draft' ? 'info' : 'error';
|
|
244
|
+
addIssue(severity, step.id, 'MISSING_REQUIRED_INPUT', `Step "${step.id}": loop must have a "step" object`, 'inputs.step');
|
|
183
245
|
} else if (step.inputs.step.tool && !ALL_TOOLS.has(step.inputs.step.tool)) {
|
|
184
|
-
|
|
246
|
+
addIssue('error', step.id, 'INVALID_TOOL', `Step "${step.id}": loop sub-step has unknown tool "${step.inputs.step.tool}"`);
|
|
185
247
|
}
|
|
186
248
|
}
|
|
187
249
|
}
|
|
188
250
|
|
|
189
251
|
// Check for circular dependencies
|
|
190
|
-
const
|
|
191
|
-
|
|
252
|
+
const cycleResult = detectCyclesAsIssues(definition.steps);
|
|
253
|
+
for (const issue of cycleResult) {
|
|
254
|
+
addIssue('error', issue.stepId, 'CIRCULAR_DEPENDENCY', issue.message);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Draft-only checks: orphan nodes
|
|
258
|
+
if (mode === 'draft') {
|
|
259
|
+
for (const step of definition.steps || []) {
|
|
260
|
+
const isReferenced = (definition.steps || []).some(
|
|
261
|
+
other => other.id !== step.id &&
|
|
262
|
+
extractStepReferences(other).some(ref => ref.stepId === step.id)
|
|
263
|
+
);
|
|
264
|
+
const hasReferences = extractStepReferences(step).length > 0;
|
|
265
|
+
if (!isReferenced && !hasReferences && (definition.steps || []).length > 1) {
|
|
266
|
+
addIssue('info', step.id, 'ORPHAN_NODE', `Step "${step.id}" is not connected to any other step`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
192
270
|
|
|
193
|
-
return
|
|
271
|
+
return formatResponse(mode, issues, (definition.steps || []).length);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Extract step references from a step for orphan detection.
|
|
276
|
+
* @param {object} step
|
|
277
|
+
* @returns {Array<{stepId: string, field?: string}>}
|
|
278
|
+
*/
|
|
279
|
+
function extractStepReferences(step) {
|
|
280
|
+
const refs = [];
|
|
281
|
+
if (!step.inputs) return refs;
|
|
282
|
+
|
|
283
|
+
const deps = extractDependencies(step.inputs);
|
|
284
|
+
for (const dep of deps) {
|
|
285
|
+
if (dep !== 'inputs' && dep !== 'defaults' && dep !== 'item' && dep !== 'index') {
|
|
286
|
+
refs.push({ stepId: dep });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return refs;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Format the response based on validation mode.
|
|
295
|
+
* @param {string} mode
|
|
296
|
+
* @param {Array} issues
|
|
297
|
+
* @param {number} totalSteps
|
|
298
|
+
* @returns {string[]|object}
|
|
299
|
+
*/
|
|
300
|
+
function formatResponse(mode, issues, totalSteps) {
|
|
301
|
+
const errors = issues.filter(i => i.severity === 'error');
|
|
302
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
303
|
+
const info = issues.filter(i => i.severity === 'info');
|
|
304
|
+
|
|
305
|
+
if (mode === 'draft') {
|
|
306
|
+
return {
|
|
307
|
+
valid: errors.length === 0,
|
|
308
|
+
mode: 'draft',
|
|
309
|
+
issues,
|
|
310
|
+
stats: {
|
|
311
|
+
totalSteps,
|
|
312
|
+
errors: errors.length,
|
|
313
|
+
warnings: warnings.length,
|
|
314
|
+
info: info.length
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Strict mode: backward-compatible format
|
|
320
|
+
if (errors.length > 0) {
|
|
321
|
+
return errors.map(e => e.message);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// In strict mode, warnings and info are also treated as errors
|
|
325
|
+
const allIssues = issues.filter(i => i.severity !== 'info' || mode === 'strict');
|
|
326
|
+
if (allIssues.length > 0) {
|
|
327
|
+
return allIssues.map(e => e.message);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Detect circular dependencies and return as issue objects.
|
|
335
|
+
* @param {Array} steps
|
|
336
|
+
* @returns {Array<{stepId: string, message: string}>}
|
|
337
|
+
*/
|
|
338
|
+
function detectCyclesAsIssues(steps) {
|
|
339
|
+
const issues = [];
|
|
340
|
+
const stepMap = new Map(steps.map(s => [s.id, s]));
|
|
341
|
+
const adjList = new Map();
|
|
342
|
+
|
|
343
|
+
// Build adjacency list from template dependencies
|
|
344
|
+
for (const step of steps) {
|
|
345
|
+
const deps = extractDependencies(step.inputs || {});
|
|
346
|
+
if (step.condition) {
|
|
347
|
+
const condDeps = extractDependencies(step.condition);
|
|
348
|
+
for (const d of condDeps) deps.add(d);
|
|
349
|
+
}
|
|
350
|
+
if (step.forEach) {
|
|
351
|
+
const forDeps = extractDependencies(step.forEach);
|
|
352
|
+
for (const d of forDeps) deps.add(d);
|
|
353
|
+
}
|
|
354
|
+
adjList.set(step.id, deps);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// DFS cycle detection
|
|
358
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
359
|
+
const color = new Map(steps.map(s => [s.id, WHITE]));
|
|
360
|
+
|
|
361
|
+
function dfs(nodeId, path) {
|
|
362
|
+
color.set(nodeId, GRAY);
|
|
363
|
+
path.push(nodeId);
|
|
364
|
+
|
|
365
|
+
const neighbors = adjList.get(nodeId) || new Set();
|
|
366
|
+
for (const dep of neighbors) {
|
|
367
|
+
if (!stepMap.has(dep)) continue; // Unknown deps caught by other checks
|
|
368
|
+
if (color.get(dep) === GRAY) {
|
|
369
|
+
const cycleStart = path.indexOf(dep);
|
|
370
|
+
const cycle = path.slice(cycleStart).concat(dep);
|
|
371
|
+
issues.push({
|
|
372
|
+
stepId: cycle[0],
|
|
373
|
+
message: `Circular dependency: ${cycle.join(' -> ')}`
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (color.get(dep) === WHITE) {
|
|
378
|
+
dfs(dep, path);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
path.pop();
|
|
383
|
+
color.set(nodeId, BLACK);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (const step of steps) {
|
|
387
|
+
if (color.get(step.id) === WHITE) {
|
|
388
|
+
dfs(step.id, []);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return issues;
|
|
194
393
|
}
|
|
195
394
|
|
|
196
395
|
/**
|
|
@@ -1397,6 +1596,55 @@ async function executeGenerate(inputs) {
|
|
|
1397
1596
|
};
|
|
1398
1597
|
}
|
|
1399
1598
|
|
|
1599
|
+
// ════════════════════════════════════════════════════════════════════
|
|
1600
|
+
// Code Search Tool Executors
|
|
1601
|
+
// ════════════════════════════════════════════════════════════════════
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* Execute a code_index step: index a local directory or GitHub repo.
|
|
1605
|
+
*/
|
|
1606
|
+
async function executeCodeIndex(inputs) {
|
|
1607
|
+
const { handleCodeIndex } = require('../mcp/tools/code-search');
|
|
1608
|
+
const result = await handleCodeIndex(inputs);
|
|
1609
|
+
return result.structuredContent;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* Execute a code_search step: semantic search across an indexed codebase.
|
|
1614
|
+
*/
|
|
1615
|
+
async function executeCodeSearch(inputs) {
|
|
1616
|
+
const { handleCodeSearch } = require('../mcp/tools/code-search');
|
|
1617
|
+
const result = await handleCodeSearch(inputs);
|
|
1618
|
+
return result.structuredContent;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* Execute a code_query step: RAG query against indexed code.
|
|
1623
|
+
*/
|
|
1624
|
+
async function executeCodeQuery(inputs) {
|
|
1625
|
+
const { handleCodeQuery } = require('../mcp/tools/code-search');
|
|
1626
|
+
const result = await handleCodeQuery(inputs);
|
|
1627
|
+
return result.structuredContent;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* Execute a code_find_similar step: find code similar to a snippet.
|
|
1632
|
+
*/
|
|
1633
|
+
async function executeCodeFindSimilar(inputs) {
|
|
1634
|
+
const { handleCodeFindSimilar } = require('../mcp/tools/code-search');
|
|
1635
|
+
const result = await handleCodeFindSimilar(inputs);
|
|
1636
|
+
return result.structuredContent;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
/**
|
|
1640
|
+
* Execute a code_status step: check index health.
|
|
1641
|
+
*/
|
|
1642
|
+
async function executeCodeStatus(inputs) {
|
|
1643
|
+
const { handleCodeStatus } = require('../mcp/tools/code-search');
|
|
1644
|
+
const result = await handleCodeStatus(inputs);
|
|
1645
|
+
return result.structuredContent;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1400
1648
|
// ════════════════════════════════════════════════════════════════════
|
|
1401
1649
|
// Step Dispatcher
|
|
1402
1650
|
// ════════════════════════════════════════════════════════════════════
|
|
@@ -1456,6 +1704,18 @@ async function executeStep(step, resolvedInputs, defaults, context) {
|
|
|
1456
1704
|
case 'estimate':
|
|
1457
1705
|
return executeEstimate(resolvedInputs);
|
|
1458
1706
|
|
|
1707
|
+
// Code search tools
|
|
1708
|
+
case 'code_index':
|
|
1709
|
+
return executeCodeIndex(resolvedInputs);
|
|
1710
|
+
case 'code_search':
|
|
1711
|
+
return executeCodeSearch(resolvedInputs);
|
|
1712
|
+
case 'code_query':
|
|
1713
|
+
return executeCodeQuery(resolvedInputs);
|
|
1714
|
+
case 'code_find_similar':
|
|
1715
|
+
return executeCodeFindSimilar(resolvedInputs);
|
|
1716
|
+
case 'code_status':
|
|
1717
|
+
return executeCodeStatus(resolvedInputs);
|
|
1718
|
+
|
|
1459
1719
|
default:
|
|
1460
1720
|
throw new Error(`Unknown tool: "${step.tool}"`);
|
|
1461
1721
|
}
|
|
@@ -1688,6 +1948,7 @@ async function executeWorkflow(definition, opts = {}) {
|
|
|
1688
1948
|
steps: stepResults,
|
|
1689
1949
|
totalTimeMs: Date.now() - startTime,
|
|
1690
1950
|
layers,
|
|
1951
|
+
formatters: definition.formatters || null,
|
|
1691
1952
|
};
|
|
1692
1953
|
}
|
|
1693
1954
|
|
|
@@ -1715,9 +1976,10 @@ function coerceInput(value, type) {
|
|
|
1715
1976
|
* prompt users for missing inputs before execution.
|
|
1716
1977
|
*
|
|
1717
1978
|
* @param {object} definition - Workflow definition with an `inputs` property
|
|
1979
|
+
* @param {object} [cachedInputs] - Previously cached input values (from last run)
|
|
1718
1980
|
* @returns {import('./wizard').Step[]}
|
|
1719
1981
|
*/
|
|
1720
|
-
function buildInputSteps(definition) {
|
|
1982
|
+
function buildInputSteps(definition, cachedInputs = {}) {
|
|
1721
1983
|
if (!definition.inputs) return [];
|
|
1722
1984
|
return Object.entries(definition.inputs).map(([key, spec]) => ({
|
|
1723
1985
|
id: key,
|
|
@@ -1726,6 +1988,10 @@ function buildInputSteps(definition) {
|
|
|
1726
1988
|
required: !!spec.required,
|
|
1727
1989
|
placeholder: spec.type === 'number' ? 'number' : (spec.type || 'string'),
|
|
1728
1990
|
defaultValue: spec.default !== undefined ? String(spec.default) : undefined,
|
|
1991
|
+
getDefault: () => {
|
|
1992
|
+
if (key in cachedInputs) return String(cachedInputs[key]);
|
|
1993
|
+
return spec.default !== undefined ? String(spec.default) : undefined;
|
|
1994
|
+
},
|
|
1729
1995
|
validate: (val) => {
|
|
1730
1996
|
if (spec.type === 'number' && val && isNaN(Number(val))) {
|
|
1731
1997
|
return 'Must be a number';
|
|
@@ -1884,6 +2150,9 @@ module.exports = {
|
|
|
1884
2150
|
executeIngest,
|
|
1885
2151
|
executeAggregate,
|
|
1886
2152
|
|
|
2153
|
+
// Dependency graph
|
|
2154
|
+
buildDependencyGraph,
|
|
2155
|
+
|
|
1887
2156
|
// Main execution
|
|
1888
2157
|
executeStep,
|
|
1889
2158
|
executeWorkflow,
|