@kernlang/core 3.1.7 → 3.1.9

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 (49) hide show
  1. package/dist/codegen/data-layer.js +6 -6
  2. package/dist/codegen/data-layer.js.map +1 -1
  3. package/dist/codegen/events.js +4 -4
  4. package/dist/codegen/events.js.map +1 -1
  5. package/dist/codegen/functions.js +11 -7
  6. package/dist/codegen/functions.js.map +1 -1
  7. package/dist/codegen/helpers.d.ts +2 -0
  8. package/dist/codegen/helpers.js +24 -0
  9. package/dist/codegen/helpers.js.map +1 -1
  10. package/dist/codegen/screens.js +74 -63
  11. package/dist/codegen/screens.js.map +1 -1
  12. package/dist/codegen/type-system.js +30 -18
  13. package/dist/codegen/type-system.js.map +1 -1
  14. package/dist/codegen-core.js +16 -0
  15. package/dist/codegen-core.js.map +1 -1
  16. package/dist/errors.d.ts +3 -1
  17. package/dist/errors.js +3 -1
  18. package/dist/errors.js.map +1 -1
  19. package/dist/importer.d.ts +38 -0
  20. package/dist/importer.js +1135 -0
  21. package/dist/importer.js.map +1 -0
  22. package/dist/index.d.ts +7 -2
  23. package/dist/index.js +4 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/node-props.d.ts +5 -0
  26. package/dist/node-props.js.map +1 -1
  27. package/dist/parser-core.js +88 -4
  28. package/dist/parser-core.js.map +1 -1
  29. package/dist/parser-keywords.js +24 -0
  30. package/dist/parser-keywords.js.map +1 -1
  31. package/dist/runtime.js +1 -1
  32. package/dist/runtime.js.map +1 -1
  33. package/dist/schema.d.ts +25 -0
  34. package/dist/schema.js +998 -42
  35. package/dist/schema.js.map +1 -1
  36. package/dist/semantic-validator.d.ts +20 -0
  37. package/dist/semantic-validator.js +82 -0
  38. package/dist/semantic-validator.js.map +1 -0
  39. package/dist/spec.d.ts +1 -1
  40. package/dist/spec.js +1 -0
  41. package/dist/spec.js.map +1 -1
  42. package/dist/types.d.ts +8 -0
  43. package/dist/version-adapters.d.ts +1 -1
  44. package/dist/version-adapters.js +11 -0
  45. package/dist/version-adapters.js.map +1 -1
  46. package/dist/version-detect.d.ts +1 -1
  47. package/dist/version-detect.js +2 -0
  48. package/dist/version-detect.js.map +1 -1
  49. 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,787 @@ 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
