@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.
- package/dist/codegen/data-layer.js +28 -29
- package/dist/codegen/data-layer.js.map +1 -1
- package/dist/codegen/emitters.js +1 -4
- package/dist/codegen/emitters.js.map +1 -1
- package/dist/codegen/events.js +31 -20
- package/dist/codegen/events.js.map +1 -1
- package/dist/codegen/functions.js +7 -5
- package/dist/codegen/functions.js.map +1 -1
- package/dist/codegen/ground-layer.js +9 -5
- package/dist/codegen/ground-layer.js.map +1 -1
- package/dist/codegen/helpers.d.ts +4 -0
- package/dist/codegen/helpers.js +43 -7
- package/dist/codegen/helpers.js.map +1 -1
- package/dist/codegen/machines.js +13 -11
- package/dist/codegen/machines.js.map +1 -1
- package/dist/codegen/modules.js +5 -2
- package/dist/codegen/modules.js.map +1 -1
- package/dist/codegen/screens.d.ts +29 -0
- package/dist/codegen/screens.js +202 -0
- package/dist/codegen/screens.js.map +1 -0
- package/dist/codegen/semantic-types.js.map +1 -1
- package/dist/codegen/test-gen.js +1 -1
- package/dist/codegen/test-gen.js.map +1 -1
- package/dist/codegen/type-system.js +36 -18
- package/dist/codegen/type-system.js.map +1 -1
- package/dist/codegen-core.d.ts +11 -10
- package/dist/codegen-core.js +225 -108
- package/dist/codegen-core.js.map +1 -1
- package/dist/concepts.js.map +1 -1
- package/dist/config.js +15 -2
- package/dist/config.js.map +1 -1
- package/dist/coverage-gap.js +5 -5
- package/dist/coverage-gap.js.map +1 -1
- package/dist/decompiler.d.ts +1 -1
- package/dist/decompiler.js +6 -4
- package/dist/decompiler.js.map +1 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +4 -2
- package/dist/errors.js.map +1 -1
- package/dist/importer.d.ts +38 -0
- package/dist/importer.js +1135 -0
- package/dist/importer.js.map +1 -0
- package/dist/index.d.ts +37 -34
- package/dist/index.js +38 -38
- package/dist/index.js.map +1 -1
- package/dist/node-props.d.ts +7 -0
- package/dist/node-props.js.map +1 -1
- package/dist/parser-core.d.ts +1 -1
- package/dist/parser-core.js +65 -9
- package/dist/parser-core.js.map +1 -1
- package/dist/parser-diagnostics.d.ts +1 -1
- package/dist/parser-diagnostics.js +3 -2
- package/dist/parser-diagnostics.js.map +1 -1
- package/dist/parser-keywords.d.ts +1 -1
- package/dist/parser-keywords.js +119 -27
- package/dist/parser-keywords.js.map +1 -1
- package/dist/parser-token-stream.js +18 -6
- package/dist/parser-token-stream.js.map +1 -1
- package/dist/parser-tokenizer.js.map +1 -1
- package/dist/parser.d.ts +3 -3
- package/dist/parser.js +15 -4
- package/dist/parser.js.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/scanner.js +85 -25
- package/dist/scanner.js.map +1 -1
- package/dist/schema.d.ts +25 -0
- package/dist/schema.js +442 -4
- package/dist/schema.js.map +1 -1
- package/dist/semantic-validator.d.ts +20 -0
- package/dist/semantic-validator.js +82 -0
- package/dist/semantic-validator.js.map +1 -0
- package/dist/source-map.js +2 -2
- package/dist/source-map.js.map +1 -1
- package/dist/spec.d.ts +1 -1
- package/dist/spec.js +197 -56
- package/dist/spec.js.map +1 -1
- package/dist/styles-react.js +1 -1
- package/dist/styles-react.js.map +1 -1
- package/dist/styles-tailwind.js +52 -15
- package/dist/styles-tailwind.js.map +1 -1
- package/dist/template-catalog.js +1 -1
- package/dist/template-catalog.js.map +1 -1
- package/dist/template-engine.d.ts +1 -1
- package/dist/template-engine.js +10 -7
- package/dist/template-engine.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +9 -5
- package/dist/utils.js.map +1 -1
- package/dist/version-adapters.js +1 -2
- package/dist/version-adapters.js.map +1 -1
- package/dist/version-detect.js +3 -3
- package/dist/version-detect.js.map +1 -1
- package/dist/walk.js +6 -2
- package/dist/walk.js.map +1 -1
- 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:
|
|
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: '
|
|
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: {
|
|
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
|