@wundr.io/cli 1.0.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.
- package/README.md +551 -0
- package/bin/wundr.js +39 -0
- package/dist/ai/ai-service.d.ts +152 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +430 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/claude-client.d.ts +130 -0
- package/dist/ai/claude-client.d.ts.map +1 -0
- package/dist/ai/claude-client.js +339 -0
- package/dist/ai/claude-client.js.map +1 -0
- package/dist/ai/conversation-manager.d.ts +164 -0
- package/dist/ai/conversation-manager.d.ts.map +1 -0
- package/dist/ai/conversation-manager.js +612 -0
- package/dist/ai/conversation-manager.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +8 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +173 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ai.d.ts +89 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +735 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/analyze-optimized.d.ts +14 -0
- package/dist/commands/analyze-optimized.d.ts.map +1 -0
- package/dist/commands/analyze-optimized.js +437 -0
- package/dist/commands/analyze-optimized.js.map +1 -0
- package/dist/commands/analyze.d.ts +65 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/batch.d.ts +71 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +738 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/chat.d.ts +71 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +674 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/claude-init.d.ts +28 -0
- package/dist/commands/claude-init.d.ts.map +1 -0
- package/dist/commands/claude-init.js +587 -0
- package/dist/commands/claude-init.js.map +1 -0
- package/dist/commands/claude-setup.d.ts +32 -0
- package/dist/commands/claude-setup.d.ts.map +1 -0
- package/dist/commands/claude-setup.js +570 -0
- package/dist/commands/claude-setup.js.map +1 -0
- package/dist/commands/computer-setup-commands.d.ts +39 -0
- package/dist/commands/computer-setup-commands.d.ts.map +1 -0
- package/dist/commands/computer-setup-commands.js +563 -0
- package/dist/commands/computer-setup-commands.js.map +1 -0
- package/dist/commands/computer-setup.d.ts +7 -0
- package/dist/commands/computer-setup.d.ts.map +1 -0
- package/dist/commands/computer-setup.js +481 -0
- package/dist/commands/computer-setup.js.map +1 -0
- package/dist/commands/create-command.d.ts +7 -0
- package/dist/commands/create-command.d.ts.map +1 -0
- package/dist/commands/create-command.js +158 -0
- package/dist/commands/create-command.js.map +1 -0
- package/dist/commands/create.d.ts +74 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +556 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dashboard.d.ts +91 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +537 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/govern.d.ts +70 -0
- package/dist/commands/govern.d.ts.map +1 -0
- package/dist/commands/govern.js +480 -0
- package/dist/commands/govern.js.map +1 -0
- package/dist/commands/init.d.ts +55 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +584 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/performance-optimizer.d.ts +30 -0
- package/dist/commands/performance-optimizer.d.ts.map +1 -0
- package/dist/commands/performance-optimizer.js +649 -0
- package/dist/commands/performance-optimizer.js.map +1 -0
- package/dist/commands/plugins.d.ts +87 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +685 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/setup.d.ts +29 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +399 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/test-init.d.ts +9 -0
- package/dist/commands/test-init.d.ts.map +1 -0
- package/dist/commands/test-init.js +222 -0
- package/dist/commands/test-init.js.map +1 -0
- package/dist/commands/test.d.ts +25 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +217 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/watch.d.ts +76 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +610 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/context/context-manager.d.ts +155 -0
- package/dist/context/context-manager.d.ts.map +1 -0
- package/dist/context/context-manager.js +383 -0
- package/dist/context/context-manager.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +6 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/session-manager.d.ts +207 -0
- package/dist/context/session-manager.d.ts.map +1 -0
- package/dist/context/session-manager.js +682 -0
- package/dist/context/session-manager.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/interactive-mode.d.ts +76 -0
- package/dist/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/interactive/interactive-mode.js +730 -0
- package/dist/interactive/interactive-mode.js.map +1 -0
- package/dist/nlp/command-mapper.d.ts +174 -0
- package/dist/nlp/command-mapper.d.ts.map +1 -0
- package/dist/nlp/command-mapper.js +623 -0
- package/dist/nlp/command-mapper.js.map +1 -0
- package/dist/nlp/command-parser.d.ts +106 -0
- package/dist/nlp/command-parser.d.ts.map +1 -0
- package/dist/nlp/command-parser.js +416 -0
- package/dist/nlp/command-parser.js.map +1 -0
- package/dist/nlp/index.d.ts +5 -0
- package/dist/nlp/index.d.ts.map +1 -0
- package/dist/nlp/index.js +8 -0
- package/dist/nlp/index.js.map +1 -0
- package/dist/nlp/intent-classifier.d.ts +59 -0
- package/dist/nlp/intent-classifier.d.ts.map +1 -0
- package/dist/nlp/intent-classifier.js +384 -0
- package/dist/nlp/intent-classifier.js.map +1 -0
- package/dist/nlp/intent-parser.d.ts +152 -0
- package/dist/nlp/intent-parser.d.ts.map +1 -0
- package/dist/nlp/intent-parser.js +739 -0
- package/dist/nlp/intent-parser.js.map +1 -0
- package/dist/plugins/plugin-manager.d.ts +120 -0
- package/dist/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/plugins/plugin-manager.js +595 -0
- package/dist/plugins/plugin-manager.js.map +1 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config-manager.d.ts +73 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +339 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/error-handler.d.ts +46 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +169 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +94 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +119 -0
- package/src/ai/ai-service.ts +595 -0
- package/src/ai/claude-client.ts +490 -0
- package/src/ai/conversation-manager.ts +907 -0
- package/src/ai/index.ts +8 -0
- package/src/cli.ts +202 -0
- package/src/commands/ai.ts +995 -0
- package/src/commands/analyze-optimized.ts +641 -0
- package/src/commands/analyze.ts +576 -0
- package/src/commands/batch.ts +935 -0
- package/src/commands/chat.ts +876 -0
- package/src/commands/claude-init.ts +715 -0
- package/src/commands/claude-setup.ts +697 -0
- package/src/commands/computer-setup-commands.ts +709 -0
- package/src/commands/computer-setup.ts +565 -0
- package/src/commands/create-command.ts +175 -0
- package/src/commands/create.ts +727 -0
- package/src/commands/dashboard.ts +691 -0
- package/src/commands/govern.ts +635 -0
- package/src/commands/init.ts +677 -0
- package/src/commands/performance-optimizer.ts +864 -0
- package/src/commands/plugins.ts +848 -0
- package/src/commands/setup.ts +508 -0
- package/src/commands/test-init.ts +242 -0
- package/src/commands/test.ts +264 -0
- package/src/commands/watch.ts +755 -0
- package/src/context/context-manager.ts +546 -0
- package/src/context/index.ts +9 -0
- package/src/context/session-manager.ts +1019 -0
- package/src/index.ts +64 -0
- package/src/interactive/interactive-mode.ts +830 -0
- package/src/nlp/command-mapper.ts +885 -0
- package/src/nlp/command-parser.ts +564 -0
- package/src/nlp/index.ts +4 -0
- package/src/nlp/intent-classifier.ts +458 -0
- package/src/nlp/intent-parser.ts +1101 -0
- package/src/plugins/plugin-manager.ts +744 -0
- package/src/types/index.ts +252 -0
- package/src/types/modules.d.ts +56 -0
- package/src/utils/config-manager.ts +391 -0
- package/src/utils/error-handler.ts +192 -0
- package/src/utils/logger.ts +104 -0
- package/templates/batch/ci-cd.yaml +62 -0
- package/templates/component/{{fileName}}.test.tsx +17 -0
- package/templates/component/{{fileName}}.tsx +21 -0
- package/templates/service/{{fileName}}.ts +98 -0
- package/templates/wundr-test.config.js +0 -0
- package/test-suites/api/health.spec.ts +134 -0
- package/test-suites/helpers/test-config.ts +84 -0
- package/test-suites/ui/accessibility.spec.ts +102 -0
- package/test-suites/ui/smoke.spec.ts +92 -0
|
@@ -0,0 +1,1101 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { ClaudeClient, ClaudeMessage } from '../ai/claude-client';
|
|
3
|
+
import { logger } from '../utils/logger';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Intent classification result
|
|
7
|
+
*/
|
|
8
|
+
export interface IntentResult {
|
|
9
|
+
intent: string;
|
|
10
|
+
confidence: number;
|
|
11
|
+
command?: string;
|
|
12
|
+
parameters?: Record<string, any>;
|
|
13
|
+
clarification?: string;
|
|
14
|
+
alternatives?: Array<{
|
|
15
|
+
command: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
description: string;
|
|
18
|
+
}>;
|
|
19
|
+
reasoning?: string;
|
|
20
|
+
context?: {
|
|
21
|
+
entities: Entity[];
|
|
22
|
+
keywords: string[];
|
|
23
|
+
sentiment: 'positive' | 'neutral' | 'negative';
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Named entity recognition result
|
|
29
|
+
*/
|
|
30
|
+
export interface Entity {
|
|
31
|
+
text: string;
|
|
32
|
+
type:
|
|
33
|
+
| 'file_path'
|
|
34
|
+
| 'package_name'
|
|
35
|
+
| 'command'
|
|
36
|
+
| 'option'
|
|
37
|
+
| 'value'
|
|
38
|
+
| 'technology';
|
|
39
|
+
confidence: number;
|
|
40
|
+
startIndex: number;
|
|
41
|
+
endIndex: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Command pattern for intent matching
|
|
46
|
+
*/
|
|
47
|
+
export interface CommandPattern {
|
|
48
|
+
intent: string;
|
|
49
|
+
patterns: string[];
|
|
50
|
+
parameters: ParameterPattern[];
|
|
51
|
+
examples: string[];
|
|
52
|
+
category: string;
|
|
53
|
+
destructive?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parameter extraction pattern
|
|
58
|
+
*/
|
|
59
|
+
export interface ParameterPattern {
|
|
60
|
+
name: string;
|
|
61
|
+
type: 'string' | 'number' | 'boolean' | 'path' | 'enum';
|
|
62
|
+
required: boolean;
|
|
63
|
+
aliases?: string[];
|
|
64
|
+
validation?: RegExp;
|
|
65
|
+
enumValues?: string[];
|
|
66
|
+
defaultValue?: any;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Context for intent analysis
|
|
71
|
+
*/
|
|
72
|
+
export interface IntentContext {
|
|
73
|
+
projectType?: string;
|
|
74
|
+
recentCommands?: string[];
|
|
75
|
+
workingDirectory?: string;
|
|
76
|
+
gitStatus?: string;
|
|
77
|
+
conversationHistory?: ClaudeMessage[];
|
|
78
|
+
userPreferences?: Record<string, any>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Intent parser configuration
|
|
83
|
+
*/
|
|
84
|
+
export interface IntentParserConfig {
|
|
85
|
+
confidenceThreshold: number;
|
|
86
|
+
maxAlternatives: number;
|
|
87
|
+
enableNER: boolean;
|
|
88
|
+
enableContextAwareness: boolean;
|
|
89
|
+
cacheDuration: number; // milliseconds
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Advanced NLP Intent Parser for converting natural language to CLI commands
|
|
94
|
+
*/
|
|
95
|
+
export class IntentParser extends EventEmitter {
|
|
96
|
+
private claudeClient: ClaudeClient;
|
|
97
|
+
private config: IntentParserConfig;
|
|
98
|
+
private commandPatterns: Map<string, CommandPattern>;
|
|
99
|
+
private intentCache: Map<string, { result: IntentResult; timestamp: number }>;
|
|
100
|
+
private entityPatterns: Map<string, RegExp>;
|
|
101
|
+
|
|
102
|
+
constructor(
|
|
103
|
+
claudeClient: ClaudeClient,
|
|
104
|
+
config: Partial<IntentParserConfig> = {}
|
|
105
|
+
) {
|
|
106
|
+
super();
|
|
107
|
+
|
|
108
|
+
this.claudeClient = claudeClient;
|
|
109
|
+
this.config = {
|
|
110
|
+
confidenceThreshold: 0.7,
|
|
111
|
+
maxAlternatives: 3,
|
|
112
|
+
enableNER: true,
|
|
113
|
+
enableContextAwareness: true,
|
|
114
|
+
cacheDuration: 5 * 60 * 1000, // 5 minutes
|
|
115
|
+
...config,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
this.commandPatterns = new Map();
|
|
119
|
+
this.intentCache = new Map();
|
|
120
|
+
this.entityPatterns = new Map();
|
|
121
|
+
|
|
122
|
+
this.initializePatterns();
|
|
123
|
+
this.initializeEntityRecognition();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parse natural language input and extract intent
|
|
128
|
+
*/
|
|
129
|
+
async parseIntent(
|
|
130
|
+
input: string,
|
|
131
|
+
availableCommands: string[],
|
|
132
|
+
context: IntentContext = {}
|
|
133
|
+
): Promise<IntentResult> {
|
|
134
|
+
const normalizedInput = this.normalizeInput(input);
|
|
135
|
+
const cacheKey = this.getCacheKey(
|
|
136
|
+
normalizedInput,
|
|
137
|
+
availableCommands,
|
|
138
|
+
context
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Check cache first
|
|
142
|
+
const cached = this.intentCache.get(cacheKey);
|
|
143
|
+
if (cached && Date.now() - cached.timestamp < this.config.cacheDuration) {
|
|
144
|
+
logger.debug('Returning cached intent result');
|
|
145
|
+
return cached.result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// Step 1: Quick pattern matching for common commands
|
|
150
|
+
const patternResult = await this.patternMatching(
|
|
151
|
+
normalizedInput,
|
|
152
|
+
availableCommands
|
|
153
|
+
);
|
|
154
|
+
if (patternResult.confidence >= 0.9) {
|
|
155
|
+
this.cacheResult(cacheKey, patternResult);
|
|
156
|
+
return patternResult;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Step 2: Named Entity Recognition
|
|
160
|
+
const entities = this.config.enableNER
|
|
161
|
+
? await this.extractEntities(normalizedInput)
|
|
162
|
+
: [];
|
|
163
|
+
|
|
164
|
+
// Step 3: AI-powered intent analysis
|
|
165
|
+
const aiResult = await this.aiIntentAnalysis(
|
|
166
|
+
normalizedInput,
|
|
167
|
+
availableCommands,
|
|
168
|
+
context,
|
|
169
|
+
entities
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Step 4: Combine results and validate
|
|
173
|
+
const finalResult = this.combineResults(
|
|
174
|
+
patternResult,
|
|
175
|
+
aiResult,
|
|
176
|
+
entities
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Step 5: Post-processing and validation
|
|
180
|
+
const validatedResult = await this.validateAndEnrich(
|
|
181
|
+
finalResult,
|
|
182
|
+
availableCommands,
|
|
183
|
+
context
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
this.cacheResult(cacheKey, validatedResult);
|
|
187
|
+
this.emit('intent_parsed', {
|
|
188
|
+
input: normalizedInput,
|
|
189
|
+
result: validatedResult,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return validatedResult;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
logger.error('Intent parsing failed:', error);
|
|
195
|
+
return this.fallbackIntentParsing(
|
|
196
|
+
normalizedInput,
|
|
197
|
+
availableCommands,
|
|
198
|
+
context
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Extract command parameters from natural language
|
|
205
|
+
*/
|
|
206
|
+
async extractParameters(
|
|
207
|
+
input: string,
|
|
208
|
+
command: string,
|
|
209
|
+
commandPattern?: CommandPattern
|
|
210
|
+
): Promise<Record<string, any>> {
|
|
211
|
+
const parameters: Record<string, any> = {};
|
|
212
|
+
|
|
213
|
+
if (!commandPattern) {
|
|
214
|
+
commandPattern = this.commandPatterns.get(command);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!commandPattern) {
|
|
218
|
+
logger.debug(`No pattern found for command: ${command}`);
|
|
219
|
+
return parameters;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Extract using AI
|
|
223
|
+
const systemPrompt = `You are a parameter extraction system. Extract parameters from user input for the command "${command}".
|
|
224
|
+
|
|
225
|
+
Command parameters:
|
|
226
|
+
${commandPattern.parameters.map(p => `- ${p.name} (${p.type}${p.required ? ', required' : ', optional'}): ${p.aliases?.join(', ') || ''}`).join('\n')}
|
|
227
|
+
|
|
228
|
+
Extract parameters from the user input and respond with JSON only:
|
|
229
|
+
{
|
|
230
|
+
"parameters": {
|
|
231
|
+
"paramName": "value"
|
|
232
|
+
},
|
|
233
|
+
"confidence": 0.9
|
|
234
|
+
}`;
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const response = await this.claudeClient.sendMessage(
|
|
238
|
+
input,
|
|
239
|
+
systemPrompt,
|
|
240
|
+
{
|
|
241
|
+
temperature: 0.1,
|
|
242
|
+
maxTokens: 1024,
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const result = JSON.parse(response.trim());
|
|
247
|
+
|
|
248
|
+
// Validate extracted parameters
|
|
249
|
+
for (const param of commandPattern.parameters) {
|
|
250
|
+
if (param.required && !result.parameters[param.name]) {
|
|
251
|
+
logger.warn(`Missing required parameter: ${param.name}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (result.parameters[param.name] && param.validation) {
|
|
255
|
+
if (!param.validation.test(result.parameters[param.name])) {
|
|
256
|
+
logger.warn(
|
|
257
|
+
`Parameter validation failed for ${param.name}: ${result.parameters[param.name]}`
|
|
258
|
+
);
|
|
259
|
+
delete result.parameters[param.name];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Apply default values
|
|
264
|
+
if (
|
|
265
|
+
!result.parameters[param.name] &&
|
|
266
|
+
param.defaultValue !== undefined
|
|
267
|
+
) {
|
|
268
|
+
result.parameters[param.name] = param.defaultValue;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return result.parameters;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
logger.error('Parameter extraction failed:', error);
|
|
275
|
+
return this.fallbackParameterExtraction(input, commandPattern);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Suggest commands based on partial input
|
|
281
|
+
*/
|
|
282
|
+
async suggestCommands(
|
|
283
|
+
partialInput: string,
|
|
284
|
+
availableCommands: string[],
|
|
285
|
+
context: IntentContext = {},
|
|
286
|
+
limit: number = 5
|
|
287
|
+
): Promise<
|
|
288
|
+
Array<{
|
|
289
|
+
command: string;
|
|
290
|
+
description: string;
|
|
291
|
+
confidence: number;
|
|
292
|
+
completion: string;
|
|
293
|
+
}>
|
|
294
|
+
> {
|
|
295
|
+
const suggestions: Array<{
|
|
296
|
+
command: string;
|
|
297
|
+
description: string;
|
|
298
|
+
confidence: number;
|
|
299
|
+
completion: string;
|
|
300
|
+
}> = [];
|
|
301
|
+
|
|
302
|
+
// Pattern-based suggestions
|
|
303
|
+
const patternSuggestions = this.getPatternSuggestions(
|
|
304
|
+
partialInput,
|
|
305
|
+
availableCommands
|
|
306
|
+
);
|
|
307
|
+
suggestions.push(...patternSuggestions);
|
|
308
|
+
|
|
309
|
+
// AI-powered suggestions if we need more
|
|
310
|
+
if (suggestions.length < limit) {
|
|
311
|
+
try {
|
|
312
|
+
const aiSuggestions = await this.getAISuggestions(
|
|
313
|
+
partialInput,
|
|
314
|
+
availableCommands,
|
|
315
|
+
context,
|
|
316
|
+
limit - suggestions.length
|
|
317
|
+
);
|
|
318
|
+
suggestions.push(...aiSuggestions);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
logger.error('AI suggestions failed:', error);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Sort by confidence and limit results
|
|
325
|
+
suggestions.sort((a, b) => b.confidence - a.confidence);
|
|
326
|
+
return suggestions.slice(0, limit);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Register a custom command pattern
|
|
331
|
+
*/
|
|
332
|
+
registerCommandPattern(pattern: CommandPattern): void {
|
|
333
|
+
this.commandPatterns.set(pattern.intent, pattern);
|
|
334
|
+
logger.debug(`Registered command pattern: ${pattern.intent}`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Batch register multiple command patterns
|
|
339
|
+
*/
|
|
340
|
+
registerCommandPatterns(patterns: CommandPattern[]): void {
|
|
341
|
+
patterns.forEach(pattern => this.registerCommandPattern(pattern));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get registered command patterns
|
|
346
|
+
*/
|
|
347
|
+
getCommandPatterns(): CommandPattern[] {
|
|
348
|
+
return Array.from(this.commandPatterns.values());
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Clear intent cache
|
|
353
|
+
*/
|
|
354
|
+
clearCache(): void {
|
|
355
|
+
this.intentCache.clear();
|
|
356
|
+
this.emit('cache_cleared');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Get cache statistics
|
|
361
|
+
*/
|
|
362
|
+
getCacheStats(): {
|
|
363
|
+
size: number;
|
|
364
|
+
hitRate: number;
|
|
365
|
+
oldestEntry: Date | null;
|
|
366
|
+
} {
|
|
367
|
+
const now = Date.now();
|
|
368
|
+
const validEntries = Array.from(this.intentCache.values()).filter(
|
|
369
|
+
entry => now - entry.timestamp < this.config.cacheDuration
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const oldestEntry =
|
|
373
|
+
validEntries.length > 0
|
|
374
|
+
? new Date(Math.min(...validEntries.map(e => e.timestamp)))
|
|
375
|
+
: null;
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
size: validEntries.length,
|
|
379
|
+
hitRate:
|
|
380
|
+
this.intentCache.size > 0
|
|
381
|
+
? validEntries.length / this.intentCache.size
|
|
382
|
+
: 0,
|
|
383
|
+
oldestEntry,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Private methods
|
|
388
|
+
|
|
389
|
+
private initializePatterns(): void {
|
|
390
|
+
const defaultPatterns: CommandPattern[] = [
|
|
391
|
+
{
|
|
392
|
+
intent: 'analyze',
|
|
393
|
+
patterns: [
|
|
394
|
+
'analyze {path?}',
|
|
395
|
+
'check {path?}',
|
|
396
|
+
'scan {path?}',
|
|
397
|
+
'examine {path?}',
|
|
398
|
+
'review {path?}',
|
|
399
|
+
],
|
|
400
|
+
parameters: [
|
|
401
|
+
{ name: 'path', type: 'path', required: false, defaultValue: '.' },
|
|
402
|
+
{
|
|
403
|
+
name: 'focus',
|
|
404
|
+
type: 'enum',
|
|
405
|
+
required: false,
|
|
406
|
+
enumValues: ['dependencies', 'quality', 'security', 'performance'],
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: 'format',
|
|
410
|
+
type: 'enum',
|
|
411
|
+
required: false,
|
|
412
|
+
enumValues: ['json', 'table', 'csv'],
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
examples: [
|
|
416
|
+
'analyze the project',
|
|
417
|
+
'check dependencies in src folder',
|
|
418
|
+
'scan for security issues',
|
|
419
|
+
],
|
|
420
|
+
category: 'analysis',
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
intent: 'create',
|
|
424
|
+
patterns: [
|
|
425
|
+
'create {type} {name}',
|
|
426
|
+
'new {type} {name}',
|
|
427
|
+
'generate {type} {name}',
|
|
428
|
+
'make {type} {name}',
|
|
429
|
+
],
|
|
430
|
+
parameters: [
|
|
431
|
+
{
|
|
432
|
+
name: 'type',
|
|
433
|
+
type: 'enum',
|
|
434
|
+
required: true,
|
|
435
|
+
enumValues: ['component', 'service', 'test', 'config'],
|
|
436
|
+
},
|
|
437
|
+
{ name: 'name', type: 'string', required: true },
|
|
438
|
+
{ name: 'template', type: 'string', required: false },
|
|
439
|
+
],
|
|
440
|
+
examples: [
|
|
441
|
+
'create component UserProfile',
|
|
442
|
+
'new service AuthService',
|
|
443
|
+
'generate test for UserService',
|
|
444
|
+
],
|
|
445
|
+
category: 'generation',
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
intent: 'init',
|
|
449
|
+
patterns: [
|
|
450
|
+
'init {project?}',
|
|
451
|
+
'initialize {project?}',
|
|
452
|
+
'setup {project?}',
|
|
453
|
+
'configure {project?}',
|
|
454
|
+
],
|
|
455
|
+
parameters: [
|
|
456
|
+
{ name: 'project', type: 'string', required: false },
|
|
457
|
+
{ name: 'template', type: 'string', required: false },
|
|
458
|
+
{ name: 'force', type: 'boolean', required: false },
|
|
459
|
+
],
|
|
460
|
+
examples: [
|
|
461
|
+
'init new project',
|
|
462
|
+
'initialize with React template',
|
|
463
|
+
'setup the workspace',
|
|
464
|
+
],
|
|
465
|
+
category: 'setup',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
intent: 'help',
|
|
469
|
+
patterns: [
|
|
470
|
+
'help {command?}',
|
|
471
|
+
'how {action?}',
|
|
472
|
+
'what {question?}',
|
|
473
|
+
'explain {topic?}',
|
|
474
|
+
],
|
|
475
|
+
parameters: [
|
|
476
|
+
{ name: 'command', type: 'string', required: false },
|
|
477
|
+
{ name: 'topic', type: 'string', required: false },
|
|
478
|
+
],
|
|
479
|
+
examples: [
|
|
480
|
+
'help with analyze command',
|
|
481
|
+
'how to create components',
|
|
482
|
+
'what does this do',
|
|
483
|
+
],
|
|
484
|
+
category: 'help',
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
intent: 'dashboard',
|
|
488
|
+
patterns: [
|
|
489
|
+
'dashboard',
|
|
490
|
+
'ui',
|
|
491
|
+
'interface',
|
|
492
|
+
'show {view?}',
|
|
493
|
+
'open {view?}',
|
|
494
|
+
],
|
|
495
|
+
parameters: [
|
|
496
|
+
{
|
|
497
|
+
name: 'view',
|
|
498
|
+
type: 'enum',
|
|
499
|
+
required: false,
|
|
500
|
+
enumValues: ['overview', 'metrics', 'reports'],
|
|
501
|
+
},
|
|
502
|
+
{ name: 'port', type: 'number', required: false, defaultValue: 3000 },
|
|
503
|
+
],
|
|
504
|
+
examples: ['open dashboard', 'show metrics view', 'start ui'],
|
|
505
|
+
category: 'interface',
|
|
506
|
+
},
|
|
507
|
+
];
|
|
508
|
+
|
|
509
|
+
defaultPatterns.forEach(pattern => this.registerCommandPattern(pattern));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private initializeEntityRecognition(): void {
|
|
513
|
+
this.entityPatterns.set('file_path', /(?:\.\/|\/|~\/)[^\s]+/g);
|
|
514
|
+
this.entityPatterns.set(
|
|
515
|
+
'package_name',
|
|
516
|
+
/(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*/g
|
|
517
|
+
);
|
|
518
|
+
this.entityPatterns.set(
|
|
519
|
+
'command',
|
|
520
|
+
/(?:wundr\s+)?(?:analyze|create|init|help|dashboard|batch|watch)\b/g
|
|
521
|
+
);
|
|
522
|
+
this.entityPatterns.set('option', /--?[a-zA-Z][a-zA-Z0-9-]*/g);
|
|
523
|
+
this.entityPatterns.set(
|
|
524
|
+
'technology',
|
|
525
|
+
/\b(?:react|angular|vue|nodejs|typescript|javascript|python|java|docker|kubernetes)\b/gi
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private normalizeInput(input: string): string {
|
|
530
|
+
return input
|
|
531
|
+
.trim()
|
|
532
|
+
.toLowerCase()
|
|
533
|
+
.replace(/[^\w\s\-\.\/]/g, ' ')
|
|
534
|
+
.replace(/\s+/g, ' ');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private getCacheKey(
|
|
538
|
+
input: string,
|
|
539
|
+
commands: string[],
|
|
540
|
+
context: IntentContext
|
|
541
|
+
): string {
|
|
542
|
+
const contextKey = JSON.stringify({
|
|
543
|
+
projectType: context.projectType,
|
|
544
|
+
workingDirectory: context.workingDirectory,
|
|
545
|
+
recentCommands: context.recentCommands?.slice(0, 3), // Only recent commands for cache key
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
return `${input}:${commands.sort().join(',')}:${contextKey}`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private async patternMatching(
|
|
552
|
+
input: string,
|
|
553
|
+
availableCommands: string[]
|
|
554
|
+
): Promise<IntentResult> {
|
|
555
|
+
let bestMatch: IntentResult = {
|
|
556
|
+
intent: 'unknown',
|
|
557
|
+
confidence: 0,
|
|
558
|
+
context: { entities: [], keywords: [], sentiment: 'neutral' },
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
for (const [intent, pattern] of this.commandPatterns) {
|
|
562
|
+
if (!availableCommands.includes(intent)) continue;
|
|
563
|
+
|
|
564
|
+
const confidence = this.calculatePatternScore(input, pattern);
|
|
565
|
+
|
|
566
|
+
if (confidence > bestMatch.confidence) {
|
|
567
|
+
bestMatch = {
|
|
568
|
+
intent,
|
|
569
|
+
confidence,
|
|
570
|
+
command: `wundr ${intent}`,
|
|
571
|
+
parameters: {},
|
|
572
|
+
reasoning: `Pattern match for "${intent}" command`,
|
|
573
|
+
context: { entities: [], keywords: [], sentiment: 'neutral' },
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return bestMatch;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private calculatePatternScore(
|
|
582
|
+
input: string,
|
|
583
|
+
pattern: CommandPattern
|
|
584
|
+
): number {
|
|
585
|
+
let score = 0;
|
|
586
|
+
let totalWeight = 0;
|
|
587
|
+
|
|
588
|
+
// Check pattern matches
|
|
589
|
+
for (const patternStr of pattern.patterns) {
|
|
590
|
+
const patternWords = patternStr.toLowerCase().split(/\s+/);
|
|
591
|
+
const inputWords = input.split(/\s+/);
|
|
592
|
+
|
|
593
|
+
let matchCount = 0;
|
|
594
|
+
for (const word of patternWords) {
|
|
595
|
+
if (word.startsWith('{') && word.endsWith('}')) continue; // Skip parameters
|
|
596
|
+
if (inputWords.includes(word)) {
|
|
597
|
+
matchCount++;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const patternScore =
|
|
602
|
+
matchCount / patternWords.filter(w => !w.startsWith('{')).length;
|
|
603
|
+
score = Math.max(score, patternScore);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
totalWeight += 0.6;
|
|
607
|
+
|
|
608
|
+
// Check examples
|
|
609
|
+
let exampleScore = 0;
|
|
610
|
+
for (const example of pattern.examples) {
|
|
611
|
+
const similarity = this.calculateStringSimilarity(
|
|
612
|
+
input,
|
|
613
|
+
example.toLowerCase()
|
|
614
|
+
);
|
|
615
|
+
exampleScore = Math.max(exampleScore, similarity);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
score += exampleScore * 0.4;
|
|
619
|
+
totalWeight += 0.4;
|
|
620
|
+
|
|
621
|
+
return score / totalWeight;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private calculateStringSimilarity(str1: string, str2: string): number {
|
|
625
|
+
const words1 = str1.split(/\s+/);
|
|
626
|
+
const words2 = str2.split(/\s+/);
|
|
627
|
+
|
|
628
|
+
let matches = 0;
|
|
629
|
+
for (const word1 of words1) {
|
|
630
|
+
if (words2.includes(word1)) {
|
|
631
|
+
matches++;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return matches / Math.max(words1.length, words2.length);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
private async extractEntities(input: string): Promise<Entity[]> {
|
|
639
|
+
const entities: Entity[] = [];
|
|
640
|
+
|
|
641
|
+
for (const [type, pattern] of this.entityPatterns) {
|
|
642
|
+
const matches = Array.from(input.matchAll(pattern));
|
|
643
|
+
|
|
644
|
+
for (const match of matches) {
|
|
645
|
+
if (match.index !== undefined) {
|
|
646
|
+
entities.push({
|
|
647
|
+
text: match[0],
|
|
648
|
+
type: type as any,
|
|
649
|
+
confidence: 0.8, // Static confidence for regex patterns
|
|
650
|
+
startIndex: match.index,
|
|
651
|
+
endIndex: match.index + match[0].length,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return entities;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
private async aiIntentAnalysis(
|
|
661
|
+
input: string,
|
|
662
|
+
availableCommands: string[],
|
|
663
|
+
context: IntentContext,
|
|
664
|
+
entities: Entity[]
|
|
665
|
+
): Promise<IntentResult> {
|
|
666
|
+
const contextInfo = this.buildContextInfo(context);
|
|
667
|
+
const entityInfo =
|
|
668
|
+
entities.length > 0
|
|
669
|
+
? `\nDetected entities: ${entities.map(e => `${e.text} (${e.type})`).join(', ')}`
|
|
670
|
+
: '';
|
|
671
|
+
|
|
672
|
+
const systemPrompt = `You are an expert CLI intent analyzer. Analyze user input and map it to appropriate commands.
|
|
673
|
+
|
|
674
|
+
Available commands: ${availableCommands.join(', ')}
|
|
675
|
+
${contextInfo}${entityInfo}
|
|
676
|
+
|
|
677
|
+
User input: "${input}"
|
|
678
|
+
|
|
679
|
+
Analyze and respond with JSON only:
|
|
680
|
+
{
|
|
681
|
+
"intent": "command_name",
|
|
682
|
+
"confidence": 0.95,
|
|
683
|
+
"command": "wundr analyze --path ./src",
|
|
684
|
+
"parameters": {"path": "./src"},
|
|
685
|
+
"alternatives": [
|
|
686
|
+
{"command": "wundr scan", "confidence": 0.7, "description": "Alternative interpretation"}
|
|
687
|
+
],
|
|
688
|
+
"reasoning": "User wants to analyze source code based on path mention",
|
|
689
|
+
"clarification": null
|
|
690
|
+
}`;
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
const response = await this.claudeClient.sendMessage(
|
|
694
|
+
input,
|
|
695
|
+
systemPrompt,
|
|
696
|
+
{
|
|
697
|
+
temperature: 0.1,
|
|
698
|
+
maxTokens: 1536,
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
const cleanResponse = response.trim();
|
|
703
|
+
const jsonMatch = cleanResponse.match(/\{[\s\S]*\}/);
|
|
704
|
+
|
|
705
|
+
if (!jsonMatch) {
|
|
706
|
+
throw new Error('No valid JSON in AI response');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
710
|
+
|
|
711
|
+
// Add context information
|
|
712
|
+
result.context = {
|
|
713
|
+
entities,
|
|
714
|
+
keywords: this.extractKeywords(input),
|
|
715
|
+
sentiment: this.analyzeSentiment(input),
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
return result;
|
|
719
|
+
} catch (error) {
|
|
720
|
+
logger.error('AI intent analysis failed:', error);
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private buildContextInfo(context: IntentContext): string {
|
|
726
|
+
const parts: string[] = [];
|
|
727
|
+
|
|
728
|
+
if (context.projectType) {
|
|
729
|
+
parts.push(`Project type: ${context.projectType}`);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (context.recentCommands?.length) {
|
|
733
|
+
parts.push(
|
|
734
|
+
`Recent commands: ${context.recentCommands.slice(0, 3).join(', ')}`
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (context.workingDirectory) {
|
|
739
|
+
parts.push(`Working directory: ${context.workingDirectory}`);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return parts.length > 0 ? `\nContext: ${parts.join(', ')}` : '';
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private combineResults(
|
|
746
|
+
patternResult: IntentResult,
|
|
747
|
+
aiResult: IntentResult,
|
|
748
|
+
entities: Entity[]
|
|
749
|
+
): IntentResult {
|
|
750
|
+
// Use AI result as base, but boost confidence if pattern matching agrees
|
|
751
|
+
let finalResult = { ...aiResult };
|
|
752
|
+
|
|
753
|
+
if (patternResult.intent === aiResult.intent) {
|
|
754
|
+
finalResult.confidence = Math.min(0.98, aiResult.confidence + 0.1);
|
|
755
|
+
finalResult.reasoning += ` (confirmed by pattern matching)`;
|
|
756
|
+
} else if (patternResult.confidence > 0.8) {
|
|
757
|
+
// High-confidence pattern match might override AI
|
|
758
|
+
if (patternResult.confidence > aiResult.confidence) {
|
|
759
|
+
finalResult = patternResult;
|
|
760
|
+
finalResult.reasoning += ` (overridden by high-confidence pattern match)`;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Ensure entities are included
|
|
765
|
+
if (finalResult.context) {
|
|
766
|
+
finalResult.context.entities = entities;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return finalResult;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
private async validateAndEnrich(
|
|
773
|
+
result: IntentResult,
|
|
774
|
+
availableCommands: string[],
|
|
775
|
+
context: IntentContext
|
|
776
|
+
): Promise<IntentResult> {
|
|
777
|
+
// Validate that the intent command is available
|
|
778
|
+
if (!availableCommands.includes(result.intent)) {
|
|
779
|
+
logger.warn(`Intent "${result.intent}" not in available commands`);
|
|
780
|
+
result.confidence *= 0.5;
|
|
781
|
+
result.clarification = `The command "${result.intent}" is not available. Did you mean one of: ${availableCommands.join(', ')}?`;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Add safety warnings for destructive commands
|
|
785
|
+
const commandPattern = this.commandPatterns.get(result.intent);
|
|
786
|
+
if (commandPattern?.destructive) {
|
|
787
|
+
result.clarification = `This is a destructive operation. Are you sure you want to proceed?`;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Enrich with context-aware suggestions
|
|
791
|
+
if (result.confidence < this.config.confidenceThreshold) {
|
|
792
|
+
result.alternatives = await this.getContextualAlternatives(
|
|
793
|
+
result,
|
|
794
|
+
availableCommands,
|
|
795
|
+
context
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return result;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
private async getContextualAlternatives(
|
|
803
|
+
result: IntentResult,
|
|
804
|
+
availableCommands: string[],
|
|
805
|
+
context: IntentContext
|
|
806
|
+
): Promise<IntentResult['alternatives']> {
|
|
807
|
+
const alternatives: NonNullable<IntentResult['alternatives']> = [];
|
|
808
|
+
|
|
809
|
+
// Add pattern-based alternatives
|
|
810
|
+
for (const command of availableCommands) {
|
|
811
|
+
if (command !== result.intent) {
|
|
812
|
+
const pattern = this.commandPatterns.get(command);
|
|
813
|
+
if (pattern) {
|
|
814
|
+
const confidence = this.calculatePatternScore(
|
|
815
|
+
result.command || '',
|
|
816
|
+
pattern
|
|
817
|
+
);
|
|
818
|
+
if (confidence > 0.3) {
|
|
819
|
+
alternatives.push({
|
|
820
|
+
command: `wundr ${command}`,
|
|
821
|
+
confidence,
|
|
822
|
+
description: `Alternative: ${pattern.examples[0] || command}`,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Sort and limit alternatives
|
|
830
|
+
alternatives.sort((a, b) => b.confidence - a.confidence);
|
|
831
|
+
return alternatives.slice(0, this.config.maxAlternatives);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
private fallbackIntentParsing(
|
|
835
|
+
input: string,
|
|
836
|
+
availableCommands: string[],
|
|
837
|
+
context: IntentContext
|
|
838
|
+
): IntentResult {
|
|
839
|
+
// Simple keyword matching as last resort
|
|
840
|
+
const keywords = input.split(/\s+/);
|
|
841
|
+
const bestMatch = 'help';
|
|
842
|
+
const bestScore = 0;
|
|
843
|
+
|
|
844
|
+
for (const command of availableCommands) {
|
|
845
|
+
if (keywords.includes(command)) {
|
|
846
|
+
return {
|
|
847
|
+
intent: command,
|
|
848
|
+
confidence: 0.6,
|
|
849
|
+
command: `wundr ${command}`,
|
|
850
|
+
parameters: {},
|
|
851
|
+
reasoning: 'Fallback keyword matching',
|
|
852
|
+
context: { entities: [], keywords, sentiment: 'neutral' },
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return {
|
|
858
|
+
intent: bestMatch,
|
|
859
|
+
confidence: 0.3,
|
|
860
|
+
command: `wundr ${bestMatch}`,
|
|
861
|
+
parameters: {},
|
|
862
|
+
clarification: `I couldn't understand "${input}". Type "help" to see available commands.`,
|
|
863
|
+
reasoning: 'Fallback to help command',
|
|
864
|
+
context: { entities: [], keywords, sentiment: 'neutral' },
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
private fallbackParameterExtraction(
|
|
869
|
+
input: string,
|
|
870
|
+
pattern: CommandPattern
|
|
871
|
+
): Record<string, any> {
|
|
872
|
+
const parameters: Record<string, any> = {};
|
|
873
|
+
const words = input.split(/\s+/);
|
|
874
|
+
|
|
875
|
+
// Simple heuristic parameter extraction
|
|
876
|
+
for (const param of pattern.parameters) {
|
|
877
|
+
if (param.type === 'path') {
|
|
878
|
+
const pathMatch = input.match(/(?:\.\/|\/|~\/)[^\s]+/);
|
|
879
|
+
if (pathMatch) {
|
|
880
|
+
parameters[param.name] = pathMatch[0];
|
|
881
|
+
}
|
|
882
|
+
} else if (param.type === 'boolean') {
|
|
883
|
+
if (
|
|
884
|
+
words.some(word =>
|
|
885
|
+
['yes', 'true', 'on', 'enable'].includes(word.toLowerCase())
|
|
886
|
+
)
|
|
887
|
+
) {
|
|
888
|
+
parameters[param.name] = true;
|
|
889
|
+
} else if (
|
|
890
|
+
words.some(word =>
|
|
891
|
+
['no', 'false', 'off', 'disable'].includes(word.toLowerCase())
|
|
892
|
+
)
|
|
893
|
+
) {
|
|
894
|
+
parameters[param.name] = false;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Apply default values
|
|
899
|
+
if (!parameters[param.name] && param.defaultValue !== undefined) {
|
|
900
|
+
parameters[param.name] = param.defaultValue;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return parameters;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
private getPatternSuggestions(
|
|
908
|
+
partialInput: string,
|
|
909
|
+
availableCommands: string[]
|
|
910
|
+
): Array<{
|
|
911
|
+
command: string;
|
|
912
|
+
description: string;
|
|
913
|
+
confidence: number;
|
|
914
|
+
completion: string;
|
|
915
|
+
}> {
|
|
916
|
+
const suggestions: Array<{
|
|
917
|
+
command: string;
|
|
918
|
+
description: string;
|
|
919
|
+
confidence: number;
|
|
920
|
+
completion: string;
|
|
921
|
+
}> = [];
|
|
922
|
+
|
|
923
|
+
for (const command of availableCommands) {
|
|
924
|
+
const pattern = this.commandPatterns.get(command);
|
|
925
|
+
if (!pattern) continue;
|
|
926
|
+
|
|
927
|
+
// Check if partial input matches command or examples
|
|
928
|
+
const confidence = Math.max(
|
|
929
|
+
this.calculateStringSimilarity(partialInput, command),
|
|
930
|
+
...pattern.examples.map(ex =>
|
|
931
|
+
this.calculateStringSimilarity(partialInput, ex)
|
|
932
|
+
)
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
if (confidence > 0.3) {
|
|
936
|
+
suggestions.push({
|
|
937
|
+
command: `wundr ${command}`,
|
|
938
|
+
description: pattern.examples[0] || `Run ${command} command`,
|
|
939
|
+
confidence,
|
|
940
|
+
completion: pattern.examples[0] || `wundr ${command}`,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return suggestions;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
private async getAISuggestions(
|
|
949
|
+
partialInput: string,
|
|
950
|
+
availableCommands: string[],
|
|
951
|
+
context: IntentContext,
|
|
952
|
+
limit: number
|
|
953
|
+
): Promise<
|
|
954
|
+
Array<{
|
|
955
|
+
command: string;
|
|
956
|
+
description: string;
|
|
957
|
+
confidence: number;
|
|
958
|
+
completion: string;
|
|
959
|
+
}>
|
|
960
|
+
> {
|
|
961
|
+
const systemPrompt = `You are a CLI auto-completion assistant. Based on partial user input, suggest the most likely command completions.
|
|
962
|
+
|
|
963
|
+
Available commands: ${availableCommands.join(', ')}
|
|
964
|
+
Partial input: "${partialInput}"
|
|
965
|
+
|
|
966
|
+
Provide ${limit} suggestions in JSON format:
|
|
967
|
+
{
|
|
968
|
+
"suggestions": [
|
|
969
|
+
{
|
|
970
|
+
"command": "wundr analyze",
|
|
971
|
+
"description": "Analyze the project for issues",
|
|
972
|
+
"confidence": 0.9,
|
|
973
|
+
"completion": "wundr analyze --path ."
|
|
974
|
+
}
|
|
975
|
+
]
|
|
976
|
+
}`;
|
|
977
|
+
|
|
978
|
+
try {
|
|
979
|
+
const response = await this.claudeClient.sendMessage('', systemPrompt, {
|
|
980
|
+
temperature: 0.3,
|
|
981
|
+
maxTokens: 1024,
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
const result = JSON.parse(response.trim());
|
|
985
|
+
return result.suggestions || [];
|
|
986
|
+
} catch (error) {
|
|
987
|
+
logger.error('AI suggestions failed:', error);
|
|
988
|
+
return [];
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
private extractKeywords(input: string): string[] {
|
|
993
|
+
// Simple keyword extraction
|
|
994
|
+
const stopWords = [
|
|
995
|
+
'the',
|
|
996
|
+
'and',
|
|
997
|
+
'or',
|
|
998
|
+
'but',
|
|
999
|
+
'in',
|
|
1000
|
+
'on',
|
|
1001
|
+
'at',
|
|
1002
|
+
'to',
|
|
1003
|
+
'for',
|
|
1004
|
+
'of',
|
|
1005
|
+
'with',
|
|
1006
|
+
'by',
|
|
1007
|
+
'is',
|
|
1008
|
+
'are',
|
|
1009
|
+
'was',
|
|
1010
|
+
'were',
|
|
1011
|
+
'be',
|
|
1012
|
+
'been',
|
|
1013
|
+
'have',
|
|
1014
|
+
'has',
|
|
1015
|
+
'had',
|
|
1016
|
+
'do',
|
|
1017
|
+
'does',
|
|
1018
|
+
'did',
|
|
1019
|
+
'will',
|
|
1020
|
+
'would',
|
|
1021
|
+
'could',
|
|
1022
|
+
'should',
|
|
1023
|
+
];
|
|
1024
|
+
|
|
1025
|
+
return input
|
|
1026
|
+
.toLowerCase()
|
|
1027
|
+
.split(/\s+/)
|
|
1028
|
+
.filter(word => word.length > 2 && !stopWords.includes(word))
|
|
1029
|
+
.slice(0, 10); // Limit to 10 keywords
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
private analyzeSentiment(input: string): 'positive' | 'neutral' | 'negative' {
|
|
1033
|
+
const positiveWords = [
|
|
1034
|
+
'good',
|
|
1035
|
+
'great',
|
|
1036
|
+
'excellent',
|
|
1037
|
+
'awesome',
|
|
1038
|
+
'perfect',
|
|
1039
|
+
'love',
|
|
1040
|
+
'like',
|
|
1041
|
+
'thanks',
|
|
1042
|
+
'help',
|
|
1043
|
+
'please',
|
|
1044
|
+
];
|
|
1045
|
+
const negativeWords = [
|
|
1046
|
+
'bad',
|
|
1047
|
+
'terrible',
|
|
1048
|
+
'awful',
|
|
1049
|
+
'hate',
|
|
1050
|
+
'problem',
|
|
1051
|
+
'error',
|
|
1052
|
+
'issue',
|
|
1053
|
+
'broken',
|
|
1054
|
+
'fail',
|
|
1055
|
+
'wrong',
|
|
1056
|
+
];
|
|
1057
|
+
|
|
1058
|
+
const words = input.toLowerCase().split(/\s+/);
|
|
1059
|
+
const positiveCount = words.filter(word =>
|
|
1060
|
+
positiveWords.includes(word)
|
|
1061
|
+
).length;
|
|
1062
|
+
const negativeCount = words.filter(word =>
|
|
1063
|
+
negativeWords.includes(word)
|
|
1064
|
+
).length;
|
|
1065
|
+
|
|
1066
|
+
if (positiveCount > negativeCount) return 'positive';
|
|
1067
|
+
if (negativeCount > positiveCount) return 'negative';
|
|
1068
|
+
return 'neutral';
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
private cacheResult(key: string, result: IntentResult): void {
|
|
1072
|
+
this.intentCache.set(key, {
|
|
1073
|
+
result,
|
|
1074
|
+
timestamp: Date.now(),
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
// Clean old entries periodically
|
|
1078
|
+
if (this.intentCache.size % 100 === 0) {
|
|
1079
|
+
this.cleanCache();
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
private cleanCache(): void {
|
|
1084
|
+
const now = Date.now();
|
|
1085
|
+
const expiredKeys: string[] = [];
|
|
1086
|
+
|
|
1087
|
+
for (const [key, entry] of this.intentCache) {
|
|
1088
|
+
if (now - entry.timestamp > this.config.cacheDuration) {
|
|
1089
|
+
expiredKeys.push(key);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
expiredKeys.forEach(key => this.intentCache.delete(key));
|
|
1094
|
+
|
|
1095
|
+
if (expiredKeys.length > 0) {
|
|
1096
|
+
logger.debug(`Cleaned ${expiredKeys.length} expired cache entries`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
export default IntentParser;
|