@kernlang/core 3.3.8 → 3.4.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 (89) 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/data-layer.d.ts +1 -1
  5. package/dist/codegen/data-layer.js +59 -23
  6. package/dist/codegen/data-layer.js.map +1 -1
  7. package/dist/codegen/emitters.d.ts +8 -0
  8. package/dist/codegen/emitters.js +10 -0
  9. package/dist/codegen/emitters.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 +29 -5
  13. package/dist/codegen/functions.js.map +1 -1
  14. package/dist/codegen/ground-layer.d.ts +1 -0
  15. package/dist/codegen/ground-layer.js +88 -10
  16. package/dist/codegen/ground-layer.js.map +1 -1
  17. package/dist/codegen/helpers.d.ts +3 -1
  18. package/dist/codegen/helpers.js +5 -1
  19. package/dist/codegen/helpers.js.map +1 -1
  20. package/dist/codegen/machines.js +4 -3
  21. package/dist/codegen/machines.js.map +1 -1
  22. package/dist/codegen/modules.d.ts +1 -0
  23. package/dist/codegen/modules.js +52 -1
  24. package/dist/codegen/modules.js.map +1 -1
  25. package/dist/codegen/screens.js +77 -13
  26. package/dist/codegen/screens.js.map +1 -1
  27. package/dist/codegen/stdlib-preamble.d.ts +39 -0
  28. package/dist/codegen/stdlib-preamble.js +213 -0
  29. package/dist/codegen/stdlib-preamble.js.map +1 -0
  30. package/dist/codegen/type-system.d.ts +113 -1
  31. package/dist/codegen/type-system.js +389 -30
  32. package/dist/codegen/type-system.js.map +1 -1
  33. package/dist/codegen-core.d.ts +3 -3
  34. package/dist/codegen-core.js +79 -16
  35. package/dist/codegen-core.js.map +1 -1
  36. package/dist/codegen-expression.d.ts +3 -0
  37. package/dist/codegen-expression.js +93 -0
  38. package/dist/codegen-expression.js.map +1 -0
  39. package/dist/concepts.d.ts +96 -0
  40. package/dist/concepts.js.map +1 -1
  41. package/dist/config.d.ts +19 -0
  42. package/dist/config.js +15 -0
  43. package/dist/config.js.map +1 -1
  44. package/dist/decompiler.js +356 -4
  45. package/dist/decompiler.js.map +1 -1
  46. package/dist/importer.d.ts +1 -0
  47. package/dist/importer.js +574 -34
  48. package/dist/importer.js.map +1 -1
  49. package/dist/index.d.ts +6 -3
  50. package/dist/index.js +9 -3
  51. package/dist/index.js.map +1 -1
  52. package/dist/node-props.d.ts +188 -1
  53. package/dist/node-props.js.map +1 -1
  54. package/dist/parser-core.js +30 -5
  55. package/dist/parser-core.js.map +1 -1
  56. package/dist/parser-diagnostics.js +5 -0
  57. package/dist/parser-diagnostics.js.map +1 -1
  58. package/dist/parser-expression.d.ts +17 -0
  59. package/dist/parser-expression.js +498 -0
  60. package/dist/parser-expression.js.map +1 -0
  61. package/dist/parser-tokenizer.d.ts +5 -3
  62. package/dist/parser-tokenizer.js +97 -16
  63. package/dist/parser-tokenizer.js.map +1 -1
  64. package/dist/parser-validate-effects.d.ts +21 -0
  65. package/dist/parser-validate-effects.js +188 -0
  66. package/dist/parser-validate-effects.js.map +1 -0
  67. package/dist/parser-validate-expressions.d.ts +6 -0
  68. package/dist/parser-validate-expressions.js +41 -0
  69. package/dist/parser-validate-expressions.js.map +1 -0
  70. package/dist/parser-validate-union-kind.d.ts +24 -0
  71. package/dist/parser-validate-union-kind.js +97 -0
  72. package/dist/parser-validate-union-kind.js.map +1 -0
  73. package/dist/scanner.js +12 -9
  74. package/dist/scanner.js.map +1 -1
  75. package/dist/schema.d.ts +1 -1
  76. package/dist/schema.js +403 -37
  77. package/dist/schema.js.map +1 -1
  78. package/dist/semantic-validator.js +73 -8
  79. package/dist/semantic-validator.js.map +1 -1
  80. package/dist/spec.d.ts +5 -2
  81. package/dist/spec.js +27 -2
  82. package/dist/spec.js.map +1 -1
  83. package/dist/types.d.ts +7 -1
  84. package/dist/types.js +7 -1
  85. package/dist/types.js.map +1 -1
  86. package/dist/value-ir.d.ts +69 -0
  87. package/dist/value-ir.js +20 -0
  88. package/dist/value-ir.js.map +1 -0
  89. package/package.json +13 -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,9 @@ 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' },
