@lokascript/framework 4.8.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/dist/api/create-dsl.d.ts +9 -10
  3. package/dist/api/create-dsl.d.ts.map +1 -1
  4. package/dist/api/index.js +835 -1670
  5. package/dist/api/index.js.map +1 -1
  6. package/dist/core/index.d.ts +0 -1
  7. package/dist/core/index.d.ts.map +1 -1
  8. package/dist/core/index.js +549 -1132
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/core/matching/index.d.ts +2 -0
  11. package/dist/core/matching/index.d.ts.map +1 -1
  12. package/dist/core/matching/possessive-keywords.d.ts.map +1 -0
  13. package/dist/core/matching/stock-confidence.d.ts +3 -4
  14. package/dist/core/matching/stock-confidence.d.ts.map +1 -1
  15. package/dist/core/matching/stock-reading.d.ts +4 -4
  16. package/dist/core/{pattern-matching/utils → matching}/type-validation.d.ts +1 -1
  17. package/dist/core/matching/type-validation.d.ts.map +1 -0
  18. package/dist/generation/index.d.ts +1 -1
  19. package/dist/generation/index.d.ts.map +1 -1
  20. package/dist/generation/index.js +20 -169
  21. package/dist/generation/index.js.map +1 -1
  22. package/dist/generation/profile.d.ts +30 -0
  23. package/dist/generation/profile.d.ts.map +1 -0
  24. package/dist/generation/renderer.d.ts +1 -1
  25. package/dist/generation/renderer.d.ts.map +1 -1
  26. package/dist/generation/schema-patterns.d.ts +3 -3
  27. package/dist/index.cjs +929 -1674
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +929 -1668
  30. package/dist/index.js.map +1 -1
  31. package/dist/multilingual/builders.d.ts +3 -3
  32. package/dist/multilingual/builders.d.ts.map +1 -1
  33. package/dist/multilingual/index.js.map +1 -1
  34. package/dist/parsing/index.d.ts +0 -1
  35. package/dist/parsing/index.d.ts.map +1 -1
  36. package/dist/parsing/index.js +0 -1145
  37. package/dist/parsing/index.js.map +1 -1
  38. package/dist/testing/gate/lint/types.d.ts +1 -1
  39. package/dist/testing/gate/lint/types.d.ts.map +1 -1
  40. package/package.json +3 -8
  41. package/src/__test__/test-utils.ts +1 -1
  42. package/src/api/create-dsl-engine.test.ts +60 -35
  43. package/src/api/create-dsl.ts +24 -35
  44. package/src/core/index.ts +0 -1
  45. package/src/core/matching/index.ts +2 -0
  46. package/src/core/matching/stock-confidence.ts +3 -4
  47. package/src/core/matching/stock-reading.ts +6 -6
  48. package/src/core/{pattern-matching/utils → matching}/type-validation.test.ts +2 -2
  49. package/src/core/{pattern-matching/utils → matching}/type-validation.ts +1 -1
  50. package/src/generation/domain-renderer.test.ts +1 -1
  51. package/src/generation/index.ts +1 -1
  52. package/src/generation/profile.ts +41 -0
  53. package/src/generation/renderer.test.ts +1 -1
  54. package/src/generation/renderer.ts +1 -1
  55. package/src/generation/schema-patterns.ts +3 -3
  56. package/src/multilingual/bridge.test.ts +31 -23
  57. package/src/multilingual/builders.ts +3 -3
  58. package/src/parsing/index.ts +0 -1
  59. package/src/testing/gate/lint/types.ts +1 -1
  60. package/dist/core/pattern-matching/index.d.ts +0 -6
  61. package/dist/core/pattern-matching/index.d.ts.map +0 -1
  62. package/dist/core/pattern-matching/index.js +0 -1211
  63. package/dist/core/pattern-matching/index.js.map +0 -1
  64. package/dist/core/pattern-matching/pattern-matcher.d.ts +0 -122
  65. package/dist/core/pattern-matching/pattern-matcher.d.ts.map +0 -1
  66. package/dist/core/pattern-matching/utils/index.d.ts +0 -6
  67. package/dist/core/pattern-matching/utils/index.d.ts.map +0 -1
  68. package/dist/core/pattern-matching/utils/possessive-keywords.d.ts.map +0 -1
  69. package/dist/core/pattern-matching/utils/type-validation.d.ts.map +0 -1
  70. package/dist/generation/pattern-generator.d.ts +0 -55
  71. package/dist/generation/pattern-generator.d.ts.map +0 -1
  72. package/src/core/pattern-matching/index.ts +0 -6
  73. package/src/core/pattern-matching/pattern-matcher.test.ts +0 -1031
  74. package/src/core/pattern-matching/pattern-matcher.ts +0 -765
  75. package/src/core/pattern-matching/utils/index.ts +0 -6
  76. package/src/generation/pattern-generator.test.ts +0 -532
  77. package/src/generation/pattern-generator.ts +0 -327
  78. /package/dist/core/{pattern-matching/utils → matching}/possessive-keywords.d.ts +0 -0
  79. /package/src/core/{pattern-matching/utils → matching}/possessive-keywords.ts +0 -0
