@lokascript/framework 2.0.0 → 2.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 (126) hide show
  1. package/dist/aot/index.d.ts +1 -0
  2. package/dist/aot/index.d.ts.map +1 -1
  3. package/dist/aot/registry-bridge.d.ts +27 -0
  4. package/dist/aot/registry-bridge.d.ts.map +1 -0
  5. package/dist/api/create-dsl.d.ts.map +1 -1
  6. package/dist/api/dispatcher.d.ts +22 -0
  7. package/dist/api/dispatcher.d.ts.map +1 -1
  8. package/dist/api/domain-registry.d.ts +41 -0
  9. package/dist/api/domain-registry.d.ts.map +1 -1
  10. package/dist/api/index.js +1351 -21
  11. package/dist/api/index.js.map +1 -1
  12. package/dist/core/index.js +114 -10
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/pattern-matching/index.js +74 -10
  15. package/dist/core/pattern-matching/index.js.map +1 -1
  16. package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
  17. package/dist/core/types.d.ts +80 -2
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/core/types.js +40 -0
  20. package/dist/core/types.js.map +1 -1
  21. package/dist/feedback/confidence-gate.d.ts +28 -0
  22. package/dist/feedback/confidence-gate.d.ts.map +1 -0
  23. package/dist/feedback/feedback-formatter.d.ts +21 -0
  24. package/dist/feedback/feedback-formatter.d.ts.map +1 -0
  25. package/dist/feedback/index.d.ts +11 -0
  26. package/dist/feedback/index.d.ts.map +1 -0
  27. package/dist/feedback/pattern-tracker.d.ts +47 -0
  28. package/dist/feedback/pattern-tracker.d.ts.map +1 -0
  29. package/dist/feedback/types.d.ts +113 -0
  30. package/dist/feedback/types.d.ts.map +1 -0
  31. package/dist/generation/index.js +2 -1
  32. package/dist/generation/index.js.map +1 -1
  33. package/dist/index.cjs +2685 -71
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.ts +6 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2652 -71
  38. package/dist/index.js.map +1 -1
  39. package/dist/ir/explicit-parser.d.ts +31 -0
  40. package/dist/ir/explicit-parser.d.ts.map +1 -0
  41. package/dist/ir/explicit-renderer.d.ts +17 -0
  42. package/dist/ir/explicit-renderer.d.ts.map +1 -0
  43. package/dist/ir/from-interchange.d.ts +51 -0
  44. package/dist/ir/from-interchange.d.ts.map +1 -0
  45. package/dist/ir/index.d.ts +29 -0
  46. package/dist/ir/index.d.ts.map +1 -0
  47. package/dist/ir/index.js +1557 -0
  48. package/dist/ir/index.js.map +1 -0
  49. package/dist/ir/json-schema.d.ts +40 -0
  50. package/dist/ir/json-schema.d.ts.map +1 -0
  51. package/dist/ir/protocol-json.d.ts +73 -0
  52. package/dist/ir/protocol-json.d.ts.map +1 -0
  53. package/dist/ir/references.d.ts +19 -0
  54. package/dist/ir/references.d.ts.map +1 -0
  55. package/dist/ir/types.d.ts +141 -0
  56. package/dist/ir/types.d.ts.map +1 -0
  57. package/dist/parsing/index.js +74 -10
  58. package/dist/parsing/index.js.map +1 -1
  59. package/dist/prompts/index.d.ts +9 -0
  60. package/dist/prompts/index.d.ts.map +1 -0
  61. package/dist/prompts/prompt-generator.d.ts +49 -0
  62. package/dist/prompts/prompt-generator.d.ts.map +1 -0
  63. package/dist/prompts/types.d.ts +64 -0
  64. package/dist/prompts/types.d.ts.map +1 -0
  65. package/dist/schema/command-schema.d.ts +17 -0
  66. package/dist/schema/command-schema.d.ts.map +1 -1
  67. package/dist/schema/index.js.map +1 -1
  68. package/dist/testing/index.js +3 -0
  69. package/dist/testing/index.js.map +1 -1
  70. package/dist/training/index.d.ts +11 -0
  71. package/dist/training/index.d.ts.map +1 -0
  72. package/dist/training/jsonl-writer.d.ts +42 -0
  73. package/dist/training/jsonl-writer.d.ts.map +1 -0
  74. package/dist/training/schema-synthesizer.d.ts +20 -0
  75. package/dist/training/schema-synthesizer.d.ts.map +1 -0
  76. package/dist/training/types.d.ts +73 -0
  77. package/dist/training/types.d.ts.map +1 -0
  78. package/package.json +5 -1
  79. package/src/aot/index.ts +1 -0
  80. package/src/aot/registry-bridge.test.ts +104 -0
  81. package/src/aot/registry-bridge.ts +66 -0
  82. package/src/api/create-dsl.test.ts +306 -0
  83. package/src/api/create-dsl.ts +21 -0
  84. package/src/api/dispatcher.test.ts +66 -0
  85. package/src/api/dispatcher.ts +77 -9
  86. package/src/api/domain-registry.ts +120 -4
  87. package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
  88. package/src/core/pattern-matching/pattern-matcher.ts +102 -19
  89. package/src/core/types.ts +145 -2
  90. package/src/feedback/confidence-gate.test.ts +111 -0
  91. package/src/feedback/confidence-gate.ts +70 -0
  92. package/src/feedback/feedback-formatter.test.ts +198 -0
  93. package/src/feedback/feedback-formatter.ts +298 -0
  94. package/src/feedback/index.ts +24 -0
  95. package/src/feedback/pattern-tracker.test.ts +198 -0
  96. package/src/feedback/pattern-tracker.ts +140 -0
  97. package/src/feedback/types.ts +149 -0
  98. package/src/generation/pattern-generator.ts +2 -2
  99. package/src/index.ts +22 -0
  100. package/src/integration/llm-lse-integration.test.ts +457 -0
  101. package/src/ir/explicit-parser.test.ts +306 -0
  102. package/src/ir/explicit-parser.ts +419 -0
  103. package/src/ir/explicit-renderer.test.ts +144 -0
  104. package/src/ir/explicit-renderer.ts +146 -0
  105. package/src/ir/from-interchange.test.ts +853 -0
  106. package/src/ir/from-interchange.ts +594 -0
  107. package/src/ir/index.ts +66 -0
  108. package/src/ir/json-schema.test.ts +281 -0
  109. package/src/ir/json-schema.ts +299 -0
  110. package/src/ir/protocol-json.test.ts +1185 -0
  111. package/src/ir/protocol-json.ts +745 -0
  112. package/src/ir/references.test.ts +38 -0
  113. package/src/ir/references.ts +33 -0
  114. package/src/ir/round-trip.test.ts +557 -0
  115. package/src/ir/types.ts +181 -0
  116. package/src/prompts/index.ts +15 -0
  117. package/src/prompts/prompt-generator.test.ts +386 -0
  118. package/src/prompts/prompt-generator.ts +531 -0
  119. package/src/prompts/types.ts +89 -0
  120. package/src/schema/command-schema.ts +19 -0
  121. package/src/training/index.ts +13 -0
  122. package/src/training/jsonl-writer.test.ts +138 -0
  123. package/src/training/jsonl-writer.ts +67 -0
  124. package/src/training/schema-synthesizer.test.ts +250 -0
  125. package/src/training/schema-synthesizer.ts +306 -0
  126. package/src/training/types.ts +103 -0
