@kernlang/core 3.3.9 → 3.4.1

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 (97) hide show
  1. package/dist/capability-matrix.d.ts +15 -0
  2. package/dist/capability-matrix.js +245 -0
  3. package/dist/capability-matrix.js.map +1 -0
  4. package/dist/codegen/body-ts.d.ts +68 -0
  5. package/dist/codegen/body-ts.js +214 -0
  6. package/dist/codegen/body-ts.js.map +1 -0
  7. package/dist/codegen/data-layer.d.ts +1 -1
  8. package/dist/codegen/data-layer.js +59 -23
  9. package/dist/codegen/data-layer.js.map +1 -1
  10. package/dist/codegen/events.js +1 -1
  11. package/dist/codegen/events.js.map +1 -1
  12. package/dist/codegen/functions.js +48 -7
  13. package/dist/codegen/functions.js.map +1 -1
  14. package/dist/codegen/ground-layer.js +10 -6
  15. package/dist/codegen/ground-layer.js.map +1 -1
  16. package/dist/codegen/helpers.d.ts +3 -1
  17. package/dist/codegen/helpers.js +5 -1
  18. package/dist/codegen/helpers.js.map +1 -1
  19. package/dist/codegen/kern-stdlib.d.ts +63 -0
  20. package/dist/codegen/kern-stdlib.js +160 -0
  21. package/dist/codegen/kern-stdlib.js.map +1 -0
  22. package/dist/codegen/machines.js +4 -3
  23. package/dist/codegen/machines.js.map +1 -1
  24. package/dist/codegen/modules.d.ts +1 -0
  25. package/dist/codegen/modules.js +52 -1
  26. package/dist/codegen/modules.js.map +1 -1
  27. package/dist/codegen/screens.js +31 -9
  28. package/dist/codegen/screens.js.map +1 -1
  29. package/dist/codegen/stdlib-preamble.d.ts +58 -0
  30. package/dist/codegen/stdlib-preamble.js +271 -0
  31. package/dist/codegen/stdlib-preamble.js.map +1 -0
  32. package/dist/codegen/type-system.d.ts +113 -1
  33. package/dist/codegen/type-system.js +404 -31
  34. package/dist/codegen/type-system.js.map +1 -1
  35. package/dist/codegen-core.d.ts +2 -2
  36. package/dist/codegen-core.js +65 -10
  37. package/dist/codegen-core.js.map +1 -1
  38. package/dist/codegen-expression.d.ts +11 -0
  39. package/dist/codegen-expression.js +199 -0
  40. package/dist/codegen-expression.js.map +1 -0
  41. package/dist/concepts.d.ts +3 -3
  42. package/dist/config.d.ts +16 -0
  43. package/dist/config.js +13 -0
  44. package/dist/config.js.map +1 -1
  45. package/dist/decompiler.js +575 -4
  46. package/dist/decompiler.js.map +1 -1
  47. package/dist/importer.d.ts +1 -0
  48. package/dist/importer.js +574 -34
  49. package/dist/importer.js.map +1 -1
  50. package/dist/index.d.ts +16 -3
  51. package/dist/index.js +19 -3
  52. package/dist/index.js.map +1 -1
  53. package/dist/node-props.d.ts +181 -1
  54. package/dist/node-props.js.map +1 -1
  55. package/dist/parser-core.d.ts +7 -1
  56. package/dist/parser-core.js +33 -7
  57. package/dist/parser-core.js.map +1 -1
  58. package/dist/parser-diagnostics.js +8 -0
  59. package/dist/parser-diagnostics.js.map +1 -1
  60. package/dist/parser-expression.d.ts +22 -0
  61. package/dist/parser-expression.js +774 -0
  62. package/dist/parser-expression.js.map +1 -0
  63. package/dist/parser-keywords.js +16 -0
  64. package/dist/parser-keywords.js.map +1 -1
  65. package/dist/parser-tokenizer.d.ts +5 -3
  66. package/dist/parser-tokenizer.js +97 -16
  67. package/dist/parser-tokenizer.js.map +1 -1
  68. package/dist/parser-validate-effects.d.ts +21 -0
  69. package/dist/parser-validate-effects.js +188 -0
  70. package/dist/parser-validate-effects.js.map +1 -0
  71. package/dist/parser-validate-expressions.d.ts +6 -0
  72. package/dist/parser-validate-expressions.js +41 -0
  73. package/dist/parser-validate-expressions.js.map +1 -0
  74. package/dist/parser-validate-propagation.d.ts +105 -0
  75. package/dist/parser-validate-propagation.js +684 -0
  76. package/dist/parser-validate-propagation.js.map +1 -0
  77. package/dist/parser-validate-union-kind.d.ts +24 -0
  78. package/dist/parser-validate-union-kind.js +97 -0
  79. package/dist/parser-validate-union-kind.js.map +1 -0
  80. package/dist/parser.d.ts +10 -3
  81. package/dist/parser.js +11 -5
  82. package/dist/parser.js.map +1 -1
  83. package/dist/schema.d.ts +1 -1
  84. package/dist/schema.js +562 -30
  85. package/dist/schema.js.map +1 -1
  86. package/dist/semantic-validator.js +24 -13
  87. package/dist/semantic-validator.js.map +1 -1
  88. package/dist/spec.d.ts +5 -2
  89. package/dist/spec.js +36 -2
  90. package/dist/spec.js.map +1 -1
  91. package/dist/types.d.ts +7 -1
  92. package/dist/types.js +7 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/value-ir.d.ts +96 -0
  95. package/dist/value-ir.js +25 -0
  96. package/dist/value-ir.js.map +1 -0
  97. package/package.json +1 -1
package/dist/schema.js CHANGED
@@ -29,24 +29,46 @@ export const NODE_SCHEMAS = {
29
29
  },
30
30
  },
31
31
  type: {
32
- description: 'TypeScript type alias — union of string literals or alias to another type',
32
+ description: 'TypeScript type alias — union of string literals, or alias to another type (including tuple types like [string, number]). Use generics="<T>" for parameterised aliases.',
33
33
  example: 'type name=Status values="active|inactive|banned"',
34
34
  props: {
35
35
  name: { required: true, kind: 'identifier' },
36
36
  values: { kind: 'string' },
37
37
  alias: { kind: 'rawExpr' },
38
+ generics: { kind: 'rawExpr' },
38
39
  export: { kind: 'boolean' },
39
40
  },
40
41
  },