package/dist/index.cjs CHANGED
@@ -41,7 +41,6 @@ __export(index_exports, {
41
41
  NullProfileProvider: () => NullProfileProvider,
42
42
  NumberExtractor: () => NumberExtractor,
43
43
  OperatorExtractor: () => OperatorExtractor,
44
- PatternMatcher: () => PatternMatcher,
45
44
  PatternTracker: () => PatternTracker,
46
45
  PunctuationExtractor: () => PunctuationExtractor,
47
46
  StockValueReader: () => StockValueReader,
@@ -105,8 +104,6 @@ __export(index_exports, {
105
104
  fromInterchangeNode: () => fromInterchangeNode,
106
105
  fromProtocolJSON: () => import_intent5.fromProtocolJSON,
107
106
  generateExamples: () => generateExamples,
108
- generatePattern: () => generatePattern,
109
- generatePatternVariants: () => generatePatternVariants,
110
107
  generatePrompt: () => generatePrompt,
111
108
  generateProtocolReference: () => generateProtocolReference,
112
109
  getAllPossessiveKeywords: () => getAllPossessiveKeywords,
@@ -145,8 +142,6 @@ __export(index_exports, {
145
142
  legacyMarkerAlternatives: () => legacyMarkerAlternatives,
146
143
  lookupKeyword: () => lookupKeyword,
147
144
  lookupMarker: () => lookupMarker,
148
- matchBest: () => matchBest,
149
- matchPattern: () => matchPattern,
150
145
  needsDisambiguation: () => needsDisambiguation,
151
146
  noChange: () => noChange,
152
147
  normalized: () => normalized,
@@ -154,7 +149,6 @@ __export(index_exports, {
154
149
  parseDocument: () => import_intent3.parseDocument,
155
150
  parseExplicit: () => import_intent3.parseExplicit,
156
151
  parseJSONL: () => parseJSONL,
157
- patternMatcher: () => patternMatcher,
158
152
  readGreedy: () => readGreedy,
159
153
  readRoleValue: () => readRoleValue,
160
154
  registryToAOTBackends: () => registryToAOTBackends,
@@ -189,256 +183,670 @@ module.exports = __toCommonJS(index_exports);
189
183
  // src/core/types.ts
190
184
  var import_intent = require("@lokascript/intent");
191
185
 
192
- // src/core/logger.ts
193
- var DebugLogger = class {
194
- constructor(namespace) {
195
- this.namespace = namespace;
196
- this.enabled = this.checkEnabled();
186
+ // src/core/tokenization/token-utils.ts
187
+ var TokenStreamImpl = class {
188
+ constructor(tokens, language) {
189
+ this.pos = 0;
190
+ this.tokens = tokens;
191
+ this.language = language;
197
192
  }
198
- checkEnabled() {
199
- if (typeof process !== "undefined" && process.env) {
200
- const DEBUG = process.env.DEBUG || "";
201
- return DEBUG === "*" || DEBUG.includes("framework:*") || DEBUG.includes(`framework:${this.namespace}`);
193
+ peek(offset = 0) {
194
+ const index = this.pos + offset;
195
+ if (index < 0 || index >= this.tokens.length) {
196
+ return null;
202
197
  }
203
- return false;
198
+ return this.tokens[index];
204
199
  }
205
- log(level, message, ...args) {
206
- if (!this.enabled) return;
207
- const prefix = `[framework:${this.namespace}]`;
208
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
209
- switch (level) {
210
- case "debug":
211
- console.debug(`${timestamp} ${prefix} DEBUG:`, message, ...args);
212
- break;
213
- case "info":
214
- console.info(`${timestamp} ${prefix} INFO:`, message, ...args);
215
- break;
216
- case "warn":
217
- console.warn(`${timestamp} ${prefix} WARN:`, message, ...args);
218
- break;
219
- case "error":
220
- console.error(`${timestamp} ${prefix} ERROR:`, message, ...args);
221
- break;
200
+ advance() {
201
+ if (this.isAtEnd()) {
202
+ throw new Error("Unexpected end of token stream");
222
203
  }
204
+ return this.tokens[this.pos++];
223
205
  }
224
- debug(message, ...args) {
225
- this.log("debug", message, ...args);
206
+ isAtEnd() {
207
+ return this.pos >= this.tokens.length;
226
208
  }
227
- info(message, ...args) {
228
- this.log("info", message, ...args);
209
+ mark() {
210
+ return { position: this.pos };
229
211
  }
230
- warn(message, ...args) {
231
- this.log("warn", message, ...args);
212
+ reset(mark) {
213
+ this.pos = mark.position;
232
214
  }
233
- error(message, ...args) {
234
- this.log("error", message, ...args);
215
+ position() {
216
+ return this.pos;
235
217
  }
236
218
  /**
237
- * Check if logging is enabled for this namespace.
219
+ * Get remaining tokens as an array.
238
220
  */
239
- isEnabled() {
240
- return this.enabled;
221
+ remaining() {
222
+ return this.tokens.slice(this.pos);
241
223
  }
242
224
  /**
243
- * Programmatically enable logging for this namespace.
225
+ * Consume tokens while predicate is true.
244
226
  */
245
- enable() {
246
- this.enabled = true;
227
+ takeWhile(predicate) {
228
+ const result = [];
229
+ while (!this.isAtEnd() && predicate(this.peek())) {
230
+ result.push(this.advance());
231
+ }
232
+ return result;
247
233
  }
248
234
  /**
249
- * Programmatically disable logging for this namespace.
235
+ * Skip tokens while predicate is true.
250
236
  */
251
- disable() {
252
- this.enabled = false;
237
+ skipWhile(predicate) {
238
+ while (!this.isAtEnd() && predicate(this.peek())) {
239
+ this.advance();
240
+ }
253
241
  }
254
242
  };
255
- function createLogger(namespace) {
256
- return new DebugLogger(namespace);
243
+ function createPosition(start, end) {
244
+ return { start, end };
257
245
  }
258
-
259
- // src/core/pattern-matching/utils/type-validation.ts
260
- function isTypeCompatible(actualType, expectedTypes) {
261
- if (!expectedTypes || expectedTypes.length === 0) {
262
- return true;
263
- }
264
- if (expectedTypes.includes(actualType)) {
265
- return true;
246
+ function createToken(valueOrParams, kind, position, normalizedOrOptions) {
247
+ if (typeof valueOrParams === "object") {
248
+ const { value: value2, kind: kind2, position: position2, normalized: normalized2, stem, stemConfidence, metadata } = valueOrParams;
249
+ return {
250
+ value: value2,
251
+ kind: kind2,
252
+ position: position2,
253
+ ...normalized2 !== void 0 && { normalized: normalized2 },
254
+ ...stem !== void 0 && { stem },
255
+ ...stemConfidence !== void 0 && { stemConfidence },
256
+ ...metadata !== void 0 && { metadata }
257
+ };
266
258
  }
267
- if (expectedTypes.includes("expression")) {
268
- return true;
259
+ const value = valueOrParams;
260
+ if (!kind || !position) {
261
+ throw new Error("createToken requires kind and position parameters");
269
262
  }
270
- if (actualType === "property-path") {
271
- return expectedTypes.some((t) => ["selector", "reference", "expression"].includes(t));
263
+ if (typeof normalizedOrOptions === "string") {
264
+ return { value, kind, position, normalized: normalizedOrOptions };
272
265
  }
273
- return false;
274
- }
275
- function validateValueType(value, expectedTypes) {
276
- if (!expectedTypes || expectedTypes.length === 0) {
277
- return true;
266
+ if (normalizedOrOptions) {
267
+ const { normalized: normalized2, stem, stemConfidence, metadata } = normalizedOrOptions;
268
+ return {
269
+ value,
270
+ kind,
271
+ position,
272
+ ...normalized2 !== void 0 && { normalized: normalized2 },
273
+ ...stem !== void 0 && { stem },
274
+ ...stemConfidence !== void 0 && { stemConfidence },
275
+ ...metadata !== void 0 && { metadata }
276
+ };
278
277
  }
279
- return isTypeCompatible(value.type, expectedTypes);
280
- }
281
- function isCSSSelector(value) {
282
- return value.startsWith(".") || value.startsWith("#") || value.startsWith("<");
283
- }
284
- function isClassName(value) {
285
- return value.startsWith(".");
286
- }
287
- function isIdSelector(value) {
288
- return value.startsWith("#");
289
- }
290
- function isCSSPropertyRef(value) {
291
- return value.startsWith("*");
278
+ return { value, kind, position };
292
279
  }
293
- function isNumericValue(value) {
294
- const durationMatch = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
295
- if (durationMatch) {
296
- return true;
297
- }
298
- const num = parseFloat(value);
299
- return !isNaN(num) && isFinite(num);
280
+ function isWhitespace(char) {
281
+ return /\s/.test(char);
300
282
  }
301
- function isPropertyName(value) {
302
- return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value);
283
+ function isSelectorStart(char) {
284
+ return char === "#" || char === "." || char === "[" || char === "@" || char === "*" || char === "<";
303
285
  }
304
- function isVariableRef(value) {
305
- return value.startsWith(":");
286
+ function isQuote(char) {
287
+ return char === '"' || char === "'" || char === "`" || char === "\u300C" || char === "\u300D";
306
288
  }
307
- function isBuiltInReference(value) {
308
- const builtIns = /* @__PURE__ */ new Set(["me", "you", "it", "result", "event", "target", "body"]);
309
- return builtIns.has(value.toLowerCase());
289
+ function isDigit(char) {
290
+ return /\d/.test(char);
310
291
  }
311
-
312
- // src/core/pattern-matching/utils/possessive-keywords.ts
313
- function getPossessiveReference(profile, keyword) {
314
- return profile?.possessive?.keywords?.[keyword];
292
+ function stripOptionalDiacritics(word) {
293
+ return word.replace(/[\u064b-\u0652\u0670]/g, "");
315
294
  }
316
- function isPossessiveKeyword(profile, keyword) {
317
- return profile?.possessive?.keywords?.[keyword] !== void 0;
295
+ function isAsciiLetter(char) {
296
+ return /[a-zA-Z]/.test(char);
318
297
  }
319
- function getAllPossessiveKeywords(profile) {
320
- return profile?.possessive?.keywords ?? {};
298
+ function isAsciiIdentifierChar(char) {
299
+ return /[a-zA-Z0-9_-]/.test(char);
321
300
  }
322
301
 
323
- // src/core/matching/stock-reading.ts
324
- function isValidReference(_value) {
325
- return false;
302
+ // src/core/matching/match-skeleton.ts
303
+ function groupHasRole(group) {
304
+ return group.tokens.some((t) => t.type === "role" || t.type === "group" && groupHasRole(t));
326
305
  }
327
- function safeToLowerCase(value) {
328
- if (typeof value === "string") {
329
- return value.toLowerCase();
330
- }
331
- if (value === null || value === void 0) {
332
- return "";
306
+ var MatchSkeleton = class {
307
+ /**
308
+ * @param makeReader - builds the grammar's reader, handed this matcher's view
309
+ * of the match in progress (one reader per matcher).
310
+ */
311
+ constructor(makeReader, confidenceModel) {
312
+ /**
313
+ * The pattern tokens still to match after the one being matched, through
314
+ * every enclosing group: a value stops at any marker the pattern still owes.
315
+ */
316
+ this.patternRest = [];
317
+ /**
318
+ * Whether the role slot being matched directly follows a literal the pattern
319
+ * matched: its marker (`[por {quantity}]`), or the command's verb.
320
+ */
321
+ this.slotAfterLiteral = false;
322
+ /**
323
+ * Selective memoization cache (Phase 6.1).
324
+ * Caches pattern match results keyed by `tokenPosition:patternId`.
325
+ * Cleared before each top-level matchBest() call.
326
+ */
327
+ this.matchCache = /* @__PURE__ */ new Map();
328
+ /**
329
+ * Stream index where each role's capture began, for the current
330
+ * `matchPattern` call. Rebuilt per call, written on the same success path as
331
+ * `captured` — see `PatternMatchResult.roleStarts`.
332
+ */
333
+ this.roleStarts = /* @__PURE__ */ new Map();
334
+ /**
335
+ * Track stem matches for confidence calculation.
336
+ * This is set during matching and read during confidence calculation.
337
+ */
338
+ this.stemMatchCount = 0;
339
+ this.totalKeywordMatches = 0;
340
+ this.confidenceModel = confidenceModel;
341
+ this.reader = makeReader({
342
+ patternRest: () => this.patternRest,
343
+ slotAfterLiteral: () => this.slotAfterLiteral,
344
+ wouldMatch: (pt, token) => this.patternTokenWouldMatch(pt, token)
345
+ });
333
346
  }
334
- return String(value).toLowerCase();
335
- }
336
- function readRoleValue(tokens, patternToken, captured, profile) {
337
- skipNoiseWords(tokens);
338
- const token = tokens.peek();
339
- if (!token) {
340
- return patternToken.optional || false;
347
+ /**
348
+ * Try to match a single pattern against the token stream.
349
+ * Returns the match result or null if no match.
350
+ */
351
+ matchPattern(tokens, pattern) {
352
+ const mark = tokens.mark();
353
+ const captured = /* @__PURE__ */ new Map();
354
+ this.reader.beginPattern(pattern);
355
+ this.stemMatchCount = 0;
356
+ this.totalKeywordMatches = 0;
357
+ this.roleStarts = /* @__PURE__ */ new Map();
358
+ this.patternRest = [];
359
+ const success = this.matchTokenSequence(tokens, pattern.template.tokens, captured);
360
+ if (!success) {
361
+ tokens.reset(mark);
362
+ return null;
363
+ }
364
+ const confidence = this.confidenceModel.calculate({
365
+ pattern,
366
+ captured,
367
+ stemMatchCount: this.stemMatchCount,
368
+ totalKeywordMatches: this.totalKeywordMatches
369
+ });
370
+ this.applyExtractionRules(pattern, captured);
371
+ return {
372
+ pattern,
373
+ captured,
374
+ consumedTokens: tokens.position() - mark.position,
375
+ confidence,
376
+ roleStarts: this.roleStarts,
377
+ ...this.reader.resultFields?.()
378
+ };
341
379
  }
342
- const possessiveValue = tryMatchPossessiveExpression(tokens, profile);
343
- if (possessiveValue) {
344
- if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
345
- if (!isTypeCompatible(possessiveValue.type, patternToken.expectedTypes)) {
346
- return patternToken.optional || false;
380
+ /**
381
+ * Try to match multiple patterns, return the best match.
382
+ */
383
+ matchBest(tokens, patterns) {
384
+ this.matchCache.clear();
385
+ const matches = [];
386
+ const startPos = tokens.position();
387
+ for (const pattern of patterns) {
388
+ const mark = tokens.mark();
389
+ const cacheKey = `${startPos}:${pattern.id}`;
390
+ let result;
391
+ if (this.matchCache.has(cacheKey)) {
392
+ result = this.matchCache.get(cacheKey);
393
+ } else {
394
+ result = this.matchPattern(tokens, pattern);
395
+ this.matchCache.set(cacheKey, result);
396
+ }
397
+ if (result) {
398
+ matches.push(result);
347
399
  }
400
+ tokens.reset(mark);
348
401
  }
349
- captured.set(patternToken.role, possessiveValue);
350
- return true;
402
+ if (matches.length === 0) {
403
+ return null;
404
+ }
405
+ matches.sort((a, b) => {
406
+ const priorityDiff = b.pattern.priority - a.pattern.priority;
407
+ if (priorityDiff !== 0) return priorityDiff;
408
+ return b.confidence - a.confidence;
409
+ });
410
+ const best = matches[0];
411
+ this.matchPattern(tokens, best.pattern);
412
+ return best;
351
413
  }
352
- const methodCallValue = tryMatchMethodCallExpression(tokens);
353
- if (methodCallValue) {
354
- if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
355
- if (!isTypeCompatible(methodCallValue.type, patternToken.expectedTypes)) {
356
- return patternToken.optional || false;
414
+ /**
415
+ * Match a sequence of pattern tokens against the token stream.
416
+ */
417
+ matchTokenSequence(tokens, patternTokens, captured, nextAfterSequence) {
418
+ this.reader.skipLeading?.(tokens, patternTokens);
419
+ for (let i = 0; i < patternTokens.length; i++) {
420
+ const patternToken = patternTokens[i];
421
+ this.reader.beforePatternToken?.(tokens, patternTokens, i, captured);
422
+ if (this.reader.maySkipSlot(
423
+ patternToken,
424
+ tokens.peek(),
425
+ patternTokens[i + 1] ?? nextAfterSequence
426
+ )) {
427
+ const skipMark = tokens.mark();
428
+ const speculative = new Map(captured);
429
+ if (this.matchTokenSequence(
430
+ tokens,
431
+ patternTokens.slice(i + 1),
432
+ speculative,
433
+ nextAfterSequence
434
+ ) && this.reader.atClauseEnd(tokens)) {
435
+ captured.clear();
436
+ for (const [role, value] of speculative) captured.set(role, value);
437
+ return true;
438
+ }
439
+ tokens.reset(skipMark);
440
+ }
441
+ const outerRest = this.patternRest;
442
+ this.patternRest = [...patternTokens.slice(i + 1), ...outerRest];
443
+ this.slotAfterLiteral = patternToken.type === "role" && patternTokens[i - 1]?.type === "literal";
444
+ const matched = this.matchPatternToken(
445
+ tokens,
446
+ patternToken,
447
+ captured,
448
+ patternTokens[i + 1] ?? nextAfterSequence
449
+ );
450
+ this.patternRest = outerRest;
451
+ if (!matched) {
452
+ if (this.isOptional(patternToken)) {
453
+ continue;
454
+ }
455
+ return false;
357
456
  }
358
457
  }
359
- captured.set(patternToken.role, methodCallValue);
360
458
  return true;
361
459
  }
362
- const possessiveSelectorValue = tryMatchPossessiveSelectorExpression(tokens);
363
- if (possessiveSelectorValue) {
364
- if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
365
- if (!isTypeCompatible(possessiveSelectorValue.type, patternToken.expectedTypes)) {
366
- return patternToken.optional || false;
460
+ /**
461
+ * Match a single pattern token against the current position in the stream.
462
+ */
463
+ matchPatternToken(tokens, patternToken, captured, nextPatternToken) {
464
+ switch (patternToken.type) {
465
+ case "literal":
466
+ return this.matchLiteralToken(tokens, patternToken);
467
+ case "role": {
468
+ const roleStart = tokens.position();
469
+ const matchedRole = this.reader.readRole(tokens, patternToken, captured, nextPatternToken);
470
+ if (matchedRole && captured.has(patternToken.role)) {
471
+ this.roleStarts.set(patternToken.role, roleStart);
472
+ }
473
+ return matchedRole;
367
474
  }
475
+ case "group":
476
+ return this.matchGroupToken(tokens, patternToken, captured, nextPatternToken);
477
+ default:
478
+ return false;
368
479
  }
369
- captured.set(patternToken.role, possessiveSelectorValue);
370
- return true;
371
480
  }
372
- const propertyAccessValue = tryMatchPropertyAccessExpression(tokens);
373
- if (propertyAccessValue) {
374
- if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
375
- if (!isTypeCompatible(propertyAccessValue.type, patternToken.expectedTypes)) {
376
- return patternToken.optional || false;
481
+ /**
482
+ * Match a literal pattern token (keyword or particle).
483
+ */
484
+ matchLiteralToken(tokens, patternToken) {
485
+ const token = tokens.peek();
486
+ if (!token) return false;
487
+ const matchType = this.getMatchType(token, patternToken.value);
488
+ if (matchType !== "none") {
489
+ this.totalKeywordMatches++;
490
+ if (matchType === "stem") {
491
+ this.stemMatchCount++;
377
492
  }
493
+ tokens.advance();
494
+ return true;
378
495
  }
379
- captured.set(patternToken.role, propertyAccessValue);
380
- return true;
496
+ if (patternToken.alternatives) {
497
+ for (const alt of patternToken.alternatives) {
498
+ const altMatchType = this.getMatchType(token, alt);
499
+ if (altMatchType !== "none") {
500
+ this.totalKeywordMatches++;
501
+ if (altMatchType === "stem") {
502
+ this.stemMatchCount++;
503
+ }
504
+ tokens.advance();
505
+ return true;
506
+ }
507
+ }
508
+ }
509
+ for (const value of [patternToken.value, ...patternToken.alternatives ?? []]) {
510
+ for (const sep of ["-", "_"]) {
511
+ if (value.includes(sep) && this.tryMatchShatteredCompound(tokens, value, sep)) {
512
+ this.totalKeywordMatches++;
513
+ return true;
514
+ }
515
+ }
516
+ }
517
+ return false;
381
518
  }
382
- const selectorPropertyValue = tryMatchSelectorPropertyExpression(tokens);
383
- if (selectorPropertyValue) {
384
- if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
385
- if (!isTypeCompatible(selectorPropertyValue.type, patternToken.expectedTypes)) {
386
- return patternToken.optional || false;
519
+ /**
520
+ * Match a compound keyword (`titik-henti`, `по_умолчанию`) against a
521
+ * shattered token run (`titik` `-` `henti` / `по` `_` `умолчанию`): word
522
+ * segments must be identifier/keyword/particle tokens equal to the expected
523
+ * segments (case-insensitive; particle because prepositions like ru `по` /
524
+ * uk `за` lead some underscore compounds), separators lone `sep` tokens.
525
+ * Consumes the run on success, resets the stream on failure.
526
+ */
527
+ tryMatchShatteredCompound(tokens, expected, sep) {
528
+ const segments = expected.split(sep);
529
+ if (segments.length < 2 || segments.some((s) => s.length === 0)) return false;
530
+ const mark = tokens.mark();
531
+ for (let i = 0; i < segments.length; i++) {
532
+ if (i > 0) {
533
+ const sepToken = tokens.peek();
534
+ if (!sepToken || sepToken.value !== sep) {
535
+ tokens.reset(mark);
536
+ return false;
537
+ }
538
+ tokens.advance();
539
+ }
540
+ const word = tokens.peek();
541
+ if (!word || word.kind !== "identifier" && word.kind !== "keyword" && word.kind !== "particle" || word.value.toLowerCase() !== segments[i].toLowerCase()) {
542
+ tokens.reset(mark);
543
+ return false;
387
544
  }
545
+ tokens.advance();
388
546
  }
389
- captured.set(patternToken.role, selectorPropertyValue);
390
547
  return true;
391
548
  }
392
- const value = tokenToSemanticValue(token);
393
- if (!value) {
394
- return patternToken.optional || false;
395
- }
396
- if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
397
- if (!isTypeCompatible(value.type, patternToken.expectedTypes)) {
549
+ /**
550
+ * Match a group pattern token (optional sequence).
551
+ */
552
+ matchGroupToken(tokens, patternToken, captured, nextPatternToken) {
553
+ const mark = tokens.mark();
554
+ const capturedBefore = new Map(captured);
555
+ let success = this.matchTokenSequence(tokens, patternToken.tokens, captured, nextPatternToken);
556
+ if (success && tokens.position() > mark.position && groupHasRole(patternToken) && [...captured].every(([role, value]) => capturedBefore.get(role) === value) && captured.size === capturedBefore.size) {
557
+ success = false;
558
+ }
559
+ if (success) {
560
+ const markerLiterals = [];
561
+ for (const inner of patternToken.tokens) {
562
+ if (inner.type === "group" && inner.renderRequired) {
563
+ for (const t of inner.tokens) {
564
+ if (t.type === "literal") markerLiterals.push(t.value, ...t.alternatives ?? []);
565
+ }
566
+ }
567
+ }
568
+ if (markerLiterals.length > 0) {
569
+ const addedRole = [...captured.keys()].some((role) => !capturedBefore.has(role));
570
+ if (addedRole) {
571
+ const last = tokens.tokens[tokens.position() - 1];
572
+ const markerSeen = last !== void 0 && tokens.position() > mark.position && markerLiterals.some((lit) => this.getMatchType(last, lit) !== "none");
573
+ if (!markerSeen) success = false;
574
+ }
575
+ }
576
+ }
577
+ if (!success) {
578
+ tokens.reset(mark);
579
+ for (const role of captured.keys()) {
580
+ if (!capturedBefore.has(role)) {
581
+ captured.delete(role);
582
+ }
583
+ }
584
+ for (const [role, value] of capturedBefore) {
585
+ if (captured.get(role) !== value) {
586
+ captured.set(role, value);
587
+ }
588
+ }
398
589
  return patternToken.optional || false;
399
590
  }
591
+ return true;
400
592
  }
401
- captured.set(patternToken.role, value);
402
- tokens.advance();
403
- return true;
404
- }
405
- function tryMatchPossessiveExpression(tokens, profile) {
406
- const token = tokens.peek();
407
- if (!token) return null;
408
- if (!profile) return null;
409
- const tokenValue = token.normalized || token.value;
410
- const tokenLower = safeToLowerCase(tokenValue);
411
- const baseRef = getPossessiveReference(profile, tokenLower);
412
- if (!baseRef) return null;
413
- const mark = tokens.mark();
414
- tokens.advance();
415
- const propertyToken = tokens.peek();
416
- if (!propertyToken) {
417
- tokens.reset(mark);
418
- return null;
419
- }
420
- if (propertyToken.kind === "identifier" || propertyToken.kind === "keyword" && !isStructuralKeyword(propertyToken.value) || propertyToken.kind === "selector" && propertyToken.value.startsWith("*") || propertyToken.kind === "selector" && propertyToken.value.startsWith("@") || propertyToken.kind === "selector" && propertyToken.value.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(propertyToken.value)) {
421
- tokens.advance();
422
- let propertyName = propertyToken.value;
423
- if (propertyToken.kind === "selector" && propertyName.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(propertyName)) {
424
- propertyName = propertyName.substring(1);
425
- }
426
- let chainedProps = propertyName;
427
- while (tokens.peek()?.kind === "selector" && tokens.peek().value.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(tokens.peek().value)) {
428
- chainedProps += tokens.peek().value;
429
- tokens.advance();
593
+ /**
594
+ * Whether a pattern token (literal, or a group starting with a literal) would
595
+ * match the given stream token. Used to keep the event-head source-clause
596
+ * consumption from stealing a marker the pattern explicitly expects.
597
+ */
598
+ patternTokenWouldMatch(pt, token) {
599
+ if (!pt) return false;
600
+ if (pt.type === "literal") {
601
+ if (this.getMatchType(token, pt.value) !== "none") return true;
602
+ return !!pt.alternatives?.some((a) => this.getMatchType(token, a) !== "none");
430
603
  }
431
- const nextPeek = tokens.peek();
432
- if (nextPeek?.kind === "literal" && nextPeek.value.startsWith("(")) {
433
- chainedProps += nextPeek.value;
434
- tokens.advance();
604
+ if (pt.type === "group") {
605
+ return this.patternTokenWouldMatch(pt.tokens?.[0], token);
435
606
  }
436
- return (0, import_intent.createPropertyPath)((0, import_intent.createReference)(baseRef), chainedProps);
607
+ return false;
437
608
  }
438
- tokens.reset(mark);
439
- return null;
440
- }
441
- function isStructuralKeyword(value) {
609
+ /**
610
+ * Get the type of match for a token against a value.
611
+ * Used for confidence calculation.
612
+ */
613
+ getMatchType(token, value) {
614
+ if (token.value === value) return "exact";
615
+ if (token.normalized === value) return "normalized";
616
+ if (token.stem === value && token.stemConfidence !== void 0 && token.stemConfidence >= 0.7) {
617
+ return "stem";
618
+ }
619
+ if (token.kind === "keyword" && token.value.toLowerCase() === value.toLowerCase()) {
620
+ return "case-insensitive";
621
+ }
622
+ if (token.kind === "keyword") {
623
+ const strippedToken = stripOptionalDiacritics(token.value).toLowerCase();
624
+ if (strippedToken && strippedToken === stripOptionalDiacritics(value).toLowerCase()) {
625
+ return "case-insensitive";
626
+ }
627
+ }
628
+ return "none";
629
+ }
630
+ /**
631
+ * Apply extraction rules to fill in static values and defaults for missing
632
+ * roles, and re-shape captured roles that declare a `transform` (the
633
+ * ExtractionRule field was documented but unwired — go-qu-url-dest is the
634
+ * first consumer: it re-types the marker-bound capture to the expression
635
+ * shape the en reference produces).
636
+ */
637
+ applyExtractionRules(pattern, captured) {
638
+ for (const [role, rule] of Object.entries(pattern.extraction)) {
639
+ const existing = captured.get(role);
640
+ if (existing) {
641
+ if (rule.transform) {
642
+ const raw = existing.type === "literal" ? String(existing.value) : String(existing.raw ?? "");
643
+ captured.set(role, rule.transform(raw));
644
+ }
645
+ } else if (rule.value !== void 0) {
646
+ captured.set(
647
+ role,
648
+ rule.valueIsExpression ? { type: "expression", raw: rule.value } : { type: "literal", value: rule.value }
649
+ );
650
+ } else if (rule.default) {
651
+ const implicitDefault = {
652
+ ...rule.default,
653
+ implicit: true
654
+ };
655
+ captured.set(role, implicitDefault);
656
+ }
657
+ }
658
+ }
659
+ /**
660
+ * Check if a pattern token is optional.
661
+ */
662
+ isOptional(patternToken) {
663
+ return patternToken.type !== "literal" && patternToken.optional === true;
664
+ }
665
+ };
666
+
667
+ // src/core/matching/type-validation.ts
668
+ function isTypeCompatible(actualType, expectedTypes) {
669
+ if (!expectedTypes || expectedTypes.length === 0) {
670
+ return true;
671
+ }
672
+ if (expectedTypes.includes(actualType)) {
673
+ return true;
674
+ }
675
+ if (expectedTypes.includes("expression")) {
676
+ return true;
677
+ }
678
+ if (actualType === "property-path") {
679
+ return expectedTypes.some((t) => ["selector", "reference", "expression"].includes(t));
680
+ }
681
+ return false;
682
+ }
683
+ function validateValueType(value, expectedTypes) {
684
+ if (!expectedTypes || expectedTypes.length === 0) {
685
+ return true;
686
+ }
687
+ return isTypeCompatible(value.type, expectedTypes);
688
+ }
689
+ function isCSSSelector(value) {
690
+ return value.startsWith(".") || value.startsWith("#") || value.startsWith("<");
691
+ }
692
+ function isClassName(value) {
693
+ return value.startsWith(".");
694
+ }
695
+ function isIdSelector(value) {
696
+ return value.startsWith("#");
697
+ }
698
+ function isCSSPropertyRef(value) {
699
+ return value.startsWith("*");
700
+ }
701
+ function isNumericValue(value) {
702
+ const durationMatch = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
703
+ if (durationMatch) {
704
+ return true;
705
+ }
706
+ const num = parseFloat(value);
707
+ return !isNaN(num) && isFinite(num);
708
+ }
709
+ function isPropertyName(value) {
710
+ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value);
711
+ }
712
+ function isVariableRef(value) {
713
+ return value.startsWith(":");
714
+ }
715
+ function isBuiltInReference(value) {
716
+ const builtIns = /* @__PURE__ */ new Set(["me", "you", "it", "result", "event", "target", "body"]);
717
+ return builtIns.has(value.toLowerCase());
718
+ }
719
+
720
+ // src/core/matching/possessive-keywords.ts
721
+ function getPossessiveReference(profile, keyword) {
722
+ return profile?.possessive?.keywords?.[keyword];
723
+ }
724
+ function isPossessiveKeyword(profile, keyword) {
725
+ return profile?.possessive?.keywords?.[keyword] !== void 0;
726
+ }
727
+ function getAllPossessiveKeywords(profile) {
728
+ return profile?.possessive?.keywords ?? {};
729
+ }
730
+
731
+ // src/core/matching/stock-reading.ts
732
+ function isValidReference(_value) {
733
+ return false;
734
+ }
735
+ function safeToLowerCase(value) {
736
+ if (typeof value === "string") {
737
+ return value.toLowerCase();
738
+ }
739
+ if (value === null || value === void 0) {
740
+ return "";
741
+ }
742
+ return String(value).toLowerCase();
743
+ }
744
+ function readRoleValue(tokens, patternToken, captured, profile) {
745
+ skipNoiseWords(tokens);
746
+ const token = tokens.peek();
747
+ if (!token) {
748
+ return patternToken.optional || false;
749
+ }
750
+ const possessiveValue = tryMatchPossessiveExpression(tokens, profile);
751
+ if (possessiveValue) {
752
+ if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
753
+ if (!isTypeCompatible(possessiveValue.type, patternToken.expectedTypes)) {
754
+ return patternToken.optional || false;
755
+ }
756
+ }
757
+ captured.set(patternToken.role, possessiveValue);
758
+ return true;
759
+ }
760
+ const methodCallValue = tryMatchMethodCallExpression(tokens);
761
+ if (methodCallValue) {
762
+ if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
763
+ if (!isTypeCompatible(methodCallValue.type, patternToken.expectedTypes)) {
764
+ return patternToken.optional || false;
765
+ }
766
+ }
767
+ captured.set(patternToken.role, methodCallValue);
768
+ return true;
769
+ }
770
+ const possessiveSelectorValue = tryMatchPossessiveSelectorExpression(tokens);
771
+ if (possessiveSelectorValue) {
772
+ if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
773
+ if (!isTypeCompatible(possessiveSelectorValue.type, patternToken.expectedTypes)) {
774
+ return patternToken.optional || false;
775
+ }
776
+ }
777
+ captured.set(patternToken.role, possessiveSelectorValue);
778
+ return true;
779
+ }
780
+ const propertyAccessValue = tryMatchPropertyAccessExpression(tokens);
781
+ if (propertyAccessValue) {
782
+ if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
783
+ if (!isTypeCompatible(propertyAccessValue.type, patternToken.expectedTypes)) {
784
+ return patternToken.optional || false;
785
+ }
786
+ }
787
+ captured.set(patternToken.role, propertyAccessValue);
788
+ return true;
789
+ }
790
+ const selectorPropertyValue = tryMatchSelectorPropertyExpression(tokens);
791
+ if (selectorPropertyValue) {
792
+ if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
793
+ if (!isTypeCompatible(selectorPropertyValue.type, patternToken.expectedTypes)) {
794
+ return patternToken.optional || false;
795
+ }
796
+ }
797
+ captured.set(patternToken.role, selectorPropertyValue);
798
+ return true;
799
+ }
800
+ const value = tokenToSemanticValue(token);
801
+ if (!value) {
802
+ return patternToken.optional || false;
803
+ }
804
+ if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
805
+ if (!isTypeCompatible(value.type, patternToken.expectedTypes)) {
806
+ return patternToken.optional || false;
807
+ }
808
+ }
809
+ captured.set(patternToken.role, value);
810
+ tokens.advance();
811
+ return true;
812
+ }
813
+ function tryMatchPossessiveExpression(tokens, profile) {
814
+ const token = tokens.peek();
815
+ if (!token) return null;
816
+ if (!profile) return null;
817
+ const tokenValue = token.normalized || token.value;
818
+ const tokenLower = safeToLowerCase(tokenValue);
819
+ const baseRef = getPossessiveReference(profile, tokenLower);
820
+ if (!baseRef) return null;
821
+ const mark = tokens.mark();
822
+ tokens.advance();
823
+ const propertyToken = tokens.peek();
824
+ if (!propertyToken) {
825
+ tokens.reset(mark);
826
+ return null;
827
+ }
828
+ if (propertyToken.kind === "identifier" || propertyToken.kind === "keyword" && !isStructuralKeyword(propertyToken.value) || propertyToken.kind === "selector" && propertyToken.value.startsWith("*") || propertyToken.kind === "selector" && propertyToken.value.startsWith("@") || propertyToken.kind === "selector" && propertyToken.value.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(propertyToken.value)) {
829
+ tokens.advance();
830
+ let propertyName = propertyToken.value;
831
+ if (propertyToken.kind === "selector" && propertyName.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(propertyName)) {
832
+ propertyName = propertyName.substring(1);
833
+ }
834
+ let chainedProps = propertyName;
835
+ while (tokens.peek()?.kind === "selector" && tokens.peek().value.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(tokens.peek().value)) {
836
+ chainedProps += tokens.peek().value;
837
+ tokens.advance();
838
+ }
839
+ const nextPeek = tokens.peek();
840
+ if (nextPeek?.kind === "literal" && nextPeek.value.startsWith("(")) {
841
+ chainedProps += nextPeek.value;
842
+ tokens.advance();
843
+ }
844
+ return (0, import_intent.createPropertyPath)((0, import_intent.createReference)(baseRef), chainedProps);
845
+ }
846
+ tokens.reset(mark);
847
+ return null;
848
+ }
849
+ function isStructuralKeyword(value) {
442
850
  const structural = /* @__PURE__ */ new Set([
443
851
  // Prepositions
444
852
  "into",
@@ -645,1201 +1053,70 @@ function tokenToSemanticValue(token) {
645
1053
  return (0, import_intent.createSelector)(token.value);
646
1054
  case "literal":
647
1055
  return parseLiteralValue(token.value);
648
- case "keyword": {
649
- const tokenValue = token.normalized || token.value;
650
- const lower = safeToLowerCase(tokenValue);
651
- if (isValidReference(lower)) {
652
- return (0, import_intent.createReference)(lower);
653
- }
654
- return (0, import_intent.createLiteral)(token.normalized || token.value);
655
- }
656
- case "identifier": {
657
- if (typeof token.value === "string" && token.value.startsWith(":")) {
658
- return (0, import_intent.createReference)(token.value);
659
- }
660
- const identLower = safeToLowerCase(token.value);
661
- if (isValidReference(identLower)) {
662
- return (0, import_intent.createReference)(identLower);
663
- }
664
- return { type: "expression", raw: token.value };
665
- }
666
- case "url":
667
- return (0, import_intent.createLiteral)(token.value, "string");
668
- default:
669
- return null;
670
- }
671
- }
672
- function parseLiteralValue(value) {
673
- if (value.startsWith('"') || value.startsWith("'") || value.startsWith("`") || value.startsWith("\u300C")) {
674
- const inner = value.slice(1, -1);
675
- return (0, import_intent.createLiteral)(inner, "string");
676
- }
677
- if (value === "true") return (0, import_intent.createLiteral)(true, "boolean");
678
- if (value === "false") return (0, import_intent.createLiteral)(false, "boolean");
679
- const durationMatch = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
680
- if (durationMatch) {
681
- const num2 = parseFloat(durationMatch[1]);
682
- const unit = durationMatch[2];
683
- if (unit) {
684
- return (0, import_intent.createLiteral)(value, "duration");
685
- }
686
- return (0, import_intent.createLiteral)(num2, "number");
687
- }
688
- const num = parseFloat(value);
689
- if (!isNaN(num)) {
690
- return (0, import_intent.createLiteral)(num, "number");
691
- }
692
- return (0, import_intent.createLiteral)(value, "string");
693
- }
694
- var ENGLISH_NOISE_WORDS = /* @__PURE__ */ new Set(["the", "a", "an"]);
695
- function skipNoiseWords(tokens) {
696
- const token = tokens.peek();
697
- if (!token) return;
698
- const tokenLower = safeToLowerCase(token.value);
699
- if (ENGLISH_NOISE_WORDS.has(tokenLower)) {
700
- const mark = tokens.mark();
701
- tokens.advance();
702
- const nextToken = tokens.peek();
703
- if (nextToken && nextToken.kind === "selector") {
704
- return;
705
- }
706
- tokens.reset(mark);
707
- }
708
- if (tokenLower === "class") {
709
- tokens.advance();
710
- }
711
- }
712
-
713
- // src/core/matching/stock-confidence.ts
714
- function scoreParts(ctx) {
715
- const { pattern, captured } = ctx;
716
- let score = 0;
717
- let maxScore = 0;
718
- const hasDefault = (role) => {
719
- return pattern.extraction?.[role]?.default !== void 0;
720
- };
721
- for (const token of pattern.template.tokens) {
722
- if (token.type === "role") {
723
- maxScore += 1;
724
- if (captured.has(token.role)) {
725
- score += 1;
726
- }
727
- } else if (token.type === "group") {
728
- for (const subToken of token.tokens) {
729
- if (subToken.type === "role") {
730
- const roleHasDefault = hasDefault(subToken.role);
731
- const weight = 0.8;
732
- maxScore += weight;
733
- if (captured.has(subToken.role)) {
734
- score += weight;
735
- } else if (roleHasDefault) {
736
- score += weight * 0.6;
737
- }
738
- }
739
- }
740
- }
741
- }
742
- const roleCoverage = maxScore > 0 ? score / maxScore : 1;
743
- let baseConfidence = roleCoverage;
744
- let stemPenalty = 0;
745
- if (ctx.stemMatchCount > 0 && ctx.totalKeywordMatches > 0) {
746
- stemPenalty = ctx.stemMatchCount / ctx.totalKeywordMatches * 0.15;
747
- baseConfidence = Math.max(0.5, baseConfidence - stemPenalty);
748
- }
749
- const vsoBoost = calculateVSOConfidenceBoost(pattern);
750
- baseConfidence = Math.min(1, baseConfidence + vsoBoost);
751
- const prepositionAdjustment = arabicPrepositionDisambiguation(pattern, captured);
752
- baseConfidence = Math.max(0, Math.min(1, baseConfidence + prepositionAdjustment));
753
- return {
754
- roleCoverage,
755
- stemPenalty,
756
- languageBoost: vsoBoost,
757
- languageAdjustment: prepositionAdjustment,
758
- final: baseConfidence
759
- };
760
- }
761
- function calculateVSOConfidenceBoost(pattern) {
762
- if (pattern.language !== "ar") {
763
- return 0;
764
- }
765
- const firstToken = pattern.template.tokens[0];
766
- if (!firstToken || firstToken.type !== "literal") {
767
- return 0;
768
- }
769
- const ARABIC_VERBS = /* @__PURE__ */ new Set([
770
- "\u0628\u062F\u0644",
771
- "\u063A\u064A\u0631",
772
- "\u0623\u0636\u0641",
773
- "\u0623\u0632\u0644",
774
- "\u0636\u0639",
775
- "\u0627\u062C\u0639\u0644",
776
- "\u0639\u064A\u0646",
777
- "\u0632\u062F",
778
- "\u0627\u0646\u0642\u0635",
779
- "\u0633\u062C\u0644",
780
- "\u0623\u0638\u0647\u0631",
781
- "\u0623\u062E\u0641",
782
- "\u0634\u063A\u0644",
783
- "\u0623\u0631\u0633\u0644",
784
- "\u0631\u0643\u0632",
785
- "\u0634\u0648\u0634",
786
- "\u062A\u0648\u0642\u0641",
787
- "\u0627\u0646\u0633\u062E",
788
- "\u0627\u062D\u0630\u0641",
789
- "\u0627\u0635\u0646\u0639",
790
- "\u0627\u0646\u062A\u0638\u0631",
791
- "\u0627\u0646\u062A\u0642\u0627\u0644",
792
- "\u0623\u0648"
793
- ]);
794
- if (ARABIC_VERBS.has(firstToken.value)) {
795
- return 0.15;
796
- }
797
- if (firstToken.alternatives) {
798
- for (const alt of firstToken.alternatives) {
799
- if (ARABIC_VERBS.has(alt)) {
800
- return 0.15;
801
- }
802
- }
803
- }
804
- return 0;
805
- }
806
- function arabicPrepositionDisambiguation(pattern, captured) {
807
- if (pattern.language !== "ar") {
808
- return 0;
809
- }
810
- let adjustment = 0;
811
- const PREFERRED_PREPOSITIONS = {
812
- patient: ["\u0639\u0644\u0649"],
813
- // element selectors prefer على (on/upon)
814
- destination: ["\u0625\u0644\u0649", "\u0627\u0644\u0649"],
815
- // destination prefers إلى (to)
816
- source: ["\u0645\u0646"],
817
- // source prefers من (from)
818
- agent: ["\u0645\u0646"],
819
- // agent/by prefers من (from/by)
820
- manner: ["\u0628"],
821
- // manner prefers ب (with/by)
822
- style: ["\u0628"],
823
- // style prefers ب (with)
824
- goal: ["\u0625\u0644\u0649", "\u0627\u0644\u0649"],
825
- // target state prefers إلى (to)
826
- method: ["\u0628"]
827
- // method prefers ب (with/by)
828
- };
829
- for (const [role, value] of captured.entries()) {
830
- const preferred = PREFERRED_PREPOSITIONS[role];
831
- if (!preferred || preferred.length === 0) {
832
- continue;
833
- }
834
- const metadata = "metadata" in value ? value.metadata : void 0;
835
- if (metadata && typeof metadata.prepositionValue === "string") {
836
- const usedPreposition = metadata.prepositionValue;
837
- if (preferred.includes(usedPreposition)) {
838
- adjustment += 0.1;
839
- } else {
840
- adjustment -= 0.1;
841
- }
842
- }
843
- }
844
- return Math.max(-0.1, Math.min(0.1, adjustment));
845
- }
846
- var stockConfidenceModel = {
847
- calculate: (ctx) => scoreParts(ctx).final,
848
- calculateWithBreakdown: (ctx) => scoreParts(ctx)
849
- };
850
-
851
- // src/core/pattern-matching/pattern-matcher.ts
852
- var _PatternMatcher = class _PatternMatcher {
853
- constructor() {
854
- /** Debug logger */
855
- this.logger = createLogger("pattern-matcher");
856
- /**
857
- * Track stem matches for confidence calculation.
858
- * This is set during matching and read during confidence calculation.
859
- */
860
- this.stemMatchCount = 0;
861
- this.totalKeywordMatches = 0;
862
- }
863
- /**
864
- * Try to match a single pattern against the token stream.
865
- * Returns the match result or null if no match.
866
- *
867
- * @param tokens - Token stream to match against
868
- * @param pattern - Pattern to match
869
- * @param profile - Optional language profile for possessive handling
870
- */
871
- matchPattern(tokens, pattern, profile) {
872
- const mark = tokens.mark();
873
- const captured = /* @__PURE__ */ new Map();
874
- this.logger.debug("========================================");
875
- this.logger.debug("matchPattern ENTRY");
876
- this.logger.debug("Pattern ID:", pattern.id);
877
- this.logger.debug("Pattern command:", pattern.command);
878
- this.logger.debug("Pattern language:", pattern.language);
879
- this.logger.debug("Pattern template:", JSON.stringify(pattern.template, null, 2));
880
- if (this.logger.isEnabled()) {
881
- const firstTokens = [];
882
- for (let i = 0; i < 10; i++) {
883
- const t = tokens.peek(i);
884
- if (t)
885
- firstTokens.push({
886
- type: t.type,
887
- value: t.value,
888
- kind: t.kind
889
- });
890
- else break;
891
- }
892
- this.logger.debug("Input tokens (first 10):", firstTokens);
893
- this.logger.debug("Profile code:", profile?.code);
894
- }
895
- this.currentProfile = profile;
896
- this.stemMatchCount = 0;
897
- this.totalKeywordMatches = 0;
898
- this.logger.debug("--- Calling matchTokenSequence ---");
899
- this.logger.debug("Pattern tokens to match:", JSON.stringify(pattern.template.tokens, null, 2));
900
- const success = this.matchTokenSequence(tokens, pattern.template.tokens, captured);
901
- this.logger.debug("matchTokenSequence returned:", success);
902
- this.logger.debug(
903
- "Captured roles:",
904
- Array.from(captured.entries()).map(([k, v]) => [k, JSON.stringify(v)])
905
- );
906
- if (!success) {
907
- this.logger.debug(">>> MATCH FAILED - resetting token position");
908
- tokens.reset(mark);
909
- return null;
910
- }
911
- const confidence = stockConfidenceModel.calculate({
912
- pattern,
913
- captured,
914
- stemMatchCount: this.stemMatchCount,
915
- totalKeywordMatches: this.totalKeywordMatches
916
- });
917
- this.applyExtractionRules(pattern, captured);
918
- return {
919
- pattern,
920
- captured,
921
- consumedTokens: tokens.position() - mark.position,
922
- confidence
923
- };
924
- }
925
- /**
926
- * Try to match multiple patterns, return the best match.
927
- *
928
- * @param tokens - Token stream to match against
929
- * @param patterns - Candidate patterns to try
930
- * @param profile - Optional language profile for possessive handling
931
- */
932
- matchBest(tokens, patterns, profile) {
933
- const matches = [];
934
- for (const pattern of patterns) {
935
- const mark = tokens.mark();
936
- const result = this.matchPattern(tokens, pattern, profile);
937
- if (result) {
938
- matches.push(result);
939
- }
940
- tokens.reset(mark);
941
- }
942
- if (matches.length === 0) {
943
- return null;
944
- }
945
- matches.sort((a, b) => {
946
- const priorityDiff = b.pattern.priority - a.pattern.priority;
947
- if (priorityDiff !== 0) return priorityDiff;
948
- const confidenceDiff = b.confidence - a.confidence;
949
- if (Math.abs(confidenceDiff) > 1e-3) return confidenceDiff;
950
- return b.consumedTokens - a.consumedTokens;
951
- });
952
- const best = matches[0];
953
- this.matchPattern(tokens, best.pattern);
954
- return best;
955
- }
956
- /**
957
- * Match a sequence of pattern tokens against the token stream.
958
- *
959
- * Supports bounded single-step backtracking: if an optional role consumes
960
- * a token and the immediately following pattern token fails, the matcher
961
- * resets to before the optional role and retries the failed token.
962
- */
963
- matchTokenSequence(tokens, patternTokens, captured, parentStopMarkers) {
964
- const firstPatternToken = patternTokens[0];
965
- const patternExpectsConjunction = firstPatternToken?.type === "literal" && (firstPatternToken.value === "and" || firstPatternToken.value === "then" || firstPatternToken.alternatives?.includes("and") || firstPatternToken.alternatives?.includes("then"));
966
- if (this.currentProfile?.code === "ar" && !patternExpectsConjunction) {
967
- while (tokens.peek()?.kind === "conjunction") {
968
- tokens.advance();
969
- }
970
- }
971
- let prevOptionalMark = null;
972
- let prevOptionalRole = null;
973
- for (let i = 0; i < patternTokens.length; i++) {
974
- const patternToken = patternTokens[i];
975
- this.logger.debug(" >> Matching pattern token:", JSON.stringify(patternToken, null, 2));
976
- const currTok = tokens.peek();
977
- this.logger.debug(
978
- " >> Current input token:",
979
- currTok ? JSON.stringify({
980
- type: currTok.type,
981
- value: currTok.value,
982
- kind: currTok.kind
983
- }) : "EOF"
984
- );
985
- if (patternToken.type === "role" && patternToken.greedy) {
986
- const stopMarkers = this.collectStopMarkers(patternTokens, i + 1);
987
- if (parentStopMarkers) {
988
- for (const m of parentStopMarkers) stopMarkers.add(m);
989
- }
990
- const values = [];
991
- while (!tokens.isAtEnd()) {
992
- const nextToken = tokens.peek();
993
- if (!nextToken) break;
994
- if (this.isStopMarker(nextToken, stopMarkers)) break;
995
- values.push(nextToken.value);
996
- tokens.advance();
997
- }
998
- if (values.length > 0) {
999
- captured.set(patternToken.role, { type: "expression", raw: values.join(" ") });
1000
- prevOptionalMark = null;
1001
- prevOptionalRole = null;
1002
- continue;
1003
- } else if (patternToken.optional) {
1004
- continue;
1005
- } else {
1006
- return false;
1007
- }
1008
- }
1009
- if (patternToken.type === "group") {
1010
- const siblingStopMarkers = this.collectStopMarkers(patternTokens, i + 1);
1011
- if (parentStopMarkers) {
1012
- for (const m of parentStopMarkers) siblingStopMarkers.add(m);
1013
- }
1014
- const groupMatched = this.matchGroupToken(
1015
- tokens,
1016
- patternToken,
1017
- captured,
1018
- siblingStopMarkers
1019
- );
1020
- if (groupMatched) {
1021
- prevOptionalMark = null;
1022
- prevOptionalRole = null;
1023
- continue;
1024
- }
1025
- if (this.isOptional(patternToken)) {
1026
- continue;
1027
- }
1028
- return false;
1029
- }
1030
- const isOptionalRole = patternToken.type === "role" && patternToken.optional === true;
1031
- const markBefore = isOptionalRole ? tokens.mark() : null;
1032
- const isRoleToken = patternToken.type === "role";
1033
- if (isRoleToken && !isOptionalRole) {
1034
- const nextPT = i + 1 < patternTokens.length ? patternTokens[i + 1] : null;
1035
- if (nextPT?.type === "literal") {
1036
- const currentToken = tokens.peek();
1037
- if (currentToken && this.getMatchType(currentToken, nextPT.value) !== "none") {
1038
- this.logger.debug(
1039
- " >> MARKER-STEAL GUARD: current token matches next literal, skipping role"
1040
- );
1041
- this.logger.debug(" >> Token match FAILED");
1042
- if (this.isOptional(patternToken)) {
1043
- continue;
1044
- }
1045
- if (prevOptionalMark && prevOptionalRole) {
1046
- this.logger.debug(" >> BACKTRACKING: undoing optional role", prevOptionalRole);
1047
- tokens.reset(prevOptionalMark);
1048
- captured.delete(prevOptionalRole);
1049
- prevOptionalMark = null;
1050
- prevOptionalRole = null;
1051
- const retryMatched = this.matchPatternToken(tokens, patternToken, captured);
1052
- this.logger.debug(" >> Backtrack retry result:", retryMatched);
1053
- if (retryMatched) {
1054
- continue;
1055
- }
1056
- }
1057
- return false;
1058
- }
1059
- }
1060
- }
1061
- const matched = this.matchPatternToken(tokens, patternToken, captured);
1062
- this.logger.debug(" >> Match result:", matched);
1063
- if (matched) {
1064
- if (isOptionalRole) {
1065
- prevOptionalMark = markBefore;
1066
- prevOptionalRole = patternToken.role;
1067
- } else {
1068
- prevOptionalMark = null;
1069
- prevOptionalRole = null;
1070
- }
1071
- continue;
1072
- }
1073
- this.logger.debug(" >> Token match FAILED");
1074
- if (this.isOptional(patternToken)) {
1075
- continue;
1076
- }
1077
- if (prevOptionalMark && prevOptionalRole) {
1078
- this.logger.debug(" >> BACKTRACKING: undoing optional role", prevOptionalRole);
1079
- tokens.reset(prevOptionalMark);
1080
- captured.delete(prevOptionalRole);
1081
- prevOptionalMark = null;
1082
- prevOptionalRole = null;
1083
- const retryMatched = this.matchPatternToken(tokens, patternToken, captured);
1084
- this.logger.debug(" >> Backtrack retry result:", retryMatched);
1085
- if (retryMatched) {
1086
- continue;
1087
- }
1088
- }
1089
- return false;
1090
- }
1091
- return true;
1092
- }
1093
- /**
1094
- * Match a single pattern token against the current position in the stream.
1095
- */
1096
- matchPatternToken(tokens, patternToken, captured) {
1097
- switch (patternToken.type) {
1098
- case "literal":
1099
- return this.matchLiteralToken(tokens, patternToken);
1100
- case "role":
1101
- return readRoleValue(tokens, patternToken, captured, this.currentProfile);
1102
- case "group":
1103
- return this.matchGroupToken(tokens, patternToken, captured);
1104
- default:
1105
- return false;
1106
- }
1107
- }
1108
- /**
1109
- * Match a literal pattern token (keyword or particle).
1110
- */
1111
- matchLiteralToken(tokens, patternToken) {
1112
- const token = tokens.peek();
1113
- this.logger.debug(" >>> matchLiteralToken: expecting", patternToken.value);
1114
- this.logger.debug(
1115
- " >>> matchLiteralToken: got token",
1116
- token ? JSON.stringify({
1117
- type: token.type,
1118
- value: token.value,
1119
- kind: token.kind
1120
- }) : "null"
1121
- );
1122
- if (!token) {
1123
- this.logger.debug(" >>> matchLiteralToken: FAIL - no token");
1124
- return false;
1125
- }
1126
- const matchType = this.getMatchType(token, patternToken.value);
1127
- this.logger.debug(
1128
- " >>> matchType for",
1129
- token.value,
1130
- "vs",
1131
- patternToken.value,
1132
- ":",
1133
- matchType
1134
- );
1135
- if (matchType !== "none") {
1136
- this.totalKeywordMatches++;
1137
- if (matchType === "stem") {
1138
- this.stemMatchCount++;
1139
- }
1140
- tokens.advance();
1141
- return true;
1142
- }
1143
- if (patternToken.alternatives) {
1144
- for (const alt of patternToken.alternatives) {
1145
- const altMatchType = this.getMatchType(token, alt);
1146
- if (altMatchType !== "none") {
1147
- this.totalKeywordMatches++;
1148
- if (altMatchType === "stem") {
1149
- this.stemMatchCount++;
1150
- }
1151
- tokens.advance();
1152
- return true;
1153
- }
1154
- }
1155
- }
1156
- return false;
1157
- }
1158
- /**
1159
- * Match a group pattern token (optional sequence).
1160
- * When the group's leading marker isn't at the current position, scans ahead
1161
- * up to MAX_MARKER_SCAN tokens to find it. This allows unmarked tokens
1162
- * (e.g., a value like 'hello') to sit between groups without blocking later
1163
- * marker matches.
1164
- */
1165
- matchGroupToken(tokens, patternToken, captured, parentStopMarkers) {
1166
- const mark = tokens.mark();
1167
- const capturedBefore = new Set(captured.keys());
1168
- const success = this.matchTokenSequence(
1169
- tokens,
1170
- patternToken.tokens,
1171
- captured,
1172
- parentStopMarkers
1173
- );
1174
- if (success) return true;
1175
- tokens.reset(mark);
1176
- for (const role of captured.keys()) {
1177
- if (!capturedBefore.has(role)) captured.delete(role);
1178
- }
1179
- if (!patternToken.optional) return false;
1180
- const leadingMarker = this.getGroupLeadingMarker(patternToken);
1181
- if (leadingMarker) {
1182
- for (let offset = 1; offset <= _PatternMatcher.MAX_MARKER_SCAN; offset++) {
1183
- const ahead = tokens.peek(offset);
1184
- if (!ahead) break;
1185
- const aheadValue = (ahead.normalized || ahead.value).toLowerCase();
1186
- if (aheadValue === leadingMarker) {
1187
- this.logger.debug(
1188
- " >> MARKER SCAN: found",
1189
- leadingMarker,
1190
- "at offset",
1191
- offset,
1192
- "- skipping intervening tokens"
1193
- );
1194
- for (let s = 0; s < offset; s++) tokens.advance();
1195
- const retrySuccess = this.matchTokenSequence(
1196
- tokens,
1197
- patternToken.tokens,
1198
- captured,
1199
- parentStopMarkers
1200
- );
1201
- if (retrySuccess) return true;
1202
- tokens.reset(mark);
1203
- for (const role of captured.keys()) {
1204
- if (!capturedBefore.has(role)) captured.delete(role);
1205
- }
1206
- break;
1207
- }
1208
- }
1209
- }
1210
- return true;
1211
- }
1212
- /**
1213
- * Get the leading marker literal from an optional group.
1214
- * Returns the lowercase marker value, or null if the group
1215
- * doesn't start with a literal token (e.g., SOV groups where
1216
- * the role comes before the marker).
1217
- */
1218
- getGroupLeadingMarker(group) {
1219
- const first = group.tokens[0];
1220
- if (first?.type === "literal") return first.value.toLowerCase();
1221
- return null;
1222
- }
1223
- /**
1224
- * Get the type of match for a token against a value.
1225
- * Used for confidence calculation.
1226
- */
1227
- getMatchType(token, value) {
1228
- if (token.value === value) return "exact";
1229
- if (token.normalized === value) return "normalized";
1230
- if (token.stem === value && token.stemConfidence !== void 0 && token.stemConfidence >= 0.7) {
1231
- return "stem";
1232
- }
1233
- if (token.kind === "keyword" && safeToLowerCase(token.value) === value.toLowerCase()) {
1234
- return "case-insensitive";
1235
- }
1236
- return "none";
1237
- }
1238
- /**
1239
- * Collect literal values from upcoming pattern tokens that act as stop markers
1240
- * for greedy role capture. Returns the set of lowercase values.
1241
- */
1242
- collectStopMarkers(patternTokens, startIndex) {
1243
- const markers = /* @__PURE__ */ new Set();
1244
- for (let j = startIndex; j < patternTokens.length; j++) {
1245
- const pt = patternTokens[j];
1246
- if (pt.type === "literal") {
1247
- markers.add(pt.value.toLowerCase());
1248
- if (pt.alternatives) {
1249
- for (const alt of pt.alternatives) {
1250
- markers.add(alt.toLowerCase());
1251
- }
1252
- }
1253
- break;
1254
- }
1255
- if (pt.type === "group") {
1256
- for (const gt of pt.tokens) {
1257
- if (gt.type === "literal") {
1258
- markers.add(gt.value.toLowerCase());
1259
- if (gt.alternatives) {
1260
- for (const alt of gt.alternatives) {
1261
- markers.add(alt.toLowerCase());
1262
- }
1263
- }
1264
- break;
1265
- }
1266
- }
1267
- if (!pt.optional) break;
1268
- }
1269
- }
1270
- return markers;
1271
- }
1272
- /**
1273
- * Check if a token matches any stop marker for greedy capture.
1274
- */
1275
- isStopMarker(token, stopMarkers) {
1276
- if (stopMarkers.size === 0) return false;
1277
- if (stopMarkers.has(token.value.toLowerCase())) return true;
1278
- if (token.normalized && stopMarkers.has(token.normalized.toLowerCase())) return true;
1279
- return false;
1280
- }
1281
- // ==========================================================================
1282
- // Depth Limits for Expression Parsing (security hardening)
1283
- // ==========================================================================
1284
- /**
1285
- * Apply extraction rules to fill in static values and defaults for missing roles.
1286
- */
1287
- applyExtractionRules(pattern, captured) {
1288
- for (const [role, rule] of Object.entries(pattern.extraction)) {
1289
- if (!captured.has(role)) {
1290
- if (rule.value !== void 0) {
1291
- captured.set(role, { type: "literal", value: rule.value });
1292
- } else if (rule.default) {
1293
- captured.set(role, rule.default);
1294
- }
1295
- }
1296
- }
1297
- }
1298
- /**
1299
- * Check if a pattern token is optional.
1300
- */
1301
- isOptional(patternToken) {
1302
- return patternToken.type !== "literal" && patternToken.optional === true;
1303
- }
1304
- // ===========================================================================
1305
- // English Idiom Support - Noise Word Handling
1306
- // ===========================================================================
1307
- /**
1308
- * Extract event modifiers from the token stream.
1309
- * Event modifiers are .once, .debounce(N), .throttle(N), .queue(strategy)
1310
- * that can appear after event names.
1311
- *
1312
- * Returns EventModifiers object or undefined if no modifiers found.
1313
- */
1314
- extractEventModifiers(tokens) {
1315
- const modifiers = {};
1316
- let foundModifier = false;
1317
- while (!tokens.isAtEnd()) {
1318
- const token = tokens.peek();
1319
- if (!token || token.kind !== "event-modifier") {
1320
- break;
1321
- }
1322
- const metadata = token.metadata;
1323
- if (!metadata) {
1324
- break;
1325
- }
1326
- foundModifier = true;
1327
- switch (metadata.modifierName) {
1328
- case "once":
1329
- modifiers.once = true;
1330
- break;
1331
- case "debounce":
1332
- if (typeof metadata.value === "number") {
1333
- modifiers.debounce = metadata.value;
1334
- }
1335
- break;
1336
- case "throttle":
1337
- if (typeof metadata.value === "number") {
1338
- modifiers.throttle = metadata.value;
1339
- }
1340
- break;
1341
- case "queue":
1342
- if (metadata.value === "first" || metadata.value === "last" || metadata.value === "all" || metadata.value === "none") {
1343
- modifiers.queue = metadata.value;
1344
- }
1345
- break;
1346
- }
1347
- tokens.advance();
1348
- }
1349
- return foundModifier ? modifiers : void 0;
1350
- }
1351
- };
1352
- /** Maximum tokens to scan ahead when looking for a group's leading marker */
1353
- _PatternMatcher.MAX_MARKER_SCAN = 3;
1354
- var PatternMatcher = _PatternMatcher;
1355
- var patternMatcher = new PatternMatcher();
1356
- function matchPattern(tokens, pattern) {
1357
- return patternMatcher.matchPattern(tokens, pattern);
1358
- }
1359
- function matchBest(tokens, patterns) {
1360
- return patternMatcher.matchBest(tokens, patterns);
1361
- }
1362
-
1363
- // src/core/tokenization/token-utils.ts
1364
- var TokenStreamImpl = class {
1365
- constructor(tokens, language) {
1366
- this.pos = 0;
1367
- this.tokens = tokens;
1368
- this.language = language;
1369
- }
1370
- peek(offset = 0) {
1371
- const index = this.pos + offset;
1372
- if (index < 0 || index >= this.tokens.length) {
1373
- return null;
1374
- }
1375
- return this.tokens[index];
1376
- }
1377
- advance() {
1378
- if (this.isAtEnd()) {
1379
- throw new Error("Unexpected end of token stream");
1380
- }
1381
- return this.tokens[this.pos++];
1382
- }
1383
- isAtEnd() {
1384
- return this.pos >= this.tokens.length;
1385
- }
1386
- mark() {
1387
- return { position: this.pos };
1388
- }
1389
- reset(mark) {
1390
- this.pos = mark.position;
1391
- }
1392
- position() {
1393
- return this.pos;
1394
- }
1395
- /**
1396
- * Get remaining tokens as an array.
1397
- */
1398
- remaining() {
1399
- return this.tokens.slice(this.pos);
1400
- }
1401
- /**
1402
- * Consume tokens while predicate is true.
1403
- */
1404
- takeWhile(predicate) {
1405
- const result = [];
1406
- while (!this.isAtEnd() && predicate(this.peek())) {
1407
- result.push(this.advance());
1408
- }
1409
- return result;
1410
- }
1411
- /**
1412
- * Skip tokens while predicate is true.
1413
- */
1414
- skipWhile(predicate) {
1415
- while (!this.isAtEnd() && predicate(this.peek())) {
1416
- this.advance();
1417
- }
1418
- }
1419
- };
1420
- function createPosition(start, end) {
1421
- return { start, end };
1422
- }
1423
- function createToken(valueOrParams, kind, position, normalizedOrOptions) {
1424
- if (typeof valueOrParams === "object") {
1425
- const { value: value2, kind: kind2, position: position2, normalized: normalized2, stem, stemConfidence, metadata } = valueOrParams;
1426
- return {
1427
- value: value2,
1428
- kind: kind2,
1429
- position: position2,
1430
- ...normalized2 !== void 0 && { normalized: normalized2 },
1431
- ...stem !== void 0 && { stem },
1432
- ...stemConfidence !== void 0 && { stemConfidence },
1433
- ...metadata !== void 0 && { metadata }
1434
- };
1435
- }
1436
- const value = valueOrParams;
1437
- if (!kind || !position) {
1438
- throw new Error("createToken requires kind and position parameters");
1439
- }
1440
- if (typeof normalizedOrOptions === "string") {
1441
- return { value, kind, position, normalized: normalizedOrOptions };
1442
- }
1443
- if (normalizedOrOptions) {
1444
- const { normalized: normalized2, stem, stemConfidence, metadata } = normalizedOrOptions;
1445
- return {
1446
- value,
1447
- kind,
1448
- position,
1449
- ...normalized2 !== void 0 && { normalized: normalized2 },
1450
- ...stem !== void 0 && { stem },
1451
- ...stemConfidence !== void 0 && { stemConfidence },
1452
- ...metadata !== void 0 && { metadata }
1453
- };
1454
- }
1455
- return { value, kind, position };
1456
- }
1457
- function isWhitespace(char) {
1458
- return /\s/.test(char);
1459
- }
1460
- function isSelectorStart(char) {
1461
- return char === "#" || char === "." || char === "[" || char === "@" || char === "*" || char === "<";
1462
- }
1463
- function isQuote(char) {
1464
- return char === '"' || char === "'" || char === "`" || char === "\u300C" || char === "\u300D";
1465
- }
1466
- function isDigit(char) {
1467
- return /\d/.test(char);
1468
- }
1469
- function stripOptionalDiacritics(word) {
1470
- return word.replace(/[\u064b-\u0652\u0670]/g, "");
1471
- }
1472
- function isAsciiLetter(char) {
1473
- return /[a-zA-Z]/.test(char);
1474
- }
1475
- function isAsciiIdentifierChar(char) {
1476
- return /[a-zA-Z0-9_-]/.test(char);
1477
- }
1478
-
1479
- // src/core/matching/match-skeleton.ts
1480
- function groupHasRole(group) {
1481
- return group.tokens.some((t) => t.type === "role" || t.type === "group" && groupHasRole(t));
1482
- }
1483
- var MatchSkeleton = class {
1484
- /**
1485
- * @param makeReader - builds the grammar's reader, handed this matcher's view
1486
- * of the match in progress (one reader per matcher).
1487
- */
1488
- constructor(makeReader, confidenceModel) {
1489
- /**
1490
- * The pattern tokens still to match after the one being matched, through
1491
- * every enclosing group: a value stops at any marker the pattern still owes.
1492
- */
1493
- this.patternRest = [];
1494
- /**
1495
- * Whether the role slot being matched directly follows a literal the pattern
1496
- * matched: its marker (`[por {quantity}]`), or the command's verb.
1497
- */
1498
- this.slotAfterLiteral = false;
1499
- /**
1500
- * Selective memoization cache (Phase 6.1).
1501
- * Caches pattern match results keyed by `tokenPosition:patternId`.
1502
- * Cleared before each top-level matchBest() call.
1503
- */
1504
- this.matchCache = /* @__PURE__ */ new Map();
1505
- /**
1506
- * Stream index where each role's capture began, for the current
1507
- * `matchPattern` call. Rebuilt per call, written on the same success path as
1508
- * `captured` — see `PatternMatchResult.roleStarts`.
1509
- */
1510
- this.roleStarts = /* @__PURE__ */ new Map();
1511
- /**
1512
- * Track stem matches for confidence calculation.
1513
- * This is set during matching and read during confidence calculation.
1514
- */
1515
- this.stemMatchCount = 0;
1516
- this.totalKeywordMatches = 0;
1517
- this.confidenceModel = confidenceModel;
1518
- this.reader = makeReader({
1519
- patternRest: () => this.patternRest,
1520
- slotAfterLiteral: () => this.slotAfterLiteral,
1521
- wouldMatch: (pt, token) => this.patternTokenWouldMatch(pt, token)
1522
- });
1523
- }
1524
- /**
1525
- * Try to match a single pattern against the token stream.
1526
- * Returns the match result or null if no match.
1527
- */
1528
- matchPattern(tokens, pattern) {
1529
- const mark = tokens.mark();
1530
- const captured = /* @__PURE__ */ new Map();
1531
- this.reader.beginPattern(pattern);
1532
- this.stemMatchCount = 0;
1533
- this.totalKeywordMatches = 0;
1534
- this.roleStarts = /* @__PURE__ */ new Map();
1535
- this.patternRest = [];
1536
- const success = this.matchTokenSequence(tokens, pattern.template.tokens, captured);
1537
- if (!success) {
1538
- tokens.reset(mark);
1539
- return null;
1540
- }
1541
- const confidence = this.confidenceModel.calculate({
1542
- pattern,
1543
- captured,
1544
- stemMatchCount: this.stemMatchCount,
1545
- totalKeywordMatches: this.totalKeywordMatches
1546
- });
1547
- this.applyExtractionRules(pattern, captured);
1548
- return {
1549
- pattern,
1550
- captured,
1551
- consumedTokens: tokens.position() - mark.position,
1552
- confidence,
1553
- roleStarts: this.roleStarts,
1554
- ...this.reader.resultFields?.()
1555
- };
1556
- }
1557
- /**
1558
- * Try to match multiple patterns, return the best match.
1559
- */
1560
- matchBest(tokens, patterns) {
1561
- this.matchCache.clear();
1562
- const matches = [];
1563
- const startPos = tokens.position();
1564
- for (const pattern of patterns) {
1565
- const mark = tokens.mark();
1566
- const cacheKey = `${startPos}:${pattern.id}`;
1567
- let result;
1568
- if (this.matchCache.has(cacheKey)) {
1569
- result = this.matchCache.get(cacheKey);
1570
- } else {
1571
- result = this.matchPattern(tokens, pattern);
1572
- this.matchCache.set(cacheKey, result);
1573
- }
1574
- if (result) {
1575
- matches.push(result);
1576
- }
1577
- tokens.reset(mark);
1578
- }
1579
- if (matches.length === 0) {
1580
- return null;
1581
- }
1582
- matches.sort((a, b) => {
1583
- const priorityDiff = b.pattern.priority - a.pattern.priority;
1584
- if (priorityDiff !== 0) return priorityDiff;
1585
- return b.confidence - a.confidence;
1586
- });
1587
- const best = matches[0];
1588
- this.matchPattern(tokens, best.pattern);
1589
- return best;
1590
- }
1591
- /**
1592
- * Match a sequence of pattern tokens against the token stream.
1593
- */
1594
- matchTokenSequence(tokens, patternTokens, captured, nextAfterSequence) {
1595
- this.reader.skipLeading?.(tokens, patternTokens);
1596
- for (let i = 0; i < patternTokens.length; i++) {
1597
- const patternToken = patternTokens[i];
1598
- this.reader.beforePatternToken?.(tokens, patternTokens, i, captured);
1599
- if (this.reader.maySkipSlot(
1600
- patternToken,
1601
- tokens.peek(),
1602
- patternTokens[i + 1] ?? nextAfterSequence
1603
- )) {
1604
- const skipMark = tokens.mark();
1605
- const speculative = new Map(captured);
1606
- if (this.matchTokenSequence(
1607
- tokens,
1608
- patternTokens.slice(i + 1),
1609
- speculative,
1610
- nextAfterSequence
1611
- ) && this.reader.atClauseEnd(tokens)) {
1612
- captured.clear();
1613
- for (const [role, value] of speculative) captured.set(role, value);
1614
- return true;
1615
- }
1616
- tokens.reset(skipMark);
1617
- }
1618
- const outerRest = this.patternRest;
1619
- this.patternRest = [...patternTokens.slice(i + 1), ...outerRest];
1620
- this.slotAfterLiteral = patternToken.type === "role" && patternTokens[i - 1]?.type === "literal";
1621
- const matched = this.matchPatternToken(
1622
- tokens,
1623
- patternToken,
1624
- captured,
1625
- patternTokens[i + 1] ?? nextAfterSequence
1626
- );
1627
- this.patternRest = outerRest;
1628
- if (!matched) {
1629
- if (this.isOptional(patternToken)) {
1630
- continue;
1631
- }
1632
- return false;
1633
- }
1634
- }
1635
- return true;
1636
- }
1637
- /**
1638
- * Match a single pattern token against the current position in the stream.
1639
- */
1640
- matchPatternToken(tokens, patternToken, captured, nextPatternToken) {
1641
- switch (patternToken.type) {
1642
- case "literal":
1643
- return this.matchLiteralToken(tokens, patternToken);
1644
- case "role": {
1645
- const roleStart = tokens.position();
1646
- const matchedRole = this.reader.readRole(tokens, patternToken, captured, nextPatternToken);
1647
- if (matchedRole && captured.has(patternToken.role)) {
1648
- this.roleStarts.set(patternToken.role, roleStart);
1649
- }
1650
- return matchedRole;
1651
- }
1652
- case "group":
1653
- return this.matchGroupToken(tokens, patternToken, captured, nextPatternToken);
1654
- default:
1655
- return false;
1656
- }
1657
- }
1658
- /**
1659
- * Match a literal pattern token (keyword or particle).
1660
- */
1661
- matchLiteralToken(tokens, patternToken) {
1662
- const token = tokens.peek();
1663
- if (!token) return false;
1664
- const matchType = this.getMatchType(token, patternToken.value);
1665
- if (matchType !== "none") {
1666
- this.totalKeywordMatches++;
1667
- if (matchType === "stem") {
1668
- this.stemMatchCount++;
1669
- }
1670
- tokens.advance();
1671
- return true;
1672
- }
1673
- if (patternToken.alternatives) {
1674
- for (const alt of patternToken.alternatives) {
1675
- const altMatchType = this.getMatchType(token, alt);
1676
- if (altMatchType !== "none") {
1677
- this.totalKeywordMatches++;
1678
- if (altMatchType === "stem") {
1679
- this.stemMatchCount++;
1680
- }
1681
- tokens.advance();
1682
- return true;
1683
- }
1684
- }
1685
- }
1686
- for (const value of [patternToken.value, ...patternToken.alternatives ?? []]) {
1687
- for (const sep of ["-", "_"]) {
1688
- if (value.includes(sep) && this.tryMatchShatteredCompound(tokens, value, sep)) {
1689
- this.totalKeywordMatches++;
1690
- return true;
1691
- }
1692
- }
1693
- }
1694
- return false;
1695
- }
1696
- /**
1697
- * Match a compound keyword (`titik-henti`, `по_умолчанию`) against a
1698
- * shattered token run (`titik` `-` `henti` / `по` `_` `умолчанию`): word
1699
- * segments must be identifier/keyword/particle tokens equal to the expected
1700
- * segments (case-insensitive; particle because prepositions like ru `по` /
1701
- * uk `за` lead some underscore compounds), separators lone `sep` tokens.
1702
- * Consumes the run on success, resets the stream on failure.
1703
- */
1704
- tryMatchShatteredCompound(tokens, expected, sep) {
1705
- const segments = expected.split(sep);
1706
- if (segments.length < 2 || segments.some((s) => s.length === 0)) return false;
1707
- const mark = tokens.mark();
1708
- for (let i = 0; i < segments.length; i++) {
1709
- if (i > 0) {
1710
- const sepToken = tokens.peek();
1711
- if (!sepToken || sepToken.value !== sep) {
1712
- tokens.reset(mark);
1713
- return false;
1714
- }
1715
- tokens.advance();
1716
- }
1717
- const word = tokens.peek();
1718
- if (!word || word.kind !== "identifier" && word.kind !== "keyword" && word.kind !== "particle" || word.value.toLowerCase() !== segments[i].toLowerCase()) {
1719
- tokens.reset(mark);
1720
- return false;
1721
- }
1722
- tokens.advance();
1723
- }
1724
- return true;
1725
- }
1726
- /**
1727
- * Match a group pattern token (optional sequence).
1728
- */
1729
- matchGroupToken(tokens, patternToken, captured, nextPatternToken) {
1730
- const mark = tokens.mark();
1731
- const capturedBefore = new Map(captured);
1732
- let success = this.matchTokenSequence(tokens, patternToken.tokens, captured, nextPatternToken);
1733
- if (success && tokens.position() > mark.position && groupHasRole(patternToken) && [...captured].every(([role, value]) => capturedBefore.get(role) === value) && captured.size === capturedBefore.size) {
1734
- success = false;
1735
- }
1736
- if (success) {
1737
- const markerLiterals = [];
1738
- for (const inner of patternToken.tokens) {
1739
- if (inner.type === "group" && inner.renderRequired) {
1740
- for (const t of inner.tokens) {
1741
- if (t.type === "literal") markerLiterals.push(t.value, ...t.alternatives ?? []);
1742
- }
1743
- }
1744
- }
1745
- if (markerLiterals.length > 0) {
1746
- const addedRole = [...captured.keys()].some((role) => !capturedBefore.has(role));
1747
- if (addedRole) {
1748
- const last = tokens.tokens[tokens.position() - 1];
1749
- const markerSeen = last !== void 0 && tokens.position() > mark.position && markerLiterals.some((lit) => this.getMatchType(last, lit) !== "none");
1750
- if (!markerSeen) success = false;
1751
- }
1752
- }
1753
- }
1754
- if (!success) {
1755
- tokens.reset(mark);
1756
- for (const role of captured.keys()) {
1757
- if (!capturedBefore.has(role)) {
1758
- captured.delete(role);
1759
- }
1760
- }
1761
- for (const [role, value] of capturedBefore) {
1762
- if (captured.get(role) !== value) {
1763
- captured.set(role, value);
1764
- }
1765
- }
1766
- return patternToken.optional || false;
1767
- }
1768
- return true;
1769
- }
1770
- /**
1771
- * Whether a pattern token (literal, or a group starting with a literal) would
1772
- * match the given stream token. Used to keep the event-head source-clause
1773
- * consumption from stealing a marker the pattern explicitly expects.
1774
- */
1775
- patternTokenWouldMatch(pt, token) {
1776
- if (!pt) return false;
1777
- if (pt.type === "literal") {
1778
- if (this.getMatchType(token, pt.value) !== "none") return true;
1779
- return !!pt.alternatives?.some((a) => this.getMatchType(token, a) !== "none");
1780
- }
1781
- if (pt.type === "group") {
1782
- return this.patternTokenWouldMatch(pt.tokens?.[0], token);
1783
- }
1784
- return false;
1785
- }
1786
- /**
1787
- * Get the type of match for a token against a value.
1788
- * Used for confidence calculation.
1789
- */
1790
- getMatchType(token, value) {
1791
- if (token.value === value) return "exact";
1792
- if (token.normalized === value) return "normalized";
1793
- if (token.stem === value && token.stemConfidence !== void 0 && token.stemConfidence >= 0.7) {
1794
- return "stem";
1795
- }
1796
- if (token.kind === "keyword" && token.value.toLowerCase() === value.toLowerCase()) {
1797
- return "case-insensitive";
1056
+ case "keyword": {
1057
+ const tokenValue = token.normalized || token.value;
1058
+ const lower = safeToLowerCase(tokenValue);
1059
+ if (isValidReference(lower)) {
1060
+ return (0, import_intent.createReference)(lower);
1061
+ }
1062
+ return (0, import_intent.createLiteral)(token.normalized || token.value);
1798
1063
  }
1799
- if (token.kind === "keyword") {
1800
- const strippedToken = stripOptionalDiacritics(token.value).toLowerCase();
1801
- if (strippedToken && strippedToken === stripOptionalDiacritics(value).toLowerCase()) {
1802
- return "case-insensitive";
1064
+ case "identifier": {
1065
+ if (typeof token.value === "string" && token.value.startsWith(":")) {
1066
+ return (0, import_intent.createReference)(token.value);
1067
+ }
1068
+ const identLower = safeToLowerCase(token.value);
1069
+ if (isValidReference(identLower)) {
1070
+ return (0, import_intent.createReference)(identLower);
1803
1071
  }
1072
+ return { type: "expression", raw: token.value };
1804
1073
  }
1805
- return "none";
1074
+ case "url":
1075
+ return (0, import_intent.createLiteral)(token.value, "string");
1076
+ default:
1077
+ return null;
1806
1078
  }
1807
- /**
1808
- * Apply extraction rules to fill in static values and defaults for missing
1809
- * roles, and re-shape captured roles that declare a `transform` (the
1810
- * ExtractionRule field was documented but unwired — go-qu-url-dest is the
1811
- * first consumer: it re-types the marker-bound capture to the expression
1812
- * shape the en reference produces).
1813
- */
1814
- applyExtractionRules(pattern, captured) {
1815
- for (const [role, rule] of Object.entries(pattern.extraction)) {
1816
- const existing = captured.get(role);
1817
- if (existing) {
1818
- if (rule.transform) {
1819
- const raw = existing.type === "literal" ? String(existing.value) : String(existing.raw ?? "");
1820
- captured.set(role, rule.transform(raw));
1821
- }
1822
- } else if (rule.value !== void 0) {
1823
- captured.set(
1824
- role,
1825
- rule.valueIsExpression ? { type: "expression", raw: rule.value } : { type: "literal", value: rule.value }
1826
- );
1827
- } else if (rule.default) {
1828
- const implicitDefault = {
1829
- ...rule.default,
1830
- implicit: true
1831
- };
1832
- captured.set(role, implicitDefault);
1833
- }
1079
+ }
1080
+ function parseLiteralValue(value) {
1081
+ if (value.startsWith('"') || value.startsWith("'") || value.startsWith("`") || value.startsWith("\u300C")) {
1082
+ const inner = value.slice(1, -1);
1083
+ return (0, import_intent.createLiteral)(inner, "string");
1084
+ }
1085
+ if (value === "true") return (0, import_intent.createLiteral)(true, "boolean");
1086
+ if (value === "false") return (0, import_intent.createLiteral)(false, "boolean");
1087
+ const durationMatch = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
1088
+ if (durationMatch) {
1089
+ const num2 = parseFloat(durationMatch[1]);
1090
+ const unit = durationMatch[2];
1091
+ if (unit) {
1092
+ return (0, import_intent.createLiteral)(value, "duration");
1834
1093
  }
1094
+ return (0, import_intent.createLiteral)(num2, "number");
1835
1095
  }
1836
- /**
1837
- * Check if a pattern token is optional.
1838
- */
1839
- isOptional(patternToken) {
1840
- return patternToken.type !== "literal" && patternToken.optional === true;
1096
+ const num = parseFloat(value);
1097
+ if (!isNaN(num)) {
1098
+ return (0, import_intent.createLiteral)(num, "number");
1841
1099
  }
1842
- };
1100
+ return (0, import_intent.createLiteral)(value, "string");
1101
+ }
1102
+ var ENGLISH_NOISE_WORDS = /* @__PURE__ */ new Set(["the", "a", "an"]);
1103
+ function skipNoiseWords(tokens) {
1104
+ const token = tokens.peek();
1105
+ if (!token) return;
1106
+ const tokenLower = safeToLowerCase(token.value);
1107
+ if (ENGLISH_NOISE_WORDS.has(tokenLower)) {
1108
+ const mark = tokens.mark();
1109
+ tokens.advance();
1110
+ const nextToken = tokens.peek();
1111
+ if (nextToken && nextToken.kind === "selector") {
1112
+ return;
1113
+ }
1114
+ tokens.reset(mark);
1115
+ }
1116
+ if (tokenLower === "class") {
1117
+ tokens.advance();
1118
+ }
1119
+ }
1843
1120
 
1844
1121
  // src/core/matching/stock-reader.ts
1845
1122
  function greedyStopMarkers(rest) {
@@ -1941,284 +1218,151 @@ function createStockReader(profiles = {}) {
1941
1218
  return (view) => new StockValueReader(view, profiles);
1942
1219
  }
1943
1220
 
1944
- // src/generation/pattern-generator.ts
1945
- var defaultConfig = {
1946
- basePriority: 100,
1947
- includeOptionalVariants: true
1948
- };
1949
- function generatePattern(schema, profile, config = defaultConfig) {
1950
- const id = `${schema.action}-${profile.code}-generated`;
1951
- const priority = config.basePriority ?? 100;
1952
- const keyword = profile.keywords[schema.action];
1953
- if (!keyword) {
1954
- throw new Error(`No keyword translation for '${schema.action}' in ${profile.code}`);
1955
- }
1956
- const tokens = buildTokens(schema, profile, keyword.primary, keyword.alternatives);
1957
- const extraction = buildExtractionRules(schema, profile);
1958
- const format = buildFormatString(schema, profile, keyword.primary);
1959
- return {
1960
- id,
1961
- language: profile.code,
1962
- command: schema.action,
1963
- priority,
1964
- template: {
1965
- format,
1966
- tokens
1967
- },
1968
- extraction
1969
- };
1970
- }
1971
- function buildTokens(schema, profile, keyword, alternatives) {
1972
- const tokens = [];
1973
- const keywordToken = {
1974
- type: "literal",
1975
- value: keyword,
1976
- ...alternatives?.length && { alternatives }
1977
- };
1978
- const sortedRoles = sortRolesByWordOrder(schema.roles, profile.wordOrder);
1979
- if (profile.wordOrder === "SVO") {
1980
- tokens.push(keywordToken);
1981
- for (const role of sortedRoles) {
1982
- addRoleWithMarker(tokens, role, profile);
1983
- }
1984
- } else if (profile.wordOrder === "SOV") {
1985
- const preVerb = [];
1986
- const postVerb = [];
1987
- for (const role of sortedRoles) {
1988
- addRoleWithMarker(role.sovSlot === "postVerb" ? postVerb : preVerb, role, profile);
1989
- }
1990
- tokens.push(...preVerb, keywordToken, ...postVerb);
1991
- } else if (profile.wordOrder === "VSO") {
1992
- tokens.push(keywordToken);
1993
- for (const role of sortedRoles) {
1994
- addRoleWithMarker(tokens, role, profile);
1995
- }
1996
- } else {
1997
- tokens.push(keywordToken);
1998
- for (const role of sortedRoles) {
1999
- addRoleWithMarker(tokens, role, profile);
2000
- }
2001
- }
2002
- return tokens;
2003
- }
2004
- function addRoleWithMarker(tokens, roleSpec, profile) {
2005
- const marker = getMarkerForRole(roleSpec, profile);
2006
- const isOptional = !roleSpec.required;
2007
- const roleToken = {
2008
- type: "role",
2009
- role: roleSpec.role,
2010
- optional: isOptional,
2011
- expectedTypes: roleSpec.expectedTypes,
2012
- ...roleSpec.greedy && { greedy: true }
1221
+ // src/core/matching/stock-confidence.ts
1222
+ function scoreParts(ctx) {
1223
+ const { pattern, captured } = ctx;
1224
+ let score = 0;
1225
+ let maxScore = 0;
1226
+ const hasDefault = (role) => {
1227
+ return pattern.extraction?.[role]?.default !== void 0;
2013
1228
  };
2014
- if (marker) {
2015
- const markerInfo = profile.roleMarkers?.[roleSpec.role];
2016
- const defaultPosition = profile.wordOrder === "SOV" ? "after" : "before";
2017
- const position = roleSpec.markerPosition ?? markerInfo?.position ?? defaultPosition;
2018
- if (isOptional) {
2019
- if (position === "before") {
2020
- tokens.push({
2021
- type: "group",
2022
- optional: true,
2023
- tokens: [{ type: "literal", value: marker }, roleToken]
2024
- });
2025
- } else {
2026
- tokens.push({
2027
- type: "group",
2028
- optional: true,
2029
- tokens: [roleToken, { type: "literal", value: marker }]
2030
- });
1229
+ for (const token of pattern.template.tokens) {
1230
+ if (token.type === "role") {
1231
+ maxScore += 1;
1232
+ if (captured.has(token.role)) {
1233
+ score += 1;
2031
1234
  }
2032
- } else {
2033
- if (position === "before") {
2034
- tokens.push({ type: "literal", value: marker });
2035
- tokens.push(roleToken);
2036
- } else {
2037
- tokens.push(roleToken);
2038
- tokens.push({ type: "literal", value: marker });
1235
+ } else if (token.type === "group") {
1236
+ for (const subToken of token.tokens) {
1237
+ if (subToken.type === "role") {
1238
+ const roleHasDefault = hasDefault(subToken.role);
1239
+ const weight = 0.8;
1240
+ maxScore += weight;
1241
+ if (captured.has(subToken.role)) {
1242
+ score += weight;
1243
+ } else if (roleHasDefault) {
1244
+ score += weight * 0.6;
1245
+ }
1246
+ }
2039
1247
  }
2040
1248
  }
2041
- } else {
2042
- tokens.push(roleToken);
2043
- }
2044
- }
2045
- function getMarkerForRole(roleSpec, profile) {
2046
- if (roleSpec.markerOverride?.[profile.code]) {
2047
- return roleSpec.markerOverride[profile.code];
2048
- }
2049
- return profile.roleMarkers?.[roleSpec.role]?.primary;
2050
- }
2051
- function sortRolesByWordOrder(roles, wordOrder) {
2052
- const sorted = [...roles];
2053
- if (wordOrder === "SVO") {
2054
- sorted.sort((a, b) => (b.svoPosition ?? 0) - (a.svoPosition ?? 0));
2055
- } else if (wordOrder === "SOV") {
2056
- sorted.sort((a, b) => (b.sovPosition ?? 0) - (a.sovPosition ?? 0));
2057
1249
  }
2058
- return sorted;
2059
- }
2060
- function buildExtractionRules(schema, profile) {
2061
- const rules = {};
2062
- for (const roleSpec of schema.roles) {
2063
- const marker = getMarkerForRole(roleSpec, profile);
2064
- const rule = {
2065
- ...roleSpec.default !== void 0 && { default: roleSpec.default },
2066
- ...marker && { marker }
2067
- };
2068
- rules[roleSpec.role] = rule;
1250
+ const roleCoverage = maxScore > 0 ? score / maxScore : 1;
1251
+ let baseConfidence = roleCoverage;
1252
+ let stemPenalty = 0;
1253
+ if (ctx.stemMatchCount > 0 && ctx.totalKeywordMatches > 0) {
1254
+ stemPenalty = ctx.stemMatchCount / ctx.totalKeywordMatches * 0.15;
1255
+ baseConfidence = Math.max(0.5, baseConfidence - stemPenalty);
2069
1256
  }
2070
- return rules;
1257
+ const vsoBoost = calculateVSOConfidenceBoost(pattern);
1258
+ baseConfidence = Math.min(1, baseConfidence + vsoBoost);
1259
+ const prepositionAdjustment = arabicPrepositionDisambiguation(pattern, captured);
1260
+ baseConfidence = Math.max(0, Math.min(1, baseConfidence + prepositionAdjustment));
1261
+ return {
1262
+ roleCoverage,
1263
+ stemPenalty,
1264
+ languageBoost: vsoBoost,
1265
+ languageAdjustment: prepositionAdjustment,
1266
+ final: baseConfidence
1267
+ };
2071
1268
  }
2072
- function buildFormatString(schema, profile, keyword) {
2073
- const isSOV = profile.wordOrder === "SOV";
2074
- const preVerb = [];
2075
- const postVerb = [];
2076
- const sortedRoles = sortRolesByWordOrder(schema.roles, profile.wordOrder);
2077
- for (const role of sortedRoles) {
2078
- const marker = getMarkerForRole(role, profile);
2079
- const roleName = `{${role.role}}`;
2080
- const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
2081
- if (marker) {
2082
- const markerInfo = profile.roleMarkers?.[role.role];
2083
- if (markerInfo?.position === "after") {
2084
- bucket.push(`${roleName} ${marker}`);
2085
- } else {
2086
- bucket.push(`${marker} ${roleName}`);
2087
- }
2088
- } else {
2089
- bucket.push(roleName);
2090
- }
1269
+ function calculateVSOConfidenceBoost(pattern) {
1270
+ if (pattern.language !== "ar") {
1271
+ return 0;
2091
1272
  }
2092
- return (isSOV ? [...preVerb, keyword, ...postVerb] : [keyword, ...preVerb, ...postVerb]).join(
2093
- " "
2094
- );
2095
- }
2096
- function generatePatternVariants(schema, profile, config = defaultConfig) {
2097
- const patterns = [];
2098
- patterns.push(generatePattern(schema, profile, config));
2099
- return patterns;
2100
- }
2101
-
2102
- // src/generation/renderer.ts
2103
- function lookupKeyword(keywords, action, language) {
2104
- return keywords[action]?.[language] ?? action;
2105
- }
2106
- function lookupMarker(markers, marker, language) {
2107
- return markers[marker]?.[language] ?? marker;
2108
- }
2109
- function buildPhrase(...parts) {
2110
- return parts.filter(Boolean).join(" ");
2111
- }
2112
- function buildTablesFromProfiles(schemas, profiles) {
2113
- const keywords = {};
2114
- const markers = {};
2115
- for (const profile of profiles) {
2116
- for (const [action, kw] of Object.entries(profile.keywords)) {
2117
- if (!keywords[action]) keywords[action] = {};
2118
- keywords[action][profile.code] = kw.primary;
2119
- }
1273
+ const firstToken = pattern.template.tokens[0];
1274
+ if (!firstToken || firstToken.type !== "literal") {
1275
+ return 0;
2120
1276
  }
2121
- for (const schema of schemas) {
2122
- for (const role of schema.roles) {
2123
- if (role.markerOverride) {
2124
- const markerKey = role.role;
2125
- if (!markers[markerKey]) markers[markerKey] = {};
2126
- for (const [lang, markerText] of Object.entries(role.markerOverride)) {
2127
- markers[markerKey][lang] = markerText;
2128
- }
2129
- }
2130
- }
1277
+ const ARABIC_VERBS = /* @__PURE__ */ new Set([
1278
+ "\u0628\u062F\u0644",
1279
+ "\u063A\u064A\u0631",
1280
+ "\u0623\u0636\u0641",
1281
+ "\u0623\u0632\u0644",
1282
+ "\u0636\u0639",
1283
+ "\u0627\u062C\u0639\u0644",
1284
+ "\u0639\u064A\u0646",
1285
+ "\u0632\u062F",
1286
+ "\u0627\u0646\u0642\u0635",
1287
+ "\u0633\u062C\u0644",
1288
+ "\u0623\u0638\u0647\u0631",
1289
+ "\u0623\u062E\u0641",
1290
+ "\u0634\u063A\u0644",
1291
+ "\u0623\u0631\u0633\u0644",
1292
+ "\u0631\u0643\u0632",
1293
+ "\u0634\u0648\u0634",
1294
+ "\u062A\u0648\u0642\u0641",
1295
+ "\u0627\u0646\u0633\u062E",
1296
+ "\u0627\u062D\u0630\u0641",
1297
+ "\u0627\u0635\u0646\u0639",
1298
+ "\u0627\u0646\u062A\u0638\u0631",
1299
+ "\u0627\u0646\u062A\u0642\u0627\u0644",
1300
+ "\u0623\u0648"
1301
+ ]);
1302
+ if (ARABIC_VERBS.has(firstToken.value)) {
1303
+ return 0.15;
2131
1304
  }
2132
- for (const profile of profiles) {
2133
- if (profile.roleMarkers) {
2134
- for (const [markerKey, markerDef] of Object.entries(profile.roleMarkers)) {
2135
- if (!markers[markerKey]) markers[markerKey] = {};
2136
- markers[markerKey][profile.code] = markerDef.primary;
1305
+ if (firstToken.alternatives) {
1306
+ for (const alt of firstToken.alternatives) {
1307
+ if (ARABIC_VERBS.has(alt)) {
1308
+ return 0.15;
2137
1309
  }
2138
1310
  }
2139
1311
  }
2140
- return { keywords, markers };
1312
+ return 0;
2141
1313
  }
2142
- function detectWordOrders(profiles) {
2143
- const sovLanguages = /* @__PURE__ */ new Set();
2144
- const vsoLanguages = /* @__PURE__ */ new Set();
2145
- for (const profile of profiles) {
2146
- if (profile.wordOrder === "SOV") sovLanguages.add(profile.code);
2147
- if (profile.wordOrder === "VSO") vsoLanguages.add(profile.code);
1314
+ function arabicPrepositionDisambiguation(pattern, captured) {
1315
+ if (pattern.language !== "ar") {
1316
+ return 0;
2148
1317
  }
2149
- return { sovLanguages, vsoLanguages };
2150
- }
2151
- function createSchemaRenderer(schemas, profiles) {
2152
- const tables = buildRenderTables(schemas, profiles);
2153
- return {
2154
- render(node, language) {
2155
- return renderFromSchema(node, language, tables) ?? node.action;
2156
- }
2157
- };
2158
- }
2159
- function createDomainRenderer(config) {
2160
- const { schemas, profiles, overrides } = config;
2161
- let tables;
2162
- return (node, language) => {
2163
- const override = overrides?.[node.action];
2164
- if (override) return override(node, language);
2165
- tables ?? (tables = buildRenderTables(schemas, profiles));
2166
- return renderFromSchema(node, language, tables);
1318
+ let adjustment = 0;
1319
+ const PREFERRED_PREPOSITIONS = {
1320
+ patient: ["\u0639\u0644\u0649"],
1321
+ // element selectors prefer على (on/upon)
1322
+ destination: ["\u0625\u0644\u0649", "\u0627\u0644\u0649"],
1323
+ // destination prefers إلى (to)
1324
+ source: ["\u0645\u0646"],
1325
+ // source prefers من (from)
1326
+ agent: ["\u0645\u0646"],
1327
+ // agent/by prefers من (from/by)
1328
+ manner: ["\u0628"],
1329
+ // manner prefers ب (with/by)
1330
+ style: ["\u0628"],
1331
+ // style prefers ب (with)
1332
+ goal: ["\u0625\u0644\u0649", "\u0627\u0644\u0649"],
1333
+ // target state prefers إلى (to)
1334
+ method: ["\u0628"]
1335
+ // method prefers ب (with/by)
2167
1336
  };
2168
- }
2169
- function buildRenderTables(schemas, profiles) {
2170
- const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
2171
- const { sovLanguages } = detectWordOrders(profiles);
2172
- const markerPositions = {};
2173
- for (const profile of profiles) {
2174
- for (const [role, markerDef] of Object.entries(profile.roleMarkers ?? {})) {
2175
- if (!markerDef.position) continue;
2176
- markerPositions[role] ?? (markerPositions[role] = {});
2177
- markerPositions[role][profile.code] = markerDef.position;
2178
- }
2179
- }
2180
- const schemaMap = /* @__PURE__ */ new Map();
2181
- for (const s of schemas) schemaMap.set(s.action, s);
2182
- return { keywords, markers, markerPositions, sovLanguages, schemaMap };
2183
- }
2184
- function renderFromSchema(node, language, tables) {
2185
- const schema = tables.schemaMap.get(node.action);
2186
- if (!schema) return null;
2187
- const keyword = lookupKeyword(tables.keywords, node.action, language);
2188
- const isSOV = tables.sovLanguages.has(language);
2189
- const ordered = sortRolesByWordOrder([...schema.roles], isSOV ? "SOV" : "SVO");
2190
- const preVerb = [];
2191
- const postVerb = [];
2192
- for (const role of ordered) {
2193
- let value = (0, import_intent.extractRoleValue)(node, role.role);
2194
- if (!value && role.default !== void 0) {
2195
- value = String((0, import_intent.extractValue)(role.default));
2196
- }
2197
- if (!value) continue;
2198
- if (role.quoteMultiword && /\s/.test(value) && !/^(["']).*\1$/.test(value)) {
2199
- value = `"${value}"`;
1337
+ for (const [role, value] of captured.entries()) {
1338
+ const preferred = PREFERRED_PREPOSITIONS[role];
1339
+ if (!preferred || preferred.length === 0) {
1340
+ continue;
2200
1341
  }
2201
- const markerText = role.renderOverride?.[language] ?? role.markerOverride?.[language] ?? role.renderOverride?.["*"] ?? tables.markers[role.role]?.[language];
2202
- const markerPosition = role.markerPositionOverride?.[language] ?? role.markerPosition ?? tables.markerPositions[role.role]?.[language] ?? (isSOV ? "after" : "before");
2203
- const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
2204
- if (markerText && markerPosition === "before") {
2205
- bucket.push(markerText, value);
2206
- } else if (markerText) {
2207
- bucket.push(value, markerText);
2208
- } else {
2209
- bucket.push(value);
1342
+ const metadata = "metadata" in value ? value.metadata : void 0;
1343
+ if (metadata && typeof metadata.prepositionValue === "string") {
1344
+ const usedPreposition = metadata.prepositionValue;
1345
+ if (preferred.includes(usedPreposition)) {
1346
+ adjustment += 0.1;
1347
+ } else {
1348
+ adjustment -= 0.1;
1349
+ }
2210
1350
  }
2211
1351
  }
2212
- return isSOV ? buildPhrase(...preVerb, keyword, ...postVerb) : buildPhrase(keyword, ...preVerb, ...postVerb);
1352
+ return Math.max(-0.1, Math.min(0.1, adjustment));
2213
1353
  }
1354
+ var stockConfidenceModel = {
1355
+ calculate: (ctx) => scoreParts(ctx).final,
1356
+ calculateWithBreakdown: (ctx) => scoreParts(ctx)
1357
+ };
2214
1358
 
2215
1359
  // src/generation/schema-patterns.ts
2216
1360
  function createSchemaPatternGenerator(grammar) {
2217
1361
  return {
2218
- generatePattern: (schema, profile, config) => generatePattern2(grammar, schema, profile, config),
1362
+ generatePattern: (schema, profile, config) => generatePattern(grammar, schema, profile, config),
2219
1363
  generateSimplePattern: (schema, profile, config) => generateSimplePattern(grammar, schema, profile, config),
2220
1364
  generateVerbFirstPattern: (schema, profile, config) => generateVerbFirstPattern(schema, profile, config),
2221
- generatePatternVariants: (schema, profile, config) => generatePatternVariants2(grammar, schema, profile, config),
1365
+ generatePatternVariants: (schema, profile, config) => generatePatternVariants(grammar, schema, profile, config),
2222
1366
  buildRoleToken
2223
1367
  };
2224
1368
  }
@@ -2268,21 +1412,21 @@ function schemaMarkerAlternatives(roleSpec, languageCode, marker) {
2268
1412
  const alternatives = [.../* @__PURE__ */ new Set([...legacy, ...variants])].filter((a) => a && a !== marker);
2269
1413
  return alternatives.length ? alternatives : void 0;
2270
1414
  }
2271
- var defaultConfig2 = {
1415
+ var defaultConfig = {
2272
1416
  basePriority: 100,
2273
1417
  generateSimpleVariants: true,
2274
1418
  generateAlternatives: true
2275
1419
  };
2276
- function generatePattern2(grammar, schema, profile, config = defaultConfig2) {
1420
+ function generatePattern(grammar, schema, profile, config = defaultConfig) {
2277
1421
  const id = `${schema.action}-${profile.code}-generated`;
2278
1422
  const priority = config.basePriority ?? 100;
2279
1423
  const keyword = profile.keywords[schema.action];
2280
1424
  if (!keyword) {
2281
1425
  throw new Error(`No keyword translation for '${schema.action}' in ${profile.code}`);
2282
1426
  }
2283
- const tokens = buildTokens2(grammar, schema, profile, keyword);
1427
+ const tokens = buildTokens(grammar, schema, profile, keyword);
2284
1428
  const extraction = buildExtractionRulesWithDefaults(schema, profile);
2285
- const format = buildFormatString2(grammar, schema, profile, keyword);
1429
+ const format = buildFormatString(grammar, schema, profile, keyword);
2286
1430
  return {
2287
1431
  id,
2288
1432
  language: profile.code,
@@ -2295,7 +1439,7 @@ function generatePattern2(grammar, schema, profile, config = defaultConfig2) {
2295
1439
  extraction
2296
1440
  };
2297
1441
  }
2298
- function generateSimplePattern(grammar, schema, profile, config = defaultConfig2) {
1442
+ function generateSimplePattern(grammar, schema, profile, config = defaultConfig) {
2299
1443
  const optionalRoles = schema.roles.filter((r) => !r.required);
2300
1444
  if (optionalRoles.length === 0) {
2301
1445
  return null;
@@ -2305,7 +1449,7 @@ function generateSimplePattern(grammar, schema, profile, config = defaultConfig2
2305
1449
  ...schema,
2306
1450
  roles: requiredRoles
2307
1451
  };
2308
- const pattern = generatePattern2(grammar, simpleSchema, profile, config);
1452
+ const pattern = generatePattern(grammar, simpleSchema, profile, config);
2309
1453
  return {
2310
1454
  ...pattern,
2311
1455
  id: `${schema.action}-${profile.code}-generated-simple`,
@@ -2314,7 +1458,7 @@ function generateSimplePattern(grammar, schema, profile, config = defaultConfig2
2314
1458
  extraction: buildExtractionRulesWithDefaults(schema, profile)
2315
1459
  };
2316
1460
  }
2317
- function generateVerbFirstPattern(schema, profile, config = defaultConfig2) {
1461
+ function generateVerbFirstPattern(schema, profile, config = defaultConfig) {
2318
1462
  if (profile.wordOrder !== "SOV") return null;
2319
1463
  const requiredRoles = schema.roles.filter((r) => r.required);
2320
1464
  if (requiredRoles.length !== 1) return null;
@@ -2347,11 +1491,11 @@ function generateVerbFirstPattern(schema, profile, config = defaultConfig2) {
2347
1491
  extraction: buildExtractionRulesWithDefaults(schema, profile)
2348
1492
  };
2349
1493
  }
2350
- function generatePatternVariants2(grammar, schema, profile, config = defaultConfig2) {
1494
+ function generatePatternVariants(grammar, schema, profile, config = defaultConfig) {
2351
1495
  const patterns = [];
2352
1496
  const full = schema;
2353
1497
  schema = withoutVariantOnlyRoles(schema);
2354
- patterns.push(generatePattern2(grammar, schema, profile, config));
1498
+ patterns.push(generatePattern(grammar, schema, profile, config));
2355
1499
  if (config.generateSimpleVariants) {
2356
1500
  const simple = generateSimplePattern(grammar, schema, profile, config);
2357
1501
  if (simple) {
@@ -2363,7 +1507,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
2363
1507
  ...schema,
2364
1508
  roles: schema.roles.filter((r) => r.role !== omitted)
2365
1509
  };
2366
- const variant = generatePattern2(grammar, variantSchema, profile, config);
1510
+ const variant = generatePattern(grammar, variantSchema, profile, config);
2367
1511
  patterns.push({
2368
1512
  ...variant,
2369
1513
  id: `${schema.action}-${profile.code}-generated-no-${omitted}`,
@@ -2378,7 +1522,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
2378
1522
  (r) => r.role === widen.role ? { ...r, expectedTypes: [...widen.types] } : r
2379
1523
  )
2380
1524
  };
2381
- const main = generatePattern2(grammar, cloneSchema, profile, config);
1525
+ const main = generatePattern(grammar, cloneSchema, profile, config);
2382
1526
  patterns.push({
2383
1527
  ...main,
2384
1528
  id: `${schema.action}-${profile.code}-generated-${widen.idSuffix}`,
@@ -2393,7 +1537,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
2393
1537
  return r.role === v.widen?.role ? { ...required, expectedTypes: [...v.widen.types] } : required;
2394
1538
  })
2395
1539
  };
2396
- const variant = generatePattern2(grammar, variantSchema, profile, config);
1540
+ const variant = generatePattern(grammar, variantSchema, profile, config);
2397
1541
  patterns.push({
2398
1542
  ...variant,
2399
1543
  id: `${schema.action}-${profile.code}-generated-${v.idSuffix}`,
@@ -2418,7 +1562,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
2418
1562
  };
2419
1563
  const delta = v.priorityDelta ?? 5;
2420
1564
  const carrier = v.methodCarrier ? { [v.methodCarrier]: { value: literal } } : {};
2421
- const main = generatePattern2(grammar, cloneSchema, profile, config);
1565
+ const main = generatePattern(grammar, cloneSchema, profile, config);
2422
1566
  patterns.push({
2423
1567
  ...main,
2424
1568
  id: `${schema.action}-${profile.code}-generated-${v.idSuffix}`,
@@ -2439,7 +1583,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
2439
1583
  }
2440
1584
  return patterns;
2441
1585
  }
2442
- function buildTokens2(grammar, schema, profile, keyword) {
1586
+ function buildTokens(grammar, schema, profile, keyword) {
2443
1587
  const tokens = [];
2444
1588
  const verbToken = keyword.alternatives ? { type: "literal", value: keyword.primary, alternatives: keyword.alternatives } : { type: "literal", value: keyword.primary };
2445
1589
  const postVerbRoles = profile.wordOrder === "SOV" ? schema.roles.filter((r) => r.sovSlot === "postVerb") : [];
@@ -2593,7 +1737,7 @@ function buildRoleToken(roleSpec, profile) {
2593
1737
  }
2594
1738
  return tokens;
2595
1739
  }
2596
- function buildExtractionRules2(schema, profile) {
1740
+ function buildExtractionRules(schema, profile) {
2597
1741
  const rules = {};
2598
1742
  for (const roleSpec of schema.roles) {
2599
1743
  const overrideMarker = roleSpec.markerOverride?.[profile.code];
@@ -2623,7 +1767,7 @@ function buildExtractionRules2(schema, profile) {
2623
1767
  return rules;
2624
1768
  }
2625
1769
  function buildExtractionRulesWithDefaults(schema, profile) {
2626
- const baseRules = buildExtractionRules2(schema, profile);
1770
+ const baseRules = buildExtractionRules(schema, profile);
2627
1771
  const rules = {};
2628
1772
  for (const roleSpec of schema.roles) {
2629
1773
  const baseRule = baseRules[roleSpec.role] || {};
@@ -2635,7 +1779,7 @@ function buildExtractionRulesWithDefaults(schema, profile) {
2635
1779
  }
2636
1780
  return rules;
2637
1781
  }
2638
- function buildFormatString2(grammar, schema, profile, keyword) {
1782
+ function buildFormatString(grammar, schema, profile, keyword) {
2639
1783
  const parts = [];
2640
1784
  const sortedRoles = sortRolesByPosition(schema.roles, profile.wordOrder, grammar.positionOrder);
2641
1785
  const roleParts = sortedRoles.map((roleSpec) => {
@@ -4440,21 +3584,14 @@ function createMultilingualDSL(config) {
4440
3584
  return new MultilingualDSLImpl(effectiveConfig, registry, transformer, profileProvider, engine);
4441
3585
  }
4442
3586
  function engineFor(config) {
4443
- const name = config.engine ?? "classic";
4444
- if (name === "classic") return classicEngine();
3587
+ const name = config.engine ?? "core";
4445
3588
  if (name === "core") return coreEngine();
4446
- throw new Error(`Unknown DSL engine "${String(name)}" (expected "classic" or "core")`);
4447
- }
4448
- function classicEngine() {
4449
- const matcher = new PatternMatcher();
4450
- return {
4451
- generate: (schema, profile) => generatePattern(schema, profile),
4452
- match: (tokens, pattern, language) => matcher.matchPattern(tokens, pattern, { code: language }),
4453
- fallbackRenderer: (config) => createDomainRenderer({
4454
- schemas: config.schemas,
4455
- profiles: config.languages.map((l) => l.patternProfile)
4456
- })
4457
- };
3589
+ if (name === "classic") {
3590
+ throw new Error(
3591
+ 'The "classic" DSL engine was removed in @lokascript/framework 5.0. Omit `engine` (or pass "core"): every domain reads on the core.'
3592
+ );
3593
+ }
3594
+ throw new Error(`Unknown DSL engine "${name}" (expected "core")`);
4458
3595
  }
4459
3596
  function schemaProfileOf(profile) {
4460
3597
  const side = profile.wordOrder === "SOV" ? "after" : "before";
@@ -6142,6 +5279,130 @@ async function registryToAOTBackends(registry) {
6142
5279
  // src/schema/command-schema.ts
6143
5280
  var import_intent6 = require("@lokascript/intent");
6144
5281
 
5282
+ // src/generation/profile.ts
5283
+ function sortRolesByWordOrder(roles, wordOrder) {
5284
+ const sorted = [...roles];
5285
+ if (wordOrder === "SVO") {
5286
+ sorted.sort((a, b) => (b.svoPosition ?? 0) - (a.svoPosition ?? 0));
5287
+ } else if (wordOrder === "SOV") {
5288
+ sorted.sort((a, b) => (b.sovPosition ?? 0) - (a.sovPosition ?? 0));
5289
+ }
5290
+ return sorted;
5291
+ }
5292
+
5293
+ // src/generation/renderer.ts
5294
+ function lookupKeyword(keywords, action, language) {
5295
+ return keywords[action]?.[language] ?? action;
5296
+ }
5297
+ function lookupMarker(markers, marker, language) {
5298
+ return markers[marker]?.[language] ?? marker;
5299
+ }
5300
+ function buildPhrase(...parts) {
5301
+ return parts.filter(Boolean).join(" ");
5302
+ }
5303
+ function buildTablesFromProfiles(schemas, profiles) {
5304
+ const keywords = {};
5305
+ const markers = {};
5306
+ for (const profile of profiles) {
5307
+ for (const [action, kw] of Object.entries(profile.keywords)) {
5308
+ if (!keywords[action]) keywords[action] = {};
5309
+ keywords[action][profile.code] = kw.primary;
5310
+ }
5311
+ }
5312
+ for (const schema of schemas) {
5313
+ for (const role of schema.roles) {
5314
+ if (role.markerOverride) {
5315
+ const markerKey = role.role;
5316
+ if (!markers[markerKey]) markers[markerKey] = {};
5317
+ for (const [lang, markerText] of Object.entries(role.markerOverride)) {
5318
+ markers[markerKey][lang] = markerText;
5319
+ }
5320
+ }
5321
+ }
5322
+ }
5323
+ for (const profile of profiles) {
5324
+ if (profile.roleMarkers) {
5325
+ for (const [markerKey, markerDef] of Object.entries(profile.roleMarkers)) {
5326
+ if (!markers[markerKey]) markers[markerKey] = {};
5327
+ markers[markerKey][profile.code] = markerDef.primary;
5328
+ }
5329
+ }
5330
+ }
5331
+ return { keywords, markers };
5332
+ }
5333
+ function detectWordOrders(profiles) {
5334
+ const sovLanguages = /* @__PURE__ */ new Set();
5335
+ const vsoLanguages = /* @__PURE__ */ new Set();
5336
+ for (const profile of profiles) {
5337
+ if (profile.wordOrder === "SOV") sovLanguages.add(profile.code);
5338
+ if (profile.wordOrder === "VSO") vsoLanguages.add(profile.code);
5339
+ }
5340
+ return { sovLanguages, vsoLanguages };
5341
+ }
5342
+ function createSchemaRenderer(schemas, profiles) {
5343
+ const tables = buildRenderTables(schemas, profiles);
5344
+ return {
5345
+ render(node, language) {
5346
+ return renderFromSchema(node, language, tables) ?? node.action;
5347
+ }
5348
+ };
5349
+ }
5350
+ function createDomainRenderer(config) {
5351
+ const { schemas, profiles, overrides } = config;
5352
+ let tables;
5353
+ return (node, language) => {
5354
+ const override = overrides?.[node.action];
5355
+ if (override) return override(node, language);
5356
+ tables ?? (tables = buildRenderTables(schemas, profiles));
5357
+ return renderFromSchema(node, language, tables);
5358
+ };
5359
+ }
5360
+ function buildRenderTables(schemas, profiles) {
5361
+ const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
5362
+ const { sovLanguages } = detectWordOrders(profiles);
5363
+ const markerPositions = {};
5364
+ for (const profile of profiles) {
5365
+ for (const [role, markerDef] of Object.entries(profile.roleMarkers ?? {})) {
5366
+ if (!markerDef.position) continue;
5367
+ markerPositions[role] ?? (markerPositions[role] = {});
5368
+ markerPositions[role][profile.code] = markerDef.position;
5369
+ }
5370
+ }
5371
+ const schemaMap = /* @__PURE__ */ new Map();
5372
+ for (const s of schemas) schemaMap.set(s.action, s);
5373
+ return { keywords, markers, markerPositions, sovLanguages, schemaMap };
5374
+ }
5375
+ function renderFromSchema(node, language, tables) {
5376
+ const schema = tables.schemaMap.get(node.action);
5377
+ if (!schema) return null;
5378
+ const keyword = lookupKeyword(tables.keywords, node.action, language);
5379
+ const isSOV = tables.sovLanguages.has(language);
5380
+ const ordered = sortRolesByWordOrder([...schema.roles], isSOV ? "SOV" : "SVO");
5381
+ const preVerb = [];
5382
+ const postVerb = [];
5383
+ for (const role of ordered) {
5384
+ let value = (0, import_intent.extractRoleValue)(node, role.role);
5385
+ if (!value && role.default !== void 0) {
5386
+ value = String((0, import_intent.extractValue)(role.default));
5387
+ }
5388
+ if (!value) continue;
5389
+ if (role.quoteMultiword && /\s/.test(value) && !/^(["']).*\1$/.test(value)) {
5390
+ value = `"${value}"`;
5391
+ }
5392
+ const markerText = role.renderOverride?.[language] ?? role.markerOverride?.[language] ?? role.renderOverride?.["*"] ?? tables.markers[role.role]?.[language];
5393
+ const markerPosition = role.markerPositionOverride?.[language] ?? role.markerPosition ?? tables.markerPositions[role.role]?.[language] ?? (isSOV ? "after" : "before");
5394
+ const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
5395
+ if (markerText && markerPosition === "before") {
5396
+ bucket.push(markerText, value);
5397
+ } else if (markerText) {
5398
+ bucket.push(value, markerText);
5399
+ } else {
5400
+ bucket.push(value);
5401
+ }
5402
+ }
5403
+ return isSOV ? buildPhrase(...preVerb, keyword, ...postVerb) : buildPhrase(keyword, ...preVerb, ...postVerb);
5404
+ }
5405
+
6145
5406
  // src/generation/diagnostics.ts
6146
5407
  var import_intent7 = require("@lokascript/intent");
6147
5408
 
@@ -7996,7 +7257,6 @@ function accumulateIndented(statements, config) {
7996
7257
  NullProfileProvider,
7997
7258
  NumberExtractor,
7998
7259
  OperatorExtractor,
7999
- PatternMatcher,
8000
7260
  PatternTracker,
8001
7261
  PunctuationExtractor,
8002
7262
  StockValueReader,
@@ -8060,8 +7320,6 @@ function accumulateIndented(statements, config) {
8060
7320
  fromInterchangeNode,
8061
7321
  fromProtocolJSON,
8062
7322
  generateExamples,
8063
- generatePattern,
8064
- generatePatternVariants,
8065
7323
  generatePrompt,
8066
7324
  generateProtocolReference,
8067
7325
  getAllPossessiveKeywords,
@@ -8100,8 +7358,6 @@ function accumulateIndented(statements, config) {
8100
7358
  legacyMarkerAlternatives,
8101
7359
  lookupKeyword,
8102
7360
  lookupMarker,
8103
- matchBest,
8104
- matchPattern,
8105
7361
  needsDisambiguation,
8106
7362
  noChange,
8107
7363
  normalized,
@@ -8109,7 +7365,6 @@ function accumulateIndented(statements, config) {
8109
7365
  parseDocument,
8110
7366
  parseExplicit,
8111
7367
  parseJSONL,
8112
- patternMatcher,
8113
7368
  readGreedy,
8114
7369
  readRoleValue,
8115
7370
  registryToAOTBackends,