@lokascript/framework 2.0.0 → 2.2.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 (138) 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 +1438 -21
  11. package/dist/api/index.js.map +1 -1
  12. package/dist/core/index.js +247 -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/tokenization/index.js +133 -0
  18. package/dist/core/tokenization/index.js.map +1 -1
  19. package/dist/core/tokenization/morphology/base-normalizer.d.ts +94 -0
  20. package/dist/core/tokenization/morphology/base-normalizer.d.ts.map +1 -0
  21. package/dist/core/tokenization/morphology/index.d.ts +3 -1
  22. package/dist/core/tokenization/morphology/index.d.ts.map +1 -1
  23. package/dist/core/types.d.ts +81 -2
  24. package/dist/core/types.d.ts.map +1 -1
  25. package/dist/core/types.js +40 -0
  26. package/dist/core/types.js.map +1 -1
  27. package/dist/feedback/confidence-gate.d.ts +28 -0
  28. package/dist/feedback/confidence-gate.d.ts.map +1 -0
  29. package/dist/feedback/feedback-formatter.d.ts +21 -0
  30. package/dist/feedback/feedback-formatter.d.ts.map +1 -0
  31. package/dist/feedback/index.d.ts +11 -0
  32. package/dist/feedback/index.d.ts.map +1 -0
  33. package/dist/feedback/pattern-tracker.d.ts +47 -0
  34. package/dist/feedback/pattern-tracker.d.ts.map +1 -0
  35. package/dist/feedback/types.d.ts +113 -0
  36. package/dist/feedback/types.d.ts.map +1 -0
  37. package/dist/generation/index.js +2 -1
  38. package/dist/generation/index.js.map +1 -1
  39. package/dist/index.cjs +3264 -132
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +8 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +3223 -132
  44. package/dist/index.js.map +1 -1
  45. package/dist/ir/explicit-parser.d.ts +86 -0
  46. package/dist/ir/explicit-parser.d.ts.map +1 -0
  47. package/dist/ir/explicit-renderer.d.ts +34 -0
  48. package/dist/ir/explicit-renderer.d.ts.map +1 -0
  49. package/dist/ir/from-interchange.d.ts +51 -0
  50. package/dist/ir/from-interchange.d.ts.map +1 -0
  51. package/dist/ir/index.d.ts +31 -0
  52. package/dist/ir/index.d.ts.map +1 -0
  53. package/dist/ir/index.js +1934 -0
  54. package/dist/ir/index.js.map +1 -0
  55. package/dist/ir/json-schema.d.ts +40 -0
  56. package/dist/ir/json-schema.d.ts.map +1 -0
  57. package/dist/ir/protocol-json.d.ts +73 -0
  58. package/dist/ir/protocol-json.d.ts.map +1 -0
  59. package/dist/ir/references.d.ts +19 -0
  60. package/dist/ir/references.d.ts.map +1 -0
  61. package/dist/ir/to-runtime-ast.d.ts +53 -0
  62. package/dist/ir/to-runtime-ast.d.ts.map +1 -0
  63. package/dist/ir/types.d.ts +151 -0
  64. package/dist/ir/types.d.ts.map +1 -0
  65. package/dist/parsing/index.js +74 -10
  66. package/dist/parsing/index.js.map +1 -1
  67. package/dist/prompts/index.d.ts +9 -0
  68. package/dist/prompts/index.d.ts.map +1 -0
  69. package/dist/prompts/prompt-generator.d.ts +49 -0
  70. package/dist/prompts/prompt-generator.d.ts.map +1 -0
  71. package/dist/prompts/types.d.ts +64 -0
  72. package/dist/prompts/types.d.ts.map +1 -0
  73. package/dist/schema/command-schema.d.ts +17 -0
  74. package/dist/schema/command-schema.d.ts.map +1 -1
  75. package/dist/schema/index.js.map +1 -1
  76. package/dist/testing/index.js +8673 -8260
  77. package/dist/testing/index.js.map +1 -1
  78. package/dist/training/index.d.ts +11 -0
  79. package/dist/training/index.d.ts.map +1 -0
  80. package/dist/training/jsonl-writer.d.ts +42 -0
  81. package/dist/training/jsonl-writer.d.ts.map +1 -0
  82. package/dist/training/schema-synthesizer.d.ts +20 -0
  83. package/dist/training/schema-synthesizer.d.ts.map +1 -0
  84. package/dist/training/types.d.ts +73 -0
  85. package/dist/training/types.d.ts.map +1 -0
  86. package/package.json +5 -1
  87. package/src/aot/index.ts +1 -0
  88. package/src/aot/registry-bridge.test.ts +104 -0
  89. package/src/aot/registry-bridge.ts +66 -0
  90. package/src/api/create-dsl.test.ts +306 -0
  91. package/src/api/create-dsl.ts +21 -0
  92. package/src/api/dispatcher.test.ts +66 -0
  93. package/src/api/dispatcher.ts +77 -9
  94. package/src/api/domain-registry.ts +120 -4
  95. package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
  96. package/src/core/pattern-matching/pattern-matcher.ts +102 -19
  97. package/src/core/tokenization/morphology/base-normalizer.ts +249 -0
  98. package/src/core/tokenization/morphology/index.ts +3 -1
  99. package/src/core/types.ts +147 -2
  100. package/src/feedback/confidence-gate.test.ts +111 -0
  101. package/src/feedback/confidence-gate.ts +70 -0
  102. package/src/feedback/feedback-formatter.test.ts +198 -0
  103. package/src/feedback/feedback-formatter.ts +298 -0
  104. package/src/feedback/index.ts +24 -0
  105. package/src/feedback/pattern-tracker.test.ts +198 -0
  106. package/src/feedback/pattern-tracker.ts +140 -0
  107. package/src/feedback/types.ts +149 -0
  108. package/src/generation/pattern-generator.ts +2 -2
  109. package/src/index.ts +29 -0
  110. package/src/integration/llm-lse-integration.test.ts +457 -0
  111. package/src/ir/conformance.test.ts +120 -0
  112. package/src/ir/explicit-parser.test.ts +612 -0
  113. package/src/ir/explicit-parser.ts +793 -0
  114. package/src/ir/explicit-renderer.test.ts +229 -0
  115. package/src/ir/explicit-renderer.ts +198 -0
  116. package/src/ir/from-interchange.test.ts +853 -0
  117. package/src/ir/from-interchange.ts +594 -0
  118. package/src/ir/index.ts +77 -0
  119. package/src/ir/json-schema.test.ts +281 -0
  120. package/src/ir/json-schema.ts +299 -0
  121. package/src/ir/protocol-json.test.ts +1187 -0
  122. package/src/ir/protocol-json.ts +751 -0
  123. package/src/ir/references.test.ts +38 -0
  124. package/src/ir/references.ts +33 -0
  125. package/src/ir/round-trip.test.ts +557 -0
  126. package/src/ir/to-runtime-ast.ts +203 -0
  127. package/src/ir/types.ts +191 -0
  128. package/src/prompts/index.ts +15 -0
  129. package/src/prompts/prompt-generator.test.ts +386 -0
  130. package/src/prompts/prompt-generator.ts +531 -0
  131. package/src/prompts/types.ts +89 -0
  132. package/src/schema/command-schema.ts +19 -0
  133. package/src/training/index.ts +13 -0
  134. package/src/training/jsonl-writer.test.ts +138 -0
  135. package/src/training/jsonl-writer.ts +67 -0
  136. package/src/training/schema-synthesizer.test.ts +250 -0
  137. package/src/training/schema-synthesizer.ts +306 -0
  138. package/src/training/types.ts +103 -0
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Tests for LLM Prompt Generator
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { generatePrompt, generateExamples, generateProtocolReference } from './prompt-generator';
7
+ import type { PromptGeneratorConfig } from './types';
8
+ import { defineCommand, defineRole } from '../schema/command-schema';
9
+ import type { CommandSchema } from '../schema/command-schema';
10
+
11
+ // =============================================================================
12
+ // Test Schemas (mimics domain-flow patterns)
13
+ // =============================================================================
14
+
15
+ const fetchSchema: CommandSchema = defineCommand({
16
+ action: 'fetch',
17
+ description: 'Fetch data from a URL and deliver to a target element',
18
+ category: 'source',
19
+ primaryRole: 'source',
20
+ roles: [
21
+ defineRole({
22
+ role: 'source',
23
+ description: 'URL to fetch from',
24
+ required: true,
25
+ expectedTypes: ['expression'],
26
+ }),
27
+ defineRole({
28
+ role: 'style',
29
+ description: 'Response format (json, html, text)',
30
+ required: false,
31
+ expectedTypes: ['expression'],
32
+ }),
33
+ defineRole({
34
+ role: 'destination',
35
+ description: 'Target element to deliver data to',
36
+ required: false,
37
+ expectedTypes: ['selector', 'expression'],
38
+ }),
39
+ ],
40
+ });
41
+
42
+ const submitSchema: CommandSchema = defineCommand({
43
+ action: 'submit',
44
+ description: 'Submit form data to a URL',
45
+ category: 'action',
46
+ primaryRole: 'patient',
47
+ roles: [
48
+ defineRole({
49
+ role: 'patient',
50
+ description: 'Form element to submit',
51
+ required: true,
52
+ expectedTypes: ['selector', 'expression'],
53
+ }),
54
+ defineRole({
55
+ role: 'destination',
56
+ description: 'URL to submit to',
57
+ required: true,
58
+ expectedTypes: ['expression'],
59
+ }),
60
+ defineRole({
61
+ role: 'style',
62
+ description: 'Request encoding (json, form, multipart)',
63
+ required: false,
64
+ expectedTypes: ['expression'],
65
+ }),
66
+ ],
67
+ });
68
+
69
+ const toggleSchema: CommandSchema = defineCommand({
70
+ action: 'toggle',
71
+ description: 'Toggle a class or attribute on an element',
72
+ category: 'dom',
73
+ primaryRole: 'patient',
74
+ roles: [
75
+ defineRole({
76
+ role: 'patient',
77
+ description: 'The CSS selector to toggle',
78
+ required: true,
79
+ expectedTypes: ['selector'],
80
+ }),
81
+ ],
82
+ });
83
+
84
+ const testSchemas: readonly CommandSchema[] = [fetchSchema, submitSchema, toggleSchema];
85
+
86
+ function makeConfig(overrides?: Partial<PromptGeneratorConfig>): PromptGeneratorConfig {
87
+ return {
88
+ domain: 'test',
89
+ description: 'Test domain for unit testing',
90
+ schemas: testSchemas,
91
+ ...overrides,
92
+ };
93
+ }
94
+
95
+ // =============================================================================
96
+ // generatePrompt()
97
+ // =============================================================================
98
+
99
+ describe('generatePrompt', () => {
100
+ it('returns a GeneratedPrompt with all sections', () => {
101
+ const result = generatePrompt(makeConfig());
102
+ expect(result.text).toBeTruthy();
103
+ expect(result.sections.length).toBeGreaterThanOrEqual(4);
104
+ expect(result.metadata.domain).toBe('test');
105
+ });
106
+
107
+ it('includes protocol section', () => {
108
+ const result = generatePrompt(makeConfig());
109
+ const protocol = result.sections.find(s => s.id === 'protocol');
110
+ expect(protocol).toBeDefined();
111
+ expect(protocol!.content).toContain('[command role1:value1');
112
+ expect(protocol!.content).toContain('lowercased');
113
+ });
114
+
115
+ it('includes value types section', () => {
116
+ const result = generatePrompt(makeConfig());
117
+ const types = result.sections.find(s => s.id === 'value-types');
118
+ expect(types).toBeDefined();
119
+ expect(types!.content).toContain('Selector');
120
+ expect(types!.content).toContain('Duration');
121
+ expect(types!.content).toContain('Reference');
122
+ });
123
+
124
+ it('includes commands section with all schemas', () => {
125
+ const result = generatePrompt(makeConfig());
126
+ const commands = result.sections.find(s => s.id === 'commands');
127
+ expect(commands).toBeDefined();
128
+ expect(commands!.content).toContain('`fetch`');
129
+ expect(commands!.content).toContain('`submit`');
130
+ expect(commands!.content).toContain('`toggle`');
131
+ });
132
+
133
+ it('includes output format section', () => {
134
+ const result = generatePrompt(makeConfig());
135
+ const format = result.sections.find(s => s.id === 'output-format');
136
+ expect(format).toBeDefined();
137
+ });
138
+
139
+ it('includes error recovery section', () => {
140
+ const result = generatePrompt(makeConfig());
141
+ const recovery = result.sections.find(s => s.id === 'error-recovery');
142
+ expect(recovery).toBeDefined();
143
+ expect(recovery!.content).toContain('Unknown command');
144
+ });
145
+
146
+ it('counts commands and roles in metadata', () => {
147
+ const result = generatePrompt(makeConfig());
148
+ expect(result.metadata.commandCount).toBe(3);
149
+ // fetch: 3 roles, submit: 3 roles, toggle: 1 role = 7
150
+ expect(result.metadata.roleCount).toBe(7);
151
+ });
152
+
153
+ it('estimates approximate tokens', () => {
154
+ const result = generatePrompt(makeConfig());
155
+ expect(result.metadata.approximateTokens).toBeGreaterThan(0);
156
+ // Roughly chars/4
157
+ expect(result.metadata.approximateTokens).toBe(Math.ceil(result.text.length / 4));
158
+ });
159
+
160
+ // --- Output Format Variants ---
161
+
162
+ it('generates explicit-only format', () => {
163
+ const result = generatePrompt(makeConfig({ outputFormat: 'explicit' }));
164
+ const format = result.sections.find(s => s.id === 'output-format');
165
+ expect(format!.content).toContain('bracket syntax');
166
+ expect(format!.content).toContain('Do NOT output JSON');
167
+ });
168
+
169
+ it('generates json-only format', () => {
170
+ const result = generatePrompt(makeConfig({ outputFormat: 'json' }));
171
+ const format = result.sections.find(s => s.id === 'output-format');
172
+ expect(format!.content).toContain('JSON');
173
+ expect(format!.content).toContain('Do NOT output bracket syntax');
174
+ });
175
+
176
+ it('generates both format', () => {
177
+ const result = generatePrompt(makeConfig({ outputFormat: 'both' }));
178
+ const format = result.sections.find(s => s.id === 'output-format');
179
+ expect(format!.content).toContain('EITHER format');
180
+ });
181
+
182
+ // --- Command Documentation ---
183
+
184
+ it('documents required and optional roles', () => {
185
+ const result = generatePrompt(makeConfig());
186
+ const commands = result.sections.find(s => s.id === 'commands')!;
187
+ // fetch has 1 required (source) and 2 optional (style, destination)
188
+ expect(commands.content).toContain('**Required roles:**');
189
+ expect(commands.content).toContain('**Optional roles:**');
190
+ expect(commands.content).toContain('`source`');
191
+ expect(commands.content).toContain('`style`');
192
+ expect(commands.content).toContain('`destination`');
193
+ });
194
+
195
+ it('includes role type information', () => {
196
+ const result = generatePrompt(makeConfig());
197
+ const commands = result.sections.find(s => s.id === 'commands')!;
198
+ expect(commands.content).toContain('(type: expression)');
199
+ expect(commands.content).toContain('(type: selector)');
200
+ });
201
+
202
+ it('includes bracket syntax examples', () => {
203
+ const result = generatePrompt(makeConfig({ outputFormat: 'explicit' }));
204
+ const commands = result.sections.find(s => s.id === 'commands')!;
205
+ expect(commands.content).toContain('[fetch');
206
+ expect(commands.content).toContain('[submit');
207
+ expect(commands.content).toContain('[toggle');
208
+ });
209
+
210
+ it('includes JSON examples when format is json or both', () => {
211
+ const result = generatePrompt(makeConfig({ outputFormat: 'json' }));
212
+ const commands = result.sections.find(s => s.id === 'commands')!;
213
+ expect(commands.content).toContain('"action": "fetch"');
214
+ expect(commands.content).toContain('"roles"');
215
+ });
216
+
217
+ // --- Token Budget ---
218
+
219
+ it('truncates to maxTokens budget', () => {
220
+ const full = generatePrompt(makeConfig());
221
+ const truncated = generatePrompt(makeConfig({ maxTokens: 100 }));
222
+
223
+ expect(truncated.sections.length).toBeLessThanOrEqual(full.sections.length);
224
+ expect(truncated.text.length).toBeLessThan(full.text.length);
225
+ });
226
+
227
+ it('includes truncation marker when budget is exceeded', () => {
228
+ const result = generatePrompt(makeConfig({ maxTokens: 100 }));
229
+ // Either fewer sections or truncation marker
230
+ const hasFewerSections = result.sections.length < 5;
231
+ const hasTruncationMarker = result.text.includes('*(truncated)*');
232
+ expect(hasFewerSections || hasTruncationMarker).toBe(true);
233
+ });
234
+
235
+ // --- Examples Per Command ---
236
+
237
+ it('respects examplesPerCommand setting', () => {
238
+ const result1 = generatePrompt(makeConfig({ examplesPerCommand: 1 }));
239
+ const result3 = generatePrompt(makeConfig({ examplesPerCommand: 3 }));
240
+ // More examples = longer text
241
+ expect(result3.text.length).toBeGreaterThan(result1.text.length);
242
+ });
243
+ });
244
+
245
+ // =============================================================================
246
+ // generateExamples()
247
+ // =============================================================================
248
+
249
+ describe('generateExamples', () => {
250
+ it('generates examples for a command with required + optional roles', () => {
251
+ const examples = generateExamples(fetchSchema, 3);
252
+ expect(examples.length).toBeGreaterThanOrEqual(2);
253
+ });
254
+
255
+ it('generates valid bracket syntax', () => {
256
+ const examples = generateExamples(fetchSchema, 2);
257
+ for (const ex of examples) {
258
+ expect(ex.explicit).toMatch(/^\[fetch\s/);
259
+ expect(ex.explicit).toMatch(/\]$/);
260
+ }
261
+ });
262
+
263
+ it('generates valid JSON format', () => {
264
+ const examples = generateExamples(fetchSchema, 2);
265
+ for (const ex of examples) {
266
+ expect(ex.json.action).toBe('fetch');
267
+ expect(ex.json.roles).toBeDefined();
268
+ expect(typeof ex.json.roles).toBe('object');
269
+ }
270
+ });
271
+
272
+ it('always includes required roles', () => {
273
+ const examples = generateExamples(fetchSchema, 3);
274
+ for (const ex of examples) {
275
+ // source is required
276
+ expect(ex.json.roles['source']).toBeDefined();
277
+ }
278
+ });
279
+
280
+ it('generates selector values for selector-typed roles', () => {
281
+ const examples = generateExamples(toggleSchema, 1);
282
+ const patientValue = examples[0].json.roles['patient'];
283
+ expect(patientValue).toBeDefined();
284
+ expect(patientValue.type).toBe('selector');
285
+ expect(String(patientValue.value)).toMatch(/^[#.@*\[]/);
286
+ });
287
+
288
+ it('generates expression values for expression-typed roles', () => {
289
+ const examples = generateExamples(fetchSchema, 1);
290
+ const sourceValue = examples[0].json.roles['source'];
291
+ expect(sourceValue).toBeDefined();
292
+ expect(sourceValue.type).toBe('expression');
293
+ });
294
+
295
+ it('limits output to requested count', () => {
296
+ const one = generateExamples(fetchSchema, 1);
297
+ expect(one.length).toBe(1);
298
+ });
299
+
300
+ it('handles schema with no optional roles', () => {
301
+ const examples = generateExamples(toggleSchema, 3);
302
+ // toggle has only 1 required role, so only 1 combination
303
+ expect(examples.length).toBe(1);
304
+ });
305
+
306
+ it('handles schema with all required roles', () => {
307
+ const examples = generateExamples(submitSchema, 3);
308
+ // submit: 2 required + 1 optional = combos: [req], [req+opt1], total 2
309
+ expect(examples.length).toBeGreaterThanOrEqual(2);
310
+ });
311
+
312
+ it('generates unique combinations', () => {
313
+ const examples = generateExamples(fetchSchema, 4);
314
+ const explicits = examples.map(e => e.explicit);
315
+ const unique = new Set(explicits);
316
+ expect(unique.size).toBe(examples.length);
317
+ });
318
+ });
319
+
320
+ // =============================================================================
321
+ // generateProtocolReference()
322
+ // =============================================================================
323
+
324
+ describe('generateProtocolReference', () => {
325
+ it('returns a markdown string with protocol info', () => {
326
+ const ref = generateProtocolReference();
327
+ expect(ref).toContain('LokaScript Explicit Syntax');
328
+ expect(ref).toContain('[command');
329
+ });
330
+
331
+ it('includes value type information', () => {
332
+ const ref = generateProtocolReference();
333
+ expect(ref).toContain('Selector');
334
+ expect(ref).toContain('Duration');
335
+ expect(ref).toContain('Boolean');
336
+ expect(ref).toContain('Reference');
337
+ });
338
+
339
+ it('returns non-empty string', () => {
340
+ const ref = generateProtocolReference();
341
+ expect(ref.length).toBeGreaterThan(100);
342
+ });
343
+ });
344
+
345
+ // =============================================================================
346
+ // Edge Cases
347
+ // =============================================================================
348
+
349
+ describe('edge cases', () => {
350
+ it('handles empty schemas array', () => {
351
+ const result = generatePrompt(makeConfig({ schemas: [] }));
352
+ expect(result.metadata.commandCount).toBe(0);
353
+ expect(result.metadata.roleCount).toBe(0);
354
+ expect(result.text).toBeTruthy();
355
+ });
356
+
357
+ it('handles schema with no roles', () => {
358
+ const noRoleSchema = defineCommand({
359
+ action: 'noop',
360
+ roles: [],
361
+ primaryRole: 'patient',
362
+ });
363
+ const examples = generateExamples(noRoleSchema, 2);
364
+ expect(examples.length).toBeGreaterThanOrEqual(0);
365
+ });
366
+
367
+ it('handles schema with only optional roles', () => {
368
+ const optionalOnlySchema = defineCommand({
369
+ action: 'log',
370
+ roles: [
371
+ defineRole({ role: 'patient', required: false, expectedTypes: ['expression'] }),
372
+ defineRole({ role: 'style', required: false, expectedTypes: ['literal'] }),
373
+ ],
374
+ primaryRole: 'patient',
375
+ });
376
+ const examples = generateExamples(optionalOnlySchema, 3);
377
+ expect(examples.length).toBeGreaterThan(0);
378
+ });
379
+
380
+ it('handles very small maxTokens', () => {
381
+ const result = generatePrompt(makeConfig({ maxTokens: 10 }));
382
+ // Should still produce something
383
+ expect(result.text.length).toBeGreaterThan(0);
384
+ expect(result.sections.length).toBeGreaterThanOrEqual(1);
385
+ });
386
+ });