234
294
  },
235
- allowedChildren: ['describe', 'it', 'handler'],
295
+ allowedChildren: ['describe', 'it', 'expect', 'fixture', 'handler'],
236
296
  },
237
297
  event: {
238
298
  description: 'Typed event with payload type children',
@@ -253,17 +313,101 @@ export const NODE_SCHEMAS = {
253
313
  types: { kind: 'boolean' },
254
314
  },
255
315
  },
316
+ use: {
317
+ description: 'Cross-`.kern` symbol resolution. Parent of `from` children — one per imported binding. Compositional shape mirrors enum/member, class/method.',
318
+ example: 'use path="./helper.kern"\n from name=foo\n from name=bar as=baz',
319
+ props: {
320
+ path: { required: true, kind: 'importPath' },
321
+ },
322
+ allowedChildren: ['from'],
323
+ },
324
+ from: {
325
+ description: 'Single binding in a `use` block. `as=` aliases the local name; `export=true` re-exports.',
326
+ example: 'from name=foo as=bar export=true',
327
+ props: {
328
+ name: { required: true, kind: 'identifier' },
329
+ as: { kind: 'identifier' },
330
+ export: { kind: 'boolean' },
331
+ },
332
+ },
256
333
  const: {
257
334
  description: 'Constant declaration with optional type and value or handler body',
258
335
  example: 'const name=MAX_RETRIES type=number value=3 export=true',
259
336
  props: {
260
337
  name: { required: true, kind: 'identifier' },
261
338
  type: { kind: 'typeAnnotation' },
262
- value: { kind: 'rawExpr' },
339
+ value: { kind: 'expression' },
263
340
  export: { kind: 'boolean' },
264
341
  },
265
342
  allowedChildren: ['handler'],
266
343
  },
344
+ destructure: {
345
+ 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.',
346
+ example: 'destructure kind=const source=user\n binding name=id\n binding name=email key=mail',
347
+ props: {
348
+ kind: { kind: 'string' },
349
+ source: { kind: 'expression' },
350
+ type: { kind: 'typeAnnotation' },
351
+ export: { kind: 'boolean' },
352
+ expr: { kind: 'rawExpr' },
353
+ },
354
+ allowedChildren: ['binding', 'element'],
355
+ },
356
+ binding: {
357
+ 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.',
358
+ example: 'binding name=foo key=a',
359
+ props: {
360
+ name: { required: true, kind: 'identifier' },
361
+ key: { kind: 'identifier' },
362
+ },
363
+ },
364
+ element: {
365
+ description: 'Array-destructuring element inside a `destructure` parent. `index` is the ordered position (zero-based). Slice 3d.',
366
+ example: 'element name=first index=0',
367
+ props: {
368
+ name: { required: true, kind: 'identifier' },
369
+ index: { kind: 'string' },
370
+ },
371
+ },
372
+ mapLit: {
373
+ 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.',
374
+ example: 'mapLit name=cache type="Map<string, number>"\n mapEntry key="foo" value=1\n mapEntry key="bar" value=2',
375
+ props: {
376
+ name: { required: true, kind: 'identifier' },
377
+ type: { kind: 'typeAnnotation' },
378
+ kind: { kind: 'string' },
379
+ export: { kind: 'boolean' },
380
+ expr: { kind: 'rawExpr' },
381
+ },
382
+ allowedChildren: ['mapEntry'],
383
+ },
384
+ mapEntry: {
385
+ description: 'Map-literal entry inside a `mapLit` parent. `key` and `value` are both expression-typed and ValueIR-canonicalised. Slice 3e.',
386
+ example: 'mapEntry key="foo" value=1',
387
+ props: {
388
+ key: { required: true, kind: 'expression' },
389
+ value: { required: true, kind: 'expression' },
390
+ },
391
+ },
392
+ setLit: {
393
+ 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.',
394
+ example: 'setLit name=allowed type="Set<string>"\n setItem value="admin"\n setItem value="user"',
395
+ props: {
396
+ name: { required: true, kind: 'identifier' },
397
+ type: { kind: 'typeAnnotation' },
398
+ kind: { kind: 'string' },
399
+ export: { kind: 'boolean' },
400
+ expr: { kind: 'rawExpr' },
401
+ },
402
+ allowedChildren: ['setItem'],
403
+ },
404
+ setItem: {
405
+ description: 'Set-literal item inside a `setLit` parent. `value` is expression-typed and ValueIR-canonicalised. Slice 3e.',
406
+ example: 'setItem value="admin"',
407
+ props: {
408
+ value: { required: true, kind: 'expression' },
409
+ },
410
+ },
267
411
  on: {
268
412
  description: 'Event listener — binds a handler to a named event',
269
413
  example: 'on event=click handler=handleClick',
@@ -293,10 +437,12 @@ export const NODE_SCHEMAS = {
293
437
  expr: { required: true, kind: 'rawExpr' },
294
438
  type: { kind: 'typeAnnotation' },
295
439
  export: { kind: 'boolean' },
440
+ // Slice 6 — see fn schema above.
441
+ effects: { kind: 'string' },
296
442
  },
297
443
  },