41
42
  interface: {
42
- description: 'TypeScript interface with typed fields',
43
+ description: 'TypeScript interface with typed fields. Use generics="<T>" for parameterised interfaces.',
43
44
  example: 'interface name=User export=true\n field name=id type=string\n field name=email type=string',
44
45
  props: {
45
46
  name: { required: true, kind: 'identifier' },
46
47
  extends: { kind: 'typeAnnotation' },
48
+ generics: { kind: 'rawExpr' },
47
49
  export: { kind: 'boolean' },
48
50
  },
49
- allowedChildren: ['field'],
51
+ allowedChildren: ['field', 'indexer'],
52
+ },
53
+ indexer: {
54
+ description: 'Index signature for an interface — [keyName: keyType]: type',
55
+ example: 'indexer keyName=key keyType=string type=Value',
56
+ props: {
57
+ keyName: { kind: 'identifier' },
58
+ keyType: { required: true, kind: 'typeAnnotation' },
59
+ type: { required: true, kind: 'typeAnnotation' },
60
+ readonly: { kind: 'boolean' },
61
+ },
62
+ },
63
+ overload: {
64
+ description: 'Function overload signature — declared as a child of fn. Each overload emits a TS overload declaration before the implementation signature.',
65
+ example: 'overload params="a:number,b:number" returns=number',
66
+ props: {
67
+ params: { kind: 'string' },
68
+ returns: { kind: 'typeAnnotation' },
69
+ generics: { kind: 'rawExpr' },
70
+ },
71
+ allowedChildren: ['param'],
50
72
  },
51
73
  union: {
52
74
  description: 'Discriminated union type with variants, each having their own fields',
@@ -55,9 +77,33 @@ export const NODE_SCHEMAS = {
55
77
  name: { required: true, kind: 'identifier' },
56
78
  discriminant: { required: true, kind: 'identifier' },
57
79
  export: { kind: 'boolean' },
80
+ // Slice 4 — `kind=result|option` opts the union into the Result/Option
81
+ // shape (see docs/language/result-option-spec.md). Default (unspecified)
82
+ // is a regular discriminated union, identical to the slice 3 behaviour.
83
+ // Validated by parser-validate-union-kind.ts.
84
+ kind: { kind: 'string' },
58
85
  },
59
86
  allowedChildren: ['variant'],
60
87
  },
