@kernlang/core 3.3.9 → 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 (82) 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/events.js +1 -1
  8. package/dist/codegen/events.js.map +1 -1
  9. package/dist/codegen/functions.js +29 -5
  10. package/dist/codegen/functions.js.map +1 -1
  11. package/dist/codegen/ground-layer.js +10 -6
  12. package/dist/codegen/ground-layer.js.map +1 -1
  13. package/dist/codegen/helpers.d.ts +3 -1
  14. package/dist/codegen/helpers.js +5 -1
  15. package/dist/codegen/helpers.js.map +1 -1
  16. package/dist/codegen/machines.js +4 -3
  17. package/dist/codegen/machines.js.map +1 -1
  18. package/dist/codegen/modules.d.ts +1 -0
  19. package/dist/codegen/modules.js +52 -1
  20. package/dist/codegen/modules.js.map +1 -1
  21. package/dist/codegen/screens.js +31 -9
  22. package/dist/codegen/screens.js.map +1 -1
  23. package/dist/codegen/stdlib-preamble.d.ts +39 -0
  24. package/dist/codegen/stdlib-preamble.js +213 -0
  25. package/dist/codegen/stdlib-preamble.js.map +1 -0
  26. package/dist/codegen/type-system.d.ts +113 -1
  27. package/dist/codegen/type-system.js +389 -30
  28. package/dist/codegen/type-system.js.map +1 -1
  29. package/dist/codegen-core.d.ts +2 -2
  30. package/dist/codegen-core.js +58 -10
  31. package/dist/codegen-core.js.map +1 -1
  32. package/dist/codegen-expression.d.ts +3 -0
  33. package/dist/codegen-expression.js +93 -0
  34. package/dist/codegen-expression.js.map +1 -0
  35. package/dist/concepts.d.ts +3 -3
  36. package/dist/config.d.ts +16 -0
  37. package/dist/config.js +13 -0
  38. package/dist/config.js.map +1 -1
  39. package/dist/decompiler.js +356 -4
  40. package/dist/decompiler.js.map +1 -1
  41. package/dist/importer.d.ts +1 -0
  42. package/dist/importer.js +574 -34
  43. package/dist/importer.js.map +1 -1
  44. package/dist/index.d.ts +6 -3
  45. package/dist/index.js +9 -3
  46. package/dist/index.js.map +1 -1
  47. package/dist/node-props.d.ts +177 -1
  48. package/dist/node-props.js.map +1 -1
  49. package/dist/parser-core.js +30 -5
  50. package/dist/parser-core.js.map +1 -1
  51. package/dist/parser-diagnostics.js +5 -0
  52. package/dist/parser-diagnostics.js.map +1 -1
  53. package/dist/parser-expression.d.ts +17 -0
  54. package/dist/parser-expression.js +498 -0
  55. package/dist/parser-expression.js.map +1 -0
  56. package/dist/parser-tokenizer.d.ts +5 -3
  57. package/dist/parser-tokenizer.js +97 -16
  58. package/dist/parser-tokenizer.js.map +1 -1
  59. package/dist/parser-validate-effects.d.ts +21 -0
  60. package/dist/parser-validate-effects.js +188 -0
  61. package/dist/parser-validate-effects.js.map +1 -0
  62. package/dist/parser-validate-expressions.d.ts +6 -0
  63. package/dist/parser-validate-expressions.js +41 -0
  64. package/dist/parser-validate-expressions.js.map +1 -0
  65. package/dist/parser-validate-union-kind.d.ts +24 -0
  66. package/dist/parser-validate-union-kind.js +97 -0
  67. package/dist/parser-validate-union-kind.js.map +1 -0
  68. package/dist/schema.d.ts +1 -1
  69. package/dist/schema.js +373 -27
  70. package/dist/schema.js.map +1 -1
  71. package/dist/semantic-validator.js +15 -8
  72. package/dist/semantic-validator.js.map +1 -1
  73. package/dist/spec.d.ts +5 -2
  74. package/dist/spec.js +24 -2
  75. package/dist/spec.js.map +1 -1
  76. package/dist/types.d.ts +7 -1
  77. package/dist/types.js +7 -1
  78. package/dist/types.js.map +1 -1
  79. package/dist/value-ir.d.ts +69 -0
  80. package/dist/value-ir.js +20 -0
  81. package/dist/value-ir.js.map +1 -0
  82. 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,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,6 +437,8 @@ 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: {
@@ -858,7 +1004,7 @@ export const NODE_SCHEMAS = {
858
1004
  reversible: { kind: 'boolean' },
859
1005
  export: { kind: 'boolean' },
860
1006
  },
861
- allowedChildren: ['handler'],
1007
+ allowedChildren: ['handler', 'param'],
862
1008
  },
863
1009
  actionRegistry: {
864
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.',
@@ -878,6 +1024,9 @@ export const NODE_SCHEMAS = {
878
1024
  confidence: { kind: 'number' },
879
1025
  kind: { kind: 'identifier' },
880
1026
  type: { kind: 'identifier' },
1027
+ covers: { kind: 'string' },
1028
+ over: { kind: 'identifier' },
1029
+ union: { kind: 'identifier' },
881
1030
  param: { kind: 'identifier' },
882
1031
  field: { kind: 'identifier' },
883
1032
  target: { kind: 'identifier' },
@@ -936,11 +1085,12 @@ export const NODE_SCHEMAS = {
936
1085
  // separately via the `let-must-be-inside-each` semantic rule.
937
1086
  },
938
1087
  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"',
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',
941
1090
  props: {
942
1091
  name: { required: true, kind: 'identifier' },
943
- expr: { required: true, kind: 'rawExpr' },
1092
+ value: { kind: 'expression' },
1093
+ expr: { kind: 'rawExpr' },
944
1094
  type: { kind: 'typeAnnotation' },
945
1095
  },
946
1096
  },
@@ -1041,7 +1191,7 @@ export const NODE_SCHEMAS = {
1041
1191
  params: { kind: 'string' },
1042
1192
  returns: { kind: 'typeAnnotation' },
1043
1193
  },
1044
- allowedChildren: ['handler', 'memo', 'callback', 'ref', 'effect'],
1194
+ allowedChildren: ['handler', 'memo', 'callback', 'ref', 'effect', 'param'],
1045
1195
  },
