@lokascript/framework 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aot/index.d.ts +1 -0
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/registry-bridge.d.ts +27 -0
- package/dist/aot/registry-bridge.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/dispatcher.d.ts +22 -0
- package/dist/api/dispatcher.d.ts.map +1 -1
- package/dist/api/domain-registry.d.ts +41 -0
- package/dist/api/domain-registry.d.ts.map +1 -1
- package/dist/api/index.js +1351 -21
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.js +114 -10
- package/dist/core/index.js.map +1 -1
- package/dist/core/pattern-matching/index.js +74 -10
- package/dist/core/pattern-matching/index.js.map +1 -1
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
- package/dist/core/types.d.ts +80 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +40 -0
- package/dist/core/types.js.map +1 -1
- package/dist/feedback/confidence-gate.d.ts +28 -0
- package/dist/feedback/confidence-gate.d.ts.map +1 -0
- package/dist/feedback/feedback-formatter.d.ts +21 -0
- package/dist/feedback/feedback-formatter.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +11 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/pattern-tracker.d.ts +47 -0
- package/dist/feedback/pattern-tracker.d.ts.map +1 -0
- package/dist/feedback/types.d.ts +113 -0
- package/dist/feedback/types.d.ts.map +1 -0
- package/dist/generation/index.js +2 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/index.cjs +2685 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2652 -71
- package/dist/index.js.map +1 -1
- package/dist/ir/explicit-parser.d.ts +31 -0
- package/dist/ir/explicit-parser.d.ts.map +1 -0
- package/dist/ir/explicit-renderer.d.ts +17 -0
- package/dist/ir/explicit-renderer.d.ts.map +1 -0
- package/dist/ir/from-interchange.d.ts +51 -0
- package/dist/ir/from-interchange.d.ts.map +1 -0
- package/dist/ir/index.d.ts +29 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +1557 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/json-schema.d.ts +40 -0
- package/dist/ir/json-schema.d.ts.map +1 -0
- package/dist/ir/protocol-json.d.ts +73 -0
- package/dist/ir/protocol-json.d.ts.map +1 -0
- package/dist/ir/references.d.ts +19 -0
- package/dist/ir/references.d.ts.map +1 -0
- package/dist/ir/types.d.ts +141 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/parsing/index.js +74 -10
- package/dist/parsing/index.js.map +1 -1
- package/dist/prompts/index.d.ts +9 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/prompt-generator.d.ts +49 -0
- package/dist/prompts/prompt-generator.d.ts.map +1 -0
- package/dist/prompts/types.d.ts +64 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +17 -0
- package/dist/schema/command-schema.d.ts.map +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/training/index.d.ts +11 -0
- package/dist/training/index.d.ts.map +1 -0
- package/dist/training/jsonl-writer.d.ts +42 -0
- package/dist/training/jsonl-writer.d.ts.map +1 -0
- package/dist/training/schema-synthesizer.d.ts +20 -0
- package/dist/training/schema-synthesizer.d.ts.map +1 -0
- package/dist/training/types.d.ts +73 -0
- package/dist/training/types.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/aot/index.ts +1 -0
- package/src/aot/registry-bridge.test.ts +104 -0
- package/src/aot/registry-bridge.ts +66 -0
- package/src/api/create-dsl.test.ts +306 -0
- package/src/api/create-dsl.ts +21 -0
- package/src/api/dispatcher.test.ts +66 -0
- package/src/api/dispatcher.ts +77 -9
- package/src/api/domain-registry.ts +120 -4
- package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
- package/src/core/pattern-matching/pattern-matcher.ts +102 -19
- package/src/core/types.ts +145 -2
- package/src/feedback/confidence-gate.test.ts +111 -0
- package/src/feedback/confidence-gate.ts +70 -0
- package/src/feedback/feedback-formatter.test.ts +198 -0
- package/src/feedback/feedback-formatter.ts +298 -0
- package/src/feedback/index.ts +24 -0
- package/src/feedback/pattern-tracker.test.ts +198 -0
- package/src/feedback/pattern-tracker.ts +140 -0
- package/src/feedback/types.ts +149 -0
- package/src/generation/pattern-generator.ts +2 -2
- package/src/index.ts +22 -0
- package/src/integration/llm-lse-integration.test.ts +457 -0
- package/src/ir/explicit-parser.test.ts +306 -0
- package/src/ir/explicit-parser.ts +419 -0
- package/src/ir/explicit-renderer.test.ts +144 -0
- package/src/ir/explicit-renderer.ts +146 -0
- package/src/ir/from-interchange.test.ts +853 -0
- package/src/ir/from-interchange.ts +594 -0
- package/src/ir/index.ts +66 -0
- package/src/ir/json-schema.test.ts +281 -0
- package/src/ir/json-schema.ts +299 -0
- package/src/ir/protocol-json.test.ts +1185 -0
- package/src/ir/protocol-json.ts +745 -0
- package/src/ir/references.test.ts +38 -0
- package/src/ir/references.ts +33 -0
- package/src/ir/round-trip.test.ts +557 -0
- package/src/ir/types.ts +181 -0
- package/src/prompts/index.ts +15 -0
- package/src/prompts/prompt-generator.test.ts +386 -0
- package/src/prompts/prompt-generator.ts +531 -0
- package/src/prompts/types.ts +89 -0
- package/src/schema/command-schema.ts +19 -0
- package/src/training/index.ts +13 -0
- package/src/training/jsonl-writer.test.ts +138 -0
- package/src/training/jsonl-writer.ts +67 -0
- package/src/training/schema-synthesizer.test.ts +250 -0
- package/src/training/schema-synthesizer.ts +306 -0
- package/src/training/types.ts +103 -0
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
CrossDomainDispatcher: () => CrossDomainDispatcher,
|
|
26
26
|
DEFAULT_OPERATORS: () => DEFAULT_OPERATORS,
|
|
27
27
|
DEFAULT_PUNCTUATION: () => DEFAULT_PUNCTUATION,
|
|
28
|
+
DEFAULT_REFERENCES: () => DEFAULT_REFERENCES,
|
|
28
29
|
DomainAwareScanner: () => DomainAwareScanner,
|
|
29
30
|
DomainRegistry: () => DomainRegistry,
|
|
30
31
|
GrammarTransformer: () => GrammarTransformer,
|
|
@@ -36,24 +37,31 @@ __export(index_exports, {
|
|
|
36
37
|
NumberExtractor: () => NumberExtractor,
|
|
37
38
|
OperatorExtractor: () => OperatorExtractor,
|
|
38
39
|
PatternMatcher: () => PatternMatcher,
|
|
40
|
+
PatternTracker: () => PatternTracker,
|
|
39
41
|
PunctuationExtractor: () => PunctuationExtractor,
|
|
40
42
|
StringLiteralExtractor: () => StringLiteralExtractor,
|
|
41
43
|
TokenStreamImpl: () => TokenStreamImpl,
|
|
42
44
|
UnicodeIdentifierExtractor: () => UnicodeIdentifierExtractor,
|
|
43
45
|
WhitespaceExtractor: () => WhitespaceExtractor,
|
|
44
46
|
accumulateBlocks: () => accumulateBlocks,
|
|
47
|
+
buildDisambiguation: () => buildDisambiguation,
|
|
48
|
+
buildFeedback: () => buildFeedback,
|
|
45
49
|
buildPhrase: () => buildPhrase,
|
|
46
50
|
buildTablesFromProfiles: () => buildTablesFromProfiles,
|
|
47
51
|
combineClassifiers: () => combineClassifiers,
|
|
52
|
+
convertValue: () => convertValue,
|
|
53
|
+
createAsyncNode: () => createAsyncNode,
|
|
48
54
|
createCommandNode: () => createCommandNode,
|
|
49
55
|
createCompoundNode: () => createCompoundNode,
|
|
50
56
|
createConditionalNode: () => createConditionalNode,
|
|
51
57
|
createDiagnosticCollector: () => createDiagnosticCollector,
|
|
52
58
|
createEventHandlerNode: () => createEventHandlerNode,
|
|
53
59
|
createExpression: () => createExpression,
|
|
60
|
+
createFlag: () => createFlag,
|
|
54
61
|
createLatinCharClassifiers: () => createLatinCharClassifiers,
|
|
55
62
|
createLiteral: () => createLiteral,
|
|
56
63
|
createLoopNode: () => createLoopNode,
|
|
64
|
+
createMatchNode: () => createMatchNode,
|
|
57
65
|
createMultiStatementParser: () => createMultiStatementParser,
|
|
58
66
|
createMultilingualDSL: () => createMultilingualDSL,
|
|
59
67
|
createPosition: () => createPosition,
|
|
@@ -64,6 +72,7 @@ __export(index_exports, {
|
|
|
64
72
|
createSimpleTokenizer: () => createSimpleTokenizer,
|
|
65
73
|
createToken: () => createToken,
|
|
66
74
|
createTokenizerContext: () => createTokenizerContext,
|
|
75
|
+
createTryNode: () => createTryNode,
|
|
67
76
|
createUnicodeRangeClassifier: () => createUnicodeRangeClassifier,
|
|
68
77
|
defineCommand: () => defineCommand,
|
|
69
78
|
defineRole: () => defineRole,
|
|
@@ -75,9 +84,15 @@ __export(index_exports, {
|
|
|
75
84
|
extractUrl: () => extractUrl,
|
|
76
85
|
extractValue: () => extractValue,
|
|
77
86
|
filterBySeverity: () => filterBySeverity,
|
|
87
|
+
fromEnvelopeJSON: () => fromEnvelopeJSON,
|
|
78
88
|
fromError: () => fromError,
|
|
89
|
+
fromInterchangeNode: () => fromInterchangeNode,
|
|
90
|
+
fromProtocolJSON: () => fromProtocolJSON,
|
|
91
|
+
generateExamples: () => generateExamples,
|
|
79
92
|
generatePattern: () => generatePattern,
|
|
80
93
|
generatePatternVariants: () => generatePatternVariants,
|
|
94
|
+
generatePrompt: () => generatePrompt,
|
|
95
|
+
generateProtocolReference: () => generateProtocolReference,
|
|
81
96
|
getAllPossessiveKeywords: () => getAllPossessiveKeywords,
|
|
82
97
|
getDefaultExtractors: () => getDefaultExtractors,
|
|
83
98
|
getPossessiveReference: () => getPossessiveReference,
|
|
@@ -90,6 +105,8 @@ __export(index_exports, {
|
|
|
90
105
|
isClassName: () => isClassName,
|
|
91
106
|
isContextAwareExtractor: () => isContextAwareExtractor,
|
|
92
107
|
isDigit: () => isDigit,
|
|
108
|
+
isEnvelope: () => isEnvelope,
|
|
109
|
+
isExplicitSyntax: () => isExplicitSyntax,
|
|
93
110
|
isIdSelector: () => isIdSelector,
|
|
94
111
|
isNumericValue: () => isNumericValue,
|
|
95
112
|
isPossessiveKeyword: () => isPossessiveKeyword,
|
|
@@ -99,17 +116,33 @@ __export(index_exports, {
|
|
|
99
116
|
isSelectorStart: () => isSelectorStart,
|
|
100
117
|
isTypeCompatible: () => isTypeCompatible,
|
|
101
118
|
isUrlStart: () => isUrlStart,
|
|
119
|
+
isValidReference: () => isValidReference2,
|
|
102
120
|
isVariableRef: () => isVariableRef,
|
|
103
121
|
isWhitespace: () => isWhitespace,
|
|
104
122
|
joinTokens: () => joinTokens,
|
|
123
|
+
jsonToSemanticNode: () => jsonToSemanticNode,
|
|
105
124
|
lookupKeyword: () => lookupKeyword,
|
|
106
125
|
lookupMarker: () => lookupMarker,
|
|
107
126
|
matchBest: () => matchBest,
|
|
108
127
|
matchPattern: () => matchPattern,
|
|
128
|
+
needsDisambiguation: () => needsDisambiguation,
|
|
109
129
|
noChange: () => noChange,
|
|
110
130
|
normalized: () => normalized,
|
|
131
|
+
parseExplicit: () => parseExplicit,
|
|
132
|
+
parseJSONL: () => parseJSONL,
|
|
111
133
|
patternMatcher: () => patternMatcher,
|
|
134
|
+
registryToAOTBackends: () => registryToAOTBackends,
|
|
135
|
+
renderExplicit: () => renderExplicit,
|
|
136
|
+
renderExpr: () => renderExpr,
|
|
112
137
|
reorderRoles: () => reorderRoles,
|
|
138
|
+
semanticNodeToJSON: () => semanticNodeToJSON,
|
|
139
|
+
synthesizeFromSchemas: () => synthesizeFromSchemas,
|
|
140
|
+
toEnvelopeJSON: () => toEnvelopeJSON,
|
|
141
|
+
toJSONL: () => toJSONL,
|
|
142
|
+
toJSONLRow: () => toJSONLRow,
|
|
143
|
+
toProtocolJSON: () => toProtocolJSON,
|
|
144
|
+
validateProtocolJSON: () => validateProtocolJSON,
|
|
145
|
+
validateSemanticJSON: () => validateSemanticJSON,
|
|
113
146
|
validateValueType: () => validateValueType,
|
|
114
147
|
withDefaultExtractors: () => withDefaultExtractors
|
|
115
148
|
});
|
|
@@ -131,6 +164,9 @@ function createPropertyPath(object, property) {
|
|
|
131
164
|
function createExpression(raw) {
|
|
132
165
|
return { type: "expression", raw };
|
|
133
166
|
}
|
|
167
|
+
function createFlag(name, enabled = true) {
|
|
168
|
+
return { type: "flag", name, enabled };
|
|
169
|
+
}
|
|
134
170
|
function createCommandNode(action, roles, metadata) {
|
|
135
171
|
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
136
172
|
const node = {
|
|
@@ -182,6 +218,7 @@ function createCompoundNode(statements, chainType = "sequential", metadata) {
|
|
|
182
218
|
return metadata ? { ...base, metadata } : base;
|
|
183
219
|
}
|
|
184
220
|
function extractValue(value) {
|
|
221
|
+
if (value.type === "flag") return value.name;
|
|
185
222
|
if ("raw" in value && value.raw !== void 0) return String(value.raw);
|
|
186
223
|
if ("value" in value && value.value !== void 0) return String(value.value);
|
|
187
224
|
if (value.type === "property-path") return `${extractValue(value.object)}.${value.property}`;
|
|
@@ -208,6 +245,38 @@ function createLoopNode(action, roles, loopVariant, body, loopVariable, indexVar
|
|
|
208
245
|
...metadata && { metadata }
|
|
209
246
|
};
|
|
210
247
|
}
|
|
248
|
+
function createTryNode(body, catchBranch, finallyBranch, metadata) {
|
|
249
|
+
return {
|
|
250
|
+
kind: "command",
|
|
251
|
+
action: "try",
|
|
252
|
+
roles: /* @__PURE__ */ new Map(),
|
|
253
|
+
body,
|
|
254
|
+
...catchBranch && catchBranch.length > 0 ? { catchBranch } : {},
|
|
255
|
+
...finallyBranch && finallyBranch.length > 0 ? { finallyBranch } : {},
|
|
256
|
+
...metadata ? { metadata } : {}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function createAsyncNode(variant, asyncBody, metadata) {
|
|
260
|
+
return {
|
|
261
|
+
kind: "command",
|
|
262
|
+
action: variant,
|
|
263
|
+
roles: /* @__PURE__ */ new Map(),
|
|
264
|
+
asyncVariant: variant,
|
|
265
|
+
asyncBody,
|
|
266
|
+
...metadata ? { metadata } : {}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function createMatchNode(roles, arms, defaultArm, metadata) {
|
|
270
|
+
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
271
|
+
return {
|
|
272
|
+
kind: "command",
|
|
273
|
+
action: "match",
|
|
274
|
+
roles: rolesMap,
|
|
275
|
+
arms,
|
|
276
|
+
...defaultArm && defaultArm.length > 0 ? { defaultArm } : {},
|
|
277
|
+
...metadata ? { metadata } : {}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
211
280
|
|
|
212
281
|
// src/core/logger.ts
|
|
213
282
|
var DebugLogger = class {
|
|
@@ -463,7 +532,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
463
532
|
* a token and the immediately following pattern token fails, the matcher
|
|
464
533
|
* resets to before the optional role and retries the failed token.
|
|
465
534
|
*/
|
|
466
|
-
matchTokenSequence(tokens, patternTokens, captured) {
|
|
535
|
+
matchTokenSequence(tokens, patternTokens, captured, parentStopMarkers) {
|
|
467
536
|
const firstPatternToken = patternTokens[0];
|
|
468
537
|
const patternExpectsConjunction = firstPatternToken?.type === "literal" && (firstPatternToken.value === "and" || firstPatternToken.value === "then" || firstPatternToken.alternatives?.includes("and") || firstPatternToken.alternatives?.includes("then"));
|
|
469
538
|
if (this.currentProfile?.code === "ar" && !patternExpectsConjunction) {
|
|
@@ -487,6 +556,9 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
487
556
|
);
|
|
488
557
|
if (patternToken.type === "role" && patternToken.greedy) {
|
|
489
558
|
const stopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
559
|
+
if (parentStopMarkers) {
|
|
560
|
+
for (const m of parentStopMarkers) stopMarkers.add(m);
|
|
561
|
+
}
|
|
490
562
|
const values = [];
|
|
491
563
|
while (!tokens.isAtEnd()) {
|
|
492
564
|
const nextToken = tokens.peek();
|
|
@@ -506,8 +578,58 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
506
578
|
return false;
|
|
507
579
|
}
|
|
508
580
|
}
|
|
581
|
+
if (patternToken.type === "group") {
|
|
582
|
+
const siblingStopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
583
|
+
if (parentStopMarkers) {
|
|
584
|
+
for (const m of parentStopMarkers) siblingStopMarkers.add(m);
|
|
585
|
+
}
|
|
586
|
+
const groupMatched = this.matchGroupToken(
|
|
587
|
+
tokens,
|
|
588
|
+
patternToken,
|
|
589
|
+
captured,
|
|
590
|
+
siblingStopMarkers
|
|
591
|
+
);
|
|
592
|
+
if (groupMatched) {
|
|
593
|
+
prevOptionalMark = null;
|
|
594
|
+
prevOptionalRole = null;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (this.isOptional(patternToken)) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
509
602
|
const isOptionalRole = patternToken.type === "role" && patternToken.optional === true;
|
|
510
603
|
const markBefore = isOptionalRole ? tokens.mark() : null;
|
|
604
|
+
const isRoleToken = patternToken.type === "role";
|
|
605
|
+
if (isRoleToken && !isOptionalRole) {
|
|
606
|
+
const nextPT = i + 1 < patternTokens.length ? patternTokens[i + 1] : null;
|
|
607
|
+
if (nextPT?.type === "literal") {
|
|
608
|
+
const currentToken = tokens.peek();
|
|
609
|
+
if (currentToken && this.getMatchType(currentToken, nextPT.value) !== "none") {
|
|
610
|
+
this.logger.debug(
|
|
611
|
+
" >> MARKER-STEAL GUARD: current token matches next literal, skipping role"
|
|
612
|
+
);
|
|
613
|
+
this.logger.debug(" >> Token match FAILED");
|
|
614
|
+
if (this.isOptional(patternToken)) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (prevOptionalMark && prevOptionalRole) {
|
|
618
|
+
this.logger.debug(" >> BACKTRACKING: undoing optional role", prevOptionalRole);
|
|
619
|
+
tokens.reset(prevOptionalMark);
|
|
620
|
+
captured.delete(prevOptionalRole);
|
|
621
|
+
prevOptionalMark = null;
|
|
622
|
+
prevOptionalRole = null;
|
|
623
|
+
const retryMatched = this.matchPatternToken(tokens, patternToken, captured);
|
|
624
|
+
this.logger.debug(" >> Backtrack retry result:", retryMatched);
|
|
625
|
+
if (retryMatched) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
511
633
|
const matched = this.matchPatternToken(tokens, patternToken, captured);
|
|
512
634
|
this.logger.debug(" >> Match result:", matched);
|
|
513
635
|
if (matched) {
|
|
@@ -628,7 +750,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
628
750
|
const possessiveValue = this.tryMatchPossessiveExpression(tokens);
|
|
629
751
|
if (possessiveValue) {
|
|
630
752
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
631
|
-
if (!
|
|
753
|
+
if (!isTypeCompatible(possessiveValue.type, patternToken.expectedTypes)) {
|
|
632
754
|
return patternToken.optional || false;
|
|
633
755
|
}
|
|
634
756
|
}
|
|
@@ -638,7 +760,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
638
760
|
const methodCallValue = this.tryMatchMethodCallExpression(tokens);
|
|
639
761
|
if (methodCallValue) {
|
|
640
762
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
641
|
-
if (!
|
|
763
|
+
if (!isTypeCompatible(methodCallValue.type, patternToken.expectedTypes)) {
|
|
642
764
|
return patternToken.optional || false;
|
|
643
765
|
}
|
|
644
766
|
}
|
|
@@ -658,7 +780,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
658
780
|
const propertyAccessValue = this.tryMatchPropertyAccessExpression(tokens);
|
|
659
781
|
if (propertyAccessValue) {
|
|
660
782
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
661
|
-
if (!
|
|
783
|
+
if (!isTypeCompatible(propertyAccessValue.type, patternToken.expectedTypes)) {
|
|
662
784
|
return patternToken.optional || false;
|
|
663
785
|
}
|
|
664
786
|
}
|
|
@@ -978,10 +1100,15 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
978
1100
|
* (e.g., a value like 'hello') to sit between groups without blocking later
|
|
979
1101
|
* marker matches.
|
|
980
1102
|
*/
|
|
981
|
-
matchGroupToken(tokens, patternToken, captured) {
|
|
1103
|
+
matchGroupToken(tokens, patternToken, captured, parentStopMarkers) {
|
|
982
1104
|
const mark = tokens.mark();
|
|
983
1105
|
const capturedBefore = new Set(captured.keys());
|
|
984
|
-
const success = this.matchTokenSequence(
|
|
1106
|
+
const success = this.matchTokenSequence(
|
|
1107
|
+
tokens,
|
|
1108
|
+
patternToken.tokens,
|
|
1109
|
+
captured,
|
|
1110
|
+
parentStopMarkers
|
|
1111
|
+
);
|
|
985
1112
|
if (success) return true;
|
|
986
1113
|
tokens.reset(mark);
|
|
987
1114
|
for (const role of captured.keys()) {
|
|
@@ -1003,7 +1130,12 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
1003
1130
|
"- skipping intervening tokens"
|
|
1004
1131
|
);
|
|
1005
1132
|
for (let s = 0; s < offset; s++) tokens.advance();
|
|
1006
|
-
const retrySuccess = this.matchTokenSequence(
|
|
1133
|
+
const retrySuccess = this.matchTokenSequence(
|
|
1134
|
+
tokens,
|
|
1135
|
+
patternToken.tokens,
|
|
1136
|
+
captured,
|
|
1137
|
+
parentStopMarkers
|
|
1138
|
+
);
|
|
1007
1139
|
if (retrySuccess) return true;
|
|
1008
1140
|
tokens.reset(mark);
|
|
1009
1141
|
for (const role of captured.keys()) {
|
|
@@ -1070,7 +1202,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
1070
1202
|
break;
|
|
1071
1203
|
}
|
|
1072
1204
|
}
|
|
1073
|
-
break;
|
|
1205
|
+
if (!pt.optional) break;
|
|
1074
1206
|
}
|
|
1075
1207
|
}
|
|
1076
1208
|
return markers;
|
|
@@ -1080,8 +1212,9 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
1080
1212
|
*/
|
|
1081
1213
|
isStopMarker(token, stopMarkers) {
|
|
1082
1214
|
if (stopMarkers.size === 0) return false;
|
|
1083
|
-
|
|
1084
|
-
|
|
1215
|
+
if (stopMarkers.has(token.value.toLowerCase())) return true;
|
|
1216
|
+
if (token.normalized && stopMarkers.has(token.normalized.toLowerCase())) return true;
|
|
1217
|
+
return false;
|
|
1085
1218
|
}
|
|
1086
1219
|
/**
|
|
1087
1220
|
* Convert a language token to a semantic value.
|
|
@@ -1486,7 +1619,7 @@ function addRoleWithMarker(tokens, roleSpec, profile) {
|
|
|
1486
1619
|
if (marker) {
|
|
1487
1620
|
const markerInfo = profile.roleMarkers?.[roleSpec.role];
|
|
1488
1621
|
const defaultPosition = profile.wordOrder === "SOV" ? "after" : "before";
|
|
1489
|
-
const position = markerInfo?.position ?? defaultPosition;
|
|
1622
|
+
const position = roleSpec.markerPosition ?? markerInfo?.position ?? defaultPosition;
|
|
1490
1623
|
if (isOptional) {
|
|
1491
1624
|
if (position === "before") {
|
|
1492
1625
|
tokens.push({
|
|
@@ -2026,6 +2159,1419 @@ function createTokenizerContext(tokenizer) {
|
|
|
2026
2159
|
return ctx;
|
|
2027
2160
|
}
|
|
2028
2161
|
|
|
2162
|
+
// src/ir/references.ts
|
|
2163
|
+
var DEFAULT_REFERENCES = /* @__PURE__ */ new Set([
|
|
2164
|
+
"me",
|
|
2165
|
+
"you",
|
|
2166
|
+
"it",
|
|
2167
|
+
"result",
|
|
2168
|
+
"event",
|
|
2169
|
+
"target",
|
|
2170
|
+
"body"
|
|
2171
|
+
]);
|
|
2172
|
+
function isValidReference2(value, referenceSet = DEFAULT_REFERENCES) {
|
|
2173
|
+
return referenceSet.has(value);
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// src/ir/explicit-parser.ts
|
|
2177
|
+
var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
|
|
2178
|
+
"body",
|
|
2179
|
+
"then",
|
|
2180
|
+
"else",
|
|
2181
|
+
"condition",
|
|
2182
|
+
"loop-body",
|
|
2183
|
+
"variable",
|
|
2184
|
+
"catch",
|
|
2185
|
+
// v1.2: try/catch/finally
|
|
2186
|
+
"finally"
|
|
2187
|
+
// v1.2: try/catch/finally
|
|
2188
|
+
]);
|
|
2189
|
+
function isNestedCommand(value) {
|
|
2190
|
+
const inner = value.slice(1, -1);
|
|
2191
|
+
let depth = 0;
|
|
2192
|
+
for (const ch of inner) {
|
|
2193
|
+
if (ch === "[") depth++;
|
|
2194
|
+
else if (ch === "]") depth--;
|
|
2195
|
+
else if (depth === 0 && (ch === " " || ch === ":")) return true;
|
|
2196
|
+
}
|
|
2197
|
+
return false;
|
|
2198
|
+
}
|
|
2199
|
+
function parseExplicit(input, options = {}) {
|
|
2200
|
+
const trimmed = input.trim();
|
|
2201
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
|
|
2202
|
+
throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
|
|
2203
|
+
}
|
|
2204
|
+
const content = trimmed.slice(1, -1).trim();
|
|
2205
|
+
if (!content) {
|
|
2206
|
+
throw new Error("Empty explicit statement");
|
|
2207
|
+
}
|
|
2208
|
+
const tokens = tokenizeExplicit(content);
|
|
2209
|
+
if (tokens.length === 0) {
|
|
2210
|
+
throw new Error("No command specified in explicit statement");
|
|
2211
|
+
}
|
|
2212
|
+
const command = tokens[0].toLowerCase();
|
|
2213
|
+
const roles = /* @__PURE__ */ new Map();
|
|
2214
|
+
const refSet = options.referenceSet ?? DEFAULT_REFERENCES;
|
|
2215
|
+
const schema = options.schemaLookup?.getSchema(command);
|
|
2216
|
+
const validRoleNames = schema ? new Set(schema.roles.map((r) => r.role)) : null;
|
|
2217
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
2218
|
+
const token = tokens[i];
|
|
2219
|
+
if (token.startsWith("+") || token.startsWith("~")) {
|
|
2220
|
+
const enabled = token.startsWith("+");
|
|
2221
|
+
const flagName = token.slice(1);
|
|
2222
|
+
if (!flagName) {
|
|
2223
|
+
throw new Error(`Empty flag name: "${token}"`);
|
|
2224
|
+
}
|
|
2225
|
+
if (validRoleNames && !validRoleNames.has(flagName)) {
|
|
2226
|
+
const roleList = [...validRoleNames].join(", ");
|
|
2227
|
+
throw new Error(
|
|
2228
|
+
`Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
roles.set(flagName, createFlag(flagName, enabled));
|
|
2232
|
+
continue;
|
|
2233
|
+
}
|
|
2234
|
+
const colonIndex = token.indexOf(":");
|
|
2235
|
+
if (colonIndex === -1) {
|
|
2236
|
+
throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
|
|
2237
|
+
}
|
|
2238
|
+
const roleName = token.slice(0, colonIndex);
|
|
2239
|
+
if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
|
|
2240
|
+
const roleList = [...validRoleNames].join(", ");
|
|
2241
|
+
throw new Error(
|
|
2242
|
+
`Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
const role = roleName;
|
|
2246
|
+
const valueStr = token.slice(colonIndex + 1);
|
|
2247
|
+
if (STRUCTURAL_ROLES.has(roleName) && valueStr.startsWith("[") && isNestedCommand(valueStr)) {
|
|
2248
|
+
const nestedEnd = findMatchingBracket(token, colonIndex + 1);
|
|
2249
|
+
const nestedSyntax = token.slice(colonIndex + 1, nestedEnd + 1);
|
|
2250
|
+
roles.set(role, { type: "expression", raw: nestedSyntax });
|
|
2251
|
+
continue;
|
|
2252
|
+
}
|
|
2253
|
+
const value = parseExplicitValue(valueStr, refSet);
|
|
2254
|
+
roles.set(role, value);
|
|
2255
|
+
}
|
|
2256
|
+
if (schema && command !== "on") {
|
|
2257
|
+
for (const roleSpec of schema.roles) {
|
|
2258
|
+
if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
|
|
2259
|
+
throw new Error(
|
|
2260
|
+
`Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
switch (command) {
|
|
2266
|
+
case "on": {
|
|
2267
|
+
const eventValue = roles.get("event");
|
|
2268
|
+
if (!eventValue) {
|
|
2269
|
+
throw new Error("Event handler requires event role: [on event:click ...]");
|
|
2270
|
+
}
|
|
2271
|
+
const body = extractStructuralBody(roles, "body", options);
|
|
2272
|
+
roles.delete("body");
|
|
2273
|
+
return createEventHandlerNode("on", roles, body, {
|
|
2274
|
+
sourceLanguage: "explicit"
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
case "if": {
|
|
2278
|
+
const thenBranch = extractStructuralBody(roles, "then", options);
|
|
2279
|
+
const elseBranch = extractStructuralBody(roles, "else", options);
|
|
2280
|
+
roles.delete("then");
|
|
2281
|
+
roles.delete("else");
|
|
2282
|
+
return createConditionalNode(
|
|
2283
|
+
command,
|
|
2284
|
+
roles,
|
|
2285
|
+
thenBranch,
|
|
2286
|
+
elseBranch.length > 0 ? elseBranch : void 0,
|
|
2287
|
+
{
|
|
2288
|
+
sourceLanguage: "explicit"
|
|
2289
|
+
}
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
case "repeat": {
|
|
2293
|
+
const loopBody = extractStructuralBody(roles, "loop-body", options);
|
|
2294
|
+
const loopVariantValue = roles.get("loop-variant");
|
|
2295
|
+
const variableValue = roles.get("variable");
|
|
2296
|
+
const indexVariableValue = roles.get("index-variable");
|
|
2297
|
+
roles.delete("loop-body");
|
|
2298
|
+
roles.delete("loop-variant");
|
|
2299
|
+
roles.delete("variable");
|
|
2300
|
+
roles.delete("index-variable");
|
|
2301
|
+
let variant;
|
|
2302
|
+
if (loopVariantValue && loopVariantValue.type === "literal" && typeof loopVariantValue.value === "string") {
|
|
2303
|
+
variant = loopVariantValue.value;
|
|
2304
|
+
} else if (roles.has("quantity")) {
|
|
2305
|
+
variant = "times";
|
|
2306
|
+
} else if (roles.has("source")) {
|
|
2307
|
+
variant = "for";
|
|
2308
|
+
} else if (roles.has("condition")) {
|
|
2309
|
+
variant = "while";
|
|
2310
|
+
} else {
|
|
2311
|
+
variant = "forever";
|
|
2312
|
+
}
|
|
2313
|
+
const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
|
|
2314
|
+
const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
|
|
2315
|
+
return createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
|
|
2316
|
+
sourceLanguage: "explicit"
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
default:
|
|
2320
|
+
return createCommandNode(command, roles, {
|
|
2321
|
+
sourceLanguage: "explicit"
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
function isExplicitSyntax(input) {
|
|
2326
|
+
const trimmed = input.trim();
|
|
2327
|
+
return trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
2328
|
+
}
|
|
2329
|
+
function extractStructuralBody(roles, roleName, options) {
|
|
2330
|
+
const value = roles.get(roleName);
|
|
2331
|
+
if (!value) return [];
|
|
2332
|
+
if (value.type === "expression") {
|
|
2333
|
+
return [parseExplicit(value.raw, options)];
|
|
2334
|
+
}
|
|
2335
|
+
return [];
|
|
2336
|
+
}
|
|
2337
|
+
function inferSelectorKind(value) {
|
|
2338
|
+
if (!value) return void 0;
|
|
2339
|
+
if (value.startsWith("#")) return "id";
|
|
2340
|
+
if (value.startsWith(".")) return "class";
|
|
2341
|
+
if (value.startsWith("[")) return "attribute";
|
|
2342
|
+
if (value.startsWith("<") || value.startsWith("*")) return "element";
|
|
2343
|
+
if (/[>+~ ]/.test(value) && value.length > 1) return "complex";
|
|
2344
|
+
return void 0;
|
|
2345
|
+
}
|
|
2346
|
+
function tokenizeExplicit(content) {
|
|
2347
|
+
const tokens = [];
|
|
2348
|
+
let current = "";
|
|
2349
|
+
let inString = false;
|
|
2350
|
+
let stringChar = "";
|
|
2351
|
+
let bracketDepth = 0;
|
|
2352
|
+
for (let i = 0; i < content.length; i++) {
|
|
2353
|
+
const char = content[i];
|
|
2354
|
+
if (inString) {
|
|
2355
|
+
current += char;
|
|
2356
|
+
if (char === stringChar && content[i - 1] !== "\\") {
|
|
2357
|
+
inString = false;
|
|
2358
|
+
}
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
if (char === '"' || char === "'") {
|
|
2362
|
+
inString = true;
|
|
2363
|
+
stringChar = char;
|
|
2364
|
+
current += char;
|
|
2365
|
+
continue;
|
|
2366
|
+
}
|
|
2367
|
+
if (char === "[") {
|
|
2368
|
+
bracketDepth++;
|
|
2369
|
+
current += char;
|
|
2370
|
+
continue;
|
|
2371
|
+
}
|
|
2372
|
+
if (char === "]") {
|
|
2373
|
+
bracketDepth--;
|
|
2374
|
+
current += char;
|
|
2375
|
+
continue;
|
|
2376
|
+
}
|
|
2377
|
+
if (char === " " && bracketDepth === 0) {
|
|
2378
|
+
if (current) {
|
|
2379
|
+
tokens.push(current);
|
|
2380
|
+
current = "";
|
|
2381
|
+
}
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
current += char;
|
|
2385
|
+
}
|
|
2386
|
+
if (current) {
|
|
2387
|
+
tokens.push(current);
|
|
2388
|
+
}
|
|
2389
|
+
return tokens;
|
|
2390
|
+
}
|
|
2391
|
+
function parseExplicitValue(valueStr, referenceSet) {
|
|
2392
|
+
if (valueStr.startsWith("#") || valueStr.startsWith(".") || valueStr.startsWith("[") || valueStr.startsWith("@") || valueStr.startsWith("*")) {
|
|
2393
|
+
return createSelector(valueStr, inferSelectorKind(valueStr));
|
|
2394
|
+
}
|
|
2395
|
+
if (valueStr.startsWith('"') || valueStr.startsWith("'")) {
|
|
2396
|
+
const inner = valueStr.slice(1, -1);
|
|
2397
|
+
return createLiteral(inner, "string");
|
|
2398
|
+
}
|
|
2399
|
+
if (valueStr === "true") return createLiteral(true, "boolean");
|
|
2400
|
+
if (valueStr === "false") return createLiteral(false, "boolean");
|
|
2401
|
+
const lowerRef = valueStr.toLowerCase();
|
|
2402
|
+
if (isValidReference2(lowerRef, referenceSet)) {
|
|
2403
|
+
return createReference(lowerRef);
|
|
2404
|
+
}
|
|
2405
|
+
const numMatch = valueStr.match(/^(-?\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
2406
|
+
if (numMatch) {
|
|
2407
|
+
const num = parseFloat(numMatch[1]);
|
|
2408
|
+
const suffix = numMatch[2];
|
|
2409
|
+
if (suffix) {
|
|
2410
|
+
return createLiteral(valueStr, "duration");
|
|
2411
|
+
}
|
|
2412
|
+
return createLiteral(num, "number");
|
|
2413
|
+
}
|
|
2414
|
+
return createLiteral(valueStr);
|
|
2415
|
+
}
|
|
2416
|
+
function findMatchingBracket(str, start) {
|
|
2417
|
+
let depth = 0;
|
|
2418
|
+
for (let i = start; i < str.length; i++) {
|
|
2419
|
+
if (str[i] === "[") depth++;
|
|
2420
|
+
else if (str[i] === "]") {
|
|
2421
|
+
depth--;
|
|
2422
|
+
if (depth === 0) return i;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
return str.length - 1;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// src/ir/explicit-renderer.ts
|
|
2429
|
+
function renderExplicit(node) {
|
|
2430
|
+
if (node.kind === "compound") {
|
|
2431
|
+
const compoundNode = node;
|
|
2432
|
+
const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
|
|
2433
|
+
return renderedStatements.join(` ${compoundNode.chainType} `);
|
|
2434
|
+
}
|
|
2435
|
+
const parts = [node.action];
|
|
2436
|
+
for (const [role, value] of node.roles) {
|
|
2437
|
+
if (value.type === "flag") {
|
|
2438
|
+
parts.push(value.enabled ? `+${value.name}` : `~${value.name}`);
|
|
2439
|
+
} else {
|
|
2440
|
+
parts.push(`${role}:${valueToString(value)}`);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
if (node.kind === "event-handler") {
|
|
2444
|
+
const eventNode = node;
|
|
2445
|
+
if (eventNode.body && eventNode.body.length > 0) {
|
|
2446
|
+
const bodyParts = eventNode.body.map((n) => renderExplicit(n));
|
|
2447
|
+
parts.push(`body:${bodyParts.join(" ")}`);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
if (node.kind === "conditional") {
|
|
2451
|
+
const condNode = node;
|
|
2452
|
+
if (condNode.thenBranch && condNode.thenBranch.length > 0) {
|
|
2453
|
+
const thenParts = condNode.thenBranch.map((n) => renderExplicit(n));
|
|
2454
|
+
parts.push(`then:${thenParts.join(" ")}`);
|
|
2455
|
+
}
|
|
2456
|
+
if (condNode.elseBranch && condNode.elseBranch.length > 0) {
|
|
2457
|
+
const elseParts = condNode.elseBranch.map((n) => renderExplicit(n));
|
|
2458
|
+
parts.push(`else:${elseParts.join(" ")}`);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
if (node.kind === "loop") {
|
|
2462
|
+
const loopNode = node;
|
|
2463
|
+
if (loopNode.loopVariant) {
|
|
2464
|
+
parts.push(`loop-variant:${loopNode.loopVariant}`);
|
|
2465
|
+
}
|
|
2466
|
+
if (loopNode.loopVariable) {
|
|
2467
|
+
parts.push(`variable:${loopNode.loopVariable}`);
|
|
2468
|
+
}
|
|
2469
|
+
if (loopNode.indexVariable) {
|
|
2470
|
+
parts.push(`index-variable:${loopNode.indexVariable}`);
|
|
2471
|
+
}
|
|
2472
|
+
if (loopNode.body && loopNode.body.length > 0) {
|
|
2473
|
+
const bodyParts = loopNode.body.map((n) => renderExplicit(n));
|
|
2474
|
+
parts.push(`loop-body:${bodyParts.join(" ")}`);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
if (node.kind === "command") {
|
|
2478
|
+
const cmd = node;
|
|
2479
|
+
if (cmd.body && cmd.body.length > 0) {
|
|
2480
|
+
const bodyParts = cmd.body.map((n) => renderExplicit(n));
|
|
2481
|
+
parts.push(`body:${bodyParts.join(" ")}`);
|
|
2482
|
+
}
|
|
2483
|
+
if (cmd.catchBranch && cmd.catchBranch.length > 0) {
|
|
2484
|
+
const catchParts = cmd.catchBranch.map((n) => renderExplicit(n));
|
|
2485
|
+
parts.push(`catch:${catchParts.join(" ")}`);
|
|
2486
|
+
}
|
|
2487
|
+
if (cmd.finallyBranch && cmd.finallyBranch.length > 0) {
|
|
2488
|
+
const finallyParts = cmd.finallyBranch.map((n) => renderExplicit(n));
|
|
2489
|
+
parts.push(`finally:${finallyParts.join(" ")}`);
|
|
2490
|
+
}
|
|
2491
|
+
if (cmd.asyncVariant) {
|
|
2492
|
+
parts.push(`async-variant:${cmd.asyncVariant}`);
|
|
2493
|
+
}
|
|
2494
|
+
if (cmd.asyncBody && cmd.asyncBody.length > 0) {
|
|
2495
|
+
const asyncParts = cmd.asyncBody.map((n) => renderExplicit(n));
|
|
2496
|
+
parts.push(`async-body:${asyncParts.join(" ")}`);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return `[${parts.join(" ")}]`;
|
|
2500
|
+
}
|
|
2501
|
+
function valueToString(value) {
|
|
2502
|
+
switch (value.type) {
|
|
2503
|
+
case "literal":
|
|
2504
|
+
if (typeof value.value === "string") {
|
|
2505
|
+
if (value.dataType === "string" || /\s/.test(value.value)) {
|
|
2506
|
+
return `"${value.value}"`;
|
|
2507
|
+
}
|
|
2508
|
+
return value.value;
|
|
2509
|
+
}
|
|
2510
|
+
return String(value.value);
|
|
2511
|
+
case "selector":
|
|
2512
|
+
return value.value;
|
|
2513
|
+
case "reference":
|
|
2514
|
+
return value.value;
|
|
2515
|
+
case "property-path":
|
|
2516
|
+
return `${valueToString(value.object)}'s ${value.property}`;
|
|
2517
|
+
case "expression":
|
|
2518
|
+
return value.raw;
|
|
2519
|
+
case "flag":
|
|
2520
|
+
return value.name;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// src/ir/json-schema.ts
|
|
2525
|
+
var VALID_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
2526
|
+
"selector",
|
|
2527
|
+
"literal",
|
|
2528
|
+
"reference",
|
|
2529
|
+
"expression",
|
|
2530
|
+
"property-path",
|
|
2531
|
+
"flag"
|
|
2532
|
+
]);
|
|
2533
|
+
function validateSemanticJSON(input) {
|
|
2534
|
+
const diagnostics = [];
|
|
2535
|
+
if (!input.action || typeof input.action !== "string") {
|
|
2536
|
+
diagnostics.push({
|
|
2537
|
+
severity: "error",
|
|
2538
|
+
code: "INVALID_ACTION",
|
|
2539
|
+
message: 'Field "action" is required and must be a string.',
|
|
2540
|
+
suggestion: 'Provide a command name like "toggle", "add", "set", etc.'
|
|
2541
|
+
});
|
|
2542
|
+
return diagnostics;
|
|
2543
|
+
}
|
|
2544
|
+
if (input.roles && typeof input.roles === "object") {
|
|
2545
|
+
for (const [role, value] of Object.entries(input.roles)) {
|
|
2546
|
+
if (!value || typeof value !== "object") {
|
|
2547
|
+
diagnostics.push({
|
|
2548
|
+
severity: "error",
|
|
2549
|
+
code: "INVALID_ROLE_VALUE",
|
|
2550
|
+
message: `Role "${role}" must be an object with "type" and "value" fields.`,
|
|
2551
|
+
suggestion: 'Use: { "type": "selector", "value": ".active" }'
|
|
2552
|
+
});
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
const v = value;
|
|
2556
|
+
if (!VALID_VALUE_TYPES.has(v.type)) {
|
|
2557
|
+
diagnostics.push({
|
|
2558
|
+
severity: "error",
|
|
2559
|
+
code: "INVALID_VALUE_TYPE",
|
|
2560
|
+
message: `Role "${role}" has invalid type "${v.type}".`,
|
|
2561
|
+
suggestion: "Valid types: selector, literal, reference, expression, property-path."
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
if (v.value === void 0 || v.value === null) {
|
|
2565
|
+
diagnostics.push({
|
|
2566
|
+
severity: "error",
|
|
2567
|
+
code: "MISSING_VALUE",
|
|
2568
|
+
message: `Role "${role}" is missing the "value" field.`
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
if (input.trigger) {
|
|
2574
|
+
if (!input.trigger.event || typeof input.trigger.event !== "string") {
|
|
2575
|
+
diagnostics.push({
|
|
2576
|
+
severity: "error",
|
|
2577
|
+
code: "INVALID_TRIGGER",
|
|
2578
|
+
message: 'Trigger "event" is required and must be a string.',
|
|
2579
|
+
suggestion: 'Use an event name like "click", "mouseover", "keydown".'
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
return diagnostics;
|
|
2584
|
+
}
|
|
2585
|
+
function jsonToSemanticNode(input) {
|
|
2586
|
+
const roles = /* @__PURE__ */ new Map();
|
|
2587
|
+
if (input.roles) {
|
|
2588
|
+
for (const [role, value] of Object.entries(input.roles)) {
|
|
2589
|
+
roles.set(role, convertJSONValue(value));
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
if (input.trigger) {
|
|
2593
|
+
roles.set("event", createLiteral(input.trigger.event, "string"));
|
|
2594
|
+
const bodyRoles = new Map(roles);
|
|
2595
|
+
bodyRoles.delete("event");
|
|
2596
|
+
const bodyNode = createCommandNode(input.action, bodyRoles);
|
|
2597
|
+
return createEventHandlerNode(
|
|
2598
|
+
"on",
|
|
2599
|
+
roles,
|
|
2600
|
+
[bodyNode],
|
|
2601
|
+
{ sourceLanguage: "json" },
|
|
2602
|
+
input.trigger.modifiers ?? {}
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2605
|
+
return createCommandNode(input.action, roles, {
|
|
2606
|
+
sourceLanguage: "json"
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
function semanticNodeToJSON(node) {
|
|
2610
|
+
if (node.kind === "compound") {
|
|
2611
|
+
const compound = node;
|
|
2612
|
+
if (compound.statements.length > 0) {
|
|
2613
|
+
return semanticNodeToJSON(compound.statements[0]);
|
|
2614
|
+
}
|
|
2615
|
+
return { action: "compound", roles: {} };
|
|
2616
|
+
}
|
|
2617
|
+
if (node.kind === "event-handler") {
|
|
2618
|
+
const eventNode = node;
|
|
2619
|
+
const bodyAction = eventNode.body?.[0]?.action ?? node.action;
|
|
2620
|
+
const roles2 = {};
|
|
2621
|
+
if (eventNode.body?.[0]) {
|
|
2622
|
+
for (const [role, value] of eventNode.body[0].roles) {
|
|
2623
|
+
roles2[role] = semanticValueToJSON(value);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
const eventValue = node.roles.get("event");
|
|
2627
|
+
const eventName = eventValue ? "value" in eventValue ? String(eventValue.value) : "raw" in eventValue ? eventValue.raw : "unknown" : "unknown";
|
|
2628
|
+
const result = {
|
|
2629
|
+
action: bodyAction === "on" ? eventNode.body?.[0]?.action ?? "on" : bodyAction,
|
|
2630
|
+
roles: roles2,
|
|
2631
|
+
trigger: {
|
|
2632
|
+
event: eventName
|
|
2633
|
+
}
|
|
2634
|
+
};
|
|
2635
|
+
if (eventNode.eventModifiers && Object.keys(eventNode.eventModifiers).length > 0) {
|
|
2636
|
+
result.trigger.modifiers = eventNode.eventModifiers;
|
|
2637
|
+
}
|
|
2638
|
+
return result;
|
|
2639
|
+
}
|
|
2640
|
+
const roles = {};
|
|
2641
|
+
for (const [role, value] of node.roles) {
|
|
2642
|
+
roles[role] = semanticValueToJSON(value);
|
|
2643
|
+
}
|
|
2644
|
+
return { action: node.action, roles };
|
|
2645
|
+
}
|
|
2646
|
+
function convertJSONValue(value) {
|
|
2647
|
+
switch (value.type) {
|
|
2648
|
+
case "selector":
|
|
2649
|
+
return createSelector(String(value.value), detectSelectorKind(String(value.value)));
|
|
2650
|
+
case "literal":
|
|
2651
|
+
return createLiteral(
|
|
2652
|
+
value.value,
|
|
2653
|
+
typeof value.value === "number" ? "number" : typeof value.value === "boolean" ? "boolean" : "string"
|
|
2654
|
+
);
|
|
2655
|
+
case "reference":
|
|
2656
|
+
return createReference(String(value.value));
|
|
2657
|
+
case "expression":
|
|
2658
|
+
return createExpression(String(value.value));
|
|
2659
|
+
case "property-path":
|
|
2660
|
+
return createExpression(String(value.value));
|
|
2661
|
+
case "flag":
|
|
2662
|
+
return createFlag(String(value.value), true);
|
|
2663
|
+
default:
|
|
2664
|
+
return createLiteral(String(value.value), "string");
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
function semanticValueToJSON(value) {
|
|
2668
|
+
switch (value.type) {
|
|
2669
|
+
case "selector":
|
|
2670
|
+
return { type: "selector", value: value.value };
|
|
2671
|
+
case "literal":
|
|
2672
|
+
return { type: "literal", value: value.value };
|
|
2673
|
+
case "reference":
|
|
2674
|
+
return { type: "reference", value: value.value };
|
|
2675
|
+
case "expression":
|
|
2676
|
+
return { type: "expression", value: value.raw };
|
|
2677
|
+
case "property-path":
|
|
2678
|
+
return {
|
|
2679
|
+
type: "property-path",
|
|
2680
|
+
value: `${semanticValueToJSON(value.object).value}.${value.property}`
|
|
2681
|
+
};
|
|
2682
|
+
case "flag":
|
|
2683
|
+
return { type: "flag", value: value.enabled };
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
function detectSelectorKind(selector) {
|
|
2687
|
+
if (selector.startsWith("#")) return "id";
|
|
2688
|
+
if (selector.startsWith(".")) return "class";
|
|
2689
|
+
if (selector.startsWith("[")) return "attribute";
|
|
2690
|
+
return "complex";
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// src/ir/protocol-json.ts
|
|
2694
|
+
function toProtocolJSON(node) {
|
|
2695
|
+
const roles = rolesToProtocol(node.roles);
|
|
2696
|
+
let result;
|
|
2697
|
+
switch (node.kind) {
|
|
2698
|
+
case "command": {
|
|
2699
|
+
const cmd = node;
|
|
2700
|
+
result = { kind: "command", action: node.action, roles };
|
|
2701
|
+
if (cmd.body && cmd.body.length > 0) {
|
|
2702
|
+
result.body = cmd.body.map(toProtocolJSON);
|
|
2703
|
+
}
|
|
2704
|
+
if (cmd.catchBranch && cmd.catchBranch.length > 0) {
|
|
2705
|
+
result.catchBranch = cmd.catchBranch.map(toProtocolJSON);
|
|
2706
|
+
}
|
|
2707
|
+
if (cmd.finallyBranch && cmd.finallyBranch.length > 0) {
|
|
2708
|
+
result.finallyBranch = cmd.finallyBranch.map(toProtocolJSON);
|
|
2709
|
+
}
|
|
2710
|
+
if (cmd.asyncVariant) {
|
|
2711
|
+
result.asyncVariant = cmd.asyncVariant;
|
|
2712
|
+
}
|
|
2713
|
+
if (cmd.asyncBody && cmd.asyncBody.length > 0) {
|
|
2714
|
+
result.asyncBody = cmd.asyncBody.map(toProtocolJSON);
|
|
2715
|
+
}
|
|
2716
|
+
if (cmd.arms && cmd.arms.length > 0) {
|
|
2717
|
+
result.arms = cmd.arms.map((arm) => ({
|
|
2718
|
+
pattern: valueToProtocol(arm.pattern),
|
|
2719
|
+
body: arm.body.map(toProtocolJSON)
|
|
2720
|
+
}));
|
|
2721
|
+
}
|
|
2722
|
+
if (cmd.defaultArm && cmd.defaultArm.length > 0) {
|
|
2723
|
+
result.defaultArm = cmd.defaultArm.map(toProtocolJSON);
|
|
2724
|
+
}
|
|
2725
|
+
break;
|
|
2726
|
+
}
|
|
2727
|
+
case "event-handler": {
|
|
2728
|
+
const eh = node;
|
|
2729
|
+
result = {
|
|
2730
|
+
kind: "event-handler",
|
|
2731
|
+
action: node.action,
|
|
2732
|
+
roles,
|
|
2733
|
+
body: eh.body.map(toProtocolJSON)
|
|
2734
|
+
};
|
|
2735
|
+
break;
|
|
2736
|
+
}
|
|
2737
|
+
case "compound": {
|
|
2738
|
+
const c = node;
|
|
2739
|
+
result = {
|
|
2740
|
+
kind: "compound",
|
|
2741
|
+
action: "compound",
|
|
2742
|
+
statements: c.statements.map(toProtocolJSON),
|
|
2743
|
+
chainType: c.chainType
|
|
2744
|
+
};
|
|
2745
|
+
break;
|
|
2746
|
+
}
|
|
2747
|
+
case "conditional": {
|
|
2748
|
+
const cond = node;
|
|
2749
|
+
result = { kind: "command", action: node.action, roles };
|
|
2750
|
+
result.thenBranch = cond.thenBranch.map(toProtocolJSON);
|
|
2751
|
+
if (cond.elseBranch && cond.elseBranch.length > 0) {
|
|
2752
|
+
result.elseBranch = cond.elseBranch.map(toProtocolJSON);
|
|
2753
|
+
}
|
|
2754
|
+
break;
|
|
2755
|
+
}
|
|
2756
|
+
case "loop": {
|
|
2757
|
+
const loop = node;
|
|
2758
|
+
result = { kind: "command", action: node.action, roles };
|
|
2759
|
+
result.loopVariant = loop.loopVariant;
|
|
2760
|
+
result.loopBody = loop.body.map(toProtocolJSON);
|
|
2761
|
+
if (loop.loopVariable) result.loopVariable = loop.loopVariable;
|
|
2762
|
+
if (loop.indexVariable) result.indexVariable = loop.indexVariable;
|
|
2763
|
+
break;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
if (node.annotations && node.annotations.length > 0) {
|
|
2767
|
+
result.annotations = node.annotations.map(
|
|
2768
|
+
(a) => a.value !== void 0 ? { name: a.name, value: a.value } : { name: a.name }
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
if (node.diagnostics && node.diagnostics.length > 0) {
|
|
2772
|
+
result.diagnostics = node.diagnostics.map((d) => ({
|
|
2773
|
+
level: d.level,
|
|
2774
|
+
role: d.role,
|
|
2775
|
+
message: d.message,
|
|
2776
|
+
code: d.code
|
|
2777
|
+
}));
|
|
2778
|
+
}
|
|
2779
|
+
return result;
|
|
2780
|
+
}
|
|
2781
|
+
function rolesToProtocol(roles) {
|
|
2782
|
+
const result = {};
|
|
2783
|
+
for (const [role, value] of roles) {
|
|
2784
|
+
result[role] = valueToProtocol(value);
|
|
2785
|
+
}
|
|
2786
|
+
return result;
|
|
2787
|
+
}
|
|
2788
|
+
function valueToProtocol(value) {
|
|
2789
|
+
switch (value.type) {
|
|
2790
|
+
case "selector": {
|
|
2791
|
+
const PROTOCOL_SELECTOR_KINDS = /* @__PURE__ */ new Set([
|
|
2792
|
+
"id",
|
|
2793
|
+
"class",
|
|
2794
|
+
"attribute",
|
|
2795
|
+
"element",
|
|
2796
|
+
"complex"
|
|
2797
|
+
]);
|
|
2798
|
+
const sk = "selectorKind" in value ? value.selectorKind : void 0;
|
|
2799
|
+
const sv = { type: "selector", value: value.value };
|
|
2800
|
+
if (sk && PROTOCOL_SELECTOR_KINDS.has(sk)) {
|
|
2801
|
+
sv.selectorKind = sk;
|
|
2802
|
+
}
|
|
2803
|
+
return sv;
|
|
2804
|
+
}
|
|
2805
|
+
case "literal":
|
|
2806
|
+
return value.dataType ? { type: "literal", value: value.value, dataType: value.dataType } : { type: "literal", value: value.value };
|
|
2807
|
+
case "reference":
|
|
2808
|
+
return { type: "reference", value: value.value };
|
|
2809
|
+
case "expression":
|
|
2810
|
+
return { type: "expression", raw: value.raw };
|
|
2811
|
+
case "property-path":
|
|
2812
|
+
return { type: "expression", raw: extractValue(value) };
|
|
2813
|
+
case "flag":
|
|
2814
|
+
return { type: "flag", name: value.name, enabled: value.enabled };
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
function fromProtocolJSON(json) {
|
|
2818
|
+
const roles = new Map(
|
|
2819
|
+
Object.entries(json.roles ?? {}).map(([role, val]) => [role, valueFromProtocol(val)])
|
|
2820
|
+
);
|
|
2821
|
+
const annotations = json.annotations?.map(
|
|
2822
|
+
(a) => a.value !== void 0 ? { name: a.name, value: a.value } : { name: a.name }
|
|
2823
|
+
);
|
|
2824
|
+
const diagnostics = json.diagnostics?.map((d) => ({
|
|
2825
|
+
level: d.level,
|
|
2826
|
+
role: d.role,
|
|
2827
|
+
message: d.message,
|
|
2828
|
+
code: d.code
|
|
2829
|
+
}));
|
|
2830
|
+
if (json.trigger) {
|
|
2831
|
+
const eventName = json.trigger.event;
|
|
2832
|
+
roles.set("event", createLiteral(eventName, "string"));
|
|
2833
|
+
const bodyRoles = new Map(roles);
|
|
2834
|
+
bodyRoles.delete("event");
|
|
2835
|
+
const bodyNode = createCommandNode(json.action, bodyRoles);
|
|
2836
|
+
const node2 = createEventHandlerNode(
|
|
2837
|
+
"on",
|
|
2838
|
+
roles,
|
|
2839
|
+
[bodyNode],
|
|
2840
|
+
{ sourceLanguage: "json" },
|
|
2841
|
+
json.trigger.modifiers ?? {}
|
|
2842
|
+
);
|
|
2843
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2844
|
+
}
|
|
2845
|
+
const kind = json.kind ?? "command";
|
|
2846
|
+
if (kind === "event-handler") {
|
|
2847
|
+
const body = (json.body ?? []).map(fromProtocolJSON);
|
|
2848
|
+
const node2 = createEventHandlerNode(json.action, roles, body);
|
|
2849
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2850
|
+
}
|
|
2851
|
+
if (kind === "compound") {
|
|
2852
|
+
const statements = (json.statements ?? []).map(fromProtocolJSON);
|
|
2853
|
+
const node2 = createCompoundNode(
|
|
2854
|
+
statements,
|
|
2855
|
+
json.chainType ?? "sequential"
|
|
2856
|
+
);
|
|
2857
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2858
|
+
}
|
|
2859
|
+
if (json.thenBranch && json.thenBranch.length > 0) {
|
|
2860
|
+
const thenBranch = json.thenBranch.map(fromProtocolJSON);
|
|
2861
|
+
const elseBranch = json.elseBranch ? json.elseBranch.map(fromProtocolJSON) : void 0;
|
|
2862
|
+
const node2 = createConditionalNode(json.action, roles, thenBranch, elseBranch);
|
|
2863
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2864
|
+
}
|
|
2865
|
+
if (json.loopVariant) {
|
|
2866
|
+
const loopBody = (json.loopBody ?? []).map(fromProtocolJSON);
|
|
2867
|
+
const node2 = createLoopNode(
|
|
2868
|
+
json.action,
|
|
2869
|
+
roles,
|
|
2870
|
+
json.loopVariant,
|
|
2871
|
+
loopBody,
|
|
2872
|
+
json.loopVariable,
|
|
2873
|
+
json.indexVariable
|
|
2874
|
+
);
|
|
2875
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2876
|
+
}
|
|
2877
|
+
if (json.catchBranch || json.finallyBranch) {
|
|
2878
|
+
const body = (json.body ?? []).map(fromProtocolJSON);
|
|
2879
|
+
const catchBranch = json.catchBranch ? json.catchBranch.map(fromProtocolJSON) : void 0;
|
|
2880
|
+
const finallyBranch = json.finallyBranch ? json.finallyBranch.map(fromProtocolJSON) : void 0;
|
|
2881
|
+
const node2 = createTryNode(body, catchBranch, finallyBranch);
|
|
2882
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2883
|
+
}
|
|
2884
|
+
if (json.asyncVariant) {
|
|
2885
|
+
const asyncBody = (json.asyncBody ?? []).map(fromProtocolJSON);
|
|
2886
|
+
const node2 = createAsyncNode(json.asyncVariant, asyncBody);
|
|
2887
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2888
|
+
}
|
|
2889
|
+
if (json.arms && json.arms.length > 0) {
|
|
2890
|
+
const arms = json.arms.map((arm) => ({
|
|
2891
|
+
pattern: valueFromProtocol(arm.pattern),
|
|
2892
|
+
body: arm.body.map(fromProtocolJSON)
|
|
2893
|
+
}));
|
|
2894
|
+
const defaultArm = json.defaultArm ? json.defaultArm.map(fromProtocolJSON) : void 0;
|
|
2895
|
+
const node2 = createMatchNode(roles, arms, defaultArm);
|
|
2896
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2897
|
+
}
|
|
2898
|
+
if (json.body && json.body.length > 0 && kind === "command") {
|
|
2899
|
+
const body = json.body.map(fromProtocolJSON);
|
|
2900
|
+
const node2 = createTryNode(body);
|
|
2901
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
2902
|
+
}
|
|
2903
|
+
const node = createCommandNode(json.action, roles);
|
|
2904
|
+
return applyV12Metadata(node, annotations, diagnostics);
|
|
2905
|
+
}
|
|
2906
|
+
function applyV12Metadata(node, annotations, diagnostics) {
|
|
2907
|
+
if ((!annotations || annotations.length === 0) && (!diagnostics || diagnostics.length === 0)) {
|
|
2908
|
+
return node;
|
|
2909
|
+
}
|
|
2910
|
+
return {
|
|
2911
|
+
...node,
|
|
2912
|
+
...annotations && annotations.length > 0 ? { annotations } : {},
|
|
2913
|
+
...diagnostics && diagnostics.length > 0 ? { diagnostics } : {}
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
function valueFromProtocol(value) {
|
|
2917
|
+
switch (value.type) {
|
|
2918
|
+
case "selector":
|
|
2919
|
+
return createSelector(String(value.value ?? ""), value.selectorKind);
|
|
2920
|
+
case "literal":
|
|
2921
|
+
return createLiteral(
|
|
2922
|
+
value.value !== void 0 ? value.value : "",
|
|
2923
|
+
value.dataType
|
|
2924
|
+
);
|
|
2925
|
+
case "reference":
|
|
2926
|
+
return createReference(String(value.value ?? ""));
|
|
2927
|
+
case "expression":
|
|
2928
|
+
return createExpression(value.raw ?? String(value.value ?? ""));
|
|
2929
|
+
case "flag":
|
|
2930
|
+
return createFlag(
|
|
2931
|
+
value.name ?? String(value.value ?? ""),
|
|
2932
|
+
value.enabled ?? (typeof value.value === "boolean" ? value.value : true)
|
|
2933
|
+
);
|
|
2934
|
+
case "property-path":
|
|
2935
|
+
return createExpression(String(value.value ?? value.raw ?? ""));
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
function toEnvelopeJSON(envelope) {
|
|
2939
|
+
const result = {
|
|
2940
|
+
lseVersion: envelope.lseVersion,
|
|
2941
|
+
nodes: envelope.nodes.map(toProtocolJSON)
|
|
2942
|
+
};
|
|
2943
|
+
if (envelope.features && envelope.features.length > 0) {
|
|
2944
|
+
result.features = [...envelope.features];
|
|
2945
|
+
}
|
|
2946
|
+
return result;
|
|
2947
|
+
}
|
|
2948
|
+
function fromEnvelopeJSON(json) {
|
|
2949
|
+
return {
|
|
2950
|
+
lseVersion: json.lseVersion,
|
|
2951
|
+
...json.features && json.features.length > 0 ? { features: json.features } : {},
|
|
2952
|
+
nodes: json.nodes.map(fromProtocolJSON)
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
function isEnvelope(json) {
|
|
2956
|
+
return typeof json === "object" && json !== null && "lseVersion" in json && "nodes" in json && Array.isArray(json.nodes);
|
|
2957
|
+
}
|
|
2958
|
+
var VALID_KINDS = /* @__PURE__ */ new Set(["command", "event-handler", "compound"]);
|
|
2959
|
+
var VALID_VALUE_TYPES2 = /* @__PURE__ */ new Set([
|
|
2960
|
+
"selector",
|
|
2961
|
+
"literal",
|
|
2962
|
+
"reference",
|
|
2963
|
+
"expression",
|
|
2964
|
+
"property-path",
|
|
2965
|
+
"flag"
|
|
2966
|
+
]);
|
|
2967
|
+
var VALID_CHAIN_TYPES = /* @__PURE__ */ new Set(["then", "and", "async", "sequential", "pipe"]);
|
|
2968
|
+
var VALID_ASYNC_VARIANTS = /* @__PURE__ */ new Set(["all", "race"]);
|
|
2969
|
+
var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning"]);
|
|
2970
|
+
function validateProtocolJSON(json) {
|
|
2971
|
+
const diagnostics = [];
|
|
2972
|
+
if (typeof json !== "object" || json === null) {
|
|
2973
|
+
return [{ severity: "error", code: "INVALID_ROOT", message: "Root must be a JSON object" }];
|
|
2974
|
+
}
|
|
2975
|
+
const node = json;
|
|
2976
|
+
const kind = "kind" in node ? String(node.kind) : "command";
|
|
2977
|
+
if ("kind" in node && !VALID_KINDS.has(kind)) {
|
|
2978
|
+
diagnostics.push({
|
|
2979
|
+
severity: "error",
|
|
2980
|
+
code: "INVALID_KIND",
|
|
2981
|
+
message: `Invalid kind "${node.kind}". Must be one of: command, event-handler, compound`
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
if (!("action" in node)) {
|
|
2985
|
+
diagnostics.push({
|
|
2986
|
+
severity: "error",
|
|
2987
|
+
code: "MISSING_ACTION",
|
|
2988
|
+
message: "Missing required field: action"
|
|
2989
|
+
});
|
|
2990
|
+
} else if (typeof node.action !== "string" || node.action.length === 0) {
|
|
2991
|
+
diagnostics.push({
|
|
2992
|
+
severity: "error",
|
|
2993
|
+
code: "INVALID_ACTION",
|
|
2994
|
+
message: "action must be a non-empty string"
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
if (!("roles" in node) && kind !== "compound") {
|
|
2998
|
+
diagnostics.push({
|
|
2999
|
+
severity: "error",
|
|
3000
|
+
code: "MISSING_ROLES",
|
|
3001
|
+
message: "Missing required field: roles"
|
|
3002
|
+
});
|
|
3003
|
+
} else if ("roles" in node && (typeof node.roles !== "object" || node.roles === null || Array.isArray(node.roles))) {
|
|
3004
|
+
diagnostics.push({
|
|
3005
|
+
severity: "error",
|
|
3006
|
+
code: "INVALID_ROLES",
|
|
3007
|
+
message: "roles must be a plain object"
|
|
3008
|
+
});
|
|
3009
|
+
} else if ("roles" in node && node.roles) {
|
|
3010
|
+
for (const [role, value] of Object.entries(node.roles)) {
|
|
3011
|
+
if (typeof value !== "object" || value === null) {
|
|
3012
|
+
diagnostics.push({
|
|
3013
|
+
severity: "error",
|
|
3014
|
+
code: "INVALID_ROLE_VALUE",
|
|
3015
|
+
message: `Role "${role}" value must be an object`
|
|
3016
|
+
});
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
3019
|
+
const v = value;
|
|
3020
|
+
if (!("type" in v) || !VALID_VALUE_TYPES2.has(String(v.type))) {
|
|
3021
|
+
diagnostics.push({
|
|
3022
|
+
severity: "error",
|
|
3023
|
+
code: "INVALID_VALUE_TYPE",
|
|
3024
|
+
message: `Role "${role}" has invalid type "${v.type}"`
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
if (v.type === "flag") {
|
|
3028
|
+
const hasName = typeof v.name === "string";
|
|
3029
|
+
const hasEnabled = typeof v.enabled === "boolean";
|
|
3030
|
+
const hasValue = "value" in v;
|
|
3031
|
+
if (!hasName && !hasValue) {
|
|
3032
|
+
diagnostics.push({
|
|
3033
|
+
severity: "error",
|
|
3034
|
+
code: "MISSING_FLAG_NAME",
|
|
3035
|
+
message: `Flag role "${role}" missing required field: name (or value)`
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
if (!hasEnabled && !hasValue) {
|
|
3039
|
+
diagnostics.push({
|
|
3040
|
+
severity: "error",
|
|
3041
|
+
code: "MISSING_FLAG_ENABLED",
|
|
3042
|
+
message: `Flag role "${role}" missing required field: enabled (or value)`
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
if (v.type === "expression" && typeof v.raw !== "string" && !("value" in v)) {
|
|
3047
|
+
diagnostics.push({
|
|
3048
|
+
severity: "error",
|
|
3049
|
+
code: "MISSING_EXPRESSION_RAW",
|
|
3050
|
+
message: `Expression role "${role}" missing required field: raw (or value)`
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
if ("trigger" in node && node.trigger) {
|
|
3056
|
+
const trigger = node.trigger;
|
|
3057
|
+
if (typeof trigger !== "object" || trigger === null) {
|
|
3058
|
+
diagnostics.push({
|
|
3059
|
+
severity: "error",
|
|
3060
|
+
code: "INVALID_TRIGGER",
|
|
3061
|
+
message: "trigger must be an object"
|
|
3062
|
+
});
|
|
3063
|
+
} else if (!trigger.event || typeof trigger.event !== "string") {
|
|
3064
|
+
diagnostics.push({
|
|
3065
|
+
severity: "error",
|
|
3066
|
+
code: "INVALID_TRIGGER",
|
|
3067
|
+
message: "trigger.event is required and must be a non-empty string"
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
if (kind === "event-handler" && !("trigger" in node)) {
|
|
3072
|
+
if (!("body" in node) || !Array.isArray(node.body)) {
|
|
3073
|
+
diagnostics.push({
|
|
3074
|
+
severity: "error",
|
|
3075
|
+
code: "MISSING_BODY",
|
|
3076
|
+
message: "event-handler node requires a body array"
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
if (kind === "compound") {
|
|
3081
|
+
if (!("statements" in node) || !Array.isArray(node.statements)) {
|
|
3082
|
+
diagnostics.push({
|
|
3083
|
+
severity: "error",
|
|
3084
|
+
code: "MISSING_STATEMENTS",
|
|
3085
|
+
message: "compound node requires a statements array"
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
if ("chainType" in node && !VALID_CHAIN_TYPES.has(String(node.chainType))) {
|
|
3089
|
+
diagnostics.push({
|
|
3090
|
+
severity: "error",
|
|
3091
|
+
code: "INVALID_CHAIN_TYPE",
|
|
3092
|
+
message: `Invalid chainType "${node.chainType}". Must be one of: then, and, async, sequential, pipe`
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
if ("diagnostics" in node) {
|
|
3097
|
+
if (!Array.isArray(node.diagnostics)) {
|
|
3098
|
+
diagnostics.push({
|
|
3099
|
+
severity: "error",
|
|
3100
|
+
code: "INVALID_DIAGNOSTICS",
|
|
3101
|
+
message: "diagnostics must be an array"
|
|
3102
|
+
});
|
|
3103
|
+
} else {
|
|
3104
|
+
for (let i = 0; i < node.diagnostics.length; i++) {
|
|
3105
|
+
const d = node.diagnostics[i];
|
|
3106
|
+
if (!d || typeof d !== "object") {
|
|
3107
|
+
diagnostics.push({
|
|
3108
|
+
severity: "error",
|
|
3109
|
+
code: "INVALID_DIAGNOSTIC_ENTRY",
|
|
3110
|
+
message: `diagnostics[${i}] must be an object`
|
|
3111
|
+
});
|
|
3112
|
+
continue;
|
|
3113
|
+
}
|
|
3114
|
+
if (!VALID_DIAGNOSTIC_LEVELS.has(String(d.level))) {
|
|
3115
|
+
diagnostics.push({
|
|
3116
|
+
severity: "error",
|
|
3117
|
+
code: "INVALID_DIAGNOSTIC_LEVEL",
|
|
3118
|
+
message: `diagnostics[${i}].level must be "error" or "warning"`
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
if (typeof d.role !== "string") {
|
|
3122
|
+
diagnostics.push({
|
|
3123
|
+
severity: "error",
|
|
3124
|
+
code: "MISSING_DIAGNOSTIC_ROLE",
|
|
3125
|
+
message: `diagnostics[${i}] missing required field: role`
|
|
3126
|
+
});
|
|
3127
|
+
}
|
|
3128
|
+
if (typeof d.message !== "string") {
|
|
3129
|
+
diagnostics.push({
|
|
3130
|
+
severity: "error",
|
|
3131
|
+
code: "MISSING_DIAGNOSTIC_MESSAGE",
|
|
3132
|
+
message: `diagnostics[${i}] missing required field: message`
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
if (typeof d.code !== "string") {
|
|
3136
|
+
diagnostics.push({
|
|
3137
|
+
severity: "error",
|
|
3138
|
+
code: "MISSING_DIAGNOSTIC_CODE",
|
|
3139
|
+
message: `diagnostics[${i}] missing required field: code`
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if ("annotations" in node) {
|
|
3146
|
+
if (!Array.isArray(node.annotations)) {
|
|
3147
|
+
diagnostics.push({
|
|
3148
|
+
severity: "error",
|
|
3149
|
+
code: "INVALID_ANNOTATIONS",
|
|
3150
|
+
message: "annotations must be an array"
|
|
3151
|
+
});
|
|
3152
|
+
} else {
|
|
3153
|
+
for (let i = 0; i < node.annotations.length; i++) {
|
|
3154
|
+
const a = node.annotations[i];
|
|
3155
|
+
if (!a || typeof a !== "object") {
|
|
3156
|
+
diagnostics.push({
|
|
3157
|
+
severity: "error",
|
|
3158
|
+
code: "INVALID_ANNOTATION_ENTRY",
|
|
3159
|
+
message: `annotations[${i}] must be an object`
|
|
3160
|
+
});
|
|
3161
|
+
continue;
|
|
3162
|
+
}
|
|
3163
|
+
if (typeof a.name !== "string" || a.name.length === 0) {
|
|
3164
|
+
diagnostics.push({
|
|
3165
|
+
severity: "error",
|
|
3166
|
+
code: "MISSING_ANNOTATION_NAME",
|
|
3167
|
+
message: `annotations[${i}] missing required field: name`
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
if ("asyncVariant" in node && !VALID_ASYNC_VARIANTS.has(String(node.asyncVariant))) {
|
|
3174
|
+
diagnostics.push({
|
|
3175
|
+
severity: "error",
|
|
3176
|
+
code: "INVALID_ASYNC_VARIANT",
|
|
3177
|
+
message: `Invalid asyncVariant "${node.asyncVariant}". Must be "all" or "race"`
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
if ("arms" in node) {
|
|
3181
|
+
if (!Array.isArray(node.arms)) {
|
|
3182
|
+
diagnostics.push({
|
|
3183
|
+
severity: "error",
|
|
3184
|
+
code: "INVALID_ARMS",
|
|
3185
|
+
message: "arms must be an array"
|
|
3186
|
+
});
|
|
3187
|
+
} else {
|
|
3188
|
+
for (let i = 0; i < node.arms.length; i++) {
|
|
3189
|
+
const arm = node.arms[i];
|
|
3190
|
+
if (!arm || typeof arm !== "object") {
|
|
3191
|
+
diagnostics.push({
|
|
3192
|
+
severity: "error",
|
|
3193
|
+
code: "INVALID_ARM_ENTRY",
|
|
3194
|
+
message: `arms[${i}] must be an object`
|
|
3195
|
+
});
|
|
3196
|
+
continue;
|
|
3197
|
+
}
|
|
3198
|
+
if (!arm.pattern || typeof arm.pattern !== "object") {
|
|
3199
|
+
diagnostics.push({
|
|
3200
|
+
severity: "error",
|
|
3201
|
+
code: "MISSING_ARM_PATTERN",
|
|
3202
|
+
message: `arms[${i}] missing required field: pattern`
|
|
3203
|
+
});
|
|
3204
|
+
}
|
|
3205
|
+
if (!Array.isArray(arm.body)) {
|
|
3206
|
+
diagnostics.push({
|
|
3207
|
+
severity: "error",
|
|
3208
|
+
code: "MISSING_ARM_BODY",
|
|
3209
|
+
message: `arms[${i}] missing required field: body (array)`
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
return diagnostics;
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
// src/ir/from-interchange.ts
|
|
3219
|
+
function fromInterchangeNode(node) {
|
|
3220
|
+
if (!node || typeof node !== "object") {
|
|
3221
|
+
return createCommandNode("unknown", {});
|
|
3222
|
+
}
|
|
3223
|
+
switch (node.type) {
|
|
3224
|
+
case "event":
|
|
3225
|
+
return convertEvent(node);
|
|
3226
|
+
case "command":
|
|
3227
|
+
return convertCommand(node);
|
|
3228
|
+
case "if":
|
|
3229
|
+
return convertIf(node);
|
|
3230
|
+
case "repeat":
|
|
3231
|
+
return convertRepeat(node);
|
|
3232
|
+
case "foreach":
|
|
3233
|
+
return convertForEach(node);
|
|
3234
|
+
case "while":
|
|
3235
|
+
return convertWhile(node);
|
|
3236
|
+
// Expression nodes — wrap as `get` command with patient role
|
|
3237
|
+
default:
|
|
3238
|
+
return createCommandNode("get", { patient: convertValue(node) });
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
function convertEvent(node) {
|
|
3242
|
+
const event = String(node.event ?? "click");
|
|
3243
|
+
const bodyNodes = Array.isArray(node.body) ? node.body : [];
|
|
3244
|
+
const body = bodyNodes.map(fromInterchangeNode);
|
|
3245
|
+
const roles = {
|
|
3246
|
+
event: createLiteral(event)
|
|
3247
|
+
};
|
|
3248
|
+
if (node.target && typeof node.target === "object") {
|
|
3249
|
+
roles.source = convertValue(node.target);
|
|
3250
|
+
}
|
|
3251
|
+
const modifiers = node.modifiers;
|
|
3252
|
+
let eventModifiers;
|
|
3253
|
+
if (modifiers) {
|
|
3254
|
+
eventModifiers = bridgeEventModifiers(modifiers);
|
|
3255
|
+
if (modifiers.prevent) roles.prevent = createLiteral(true);
|
|
3256
|
+
if (modifiers.stop) roles.stop = createLiteral(true);
|
|
3257
|
+
if (modifiers.passive) roles.passive = createLiteral(true);
|
|
3258
|
+
if (modifiers.capture) roles.capture = createLiteral(true);
|
|
3259
|
+
}
|
|
3260
|
+
return createEventHandlerNode("on", roles, body, void 0, eventModifiers);
|
|
3261
|
+
}
|
|
3262
|
+
function convertCommand(node) {
|
|
3263
|
+
const name = String(node.name ?? "unknown");
|
|
3264
|
+
const args = Array.isArray(node.args) ? node.args : [];
|
|
3265
|
+
const target = node.target && typeof node.target === "object" ? node.target : void 0;
|
|
3266
|
+
const existingRoles = node.roles;
|
|
3267
|
+
const modifiers = node.modifiers;
|
|
3268
|
+
let roles;
|
|
3269
|
+
if (existingRoles && typeof existingRoles === "object" && Object.keys(existingRoles).length > 0) {
|
|
3270
|
+
roles = {};
|
|
3271
|
+
for (const [role, value] of Object.entries(existingRoles)) {
|
|
3272
|
+
if (value && typeof value === "object" && "type" in value) {
|
|
3273
|
+
roles[role] = convertValue(value);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
} else {
|
|
3277
|
+
roles = inferRoles(name, args, modifiers, target);
|
|
3278
|
+
}
|
|
3279
|
+
return createCommandNode(name, roles);
|
|
3280
|
+
}
|
|
3281
|
+
function convertIf(node) {
|
|
3282
|
+
const condition = node.condition && typeof node.condition === "object" ? convertValue(node.condition) : createExpression("true");
|
|
3283
|
+
const thenBody = Array.isArray(node.thenBranch) ? node.thenBranch : [];
|
|
3284
|
+
const thenBranch = thenBody.map(fromInterchangeNode);
|
|
3285
|
+
let elseBranch;
|
|
3286
|
+
const elseIfBranches = node.elseIfBranches;
|
|
3287
|
+
if (elseIfBranches && elseIfBranches.length > 0) {
|
|
3288
|
+
elseBranch = [chainElseIfs(elseIfBranches, node.elseBranch)];
|
|
3289
|
+
} else if (Array.isArray(node.elseBranch) && node.elseBranch.length > 0) {
|
|
3290
|
+
elseBranch = node.elseBranch.map(fromInterchangeNode);
|
|
3291
|
+
}
|
|
3292
|
+
const roles = { condition };
|
|
3293
|
+
return createConditionalNode("if", roles, thenBranch, elseBranch);
|
|
3294
|
+
}
|
|
3295
|
+
function chainElseIfs(branches, finalElse) {
|
|
3296
|
+
const [first, ...rest] = branches;
|
|
3297
|
+
const condition = convertValue(first.condition);
|
|
3298
|
+
const thenBranch = first.body.map(fromInterchangeNode);
|
|
3299
|
+
let elseBranch;
|
|
3300
|
+
if (rest.length > 0) {
|
|
3301
|
+
elseBranch = [chainElseIfs(rest, finalElse)];
|
|
3302
|
+
} else if (finalElse && finalElse.length > 0) {
|
|
3303
|
+
elseBranch = finalElse.map(fromInterchangeNode);
|
|
3304
|
+
}
|
|
3305
|
+
return createConditionalNode("if", { condition }, thenBranch, elseBranch);
|
|
3306
|
+
}
|
|
3307
|
+
function convertRepeat(node) {
|
|
3308
|
+
const bodyNodes = Array.isArray(node.body) ? node.body : [];
|
|
3309
|
+
const body = bodyNodes.map(fromInterchangeNode);
|
|
3310
|
+
const roles = {};
|
|
3311
|
+
let variant;
|
|
3312
|
+
if (node.count !== void 0 && node.count !== null) {
|
|
3313
|
+
variant = "times";
|
|
3314
|
+
if (typeof node.count === "number") {
|
|
3315
|
+
roles.quantity = createLiteral(node.count, "number");
|
|
3316
|
+
} else if (typeof node.count === "object") {
|
|
3317
|
+
roles.quantity = convertValue(node.count);
|
|
3318
|
+
}
|
|
3319
|
+
} else if (node.whileCondition && typeof node.whileCondition === "object") {
|
|
3320
|
+
variant = "while";
|
|
3321
|
+
roles.condition = convertValue(node.whileCondition);
|
|
3322
|
+
} else if (typeof node.untilEvent === "string") {
|
|
3323
|
+
variant = "until";
|
|
3324
|
+
roles.condition = createLiteral(node.untilEvent);
|
|
3325
|
+
} else {
|
|
3326
|
+
variant = "forever";
|
|
3327
|
+
}
|
|
3328
|
+
return createLoopNode("repeat", roles, variant, body);
|
|
3329
|
+
}
|
|
3330
|
+
function convertForEach(node) {
|
|
3331
|
+
const bodyNodes = Array.isArray(node.body) ? node.body : [];
|
|
3332
|
+
const body = bodyNodes.map(fromInterchangeNode);
|
|
3333
|
+
const roles = {};
|
|
3334
|
+
if (node.collection && typeof node.collection === "object") {
|
|
3335
|
+
roles.source = convertValue(node.collection);
|
|
3336
|
+
}
|
|
3337
|
+
const itemName = typeof node.itemName === "string" ? node.itemName : void 0;
|
|
3338
|
+
const indexName = typeof node.indexName === "string" ? node.indexName : void 0;
|
|
3339
|
+
return createLoopNode("repeat", roles, "for", body, itemName, indexName);
|
|
3340
|
+
}
|
|
3341
|
+
function convertWhile(node) {
|
|
3342
|
+
const bodyNodes = Array.isArray(node.body) ? node.body : [];
|
|
3343
|
+
const body = bodyNodes.map(fromInterchangeNode);
|
|
3344
|
+
const roles = {};
|
|
3345
|
+
if (node.condition && typeof node.condition === "object") {
|
|
3346
|
+
roles.condition = convertValue(node.condition);
|
|
3347
|
+
}
|
|
3348
|
+
return createLoopNode("repeat", roles, "while", body);
|
|
3349
|
+
}
|
|
3350
|
+
function convertValue(node) {
|
|
3351
|
+
switch (node.type) {
|
|
3352
|
+
case "literal":
|
|
3353
|
+
return convertLiteral(node);
|
|
3354
|
+
case "selector":
|
|
3355
|
+
return createSelector(String(node.value ?? ""), inferSelectorKind2(String(node.value ?? "")));
|
|
3356
|
+
case "identifier":
|
|
3357
|
+
return convertIdentifier(node);
|
|
3358
|
+
case "variable":
|
|
3359
|
+
return convertVariable(node);
|
|
3360
|
+
case "possessive":
|
|
3361
|
+
return createPropertyPath(
|
|
3362
|
+
node.object && typeof node.object === "object" ? convertValue(node.object) : createReference("me"),
|
|
3363
|
+
String(node.property ?? "")
|
|
3364
|
+
);
|
|
3365
|
+
// Complex expressions → collapse to raw string
|
|
3366
|
+
case "binary":
|
|
3367
|
+
case "unary":
|
|
3368
|
+
case "member":
|
|
3369
|
+
case "call":
|
|
3370
|
+
case "positional":
|
|
3371
|
+
return createExpression(renderExpr(node));
|
|
3372
|
+
default:
|
|
3373
|
+
if ("value" in node) return createLiteral(String(node.value ?? ""));
|
|
3374
|
+
return createExpression(renderExpr(node));
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
function convertLiteral(node) {
|
|
3378
|
+
const value = node.value;
|
|
3379
|
+
if (value === null || value === void 0) return createLiteral("null");
|
|
3380
|
+
const dataType = typeof value === "number" ? "number" : typeof value === "boolean" ? "boolean" : void 0;
|
|
3381
|
+
return createLiteral(value, dataType);
|
|
3382
|
+
}
|
|
3383
|
+
function convertIdentifier(node) {
|
|
3384
|
+
const value = String(node.value ?? "");
|
|
3385
|
+
if (DEFAULT_REFERENCES.has(value)) {
|
|
3386
|
+
return createReference(value);
|
|
3387
|
+
}
|
|
3388
|
+
return createLiteral(value);
|
|
3389
|
+
}
|
|
3390
|
+
function convertVariable(node) {
|
|
3391
|
+
const name = String(node.name ?? "");
|
|
3392
|
+
const scope = String(node.scope ?? "local");
|
|
3393
|
+
switch (scope) {
|
|
3394
|
+
case "element":
|
|
3395
|
+
return createExpression(`:${name}`);
|
|
3396
|
+
case "global":
|
|
3397
|
+
return createExpression(`$${name}`);
|
|
3398
|
+
default:
|
|
3399
|
+
return createExpression(name);
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
function renderExpr(node) {
|
|
3403
|
+
if (!node || typeof node !== "object") return "";
|
|
3404
|
+
switch (node.type) {
|
|
3405
|
+
case "literal": {
|
|
3406
|
+
const v = node.value;
|
|
3407
|
+
if (v === null || v === void 0) return "null";
|
|
3408
|
+
if (typeof v === "string") return /\s/.test(v) ? `"${v}"` : v;
|
|
3409
|
+
return String(v);
|
|
3410
|
+
}
|
|
3411
|
+
case "identifier":
|
|
3412
|
+
return String(node.value ?? "");
|
|
3413
|
+
case "selector":
|
|
3414
|
+
return String(node.value ?? "");
|
|
3415
|
+
case "variable": {
|
|
3416
|
+
const name = String(node.name ?? "");
|
|
3417
|
+
const scope = String(node.scope ?? "local");
|
|
3418
|
+
if (scope === "element") return `:${name}`;
|
|
3419
|
+
if (scope === "global") return `$${name}`;
|
|
3420
|
+
return name;
|
|
3421
|
+
}
|
|
3422
|
+
case "binary": {
|
|
3423
|
+
const left = node.left && typeof node.left === "object" ? renderExpr(node.left) : "";
|
|
3424
|
+
const right = node.right && typeof node.right === "object" ? renderExpr(node.right) : "";
|
|
3425
|
+
return `${left} ${node.operator ?? ""} ${right}`;
|
|
3426
|
+
}
|
|
3427
|
+
case "unary": {
|
|
3428
|
+
const operand = node.operand && typeof node.operand === "object" ? renderExpr(node.operand) : "";
|
|
3429
|
+
return `${node.operator ?? ""} ${operand}`;
|
|
3430
|
+
}
|
|
3431
|
+
case "member": {
|
|
3432
|
+
const obj = node.object && typeof node.object === "object" ? renderExpr(node.object) : "";
|
|
3433
|
+
if (node.computed && node.property && typeof node.property === "object") {
|
|
3434
|
+
return `${obj}[${renderExpr(node.property)}]`;
|
|
3435
|
+
}
|
|
3436
|
+
const prop = typeof node.property === "string" ? node.property : node.property && typeof node.property === "object" ? renderExpr(node.property) : "";
|
|
3437
|
+
return `${obj}.${prop}`;
|
|
3438
|
+
}
|
|
3439
|
+
case "possessive": {
|
|
3440
|
+
const obj = node.object && typeof node.object === "object" ? renderExpr(node.object) : "";
|
|
3441
|
+
return `${obj}'s ${node.property ?? ""}`;
|
|
3442
|
+
}
|
|
3443
|
+
case "call": {
|
|
3444
|
+
const callee = node.callee && typeof node.callee === "object" ? renderExpr(node.callee) : "";
|
|
3445
|
+
const args = Array.isArray(node.args) ? node.args.map((a) => renderExpr(a)).join(", ") : "";
|
|
3446
|
+
return `${callee}(${args})`;
|
|
3447
|
+
}
|
|
3448
|
+
case "positional": {
|
|
3449
|
+
const target = node.target && typeof node.target === "object" ? renderExpr(node.target) : "";
|
|
3450
|
+
return target ? `${node.position ?? ""} ${target}` : String(node.position ?? "");
|
|
3451
|
+
}
|
|
3452
|
+
default:
|
|
3453
|
+
if ("value" in node) return String(node.value ?? "");
|
|
3454
|
+
return "";
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
function inferRoles(name, args, modifiers, target) {
|
|
3458
|
+
const roles = {};
|
|
3459
|
+
switch (name) {
|
|
3460
|
+
case "set": {
|
|
3461
|
+
if (args[0]) roles.destination = convertValue(args[0]);
|
|
3462
|
+
const toVal = modifiers?.to;
|
|
3463
|
+
if (toVal && typeof toVal === "object" && "type" in toVal) {
|
|
3464
|
+
roles.patient = convertValue(toVal);
|
|
3465
|
+
} else if (args[1]) {
|
|
3466
|
+
roles.patient = convertValue(args[1]);
|
|
3467
|
+
}
|
|
3468
|
+
break;
|
|
3469
|
+
}
|
|
3470
|
+
case "put": {
|
|
3471
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3472
|
+
if (modifiers) {
|
|
3473
|
+
for (const key of ["into", "before", "after"]) {
|
|
3474
|
+
const val = modifiers[key];
|
|
3475
|
+
if (val && typeof val === "object" && "type" in val) {
|
|
3476
|
+
roles.destination = convertValue(val);
|
|
3477
|
+
roles.method = createLiteral(key);
|
|
3478
|
+
break;
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
if (!roles.destination && target) {
|
|
3483
|
+
roles.destination = convertValue(target);
|
|
3484
|
+
}
|
|
3485
|
+
break;
|
|
3486
|
+
}
|
|
3487
|
+
case "increment":
|
|
3488
|
+
case "decrement": {
|
|
3489
|
+
if (args[0]) roles.destination = convertValue(args[0]);
|
|
3490
|
+
const byVal = modifiers?.by;
|
|
3491
|
+
if (byVal && typeof byVal === "object" && "type" in byVal) {
|
|
3492
|
+
roles.quantity = convertValue(byVal);
|
|
3493
|
+
} else if (args[1]) {
|
|
3494
|
+
roles.quantity = convertValue(args[1]);
|
|
3495
|
+
}
|
|
3496
|
+
break;
|
|
3497
|
+
}
|
|
3498
|
+
case "fetch": {
|
|
3499
|
+
if (args[0]) roles.source = convertValue(args[0]);
|
|
3500
|
+
const asVal = modifiers?.as;
|
|
3501
|
+
if (asVal && typeof asVal === "object" && "type" in asVal) {
|
|
3502
|
+
roles.responseType = convertValue(asVal);
|
|
3503
|
+
} else if (typeof asVal === "string") {
|
|
3504
|
+
roles.responseType = createLiteral(asVal);
|
|
3505
|
+
}
|
|
3506
|
+
break;
|
|
3507
|
+
}
|
|
3508
|
+
case "wait":
|
|
3509
|
+
case "settle": {
|
|
3510
|
+
if (args[0]) roles.duration = convertValue(args[0]);
|
|
3511
|
+
break;
|
|
3512
|
+
}
|
|
3513
|
+
case "toggle":
|
|
3514
|
+
case "add":
|
|
3515
|
+
case "show":
|
|
3516
|
+
case "hide": {
|
|
3517
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3518
|
+
if (target) roles.destination = convertValue(target);
|
|
3519
|
+
break;
|
|
3520
|
+
}
|
|
3521
|
+
case "remove": {
|
|
3522
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3523
|
+
if (target) roles.source = convertValue(target);
|
|
3524
|
+
break;
|
|
3525
|
+
}
|
|
3526
|
+
case "send":
|
|
3527
|
+
case "trigger": {
|
|
3528
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3529
|
+
if (target) roles.destination = convertValue(target);
|
|
3530
|
+
break;
|
|
3531
|
+
}
|
|
3532
|
+
case "log": {
|
|
3533
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3534
|
+
break;
|
|
3535
|
+
}
|
|
3536
|
+
default: {
|
|
3537
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3538
|
+
if (target) roles.destination = convertValue(target);
|
|
3539
|
+
break;
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
return roles;
|
|
3543
|
+
}
|
|
3544
|
+
function inferSelectorKind2(value) {
|
|
3545
|
+
if (!value) return void 0;
|
|
3546
|
+
if (value.startsWith("#")) return "id";
|
|
3547
|
+
if (value.startsWith(".")) return "class";
|
|
3548
|
+
if (value.startsWith("[")) return "attribute";
|
|
3549
|
+
if (value.startsWith("<") || value.startsWith("*")) return "element";
|
|
3550
|
+
if (/[>\+~ ]/.test(value) && value.length > 1) return "complex";
|
|
3551
|
+
return void 0;
|
|
3552
|
+
}
|
|
3553
|
+
function inferValueFromString(value) {
|
|
3554
|
+
if (DEFAULT_REFERENCES.has(value)) return createReference(value);
|
|
3555
|
+
const kind = inferSelectorKind2(value);
|
|
3556
|
+
if (kind) return createSelector(value, kind);
|
|
3557
|
+
return createLiteral(value);
|
|
3558
|
+
}
|
|
3559
|
+
function bridgeEventModifiers(mods) {
|
|
3560
|
+
const once = mods.once === true ? true : void 0;
|
|
3561
|
+
const debounce = typeof mods.debounce === "number" ? mods.debounce : void 0;
|
|
3562
|
+
const throttle = typeof mods.throttle === "number" ? mods.throttle : void 0;
|
|
3563
|
+
const from = typeof mods.from === "string" ? inferValueFromString(mods.from) : void 0;
|
|
3564
|
+
if (once === void 0 && debounce === void 0 && throttle === void 0 && from === void 0) {
|
|
3565
|
+
return void 0;
|
|
3566
|
+
}
|
|
3567
|
+
return {
|
|
3568
|
+
...once !== void 0 ? { once } : {},
|
|
3569
|
+
...debounce !== void 0 ? { debounce } : {},
|
|
3570
|
+
...throttle !== void 0 ? { throttle } : {},
|
|
3571
|
+
...from !== void 0 ? { from } : {}
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
|
|
2029
3575
|
// src/api/create-dsl.ts
|
|
2030
3576
|
var DSLRegistry = class {
|
|
2031
3577
|
constructor(config) {
|
|
@@ -2063,12 +3609,23 @@ var MultilingualDSLImpl = class {
|
|
|
2063
3609
|
if (config.codeGenerator) {
|
|
2064
3610
|
this.codeGenerator = config.codeGenerator;
|
|
2065
3611
|
}
|
|
3612
|
+
const schemaMap = new Map(config.schemas.map((s) => [s.action, s]));
|
|
3613
|
+
this.schemaLookup = {
|
|
3614
|
+
getSchema: (action) => schemaMap.get(action)
|
|
3615
|
+
};
|
|
2066
3616
|
}
|
|
2067
3617
|
parse(input, language) {
|
|
2068
3618
|
const result = this.parseWithConfidence(input, language);
|
|
2069
3619
|
return result.node;
|
|
2070
3620
|
}
|
|
2071
3621
|
parseWithConfidence(input, language) {
|
|
3622
|
+
if (isExplicitSyntax(input)) {
|
|
3623
|
+
const node = parseExplicit(input, { schemaLookup: this.schemaLookup });
|
|
3624
|
+
if (!this.schemaLookup.getSchema(node.action)) {
|
|
3625
|
+
throw new Error(`No schema for action "${node.action}" in this DSL`);
|
|
3626
|
+
}
|
|
3627
|
+
return { node, confidence: 1 };
|
|
3628
|
+
}
|
|
2072
3629
|
const tokenizer = this.registry.getTokenizer(language);
|
|
2073
3630
|
if (!tokenizer) {
|
|
2074
3631
|
throw new Error(`No tokenizer registered for language: ${language}`);
|
|
@@ -2104,75 +3661,802 @@ var MultilingualDSLImpl = class {
|
|
|
2104
3661
|
valid: true,
|
|
2105
3662
|
node: result.node
|
|
2106
3663
|
};
|
|
2107
|
-
} catch (error) {
|
|
3664
|
+
} catch (error) {
|
|
3665
|
+
return {
|
|
3666
|
+
valid: false,
|
|
3667
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
translate(input, fromLanguage, toLanguage) {
|
|
3672
|
+
if (isExplicitSyntax(input)) return input;
|
|
3673
|
+
return this.transformer.transform(input, fromLanguage, toLanguage);
|
|
3674
|
+
}
|
|
3675
|
+
compile(input, language) {
|
|
3676
|
+
if (!this.codeGenerator) {
|
|
3677
|
+
return {
|
|
3678
|
+
ok: false,
|
|
3679
|
+
errors: ["No code generator configured for this DSL"]
|
|
3680
|
+
};
|
|
3681
|
+
}
|
|
3682
|
+
try {
|
|
3683
|
+
const result = this.parseWithConfidence(input, language);
|
|
3684
|
+
const code = this.codeGenerator.generate(result.node);
|
|
3685
|
+
return {
|
|
3686
|
+
ok: true,
|
|
3687
|
+
code,
|
|
3688
|
+
node: result.node,
|
|
3689
|
+
metadata: {
|
|
3690
|
+
parser: "semantic",
|
|
3691
|
+
confidence: result.confidence
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
} catch (error) {
|
|
3695
|
+
return {
|
|
3696
|
+
ok: false,
|
|
3697
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
getSupportedLanguages() {
|
|
3702
|
+
return this.registry.getSupportedLanguages();
|
|
3703
|
+
}
|
|
3704
|
+
};
|
|
3705
|
+
function createDefaultDictionary(config) {
|
|
3706
|
+
const translations = {};
|
|
3707
|
+
for (const lang of config.languages) {
|
|
3708
|
+
const keywords = {};
|
|
3709
|
+
for (const [canonical, keywordDef] of Object.entries(lang.patternProfile.keywords)) {
|
|
3710
|
+
keywords[canonical] = keywordDef.primary;
|
|
3711
|
+
}
|
|
3712
|
+
translations[lang.code] = keywords;
|
|
3713
|
+
}
|
|
3714
|
+
return new InMemoryDictionary(translations);
|
|
3715
|
+
}
|
|
3716
|
+
function createDefaultProfileProvider(config) {
|
|
3717
|
+
const profiles = {};
|
|
3718
|
+
for (const lang of config.languages) {
|
|
3719
|
+
if (lang.grammarProfile) {
|
|
3720
|
+
profiles[lang.code] = lang.grammarProfile;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
return new InMemoryProfileProvider(profiles);
|
|
3724
|
+
}
|
|
3725
|
+
function createMultilingualDSL(config) {
|
|
3726
|
+
const dictionary = config.dictionary ?? createDefaultDictionary(config);
|
|
3727
|
+
const profileProvider = config.profileProvider ?? createDefaultProfileProvider(config);
|
|
3728
|
+
const transformer = new GrammarTransformer({
|
|
3729
|
+
dictionary,
|
|
3730
|
+
profileProvider
|
|
3731
|
+
});
|
|
3732
|
+
const registry = new DSLRegistry(config);
|
|
3733
|
+
return new MultilingualDSLImpl(config, registry, transformer);
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3736
|
+
// src/prompts/prompt-generator.ts
|
|
3737
|
+
function generatePrompt(config) {
|
|
3738
|
+
const outputFormat = config.outputFormat ?? "both";
|
|
3739
|
+
const examplesPerCommand = config.examplesPerCommand ?? 2;
|
|
3740
|
+
const sections = [
|
|
3741
|
+
buildProtocolSection(),
|
|
3742
|
+
buildValueTypeSection(),
|
|
3743
|
+
buildCommandsSection(config.schemas, outputFormat, examplesPerCommand),
|
|
3744
|
+
buildOutputFormatSection(outputFormat),
|
|
3745
|
+
buildErrorRecoverySection()
|
|
3746
|
+
];
|
|
3747
|
+
let finalSections = sections;
|
|
3748
|
+
if (config.maxTokens && config.maxTokens > 0) {
|
|
3749
|
+
finalSections = truncateToTokenBudget(sections, config.maxTokens);
|
|
3750
|
+
}
|
|
3751
|
+
const text = finalSections.map((s) => `## ${s.title}
|
|
3752
|
+
|
|
3753
|
+
${s.content}`).join("\n\n---\n\n");
|
|
3754
|
+
const totalRoles = config.schemas.reduce((sum, s) => sum + s.roles.length, 0);
|
|
3755
|
+
const approximateTokens = estimateTokens(text);
|
|
3756
|
+
const metadata = {
|
|
3757
|
+
domain: config.domain,
|
|
3758
|
+
commandCount: config.schemas.length,
|
|
3759
|
+
roleCount: totalRoles,
|
|
3760
|
+
approximateTokens
|
|
3761
|
+
};
|
|
3762
|
+
return { text, sections: finalSections, metadata };
|
|
3763
|
+
}
|
|
3764
|
+
function generateExamples(schema, count = 2) {
|
|
3765
|
+
const combos = generateRoleCombinations(schema);
|
|
3766
|
+
const examples = [];
|
|
3767
|
+
for (let i = 0; i < Math.min(count, combos.length); i++) {
|
|
3768
|
+
const combo = combos[i];
|
|
3769
|
+
const roles = /* @__PURE__ */ new Map();
|
|
3770
|
+
for (const role of combo) {
|
|
3771
|
+
const sample = sampleValue(role);
|
|
3772
|
+
roles.set(role.role, sample);
|
|
3773
|
+
}
|
|
3774
|
+
const nodeRoles = /* @__PURE__ */ new Map();
|
|
3775
|
+
for (const [roleName, sample] of roles) {
|
|
3776
|
+
nodeRoles.set(roleName, toSemanticValue(sample));
|
|
3777
|
+
}
|
|
3778
|
+
const node = createCommandNode(schema.action, nodeRoles);
|
|
3779
|
+
const explicit = renderExplicit(node);
|
|
3780
|
+
const jsonRoles = {};
|
|
3781
|
+
for (const [roleName, sample] of roles) {
|
|
3782
|
+
jsonRoles[roleName] = sample;
|
|
3783
|
+
}
|
|
3784
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
3785
|
+
examples.push({ explicit, json });
|
|
3786
|
+
}
|
|
3787
|
+
return examples;
|
|
3788
|
+
}
|
|
3789
|
+
function generateProtocolReference() {
|
|
3790
|
+
const protocol = buildProtocolSection();
|
|
3791
|
+
const valueTypes = buildValueTypeSection();
|
|
3792
|
+
return `# LokaScript Explicit Syntax (LSE) Quick Reference
|
|
3793
|
+
|
|
3794
|
+
${protocol.content}
|
|
3795
|
+
|
|
3796
|
+
${valueTypes.content}`;
|
|
3797
|
+
}
|
|
3798
|
+
function buildProtocolSection() {
|
|
3799
|
+
const content = `LokaScript Explicit Syntax (LSE) is a bracket-based format for imperative commands.
|
|
3800
|
+
|
|
3801
|
+
### Syntax
|
|
3802
|
+
|
|
3803
|
+
\`\`\`
|
|
3804
|
+
[command role1:value1 role2:value2 +flag1 ~flag2]
|
|
3805
|
+
\`\`\`
|
|
3806
|
+
|
|
3807
|
+
- **Command**: The first token inside brackets (lowercased)
|
|
3808
|
+
- **Role pair**: \`name:value\` \u2014 a named semantic role with a typed value (no space around colon)
|
|
3809
|
+
- **Enabled flag**: \`+name\` \u2014 boolean attribute present
|
|
3810
|
+
- **Disabled flag**: \`~name\` \u2014 boolean attribute negated
|
|
3811
|
+
- **Nested body**: \`body:[command ...]\` \u2014 a bracket command inside a role value
|
|
3812
|
+
|
|
3813
|
+
### Rules
|
|
3814
|
+
|
|
3815
|
+
1. Commands are always lowercased
|
|
3816
|
+
2. Role names preserve their original case
|
|
3817
|
+
3. No spaces around the colon in role:value pairs
|
|
3818
|
+
4. Strings with spaces must be quoted: \`patient:"hello world"\`
|
|
3819
|
+
5. Selectors start with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`
|
|
3820
|
+
6. Output must be valid bracket syntax: \`[action role:value ...]\``;
|
|
3821
|
+
return {
|
|
3822
|
+
id: "protocol",
|
|
3823
|
+
title: "LSE Protocol",
|
|
3824
|
+
content,
|
|
3825
|
+
approximateTokens: estimateTokens(content)
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
function buildValueTypeSection() {
|
|
3829
|
+
const content = `Values in LSE are classified by their syntactic form (first match wins):
|
|
3830
|
+
|
|
3831
|
+
| Type | Syntax | Example |
|
|
3832
|
+
|------|--------|---------|
|
|
3833
|
+
| Selector | Starts with \`#\` \`.\` \`[\` \`@\` \`*\` | \`#button\`, \`.active\`, \`[data-id]\` |
|
|
3834
|
+
| String | Quoted with \`"\` or \`'\` | \`"hello world"\`, \`'json'\` |
|
|
3835
|
+
| Boolean | Exact: \`true\` / \`false\` | \`visible:true\` |
|
|
3836
|
+
| Reference | Built-in name | \`me\`, \`you\`, \`it\`, \`result\`, \`event\`, \`target\`, \`body\` |
|
|
3837
|
+
| Duration | Number + suffix | \`500ms\`, \`2s\`, \`1m\`, \`1h\` |
|
|
3838
|
+
| Number | Digits with optional decimal | \`5\`, \`3.14\`, \`-1\` |
|
|
3839
|
+
| Plain | Fallback (any non-whitespace) | \`/api/users\`, \`json\`, \`production\` |
|
|
3840
|
+
|
|
3841
|
+
**Important**: Classification is by prefix, not by intent. \`#true\` is a selector (not boolean). \`event\` as a value is a reference (not plain text).`;
|
|
3842
|
+
return {
|
|
3843
|
+
id: "value-types",
|
|
3844
|
+
title: "Value Types",
|
|
3845
|
+
content,
|
|
3846
|
+
approximateTokens: estimateTokens(content)
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
function buildCommandsSection(schemas, outputFormat, examplesPerCommand) {
|
|
3850
|
+
const parts = [];
|
|
3851
|
+
for (const schema of schemas) {
|
|
3852
|
+
parts.push(formatCommandDoc(schema, outputFormat, examplesPerCommand));
|
|
3853
|
+
}
|
|
3854
|
+
const content = parts.join("\n\n");
|
|
3855
|
+
return {
|
|
3856
|
+
id: "commands",
|
|
3857
|
+
title: "Available Commands",
|
|
3858
|
+
content,
|
|
3859
|
+
approximateTokens: estimateTokens(content)
|
|
3860
|
+
};
|
|
3861
|
+
}
|
|
3862
|
+
function buildOutputFormatSection(outputFormat) {
|
|
3863
|
+
let content;
|
|
3864
|
+
if (outputFormat === "explicit") {
|
|
3865
|
+
content = `Output valid LSE bracket syntax. Each command must be wrapped in brackets:
|
|
3866
|
+
|
|
3867
|
+
\`\`\`
|
|
3868
|
+
[command role:value ...]
|
|
3869
|
+
\`\`\`
|
|
3870
|
+
|
|
3871
|
+
Do NOT output JSON. Only use bracket syntax.`;
|
|
3872
|
+
} else if (outputFormat === "json") {
|
|
3873
|
+
content = `Output valid JSON in the LLM-simplified format:
|
|
3874
|
+
|
|
3875
|
+
\`\`\`json
|
|
3876
|
+
{
|
|
3877
|
+
"action": "command-name",
|
|
3878
|
+
"roles": {
|
|
3879
|
+
"roleName": { "type": "valueType", "value": "theValue" }
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
\`\`\`
|
|
3883
|
+
|
|
3884
|
+
Valid value types: \`selector\`, \`literal\`, \`reference\`, \`expression\`.
|
|
3885
|
+
Do NOT output bracket syntax. Only use JSON.`;
|
|
3886
|
+
} else {
|
|
3887
|
+
content = `You may output EITHER format:
|
|
3888
|
+
|
|
3889
|
+
**Bracket syntax** (preferred for single commands):
|
|
3890
|
+
\`\`\`
|
|
3891
|
+
[command role:value ...]
|
|
3892
|
+
\`\`\`
|
|
3893
|
+
|
|
3894
|
+
**JSON format** (preferred when structured data is needed):
|
|
3895
|
+
\`\`\`json
|
|
3896
|
+
{
|
|
3897
|
+
"action": "command-name",
|
|
3898
|
+
"roles": {
|
|
3899
|
+
"roleName": { "type": "valueType", "value": "theValue" }
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
\`\`\`
|
|
3903
|
+
|
|
3904
|
+
Both formats are equally valid. Use whichever is more natural for the context.`;
|
|
3905
|
+
}
|
|
3906
|
+
return {
|
|
3907
|
+
id: "output-format",
|
|
3908
|
+
title: "Output Format",
|
|
3909
|
+
content,
|
|
3910
|
+
approximateTokens: estimateTokens(content)
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
function buildErrorRecoverySection() {
|
|
3914
|
+
const content = `If you're unsure about a command or role:
|
|
3915
|
+
|
|
3916
|
+
1. **Unknown command**: Use the closest matching command from the Available Commands section. Do not invent commands.
|
|
3917
|
+
2. **Unknown role**: Use only the roles listed for each command. Do not add roles not in the schema.
|
|
3918
|
+
3. **Ambiguous value type**: When in doubt, use \`expression\` type (it's the most permissive).
|
|
3919
|
+
4. **Missing required role**: Always include all required roles. Check the Required/Optional labels.
|
|
3920
|
+
5. **Selector vs plain value**: If a value starts with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`, it's a selector. URLs like \`/api/data\` are plain values.
|
|
3921
|
+
|
|
3922
|
+
When generating LSE, prefer explicit role labeling over positional guessing. Every value should have a role name.`;
|
|
3923
|
+
return {
|
|
3924
|
+
id: "error-recovery",
|
|
3925
|
+
title: "Error Recovery",
|
|
3926
|
+
content,
|
|
3927
|
+
approximateTokens: estimateTokens(content)
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
function formatCommandDoc(schema, outputFormat, examplesPerCommand) {
|
|
3931
|
+
const lines = [];
|
|
3932
|
+
lines.push(`### \`${schema.action}\` \u2014 ${schema.description}`);
|
|
3933
|
+
lines.push("");
|
|
3934
|
+
const required = schema.roles.filter((r) => r.required);
|
|
3935
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
3936
|
+
if (required.length > 0) {
|
|
3937
|
+
lines.push("**Required roles:**");
|
|
3938
|
+
for (const role of required) {
|
|
3939
|
+
lines.push(`- \`${role.role}\`: ${role.description} (type: ${formatExpectedTypes(role)})`);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
if (optional.length > 0) {
|
|
3943
|
+
lines.push("**Optional roles:**");
|
|
3944
|
+
for (const role of optional) {
|
|
3945
|
+
lines.push(`- \`${role.role}\`: ${role.description} (type: ${formatExpectedTypes(role)})`);
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
const examples = generateExamples(schema, examplesPerCommand);
|
|
3949
|
+
if (examples.length > 0) {
|
|
3950
|
+
lines.push("");
|
|
3951
|
+
if (outputFormat === "explicit" || outputFormat === "both") {
|
|
3952
|
+
lines.push("**Bracket syntax:**");
|
|
3953
|
+
for (const ex of examples) {
|
|
3954
|
+
lines.push(`\`\`\`
|
|
3955
|
+
${ex.explicit}
|
|
3956
|
+
\`\`\``);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
if (outputFormat === "json" || outputFormat === "both") {
|
|
3960
|
+
lines.push("**JSON format:**");
|
|
3961
|
+
for (const ex of examples) {
|
|
3962
|
+
lines.push(`\`\`\`json
|
|
3963
|
+
${JSON.stringify(ex.json, null, 2)}
|
|
3964
|
+
\`\`\``);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
return lines.join("\n");
|
|
3969
|
+
}
|
|
3970
|
+
function formatExpectedTypes(role) {
|
|
3971
|
+
return role.expectedTypes.join(" | ");
|
|
3972
|
+
}
|
|
3973
|
+
function generateRoleCombinations(schema) {
|
|
3974
|
+
const required = schema.roles.filter((r) => r.required);
|
|
3975
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
3976
|
+
const combos = [];
|
|
3977
|
+
if (required.length > 0) {
|
|
3978
|
+
combos.push(required);
|
|
3979
|
+
}
|
|
3980
|
+
for (const opt of optional) {
|
|
3981
|
+
combos.push([...required, opt]);
|
|
3982
|
+
}
|
|
3983
|
+
if (optional.length >= 2) {
|
|
3984
|
+
combos.push([...required, ...optional]);
|
|
3985
|
+
}
|
|
3986
|
+
if (combos.length === 0) {
|
|
3987
|
+
combos.push(schema.roles.slice());
|
|
3988
|
+
}
|
|
3989
|
+
return combos;
|
|
3990
|
+
}
|
|
3991
|
+
function sampleValue(role) {
|
|
3992
|
+
const primaryType = role.expectedTypes[0] || "expression";
|
|
3993
|
+
switch (primaryType) {
|
|
3994
|
+
case "selector":
|
|
3995
|
+
return sampleSelector(role.role);
|
|
3996
|
+
case "literal":
|
|
3997
|
+
return sampleLiteral(role.role);
|
|
3998
|
+
case "reference":
|
|
3999
|
+
return { type: "reference", value: "me" };
|
|
4000
|
+
case "expression":
|
|
4001
|
+
return sampleExpression(role.role);
|
|
4002
|
+
case "flag":
|
|
4003
|
+
return { type: "flag", value: true };
|
|
4004
|
+
default:
|
|
4005
|
+
return { type: "literal", value: "example" };
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
function sampleSelector(roleName) {
|
|
4009
|
+
const selectors = {
|
|
4010
|
+
patient: ".active",
|
|
4011
|
+
destination: "#output",
|
|
4012
|
+
source: "#input",
|
|
4013
|
+
target: "#target"
|
|
4014
|
+
};
|
|
4015
|
+
return { type: "selector", value: selectors[roleName] || `#${roleName}` };
|
|
4016
|
+
}
|
|
4017
|
+
function sampleLiteral(roleName) {
|
|
4018
|
+
const literals = {
|
|
4019
|
+
duration: "30s",
|
|
4020
|
+
interval: "5s",
|
|
4021
|
+
delay: "500ms",
|
|
4022
|
+
timeout: "10s",
|
|
4023
|
+
quantity: 5,
|
|
4024
|
+
limit: 10,
|
|
4025
|
+
style: "json",
|
|
4026
|
+
manner: "rolling",
|
|
4027
|
+
format: "html"
|
|
4028
|
+
};
|
|
4029
|
+
return { type: "literal", value: literals[roleName] || roleName };
|
|
4030
|
+
}
|
|
4031
|
+
function sampleExpression(roleName) {
|
|
4032
|
+
const expressions = {
|
|
4033
|
+
source: "/api/data",
|
|
4034
|
+
destination: "#output",
|
|
4035
|
+
patient: ".active",
|
|
4036
|
+
instrument: "toUpperCase",
|
|
4037
|
+
condition: "age > 18",
|
|
4038
|
+
style: "json",
|
|
4039
|
+
duration: "5s",
|
|
4040
|
+
url: "/api/users"
|
|
4041
|
+
};
|
|
4042
|
+
return { type: "expression", value: expressions[roleName] || `/api/${roleName}` };
|
|
4043
|
+
}
|
|
4044
|
+
function toSemanticValue(sample) {
|
|
4045
|
+
switch (sample.type) {
|
|
4046
|
+
case "selector":
|
|
4047
|
+
return {
|
|
4048
|
+
type: "selector",
|
|
4049
|
+
value: String(sample.value),
|
|
4050
|
+
selectorKind: detectSelectorKind2(String(sample.value))
|
|
4051
|
+
};
|
|
4052
|
+
case "literal":
|
|
2108
4053
|
return {
|
|
2109
|
-
|
|
2110
|
-
|
|
4054
|
+
type: "literal",
|
|
4055
|
+
value: sample.value,
|
|
4056
|
+
dataType: typeof sample.value === "number" ? "number" : typeof sample.value === "boolean" ? "boolean" : "string"
|
|
2111
4057
|
};
|
|
4058
|
+
case "reference":
|
|
4059
|
+
return { type: "reference", value: String(sample.value) };
|
|
4060
|
+
case "expression":
|
|
4061
|
+
return { type: "expression", value: String(sample.value), raw: String(sample.value) };
|
|
4062
|
+
case "flag":
|
|
4063
|
+
return { type: "flag", value: sample.value };
|
|
4064
|
+
default:
|
|
4065
|
+
return { type: "literal", value: String(sample.value), dataType: "string" };
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
function detectSelectorKind2(selector) {
|
|
4069
|
+
if (selector.startsWith("#")) return "id";
|
|
4070
|
+
if (selector.startsWith(".")) return "class";
|
|
4071
|
+
if (selector.startsWith("[")) return "attribute";
|
|
4072
|
+
return "complex";
|
|
4073
|
+
}
|
|
4074
|
+
function estimateTokens(text) {
|
|
4075
|
+
return Math.ceil(text.length / 4);
|
|
4076
|
+
}
|
|
4077
|
+
function truncateToTokenBudget(sections, maxTokens) {
|
|
4078
|
+
const result = [];
|
|
4079
|
+
let budget = maxTokens;
|
|
4080
|
+
for (const section of sections) {
|
|
4081
|
+
if (section.approximateTokens <= budget) {
|
|
4082
|
+
result.push(section);
|
|
4083
|
+
budget -= section.approximateTokens;
|
|
4084
|
+
} else {
|
|
4085
|
+
const charBudget = Math.max(budget * 4, 40);
|
|
4086
|
+
const truncatedContent = section.content.slice(0, charBudget) + "\n\n*(truncated)*";
|
|
4087
|
+
result.push({
|
|
4088
|
+
...section,
|
|
4089
|
+
content: truncatedContent,
|
|
4090
|
+
approximateTokens: estimateTokens(truncatedContent)
|
|
4091
|
+
});
|
|
4092
|
+
break;
|
|
2112
4093
|
}
|
|
2113
4094
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
4095
|
+
return result;
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
// src/training/schema-synthesizer.ts
|
|
4099
|
+
function synthesizeFromSchemas(schemas, config) {
|
|
4100
|
+
const maxPerCommand = config.maxPairsPerCommand ?? 5;
|
|
4101
|
+
const minQuality = config.minQuality ?? 0.5;
|
|
4102
|
+
const shuffleRoles = config.shuffleRoles ?? true;
|
|
4103
|
+
const languages = config.languages ?? ["en"];
|
|
4104
|
+
const pairs = [];
|
|
4105
|
+
let idCounter = 0;
|
|
4106
|
+
for (const schema of schemas) {
|
|
4107
|
+
const combos = generateRoleCombinations2(schema);
|
|
4108
|
+
for (const language of languages) {
|
|
4109
|
+
let commandPairCount = 0;
|
|
4110
|
+
for (const combo of combos) {
|
|
4111
|
+
if (commandPairCount >= maxPerCommand) break;
|
|
4112
|
+
const pair = buildTrainingPair(
|
|
4113
|
+
schema,
|
|
4114
|
+
combo,
|
|
4115
|
+
language,
|
|
4116
|
+
config.domain,
|
|
4117
|
+
`${config.domain}-synth-${idCounter++}`
|
|
4118
|
+
);
|
|
4119
|
+
if (pair.quality >= minQuality) {
|
|
4120
|
+
pairs.push(pair);
|
|
4121
|
+
commandPairCount++;
|
|
4122
|
+
}
|
|
4123
|
+
if (shuffleRoles && combo.length >= 2 && commandPairCount < maxPerCommand) {
|
|
4124
|
+
const shuffled = [...combo].reverse();
|
|
4125
|
+
const shuffledPair = buildTrainingPair(
|
|
4126
|
+
schema,
|
|
4127
|
+
shuffled,
|
|
4128
|
+
language,
|
|
4129
|
+
config.domain,
|
|
4130
|
+
`${config.domain}-synth-${idCounter++}`
|
|
4131
|
+
);
|
|
4132
|
+
if (shuffledPair.quality >= minQuality) {
|
|
4133
|
+
pairs.push(shuffledPair);
|
|
4134
|
+
commandPairCount++;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
2116
4139
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
4140
|
+
const metadata = {
|
|
4141
|
+
domain: config.domain,
|
|
4142
|
+
commandCount: schemas.length,
|
|
4143
|
+
pairCount: pairs.length,
|
|
4144
|
+
bySource: { synthetic: pairs.length },
|
|
4145
|
+
languages
|
|
4146
|
+
};
|
|
4147
|
+
return { pairs, metadata };
|
|
4148
|
+
}
|
|
4149
|
+
function generateRoleCombinations2(schema) {
|
|
4150
|
+
const required = schema.roles.filter((r) => r.required);
|
|
4151
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
4152
|
+
const combos = [];
|
|
4153
|
+
if (required.length > 0) {
|
|
4154
|
+
combos.push(required);
|
|
4155
|
+
}
|
|
4156
|
+
for (const opt of optional) {
|
|
4157
|
+
combos.push([...required, opt]);
|
|
4158
|
+
}
|
|
4159
|
+
if (optional.length >= 2) {
|
|
4160
|
+
combos.push([...required, ...optional]);
|
|
4161
|
+
}
|
|
4162
|
+
if (combos.length === 0) {
|
|
4163
|
+
combos.push(schema.roles.slice());
|
|
4164
|
+
}
|
|
4165
|
+
return combos;
|
|
4166
|
+
}
|
|
4167
|
+
function buildTrainingPair(schema, roles, language, domain, id) {
|
|
4168
|
+
const sampledRoles = /* @__PURE__ */ new Map();
|
|
4169
|
+
for (const role of roles) {
|
|
4170
|
+
sampledRoles.set(role.role, sampleValue2(role));
|
|
4171
|
+
}
|
|
4172
|
+
const nodeRoles = /* @__PURE__ */ new Map();
|
|
4173
|
+
for (const [name, sample] of sampledRoles) {
|
|
4174
|
+
nodeRoles.set(name, toSemanticValue2(sample));
|
|
4175
|
+
}
|
|
4176
|
+
const node = createCommandNode(schema.action, nodeRoles);
|
|
4177
|
+
const explicit = renderExplicit(node);
|
|
4178
|
+
const jsonRoles = {};
|
|
4179
|
+
for (const [name, sample] of sampledRoles) {
|
|
4180
|
+
jsonRoles[name] = sample;
|
|
4181
|
+
}
|
|
4182
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
4183
|
+
const natural = buildNaturalForm(schema, sampledRoles, language);
|
|
4184
|
+
const requiredCount = schema.roles.filter((r) => r.required).length;
|
|
4185
|
+
const filledRequired = roles.filter((r) => r.required).length;
|
|
4186
|
+
const quality = requiredCount > 0 ? filledRequired / requiredCount : 0.8;
|
|
4187
|
+
return {
|
|
4188
|
+
id,
|
|
4189
|
+
natural,
|
|
4190
|
+
language,
|
|
4191
|
+
explicit,
|
|
4192
|
+
json,
|
|
4193
|
+
domain,
|
|
4194
|
+
action: schema.action,
|
|
4195
|
+
source: "synthetic",
|
|
4196
|
+
quality
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
function buildNaturalForm(schema, roles, language) {
|
|
4200
|
+
const parts = [schema.action];
|
|
4201
|
+
for (const roleSpec of schema.roles) {
|
|
4202
|
+
const sample = roles.get(roleSpec.role);
|
|
4203
|
+
if (!sample) continue;
|
|
4204
|
+
const marker = roleSpec.markerOverride?.[language];
|
|
4205
|
+
if (marker) {
|
|
4206
|
+
parts.push(marker);
|
|
2123
4207
|
}
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
4208
|
+
parts.push(String(sample.value));
|
|
4209
|
+
}
|
|
4210
|
+
return parts.join(" ");
|
|
4211
|
+
}
|
|
4212
|
+
function sampleValue2(role) {
|
|
4213
|
+
const primaryType = role.expectedTypes[0] || "expression";
|
|
4214
|
+
switch (primaryType) {
|
|
4215
|
+
case "selector":
|
|
4216
|
+
return sampleSelector2(role.role);
|
|
4217
|
+
case "literal":
|
|
4218
|
+
return sampleLiteral2(role.role);
|
|
4219
|
+
case "reference":
|
|
4220
|
+
return { type: "reference", value: "me" };
|
|
4221
|
+
case "expression":
|
|
4222
|
+
return sampleExpression2(role.role);
|
|
4223
|
+
case "flag":
|
|
4224
|
+
return { type: "flag", value: true };
|
|
4225
|
+
default:
|
|
4226
|
+
return { type: "literal", value: "example" };
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
function sampleSelector2(roleName) {
|
|
4230
|
+
const selectors = {
|
|
4231
|
+
patient: ".active",
|
|
4232
|
+
destination: "#output",
|
|
4233
|
+
source: "#input",
|
|
4234
|
+
target: "#target"
|
|
4235
|
+
};
|
|
4236
|
+
return { type: "selector", value: selectors[roleName] || `#${roleName}` };
|
|
4237
|
+
}
|
|
4238
|
+
function sampleLiteral2(roleName) {
|
|
4239
|
+
const literals = {
|
|
4240
|
+
duration: "30s",
|
|
4241
|
+
interval: "5s",
|
|
4242
|
+
delay: "500ms",
|
|
4243
|
+
timeout: "10s",
|
|
4244
|
+
quantity: 5,
|
|
4245
|
+
limit: 10,
|
|
4246
|
+
style: "json",
|
|
4247
|
+
manner: "rolling",
|
|
4248
|
+
format: "html"
|
|
4249
|
+
};
|
|
4250
|
+
return { type: "literal", value: literals[roleName] || roleName };
|
|
4251
|
+
}
|
|
4252
|
+
function sampleExpression2(roleName) {
|
|
4253
|
+
const expressions = {
|
|
4254
|
+
source: "/api/data",
|
|
4255
|
+
destination: "#output",
|
|
4256
|
+
patient: ".active",
|
|
4257
|
+
instrument: "toUpperCase",
|
|
4258
|
+
condition: "age > 18",
|
|
4259
|
+
style: "json",
|
|
4260
|
+
duration: "5s",
|
|
4261
|
+
url: "/api/users"
|
|
4262
|
+
};
|
|
4263
|
+
return { type: "expression", value: expressions[roleName] || `/api/${roleName}` };
|
|
4264
|
+
}
|
|
4265
|
+
function toSemanticValue2(sample) {
|
|
4266
|
+
switch (sample.type) {
|
|
4267
|
+
case "selector":
|
|
2127
4268
|
return {
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
metadata: {
|
|
2132
|
-
parser: "semantic",
|
|
2133
|
-
confidence: result.confidence
|
|
2134
|
-
}
|
|
4269
|
+
type: "selector",
|
|
4270
|
+
value: String(sample.value),
|
|
4271
|
+
selectorKind: detectSelectorKind3(String(sample.value))
|
|
2135
4272
|
};
|
|
2136
|
-
|
|
4273
|
+
case "literal":
|
|
2137
4274
|
return {
|
|
2138
|
-
|
|
2139
|
-
|
|
4275
|
+
type: "literal",
|
|
4276
|
+
value: sample.value,
|
|
4277
|
+
dataType: typeof sample.value === "number" ? "number" : typeof sample.value === "boolean" ? "boolean" : "string"
|
|
2140
4278
|
};
|
|
4279
|
+
case "reference":
|
|
4280
|
+
return { type: "reference", value: String(sample.value) };
|
|
4281
|
+
case "expression":
|
|
4282
|
+
return { type: "expression", value: String(sample.value), raw: String(sample.value) };
|
|
4283
|
+
case "flag":
|
|
4284
|
+
return { type: "flag", value: sample.value };
|
|
4285
|
+
default:
|
|
4286
|
+
return { type: "literal", value: String(sample.value), dataType: "string" };
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
function detectSelectorKind3(selector) {
|
|
4290
|
+
if (selector.startsWith("#")) return "id";
|
|
4291
|
+
if (selector.startsWith(".")) return "class";
|
|
4292
|
+
if (selector.startsWith("[")) return "attribute";
|
|
4293
|
+
return "complex";
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
// src/feedback/feedback-formatter.ts
|
|
4297
|
+
function buildFeedback(input, inputFormat, diagnostics, schemaLookup, action) {
|
|
4298
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
4299
|
+
const feedbackDiagnostics = diagnostics.map((d) => toFeedbackDiagnostic(d));
|
|
4300
|
+
const hints = generateHints(feedbackDiagnostics, schemaLookup, action);
|
|
4301
|
+
let schemaInfo;
|
|
4302
|
+
let correctedExample;
|
|
4303
|
+
if (action && schemaLookup) {
|
|
4304
|
+
const schema = schemaLookup.getSchema(action);
|
|
4305
|
+
if (schema) {
|
|
4306
|
+
schemaInfo = extractSchemaInfo(schema);
|
|
4307
|
+
if (hasErrors) {
|
|
4308
|
+
correctedExample = generateCorrectedExample(schema);
|
|
4309
|
+
}
|
|
2141
4310
|
}
|
|
2142
4311
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
4312
|
+
return {
|
|
4313
|
+
accepted: !hasErrors,
|
|
4314
|
+
input: { format: inputFormat, text: input },
|
|
4315
|
+
diagnostics: feedbackDiagnostics,
|
|
4316
|
+
hints,
|
|
4317
|
+
...schemaInfo && { schema: schemaInfo },
|
|
4318
|
+
...correctedExample && { correctedExample }
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
function toFeedbackDiagnostic(d) {
|
|
4322
|
+
const fixType = classifyFixType(d);
|
|
4323
|
+
return {
|
|
4324
|
+
severity: d.severity,
|
|
4325
|
+
code: d.code || "unknown",
|
|
4326
|
+
message: d.message,
|
|
4327
|
+
...d.suggestions?.[0] && { suggestion: d.suggestions[0] },
|
|
4328
|
+
...fixType && { fixType }
|
|
4329
|
+
};
|
|
4330
|
+
}
|
|
4331
|
+
function classifyFixType(d) {
|
|
4332
|
+
const code = (d.code || "").toLowerCase();
|
|
4333
|
+
const msg = d.message.toLowerCase();
|
|
4334
|
+
if (code.includes("missing-role") || code.includes("missing_role") || msg.includes("required role") || msg.includes("missing role")) {
|
|
4335
|
+
return "missing_role";
|
|
2145
4336
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
4337
|
+
if (code.includes("invalid-type") || code.includes("invalid_type") || code.includes("type-mismatch") || msg.includes("invalid type") || msg.includes("expected type")) {
|
|
4338
|
+
return "invalid_type";
|
|
4339
|
+
}
|
|
4340
|
+
if (code.includes("unknown-command") || code.includes("unknown_command") || msg.includes("unknown command") || msg.includes("not recognized") && msg.includes("command")) {
|
|
4341
|
+
return "unknown_command";
|
|
4342
|
+
}
|
|
4343
|
+
if (code.includes("unknown-role") || code.includes("unknown_role") || msg.includes("unknown role") || msg.includes("not recognized") && msg.includes("role")) {
|
|
4344
|
+
return "unknown_role";
|
|
4345
|
+
}
|
|
4346
|
+
if (code.includes("parse") || code.includes("syntax") || msg.includes("syntax error") || msg.includes("parse error") || msg.includes("unexpected")) {
|
|
4347
|
+
return "syntax_error";
|
|
4348
|
+
}
|
|
4349
|
+
return void 0;
|
|
4350
|
+
}
|
|
4351
|
+
function generateHints(diagnostics, schemaLookup, action) {
|
|
4352
|
+
const hints = [];
|
|
4353
|
+
const schema = action && schemaLookup ? schemaLookup.getSchema(action) : void 0;
|
|
4354
|
+
for (const d of diagnostics) {
|
|
4355
|
+
if (d.severity !== "error") continue;
|
|
4356
|
+
switch (d.fixType) {
|
|
4357
|
+
case "missing_role": {
|
|
4358
|
+
if (schema) {
|
|
4359
|
+
const required = schema.roles.filter((r) => r.required).map((r) => `'${r.role}' (${r.description}, type: ${r.expectedTypes.join("|")})`).join(", ");
|
|
4360
|
+
hints.push(`Required roles for '${action}': ${required}`);
|
|
4361
|
+
} else {
|
|
4362
|
+
hints.push("Check that all required roles are present.");
|
|
4363
|
+
}
|
|
4364
|
+
break;
|
|
4365
|
+
}
|
|
4366
|
+
case "unknown_command": {
|
|
4367
|
+
hints.push(
|
|
4368
|
+
`Command not recognized. Ensure you're using a valid command from the domain schema.`
|
|
4369
|
+
);
|
|
4370
|
+
break;
|
|
4371
|
+
}
|
|
4372
|
+
case "unknown_role": {
|
|
4373
|
+
if (schema) {
|
|
4374
|
+
const validRoles = schema.roles.map((r) => `'${r.role}'`).join(", ");
|
|
4375
|
+
hints.push(`Valid roles for '${action}': ${validRoles}`);
|
|
4376
|
+
} else {
|
|
4377
|
+
hints.push("Use only roles defined in the command schema.");
|
|
4378
|
+
}
|
|
4379
|
+
break;
|
|
4380
|
+
}
|
|
4381
|
+
case "invalid_type": {
|
|
4382
|
+
hints.push(
|
|
4383
|
+
"Check value types: selectors start with #.[@*, strings use quotes, numbers are plain digits."
|
|
4384
|
+
);
|
|
4385
|
+
break;
|
|
4386
|
+
}
|
|
4387
|
+
case "syntax_error": {
|
|
4388
|
+
hints.push("Ensure bracket syntax: [command role:value ...]. No spaces around the colon.");
|
|
4389
|
+
break;
|
|
4390
|
+
}
|
|
4391
|
+
default: {
|
|
4392
|
+
if (d.suggestion) {
|
|
4393
|
+
hints.push(d.suggestion);
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
2153
4396
|
}
|
|
2154
|
-
translations[lang.code] = keywords;
|
|
2155
4397
|
}
|
|
2156
|
-
return new
|
|
4398
|
+
return [...new Set(hints)];
|
|
2157
4399
|
}
|
|
2158
|
-
function
|
|
2159
|
-
const
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
4400
|
+
function extractSchemaInfo(schema) {
|
|
4401
|
+
const requiredRoles = [];
|
|
4402
|
+
const optionalRoles = [];
|
|
4403
|
+
const roleDescriptions = {};
|
|
4404
|
+
for (const role of schema.roles) {
|
|
4405
|
+
if (role.required) {
|
|
4406
|
+
requiredRoles.push(role.role);
|
|
4407
|
+
} else {
|
|
4408
|
+
optionalRoles.push(role.role);
|
|
2163
4409
|
}
|
|
4410
|
+
roleDescriptions[role.role] = role.description;
|
|
2164
4411
|
}
|
|
2165
|
-
return
|
|
4412
|
+
return {
|
|
4413
|
+
action: schema.action,
|
|
4414
|
+
requiredRoles,
|
|
4415
|
+
optionalRoles,
|
|
4416
|
+
roleDescriptions
|
|
4417
|
+
};
|
|
2166
4418
|
}
|
|
2167
|
-
function
|
|
2168
|
-
const
|
|
2169
|
-
const
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
4419
|
+
function generateCorrectedExample(schema) {
|
|
4420
|
+
const roles = /* @__PURE__ */ new Map();
|
|
4421
|
+
const jsonRoles = {};
|
|
4422
|
+
for (const role of schema.roles) {
|
|
4423
|
+
if (!role.required) continue;
|
|
4424
|
+
const sample = sampleDefaultValue(role.role, role.expectedTypes[0] || "expression");
|
|
4425
|
+
roles.set(role.role, toSemanticValue3(sample));
|
|
4426
|
+
jsonRoles[role.role] = sample;
|
|
4427
|
+
}
|
|
4428
|
+
const node = createCommandNode(schema.action, roles);
|
|
4429
|
+
const explicit = renderExplicit(node);
|
|
4430
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
4431
|
+
return { explicit, json };
|
|
4432
|
+
}
|
|
4433
|
+
function sampleDefaultValue(roleName, expectedType) {
|
|
4434
|
+
switch (expectedType) {
|
|
4435
|
+
case "selector":
|
|
4436
|
+
return { type: "selector", value: `#${roleName}` };
|
|
4437
|
+
case "expression":
|
|
4438
|
+
return { type: "expression", value: `<${roleName}-value>` };
|
|
4439
|
+
case "literal":
|
|
4440
|
+
return { type: "literal", value: `<${roleName}-value>` };
|
|
4441
|
+
case "reference":
|
|
4442
|
+
return { type: "reference", value: "me" };
|
|
4443
|
+
default:
|
|
4444
|
+
return { type: "expression", value: `<${roleName}-value>` };
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
function toSemanticValue3(sample) {
|
|
4448
|
+
switch (sample.type) {
|
|
4449
|
+
case "selector":
|
|
4450
|
+
return { type: "selector", value: sample.value, selectorKind: "id" };
|
|
4451
|
+
case "expression":
|
|
4452
|
+
return { type: "expression", value: sample.value, raw: sample.value };
|
|
4453
|
+
case "literal":
|
|
4454
|
+
return { type: "literal", value: sample.value, dataType: "string" };
|
|
4455
|
+
case "reference":
|
|
4456
|
+
return { type: "reference", value: sample.value };
|
|
4457
|
+
default:
|
|
4458
|
+
return { type: "literal", value: sample.value, dataType: "string" };
|
|
4459
|
+
}
|
|
2176
4460
|
}
|
|
2177
4461
|
|
|
2178
4462
|
// src/api/domain-registry.ts
|
|
@@ -2192,6 +4476,17 @@ var DomainRegistry = class {
|
|
|
2192
4476
|
}
|
|
2193
4477
|
this.descriptors.set(descriptor.name, descriptor);
|
|
2194
4478
|
}
|
|
4479
|
+
/**
|
|
4480
|
+
* Attach schemas to an already-registered domain.
|
|
4481
|
+
* Useful for lazy loading schemas after initial registration.
|
|
4482
|
+
*/
|
|
4483
|
+
setSchemas(name, schemas) {
|
|
4484
|
+
const existing = this.descriptors.get(name);
|
|
4485
|
+
if (!existing) {
|
|
4486
|
+
throw new Error(`Domain not registered: ${name}`);
|
|
4487
|
+
}
|
|
4488
|
+
this.descriptors.set(name, { ...existing, schemas });
|
|
4489
|
+
}
|
|
2195
4490
|
/**
|
|
2196
4491
|
* Get all registered domain names.
|
|
2197
4492
|
*/
|
|
@@ -2261,6 +4556,71 @@ var DomainRegistry = class {
|
|
|
2261
4556
|
return this.getDSL(descriptor);
|
|
2262
4557
|
}
|
|
2263
4558
|
// ---------------------------------------------------------------------------
|
|
4559
|
+
// LLM ↔ LSE Convenience Methods
|
|
4560
|
+
// ---------------------------------------------------------------------------
|
|
4561
|
+
/**
|
|
4562
|
+
* Generate an LLM system prompt for a domain from its schemas.
|
|
4563
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
4564
|
+
*/
|
|
4565
|
+
generatePrompt(domain, config) {
|
|
4566
|
+
const descriptor = this.descriptors.get(domain);
|
|
4567
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
4568
|
+
return generatePrompt({
|
|
4569
|
+
domain,
|
|
4570
|
+
description: descriptor.description,
|
|
4571
|
+
schemas: descriptor.schemas,
|
|
4572
|
+
...config
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
/**
|
|
4576
|
+
* Synthesize training data for a domain from its schemas.
|
|
4577
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
4578
|
+
*/
|
|
4579
|
+
generateTrainingData(domain, config) {
|
|
4580
|
+
const descriptor = this.descriptors.get(domain);
|
|
4581
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
4582
|
+
return synthesizeFromSchemas(descriptor.schemas, {
|
|
4583
|
+
domain,
|
|
4584
|
+
...config
|
|
4585
|
+
});
|
|
4586
|
+
}
|
|
4587
|
+
/**
|
|
4588
|
+
* Build structured feedback for a failed LSE input in a domain.
|
|
4589
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
4590
|
+
*/
|
|
4591
|
+
buildFeedback(domain, input, inputFormat, diagnostics) {
|
|
4592
|
+
const descriptor = this.descriptors.get(domain);
|
|
4593
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
4594
|
+
const schemaLookup = this.createSchemaLookup(descriptor.schemas);
|
|
4595
|
+
let action;
|
|
4596
|
+
try {
|
|
4597
|
+
if (inputFormat === "explicit") {
|
|
4598
|
+
const node = parseExplicit(input, { schemaLookup });
|
|
4599
|
+
action = node.action;
|
|
4600
|
+
}
|
|
4601
|
+
} catch {
|
|
4602
|
+
}
|
|
4603
|
+
return buildFeedback(input, inputFormat, diagnostics, schemaLookup, action);
|
|
4604
|
+
}
|
|
4605
|
+
/**
|
|
4606
|
+
* Get schemas for a domain (if available).
|
|
4607
|
+
*/
|
|
4608
|
+
getSchemas(domain) {
|
|
4609
|
+
const descriptor = this.descriptors.get(domain);
|
|
4610
|
+
if (!descriptor?.schemas) return null;
|
|
4611
|
+
return descriptor.schemas;
|
|
4612
|
+
}
|
|
4613
|
+
/**
|
|
4614
|
+
* Create a SchemaLookup from a domain's schemas.
|
|
4615
|
+
*/
|
|
4616
|
+
createSchemaLookup(schemas) {
|
|
4617
|
+
const map = /* @__PURE__ */ new Map();
|
|
4618
|
+
for (const s of schemas) {
|
|
4619
|
+
map.set(s.action, s);
|
|
4620
|
+
}
|
|
4621
|
+
return { getSchema: (action) => map.get(action) };
|
|
4622
|
+
}
|
|
4623
|
+
// ---------------------------------------------------------------------------
|
|
2264
4624
|
// Private: DSL/renderer lifecycle
|
|
2265
4625
|
// ---------------------------------------------------------------------------
|
|
2266
4626
|
async getDSL(descriptor) {
|
|
@@ -2360,7 +4720,7 @@ function generateToolDefinitions(desc) {
|
|
|
2360
4720
|
case "parse":
|
|
2361
4721
|
tools.push({
|
|
2362
4722
|
name: `parse_${desc.name}`,
|
|
2363
|
-
description: `Parse a ${desc.description} input into a semantic representation. Supports: ${langList}.`,
|
|
4723
|
+
description: `Parse a ${desc.description} input into a semantic representation (action, roles, confidence). Supports: ${langList}.`,
|
|
2364
4724
|
inputSchema: {
|
|
2365
4725
|
type: "object",
|
|
2366
4726
|
properties: {
|
|
@@ -2381,7 +4741,7 @@ function generateToolDefinitions(desc) {
|
|
|
2381
4741
|
case "compile":
|
|
2382
4742
|
tools.push({
|
|
2383
4743
|
name: `compile_${desc.name}`,
|
|
2384
|
-
description: `Compile a ${desc.description} input to target code. Supports: ${langList}.`,
|
|
4744
|
+
description: `Compile a ${desc.description} input to ${desc.outputDescription ?? "target code"}. Supports: ${langList}.`,
|
|
2385
4745
|
inputSchema: {
|
|
2386
4746
|
type: "object",
|
|
2387
4747
|
properties: {
|
|
@@ -2402,7 +4762,7 @@ function generateToolDefinitions(desc) {
|
|
|
2402
4762
|
case "validate":
|
|
2403
4763
|
tools.push({
|
|
2404
4764
|
name: `validate_${desc.name}`,
|
|
2405
|
-
description: `
|
|
4765
|
+
description: `Check ${desc.description} syntax for errors. Returns {valid, errors[]}. Supports: ${langList}.`,
|
|
2406
4766
|
inputSchema: {
|
|
2407
4767
|
type: "object",
|
|
2408
4768
|
properties: {
|
|
@@ -2423,7 +4783,7 @@ function generateToolDefinitions(desc) {
|
|
|
2423
4783
|
case "translate":
|
|
2424
4784
|
tools.push({
|
|
2425
4785
|
name: `translate_${desc.name}`,
|
|
2426
|
-
description: `Translate ${desc.description} input between natural languages
|
|
4786
|
+
description: `Translate ${desc.description} input between natural languages (requires 'from' and 'to' codes). Supports: ${langList}.`,
|
|
2427
4787
|
inputSchema: {
|
|
2428
4788
|
type: "object",
|
|
2429
4789
|
properties: {
|
|
@@ -2489,9 +4849,9 @@ var CrossDomainDispatcher = class {
|
|
|
2489
4849
|
if (domainNames.length === 0) return null;
|
|
2490
4850
|
const candidates = [];
|
|
2491
4851
|
for (const name of domainNames) {
|
|
2492
|
-
const dsl = await this.registry.getDSLForDomain(name);
|
|
2493
|
-
if (!dsl) continue;
|
|
2494
4852
|
try {
|
|
4853
|
+
const dsl = await this.registry.getDSLForDomain(name);
|
|
4854
|
+
if (!dsl) continue;
|
|
2495
4855
|
const { node, confidence } = dsl.parseWithConfidence(input, language);
|
|
2496
4856
|
if (confidence >= this.minConfidence) {
|
|
2497
4857
|
candidates.push({ domain: name, node, confidence, dsl });
|
|
@@ -2500,10 +4860,16 @@ var CrossDomainDispatcher = class {
|
|
|
2500
4860
|
}
|
|
2501
4861
|
}
|
|
2502
4862
|
if (candidates.length === 0) return null;
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
4863
|
+
if (this.priority.length > 0) {
|
|
4864
|
+
candidates.sort((a, b) => {
|
|
4865
|
+
const pa = this.getPriorityIndex(a.domain);
|
|
4866
|
+
const pb = this.getPriorityIndex(b.domain);
|
|
4867
|
+
if (pa !== pb) return pa - pb;
|
|
4868
|
+
return b.confidence - a.confidence;
|
|
4869
|
+
});
|
|
4870
|
+
} else {
|
|
4871
|
+
candidates.sort((a, b) => b.confidence - a.confidence);
|
|
4872
|
+
}
|
|
2507
4873
|
return candidates[0];
|
|
2508
4874
|
}
|
|
2509
4875
|
/**
|
|
@@ -2547,6 +4913,40 @@ var CrossDomainDispatcher = class {
|
|
|
2547
4913
|
const result = detected.dsl.compile(input, language);
|
|
2548
4914
|
return { ...result, domain: detected.domain };
|
|
2549
4915
|
}
|
|
4916
|
+
/**
|
|
4917
|
+
* Compile multi-line input where each line may belong to a different domain.
|
|
4918
|
+
* Detects the domain for each line and compiles it, returning per-line results.
|
|
4919
|
+
* Empty lines and comment lines are skipped.
|
|
4920
|
+
*/
|
|
4921
|
+
async compileComposite(input, language = "en") {
|
|
4922
|
+
const lines = input.split("\n");
|
|
4923
|
+
const statements = [];
|
|
4924
|
+
const errors = [];
|
|
4925
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4926
|
+
const line = lines[i];
|
|
4927
|
+
if (isSkippableLine(line)) continue;
|
|
4928
|
+
const trimmed = line.trim();
|
|
4929
|
+
const detected = await this.detect(trimmed, language);
|
|
4930
|
+
if (detected) {
|
|
4931
|
+
const result = detected.dsl.compile(trimmed, language);
|
|
4932
|
+
statements.push({
|
|
4933
|
+
line: i + 1,
|
|
4934
|
+
input: trimmed,
|
|
4935
|
+
domain: detected.domain,
|
|
4936
|
+
ok: result.ok,
|
|
4937
|
+
code: result.code,
|
|
4938
|
+
errors: result.errors
|
|
4939
|
+
});
|
|
4940
|
+
} else {
|
|
4941
|
+
errors.push({
|
|
4942
|
+
line: i + 1,
|
|
4943
|
+
input: trimmed,
|
|
4944
|
+
message: "No domain matched this input"
|
|
4945
|
+
});
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
return { statements, errors };
|
|
4949
|
+
}
|
|
2550
4950
|
/**
|
|
2551
4951
|
* Validate input against all domains, returning the best match.
|
|
2552
4952
|
*/
|
|
@@ -2828,6 +5228,30 @@ ${body}
|
|
|
2828
5228
|
}
|
|
2829
5229
|
};
|
|
2830
5230
|
|
|
5231
|
+
// src/aot/registry-bridge.ts
|
|
5232
|
+
async function registryToAOTBackends(registry) {
|
|
5233
|
+
const backends = [];
|
|
5234
|
+
for (const name of registry.getDomainNames()) {
|
|
5235
|
+
const descriptor = registry.getDescriptor(name);
|
|
5236
|
+
if (!descriptor?.scanConfig) continue;
|
|
5237
|
+
const dsl = await registry.getDSLForDomain(name);
|
|
5238
|
+
if (!dsl) continue;
|
|
5239
|
+
const scanConfig = {
|
|
5240
|
+
domain: name,
|
|
5241
|
+
attributes: [...descriptor.scanConfig.attributes],
|
|
5242
|
+
...descriptor.scanConfig.scriptTypes ? { scriptTypes: [...descriptor.scanConfig.scriptTypes] } : {},
|
|
5243
|
+
defaultLanguage: descriptor.scanConfig.defaultLanguage ?? "en"
|
|
5244
|
+
};
|
|
5245
|
+
const codeGenerator = {
|
|
5246
|
+
generate(node) {
|
|
5247
|
+
return JSON.stringify({ action: node.action, roles: Object.fromEntries(node.roles) });
|
|
5248
|
+
}
|
|
5249
|
+
};
|
|
5250
|
+
backends.push({ domain: name, dsl, codeGenerator, scanConfig });
|
|
5251
|
+
}
|
|
5252
|
+
return backends;
|
|
5253
|
+
}
|
|
5254
|
+
|
|
2831
5255
|
// src/schema/command-schema.ts
|
|
2832
5256
|
function defineCommand(schema) {
|
|
2833
5257
|
return {
|
|
@@ -4119,6 +6543,163 @@ function normalized(stem, confidence, metadata) {
|
|
|
4119
6543
|
return { stem, confidence };
|
|
4120
6544
|
}
|
|
4121
6545
|
|
|
6546
|
+
// src/training/jsonl-writer.ts
|
|
6547
|
+
function toJSONL(pairs) {
|
|
6548
|
+
return pairs.map((pair) => JSON.stringify(toJSONLRow(pair))).join("\n");
|
|
6549
|
+
}
|
|
6550
|
+
function toJSONLRow(pair) {
|
|
6551
|
+
return {
|
|
6552
|
+
id: pair.id,
|
|
6553
|
+
prompt: pair.natural,
|
|
6554
|
+
completion: pair.explicit,
|
|
6555
|
+
metadata: {
|
|
6556
|
+
domain: pair.domain,
|
|
6557
|
+
action: pair.action,
|
|
6558
|
+
language: pair.language,
|
|
6559
|
+
source: pair.source,
|
|
6560
|
+
quality: pair.quality,
|
|
6561
|
+
...pair.confidence !== void 0 && { confidence: pair.confidence }
|
|
6562
|
+
}
|
|
6563
|
+
};
|
|
6564
|
+
}
|
|
6565
|
+
function parseJSONL(text) {
|
|
6566
|
+
return text.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
6567
|
+
}
|
|
6568
|
+
|
|
6569
|
+
// src/feedback/confidence-gate.ts
|
|
6570
|
+
function needsDisambiguation(confidence, low = 0.5, high = 0.7) {
|
|
6571
|
+
return confidence >= low && confidence < high;
|
|
6572
|
+
}
|
|
6573
|
+
function buildDisambiguation(input, candidates) {
|
|
6574
|
+
const sortedCandidates = [...candidates].sort((a, b) => b.confidence - a.confidence);
|
|
6575
|
+
const disambiguationCandidates = sortedCandidates.map((c, i) => ({
|
|
6576
|
+
action: c.action,
|
|
6577
|
+
confidence: c.confidence,
|
|
6578
|
+
explicit: renderExplicit(c.node),
|
|
6579
|
+
description: c.description || `${c.action} (option ${String.fromCharCode(97 + i)})`
|
|
6580
|
+
}));
|
|
6581
|
+
const optionLines = disambiguationCandidates.map(
|
|
6582
|
+
(c, i) => ` (${String.fromCharCode(97 + i)}) ${c.explicit} \u2014 ${c.description} (conf: ${c.confidence.toFixed(2)})`
|
|
6583
|
+
);
|
|
6584
|
+
const question = [
|
|
6585
|
+
`Your input parsed with confidence ${sortedCandidates[0]?.confidence.toFixed(2) ?? "?"}.`,
|
|
6586
|
+
...optionLines,
|
|
6587
|
+
"Which did you mean?"
|
|
6588
|
+
].join("\n");
|
|
6589
|
+
return {
|
|
6590
|
+
input,
|
|
6591
|
+
candidates: disambiguationCandidates,
|
|
6592
|
+
question
|
|
6593
|
+
};
|
|
6594
|
+
}
|
|
6595
|
+
|
|
6596
|
+
// src/feedback/pattern-tracker.ts
|
|
6597
|
+
var PatternTracker = class {
|
|
6598
|
+
constructor(maxEvents = 1e3) {
|
|
6599
|
+
this.events = [];
|
|
6600
|
+
this.maxEvents = maxEvents;
|
|
6601
|
+
}
|
|
6602
|
+
/**
|
|
6603
|
+
* Record a pattern event.
|
|
6604
|
+
*/
|
|
6605
|
+
record(event) {
|
|
6606
|
+
this.events.push(event);
|
|
6607
|
+
if (this.events.length > this.maxEvents) {
|
|
6608
|
+
this.events = this.events.slice(this.events.length - this.maxEvents);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
/**
|
|
6612
|
+
* Get hit rate by command action.
|
|
6613
|
+
*/
|
|
6614
|
+
getHitRateByCommand() {
|
|
6615
|
+
return this.computeHitRate((e) => e.action);
|
|
6616
|
+
}
|
|
6617
|
+
/**
|
|
6618
|
+
* Get hit rate by language.
|
|
6619
|
+
*/
|
|
6620
|
+
getHitRateByLanguage() {
|
|
6621
|
+
return this.computeHitRate((e) => e.language);
|
|
6622
|
+
}
|
|
6623
|
+
/**
|
|
6624
|
+
* Get the most frequent failure diagnostic codes.
|
|
6625
|
+
*/
|
|
6626
|
+
getTopFailures(limit = 10) {
|
|
6627
|
+
const codeMap = /* @__PURE__ */ new Map();
|
|
6628
|
+
for (const event of this.events) {
|
|
6629
|
+
if (event.outcome !== "rejected" || !event.diagnosticCodes) continue;
|
|
6630
|
+
for (const code of event.diagnosticCodes) {
|
|
6631
|
+
const entry = codeMap.get(code) || { count: 0, actions: /* @__PURE__ */ new Set() };
|
|
6632
|
+
entry.count++;
|
|
6633
|
+
entry.actions.add(event.action);
|
|
6634
|
+
codeMap.set(code, entry);
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
return Array.from(codeMap.entries()).map(([code, { count, actions }]) => ({
|
|
6638
|
+
code,
|
|
6639
|
+
count,
|
|
6640
|
+
actions: Array.from(actions)
|
|
6641
|
+
})).sort((a, b) => b.count - a.count).slice(0, limit);
|
|
6642
|
+
}
|
|
6643
|
+
/**
|
|
6644
|
+
* Get a complete summary of tracking data.
|
|
6645
|
+
*/
|
|
6646
|
+
getSummary() {
|
|
6647
|
+
const byOutcome = {};
|
|
6648
|
+
for (const event of this.events) {
|
|
6649
|
+
byOutcome[event.outcome] = (byOutcome[event.outcome] || 0) + 1;
|
|
6650
|
+
}
|
|
6651
|
+
return {
|
|
6652
|
+
totalEvents: this.events.length,
|
|
6653
|
+
byOutcome,
|
|
6654
|
+
byCommand: this.getHitRateByCommand(),
|
|
6655
|
+
byLanguage: this.getHitRateByLanguage(),
|
|
6656
|
+
topFailures: this.getTopFailures()
|
|
6657
|
+
};
|
|
6658
|
+
}
|
|
6659
|
+
/**
|
|
6660
|
+
* Export all events as JSONL.
|
|
6661
|
+
*/
|
|
6662
|
+
exportJSONL() {
|
|
6663
|
+
return this.events.map((e) => JSON.stringify(e)).join("\n");
|
|
6664
|
+
}
|
|
6665
|
+
/**
|
|
6666
|
+
* Get the number of recorded events.
|
|
6667
|
+
*/
|
|
6668
|
+
get size() {
|
|
6669
|
+
return this.events.length;
|
|
6670
|
+
}
|
|
6671
|
+
/**
|
|
6672
|
+
* Clear all events.
|
|
6673
|
+
*/
|
|
6674
|
+
clear() {
|
|
6675
|
+
this.events = [];
|
|
6676
|
+
}
|
|
6677
|
+
// ---------------------------------------------------------------------------
|
|
6678
|
+
// Private
|
|
6679
|
+
// ---------------------------------------------------------------------------
|
|
6680
|
+
computeHitRate(keyFn) {
|
|
6681
|
+
const map = /* @__PURE__ */ new Map();
|
|
6682
|
+
for (const event of this.events) {
|
|
6683
|
+
const key = keyFn(event);
|
|
6684
|
+
const entry = map.get(key) || { attempts: 0, successes: 0 };
|
|
6685
|
+
entry.attempts++;
|
|
6686
|
+
if (event.outcome === "accepted") {
|
|
6687
|
+
entry.successes++;
|
|
6688
|
+
}
|
|
6689
|
+
map.set(key, entry);
|
|
6690
|
+
}
|
|
6691
|
+
const result = {};
|
|
6692
|
+
for (const [key, { attempts, successes }] of map) {
|
|
6693
|
+
result[key] = {
|
|
6694
|
+
attempts,
|
|
6695
|
+
successes,
|
|
6696
|
+
rate: attempts > 0 ? successes / attempts : 0
|
|
6697
|
+
};
|
|
6698
|
+
}
|
|
6699
|
+
return result;
|
|
6700
|
+
}
|
|
6701
|
+
};
|
|
6702
|
+
|
|
4122
6703
|
// src/parsing/multi-statement.ts
|
|
4123
6704
|
function createMultiStatementParser(dsl, config) {
|
|
4124
6705
|
return new MultiStatementParserImpl(dsl, config);
|
|
@@ -4363,6 +6944,7 @@ function accumulateIndented(statements, config) {
|
|
|
4363
6944
|
CrossDomainDispatcher,
|
|
4364
6945
|
DEFAULT_OPERATORS,
|
|
4365
6946
|
DEFAULT_PUNCTUATION,
|
|
6947
|
+
DEFAULT_REFERENCES,
|
|
4366
6948
|
DomainAwareScanner,
|
|
4367
6949
|
DomainRegistry,
|
|
4368
6950
|
GrammarTransformer,
|
|
@@ -4374,24 +6956,31 @@ function accumulateIndented(statements, config) {
|
|
|
4374
6956
|
NumberExtractor,
|
|
4375
6957
|
OperatorExtractor,
|
|
4376
6958
|
PatternMatcher,
|
|
6959
|
+
PatternTracker,
|
|
4377
6960
|
PunctuationExtractor,
|
|
4378
6961
|
StringLiteralExtractor,
|
|
4379
6962
|
TokenStreamImpl,
|
|
4380
6963
|
UnicodeIdentifierExtractor,
|
|
4381
6964
|
WhitespaceExtractor,
|
|
4382
6965
|
accumulateBlocks,
|
|
6966
|
+
buildDisambiguation,
|
|
6967
|
+
buildFeedback,
|
|
4383
6968
|
buildPhrase,
|
|
4384
6969
|
buildTablesFromProfiles,
|
|
4385
6970
|
combineClassifiers,
|
|
6971
|
+
convertValue,
|
|
6972
|
+
createAsyncNode,
|
|
4386
6973
|
createCommandNode,
|
|
4387
6974
|
createCompoundNode,
|
|
4388
6975
|
createConditionalNode,
|
|
4389
6976
|
createDiagnosticCollector,
|
|
4390
6977
|
createEventHandlerNode,
|
|
4391
6978
|
createExpression,
|
|
6979
|
+
createFlag,
|
|
4392
6980
|
createLatinCharClassifiers,
|
|
4393
6981
|
createLiteral,
|
|
4394
6982
|
createLoopNode,
|
|
6983
|
+
createMatchNode,
|
|
4395
6984
|
createMultiStatementParser,
|
|
4396
6985
|
createMultilingualDSL,
|
|
4397
6986
|
createPosition,
|
|
@@ -4402,6 +6991,7 @@ function accumulateIndented(statements, config) {
|
|
|
4402
6991
|
createSimpleTokenizer,
|
|
4403
6992
|
createToken,
|
|
4404
6993
|
createTokenizerContext,
|
|
6994
|
+
createTryNode,
|
|
4405
6995
|
createUnicodeRangeClassifier,
|
|
4406
6996
|
defineCommand,
|
|
4407
6997
|
defineRole,
|
|
@@ -4413,9 +7003,15 @@ function accumulateIndented(statements, config) {
|
|
|
4413
7003
|
extractUrl,
|
|
4414
7004
|
extractValue,
|
|
4415
7005
|
filterBySeverity,
|
|
7006
|
+
fromEnvelopeJSON,
|
|
4416
7007
|
fromError,
|
|
7008
|
+
fromInterchangeNode,
|
|
7009
|
+
fromProtocolJSON,
|
|
7010
|
+
generateExamples,
|
|
4417
7011
|
generatePattern,
|
|
4418
7012
|
generatePatternVariants,
|
|
7013
|
+
generatePrompt,
|
|
7014
|
+
generateProtocolReference,
|
|
4419
7015
|
getAllPossessiveKeywords,
|
|
4420
7016
|
getDefaultExtractors,
|
|
4421
7017
|
getPossessiveReference,
|
|
@@ -4428,6 +7024,8 @@ function accumulateIndented(statements, config) {
|
|
|
4428
7024
|
isClassName,
|
|
4429
7025
|
isContextAwareExtractor,
|
|
4430
7026
|
isDigit,
|
|
7027
|
+
isEnvelope,
|
|
7028
|
+
isExplicitSyntax,
|
|
4431
7029
|
isIdSelector,
|
|
4432
7030
|
isNumericValue,
|
|
4433
7031
|
isPossessiveKeyword,
|
|
@@ -4437,17 +7035,33 @@ function accumulateIndented(statements, config) {
|
|
|
4437
7035
|
isSelectorStart,
|
|
4438
7036
|
isTypeCompatible,
|
|
4439
7037
|
isUrlStart,
|
|
7038
|
+
isValidReference,
|
|
4440
7039
|
isVariableRef,
|
|
4441
7040
|
isWhitespace,
|
|
4442
7041
|
joinTokens,
|
|
7042
|
+
jsonToSemanticNode,
|
|
4443
7043
|
lookupKeyword,
|
|
4444
7044
|
lookupMarker,
|
|
4445
7045
|
matchBest,
|
|
4446
7046
|
matchPattern,
|
|
7047
|
+
needsDisambiguation,
|
|
4447
7048
|
noChange,
|
|
4448
7049
|
normalized,
|
|
7050
|
+
parseExplicit,
|
|
7051
|
+
parseJSONL,
|
|
4449
7052
|
patternMatcher,
|
|
7053
|
+
registryToAOTBackends,
|
|
7054
|
+
renderExplicit,
|
|
7055
|
+
renderExpr,
|
|
4450
7056
|
reorderRoles,
|
|
7057
|
+
semanticNodeToJSON,
|
|
7058
|
+
synthesizeFromSchemas,
|
|
7059
|
+
toEnvelopeJSON,
|
|
7060
|
+
toJSONL,
|
|
7061
|
+
toJSONLRow,
|
|
7062
|
+
toProtocolJSON,
|
|
7063
|
+
validateProtocolJSON,
|
|
7064
|
+
validateSemanticJSON,
|
|
4451
7065
|
validateValueType,
|
|
4452
7066
|
withDefaultExtractors
|
|
4453
7067
|
});
|