@agentix-e/nl2spel 1.2.1 → 1.3.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 (76) hide show
  1. package/README.md +3 -1
  2. package/dist/index.cjs +3062 -0
  3. package/dist/index.d.cts +917 -0
  4. package/dist/index.d.ts +917 -28
  5. package/dist/index.js +3021 -24
  6. package/package.json +18 -7
  7. package/dist/context/context-extractor.d.ts +0 -86
  8. package/dist/context/context-extractor.d.ts.map +0 -1
  9. package/dist/context/context-extractor.js +0 -181
  10. package/dist/context/context-extractor.js.map +0 -1
  11. package/dist/context/schema-formatter.d.ts +0 -20
  12. package/dist/context/schema-formatter.d.ts.map +0 -1
  13. package/dist/context/schema-formatter.js +0 -104
  14. package/dist/context/schema-formatter.js.map +0 -1
  15. package/dist/engine/nl2spel-engine.d.ts +0 -145
  16. package/dist/engine/nl2spel-engine.d.ts.map +0 -1
  17. package/dist/engine/nl2spel-engine.js +0 -130
  18. package/dist/engine/nl2spel-engine.js.map +0 -1
  19. package/dist/index.d.ts.map +0 -1
  20. package/dist/index.js.map +0 -1
  21. package/dist/pattern/builtin-patterns.d.ts +0 -12
  22. package/dist/pattern/builtin-patterns.d.ts.map +0 -1
  23. package/dist/pattern/builtin-patterns.js +0 -812
  24. package/dist/pattern/builtin-patterns.js.map +0 -1
  25. package/dist/pattern/pattern-definition.d.ts +0 -49
  26. package/dist/pattern/pattern-definition.d.ts.map +0 -1
  27. package/dist/pattern/pattern-definition.js +0 -2
  28. package/dist/pattern/pattern-definition.js.map +0 -1
  29. package/dist/pattern/pattern-matcher.d.ts +0 -55
  30. package/dist/pattern/pattern-matcher.d.ts.map +0 -1
  31. package/dist/pattern/pattern-matcher.js +0 -216
  32. package/dist/pattern/pattern-matcher.js.map +0 -1
  33. package/dist/provider/llm-provider.d.ts +0 -146
  34. package/dist/provider/llm-provider.d.ts.map +0 -1
  35. package/dist/provider/llm-provider.js +0 -2
  36. package/dist/provider/llm-provider.js.map +0 -1
  37. package/dist/provider/provider-registry.d.ts +0 -28
  38. package/dist/provider/provider-registry.d.ts.map +0 -1
  39. package/dist/provider/provider-registry.js +0 -65
  40. package/dist/provider/provider-registry.js.map +0 -1
  41. package/dist/strategy/strategy-router.d.ts +0 -79
  42. package/dist/strategy/strategy-router.d.ts.map +0 -1
  43. package/dist/strategy/strategy-router.js +0 -182
  44. package/dist/strategy/strategy-router.js.map +0 -1
  45. package/dist/template/intent-classifier.d.ts +0 -56
  46. package/dist/template/intent-classifier.d.ts.map +0 -1
  47. package/dist/template/intent-classifier.js +0 -251
  48. package/dist/template/intent-classifier.js.map +0 -1
  49. package/dist/template/nl-intent.d.ts +0 -38
  50. package/dist/template/nl-intent.d.ts.map +0 -1
  51. package/dist/template/nl-intent.js +0 -39
  52. package/dist/template/nl-intent.js.map +0 -1
  53. package/dist/template/prompts/prompt-builder.d.ts +0 -20
  54. package/dist/template/prompts/prompt-builder.d.ts.map +0 -1
  55. package/dist/template/prompts/prompt-builder.js +0 -243
  56. package/dist/template/prompts/prompt-builder.js.map +0 -1
  57. package/dist/template/template-engine.d.ts +0 -27
  58. package/dist/template/template-engine.d.ts.map +0 -1
  59. package/dist/template/template-engine.js +0 -378
  60. package/dist/template/template-engine.js.map +0 -1
  61. package/dist/utils/chinese-number-parser.d.ts +0 -30
  62. package/dist/utils/chinese-number-parser.d.ts.map +0 -1
  63. package/dist/utils/chinese-number-parser.js +0 -92
  64. package/dist/utils/chinese-number-parser.js.map +0 -1
  65. package/dist/validation/auto-fixer.d.ts +0 -13
  66. package/dist/validation/auto-fixer.d.ts.map +0 -1
  67. package/dist/validation/auto-fixer.js +0 -110
  68. package/dist/validation/auto-fixer.js.map +0 -1
  69. package/dist/validation/self-correction-loop.d.ts +0 -46
  70. package/dist/validation/self-correction-loop.d.ts.map +0 -1
  71. package/dist/validation/self-correction-loop.js +0 -132
  72. package/dist/validation/self-correction-loop.js.map +0 -1
  73. package/dist/validation/validation-pipeline.d.ts +0 -81
  74. package/dist/validation/validation-pipeline.d.ts.map +0 -1
  75. package/dist/validation/validation-pipeline.js +0 -306
  76. package/dist/validation/validation-pipeline.js.map +0 -1
