@kernlang/core 3.1.6 → 3.1.8

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 (96) hide show
  1. package/dist/codegen/data-layer.js +28 -29
  2. package/dist/codegen/data-layer.js.map +1 -1
  3. package/dist/codegen/emitters.js +1 -4
  4. package/dist/codegen/emitters.js.map +1 -1
  5. package/dist/codegen/events.js +31 -20
  6. package/dist/codegen/events.js.map +1 -1
  7. package/dist/codegen/functions.js +7 -5
  8. package/dist/codegen/functions.js.map +1 -1
  9. package/dist/codegen/ground-layer.js +9 -5
  10. package/dist/codegen/ground-layer.js.map +1 -1
  11. package/dist/codegen/helpers.d.ts +4 -0
  12. package/dist/codegen/helpers.js +43 -7
  13. package/dist/codegen/helpers.js.map +1 -1
  14. package/dist/codegen/machines.js +13 -11
  15. package/dist/codegen/machines.js.map +1 -1
  16. package/dist/codegen/modules.js +5 -2
  17. package/dist/codegen/modules.js.map +1 -1
  18. package/dist/codegen/screens.d.ts +29 -0
  19. package/dist/codegen/screens.js +202 -0
  20. package/dist/codegen/screens.js.map +1 -0
  21. package/dist/codegen/semantic-types.js.map +1 -1
  22. package/dist/codegen/test-gen.js +1 -1
  23. package/dist/codegen/test-gen.js.map +1 -1
  24. package/dist/codegen/type-system.js +36 -18
  25. package/dist/codegen/type-system.js.map +1 -1
  26. package/dist/codegen-core.d.ts +11 -10
  27. package/dist/codegen-core.js +225 -108
  28. package/dist/codegen-core.js.map +1 -1
  29. package/dist/concepts.js.map +1 -1
  30. package/dist/config.js +15 -2
  31. package/dist/config.js.map +1 -1
  32. package/dist/coverage-gap.js +5 -5
  33. package/dist/coverage-gap.js.map +1 -1
  34. package/dist/decompiler.d.ts +1 -1
  35. package/dist/decompiler.js +6 -4
  36. package/dist/decompiler.js.map +1 -1
  37. package/dist/errors.d.ts +3 -1
  38. package/dist/errors.js +4 -2
  39. package/dist/errors.js.map +1 -1
  40. package/dist/importer.d.ts +38 -0
  41. package/dist/importer.js +1135 -0
  42. package/dist/importer.js.map +1 -0
  43. package/dist/index.d.ts +37 -34
  44. package/dist/index.js +38 -38
  45. package/dist/index.js.map +1 -1
  46. package/dist/node-props.d.ts +7 -0
  47. package/dist/node-props.js.map +1 -1
  48. package/dist/parser-core.d.ts +1 -1
  49. package/dist/parser-core.js +65 -9
  50. package/dist/parser-core.js.map +1 -1
  51. package/dist/parser-diagnostics.d.ts +1 -1
  52. package/dist/parser-diagnostics.js +3 -2
  53. package/dist/parser-diagnostics.js.map +1 -1
  54. package/dist/parser-keywords.d.ts +1 -1
  55. package/dist/parser-keywords.js +119 -27
  56. package/dist/parser-keywords.js.map +1 -1
  57. package/dist/parser-token-stream.js +18 -6
  58. package/dist/parser-token-stream.js.map +1 -1
  59. package/dist/parser-tokenizer.js.map +1 -1
  60. package/dist/parser.d.ts +3 -3
  61. package/dist/parser.js +15 -4
  62. package/dist/parser.js.map +1 -1
  63. package/dist/runtime.js +1 -1
  64. package/dist/runtime.js.map +1 -1
  65. package/dist/scanner.js +85 -25
  66. package/dist/scanner.js.map +1 -1
  67. package/dist/schema.d.ts +25 -0
  68. package/dist/schema.js +442 -4
  69. package/dist/schema.js.map +1 -1
  70. package/dist/semantic-validator.d.ts +20 -0
  71. package/dist/semantic-validator.js +82 -0
  72. package/dist/semantic-validator.js.map +1 -0
  73. package/dist/source-map.js +2 -2
  74. package/dist/source-map.js.map +1 -1
  75. package/dist/spec.d.ts +1 -1
  76. package/dist/spec.js +197 -56
  77. package/dist/spec.js.map +1 -1
  78. package/dist/styles-react.js +1 -1
  79. package/dist/styles-react.js.map +1 -1
  80. package/dist/styles-tailwind.js +52 -15
  81. package/dist/styles-tailwind.js.map +1 -1
  82. package/dist/template-catalog.js +1 -1
  83. package/dist/template-catalog.js.map +1 -1
  84. package/dist/template-engine.d.ts +1 -1
  85. package/dist/template-engine.js +10 -7
  86. package/dist/template-engine.js.map +1 -1
  87. package/dist/utils.d.ts +1 -1
  88. package/dist/utils.js +9 -5
  89. package/dist/utils.js.map +1 -1
  90. package/dist/version-adapters.js +1 -2
  91. package/dist/version-adapters.js.map +1 -1
  92. package/dist/version-detect.js +3 -3
  93. package/dist/version-detect.js.map +1 -1
  94. package/dist/walk.js +6 -2
  95. package/dist/walk.js.map +1 -1
  96. package/package.json +1 -1
package/dist/schema.js CHANGED
@@ -15,9 +15,22 @@
15
15
  * - 'boolean' → 'true'/'false'
