@agentix-e/nl2spel 1.2.1 → 1.2.2

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