package/dist/index.cjs ADDED
@@ -0,0 +1,3062 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AutoFixer: () => AutoFixer,
24
+ BUILTIN_PATTERNS: () => BUILTIN_PATTERNS,
25
+ ChineseNumberParser: () => ChineseNumberParser,
26
+ ContextExtractor: () => ContextExtractor,
27
+ IntentClassifier: () => IntentClassifier,
28
+ NL2SpelEngine: () => NL2SpelEngine,
29
+ NLIntent: () => NLIntent,
30
+ PatternMatcher: () => PatternMatcher,
31
+ PromptBuilder: () => PromptBuilder,
32
+ ProviderRegistry: () => ProviderRegistry,
33
+ SchemaFormatter: () => SchemaFormatter,
34
+ SelfCorrectionLoop: () => SelfCorrectionLoop,
35
+ StrategyRouter: () => StrategyRouter,
36
+ TemplateEngine: () => TemplateEngine,
37
+ ValidationPipeline: () => ValidationPipeline
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/provider/provider-registry.ts
42
+ var ProviderRegistry = class {
43
+ _providers = [];
44
+ _nextIndex = 0;
45
+ /**
46
+ * Register a Provider.
47
+ * @param provider LLMProvider instance
48
+ * @param options.priority User-assigned priority (lower = preferred). Defaults to registration order.
49
+ */
50
+ register(provider, options) {
51
+ if (this._providers.some((p) => p.provider.name === provider.name)) {
52
+ throw new Error(`Provider '${provider.name}' already registered`);
53
+ }
54
+ this._providers.push({
55
+ provider,
56
+ priority: options?.priority ?? this._nextIndex,
57
+ index: this._nextIndex
58
+ });
59
+ this._nextIndex++;
60
+ }
61
+ /** Unregister a Provider */
62
+ unregister(name) {
63
+ this._providers = this._providers.filter((p) => p.provider.name !== name);
64
+ }
65
+ /** Get a Provider by name */
66
+ get(name) {
67
+ return this._providers.find((p) => p.provider.name === name)?.provider;
68
+ }
69
+ /**
70
+ * Get available Providers sorted by priority.
71
+ * Sort rule: offline first → user priority (asc) → registration order (asc)
72
+ */
73
+ async getPrioritized() {
74
+ const available = [];
75
+ for (const entry of this._providers) {
76
+ if (await entry.provider.isAvailable()) {
77
+ available.push(entry);
78
+ }
79
+ }
80
+ return available.sort((a, b) => {
81
+ const aOffline = a.provider.capabilities.offlineAvailable;
82
+ const bOffline = b.provider.capabilities.offlineAvailable;
83
+ if (aOffline && !bOffline) return -1;
84
+ if (!aOffline && bOffline) return 1;
85
+ if (a.priority !== b.priority) return a.priority - b.priority;
86
+ return a.index - b.index;
87
+ }).map((entry) => entry.provider);
88
+ }
89
+ /**
90
+ * Explicitly reorder providers by name.
91
+ * Providers not listed retain their position after the reordered ones.
92
+ */
93
+ reorder(providerNames) {
94
+ const orderMap = new Map(providerNames.map((name, i) => [name, i]));
95
+ const maxExisting = this._providers.reduce(
96
+ (max, p) => Math.max(max, p.priority),
97
+ providerNames.length - 1
98
+ );
99
+ for (const entry of this._providers) {
100
+ const explicitIndex = orderMap.get(entry.provider.name);
101
+ entry.priority = explicitIndex ?? maxExisting + entry.index + 1;
102
+ }
103
+ }
104
+ /** List all registered Providers */
105
+ list() {
106
+ return this._providers.map((p) => p.provider);
107
+ }
108
+ /** Number of registered Providers */
109
+ get count() {
110
+ return this._providers.length;
111
+ }
112
+ };
113
+
114
+ // src/context/context-extractor.ts
115
+ var ContextExtractor = class {
116
+ /**
117
+ * Extract ContextSchema from plain data structures (no spel-ts dependency)
118
+ */
119
+ extract(context) {
120
+ const schema = {
121
+ root: this.extractRoot(context),
122
+ variables: this.extractVariables(context),
123
+ beans: this.extractBeans(context),
124
+ types: this.extractTypes(context),
125
+ functions: this.extractFunctions(context)
126
+ };
127
+ return schema;
128
+ }
129
+ /**
130
+ * Extract root object schema
131
+ */
132
+ extractRoot(context) {
133
+ const { rootObject, rootName } = context;
134
+ if (rootObject === null || rootObject === void 0) return null;
135
+ const name = rootName ?? "root";
136
+ const type = typeof rootObject === "object" ? rootObject?.constructor?.name ?? "Object" : typeof rootObject;
137
+ const fields = this.extractFields(rootObject);
138
+ return { name, type, fields, methods: {} };
139
+ }
140
+ /**
141
+ * Extract object fields
142
+ */
143
+ /**
144
+ * Recursively extract fields from an object, following nested objects
145
+ * all the way to leaf values. Uses a visited set to prevent circular references.
146
+ */
147
+ extractFields(obj, visited = /* @__PURE__ */ new WeakSet()) {
148
+ if (obj === null || obj === void 0 || typeof obj !== "object") {
149
+ return {};
150
+ }
151
+ if (visited.has(obj)) return {};
152
+ visited.add(obj);
153
+ const fields = {};
154
+ for (const key of Object.keys(obj)) {
155
+ try {
156
+ const value = obj[key];
157
+ const fieldType = this.inferSpelType(value);
158
+ const isArray = Array.isArray(value);
159
+ const isObject = !isArray && typeof value === "object" && value !== null && !(value instanceof Date);
160
+ const field = {
161
+ type: fieldType,
162
+ description: this.generateDescription(key, fieldType),
163
+ isCollection: isArray,
164
+ elementType: isArray && value.length > 0 ? typeof value[0] : void 0,
165
+ nullable: value === null,
166
+ example: this.safeExample(value)
167
+ };
168
+ if (isObject) {
169
+ field.fields = this.extractFields(value, visited);
170
+ }
171
+ fields[key] = field;
172
+ } catch {
173
+ }
174
+ }
175
+ return fields;
176
+ }
177
+ /**
178
+ * Infer SpEL type
179
+ */
180
+ inferSpelType(value) {
181
+ if (value === null || value === void 0) return "string";
182
+ if (typeof value === "string") return "string";
183
+ if (typeof value === "number") return "number";
184
+ if (typeof value === "boolean") return "boolean";
185
+ if (value instanceof Date) return "date";
186
+ if (Array.isArray(value)) return "array";
187
+ if (typeof value === "object") return "object";
188
+ return "string";
189
+ }
190
+ /**
191
+ * Generate field description
192
+ */
193
+ generateDescription(key, type) {
194
+ return `Field '${key}' of type ${type}`;
195
+ }
196
+ /**
197
+ * Safely get example value
198
+ */
199
+ safeExample(value) {
200
+ if (value === null) return null;
201
+ if (typeof value === "object") return void 0;
202
+ return value;
203
+ }
204
+ /**
205
+ * Extract variables
206
+ */
207
+ extractVariables(context) {
208
+ const variables = {};
209
+ if (!context.variables) return variables;
210
+ for (const [name, vdef] of Object.entries(context.variables)) {
211
+ variables[name] = {
212
+ type: vdef.type,
213
+ description: vdef.description ?? `Variable '#${name}'`,
214
+ nullable: vdef.nullable ?? false,
215
+ value: vdef.value
216
+ };
217
+ }
218
+ return variables;
219
+ }
220
+ /**
221
+ * Extract beans
222
+ */
223
+ extractBeans(context) {
224
+ const beans = {};
225
+ if (!context.beans) return beans;
226
+ for (const bdef of context.beans) {
227
+ beans[bdef.name] = {
228
+ type: bdef.type,
229
+ description: bdef.description,
230
+ singleton: bdef.singleton
231
+ };
232
+ }
233
+ return beans;
234
+ }
235
+ /**
236
+ * Extract types
237
+ */
238
+ extractTypes(context) {
239
+ const types = {};
240
+ if (!context.types) return types;
241
+ for (const tdef of context.types) {
242
+ types[tdef.name] = {
243
+ className: tdef.className,
244
+ description: tdef.description
245
+ };
246
+ }
247
+ return types;
248
+ }
249
+ /**
250
+ * Extract functions
251
+ */
252
+ extractFunctions(context) {
253
+ const functions = {};
254
+ if (!context.functions) return functions;
255
+ for (const fdef of context.functions) {
256
+ functions[fdef.name] = {
257
+ returnType: fdef.returnType,
258
+ params: fdef.params,
259
+ description: fdef.description
260
+ };
261
+ }
262
+ return functions;
263
+ }
264
+ };
265
+
266
+ // src/context/schema-formatter.ts
267
+ var SchemaFormatter = class {
268
+ /**
269
+ * LLM-optimized format — compact, injected into the User part of the prompt
270
+ */
271
+ formatForLLM(schema) {
272
+ const lines = ["## Context Schema"];
273
+ if (schema.root) {
274
+ lines.push("");
275
+ lines.push(`Root Object (accessible as #${schema.root.name}):`);
276
+ lines.push(` Type: ${schema.root.type}`);
277
+ lines.push(` Fields:`);
278
+ for (const [name, field] of Object.entries(schema.root.fields)) {
279
+ lines.push(this.formatFieldForLLM(name, field, " "));
280
+ }
281
+ }
282
+ if (Object.keys(schema.variables).length > 0) {
283
+ lines.push("");
284
+ lines.push(`Variables:`);
285
+ for (const [name, varDef] of Object.entries(schema.variables)) {
286
+ lines.push(
287
+ ` #${name}: ${varDef.type}` + (varDef.description ? ` (${varDef.description})` : "")
288
+ );
289
+ }
290
+ }
291
+ if (Object.keys(schema.beans).length > 0) {
292
+ lines.push("");
293
+ lines.push(`Beans:`);
294
+ for (const [name, beanDef] of Object.entries(schema.beans)) {
295
+ lines.push(
296
+ ` @${name}: ${beanDef.type}` + (beanDef.description ? ` (${beanDef.description})` : "")
297
+ );
298
+ }
299
+ }
300
+ if (Object.keys(schema.types).length > 0) {
301
+ lines.push("");
302
+ lines.push(`Types (accessible via T(typeName)):`);
303
+ for (const [name, typeDef] of Object.entries(schema.types)) {
304
+ lines.push(` ${name}` + (typeDef.description ? `: ${typeDef.description}` : ""));
305
+ }
306
+ }
307
+ if (Object.keys(schema.functions).length > 0) {
308
+ lines.push("");
309
+ lines.push(`Functions:`);
310
+ for (const [name, funcDef] of Object.entries(schema.functions)) {
311
+ const params = funcDef.params.map((p) => `${p.name}: ${p.type}`).join(", ");
312
+ lines.push(` #${name}(${params}): ${funcDef.returnType}`);
313
+ }
314
+ }
315
+ lines.push("");
316
+ return lines.join("\n");
317
+ }
318
+ /**
319
+ * Human-readable format
320
+ */
321
+ formatForHuman(schema) {
322
+ const lines = [
323
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
324
+ " Context Schema (Human-Readable)",
325
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"
326
+ ];
327
+ if (schema.root) {
328
+ lines.push("");
329
+ lines.push(`\u250C\u2500 Root: #${schema.root.name}`);
330
+ lines.push(`\u2502 Type: ${schema.root.type}`);
331
+ lines.push(`\u2502 Fields:`);
332
+ const entries = Object.entries(schema.root.fields);
333
+ for (let i = 0; i < entries.length; i++) {
334
+ const [name, field] = entries[i];
335
+ const prefix = i < entries.length - 1 ? "\u251C\u2500" : "\u2514\u2500";
336
+ lines.push(
337
+ `\u2502 ${prefix} ${name} (${field.type})` + (field.description ? ` \u2014 ${field.description}` : "") + (field.nullable ? " | nullable" : "") + (field.isCollection ? ` | collection<${field.elementType ?? "unknown"}>` : "")
338
+ );
339
+ }
340
+ lines.push(`\u2502 \u2514\u2500 (${entries.length} fields total)`);
341
+ }
342
+ return lines.join("\n");
343
+ }
344
+ formatFieldForLLM(name, field, indent) {
345
+ const parts = [`${indent}- ${name}: ${field.type}`];
346
+ if (field.description) {
347
+ parts.push(`(${field.description})`);
348
+ }
349
+ if (field.isCollection) {
350
+ parts.push(`[array of ${field.elementType ?? "unknown"}]`);
351
+ }
352
+ if (field.nullable) {
353
+ parts.push(`[nullable]`);
354
+ }
355
+ if (field.example !== void 0) {
356
+ parts.push(`(e.g. ${JSON.stringify(field.example)})`);
357
+ }
358
+ return parts.join(" ");
359
+ }
360
+ };
361
+
362
+ // src/utils/chinese-number-parser.ts
363
+ var ChineseNumberParser = class {
364
+ static DIGITS = {
365
+ \u96F6: 0,
366
+ \u4E00: 1,
367
+ \u4E8C: 2,
368
+ \u4E09: 3,
369
+ \u56DB: 4,
370
+ \u4E94: 5,
371
+ \u516D: 6,
372
+ \u4E03: 7,
373
+ \u516B: 8,
374
+ \u4E5D: 9,
375
+ \u4E24: 2
376
+ };
377
+ static UNITS = {
378
+ \u5341: 10,
379
+ \u767E: 100,
380
+ \u5343: 1e3,
381
+ \u4E07: 1e4,
382
+ \u4EBF: 1e8
383
+ };
384
+ static CHINESE_NUM_PATTERN = /^[零一二三四五六七八九十百千万亿两]+$/;
385
+ /**
386
+ * Check if a string is pure Chinese numbers
387
+ */
388
+ static isChineseNumber(text) {
389
+ return this.CHINESE_NUM_PATTERN.test(text);
390
+ }
391
+ /**
392
+ * Parse Chinese numbers → Arabic numerals
393
+ *
394
+ * @param text Chinese number string
395
+ * @returns Parsed number, returns NaN on failure
396
+ */
397
+ static parse(text) {
398
+ if (!this.isChineseNumber(text)) {
399
+ return NaN;
400
+ }
401
+ let s = text;
402
+ if (s.startsWith("\u5341")) {
403
+ s = "\u4E00" + s;
404
+ }
405
+ let result = 0;
406
+ let section = 0;
407
+ let current = 0;
408
+ for (const ch of s) {
409
+ const digit = this.DIGITS[ch];
410
+ if (digit !== void 0) {
411
+ current = digit;
412
+ } else {
413
+ const unit = this.UNITS[ch];
414
+ if (unit === void 0) continue;
415
+ if (unit >= 1e4) {
416
+ result += (section + current || 1) * unit;
417
+ section = 0;
418
+ current = 0;
419
+ } else {
420
+ section += (current || 1) * unit;
421
+ current = 0;
422
+ }
423
+ }
424
+ }
425
+ return result + section + current;
426
+ }
427
+ /**
428
+ * Safe parse: returns null if parsing fails
429
+ */
430
+ static parseSafe(text) {
431
+ const value = this.parse(text);
432
+ return Number.isNaN(value) ? null : value;
433
+ }
434
+ };
435
+
436
+ // src/pattern/pattern-matcher.ts
437
+ var PatternMatcher = class {
438
+ _patterns;
439
+ constructor(patterns = []) {
440
+ this._patterns = [...patterns];
441
+ this.sortByPriority();
442
+ }
443
+ get patternCount() {
444
+ return this._patterns.length;
445
+ }
446
+ register(pattern) {
447
+ this._patterns.push(pattern);
448
+ this.sortByPriority();
449
+ }
450
+ registerAll(patterns) {
451
+ this._patterns.push(...patterns);
452
+ this.sortByPriority();
453
+ }
454
+ sortByPriority() {
455
+ this._patterns.sort((a, b) => b.priority - a.priority);
456
+ }
457
+ /**
458
+ * Match natural language input, return the best match
459
+ */
460
+ match(nl) {
461
+ const startTime = Date.now();
462
+ const normalized = this.normalize(nl);
463
+ for (const pattern of this._patterns) {
464
+ const matchResult = pattern.match.exec(normalized);
465
+ if (!matchResult) continue;
466
+ const slots = {};
467
+ if (matchResult.groups) {
468
+ for (const [key, value] of Object.entries(matchResult.groups)) {
469
+ if (value !== void 0) {
470
+ slots[key] = value;
471
+ }
472
+ }
473
+ }
474
+ const spel = this.fillTemplate(pattern, slots, normalized, matchResult);
475
+ const latencyMs2 = Date.now() - startTime;
476
+ return { matched: true, pattern, spel, confidence: pattern.confidence, latencyMs: latencyMs2, slots };
477
+ }
478
+ const latencyMs = Date.now() - startTime;
479
+ return { matched: false, confidence: 0, latencyMs };
480
+ }
481
+ /**
482
+ * Batch match
483
+ */
484
+ matchAll(nl, maxResults = 5) {
485
+ const normalized = this.normalize(nl);
486
+ const results = [];
487
+ for (const pattern of this._patterns) {
488
+ if (results.length >= maxResults) break;
489
+ const matchResult = pattern.match.exec(normalized);
490
+ if (!matchResult) continue;
491
+ const slots = {};
492
+ if (matchResult.groups) {
493
+ for (const [key, value] of Object.entries(matchResult.groups)) {
494
+ if (value !== void 0) slots[key] = value;
495
+ }
496
+ }
497
+ results.push({
498
+ matched: true,
499
+ pattern,
500
+ spel: this.fillTemplate(pattern, slots, normalized, matchResult),
501
+ confidence: pattern.confidence,
502
+ latencyMs: 0,
503
+ slots
504
+ });
505
+ }
506
+ return results;
507
+ }
508
+ /**
509
+ * Input normalization
510
+ */
511
+ normalize(input) {
512
+ return input.trim().replace(/[\uFF01-\uFF5E]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 65248)).replace(/\s+/g, " ").replace(/[,,。.!!??;;::]+$/, "");
513
+ }
514
+ /**
515
+ * Infer SpEL root object name from Chinese input
516
+ */
517
+ inferRoot(input) {
518
+ if (/^(?:订单|order)/i.test(input)) return "order";
519
+ if (/^(?:用户|user)/i.test(input)) return "user";
520
+ if (/^(?:文件|file)/i.test(input)) return "file";
521
+ if (/^(?:账号|account)/i.test(input)) return "account";
522
+ if (/^(?:商品|product|item)/i.test(input)) return "product";
523
+ return "order";
524
+ }
525
+ /**
526
+ * Extract Chinese field names from input and map to SpEL fields
527
+ */
528
+ extractChineseField(input) {
529
+ const m = input.match(/^[^\s,,、]+/);
530
+ if (!m) return "value";
531
+ const first = m[0];
532
+ const cnMap = {
533
+ \u5907\u6CE8: "remark",
534
+ \u8BF4\u660E: "description",
535
+ \u63CF\u8FF0: "description",
536
+ \u91D1\u989D: "amount",
537
+ \u6570\u91CF: "count",
538
+ \u4E2A\u6570: "count",
539
+ \u72B6\u6001: "status",
540
+ \u7C7B\u578B: "type",
541
+ \u540D\u79F0: "name",
542
+ \u6807\u9898: "title",
543
+ \u5730\u5740: "address",
544
+ \u90AE\u7BB1: "email",
545
+ \u624B\u673A: "phone",
546
+ \u7535\u8BDD: "phone",
547
+ \u65E5\u671F: "date",
548
+ \u65F6\u95F4: "time",
549
+ \u5E74\u9F84: "age",
550
+ \u4EF7\u683C: "price",
551
+ \u7528\u6237\u540D: "name",
552
+ \u6743\u9650: "role",
553
+ \u6807\u7B7E: "tags",
554
+ \u5217\u8868: "list",
555
+ \u6570\u7EC4: "items",
556
+ \u6587\u4EF6: "file",
557
+ \u6587\u4EF6\u540D: "name",
558
+ \u8FC7\u671F: "expiryDate",
559
+ \u521B\u5EFA: "createdAt",
560
+ \u6709\u6548: "valid",
561
+ \u6D3B\u8DC3: "active",
562
+ \u6FC0\u6D3B: "active"
563
+ };
564
+ return cnMap[first] ?? first;
565
+ }
566
+ /**
567
+ * Template filling and value transformation
568
+ */
569
+ fillTemplate(pattern, slots, originalInput, _matchResult) {
570
+ let result = pattern.spelTemplate;
571
+ const hasFieldSlot = "field" in slots;
572
+ const field = hasFieldSlot ? this.inferFieldFromCapture(slots["field"]) : this.extractChineseField(originalInput);
573
+ const root = this.inferRoot(originalInput);
574
+ result = result.replace(/\{field\}/g, field);
575
+ result = result.replace(/\{root\}/g, root);
576
+ if (originalInput.includes(">=") || originalInput.includes("\u2265")) {
577
+ result = result.replace(/\{op\}/g, ">=");
578
+ } else if (originalInput.includes("<=") || originalInput.includes("\u2264")) {
579
+ result = result.replace(/\{op\}/g, "<=");
580
+ } else if (originalInput.includes("!=") || originalInput.includes("\u2260")) {
581
+ result = result.replace(/\{op\}/g, "!=");
582
+ } else if (originalInput.includes(">") || originalInput.includes("\u5927\u4E8E") || originalInput.includes("\u8D85\u8FC7")) {
583
+ result = result.replace(/\{op\}/g, ">");
584
+ } else if (originalInput.includes("<") || originalInput.includes("\u5C0F\u4E8E") || originalInput.includes("\u4F4E\u4E8E")) {
585
+ result = result.replace(/\{op\}/g, "<");
586
+ } else {
587
+ result = result.replace(/\{op\}/g, ">");
588
+ }
589
+ for (const [key, value] of Object.entries(slots)) {
590
+ if (key === "field") continue;
591
+ const def = pattern.slots[key];
592
+ let transformedValue = value;
593
+ if (def?.transform === "toNumber") {
594
+ const cnNumber = ChineseNumberParser.parseSafe(value);
595
+ transformedValue = cnNumber !== null ? String(cnNumber) : value;
596
+ }
597
+ result = result.replace(`{${key}}`, transformedValue ?? "");
598
+ }
599
+ result = result.replace(/\{[a-zA-Z_]+\}/g, "");
600
+ return result.trim();
601
+ }
602
+ /**
603
+ * Infer SpEL field name from capture group
604
+ */
605
+ inferFieldFromCapture(captured) {
606
+ if (/^[a-zA-Z_]\w*$/.test(captured)) return captured;
607
+ return this.extractChineseField(captured);
608
+ }
609
+ };
610
+
611
+ // src/pattern/builtin-patterns.ts
612
+ var BUILTIN_PATTERNS = [
613
+ // ================================================================
614
+ // P93+: Pure English numeric comparison (covers "amount > 500" etc.)
615
+ // ================================================================
616
+ {
617
+ id: "EN-CMP-GT",
618
+ match: /^(?<field>\w+)\s*>\s*(?<value>\d+(?:\.\d+)?)/,
619
+ spelTemplate: "#{field} > {value}",
620
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
621
+ priority: 93,
622
+ tags: ["comparison", "gt", "english"],
623
+ examples: [{ nl: "amount > 500", spel: "#amount > 500" }],
624
+ difficulty: "easy",
625
+ confidence: 0.98
626
+ },
627
+ {
628
+ id: "EN-CMP-LT",
629
+ match: /^(?<field>\w+)\s*(?:less\s+than|lower\s+than|<)\s*(?<value>\d+(?:\.\d+)?)/i,
630
+ spelTemplate: "#{field} < {value}",
631
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
632
+ priority: 94,
633
+ tags: ["comparison", "lt", "english"],
634
+ examples: [
635
+ { nl: "amount less than 100", spel: "#amount < 100" },
636
+ { nl: "price lower than 50", spel: "#price < 50" }
637
+ ],
638
+ difficulty: "easy",
639
+ confidence: 0.98
640
+ },
641
+ {
642
+ id: "EN-CMP-GE",
643
+ match: /^(?<field>\w+)\s*>=\s*(?<value>\d+(?:\.\d+)?)/,
644
+ spelTemplate: "#{field} >= {value}",
645
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
646
+ priority: 93,
647
+ tags: ["comparison", "ge", "english"],
648
+ examples: [{ nl: "amount >= 50", spel: "#amount >= 50" }],
649
+ difficulty: "easy",
650
+ confidence: 0.98
651
+ },
652
+ {
653
+ id: "EN-CMP-LE",
654
+ match: /^(?<field>\w+)\s*<=\s*(?<value>\d+(?:\.\d+)?)/,
655
+ spelTemplate: "#{field} <= {value}",
656
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
657
+ priority: 93,
658
+ tags: ["comparison", "le", "english"],
659
+ examples: [{ nl: "amount <= 200", spel: "#amount <= 200" }],
660
+ difficulty: "easy",
661
+ confidence: 0.98
662
+ },
663
+ // ================================================================
664
+ // Group 1: Null / Empty — pri 97-96 (highest specificity)
665
+ // ================================================================
666
+ {
667
+ id: "CN-NULL-NOT",
668
+ match: /^(?<field>\S+?)\s*(?:不为空|不是空的|有值|存在|不为null)$/,
669
+ spelTemplate: "#{field} != null",
670
+ slots: {},
671
+ priority: 98,
672
+ tags: ["null", "isNotNull"],
673
+ examples: [{ nl: "\u5907\u6CE8\u4E0D\u4E3A\u7A7A", spel: "#\u5907\u6CE8 != null" }],
674
+ difficulty: "easy",
675
+ confidence: 0.98
676
+ },
677
+ {
678
+ id: "CN-NULL-IS",
679
+ match: /^(?<field>\S+?)\s*(?:为空|是空的|不存在|无值|为null)$/,
680
+ spelTemplate: "#{field} == null",
681
+ slots: {},
682
+ priority: 97,
683
+ tags: ["null", "isNull"],
684
+ examples: [{ nl: "\u5907\u6CE8\u4E3A\u7A7A", spel: "#\u5907\u6CE8 == null" }],
685
+ difficulty: "easy",
686
+ confidence: 0.98
687
+ },
688
+ {
689
+ id: "EN-NULL-IS",
690
+ match: /^(?<field>\w+)\s+is\s+(?:null|empty)$/i,
691
+ spelTemplate: "#{field} == null",
692
+ slots: {},
693
+ priority: 96,
694
+ tags: ["null", "isNull", "english"],
695
+ examples: [{ nl: "remark is null", spel: "#remark == null" }],
696
+ difficulty: "easy",
697
+ confidence: 0.98
698
+ },
699
+ {
700
+ id: "EN-NULL-NOT",
701
+ match: /^(?<field>\w+)\s+is\s+not\s+(?:null|empty)$/i,
702
+ spelTemplate: "#{field} != null",
703
+ slots: {},
704
+ priority: 96,
705
+ tags: ["null", "isNotNull", "english"],
706
+ examples: [{ nl: "remark is not null", spel: "#remark != null" }],
707
+ difficulty: "easy",
708
+ confidence: 0.98
709
+ },
710
+ // ================================================================
711
+ // Group 2: Chinese numeric comparison — pri 93
712
+ // ================================================================
713
+ {
714
+ id: "CN-CMP-GE",
715
+ match: /^(?<field>[^\s,,、]+?)\s*(?:金额|值)?\s*(?:不小于|不低于|大于等于|>=)\s*(?<value>\d+(?:\.\d+)?)/,
716
+ spelTemplate: "#{field} >= {value}",
717
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
718
+ priority: 93,
719
+ tags: ["comparison", "ge", "chinese"],
720
+ examples: [{ nl: "\u91D1\u989D\u4E0D\u5C0F\u4E8E100", spel: "#\u91D1\u989D >= 100" }],
721
+ difficulty: "easy",
722
+ confidence: 0.95
723
+ },
724
+ {
725
+ id: "CN-CMP-LE",
726
+ match: /^(?<field>[^\s,,、]+?)\s*(?:金额|值)?\s*(?:不大于|不超过|小于等于|<=)\s*(?<value>\d+(?:\.\d+)?)/,
727
+ spelTemplate: "#{field} <= {value}",
728
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
729
+ priority: 93,
730
+ tags: ["comparison", "le", "chinese"],
731
+ examples: [{ nl: "\u91D1\u989D\u4E0D\u8D85\u8FC7500", spel: "#\u91D1\u989D <= 500" }],
732
+ difficulty: "easy",
733
+ confidence: 0.95
734
+ },
735
+ {
736
+ id: "CN-CMP-GT",
737
+ match: /^(?<field>[^\s,,、]+?)\s*(?:金额|值|数量|价格)?\s*(?:大于|超过|高于)\s*(?<value>\d+(?:\.\d+)?)/,
738
+ spelTemplate: "#{field} > {value}",
739
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
740
+ priority: 92,
741
+ tags: ["comparison", "gt", "chinese"],
742
+ examples: [{ nl: "\u8BA2\u5355\u91D1\u989D\u5927\u4E8E1000", spel: "#\u8BA2\u5355\u91D1\u989D > 1000" }],
743
+ difficulty: "easy",
744
+ confidence: 0.95
745
+ },
746
+ {
747
+ id: "CN-CMP-LT",
748
+ match: /^(?<field>[^\s,,、]+?)\s*(?:金额|值|数量|价格)?\s*(?:小于|低于|不到)\s*(?<value>\d+(?:\.\d+)?)/,
749
+ spelTemplate: "#{field} < {value}",
750
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
751
+ priority: 92,
752
+ tags: ["comparison", "lt", "chinese"],
753
+ examples: [{ nl: "\u8BA2\u5355\u91D1\u989D\u5C0F\u4E8E500", spel: "#\u8BA2\u5355\u91D1\u989D < 500" }],
754
+ difficulty: "easy",
755
+ confidence: 0.95
756
+ },
757
+ // ================================================================
758
+ // Group 3: String/status equality — pri 91
759
+ // ================================================================
760
+ {
761
+ id: "CN-EQ-STATUS",
762
+ match: /^(?<field>[^\s,,、]+?)\s*(?:状态|类型)?\s*(?:等于|是|为)\s*(?<value>[^\s,,、]+)$/,
763
+ spelTemplate: "#{field} == '{value}'",
764
+ slots: { value: { key: "value", type: "string" } },
765
+ priority: 85,
766
+ tags: ["comparison", "eq", "string"],
767
+ examples: [{ nl: "\u8BA2\u5355\u72B6\u6001\u662F\u5DF2\u53D1\u8D27", spel: "#\u8BA2\u5355\u72B6\u6001 == '\u5DF2\u53D1\u8D27'" }],
768
+ difficulty: "easy",
769
+ confidence: 0.95
770
+ },
771
+ {
772
+ id: "EN-EQ-STRING",
773
+ match: /\b(?<field>\w+)\s+(?:equals?|==)\s+(?<value>[a-zA-Z_]\w*)/i,
774
+ spelTemplate: "#{field} == '{value}'",
775
+ slots: { value: { key: "value", type: "string" } },
776
+ priority: 91,
777
+ tags: ["comparison", "eq", "string", "english"],
778
+ examples: [{ nl: "status equals completed", spel: "#status == 'completed'" }],
779
+ difficulty: "easy",
780
+ confidence: 0.95
781
+ },
782
+ // CN: "order status is not cancelled" — must be before NOT pattern
783
+ {
784
+ id: "CN-NE-STATUS",
785
+ match: /^(?<field>[^\s,,、]+?)\s*(?:状态|类型)?\s*(?:不等于|!=|不是)\s*(?<value>[^\s,,、]+)$/,
786
+ spelTemplate: "#{field} != '{value}'",
787
+ slots: { value: { key: "value", type: "string" } },
788
+ priority: 91,
789
+ tags: ["comparison", "ne", "string"],
790
+ examples: [{ nl: "\u8BA2\u5355\u72B6\u6001\u4E0D\u662F\u5DF2\u53D6\u6D88", spel: "#\u8BA2\u5355\u72B6\u6001 != '\u5DF2\u53D6\u6D88'" }],
791
+ difficulty: "easy",
792
+ confidence: 0.95
793
+ },
794
+ {
795
+ id: "EN-NE-STRING",
796
+ match: /\b(?<field>\w+)\s*!=\s*(?<value>[a-zA-Z_]\w*)/i,
797
+ spelTemplate: "#{field} != '{value}'",
798
+ slots: { value: { key: "value", type: "string" } },
799
+ priority: 91,
800
+ tags: ["comparison", "ne", "string", "english"],
801
+ examples: [{ nl: "status != cancelled", spel: "#status != 'cancelled'" }],
802
+ difficulty: "easy",
803
+ confidence: 0.95
804
+ },
805
+ // CN/EN: count equality
806
+ {
807
+ id: "CN-EQ-COUNT",
808
+ match: /^(?<field>[^\s,,、]+?)\s*(?:数量|个数|计数)?\s*(?:等于|==)\s*(?<value>\d+)/,
809
+ spelTemplate: "#{field} == {value}",
810
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
811
+ priority: 91,
812
+ tags: ["comparison", "eq", "number"],
813
+ examples: [{ nl: "\u6570\u91CF\u7B49\u4E8E5", spel: "#\u6570\u91CF == 5" }],
814
+ difficulty: "easy",
815
+ confidence: 0.95
816
+ },
817
+ {
818
+ id: "EN-EQ-NUMBER",
819
+ match: /\b(?<field>\w+)\s+(?:equals?|==)\s+(?<value>\d+)/i,
820
+ spelTemplate: "#{field} == {value}",
821
+ slots: { value: { key: "value", type: "number", transform: "toNumber" } },
822
+ priority: 91,
823
+ tags: ["comparison", "eq", "number", "english"],
824
+ examples: [{ nl: "count equals 10", spel: "#count == 10" }],
825
+ difficulty: "easy",
826
+ confidence: 0.95
827
+ },
828
+ // ================================================================
829
+ // Group 4: Permission checks — pri 88
830
+ // ================================================================
831
+ {
832
+ id: "CN-PERM-ROLE",
833
+ match: /^(?<field>[^\s,,、]+?)\s*(?:是|拥有|有)\s*(?<role>[^\s,,、]+?)(?:角色|权限)?$/,
834
+ spelTemplate: "hasRole('{role}')",
835
+ slots: { role: { key: "role", type: "string" } },
836
+ priority: 86,
837
+ tags: ["permission", "hasRole"],
838
+ examples: [{ nl: "\u7528\u6237\u662F\u7BA1\u7406\u5458", spel: "hasRole('\u7BA1\u7406\u5458')" }],
839
+ difficulty: "easy",
840
+ confidence: 0.9
841
+ },
842
+ {
843
+ id: "EN-PERM-ROLE",
844
+ match: /\buser\s+(?:has|is)\s+(?<role>[a-zA-Z_]+)\s*(?:role)?$/i,
845
+ spelTemplate: "hasRole('{role}')",
846
+ slots: { role: { key: "role", type: "string" } },
847
+ priority: 88,
848
+ tags: ["permission", "hasRole", "english"],
849
+ examples: [{ nl: "user has admin role", spel: "hasRole('admin')" }],
850
+ difficulty: "easy",
851
+ confidence: 0.9
852
+ },
853
+ {
854
+ id: "CN-PERM-PERM",
855
+ match: /^(?<field>[^\s,,、]+)\s*(?:可以|能够|允许|may|can)\s+(?<permission>.+)$/i,
856
+ spelTemplate: "hasPermission('{permission}')",
857
+ slots: { permission: { key: "permission", type: "string" } },
858
+ priority: 88,
859
+ tags: ["permission", "hasPermission"],
860
+ examples: [{ nl: "\u7528\u6237\u53EF\u4EE5\u5220\u9664\u8BA2\u5355", spel: "hasPermission('\u5220\u9664\u8BA2\u5355')" }],
861
+ difficulty: "easy",
862
+ confidence: 0.9
863
+ },
864
+ {
865
+ id: "EN-PERM-PERM",
866
+ match: /\buser\s+(?:can|may|is\s+allowed\s+to)\s+(?<permission>\w+)/i,
867
+ spelTemplate: "hasPermission('{permission}')",
868
+ slots: { permission: { key: "permission", type: "string" } },
869
+ priority: 88,
870
+ tags: ["permission", "hasPermission", "english"],
871
+ examples: [{ nl: "user can delete", spel: "hasPermission('delete')" }],
872
+ difficulty: "easy",
873
+ confidence: 0.9
874
+ },
875
+ // ================================================================
876
+ // Group 5: Collection operations — pri 85-89
877
+ // ================================================================
878
+ {
879
+ id: "CN-COLL-EMPTY",
880
+ match: /^(?<field>[^\s,,、]+?)\s*(?:列表|数组|集合)?\s*(?:为空|是空的|没有|无)\s*(?:元素|数据|项)?$/,
881
+ spelTemplate: "#{field}.isEmpty()",
882
+ slots: {},
883
+ priority: 89,
884
+ tags: ["collection", "isEmpty"],
885
+ examples: [{ nl: "\u8BA2\u5355\u5217\u8868\u4E3A\u7A7A", spel: "#\u8BA2\u5355\u5217\u8868.isEmpty()" }],
886
+ difficulty: "easy",
887
+ confidence: 0.98
888
+ },
889
+ {
890
+ id: "EN-COLL-EMPTY",
891
+ match: /^(?<field>\w+)\s+is\s+empty$/i,
892
+ spelTemplate: "#{field}.isEmpty()",
893
+ slots: {},
894
+ priority: 88,
895
+ tags: ["collection", "isEmpty", "english"],
896
+ examples: [{ nl: "items is empty", spel: "#items.isEmpty()" }],
897
+ difficulty: "easy",
898
+ confidence: 0.98
899
+ },
900
+ {
901
+ id: "CN-COLL-NOTEMPTY",
902
+ match: /^(?<field>[^\s,,、]+?)\s*(?:列表|数组|集合)?\s*(?:不为空|有)\s*(?:元素|数据|项)?$/,
903
+ spelTemplate: "!#{field}.isEmpty()",
904
+ slots: {},
905
+ priority: 89,
906
+ tags: ["collection", "isNotEmpty"],
907
+ examples: [{ nl: "\u8BA2\u5355\u5217\u8868\u6709\u6570\u636E", spel: "!#\u8BA2\u5355\u5217\u8868.isEmpty()" }],
908
+ difficulty: "easy",
909
+ confidence: 0.95
910
+ },
911
+ {
912
+ id: "EN-COLL-NOTEMPTY",
913
+ match: /^(?<field>\w+)\s+(?:has\s+items|is\s+not\s+empty)$/i,
914
+ spelTemplate: "!#{field}.isEmpty()",
915
+ slots: {},
916
+ priority: 86,
917
+ tags: ["collection", "isNotEmpty", "english"],
918
+ examples: [{ nl: "items is not empty", spel: "!#items.isEmpty()" }],
919
+ difficulty: "easy",
920
+ confidence: 0.95
921
+ },
922
+ {
923
+ id: "CN-COLL-CONTAINS",
924
+ match: /^(?<field>[^\s,,、]+?)\s*(?:列表|数组|集合)?\s*(?:中\s*)?(?:包含|含有|有)\s*(?<element>[^\s,,、]+)/,
925
+ spelTemplate: "#{field}.contains('{element}')",
926
+ slots: { element: { key: "element", type: "string" } },
927
+ priority: 87,
928
+ tags: ["collection", "contains"],
929
+ examples: [{ nl: "\u6807\u7B7E\u5217\u8868\u4E2D\u5305\u542BVIP", spel: "#\u6807\u7B7E\u5217\u8868.contains('VIP')" }],
930
+ difficulty: "easy",
931
+ confidence: 0.95
932
+ },
933
+ {
934
+ id: "EN-COLL-CONTAINS",
935
+ match: /\b(?<field>\w+)\s+(?:contains?|includes?)\s+(?<element>\S+)/i,
936
+ spelTemplate: "#{field}.contains('{element}')",
937
+ slots: { element: { key: "element", type: "string" } },
938
+ priority: 86,
939
+ tags: ["collection", "contains", "english"],
940
+ examples: [{ nl: "tags contains premium", spel: "#tags.contains('premium')" }],
941
+ difficulty: "easy",
942
+ confidence: 0.95
943
+ },
944
+ {
945
+ id: "CN-COLL-SIZE",
946
+ match: /^(?<field>[^\s,,、]+?)\s*(?:列表|数组|集合)?\s*(?:数量|个数|大小|长度)\s*(?<op>大于|>|超过|小于|<|等于|==)\s*(?<value>\d+)/,
947
+ spelTemplate: "#{field}.size() {op} {value}",
948
+ slots: {
949
+ value: { key: "value", type: "number", transform: "toNumber" },
950
+ op: { key: "op", type: "literal" }
951
+ },
952
+ priority: 85,
953
+ tags: ["collection", "size"],
954
+ examples: [{ nl: "\u8BA2\u5355\u5217\u8868\u6570\u91CF\u5927\u4E8E10", spel: "#\u8BA2\u5355\u5217\u8868.size() > 10" }],
955
+ difficulty: "medium",
956
+ confidence: 0.85
957
+ },
958
+ {
959
+ id: "EN-COLL-SIZE",
960
+ match: /\b(?<field>\w+)\s+(?:size|length|count)\s*(?<op>>|<|>=|<=|==)\s*(?<value>\d+)/i,
961
+ spelTemplate: "#{field}.size() {op} {value}",
962
+ slots: {
963
+ value: { key: "value", type: "number", transform: "toNumber" },
964
+ op: { key: "op", type: "literal" }
965
+ },
966
+ priority: 84,
967
+ tags: ["collection", "size", "english"],
968
+ examples: [{ nl: "items size > 5", spel: "#items.size() > 5" }],
969
+ difficulty: "medium",
970
+ confidence: 0.9
971
+ },
972
+ // ================================================================
973
+ // Group 6: String operations — pri 82-85
974
+ // ================================================================
975
+ {
976
+ id: "CN-STR-CONTAINS",
977
+ match: /^(?<field>[^\s,,、]+?)(?:备注|名称|描述|标签|标题)?\s*(?:包含|含有|包括)\s*(?<substr>[^\s,,、]+)/,
978
+ spelTemplate: "#{field}.contains('{substr}')",
979
+ slots: { substr: { key: "substr", type: "string" } },
980
+ priority: 85,
981
+ tags: ["string", "contains"],
982
+ examples: [{ nl: "\u8BA2\u5355\u5907\u6CE8\u5305\u542B\u52A0\u6025", spel: "#\u8BA2\u5355\u5907\u6CE8.contains('\u52A0\u6025')" }],
983
+ difficulty: "easy",
984
+ confidence: 0.95
985
+ },
986
+ {
987
+ id: "EN-STR-CONTAINS",
988
+ match: /\b(?<field>\w+)\s+(?:contains?|includes?)\s+(?<substr>\S+)/i,
989
+ spelTemplate: "#{field}.contains('{substr}')",
990
+ slots: { substr: { key: "substr", type: "string" } },
991
+ priority: 84,
992
+ tags: ["string", "contains", "english"],
993
+ examples: [{ nl: "name contains test", spel: "#name.contains('test')" }],
994
+ difficulty: "easy",
995
+ confidence: 0.95
996
+ },
997
+ {
998
+ id: "CN-STR-STARTS",
999
+ match: /^(?<field>[^\s,,、]+?)\s*以\s*(?<prefix>[^\s,,、]+)\s*(?:开头|开始)/,
1000
+ spelTemplate: "#{field}.startsWith('{prefix}')",
1001
+ slots: { prefix: { key: "prefix", type: "string" } },
1002
+ priority: 85,
1003
+ tags: ["string", "startsWith"],
1004
+ examples: [{ nl: "\u8BA2\u5355\u53F7\u4EE5ORD\u5F00\u5934", spel: "#\u8BA2\u5355\u53F7.startsWith('ORD')" }],
1005
+ difficulty: "easy",
1006
+ confidence: 0.95
1007
+ },
1008
+ {
1009
+ id: "EN-STR-STARTS",
1010
+ match: /\b(?<field>\w+)\s+(?:startsWith|starts\s+with)\s+(?<prefix>\S+)/i,
1011
+ spelTemplate: "#{field}.startsWith('{prefix}')",
1012
+ slots: { prefix: { key: "prefix", type: "string" } },
1013
+ priority: 84,
1014
+ tags: ["string", "startsWith", "english"],
1015
+ examples: [{ nl: "id starts with ORD", spel: "#id.startsWith('ORD')" }],
1016
+ difficulty: "easy",
1017
+ confidence: 0.95
1018
+ },
1019
+ {
1020
+ id: "CN-STR-ENDS",
1021
+ match: /^(?<field>[^\s,,、]+?)\s*以\s*(?<suffix>[^\s,,、]+)\s*(?:结尾|结束)/,
1022
+ spelTemplate: "#{field}.endsWith('{suffix}')",
1023
+ slots: { suffix: { key: "suffix", type: "string" } },
1024
+ priority: 85,
1025
+ tags: ["string", "endsWith"],
1026
+ examples: [{ nl: "\u6587\u4EF6\u540D\u4EE5.pdf\u7ED3\u5C3E", spel: "#\u6587\u4EF6\u540D.endsWith('.pdf')" }],
1027
+ difficulty: "easy",
1028
+ confidence: 0.95
1029
+ },
1030
+ {
1031
+ id: "EN-STR-ENDS",
1032
+ match: /\b(?<field>\w+)\s+(?:endsWith|ends\s+with)\s+(?<suffix>\S+)/i,
1033
+ spelTemplate: "#{field}.endsWith('{suffix}')",
1034
+ slots: { suffix: { key: "suffix", type: "string" } },
1035
+ priority: 84,
1036
+ tags: ["string", "endsWith", "english"],
1037
+ examples: [{ nl: "name endsWith .pdf", spel: "#name.endsWith('.pdf')" }],
1038
+ difficulty: "easy",
1039
+ confidence: 0.95
1040
+ },
1041
+ {
1042
+ id: "CN-STR-MATCHES",
1043
+ match: /^(?<field>[^\s,,、]+?)\s*(?:匹配|符合)\s*(?<pattern>[^\s,,、]+)/,
1044
+ spelTemplate: "#{field} matches '{pattern}'",
1045
+ slots: { pattern: { key: "pattern", type: "string" } },
1046
+ priority: 82,
1047
+ tags: ["string", "matches"],
1048
+ examples: [{ nl: "\u624B\u673A\u53F7\u5339\u914D\u6B63\u5219", spel: "#\u624B\u673A\u53F7 matches '\u6B63\u5219'" }],
1049
+ difficulty: "medium",
1050
+ confidence: 0.9
1051
+ },
1052
+ {
1053
+ id: "EN-STR-MATCHES",
1054
+ match: /\b(?<field>\w+)\s+matches\s+(?<pattern>\S+)/i,
1055
+ spelTemplate: "#{field} matches '{pattern}'",
1056
+ slots: { pattern: { key: "pattern", type: "string" } },
1057
+ priority: 81,
1058
+ tags: ["string", "matches", "english"],
1059
+ examples: [{ nl: "id matches REGEX", spel: "#id matches 'REGEX'" }],
1060
+ difficulty: "medium",
1061
+ confidence: 0.9
1062
+ },
1063
+ // ================================================================
1064
+ // Group 7: Range — pri 82
1065
+ // ================================================================
1066
+ {
1067
+ id: "CN-RANGE-BETWEEN",
1068
+ match: /^(?<field>[^\s,,、]+?)\s*(?:在|介于)\s*(?<min>\d+)\s*(?:和|到|~)\s*(?<max>\d+)\s*(?:之间|范围)?/,
1069
+ spelTemplate: "#{field} between {{{min}, {max}}}",
1070
+ slots: {
1071
+ min: { key: "min", type: "number", transform: "toNumber" },
1072
+ max: { key: "max", type: "number", transform: "toNumber" }
1073
+ },
1074
+ priority: 82,
1075
+ tags: ["range", "between"],
1076
+ examples: [{ nl: "\u5E74\u9F84\u572818\u523060\u4E4B\u95F4", spel: "#\u5E74\u9F84 between {18, 60}" }],
1077
+ difficulty: "easy",
1078
+ confidence: 0.95
1079
+ },
1080
+ {
1081
+ id: "EN-RANGE-BETWEEN",
1082
+ match: /\b(?<field>\w+)\s+between\s+(?<min>\d+)\s+and\s+(?<max>\d+)/i,
1083
+ spelTemplate: "#{field} between {{{min}, {max}}}",
1084
+ slots: {
1085
+ min: { key: "min", type: "number", transform: "toNumber" },
1086
+ max: { key: "max", type: "number", transform: "toNumber" }
1087
+ },
1088
+ priority: 82,
1089
+ tags: ["range", "between", "english"],
1090
+ examples: [{ nl: "amount between 100 and 500", spel: "#amount between {100, 500}" }],
1091
+ difficulty: "easy",
1092
+ confidence: 0.95
1093
+ },
1094
+ // ================================================================
1095
+ // Group 8: Elvis — pri 78
1096
+ // ================================================================
1097
+ {
1098
+ id: "CN-ELVIS",
1099
+ match: /^(?<field>[^\s,,、]+?)\s*(?:或者|或\s+|默认|否则)\s*(?<default>[^\s,,、]+)/,
1100
+ spelTemplate: "#{field} ?: '{default}'",
1101
+ slots: { default: { key: "default", type: "string" } },
1102
+ priority: 78,
1103
+ tags: ["elvis"],
1104
+ examples: [{ nl: "\u7528\u6237\u540D\u6216\u8005\u533F\u540D\u7528\u6237", spel: "#\u7528\u6237\u540D ?: '\u533F\u540D\u7528\u6237'" }],
1105
+ difficulty: "medium",
1106
+ confidence: 0.85
1107
+ },
1108
+ {
1109
+ id: "EN-ELVIS",
1110
+ match: /\b(?<field>\w+)\s+(?:or\s+default|default\s+to)\s+(?<default>\S+)/i,
1111
+ spelTemplate: "#{field} ?: '{default}'",
1112
+ slots: { default: { key: "default", type: "string" } },
1113
+ priority: 77,
1114
+ tags: ["elvis", "english"],
1115
+ examples: [{ nl: "name or default Guest", spel: "#name ?: 'Guest'" }],
1116
+ difficulty: "medium",
1117
+ confidence: 0.85
1118
+ },
1119
+ // ================================================================
1120
+ // Group 9: Date — pri 75
1121
+ // ================================================================
1122
+ {
1123
+ id: "CN-DATE-AFTER",
1124
+ match: /^(?<field>[^\s,,、]+?)\s*在\s*(?<date>[^\s,,、]+?)\s*之后/,
1125
+ spelTemplate: "#{field} > T(java.time.LocalDate).parse('{date}')",
1126
+ slots: { date: { key: "date", type: "string" } },
1127
+ priority: 75,
1128
+ tags: ["date", "after"],
1129
+ examples: [
1130
+ {
1131
+ nl: "\u521B\u5EFA\u65E5\u671F\u57282024-01-01\u4E4B\u540E",
1132
+ spel: "#\u521B\u5EFA\u65E5\u671F > T(java.time.LocalDate).parse('2024-01-01')"
1133
+ }
1134
+ ],
1135
+ difficulty: "easy",
1136
+ confidence: 0.9
1137
+ },
1138
+ {
1139
+ id: "CN-DATE-BEFORE",
1140
+ match: /^(?<field>[^\s,,、]+?)\s*在\s*(?<date>[^\s,,、]+?)\s*之前/,
1141
+ spelTemplate: "#{field} < T(java.time.LocalDate).parse('{date}')",
1142
+ slots: { date: { key: "date", type: "string" } },
1143
+ priority: 75,
1144
+ tags: ["date", "before"],
1145
+ examples: [
1146
+ {
1147
+ nl: "\u8FC7\u671F\u65E5\u671F\u57282025-12-31\u4E4B\u524D",
1148
+ spel: "#\u8FC7\u671F\u65E5\u671F < T(java.time.LocalDate).parse('2025-12-31')"
1149
+ }
1150
+ ],
1151
+ difficulty: "easy",
1152
+ confidence: 0.9
1153
+ },
1154
+ {
1155
+ id: "EN-DATE-AFTER",
1156
+ match: /\b(?<field>\w+)\s+after\s+(?<date>\S+)/i,
1157
+ spelTemplate: "#{field} > T(java.time.LocalDate).parse('{date}')",
1158
+ slots: { date: { key: "date", type: "string" } },
1159
+ priority: 75,
1160
+ tags: ["date", "after", "english"],
1161
+ examples: [
1162
+ { nl: "date after 2024-06-01", spel: "#date > T(java.time.LocalDate).parse('2024-06-01')" }
1163
+ ],
1164
+ difficulty: "easy",
1165
+ confidence: 0.9
1166
+ },
1167
+ {
1168
+ id: "EN-DATE-BEFORE",
1169
+ match: /\b(?<field>\w+)\s+before\s+(?<date>\S+)/i,
1170
+ spelTemplate: "#{field} < T(java.time.LocalDate).parse('{date}')",
1171
+ slots: { date: { key: "date", type: "string" } },
1172
+ priority: 75,
1173
+ tags: ["date", "before", "english"],
1174
+ examples: [
1175
+ { nl: "date before 2025-01-01", spel: "#date < T(java.time.LocalDate).parse('2025-01-01')" }
1176
+ ],
1177
+ difficulty: "easy",
1178
+ confidence: 0.9
1179
+ },
1180
+ // ================================================================
1181
+ // Group 10: Type check — pri 73
1182
+ // ================================================================
1183
+ {
1184
+ id: "CN-TYPE-INSTANCEOF",
1185
+ match: /^(?<field>[^\s,,、]+?)\s*(?:是否是|是否为|是|instanceof)\s*(?<type>[^\s,,、]+?)\s*(?:类型|类型|type|class)?$/i,
1186
+ spelTemplate: "#{field} instanceof T({type})",
1187
+ slots: { type: { key: "type", type: "variable" } },
1188
+ priority: 74,
1189
+ tags: ["type", "instanceof"],
1190
+ examples: [{ nl: "\u8D26\u53F7\u662F\u5426\u662FAdmin\u7C7B\u578B", spel: "#\u8D26\u53F7 instanceof T(Admin)" }],
1191
+ difficulty: "medium",
1192
+ confidence: 0.85
1193
+ },
1194
+ {
1195
+ id: "EN-TYPE-INSTANCEOF",
1196
+ match: /\b(?<field>\w+)\s+(?:is|instanceof)\s+(?<type>\w+)/i,
1197
+ spelTemplate: "#{field} instanceof T({type})",
1198
+ slots: { type: { key: "type", type: "variable" } },
1199
+ priority: 72,
1200
+ tags: ["type", "instanceof", "english"],
1201
+ examples: [{ nl: "account is Admin", spel: "#account instanceof T(Admin)" }],
1202
+ difficulty: "medium",
1203
+ confidence: 0.9
1204
+ },
1205
+ // ================================================================
1206
+ // Group 11: Boolean — pri 70-74
1207
+ // ================================================================
1208
+ {
1209
+ id: "CN-BOOL-TRUE",
1210
+ match: /^(?<field>[^\s,,、]+?)\s*(?:是|为|等于|==)\s*(?:true|真|是|yes)$/i,
1211
+ spelTemplate: "#{field} == true",
1212
+ slots: {},
1213
+ priority: 74,
1214
+ tags: ["boolean", "isTrue"],
1215
+ examples: [{ nl: "\u7528\u6237\u662FVIP", spel: "#\u7528\u6237 == true" }],
1216
+ difficulty: "easy",
1217
+ confidence: 0.92
1218
+ },
1219
+ {
1220
+ id: "CN-BOOL-FALSE",
1221
+ match: /^(?<field>[^\s,,、]+?)\s*(?:不是|非|为|是|等于|==)\s*(?:false|假|否|no)$/i,
1222
+ spelTemplate: "#{field} == false",
1223
+ slots: {},
1224
+ priority: 73,
1225
+ tags: ["boolean", "isFalse"],
1226
+ examples: [{ nl: "\u7528\u6237\u4E0D\u662FVIP", spel: "#\u7528\u6237 == false" }],
1227
+ difficulty: "easy",
1228
+ confidence: 0.92
1229
+ },
1230
+ {
1231
+ id: "EN-BOOL-TRUE",
1232
+ match: /^(?<field>\w+)\s+(?:is\s+)?true$/i,
1233
+ spelTemplate: "#{field} == true",
1234
+ slots: {},
1235
+ priority: 75,
1236
+ tags: ["boolean", "isTrue", "english"],
1237
+ examples: [{ nl: "isVIP is true", spel: "#isVIP == true" }],
1238
+ difficulty: "easy",
1239
+ confidence: 0.95
1240
+ },
1241
+ {
1242
+ id: "EN-BOOL-FALSE",
1243
+ match: /^(?<field>\w+)\s+(?:is\s+)?false$/i,
1244
+ spelTemplate: "#{field} == false",
1245
+ slots: {},
1246
+ priority: 75,
1247
+ tags: ["boolean", "isFalse", "english"],
1248
+ examples: [{ nl: "flag is false", spel: "#flag == false" }],
1249
+ difficulty: "easy",
1250
+ confidence: 0.95
1251
+ },
1252
+ {
1253
+ id: "CN-BOOL-FIELD",
1254
+ match: /^(?:是否|is\s+)(?<field>[^\s,,、]+?)(?:\?|吗|呢)?$/i,
1255
+ spelTemplate: "#{field}",
1256
+ slots: {},
1257
+ priority: 70,
1258
+ tags: ["boolean", "field"],
1259
+ examples: [{ nl: "\u662F\u5426\u5DF2\u652F\u4ED8", spel: "#\u5DF2\u652F\u4ED8" }],
1260
+ difficulty: "easy",
1261
+ confidence: 0.8
1262
+ },
1263
+ // ================================================================
1264
+ // Group 12: Logical combination — pri 63-65
1265
+ // ================================================================
1266
+ {
1267
+ id: "CN-LOGIC-AND",
1268
+ match: /(?<left>.+?)\s*(?:且|并且|而且|同时|&&)\s*(?<right>.+)/,
1269
+ spelTemplate: "({left}) and ({right})",
1270
+ slots: {
1271
+ left: { key: "left", type: "variable" },
1272
+ right: { key: "right", type: "variable" }
1273
+ },
1274
+ priority: 65,
1275
+ tags: ["logic", "and"],
1276
+ examples: [{ nl: "\u91D1\u989D\u5927\u4E8E1000\u4E14\u8BA2\u5355\u5DF2\u786E\u8BA4", spel: "(\u91D1\u989D\u5927\u4E8E1000) and (\u8BA2\u5355\u5DF2\u786E\u8BA4)" }],
1277
+ difficulty: "medium",
1278
+ confidence: 0.75
1279
+ },
1280
+ {
1281
+ id: "CN-LOGIC-OR",
1282
+ match: /(?<left>.+?)\s*(?:或|或者|\|\|)\s*(?<right>.+)/,
1283
+ spelTemplate: "({left}) or ({right})",
1284
+ slots: {
1285
+ left: { key: "left", type: "variable" },
1286
+ right: { key: "right", type: "variable" }
1287
+ },
1288
+ priority: 65,
1289
+ tags: ["logic", "or"],
1290
+ examples: [{ nl: "\u91D1\u989D\u5927\u4E8E1000\u6216\u5DF2\u53D1\u8D27", spel: "(\u91D1\u989D\u5927\u4E8E1000) or (\u5DF2\u53D1\u8D27)" }],
1291
+ difficulty: "medium",
1292
+ confidence: 0.75
1293
+ },
1294
+ {
1295
+ id: "EN-LOGIC-AND",
1296
+ match: /\b(?<left>.+?)\s+\band\b\s+(?<right>.+)/i,
1297
+ spelTemplate: "({left}) and ({right})",
1298
+ slots: {
1299
+ left: { key: "left", type: "variable" },
1300
+ right: { key: "right", type: "variable" }
1301
+ },
1302
+ priority: 64,
1303
+ tags: ["logic", "and", "english"],
1304
+ examples: [
1305
+ { nl: "amount > 100 and status == done", spel: "(amount > 100) and (status == done)" }
1306
+ ],
1307
+ difficulty: "medium",
1308
+ confidence: 0.75
1309
+ },
1310
+ {
1311
+ id: "EN-LOGIC-OR",
1312
+ match: /\b(?<left>.+?)\s+\bor\b\s+(?<right>.+)/i,
1313
+ spelTemplate: "({left}) or ({right})",
1314
+ slots: {
1315
+ left: { key: "left", type: "variable" },
1316
+ right: { key: "right", type: "variable" }
1317
+ },
1318
+ priority: 64,
1319
+ tags: ["logic", "or", "english"],
1320
+ examples: [{ nl: "VIP or amount > 1000", spel: "(VIP) or (amount > 1000)" }],
1321
+ difficulty: "medium",
1322
+ confidence: 0.75
1323
+ },
1324
+ // ================================================================
1325
+ // Group 13: Logical NOT — pri 61-62
1326
+ // ================================================================
1327
+ {
1328
+ id: "CN-LOGIC-NOT",
1329
+ match: /^不是\s+(?<expr>.+)/,
1330
+ spelTemplate: "!({expr})",
1331
+ slots: { expr: { key: "expr", type: "variable" } },
1332
+ priority: 62,
1333
+ tags: ["logic", "not"],
1334
+ examples: [{ nl: "\u4E0D\u662F\u6709\u6548", spel: "!(\u6709\u6548)" }],
1335
+ difficulty: "easy",
1336
+ confidence: 0.85
1337
+ },
1338
+ {
1339
+ id: "EN-LOGIC-NOT",
1340
+ match: /^not\s+(?<expr>.+)/i,
1341
+ spelTemplate: "!({expr})",
1342
+ slots: { expr: { key: "expr", type: "variable" } },
1343
+ priority: 61,
1344
+ tags: ["logic", "not", "english"],
1345
+ examples: [{ nl: "not cancelled", spel: "!(cancelled)" }],
1346
+ difficulty: "easy",
1347
+ confidence: 0.85
1348
+ },
1349
+ // ================================================================
1350
+ // Group 14: Projection & selection — pri 55-60
1351
+ // ================================================================
1352
+ {
1353
+ id: "CN-SELECT-FIRST",
1354
+ match: /^(?<root>[^\s,,、]+?)\s*中\s*第一[个位]\s*(?<field>[^\s,,、]+?)\s*(?:大于|>|超过|<|小于|等于|==)\s*(?<value>[^\s,,、]+)/,
1355
+ spelTemplate: "#{root}.items.^[#{this}.{field} > {value}]",
1356
+ slots: {
1357
+ root: { key: "root", type: "variable" },
1358
+ field: { key: "field", type: "variable" },
1359
+ value: { key: "value", type: "literal" }
1360
+ },
1361
+ priority: 60,
1362
+ tags: ["selection", "first"],
1363
+ examples: [{ nl: "\u8BA2\u5355\u4E2D\u7B2C\u4E00\u4E2A\u91D1\u989D\u5927\u4E8E1000\u7684", spel: "#\u8BA2\u5355.items.^[#this.\u91D1\u989D > 1000]" }],
1364
+ difficulty: "medium",
1365
+ confidence: 0.8
1366
+ },
1367
+ {
1368
+ id: "CN-SELECT-ALL",
1369
+ match: /^(?<root>[^\s,,、]+?)\s*中\s*所有\s*(?<field>[^\s,,、]+?)\s*(?:大于|>|超过|<|小于|等于|==|包含|满足)\s*(?<value>[^\s,,、]+)/,
1370
+ spelTemplate: "#{root}.items.?[#{this}.{field} > {value}]",
1371
+ slots: {
1372
+ root: { key: "root", type: "variable" },
1373
+ field: { key: "field", type: "variable" },
1374
+ value: { key: "value", type: "literal" }
1375
+ },
1376
+ priority: 60,
1377
+ tags: ["selection", "all"],
1378
+ examples: [{ nl: "\u8BA2\u5355\u4E2D\u6240\u6709\u91D1\u989D\u5927\u4E8E1000\u7684", spel: "#\u8BA2\u5355.items.?[#this.\u91D1\u989D > 1000]" }],
1379
+ difficulty: "medium",
1380
+ confidence: 0.8
1381
+ },
1382
+ {
1383
+ id: "CN-PROJ",
1384
+ match: /^(?<root>[^\s,,、]+?)\s*中\s*每[个一]\s*(?:的)?(?<field>[^\s,,、]+?)\s*(?:值|金额|名称|价格|name|amount|price)?/,
1385
+ spelTemplate: "#{root}.items.![#{this}.{field}]",
1386
+ slots: {
1387
+ root: { key: "root", type: "variable" },
1388
+ field: { key: "field", type: "variable" }
1389
+ },
1390
+ priority: 55,
1391
+ tags: ["projection"],
1392
+ examples: [{ nl: "\u8BA2\u5355\u4E2D\u6BCF\u4E2A\u5546\u54C1\u7684\u4EF7\u683C", spel: "#\u8BA2\u5355.items.![#this.\u5546\u54C1]" }],
1393
+ difficulty: "medium",
1394
+ confidence: 0.75
1395
+ },
1396
+ {
1397
+ id: "EN-SELECT-ALL",
1398
+ match: /all\s+(?<root>\w+)\s+with\s+(?<field>\w+)\s*>\s*(?<value>\d+)/i,
1399
+ spelTemplate: "#{root}.items.?[#{this}.{field} > {value}]",
1400
+ slots: {
1401
+ root: { key: "root", type: "variable" },
1402
+ field: { key: "field", type: "variable" },
1403
+ value: { key: "value", type: "number", transform: "toNumber" }
1404
+ },
1405
+ priority: 50,
1406
+ tags: ["selection", "all", "english"],
1407
+ examples: [{ nl: "all items with price > 100", spel: "#items.items.?[#this.price > 100]" }],
1408
+ difficulty: "medium",
1409
+ confidence: 0.8
1410
+ }
1411
+ ];
1412
+ BUILTIN_PATTERNS.sort((a, b) => b.priority - a.priority);
1413
+
1414
+ // src/template/nl-intent.ts
1415
+ var NLIntent = /* @__PURE__ */ ((NLIntent2) => {
1416
+ NLIntent2["COMPARISON"] = "COMPARISON";
1417
+ NLIntent2["PERMISSION_CHECK"] = "PERMISSION_CHECK";
1418
+ NLIntent2["NULL_CHECK"] = "NULL_CHECK";
1419
+ NLIntent2["LOGICAL"] = "LOGICAL";
1420
+ NLIntent2["STRING_MATCH"] = "STRING_MATCH";
1421
+ NLIntent2["COLLECTION"] = "COLLECTION";
1422
+ NLIntent2["RANGE"] = "RANGE";
1423
+ NLIntent2["SELECTION"] = "SELECTION";
1424
+ NLIntent2["PROJECTION"] = "PROJECTION";
1425
+ NLIntent2["TYPE_CHECK"] = "TYPE_CHECK";
1426
+ NLIntent2["BOOLEAN"] = "BOOLEAN";
1427
+ NLIntent2["DATE"] = "DATE";
1428
+ NLIntent2["ELVIS"] = "ELVIS";
1429
+ NLIntent2["ASSIGNMENT"] = "ASSIGNMENT";
1430
+ NLIntent2["ARITHMETIC"] = "ARITHMETIC";
1431
+ return NLIntent2;
1432
+ })(NLIntent || {});
1433
+
1434
+ // src/template/intent-classifier.ts
1435
+ var INTENT_KEYWORDS = {
1436
+ ["COMPARISON" /* COMPARISON */]: {
1437
+ zh: [
1438
+ "\u5927\u4E8E",
1439
+ "\u5C0F\u4E8E",
1440
+ "\u7B49\u4E8E",
1441
+ "\u8D85\u8FC7",
1442
+ "\u4F4E\u4E8E",
1443
+ "\u4E0D\u5C0F\u4E8E",
1444
+ "\u4E0D\u5927\u4E8E",
1445
+ "\u5927\u4E8E\u7B49\u4E8E",
1446
+ "\u5C0F\u4E8E\u7B49\u4E8E",
1447
+ "\u4E0D\u7B49\u4E8E"
1448
+ ],
1449
+ en: ["greater", "less", "equal", "above", "below", "exceed", ">", "<", "==", "!=", ">=", "<="]
1450
+ },
1451
+ ["NULL_CHECK" /* NULL_CHECK */]: {
1452
+ zh: ["\u4E3A\u7A7A", "\u4E0D\u4E3A\u7A7A", "\u662F\u7A7A", "\u5B58\u5728", "\u4E0D\u5B58\u5728", "null", "\u6CA1\u6709\u503C"],
1453
+ en: ["null", "empty", "is null", "is not null", "is empty", "is not empty"]
1454
+ },
1455
+ ["PERMISSION_CHECK" /* PERMISSION_CHECK */]: {
1456
+ zh: ["\u89D2\u8272", "\u6743\u9650", "\u7BA1\u7406\u5458", "\u53EF\u4EE5", "\u5141\u8BB8", "\u7981\u6B62", "\u62D2\u7EDD"],
1457
+ en: ["role", "permission", "can", "allow", "deny", "has role", "has permission"]
1458
+ },
1459
+ ["LOGICAL" /* LOGICAL */]: {
1460
+ zh: ["\u4E14", "\u5E76\u4E14", "\u540C\u65F6", "\u6216", "\u6216\u8005", "\u975E", "\u4E0D\u662F", "\u4E5F\u4E0D"],
1461
+ en: ["and", "or", "not", "&&", "||", "!"]
1462
+ },
1463
+ ["STRING_MATCH" /* STRING_MATCH */]: {
1464
+ zh: ["\u5305\u542B", "\u542B\u6709", "\u5305\u62EC", "\u4EE5...\u5F00\u5934", "\u4EE5...\u7ED3\u5C3E", "\u5339\u914D", "\u7B26\u5408", "\u6B63\u5219", "\u5F00\u5934", "\u7ED3\u5C3E"],
1465
+ en: ["contains", "includes", "starts with", "ends with", "matches", "regex"]
1466
+ },
1467
+ ["COLLECTION" /* COLLECTION */]: {
1468
+ zh: ["\u5217\u8868", "\u6570\u7EC4", "\u96C6\u5408", "\u5927\u5C0F", "\u957F\u5EA6", "isEmpty", "\u4E3A\u7A7A", "\u4E0D\u4E3A\u7A7A", "\u9879", "items"],
1469
+ en: ["list", "array", "collection", "size", "length", "count", "isempty", "empty", "items"]
1470
+ },
1471
+ ["RANGE" /* RANGE */]: {
1472
+ zh: ["\u4E4B\u95F4", "\u8303\u56F4", "\u4ECB\u4E8E", "between"],
1473
+ en: ["between", "range", "from...to"]
1474
+ },
1475
+ ["SELECTION" /* SELECTION */]: {
1476
+ zh: ["\u7B5B\u9009", "\u9009\u62E9", "\u6240\u6709\u6EE1\u8DB3", "\u7B2C\u4E00\u4E2A\u6EE1\u8DB3", "\u7B26\u5408\u6761\u4EF6\u7684"],
1477
+ en: ["select", "filter", "first", "matching"]
1478
+ },
1479
+ ["PROJECTION" /* PROJECTION */]: {
1480
+ zh: ["\u6BCF\u4E2A", "\u6240\u6709", "\u5168\u90E8", "\u6295\u5F71"],
1481
+ en: ["each", "every", "project"]
1482
+ },
1483
+ ["TYPE_CHECK" /* TYPE_CHECK */]: {
1484
+ zh: ["\u662F\u5426\u662F", "\u7C7B\u578B", "instanceof"],
1485
+ en: ["instanceof", "type of", "is a"]
1486
+ },
1487
+ ["BOOLEAN" /* BOOLEAN */]: {
1488
+ zh: ["\u662F\u5426", "\u771F\u5047", "true", "false", "\u662F", "\u5426"],
1489
+ en: ["true", "false", "yes", "no", "is", "is not"]
1490
+ },
1491
+ ["DATE" /* DATE */]: {
1492
+ zh: ["\u65E5\u671F", "\u65F6\u95F4", "\u4E4B\u540E", "\u4E4B\u524D", "\u65E9\u4E8E", "\u665A\u4E8E"],
1493
+ en: ["date", "time", "after", "before", "earlier", "later"]
1494
+ },
1495
+ ["ELVIS" /* ELVIS */]: {
1496
+ zh: ["\u6216\u8005", "\u9ED8\u8BA4\u503C", "\u515C\u5E95", "\u9ED8\u8BA4", "\u5426\u5219"],
1497
+ en: ["default", "fallback", "or", "otherwise"]
1498
+ },
1499
+ ["ASSIGNMENT" /* ASSIGNMENT */]: {
1500
+ zh: ["\u8BBE\u7F6E", "\u8D4B\u503C", "\u8D4B\u4E88", "\u8BBE\u5B9A"],
1501
+ en: ["set", "assign", "put"]
1502
+ },
1503
+ ["ARITHMETIC" /* ARITHMETIC */]: {
1504
+ zh: ["\u52A0", "\u51CF", "\u4E58", "\u9664", "\u53D6\u6A21", "\u6C42\u548C", "\u5E73\u5747\u503C", "\u603B\u548C"],
1505
+ en: ["plus", "minus", "multiply", "divide", "mod", "sum", "average"]
1506
+ }
1507
+ };
1508
+ var IntentClassifier = class {
1509
+ /**
1510
+ * Main method for classifying natural language input
1511
+ */
1512
+ classify(input) {
1513
+ const normalized = this.normalize(input);
1514
+ const intentScores = /* @__PURE__ */ new Map();
1515
+ for (const intent of Object.keys(INTENT_KEYWORDS)) {
1516
+ const keywords = INTENT_KEYWORDS[intent];
1517
+ let score = 0;
1518
+ for (const kw of keywords.zh) {
1519
+ if (normalized.includes(kw)) score += 3;
1520
+ }
1521
+ for (const kw of keywords.en) {
1522
+ if (normalized.toLowerCase().includes(kw.toLowerCase())) score += 2;
1523
+ }
1524
+ if (score > 0) intentScores.set(intent, score);
1525
+ }
1526
+ if (intentScores.has("LOGICAL" /* LOGICAL */) && intentScores.has("COMPARISON" /* COMPARISON */)) {
1527
+ intentScores.set("LOGICAL" /* LOGICAL */, (intentScores.get("LOGICAL" /* LOGICAL */) ?? 0) + 2);
1528
+ }
1529
+ if (/between\s+\S+\s+(and|,)\s+\S+/i.test(normalized)) {
1530
+ intentScores.set("RANGE" /* RANGE */, (intentScores.get("RANGE" /* RANGE */) ?? 0) + 3);
1531
+ }
1532
+ if (/instanceof/i.test(normalized) || /是否是/.test(normalized) && /类型/.test(normalized)) {
1533
+ intentScores.set("TYPE_CHECK" /* TYPE_CHECK */, (intentScores.get("TYPE_CHECK" /* TYPE_CHECK */) ?? 0) + 3);
1534
+ }
1535
+ if (/每个/.test(normalized) || /each/.test(normalized)) {
1536
+ intentScores.set("PROJECTION" /* PROJECTION */, (intentScores.get("PROJECTION" /* PROJECTION */) ?? 0) + 2);
1537
+ }
1538
+ if (/isempty|\.isEmpty|列表|数组|集合/.test(normalized)) {
1539
+ intentScores.set("COLLECTION" /* COLLECTION */, (intentScores.get("COLLECTION" /* COLLECTION */) ?? 0) + 1);
1540
+ }
1541
+ const entities = this.extractEntities(normalized);
1542
+ const operators = this.extractOperators(normalized);
1543
+ const logicalConnectors = this.extractLogicalConnectors(normalized);
1544
+ const complexity = this.calculateComplexity(
1545
+ normalized,
1546
+ intentScores,
1547
+ entities,
1548
+ operators,
1549
+ logicalConnectors
1550
+ );
1551
+ const sortedIntents = Array.from(intentScores.entries()).sort((a, b) => b[1] - a[1]).map(([intent, score]) => ({
1552
+ intent,
1553
+ confidence: Math.min(score / 15, 1)
1554
+ }));
1555
+ return {
1556
+ primaryIntent: sortedIntents[0]?.intent ?? "COMPARISON" /* COMPARISON */,
1557
+ intents: sortedIntents.length > 0 ? sortedIntents : [{ intent: "COMPARISON" /* COMPARISON */, confidence: 0.3 }],
1558
+ entities,
1559
+ operators,
1560
+ logicalConnectors,
1561
+ complexity
1562
+ };
1563
+ }
1564
+ /**
1565
+ * Entity extraction
1566
+ */
1567
+ extractEntities(normalized) {
1568
+ const entities = [];
1569
+ const numRegex = /\b(\d+(?:\.\d+)?)\b/g;
1570
+ let match;
1571
+ while ((match = numRegex.exec(normalized)) !== null) {
1572
+ entities.push({
1573
+ text: match[1],
1574
+ type: "value",
1575
+ position: { start: match.index, end: match.index + match[1].length }
1576
+ });
1577
+ }
1578
+ const strRegex = /['""]([^'""]*)['""]/g;
1579
+ while ((match = strRegex.exec(normalized)) !== null) {
1580
+ entities.push({
1581
+ text: match[1],
1582
+ type: "value",
1583
+ position: { start: match.index, end: match.index + match[0].length }
1584
+ });
1585
+ }
1586
+ for (const op of [">=", "<=", "!=", "==", ">", "<", "="]) {
1587
+ let idx = normalized.indexOf(op);
1588
+ if (idx >= 0) {
1589
+ entities.push({
1590
+ text: op,
1591
+ type: "operator",
1592
+ position: { start: idx, end: idx + op.length }
1593
+ });
1594
+ }
1595
+ }
1596
+ return entities;
1597
+ }
1598
+ /**
1599
+ * Operator recognition
1600
+ */
1601
+ extractOperators(normalized) {
1602
+ const operators = [];
1603
+ const OP_MAP = [
1604
+ { pattern: /大于等于|>=|≥/, op: ">=" },
1605
+ { pattern: /小于等于|<=|≤/, op: "<=" },
1606
+ { pattern: /不等于|!=|≠/, op: "!=" },
1607
+ { pattern: /等于|==/, op: "==" },
1608
+ { pattern: /大于|>/, op: ">" },
1609
+ { pattern: /小于|</, op: "<" },
1610
+ { pattern: /匹配|match/, op: "matches" },
1611
+ { pattern: /之间|between/, op: "between" },
1612
+ { pattern: /包含|contains/, op: "contains" }
1613
+ ];
1614
+ for (const { pattern, op } of OP_MAP) {
1615
+ if (pattern.test(normalized)) {
1616
+ operators.push(op);
1617
+ }
1618
+ }
1619
+ return operators;
1620
+ }
1621
+ /**
1622
+ * Logical connector recognition
1623
+ */
1624
+ extractLogicalConnectors(normalized) {
1625
+ const connectors = [];
1626
+ const isBetween = /between\s+\d+(\s*,\s*|\s+and\s+)\d+/i.test(normalized);
1627
+ if (/且|并且|and|&&/i.test(normalized) && !isBetween) connectors.push("and");
1628
+ if (/或|或者|or|\|\|/i.test(normalized)) connectors.push("or");
1629
+ if (/非|不是|不|not|!/i.test(normalized)) connectors.push("not");
1630
+ return connectors;
1631
+ }
1632
+ /**
1633
+ * Complexity calculation
1634
+ */
1635
+ calculateComplexity(input, intentScores, entities, operators, logicalConnectors) {
1636
+ let complexity = 0;
1637
+ complexity += Math.min(input.length / 5, 15);
1638
+ complexity += (intentScores.size - 1) * 10;
1639
+ complexity += logicalConnectors.length * 15;
1640
+ complexity += entities.length * 5;
1641
+ complexity += operators.length * 8;
1642
+ return Math.max(0, Math.min(complexity, 100));
1643
+ }
1644
+ /**
1645
+ * Input normalization
1646
+ */
1647
+ normalize(input) {
1648
+ return input.trim().toLowerCase().replace(/[\uFF01-\uFF5E]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 65248)).replace(/\s+/g, " ");
1649
+ }
1650
+ };
1651
+
1652
+ // src/template/template-engine.ts
1653
+ var TEMPLATE_LIBRARY = {
1654
+ ["COMPARISON" /* COMPARISON */]: [
1655
+ {
1656
+ name: "COMPARISON-SIMPLE",
1657
+ template: "#{root}.{field} {operator} {value}",
1658
+ conditions: { hasComparison: true, entityCount: { min: 1 } },
1659
+ confidence: 0.9
1660
+ },
1661
+ {
1662
+ name: "COMPARISON-DUAL",
1663
+ template: "(#{root}.{field1} {operator1} {value1}) {logic} (#{root}.{field2} {operator2} {value2})",
1664
+ conditions: { hasComparison: true, hasLogical: true, entityCount: { min: 2 } },
1665
+ confidence: 0.8
1666
+ }
1667
+ ],
1668
+ ["NULL_CHECK" /* NULL_CHECK */]: [
1669
+ {
1670
+ name: "NULL-IS_NULL",
1671
+ template: "#{root}.{field} == null",
1672
+ conditions: { hasNull: true },
1673
+ confidence: 0.95
1674
+ },
1675
+ {
1676
+ name: "NULL-IS_NOT_NULL",
1677
+ template: "#{root}.{field} != null",
1678
+ conditions: { hasNull: true },
1679
+ confidence: 0.95
1680
+ }
1681
+ ],
1682
+ ["PERMISSION_CHECK" /* PERMISSION_CHECK */]: [
1683
+ {
1684
+ name: "PERM-HAS_ROLE",
1685
+ template: "hasRole('{role}')",
1686
+ conditions: {},
1687
+ confidence: 0.9
1688
+ },
1689
+ {
1690
+ name: "PERM-HAS_PERMISSION",
1691
+ template: "hasPermission('{permission}')",
1692
+ conditions: {},
1693
+ confidence: 0.9
1694
+ }
1695
+ ],
1696
+ ["LOGICAL" /* LOGICAL */]: [
1697
+ {
1698
+ name: "LOGICAL-AND",
1699
+ template: "({left}) and ({right})",
1700
+ conditions: { hasLogical: true },
1701
+ confidence: 0.75
1702
+ },
1703
+ {
1704
+ name: "LOGICAL-OR",
1705
+ template: "({left}) or ({right})",
1706
+ conditions: { hasLogical: true },
1707
+ confidence: 0.75
1708
+ },
1709
+ {
1710
+ name: "LOGICAL-NOT",
1711
+ template: "!({expr})",
1712
+ conditions: { hasLogical: true },
1713
+ confidence: 0.85
1714
+ }
1715
+ ],
1716
+ ["STRING_MATCH" /* STRING_MATCH */]: [
1717
+ {
1718
+ name: "STR-CONTAINS",
1719
+ template: "#{root}.{field}.contains('{substring}')",
1720
+ conditions: { hasString: true },
1721
+ confidence: 0.9
1722
+ },
1723
+ {
1724
+ name: "STR-STARTS_WITH",
1725
+ template: "#{root}.{field}.startsWith('{prefix}')",
1726
+ conditions: { hasString: true },
1727
+ confidence: 0.9
1728
+ },
1729
+ {
1730
+ name: "STR-ENDS_WITH",
1731
+ template: "#{root}.{field}.endsWith('{suffix}')",
1732
+ conditions: { hasString: true },
1733
+ confidence: 0.9
1734
+ },
1735
+ {
1736
+ name: "STR-MATCHES",
1737
+ template: "#{root}.{field} matches '{pattern}'",
1738
+ conditions: { hasString: true },
1739
+ confidence: 0.8
1740
+ }
1741
+ ],
1742
+ ["COLLECTION" /* COLLECTION */]: [
1743
+ {
1744
+ name: "COLL-IS_EMPTY",
1745
+ template: "#{root}.{list}.isEmpty()",
1746
+ conditions: { hasCollection: true },
1747
+ confidence: 0.95
1748
+ },
1749
+ {
1750
+ name: "COLL-IS_NOT_EMPTY",
1751
+ template: "!#{root}.{list}.isEmpty()",
1752
+ conditions: { hasCollection: true },
1753
+ confidence: 0.9
1754
+ },
1755
+ {
1756
+ name: "COLL-SIZE",
1757
+ template: "#{root}.{list}.size() {operator} {value}",
1758
+ conditions: { hasCollection: true, hasComparison: true },
1759
+ confidence: 0.85
1760
+ },
1761
+ {
1762
+ name: "COLL-CONTAINS",
1763
+ template: "#{root}.{list}.contains('{element}')",
1764
+ conditions: { hasCollection: true },
1765
+ confidence: 0.9
1766
+ }
1767
+ ],
1768
+ ["RANGE" /* RANGE */]: [
1769
+ {
1770
+ name: "RANGE-BETWEEN",
1771
+ template: "#{root}.{field} between {{{min}, {max}}}",
1772
+ conditions: { entityCount: { min: 1 } },
1773
+ confidence: 0.9
1774
+ }
1775
+ ],
1776
+ ["SELECTION" /* SELECTION */]: [
1777
+ {
1778
+ name: "SELECT-ALL",
1779
+ template: "#{root}.{list}.?[#{this}.{conditionField} {conditionOp} {conditionValue}]",
1780
+ conditions: {},
1781
+ confidence: 0.75
1782
+ },
1783
+ {
1784
+ name: "SELECT-FIRST",
1785
+ template: "#{root}.{list}.^[#{this}.{conditionField} {conditionOp} {conditionValue}]",
1786
+ conditions: {},
1787
+ confidence: 0.75
1788
+ }
1789
+ ],
1790
+ ["PROJECTION" /* PROJECTION */]: [
1791
+ {
1792
+ name: "PROJ-FIELD",
1793
+ template: "#{root}.{list}.![#{this}.{field}]",
1794
+ conditions: {},
1795
+ confidence: 0.75
1796
+ }
1797
+ ],
1798
+ ["TYPE_CHECK" /* TYPE_CHECK */]: [
1799
+ {
1800
+ name: "TYPE-INSTANCEOF",
1801
+ template: "#{root}.{field} instanceof T({typeName})",
1802
+ conditions: {},
1803
+ confidence: 0.85
1804
+ }
1805
+ ],
1806
+ ["BOOLEAN" /* BOOLEAN */]: [
1807
+ {
1808
+ name: "BOOL-IS_TRUE",
1809
+ template: "#{root}.{field} == true",
1810
+ conditions: {},
1811
+ confidence: 0.9
1812
+ },
1813
+ {
1814
+ name: "BOOL-IS_FALSE",
1815
+ template: "#{root}.{field} == false",
1816
+ conditions: {},
1817
+ confidence: 0.9
1818
+ },
1819
+ {
1820
+ name: "BOOL-FIELD",
1821
+ template: "#{root}.{field}",
1822
+ conditions: {},
1823
+ confidence: 0.85
1824
+ }
1825
+ ],
1826
+ ["DATE" /* DATE */]: [
1827
+ {
1828
+ name: "DATE-AFTER",
1829
+ template: "#{root}.{field}.after(T(java.util.Date).valueOf('{date}'))",
1830
+ conditions: {},
1831
+ confidence: 0.85
1832
+ },
1833
+ {
1834
+ name: "DATE-BEFORE",
1835
+ template: "#{root}.{field}.before(T(java.util.Date).valueOf('{date}'))",
1836
+ conditions: {},
1837
+ confidence: 0.85
1838
+ }
1839
+ ],
1840
+ ["ELVIS" /* ELVIS */]: [
1841
+ {
1842
+ name: "ELVIS-DEFAULT",
1843
+ template: "#{root}.{field} ?: '{defaultValue}'",
1844
+ conditions: {},
1845
+ confidence: 0.8
1846
+ }
1847
+ ],
1848
+ ["ASSIGNMENT" /* ASSIGNMENT */]: [
1849
+ {
1850
+ name: "ASSIGN-SIMPLE",
1851
+ template: "#{root}.{field} = {value}",
1852
+ conditions: {},
1853
+ confidence: 0.8
1854
+ }
1855
+ ],
1856
+ ["ARITHMETIC" /* ARITHMETIC */]: [
1857
+ {
1858
+ name: "ARITH-OP",
1859
+ template: "#{root}.{field1} {operator} #{root}.{field2}",
1860
+ conditions: {},
1861
+ confidence: 0.75
1862
+ }
1863
+ ]
1864
+ };
1865
+ var TemplateEngine = class {
1866
+ contextSchema;
1867
+ constructor(contextSchema) {
1868
+ this.contextSchema = contextSchema;
1869
+ }
1870
+ setContext(schema) {
1871
+ this.contextSchema = schema;
1872
+ }
1873
+ /**
1874
+ * Generate a SpEL expression based on intent classification results
1875
+ */
1876
+ generate(input, intentResult) {
1877
+ const templates = TEMPLATE_LIBRARY[intentResult.primaryIntent];
1878
+ if (!templates || templates.length === 0) return null;
1879
+ const bestTemplate = this.selectBestTemplate(templates, intentResult, input);
1880
+ if (!bestTemplate) return null;
1881
+ const { expression, unfilledSlots } = this.fillTemplate(
1882
+ bestTemplate.template,
1883
+ input,
1884
+ intentResult
1885
+ );
1886
+ return {
1887
+ expression,
1888
+ intent: intentResult.primaryIntent,
1889
+ templateName: bestTemplate.name,
1890
+ confidence: bestTemplate.confidence * (intentResult.intents[0]?.confidence ?? 0.5),
1891
+ unfilledSlots
1892
+ };
1893
+ }
1894
+ selectBestTemplate(templates, intentResult, input) {
1895
+ let bestScore = -1;
1896
+ let bestTemplate = null;
1897
+ const hasEmptyKeyword = /为空|empty|null/i.test(input);
1898
+ const hasNotEmptyKeyword = /不为空|not empty|not null/i.test(input);
1899
+ for (const template of templates) {
1900
+ const conditions = template.conditions;
1901
+ let score = 0;
1902
+ if (conditions.hasComparison && intentResult.operators.length > 0) score += 1;
1903
+ if (conditions.hasLogical && intentResult.logicalConnectors.length > 0) score += 1;
1904
+ if (conditions.hasCollection) score += 0.5;
1905
+ if (conditions.hasNull) score += 0.5;
1906
+ if (conditions.hasString) score += 1;
1907
+ if (template.name.includes("IS_EMPTY") && hasEmptyKeyword) score += 2;
1908
+ if (template.name.includes("IS_NOT_EMPTY") && hasNotEmptyKeyword) score += 2;
1909
+ if (conditions.entityCount) {
1910
+ if (conditions.entityCount.min && intentResult.entities.length < conditions.entityCount.min) {
1911
+ continue;
1912
+ }
1913
+ }
1914
+ if (score > bestScore) {
1915
+ bestScore = score;
1916
+ bestTemplate = template;
1917
+ }
1918
+ }
1919
+ return bestTemplate;
1920
+ }
1921
+ fillTemplate(template, input, intentResult) {
1922
+ let expression = template;
1923
+ const unfilledSlots = [];
1924
+ let rootName = "order";
1925
+ let fieldName = "field";
1926
+ if (this.contextSchema?.root) {
1927
+ rootName = this.contextSchema.root.name;
1928
+ const fields = Object.keys(this.contextSchema.root.fields ?? {});
1929
+ for (const f of fields) {
1930
+ if (input.includes(f)) {
1931
+ fieldName = f;
1932
+ break;
1933
+ }
1934
+ }
1935
+ } else {
1936
+ const rootMap = {
1937
+ \u8BA2\u5355: "order",
1938
+ order: "order",
1939
+ \u7528\u6237: "user",
1940
+ user: "user",
1941
+ \u6587\u4EF6: "file",
1942
+ file: "file",
1943
+ \u8D26\u53F7: "account",
1944
+ account: "account",
1945
+ \u5546\u54C1: "item",
1946
+ product: "item"
1947
+ };
1948
+ for (const [key, val] of Object.entries(rootMap)) {
1949
+ if (input.includes(key)) {
1950
+ rootName = val;
1951
+ break;
1952
+ }
1953
+ }
1954
+ }
1955
+ expression = expression.replace(/\{root\}/g, rootName);
1956
+ expression = expression.replace(/\{field\}/g, fieldName);
1957
+ const fieldEntities = intentResult.entities.filter((e) => e.type === "field");
1958
+ const field1Name = fieldEntities[0]?.text ?? fieldName;
1959
+ const field2Name = fieldEntities[1]?.text ?? fieldName;
1960
+ expression = expression.replace(/\{field1\}/g, field1Name);
1961
+ expression = expression.replace(/\{field2\}/g, field2Name);
1962
+ expression = expression.replace(/\{conditionField\}/g, "condition");
1963
+ expression = expression.replace(/\{list\}/g, "items");
1964
+ const numberEntities = intentResult.entities.filter(
1965
+ (e) => e.type === "value" && !isNaN(Number(e.text))
1966
+ );
1967
+ const stringEntities = intentResult.entities.filter(
1968
+ (e) => e.type === "value" && isNaN(Number(e.text))
1969
+ );
1970
+ if (numberEntities.length > 0) {
1971
+ expression = expression.replace(/\{value\}/, numberEntities[0].text);
1972
+ expression = expression.replace(/\{value1\}/, numberEntities[0].text);
1973
+ expression = expression.replace(/\{value2\}/, (numberEntities[1] ?? numberEntities[0]).text);
1974
+ expression = expression.replace(/\{min\}/, numberEntities[0].text);
1975
+ expression = expression.replace(/\{max\}/, (numberEntities[1] ?? numberEntities[0]).text);
1976
+ expression = expression.replace(/\{conditionValue\}/, numberEntities[0].text);
1977
+ }
1978
+ if (stringEntities.length > 0) {
1979
+ expression = expression.replace(/\{substring\}/, stringEntities[0].text);
1980
+ expression = expression.replace(/\{element\}/, stringEntities[0].text);
1981
+ expression = expression.replace(/\{role\}/, stringEntities[0].text);
1982
+ expression = expression.replace(/\{permission\}/, stringEntities[0].text);
1983
+ expression = expression.replace(/\{prefix\}/, stringEntities[0].text);
1984
+ expression = expression.replace(/\{suffix\}/, stringEntities[0].text);
1985
+ expression = expression.replace(/\{pattern\}/, stringEntities[0].text);
1986
+ expression = expression.replace(/\{defaultValue\}/, stringEntities[0].text);
1987
+ expression = expression.replace(/\{date\}/, stringEntities[0].text);
1988
+ expression = expression.replace(/\{typeName\}/, stringEntities[0].text);
1989
+ }
1990
+ if (intentResult.operators.length > 0) {
1991
+ expression = expression.replace(/\{operator\}/g, intentResult.operators[0]);
1992
+ expression = expression.replace(/\{operator1\}/g, intentResult.operators[0]);
1993
+ expression = expression.replace(
1994
+ /\{operator2\}/g,
1995
+ intentResult.operators[1] ?? intentResult.operators[0]
1996
+ );
1997
+ expression = expression.replace(/\{conditionOp\}/g, intentResult.operators[0]);
1998
+ }
1999
+ if (intentResult.logicalConnectors.length > 0) {
2000
+ expression = expression.replace(/\{logic\}/g, intentResult.logicalConnectors[0]);
2001
+ } else {
2002
+ expression = expression.replace(/\{logic\}/g, "and");
2003
+ }
2004
+ const unfilledMatches = expression.matchAll(/\{(\w+)\}/g);
2005
+ for (const m of unfilledMatches) {
2006
+ const slot = m[1];
2007
+ const defaults = {
2008
+ operator: "==",
2009
+ operator1: "==",
2010
+ operator2: "==",
2011
+ logic: "and",
2012
+ conditionOp: "=="
2013
+ };
2014
+ if (defaults[slot]) {
2015
+ expression = expression.replace(new RegExp(`\\{${slot}\\}`, "g"), defaults[slot]);
2016
+ } else {
2017
+ unfilledSlots.push(slot);
2018
+ expression = expression.replace(new RegExp(`\\{${slot}\\}`, "g"), `\${${slot}}`);
2019
+ }
2020
+ }
2021
+ return { expression, unfilledSlots };
2022
+ }
2023
+ };
2024
+
2025
+ // src/template/prompts/prompt-builder.ts
2026
+ var SYSTEM_PROMPT_ZH = `You are a Spring Expression Language (SpEL) expert. Your task is to convert natural language descriptions into precise SpEL expressions.
2027
+
2028
+ ## Rules
2029
+ 1. SpEL uses #variable to reference variables, and #root.field to reference root object fields
2030
+ 2. String literals use single quotes: 'value'
2031
+ 3. Null comparison: == null or != null
2032
+ 4. Logical operators: and, or, not (NOT &&, ||, !)
2033
+ 5. Comparison operators: ==, !=, >, <, >=, <=
2034
+ 6. Ternary operator uses Elvis syntax: value ?: default
2035
+ 7. Method calls: list.contains('item'), list.size(), list.isEmpty()
2036
+ 8. Collection projection: list.?[condition], list.^[condition], list.![projection]
2037
+ 9. Range check: value between {min, max}
2038
+ 10. Type check: variable instanceof T(ClassName)
2039
+ 11. Permission check: hasRole('role'), hasPermission('permission')
2040
+ 12. Date comparison: field.after(T(java.util.Date).valueOf('date')), field.before(...)
2041
+
2042
+ ## Output Format
2043
+ Output ONLY the SpEL expression. Do not include any explanation or comments.`;
2044
+ var SYSTEM_PROMPT_EN = `You are a Spring Expression Language (SpEL) expert. Convert natural language descriptions into precise SpEL expressions.
2045
+
2046
+ ## Rules
2047
+ 1. SpEL uses #variable for variables, #root.field for root object fields
2048
+ 2. String literals use single quotes: 'value'
2049
+ 3. Null comparison: == null or != null
2050
+ 4. Logical operators: and, or, not (NOT &&, ||, !)
2051
+ 5. Comparison operators: ==, !=, >, <, >=, <=
2052
+ 6. Elvis operator for defaults: value ?: default
2053
+ 7. Method calls: list.contains('item'), list.size(), list.isEmpty()
2054
+ 8. Collection projection: list.?[condition], list.^[condition], list.![projection]
2055
+ 9. Range check: value between {min, max}
2056
+ 10. Type check: variable instanceof T(ClassName)
2057
+ 11. Permission check: hasRole('role'), hasPermission('permission')
2058
+ 12. Date comparison: field.after(T(java.util.Date).valueOf('date')), field.before(...)
2059
+
2060
+ ## Output Format
2061
+ Output ONLY the SpEL expression. No explanation, no markdown.`;
2062
+ var SPEL_EBNF_GRAMMAR = `
2063
+ ## SpEL EBNF (simplified)
2064
+ expression := logicalOr
2065
+ logicalOr := logicalAnd ('or' logicalAnd)*
2066
+ logicalAnd := equality ('and' equality)*
2067
+ equality := relational (('==' | '!=') relational)?
2068
+ relational := additive (('>' | '<' | '>=' | '<=') additive)?
2069
+ additive := multiplicative (('+' | '-') multiplicative)*
2070
+ multiplicative := unary (('*' | '/' | '%') unary)*
2071
+ unary := ('not' | '!')? primary
2072
+ primary := literal | reference | method | '(' expression ')'
2073
+ | projection | selection | elvis | typeExpr
2074
+ literal := number | string | 'true' | 'false' | 'null'
2075
+ string := "'" (char)* "'"
2076
+ number := [0-9]+ ('.' [0-9]+)?
2077
+ reference := '#' identifier ('.' identifier)*
2078
+ method := reference '.'? identifier '(' args? ')'
2079
+ projection := reference '.[' projectionExpr ']'
2080
+ selection := reference '.?[' expr ']' | reference '.^[' expr ']'
2081
+ elvis := expression '?:' expression
2082
+ typeExpr := 'T(' qualifiedName ')'
2083
+ identifier := [a-zA-Z_][a-zA-Z0-9_]*
2084
+ `;
2085
+ var FEWSHOT_EXAMPLES = [
2086
+ // Easy (9 examples)
2087
+ {
2088
+ nl: "\u8BA2\u5355\u91D1\u989D\u5927\u4E8E1000",
2089
+ spel: "#order.amount > 1000",
2090
+ difficulty: "easy",
2091
+ category: "comparison"
2092
+ },
2093
+ { nl: "amount > 500", spel: "#order.amount > 500", difficulty: "easy", category: "comparison" },
2094
+ {
2095
+ nl: "\u8BA2\u5355\u72B6\u6001\u662F\u5DF2\u53D1\u8D27",
2096
+ spel: "#order.status == '\u5DF2\u53D1\u8D27'",
2097
+ difficulty: "easy",
2098
+ category: "comparison"
2099
+ },
2100
+ {
2101
+ nl: "status equals completed",
2102
+ spel: "#order.status == 'completed'",
2103
+ difficulty: "easy",
2104
+ category: "comparison"
2105
+ },
2106
+ { nl: "\u5907\u6CE8\u4E3A\u7A7A", spel: "#order.remark == null", difficulty: "easy", category: "null_check" },
2107
+ {
2108
+ nl: "remark is null",
2109
+ spel: "#order.remark == null",
2110
+ difficulty: "easy",
2111
+ category: "null_check"
2112
+ },
2113
+ {
2114
+ nl: "\u7528\u6237\u62E5\u6709\u7BA1\u7406\u5458\u89D2\u8272",
2115
+ spel: "hasRole('\u7BA1\u7406\u5458')",
2116
+ difficulty: "easy",
2117
+ category: "permission"
2118
+ },
2119
+ {
2120
+ nl: "user has admin role",
2121
+ spel: "hasRole('admin')",
2122
+ difficulty: "easy",
2123
+ category: "permission"
2124
+ },
2125
+ {
2126
+ nl: "\u6807\u7B7E\u5305\u542BVIP",
2127
+ spel: "#order.tags.contains('VIP')",
2128
+ difficulty: "easy",
2129
+ category: "collection"
2130
+ },
2131
+ // Medium (7 examples)
2132
+ {
2133
+ nl: "\u91D1\u989D\u5927\u4E8E100\u4E14\u8BA2\u5355\u5DF2\u786E\u8BA4",
2134
+ spel: "#order.amount > 100 and #order.status == '\u5DF2\u786E\u8BA4'",
2135
+ difficulty: "medium",
2136
+ category: "logical"
2137
+ },
2138
+ {
2139
+ nl: "amount > 100 and status == done",
2140
+ spel: "#order.amount > 100 and #order.status == 'done'",
2141
+ difficulty: "medium",
2142
+ category: "logical"
2143
+ },
2144
+ {
2145
+ nl: "\u91D1\u989D\u5927\u4E8E500\u6216\u5DF2\u53D1\u8D27",
2146
+ spel: "#order.amount > 500 or #order.status == '\u5DF2\u53D1\u8D27'",
2147
+ difficulty: "medium",
2148
+ category: "logical"
2149
+ },
2150
+ {
2151
+ nl: "\u5E74\u9F84\u572818\u523060\u4E4B\u95F4",
2152
+ spel: "#user.age between {18, 60}",
2153
+ difficulty: "medium",
2154
+ category: "range"
2155
+ },
2156
+ {
2157
+ nl: "amount between 100 and 500",
2158
+ spel: "#order.amount between {100, 500}",
2159
+ difficulty: "medium",
2160
+ category: "range"
2161
+ },
2162
+ {
2163
+ nl: "\u8BA2\u5355\u5907\u6CE8\u5305\u542B\u52A0\u6025",
2164
+ spel: "#order.remark.contains('\u52A0\u6025')",
2165
+ difficulty: "medium",
2166
+ category: "string"
2167
+ },
2168
+ {
2169
+ nl: "name contains test",
2170
+ spel: "#order.name.contains('test')",
2171
+ difficulty: "medium",
2172
+ category: "string"
2173
+ },
2174
+ // Hard (4 examples)
2175
+ {
2176
+ nl: "\u91D1\u989D\u5927\u4E8E1000\u4E14\u72B6\u6001\u4E3A\u5DF2\u53D1\u8D27\u4E14\u7528\u6237\u662FVIP",
2177
+ spel: "#order.amount > 1000 and #order.status == '\u5DF2\u53D1\u8D27' and #user.vip == true",
2178
+ difficulty: "hard",
2179
+ category: "complex_logical"
2180
+ },
2181
+ {
2182
+ nl: "\u7B5B\u9009\u51FA\u6240\u6709\u91D1\u989D\u5927\u4E8E1000\u7684\u8BA2\u5355",
2183
+ spel: "#order.items.?[#this.amount > 1000]",
2184
+ difficulty: "hard",
2185
+ category: "selection"
2186
+ },
2187
+ {
2188
+ nl: "amount > 500 and (status == done or status == shipped)",
2189
+ spel: "#order.amount > 500 and (#order.status == 'done' or #order.status == 'shipped')",
2190
+ difficulty: "hard",
2191
+ category: "complex_logical"
2192
+ },
2193
+ {
2194
+ nl: "\u8BA2\u5355\u4E2D\u7B2C\u4E00\u4E2A\u91D1\u989D\u5927\u4E8E1000\u7684\u5546\u54C1",
2195
+ spel: "#order.items.^[#this.amount > 1000]",
2196
+ difficulty: "hard",
2197
+ category: "selection"
2198
+ }
2199
+ ];
2200
+ var PromptBuilder = class {
2201
+ formatter = new SchemaFormatter();
2202
+ /**
2203
+ * Build a complete LLM Prompt
2204
+ */
2205
+ build(userInput, contextSchema, options = {}) {
2206
+ const lang = options.language ?? "zh";
2207
+ const includeEBNF = options.includeEBNF ?? true;
2208
+ const systemPrompt = lang === "zh" ? SYSTEM_PROMPT_ZH : SYSTEM_PROMPT_EN;
2209
+ const parts = [this.buildExamples(lang)];
2210
+ if (contextSchema && contextSchema.root) {
2211
+ parts.push(this.formatter.formatForLLM(contextSchema));
2212
+ }
2213
+ if (includeEBNF) {
2214
+ parts.push(SPEL_EBNF_GRAMMAR);
2215
+ }
2216
+ parts.push(`## User Input
2217
+ ${userInput}
2218
+
2219
+ ## SpEL Expression
2220
+ `);
2221
+ const user = parts.join("\n\n");
2222
+ return {
2223
+ system: systemPrompt,
2224
+ user,
2225
+ contextSchema: contextSchema ?? {
2226
+ root: null,
2227
+ variables: {},
2228
+ beans: {},
2229
+ types: {},
2230
+ functions: {}
2231
+ },
2232
+ examples: FEWSHOT_EXAMPLES
2233
+ };
2234
+ }
2235
+ /**
2236
+ * Build Few-Shot example string
2237
+ */
2238
+ buildExamples(lang) {
2239
+ const lines = [];
2240
+ const examples = lang === "zh" ? FEWSHOT_EXAMPLES : FEWSHOT_EXAMPLES.filter(
2241
+ (e) => (
2242
+ // Include all English examples (CJK characters indicate non-English)
2243
+ !/[\u4e00-\u9fff\u3400-\u4dbf]/.test(e.nl) || e.category === "comparison" || e.category === "logical"
2244
+ )
2245
+ );
2246
+ if (lang === "zh") {
2247
+ lines.push("## Examples (Few-Shot)");
2248
+ } else {
2249
+ lines.push("## Few-Shot Examples");
2250
+ }
2251
+ for (const example of examples) {
2252
+ lines.push(`NL: ${example.nl}`);
2253
+ lines.push(`SpEL: ${example.spel}`);
2254
+ lines.push("");
2255
+ }
2256
+ return lines.join("\n");
2257
+ }
2258
+ };
2259
+
2260
+ // src/validation/validation-pipeline.ts
2261
+ var ValidationPipeline = class {
2262
+ evaluator;
2263
+ constructor(evaluator = null) {
2264
+ this.evaluator = evaluator;
2265
+ }
2266
+ setEvaluator(evaluator) {
2267
+ this.evaluator = evaluator;
2268
+ }
2269
+ /**
2270
+ * Run the full validation pipeline
2271
+ */
2272
+ async validate(expression, contextSchema) {
2273
+ const errors = [];
2274
+ const warnings = [];
2275
+ const parseStage = await this.validateParse(expression);
2276
+ const typeStage = this.validateTypes(expression, contextSchema);
2277
+ const semanticStage = this.validateSemantic(expression);
2278
+ const contextStage = this.validateContext(expression, contextSchema);
2279
+ errors.push(
2280
+ ...parseStage.errors,
2281
+ ...typeStage.errors,
2282
+ ...semanticStage.errors,
2283
+ ...contextStage.errors
2284
+ );
2285
+ warnings.push(
2286
+ ...parseStage.warnings,
2287
+ ...typeStage.warnings,
2288
+ ...semanticStage.warnings,
2289
+ ...contextStage.warnings
2290
+ );
2291
+ return {
2292
+ valid: parseStage.passed && typeStage.passed && semanticStage.passed && contextStage.passed,
2293
+ stages: {
2294
+ parse: parseStage,
2295
+ type: typeStage,
2296
+ semantic: semanticStage,
2297
+ context: contextStage
2298
+ },
2299
+ errors,
2300
+ warnings
2301
+ };
2302
+ }
2303
+ /**
2304
+ * Stage 1: Parse Check — syntax validation
2305
+ */
2306
+ async validateParse(expression) {
2307
+ const errors = [];
2308
+ const warnings = [];
2309
+ if (!expression || expression.trim().length === 0) {
2310
+ errors.push({
2311
+ code: "PARSE-EMPTY",
2312
+ message: "Expression is empty",
2313
+ stage: "parse",
2314
+ requiresLLM: true
2315
+ });
2316
+ return { passed: false, errors, warnings };
2317
+ }
2318
+ if (!this.hasBalancedParentheses(expression)) {
2319
+ errors.push({
2320
+ code: "PARSE-UNBALANCED_PARENS",
2321
+ message: "Unbalanced parentheses in expression",
2322
+ stage: "parse",
2323
+ requiresLLM: true
2324
+ });
2325
+ }
2326
+ if (expression.includes("===") || expression.includes("!==")) {
2327
+ errors.push({
2328
+ code: "PARSE-JS_OPERATOR",
2329
+ message: "JavaScript operators detected (=== or !==), use == or != in SpEL",
2330
+ stage: "parse",
2331
+ requiresLLM: true
2332
+ });
2333
+ }
2334
+ if (expression.includes("&&")) {
2335
+ errors.push({
2336
+ code: "PARSE-JS_LOGIC",
2337
+ message: 'JavaScript && detected, use "and" in SpEL',
2338
+ stage: "parse",
2339
+ requiresLLM: true
2340
+ });
2341
+ }
2342
+ if (expression.includes("||")) {
2343
+ errors.push({
2344
+ code: "PARSE-JS_LOGIC",
2345
+ message: 'JavaScript || detected, use "or" in SpEL',
2346
+ stage: "parse",
2347
+ requiresLLM: true
2348
+ });
2349
+ }
2350
+ if (this.evaluator) {
2351
+ try {
2352
+ const parseResult = await this.evaluator.parse(expression);
2353
+ if (!parseResult.valid) {
2354
+ for (const pe of parseResult.errors) {
2355
+ errors.push({
2356
+ code: `PARSE-${pe.code ?? "SYNTAX"}`,
2357
+ message: pe.message,
2358
+ position: pe.position,
2359
+ stage: "parse",
2360
+ requiresLLM: true
2361
+ });
2362
+ }
2363
+ }
2364
+ } catch (err) {
2365
+ errors.push({
2366
+ code: "PARSE-EXCEPTION",
2367
+ message: `Parse threw exception: ${err.message}`,
2368
+ stage: "parse",
2369
+ requiresLLM: true
2370
+ });
2371
+ }
2372
+ }
2373
+ return {
2374
+ passed: errors.length === 0,
2375
+ errors,
2376
+ warnings
2377
+ };
2378
+ }
2379
+ /**
2380
+ * Stage 2: Type Check — type validation
2381
+ */
2382
+ validateTypes(expression, contextSchema) {
2383
+ const errors = [];
2384
+ const warnings = [];
2385
+ const strNumMismatch = /'(?:\\.|[^'\\])*'\s*(?:>|<|>=|<=)\s*\d+/;
2386
+ if (strNumMismatch.test(expression)) {
2387
+ warnings.push({
2388
+ code: "TYPE-STR_NUM_CMP",
2389
+ message: "String literal compared with number using arithmetic operator",
2390
+ stage: "type"
2391
+ });
2392
+ }
2393
+ if (contextSchema?.root) {
2394
+ for (const [fieldName, field] of Object.entries(contextSchema.root.fields)) {
2395
+ if (field.type === "boolean") {
2396
+ const boolNumPattern = new RegExp(`#\\w+\\.${fieldName}\\s*(?:>|<|>=|<=)\\s*\\d+`);
2397
+ if (boolNumPattern.test(expression)) {
2398
+ warnings.push({
2399
+ code: "TYPE-BOOL_NUM_CMP",
2400
+ message: `Boolean field '${fieldName}' compared with number`,
2401
+ stage: "type"
2402
+ });
2403
+ }
2404
+ }
2405
+ }
2406
+ }
2407
+ return {
2408
+ passed: errors.length === 0,
2409
+ errors,
2410
+ warnings
2411
+ };
2412
+ }
2413
+ /**
2414
+ * Stage 3: Semantic Check — semantic reasonableness validation
2415
+ */
2416
+ validateSemantic(expression) {
2417
+ const errors = [];
2418
+ const warnings = [];
2419
+ const selfCompare = /(#\w+(?:\.\w+)*)\s*==\s*\1/;
2420
+ if (selfCompare.test(expression)) {
2421
+ warnings.push({
2422
+ code: "SEM-SELF_COMPARE",
2423
+ message: "Self-comparison detected: expression is always true",
2424
+ stage: "semantic"
2425
+ });
2426
+ }
2427
+ if (expression.includes("!!")) {
2428
+ warnings.push({
2429
+ code: "SEM-DOUBLE_NEGATION",
2430
+ message: "Double negation detected, consider simplifying",
2431
+ stage: "semantic"
2432
+ });
2433
+ }
2434
+ return {
2435
+ passed: errors.length === 0,
2436
+ errors,
2437
+ warnings
2438
+ };
2439
+ }
2440
+ /**
2441
+ * Stage 4: Context Check — context reference validation
2442
+ */
2443
+ validateContext(expression, contextSchema) {
2444
+ const errors = [];
2445
+ const warnings = [];
2446
+ if (!contextSchema) {
2447
+ warnings.push({
2448
+ code: "CTX-NO_SCHEMA",
2449
+ message: "No ContextSchema provided, skipping context validation",
2450
+ stage: "context"
2451
+ });
2452
+ return { passed: true, errors, warnings };
2453
+ }
2454
+ const refs = this.extractReferences(expression);
2455
+ if (contextSchema.root) {
2456
+ const rootName = contextSchema.root.name;
2457
+ for (const ref of refs) {
2458
+ if (ref.startsWith(`#${rootName}`)) {
2459
+ const parts = ref.split(".");
2460
+ const field = parts[1];
2461
+ if (field && !(field in contextSchema.root.fields)) {
2462
+ warnings.push({
2463
+ code: "CTX-UNKNOWN_FIELD",
2464
+ message: `Field '${field}' not found in root '${rootName}'`,
2465
+ stage: "context"
2466
+ });
2467
+ }
2468
+ }
2469
+ }
2470
+ }
2471
+ for (const ref of refs) {
2472
+ if (ref.startsWith("#") && !ref.includes(".")) {
2473
+ const varName = ref.slice(1);
2474
+ const isRoot = contextSchema.root?.name === varName;
2475
+ const isVariable = varName in contextSchema.variables;
2476
+ const isFunction = varName in contextSchema.functions;
2477
+ if (!isRoot && !isVariable && !isFunction) {
2478
+ warnings.push({
2479
+ code: "CTX-UNKNOWN_REF",
2480
+ message: `Unknown reference '${ref}'`,
2481
+ stage: "context"
2482
+ });
2483
+ }
2484
+ }
2485
+ }
2486
+ const beanMatch = expression.match(/@(\w+)/g);
2487
+ if (beanMatch && contextSchema.beans) {
2488
+ for (const b of beanMatch) {
2489
+ const beanName = b.slice(1);
2490
+ if (!(beanName in contextSchema.beans)) {
2491
+ warnings.push({
2492
+ code: "CTX-UNKNOWN_BEAN",
2493
+ message: `Bean '${beanName}' not found in ContextSchema`,
2494
+ stage: "context"
2495
+ });
2496
+ }
2497
+ }
2498
+ }
2499
+ return {
2500
+ passed: errors.length === 0,
2501
+ errors,
2502
+ warnings
2503
+ };
2504
+ }
2505
+ /**
2506
+ * Check if parentheses are balanced
2507
+ */
2508
+ hasBalancedParentheses(expression) {
2509
+ const stack = [];
2510
+ const pairs = { "(": ")", "[": "]", "{": "}" };
2511
+ for (const ch of expression) {
2512
+ if (ch in pairs) {
2513
+ stack.push(ch);
2514
+ } else if (ch === ")" || ch === "]" || ch === "}") {
2515
+ const last = stack.pop();
2516
+ if (!last || pairs[last] !== ch) return false;
2517
+ }
2518
+ }
2519
+ return stack.length === 0;
2520
+ }
2521
+ /**
2522
+ * Extract all identifier references from expression
2523
+ */
2524
+ extractReferences(expression) {
2525
+ const refs = [];
2526
+ const varMatch = expression.matchAll(/#(\w+(?:\.\w+(?:\.\w+)?)?)/g);
2527
+ for (const m of varMatch) {
2528
+ refs.push(`#${m[1]}`);
2529
+ }
2530
+ const simpleMatch = expression.matchAll(/#(\w+)(?!\w*\()/g);
2531
+ for (const m of simpleMatch) {
2532
+ const ref = `#${m[1]}`;
2533
+ if (!refs.includes(ref)) {
2534
+ refs.push(ref);
2535
+ }
2536
+ }
2537
+ return refs;
2538
+ }
2539
+ };
2540
+
2541
+ // src/validation/auto-fixer.ts
2542
+ var AutoFixer = class {
2543
+ fix(expression) {
2544
+ let fixed = expression;
2545
+ const changes = [];
2546
+ if (fixed.includes("=== undefined")) {
2547
+ fixed = fixed.replace(/=== undefined/g, "== null");
2548
+ changes.push("Replaced === undefined with == null");
2549
+ }
2550
+ if (fixed.includes("!== undefined")) {
2551
+ fixed = fixed.replace(/!== undefined/g, "!= null");
2552
+ changes.push("Replaced !== undefined with != null");
2553
+ }
2554
+ if (fixed.includes("!==")) {
2555
+ const count = fixed.match(/!==/g).length;
2556
+ fixed = fixed.replace(/!==/g, "!=");
2557
+ changes.push(`Replaced ${count}x !== with !=`);
2558
+ }
2559
+ if (fixed.includes("===")) {
2560
+ const count = fixed.match(/===/g).length;
2561
+ fixed = fixed.replace(/===/g, "==");
2562
+ changes.push(`Replaced ${count}x === with ==`);
2563
+ }
2564
+ if (fixed.includes("&&")) {
2565
+ const count = fixed.match(/&&/g).length;
2566
+ fixed = fixed.replace(/&&/g, "and");
2567
+ changes.push(`Replaced ${count}x && with and`);
2568
+ }
2569
+ if (fixed.includes("||")) {
2570
+ const count = fixed.match(/\|\|/g).length;
2571
+ fixed = fixed.replace(/\|\|/g, "or");
2572
+ changes.push(`Replaced ${count}x || with or`);
2573
+ }
2574
+ const singleQuoteCount = (fixed.match(/'/g) ?? []).length;
2575
+ if (singleQuoteCount % 2 !== 0) {
2576
+ if (!fixed.endsWith("'")) {
2577
+ fixed = fixed + "'";
2578
+ changes.push("Added missing closing single quote");
2579
+ }
2580
+ }
2581
+ const doubleQuoteCount = (fixed.match(/"/g) ?? []).length;
2582
+ if (doubleQuoteCount % 2 !== 0) {
2583
+ if (!fixed.endsWith('"')) {
2584
+ fixed = fixed + '"';
2585
+ changes.push("Added missing closing double quote");
2586
+ }
2587
+ }
2588
+ const parenFix = this.fixAllBrackets(fixed);
2589
+ if (parenFix !== fixed) {
2590
+ changes.push("Fixed unbalanced brackets");
2591
+ fixed = parenFix;
2592
+ }
2593
+ if (fixed.includes("> ==")) {
2594
+ fixed = fixed.replace(/> ==/g, ">=");
2595
+ changes.push("Replaced > == with >=");
2596
+ }
2597
+ if (fixed.includes("< ==")) {
2598
+ fixed = fixed.replace(/< ==/g, "<=");
2599
+ changes.push("Replaced < == with <=");
2600
+ }
2601
+ if (fixed.includes("? :")) {
2602
+ fixed = fixed.replace(/\?\s*:\s*/g, " ?: ");
2603
+ changes.push("Fixed Elvis operator spacing");
2604
+ }
2605
+ const wasFixed = changes.length > 0;
2606
+ return {
2607
+ wasFixed,
2608
+ expression: wasFixed ? fixed : expression,
2609
+ changes
2610
+ };
2611
+ }
2612
+ fixAllBrackets(expr) {
2613
+ let result = expr;
2614
+ const openParen = (result.match(/\(/g) ?? []).length;
2615
+ const closeParen = (result.match(/\)/g) ?? []).length;
2616
+ if (openParen > closeParen) {
2617
+ result += ")".repeat(openParen - closeParen);
2618
+ }
2619
+ const openBracket = (result.match(/\[/g) ?? []).length;
2620
+ const closeBracket = (result.match(/\]/g) ?? []).length;
2621
+ if (openBracket > closeBracket) {
2622
+ result += "]".repeat(openBracket - closeBracket);
2623
+ }
2624
+ const openBrace = (result.match(/\{/g) ?? []).length;
2625
+ const closeBrace = (result.match(/\}/g) ?? []).length;
2626
+ if (openBrace > closeBrace) {
2627
+ result += "}".repeat(openBrace - closeBrace);
2628
+ }
2629
+ return result;
2630
+ }
2631
+ };
2632
+
2633
+ // src/validation/self-correction-loop.ts
2634
+ var SelfCorrectionLoop = class {
2635
+ pipeline;
2636
+ autoFixer;
2637
+ config;
2638
+ constructor(config = {}, pipeline, autoFixer) {
2639
+ this.config = {
2640
+ maxAttempts: config.maxAttempts ?? 3,
2641
+ minConfidence: config.minConfidence ?? 0.7,
2642
+ enableAutoFix: config.enableAutoFix ?? true
2643
+ };
2644
+ this.pipeline = pipeline ?? new ValidationPipeline();
2645
+ this.autoFixer = autoFixer ?? new AutoFixer();
2646
+ }
2647
+ /**
2648
+ * Execute the self-correction loop
2649
+ */
2650
+ async correct(expression, contextSchema, generateFn, originalPrompt) {
2651
+ const startTime = Date.now();
2652
+ const corrections = [];
2653
+ let currentExpression = expression;
2654
+ let valid = false;
2655
+ const initialResult = await this.pipeline.validate(currentExpression, contextSchema);
2656
+ if (initialResult.valid) {
2657
+ return {
2658
+ expression: currentExpression,
2659
+ valid: true,
2660
+ originalOutput: expression,
2661
+ correctionAttempts: 0,
2662
+ corrections,
2663
+ totalLatencyMs: Date.now() - startTime
2664
+ };
2665
+ }
2666
+ if (this.config.enableAutoFix && initialResult.errors.length > 0) {
2667
+ const autoFixResult = this.autoFixer.fix(currentExpression);
2668
+ if (autoFixResult.wasFixed) {
2669
+ currentExpression = autoFixResult.expression;
2670
+ const afterFix = await this.pipeline.validate(currentExpression, contextSchema);
2671
+ corrections.push({
2672
+ attempt: 0,
2673
+ expression: currentExpression,
2674
+ valid: afterFix.valid,
2675
+ errorCount: afterFix.errors.length,
2676
+ warningCount: afterFix.warnings.length,
2677
+ autoFixed: true,
2678
+ autoFixChanges: autoFixResult.changes
2679
+ });
2680
+ if (afterFix.valid) {
2681
+ return {
2682
+ expression: currentExpression,
2683
+ valid: true,
2684
+ originalOutput: expression,
2685
+ correctionAttempts: 1,
2686
+ corrections,
2687
+ totalLatencyMs: Date.now() - startTime
2688
+ };
2689
+ }
2690
+ }
2691
+ }
2692
+ for (let attempt = 1; attempt <= this.config.maxAttempts; attempt++) {
2693
+ const lastValidation = await this.pipeline.validate(currentExpression, contextSchema);
2694
+ const errorDescriptions = lastValidation.errors.map((e) => `- [${e.code}] ${e.message}`).join("\n");
2695
+ const correctionPrompt = {
2696
+ ...originalPrompt,
2697
+ user: [
2698
+ originalPrompt.user,
2699
+ "",
2700
+ "---",
2701
+ "The previous generated expression was INVALID. Please fix it:",
2702
+ `Expression: ${currentExpression}`,
2703
+ `Errors:
2704
+ ${errorDescriptions}`,
2705
+ "---",
2706
+ "Generate ONLY the corrected SpEL expression, nothing else."
2707
+ ].join("\n")
2708
+ };
2709
+ try {
2710
+ const response = await generateFn(correctionPrompt);
2711
+ currentExpression = response.text.trim();
2712
+ if (this.config.enableAutoFix) {
2713
+ const afResult = this.autoFixer.fix(currentExpression);
2714
+ if (afResult.wasFixed) {
2715
+ currentExpression = afResult.expression;
2716
+ }
2717
+ }
2718
+ const currentValidation = await this.pipeline.validate(currentExpression, contextSchema);
2719
+ corrections.push({
2720
+ attempt,
2721
+ expression: currentExpression,
2722
+ valid: currentValidation.valid,
2723
+ errorCount: currentValidation.errors.length,
2724
+ warningCount: currentValidation.warnings.length,
2725
+ autoFixed: false,
2726
+ autoFixChanges: []
2727
+ });
2728
+ if (currentValidation.valid) {
2729
+ valid = true;
2730
+ break;
2731
+ }
2732
+ } catch (err) {
2733
+ corrections.push({
2734
+ attempt,
2735
+ expression: currentExpression,
2736
+ valid: false,
2737
+ errorCount: 1,
2738
+ warningCount: 0,
2739
+ autoFixed: false,
2740
+ autoFixChanges: []
2741
+ });
2742
+ }
2743
+ }
2744
+ return {
2745
+ expression: currentExpression,
2746
+ valid,
2747
+ originalOutput: expression,
2748
+ correctionAttempts: corrections.filter((c) => c.attempt > 0).length,
2749
+ corrections,
2750
+ totalLatencyMs: Date.now() - startTime
2751
+ };
2752
+ }
2753
+ };
2754
+
2755
+ // src/strategy/strategy-router.ts
2756
+ var StrategyRouter = class {
2757
+ patternMatcher;
2758
+ intentClassifier;
2759
+ templateEngine;
2760
+ promptBuilder;
2761
+ validationPipeline;
2762
+ autoFixer;
2763
+ providerRegistry;
2764
+ config;
2765
+ constructor(providerRegistry, config = {}) {
2766
+ this.patternMatcher = new PatternMatcher(BUILTIN_PATTERNS);
2767
+ this.intentClassifier = new IntentClassifier();
2768
+ this.templateEngine = new TemplateEngine();
2769
+ this.promptBuilder = new PromptBuilder();
2770
+ this.validationPipeline = new ValidationPipeline();
2771
+ this.autoFixer = new AutoFixer();
2772
+ this.providerRegistry = providerRegistry;
2773
+ this.config = {
2774
+ patternMinConfidence: config.patternMinConfidence ?? 0.7,
2775
+ templateMinConfidence: config.templateMinConfidence ?? 0.6,
2776
+ llmMinConfidence: config.llmMinConfidence ?? 0.5,
2777
+ enableSelfCorrection: config.enableSelfCorrection ?? true,
2778
+ maxCorrectionAttempts: config.maxCorrectionAttempts ?? 3
2779
+ };
2780
+ }
2781
+ /**
2782
+ * Execute the full generation strategy routing
2783
+ */
2784
+ async generate(nl, contextSchema, forceLLMProvider) {
2785
+ const startTime = Date.now();
2786
+ if (contextSchema) {
2787
+ this.templateEngine.setContext(contextSchema);
2788
+ }
2789
+ const patternResult = this.patternMatcher.match(nl);
2790
+ if (patternResult.matched && patternResult.confidence >= this.config.patternMinConfidence) {
2791
+ const validation = await this.validationPipeline.validate(patternResult.spel, contextSchema);
2792
+ if (validation.valid) {
2793
+ return {
2794
+ expression: patternResult.spel,
2795
+ strategy: "pattern",
2796
+ confidence: patternResult.confidence,
2797
+ metadata: { patternId: patternResult.pattern?.id },
2798
+ latencyMs: Date.now() - startTime
2799
+ };
2800
+ }
2801
+ try {
2802
+ const afResult = this.autoFixer.fix(patternResult.spel);
2803
+ if (afResult.wasFixed) {
2804
+ const afValidation = await this.validationPipeline.validate(
2805
+ afResult.expression,
2806
+ contextSchema
2807
+ );
2808
+ if (afValidation.valid) {
2809
+ return {
2810
+ expression: afResult.expression,
2811
+ strategy: "pattern",
2812
+ confidence: patternResult.confidence * 0.95,
2813
+ metadata: { patternId: patternResult.pattern?.id },
2814
+ latencyMs: Date.now() - startTime
2815
+ };
2816
+ }
2817
+ }
2818
+ } catch {
2819
+ }
2820
+ }
2821
+ const intentResult = this.intentClassifier.classify(nl);
2822
+ let templateResult = null;
2823
+ try {
2824
+ templateResult = this.templateEngine.generate(nl, intentResult);
2825
+ } catch {
2826
+ }
2827
+ if (templateResult && templateResult.confidence >= this.config.templateMinConfidence && templateResult.unfilledSlots.length === 0) {
2828
+ const validation = await this.validationPipeline.validate(
2829
+ templateResult.expression,
2830
+ contextSchema
2831
+ );
2832
+ if (validation.valid) {
2833
+ return {
2834
+ expression: templateResult.expression,
2835
+ strategy: "template",
2836
+ confidence: templateResult.confidence,
2837
+ metadata: {
2838
+ intent: templateResult.intent,
2839
+ templateName: templateResult.templateName
2840
+ },
2841
+ latencyMs: Date.now() - startTime
2842
+ };
2843
+ }
2844
+ }
2845
+ const prompt = this.promptBuilder.build(nl, contextSchema);
2846
+ let providers = await this.providerRegistry.getPrioritized();
2847
+ if (forceLLMProvider) {
2848
+ const forced = providers.find((p) => p.name === forceLLMProvider);
2849
+ if (forced) {
2850
+ providers = [forced, ...providers.filter((p) => p !== forced)];
2851
+ }
2852
+ }
2853
+ if (providers.length === 0) {
2854
+ throw new Error("No LLM providers available");
2855
+ }
2856
+ let lastError = null;
2857
+ for (const provider of providers) {
2858
+ try {
2859
+ const response = await provider.generate(prompt, { timeout: 3e4 });
2860
+ let expression = response.text.trim();
2861
+ let corrections = 0;
2862
+ if (this.config.enableSelfCorrection && contextSchema) {
2863
+ const correctionLoop = new SelfCorrectionLoop(
2864
+ {
2865
+ maxAttempts: this.config.maxCorrectionAttempts
2866
+ },
2867
+ this.validationPipeline
2868
+ );
2869
+ const correctionResult = await correctionLoop.correct(
2870
+ expression,
2871
+ contextSchema,
2872
+ (prompt2) => provider.generate(prompt2),
2873
+ prompt
2874
+ );
2875
+ expression = correctionResult.expression;
2876
+ corrections = correctionResult.correctionAttempts;
2877
+ }
2878
+ return {
2879
+ expression,
2880
+ strategy: "llm-api",
2881
+ confidence: 0.8,
2882
+ metadata: {
2883
+ providerName: provider.name,
2884
+ model: response.model,
2885
+ corrections,
2886
+ rawOutput: response.text
2887
+ },
2888
+ latencyMs: Date.now() - startTime
2889
+ };
2890
+ } catch (err) {
2891
+ lastError = err;
2892
+ continue;
2893
+ }
2894
+ }
2895
+ throw new Error(`All LLM providers failed. Last error: ${lastError?.message ?? "Unknown"}`);
2896
+ }
2897
+ /**
2898
+ * Get PatternMatcher (for external testing/debugging)
2899
+ */
2900
+ getPatternMatcher() {
2901
+ return this.patternMatcher;
2902
+ }
2903
+ /**
2904
+ * Get TemplateEngine
2905
+ */
2906
+ getTemplateEngine() {
2907
+ return this.templateEngine;
2908
+ }
2909
+ /**
2910
+ * Get PromptBuilder
2911
+ */
2912
+ getPromptBuilder() {
2913
+ return this.promptBuilder;
2914
+ }
2915
+ /**
2916
+ * Set SpelEvaluator for validation pipeline.
2917
+ * Must be called before generate() if parse-level validation is needed.
2918
+ */
2919
+ setEvaluator(evaluator) {
2920
+ this.validationPipeline.setEvaluator(evaluator);
2921
+ }
2922
+ };
2923
+
2924
+ // src/engine/nl2spel-engine.ts
2925
+ var NL2SpelEngine = class {
2926
+ router;
2927
+ providerRegistry;
2928
+ evaluatorInstance = null;
2929
+ constructor(config = {}) {
2930
+ this.providerRegistry = new ProviderRegistry();
2931
+ this.router = new StrategyRouter(this.providerRegistry, config);
2932
+ }
2933
+ /**
2934
+ * Register an LLM Provider
2935
+ */
2936
+ registerProvider(provider) {
2937
+ this.providerRegistry.register(provider);
2938
+ }
2939
+ /**
2940
+ * Unregister an LLM Provider
2941
+ */
2942
+ unregisterProvider(name) {
2943
+ this.providerRegistry.unregister(name);
2944
+ }
2945
+ /**
2946
+ * Register a custom Pattern
2947
+ */
2948
+ registerPattern(pattern) {
2949
+ this.router.getPatternMatcher().register(pattern);
2950
+ }
2951
+ /**
2952
+ * Set SpelEvaluator for validation pipeline.
2953
+ * Must be called before generate() if parse-level validation is required.
2954
+ */
2955
+ setSpelEvaluator(evaluator) {
2956
+ this.evaluatorInstance = evaluator;
2957
+ this.router.setEvaluator(evaluator);
2958
+ }
2959
+ /**
2960
+ * Extract ContextSchema from raw context object
2961
+ */
2962
+ extractContextSchema(context) {
2963
+ const extractor = new ContextExtractor();
2964
+ return extractor.extract(context);
2965
+ }
2966
+ /**
2967
+ * Core generation API
2968
+ */
2969
+ async generate(nl, options = {}) {
2970
+ const startTime = Date.now();
2971
+ let contextSchema = options.contextSchema;
2972
+ if (!contextSchema && this.evaluatorInstance) {
2973
+ const schema = await Promise.resolve(this.evaluatorInstance.getContextSchema());
2974
+ if (schema) contextSchema = schema;
2975
+ }
2976
+ if (!contextSchema && options.context) {
2977
+ const extractor = new ContextExtractor();
2978
+ contextSchema = extractor.extract(options.context);
2979
+ }
2980
+ if (options.offlineOnly) {
2981
+ const patternMatcher = this.router.getPatternMatcher();
2982
+ const patternResult = patternMatcher.match(nl);
2983
+ if (patternResult.matched) {
2984
+ return {
2985
+ expression: patternResult.spel,
2986
+ strategy: "pattern",
2987
+ confidence: patternResult.confidence,
2988
+ latencyMs: Date.now() - startTime
2989
+ };
2990
+ }
2991
+ const classifier = new IntentClassifier();
2992
+ const intentResult = classifier.classify(nl);
2993
+ const templateEngine = this.router.getTemplateEngine();
2994
+ if (contextSchema) templateEngine.setContext(contextSchema);
2995
+ const templateResult = templateEngine.generate(nl, intentResult);
2996
+ if (templateResult) {
2997
+ return {
2998
+ expression: templateResult.expression,
2999
+ strategy: "template",
3000
+ confidence: templateResult.confidence,
3001
+ latencyMs: Date.now() - startTime
3002
+ };
3003
+ }
3004
+ throw new Error("Cannot generate expression offline: no pattern or template matched.");
3005
+ }
3006
+ const result = await this.router.generate(nl, contextSchema, options.preferredProvider);
3007
+ return {
3008
+ expression: result.expression,
3009
+ strategy: result.strategy,
3010
+ confidence: result.confidence,
3011
+ latencyMs: Date.now() - startTime
3012
+ };
3013
+ }
3014
+ /**
3015
+ * Batch generation
3016
+ */
3017
+ async generateBatch(nls, options = {}) {
3018
+ return Promise.all(nls.map((nl) => this.generate(nl, options)));
3019
+ }
3020
+ /**
3021
+ * Debug explanation
3022
+ */
3023
+ async explain(nl, options = {}) {
3024
+ const classifier = new IntentClassifier();
3025
+ const intentResult = classifier.classify(nl);
3026
+ const patternMatcher = this.router.getPatternMatcher();
3027
+ const patternResult = patternMatcher.match(nl);
3028
+ const allPatternResults = patternMatcher.matchAll(nl, 5);
3029
+ const result = await this.generate(nl, { ...options });
3030
+ return {
3031
+ input: nl,
3032
+ strategy: result.strategy,
3033
+ intent: {
3034
+ primary: intentResult.primaryIntent,
3035
+ complexity: intentResult.complexity,
3036
+ all: intentResult.intents
3037
+ },
3038
+ patternMatched: patternResult.matched,
3039
+ patternId: patternResult.pattern?.id,
3040
+ expression: result.expression,
3041
+ alternatives: allPatternResults.filter((r) => r.spel).map((r) => r.spel)
3042
+ };
3043
+ }
3044
+ };
3045
+ // Annotate the CommonJS export names for ESM import in node:
3046
+ 0 && (module.exports = {
3047
+ AutoFixer,
3048
+ BUILTIN_PATTERNS,
3049
+ ChineseNumberParser,
3050
+ ContextExtractor,
3051
+ IntentClassifier,
3052
+ NL2SpelEngine,
3053
+ NLIntent,
3054
+ PatternMatcher,
3055
+ PromptBuilder,
3056
+ ProviderRegistry,
3057
+ SchemaFormatter,
3058
+ SelfCorrectionLoop,
3059
+ StrategyRouter,
3060
+ TemplateEngine,
3061
+ ValidationPipeline
3062
+ });