16
16
  * - 'number' → numeric value
17
17
  */
18
+ import { VALID_TARGETS } from './config.js';
19
+ import { defaultRuntime } from './runtime.js';
20
+ import { KERN_VERSION, NODE_TYPES, STYLE_SHORTHANDS, VALUE_SHORTHANDS } from './spec.js';
18
21
  // ── Schema Definitions ──────────────────────────────────────────────────
19
22
  export const NODE_SCHEMAS = {
23
+ doc: {
24
+ description: 'JSDoc documentation comment attached to the next declaration. Supports inline (text=) or multiline (<<<>>>)',
25
+ example: 'doc text="Represents a user account"',
26
+ props: {
27
+ text: { kind: 'string' },
28
+ code: { kind: 'rawBlock' },
29
+ },
30
+ },
20
31
  type: {
32
+ description: 'TypeScript type alias — union of string literals or alias to another type',
33
+ example: 'type name=Status values="active|inactive|banned"',
21
34
  props: {
22
35
  name: { required: true, kind: 'identifier' },
23
36
  values: { kind: 'string' },
@@ -26,6 +39,8 @@ export const NODE_SCHEMAS = {
26
39
  },
27
40
  },
28
41
  interface: {
42
+ description: 'TypeScript interface with typed fields',
43
+ example: 'interface name=User export=true\n field name=id type=string\n field name=email type=string',
29
44
  props: {
30
45
  name: { required: true, kind: 'identifier' },
31
46
  extends: { kind: 'typeAnnotation' },
@@ -34,6 +49,8 @@ export const NODE_SCHEMAS = {
34
49
  allowedChildren: ['field'],
35
50
  },
36
51
  union: {
52
+ description: 'Discriminated union type with variants, each having their own fields',
53
+ example: 'union name=Shape discriminant=kind\n variant name=circle\n field name=radius type=number',
37
54
  props: {
38
55
  name: { required: true, kind: 'identifier' },
39
56
  discriminant: { required: true, kind: 'identifier' },
@@ -42,12 +59,17 @@ export const NODE_SCHEMAS = {
42
59
  allowedChildren: ['variant'],
43
60
  },
44
61
  variant: {
62
+ description: 'A case within a discriminated union. Use name= for inline variants with fields, or type= to reference an existing interface.',
63
+ example: 'variant name=circle\n field name=radius type=number',
45
64
  props: {
46
- name: { required: true, kind: 'identifier' },
65
+ name: { required: false, kind: 'identifier' },
66
+ type: { required: false, kind: 'typeAnnotation' },
47
67
  },
48
68
  allowedChildren: ['field'],
49
69
  },
50
70
  field: {
71
+ description: 'A typed property within an interface, variant, service, config, or error',
72
+ example: 'field name=email type=string optional=true',
51
73
  props: {
52
74
  name: { required: true, kind: 'identifier' },
53
75
  type: { kind: 'typeAnnotation' },
@@ -58,6 +80,8 @@ export const NODE_SCHEMAS = {
58
80
  },
59
81
  },
60
82
  service: {
83
+ description: 'Class-based service with methods and dependency injection',
84
+ example: 'service name=AuthService export=true\n method name=login params="email:string,password:string" async=true',
61
85
  props: {
62
86
  name: { required: true, kind: 'identifier' },
63
87
  implements: { kind: 'typeAnnotation' },
@@ -66,6 +90,8 @@ export const NODE_SCHEMAS = {
66
90
  allowedChildren: ['field', 'method', 'constructor', 'singleton'],
67
91
  },
68
92
  method: {
93
+ description: 'A method within a service or repository, with handler body',
94
+ example: 'method name=findById params="id:string" returns=User async=true\n handler <<<\n return db.users.find(id)\n >>>',
69
95
  props: {
70
96
  name: { required: true, kind: 'identifier' },
71
97
  params: { kind: 'string' },
@@ -78,6 +104,8 @@ export const NODE_SCHEMAS = {
78
104
  allowedChildren: ['handler'],
79
105
  },
80
106
  fn: {
107
+ description: 'Standalone function — the most common code unit in KERN',
108
+ example: 'fn name=calculateTotal params="items:CartItem[]" returns=number export=true\n handler <<<\n return items.reduce((sum, i) => sum + i.price, 0)\n >>>',
81
109
  props: {
82
110
  name: { required: true, kind: 'identifier' },
83
111
  params: { kind: 'string' },
@@ -89,6 +117,8 @@ export const NODE_SCHEMAS = {
89
117
  allowedChildren: ['handler', 'signal', 'cleanup'],
90
118
  },
91
119
  machine: {
120
+ description: 'State machine with states and guarded transitions — 12 lines of KERN generates 140+ lines of TypeScript',
121
+ example: 'machine name=OrderStatus export=true\n state name=pending initial=true\n state name=confirmed\n transition name=confirm from=pending to=confirmed',
92
122
  props: {
93
123
  name: { required: true, kind: 'identifier' },
94
124
  export: { kind: 'boolean' },
@@ -96,12 +126,17 @@ export const NODE_SCHEMAS = {
96
126
  allowedChildren: ['state', 'transition'],
97
127
  },
98
128
  state: {
129
+ description: 'State — machine state (initial=true/false) or React component state (initial=expression, type=Type)',
130
+ example: 'state name=pending initial=true\nstate name=count initial=0 type=number',
99
131
  props: {
100
132
  name: { required: true, kind: 'identifier' },
101
- initial: { kind: 'boolean' },
133
+ initial: { kind: 'rawExpr' },
134
+ type: { kind: 'typeAnnotation' },
102
135
  },
103
136
  },
104
137
  transition: {
138
+ description: 'A guarded transition between machine states, with optional handler',
139
+ example: 'transition name=confirm from=pending to=confirmed\n handler <<<\n await notifyUser()\n >>>',
105
140
  props: {
106
141
  name: { required: true, kind: 'identifier' },
107
142
  from: { required: true, kind: 'string' },
@@ -110,6 +145,8 @@ export const NODE_SCHEMAS = {
110
145
  allowedChildren: ['handler'],
111
146
  },
112
147
  error: {
148
+ description: 'Custom error class extending a base error, with typed fields',
149
+ example: 'error name=ValidationError extends=Error message="Invalid input" export=true\n field name=field type=string',
113
150
  props: {
114
151
  name: { required: true, kind: 'identifier' },
115
152
  extends: { required: true, kind: 'identifier' },
@@ -119,6 +156,8 @@ export const NODE_SCHEMAS = {
119
156
  allowedChildren: ['field', 'handler'],
120
157
  },
121
158
  config: {
159
+ description: 'Configuration interface with typed fields — generates an interface',
160
+ example: 'config name=AppConfig export=true\n field name=port type=number default=3000\n field name=debug type=boolean',
122
161
  props: {
123
162
  name: { required: true, kind: 'identifier' },
124
163
  export: { kind: 'boolean' },
@@ -126,6 +165,8 @@ export const NODE_SCHEMAS = {
126
165
  allowedChildren: ['field'],
127
166
  },
128
167
  store: {
168
+ description: 'File-based JSON store with typed key and model',
169
+ example: 'store name=UserStore path="data/users" key=id model=User export=true',
129
170
  props: {
130
171
  name: { required: true, kind: 'identifier' },
131
172
  path: { required: true, kind: 'string' },
@@ -135,12 +176,16 @@ export const NODE_SCHEMAS = {
135
176
  },
136
177
  },
137
178
  test: {
179
+ description: 'Test suite container with describe/it blocks',
180
+ example: 'test name="AuthService"\n describe name="login"\n it name="rejects invalid email"',
138
181
  props: {
139
182
  name: { required: true, kind: 'string' },
140
183
  },
141
184
  allowedChildren: ['describe', 'it', 'handler'],
142
185
  },
143
186
  event: {
187
+ description: 'Typed event with payload type children',
188
+ example: 'event name=UserCreated export=true\n type name=id type=string\n type name=email type=string',
144
189
  props: {
145
190
  name: { required: true, kind: 'identifier' },
146
191
  export: { kind: 'boolean' },
@@ -148,6 +193,8 @@ export const NODE_SCHEMAS = {
148
193
  allowedChildren: ['type'],
149
194
  },
150
195
  import: {
196
+ description: 'ES module import — named, default, or type-only',
197
+ example: 'import from="./user.js" names="User,UserRole" types=true',
151
198
  props: {
152
199
  from: { required: true, kind: 'importPath' },
153
200
  names: { kind: 'string' },
@@ -156,6 +203,8 @@ export const NODE_SCHEMAS = {
156
203
  },
157
204
  },
158
205
  const: {
206
+ description: 'Constant declaration with optional type and value or handler body',
207
+ example: 'const name=MAX_RETRIES type=number value=3 export=true',
159
208
  props: {
160
209
  name: { required: true, kind: 'identifier' },
161
210
  type: { kind: 'typeAnnotation' },
@@ -165,6 +214,8 @@ export const NODE_SCHEMAS = {
165
214
  allowedChildren: ['handler'],
166
215
  },
167
216
  on: {
217
+ description: 'Event listener — binds a handler to a named event',
218
+ example: 'on event=click handler=handleClick',
168
219
  props: {
169
220
  event: { required: true, kind: 'string' },
170
221
  handler: { kind: 'identifier' },
@@ -174,6 +225,8 @@ export const NODE_SCHEMAS = {
174
225
  allowedChildren: ['handler'],
175
226
  },
176
227
  websocket: {
228
+ description: 'WebSocket server endpoint with event handlers',
229
+ example: 'websocket path="/ws" name=chatSocket export=true\n on event=message\n handler <<<\n broadcast(data)\n >>>',
177
230
  props: {
178
231
  path: { kind: 'string' },
179
232
  name: { kind: 'identifier' },
@@ -182,6 +235,8 @@ export const NODE_SCHEMAS = {
182
235
  allowedChildren: ['on'],
183
236
  },
184
237
  derive: {
238
+ description: 'Computed/derived value from an expression',
239
+ example: 'derive name=fullName expr="first + " " + last" type=string',
185
240
  props: {
186
241
  name: { required: true, kind: 'identifier' },
187
242
  expr: { required: true, kind: 'rawExpr' },
@@ -190,6 +245,8 @@ export const NODE_SCHEMAS = {
190
245
  },
191
246
  },
192
247
  transform: {
248
+ description: 'Data transformation pipeline — maps target through a via function or handler',
249
+ example: 'transform name=normalized target=rawData via=normalize type=NormalizedData',
193
250
  props: {
194
251
  name: { required: true, kind: 'identifier' },
195
252
  target: { kind: 'rawExpr' },
@@ -200,6 +257,8 @@ export const NODE_SCHEMAS = {
200
257
  allowedChildren: ['handler'],
201
258
  },
202
259
  action: {
260
+ description: 'Named side-effecting operation — can be idempotent or reversible',
261
+ example: 'action name=sendEmail params="to:string,body:string" async=true export=true\n handler <<<\n await mailer.send(to, body)\n >>>',
203
262
  props: {
204
263
  name: { required: true, kind: 'identifier' },
205
264
  params: { kind: 'string' },
@@ -211,14 +270,42 @@ export const NODE_SCHEMAS = {
211
270
  allowedChildren: ['handler'],
212
271
  },
213
272
  guard: {
273
+ description: 'Guard — runtime assertion (expr-based) or MCP security guard (kind-based: sanitize, pathContainment, validate, auth, rateLimit, sizeLimit, sanitizeOutput)',
274
+ example: 'guard expr="user !== null" else="throw new Error(\'No user\')"\nguard type=sanitize param=query\nguard type=pathContainment param=filePath allowlist=/data,/home',
214
275
  props: {
215
276
  name: { kind: 'string' },
216
- expr: { required: true, kind: 'rawExpr' },
277
+ expr: { kind: 'rawExpr' },
217
278
  else: { kind: 'rawExpr' },
218
279
  confidence: { kind: 'number' },
280
+ kind: { kind: 'identifier' },
281
+ type: { kind: 'identifier' },
282
+ param: { kind: 'identifier' },
283
+ field: { kind: 'identifier' },
284
+ target: { kind: 'identifier' },
285
+ pattern: { kind: 'string' },
286
+ replacement: { kind: 'string' },
287
+ regex: { kind: 'string' },
288
+ min: { kind: 'number' },
289
+ max: { kind: 'number' },
290
+ allowlist: { kind: 'string' },
291
+ allow: { kind: 'string' },
292
+ roots: { kind: 'string' },
293
+ baseDir: { kind: 'string' },
294
+ base: { kind: 'string' },
295
+ root: { kind: 'string' },
296
+ envVar: { kind: 'string' },
297
+ env: { kind: 'string' },
298
+ header: { kind: 'string' },
299
+ windowMs: { kind: 'number' },
300
+ window: { kind: 'number' },
301
+ maxRequests: { kind: 'number' },
302
+ requests: { kind: 'number' },
303
+ maxBytes: { kind: 'number' },
219
304
  },
220
305
  },
221
306
  assume: {
307
+ description: 'Documented assumption with evidence and fallback for when it breaks',
308
+ example: 'assume expr="items.length > 0" evidence="validated upstream" fallback="return []"',
222
309
  props: {
223
310
  expr: { required: true, kind: 'rawExpr' },
224
311
  scope: { kind: 'string' },
@@ -228,6 +315,8 @@ export const NODE_SCHEMAS = {
228
315
  },
229
316
  },
230
317
  invariant: {
318
+ description: 'Compile-time documented invariant — runtime assertion with confidence score',
319
+ example: 'invariant name="positive balance" expr="balance >= 0"',
231
320
  props: {
232
321
  name: { kind: 'string' },
233
322
  expr: { required: true, kind: 'rawExpr' },
@@ -235,6 +324,8 @@ export const NODE_SCHEMAS = {
235
324
  },
236
325
  },
237
326
  each: {
327
+ description: 'Iteration — renders children for each item in a collection (target-agnostic loop)',
328
+ example: 'each name=item in=items index=i',
238
329
  props: {
239
330
  name: { required: true, kind: 'identifier' },
240
331
  in: { required: true, kind: 'rawExpr' },
@@ -242,6 +333,8 @@ export const NODE_SCHEMAS = {
242
333
  },
243
334
  },
244
335
  collect: {
336
+ description: 'Query/collect from a data source with optional filter, sort, and limit',
337
+ example: 'collect name=activeUsers from=users where="u => u.active" order="u => u.name" limit=10',
245
338
  props: {
246
339
  name: { required: true, kind: 'identifier' },
247
340
  from: { required: true, kind: 'rawExpr' },
@@ -252,6 +345,8 @@ export const NODE_SCHEMAS = {
252
345
  },
253
346
  },
254
347
  branch: {
348
+ description: 'Pattern-match/switch on an expression — contains path children',
349
+ example: 'branch name=route on=path\n path value="/home"\n path value="/about"',
255
350
  props: {
256
351
  name: { required: true, kind: 'identifier' },
257
352
  on: { required: true, kind: 'rawExpr' },
@@ -259,6 +354,8 @@ export const NODE_SCHEMAS = {
259
354
  allowedChildren: ['path'],
260
355
  },
261
356
  model: {
357
+ description: 'Database model/entity with columns and relations (generates Prisma or TypeORM)',
358
+ example: 'model name=User table="users" export=true\n column name=id type=string\n column name=email type=string\n relation name=posts type=Post[]',
262
359
  props: {
263
360
  name: { required: true, kind: 'identifier' },
264
361
  table: { kind: 'string' },
@@ -267,6 +364,8 @@ export const NODE_SCHEMAS = {
267
364
  allowedChildren: ['column', 'relation'],
268
365
  },
269
366
  repository: {
367
+ description: 'Data access layer class with typed methods for a model',
368
+ example: 'repository name=UserRepo model=User export=true\n method name=findByEmail params="email:string" returns=User async=true',
270
369
  props: {
271
370
  name: { required: true, kind: 'identifier' },
272
371
  model: { required: true, kind: 'identifier' },
@@ -275,6 +374,8 @@ export const NODE_SCHEMAS = {
275
374
  allowedChildren: ['method'],
276
375
  },
277
376
  dependency: {
377
+ description: 'Dependency injection container entry with scope and injected services',
378
+ example: 'dependency name=authService scope=singleton export=true',
278
379
  props: {
279
380
  name: { required: true, kind: 'identifier' },
280
381
  scope: { kind: 'string' },
@@ -283,6 +384,8 @@ export const NODE_SCHEMAS = {
283
384
  allowedChildren: ['inject', 'returns'],
284
385
  },
285
386
  cache: {
387
+ description: 'Cache layer with backend selection, TTL, and entry/invalidation rules',
388
+ example: 'cache name=userCache backend=redis prefix="user:" ttl=3600 export=true',
286
389
  props: {
287
390
  name: { required: true, kind: 'identifier' },
288
391
  backend: { kind: 'string' },
@@ -293,12 +396,16 @@ export const NODE_SCHEMAS = {
293
396
  allowedChildren: ['entry', 'invalidate'],
294
397
  },
295
398
  module: {
399
+ description: 'Logical module grouping for code organization',
400
+ example: 'module name=auth export=true',
296
401
  props: {
297
402
  name: { required: true, kind: 'identifier' },
298
403
  export: { kind: 'boolean' },
299
404
  },
300
405
  },
301
406
  provider: {
407
+ description: 'React context provider component with typed value',
408
+ example: 'provider name=AuthProvider type=AuthContext',
302
409
  props: {
303
410
  name: { required: true, kind: 'identifier' },
304
411
  type: { required: true, kind: 'typeAnnotation' },
@@ -306,6 +413,8 @@ export const NODE_SCHEMAS = {
306
413
  allowedChildren: ['prop', 'handler'],
307
414
  },
308
415
  hook: {
416
+ description: 'React custom hook with lifecycle methods',
417
+ example: 'hook name=useAuth returns=AuthState\n handler <<<\n const [user, setUser] = useState(null)\n return { user }\n >>>',
309
418
  props: {
310
419
  name: { required: true, kind: 'identifier' },
311
420
  params: { kind: 'string' },
@@ -314,6 +423,8 @@ export const NODE_SCHEMAS = {
314
423
  allowedChildren: ['handler', 'memo', 'callback', 'ref', 'effect'],
315
424
  },
316
425
  effect: {
426
+ description: 'React useEffect — side effect with dependency tracking',
427
+ example: 'effect deps="userId" once=false\n handler <<<\n fetchUser(userId)\n >>>\n cleanup <<<\n controller.abort()\n >>>',
317
428
  props: {
318
429
  name: { kind: 'identifier' },
319
430
  deps: { kind: 'string' },
@@ -323,6 +434,8 @@ export const NODE_SCHEMAS = {
323
434
  },
324
435
  // ── Web / UI node types ──────────────────────────────────────────────
325
436
  page: {
437
+ description: 'Page/route component — generates Next.js page or React route component',
438
+ example: 'page name=Dashboard client=true route="/dashboard"',
326
439
  props: {
327
440
  name: { required: true, kind: 'identifier' },
328
441
  client: { kind: 'boolean' },
@@ -332,15 +445,21 @@ export const NODE_SCHEMAS = {
332
445
  },
333
446
  },
334
447
  layout: {
448
+ description: 'Layout wrapper component (Next.js layout or generic wrapper)',
449
+ example: 'layout lang="en" route="/"',
335
450
  props: {
336
451
  lang: { kind: 'string' },
337
452
  route: { kind: 'string' },
338
453
  },
339
454
  },
340
455
  loading: {
456
+ description: 'Next.js loading.tsx — shown while page content loads',
457
+ example: 'loading\n spinner',
341
458
  props: {},
342
459
  },
343
460
  metadata: {
461
+ description: 'Page metadata — title, description, og:image for SEO',
462
+ example: 'metadata title="Dashboard" description="Your account overview"',
344
463
  props: {
345
464
  title: { kind: 'string' },
346
465
  description: { kind: 'string' },
@@ -349,11 +468,15 @@ export const NODE_SCHEMAS = {
349
468
  },
350
469
  },
351
470
  link: {
471
+ description: 'Navigation link to an internal route',
472
+ example: 'link to="/about"\n text "About Us"',
352
473
  props: {
353
474
  to: { required: true, kind: 'string' },
354
475
  },
355
476
  },
356
477
  textarea: {
478
+ description: 'Multi-line text input with optional two-way binding',
479
+ example: 'textarea bind=notes placeholder="Enter notes..."',
357
480
  props: {
358
481
  bind: { kind: 'identifier' },
359
482
  placeholder: { kind: 'string' },
@@ -361,6 +484,8 @@ export const NODE_SCHEMAS = {
361
484
  },
362
485
  },
363
486
  slider: {
487
+ description: 'Range slider input with min/max/step',
488
+ example: 'slider bind=volume min=0 max=100 step=5',
364
489
  props: {
365
490
  bind: { kind: 'identifier' },
366
491
  min: { kind: 'number' },
@@ -369,17 +494,23 @@ export const NODE_SCHEMAS = {
369
494
  },
370
495
  },
371
496
  toggle: {
497
+ description: 'Boolean toggle/switch input',
498
+ example: 'toggle bind=darkMode',
372
499
  props: {
373
500
  bind: { kind: 'identifier' },
374
501
  },
375
502
  },
376
503
  grid: {
504
+ description: 'CSS grid container with column count and gap',
505
+ example: 'grid cols=3 gap=4',
377
506
  props: {
378
507
  cols: { kind: 'number' },
379
508
  gap: { kind: 'number' },
380
509
  },
381
510
  },
382
511
  component: {
512
+ description: 'Reference to an external or dynamic React component',
513
+ example: 'component ref=UserCard props="user,onEdit"',
383
514
  props: {
384
515
  ref: { kind: 'identifier' },
385
516
  name: { kind: 'identifier' },
@@ -390,23 +521,31 @@ export const NODE_SCHEMAS = {
390
521
  },
391
522
  },
392
523
  icon: {
524
+ description: 'Icon component by name',
525
+ example: 'icon name=ArrowRight',
393
526
  props: {
394
527
  name: { required: true, kind: 'identifier' },
395
528
  },
396
529
  },
397
530
  logic: {
531
+ description: 'Inline TypeScript logic block — embedded code in a component',
532
+ example: 'logic <<<\n const filtered = items.filter(i => i.active)\n>>>',
398
533
  props: {
399
534
  code: { kind: 'rawBlock' },
400
535
  },
401
536
  allowedChildren: ['handler'],
402
537
  },
403
538
  form: {
539
+ description: 'HTML form element with action and method',
540
+ example: 'form action="/api/submit" method="POST"',
404
541
  props: {
405
542
  action: { kind: 'string' },
406
543
  method: { kind: 'string' },
407
544
  },
408
545
  },
409
546
  svg: {
547
+ description: 'SVG element with viewBox, dimensions, and fill/stroke',
548
+ example: 'svg icon=logo width=24 height=24 viewBox="0 0 24 24"',
410
549
  props: {
411
550
  icon: { kind: 'string' },
412
551
  size: { kind: 'number' },
@@ -418,6 +557,269 @@ export const NODE_SCHEMAS = {
418
557
  stroke: { kind: 'string' },
419
558
  },
420
559
  },
560
+ // ── Cross-target nodes ────────────────────────────────────────────────
561
+ handler: {
562
+ description: 'Code block — the body of a function, method, route, tool, or event handler. Use <<<...>>> for multiline code.',
563
+ example: 'handler <<<\n const result = await doWork();\n return result;\n>>>',
564
+ props: {
565
+ code: { kind: 'rawBlock' },
566
+ lang: { kind: 'string' },
567
+ },
568
+ },
569
+ conditional: {
570
+ description: 'Conditional rendering — shows children when if-expression is truthy',
571
+ example: 'conditional if="user !== null"',
572
+ props: {
573
+ if: { required: true, kind: 'rawExpr' },
574
+ },
575
+ },
576
+ // ── Express / Backend nodes ───────────────────────────────────────────
577
+ server: {
578
+ description: 'Express server entry point with name and port',
579
+ example: 'server name=MyAPI port=3000\n route path="/api/users" method=get\n handler <<<\n res.json(users)\n >>>',
580
+ props: {
581
+ name: { kind: 'identifier' },
582
+ port: { kind: 'number' },
583
+ },
584
+ allowedChildren: ['route', 'middleware', 'websocket', 'model', 'dependency', 'job', 'storage', 'email'],
585
+ },
586
+ route: {
587
+ description: 'HTTP route — defines an endpoint with method, path, and handler',
588
+ example: 'route path="/api/users" method=get\n handler <<<\n res.json(users)\n >>>',
589
+ props: {
590
+ path: { required: true, kind: 'string' },
591
+ method: { kind: 'identifier' },
592
+ },
593
+ allowedChildren: [
594
+ 'handler',
595
+ 'middleware',
596
+ 'schema',
597
+ 'auth',
598
+ 'validate',
599
+ 'params',
600
+ 'respond',
601
+ 'error',
602
+ 'guard',
603
+ 'derive',
604
+ 'branch',
605
+ 'each',
606
+ 'collect',
607
+ 'effect',
608
+ ],
609
+ },
610
+ middleware: {
611
+ description: 'Express middleware — named built-in (json, cors, rateLimit) or custom with handler',
612
+ example: 'middleware name=cors\nmiddleware name=auth\n handler <<<\n if (!req.user) return res.status(401).json({ error: "Unauthorized" });\n next();\n >>>',
613
+ props: {
614
+ name: { required: true, kind: 'identifier' },
615
+ names: { kind: 'string' },
616
+ },
617
+ allowedChildren: ['handler'],
618
+ },
619
+ params: {
620
+ description: 'Query/path parameter definitions for a route — items is an array of {name, type, default?}',
621
+ example: 'params items="[{name:page,type:number,default:1},{name:limit,type:number,default:20}]"',
622
+ props: {
623
+ items: { kind: 'rawExpr' },
624
+ },
625
+ },
626
+ auth: {
627
+ description: 'Authentication requirement on a route — required or optional',
628
+ example: 'auth mode=required',
629
+ props: {
630
+ mode: { kind: 'identifier' },
631
+ },
632
+ },
633
+ validate: {
634
+ description: 'Request validation schema reference for a route',
635
+ example: 'validate schema=CreateUserSchema',
636
+ props: {
637
+ schema: { kind: 'identifier' },
638
+ },
639
+ },
640
+ respond: {
641
+ description: 'Declarative HTTP response — status, body, redirect, or error',
642
+ example: 'respond status=200 json="{ success: true }"',
643
+ props: {
644
+ status: { kind: 'number' },
645
+ json: { kind: 'rawExpr' },
646
+ text: { kind: 'string' },
647
+ error: { kind: 'string' },
648
+ redirect: { kind: 'string' },
649
+ type: { kind: 'string' },
650
+ headers: { kind: 'rawExpr' },
651
+ },
652
+ },
653
+ schema: {
654
+ description: 'Request schema — TypeScript types for body, params, query, and response validation',
655
+ example: 'schema body=CreateUserInput params="{id: string}" response=UserResponse',
656
+ props: {
657
+ body: { kind: 'typeAnnotation' },
658
+ params: { kind: 'typeAnnotation' },
659
+ query: { kind: 'typeAnnotation' },
660
+ response: { kind: 'typeAnnotation' },
661
+ },
662
+ },
663
+ // ── MCP (Model Context Protocol) nodes ────────────────────────────────
664
+ mcp: {
665
+ description: 'MCP server definition — compiles to a full Model Context Protocol server with tools, resources, and prompts',
666
+ example: 'mcp name=FileTools version=1.0 transport=stdio\n tool name=readFile\n param name=path type=string required=true\n handler <<<\n return { content: [{ type: "text", text: await fs.readFile(path) }] };\n >>>',
667
+ props: {
668
+ name: { kind: 'identifier' },
669
+ version: { kind: 'string' },
670
+ transport: { kind: 'identifier' },
671
+ port: { kind: 'number' },
672
+ allowlist: { kind: 'string' },
673
+ allowedPaths: { kind: 'string' },
674
+ baseDir: { kind: 'string' },
675
+ },
676
+ allowedChildren: ['tool', 'resource', 'prompt'],
677
+ },
678
+ tool: {
679
+ description: 'MCP tool definition — a callable function exposed to AI agents with typed params and security guards',
680
+ example: 'tool name=searchFiles\n description text="Search for files"\n param name=query type=string required=true\n guard type=sanitize param=query\n handler <<<\n return { content: [{ type: "text", text: results }] };\n >>>',
681
+ props: {
682
+ name: { required: true, kind: 'identifier' },
683
+ },
684
+ allowedChildren: ['param', 'handler', 'description', 'guard', 'sampling', 'elicitation'],
685
+ },
686
+ resource: {
687
+ description: 'MCP resource — a data source exposed to AI agents via URI. Use {variables} for templated URIs.',
688
+ example: 'resource name=config uri="config://app"\n description text="Application configuration"\n handler <<<\n return { contents: [{ uri: uri.href, text: JSON.stringify(config) }] };\n >>>',
689
+ props: {
690
+ name: { required: true, kind: 'identifier' },
691
+ uri: { required: true, kind: 'string' },
692
+ },
693
+ allowedChildren: ['param', 'handler', 'description', 'guard'],
694
+ },
695
+ param: {
696
+ description: 'Parameter definition for a tool, resource, or prompt — name, type, required, default, and description',
697
+ example: 'param name=query type=string required=true description="Search query"',
698
+ props: {
699
+ name: { required: true, kind: 'identifier' },
700
+ type: { kind: 'identifier' },
701
+ required: { kind: 'boolean' },
702
+ default: { kind: 'rawExpr' },
703
+ description: { kind: 'string' },
704
+ min: { kind: 'number' },
705
+ max: { kind: 'number' },
706
+ },
707
+ allowedChildren: ['guard', 'description'],
708
+ },
709
+ prompt: {
710
+ description: 'MCP prompt template — a reusable system prompt exposed to AI agents',
711
+ example: 'prompt name=analyzeFile\n param name=filePath type=string required=true\n handler <<<\n return { messages: [{ role: "user", content: { type: "text", text: `Analyze ${filePath}` } }] };\n >>>',
712
+ props: {
713
+ name: { required: true, kind: 'identifier' },
714
+ },
715
+ allowedChildren: ['param', 'handler', 'description'],
716
+ },
717
+ description: {
718
+ description: 'Documentation text for a tool, resource, or prompt',
719
+ example: 'description text="Read a file within allowed directories"',
720
+ props: {
721
+ text: { kind: 'string' },
722
+ value: { kind: 'string' },
723
+ },
724
+ },
725
+ sampling: {
726
+ description: 'MCP sampling configuration — requests LLM completion within a tool handler',
727
+ example: 'sampling maxTokens=500',
728
+ props: {
729
+ maxTokens: { kind: 'number' },
730
+ },
731
+ },
732
+ elicitation: {
733
+ description: 'MCP elicitation — requests user input during tool execution',
734
+ example: 'elicitation message="Confirm deletion?"',
735
+ props: {
736
+ message: { kind: 'string' },
737
+ text: { kind: 'string' },
738
+ },
739
+ },
740
+ // ── React / UI element nodes ──────────────────────────────────────────
741
+ screen: {
742
+ description: 'Full-screen container component (minHeight: 100vh flex column)',
743
+ example: 'screen name=Dashboard\n row\n text value="Welcome"',
744
+ props: {
745
+ name: { kind: 'identifier' },
746
+ },
747
+ },
748
+ row: {
749
+ description: 'Flexbox row container — horizontal layout',
750
+ example: 'row\n col\n text value="Left"\n col\n text value="Right"',
751
+ props: {},
752
+ },
753
+ col: {
754
+ description: 'Flexbox column container — vertical layout',
755
+ example: 'col\n text value="Stacked content"',
756
+ props: {},
757
+ },
758
+ card: {
759
+ description: 'Card component — rounded container with shadow',
760
+ example: 'card\n text value="Card title"\n text value="Card body"',
761
+ props: {},
762
+ },
763
+ text: {
764
+ description: 'Text element — renders a paragraph or span with content',
765
+ example: 'text value="Hello, world!"',
766
+ props: {
767
+ value: { kind: 'string' },
768
+ },
769
+ },
770
+ button: {
771
+ description: 'Button element with label text and optional navigation',
772
+ example: 'button text="Submit"\nbutton text="Go Home" to="/home"',
773
+ props: {
774
+ text: { kind: 'string' },
775
+ to: { kind: 'string' },
776
+ },
777
+ },
778
+ input: {
779
+ description: 'Form input — text, number, email, etc. with optional state binding',
780
+ example: 'input bind=email type=email placeholder="Enter email"',
781
+ props: {
782
+ bind: { kind: 'identifier' },
783
+ type: { kind: 'identifier' },
784
+ placeholder: { kind: 'string' },
785
+ },
786
+ },
787
+ image: {
788
+ description: 'Image element with source and alt text',
789
+ example: 'image src="/logo.png" alt="Company logo"',
790
+ props: {
791
+ src: { required: true, kind: 'string' },
792
+ alt: { kind: 'string' },
793
+ },
794
+ },
795
+ modal: {
796
+ description: 'Modal dialog overlay — renders a centered popup',
797
+ example: 'modal\n text value="Are you sure?"\n button text="Confirm"\n button text="Cancel"',
798
+ props: {},
799
+ },
800
+ table: {
801
+ description: 'Table container for tabular data display',
802
+ example: 'table\n header\n text value="Name"\n text value="Email"',
803
+ props: {},
804
+ },
805
+ header: {
806
+ description: 'Header/heading element or table header row',
807
+ example: 'header\n text value="Page Title"',
808
+ props: {},
809
+ },
810
+ tabs: {
811
+ description: 'Tabbed navigation container',
812
+ example: 'tabs\n tab label="Profile"\n text value="Profile content"\n tab label="Settings"\n text value="Settings content"',
813
+ props: {},
814
+ },
815
+ theme: {
816
+ description: 'Theme/styling definitions — CSS custom properties and style objects applied to descendant nodes',
817
+ example: 'theme styles="{ background: #1a1a2e, color: #e0e0e0, fontFamily: system-ui }"',
818
+ props: {
819
+ name: { kind: 'identifier' },
820
+ styles: { kind: 'rawExpr' },
821
+ },
822
+ },
421
823
  };
422
824
  /**
423
825
  * Validate an IR tree against the schema definitions (required props, allowed children, cross-prop rules).
@@ -457,13 +859,22 @@ function validateNode(node, violations) {
457
859
  col: node.loc?.col,
458
860
  });
459
861
  }
862
+ // Cross-prop validation: guard needs expr (assertion) OR kind/type (security guard)
863
+ if (node.type === 'guard' && !('expr' in props) && !('kind' in props) && !('type' in props)) {
864
+ violations.push({
865
+ nodeType: 'guard',
866
+ message: "'guard' requires either 'expr' (assertion) or 'kind'/'type' (security guard)",
867
+ line: node.loc?.line,
868
+ col: node.loc?.col,
869
+ });
870
+ }
460
871
  // Check allowed children
461
872
  if (schema.allowedChildren && node.children) {
462
873
  for (const child of node.children) {
463
874
  if (!schema.allowedChildren.includes(child.type)) {
464
875
  // Don't flag structural children that are consumed by parents
465
876
  // (handler, reason, evidence, needs, etc.)
466
- const universalChildren = ['handler', 'cleanup', 'reason', 'evidence', 'needs', 'signal'];
877
+ const universalChildren = ['handler', 'cleanup', 'reason', 'evidence', 'needs', 'signal', 'doc'];
467
878
  if (!universalChildren.includes(child.type)) {
468
879
  violations.push({
469
880
  nodeType: node.type,
@@ -483,4 +894,31 @@ function validateNode(node, violations) {
483
894
  }
484
895
  }
485
896
  }
897
+ /**
898
+ * Export the full KERN schema as a JSON-serializable object.
899
+ *
900
+ * Designed for LLM consumption — an LLM can call `kern schema --json`
901
+ * and use the output to generate valid `.kern` files.
902
+ *
903
+ * @param runtime - Optional KernRuntime instance (includes evolved types)
904
+ */
905
+ export function exportSchemaJSON(runtime) {
906
+ const rt = runtime ?? defaultRuntime;
907
+ const schemaedTypes = new Set(Object.keys(NODE_SCHEMAS));
908
+ const unschemaed = NODE_TYPES.filter((t) => !schemaedTypes.has(t));
909
+ const evolvedTypes = [...rt.dynamicNodeTypes];
910
+ // Return defensive copies so callers can't mutate process-wide state
911
+ return {
912
+ version: KERN_VERSION,
913
+ nodeTypes: [...NODE_TYPES],
914
+ schemas: JSON.parse(JSON.stringify(NODE_SCHEMAS)),
915
+ unschemaed,
916
+ targets: [...VALID_TARGETS],
917
+ styleShorthands: { ...STYLE_SHORTHANDS },
918
+ valueShorthands: { ...VALUE_SHORTHANDS },
919
+ multilineBlockTypes: [...rt.multilineBlockTypes],
920
+ propKinds: ['identifier', 'typeAnnotation', 'importPath', 'rawExpr', 'rawBlock', 'string', 'boolean', 'number'],
921
+ ...(evolvedTypes.length > 0 ? { evolvedTypes } : {}),
922
+ };
923
+ }
486
924
  //# sourceMappingURL=schema.js.map