@baselineos/lang 0.1.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/.turbo/turbo-build.log +14 -0
- package/.turbo/turbo-test.log +16 -0
- package/LICENSE +17 -0
- package/README.md +19 -0
- package/dist/index.d.ts +176 -0
- package/dist/index.js +1082 -0
- package/package.json +34 -0
- package/src/__tests__/smoke.test.ts +14 -0
- package/src/__tests__/validation.test.ts +83 -0
- package/src/__tests__/workflow.test.ts +11 -0
- package/src/config/lang-system-config.json +346 -0
- package/src/index.ts +25 -0
- package/src/system.ts +1025 -0
- package/tsconfig.json +9 -0
package/src/system.ts
ADDED
|
@@ -0,0 +1,1025 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline Lang System
|
|
3
|
+
*
|
|
4
|
+
* The foundation layer of the Baseline Protocol that handles natural language
|
|
5
|
+
* processing, command interpretation, and language syntax management.
|
|
6
|
+
*
|
|
7
|
+
* @license Apache-2.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import langSystemConfig from './config/lang-system-config.json' with { type: 'json' };
|
|
13
|
+
|
|
14
|
+
// ─── Types ──────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export interface LangSystemConfig {
|
|
17
|
+
language: string;
|
|
18
|
+
syntax: SyntaxConfig;
|
|
19
|
+
patterns: PatternConfig;
|
|
20
|
+
commands: Record<string, CommandConfig>;
|
|
21
|
+
lexicon?: Record<string, LexiconEntry>;
|
|
22
|
+
validation?: LangValidationConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SyntaxConfig {
|
|
26
|
+
commandPrefix: string;
|
|
27
|
+
optionPrefix: string;
|
|
28
|
+
separator: string;
|
|
29
|
+
quoteChar: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PatternConfig {
|
|
33
|
+
intent: RegExp;
|
|
34
|
+
command: RegExp;
|
|
35
|
+
option: RegExp;
|
|
36
|
+
value: RegExp;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface LangValidationConfig {
|
|
40
|
+
allowUnknownOptions?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CommandOptionSpec {
|
|
44
|
+
type?: 'string' | 'number' | 'boolean';
|
|
45
|
+
description?: string;
|
|
46
|
+
required?: boolean;
|
|
47
|
+
default?: unknown;
|
|
48
|
+
values?: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CommandValidationSpec {
|
|
52
|
+
minArgs?: number;
|
|
53
|
+
maxArgs?: number;
|
|
54
|
+
allowUnknownOptions?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface LexiconEntry {
|
|
58
|
+
description: string;
|
|
59
|
+
aliases?: string[];
|
|
60
|
+
tags?: string[];
|
|
61
|
+
examples?: string[];
|
|
62
|
+
source?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CommandConfig {
|
|
66
|
+
aliases: string[];
|
|
67
|
+
description: string;
|
|
68
|
+
usage: string;
|
|
69
|
+
examples: string[];
|
|
70
|
+
handler?: CommandHandler;
|
|
71
|
+
options?: Record<string, CommandOptionSpec>;
|
|
72
|
+
validation?: CommandValidationSpec;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface RegisteredCommand {
|
|
76
|
+
name: string;
|
|
77
|
+
aliases: string[];
|
|
78
|
+
description: string;
|
|
79
|
+
usage: string;
|
|
80
|
+
examples: string[];
|
|
81
|
+
handler: CommandHandler;
|
|
82
|
+
options: Record<string, CommandOptionSpec>;
|
|
83
|
+
validation: CommandValidationSpec;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type CommandHandler = (
|
|
87
|
+
command: string,
|
|
88
|
+
options: Map<string, unknown>,
|
|
89
|
+
args: string[],
|
|
90
|
+
) => CommandResult;
|
|
91
|
+
|
|
92
|
+
export interface CommandResult {
|
|
93
|
+
success: boolean;
|
|
94
|
+
message?: string;
|
|
95
|
+
error?: string;
|
|
96
|
+
options?: Map<string, unknown>;
|
|
97
|
+
arguments?: string[];
|
|
98
|
+
suggestions?: string[];
|
|
99
|
+
timestamp?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ParsedInput {
|
|
103
|
+
original: string;
|
|
104
|
+
tokens: string[];
|
|
105
|
+
command: string | null;
|
|
106
|
+
options: Map<string, unknown>;
|
|
107
|
+
arguments: string[];
|
|
108
|
+
intent: string | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface IntentResult {
|
|
112
|
+
type: string;
|
|
113
|
+
confidence: number;
|
|
114
|
+
patterns: RegExp[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ProcessResult {
|
|
118
|
+
success: boolean;
|
|
119
|
+
input?: string;
|
|
120
|
+
parsed?: ParsedInput;
|
|
121
|
+
intent?: IntentResult;
|
|
122
|
+
result?: CommandResult;
|
|
123
|
+
error?: string;
|
|
124
|
+
details?: string | string[];
|
|
125
|
+
suggestions?: string[];
|
|
126
|
+
timestamp?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface ParsedOption {
|
|
130
|
+
name: string;
|
|
131
|
+
value: string | null;
|
|
132
|
+
type: 'long' | 'short' | 'unknown';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface BaselineLangOptions {
|
|
136
|
+
projectRoot?: string;
|
|
137
|
+
projectLexicon?: Record<string, LexiconEntry>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ─── Intent Recognizer ──────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
export class IntentRecognizer {
|
|
143
|
+
private patterns = new Map<string, Record<string, RegExp>>();
|
|
144
|
+
|
|
145
|
+
initialize(patterns: Record<string, unknown>): void {
|
|
146
|
+
// Store patterns — the config may have flat RegExp values or nested objects
|
|
147
|
+
for (const [key, value] of Object.entries(patterns)) {
|
|
148
|
+
if (value instanceof RegExp) {
|
|
149
|
+
this.patterns.set(key, { default: value });
|
|
150
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
151
|
+
this.patterns.set(key, value as Record<string, RegExp>);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
recognize(parsed: ParsedInput): IntentResult {
|
|
157
|
+
const intent: IntentResult = {
|
|
158
|
+
type: 'unknown',
|
|
159
|
+
confidence: 0,
|
|
160
|
+
patterns: [],
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (parsed.command) {
|
|
164
|
+
const commandIntent = this.analyzeCommand(parsed.command);
|
|
165
|
+
if (commandIntent.confidence > intent.confidence) {
|
|
166
|
+
intent.type = commandIntent.type;
|
|
167
|
+
intent.confidence = commandIntent.confidence;
|
|
168
|
+
intent.patterns = commandIntent.patterns;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (parsed.arguments.length > 0) {
|
|
173
|
+
const argIntent = this.analyzeArguments(parsed.arguments);
|
|
174
|
+
if (argIntent.confidence > intent.confidence) {
|
|
175
|
+
intent.type = argIntent.type;
|
|
176
|
+
intent.confidence = argIntent.confidence;
|
|
177
|
+
intent.patterns = argIntent.patterns;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return intent;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private analyzeCommand(command: string): IntentResult {
|
|
185
|
+
const patterns = (this.patterns.get('intent') as Record<string, RegExp>) || {};
|
|
186
|
+
let bestMatch: IntentResult = { type: 'unknown', confidence: 0, patterns: [] };
|
|
187
|
+
|
|
188
|
+
for (const [type, pattern] of Object.entries(patterns)) {
|
|
189
|
+
if (pattern.test(command)) {
|
|
190
|
+
bestMatch = { type, confidence: 0.8, patterns: [pattern] };
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return bestMatch;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private analyzeArguments(args: string[]): IntentResult {
|
|
199
|
+
const patterns = (this.patterns.get('intent') as Record<string, RegExp>) || {};
|
|
200
|
+
let bestMatch: IntentResult = { type: 'unknown', confidence: 0, patterns: [] };
|
|
201
|
+
|
|
202
|
+
for (const arg of args) {
|
|
203
|
+
for (const [type, pattern] of Object.entries(patterns)) {
|
|
204
|
+
if (pattern.test(arg)) {
|
|
205
|
+
bestMatch = { type, confidence: 0.6, patterns: [pattern] };
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (bestMatch.type !== 'unknown') break;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return bestMatch;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── Command Processor ──────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
export class CommandProcessor {
|
|
219
|
+
commandRegistry: Map<string, RegisteredCommand> | null = null;
|
|
220
|
+
|
|
221
|
+
process(
|
|
222
|
+
parsed: ParsedInput,
|
|
223
|
+
_intent: IntentResult,
|
|
224
|
+
): CommandResult {
|
|
225
|
+
const command = this.findCommand(parsed.command);
|
|
226
|
+
|
|
227
|
+
if (!command) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
error: `Command "${parsed.command}" not found`,
|
|
231
|
+
suggestions: ['Use "help" to see available commands'],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const result = command.handler(parsed.command!, parsed.options, parsed.arguments);
|
|
237
|
+
return { ...result };
|
|
238
|
+
} catch (error) {
|
|
239
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error: `Command execution failed: ${msg}`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private findCommand(commandName: string | null): RegisteredCommand | null {
|
|
248
|
+
if (!commandName || !this.commandRegistry) return null;
|
|
249
|
+
|
|
250
|
+
if (this.commandRegistry.has(commandName)) {
|
|
251
|
+
return this.commandRegistry.get(commandName)!;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const [, command] of this.commandRegistry) {
|
|
255
|
+
if (command.aliases.includes(commandName)) {
|
|
256
|
+
return command;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ─── Syntax Validator ───────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
export class SyntaxValidator {
|
|
267
|
+
validate(parsed: ParsedInput): { valid: boolean; errors: string[] } {
|
|
268
|
+
const errors: string[] = [];
|
|
269
|
+
|
|
270
|
+
if (!parsed.command) {
|
|
271
|
+
errors.push('No command specified');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (parsed.options.size > 0) {
|
|
275
|
+
for (const [option] of parsed.options) {
|
|
276
|
+
if (!this.isValidOption(option)) {
|
|
277
|
+
errors.push(`Invalid option: ${option}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { valid: errors.length === 0, errors };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private isValidOption(option: string): boolean {
|
|
286
|
+
return /^[a-z][a-z0-9-]*$/i.test(option);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ─── Baseline Lang System ───────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
export class BaselineLangSystem {
|
|
293
|
+
private config: LangSystemConfig;
|
|
294
|
+
private commandRegistry = new Map<string, RegisteredCommand>();
|
|
295
|
+
private languagePatterns = new Map<string, Record<string, RegExp>>();
|
|
296
|
+
private intentRecognizer = new IntentRecognizer();
|
|
297
|
+
private commandProcessor = new CommandProcessor();
|
|
298
|
+
private syntaxValidator = new SyntaxValidator();
|
|
299
|
+
private lexicon = new Map<string, LexiconEntry>();
|
|
300
|
+
private validationDefaults: Required<LangValidationConfig> = {
|
|
301
|
+
allowUnknownOptions: true,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
constructor(options: BaselineLangOptions = {}) {
|
|
305
|
+
this.config = this.loadConfiguration();
|
|
306
|
+
this.initializeSystem();
|
|
307
|
+
if (options.projectLexicon) {
|
|
308
|
+
this.mergeLexicon(options.projectLexicon, 'project');
|
|
309
|
+
}
|
|
310
|
+
if (options.projectRoot) {
|
|
311
|
+
this.loadProjectLexicon(options.projectRoot);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private loadConfiguration(): LangSystemConfig {
|
|
316
|
+
const cfg = langSystemConfig as {
|
|
317
|
+
system?: Record<string, string>;
|
|
318
|
+
syntax?: Record<string, string>;
|
|
319
|
+
patterns?: { intent?: Record<string, string>; command?: Record<string, string>; option?: Record<string, string>; value?: Record<string, string> };
|
|
320
|
+
commands?: Record<string, {
|
|
321
|
+
aliases?: string[];
|
|
322
|
+
description?: string;
|
|
323
|
+
usage?: string;
|
|
324
|
+
examples?: string[];
|
|
325
|
+
options?: Record<string, CommandOptionSpec>;
|
|
326
|
+
validation?: CommandValidationSpec;
|
|
327
|
+
}>;
|
|
328
|
+
lexicon?: Record<string, LexiconEntry>;
|
|
329
|
+
validation?: LangValidationConfig;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const syntaxCfg = cfg.syntax ?? {};
|
|
333
|
+
const patternsCfg = cfg.patterns ?? {};
|
|
334
|
+
const commandsCfg = cfg.commands ?? {};
|
|
335
|
+
|
|
336
|
+
// Build intent regex from config's intent patterns (combine all intent types)
|
|
337
|
+
const intentPatterns = patternsCfg.intent ?? {};
|
|
338
|
+
const intentSources = Object.values(intentPatterns);
|
|
339
|
+
const combinedIntent = intentSources.length > 0
|
|
340
|
+
? new RegExp(intentSources.map((p) => `(${p})`).join('|'), 'i')
|
|
341
|
+
: /^(what|how|when|where|why|who|which|can|should|will|do|does|is|are|was|were)/i;
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
language: (cfg.system?.language as string) ?? 'en',
|
|
345
|
+
syntax: {
|
|
346
|
+
commandPrefix: syntaxCfg.commandPrefix ?? '--',
|
|
347
|
+
optionPrefix: syntaxCfg.optionPrefix ?? '-',
|
|
348
|
+
separator: syntaxCfg.separator ?? ' ',
|
|
349
|
+
quoteChar: syntaxCfg.quoteChar ?? '"',
|
|
350
|
+
},
|
|
351
|
+
patterns: {
|
|
352
|
+
intent: combinedIntent,
|
|
353
|
+
command: new RegExp(patternsCfg.command?.basic ?? '^[a-z][a-z0-9-]*$', 'i'),
|
|
354
|
+
option: new RegExp(patternsCfg.option?.long ?? '^[a-z][a-z0-9-]*$', 'i'),
|
|
355
|
+
value: new RegExp(patternsCfg.value?.text ?? '^[a-zA-Z0-9\\s\\-_./\\\\]+$'),
|
|
356
|
+
},
|
|
357
|
+
commands: Object.fromEntries(
|
|
358
|
+
Object.entries(commandsCfg).map(([name, cmd]) => [
|
|
359
|
+
name,
|
|
360
|
+
{
|
|
361
|
+
aliases: cmd.aliases ?? [],
|
|
362
|
+
description: cmd.description ?? '',
|
|
363
|
+
usage: cmd.usage ?? '',
|
|
364
|
+
examples: cmd.examples ?? [],
|
|
365
|
+
options: cmd.options ?? {},
|
|
366
|
+
validation: cmd.validation ?? {},
|
|
367
|
+
},
|
|
368
|
+
])
|
|
369
|
+
),
|
|
370
|
+
lexicon: cfg.lexicon ?? {},
|
|
371
|
+
validation: {
|
|
372
|
+
allowUnknownOptions: cfg.validation?.allowUnknownOptions ?? this.validationDefaults.allowUnknownOptions,
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private initializeSystem(): void {
|
|
378
|
+
this.registerBuiltInCommands();
|
|
379
|
+
this.loadLexicon();
|
|
380
|
+
this.loadLanguagePatterns();
|
|
381
|
+
this.intentRecognizer.initialize(this.config.patterns as unknown as Record<string, RegExp>);
|
|
382
|
+
this.commandProcessor.commandRegistry = this.commandRegistry;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private registerBuiltInCommands(): void {
|
|
386
|
+
for (const [command, config] of Object.entries(this.config.commands)) {
|
|
387
|
+
const handler = command === 'help'
|
|
388
|
+
? this.helpCommandHandler.bind(this)
|
|
389
|
+
: config.handler;
|
|
390
|
+
this.registerCommand(command, { ...config, handler });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
registerCommand(name: string, config: CommandConfig): void {
|
|
395
|
+
const registered: RegisteredCommand = {
|
|
396
|
+
name,
|
|
397
|
+
aliases: config.aliases || [],
|
|
398
|
+
description: config.description || '',
|
|
399
|
+
usage: config.usage || '',
|
|
400
|
+
examples: config.examples || [],
|
|
401
|
+
handler: config.handler || this.defaultCommandHandler.bind(this),
|
|
402
|
+
options: config.options || {},
|
|
403
|
+
validation: config.validation || {},
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
this.commandRegistry.set(name, registered);
|
|
407
|
+
|
|
408
|
+
if (config.aliases) {
|
|
409
|
+
for (const alias of config.aliases) {
|
|
410
|
+
this.commandRegistry.set(alias, registered);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private loadLanguagePatterns(): void {
|
|
416
|
+
this.languagePatterns.set('intent', {
|
|
417
|
+
question: /^(what|how|when|where|why|who|which)/i,
|
|
418
|
+
action: /^(create|make|build|generate|start|begin|init|initialize)/i,
|
|
419
|
+
query: /^(show|display|list|find|search|get|fetch|retrieve)/i,
|
|
420
|
+
modification: /^(update|modify|edit|change|alter|adjust)/i,
|
|
421
|
+
deletion: /^(delete|remove|destroy|eliminate|clear|wipe)/i,
|
|
422
|
+
status: /^(status|info|details|state|condition|health)/i,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
this.languagePatterns.set('command', {
|
|
426
|
+
basic: /^[a-z][a-z0-9-]*$/i,
|
|
427
|
+
compound: /^[a-z][a-z0-9-]*:[a-z][a-z0-9-]*$/i,
|
|
428
|
+
nested: /^[a-z][a-z0-9-]*:[a-z][a-z0-9-]*:[a-z][a-z0-9-]*$/i,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
this.languagePatterns.set('option', {
|
|
432
|
+
short: /^-[a-zA-Z]$/,
|
|
433
|
+
long: /^--[a-z][a-z0-9-]*$/i,
|
|
434
|
+
withValue: /^--[a-z][a-z0-9-]*=/i,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private resolveCommand(commandName: string | null): RegisteredCommand | null {
|
|
439
|
+
if (!commandName) return null;
|
|
440
|
+
if (this.commandRegistry.has(commandName)) {
|
|
441
|
+
return this.commandRegistry.get(commandName) ?? null;
|
|
442
|
+
}
|
|
443
|
+
const normalized = commandName.startsWith('--') ? commandName.slice(2) : commandName;
|
|
444
|
+
if (this.commandRegistry.has(normalized)) {
|
|
445
|
+
return this.commandRegistry.get(normalized) ?? null;
|
|
446
|
+
}
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private validateCommand(
|
|
451
|
+
parsed: ParsedInput,
|
|
452
|
+
command: RegisteredCommand | null,
|
|
453
|
+
): { valid: boolean; errors: string[] } {
|
|
454
|
+
const errors: string[] = [];
|
|
455
|
+
|
|
456
|
+
if (!command) {
|
|
457
|
+
errors.push(`Command "${parsed.command ?? ''}" not found`);
|
|
458
|
+
return { valid: false, errors };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const minArgs = command.validation?.minArgs;
|
|
462
|
+
const maxArgs = command.validation?.maxArgs;
|
|
463
|
+
if (typeof minArgs === 'number' && parsed.arguments.length < minArgs) {
|
|
464
|
+
errors.push(`Expected at least ${minArgs} arguments`);
|
|
465
|
+
}
|
|
466
|
+
if (typeof maxArgs === 'number' && parsed.arguments.length > maxArgs) {
|
|
467
|
+
errors.push(`Expected no more than ${maxArgs} arguments`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const allowUnknownOptions =
|
|
471
|
+
command.validation?.allowUnknownOptions ??
|
|
472
|
+
this.config.validation?.allowUnknownOptions ??
|
|
473
|
+
this.validationDefaults.allowUnknownOptions;
|
|
474
|
+
|
|
475
|
+
if (!allowUnknownOptions) {
|
|
476
|
+
for (const optionName of parsed.options.keys()) {
|
|
477
|
+
if (!command.options?.[optionName]) {
|
|
478
|
+
errors.push(`Unknown option: ${optionName}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const optionSpecs = command.options ?? {};
|
|
484
|
+
for (const [optionName, spec] of Object.entries(optionSpecs)) {
|
|
485
|
+
if (spec.required && !parsed.options.has(optionName)) {
|
|
486
|
+
errors.push(`Missing required option: ${optionName}`);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (!parsed.options.has(optionName)) continue;
|
|
490
|
+
const value = parsed.options.get(optionName);
|
|
491
|
+
const coerced = this.coerceOptionValue(value, spec);
|
|
492
|
+
if (!coerced.valid) {
|
|
493
|
+
errors.push(`Invalid value for option: ${optionName}`);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (spec.values && coerced.value !== undefined) {
|
|
497
|
+
const stringValue = String(coerced.value);
|
|
498
|
+
if (!spec.values.includes(stringValue)) {
|
|
499
|
+
errors.push(`Invalid value for option: ${optionName}`);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return { valid: errors.length === 0, errors };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private normalizeOptions(parsed: ParsedInput, command: RegisteredCommand): void {
|
|
509
|
+
const optionSpecs = command.options ?? {};
|
|
510
|
+
for (const [name, spec] of Object.entries(optionSpecs)) {
|
|
511
|
+
if (!parsed.options.has(name) && spec.default !== undefined) {
|
|
512
|
+
parsed.options.set(name, spec.default);
|
|
513
|
+
}
|
|
514
|
+
if (!parsed.options.has(name)) continue;
|
|
515
|
+
const value = parsed.options.get(name);
|
|
516
|
+
const coerced = this.coerceOptionValue(value, spec);
|
|
517
|
+
if (coerced.valid) {
|
|
518
|
+
parsed.options.set(name, coerced.value);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private listCommands(): RegisteredCommand[] {
|
|
524
|
+
const seen = new Set<string>();
|
|
525
|
+
const commands: RegisteredCommand[] = [];
|
|
526
|
+
for (const command of this.commandRegistry.values()) {
|
|
527
|
+
if (seen.has(command.name)) continue;
|
|
528
|
+
seen.add(command.name);
|
|
529
|
+
commands.push(command);
|
|
530
|
+
}
|
|
531
|
+
return commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private helpCommandHandler(
|
|
535
|
+
_command: string,
|
|
536
|
+
options: Map<string, unknown>,
|
|
537
|
+
args: string[],
|
|
538
|
+
): CommandResult {
|
|
539
|
+
const format = (options.get('format') as string | undefined) ?? 'text';
|
|
540
|
+
const verbose = Boolean(options.get('verbose'));
|
|
541
|
+
const target = args[0];
|
|
542
|
+
|
|
543
|
+
if (target) {
|
|
544
|
+
const command = this.resolveCommand(target);
|
|
545
|
+
if (!command) {
|
|
546
|
+
return {
|
|
547
|
+
success: false,
|
|
548
|
+
error: `Unknown command "${target}"`,
|
|
549
|
+
suggestions: this.findSimilarCommands(target),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const payload = {
|
|
553
|
+
name: command.name,
|
|
554
|
+
description: command.description,
|
|
555
|
+
usage: command.usage,
|
|
556
|
+
aliases: command.aliases,
|
|
557
|
+
examples: command.examples,
|
|
558
|
+
options: command.options,
|
|
559
|
+
};
|
|
560
|
+
return {
|
|
561
|
+
success: true,
|
|
562
|
+
message: this.formatHelpPayload(payload, format, verbose),
|
|
563
|
+
options,
|
|
564
|
+
arguments: args,
|
|
565
|
+
timestamp: new Date().toISOString(),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const commands = this.listCommands();
|
|
570
|
+
const payload = commands.map((command) => ({
|
|
571
|
+
name: command.name,
|
|
572
|
+
description: command.description,
|
|
573
|
+
usage: command.usage,
|
|
574
|
+
aliases: command.aliases,
|
|
575
|
+
examples: command.examples,
|
|
576
|
+
options: command.options,
|
|
577
|
+
}));
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
success: true,
|
|
581
|
+
message: this.formatHelpPayload(payload, format, verbose),
|
|
582
|
+
options,
|
|
583
|
+
arguments: args,
|
|
584
|
+
timestamp: new Date().toISOString(),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private formatHelpPayload(
|
|
589
|
+
payload: Record<string, unknown> | Array<Record<string, unknown>>,
|
|
590
|
+
format: string,
|
|
591
|
+
verbose: boolean,
|
|
592
|
+
): string {
|
|
593
|
+
if (format === 'json') {
|
|
594
|
+
return JSON.stringify(payload, null, 2);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (Array.isArray(payload)) {
|
|
598
|
+
const lines = payload.map((entry) => {
|
|
599
|
+
const item = entry as { name?: string; description?: string };
|
|
600
|
+
return `${item.name ?? ''} — ${item.description ?? ''}`.trim();
|
|
601
|
+
});
|
|
602
|
+
return lines.join('\n');
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const entry = payload as {
|
|
606
|
+
name?: string;
|
|
607
|
+
description?: string;
|
|
608
|
+
usage?: string;
|
|
609
|
+
aliases?: string[];
|
|
610
|
+
examples?: string[];
|
|
611
|
+
options?: Record<string, CommandOptionSpec>;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const lines: string[] = [];
|
|
615
|
+
if (entry.name) {
|
|
616
|
+
lines.push(`${entry.name} — ${entry.description ?? ''}`.trim());
|
|
617
|
+
}
|
|
618
|
+
if (entry.usage) {
|
|
619
|
+
lines.push(`Usage: ${entry.usage}`);
|
|
620
|
+
}
|
|
621
|
+
if (verbose && entry.aliases && entry.aliases.length > 0) {
|
|
622
|
+
lines.push(`Aliases: ${entry.aliases.join(', ')}`);
|
|
623
|
+
}
|
|
624
|
+
if (verbose && entry.examples && entry.examples.length > 0) {
|
|
625
|
+
lines.push(`Examples: ${entry.examples.join(' | ')}`);
|
|
626
|
+
}
|
|
627
|
+
if (verbose && entry.options && Object.keys(entry.options).length > 0) {
|
|
628
|
+
lines.push(`Options: ${Object.keys(entry.options).join(', ')}`);
|
|
629
|
+
}
|
|
630
|
+
return lines.join('\n');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private coerceOptionValue(
|
|
634
|
+
value: unknown,
|
|
635
|
+
spec: CommandOptionSpec,
|
|
636
|
+
): { valid: boolean; value: unknown } {
|
|
637
|
+
if (spec.type === 'boolean') {
|
|
638
|
+
if (typeof value === 'boolean') return { valid: true, value };
|
|
639
|
+
if (typeof value === 'string') {
|
|
640
|
+
const normalized = value.trim().toLowerCase();
|
|
641
|
+
if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) {
|
|
642
|
+
return { valid: true, value: true };
|
|
643
|
+
}
|
|
644
|
+
if (['false', '0', 'no', 'n', 'off'].includes(normalized)) {
|
|
645
|
+
return { valid: true, value: false };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (value === null || value === undefined) {
|
|
649
|
+
return { valid: true, value: true };
|
|
650
|
+
}
|
|
651
|
+
return { valid: false, value };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (spec.type === 'number') {
|
|
655
|
+
if (typeof value === 'number' && !Number.isNaN(value)) {
|
|
656
|
+
return { valid: true, value };
|
|
657
|
+
}
|
|
658
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
659
|
+
const parsed = Number(value);
|
|
660
|
+
if (!Number.isNaN(parsed)) {
|
|
661
|
+
return { valid: true, value: parsed };
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return { valid: false, value };
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (spec.type === 'string') {
|
|
668
|
+
if (typeof value === 'string') {
|
|
669
|
+
return { valid: true, value };
|
|
670
|
+
}
|
|
671
|
+
if (value === null || value === undefined) {
|
|
672
|
+
return { valid: false, value };
|
|
673
|
+
}
|
|
674
|
+
return { valid: true, value: String(value) };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return { valid: true, value };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private loadLexicon(): void {
|
|
681
|
+
const entries = this.config.lexicon ?? {};
|
|
682
|
+
if (Object.keys(entries).length === 0) {
|
|
683
|
+
for (const [name, command] of Object.entries(this.config.commands)) {
|
|
684
|
+
this.lexicon.set(name, {
|
|
685
|
+
description: command.description || '',
|
|
686
|
+
aliases: command.aliases ?? [],
|
|
687
|
+
examples: command.examples ?? [],
|
|
688
|
+
source: 'built-in',
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
this.mergeLexicon(entries, 'config');
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
private loadProjectLexicon(projectRoot: string): void {
|
|
698
|
+
const candidates = [
|
|
699
|
+
join(projectRoot, '.baseline', 'lang.json'),
|
|
700
|
+
join(projectRoot, '.baseline', 'lexicon.json'),
|
|
701
|
+
join(projectRoot, 'baseline.lang.json'),
|
|
702
|
+
];
|
|
703
|
+
|
|
704
|
+
for (const path of candidates) {
|
|
705
|
+
if (!existsSync(path)) continue;
|
|
706
|
+
const raw = readFileSync(path, 'utf8');
|
|
707
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
708
|
+
const lexicon = this.extractLexicon(parsed);
|
|
709
|
+
if (lexicon) {
|
|
710
|
+
this.mergeLexicon(lexicon, 'project');
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
private extractLexicon(input: unknown): Record<string, LexiconEntry> | null {
|
|
716
|
+
if (!input || typeof input !== 'object') return null;
|
|
717
|
+
const candidate = input as Record<string, unknown>;
|
|
718
|
+
const fromContainer = candidate.lexicon;
|
|
719
|
+
if (fromContainer && typeof fromContainer === 'object') {
|
|
720
|
+
return this.sanitizeLexicon(fromContainer as Record<string, LexiconEntry>);
|
|
721
|
+
}
|
|
722
|
+
return this.sanitizeLexicon(candidate as Record<string, LexiconEntry>);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private sanitizeLexicon(entries: Record<string, LexiconEntry>): Record<string, LexiconEntry> {
|
|
726
|
+
const sanitized: Record<string, LexiconEntry> = {};
|
|
727
|
+
for (const [term, entry] of Object.entries(entries)) {
|
|
728
|
+
if (!term.trim()) continue;
|
|
729
|
+
const description = entry?.description ?? '';
|
|
730
|
+
sanitized[term.trim()] = {
|
|
731
|
+
description,
|
|
732
|
+
aliases: entry?.aliases ?? [],
|
|
733
|
+
tags: entry?.tags ?? [],
|
|
734
|
+
examples: entry?.examples ?? [],
|
|
735
|
+
source: entry?.source,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
return sanitized;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
public mergeLexicon(entries: Record<string, LexiconEntry>, source?: string): void {
|
|
742
|
+
for (const [term, entry] of Object.entries(entries)) {
|
|
743
|
+
const normalized = term.trim();
|
|
744
|
+
if (!normalized) continue;
|
|
745
|
+
const existing = this.lexicon.get(normalized);
|
|
746
|
+
const merged: LexiconEntry = {
|
|
747
|
+
description: entry.description || existing?.description || '',
|
|
748
|
+
aliases: this.mergeStringArrays(existing?.aliases, entry.aliases),
|
|
749
|
+
tags: this.mergeStringArrays(existing?.tags, entry.tags),
|
|
750
|
+
examples: this.mergeStringArrays(existing?.examples, entry.examples),
|
|
751
|
+
source: source ?? entry.source ?? existing?.source,
|
|
752
|
+
};
|
|
753
|
+
this.lexicon.set(normalized, merged);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
private mergeStringArrays(
|
|
758
|
+
left: string[] | undefined,
|
|
759
|
+
right: string[] | undefined,
|
|
760
|
+
): string[] {
|
|
761
|
+
const combined = [...(left ?? []), ...(right ?? [])].map((value) => value.trim()).filter(Boolean);
|
|
762
|
+
return Array.from(new Set(combined));
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
processInput(input: string): ProcessResult {
|
|
766
|
+
try {
|
|
767
|
+
const parsed = this.parseInput(input);
|
|
768
|
+
const intent = this.intentRecognizer.recognize(parsed);
|
|
769
|
+
const validation = this.syntaxValidator.validate(parsed);
|
|
770
|
+
|
|
771
|
+
if (!validation.valid) {
|
|
772
|
+
return {
|
|
773
|
+
success: false,
|
|
774
|
+
error: 'Syntax validation failed',
|
|
775
|
+
details: validation.errors,
|
|
776
|
+
suggestions: this.generateSuggestions(input),
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const command = this.resolveCommand(parsed.command);
|
|
781
|
+
const commandValidation = this.validateCommand(parsed, command);
|
|
782
|
+
if (!commandValidation.valid) {
|
|
783
|
+
return {
|
|
784
|
+
success: false,
|
|
785
|
+
error: 'Command validation failed',
|
|
786
|
+
details: commandValidation.errors,
|
|
787
|
+
suggestions: this.generateSuggestions(input),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (command) {
|
|
792
|
+
this.normalizeOptions(parsed, command);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const result = this.commandProcessor.process(parsed, intent);
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
success: true,
|
|
799
|
+
input,
|
|
800
|
+
parsed,
|
|
801
|
+
intent,
|
|
802
|
+
result,
|
|
803
|
+
timestamp: new Date().toISOString(),
|
|
804
|
+
};
|
|
805
|
+
} catch (error) {
|
|
806
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
807
|
+
return {
|
|
808
|
+
success: false,
|
|
809
|
+
error: 'Processing failed',
|
|
810
|
+
details: msg,
|
|
811
|
+
suggestions: this.generateSuggestions(input),
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
parseInput(input: string): ParsedInput {
|
|
817
|
+
const tokens = this.tokenize(input);
|
|
818
|
+
const parsed: ParsedInput = {
|
|
819
|
+
original: input,
|
|
820
|
+
tokens: [...tokens],
|
|
821
|
+
command: null,
|
|
822
|
+
options: new Map(),
|
|
823
|
+
arguments: [],
|
|
824
|
+
intent: null,
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
if (tokens.length > 0) {
|
|
828
|
+
const firstToken = tokens[0];
|
|
829
|
+
if (this.isCommand(firstToken)) {
|
|
830
|
+
parsed.command = firstToken;
|
|
831
|
+
tokens.splice(0, 1);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
836
|
+
const token = tokens[i];
|
|
837
|
+
|
|
838
|
+
if (this.isOption(token)) {
|
|
839
|
+
const option = this.parseOption(token);
|
|
840
|
+
if (option.value) {
|
|
841
|
+
parsed.options.set(option.name, option.value);
|
|
842
|
+
} else if (i + 1 < tokens.length && !this.isOption(tokens[i + 1])) {
|
|
843
|
+
parsed.options.set(option.name, tokens[i + 1]);
|
|
844
|
+
i++;
|
|
845
|
+
} else {
|
|
846
|
+
parsed.options.set(option.name, true);
|
|
847
|
+
}
|
|
848
|
+
} else {
|
|
849
|
+
parsed.arguments.push(token);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return parsed;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
tokenize(input: string): string[] {
|
|
857
|
+
const tokens: string[] = [];
|
|
858
|
+
let current = '';
|
|
859
|
+
let inQuotes = false;
|
|
860
|
+
let quoteChar = '';
|
|
861
|
+
|
|
862
|
+
for (let i = 0; i < input.length; i++) {
|
|
863
|
+
const char = input[i];
|
|
864
|
+
|
|
865
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
866
|
+
inQuotes = true;
|
|
867
|
+
quoteChar = char;
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if (char === quoteChar && inQuotes) {
|
|
872
|
+
inQuotes = false;
|
|
873
|
+
quoteChar = '';
|
|
874
|
+
if (current.trim()) {
|
|
875
|
+
tokens.push(current.trim());
|
|
876
|
+
current = '';
|
|
877
|
+
}
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (char === ' ' && !inQuotes) {
|
|
882
|
+
if (current.trim()) {
|
|
883
|
+
tokens.push(current.trim());
|
|
884
|
+
current = '';
|
|
885
|
+
}
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
current += char;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (current.trim()) {
|
|
893
|
+
tokens.push(current.trim());
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return tokens.filter((token) => token.length > 0);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
private isCommand(token: string): boolean {
|
|
900
|
+
if (this.commandRegistry.has(token)) return true;
|
|
901
|
+
if (this.commandRegistry.has(token.replace(/^--/, ''))) return true;
|
|
902
|
+
if (/^[a-z][a-z0-9-]*$/i.test(token)) return true;
|
|
903
|
+
return false;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
private isOption(token: string): boolean {
|
|
907
|
+
return token.startsWith('-') || token.startsWith('--');
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
private parseOption(token: string): ParsedOption {
|
|
911
|
+
if (token.startsWith('--')) {
|
|
912
|
+
const [name, value] = token.split('=');
|
|
913
|
+
return { name: name.substring(2), value: value || null, type: 'long' };
|
|
914
|
+
} else if (token.startsWith('-')) {
|
|
915
|
+
return { name: token.substring(1), value: null, type: 'short' };
|
|
916
|
+
}
|
|
917
|
+
return { name: token, value: null, type: 'unknown' };
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
generateSuggestions(input: string): string[] {
|
|
921
|
+
const suggestions: string[] = [];
|
|
922
|
+
const tokens = this.tokenize(input);
|
|
923
|
+
|
|
924
|
+
if (tokens.length === 0) {
|
|
925
|
+
suggestions.push('Try starting with a command like "help" or "create"');
|
|
926
|
+
return suggestions;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const firstToken = tokens[0];
|
|
930
|
+
|
|
931
|
+
if (!this.isCommand(firstToken)) {
|
|
932
|
+
const similarCommands = this.findSimilarCommands(firstToken);
|
|
933
|
+
if (similarCommands.length > 0) {
|
|
934
|
+
suggestions.push(`Did you mean: ${similarCommands.join(', ')}?`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
suggestions.push('Use "help" to see available commands');
|
|
939
|
+
suggestions.push('Use "help <command>" for specific command help');
|
|
940
|
+
|
|
941
|
+
return suggestions;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
private getSuggestionTerms(): string[] {
|
|
945
|
+
const terms = new Set<string>();
|
|
946
|
+
for (const key of this.commandRegistry.keys()) {
|
|
947
|
+
terms.add(key);
|
|
948
|
+
}
|
|
949
|
+
for (const [term, entry] of this.lexicon.entries()) {
|
|
950
|
+
terms.add(term);
|
|
951
|
+
for (const alias of entry.aliases ?? []) {
|
|
952
|
+
if (alias.trim()) terms.add(alias.trim());
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return Array.from(terms);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
findSimilarCommands(input: string, maxResults = 3): string[] {
|
|
959
|
+
const commands = this.getSuggestionTerms();
|
|
960
|
+
const similar: string[] = [];
|
|
961
|
+
|
|
962
|
+
for (const command of commands) {
|
|
963
|
+
const distance = this.levenshteinDistance(input.toLowerCase(), command.toLowerCase());
|
|
964
|
+
if (distance <= 2) {
|
|
965
|
+
similar.push(command);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return similar.slice(0, maxResults);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
private levenshteinDistance(str1: string, str2: string): number {
|
|
973
|
+
const matrix: number[][] = [];
|
|
974
|
+
|
|
975
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
976
|
+
matrix[i] = [i];
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
980
|
+
matrix[0][j] = j;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
984
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
985
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
986
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
987
|
+
} else {
|
|
988
|
+
matrix[i][j] = Math.min(
|
|
989
|
+
matrix[i - 1][j - 1] + 1,
|
|
990
|
+
matrix[i][j - 1] + 1,
|
|
991
|
+
matrix[i - 1][j] + 1,
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return matrix[str2.length][str1.length];
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
private defaultCommandHandler(
|
|
1001
|
+
command: string,
|
|
1002
|
+
options: Map<string, unknown>,
|
|
1003
|
+
args: string[],
|
|
1004
|
+
): CommandResult {
|
|
1005
|
+
return {
|
|
1006
|
+
success: true,
|
|
1007
|
+
message: `Command "${command}" executed successfully`,
|
|
1008
|
+
options,
|
|
1009
|
+
arguments: args,
|
|
1010
|
+
timestamp: new Date().toISOString(),
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
getCommandRegistry(): Map<string, RegisteredCommand> {
|
|
1015
|
+
return this.commandRegistry;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
getLanguagePatterns(): Map<string, Record<string, RegExp>> {
|
|
1019
|
+
return this.languagePatterns;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
getLexicon(): Map<string, LexiconEntry> {
|
|
1023
|
+
return this.lexicon;
|
|
1024
|
+
}
|
|
1025
|
+
}
|