@hyperfixi/core 2.8.0 → 2.9.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.
@@ -3756,6 +3756,9 @@ function isQuote(char) {
3756
3756
  function isDigit(char) {
3757
3757
  return /\d/.test(char);
3758
3758
  }
3759
+ function stripOptionalDiacritics(word) {
3760
+ return word.replace(/[ً-ْٰ]/g, "");
3761
+ }
3759
3762
  function isAsciiLetter(char) {
3760
3763
  return /[a-zA-Z]/.test(char);
3761
3764
  }
@@ -4427,7 +4430,7 @@ var _BaseTokenizer = class _BaseTokenizer {
4427
4430
  * @returns Word without diacritics
4428
4431
  */
4429
4432
  removeDiacritics(word) {
4430
- return word.replace(/[\u064B-\u0652\u0670]/g, "");
4433
+ return stripOptionalDiacritics(word);
4431
4434
  }
4432
4435
  /**
4433
4436
  * Try to match a keyword from profile at the current position.
@@ -4518,24 +4521,40 @@ var _BaseTokenizer = class _BaseTokenizer {
4518
4521
  });
4519
4522
  }
4520
4523
  /**
4521
- * Look up a keyword by native word (case-insensitive).
4524
+ * Look up a keyword by native word (case-insensitive, diacritic-insensitive).
4522
4525
  * O(1) lookup using the keyword map.
4523
4526
  *
4527
+ * The map is INDEXED both with and without diacritics (see
4528
+ * `initializeKeywordsFromProfile`), so a stripped QUERY is the other half of
4529
+ * that: it lets a surface form carrying harakat the profile does not happen to
4530
+ * spell still find its entry. Only consulted after the exact lookup misses, so
4531
+ * every previously-matching word resolves byte-identically.
4532
+ *
4533
+ * Half-implementing this — indexing stripped but querying exact — is what made
4534
+ * diacritized `بَدِّل` (toggle) tokenize as `kind=particle normalized=with`:
4535
+ * `isKeyword` returned false, so the guard in `ArabicProcliticExtractor` that
4536
+ * exists to prevent exactly that handed the word on, and the single-char `ب`
4537
+ * bi- proclitic claimed it. A wrong CONCEPT, not a failed parse.
4538
+ *
4524
4539
  * @param native - Native word to look up
4525
4540
  * @returns KeywordEntry if found, undefined otherwise
4526
4541
  */
4527
4542
  lookupKeyword(native) {
4528
- return this.profileKeywordMap.get(native.toLowerCase());
4543
+ const exact = this.profileKeywordMap.get(native.toLowerCase());
4544
+ if (exact) return exact;
4545
+ const stripped = this.removeDiacritics(native);
4546
+ if (stripped === native) return void 0;
4547
+ return this.profileKeywordMap.get(stripped.toLowerCase());
4529
4548
  }
4530
4549
  /**
4531
- * Check if a word is a known keyword (case-insensitive).
4532
- * O(1) lookup using the keyword map.
4550
+ * Check if a word is a known keyword (case-insensitive, diacritic-insensitive).
4551
+ * O(1) lookup using the keyword map. See {@link lookupKeyword}.
4533
4552
  *
4534
4553
  * @param native - Native word to check
4535
4554
  * @returns true if the word is a keyword
4536
4555
  */
4537
4556
  isKeyword(native) {
4538
- return this.profileKeywordMap.has(native.toLowerCase());
4557
+ return this.lookupKeyword(native) !== void 0;
4539
4558
  }
4540
4559
  /**
4541
4560
  * Set the morphological normalizer for this tokenizer.
@@ -5183,9 +5202,12 @@ var init_arabic = __esm({
5183
5202
  behavior: { primary: "\u0633\u0644\u0648\u0643", normalized: "behavior" },
5184
5203
  install: { primary: "\u062A\u062B\u0628\u064A\u062A", alternatives: ["\u062B\u0628\u0651\u062A"], normalized: "install" },
5185
5204
  // `قِس` is the imperative with the kasra diacritic; the i18n dict (and real
5186
- // Arabic prose) emits it undiacritized as `قس`, so list both — otherwise the
5187
- // generated `قس width`/`قس x` (behavior-draggable/resizable) parse to null and
5188
- // the whole `measure` command drops from the event-handler body (lossy).
5205
+ // Arabic prose) emits it undiacritized as `قس`. BOTH stay listed, and not
5206
+ // for the tokenizer's sake — keyword lookup is diacritic-insensitive now, so
5207
+ // either spelling resolves. It is the vocab gate's V1 check, which compares
5208
+ // the profile against the i18n DICTIONARY as strings: the dictionary says
5209
+ // `قس`, so dropping it here fails V1 (verified). Diacritic-insensitivity
5210
+ // would have to reach that comparison too before this pair can collapse.
5189
5211
  measure: { primary: "\u0642\u064A\u0627\u0633", alternatives: ["\u0642\u0650\u0633", "\u0642\u0633"], normalized: "measure" },
5190
5212
  beep: { primary: "\u0635\u0641\u0651\u0631", normalized: "beep" },
5191
5213
  break: { primary: "\u062A\u0648\u0642\u0641", normalized: "break" },
@@ -5869,6 +5891,15 @@ var init_spanish = __esm({
5869
5891
  patient: { primary: "", position: "before" },
5870
5892
  style: { primary: "con", position: "before" }
5871
5893
  },
5894
+ // Imperative command forms are accepted on INPUT only — `primary` stays the
5895
+ // dictionary form, so rendering is unchanged (see LanguageProfile.defaultVerbForm:
5896
+ // infinitive is the industry standard for UI localization). Hyperscript is a
5897
+ // command language, though, and a native speaker giving a command writes the
5898
+ // imperative, so the parser should read it.
5899
+ //
5900
+ // Only the IRREGULARS are listed. The regular ones reach their keyword through
5901
+ // the morphological normalizer's stem (see spanish-keyword.ts and siblings),
5902
+ // which also covers conjugations nobody enumerated here.
5872
5903
  keywords: {
5873
5904
  // Class/Attribute operations
5874
5905
  toggle: { primary: "alternar", alternatives: ["conmutar", "toggle"], normalized: "toggle" },
@@ -5888,19 +5919,23 @@ var init_spanish = __esm({
5888
5919
  swap: { primary: "intercambiar", alternatives: ["permutar"], normalized: "swap" },
5889
5920
  morph: { primary: "transformar", alternatives: ["convertir"], normalized: "morph" },
5890
5921
  // Variable operations
5891
- set: { primary: "establecer", alternatives: ["fijar", "definir"], normalized: "set" },
5892
- get: { primary: "obtener", alternatives: ["conseguir"], normalized: "get" },
5922
+ set: {
5923
+ primary: "establecer",
5924
+ alternatives: ["fijar", "definir", "establece"],
5925
+ normalized: "set"
5926
+ },
5927
+ get: { primary: "obtener", alternatives: ["conseguir", "obt\xE9n"], normalized: "get" },
5893
5928
  increment: { primary: "incrementar", alternatives: ["aumentar"], normalized: "increment" },
5894
5929
  decrement: { primary: "decrementar", alternatives: ["disminuir"], normalized: "decrement" },
5895
5930
  log: { primary: "registrar", alternatives: ["imprimir"], normalized: "log" },
5896
5931
  // Visibility
5897
- show: { primary: "mostrar", alternatives: ["ense\xF1ar"], normalized: "show" },
5932
+ show: { primary: "mostrar", alternatives: ["ense\xF1ar", "muestra"], normalized: "show" },
5898
5933
  hide: { primary: "ocultar", alternatives: ["esconder"], normalized: "hide" },
5899
5934
  transition: { primary: "transici\xF3n", alternatives: ["animar"], normalized: "transition" },
5900
5935
  // Events
5901
5936
  on: { primary: "en", alternatives: ["al"], normalized: "on" },
5902
5937
  trigger: { primary: "disparar", alternatives: ["activar"], normalized: "trigger" },
5903
- send: { primary: "enviar", normalized: "send" },
5938
+ send: { primary: "enviar", alternatives: ["env\xEDa"], normalized: "send" },
5904
5939
  // DOM focus
5905
5940
  focus: { primary: "enfocar", alternatives: ["enfoque"], normalized: "focus" },
5906
5941
  blur: { primary: "desenfocar", alternatives: ["desenfoque"], normalized: "blur" },
@@ -5937,7 +5972,7 @@ var init_spanish = __esm({
5937
5972
  mousedown: { primary: "rat\xF3nabajo", normalized: "mousedown" },
5938
5973
  mouseup: { primary: "rat\xF3narriba", normalized: "mouseup" },
5939
5974
  // Navigation
5940
- go: { primary: "ir", alternatives: ["navegar"], normalized: "go" },
5975
+ go: { primary: "ir", alternatives: ["navegar", "ve"], normalized: "go" },
5941
5976
  push: { primary: "empujar", alternatives: ["push"], normalized: "push" },
5942
5977
  replace: { primary: "reemplazar", alternatives: ["sustituir"], normalized: "replace" },
5943
5978
  process: { primary: "procesar", normalized: "process" },
@@ -6126,11 +6161,24 @@ var init_french = __esm({
6126
6161
  patient: { primary: "", position: "before" },
6127
6162
  style: { primary: "avec", position: "before" }
6128
6163
  },
6164
+ // Imperative command forms are accepted on INPUT only — `primary` stays the
6165
+ // dictionary form, so rendering is unchanged (see LanguageProfile.defaultVerbForm:
6166
+ // infinitive is the industry standard for UI localization). Hyperscript is a
6167
+ // command language, though, and a native speaker giving a command writes the
6168
+ // imperative, so the parser should read it.
6169
+ //
6170
+ // Only the IRREGULARS are listed. The regular ones reach their keyword through
6171
+ // the morphological normalizer's stem (see spanish-keyword.ts and siblings),
6172
+ // which also covers conjugations nobody enumerated here.
6129
6173
  keywords: {
6130
6174
  toggle: { primary: "basculer", alternatives: ["alterner"], normalized: "toggle" },
6131
6175
  add: { primary: "ajouter", normalized: "add" },
6132
- remove: { primary: "supprimer", alternatives: ["enlever", "retirer"], normalized: "remove" },
6133
- put: { primary: "mettre", alternatives: ["placer"], normalized: "put" },
6176
+ remove: {
6177
+ primary: "supprimer",
6178
+ alternatives: ["enlever", "retirer", "retire"],
6179
+ normalized: "remove"
6180
+ },
6181
+ put: { primary: "mettre", alternatives: ["placer", "mets"], normalized: "put" },
6134
6182
  append: { primary: "annexer", normalized: "append" },
6135
6183
  prepend: { primary: "pr\xE9fixer", normalized: "prepend" },
6136
6184
  take: { primary: "prendre", normalized: "take" },
@@ -6139,16 +6187,16 @@ var init_french = __esm({
6139
6187
  swap: { primary: "\xE9changer", alternatives: ["permuter"], normalized: "swap" },
6140
6188
  morph: { primary: "transformer", alternatives: ["m\xE9tamorphoser"], normalized: "morph" },
6141
6189
  set: { primary: "d\xE9finir", alternatives: ["\xE9tablir"], normalized: "set" },
6142
- get: { primary: "obtenir", normalized: "get" },
6190
+ get: { primary: "obtenir", alternatives: ["obtiens"], normalized: "get" },
6143
6191
  increment: { primary: "incr\xE9menter", alternatives: ["augmenter"], normalized: "increment" },
6144
6192
  decrement: { primary: "d\xE9cr\xE9menter", alternatives: ["diminuer"], normalized: "decrement" },
6145
6193
  log: { primary: "enregistrer", alternatives: ["journaliser"], normalized: "log" },
6146
- show: { primary: "montrer", alternatives: ["afficher"], normalized: "show" },
6194
+ show: { primary: "montrer", alternatives: ["afficher", "montre"], normalized: "show" },
6147
6195
  hide: { primary: "cacher", alternatives: ["masquer"], normalized: "hide" },
6148
6196
  transition: { primary: "transition", alternatives: ["animer"], normalized: "transition" },
6149
6197
  on: { primary: "sur", alternatives: ["lors"], normalized: "on" },
6150
6198
  trigger: { primary: "d\xE9clencher", normalized: "trigger" },
6151
- send: { primary: "envoyer", normalized: "send" },
6199
+ send: { primary: "envoyer", alternatives: ["envoie"], normalized: "send" },
6152
6200
  focus: { primary: "focaliser", alternatives: ["concentrer"], normalized: "focus" },
6153
6201
  blur: { primary: "d\xE9focaliser", normalized: "blur" },
6154
6202
  // Phase 1 (v0.9.90): DOM / form state / debug
@@ -6162,13 +6210,13 @@ var init_french = __esm({
6162
6210
  clear: { primary: "effacer", normalized: "clear" },
6163
6211
  reset: { primary: "r\xE9initialiser", alternatives: ["reinitialiser"], normalized: "reset" },
6164
6212
  breakpoint: { primary: "point-arr\xEAt", alternatives: ["point-arret"], normalized: "breakpoint" },
6165
- go: { primary: "aller", alternatives: ["naviguer"], normalized: "go" },
6213
+ go: { primary: "aller", alternatives: ["naviguer", "va"], normalized: "go" },
6166
6214
  scroll: { primary: "d\xE9filer", alternatives: ["faire-d\xE9filer"], normalized: "scroll" },
6167
6215
  push: { primary: "pousser", normalized: "push" },
6168
6216
  replace: { primary: "remplacer", normalized: "replace" },
6169
6217
  process: { primary: "traiter", normalized: "process" },
6170
6218
  wait: { primary: "attendre", normalized: "wait" },
6171
- fetch: { primary: "chercher", alternatives: ["r\xE9cup\xE9rer"], normalized: "fetch" },
6219
+ fetch: { primary: "chercher", alternatives: ["r\xE9cup\xE9rer", "r\xE9cup\xE8re"], normalized: "fetch" },
6172
6220
  settle: { primary: "stabiliser", normalized: "settle" },
6173
6221
  if: { primary: "si", normalized: "if" },
6174
6222
  unless: { primary: "saufsi", normalized: "unless" },
@@ -7539,16 +7587,25 @@ var init_korean = __esm({
7539
7587
  event: { primary: "\uC744", alternatives: ["\uB97C"], position: "after" }
7540
7588
  // Event as object marker
7541
7589
  },
7590
+ // Imperative command forms are accepted on INPUT only — `primary` stays the
7591
+ // dictionary form, so rendering is unchanged (see LanguageProfile.defaultVerbForm:
7592
+ // infinitive is the industry standard for UI localization). Hyperscript is a
7593
+ // command language, though, and a native speaker giving a command writes the
7594
+ // imperative, so the parser should read it.
7595
+ //
7596
+ // Only the IRREGULARS are listed. The regular ones reach their keyword through
7597
+ // the morphological normalizer's stem (see spanish-keyword.ts and siblings),
7598
+ // which also covers conjugations nobody enumerated here.
7542
7599
  keywords: {
7543
7600
  // Class/Attribute operations
7544
7601
  toggle: { primary: "\uD1A0\uAE00", normalized: "toggle" },
7545
7602
  add: { primary: "\uCD94\uAC00", normalized: "add" },
7546
7603
  remove: { primary: "\uC81C\uAC70", alternatives: ["\uC0AD\uC81C"], normalized: "remove" },
7547
7604
  // Content operations
7548
- put: { primary: "\uB123\uB2E4", alternatives: ["\uB123\uAE30", "\uB193\uAE30"], normalized: "put" },
7605
+ put: { primary: "\uB123\uB2E4", alternatives: ["\uB123\uAE30", "\uB193\uAE30", "\uB123\uC73C\uC138\uC694"], normalized: "put" },
7549
7606
  append: { primary: "\uB367\uBD99\uC774\uB2E4", alternatives: ["\uB05D\uC5D0\uCD94\uAC00"], normalized: "append" },
7550
7607
  prepend: { primary: "\uC55E\uC5D0\uCD94\uAC00", alternatives: ["\uC120\uB450\uCD94\uAC00"], normalized: "prepend" },
7551
- take: { primary: "\uAC00\uC838\uC624\uB2E4", normalized: "take" },
7608
+ take: { primary: "\uAC00\uC838\uC624\uB2E4", alternatives: ["\uAC00\uC838\uC624\uC138\uC694"], normalized: "take" },
7552
7609
  make: { primary: "\uB9CC\uB4E4\uB2E4", normalized: "make" },
7553
7610
  clone: { primary: "\uBCF5\uC81C", normalized: "clone" },
7554
7611
  // 복제=duplicate/clone, 복사=copy
@@ -7556,13 +7613,13 @@ var init_korean = __esm({
7556
7613
  morph: { primary: "\uBCC0\uD615", alternatives: ["\uBCC0\uD658"], normalized: "morph" },
7557
7614
  // Variable operations
7558
7615
  set: { primary: "\uC124\uC815", normalized: "set" },
7559
- get: { primary: "\uC5BB\uB2E4", normalized: "get" },
7616
+ get: { primary: "\uC5BB\uB2E4", alternatives: ["\uC5BB\uC73C\uC138\uC694"], normalized: "get" },
7560
7617
  increment: { primary: "\uC99D\uAC00", normalized: "increment" },
7561
7618
  decrement: { primary: "\uAC10\uC18C", normalized: "decrement" },
7562
7619
  log: { primary: "\uB85C\uADF8", normalized: "log" },
7563
7620
  // Visibility
7564
- show: { primary: "\uBCF4\uC774\uB2E4", alternatives: ["\uD45C\uC2DC", "\uBCF4\uC774\uAE30"], normalized: "show" },
7565
- hide: { primary: "\uC228\uAE30\uB2E4", alternatives: ["\uC228\uAE30\uAE30"], normalized: "hide" },
7621
+ show: { primary: "\uBCF4\uC774\uB2E4", alternatives: ["\uD45C\uC2DC", "\uBCF4\uC774\uAE30", "\uBCF4\uC774\uC138\uC694"], normalized: "show" },
7622
+ hide: { primary: "\uC228\uAE30\uB2E4", alternatives: ["\uC228\uAE30\uAE30", "\uC228\uAE30\uC138\uC694"], normalized: "hide" },
7566
7623
  // primary is the loanword 트랜지션; 전환 ("switch/transition") is the form the
7567
7624
  // i18n transformer emits — registered as an alternative (passthrough-alignment).
7568
7625
  // toggle uses 토글, so 전환 carries no collision.
@@ -7570,7 +7627,7 @@ var init_korean = __esm({
7570
7627
  // Events
7571
7628
  on: { primary: "\uC5D0", alternatives: ["\uC2DC", "\uD560 \uB54C"], normalized: "on" },
7572
7629
  trigger: { primary: "\uD2B8\uB9AC\uAC70", normalized: "trigger" },
7573
- send: { primary: "\uBCF4\uB0B4\uB2E4", normalized: "send" },
7630
+ send: { primary: "\uBCF4\uB0B4\uB2E4", alternatives: ["\uBCF4\uB0B4\uC138\uC694"], normalized: "send" },
7574
7631
  // DOM focus
7575
7632
  focus: { primary: "\uD3EC\uCEE4\uC2A4", normalized: "focus" },
7576
7633
  blur: { primary: "\uBE14\uB7EC", normalized: "blur" },
@@ -8352,25 +8409,38 @@ var init_portuguese = __esm({
8352
8409
  patient: { primary: "", position: "before" },
8353
8410
  style: { primary: "com", position: "before" }
8354
8411
  },
8412
+ // Imperative command forms are accepted on INPUT only — `primary` stays the
8413
+ // dictionary form, so rendering is unchanged (see LanguageProfile.defaultVerbForm:
8414
+ // infinitive is the industry standard for UI localization). Hyperscript is a
8415
+ // command language, though, and a native speaker giving a command writes the
8416
+ // imperative, so the parser should read it.
8417
+ //
8418
+ // Only the IRREGULARS are listed. The regular ones reach their keyword through
8419
+ // the morphological normalizer's stem (see spanish-keyword.ts and siblings),
8420
+ // which also covers conjugations nobody enumerated here.
8355
8421
  keywords: {
8356
8422
  toggle: { primary: "alternar", alternatives: [], normalized: "toggle" },
8357
8423
  add: { primary: "adicionar", alternatives: ["acrescentar"], normalized: "add" },
8358
- remove: { primary: "remover", alternatives: ["eliminar", "apagar"], normalized: "remove" },
8359
- put: { primary: "colocar", alternatives: ["p\xF4r", "por"], normalized: "put" },
8424
+ remove: {
8425
+ primary: "remover",
8426
+ alternatives: ["eliminar", "apagar", "remova"],
8427
+ normalized: "remove"
8428
+ },
8429
+ put: { primary: "colocar", alternatives: ["p\xF4r", "por", "coloque"], normalized: "put" },
8360
8430
  append: { primary: "anexar", normalized: "append" },
8361
8431
  prepend: { primary: "preceder", normalized: "prepend" },
8362
- take: { primary: "pegar", normalized: "take" },
8432
+ take: { primary: "pegar", alternatives: ["pegue"], normalized: "take" },
8363
8433
  make: { primary: "fazer", alternatives: ["criar"], normalized: "make" },
8364
8434
  clone: { primary: "clonar", alternatives: [], normalized: "clone" },
8365
8435
  swap: { primary: "trocar", alternatives: ["substituir"], normalized: "swap" },
8366
8436
  morph: { primary: "transformar", alternatives: ["converter"], normalized: "morph" },
8367
- set: { primary: "definir", alternatives: ["configurar"], normalized: "set" },
8368
- get: { primary: "obter", normalized: "get" },
8437
+ set: { primary: "definir", alternatives: ["configurar", "defina"], normalized: "set" },
8438
+ get: { primary: "obter", alternatives: ["obtenha"], normalized: "get" },
8369
8439
  increment: { primary: "incrementar", alternatives: ["aumentar"], normalized: "increment" },
8370
8440
  decrement: { primary: "decrementar", alternatives: ["diminuir"], normalized: "decrement" },
8371
8441
  log: { primary: "registrar", alternatives: ["imprimir"], normalized: "log" },
8372
8442
  show: { primary: "mostrar", alternatives: ["exibir"], normalized: "show" },
8373
- hide: { primary: "ocultar", alternatives: ["esconder"], normalized: "hide" },
8443
+ hide: { primary: "ocultar", alternatives: ["esconder", "esconda"], normalized: "hide" },
8374
8444
  transition: { primary: "transi\xE7\xE3o", alternatives: ["animar"], normalized: "transition" },
8375
8445
  on: { primary: "em", alternatives: ["ao"], normalized: "on" },
8376
8446
  trigger: { primary: "disparar", alternatives: ["ativar"], normalized: "trigger" },
@@ -8389,13 +8459,13 @@ var init_portuguese = __esm({
8389
8459
  alternatives: ["ponto-interrupcao"],
8390
8460
  normalized: "breakpoint"
8391
8461
  },
8392
- go: { primary: "ir", alternatives: ["navegar"], normalized: "go" },
8462
+ go: { primary: "ir", alternatives: ["navegar", "v\xE1"], normalized: "go" },
8393
8463
  scroll: { primary: "rolar", alternatives: ["scroll"], normalized: "scroll" },
8394
8464
  push: { primary: "empurrar", alternatives: ["push"], normalized: "push" },
8395
8465
  replace: { primary: "repor", alternatives: ["recolocar"], normalized: "replace" },
8396
8466
  process: { primary: "processar", normalized: "process" },
8397
8467
  wait: { primary: "esperar", alternatives: ["aguardar"], normalized: "wait" },
8398
- fetch: { primary: "buscar", normalized: "fetch" },
8468
+ fetch: { primary: "buscar", alternatives: ["busque"], normalized: "fetch" },
8399
8469
  settle: { primary: "estabilizar", normalized: "settle" },
8400
8470
  if: { primary: "se", normalized: "if" },
8401
8471
  // salvo — single token ('salvo se' = unless). a_menos kept as an
@@ -11466,8 +11536,53 @@ var init_command_schemas = __esm({
11466
11536
  default: { type: "reference", value: "me" },
11467
11537
  svoPosition: 2,
11468
11538
  sovPosition: 1,
11469
- markerOverride: { en: "to" }
11470
- // "add .class to #element"
11539
+ // `add` is directional, but every profile's `destination` marker is
11540
+ // LOCATIVE (en on, es en, ar على, zh 在, fr sur, de auf, pt em) because
11541
+ // it also serves `toggle`/`show`. Without a per-language override the
11542
+ // rendered text said "add .class ON #element" in every language but
11543
+ // English — the gap lokascript-learn corrects with 6 of its 16 override
11544
+ // entries. ja に / ko 에 / tr e are already directional, so they keep
11545
+ // the profile default.
11546
+ //
11547
+ // Tier B (2.9): he/id/it/sw were the remaining locatives that this
11548
+ // language actually distinguishes.
11549
+ // he — `על` is "ON"; Hebrew adds with the allative `אל` (`ל` is a bound
11550
+ // prefix, so it cannot stand as a separate marker token).
11551
+ // id — `pada` is "at/on"; `ke` is the directional, and it is what the
11552
+ // i18n corpus already renders for every id destination.
11553
+ // it — `in` is locative; Italian adds with `a` (`aggiungere a`).
11554
+ // sw — `kwenye` is not merely locative, it is sw's EVENT keyword
11555
+ // (`on: 'kwenye'` in the dictionary), so reusing it as a
11556
+ // destination marker collides. `kwa` is the corpus rendering.
11557
+ // hi `में` / ru+uk `в` / th `ใน` / vi `vào` are already the right
11558
+ // container-directional for "add to", and keep the profile default.
11559
+ markerOverride: {
11560
+ en: "to",
11561
+ es: "a",
11562
+ ar: "\u0625\u0644\u0649",
11563
+ zh: "\u5230",
11564
+ fr: "\xE0",
11565
+ de: "zu",
11566
+ pt: "a",
11567
+ he: "\u05D0\u05DC",
11568
+ id: "ke",
11569
+ it: "a",
11570
+ sw: "kwa"
11571
+ },
11572
+ // Each language's previous primary marker (and its alternates) still
11573
+ // parses, so source written against ≤2.8 keeps working.
11574
+ markerLegacy: {
11575
+ es: ["en", "sobre", "hacia"],
11576
+ ar: ["\u0639\u0644\u0649", "\u0641\u064A", "\u0628"],
11577
+ zh: ["\u5728", "\u4E8E"],
11578
+ fr: ["sur", "dans"],
11579
+ de: ["auf", "in"],
11580
+ pt: ["em", "para"],
11581
+ he: ["\u05E2\u05DC", "\u05D1", "\u05DC"],
11582
+ id: ["pada", "di"],
11583
+ it: ["in", "su"],
11584
+ sw: ["kwenye"]
11585
+ }
11471
11586
  }
11472
11587
  ],
11473
11588
  // Runtime error documentation
@@ -11557,12 +11672,39 @@ var init_command_schemas = __esm({
11557
11672
  svoPosition: 2,
11558
11673
  sovPosition: 2,
11559
11674
  // SOV: destination comes second (に/에/a marker)
11560
- markerOverride: { en: "into" },
11561
- // "put 'hello' into #output"
11675
+ // "put 'hello' into #output" — directional, so the same locative-default
11676
+ // correction as `add`. es `en` and pt `em` are already right for "into",
11677
+ // as are ja に / ko 에 / tr e; only ar/zh/fr/de need an override.
11678
+ //
11679
+ // Tier B (2.9): `put` is ILLATIVE, so it diverges from `add` where the
11680
+ // two senses differ. he takes `ב` ("in/into" — `שים ב`), NOT the allative
11681
+ // `אל` that `add`/`go` take. it keeps its locative `in` (`mettere in`) —
11682
+ // it is `add`/`go` that needed `a`. id/sw change for the same reason as
11683
+ // `add` (directional / event-keyword collision). hi `में`, ru+uk `в`,
11684
+ // th `ใน` and vi `vào` are all already the illative.
11685
+ markerOverride: {
11686
+ en: "into",
11687
+ ar: "\u0641\u064A",
11688
+ zh: "\u5230",
11689
+ fr: "dans",
11690
+ de: "in",
11691
+ he: "\u05D1",
11692
+ id: "ke",
11693
+ sw: "kwa"
11694
+ },
11562
11695
  // `before` / `after` are alternate position markers; the matched marker
11563
11696
  // is recorded as a literal in the `method` role (a derived role with no
11564
11697
  // surface form of its own — populated by schema-driven role inference).
11565
11698
  markerVariants: { en: ["before", "after"] },
11699
+ markerLegacy: {
11700
+ ar: ["\u0639\u0644\u0649", "\u0625\u0644\u0649", "\u0628"],
11701
+ zh: ["\u5728", "\u4E8E"],
11702
+ fr: ["sur", "\xE0"],
11703
+ de: ["auf", "zu"],
11704
+ he: ["\u05E2\u05DC", "\u05D0\u05DC", "\u05DC"],
11705
+ id: ["pada", "di"],
11706
+ sw: ["kwenye"]
11707
+ },
11566
11708
  methodCarrier: "method"
11567
11709
  }
11568
11710
  ],
@@ -11697,8 +11839,11 @@ var init_command_schemas = __esm({
11697
11839
  // ending in a vowel (`doğru ya` = "true" in set-attribute). markerOverride
11698
11840
  // is a single string, so the generated tr set patterns carried only `e`
11699
11841
  // and set-attribute fell to the role-scrambling generic SOV extraction.
11700
- // markerVariants supplies the allomorphs the SOV two-role generators merge
11701
- // in as marker alternatives. See STRUCTURAL_ARCS_ROADMAP.md (tr set-attribute).
11842
+ // markerVariants supplies the allomorphs, merged in as marker alternatives.
11843
+ // Until 2026-07-25 only the SOV two-role generators merged them, so this
11844
+ // worked ONLY inside an event handler: `@disabled i doğru ya ayarla` did
11845
+ // not parse as a bare command while `tıklama da @disabled i doğru ya
11846
+ // ayarla` did. See STRUCTURAL_ARCS_ROADMAP.md (tr set-attribute).
11702
11847
  markerVariants: {
11703
11848
  tr: ["e", "a", "ye", "ya"]
11704
11849
  }
@@ -12630,17 +12775,100 @@ var init_command_schemas = __esm({
12630
12775
  expectedTypes: ["literal", "expression"],
12631
12776
  svoPosition: 1,
12632
12777
  sovPosition: 1,
12633
- markerOverride: { en: "to" },
12634
- // "go to /page" (parsing)
12635
- renderOverride: { en: "" },
12636
- // "go /page" (rendering — no preposition)
12778
+ // "go to /page" (parsing). Directional, so the same locative-default
12779
+ // correction as `add`/`put`.
12780
+ //
12781
+ // Tier B (2.9): `go` is pure ALLATIVE — motion toward a target — so it
12782
+ // needs the directional in more languages than `add`/`put` do, including
12783
+ // ones where a container-locative was fine for those two.
12784
+ // he — `אל` ("toward"), as `add`; `לך על url` read "go ON url".
12785
+ // hi — `पर`: Hindi navigates to a page with `पर जाएं`; `में` is
12786
+ // "go INTO", which is entering a place, not opening a URL.
12787
+ // id — `ke`, as `add`.
12788
+ // it — `a`: `andare a` for a specific target (`andare in` is for
12789
+ // regions — `andare in Italia`).
12790
+ // ru/uk — `на`: `перейти на сторінку` is the navigation idiom; `в`
12791
+ // ("into") is right for `add`/`put` but not for opening a page.
12792
+ // sw — `kwa`, as `add`.
12793
+ // th is NOT here — it renders bare, with zh and vi; see below.
12794
+ markerOverride: {
12795
+ en: "to",
12796
+ es: "a",
12797
+ ar: "\u0625\u0644\u0649",
12798
+ fr: "\xE0",
12799
+ de: "zu",
12800
+ pt: "para",
12801
+ he: "\u05D0\u05DC",
12802
+ hi: "\u092A\u0930",
12803
+ id: "ke",
12804
+ it: "a",
12805
+ ru: "\u043D\u0430",
12806
+ sw: "kwa",
12807
+ uk: "\u043D\u0430"
12808
+ },
12809
+ // "go /page" (rendering — no preposition).
12810
+ //
12811
+ // zh, vi and th render BARE.
12812
+ //
12813
+ // zh and vi because their `go` keyword already encodes the direction, so
12814
+ // any destination marker is a second one: zh `前往` is "proceed-to"
12815
+ // (`前往 到 url` = "proceed-to to url") and vi `đi đến` is literally
12816
+ // "go to" (`đi đến vào url` = "go-to into url"). Both are corrected in
12817
+ // the i18n corpus in the same change
12818
+ // (`patterns-reference/scripts/fix-translations.sql`).
12819
+ //
12820
+ // th because Thai motion verbs take a BARE destination — `ไปบ้าน`
12821
+ // ("go home"), `ไปโรงเรียน` ("go school") — so `ไป url` is the idiomatic
12822
+ // form. The profile default rendered `ไป ใน url` ("go IN url"), which is
12823
+ // what needed fixing; the obvious replacement `ยัง` (giving the formal
12824
+ // `ไปยัง`) is rejected because `ยัง` is also the very common adverb
12825
+ // "still/yet", and the V4 vocab gate correctly refuses to classify it as
12826
+ // a particle — promoting it would mis-tokenize ordinary Thai.
12827
+ //
12828
+ // Parsing is unaffected for all three: none has a `markerOverride`, so
12829
+ // each stays on the profile-default branch and keeps accepting its old
12830
+ // markers (th `ใน` / `ไปยัง`) from the profile itself.
12831
+ renderOverride: { en: "", zh: "", vi: "", th: "" },
12637
12832
  // `go back` renders the destination bare in en (history nav has no `to`),
12638
12833
  // and he/zh render it with their PATIENT marker (לך את back / 前往 把 back)
12639
12834
  // while go-url keeps the destination marker (לך על url / 前往 到 url) —
12640
12835
  // the corpus is ground truth, so en's `to` is optional and he/zh accept
12641
12836
  // the patient particle as a destination-marker alternative, scoped to go.
12642
- markerOptional: { en: true },
12643
- markerVariants: { he: ["\u05D0\u05EA"], zh: ["\u628A"] }
12837
+ // The render side drops the preposition for these four, so the parse
12838
+ // side cannot require it: `go /page`, `前往 url`, `đi đến url`, `ไป url`
12839
+ // must parse alongside the marked forms the profile still accepts.
12840
+ markerOptional: { en: true, zh: true, vi: true, th: true },
12841
+ // zh renders `前往 把 back` with its PATIENT particle before go's
12842
+ // destination — a synonym here, not a distinct shape, so it is accepted as
12843
+ // a marker alternative scoped to go. he's `את` is the same thing and sits
12844
+ // in `markerLegacy` below: it moved there in #763 because the two fields
12845
+ // were then read by DIFFERENT branches, so leaving it here silently
12846
+ // stopped `לך את back` parsing the moment he gained a `markerOverride`.
12847
+ // Both fields now merge on both branches (`schemaMarkerAlternatives`), so
12848
+ // that trap is gone and the split is historical.
12849
+ markerVariants: { zh: ["\u628A"] },
12850
+ markerLegacy: {
12851
+ es: ["en", "sobre", "hacia"],
12852
+ ar: ["\u0639\u0644\u0649", "\u0641\u064A", "\u0628"],
12853
+ fr: ["sur", "dans"],
12854
+ de: ["auf", "in"],
12855
+ pt: ["em", "a"],
12856
+ // `את` is he's PATIENT particle, which the transformer renders before
12857
+ // go's destination in `go back` (`לך את back`) — a parse-only synonym
12858
+ // here, never rendered, which is exactly what markerLegacy is for.
12859
+ he: ["\u05E2\u05DC", "\u05D1", "\u05DC", "\u05D0\u05EA"],
12860
+ hi: ["\u092E\u0947\u0902"],
12861
+ id: ["pada", "di"],
12862
+ it: ["in", "su"],
12863
+ ru: ["\u0432", "\u043A"],
12864
+ sw: ["kwenye"],
12865
+ uk: ["\u0432", "\u0434\u043E"]
12866
+ // zh, vi and th are NOT listed: none has a markerOverride, so all three
12867
+ // stay on the profile-default branch and keep accepting their old
12868
+ // markers from the profile itself. Only their RENDERING changed.
12869
+ // Listing them here would be dead config — markerLegacy is read ONLY by
12870
+ // the override branch.
12871
+ }
12644
12872
  }
12645
12873
  ],
12646
12874
  // `go to url "/page"` — without this variant the destination captures the
@@ -13642,7 +13870,7 @@ var init_command_schemas = __esm({
13642
13870
  roles: []
13643
13871
  }
13644
13872
  };
13645
- if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
13873
+ if (typeof process !== "undefined" && process.env.LOKASCRIPT_SCHEMA_VALIDATION === "1") {
13646
13874
  Promise.resolve().then(() => (init_schema_validator(), schema_validator_exports)).then(({ validateAllSchemas: validateAllSchemas2, formatValidationResults: formatValidationResults2 }) => {
13647
13875
  const validations = validateAllSchemas2(commandSchemas);
13648
13876
  if (validations.size > 0) {
@@ -20199,12 +20427,16 @@ var init_spanish_keyword = __esm({
20199
20427
  const keywordEntry = this.context.lookupKeyword(word);
20200
20428
  const normalized2 = keywordEntry && keywordEntry.normalized !== keywordEntry.native ? keywordEntry.normalized : void 0;
20201
20429
  let morphNormalized;
20430
+ let morphStem;
20431
+ let morphConfidence;
20202
20432
  if (!keywordEntry && this.context.normalizer) {
20203
20433
  const morphResult = this.context.normalizer.normalize(word);
20204
20434
  if (morphResult.stem !== word && morphResult.confidence >= 0.7) {
20205
20435
  const stemEntry = this.context.lookupKeyword(morphResult.stem);
20206
20436
  if (stemEntry) {
20207
20437
  morphNormalized = stemEntry.normalized;
20438
+ morphStem = morphResult.stem;
20439
+ morphConfidence = morphResult.confidence;
20208
20440
  }
20209
20441
  }
20210
20442
  }
@@ -20213,6 +20445,8 @@ var init_spanish_keyword = __esm({
20213
20445
  length: pos2 - position,
20214
20446
  metadata: {
20215
20447
  normalized: normalized2 || morphNormalized,
20448
+ stem: morphStem,
20449
+ stemConfidence: morphConfidence,
20216
20450
  isPreposition
20217
20451
  }
20218
20452
  };
@@ -21332,12 +21566,16 @@ var init_portuguese_keyword = __esm({
21332
21566
  const keywordEntry = this.context.lookupKeyword(lower);
21333
21567
  const normalized2 = keywordEntry && keywordEntry.normalized !== keywordEntry.native ? keywordEntry.normalized : void 0;
21334
21568
  let morphNormalized;
21569
+ let morphStem;
21570
+ let morphConfidence;
21335
21571
  if (!keywordEntry && this.context.normalizer) {
21336
21572
  const morphResult = this.context.normalizer.normalize(word);
21337
21573
  if (morphResult.stem !== word && morphResult.confidence >= 0.7) {
21338
21574
  const stemEntry = this.context.lookupKeyword(morphResult.stem);
21339
21575
  if (stemEntry) {
21340
21576
  morphNormalized = stemEntry.normalized;
21577
+ morphStem = morphResult.stem;
21578
+ morphConfidence = morphResult.confidence;
21341
21579
  }
21342
21580
  }
21343
21581
  }
@@ -21346,6 +21584,8 @@ var init_portuguese_keyword = __esm({
21346
21584
  length: pos2 - position,
21347
21585
  metadata: {
21348
21586
  normalized: normalized2 || morphNormalized,
21587
+ stem: morphStem,
21588
+ stemConfidence: morphConfidence,
21349
21589
  isPreposition
21350
21590
  }
21351
21591
  };
@@ -21825,12 +22065,16 @@ var init_french_keyword = __esm({
21825
22065
  const keywordEntry = this.context.lookupKeyword(lower);
21826
22066
  const normalized2 = keywordEntry && keywordEntry.normalized !== keywordEntry.native ? keywordEntry.normalized : void 0;
21827
22067
  let morphNormalized;
22068
+ let morphStem;
22069
+ let morphConfidence;
21828
22070
  if (!keywordEntry && this.context.normalizer) {
21829
22071
  const morphResult = this.context.normalizer.normalize(word);
21830
22072
  if (morphResult.stem !== word && morphResult.confidence >= 0.7) {
21831
22073
  const stemEntry = this.context.lookupKeyword(morphResult.stem);
21832
22074
  if (stemEntry) {
21833
22075
  morphNormalized = stemEntry.normalized;
22076
+ morphStem = morphResult.stem;
22077
+ morphConfidence = morphResult.confidence;
21834
22078
  }
21835
22079
  }
21836
22080
  }
@@ -21839,6 +22083,8 @@ var init_french_keyword = __esm({
21839
22083
  length: pos2 - position,
21840
22084
  metadata: {
21841
22085
  normalized: normalized2 || morphNormalized,
22086
+ stem: morphStem,
22087
+ stemConfidence: morphConfidence,
21842
22088
  isPreposition
21843
22089
  }
21844
22090
  };
@@ -28421,8 +28667,10 @@ function resolveMarkerForRole(roleSpec, profile) {
28421
28667
  const overrideMarker = roleSpec.markerOverride?.[profile.code];
28422
28668
  const defaultMarker = profile.roleMarkers[roleSpec.role];
28423
28669
  if (overrideMarker !== void 0) {
28670
+ const alternatives = legacyMarkerAlternatives(roleSpec, profile.code, overrideMarker);
28424
28671
  return {
28425
28672
  primary: overrideMarker,
28673
+ ...alternatives && { alternatives },
28426
28674
  position: defaultMarker?.position ?? "before",
28427
28675
  isOverride: true
28428
28676
  };
@@ -28440,6 +28688,18 @@ function resolveMarkerForRole(roleSpec, profile) {
28440
28688
  }
28441
28689
  return null;
28442
28690
  }
28691
+ function legacyMarkerAlternatives(roleSpec, languageCode, overrideMarker) {
28692
+ const legacy = roleSpec.markerLegacy?.[languageCode];
28693
+ if (!legacy?.length) return void 0;
28694
+ const alternatives = [...new Set(legacy)].filter((a) => a && a !== overrideMarker);
28695
+ return alternatives.length ? alternatives : void 0;
28696
+ }
28697
+ function schemaMarkerAlternatives(roleSpec, languageCode, marker) {
28698
+ const legacy = roleSpec.markerLegacy?.[languageCode] ?? [];
28699
+ const variants = roleSpec.methodCarrier ? [] : roleSpec.markerVariants?.[languageCode] ?? [];
28700
+ const alternatives = [.../* @__PURE__ */ new Set([...legacy, ...variants])].filter((a) => a && a !== marker);
28701
+ return alternatives.length ? alternatives : void 0;
28702
+ }
28443
28703
  var init_marker_resolution = __esm({
28444
28704
  "src/parser/utils/marker-resolution.ts"() {
28445
28705
  }
@@ -28451,20 +28711,17 @@ function resolveRoleMarker(roleSpec, profile) {
28451
28711
  let alternatives;
28452
28712
  if (roleSpec.markerOverride && roleSpec.markerOverride[profile.code] !== void 0) {
28453
28713
  marker = roleSpec.markerOverride[profile.code];
28714
+ alternatives = marker ? schemaMarkerAlternatives(roleSpec, profile.code, marker) : void 0;
28454
28715
  } else {
28455
28716
  const roleMarker = profile.roleMarkers[roleSpec.role];
28456
28717
  if (roleMarker) {
28457
28718
  marker = roleMarker.primary;
28458
- alternatives = roleMarker.alternatives ? [...roleMarker.alternatives] : void 0;
28459
- }
28460
- }
28461
- const variants = roleSpec.markerVariants?.[profile.code];
28462
- if (variants && variants.length > 0) {
28463
- const merged = alternatives ? [...alternatives] : [];
28464
- for (const v of variants) {
28465
- if (v !== marker && !merged.includes(v)) merged.push(v);
28719
+ const schemaAlts = schemaMarkerAlternatives(roleSpec, profile.code, marker) ?? [];
28720
+ const merged = [.../* @__PURE__ */ new Set([...roleMarker.alternatives ?? [], ...schemaAlts])].filter(
28721
+ (a) => a !== marker
28722
+ );
28723
+ alternatives = merged.length ? merged : void 0;
28466
28724
  }
28467
- alternatives = merged;
28468
28725
  }
28469
28726
  return { marker, alternatives };
28470
28727
  }
@@ -28933,10 +29190,18 @@ function generateSOVTwoRoleDestFirstEventHandlerPattern(commandSchema, profile,
28933
29190
  var init_event_handlers_sov = __esm({
28934
29191
  "src/generators/event-handlers-sov.ts"() {
28935
29192
  init_command_schemas();
29193
+ init_marker_resolution();
28936
29194
  }
28937
29195
  });
28938
29196
 
28939
29197
  // src/generators/event-handlers-vso.ts
29198
+ function mergeSchemaAlternatives(roleSpec, profile, roleMarker) {
29199
+ const schemaAlts = schemaMarkerAlternatives(roleSpec, profile.code, roleMarker.primary) ?? [];
29200
+ const merged = [.../* @__PURE__ */ new Set([...roleMarker.alternatives ?? [], ...schemaAlts])].filter(
29201
+ (a) => a !== roleMarker.primary
29202
+ );
29203
+ return merged.length ? merged : void 0;
29204
+ }
28940
29205
  function generateVSOEventHandlerPattern(commandSchema, profile, keyword, eventMarker, config) {
28941
29206
  const tokens = [];
28942
29207
  if (eventMarker.position === "before") {
@@ -29049,11 +29314,12 @@ function generateVSOVerbFirstTwoRoleEventHandlerPattern(commandSchema, profile,
29049
29314
  let markerAlternatives;
29050
29315
  if (roleSpec.markerOverride && roleSpec.markerOverride[profile.code] !== void 0) {
29051
29316
  marker = roleSpec.markerOverride[profile.code];
29317
+ markerAlternatives = marker ? schemaMarkerAlternatives(roleSpec, profile.code, marker) : void 0;
29052
29318
  } else {
29053
29319
  const roleMarker = profile.roleMarkers[roleSpec.role];
29054
29320
  if (roleMarker) {
29055
29321
  marker = roleMarker.primary;
29056
- markerAlternatives = roleMarker.alternatives;
29322
+ markerAlternatives = mergeSchemaAlternatives(roleSpec, profile, roleMarker);
29057
29323
  }
29058
29324
  }
29059
29325
  if (marker) {
@@ -29106,11 +29372,12 @@ function generateVSOTwoRoleEventHandlerPattern(commandSchema, profile, keyword,
29106
29372
  let markerAlternatives;
29107
29373
  if (roleSpec.markerOverride && roleSpec.markerOverride[profile.code] !== void 0) {
29108
29374
  marker = roleSpec.markerOverride[profile.code];
29375
+ markerAlternatives = marker ? schemaMarkerAlternatives(roleSpec, profile.code, marker) : void 0;
29109
29376
  } else {
29110
29377
  const roleMarker = profile.roleMarkers[roleSpec.role];
29111
29378
  if (roleMarker) {
29112
29379
  marker = roleMarker.primary;
29113
- markerAlternatives = roleMarker.alternatives;
29380
+ markerAlternatives = mergeSchemaAlternatives(roleSpec, profile, roleMarker);
29114
29381
  }
29115
29382
  }
29116
29383
  if (marker) {
@@ -29243,6 +29510,7 @@ var init_event_handlers_vso = __esm({
29243
29510
  "src/generators/event-handlers-vso.ts"() {
29244
29511
  init_command_schemas();
29245
29512
  init_event_handlers_sov();
29513
+ init_marker_resolution();
29246
29514
  }
29247
29515
  });
29248
29516
  function generatePattern(schema, profile, config = defaultConfig) {
@@ -29615,7 +29883,13 @@ function buildRoleToken(roleSpec, profile) {
29615
29883
  const position = defaultMarker?.position ?? "before";
29616
29884
  const optionalMarker = roleSpec.markerOptional?.[profile.code] === true;
29617
29885
  const pushWord = (word) => {
29618
- const literal = suppressMarker ? { type: "literal", value: word, renderSuppress: true } : { type: "literal", value: word };
29886
+ const alternatives = markerWords.length === 1 ? schemaMarkerAlternatives(roleSpec, profile.code, word) ?? [] : [];
29887
+ const literal = {
29888
+ type: "literal",
29889
+ value: word,
29890
+ ...alternatives.length ? { alternatives } : {},
29891
+ ...suppressMarker ? { renderSuppress: true } : {}
29892
+ };
29619
29893
  tokens.push(optionalMarker ? { type: "group", optional: true, tokens: [literal] } : literal);
29620
29894
  };
29621
29895
  if (position === "before") {
@@ -29626,10 +29900,12 @@ function buildRoleToken(roleSpec, profile) {
29626
29900
  for (const word of markerWords) pushWord(word);
29627
29901
  }
29628
29902
  } else if (defaultMarker) {
29629
- const variantAlts = roleSpec.markerVariants?.[profile.code] ?? [];
29630
29903
  const asMarker = () => {
29631
29904
  const alternatives = [
29632
- .../* @__PURE__ */ new Set([...defaultMarker.alternatives ?? [], ...variantAlts])
29905
+ .../* @__PURE__ */ new Set([
29906
+ ...defaultMarker.alternatives ?? [],
29907
+ ...schemaMarkerAlternatives(roleSpec, profile.code, defaultMarker.primary) ?? []
29908
+ ])
29633
29909
  ].filter((a) => a !== defaultMarker.primary);
29634
29910
  return {
29635
29911
  type: "literal",
@@ -29665,11 +29941,19 @@ function buildExtractionRules(schema, profile) {
29665
29941
  if (roleSpec.valuePrefixLiteral?.[profile.code]) {
29666
29942
  rules[roleSpec.role] = { marker: roleSpec.valuePrefixLiteral[profile.code] };
29667
29943
  } else if (overrideMarker !== void 0) {
29668
- rules[roleSpec.role] = overrideMarker ? { marker: overrideMarker } : {};
29944
+ if (!overrideMarker) {
29945
+ rules[roleSpec.role] = {};
29946
+ } else {
29947
+ const isSingleWord = !/\s/.test(overrideMarker.trim());
29948
+ const markerAlternatives = isSingleWord ? schemaMarkerAlternatives(roleSpec, profile.code, overrideMarker) ?? [] : [];
29949
+ rules[roleSpec.role] = markerAlternatives.length ? { marker: overrideMarker, markerAlternatives } : { marker: overrideMarker };
29950
+ }
29669
29951
  } else if (defaultMarker && defaultMarker.primary) {
29670
- const variantAlts = roleSpec.markerVariants?.[profile.code] ?? [];
29671
29952
  const markerAlternatives = [
29672
- .../* @__PURE__ */ new Set([...defaultMarker.alternatives ?? [], ...variantAlts])
29953
+ .../* @__PURE__ */ new Set([
29954
+ ...defaultMarker.alternatives ?? [],
29955
+ ...schemaMarkerAlternatives(roleSpec, profile.code, defaultMarker.primary) ?? []
29956
+ ])
29673
29957
  ].filter((a) => a !== defaultMarker.primary);
29674
29958
  rules[roleSpec.role] = markerAlternatives.length ? { marker: defaultMarker.primary, markerAlternatives } : { marker: defaultMarker.primary };
29675
29959
  } else {
@@ -31675,6 +31959,9 @@ init_command_schemas();
31675
31959
  // src/utils/confidence-calculator.ts
31676
31960
  init_registry();
31677
31961
 
31962
+ // src/explicit/converter.ts
31963
+ init_registry();
31964
+
31678
31965
  // src/cache/semantic-cache.ts
31679
31966
  var SemanticCache = class {
31680
31967
  constructor(config = {}) {