1046
1196
  effect: {
1047
1197
  description: 'React useEffect — side effect with dependency tracking',
@@ -1330,18 +1480,42 @@ export const NODE_SCHEMAS = {
1330
1480
  allowedChildren: ['param', 'handler', 'description', 'guard'],
1331
1481
  },
1332
1482
  param: {
1333
- 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).',
1334
1484
  example: 'param name=query type=string required=true description="Search query"',
1335
1485
  props: {
1336
- name: { required: true, kind: 'identifier' },
1337
- 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' },
1338
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' },
1339
1505
  default: { kind: 'rawExpr' },
1340
1506
  description: { kind: 'string' },
1341
1507
  min: { kind: 'number' },
1342
1508
  max: { kind: 'number' },
1343
1509
  },
1344
- 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'],
1345
1519
  },
1346
1520
  prompt: {
1347
1521
  description: 'MCP prompt template — a reusable system prompt exposed to AI agents',
@@ -1595,6 +1769,8 @@ export const NODE_SCHEMAS = {
1595
1769
  props: {
1596
1770
  name: { required: true, kind: 'identifier' },
1597
1771
  deps: { kind: 'string' },
1772
+ // Slice 6 — see fn schema above.
1773
+ effects: { kind: 'string' },
1598
1774
  },
1599
1775
  allowedChildren: ['handler'],
1600
1776
  },
@@ -1607,7 +1783,7 @@ export const NODE_SCHEMAS = {
1607
1783
  deps: { kind: 'string' },
1608
1784
  async: { kind: 'boolean' },
1609
1785
  },
1610
- allowedChildren: ['handler'],
1786
+ allowedChildren: ['handler', 'param'],
1611
1787
  },
1612
1788
  ref: {
1613
1789
  description: 'React useRef — mutable ref object that persists across renders',
@@ -1961,8 +2137,11 @@ export const NODE_SCHEMAS = {
1961
2137
  constructor: {
1962
2138
  description: 'Constructor for a service — runs on instantiation',
1963
2139
  example: 'constructor params="size:number"\n handler <<<\n this.data = new Map();\n >>>',
1964
- props: { params: { kind: 'string' } },
1965
- allowedChildren: ['handler'],
2140
+ props: {
2141
+ params: { kind: 'string' },
2142
+ generics: { kind: 'rawExpr' },
2143
+ },
2144
+ allowedChildren: ['handler', 'param'],
1966
2145
  },
1967
2146
  cleanup: {
1968
2147
  description: 'Cleanup handler — runs on teardown (useEffect return, signal dispose)',
@@ -1984,13 +2163,13 @@ export const NODE_SCHEMAS = {
1984
2163
  description: 'Test suite — groups related test cases',
1985
2164
  example: 'describe name="UserService"\n it name="creates a user"\n handler <<<\n expect(createUser()).toBeDefined();\n >>>',
1986
2165
  props: { name: { required: true, kind: 'string' } },
1987
- allowedChildren: ['it', 'describe', 'handler'],
2166
+ allowedChildren: ['it', 'describe', 'expect', 'fixture', 'handler'],
1988
2167
  },
1989
2168
  it: {
1990
2169
  description: 'Test case — single test assertion',
1991
2170
  example: 'it name="returns 200 on success"\n handler <<<\n expect(res.status).toBe(200);\n >>>',
1992
2171
  props: { name: { required: true, kind: 'string' } },
1993
- allowedChildren: ['handler'],
2172
+ allowedChildren: ['expect', 'fixture', 'handler'],
1994
2173
  },
1995
2174
  // Ground layer — semantic reasoning
1996
2175
  path: {
@@ -2028,9 +2207,55 @@ export const NODE_SCHEMAS = {
2028
2207
  props: { fn: { kind: 'identifier' }, to: { kind: 'rawExpr' } },
2029
2208
  },
2030
2209
  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' } },
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
+ },
2034
2259
  },
2035
2260
  recover: {
2036
2261
  description: 'Recovery handler — runs when a parent node fails',
@@ -2124,6 +2349,116 @@ function checkCrossProps(node, violations) {
2124
2349
  col: node.loc?.col,
2125
2350
  });
2126
2351
  }
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) {
2361
+ violations.push({
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>",
2397
+ line: node.loc?.line,
2398
+ col: node.loc?.col,
2399
+ });
2400
+ }
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
+ }
2127
2462
  if (node.type === 'fmt') {
2128
2463
  const returnMode = isTruthyProp(props.return);
2129
2464
  if (returnMode && 'name' in props) {
@@ -2191,7 +2526,18 @@ export function exportSchemaJSON(runtime) {
2191
2526
  styleShorthands: { ...STYLE_SHORTHANDS },
2192
2527
  valueShorthands: { ...VALUE_SHORTHANDS },
2193
2528
  multilineBlockTypes: [...rt.multilineBlockTypes],
2194
- 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
+ ],
2195
2541
  ...(evolvedTypes.length > 0 ? { evolvedTypes } : {}),
2196
2542
  };
2197
2543
  }