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.
Files changed (82) hide show
  1. package/README.md +4 -4
  2. package/package.json +1 -1
  3. package/src/cli.js +8 -0
  4. package/src/commands/about.js +3 -3
  5. package/src/commands/chat.js +32 -11
  6. package/src/commands/code-search.js +751 -0
  7. package/src/commands/doctor.js +1 -1
  8. package/src/commands/export.js +124 -0
  9. package/src/commands/import.js +195 -0
  10. package/src/commands/index-workspace.js +243 -0
  11. package/src/commands/mcp-server.js +113 -3
  12. package/src/commands/playground.js +120 -4
  13. package/src/commands/quickstart.js +4 -4
  14. package/src/commands/workflow.js +132 -65
  15. package/src/lib/catalog.js +4 -2
  16. package/src/lib/code-search.js +315 -0
  17. package/src/lib/codegen.js +1 -1
  18. package/src/lib/explanations.js +3 -3
  19. package/src/lib/export/contexts/benchmark-export.js +27 -0
  20. package/src/lib/export/contexts/chat-export.js +41 -0
  21. package/src/lib/export/contexts/explore-export.js +22 -0
  22. package/src/lib/export/contexts/search-export.js +54 -0
  23. package/src/lib/export/contexts/workflow-export.js +80 -0
  24. package/src/lib/export/formats/clipboard-export.js +29 -0
  25. package/src/lib/export/formats/csv-export.js +45 -0
  26. package/src/lib/export/formats/json-export.js +50 -0
  27. package/src/lib/export/formats/markdown-export.js +189 -0
  28. package/src/lib/export/formats/mermaid-export.js +274 -0
  29. package/src/lib/export/formats/pdf-export.js +117 -0
  30. package/src/lib/export/formats/png-export.js +96 -0
  31. package/src/lib/export/formats/svg-export.js +116 -0
  32. package/src/lib/export/index.js +175 -0
  33. package/src/lib/github.js +226 -0
  34. package/src/lib/template-engine.js +154 -20
  35. package/src/lib/workflow-builder.js +753 -0
  36. package/src/lib/workflow-formatters.js +454 -0
  37. package/src/lib/workflow-input-cache.js +111 -0
  38. package/src/lib/workflow-scaffold.js +1 -1
  39. package/src/lib/workflow.js +297 -28
  40. package/src/mcp/install.js +280 -7
  41. package/src/mcp/schemas/index.js +170 -0
  42. package/src/mcp/server.js +19 -4
  43. package/src/mcp/tools/authoring.js +662 -0
  44. package/src/mcp/tools/code-search.js +620 -0
  45. package/src/mcp/tools/ingest.js +2 -5
  46. package/src/mcp/tools/retrieval.js +2 -15
  47. package/src/mcp/tools/workspace.js +452 -0
  48. package/src/mcp/utils.js +20 -0
  49. package/src/playground/announcements.md +52 -5
  50. package/src/playground/help/workflow-nodes.js +127 -2
  51. package/src/playground/index.html +17109 -12438
  52. package/src/playground/vendor/mermaid.min.js +2811 -0
  53. package/src/workflows/code-review.json +110 -0
  54. package/src/workflows/cost-analysis.json +5 -0
  55. package/src/workflows/rag-chat.json +165 -0
  56. package/src/workflows/tests/code-review.fresh-index.test.json +83 -0
  57. package/src/workflows/tests/code-review.happy-path.test.json +121 -0
  58. package/src/workflows/tests/code-review.no-question.test.json +70 -0
  59. package/src/workflows/tests/consistency-check.happy-path.test.json +28 -0
  60. package/src/workflows/tests/consistency-check.missing-source.test.json +26 -0
  61. package/src/workflows/tests/cost-analysis.happy-path.test.json +28 -0
  62. package/src/workflows/tests/enrich-and-ingest.happy-path.test.json +38 -0
  63. package/src/workflows/tests/enrich-and-ingest.notify-fails.test.json +38 -0
  64. package/src/workflows/tests/intelligent-ingest.all-filtered.test.json +26 -0
  65. package/src/workflows/tests/intelligent-ingest.happy-path.test.json +28 -0
  66. package/src/workflows/tests/kb-health-report.custom-queries.test.json +24 -0
  67. package/src/workflows/tests/kb-health-report.happy-path.test.json +26 -0
  68. package/src/workflows/tests/multi-collection-search.happy-path.test.json +40 -0
  69. package/src/workflows/tests/multi-collection-search.one-empty.test.json +28 -0
  70. package/src/workflows/tests/rag-chat.happy-path.test.json +26 -0
  71. package/src/workflows/tests/rag-chat.no-relevant-results.test.json +25 -0
  72. package/src/workflows/tests/research-and-summarize.happy-path.test.json +33 -0
  73. package/src/workflows/tests/research-and-summarize.no-results.test.json +29 -0
  74. package/src/workflows/tests/search-with-fallback.empty-both.test.json +24 -0
  75. package/src/workflows/tests/search-with-fallback.fallback-branch.test.json +24 -0
  76. package/src/workflows/tests/search-with-fallback.happy-path.test.json +27 -0
  77. package/src/workflows/tests/smart-ingest.duplicate-detected.test.json +34 -0
  78. package/src/workflows/tests/smart-ingest.happy-path.test.json +31 -0
  79. package/src/playground/assets/announcements/appstore.jpg +0 -0
  80. package/src/playground/assets/announcements/circuits.jpg +0 -0
  81. package/src/playground/assets/announcements/csvingest.jpg +0 -0
  82. package/src/playground/assets/announcements/green-wave.jpg +0 -0
