@lokascript/framework 4.5.0 → 4.7.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 (123) hide show
  1. package/CHANGELOG.md +220 -8
  2. package/dist/api/create-dsl.d.ts +59 -1
  3. package/dist/api/create-dsl.d.ts.map +1 -1
  4. package/dist/api/domain-registry.d.ts.map +1 -1
  5. package/dist/api/index.d.ts +1 -0
  6. package/dist/api/index.d.ts.map +1 -1
  7. package/dist/api/index.js +1938 -724
  8. package/dist/api/index.js.map +1 -1
  9. package/dist/api/reading.d.ts +8 -0
  10. package/dist/api/reading.d.ts.map +1 -0
  11. package/dist/api/translation-loss.d.ts +84 -0
  12. package/dist/api/translation-loss.d.ts.map +1 -0
  13. package/dist/core/index.d.ts +1 -0
  14. package/dist/core/index.d.ts.map +1 -1
  15. package/dist/core/index.js +1047 -684
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/core/matching/confidence-model.d.ts +54 -0
  18. package/dist/core/matching/confidence-model.d.ts.map +1 -0
  19. package/dist/core/matching/index.d.ts +12 -0
  20. package/dist/core/matching/index.d.ts.map +1 -0
  21. package/dist/core/matching/match-skeleton.d.ts +114 -0
  22. package/dist/core/matching/match-skeleton.d.ts.map +1 -0
  23. package/dist/core/matching/stock-confidence.d.ts +15 -0
  24. package/dist/core/matching/stock-confidence.d.ts.map +1 -0
  25. package/dist/core/matching/stock-reader.d.ts +56 -0
  26. package/dist/core/matching/stock-reader.d.ts.map +1 -0
  27. package/dist/core/matching/stock-reading.d.ts +44 -0
  28. package/dist/core/matching/stock-reading.d.ts.map +1 -0
  29. package/dist/core/matching/value-reader.d.ts +62 -0
  30. package/dist/core/matching/value-reader.d.ts.map +1 -0
  31. package/dist/core/pattern-matching/index.js +561 -660
  32. package/dist/core/pattern-matching/index.js.map +1 -1
  33. package/dist/core/pattern-matching/pattern-matcher.d.ts +0 -117
  34. package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
  35. package/dist/core/types.d.ts +48 -0
  36. package/dist/core/types.d.ts.map +1 -1
  37. package/dist/core/types.js.map +1 -1
  38. package/dist/generation/index.d.ts +2 -0
  39. package/dist/generation/index.d.ts.map +1 -1
  40. package/dist/generation/index.js +583 -1
  41. package/dist/generation/index.js.map +1 -1
  42. package/dist/generation/pattern-render.d.ts +72 -0
  43. package/dist/generation/pattern-render.d.ts.map +1 -0
  44. package/dist/generation/schema-patterns.d.ts +152 -0
  45. package/dist/generation/schema-patterns.d.ts.map +1 -0
  46. package/dist/index.cjs +2132 -890
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.js +2110 -887
  49. package/dist/index.js.map +1 -1
  50. package/dist/ir/index.js.map +1 -1
  51. package/dist/parsing/index.js +561 -660
  52. package/dist/parsing/index.js.map +1 -1
  53. package/dist/testing/gate/index.d.ts +8 -0
  54. package/dist/testing/gate/index.d.ts.map +1 -0
  55. package/dist/testing/gate/index.js +632 -0
  56. package/dist/testing/gate/index.js.map +1 -0
  57. package/dist/testing/gate/lint/format.d.ts +7 -0
  58. package/dist/testing/gate/lint/format.d.ts.map +1 -0
  59. package/dist/testing/gate/lint/index.d.ts +30 -0
  60. package/dist/testing/gate/lint/index.d.ts.map +1 -0
  61. package/dist/testing/gate/lint/rules/extractor-coverage.d.ts +18 -0
  62. package/dist/testing/gate/lint/rules/extractor-coverage.d.ts.map +1 -0
  63. package/dist/testing/gate/lint/rules/index.d.ts +14 -0
  64. package/dist/testing/gate/lint/rules/index.d.ts.map +1 -0
  65. package/dist/testing/gate/lint/rules/keyword-classification.d.ts +19 -0
  66. package/dist/testing/gate/lint/rules/keyword-classification.d.ts.map +1 -0
  67. package/dist/testing/gate/lint/rules/keyword-coverage.d.ts +13 -0
  68. package/dist/testing/gate/lint/rules/keyword-coverage.d.ts.map +1 -0
  69. package/dist/testing/gate/lint/rules/marker-tokenization.d.ts +20 -0
  70. package/dist/testing/gate/lint/rules/marker-tokenization.d.ts.map +1 -0
  71. package/dist/testing/gate/lint/rules/position-ordering.d.ts +25 -0
  72. package/dist/testing/gate/lint/rules/position-ordering.d.ts.map +1 -0
  73. package/dist/testing/gate/lint/rules/renderer-coherence.d.ts +24 -0
  74. package/dist/testing/gate/lint/rules/renderer-coherence.d.ts.map +1 -0
  75. package/dist/testing/gate/lint/rules/schema-rules.d.ts +10 -0
  76. package/dist/testing/gate/lint/rules/schema-rules.d.ts.map +1 -0
  77. package/dist/testing/gate/lint/types.d.ts +74 -0
  78. package/dist/testing/gate/lint/types.d.ts.map +1 -0
  79. package/dist/testing/gate/round-trip.d.ts +163 -0
  80. package/dist/testing/gate/round-trip.d.ts.map +1 -0
  81. package/package.json +7 -3
  82. package/src/__test__/fixtures/sql-dsl.ts +11 -2
  83. package/src/__test__/sql-integration.test.ts +2 -2
  84. package/src/api/create-dsl-engine.test.ts +86 -0
  85. package/src/api/create-dsl.test.ts +120 -0
  86. package/src/api/create-dsl.ts +314 -39
  87. package/src/api/domain-registry.test.ts +70 -5
  88. package/src/api/domain-registry.ts +82 -1
  89. package/src/api/index.ts +1 -0
  90. package/src/api/reading.ts +29 -0
  91. package/src/api/translation-loss.test.ts +89 -0
  92. package/src/api/translation-loss.ts +128 -0
  93. package/src/core/index.ts +1 -0
  94. package/src/core/matching/confidence-model.ts +58 -0
  95. package/src/core/matching/index.ts +12 -0
  96. package/src/core/matching/match-skeleton.test.ts +179 -0
  97. package/src/core/matching/match-skeleton.ts +621 -0
  98. package/src/core/matching/stock-confidence.ts +238 -0
  99. package/src/core/matching/stock-reader.test.ts +181 -0
  100. package/src/core/matching/stock-reader.ts +186 -0
  101. package/src/core/matching/stock-reading.ts +681 -0
  102. package/src/core/matching/value-reader.ts +85 -0
  103. package/src/core/pattern-matching/pattern-matcher.ts +11 -876
  104. package/src/core/types.ts +48 -0
  105. package/src/generation/index.ts +2 -0
  106. package/src/generation/pattern-render.ts +378 -0
  107. package/src/generation/schema-patterns.test.ts +99 -0
  108. package/src/generation/schema-patterns.ts +962 -0
  109. package/src/testing/gate/index.ts +8 -0
  110. package/src/testing/gate/lint/format.ts +23 -0
  111. package/src/testing/gate/lint/index.ts +73 -0
  112. package/src/testing/gate/lint/lint.test.ts +131 -0
  113. package/src/testing/gate/lint/rules/extractor-coverage.ts +63 -0
  114. package/src/testing/gate/lint/rules/index.ts +37 -0
  115. package/src/testing/gate/lint/rules/keyword-classification.ts +52 -0
  116. package/src/testing/gate/lint/rules/keyword-coverage.ts +38 -0
  117. package/src/testing/gate/lint/rules/marker-tokenization.ts +129 -0
  118. package/src/testing/gate/lint/rules/position-ordering.ts +87 -0
  119. package/src/testing/gate/lint/rules/renderer-coherence.ts +100 -0
  120. package/src/testing/gate/lint/rules/schema-rules.ts +35 -0
  121. package/src/testing/gate/lint/types.ts +83 -0
  122. package/src/testing/gate/round-trip.test.ts +118 -0
  123. package/src/testing/gate/round-trip.ts +438 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @lokascript/framework/testing/gate — the round-trip gate kit and the grammar linter