298
444
  fmt: {
299
- description: 'Formatted string binding — declarative template literal. The `template` body is emitted verbatim between backticks, so `${expr}` placeholders interpolate normally. Use this instead of dropping into a handler just to build an interpolated string. Set `return=true` to emit `return \\`...\\`;` inside a `fn` body (in which case `name` must be omitted).',
445
+ description: 'Formatted string — declarative template literal. The `template` body is emitted verbatim between backticks, so `${expr}` placeholders interpolate normally. Three positional modes: (1) binding form `fmt name=X template=...` emits `const X = \\`...\\`;` at the current scope; (2) return form `fmt return=true template=...` emits `return \\`...\\`;` inside a `fn` body (name must be omitted); (3) inline-JSX form `fmt template=...` (no name, no return=true) appears as a direct child of `render`/`group` and emits `{\\`...\\`}` as a JSX expression — use this to replace handler-wrapped `{\\`${x} files\\`}` text inside composed renders.',
300
446
  example: 'fmt name=label template="${count} files over ${totalMb.toFixed(1)} MB"',
301
447
  props: {
302
448
  name: { required: false, kind: 'identifier' },
@@ -322,6 +468,31 @@ export const NODE_SCHEMAS = {
322
468
  },
323
469
  allowedChildren: ['handler', 'recover'],
324
470
  },
471
+ try: {
472
+ description: 'Declarative async orchestration — a sequential try/catch where each `step name=X await="expr"` child lowers to `const X = await (expr);`. Step bindings are in scope for later steps and the optional `handler` body (post-steps), but NOT inside the `catch` block — JS `const` declared inside a `try` block is not visible to `catch` (use closure-scoped `derive`/`local` if the catch needs to reference earlier values). Use this instead of a raw handler to express the "fetch → parse → store, fall back on error" shape declaratively.',
473
+ example: 'try name=loadUser\n step name=res await="fetch(`/api/users/${id}`)"\n step name=body await="res.json()"\n handler <<<\n setUser(body);\n >>>\n catch name=err\n handler <<<\n setUser(null);\n >>>',
474
+ props: {
475
+ name: { kind: 'identifier' },
476
+ },
477
+ allowedChildren: ['step', 'handler', 'catch'],
478
+ },
479
+ step: {
480
+ description: 'Sequential awaited step inside a `try` block — `step name=X await="expr"` emits `const X = await (expr);` in order with its siblings. Must be a direct child of `try`. Earlier step names are in scope for later ones.',
481
+ example: 'step name=res await="fetch(url)"',
482
+ props: {
483
+ name: { required: true, kind: 'identifier' },
484
+ await: { required: true, kind: 'rawExpr' },
485
+ type: { kind: 'typeAnnotation' },
486
+ },
487
+ },
488
+ catch: {
489
+ description: 'Catch clause of a `try` block — binds the thrown value to `name` (default `e`) and runs its `handler` body. Must be a direct child of `try`. Without a `catch`, a `try` still surrounds its steps + handler but any rejection propagates unchanged.',
490
+ example: 'catch name=err\n handler <<<\n setError(err);\n >>>',
491
+ props: {
492
+ name: { kind: 'identifier' },
493
+ },
494
+ allowedChildren: ['handler'],
495
+ },
325
496
  filter: {
326
497
  description: 'Declarative `.filter` binding — `filter name=active in=items where="item.active"` lowers to `const active = items.filter(item => item.active);`. Use `item=x` to rename the per-item binding.',
327
498
  example: 'filter name=active in=items where="item.active"',
@@ -833,7 +1004,7 @@ export const NODE_SCHEMAS = {
833
1004
  reversible: { kind: 'boolean' },
834
1005
  export: { kind: 'boolean' },
835
1006
  },
836
- allowedChildren: ['handler'],
1007
+ allowedChildren: ['handler', 'param'],
837
1008
  },
838
1009
  actionRegistry: {
839
1010
  description: 'Calls an imported registration function with a map of string-keyed async action handlers. Emits `target({ key: async (...) => body, ... })` directly — no IIFE wrapper.',
@@ -853,6 +1024,9 @@ export const NODE_SCHEMAS = {
853
1024
  confidence: { kind: 'number' },
854
1025
  kind: { kind: 'identifier' },
855
1026
  type: { kind: 'identifier' },
1027
+ covers: { kind: 'string' },
1028
+ over: { kind: 'identifier' },
1029
+ union: { kind: 'identifier' },
856
1030
  param: { kind: 'identifier' },
857
1031
  field: { kind: 'identifier' },
858
1032
  target: { kind: 'identifier' },
@@ -911,11 +1085,12 @@ export const NODE_SCHEMAS = {
911
1085
  // separately via the `let-must-be-inside-each` semantic rule.
912
1086
  },
913
1087
  let: {
914
- 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.',
915
- example: 'let name=idx expr="start + i"',
1088
+ 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).',
1089
+ example: 'let name=idx value=i+1',
916
1090
  props: {
917
1091
  name: { required: true, kind: 'identifier' },
918
- expr: { required: true, kind: 'rawExpr' },
1092
+ value: { kind: 'expression' },
1093
+ expr: { kind: 'rawExpr' },
919
1094
  type: { kind: 'typeAnnotation' },
920
1095
  },
921
1096
  },
@@ -1016,7 +1191,7 @@ export const NODE_SCHEMAS = {
1016
1191
  params: { kind: 'string' },
1017
1192
  returns: { kind: 'typeAnnotation' },
1018
1193
  },
1019
- allowedChildren: ['handler', 'memo', 'callback', 'ref', 'effect'],
1194
+ allowedChildren: ['handler', 'memo', 'callback', 'ref', 'effect', 'param'],
1020
1195
  },
1021
1196
  effect: {
1022
1197
  description: 'React useEffect — side effect with dependency tracking',
@@ -1305,18 +1480,42 @@ export const NODE_SCHEMAS = {
1305
1480
  allowedChildren: ['param', 'handler', 'description', 'guard'],
1306
1481
  },
1307
1482
  param: {
1308
- description: 'Parameter definition for a tool, resource, or promptname, type, required, default, and description',
1483
+ 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).',
1309
1484
  example: 'param name=query type=string required=true description="Search query"',
1310
1485
  props: {
1311
- name: { required: true, kind: 'identifier' },
1312
- type: { kind: 'identifier' },
1486
+ // Slice 3c-extension #3: `name` is required UNLESS the param carries
1487
+ // `binding`/`element` destructure children — destructured params encode
1488
+ // the LHS pattern in the children, not in `name`. The required-OR-children
1489
+ // invariant lives in `checkCrossProps` so `validateSchema` accepts both
1490
+ // forms. Keeping the schema-level `required: true` here would reject the
1491
+ // canonical destructured form emitted by the importer.
1492
+ name: { kind: 'identifier' },
1493
+ type: { kind: 'typeAnnotation' },
1494
+ value: { kind: 'expression' },
1313
1495
  required: { kind: 'boolean' },
1496
+ // Slice 3c-extension: TS-style optional `?` on the LHS, distinct from MCP
1497
+ // `required`. When `optional=true`, codegen emits `name?: type` (with the
1498
+ // `?` inside the parameter list) so callers may omit the argument.
1499
+ optional: { kind: 'boolean' },
1500
+ // Slice 3c-extension: TS-style variadic `...rest`. When `variadic=true`,
1501
+ // codegen prepends `...` to the parameter name; the type should be an
1502
+ // array (e.g. `string[]`). Variadic params can't have defaults — that's
1503
+ // user error and TS will surface it at the call site.
1504
+ variadic: { kind: 'boolean' },
1314
1505
  default: { kind: 'rawExpr' },
1315
1506
  description: { kind: 'string' },
1316
1507
  min: { kind: 'number' },
1317
1508
  max: { kind: 'number' },
1318
1509
  },
1319
- allowedChildren: ['guard', 'description'],
1510
+ // Slice 3c-extension #3: TS-style destructured params via slice 3d's
1511
+ // `binding` (object pattern) / `element` (array pattern) children. When
1512
+ // present, codegen uses the pattern as the LHS instead of `name`, e.g.
1513
+ // param type="Point"
1514
+ // binding name=x
1515
+ // binding name=y
1516
+ // → `{x, y}: Point`. Same node types as slice 3d destructure — no new
1517
+ // node types needed. `name=` is omitted on destructured params.
1518
+ allowedChildren: ['guard', 'description', 'binding', 'element'],
1320
1519
  },
1321
1520
  prompt: {
1322
1521
  description: 'MCP prompt template — a reusable system prompt exposed to AI agents',
@@ -1570,6 +1769,8 @@ export const NODE_SCHEMAS = {
1570
1769
  props: {
1571
1770
  name: { required: true, kind: 'identifier' },
1572
1771
  deps: { kind: 'string' },
1772
+ // Slice 6 — see fn schema above.
1773
+ effects: { kind: 'string' },
1573
1774
  },
1574
1775
  allowedChildren: ['handler'],
1575
1776
  },
@@ -1582,7 +1783,7 @@ export const NODE_SCHEMAS = {
1582
1783
  deps: { kind: 'string' },
1583
1784
  async: { kind: 'boolean' },
1584
1785
  },
1585
- allowedChildren: ['handler'],
1786
+ allowedChildren: ['handler', 'param'],
1586
1787
  },
1587
1788
  ref: {
1588
1789
  description: 'React useRef — mutable ref object that persists across renders',
@@ -1626,7 +1827,7 @@ export const NODE_SCHEMAS = {
1626
1827
  props: {
1627
1828
  wrapper: { kind: 'string' },
1628
1829
  },
1629
- allowedChildren: ['handler', 'each', 'conditional', 'local', 'group'],
1830
+ allowedChildren: ['handler', 'each', 'conditional', 'local', 'group', 'fmt'],
1630
1831
  },
1631
1832
  group: {
1632
1833
  description: 'Nested JSX wrapper — emits an inner tag around a subset of a `render` block\'s children. `group` carries its own `wrapper="<Tag attrs>"` prop (required) and may hold `each`, `conditional`, `handler`, or further nested `group` children. Use it to build multi-level JSX trees (e.g. `<Box><Header /><Box paddingLeft>…</Box></Box>`) without dropping into a raw handler. Must appear as a direct or transitive child of `render`.',
@@ -1634,7 +1835,7 @@ export const NODE_SCHEMAS = {
1634
1835
  props: {
1635
1836
  wrapper: { required: true, kind: 'string' },
1636
1837
  },
1637
- allowedChildren: ['handler', 'each', 'conditional', 'group'],
1838
+ allowedChildren: ['handler', 'each', 'conditional', 'group', 'fmt'],
1638
1839
  },
1639
1840
  template: {
1640
1841
  description: 'Reusable template with named slots — defines a composable layout pattern',
@@ -1936,8 +2137,11 @@ export const NODE_SCHEMAS = {
1936
2137
  constructor: {
1937
2138
  description: 'Constructor for a service — runs on instantiation',
1938
2139
  example: 'constructor params="size:number"\n handler <<<\n this.data = new Map();\n >>>',
1939
- props: { params: { kind: 'string' } },
1940
- allowedChildren: ['handler'],
2140
+ props: {
2141
+ params: { kind: 'string' },
2142
+ generics: { kind: 'rawExpr' },
2143
+ },
2144
+ allowedChildren: ['handler', 'param'],
1941
2145
  },
1942
2146
  cleanup: {
1943
2147
  description: 'Cleanup handler — runs on teardown (useEffect return, signal dispose)',
@@ -1959,13 +2163,13 @@ export const NODE_SCHEMAS = {
1959
2163
  description: 'Test suite — groups related test cases',
1960
2164
  example: 'describe name="UserService"\n it name="creates a user"\n handler <<<\n expect(createUser()).toBeDefined();\n >>>',
1961
2165
  props: { name: { required: true, kind: 'string' } },
1962
- allowedChildren: ['it', 'describe', 'handler'],
2166
+ allowedChildren: ['it', 'describe', 'expect', 'fixture', 'handler'],
1963
2167
  },
1964
2168
  it: {
1965
2169
  description: 'Test case — single test assertion',
1966
2170
  example: 'it name="returns 200 on success"\n handler <<<\n expect(res.status).toBe(200);\n >>>',
1967
2171
  props: { name: { required: true, kind: 'string' } },
1968
- allowedChildren: ['handler'],
2172
+ allowedChildren: ['expect', 'fixture', 'handler'],
1969
2173
  },
1970
2174
  // Ground layer — semantic reasoning
1971
2175
  path: {
@@ -2003,9 +2207,55 @@ export const NODE_SCHEMAS = {
2003
2207
  props: { fn: { kind: 'identifier' }, to: { kind: 'rawExpr' } },
2004
2208
  },
2005
2209
  expect: {
2006
- description: 'Assertion — declare an expected condition at runtime',
2007
- example: 'expect expr={{items.length > 0}} message="Items must not be empty"',
2008
- props: { expr: { required: true, kind: 'rawExpr' }, message: { kind: 'string' } },
2210
+ description: 'Assertion — declare an expected runtime condition or KERN structural invariant',
2211
+ example: 'expect expr={{items.length > 0}} message="Items must not be empty"\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',
2212
+ props: {
2213
+ expr: { kind: 'rawExpr' },
2214
+ fn: { kind: 'identifier' },
2215
+ derive: { kind: 'identifier' },
2216
+ args: { kind: 'rawExpr' },
2217
+ with: { kind: 'rawExpr' },
2218
+ equals: { kind: 'rawExpr' },
2219
+ matches: { kind: 'string' },
2220
+ throws: { kind: 'string' },
2221
+ message: { kind: 'string' },
2222
+ preset: { kind: 'identifier' },
2223
+ severity: { kind: 'identifier' },
2224
+ node: { kind: 'identifier' },
2225
+ name: { kind: 'string' },
2226
+ within: { kind: 'string' },
2227
+ child: { kind: 'identifier' },
2228
+ childName: { kind: 'string' },
2229
+ prop: { kind: 'identifier' },
2230
+ is: { kind: 'string' },
2231
+ count: { kind: 'number' },
2232
+ machine: { kind: 'identifier' },
2233
+ transition: { kind: 'identifier' },
2234
+ from: { kind: 'identifier' },
2235
+ to: { kind: 'identifier' },
2236
+ guarded: { kind: 'boolean' },
2237
+ reaches: { kind: 'identifier' },
2238
+ through: { kind: 'string' },
2239
+ avoid: { kind: 'string' },
2240
+ avoids: { kind: 'string' },
2241
+ maxSteps: { kind: 'number' },
2242
+ via: { kind: 'string' },
2243
+ no: { kind: 'identifier' },
2244
+ guard: { kind: 'identifier' },
2245
+ exhaustive: { kind: 'boolean' },
2246
+ over: { kind: 'identifier' },
2247
+ union: { kind: 'identifier' },
2248
+ covers: { kind: 'string' },
2249
+ },
2250
+ },
2251
+ fixture: {
2252
+ description: 'Native test fixture — named runtime data available to scoped expect assertions',
2253
+ example: 'fixture name=paidOrder value={{({ id: "ord_1", status: "paid" })}}',
2254
+ props: {
2255
+ name: { required: true, kind: 'identifier' },
2256
+ value: { kind: 'rawExpr' },
2257
+ expr: { kind: 'rawExpr' },
2258
+ },
2009
2259
  },
2010
2260
  recover: {
2011
2261
  description: 'Recovery handler — runs when a parent node fails',
@@ -2099,24 +2349,129 @@ function checkCrossProps(node, violations) {
2099
2349
  col: node.loc?.col,
2100
2350
  });
2101
2351
  }
2102
- if (node.type === 'fmt') {
2103
- const returnMode = isTruthyProp(props.return);
2104
- if (returnMode && 'name' in props) {
2352
+ if (node.type === 'param') {
2353
+ // Slice 3c-extension #3: `param` requires `name=` UNLESS it carries
2354
+ // `binding`/`element` destructure children destructured params encode
2355
+ // the LHS pattern in the children. Replaces the old prop-level
2356
+ // `required: true` constraint which rejected the canonical destructured
2357
+ // form emitted by importer/decompiler.
2358
+ const hasName = 'name' in props;
2359
+ const hasDestructure = (node.children ?? []).some((c) => c.type === 'binding' || c.type === 'element');
2360
+ if (!hasName && !hasDestructure) {
2105
2361
  violations.push({
2106
- nodeType: 'fmt',
2107
- message: "'fmt' with return=true must not carry a 'name' prop (return-position emits `return \\`...\\`;`)",
2362
+ nodeType: 'param',
2363
+ message: "'param' requires either 'name' or destructure children ('binding'/'element')",
2364
+ line: node.loc?.line,
2365
+ col: node.loc?.col,
2366
+ });
2367
+ }
2368
+ }
2369
+ if (node.type === 'expect') {
2370
+ const hasRuntimeAssertion = 'expr' in props;
2371
+ const hasRuntimeBehavior = 'fn' in props || 'derive' in props;
2372
+ const hasPreset = 'preset' in props;
2373
+ const hasNodeShape = 'node' in props;
2374
+ const hasNegativeInvariant = 'no' in props;
2375
+ const hasGuardExhaustiveness = 'guard' in props;
2376
+ const hasMachineTransition = 'transition' in props;
2377
+ const hasMachineReachability = 'reaches' in props || ('machine' in props && !hasNegativeInvariant && !hasMachineTransition);
2378
+ if (!hasRuntimeAssertion &&
2379
+ !hasRuntimeBehavior &&
2380
+ !hasPreset &&
2381
+ !hasNodeShape &&
2382
+ !hasMachineTransition &&
2383
+ !hasMachineReachability &&
2384
+ !hasNegativeInvariant &&
2385
+ !hasGuardExhaustiveness) {
2386
+ violations.push({
2387
+ nodeType: 'expect',
2388
+ message: "'expect' requires 'expr', 'fn', 'derive', 'preset', 'node', 'machine' reachability, machine transition, 'no', or 'guard'",
2389
+ line: node.loc?.line,
2390
+ col: node.loc?.col,
2391
+ });
2392
+ }
2393
+ if ('fn' in props && 'derive' in props) {
2394
+ violations.push({
2395
+ nodeType: 'expect',
2396
+ message: "'expect' cannot combine fn=<name> and derive=<name>",
2108
2397
  line: node.loc?.line,
2109
2398
  col: node.loc?.col,
2110
2399
  });
2111
2400
  }
2112
- if (!returnMode && !('name' in props)) {
2401
+ if (hasRuntimeBehavior && hasRuntimeAssertion) {
2402
+ violations.push({
2403
+ nodeType: 'expect',
2404
+ message: "'expect' cannot combine fn/derive behavioral assertions with expr={{...}}",
2405
+ line: node.loc?.line,
2406
+ col: node.loc?.col,
2407
+ });
2408
+ }
2409
+ if (hasMachineTransition && !('machine' in props)) {
2410
+ violations.push({
2411
+ nodeType: 'expect',
2412
+ message: "'expect' machine transition assertions require machine=<name>",
2413
+ line: node.loc?.line,
2414
+ col: node.loc?.col,
2415
+ });
2416
+ }
2417
+ if (hasMachineTransition && 'reaches' in props) {
2418
+ violations.push({
2419
+ nodeType: 'expect',
2420
+ message: "'expect' cannot combine machine transition assertions with reaches=<state>",
2421
+ line: node.loc?.line,
2422
+ col: node.loc?.col,
2423
+ });
2424
+ }
2425
+ if (hasMachineReachability && (!('machine' in props) || !('reaches' in props))) {
2426
+ violations.push({
2427
+ nodeType: 'expect',
2428
+ message: "'expect' machine reachability requires both 'machine' and 'reaches'",
2429
+ line: node.loc?.line,
2430
+ col: node.loc?.col,
2431
+ });
2432
+ }
2433
+ if (hasGuardExhaustiveness && props.exhaustive !== true && props.exhaustive !== 'true') {
2434
+ violations.push({
2435
+ nodeType: 'expect',
2436
+ message: "'expect' guard assertions require exhaustive=true",
2437
+ line: node.loc?.line,
2438
+ col: node.loc?.col,
2439
+ });
2440
+ }
2441
+ }
2442
+ if (node.type === 'fixture') {
2443
+ const hasValue = 'value' in props;
2444
+ const hasExpr = 'expr' in props;
2445
+ if (!hasValue && !hasExpr) {
2446
+ violations.push({
2447
+ nodeType: 'fixture',
2448
+ message: "'fixture' requires either value={{...}} or expr={{...}}",
2449
+ line: node.loc?.line,
2450
+ col: node.loc?.col,
2451
+ });
2452
+ }
2453
+ if (hasValue && hasExpr) {
2454
+ violations.push({
2455
+ nodeType: 'fixture',
2456
+ message: "'fixture' must not combine value={{...}} and expr={{...}}",
2457
+ line: node.loc?.line,
2458
+ col: node.loc?.col,
2459
+ });
2460
+ }
2461
+ }
2462
+ if (node.type === 'fmt') {
2463
+ const returnMode = isTruthyProp(props.return);
2464
+ if (returnMode && 'name' in props) {
2113
2465
  violations.push({
2114
2466
  nodeType: 'fmt',
2115
- message: "'fmt' requires a 'name' prop for binding form, or 'return=true' for return-position form",
2467
+ message: "'fmt' with return=true must not carry a 'name' prop (return-position emits `return \\`...\\`;`)",
2116
2468
  line: node.loc?.line,
2117
2469
  col: node.loc?.col,
2118
2470
  });
2119
2471
  }
2472
+ // Neither `name` nor `return=true` selects the inline-JSX form; that form
2473
+ // is only valid inside `render`/`group` — the positional check lives in
2474
+ // the semantic validator, which has ancestry context.
2120
2475
  }
2121
2476
  }
2122
2477
  function isTruthyProp(raw) {
@@ -2171,7 +2526,18 @@ export function exportSchemaJSON(runtime) {
2171
2526
  styleShorthands: { ...STYLE_SHORTHANDS },
2172
2527
  valueShorthands: { ...VALUE_SHORTHANDS },
2173
2528
  multilineBlockTypes: [...rt.multilineBlockTypes],
2174
- propKinds: ['identifier', 'typeAnnotation', 'importPath', 'rawExpr', 'rawBlock', 'string', 'boolean', 'number'],
2529
+ propKinds: [
2530
+ 'identifier',
2531
+ 'typeAnnotation',
2532
+ 'importPath',
2533
+ 'rawExpr',
2534
+ 'rawBlock',
2535
+ 'string',
2536
+ 'boolean',
2537
+ 'number',
2538
+ 'expression',
2539
+ 'regex',
2540
+ ],
2175
2541
  ...(evolvedTypes.length > 0 ? { evolvedTypes } : {}),
2176
2542
  };
2177
2543
  }