@@ -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
- * Returns an array of error strings (empty = valid).
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
- * @returns {string[]} errors
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 errors = [];
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
- return ['Workflow definition must be a JSON object'];
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
- errors.push('Workflow must have a "name" string');
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
- errors.push('Workflow must have a non-empty "steps" array');
84
+ addIssue('error', null, 'MISSING_STEPS', 'Workflow must have a non-empty "steps" array');
65
85
  }
66
86
 
67
- if (errors.length > 0) return errors; // Can't validate steps without them
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
- errors.push(`Input "${key}" has invalid type "${schema.type}" (must be string, number, boolean, or array)`);
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
- errors.push(`${prefix}: must have a string "id"`);
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
- errors.push(`${stepPrefix}: must have a string "tool"`);
144
+ addIssue('error', step.id, 'MISSING_TOOL', `${stepPrefix}: must have a string "tool"`);
102
145
  } else if (!ALL_TOOLS.has(step.tool)) {
103
- errors.push(`${stepPrefix}: unknown tool "${step.tool}" (available: ${[...ALL_TOOLS].join(', ')})`);
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
- errors.push(`${stepPrefix}: must have an "inputs" object`);
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
- errors.push(`${stepPrefix}: references unknown step "${dep}"`);
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
- errors.push(`${stepPrefix}: "condition" must be a string`);
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
- errors.push(`${stepPrefix}: "forEach" must be a string`);
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
- errors.push(`Duplicate step id: "${id}"`);
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
- errors.push(`Step "${step.id}": conditional ${branch} references unknown step "${ref}"`);
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
- errors.push(`Step "${step.id}": conditional must have a "condition" input`);
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
- errors.push(`Step "${step.id}": conditional must have a "then" array`);
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
- errors.push(`Step "${step.id}": loop must have an "items" input`);
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
- errors.push(`Step "${step.id}": loop must have a string "as" input`);
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
- errors.push(`Step "${step.id}": loop must have a "step" object`);
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
- errors.push(`Step "${step.id}": loop sub-step has unknown tool "${step.inputs.step.tool}"`);
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 cycleErrors = detectCycles(definition.steps);
191
- errors.push(...cycleErrors);
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 errors;
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,