@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,306 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseExplicit, isExplicitSyntax } from './explicit-parser';
3
+ import type { ParseExplicitOptions, SchemaLookup } from './types';
4
+ import { defineCommand, defineRole } from '../schema/command-schema';
5
+
6
+ describe('isExplicitSyntax', () => {
7
+ it('detects bracket syntax', () => {
8
+ expect(isExplicitSyntax('[toggle patient:.active]')).toBe(true);
9
+ expect(isExplicitSyntax(' [toggle patient:.active] ')).toBe(true);
10
+ });
11
+
12
+ it('rejects non-bracket syntax', () => {
13
+ expect(isExplicitSyntax('toggle .active')).toBe(false);
14
+ expect(isExplicitSyntax('[incomplete')).toBe(false);
15
+ expect(isExplicitSyntax('incomplete]')).toBe(false);
16
+ expect(isExplicitSyntax('')).toBe(false);
17
+ });
18
+ });
19
+
20
+ describe('parseExplicit — without schema', () => {
21
+ it('parses basic command with roles', () => {
22
+ const node = parseExplicit('[toggle patient:.active destination:#button]');
23
+ expect(node.kind).toBe('command');
24
+ expect(node.action).toBe('toggle');
25
+ expect(node.roles.get('patient')).toEqual({
26
+ type: 'selector',
27
+ value: '.active',
28
+ selectorKind: 'class',
29
+ });
30
+ expect(node.roles.get('destination')).toEqual({
31
+ type: 'selector',
32
+ value: '#button',
33
+ selectorKind: 'id',
34
+ });
35
+ });
36
+
37
+ it('parses selector values', () => {
38
+ const node = parseExplicit('[add patient:.highlight destination:#output]');
39
+ const patient = node.roles.get('patient');
40
+ expect(patient?.type).toBe('selector');
41
+ expect(patient && 'value' in patient && patient.value).toBe('.highlight');
42
+ });
43
+
44
+ it('parses string literal values', () => {
45
+ const node = parseExplicit('[put patient:"hello world" destination:#output]');
46
+ const patient = node.roles.get('patient');
47
+ expect(patient).toEqual({ type: 'literal', value: 'hello world', dataType: 'string' });
48
+ });
49
+
50
+ it('parses boolean values', () => {
51
+ const node = parseExplicit('[set destination:myVar goal:true]');
52
+ expect(node.roles.get('goal')).toEqual({ type: 'literal', value: true, dataType: 'boolean' });
53
+ });
54
+
55
+ it('parses numeric values', () => {
56
+ const node = parseExplicit('[increment destination:count quantity:5]');
57
+ expect(node.roles.get('quantity')).toEqual({ type: 'literal', value: 5, dataType: 'number' });
58
+ });
59
+
60
+ it('parses duration values', () => {
61
+ const node = parseExplicit('[wait duration:500ms]');
62
+ expect(node.roles.get('duration')).toEqual({
63
+ type: 'literal',
64
+ value: '500ms',
65
+ dataType: 'duration',
66
+ });
67
+
68
+ const node2 = parseExplicit('[wait duration:2s]');
69
+ expect(node2.roles.get('duration')).toEqual({
70
+ type: 'literal',
71
+ value: '2s',
72
+ dataType: 'duration',
73
+ });
74
+ });
75
+
76
+ it('parses reference values', () => {
77
+ const node = parseExplicit('[add patient:.active destination:me]');
78
+ expect(node.roles.get('destination')).toEqual({ type: 'reference', value: 'me' });
79
+ });
80
+
81
+ it('parses plain string values (unquoted, non-special)', () => {
82
+ const node = parseExplicit('[fetch source:/api/users responseType:json]');
83
+ // Unquoted values get no dataType — only explicitly quoted values get dataType: 'string'
84
+ expect(node.roles.get('source')).toEqual({
85
+ type: 'literal',
86
+ value: '/api/users',
87
+ });
88
+ expect(node.roles.get('responseType')).toEqual({
89
+ type: 'literal',
90
+ value: 'json',
91
+ });
92
+ });
93
+
94
+ it('parses event handlers with body', () => {
95
+ const node = parseExplicit('[on event:click body:[toggle patient:.active]]');
96
+ expect(node.kind).toBe('event-handler');
97
+ expect(node.action).toBe('on');
98
+ const eventNode = node as { body: unknown[] };
99
+ expect(eventNode.body).toHaveLength(1);
100
+ expect(eventNode.body[0]).toMatchObject({ kind: 'command', action: 'toggle' });
101
+ });
102
+
103
+ it('accepts any role names without schema', () => {
104
+ // Custom domain roles should parse fine
105
+ const node = parseExplicit('[deploy destination:production source:main manner:rolling]');
106
+ expect(node.action).toBe('deploy');
107
+ expect(node.roles.get('destination')).toEqual({
108
+ type: 'literal',
109
+ value: 'production',
110
+ });
111
+ expect(node.roles.get('source')).toEqual({
112
+ type: 'literal',
113
+ value: 'main',
114
+ });
115
+ expect(node.roles.get('manner')).toEqual({
116
+ type: 'literal',
117
+ value: 'rolling',
118
+ });
119
+ });
120
+
121
+ it('sets sourceLanguage to explicit', () => {
122
+ const node = parseExplicit('[toggle patient:.active]');
123
+ expect(node.metadata?.sourceLanguage).toBe('explicit');
124
+ });
125
+ });
126
+
127
+ describe('parseExplicit — error handling', () => {
128
+ it('throws on missing brackets', () => {
129
+ expect(() => parseExplicit('toggle patient:.active')).toThrow('must be wrapped in brackets');
130
+ });
131
+
132
+ it('throws on empty brackets', () => {
133
+ expect(() => parseExplicit('[]')).toThrow('Empty explicit statement');
134
+ expect(() => parseExplicit('[ ]')).toThrow('Empty explicit statement');
135
+ });
136
+
137
+ it('throws on missing colon in role', () => {
138
+ expect(() => parseExplicit('[toggle active]')).toThrow('Expected role:value');
139
+ });
140
+
141
+ it('throws on missing event role for event handler', () => {
142
+ expect(() => parseExplicit('[on body:[toggle patient:.active]]')).toThrow(
143
+ 'requires event role'
144
+ );
145
+ });
146
+ });
147
+
148
+ describe('parseExplicit — with SchemaLookup', () => {
149
+ const toggleSchema = defineCommand({
150
+ action: 'toggle',
151
+ roles: [
152
+ defineRole({ role: 'patient', required: true, expectedTypes: ['selector'] }),
153
+ defineRole({ role: 'destination', required: false, expectedTypes: ['selector'] }),
154
+ ],
155
+ });
156
+
157
+ const schemaLookup: SchemaLookup = {
158
+ getSchema(action: string) {
159
+ if (action === 'toggle') return toggleSchema;
160
+ return undefined;
161
+ },
162
+ };
163
+
164
+ const options: ParseExplicitOptions = { schemaLookup };
165
+
166
+ it('validates roles against schema', () => {
167
+ // Valid roles pass
168
+ const node = parseExplicit('[toggle patient:.active]', options);
169
+ expect(node.action).toBe('toggle');
170
+ });
171
+
172
+ it('rejects unknown roles for known commands', () => {
173
+ expect(() => parseExplicit('[toggle patient:.active bogus:value]', options)).toThrow(
174
+ 'Unknown role "bogus"'
175
+ );
176
+ });
177
+
178
+ it('enforces required roles', () => {
179
+ expect(() => parseExplicit('[toggle destination:#btn]', options)).toThrow(
180
+ 'Missing required role "patient"'
181
+ );
182
+ });
183
+
184
+ it('allows any roles for unknown commands', () => {
185
+ // 'deploy' has no schema registered
186
+ const node = parseExplicit('[deploy target:prod]', options);
187
+ expect(node.action).toBe('deploy');
188
+ expect(node.roles.get('target')).toBeDefined();
189
+ });
190
+ });
191
+
192
+ describe('parseExplicit — boolean flags', () => {
193
+ it('parses +flag as enabled FlagValue', () => {
194
+ const node = parseExplicit('[column name:id +primary-key]');
195
+ expect(node.roles.get('primary-key')).toEqual({
196
+ type: 'flag',
197
+ name: 'primary-key',
198
+ enabled: true,
199
+ });
200
+ });
201
+
202
+ it('parses ~flag as disabled FlagValue', () => {
203
+ const node = parseExplicit('[field name:email ~nullable]');
204
+ expect(node.roles.get('nullable')).toEqual({
205
+ type: 'flag',
206
+ name: 'nullable',
207
+ enabled: false,
208
+ });
209
+ });
210
+
211
+ it('parses flags alongside role:value pairs', () => {
212
+ const node = parseExplicit('[column name:id type:uuid +primary-key +not-null]');
213
+ expect(node.action).toBe('column');
214
+ expect(node.roles.get('name')).toEqual({ type: 'literal', value: 'id' });
215
+ expect(node.roles.get('type')).toEqual({ type: 'literal', value: 'uuid' });
216
+ expect(node.roles.get('primary-key')).toEqual({
217
+ type: 'flag',
218
+ name: 'primary-key',
219
+ enabled: true,
220
+ });
221
+ expect(node.roles.get('not-null')).toEqual({
222
+ type: 'flag',
223
+ name: 'not-null',
224
+ enabled: true,
225
+ });
226
+ });
227
+
228
+ it('parses mixed + and ~ flags', () => {
229
+ const node = parseExplicit('[field name:email +required ~nullable]');
230
+ expect(node.roles.get('required')).toEqual({
231
+ type: 'flag',
232
+ name: 'required',
233
+ enabled: true,
234
+ });
235
+ expect(node.roles.get('nullable')).toEqual({
236
+ type: 'flag',
237
+ name: 'nullable',
238
+ enabled: false,
239
+ });
240
+ });
241
+
242
+ it('throws on empty flag name', () => {
243
+ expect(() => parseExplicit('[column name:id +]')).toThrow('Empty flag name');
244
+ expect(() => parseExplicit('[column name:id ~]')).toThrow('Empty flag name');
245
+ });
246
+
247
+ it('validates flag names against schema', () => {
248
+ const schema = defineCommand({
249
+ action: 'column',
250
+ roles: [
251
+ defineRole({ role: 'name', required: true, expectedTypes: ['literal'] }),
252
+ defineRole({ role: 'primary-key', required: false, expectedTypes: ['flag'] }),
253
+ ],
254
+ });
255
+ const schemaLookup: SchemaLookup = {
256
+ getSchema(action: string) {
257
+ if (action === 'column') return schema;
258
+ return undefined;
259
+ },
260
+ };
261
+ // Valid flag
262
+ const node = parseExplicit('[column name:id +primary-key]', { schemaLookup });
263
+ expect(node.roles.get('primary-key')).toBeDefined();
264
+
265
+ // Invalid flag
266
+ expect(() => parseExplicit('[column name:id +unknown-flag]', { schemaLookup })).toThrow(
267
+ 'Unknown flag "unknown-flag"'
268
+ );
269
+ });
270
+
271
+ it('allows any flags for unknown commands (no schema)', () => {
272
+ const node = parseExplicit('[widget +draggable +resizable]');
273
+ expect(node.roles.get('draggable')).toEqual({
274
+ type: 'flag',
275
+ name: 'draggable',
276
+ enabled: true,
277
+ });
278
+ expect(node.roles.get('resizable')).toEqual({
279
+ type: 'flag',
280
+ name: 'resizable',
281
+ enabled: true,
282
+ });
283
+ });
284
+ });
285
+
286
+ describe('parseExplicit — custom reference set', () => {
287
+ it('uses custom references', () => {
288
+ const options: ParseExplicitOptions = {
289
+ referenceSet: new Set(['self', 'parent']),
290
+ };
291
+ const node = parseExplicit('[add patient:.active destination:self]', options);
292
+ expect(node.roles.get('destination')).toEqual({ type: 'reference', value: 'self' });
293
+ });
294
+
295
+ it('does not recognize default references with custom set', () => {
296
+ const options: ParseExplicitOptions = {
297
+ referenceSet: new Set(['self']),
298
+ };
299
+ // 'me' is not in the custom set, so it becomes a literal (no dataType for unquoted values)
300
+ const node = parseExplicit('[add patient:.active destination:me]', options);
301
+ expect(node.roles.get('destination')).toEqual({
302
+ type: 'literal',
303
+ value: 'me',
304
+ });
305
+ });
306
+ });