@kernlang/core 3.1.8 → 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.
- package/dist/codegen/functions.js +8 -4
- package/dist/codegen/functions.js.map +1 -1
- package/dist/codegen/screens.js +74 -63
- package/dist/codegen/screens.js.map +1 -1
- package/dist/codegen/type-system.js +8 -5
- package/dist/codegen/type-system.js.map +1 -1
- package/dist/codegen-core.js +7 -0
- package/dist/codegen-core.js.map +1 -1
- package/dist/parser-core.js +32 -3
- package/dist/parser-core.js.map +1 -1
- package/dist/schema.js +564 -46
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/version-adapters.d.ts +1 -1
- package/dist/version-adapters.js +11 -0
- package/dist/version-adapters.js.map +1 -1
- package/dist/version-detect.d.ts +1 -1
- package/dist/version-detect.js +2 -0
- package/dist/version-detect.js.map +1 -1
- package/package.json +1 -1
package/dist/schema.js
CHANGED
|
@@ -820,6 +820,524 @@ export const NODE_SCHEMAS = {
|
|
|
820
820
|
styles: { kind: 'rawExpr' },
|
|
821
821
|
},
|
|
822
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
|
+
},
|
|
823
1341
|
};
|
|
824
1342
|
/**
|
|
825
1343
|
* Validate an IR tree against the schema definitions (required props, allowed children, cross-prop rules).
|
|
@@ -835,63 +1353,63 @@ export function validateSchema(root) {
|
|
|
835
1353
|
validateNode(root, violations);
|
|
836
1354
|
return violations;
|
|
837
1355
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
for (const [propName, propSchema] of Object.entries(schema.props)) {
|
|
844
|
-
if (propSchema.required && !(propName in props)) {
|
|
845
|
-
violations.push({
|
|
846
|
-
nodeType: node.type,
|
|
847
|
-
message: `'${node.type}' requires prop '${propName}'`,
|
|
848
|
-
line: node.loc?.line,
|
|
849
|
-
col: node.loc?.col,
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
// Cross-prop validation: component needs ref or name
|
|
854
|
-
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)) {
|
|
855
1361
|
violations.push({
|
|
856
|
-
nodeType:
|
|
857
|
-
message:
|
|
1362
|
+
nodeType: node.type,
|
|
1363
|
+
message: `'${node.type}' requires prop '${propName}'`,
|
|
858
1364
|
line: node.loc?.line,
|
|
859
1365
|
col: node.loc?.col,
|
|
860
1366
|
});
|
|
861
1367
|
}
|
|
862
|
-
|
|
863
|
-
|
|
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)) {
|
|
864
1394
|
violations.push({
|
|
865
|
-
nodeType:
|
|
866
|
-
message:
|
|
867
|
-
line:
|
|
868
|
-
col:
|
|
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,
|
|
869
1399
|
});
|
|
870
1400
|
}
|
|
871
|
-
// Check allowed children
|
|
872
|
-
if (schema.allowedChildren && node.children) {
|
|
873
|
-
for (const child of node.children) {
|
|
874
|
-
if (!schema.allowedChildren.includes(child.type)) {
|
|
875
|
-
// Don't flag structural children that are consumed by parents
|
|
876
|
-
// (handler, reason, evidence, needs, etc.)
|
|
877
|
-
const universalChildren = ['handler', 'cleanup', 'reason', 'evidence', 'needs', 'signal', 'doc'];
|
|
878
|
-
if (!universalChildren.includes(child.type)) {
|
|
879
|
-
violations.push({
|
|
880
|
-
nodeType: node.type,
|
|
881
|
-
message: `'${node.type}' does not allow child type '${child.type}' (allowed: ${schema.allowedChildren.join(', ')})`,
|
|
882
|
-
line: child.loc?.line,
|
|
883
|
-
col: child.loc?.col,
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
1401
|
}
|
|
890
|
-
|
|
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
|
+
}
|
|
891
1410
|
if (node.children) {
|
|
892
|
-
for (const child of node.children)
|
|
1411
|
+
for (const child of node.children)
|
|
893
1412
|
validateNode(child, violations);
|
|
894
|
-
}
|
|
895
1413
|
}
|
|
896
1414
|
}
|
|
897
1415
|
/**
|