@@ -0,0 +1,457 @@
1
+ /**
2
+ * End-to-End Integration Tests — LLM ↔ LSE Pipeline
3
+ *
4
+ * Tests the full integration of system prompts, training data,
5
+ * feedback loops, and registry convenience methods.
6
+ */
7
+
8
+ import { describe, it, expect, beforeAll } from 'vitest';
9
+ import {
10
+ DomainRegistry,
11
+ generatePrompt,
12
+ synthesizeFromSchemas,
13
+ toJSONL,
14
+ parseJSONL,
15
+ buildFeedback,
16
+ needsDisambiguation,
17
+ buildDisambiguation,
18
+ PatternTracker,
19
+ parseExplicit,
20
+ renderExplicit,
21
+ semanticNodeToJSON,
22
+ createCommandNode,
23
+ createSelector,
24
+ createLiteral,
25
+ createExpression,
26
+ defineCommand,
27
+ defineRole,
28
+ } from '../index';
29
+
30
+ // =============================================================================
31
+ // Test schemas (minimal — for fast, self-contained tests)
32
+ // =============================================================================
33
+
34
+ const testSchemas = [
35
+ defineCommand({
36
+ action: 'fetch',
37
+ description: 'Fetch data from a URL',
38
+ category: 'source',
39
+ primaryRole: 'source',
40
+ roles: [
41
+ defineRole({
42
+ role: 'source',
43
+ description: 'URL to fetch from',
44
+ required: true,
45
+ expectedTypes: ['expression'],
46
+ }),
47
+ defineRole({
48
+ role: 'style',
49
+ description: 'Response format',
50
+ required: false,
51
+ expectedTypes: ['expression'],
52
+ }),
53
+ defineRole({
54
+ role: 'destination',
55
+ description: 'Target element',
56
+ required: false,
57
+ expectedTypes: ['selector'],
58
+ }),
59
+ ],
60
+ }),
61
+ defineCommand({
62
+ action: 'toggle',
63
+ description: 'Toggle a class on an element',
64
+ category: 'mutation',
65
+ primaryRole: 'patient',
66
+ roles: [
67
+ defineRole({
68
+ role: 'patient',
69
+ description: 'CSS class or attribute to toggle',
70
+ required: true,
71
+ expectedTypes: ['selector'],
72
+ }),
73
+ defineRole({
74
+ role: 'destination',
75
+ description: 'Target element',
76
+ required: false,
77
+ expectedTypes: ['selector'],
78
+ }),
79
+ ],
80
+ }),
81
+ ];
82
+
83
+ // =============================================================================
84
+ // Pillar 1: System Prompt Generation → Parse round-trip
85
+ // =============================================================================
86
+
87
+ describe('Pillar 1: System Prompt Generation', () => {
88
+ it('generates prompt containing all commands', () => {
89
+ const prompt = generatePrompt({
90
+ domain: 'test',
91
+ description: 'Test domain',
92
+ schemas: testSchemas,
93
+ });
94
+
95
+ expect(prompt.text).toContain('fetch');
96
+ expect(prompt.text).toContain('toggle');
97
+ expect(prompt.metadata.commandCount).toBe(2);
98
+ expect(prompt.metadata.domain).toBe('test');
99
+ });
100
+
101
+ it('prompt includes role documentation', () => {
102
+ const prompt = generatePrompt({
103
+ domain: 'test',
104
+ description: 'Test domain',
105
+ schemas: testSchemas,
106
+ });
107
+
108
+ expect(prompt.text).toContain('source');
109
+ expect(prompt.text).toContain('patient');
110
+ expect(prompt.text).toContain('URL to fetch from');
111
+ });
112
+
113
+ it('prompt includes examples in bracket syntax', () => {
114
+ const prompt = generatePrompt({
115
+ domain: 'test',
116
+ description: 'Test domain',
117
+ schemas: testSchemas,
118
+ outputFormat: 'explicit',
119
+ });
120
+
121
+ expect(prompt.text).toContain('[fetch');
122
+ expect(prompt.text).toContain('[toggle');
123
+ });
124
+
125
+ it('prompt includes JSON format examples', () => {
126
+ const prompt = generatePrompt({
127
+ domain: 'test',
128
+ description: 'Test domain',
129
+ schemas: testSchemas,
130
+ outputFormat: 'json',
131
+ });
132
+
133
+ expect(prompt.text).toContain('"action"');
134
+ expect(prompt.text).toContain('"roles"');
135
+ });
136
+
137
+ it('respects token budget', () => {
138
+ const small = generatePrompt({
139
+ domain: 'test',
140
+ description: 'Test domain',
141
+ schemas: testSchemas,
142
+ maxTokens: 200,
143
+ });
144
+
145
+ const large = generatePrompt({
146
+ domain: 'test',
147
+ description: 'Test domain',
148
+ schemas: testSchemas,
149
+ maxTokens: 10000,
150
+ });
151
+
152
+ expect(small.text.length).toBeLessThan(large.text.length);
153
+ });
154
+ });
155
+
156
+ // =============================================================================
157
+ // Pillar 2: Training Data → JSONL Round-trip
158
+ // =============================================================================
159
+
160
+ describe('Pillar 2: Training Data Generation', () => {
161
+ it('synthesizes pairs from schemas', () => {
162
+ const result = synthesizeFromSchemas(testSchemas, { domain: 'test' });
163
+
164
+ expect(result.pairs.length).toBeGreaterThan(0);
165
+ expect(result.metadata.domain).toBe('test');
166
+ expect(result.metadata.commandCount).toBe(2);
167
+ });
168
+
169
+ it('each pair has required fields', () => {
170
+ const result = synthesizeFromSchemas(testSchemas, { domain: 'test' });
171
+
172
+ for (const pair of result.pairs) {
173
+ expect(pair.domain).toBe('test');
174
+ expect(pair.action).toBeTruthy();
175
+ expect(pair.explicit).toContain('[');
176
+ expect(pair.natural).toBeTruthy();
177
+ expect(pair.quality).toBeGreaterThanOrEqual(0);
178
+ expect(pair.quality).toBeLessThanOrEqual(1);
179
+ }
180
+ });
181
+
182
+ it('JSONL round-trips correctly', () => {
183
+ const result = synthesizeFromSchemas(testSchemas, { domain: 'test' });
184
+ const jsonl = toJSONL(result.pairs);
185
+ const parsed = parseJSONL(jsonl);
186
+
187
+ expect(parsed.length).toBe(result.pairs.length);
188
+ for (let i = 0; i < parsed.length; i++) {
189
+ expect(parsed[i].metadata.action).toBe(result.pairs[i].action);
190
+ expect(parsed[i].completion).toBe(result.pairs[i].explicit);
191
+ }
192
+ });
193
+
194
+ it('synthetic examples parse back at full confidence', () => {
195
+ const result = synthesizeFromSchemas(testSchemas, { domain: 'test' });
196
+
197
+ for (const pair of result.pairs) {
198
+ // Each explicit syntax should parse without error
199
+ const node = parseExplicit(pair.explicit);
200
+ expect(node.action).toBe(pair.action);
201
+ }
202
+ });
203
+
204
+ it('respects maxPairsPerCommand', () => {
205
+ const result = synthesizeFromSchemas(testSchemas, {
206
+ domain: 'test',
207
+ maxPairsPerCommand: 2,
208
+ });
209
+
210
+ // Count pairs per action
211
+ const counts = new Map<string, number>();
212
+ for (const pair of result.pairs) {
213
+ counts.set(pair.action, (counts.get(pair.action) || 0) + 1);
214
+ }
215
+
216
+ for (const count of counts.values()) {
217
+ expect(count).toBeLessThanOrEqual(2);
218
+ }
219
+ });
220
+ });
221
+
222
+ // =============================================================================
223
+ // Pillar 3: Feedback Loop
224
+ // =============================================================================
225
+
226
+ describe('Pillar 3: Feedback Loop', () => {
227
+ // --- 3A: Structured Error Feedback ---
228
+
229
+ it('invalid bracket syntax → structured feedback with hints', () => {
230
+ const feedback = buildFeedback(
231
+ '[fetch]',
232
+ 'explicit',
233
+ [{ severity: 'error', message: 'Required role source missing', code: 'missing-role' }],
234
+ { getSchema: a => testSchemas.find(s => s.action === a) },
235
+ 'fetch'
236
+ );
237
+
238
+ expect(feedback.accepted).toBe(false);
239
+ expect(feedback.diagnostics[0].fixType).toBe('missing_role');
240
+ expect(feedback.hints.length).toBeGreaterThan(0);
241
+ expect(feedback.schema?.requiredRoles).toContain('source');
242
+ expect(feedback.correctedExample?.explicit).toContain('[fetch');
243
+ });
244
+
245
+ it('accepted input → accepted: true, no corrected example', () => {
246
+ const feedback = buildFeedback(
247
+ '[fetch source:/api/users]',
248
+ 'explicit',
249
+ [],
250
+ { getSchema: a => testSchemas.find(s => s.action === a) },
251
+ 'fetch'
252
+ );
253
+
254
+ expect(feedback.accepted).toBe(true);
255
+ expect(feedback.correctedExample).toBeUndefined();
256
+ });
257
+
258
+ // --- 3B: Confidence-Aware Disambiguation ---
259
+
260
+ it('borderline confidence → disambiguation', () => {
261
+ expect(needsDisambiguation(0.6)).toBe(true);
262
+ expect(needsDisambiguation(0.9)).toBe(false);
263
+ expect(needsDisambiguation(0.3)).toBe(false);
264
+ });
265
+
266
+ it('buildDisambiguation produces sorted candidates', () => {
267
+ const nodeA = createCommandNode('toggle', { patient: createSelector('#button') });
268
+ const nodeB = createCommandNode('toggle', { patient: createSelector('.button') });
269
+
270
+ const result = buildDisambiguation('toggle button', [
271
+ { action: 'toggle', confidence: 0.55, node: nodeB },
272
+ { action: 'toggle', confidence: 0.65, node: nodeA },
273
+ ]);
274
+
275
+ expect(result.candidates[0].confidence).toBe(0.65);
276
+ expect(result.candidates[1].confidence).toBe(0.55);
277
+ expect(result.question).toContain('Which did you mean?');
278
+ });
279
+
280
+ // --- 3C: Pattern Hit-Rate Tracking ---
281
+
282
+ it('tracker records events and computes hit rates', () => {
283
+ const tracker = new PatternTracker();
284
+
285
+ tracker.record({
286
+ timestamp: Date.now(),
287
+ domain: 'flow',
288
+ action: 'fetch',
289
+ language: 'en',
290
+ inputFormat: 'explicit',
291
+ confidence: 0.95,
292
+ outcome: 'accepted',
293
+ });
294
+ tracker.record({
295
+ timestamp: Date.now(),
296
+ domain: 'flow',
297
+ action: 'fetch',
298
+ language: 'en',
299
+ inputFormat: 'explicit',
300
+ confidence: 0.4,
301
+ outcome: 'rejected',
302
+ diagnosticCodes: ['missing-role'],
303
+ });
304
+
305
+ const summary = tracker.getSummary();
306
+ expect(summary.totalEvents).toBe(2);
307
+ expect(summary.byCommand['fetch'].rate).toBe(0.5);
308
+ expect(summary.byOutcome['accepted']).toBe(1);
309
+ expect(summary.byOutcome['rejected']).toBe(1);
310
+ expect(summary.topFailures[0].code).toBe('missing-role');
311
+ });
312
+
313
+ it('tracker exports as JSONL', () => {
314
+ const tracker = new PatternTracker();
315
+ tracker.record({
316
+ timestamp: 1000,
317
+ domain: 'flow',
318
+ action: 'fetch',
319
+ language: 'en',
320
+ inputFormat: 'explicit',
321
+ confidence: 0.9,
322
+ outcome: 'accepted',
323
+ });
324
+
325
+ const jsonl = tracker.exportJSONL();
326
+ const parsed = JSON.parse(jsonl);
327
+ expect(parsed.action).toBe('fetch');
328
+ });
329
+ });
330
+
331
+ // =============================================================================
332
+ // Pillar 4: Registry Convenience Methods
333
+ // =============================================================================
334
+
335
+ describe('Pillar 4: Registry Convenience Methods', () => {
336
+ let registry: DomainRegistry;
337
+
338
+ beforeAll(() => {
339
+ registry = new DomainRegistry();
340
+ registry.register({
341
+ name: 'test-domain',
342
+ description: 'Integration test domain',
343
+ languages: ['en'],
344
+ inputLabel: 'command',
345
+ inputDescription: 'Test command',
346
+ schemas: testSchemas,
347
+ getDSL: () => {
348
+ throw new Error('Not implemented for this test');
349
+ },
350
+ });
351
+ });
352
+
353
+ it('generatePrompt returns prompt for domain with schemas', () => {
354
+ const prompt = registry.generatePrompt('test-domain');
355
+ expect(prompt).not.toBeNull();
356
+ expect(prompt!.text).toContain('fetch');
357
+ expect(prompt!.text).toContain('toggle');
358
+ expect(prompt!.metadata.commandCount).toBe(2);
359
+ });
360
+
361
+ it('generatePrompt returns null for unknown domain', () => {
362
+ expect(registry.generatePrompt('nonexistent')).toBeNull();
363
+ });
364
+
365
+ it('generateTrainingData returns pairs for domain with schemas', () => {
366
+ const result = registry.generateTrainingData('test-domain');
367
+ expect(result).not.toBeNull();
368
+ expect(result!.pairs.length).toBeGreaterThan(0);
369
+ expect(result!.metadata.domain).toBe('test-domain');
370
+ });
371
+
372
+ it('generateTrainingData returns null for unknown domain', () => {
373
+ expect(registry.generateTrainingData('nonexistent')).toBeNull();
374
+ });
375
+
376
+ it('buildFeedback returns structured feedback', () => {
377
+ const feedback = registry.buildFeedback(
378
+ 'test-domain',
379
+ '[fetch source:/api/users]',
380
+ 'explicit',
381
+ [{ severity: 'error', message: 'Required role source missing', code: 'missing-role' }]
382
+ );
383
+
384
+ expect(feedback).not.toBeNull();
385
+ expect(feedback!.accepted).toBe(false);
386
+ expect(feedback!.diagnostics[0].fixType).toBe('missing_role');
387
+ // Schema info is included because action was detected from input
388
+ expect(feedback!.schema?.action).toBe('fetch');
389
+ expect(feedback!.hints.length).toBeGreaterThan(0);
390
+ });
391
+
392
+ it('buildFeedback returns null for unknown domain', () => {
393
+ expect(registry.buildFeedback('nonexistent', '[test]', 'explicit', [])).toBeNull();
394
+ });
395
+
396
+ it('getSchemas returns schemas for domain', () => {
397
+ const schemas = registry.getSchemas('test-domain');
398
+ expect(schemas).not.toBeNull();
399
+ expect(schemas!.length).toBe(2);
400
+ });
401
+
402
+ it('setSchemas attaches schemas post-registration', () => {
403
+ const reg = new DomainRegistry();
404
+ reg.register({
405
+ name: 'lazy',
406
+ description: 'Lazy schema domain',
407
+ languages: ['en'],
408
+ inputLabel: 'input',
409
+ inputDescription: 'Test input',
410
+ getDSL: () => {
411
+ throw new Error('Not implemented');
412
+ },
413
+ });
414
+
415
+ // Initially no schemas
416
+ expect(reg.getSchemas('lazy')).toBeNull();
417
+
418
+ // Attach schemas
419
+ reg.setSchemas('lazy', testSchemas);
420
+
421
+ expect(reg.getSchemas('lazy')!.length).toBe(2);
422
+ const prompt = reg.generatePrompt('lazy');
423
+ expect(prompt).not.toBeNull();
424
+ });
425
+ });
426
+
427
+ // =============================================================================
428
+ // Cross-Cutting: Explicit Syntax ↔ JSON Round-trip
429
+ // =============================================================================
430
+
431
+ describe('Cross-Cutting: Explicit ↔ JSON Round-trip', () => {
432
+ it('node → explicit → parse → node preserves structure', () => {
433
+ const original = createCommandNode('fetch', {
434
+ source: createExpression('/api/users'),
435
+ style: createLiteral('json'),
436
+ destination: createSelector('#list'),
437
+ });
438
+
439
+ const explicit = renderExplicit(original);
440
+ const parsed = parseExplicit(explicit);
441
+
442
+ expect(parsed.action).toBe('fetch');
443
+ expect(parsed.roles.get('source')).toBeDefined();
444
+ expect(parsed.roles.get('style')).toBeDefined();
445
+ expect(parsed.roles.get('destination')).toBeDefined();
446
+ });
447
+
448
+ it('node → JSON → node preserves action and roles', () => {
449
+ const original = createCommandNode('toggle', {
450
+ patient: createSelector('.active'),
451
+ });
452
+
453
+ const json = semanticNodeToJSON(original);
454
+ expect(json.action).toBe('toggle');
455
+ expect(json.roles['patient']).toBeDefined();
456
+ });
457
+ });