88
+ enum: {
89
+ description: 'TypeScript enum — numeric (auto-incremented) via values="A|B|C", or string-valued via member children',
90
+ example: 'enum name=Status values="Pending|Active|Done"',
91
+ props: {
92
+ name: { required: true, kind: 'identifier' },
93
+ values: { kind: 'string' },
94
+ const: { kind: 'boolean' },
95
+ export: { kind: 'boolean' },
96
+ },
97
+ allowedChildren: ['member'],
98
+ },
99
+ member: {
100
+ description: 'Enum member with explicit value (for string-valued or computed enums)',
101
+ example: 'member name=Up value="UP"',
102
+ props: {
103
+ name: { required: true, kind: 'identifier' },
104
+ value: { kind: 'expression' },
105
+ },
106
+ },
61
107
  variant: {
62
108
  description: 'A case within a discriminated union. Use name= for inline variants with fields, or type= to reference an existing interface.',
63
109
  example: 'variant name=circle\n field name=radius type=number',
@@ -74,6 +120,12 @@ export const NODE_SCHEMAS = {
74
120
  name: { required: true, kind: 'identifier' },
75
121
  type: { kind: 'typeAnnotation' },
76
122
  optional: { kind: 'boolean' },
123
+ // Slice 3b — `value` is the native ValueIR-canonicalised initializer;
124
+ // `default` remains as the rawExpr passthrough escape hatch. `value`
125
+ // takes precedence when both are set. Either marks the field as having
126
+ // an initializer (which makes the interface-side property optional in
127
+ // config emit).
128
+ value: { kind: 'expression' },
77
129
  default: { kind: 'rawExpr' },
78
130
  private: { kind: 'boolean' },
79
131
  readonly: { kind: 'boolean' },
@@ -86,6 +138,7 @@ export const NODE_SCHEMAS = {
86
138
  props: {
87
139
  name: { required: true, kind: 'identifier' },
88
140
  implements: { kind: 'typeAnnotation' },
141
+ generics: { kind: 'rawExpr' },
89
142
  export: { kind: 'boolean' },
90
143
  },
91
144
  allowedChildren: ['field', 'method', 'constructor', 'singleton', 'getter', 'setter'],
@@ -98,6 +151,7 @@ export const NODE_SCHEMAS = {
98
151
  extends: { kind: 'typeAnnotation' },
99
152
  implements: { kind: 'typeAnnotation' },
100
153
  abstract: { kind: 'boolean' },
154
+ generics: { kind: 'rawExpr' },
101
155
  export: { kind: 'boolean' },
102
156
  },
103
157
  allowedChildren: ['field', 'method', 'constructor', 'singleton', 'getter', 'setter'],
@@ -113,8 +167,9 @@ export const NODE_SCHEMAS = {
113
167
  stream: { kind: 'boolean' },
114
168
  private: { kind: 'boolean' },
115
169
  static: { kind: 'boolean' },
170
+ generics: { kind: 'rawExpr' },
116
171
  },
117
- allowedChildren: ['handler'],
172
+ allowedChildren: ['handler', 'param'],
118
173
  },
119
174
  getter: {
120
175
  description: 'A getter accessor within a class or service — emits `get name(): T { body }`.',
@@ -136,7 +191,7 @@ export const NODE_SCHEMAS = {
136
191
  private: { kind: 'boolean' },
137
192
  static: { kind: 'boolean' },
138
193
  },
139
- allowedChildren: ['handler'],
194
+ allowedChildren: ['handler', 'param'],
140
195
  },
141
196
  fn: {
142
197
  description: 'Standalone function — the most common code unit in KERN',
@@ -149,8 +204,12 @@ export const NODE_SCHEMAS = {
149
204
  stream: { kind: 'boolean' },
150
205
  export: { kind: 'boolean' },
151
206
  expr: { kind: 'rawExpr' },
207
+ generics: { kind: 'rawExpr' },
208
+ // Slice 6 — effects=pure declares the body has no observable side effects.
209
+ // Validated by parser-validate-effects.ts; see docs/language/effects-pure-spec.md.
210
+ effects: { kind: 'string' },
152
211
  },
153
- allowedChildren: ['handler', 'signal', 'cleanup'],
212
+ allowedChildren: ['handler', 'signal', 'cleanup', 'overload', 'param'],
154
213
  },
155
214
  machine: {
156
215
  description: 'State machine with states and guarded transitions — 12 lines of KERN generates 140+ lines of TypeScript',
@@ -193,7 +252,7 @@ export const NODE_SCHEMAS = {
193
252
  params: { kind: 'string' },
194
253
  guard: { kind: 'rawExpr' },
195
254
  },
196
- allowedChildren: ['handler'],
255
+ allowedChildren: ['handler', 'param'],
197
256
  },
198
257
  error: {
199
258
  description: 'Custom error class extending a base error, with typed fields',
@@ -231,8 +290,10 @@ export const NODE_SCHEMAS = {
231
290
  example: 'test name="AuthService"\n describe name="login"\n it name="rejects invalid email"',
232
291
  props: {
233
292
  name: { required: true, kind: 'string' },
293
+ target: { kind: 'string' },
294
+ coverage: { kind: 'boolean' },
234
295
  },
235
- allowedChildren: ['describe', 'it', 'handler'],
296
+ allowedChildren: ['describe', 'it', 'expect', 'fixture', 'mock', 'handler'],
236
297
  },
237
298
  event: {
238
299
  description: 'Typed event with payload type children',
@@ -253,17 +314,101 @@ export const NODE_SCHEMAS = {
253
314
  types: { kind: 'boolean' },
254
315
  },
255
316
  },
317
+ use: {
318
+ description: 'Cross-`.kern` symbol resolution. Parent of `from` children — one per imported binding. Compositional shape mirrors enum/member, class/method.',
319
+ example: 'use path="./helper.kern"\n from name=foo\n from name=bar as=baz',
320
+ props: {
321
+ path: { required: true, kind: 'importPath' },
322
+ },
323
+ allowedChildren: ['from'],
324
+ },
325
+ from: {
326
+ description: 'Single binding in a `use` block. `as=` aliases the local name; `export=true` re-exports.',
327
+ example: 'from name=foo as=bar export=true',
328
+ props: {
329
+ name: { required: true, kind: 'identifier' },
330
+ as: { kind: 'identifier' },
331
+ export: { kind: 'boolean' },
332
+ },
333
+ },
256
334
  const: {
257
335
  description: 'Constant declaration with optional type and value or handler body',
258
336
  example: 'const name=MAX_RETRIES type=number value=3 export=true',
259
337
  props: {
260
338
  name: { required: true, kind: 'identifier' },
261
339
  type: { kind: 'typeAnnotation' },
262
- value: { kind: 'rawExpr' },
340
+ value: { kind: 'expression' },
263
341
  export: { kind: 'boolean' },
264
342
  },
265
343
  allowedChildren: ['handler'],
266
344
  },
345
+ destructure: {
346
+ description: 'Native destructuring statement — emits `const {a,b} = expr;` (object pattern with `binding` children) or `const [x,y] = expr;` (array pattern with `element` children). For complex patterns (rest `...`, defaults `=v`, nested `{a:{b}}`), use the `expr={{...}}` escape hatch which carries the raw TS statement verbatim. Slice 3d.',
347
+ example: 'destructure kind=const source=user\n binding name=id\n binding name=email key=mail',
348
+ props: {
349
+ kind: { kind: 'string' },
350
+ source: { kind: 'expression' },
351
+ type: { kind: 'typeAnnotation' },
352
+ export: { kind: 'boolean' },
353
+ expr: { kind: 'rawExpr' },
354
+ },
355
+ allowedChildren: ['binding', 'element'],
356
+ },
357
+ binding: {
358
+ description: 'Object-destructuring binding inside a `destructure` parent. `name` is the local binding; `key` is the optional property key when renaming, e.g. `{a: foo}` → `binding name=foo key=a`. Slice 3d.',
359
+ example: 'binding name=foo key=a',
360
+ props: {
361
+ name: { required: true, kind: 'identifier' },
362
+ key: { kind: 'identifier' },
363
+ },
364
+ },
365
+ element: {
366
+ description: 'Array-destructuring element inside a `destructure` parent. `index` is the ordered position (zero-based). Slice 3d.',
367
+ example: 'element name=first index=0',
368
+ props: {
369
+ name: { required: true, kind: 'identifier' },
370
+ index: { kind: 'string' },
371
+ },
372
+ },
373
+ mapLit: {
374
+ description: 'Native Map<K,V> literal — emits `const name: Type = new Map([[k1, v1], [k2, v2]]);` from `mapEntry` children. For complex shapes (computed keys, conditional entries, spread), use the `expr={{...}}` escape hatch which carries the raw TS statement verbatim. Slice 3e.',
375
+ example: 'mapLit name=cache type="Map<string, number>"\n mapEntry key="foo" value=1\n mapEntry key="bar" value=2',
376
+ props: {
377
+ name: { required: true, kind: 'identifier' },
378
+ type: { kind: 'typeAnnotation' },
379
+ kind: { kind: 'string' },
380
+ export: { kind: 'boolean' },
381
+ expr: { kind: 'rawExpr' },
382
+ },
383
+ allowedChildren: ['mapEntry'],
384
+ },
385
+ mapEntry: {
386
+ description: 'Map-literal entry inside a `mapLit` parent. `key` and `value` are both expression-typed and ValueIR-canonicalised. Slice 3e.',
387
+ example: 'mapEntry key="foo" value=1',
388
+ props: {
389
+ key: { required: true, kind: 'expression' },
390
+ value: { required: true, kind: 'expression' },
391
+ },
392
+ },
393
+ setLit: {
394
+ description: 'Native Set<T> literal — emits `const name: Type = new Set([v1, v2]);` from `setItem` children. For complex shapes (conditional members, spread), use the `expr={{...}}` escape hatch which carries the raw TS statement verbatim. Slice 3e.',
395
+ example: 'setLit name=allowed type="Set<string>"\n setItem value="admin"\n setItem value="user"',
396
+ props: {
397
+ name: { required: true, kind: 'identifier' },
398
+ type: { kind: 'typeAnnotation' },
399
+ kind: { kind: 'string' },
400
+ export: { kind: 'boolean' },
401
+ expr: { kind: 'rawExpr' },
402
+ },
403
+ allowedChildren: ['setItem'],
404
+ },
405
+ setItem: {
406
+ description: 'Set-literal item inside a `setLit` parent. `value` is expression-typed and ValueIR-canonicalised. Slice 3e.',
407
+ example: 'setItem value="admin"',
408
+ props: {
409
+ value: { required: true, kind: 'expression' },
410
+ },
411
+ },
267
412
  on: {
268
413
  description: 'Event listener — binds a handler to a named event',
269
414
  example: 'on event=click handler=handleClick',
@@ -293,6 +438,8 @@ export const NODE_SCHEMAS = {
293
438
  expr: { required: true, kind: 'rawExpr' },
294
439
  type: { kind: 'typeAnnotation' },
295
440
  export: { kind: 'boolean' },
441
+ // Slice 6 — see fn schema above.
442
+ effects: { kind: 'string' },
296
443
  },
297
444
  },
298
445
  fmt: {
@@ -858,7 +1005,7 @@ export const NODE_SCHEMAS = {
858
1005
  reversible: { kind: 'boolean' },
859
1006
  export: { kind: 'boolean' },
860
1007
  },
861
- allowedChildren: ['handler'],
1008
+ allowedChildren: ['handler', 'param'],
862
1009
  },
863
1010
  actionRegistry: {
864
1011
  description: 'Calls an imported registration function with a map of string-keyed async action handlers. Emits `target({ key: async (...) => body, ... })` directly — no IIFE wrapper.',
@@ -878,6 +1025,9 @@ export const NODE_SCHEMAS = {
878
1025
  confidence: { kind: 'number' },
879
1026
  kind: { kind: 'identifier' },
880
1027
  type: { kind: 'identifier' },
1028
+ covers: { kind: 'string' },
1029
+ over: { kind: 'identifier' },
1030
+ union: { kind: 'identifier' },
881
1031
  param: { kind: 'identifier' },
882
1032
  field: { kind: 'identifier' },
883
1033
  target: { kind: 'identifier' },
@@ -936,11 +1086,12 @@ export const NODE_SCHEMAS = {
936
1086
  // separately via the `let-must-be-inside-each` semantic rule.
937
1087
  },
938
1088
  let: {
939
- description: 'Iteration-scoped binding — emits a plain `const` inside the containing `each` callback. Use for values that depend on the iteration variable or index. Unlike `derive` (which compiles to `useMemo` and violates Rules of Hooks inside `.map`), `let` is hook-safe by construction.',
940
- example: 'let name=idx expr="start + i"',
1089
+ description: 'Iteration-scoped binding — emits a plain `const` inside the containing `each` callback. Use for values that depend on the iteration variable or index. Unlike `derive` (which compiles to `useMemo` and violates Rules of Hooks inside `.map`), `let` is hook-safe by construction. Provide either `value=` (native expression form, ValueIR-canonicalised — slice 3a) or `expr=` (raw passthrough escape hatch).',
1090
+ example: 'let name=idx value=i+1',
941
1091
  props: {
942
1092
  name: { required: true, kind: 'identifier' },
943
- expr: { required: true, kind: 'rawExpr' },
1093
+ value: { kind: 'expression' },
1094
+ expr: { kind: 'rawExpr' },
944
1095
  type: { kind: 'typeAnnotation' },
945
1096
  },
946
1097
  },
@@ -1041,7 +1192,7 @@ export const NODE_SCHEMAS = {
1041
1192
  params: { kind: 'string' },
1042
1193
  returns: { kind: 'typeAnnotation' },
1043
1194
  },
1044
- allowedChildren: ['handler', 'memo', 'callback', 'ref', 'effect'],
1195
+ allowedChildren: ['handler', 'memo', 'callback', 'ref', 'effect', 'param'],
1045
1196
  },
1046
1197
  effect: {
1047
1198
  description: 'React useEffect — side effect with dependency tracking',
@@ -1051,7 +1202,7 @@ export const NODE_SCHEMAS = {
1051
1202
  deps: { kind: 'string' },
1052
1203
  once: { kind: 'boolean' },
1053
1204
  },
1054
- allowedChildren: ['prop', 'handler', 'cleanup'],
1205
+ allowedChildren: ['prop', 'handler', 'cleanup', 'trigger', 'recover'],
1055
1206
  },
1056
1207
  // ── Web / UI node types ──────────────────────────────────────────────
1057
1208
  page: {
@@ -1318,7 +1469,22 @@ export const NODE_SCHEMAS = {
1318
1469
  props: {
1319
1470
  name: { required: true, kind: 'identifier' },
1320
1471
  },
1321
- allowedChildren: ['param', 'handler', 'description', 'guard', 'sampling', 'elicitation'],
1472
+ allowedChildren: [
1473
+ 'param',
1474
+ 'handler',
1475
+ 'description',
1476
+ 'guard',
1477
+ 'sampling',
1478
+ 'elicitation',
1479
+ 'derive',
1480
+ 'effect',
1481
+ 'respond',
1482
+ 'branch',
1483
+ 'each',
1484
+ 'collect',
1485
+ 'destructure',
1486
+ 'partition',
1487
+ ],
1322
1488
  },
1323
1489
  resource: {
1324
1490
  description: 'MCP resource — a data source exposed to AI agents via URI. Use {variables} for templated URIs.',
@@ -1330,18 +1496,42 @@ export const NODE_SCHEMAS = {
1330
1496
  allowedChildren: ['param', 'handler', 'description', 'guard'],
1331
1497
  },
1332
1498
  param: {
1333
- description: 'Parameter definition for a tool, resource, or promptname, type, required, default, and description',
1499
+ description: 'Parameter definition. Used in two contexts: (a) MCP tool/resource/prompt params (description/required/min/max apply); (b) fn/method/constructor/etc. parameter defaults via slice 3c value flows through ValueIR canonicalisation (mirrors slice 1j const.value, 3a let.value, 3b field.value).',
1334
1500
  example: 'param name=query type=string required=true description="Search query"',
1335
1501
  props: {
1336
- name: { required: true, kind: 'identifier' },
1337
- type: { kind: 'identifier' },
1502
+ // Slice 3c-extension #3: `name` is required UNLESS the param carries
1503
+ // `binding`/`element` destructure children — destructured params encode
1504
+ // the LHS pattern in the children, not in `name`. The required-OR-children
1505
+ // invariant lives in `checkCrossProps` so `validateSchema` accepts both
1506
+ // forms. Keeping the schema-level `required: true` here would reject the
1507
+ // canonical destructured form emitted by the importer.
1508
+ name: { kind: 'identifier' },
1509
+ type: { kind: 'typeAnnotation' },
1510
+ value: { kind: 'expression' },
1338
1511
  required: { kind: 'boolean' },
1512
+ // Slice 3c-extension: TS-style optional `?` on the LHS, distinct from MCP
1513
+ // `required`. When `optional=true`, codegen emits `name?: type` (with the
1514
+ // `?` inside the parameter list) so callers may omit the argument.
1515
+ optional: { kind: 'boolean' },
1516
+ // Slice 3c-extension: TS-style variadic `...rest`. When `variadic=true`,
1517
+ // codegen prepends `...` to the parameter name; the type should be an
1518
+ // array (e.g. `string[]`). Variadic params can't have defaults — that's
1519
+ // user error and TS will surface it at the call site.
1520
+ variadic: { kind: 'boolean' },
1339
1521
  default: { kind: 'rawExpr' },
1340
1522
  description: { kind: 'string' },
1341
1523
  min: { kind: 'number' },
1342
1524
  max: { kind: 'number' },
1343
1525
  },
1344
- allowedChildren: ['guard', 'description'],
1526
+ // Slice 3c-extension #3: TS-style destructured params via slice 3d's
1527
+ // `binding` (object pattern) / `element` (array pattern) children. When
1528
+ // present, codegen uses the pattern as the LHS instead of `name`, e.g.
1529
+ // param type="Point"
1530
+ // binding name=x
1531
+ // binding name=y
1532
+ // → `{x, y}: Point`. Same node types as slice 3d destructure — no new
1533
+ // node types needed. `name=` is omitted on destructured params.
1534
+ allowedChildren: ['guard', 'description', 'binding', 'element'],
1345
1535
  },
1346
1536
  prompt: {
1347
1537
  description: 'MCP prompt template — a reusable system prompt exposed to AI agents',
@@ -1508,6 +1698,10 @@ export const NODE_SCHEMAS = {
1508
1698
  kind: { kind: 'identifier' },
1509
1699
  on: { kind: 'string' },
1510
1700
  from: { kind: 'string' },
1701
+ expr: { kind: 'rawExpr' },
1702
+ query: { kind: 'string' },
1703
+ url: { kind: 'string' },
1704
+ call: { kind: 'rawExpr' },
1511
1705
  },
1512
1706
  allowedChildren: ['handler'],
1513
1707
  },
@@ -1595,6 +1789,8 @@ export const NODE_SCHEMAS = {
1595
1789
  props: {
1596
1790
  name: { required: true, kind: 'identifier' },
1597
1791
  deps: { kind: 'string' },
1792
+ // Slice 6 — see fn schema above.
1793
+ effects: { kind: 'string' },
1598
1794
  },
1599
1795
  allowedChildren: ['handler'],
1600
1796
  },
@@ -1607,7 +1803,7 @@ export const NODE_SCHEMAS = {
1607
1803
  deps: { kind: 'string' },
1608
1804
  async: { kind: 'boolean' },
1609
1805
  },
1610
- allowedChildren: ['handler'],
1806
+ allowedChildren: ['handler', 'param'],
1611
1807
  },
1612
1808
  ref: {
1613
1809
  description: 'React useRef — mutable ref object that persists across renders',
@@ -1961,8 +2157,11 @@ export const NODE_SCHEMAS = {
1961
2157
  constructor: {
1962
2158
  description: 'Constructor for a service — runs on instantiation',
1963
2159
  example: 'constructor params="size:number"\n handler <<<\n this.data = new Map();\n >>>',
1964
- props: { params: { kind: 'string' } },
1965
- allowedChildren: ['handler'],
2160
+ props: {
2161
+ params: { kind: 'string' },
2162
+ generics: { kind: 'rawExpr' },
2163
+ },
2164
+ allowedChildren: ['handler', 'param'],
1966
2165
  },
1967
2166
  cleanup: {
1968
2167
  description: 'Cleanup handler — runs on teardown (useEffect return, signal dispose)',
@@ -1984,13 +2183,13 @@ export const NODE_SCHEMAS = {
1984
2183
  description: 'Test suite — groups related test cases',
1985
2184
  example: 'describe name="UserService"\n it name="creates a user"\n handler <<<\n expect(createUser()).toBeDefined();\n >>>',
1986
2185
  props: { name: { required: true, kind: 'string' } },
1987
- allowedChildren: ['it', 'describe', 'handler'],
2186
+ allowedChildren: ['it', 'describe', 'expect', 'fixture', 'mock', 'handler'],
1988
2187
  },
1989
2188
  it: {
1990
2189
  description: 'Test case — single test assertion',
1991
2190
  example: 'it name="returns 200 on success"\n handler <<<\n expect(res.status).toBe(200);\n >>>',
1992
2191
  props: { name: { required: true, kind: 'string' } },
1993
- allowedChildren: ['handler'],
2192
+ allowedChildren: ['expect', 'case', 'fixture', 'mock', 'handler'],
1994
2193
  },
1995
2194
  // Ground layer — semantic reasoning
1996
2195
  path: {
@@ -2028,14 +2227,110 @@ export const NODE_SCHEMAS = {
2028
2227
  props: { fn: { kind: 'identifier' }, to: { kind: 'rawExpr' } },
2029
2228
  },
2030
2229
  expect: {
2031
- description: 'Assertion — declare an expected condition at runtime',
2032
- example: 'expect expr={{items.length > 0}} message="Items must not be empty"',
2033
- props: { expr: { required: true, kind: 'rawExpr' }, message: { kind: 'string' } },
2230
+ description: 'Assertion — declare an expected runtime condition or KERN structural invariant',
2231
+ example: 'expect expr={{items.length > 0}} message="Items must not be empty"\nexpect route="GET /api/users" with={{({ query: { role: "admin" } })}} returns={{adminUsers}}\nexpect machine=Order reaches=paid via=confirm,capture\nexpect machine=Order transition=capture from=confirmed to=paid\nexpect node=interface name=User child=field count=3\nexpect no=deriveCycles',
2232
+ props: {
2233
+ expr: { kind: 'rawExpr' },
2234
+ fn: { kind: 'identifier' },
2235
+ derive: { kind: 'identifier' },
2236
+ route: { kind: 'string' },
2237
+ tool: { kind: 'identifier' },
2238
+ effect: { kind: 'identifier' },
2239
+ mock: { kind: 'identifier' },
2240
+ called: { kind: 'number' },
2241
+ import: { kind: 'string' },
2242
+ source: { kind: 'string' },
2243
+ unmapped: { kind: 'number' },
2244
+ allowWarnings: { kind: 'boolean' },
2245
+ args: { kind: 'rawExpr' },
2246
+ with: { kind: 'rawExpr' },
2247
+ input: { kind: 'rawExpr' },
2248
+ equals: { kind: 'rawExpr' },
2249
+ returns: { kind: 'rawExpr' },
2250
+ recovers: { kind: 'boolean' },
2251
+ fallback: { kind: 'rawExpr' },
2252
+ codegen: { kind: 'boolean' },
2253
+ decompile: { kind: 'boolean' },
2254
+ roundtrip: { kind: 'boolean' },
2255
+ contains: { kind: 'string' },
2256
+ notContains: { kind: 'string' },
2257
+ matches: { kind: 'string' },
2258
+ throws: { kind: 'string' },
2259
+ message: { kind: 'string' },
2260
+ preset: { kind: 'identifier' },
2261
+ severity: { kind: 'identifier' },
2262
+ node: { kind: 'identifier' },
2263
+ name: { kind: 'string' },
2264
+ within: { kind: 'string' },
2265
+ child: { kind: 'identifier' },
2266
+ childName: { kind: 'string' },
2267
+ prop: { kind: 'identifier' },
2268
+ is: { kind: 'string' },
2269
+ count: { kind: 'number' },
2270
+ machine: { kind: 'identifier' },
2271
+ transition: { kind: 'identifier' },
2272
+ from: { kind: 'identifier' },
2273
+ to: { kind: 'identifier' },
2274
+ guarded: { kind: 'boolean' },
2275
+ reaches: { kind: 'identifier' },
2276
+ through: { kind: 'string' },
2277
+ avoid: { kind: 'string' },
2278
+ avoids: { kind: 'string' },
2279
+ maxSteps: { kind: 'number' },
2280
+ via: { kind: 'string' },
2281
+ no: { kind: 'identifier' },
2282
+ guard: { kind: 'identifier' },
2283
+ exhaustive: { kind: 'boolean' },
2284
+ over: { kind: 'identifier' },
2285
+ union: { kind: 'identifier' },
2286
+ covers: { kind: 'string' },
2287
+ },
2288
+ },
2289
+ case: {
2290
+ description: 'Native test table row — supplies input and expected output for a parent expect assertion',
2291
+ example: 'case name=admin with={{({ query: { role: "admin" } })}} returns={{adminUsers}}\nexpect route="GET /api/users"',
2292
+ props: {
2293
+ name: { kind: 'string' },
2294
+ args: { kind: 'rawExpr' },
2295
+ with: { kind: 'rawExpr' },
2296
+ input: { kind: 'rawExpr' },
2297
+ equals: { kind: 'rawExpr' },
2298
+ returns: { kind: 'rawExpr' },
2299
+ recovers: { kind: 'boolean' },
2300
+ fallback: { kind: 'rawExpr' },
2301
+ matches: { kind: 'string' },
2302
+ throws: { kind: 'string' },
2303
+ called: { kind: 'number' },
2304
+ message: { kind: 'string' },
2305
+ severity: { kind: 'identifier' },
2306
+ },
2307
+ allowedChildren: ['fixture', 'mock'],
2308
+ },
2309
+ fixture: {
2310
+ description: 'Native test fixture — named runtime data available to scoped expect assertions',
2311
+ example: 'fixture name=paidOrder value={{({ id: "ord_1", status: "paid" })}}',
2312
+ props: {
2313
+ name: { required: true, kind: 'identifier' },
2314
+ value: { kind: 'rawExpr' },
2315
+ expr: { kind: 'rawExpr' },
2316
+ },
2317
+ },
2318
+ mock: {
2319
+ description: 'Native test mock — replaces a scoped KERN effect with deterministic runtime data',
2320
+ example: 'mock effect=fetchUsers returns={{users}}\nmock effect=fetchUsers throws=NetworkError',
2321
+ props: {
2322
+ effect: { required: true, kind: 'identifier' },
2323
+ returns: { kind: 'rawExpr' },
2324
+ throws: { kind: 'string' },
2325
+ },
2034
2326
  },
2035
2327
  recover: {
2036
2328
  description: 'Recovery handler — runs when a parent node fails',
2037
2329
  example: 'recover\n handler <<<\n return fallbackValue;\n >>>',
2038
- props: {},
2330
+ props: {
2331
+ retry: { kind: 'number' },
2332
+ fallback: { kind: 'rawExpr' },
2333
+ },
2039
2334
  allowedChildren: ['handler'],
2040
2335
  },
2041
2336
  strategy: {
@@ -2124,6 +2419,232 @@ function checkCrossProps(node, violations) {
2124
2419
  col: node.loc?.col,
2125
2420
  });
2126
2421
  }
2422
+ if (node.type === 'param') {
2423
+ // Slice 3c-extension #3: `param` requires `name=` UNLESS it carries
2424
+ // `binding`/`element` destructure children — destructured params encode
2425
+ // the LHS pattern in the children. Replaces the old prop-level
2426
+ // `required: true` constraint which rejected the canonical destructured
2427
+ // form emitted by importer/decompiler.
2428
+ const hasName = 'name' in props;
2429
+ const hasDestructure = (node.children ?? []).some((c) => c.type === 'binding' || c.type === 'element');
2430
+ if (!hasName && !hasDestructure) {
2431
+ violations.push({
2432
+ nodeType: 'param',
2433
+ message: "'param' requires either 'name' or destructure children ('binding'/'element')",
2434
+ line: node.loc?.line,
2435
+ col: node.loc?.col,
2436
+ });
2437
+ }
2438
+ }
2439
+ if (node.type === 'expect') {
2440
+ const hasRuntimeAssertion = 'expr' in props;
2441
+ const hasRuntimeBehavior = 'fn' in props || 'derive' in props;
2442
+ const hasRuntimeWorkflow = 'route' in props || 'tool' in props || 'effect' in props;
2443
+ const hasMockCallAssertion = 'mock' in props || 'called' in props;
2444
+ const hasCodegenAssertion = 'codegen' in props;
2445
+ const hasDecompileAssertion = 'decompile' in props;
2446
+ const hasImportAssertion = 'import' in props;
2447
+ const hasRoundtripAssertion = 'roundtrip' in props;
2448
+ const hasPreset = 'preset' in props;
2449
+ const hasNodeShape = 'node' in props;
2450
+ const hasNegativeInvariant = 'no' in props;
2451
+ const hasPositiveInvariant = 'has' in props;
2452
+ const hasGuardExhaustiveness = 'guard' in props;
2453
+ const hasMachineTransition = 'transition' in props;
2454
+ const hasMachineReachability = 'reaches' in props ||
2455
+ ('machine' in props && !hasNegativeInvariant && !hasPositiveInvariant && !hasMachineTransition);
2456
+ if (!hasRuntimeAssertion &&
2457
+ !hasRuntimeBehavior &&
2458
+ !hasRuntimeWorkflow &&
2459
+ !hasMockCallAssertion &&
2460
+ !hasCodegenAssertion &&
2461
+ !hasDecompileAssertion &&
2462
+ !hasImportAssertion &&
2463
+ !hasRoundtripAssertion &&
2464
+ !hasPreset &&
2465
+ !hasNodeShape &&
2466
+ !hasMachineTransition &&
2467
+ !hasMachineReachability &&
2468
+ !hasNegativeInvariant &&
2469
+ !hasPositiveInvariant &&
2470
+ !hasGuardExhaustiveness) {
2471
+ violations.push({
2472
+ nodeType: 'expect',
2473
+ message: "'expect' requires 'expr', 'fn', 'derive', 'route', 'tool', 'effect', 'mock' call count, 'codegen', 'decompile', 'import', 'roundtrip', 'preset', 'node', 'machine' reachability, machine transition, 'no', 'has', or 'guard'",
2474
+ line: node.loc?.line,
2475
+ col: node.loc?.col,
2476
+ });
2477
+ }
2478
+ if (hasCodegenAssertion && !('contains' in props) && !('notContains' in props) && !('matches' in props)) {
2479
+ violations.push({
2480
+ nodeType: 'expect',
2481
+ message: "'expect codegen' requires contains=, notContains=, or matches=",
2482
+ line: node.loc?.line,
2483
+ col: node.loc?.col,
2484
+ });
2485
+ }
2486
+ if (hasDecompileAssertion && !('contains' in props) && !('notContains' in props) && !('matches' in props)) {
2487
+ violations.push({
2488
+ nodeType: 'expect',
2489
+ message: "'expect decompile' requires contains=, notContains=, or matches=",
2490
+ line: node.loc?.line,
2491
+ col: node.loc?.col,
2492
+ });
2493
+ }
2494
+ if (hasImportAssertion) {
2495
+ const importValue = props.import === undefined ? '' : String(props.import);
2496
+ const hasPath = (importValue !== '' && importValue !== 'true') || 'from' in props || 'source' in props;
2497
+ const hasTextAssertion = 'contains' in props || 'notContains' in props || 'matches' in props;
2498
+ const hasUnmappedAssertion = 'unmapped' in props || props.no === 'unmapped';
2499
+ if (!hasPath) {
2500
+ violations.push({
2501
+ nodeType: 'expect',
2502
+ message: "'expect import' requires import=<ts-file>, from=<ts-file>, or source=<typescript>",
2503
+ line: node.loc?.line,
2504
+ col: node.loc?.col,
2505
+ });
2506
+ }
2507
+ if (!hasTextAssertion && !hasRoundtripAssertion && !hasUnmappedAssertion) {
2508
+ violations.push({
2509
+ nodeType: 'expect',
2510
+ message: "'expect import' requires contains=, notContains=, matches=, roundtrip=true, unmapped=<count>, or no=unmapped",
2511
+ line: node.loc?.line,
2512
+ col: node.loc?.col,
2513
+ });
2514
+ }
2515
+ }
2516
+ if (hasNegativeInvariant && hasPositiveInvariant) {
2517
+ violations.push({
2518
+ nodeType: 'expect',
2519
+ message: "'expect' cannot combine no=<invariant> and has=<invariant>",
2520
+ line: node.loc?.line,
2521
+ col: node.loc?.col,
2522
+ });
2523
+ }
2524
+ if (Number('fn' in props) +
2525
+ Number('derive' in props) +
2526
+ Number('route' in props) +
2527
+ Number('tool' in props) +
2528
+ Number('effect' in props) +
2529
+ Number('mock' in props) >
2530
+ 1) {
2531
+ violations.push({
2532
+ nodeType: 'expect',
2533
+ message: "'expect' cannot combine fn=<name>, derive=<name>, route=<spec>, tool=<name>, effect=<name>, and mock=<name>",
2534
+ line: node.loc?.line,
2535
+ col: node.loc?.col,
2536
+ });
2537
+ }
2538
+ if ((hasRuntimeBehavior || hasRuntimeWorkflow || hasMockCallAssertion) && hasRuntimeAssertion) {
2539
+ violations.push({
2540
+ nodeType: 'expect',
2541
+ message: "'expect' cannot combine fn/derive/route/tool/effect/mock behavioral assertions with expr={{...}}",
2542
+ line: node.loc?.line,
2543
+ col: node.loc?.col,
2544
+ });
2545
+ }
2546
+ if (hasMockCallAssertion && (!('mock' in props) || !('called' in props))) {
2547
+ violations.push({
2548
+ nodeType: 'expect',
2549
+ message: "'expect' mock call assertions require both mock=<effect> and called=<count>",
2550
+ line: node.loc?.line,
2551
+ col: node.loc?.col,
2552
+ });
2553
+ }
2554
+ if ('called' in props) {
2555
+ const rawCalled = props.called;
2556
+ const called = Number(props.called);
2557
+ if (rawCalled === '' || !Number.isInteger(called) || called < 0) {
2558
+ violations.push({
2559
+ nodeType: 'expect',
2560
+ message: "'expect' mock call assertions require called=<non-negative integer>",
2561
+ line: node.loc?.line,
2562
+ col: node.loc?.col,
2563
+ });
2564
+ }
2565
+ }
2566
+ if (hasMockCallAssertion &&
2567
+ ['args', 'with', 'input', 'equals', 'returns', 'recovers', 'fallback', 'matches', 'throws'].some((prop) => prop in props)) {
2568
+ violations.push({
2569
+ nodeType: 'expect',
2570
+ message: "'expect' mock call assertions cannot combine with runtime value/result props",
2571
+ line: node.loc?.line,
2572
+ col: node.loc?.col,
2573
+ });
2574
+ }
2575
+ if (hasMachineTransition && !('machine' in props)) {
2576
+ violations.push({
2577
+ nodeType: 'expect',
2578
+ message: "'expect' machine transition assertions require machine=<name>",
2579
+ line: node.loc?.line,
2580
+ col: node.loc?.col,
2581
+ });
2582
+ }
2583
+ if (hasMachineTransition && 'reaches' in props) {
2584
+ violations.push({
2585
+ nodeType: 'expect',
2586
+ message: "'expect' cannot combine machine transition assertions with reaches=<state>",
2587
+ line: node.loc?.line,
2588
+ col: node.loc?.col,
2589
+ });
2590
+ }
2591
+ if (hasMachineReachability && (!('machine' in props) || !('reaches' in props))) {
2592
+ violations.push({
2593
+ nodeType: 'expect',
2594
+ message: "'expect' machine reachability requires both 'machine' and 'reaches'",
2595
+ line: node.loc?.line,
2596
+ col: node.loc?.col,
2597
+ });
2598
+ }
2599
+ if (hasGuardExhaustiveness && props.exhaustive !== true && props.exhaustive !== 'true') {
2600
+ violations.push({
2601
+ nodeType: 'expect',
2602
+ message: "'expect' guard assertions require exhaustive=true",
2603
+ line: node.loc?.line,
2604
+ col: node.loc?.col,
2605
+ });
2606
+ }
2607
+ }
2608
+ if (node.type === 'fixture') {
2609
+ const hasValue = 'value' in props;
2610
+ const hasExpr = 'expr' in props;
2611
+ if (!hasValue && !hasExpr) {
2612
+ violations.push({
2613
+ nodeType: 'fixture',
2614
+ message: "'fixture' requires either value={{...}} or expr={{...}}",
2615
+ line: node.loc?.line,
2616
+ col: node.loc?.col,
2617
+ });
2618
+ }
2619
+ if (hasValue && hasExpr) {
2620
+ violations.push({
2621
+ nodeType: 'fixture',
2622
+ message: "'fixture' must not combine value={{...}} and expr={{...}}",
2623
+ line: node.loc?.line,
2624
+ col: node.loc?.col,
2625
+ });
2626
+ }
2627
+ }
2628
+ if (node.type === 'mock') {
2629
+ const hasReturns = 'returns' in props;
2630
+ const hasThrows = 'throws' in props;
2631
+ if (!hasReturns && !hasThrows) {
2632
+ violations.push({
2633
+ nodeType: 'mock',
2634
+ message: "'mock' requires either returns={{...}} or throws=<ErrorName>",
2635
+ line: node.loc?.line,
2636
+ col: node.loc?.col,
2637
+ });
2638
+ }
2639
+ if (hasReturns && hasThrows) {
2640
+ violations.push({
2641
+ nodeType: 'mock',
2642
+ message: "'mock' must not combine returns={{...}} and throws=<ErrorName>",
2643
+ line: node.loc?.line,
2644
+ col: node.loc?.col,
2645
+ });
2646
+ }
2647
+ }
2127
2648
  if (node.type === 'fmt') {
2128
2649
  const returnMode = isTruthyProp(props.return);
2129
2650
  if (returnMode && 'name' in props) {
@@ -2191,7 +2712,18 @@ export function exportSchemaJSON(runtime) {
2191
2712
  styleShorthands: { ...STYLE_SHORTHANDS },
2192
2713
  valueShorthands: { ...VALUE_SHORTHANDS },
2193
2714
  multilineBlockTypes: [...rt.multilineBlockTypes],
2194
- propKinds: ['identifier', 'typeAnnotation', 'importPath', 'rawExpr', 'rawBlock', 'string', 'boolean', 'number'],
2715
+ propKinds: [
2716
+ 'identifier',
2717
+ 'typeAnnotation',
2718
+ 'importPath',
2719
+ 'rawExpr',
2720
+ 'rawBlock',
2721
+ 'string',
2722
+ 'boolean',
2723
+ 'number',
2724
+ 'expression',
2725
+ 'regex',
2726
+ ],
2195
2727
  ...(evolvedTypes.length > 0 ? { evolvedTypes } : {}),
2196
2728
  };
2197
2729
  }