aios-core 3.7.0 → 3.8.0

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 (47) hide show
  1. package/.aios-core/core/session/context-detector.js +3 -0
  2. package/.aios-core/core/session/context-loader.js +154 -0
  3. package/.aios-core/data/learned-patterns.yaml +3 -0
  4. package/.aios-core/data/workflow-patterns.yaml +347 -3
  5. package/.aios-core/development/agents/dev.md +6 -0
  6. package/.aios-core/development/agents/squad-creator.md +30 -0
  7. package/.aios-core/development/scripts/squad/squad-analyzer.js +638 -0
  8. package/.aios-core/development/scripts/squad/squad-extender.js +871 -0
  9. package/.aios-core/development/scripts/squad/squad-generator.js +107 -19
  10. package/.aios-core/development/scripts/squad/squad-migrator.js +3 -5
  11. package/.aios-core/development/scripts/squad/squad-validator.js +98 -0
  12. package/.aios-core/development/tasks/next.md +294 -0
  13. package/.aios-core/development/tasks/patterns.md +334 -0
  14. package/.aios-core/development/tasks/squad-creator-analyze.md +315 -0
  15. package/.aios-core/development/tasks/squad-creator-create.md +26 -3
  16. package/.aios-core/development/tasks/squad-creator-extend.md +411 -0
  17. package/.aios-core/development/tasks/squad-creator-validate.md +9 -1
  18. package/.aios-core/development/tasks/waves.md +205 -0
  19. package/.aios-core/development/templates/squad/agent-template.md +69 -0
  20. package/.aios-core/development/templates/squad/checklist-template.md +82 -0
  21. package/.aios-core/development/templates/squad/data-template.yaml +105 -0
  22. package/.aios-core/development/templates/squad/script-template.js +179 -0
  23. package/.aios-core/development/templates/squad/task-template.md +125 -0
  24. package/.aios-core/development/templates/squad/template-template.md +97 -0
  25. package/.aios-core/development/templates/squad/tool-template.js +103 -0
  26. package/.aios-core/development/templates/squad/workflow-template.yaml +108 -0
  27. package/.aios-core/install-manifest.yaml +89 -25
  28. package/.aios-core/quality/metrics-collector.js +27 -0
  29. package/.aios-core/scripts/session-context-loader.js +13 -254
  30. package/.aios-core/utils/aios-validator.js +25 -0
  31. package/.aios-core/workflow-intelligence/__tests__/confidence-scorer.test.js +334 -0
  32. package/.aios-core/workflow-intelligence/__tests__/integration.test.js +337 -0
  33. package/.aios-core/workflow-intelligence/__tests__/suggestion-engine.test.js +431 -0
  34. package/.aios-core/workflow-intelligence/__tests__/wave-analyzer.test.js +458 -0
  35. package/.aios-core/workflow-intelligence/__tests__/workflow-registry.test.js +302 -0
  36. package/.aios-core/workflow-intelligence/engine/confidence-scorer.js +305 -0
  37. package/.aios-core/workflow-intelligence/engine/output-formatter.js +285 -0
  38. package/.aios-core/workflow-intelligence/engine/suggestion-engine.js +603 -0
  39. package/.aios-core/workflow-intelligence/engine/wave-analyzer.js +676 -0
  40. package/.aios-core/workflow-intelligence/index.js +327 -0
  41. package/.aios-core/workflow-intelligence/learning/capture-hook.js +147 -0
  42. package/.aios-core/workflow-intelligence/learning/index.js +230 -0
  43. package/.aios-core/workflow-intelligence/learning/pattern-capture.js +340 -0
  44. package/.aios-core/workflow-intelligence/learning/pattern-store.js +498 -0
  45. package/.aios-core/workflow-intelligence/learning/pattern-validator.js +309 -0
  46. package/.aios-core/workflow-intelligence/registry/workflow-registry.js +358 -0
  47. package/package.json +1 -1