+ },
823
+ // ── Backend: Stream / Spawn / Timer ───────────────────────────────────
824
+ stream: {
825
+ description: 'SSE stream route — sets up Server-Sent Events with heartbeat and structured emit helper',
826
+ example: 'route path="/api/stream" method=get\n stream\n spawn binary=ffmpeg args="[\'-i\',input]"\n on name=stdout\n handler <<<\n emit({ chunk: chunk.toString() })\n >>>',
827
+ props: {
828
+ name: { kind: 'identifier' },
829
+ source: { kind: 'rawExpr' },
830
+ append: { kind: 'boolean' },
831
+ },
832
+ allowedChildren: ['spawn', 'handler', 'on', 'timer'],
833
+ },
834
+ spawn: {
835
+ description: 'Child process — spawns a binary with shell:false safety, SIGTERM/SIGKILL escalation, and abort-on-disconnect',
836
+ example: "spawn binary=ffmpeg args=\"['-i',input,'-f','mp3','pipe:1']\" timeout=30",
837
+ props: {
838
+ binary: { required: true, kind: 'string' },
839
+ args: { kind: 'rawExpr' },
840
+ timeout: { kind: 'number' },
841
+ stdin: { kind: 'rawExpr' },
842
+ },
843
+ allowedChildren: ['on', 'env', 'handler'],
844
+ },
845
+ timer: {
846
+ description: 'Request timeout — wraps handler in a deadline with AbortController and configurable timeout handler',
847
+ example: 'timer timeout=15\n handler <<<\n const result = await longRunningTask();\n res.json(result);\n >>>',
848
+ props: {
849
+ timeout: { kind: 'number' },
850
+ name: { kind: 'identifier' },
851
+ },
852
+ allowedChildren: ['handler', 'on'],
853
+ },
854
+ env: {
855
+ description: 'Environment variable — declares a required or optional env var, used in spawn or server config',
856
+ example: 'env name=DATABASE_URL required=true',
857
+ props: {
858
+ name: { required: true, kind: 'identifier' },
859
+ value: { kind: 'rawExpr' },
860
+ required: { kind: 'boolean' },
861
+ },
862
+ },
863
+ trigger: {
864
+ description: 'Event trigger — fires an action on a named event from a source',
865
+ example: 'trigger kind=webhook on=push from=github',
866
+ props: {
867
+ kind: { kind: 'identifier' },
868
+ on: { kind: 'string' },
869
+ from: { kind: 'string' },
870
+ },
871
+ allowedChildren: ['handler'],
872
+ },
873
+ // ── Next.js production patterns ───────────────────────────────────────
874
+ fetch: {
875
+ description: 'Server-side data fetch — generates an async fetch call in a Next.js server component',
876
+ example: 'fetch name=posts url="/api/posts" options="{ next: { revalidate: 60 } }"',
877
+ props: {
878
+ name: { required: true, kind: 'identifier' },
879
+ url: { required: true, kind: 'rawExpr' },
880
+ options: { kind: 'rawExpr' },
881
+ },
882
+ },
883
+ generateMetadata: {
884
+ description: 'Next.js generateMetadata export — async function for dynamic page metadata',
885
+ example: 'generateMetadata params="slug:string"',
886
+ props: {
887
+ params: { kind: 'string' },
888
+ },
889
+ allowedChildren: ['handler'],
890
+ },
891
+ notFound: {
892
+ description: 'Next.js notFound() call — triggers 404 page',
893
+ example: 'notFound',
894
+ props: {},
895
+ },
896
+ redirect: {
897
+ description: 'Next.js redirect() call — server-side redirect to another route',
898
+ example: 'redirect to="/login"',
899
+ props: {
900
+ to: { required: true, kind: 'string' },
901
+ },
902
+ },
903
+ // ── CLI nodes ─────────────────────────────────────────────────────────
904
+ cli: {
905
+ description: 'CLI application root — defines a command-line tool with commands, flags, and imports',
906
+ example: 'cli name=myapp version=1.0.0 description="My CLI tool"\n command name=init description="Initialize project"\n handler <<<\n console.log("Initializing...")\n >>>',
907
+ props: {
908
+ name: { required: true, kind: 'identifier' },
909
+ version: { kind: 'string' },
910
+ description: { kind: 'string' },
911
+ },
912
+ allowedChildren: ['command', 'flag', 'import'],
913
+ },
914
+ command: {
915
+ description: 'CLI subcommand with arguments, flags, and handler',
916
+ example: 'command name=deploy description="Deploy to production" alias=d\n arg name=target type=string required=true\n flag name=dry-run alias=n type=boolean\n handler <<<\n deploy(target, { dryRun })\n >>>',
917
+ props: {
918
+ name: { required: true, kind: 'identifier' },
919
+ description: { kind: 'string' },
920
+ alias: { kind: 'string' },
921
+ },
922
+ allowedChildren: ['arg', 'flag', 'handler', 'import'],
923
+ },
924
+ arg: {
925
+ description: 'CLI positional argument — required args must come before optional ones',
926
+ example: 'arg name=target type=string required=true description="Deploy target"',
927
+ props: {
928
+ name: { required: true, kind: 'identifier' },
929
+ type: { kind: 'identifier' },
930
+ required: { kind: 'boolean' },
931
+ description: { kind: 'string' },
932
+ default: { kind: 'rawExpr' },
933
+ },
934
+ },
935
+ flag: {
936
+ description: 'CLI flag/option — named with optional short alias',
937
+ example: 'flag name=verbose alias=v type=boolean description="Enable verbose output"',
938
+ props: {
939
+ name: { required: true, kind: 'identifier' },
940
+ alias: { kind: 'string' },
941
+ type: { kind: 'identifier' },
942
+ required: { kind: 'boolean' },
943
+ description: { kind: 'string' },
944
+ default: { kind: 'rawExpr' },
945
+ },
946
+ },
947
+ // ── React lifecycle hooks (Batch 2) ───────────────────────────────────
948
+ memo: {
949
+ description: 'React useMemo — memoized computation with dependency tracking',
950
+ example: 'memo name=filtered deps="items,filter"\n handler <<<\n return items.filter(i => i.active)\n >>>',
951
+ props: {
952
+ name: { required: true, kind: 'identifier' },
953
+ deps: { kind: 'string' },
954
+ },
955
+ allowedChildren: ['handler'],
956
+ },
957
+ callback: {
958
+ description: 'React useCallback — memoized function reference with dependency tracking',
959
+ example: 'callback name=handleSubmit deps="formData" async=true\n handler <<<\n await api.submit(formData)\n >>>',
960
+ props: {
961
+ name: { required: true, kind: 'identifier' },
962
+ params: { kind: 'string' },
963
+ deps: { kind: 'string' },
964
+ async: { kind: 'boolean' },
965
+ },
966
+ allowedChildren: ['handler'],
967
+ },
968
+ ref: {
969
+ description: 'React useRef — mutable ref object that persists across renders',
970
+ example: 'ref name=inputRef type=HTMLInputElement initial=null',
971
+ props: {
972
+ name: { required: true, kind: 'identifier' },
973
+ type: { kind: 'typeAnnotation' },
974
+ initial: { kind: 'rawExpr' },
975
+ },
976
+ },
977
+ context: {
978
+ description: 'React useContext — consume a React context by name',
979
+ example: 'context name=theme source=ThemeContext',
980
+ props: {
981
+ name: { required: true, kind: 'identifier' },
982
+ source: { required: true, kind: 'identifier' },
983
+ },
984
+ },
985
+ prop: {
986
+ description: 'Component prop declaration — name, type, optionality, and default value',
987
+ example: 'prop name=title type=string\nprop name=count type=number optional=true default=0',
988
+ props: {
989
+ name: { required: true, kind: 'identifier' },
990
+ type: { kind: 'typeAnnotation' },
991
+ optional: { kind: 'boolean' },
992
+ default: { kind: 'rawExpr' },
993
+ },
994
+ },
995
+ returns: {
996
+ description: 'Return type declaration or return statement for a hook/function',
997
+ example: 'returns type=AuthState with="{ user, login, logout }"',
998
+ props: {
999
+ name: { kind: 'identifier' },
1000
+ type: { kind: 'typeAnnotation' },
1001
+ with: { kind: 'rawExpr' },
1002
+ },
1003
+ },
1004
+ render: {
1005
+ description: 'Render function — JSX output block for a component or hook',
1006
+ example: 'render\n handler <<<\n return <div>{children}</div>\n >>>',
1007
+ props: {},
1008
+ allowedChildren: ['handler'],
1009
+ },
1010
+ template: {
1011
+ description: 'Reusable template with named slots — defines a composable layout pattern',
1012
+ example: 'template name=PageLayout\n slot name=header\n slot name=content\n slot name=footer optional=true',
1013
+ props: {
1014
+ name: { required: true, kind: 'identifier' },
1015
+ },
1016
+ allowedChildren: ['slot', 'body', 'handler'],
1017
+ },
1018
+ // ── Data layer (Batch 3) ──────────────────────────────────────────────
1019
+ column: {
1020
+ description: 'Database column definition within a model — type, constraints, and default value',
1021
+ example: 'column name=email type=string unique=true\ncolumn name=age type=number optional=true',
1022
+ props: {
1023
+ name: { required: true, kind: 'identifier' },
1024
+ type: { kind: 'typeAnnotation' },
1025
+ optional: { kind: 'boolean' },
1026
+ primary: { kind: 'boolean' },
1027
+ unique: { kind: 'boolean' },
1028
+ default: { kind: 'rawExpr' },
1029
+ },
1030
+ },
1031
+ relation: {
1032
+ description: 'Database relation — defines a foreign key relationship between models',
1033
+ example: 'relation name=author target=User kind=many-to-one',
1034
+ props: {
1035
+ name: { required: true, kind: 'identifier' },
1036
+ target: { required: true, kind: 'identifier' },
1037
+ kind: { kind: 'string' },
1038
+ },
1039
+ },
1040
+ inject: {
1041
+ description: 'Dependency injection — inject a service or value into the current scope',
1042
+ example: 'inject name=db type=Database from="./database.js"',
1043
+ props: {
1044
+ name: { required: true, kind: 'identifier' },
1045
+ type: { kind: 'typeAnnotation' },
1046
+ from: { kind: 'rawExpr' },
1047
+ with: { kind: 'rawExpr' },
1048
+ },
1049
+ },
1050
+ entry: {
1051
+ description: 'Cache entry — defines a cached value with key and optional strategy',
1052
+ example: 'entry name=userProfile key="user:{id}"\n strategy name=stale-while-revalidate max=60',
1053
+ props: {
1054
+ name: { required: true, kind: 'identifier' },
1055
+ key: { kind: 'string' },
1056
+ },
1057
+ allowedChildren: ['strategy', 'handler'],
1058
+ },
1059
+ invalidate: {
1060
+ description: 'Cache invalidation rule — trigger cache clearing on an event',
1061
+ example: 'invalidate on=userUpdate tags="user,profile"',
1062
+ props: {
1063
+ on: { required: true, kind: 'string' },
1064
+ tags: { kind: 'string' },
1065
+ },
1066
+ },
1067
+ signal: {
1068
+ description: 'Reactive signal — named state that triggers updates on change (used in hooks/components)',
1069
+ example: 'signal name=isLoading',
1070
+ props: {
1071
+ name: { required: true, kind: 'identifier' },
1072
+ },
1073
+ },
1074
+ // ── Structural + UI controls (Batch 4) ────────────────────────────────
1075
+ section: {
1076
+ description: 'Semantic section container — groups related content with optional title',
1077
+ example: 'section title="User Settings"',
1078
+ props: {
1079
+ title: { kind: 'string' },
1080
+ },
1081
+ },
1082
+ list: {
1083
+ description: 'List container — renders child items as an ordered or unordered list',
1084
+ example: 'list\n item value="First"\n item value="Second"',
1085
+ props: {},
1086
+ allowedChildren: ['item'],
1087
+ },
1088
+ item: {
1089
+ description: 'List item — single entry within a list container',
1090
+ example: 'item value="Buy groceries"',
1091
+ props: {
1092
+ value: { kind: 'string' },
1093
+ },
1094
+ },
1095
+ option: {
1096
+ description: 'Select option — a selectable choice within a select dropdown',
1097
+ example: 'option value=admin label="Administrator"',
1098
+ props: {
1099
+ value: { required: true, kind: 'string' },
1100
+ label: { kind: 'string' },
1101
+ },
1102
+ },
1103
+ select: {
1104
+ description: 'Select dropdown — bound to state with child options',
1105
+ example: 'select bind=role\n option value=admin label="Admin"\n option value=user label="User"',
1106
+ props: {
1107
+ bind: { kind: 'identifier' },
1108
+ },
1109
+ allowedChildren: ['option'],
1110
+ },
1111
+ slot: {
1112
+ description: 'Template slot — named insertion point within a template',
1113
+ example: 'slot name=header optional=true default="Default Header"',
1114
+ props: {
1115
+ name: { required: true, kind: 'identifier' },
1116
+ slotType: { kind: 'string' },
1117
+ optional: { kind: 'boolean' },
1118
+ default: { kind: 'rawExpr' },
1119
+ },
1120
+ },
1121
+ body: {
1122
+ description: 'Body block — raw code content for templates or structural containers',
1123
+ example: 'body <<<\n <main>{children}</main>\n>>>',
1124
+ props: {
1125
+ code: { kind: 'rawBlock' },
1126
+ },
1127
+ },
1128
+ // ── Phase 3: Remaining node schemas (100% coverage) ───────────────────
1129
+ // Terminal / Ink UI
1130
+ scroll: { description: 'Scrollable container', example: 'scroll', props: {} },
1131
+ progress: {
1132
+ description: 'Progress bar — shows completion status',
1133
+ example: 'progress value=75 max=100 label="Loading"',
1134
+ props: { value: { kind: 'number' }, max: { kind: 'number' }, label: { kind: 'string' } },
1135
+ },
1136
+ divider: { description: 'Visual divider / horizontal rule', example: 'divider', props: {} },
1137
+ codeblock: {
1138
+ description: 'Code block with syntax highlighting',
1139
+ example: 'codeblock lang=typescript <<<\n const x = 1;\n>>>',
1140
+ props: { lang: { kind: 'string' }, code: { kind: 'rawBlock' } },
1141
+ },
1142
+ tab: {
1143
+ description: 'Single tab within a tabs container',
1144
+ example: 'tab label="Settings"\n text value="Settings content"',
1145
+ props: { label: { kind: 'string' } },
1146
+ },
1147
+ separator: { description: 'Ink horizontal rule / separator', example: 'separator', props: {} },
1148
+ thead: { description: 'Table head section', example: 'thead', props: {} },
1149
+ tbody: { description: 'Table body section', example: 'tbody', props: {} },
1150
+ tr: { description: 'Table row', example: 'tr', props: {} },
1151
+ th: { description: 'Table header cell', example: 'th value="Name"', props: { value: { kind: 'string' } } },
1152
+ td: { description: 'Table data cell', example: 'td value="John"', props: { value: { kind: 'string' } } },
1153
+ scoreboard: {
1154
+ description: 'Dashboard scoreboard — container for metric widgets',
1155
+ example: 'scoreboard\n metric label="Users" value=1234',
1156
+ props: {},
1157
+ allowedChildren: ['metric'],
1158
+ },
1159
+ metric: {
1160
+ description: 'Single metric display — label + value pair',
1161
+ example: 'metric label="Active Users" value={{users.length}}',
1162
+ props: { label: { required: true, kind: 'string' }, value: { required: true, kind: 'rawExpr' } },
1163
+ },
1164
+ spinner: {
1165
+ description: 'Loading spinner with optional text',
1166
+ example: 'spinner text="Loading..."',
1167
+ props: { text: { kind: 'string' } },
1168
+ },
1169
+ box: {
1170
+ description: 'Ink box container with border styling',
1171
+ example: 'box borderStyle=round borderColor=green',
1172
+ props: { borderStyle: { kind: 'string' }, borderColor: { kind: 'string' } },
1173
+ },
1174
+ gradient: {
1175
+ description: 'Gradient text effect (Ink)',
1176
+ example: 'gradient text="Hello" colors="red,blue"',
1177
+ props: { text: { kind: 'string' }, colors: { kind: 'string' } },
1178
+ },
1179
+ // Ink-specific input nodes
1180
+ 'input-area': { description: 'Ink text input area', example: 'input-area', props: {} },
1181
+ 'output-area': { description: 'Ink text output area', example: 'output-area', props: {} },
1182
+ 'text-input': {
1183
+ description: 'Ink text input with binding',
1184
+ example: 'text-input value={{query}} onChange={{setQuery}} placeholder="Search..."',
1185
+ props: { value: { kind: 'rawExpr' }, onChange: { kind: 'rawExpr' }, placeholder: { kind: 'string' } },
1186
+ },
1187
+ 'select-input': {
1188
+ description: 'Ink select input — choose from a list',
1189
+ example: 'select-input items={{options}} onSelect={{handleSelect}}',
1190
+ props: { items: { kind: 'rawExpr' }, onSelect: { kind: 'rawExpr' } },
1191
+ },
1192
+ // Control flow / structural
1193
+ repl: {
1194
+ description: 'Read-eval-print loop — interactive terminal command loop',
1195
+ example: 'repl name=shell prompt=">"',
1196
+ props: { name: { kind: 'identifier' }, prompt: { kind: 'string' } },
1197
+ allowedChildren: ['on', 'handler'],
1198
+ },
1199
+ parallel: {
1200
+ description: 'Parallel execution — run children concurrently',
1201
+ example: 'parallel\n dispatch to=worker1\n dispatch to=worker2',
1202
+ props: { name: { kind: 'identifier' } },
1203
+ },
1204
+ dispatch: {
1205
+ description: 'Dispatch an action or message to a target',
1206
+ example: 'dispatch to=worker payload={{data}}',
1207
+ props: { to: { required: true, kind: 'string' }, payload: { kind: 'rawExpr' } },
1208
+ },
1209
+ // biome-ignore lint/suspicious/noThenProperty: `then` is a valid KERN node type, not a Promise thenable
1210
+ then: {
1211
+ description: 'Sequential continuation — runs after parent completes',
1212
+ example: 'then\n handler <<<\n console.log("done")\n >>>',
1213
+ props: {},
1214
+ allowedChildren: ['handler'],
1215
+ },
1216
+ // Lifecycle / structural children
1217
+ singleton: {
1218
+ description: 'Singleton marker — service is instantiated once',
1219
+ example: 'singleton name=cache',
1220
+ props: { name: { kind: 'identifier' } },
1221
+ },
1222
+ constructor: {
1223
+ description: 'Constructor for a service — runs on instantiation',
1224
+ example: 'constructor params="size:number"\n handler <<<\n this.data = new Map();\n >>>',
1225
+ props: { params: { kind: 'string' } },
1226
+ allowedChildren: ['handler'],
1227
+ },
1228
+ cleanup: {
1229
+ description: 'Cleanup handler — runs on teardown (useEffect return, signal dispose)',
1230
+ example: 'cleanup <<<\n controller.abort();\n>>>',
1231
+ props: { code: { kind: 'rawBlock' } },
1232
+ },
1233
+ export: {
1234
+ description: 'Re-export statement — export names from another module',
1235
+ example: 'export from="./utils.js" names="add,subtract"',
1236
+ props: {
1237
+ from: { kind: 'importPath' },
1238
+ names: { kind: 'string' },
1239
+ types: { kind: 'string' },
1240
+ star: { kind: 'boolean' },
1241
+ default: { kind: 'identifier' },
1242
+ },
1243
+ },
1244
+ describe: {
1245
+ description: 'Test suite — groups related test cases',
1246
+ example: 'describe name="UserService"\n it name="creates a user"\n handler <<<\n expect(createUser()).toBeDefined();\n >>>',
1247
+ props: { name: { required: true, kind: 'string' } },
1248
+ allowedChildren: ['it', 'describe', 'handler'],
1249
+ },
1250
+ it: {
1251
+ description: 'Test case — single test assertion',
1252
+ example: 'it name="returns 200 on success"\n handler <<<\n expect(res.status).toBe(200);\n >>>',
1253
+ props: { name: { required: true, kind: 'string' } },
1254
+ allowedChildren: ['handler'],
1255
+ },
1256
+ // Ground layer — semantic reasoning
1257
+ path: {
1258
+ description: 'Decision path — a named branch in a resolve/branch tree',
1259
+ example: 'path value="/api/users"',
1260
+ props: { value: { required: true, kind: 'string' } },
1261
+ },
1262
+ resolve: {
1263
+ description: 'Resolution node — selects among candidates using a discriminator',
1264
+ example: 'resolve name=bestRoute\n candidate name=fast\n candidate name=reliable',
1265
+ props: { name: { kind: 'identifier' } },
1266
+ allowedChildren: ['candidate', 'discriminator', 'handler'],
1267
+ },
1268
+ candidate: {
1269
+ description: 'Candidate option within a resolve block',
1270
+ example: 'candidate name=primary\n handler <<<\n return fastPath();\n >>>',
1271
+ props: { name: { required: true, kind: 'identifier' } },
1272
+ allowedChildren: ['handler'],
1273
+ },
1274
+ discriminator: {
1275
+ description: 'Selection strategy for choosing among candidates',
1276
+ example: 'discriminator method=latency metric=p99',
1277
+ props: { method: { kind: 'identifier' }, metric: { kind: 'string' } },
1278
+ allowedChildren: ['handler'],
1279
+ },
1280
+ pattern: {
1281
+ description: 'Pattern match — structural matching on values',
1282
+ example: 'pattern name=classify on={{input.type}}',
1283
+ props: { name: { kind: 'identifier' }, on: { kind: 'rawExpr' } },
1284
+ allowedChildren: ['path', 'handler'],
1285
+ },
1286
+ apply: {
1287
+ description: 'Apply a transform or function to data',
1288
+ example: 'apply fn=normalize to={{rawData}}',
1289
+ props: { fn: { kind: 'identifier' }, to: { kind: 'rawExpr' } },
1290
+ },
1291
+ expect: {
1292
+ description: 'Assertion — declare an expected condition at runtime',
1293
+ example: 'expect expr={{items.length > 0}} message="Items must not be empty"',
1294
+ props: { expr: { required: true, kind: 'rawExpr' }, message: { kind: 'string' } },
1295
+ },
1296
+ recover: {
1297
+ description: 'Recovery handler — runs when a parent node fails',
1298
+ example: 'recover\n handler <<<\n return fallbackValue;\n >>>',
1299
+ props: {},
1300
+ allowedChildren: ['handler'],
1301
+ },
1302
+ strategy: {
1303
+ description: 'Retry/fallback strategy configuration',
1304
+ example: 'strategy name=exponential-backoff max=3 delay=1000',
1305
+ props: { name: { required: true, kind: 'identifier' }, max: { kind: 'number' }, delay: { kind: 'number' } },
1306
+ allowedChildren: ['handler'],
1307
+ },
1308
+ // Reason layer — metadata children
1309
+ reason: {
1310
+ description: 'Reason annotation — explains why a decision was made',
1311
+ example: 'reason text="Using cache to avoid repeated API calls"',
1312
+ props: { text: { kind: 'string' } },
1313
+ },
1314
+ evidence: {
1315
+ description: 'Evidence annotation — links to supporting data for a decision',
1316
+ example: 'evidence text="Benchmarks show 3x speedup" source="perf-report.md"',
1317
+ props: { text: { kind: 'string' }, source: { kind: 'string' } },
1318
+ },
1319
+ needs: {
1320
+ description: 'Confidence gap — declares what evidence is missing',
1321
+ example: 'needs text="Integration test for concurrent writes"',
1322
+ props: { text: { kind: 'string' } },
1323
+ },
1324
+ // Rule layer — native .kern lint rules
1325
+ rule: {
1326
+ description: 'Custom lint rule definition — matches patterns and emits findings',
1327
+ example: 'rule id=no-console severity=warning category=style\n message template="Avoid console.log in production"',
1328
+ props: {
1329
+ id: { required: true, kind: 'identifier' },
1330
+ severity: { kind: 'string' },
1331
+ category: { kind: 'string' },
1332
+ confidence: { kind: 'number' },
1333
+ },
1334
+ allowedChildren: ['message', 'handler'],
1335
+ },
1336
+ message: {
1337
+ description: 'Rule message template — the text shown when a lint rule matches',
1338
+ example: 'message template="Found {count} unused imports"',
1339
+ props: { template: { kind: 'string' } },
1340
+ },
421
1341
  };