3
+ * (domains rethink Phase 4.6). No test runner needed: a grammar's gate test or regen
4
+ * tool calls `runRoundTrip` and `compare`, its lint test `lintDomain`.
5
+ */
6
+ export * from './round-trip';
7
+ export * from './lint';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/testing/gate/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC"}
@@ -0,0 +1,632 @@
1
+ // src/testing/gate/round-trip.ts
2
+ var RANK = {
3
+ roundTrip: { pass: 2, refused: 1, silent: 0 },
4
+ leftover: { refused: 2, pass: 2, silent: 0 }
5
+ };
6
+ var BEST = { roundTrip: "pass", leftover: "refused" };
7
+ var UNKNOWN_WORD = "zzqx";
8
+ function defaultValue(role, i) {
9
+ const types = role.expectedTypes ?? [];
10
+ if (types.includes("selector") && !types.includes("literal") && !types.includes("expression"))
11
+ return `#box${i}`;
12
+ return `v${i}x`;
13
+ }
14
+ function valuesFor(fixture, action, role, i) {
15
+ return fixture.values[`${action}.${role.role}`] ?? fixture.values[role.role] ?? [defaultValue(role, i)];
16
+ }
17
+ function commandsFor(schemas, fixture) {
18
+ const out = /* @__PURE__ */ new Set();
19
+ for (const schema of schemas) {
20
+ const required = schema.roles.filter((r) => r.required);
21
+ const variants = required.length === schema.roles.length ? [required] : [required, [...schema.roles]];
22
+ for (const roles of variants) {
23
+ const values = roles.map((r, i) => valuesFor(fixture, schema.action, r, i));
24
+ const write = (pick) => `[${schema.action}${roles.map((r, i) => ` ${r.role}:${pick[i]}`).join("")}]`;
25
+ const first = values.map((v) => v[0]);
26
+ out.add(write(first));
27
+ values.forEach((vs, i) => {
28
+ for (const v of vs.slice(1)) out.add(write(first.map((f, j) => j === i ? v : f)));
29
+ });
30
+ }
31
+ }
32
+ return [...out];
33
+ }
34
+ function roleEntries(node) {
35
+ return node.roles instanceof Map ? [...node.roles] : Object.entries(node.roles ?? {});
36
+ }
37
+ function roleValues(node) {
38
+ return Object.fromEntries(
39
+ roleEntries(node).map(([k, v]) => [
40
+ k,
41
+ String(v?.value ?? v?.raw ?? v)
42
+ ]).sort(([a], [b]) => a < b ? -1 : 1)
43
+ );
44
+ }
45
+ function summarize(node) {
46
+ const roles = roleEntries(node).map(([k, v]) => {
47
+ const t = v?.type;
48
+ const raw = v?.value ?? v?.raw ?? v;
49
+ const text = String(raw);
50
+ return `${k}${t ? `:${String(t)}` : ""}=${/[\s=]/.test(text) ? JSON.stringify(text) : text}`;
51
+ }).sort();
52
+ return [node.action, ...roles].join(" ");
53
+ }
54
+ function describeRead(want, ref, back) {
55
+ if (back.action !== ref.action) return `another command: ${back.action}`;
56
+ const got = roleValues(back);
57
+ const lost = Object.keys(want).filter((k) => !(k in got));
58
+ const added = Object.keys(got).filter((k) => !(k in want));
59
+ const changed = Object.keys(want).filter((k) => k in got && got[k] !== want[k]);
60
+ const parts = [
61
+ ...lost.map((k) => `${k} lost`),
62
+ ...added.map((k) => `${k} added (${got[k]})`),
63
+ ...changed.map((k) => `${k}: ${got[k]}`)
64
+ ];
65
+ return parts.join(", ");
66
+ }
67
+ var message = (e) => String(e?.message ?? e).slice(0, 160);
68
+ function referenceCode(dsl, source) {
69
+ try {
70
+ const c = dsl.compile(source, "en");
71
+ return c.ok && typeof c.code === "string" ? c.code : null;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function runRoundTrip(grammar, dsl, schemas, fixture) {
77
+ const languages = dsl.getSupportedLanguages();
78
+ const results = [];
79
+ for (const source of commandsFor(schemas, fixture)) {
80
+ const ref = dsl.parse(source, "en");
81
+ const want = roleValues(ref);
82
+ const sameNode = (back) => back.action === ref.action && JSON.stringify(roleValues(back)) === JSON.stringify(want);
83
+ const refCode = fixture.oracle === "compile" ? referenceCode(dsl, source) : null;
84
+ for (const lang of languages) {
85
+ const at = { grammar, source, lang };
86
+ const text = dsl.render?.(ref, lang) ?? null;
87
+ if (!text) {
88
+ results.push({ ...at, kind: "roundTrip", outcome: "refused", why: "no render" });
89
+ continue;
90
+ }
91
+ let back;
92
+ try {
93
+ back = dsl.parse(text, lang);
94
+ } catch (e) {
95
+ results.push({ ...at, kind: "roundTrip", outcome: "refused", text, why: message(e) });
96
+ continue;
97
+ }
98
+ let outcome = "silent";
99
+ let why;
100
+ if (sameNode(back)) outcome = "pass";
101
+ else if (refCode !== null) {
102
+ let code;
103
+ try {
104
+ const c = dsl.compile(text, lang);
105
+ code = c.ok ? c.code : void 0;
106
+ } catch {
107
+ code = void 0;
108
+ }
109
+ if (code === refCode) outcome = "pass";
110
+ else why = `compiles differently; reads ${describeRead(want, ref, back)}`;
111
+ } else why = `reads ${describeRead(want, ref, back)}`;
112
+ results.push({
113
+ ...at,
114
+ kind: "roundTrip",
115
+ outcome,
116
+ text,
117
+ ...why ? { why } : {},
118
+ read: summarize(back)
119
+ });
120
+ const extended = `${text} ${UNKNOWN_WORD}`;
121
+ try {
122
+ const read = dsl.parse(extended, lang);
123
+ results.push({
124
+ ...at,
125
+ kind: "leftover",
126
+ outcome: "silent",
127
+ text: extended,
128
+ why: `accepted as ${read.action}`
129
+ });
130
+ } catch {
131
+ results.push({ ...at, kind: "leftover", outcome: "refused", text: extended });
132
+ }
133
+ }
134
+ }
135
+ return results;
136
+ }
137
+ function listedOutcome(baseline, r) {
138
+ if (r.kind === "roundTrip")
139
+ return baseline.roundTrip[r.grammar]?.[r.source]?.[r.lang]?.outcome ?? "pass";
140
+ return baseline.leftover[r.grammar]?.[r.source]?.includes(r.lang) ? "silent" : "refused";
141
+ }
142
+ function compare(grammar, results, baseline) {
143
+ const drift = { worse: [], better: [], stale: [] };
144
+ const seen = /* @__PURE__ */ new Set();
145
+ for (const r of results) {
146
+ seen.add(`${r.kind}\0${r.source}\0${r.lang}`);
147
+ const listed = listedOutcome(baseline, r);
148
+ const rank = RANK[r.kind];
149
+ const entry = { kind: r.kind, source: r.source, lang: r.lang, listed, now: r.outcome };
150
+ if (rank[r.outcome] < rank[listed])
151
+ drift.worse.push({
152
+ ...entry,
153
+ ...r.text !== void 0 ? { text: r.text } : {},
154
+ ...r.why !== void 0 ? { why: r.why } : {}
155
+ });
156
+ else if (rank[r.outcome] > rank[listed]) drift.better.push(entry);
157
+ }
158
+ const listedCases = [
159
+ ...Object.entries(baseline.roundTrip[grammar] ?? {}).flatMap(
160
+ ([source, langs]) => Object.keys(langs).map((lang) => ({ kind: "roundTrip", source, lang }))
161
+ ),
162
+ ...Object.entries(baseline.leftover[grammar] ?? {}).flatMap(
163
+ ([source, langs]) => langs.map((lang) => ({ kind: "leftover", source, lang }))
164
+ )
165
+ ];
166
+ for (const c of listedCases)
167
+ if (!seen.has(`${c.kind}\0${c.source}\0${c.lang}`)) drift.stale.push(c);
168
+ return drift;
169
+ }
170
+ function listingOf(results) {
171
+ var _a, _b;
172
+ const roundTrip = {};
173
+ const leftover = {};
174
+ for (const r of results) {
175
+ if (r.kind === "roundTrip" && r.outcome !== "pass") {
176
+ (roundTrip[_a = r.source] ?? (roundTrip[_a] = {}))[r.lang] = {
177
+ outcome: r.outcome,
178
+ ...r.text ? { text: r.text } : {},
179
+ ...r.why ? { why: r.why } : {}
180
+ };
181
+ } else if (r.kind === "leftover" && r.outcome === "silent")
182
+ (leftover[_b = r.source] ?? (leftover[_b] = [])).push(r.lang);
183
+ }
184
+ return { roundTrip, leftover };
185
+ }
186
+ function countsOf(results) {
187
+ const counts = {
188
+ roundTrip: { cases: 0, pass: 0, refused: 0, silent: 0 },
189
+ leftover: { cases: 0, pass: 0, refused: 0, silent: 0 }
190
+ };
191
+ for (const r of results) {
192
+ counts[r.kind].cases++;
193
+ counts[r.kind][r.outcome]++;
194
+ }
195
+ return counts;
196
+ }
197
+ function snapshotOf(results) {
198
+ var _a;
199
+ const out = {};
200
+ for (const r of results)
201
+ if (r.kind === "roundTrip") (out[_a = r.source] ?? (out[_a] = {}))[r.lang] = r.read ?? "refused";
202
+ return out;
203
+ }
204
+ function snapshotChanges(grammar, results, snapshot) {
205
+ const now = snapshotOf(results);
206
+ const was = snapshot.parses[grammar] ?? {};
207
+ const changes = [];
208
+ for (const source of /* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(now)]))
209
+ for (const lang of /* @__PURE__ */ new Set([
210
+ ...Object.keys(was[source] ?? {}),
211
+ ...Object.keys(now[source] ?? {})
212
+ ])) {
213
+ const a = was[source]?.[lang];
214
+ const b = now[source]?.[lang];
215
+ if (a !== b)
216
+ changes.push({
217
+ source,
218
+ lang,
219
+ ...a !== void 0 ? { was: a } : {},
220
+ ...b !== void 0 ? { now: b } : {}
221
+ });
222
+ }
223
+ return changes;
224
+ }
225
+
226
+ // src/testing/gate/lint/rules/schema-rules.ts
227
+ var RULE_ID = "schema-structure";
228
+ var schemaStructureRule = (input) => {
229
+ const findings = [];
230
+ if (!input.validateSchema) return findings;
231
+ for (const schema of input.schemas) {
232
+ const result = input.validateSchema(schema);
233
+ for (const item of result.items) {
234
+ if (item.severity === "note") continue;
235
+ findings.push({
236
+ rule: `${RULE_ID}:${item.code}`,
237
+ severity: item.severity,
238
+ message: `[${schema.action}] ${item.message}`,
239
+ context: {
240
+ domain: input.name,
241
+ action: String(schema.action),
242
+ code: item.code
243
+ }
244
+ });
245
+ }
246
+ }
247
+ return findings;
248
+ };
249
+
250
+ // src/testing/gate/lint/rules/keyword-coverage.ts
251
+ var RULE_ID2 = "keyword-coverage";
252
+ var keywordCoverageRule = (input) => {
253
+ const findings = [];
254
+ for (const profile of input.profiles) {
255
+ for (const schema of input.schemas) {
256
+ const action = schema.action;
257
+ if (!profile.keywords[action]) {
258
+ findings.push({
259
+ rule: RULE_ID2,
260
+ severity: "error",
261
+ message: `profile [${profile.code}] has no keyword for action '${String(action)}'`,
262
+ context: {
263
+ domain: input.name,
264
+ lang: profile.code,
265
+ action: String(action)
266
+ }
267
+ });
268
+ }
269
+ }
270
+ }
271
+ return findings;
272
+ };
273
+
274
+ // src/testing/gate/lint/rules/marker-tokenization.ts
275
+ var RULE_ID3 = "marker-tokenization";
276
+ function tokenCount(tokenizer, word) {
277
+ const stream = tokenizer.tokenize(word);
278
+ return stream.tokens.length;
279
+ }
280
+ function collectLexicalItems(input) {
281
+ const items = [];
282
+ for (const schema of input.schemas) {
283
+ for (const role of schema.roles) {
284
+ if (!role.markerOverride) continue;
285
+ for (const [lang, word] of Object.entries(role.markerOverride)) {
286
+ items.push({
287
+ lang,
288
+ word,
289
+ source: `schema markerOverride ${String(schema.action)}.${role.role}`
290
+ });
291
+ }
292
+ }
293
+ }
294
+ for (const profile of input.profiles) {
295
+ for (const [action, entry] of Object.entries(profile.keywords)) {
296
+ if (entry.primary) {
297
+ items.push({
298
+ lang: profile.code,
299
+ word: entry.primary,
300
+ source: `profile keyword ${action}.primary`
301
+ });
302
+ }
303
+ for (const alt of entry.alternatives ?? []) {
304
+ items.push({
305
+ lang: profile.code,
306
+ word: alt,
307
+ source: `profile keyword ${action}.alternatives`
308
+ });
309
+ }
310
+ }
311
+ }
312
+ for (const profile of input.profiles) {
313
+ for (const [role, marker] of Object.entries(profile.roleMarkers ?? {})) {
314
+ if (marker.primary) {
315
+ items.push({
316
+ lang: profile.code,
317
+ word: marker.primary,
318
+ source: `profile roleMarker ${role}.primary`
319
+ });
320
+ }
321
+ for (const alt of marker.alternatives ?? []) {
322
+ items.push({
323
+ lang: profile.code,
324
+ word: alt,
325
+ source: `profile roleMarker ${role}.alternatives`
326
+ });
327
+ }
328
+ }
329
+ }
330
+ return items;
331
+ }
332
+ var markerTokenizationRule = (input) => {
333
+ const findings = [];
334
+ const items = collectLexicalItems(input);
335
+ for (const { lang, word, source } of items) {
336
+ const tokenizer = input.tokenizers[lang];
337
+ if (!tokenizer) {
338
+ continue;
339
+ }
340
+ if (word === "") continue;
341
+ if (/\s/.test(word)) continue;
342
+ const count = tokenCount(tokenizer, word);
343
+ if (count !== 1) {
344
+ findings.push({
345
+ rule: RULE_ID3,
346
+ severity: "error",
347
+ message: `"${word}" tokenizes as ${count} tokens in ${lang} (${source})`,
348
+ context: {
349
+ domain: input.name,
350
+ lang,
351
+ word,
352
+ source,
353
+ tokenCount: count
354
+ }
355
+ });
356
+ }
357
+ }
358
+ return findings;
359
+ };
360
+
361
+ // src/testing/gate/lint/rules/keyword-classification.ts
362
+ var RULE_ID4 = "keyword-classification";
363
+ var keywordClassificationRule = (input) => {
364
+ const findings = [];
365
+ for (const { lang, word, source } of collectLexicalItems(input)) {
366
+ const tokenizer = input.tokenizers[lang];
367
+ if (!tokenizer) continue;
368
+ if (word === "" || /\s/.test(word)) continue;
369
+ const kind = tokenizer.classifyToken(word);
370
+ if (kind !== "keyword" && kind !== "particle") {
371
+ findings.push({
372
+ rule: RULE_ID4,
373
+ severity: "error",
374
+ message: `"${word}" classifies as '${kind}' (not 'keyword') in ${lang} (${source})`,
375
+ context: {
376
+ domain: input.name,
377
+ lang,
378
+ word,
379
+ source,
380
+ kind
381
+ }
382
+ });
383
+ }
384
+ }
385
+ return findings;
386
+ };
387
+
388
+ // src/testing/gate/lint/rules/extractor-coverage.ts
389
+ var RULE_ID5 = "extractor-coverage";
390
+ var DIACRITIC_SAMPLES = {
391
+ es: "a\xF1adir",
392
+ // ñ
393
+ fr: "d\xE9j\xE0",
394
+ // é, à
395
+ pt: "cora\xE7\xE3o",
396
+ // ç, ã
397
+ de: "gr\xF6\xDFer",
398
+ // ö, ß
399
+ tr: "de\u011Fi\u015Ftir",
400
+ // ğ, ş
401
+ it: "per\xF2",
402
+ // ò
403
+ pl: "kocha\u0107",
404
+ // ć
405
+ cs: "p\u0159\xEDli\u0161"
406
+ // ř, í
407
+ // Not run for en, ja, ar, ko, zh, ru, hi, etc. — either pure ASCII
408
+ // (en) or non-Latin scripts where the Unicode extractor handles them.
409
+ };
410
+ var extractorCoverageRule = (input) => {
411
+ const findings = [];
412
+ for (const [lang, sample] of Object.entries(DIACRITIC_SAMPLES)) {
413
+ const tokenizer = input.tokenizers[lang];
414
+ if (!tokenizer) continue;
415
+ const stream = tokenizer.tokenize(sample);
416
+ if (stream.tokens.length !== 1) {
417
+ findings.push({
418
+ rule: RULE_ID5,
419
+ severity: "warning",
420
+ message: `[${lang}] diacritic probe "${sample}" tokenizes as ${stream.tokens.length} tokens \u2014 tokenizer likely missing LatinExtendedIdentifierExtractor`,
421
+ context: {
422
+ domain: input.name,
423
+ lang,
424
+ sample,
425
+ tokenCount: stream.tokens.length
426
+ }
427
+ });
428
+ }
429
+ }
430
+ return findings;
431
+ };
432
+
433
+ // src/testing/gate/lint/rules/position-ordering.ts
434
+ var RULE_ID6 = "position-ordering";
435
+ function findCollisions(values) {
436
+ const byPosition = /* @__PURE__ */ new Map();
437
+ for (const { role, position } of values) {
438
+ const existing = byPosition.get(position) ?? [];
439
+ existing.push(role);
440
+ byPosition.set(position, existing);
441
+ }
442
+ return Array.from(byPosition.entries()).filter(([, roles]) => roles.length > 1).map(([position, roles]) => ({ position, roles }));
443
+ }
444
+ var positionOrderingRule = (input) => {
445
+ const findings = [];
446
+ for (const schema of input.schemas) {
447
+ if (schema.roles.length < 2) continue;
448
+ const svoValues = schema.roles.filter((r) => typeof r.svoPosition === "number" && r.svoPosition !== 0).map((r) => ({ role: r.role, position: r.svoPosition }));
449
+ const sovValues = schema.roles.filter((r) => typeof r.sovPosition === "number" && r.sovPosition !== 0).map((r) => ({ role: r.role, position: r.sovPosition }));
450
+ for (const { position, roles } of findCollisions(svoValues)) {
451
+ findings.push({
452
+ rule: RULE_ID6,
453
+ severity: "warning",
454
+ message: `[${String(schema.action)}] roles ${roles.join(", ")} share svoPosition=${position} \u2014 order is undefined`,
455
+ context: {
456
+ domain: input.name,
457
+ action: String(schema.action),
458
+ wordOrder: "SVO",
459
+ position,
460
+ roles: roles.join(",")
461
+ }
462
+ });
463
+ }
464
+ for (const { position, roles } of findCollisions(sovValues)) {
465
+ findings.push({
466
+ rule: RULE_ID6,
467
+ severity: "warning",
468
+ message: `[${String(schema.action)}] roles ${roles.join(", ")} share sovPosition=${position} \u2014 order is undefined`,
469
+ context: {
470
+ domain: input.name,
471
+ action: String(schema.action),
472
+ wordOrder: "SOV",
473
+ position,
474
+ roles: roles.join(",")
475
+ }
476
+ });
477
+ }
478
+ }
479
+ return findings;
480
+ };
481
+
482
+ // src/testing/gate/lint/rules/renderer-coherence.ts
483
+ var RULE_ID7 = "renderer-coherence";
484
+ var rendererCoherenceRule = (input) => {
485
+ if (!input.renderer) return [];
486
+ const findings = [];
487
+ for (const [action, langMap] of Object.entries(input.renderer.commandKeywords)) {
488
+ for (const [lang, rendererWord] of Object.entries(langMap)) {
489
+ const profile = input.profiles.find((p) => p.code === lang);
490
+ if (!profile) continue;
491
+ const profileEntry = profile.keywords[action];
492
+ if (!profileEntry) {
493
+ findings.push({
494
+ rule: RULE_ID7,
495
+ severity: "warning",
496
+ message: `renderer has '${action}' keyword for ${lang} ("${rendererWord}") but profile has no entry for that action`,
497
+ context: { domain: input.name, lang, action, rendererWord }
498
+ });
499
+ continue;
500
+ }
501
+ const profilePrimary = profileEntry.primary;
502
+ const profileAlts = profileEntry.alternatives ?? [];
503
+ if (rendererWord !== profilePrimary && !profileAlts.includes(rendererWord)) {
504
+ findings.push({
505
+ rule: RULE_ID7,
506
+ severity: "warning",
507
+ message: `renderer '${action}' keyword for ${lang} is "${rendererWord}" but profile has primary="${profilePrimary}"${profileAlts.length ? ` (alternatives: ${profileAlts.join(", ")})` : ""}`,
508
+ context: {
509
+ domain: input.name,
510
+ lang,
511
+ action,
512
+ rendererWord,
513
+ profilePrimary
514
+ }
515
+ });
516
+ }
517
+ }
518
+ }
519
+ const markerByLang = /* @__PURE__ */ new Map();
520
+ for (const schema of input.schemas) {
521
+ for (const role of schema.roles) {
522
+ if (!role.markerOverride) continue;
523
+ for (const [lang, word] of Object.entries(role.markerOverride)) {
524
+ const set = markerByLang.get(lang) ?? /* @__PURE__ */ new Set();
525
+ set.add(word);
526
+ markerByLang.set(lang, set);
527
+ }
528
+ }
529
+ }
530
+ for (const [markerName, langMap] of Object.entries(input.renderer.markers)) {
531
+ for (const [lang, rendererWord] of Object.entries(langMap)) {
532
+ const schemaMarkers = markerByLang.get(lang);
533
+ if (!schemaMarkers) continue;
534
+ if (!schemaMarkers.has(rendererWord)) {
535
+ findings.push({
536
+ rule: RULE_ID7,
537
+ severity: "warning",
538
+ message: `renderer marker "${markerName}" in ${lang} is "${rendererWord}" but no schema role has that markerOverride value \u2014 likely drift`,
539
+ context: {
540
+ domain: input.name,
541
+ lang,
542
+ markerName,
543
+ rendererWord
544
+ }
545
+ });
546
+ }
547
+ }
548
+ }
549
+ return findings;
550
+ };
551
+
552
+ // src/testing/gate/lint/rules/index.ts
553
+ var ALL_RULES = [
554
+ schemaStructureRule,
555
+ keywordCoverageRule,
556
+ keywordClassificationRule,
557
+ markerTokenizationRule,
558
+ extractorCoverageRule,
559
+ positionOrderingRule,
560
+ rendererCoherenceRule
561
+ ];
562
+ function runRules(input) {
563
+ const findings = [];
564
+ for (const rule of ALL_RULES) {
565
+ findings.push(...rule(input));
566
+ }
567
+ return findings;
568
+ }
569
+
570
+ // src/testing/gate/lint/format.ts
571
+ var SEVERITY_GLYPH = {
572
+ error: "\u2717",
573
+ warning: "\u26A0",
574
+ note: "\u2139"
575
+ };
576
+ function formatFinding(finding) {
577
+ const glyph = SEVERITY_GLYPH[finding.severity];
578
+ return ` ${glyph} [${finding.rule}] ${finding.message}`;
579
+ }
580
+ function formatResult(result) {
581
+ const header = `domain-toolkit: lint(${result.domain}) \u2014 ${result.errorCount} error(s), ${result.warningCount} warning(s)`;
582
+ if (result.findings.length === 0) return `${header}
583
+ (clean)`;
584
+ const body = result.findings.map(formatFinding).join("\n");
585
+ return `${header}
586
+ ${body}`;
587
+ }
588
+
589
+ // src/testing/gate/lint/index.ts
590
+ function matchesWaiver(finding, waiver) {
591
+ if (finding.rule !== waiver.rule) return false;
592
+ if (!waiver.matches) return true;
593
+ const ctx = finding.context ?? {};
594
+ for (const [key, expected] of Object.entries(waiver.matches)) {
595
+ if (ctx[key] !== expected) return false;
596
+ }
597
+ return true;
598
+ }
599
+ function applyWaivers(findings, waivers) {
600
+ if (!waivers || waivers.length === 0) return findings;
601
+ return findings.filter((f) => !waivers.some((w) => matchesWaiver(f, w)));
602
+ }
603
+ function lintDomain(input) {
604
+ const raw = runRules(input);
605
+ const findings = applyWaivers(raw, input.waivers);
606
+ const errorCount = findings.filter((f) => f.severity === "error").length;
607
+ const warningCount = findings.filter((f) => f.severity === "warning").length;
608
+ return {
609
+ domain: input.name,
610
+ findings,
611
+ errorCount,
612
+ warningCount
613
+ };
614
+ }
615
+ export {
616
+ BEST,
617
+ RANK,
618
+ UNKNOWN_WORD,
619
+ commandsFor,
620
+ compare,
621
+ countsOf,
622
+ formatFinding,
623
+ formatResult,
624
+ lintDomain,
625
+ listingOf,
626
+ referenceCode,
627
+ runRoundTrip,
628
+ snapshotChanges,
629
+ snapshotOf,
630
+ summarize
631
+ };
632
+ //# sourceMappingURL=index.js.map