@@ -0,0 +1,603 @@
1
+ /**
2
+ * @module SuggestionEngine
3
+ * @description High-level suggestion engine for the *next task
4
+ * @story WIS-3 - *next Task Implementation
5
+ * @story WIS-5 - Pattern Capture Integration
6
+ * @version 1.1.0
7
+ *
8
+ * @example
9
+ * const { SuggestionEngine } = require('./suggestion-engine');
10
+ * const engine = new SuggestionEngine();
11
+ *
12
+ * const context = await engine.buildContext({ storyOverride: 'path/to/story.md' });
13
+ * const result = await engine.suggestNext(context);
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ // Lazy-loaded dependencies for performance
22
+ let wis = null;
23
+ let SessionContextLoader = null;
24
+ let learning = null;
25
+
26
+ /**
27
+ * Default cache TTL for suggestions (5 minutes)
28
+ * @type {number}
29
+ */
30
+ const SUGGESTION_CACHE_TTL = 5 * 60 * 1000;
31
+
32
+ /**
33
+ * Low confidence threshold for marking suggestions as "uncertain"
34
+ * @type {number}
35
+ */
36
+ const LOW_CONFIDENCE_THRESHOLD = 0.50;
37
+
38
+ /**
39
+ * SuggestionEngine class for generating context-aware command suggestions
40
+ */
41
+ class SuggestionEngine {
42
+ /**
43
+ * Create a SuggestionEngine instance
44
+ * @param {Object} options - Configuration options
45
+ * @param {number} options.cacheTTL - Cache time-to-live in milliseconds
46
+ * @param {boolean} options.lazyLoad - Whether to lazy-load dependencies
47
+ * @param {boolean} options.useLearnedPatterns - Whether to use learned patterns (default: true)
48
+ * @param {number} options.learnedPatternBoost - Confidence boost for learned patterns (default: 0.15)
49
+ */
50
+ constructor(options = {}) {
51
+ this.cacheTTL = options.cacheTTL || SUGGESTION_CACHE_TTL;
52
+ this.lazyLoad = options.lazyLoad !== false;
53
+ this.useLearnedPatterns = options.useLearnedPatterns !== false;
54
+ this.learnedPatternBoost = options.learnedPatternBoost || 0.15;
55
+ this.suggestionCache = null;
56
+ this.cacheTimestamp = null;
57
+ this.cacheKey = null;
58
+
59
+ // Load dependencies immediately if not lazy loading
60
+ if (!this.lazyLoad) {
61
+ this._loadDependencies();
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Lazy-load WIS and session dependencies
67
+ * @private
68
+ */
69
+ _loadDependencies() {
70
+ if (!wis) {
71
+ try {
72
+ wis = require('../index');
73
+ } catch (error) {
74
+ console.warn('[SuggestionEngine] Failed to load WIS module:', error.message);
75
+ wis = null;
76
+ }
77
+ }
78
+
79
+ if (!SessionContextLoader) {
80
+ try {
81
+ SessionContextLoader = require('../../core/session/context-loader');
82
+ } catch (error) {
83
+ console.warn('[SuggestionEngine] Failed to load SessionContextLoader:', error.message);
84
+ SessionContextLoader = null;
85
+ }
86
+ }
87
+
88
+ if (!learning && this.useLearnedPatterns) {
89
+ try {
90
+ learning = require('../learning');
91
+ } catch (error) {
92
+ console.warn('[SuggestionEngine] Failed to load learning module:', error.message);
93
+ learning = null;
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Build context from multiple sources
100
+ * @param {Object} options - Context building options
101
+ * @param {string} options.storyOverride - Explicit story path (optional)
102
+ * @param {boolean} options.autoDetect - Whether to auto-detect context (default: true)
103
+ * @param {string} options.agentId - Current agent ID (optional)
104
+ * @returns {Promise<Object>} Built context object
105
+ */
106
+ async buildContext(options = {}) {
107
+ this._loadDependencies();
108
+
109
+ const context = {
110
+ agentId: options.agentId || this._detectCurrentAgent(),
111
+ lastCommand: null,
112
+ lastCommands: [],
113
+ storyPath: null,
114
+ branch: null,
115
+ projectState: {}
116
+ };
117
+
118
+ // 1. Load session context if available
119
+ if (options.autoDetect !== false && SessionContextLoader) {
120
+ try {
121
+ const loader = new SessionContextLoader();
122
+ const sessionContext = loader.loadContext(context.agentId);
123
+
124
+ context.lastCommands = sessionContext.lastCommands || [];
125
+ context.lastCommand = context.lastCommands[context.lastCommands.length - 1] || null;
126
+ context.storyPath = sessionContext.currentStory || null;
127
+ context.workflowActive = sessionContext.workflowActive || null;
128
+ } catch (error) {
129
+ console.warn('[SuggestionEngine] Failed to load session context:', error.message);
130
+ }
131
+ }
132
+
133
+ // 2. Story override takes precedence
134
+ if (options.storyOverride) {
135
+ const resolvedPath = this._resolveStoryPath(options.storyOverride);
136
+ if (resolvedPath) {
137
+ context.storyPath = resolvedPath;
138
+ }
139
+ }
140
+
141
+ // 3. Detect git branch
142
+ context.branch = this._detectGitBranch();
143
+
144
+ // 4. Build project state
145
+ context.projectState = await this._buildProjectState(context);
146
+
147
+ return context;
148
+ }
149
+
150
+ /**
151
+ * Get suggestions for next commands based on context
152
+ * @param {Object} context - Current session context
153
+ * @returns {Promise<Object>} Suggestion result
154
+ */
155
+ async suggestNext(context) {
156
+ this._loadDependencies();
157
+
158
+ // Check cache first
159
+ const cacheKey = this._generateCacheKey(context);
160
+ if (this._isCacheValid(cacheKey)) {
161
+ return this.suggestionCache;
162
+ }
163
+
164
+ // Default result for when WIS is not available
165
+ const defaultResult = {
166
+ workflow: null,
167
+ currentState: null,
168
+ confidence: 0,
169
+ suggestions: [],
170
+ isUncertain: true,
171
+ message: 'Unable to determine workflow context'
172
+ };
173
+
174
+ if (!wis) {
175
+ return defaultResult;
176
+ }
177
+
178
+ try {
179
+ // Get suggestions from WIS
180
+ const suggestions = wis.getSuggestions(context);
181
+
182
+ if (!suggestions || suggestions.length === 0) {
183
+ return {
184
+ ...defaultResult,
185
+ message: 'No matching workflow found for current context'
186
+ };
187
+ }
188
+
189
+ // Get workflow match info
190
+ const commands = context.lastCommands || (context.lastCommand ? [context.lastCommand] : []);
191
+ const match = wis.matchWorkflow(commands);
192
+
193
+ // Calculate overall confidence
194
+ const avgConfidence = suggestions.length > 0
195
+ ? suggestions.reduce((sum, s) => sum + (s.confidence || 0), 0) / suggestions.length
196
+ : 0;
197
+
198
+ // Format base suggestions
199
+ let formattedSuggestions = suggestions.map((s, index) => ({
200
+ command: `*${s.command}`,
201
+ args: this._interpolateArgs(s.args_template, context),
202
+ description: s.description || '',
203
+ confidence: Math.round((s.confidence || 0) * 100) / 100,
204
+ priority: s.priority || index + 1,
205
+ source: 'workflow'
206
+ }));
207
+
208
+ // Apply learned pattern boost (WIS-5)
209
+ if (this.useLearnedPatterns && learning) {
210
+ formattedSuggestions = this._applyLearnedPatternBoost(
211
+ formattedSuggestions,
212
+ context
213
+ );
214
+ }
215
+
216
+ // Re-sort after boost
217
+ formattedSuggestions.sort((a, b) => b.confidence - a.confidence);
218
+
219
+ // Recalculate average confidence
220
+ const finalAvgConfidence = formattedSuggestions.length > 0
221
+ ? formattedSuggestions.reduce((sum, s) => sum + s.confidence, 0) / formattedSuggestions.length
222
+ : 0;
223
+
224
+ // Build result
225
+ const result = {
226
+ workflow: match?.name || suggestions[0]?.workflow || null,
227
+ currentState: suggestions[0]?.state || null,
228
+ confidence: Math.round(finalAvgConfidence * 100) / 100,
229
+ suggestions: formattedSuggestions,
230
+ isUncertain: finalAvgConfidence < LOW_CONFIDENCE_THRESHOLD,
231
+ message: null
232
+ };
233
+
234
+ // Cache the result
235
+ this._cacheResult(cacheKey, result);
236
+
237
+ return result;
238
+ } catch (error) {
239
+ console.error('[SuggestionEngine] Error getting suggestions:', error.message);
240
+ return {
241
+ ...defaultResult,
242
+ message: `Error: ${error.message}`
243
+ };
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Detect current active agent from environment or session
249
+ * @returns {string} Agent ID
250
+ * @private
251
+ */
252
+ _detectCurrentAgent() {
253
+ // Check environment variable first
254
+ if (process.env.AIOS_CURRENT_AGENT) {
255
+ return process.env.AIOS_CURRENT_AGENT.replace('@', '');
256
+ }
257
+
258
+ // Default to 'dev' if unknown
259
+ return 'dev';
260
+ }
261
+
262
+ /**
263
+ * Resolve and validate story path
264
+ * @param {string} storyPath - Story path to resolve
265
+ * @returns {string|null} Resolved path or null if invalid
266
+ * @private
267
+ */
268
+ _resolveStoryPath(storyPath) {
269
+ if (!storyPath) return null;
270
+
271
+ // Handle relative paths
272
+ const resolved = path.isAbsolute(storyPath)
273
+ ? storyPath
274
+ : path.resolve(process.cwd(), storyPath);
275
+
276
+ // Validate file exists
277
+ try {
278
+ if (fs.existsSync(resolved)) {
279
+ return resolved;
280
+ }
281
+ } catch (error) {
282
+ // File doesn't exist
283
+ }
284
+
285
+ console.warn(`[SuggestionEngine] Story path not found: ${storyPath}`);
286
+ return null;
287
+ }
288
+
289
+ /**
290
+ * Detect current git branch
291
+ * @returns {string|null} Branch name or null
292
+ * @private
293
+ */
294
+ _detectGitBranch() {
295
+ try {
296
+ const gitHeadPath = path.join(process.cwd(), '.git', 'HEAD');
297
+ if (fs.existsSync(gitHeadPath)) {
298
+ const content = fs.readFileSync(gitHeadPath, 'utf8').trim();
299
+ if (content.startsWith('ref: refs/heads/')) {
300
+ return content.replace('ref: refs/heads/', '');
301
+ }
302
+ }
303
+ } catch (error) {
304
+ // Git not available or not a git repo
305
+ }
306
+ return null;
307
+ }
308
+
309
+ /**
310
+ * Build project state object
311
+ * @param {Object} context - Current context
312
+ * @returns {Promise<Object>} Project state
313
+ * @private
314
+ */
315
+ async _buildProjectState(context) {
316
+ const state = {
317
+ activeStory: !!context.storyPath,
318
+ hasUncommittedChanges: false,
319
+ failingTests: false,
320
+ workflowPhase: null
321
+ };
322
+
323
+ // Check for uncommitted changes
324
+ try {
325
+ const gitStatusPath = path.join(process.cwd(), '.git', 'index');
326
+ if (fs.existsSync(gitStatusPath)) {
327
+ // Simple check - if there are modified files in git status
328
+ // For production, would use git status command
329
+ state.hasUncommittedChanges = true; // Assume true for now
330
+ }
331
+ } catch (error) {
332
+ // Ignore
333
+ }
334
+
335
+ // Infer workflow phase from last command
336
+ if (context.lastCommand) {
337
+ const cmd = context.lastCommand.toLowerCase();
338
+ if (cmd.includes('develop') || cmd.includes('implement')) {
339
+ state.workflowPhase = 'development';
340
+ } else if (cmd.includes('review') || cmd.includes('qa')) {
341
+ state.workflowPhase = 'review';
342
+ } else if (cmd.includes('push') || cmd.includes('pr') || cmd.includes('deploy')) {
343
+ state.workflowPhase = 'deployment';
344
+ } else if (cmd.includes('create') || cmd.includes('story') || cmd.includes('epic')) {
345
+ state.workflowPhase = 'planning';
346
+ }
347
+ }
348
+
349
+ return state;
350
+ }
351
+
352
+ /**
353
+ * Interpolate argument templates with context values
354
+ * @param {string} argsTemplate - Template string with ${var} placeholders
355
+ * @param {Object} context - Context for interpolation
356
+ * @returns {string} Interpolated arguments
357
+ * @private
358
+ */
359
+ _interpolateArgs(argsTemplate, context) {
360
+ if (!argsTemplate) return '';
361
+
362
+ return argsTemplate
363
+ .replace(/\$\{story_path\}/g, context.storyPath || '')
364
+ .replace(/\$\{epic_path\}/g, context.epicPath || '')
365
+ .replace(/\$\{doc_path\}/g, context.docPath || '')
366
+ .replace(/\$\{file_path\}/g, context.filePath || '')
367
+ .replace(/\$\{feature_name\}/g, context.featureName || '')
368
+ .replace(/\$\{topic\}/g, context.topic || '')
369
+ .replace(/\$\{branch\}/g, context.branch || '')
370
+ .trim();
371
+ }
372
+
373
+ /**
374
+ * Generate cache key from context
375
+ * @param {Object} context - Context object
376
+ * @returns {string} Cache key
377
+ * @private
378
+ */
379
+ _generateCacheKey(context) {
380
+ const keyParts = [
381
+ context.agentId || '',
382
+ context.lastCommand || '',
383
+ (context.lastCommands || []).slice(-3).join(','),
384
+ context.storyPath || '',
385
+ context.branch || ''
386
+ ];
387
+ return keyParts.join('|');
388
+ }
389
+
390
+ /**
391
+ * Check if cache is valid for given key
392
+ * @param {string} key - Cache key
393
+ * @returns {boolean} True if cache is valid
394
+ * @private
395
+ */
396
+ _isCacheValid(key) {
397
+ if (!this.suggestionCache || !this.cacheTimestamp || !this.cacheKey) {
398
+ return false;
399
+ }
400
+ if (this.cacheKey !== key) {
401
+ return false;
402
+ }
403
+ return (Date.now() - this.cacheTimestamp) < this.cacheTTL;
404
+ }
405
+
406
+ /**
407
+ * Cache suggestion result
408
+ * @param {string} key - Cache key
409
+ * @param {Object} result - Result to cache
410
+ * @private
411
+ */
412
+ _cacheResult(key, result) {
413
+ this.suggestionCache = result;
414
+ this.cacheTimestamp = Date.now();
415
+ this.cacheKey = key;
416
+ }
417
+
418
+ /**
419
+ * Apply learned pattern boost to suggestions
420
+ * @param {Object[]} suggestions - Base suggestions
421
+ * @param {Object} context - Session context
422
+ * @returns {Object[]} Boosted suggestions
423
+ * @private
424
+ */
425
+ _applyLearnedPatternBoost(suggestions, context) {
426
+ if (!learning) {
427
+ return suggestions;
428
+ }
429
+
430
+ try {
431
+ // Get commands to match against
432
+ const lastCommands = context.lastCommands || [];
433
+ if (lastCommands.length === 0 && context.lastCommand) {
434
+ lastCommands.push(context.lastCommand);
435
+ }
436
+
437
+ if (lastCommands.length === 0) {
438
+ return suggestions;
439
+ }
440
+
441
+ // Find matching learned patterns
442
+ const matchingPatterns = learning.findMatchingPatterns(lastCommands);
443
+
444
+ if (!matchingPatterns || matchingPatterns.length === 0) {
445
+ return suggestions;
446
+ }
447
+
448
+ // Build a map of command -> boost based on learned patterns
449
+ const boostMap = new Map();
450
+
451
+ for (const pattern of matchingPatterns) {
452
+ // Find the next command in the pattern after the current position
453
+ const patternSeq = pattern.sequence || [];
454
+ const matchIndex = this._findSequencePosition(lastCommands, patternSeq);
455
+
456
+ if (matchIndex >= 0 && matchIndex < patternSeq.length - 1) {
457
+ const nextCommand = patternSeq[matchIndex + 1];
458
+ const currentBoost = boostMap.get(nextCommand) || 0;
459
+
460
+ // Calculate boost based on pattern quality
461
+ const occurrenceBoost = Math.min(pattern.occurrences * 0.02, 0.1);
462
+ const successBoost = (pattern.successRate || 1) * 0.05;
463
+ const similarityBoost = (pattern.similarity || 0.5) * 0.05;
464
+
465
+ const totalBoost = this.learnedPatternBoost + occurrenceBoost + successBoost + similarityBoost;
466
+ boostMap.set(nextCommand, Math.max(currentBoost, totalBoost));
467
+ }
468
+ }
469
+
470
+ // Apply boosts to suggestions
471
+ return suggestions.map(suggestion => {
472
+ const cmdNormalized = suggestion.command.replace(/^\*/, '').toLowerCase();
473
+ const boost = boostMap.get(cmdNormalized) || 0;
474
+
475
+ if (boost > 0) {
476
+ return {
477
+ ...suggestion,
478
+ confidence: Math.min(1.0, suggestion.confidence + boost),
479
+ source: 'learned_pattern',
480
+ learnedBoost: Math.round(boost * 100) / 100
481
+ };
482
+ }
483
+
484
+ return suggestion;
485
+ });
486
+ } catch (error) {
487
+ console.warn('[SuggestionEngine] Failed to apply learned pattern boost:', error.message);
488
+ return suggestions;
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Find position of a subsequence in a pattern sequence
494
+ * @param {string[]} subseq - Subsequence to find
495
+ * @param {string[]} pattern - Full pattern sequence
496
+ * @returns {number} End index of match, or -1 if not found
497
+ * @private
498
+ */
499
+ _findSequencePosition(subseq, pattern) {
500
+ if (!subseq || !pattern || subseq.length === 0) {
501
+ return -1;
502
+ }
503
+
504
+ // Normalize for comparison
505
+ const normalizedSubseq = subseq.map(c => c.toLowerCase().replace(/^\*/, ''));
506
+ const normalizedPattern = pattern.map(c => c.toLowerCase().replace(/^\*/, ''));
507
+
508
+ // Find where the subsequence ends in the pattern
509
+ for (let i = 0; i <= normalizedPattern.length - normalizedSubseq.length; i++) {
510
+ let matches = true;
511
+ for (let j = 0; j < normalizedSubseq.length; j++) {
512
+ if (normalizedPattern[i + j] !== normalizedSubseq[j]) {
513
+ matches = false;
514
+ break;
515
+ }
516
+ }
517
+ if (matches) {
518
+ return i + normalizedSubseq.length - 1;
519
+ }
520
+ }
521
+
522
+ // Try partial match (last command matches)
523
+ const lastCmd = normalizedSubseq[normalizedSubseq.length - 1];
524
+ for (let i = 0; i < normalizedPattern.length; i++) {
525
+ if (normalizedPattern[i] === lastCmd) {
526
+ return i;
527
+ }
528
+ }
529
+
530
+ return -1;
531
+ }
532
+
533
+ /**
534
+ * Invalidate the cache
535
+ */
536
+ invalidateCache() {
537
+ this.suggestionCache = null;
538
+ this.cacheTimestamp = null;
539
+ this.cacheKey = null;
540
+ }
541
+
542
+ /**
543
+ * Get fallback suggestions when WIS is unavailable
544
+ * @param {Object} context - Context object
545
+ * @returns {Object} Fallback suggestions
546
+ */
547
+ getFallbackSuggestions(context) {
548
+ const agent = context.agentId || 'dev';
549
+
550
+ // Agent-specific fallback suggestions
551
+ const fallbacks = {
552
+ dev: [
553
+ { command: '*help', args: '', description: 'Show available commands', confidence: 0.30, priority: 1 },
554
+ { command: '*run-tests', args: '', description: 'Run test suite', confidence: 0.25, priority: 2 },
555
+ { command: '*develop', args: '', description: 'Start development mode', confidence: 0.20, priority: 3 }
556
+ ],
557
+ po: [
558
+ { command: '*help', args: '', description: 'Show available commands', confidence: 0.30, priority: 1 },
559
+ { command: '*backlog-review', args: '', description: 'Review backlog', confidence: 0.25, priority: 2 },
560
+ { command: '*create-story', args: '', description: 'Create new story', confidence: 0.20, priority: 3 }
561
+ ],
562
+ qa: [
563
+ { command: '*help', args: '', description: 'Show available commands', confidence: 0.30, priority: 1 },
564
+ { command: '*run-tests', args: '', description: 'Run test suite', confidence: 0.25, priority: 2 },
565
+ { command: '*review-qa', args: '', description: 'Start QA review', confidence: 0.20, priority: 3 }
566
+ ],
567
+ sm: [
568
+ { command: '*help', args: '', description: 'Show available commands', confidence: 0.30, priority: 1 },
569
+ { command: '*create-next-story', args: '', description: 'Create next story', confidence: 0.25, priority: 2 },
570
+ { command: '*validate-story-draft', args: '', description: 'Validate story', confidence: 0.20, priority: 3 }
571
+ ],
572
+ default: [
573
+ { command: '*help', args: '', description: 'Show available commands', confidence: 0.30, priority: 1 },
574
+ { command: '*status', args: '', description: 'Show project status', confidence: 0.20, priority: 2 }
575
+ ]
576
+ };
577
+
578
+ return {
579
+ workflow: null,
580
+ currentState: null,
581
+ confidence: 0.25,
582
+ suggestions: fallbacks[agent] || fallbacks.default,
583
+ isUncertain: true,
584
+ message: 'Using fallback suggestions - context unclear'
585
+ };
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Create a new SuggestionEngine instance
591
+ * @param {Object} options - Configuration options
592
+ * @returns {SuggestionEngine} New engine instance
593
+ */
594
+ function createSuggestionEngine(options = {}) {
595
+ return new SuggestionEngine(options);
596
+ }
597
+
598
+ module.exports = {
599
+ SuggestionEngine,
600
+ createSuggestionEngine,
601
+ SUGGESTION_CACHE_TTL,
602
+ LOW_CONFIDENCE_THRESHOLD
603
+ };