422
1342
  /**
423
1343
  * Validate an IR tree against the schema definitions (required props, allowed children, cross-prop rules).
@@ -433,54 +1353,90 @@ export function validateSchema(root) {
433
1353
  validateNode(root, violations);
434
1354
  return violations;
435
1355
  }
436
- function validateNode(node, violations) {
437
- const schema = Object.hasOwn(NODE_SCHEMAS, node.type) ? NODE_SCHEMAS[node.type] : undefined;
438
- if (schema) {
439
- const props = node.props || {};
440
- // Check required props
441
- for (const [propName, propSchema] of Object.entries(schema.props)) {
442
- if (propSchema.required && !(propName in props)) {
443
- violations.push({
444
- nodeType: node.type,
445
- message: `'${node.type}' requires prop '${propName}'`,
446
- line: node.loc?.line,
447
- col: node.loc?.col,
448
- });
449
- }
450
- }
451
- // Cross-prop validation: component needs ref or name
452
- if (node.type === 'component' && !('ref' in props) && !('name' in props)) {
1356
+ const UNIVERSAL_CHILDREN = new Set(['handler', 'cleanup', 'reason', 'evidence', 'needs', 'signal', 'doc']);
1357
+ function checkRequiredProps(node, schema, violations) {
1358
+ const props = node.props || {};
1359
+ for (const [propName, propSchema] of Object.entries(schema.props)) {
1360
+ if (propSchema.required && !(propName in props)) {
453
1361
  violations.push({
454
- nodeType: 'component',
455
- message: "'component' requires either 'ref' or 'name' prop",
1362
+ nodeType: node.type,
1363
+ message: `'${node.type}' requires prop '${propName}'`,
456
1364
  line: node.loc?.line,
457
1365
  col: node.loc?.col,
458
1366
  });
459
1367
  }
460
- // Check allowed children
461
- if (schema.allowedChildren && node.children) {
462
- for (const child of node.children) {
463
- if (!schema.allowedChildren.includes(child.type)) {
464
- // Don't flag structural children that are consumed by parents
465
- // (handler, reason, evidence, needs, etc.)
466
- const universalChildren = ['handler', 'cleanup', 'reason', 'evidence', 'needs', 'signal'];
467
- if (!universalChildren.includes(child.type)) {
468
- violations.push({
469
- nodeType: node.type,
470
- message: `'${node.type}' does not allow child type '${child.type}' (allowed: ${schema.allowedChildren.join(', ')})`,
471
- line: child.loc?.line,
472
- col: child.loc?.col,
473
- });
474
- }
475
- }
476
- }
1368
+ }
1369
+ }
1370
+ function checkCrossProps(node, violations) {
1371
+ const props = node.props || {};
1372
+ if (node.type === 'component' && !('ref' in props) && !('name' in props)) {
1373
+ violations.push({
1374
+ nodeType: 'component',
1375
+ message: "'component' requires either 'ref' or 'name' prop",
1376
+ line: node.loc?.line,
1377
+ col: node.loc?.col,
1378
+ });
1379
+ }
1380
+ if (node.type === 'guard' && !('expr' in props) && !('kind' in props) && !('type' in props)) {
1381
+ violations.push({
1382
+ nodeType: 'guard',
1383
+ message: "'guard' requires either 'expr' (assertion) or 'kind'/'type' (security guard)",
1384
+ line: node.loc?.line,
1385
+ col: node.loc?.col,
1386
+ });
1387
+ }
1388
+ }
1389
+ function checkAllowedChildren(node, schema, violations) {
1390
+ if (!schema.allowedChildren || !node.children)
1391
+ return;
1392
+ for (const child of node.children) {
1393
+ if (!schema.allowedChildren.includes(child.type) && !UNIVERSAL_CHILDREN.has(child.type)) {
1394
+ violations.push({
1395
+ nodeType: node.type,
1396
+ message: `'${node.type}' does not allow child type '${child.type}' (allowed: ${schema.allowedChildren.join(', ')})`,
1397
+ line: child.loc?.line,
1398
+ col: child.loc?.col,
1399
+ });
477
1400
  }
478
1401
  }
479
- // Recurse into children
1402
+ }
1403
+ function validateNode(node, violations) {
1404
+ const schema = Object.hasOwn(NODE_SCHEMAS, node.type) ? NODE_SCHEMAS[node.type] : undefined;
1405
+ if (schema) {
1406
+ checkRequiredProps(node, schema, violations);
1407
+ checkCrossProps(node, violations);
1408
+ checkAllowedChildren(node, schema, violations);
1409
+ }
480
1410
  if (node.children) {
481
- for (const child of node.children) {
1411
+ for (const child of node.children)
482
1412
  validateNode(child, violations);
483
- }
484
1413
  }
485
1414
  }
1415
+ /**
1416
+ * Export the full KERN schema as a JSON-serializable object.
1417
+ *
1418
+ * Designed for LLM consumption — an LLM can call `kern schema --json`
1419
+ * and use the output to generate valid `.kern` files.
1420
+ *
1421
+ * @param runtime - Optional KernRuntime instance (includes evolved types)
1422
+ */
1423
+ export function exportSchemaJSON(runtime) {
1424
+ const rt = runtime ?? defaultRuntime;
1425
+ const schemaedTypes = new Set(Object.keys(NODE_SCHEMAS));
1426
+ const unschemaed = NODE_TYPES.filter((t) => !schemaedTypes.has(t));
1427
+ const evolvedTypes = [...rt.dynamicNodeTypes];
1428
+ // Return defensive copies so callers can't mutate process-wide state
1429
+ return {
1430
+ version: KERN_VERSION,
1431
+ nodeTypes: [...NODE_TYPES],
1432
+ schemas: JSON.parse(JSON.stringify(NODE_SCHEMAS)),
1433
+ unschemaed,
1434
+ targets: [...VALID_TARGETS],
1435
+ styleShorthands: { ...STYLE_SHORTHANDS },
1436
+ valueShorthands: { ...VALUE_SHORTHANDS },
1437
+ multilineBlockTypes: [...rt.multilineBlockTypes],
1438
+ propKinds: ['identifier', 'typeAnnotation', 'importPath', 'rawExpr', 'rawBlock', 'string', 'boolean', 'number'],
1439
+ ...(evolvedTypes.length > 0 ? { evolvedTypes } : {}),
1440
+ };
1441
+ }
486
1442
  //# sourceMappingURL=schema.js.map