@almadar/agent 1.0.13 → 1.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.
@@ -1,4 +1,4 @@
1
- import { isOrbitalDefinition, getTraitName, isEntityReference, isPageReferenceString, isPageReferenceObject } from '@almadar/core/types';
1
+ import { isOrbitalDefinition, isEntityReference, getTraitName } from '@almadar/core/types';
2
2
  import { tool } from '@langchain/core/tools';
3
3
  import { z } from 'zod';
4
4
  import { exec, spawn } from 'child_process';
@@ -6,16 +6,33 @@ import * as path from 'path';
6
6
  import { promisify } from 'util';
7
7
  import * as fs4 from 'fs/promises';
8
8
  import { isStructuredOutputAvailable, getStructuredOutputClient, LLMClient } from '@almadar/llm';
9
- import { getPatternDefinition } from '@almadar/patterns';
9
+ import * as domain_language_star from '@almadar/core/domain-language';
10
10
  import * as fs3 from 'fs';
11
11
  import crypto, { randomUUID } from 'crypto';
12
- import { getKeyBehaviorsReference, getSExprQuickRef, getCommonErrorsSection, getArchitectureSection, getFullOrbitalPrompt, getRequirementsTraitPrompt } from '@almadar/skills';
12
+ import { getKeyBehaviorsReference, getSExprQuickRef, getCommonErrorsSection, getArchitectureSection, getFullOrbitalPrompt, generateKflowDesignSkill, getRequirementsTraitPrompt } from '@almadar/skills';
13
+ import { formatRecommendationsForPrompt, buildRecommendationContext, recommendPatterns } from '@almadar/patterns';
13
14
  import { GitHubIntegration } from '@almadar/integrations';
14
15
 
16
+ var __defProp = Object.defineProperty;
17
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
15
18
  var __getOwnPropNames = Object.getOwnPropertyNames;
19
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
20
  var __esm = (fn, res) => function __init() {
17
21
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
18
22
  };
23
+ var __export = (target, all) => {
24
+ for (var name in all)
25
+ __defProp(target, name, { get: all[name], enumerable: true });
26
+ };
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") {
29
+ for (let key of __getOwnPropNames(from))
30
+ if (!__hasOwnProp.call(to, key) && key !== except)
31
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
32
+ }
33
+ return to;
34
+ };
35
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget);
19
36
  function getInlineEntity(entity) {
20
37
  if (isEntityReference(entity)) {
21
38
  return null;
@@ -710,3036 +727,392 @@ function combineOrbitals(orbitals, options) {
710
727
  };
711
728
  } catch (error) {
712
729
  return {
713
- success: false,
714
- error: error instanceof Error ? error.message : String(error),
715
- stats
716
- };
717
- }
718
- }
719
-
720
- // src/orbitals/domain-language/tokens.ts
721
- var KEYWORDS = {
722
- "a": "A" /* A */,
723
- "an": "AN" /* AN */,
724
- "is": "IS" /* IS */,
725
- "it": "IT" /* IT */,
726
- "has": "HAS" /* HAS */,
727
- "belongs": "BELONGS" /* BELONGS */,
728
- "to": "TO" /* TO */,
729
- "many": "MANY" /* MANY */,
730
- "one": "ONE" /* ONE */,
731
- "as": "AS" /* AS */,
732
- "can": "CAN" /* CAN */,
733
- "be": "BE" /* BE */,
734
- "starts": "STARTS" /* STARTS */,
735
- "the": "THE" /* THE */,
736
- "shows": "SHOWS" /* SHOWS */,
737
- "entity": "ENTITY" /* ENTITY */,
738
- "purpose": "PURPOSE" /* PURPOSE */,
739
- "url": "URL" /* URL */,
740
- "displays": "DISPLAYS" /* DISPLAYS */,
741
- "users": "USERS" /* USERS */,
742
- "when": "WHEN" /* WHEN */,
743
- "on": "WHEN" /* WHEN */,
744
- // "on EVENT" is equivalent to "when EVENT"
745
- "accessed": "ACCESSED" /* ACCESSED */,
746
- "lifecycle": "LIFECYCLE" /* LIFECYCLE */,
747
- "behavior": "BEHAVIOR" /* BEHAVIOR */,
748
- "states": "STATES" /* STATES */,
749
- "initial": "INITIAL" /* INITIAL */,
750
- "transitions": "TRANSITIONS" /* TRANSITIONS */,
751
- "from": "FROM" /* FROM */,
752
- "if": "IF" /* IF */,
753
- "then": "THEN" /* THEN */,
754
- "rules": "RULES" /* RULES */,
755
- "every": "EVERY" /* EVERY */,
756
- "check": "CHECK" /* CHECK */,
757
- "and": "AND" /* AND */,
758
- "or": "OR" /* OR */,
759
- "not": "NOT" /* NOT */,
760
- "provided": "PROVIDED" /* PROVIDED */,
761
- "empty": "EMPTY" /* EMPTY */,
762
- "user": "USER" /* USER */,
763
- "owns": "OWNS" /* OWNS */,
764
- "this": "THIS" /* THIS */,
765
- "text": "TEXT" /* TEXT */,
766
- "number": "NUMBER" /* NUMBER */,
767
- "currency": "CURRENCY" /* CURRENCY */,
768
- "date": "DATE" /* DATE */,
769
- "timestamp": "TIMESTAMP" /* TIMESTAMP */,
770
- "required": "REQUIRED" /* REQUIRED */,
771
- "unique": "UNIQUE" /* UNIQUE */,
772
- "auto": "AUTO" /* AUTO */,
773
- "default": "DEFAULT" /* DEFAULT */,
774
- "true": "BOOLEAN" /* BOOLEAN */,
775
- "false": "BOOLEAN" /* BOOLEAN */
776
- };
777
-
778
- // src/orbitals/domain-language/lexer.ts
779
- var Lexer = class {
780
- constructor(input) {
781
- this.pos = 0;
782
- this.line = 1;
783
- this.column = 1;
784
- this.indentStack = [0];
785
- this.input = input;
786
- }
787
- /**
788
- * Tokenize the entire input
789
- */
790
- tokenize() {
791
- const tokens = [];
792
- while (!this.isAtEnd()) {
793
- const token = this.nextToken();
794
- if (token) {
795
- tokens.push(token);
796
- }
797
- }
798
- while (this.indentStack.length > 1) {
799
- this.indentStack.pop();
800
- tokens.push(this.makeToken("DEDENT" /* DEDENT */, ""));
801
- }
802
- tokens.push(this.makeToken("EOF" /* EOF */, ""));
803
- return tokens;
804
- }
805
- nextToken() {
806
- if (this.column === 1) {
807
- const indentToken = this.handleIndentation();
808
- if (indentToken) {
809
- return indentToken;
810
- }
811
- }
812
- this.skipWhitespace();
813
- if (this.isAtEnd()) {
814
- return null;
815
- }
816
- const char = this.peek();
817
- if (char === "\n") {
818
- return this.consumeNewline();
819
- }
820
- if (char === "#") {
821
- this.skipToEndOfLine();
822
- return this.nextToken();
823
- }
824
- if (char === '"' || char === "'") {
825
- return this.consumeString(char);
826
- }
827
- if (this.isDigit(char)) {
828
- return this.consumeNumber();
829
- }
830
- switch (char) {
831
- case ":":
832
- return this.consumeChar("COLON" /* COLON */);
833
- case ",":
834
- return this.consumeChar("COMMA" /* COMMA */);
835
- case "|":
836
- return this.consumeChar("PIPE" /* PIPE */);
837
- case ".":
838
- return this.consumeChar("DOT" /* DOT */);
839
- case "-":
840
- return this.consumeChar("DASH" /* DASH */);
841
- case "[":
842
- return this.consumeChar("LBRACKET" /* LBRACKET */);
843
- case "]":
844
- return this.consumeChar("RBRACKET" /* RBRACKET */);
845
- case "(":
846
- return this.consumeChar("LPAREN" /* LPAREN */);
847
- case ")":
848
- return this.consumeChar("RPAREN" /* RPAREN */);
849
- case ">":
850
- if (this.peekNext() === "=") {
851
- return this.consumeChars(2, "GREATER_EQUAL" /* GREATER_EQUAL */);
852
- }
853
- return this.consumeChar("GREATER_THAN" /* GREATER_THAN */);
854
- case "<":
855
- if (this.peekNext() === "=") {
856
- return this.consumeChars(2, "LESS_EQUAL" /* LESS_EQUAL */);
857
- }
858
- return this.consumeChar("LESS_THAN" /* LESS_THAN */);
859
- case "=":
860
- if (this.peekNext() === "=") {
861
- return this.consumeChars(2, "EQUALS" /* EQUALS */);
862
- }
863
- break;
864
- case "!":
865
- if (this.peekNext() === "=") {
866
- return this.consumeChars(2, "NOT_EQUALS" /* NOT_EQUALS */);
867
- }
868
- break;
869
- }
870
- if (char === "{") {
871
- return this.consumeTemplateVar();
872
- }
873
- if (this.isAlpha(char)) {
874
- return this.consumeIdentifier();
875
- }
876
- this.advance();
877
- return this.nextToken();
878
- }
879
- consumeTemplateVar() {
880
- const start = this.pos;
881
- this.advance();
882
- let braceDepth = 1;
883
- while (!this.isAtEnd() && braceDepth > 0 && this.peek() !== "\n") {
884
- const ch = this.peek();
885
- if (ch === "{") {
886
- braceDepth++;
887
- } else if (ch === "}") {
888
- braceDepth--;
889
- if (braceDepth === 0) {
890
- this.advance();
891
- break;
892
- }
893
- }
894
- this.advance();
895
- }
896
- const value = this.input.substring(start, this.pos);
897
- return this.makeToken("TEMPLATE_VAR" /* TEMPLATE_VAR */, value);
898
- }
899
- handleIndentation() {
900
- if (this.isAtEnd() || this.peek() === "\n") {
901
- return null;
902
- }
903
- let indent = 0;
904
- while (this.peek() === " " || this.peek() === " ") {
905
- indent += this.peek() === " " ? 4 : 1;
906
- this.advance();
907
- }
908
- if (this.peek() === "\n" || this.peek() === "#") {
909
- return null;
910
- }
911
- const currentIndent = this.indentStack[this.indentStack.length - 1];
912
- if (indent > currentIndent) {
913
- this.indentStack.push(indent);
914
- return this.makeToken("INDENT" /* INDENT */, "");
915
- } else if (indent < currentIndent) {
916
- while (this.indentStack.length > 1 && this.indentStack[this.indentStack.length - 1] > indent) {
917
- this.indentStack.pop();
918
- }
919
- return this.makeToken("DEDENT" /* DEDENT */, "");
920
- }
921
- return null;
922
- }
923
- consumeNewline() {
924
- const token = this.makeToken("NEWLINE" /* NEWLINE */, "\n");
925
- this.advance();
926
- this.line++;
927
- this.column = 1;
928
- return token;
929
- }
930
- consumeString(quote) {
931
- const startLine = this.line;
932
- const startColumn = this.column;
933
- const startOffset = this.pos;
934
- this.advance();
935
- let value = "";
936
- while (!this.isAtEnd() && this.peek() !== quote) {
937
- if (this.peek() === "\\" && this.peekNext() === quote) {
938
- this.advance();
939
- value += quote;
940
- } else if (this.peek() === "\n") {
941
- break;
942
- } else {
943
- value += this.peek();
944
- }
945
- this.advance();
946
- }
947
- if (this.peek() === quote) {
948
- this.advance();
949
- }
950
- return {
951
- type: "STRING" /* STRING */,
952
- value,
953
- line: startLine,
954
- column: startColumn,
955
- offset: startOffset
956
- };
957
- }
958
- consumeNumber() {
959
- const startLine = this.line;
960
- const startColumn = this.column;
961
- const startOffset = this.pos;
962
- let value = "";
963
- while (this.isDigit(this.peek())) {
964
- value += this.advance();
965
- }
966
- if (this.peek() === "." && this.isDigit(this.peekNext())) {
967
- value += this.advance();
968
- while (this.isDigit(this.peek())) {
969
- value += this.advance();
970
- }
971
- }
972
- return {
973
- type: "NUMBER_LITERAL" /* NUMBER_LITERAL */,
974
- value,
975
- line: startLine,
976
- column: startColumn,
977
- offset: startOffset
978
- };
979
- }
980
- consumeIdentifier() {
981
- const startLine = this.line;
982
- const startColumn = this.column;
983
- const startOffset = this.pos;
984
- let value = "";
985
- while (this.isAlphaNumeric(this.peek()) || this.peek() === "_" || this.peek() === "/") {
986
- value += this.advance();
987
- }
988
- const lowerValue = value.toLowerCase();
989
- const keywordType = KEYWORDS[lowerValue];
990
- return {
991
- type: keywordType || "IDENTIFIER" /* IDENTIFIER */,
992
- value,
993
- line: startLine,
994
- column: startColumn,
995
- offset: startOffset
996
- };
997
- }
998
- consumeChar(type) {
999
- const token = this.makeToken(type, this.peek());
1000
- this.advance();
1001
- return token;
1002
- }
1003
- consumeChars(count, type) {
1004
- let value = "";
1005
- for (let i = 0; i < count; i++) {
1006
- value += this.advance();
1007
- }
1008
- return {
1009
- type,
1010
- value,
1011
- line: this.line,
1012
- column: this.column - count,
1013
- offset: this.pos - count
1014
- };
1015
- }
1016
- skipWhitespace() {
1017
- while (!this.isAtEnd() && (this.peek() === " " || this.peek() === " ")) {
1018
- this.advance();
1019
- }
1020
- }
1021
- skipToEndOfLine() {
1022
- while (!this.isAtEnd() && this.peek() !== "\n") {
1023
- this.advance();
1024
- }
1025
- }
1026
- makeToken(type, value) {
1027
- return {
1028
- type,
1029
- value,
1030
- line: this.line,
1031
- column: this.column,
1032
- offset: this.pos
1033
- };
1034
- }
1035
- peek() {
1036
- return this.input[this.pos] || "\0";
1037
- }
1038
- peekNext() {
1039
- return this.input[this.pos + 1] || "\0";
1040
- }
1041
- advance() {
1042
- const char = this.peek();
1043
- this.pos++;
1044
- this.column++;
1045
- return char;
1046
- }
1047
- isAtEnd() {
1048
- return this.pos >= this.input.length;
1049
- }
1050
- isDigit(char) {
1051
- return char >= "0" && char <= "9";
1052
- }
1053
- isAlpha(char) {
1054
- return char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char === "_";
1055
- }
1056
- isAlphaNumeric(char) {
1057
- return this.isAlpha(char) || this.isDigit(char);
1058
- }
1059
- };
1060
-
1061
- // src/orbitals/domain-language/parsers/entity-parser.ts
1062
- function parseEntity(text) {
1063
- const ctx = { errors: [], warnings: [] };
1064
- const lexer = new Lexer(text);
1065
- const tokens = lexer.tokenize();
1066
- let pos = 0;
1067
- const current = () => tokens[pos] || { type: "EOF" /* EOF */, value: "", line: 0, column: 0, offset: 0 };
1068
- const advance = () => tokens[pos++];
1069
- const isAtEnd = () => current().type === "EOF" /* EOF */;
1070
- const skip = (type) => {
1071
- while (current().type === type) advance();
1072
- };
1073
- skip("NEWLINE" /* NEWLINE */);
1074
- const entityHeader = parseEntityHeader();
1075
- if (!entityHeader) {
1076
- return {
1077
- success: false,
1078
- errors: ctx.errors.length > 0 ? ctx.errors : [{
1079
- message: 'Expected entity definition starting with "A [Name] is..."'
1080
- }],
1081
- warnings: []
1082
- };
1083
- }
1084
- const entity = {
1085
- type: "entity",
1086
- name: entityHeader.name,
1087
- description: entityHeader.description,
1088
- fields: [],
1089
- relationships: []
1090
- };
1091
- skip("NEWLINE" /* NEWLINE */);
1092
- while (!isAtEnd()) {
1093
- skip("NEWLINE" /* NEWLINE */);
1094
- if (isAtEnd()) break;
1095
- const sectionResult = parseSection(entity);
1096
- if (!sectionResult) {
1097
- advance();
1098
- }
1099
- }
1100
- return {
1101
- success: true,
1102
- data: entity,
1103
- errors: ctx.errors,
1104
- warnings: ctx.warnings
1105
- };
1106
- function isEntityName(token) {
1107
- if (token.type === "IDENTIFIER" /* IDENTIFIER */) return true;
1108
- const keywordEntityNames = [
1109
- "USER" /* USER */,
1110
- // "User"
1111
- "TEXT" /* TEXT */,
1112
- // "Text" (unlikely but possible)
1113
- "NUMBER" /* NUMBER */,
1114
- // "Number" (unlikely but possible)
1115
- "DATE" /* DATE */
1116
- // "Date" (unlikely but possible)
1117
- ];
1118
- return keywordEntityNames.includes(token.type);
1119
- }
1120
- function parseEntityHeader() {
1121
- if (current().type !== "A" /* A */ && current().type !== "AN" /* AN */) {
1122
- ctx.errors.push({ message: 'Expected "A" or "An" at start of entity definition' });
1123
- return null;
1124
- }
1125
- advance();
1126
- if (!isEntityName(current())) {
1127
- ctx.errors.push({ message: 'Expected entity name after "A"' });
1128
- return null;
1129
- }
1130
- const name = current().value;
1131
- advance();
1132
- if (current().type !== "IS" /* IS */) {
1133
- ctx.errors.push({ message: `Expected "is" after entity name "${name}"` });
1134
- return null;
1135
- }
1136
- advance();
1137
- const descParts = [];
1138
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */) {
1139
- descParts.push(current().value);
1140
- advance();
1141
- }
1142
- return {
1143
- name,
1144
- description: descParts.join(" ").trim()
1145
- };
1146
- }
1147
- function parseSection(entity2) {
1148
- const token = current();
1149
- if (token.type === "INDENT" /* INDENT */) {
1150
- advance();
1151
- parseIndentedFields(entity2);
1152
- return true;
1153
- }
1154
- if (token.type === "IT" /* IT */) {
1155
- advance();
1156
- if (current().type === "HAS" /* HAS */) {
1157
- advance();
1158
- skip("COLON" /* COLON */);
1159
- skip("NEWLINE" /* NEWLINE */);
1160
- parseFieldsSection(entity2);
1161
- return true;
1162
- }
1163
- if (current().type === "BELONGS" /* BELONGS */) {
1164
- advance();
1165
- if (current().type === "TO" /* TO */) {
1166
- advance();
1167
- parseRelationship(entity2, "belongs_to");
1168
- return true;
1169
- }
1170
- }
1171
- if (current().type === "CAN" /* CAN */) {
1172
- advance();
1173
- if (current().type === "BE" /* BE */) {
1174
- advance();
1175
- skip("COLON" /* COLON */);
1176
- parseStates(entity2);
1177
- return true;
1178
- }
1179
- }
1180
- if (current().type === "STARTS" /* STARTS */) {
1181
- advance();
1182
- if (current().type === "AS" /* AS */) {
1183
- advance();
1184
- parseInitialState(entity2);
1185
- return true;
1186
- }
1187
- }
1188
- }
1189
- if (token.type === "HAS" /* HAS */) {
1190
- advance();
1191
- if (current().type === "MANY" /* MANY */) {
1192
- advance();
1193
- parseRelationship(entity2, "has_many");
1194
- return true;
1195
- }
1196
- if (current().type === "ONE" /* ONE */) {
1197
- advance();
1198
- parseRelationship(entity2, "has_one");
1199
- return true;
1200
- }
1201
- }
1202
- return false;
1203
- }
1204
- function parseIndentedFields(entity2) {
1205
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
1206
- skip("NEWLINE" /* NEWLINE */);
1207
- if (current().type === "DEDENT" /* DEDENT */) break;
1208
- if (current().type === "DASH" /* DASH */) {
1209
- advance();
1210
- }
1211
- if (current().type === "HAS" /* HAS */) {
1212
- advance();
1213
- if (current().type === "MANY" /* MANY */) {
1214
- advance();
1215
- parseRelationship(entity2, "has_many");
1216
- continue;
1217
- }
1218
- const field = parseHasFieldLine();
1219
- if (field) {
1220
- entity2.fields.push(field);
1221
- }
1222
- continue;
1223
- }
1224
- if (current().type === "BELONGS" /* BELONGS */) {
1225
- advance();
1226
- if (current().type === "TO" /* TO */) {
1227
- advance();
1228
- parseRelationship(entity2, "belongs_to");
1229
- continue;
1230
- }
1231
- }
1232
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */ && current().type !== "DEDENT" /* DEDENT */) {
1233
- advance();
1234
- }
1235
- skip("NEWLINE" /* NEWLINE */);
1236
- }
1237
- if (current().type === "DEDENT" /* DEDENT */) {
1238
- advance();
1239
- }
1240
- }
1241
- function parseHasFieldLine() {
1242
- if (current().type !== "IDENTIFIER" /* IDENTIFIER */) {
1243
- return null;
1244
- }
1245
- const fieldName = toCamelCase(current().value);
1246
- advance();
1247
- if (current().type !== "AS" /* AS */) {
1248
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */ && current().type !== "DEDENT" /* DEDENT */) {
1249
- advance();
1250
- }
1251
- return null;
1252
- }
1253
- advance();
1254
- const field = {
1255
- type: "field",
1256
- name: fieldName,
1257
- fieldType: "text",
1258
- required: false,
1259
- unique: false,
1260
- auto: false
1261
- };
1262
- const typeParts = [];
1263
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */ && current().type !== "DEDENT" /* DEDENT */ && current().type !== "LPAREN" /* LPAREN */ && current().value !== "with") {
1264
- typeParts.push(current().value);
1265
- advance();
1266
- }
1267
- const typeText = typeParts.join(" ").trim().toLowerCase();
1268
- if (typeText.includes("long text")) {
1269
- field.fieldType = "long text";
1270
- } else if (typeText === "text" || typeText === "string") {
1271
- field.fieldType = "text";
1272
- } else if (typeText === "number" || typeText === "integer") {
1273
- field.fieldType = "number";
1274
- } else if (typeText === "currency") {
1275
- field.fieldType = "currency";
1276
- } else if (typeText === "yes/no" || typeText === "boolean") {
1277
- field.fieldType = "yes/no";
1278
- } else if (typeText === "date") {
1279
- field.fieldType = "date";
1280
- } else if (typeText === "timestamp" || typeText === "datetime") {
1281
- field.fieldType = "timestamp";
1282
- } else if (typeText.startsWith("enum")) {
1283
- field.fieldType = "enum";
1284
- const enumMatch = typeText.match(/enum\s*\[([^\]]+)\]/);
1285
- if (enumMatch) {
1286
- field.enumValues = enumMatch[1].split(",").map((v) => v.trim());
1287
- }
1288
- } else if (typeText === "list" || typeText === "array") {
1289
- field.fieldType = "list";
1290
- } else if (typeText === "object") {
1291
- field.fieldType = "object";
1292
- } else {
1293
- field.fieldType = "text";
1294
- }
1295
- if (current().type === "LPAREN" /* LPAREN */) {
1296
- advance();
1297
- while (!isAtEnd() && current().type !== "RPAREN" /* RPAREN */ && current().type !== "NEWLINE" /* NEWLINE */) {
1298
- const constraint = current().value.toLowerCase();
1299
- if (constraint === "required") {
1300
- field.required = true;
1301
- } else if (constraint === "optional") {
1302
- field.required = false;
1303
- } else if (constraint === "unique") {
1304
- field.unique = true;
1305
- }
1306
- advance();
1307
- }
1308
- if (current().type === "RPAREN" /* RPAREN */) {
1309
- advance();
1310
- }
1311
- }
1312
- if (current().type === "IDENTIFIER" /* IDENTIFIER */ && current().value.toLowerCase() === "with") {
1313
- advance();
1314
- if (current().type === "DEFAULT" /* DEFAULT */) {
1315
- advance();
1316
- if (current().type === "STRING" /* STRING */) {
1317
- field.default = current().value;
1318
- advance();
1319
- } else if (current().type === "NUMBER_LITERAL" /* NUMBER_LITERAL */) {
1320
- field.default = parseFloat(current().value);
1321
- advance();
1322
- } else if (current().type === "BOOLEAN" /* BOOLEAN */) {
1323
- field.default = current().value === "true";
1324
- advance();
1325
- } else if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
1326
- field.default = current().value;
1327
- advance();
1328
- }
1329
- }
1330
- }
1331
- return field;
1332
- }
1333
- function parseFieldsSection(entity2) {
1334
- if (current().type !== "INDENT" /* INDENT */) {
1335
- return;
1336
- }
1337
- advance();
1338
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
1339
- skip("NEWLINE" /* NEWLINE */);
1340
- if (current().type === "DEDENT" /* DEDENT */) break;
1341
- const field = parseFieldLine();
1342
- if (field) {
1343
- entity2.fields.push(field);
1344
- }
1345
- skip("NEWLINE" /* NEWLINE */);
1346
- }
1347
- if (current().type === "DEDENT" /* DEDENT */) {
1348
- advance();
1349
- }
1350
- }
1351
- function parseFieldLine() {
1352
- if (current().type === "DASH" /* DASH */) {
1353
- advance();
1354
- }
1355
- const nameParts = [];
1356
- while (!isAtEnd() && current().type !== "COLON" /* COLON */ && current().type !== "NEWLINE" /* NEWLINE */) {
1357
- nameParts.push(current().value);
1358
- advance();
1359
- }
1360
- if (nameParts.length === 0) {
1361
- return null;
1362
- }
1363
- const fieldName = toCamelCase(nameParts.join(" "));
1364
- if (current().type !== "COLON" /* COLON */) {
1365
- ctx.errors.push({ message: `Expected ":" after field name "${fieldName}"` });
1366
- return null;
1367
- }
1368
- advance();
1369
- return parseFieldTypeAndConstraints(fieldName);
1370
- }
1371
- function parseFieldTypeAndConstraints(fieldName) {
1372
- const field = {
1373
- type: "field",
1374
- name: fieldName,
1375
- fieldType: "text",
1376
- required: false,
1377
- unique: false,
1378
- auto: false
1379
- };
1380
- const parts = [];
1381
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */) {
1382
- parts.push(current().value);
1383
- advance();
1384
- }
1385
- const content = parts.join(" ").trim();
1386
- if (content.includes("|")) {
1387
- field.fieldType = "enum";
1388
- field.enumValues = content.split("|").map((v) => v.trim()).filter((v) => v);
1389
- return field;
1390
- }
1391
- const segments = content.split(",").map((s) => s.trim().toLowerCase());
1392
- for (const segment of segments) {
1393
- if (segment === "text") field.fieldType = "text";
1394
- else if (segment === "long text") field.fieldType = "long text";
1395
- else if (segment === "number") field.fieldType = "number";
1396
- else if (segment === "currency") field.fieldType = "currency";
1397
- else if (segment === "date") field.fieldType = "date";
1398
- else if (segment === "timestamp") field.fieldType = "timestamp";
1399
- else if (segment === "datetime") field.fieldType = "datetime";
1400
- else if (segment === "yes/no" || segment === "boolean") field.fieldType = "yes/no";
1401
- else if (segment === "list") field.fieldType = "list";
1402
- else if (segment === "object") field.fieldType = "object";
1403
- else if (segment === "required") field.required = true;
1404
- else if (segment === "unique") field.unique = true;
1405
- else if (segment === "auto") field.auto = true;
1406
- else if (segment.startsWith("default ")) {
1407
- field.default = parseValue(segment.slice(8));
1408
- }
1409
- }
1410
- return field;
1411
- }
1412
- function parseRelationship(entity2, relType) {
1413
- if (current().type !== "IDENTIFIER" /* IDENTIFIER */) {
1414
- return;
1415
- }
1416
- const targetEntity = current().value;
1417
- advance();
1418
- let alias;
1419
- if (current().type === "AS" /* AS */) {
1420
- advance();
1421
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
1422
- alias = current().value;
1423
- advance();
1424
- }
1425
- }
1426
- entity2.relationships.push({
1427
- type: "relationship",
1428
- relationshipType: relType,
1429
- targetEntity,
1430
- alias
1431
- });
1432
- }
1433
- function parseStates(entity2) {
1434
- const states = [];
1435
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */) {
1436
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
1437
- states.push(current().value);
1438
- }
1439
- advance();
1440
- }
1441
- entity2.states = states;
1442
- }
1443
- function parseInitialState(entity2) {
1444
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
1445
- entity2.initialState = current().value;
1446
- advance();
1447
- }
1448
- }
1449
- }
1450
- function formatEntityToSchema(entity) {
1451
- const fields = entity.fields.map((field) => ({
1452
- name: field.name,
1453
- type: mapFieldTypeToSchema(field.fieldType),
1454
- required: field.required || void 0,
1455
- unique: field.unique || void 0,
1456
- auto: field.auto || void 0,
1457
- values: field.enumValues,
1458
- // OrbitalSchema uses 'values' not 'enumValues'
1459
- default: field.default
1460
- }));
1461
- for (const rel of entity.relationships) {
1462
- if (rel.relationshipType === "belongs_to") {
1463
- const fieldName = rel.alias ? toCamelCase(rel.alias) + "Id" : toCamelCase(rel.targetEntity) + "Id";
1464
- fields.push({
1465
- name: fieldName,
1466
- type: "relation",
1467
- required: void 0,
1468
- unique: void 0,
1469
- auto: void 0,
1470
- values: void 0,
1471
- default: void 0,
1472
- relation: {
1473
- entity: rel.targetEntity,
1474
- type: "many-to-one"
1475
- }
1476
- });
1477
- }
1478
- }
1479
- return {
1480
- name: entity.name,
1481
- collection: toKebabCase(entity.name) + "s",
1482
- fields: fields.filter((f) => Object.keys(f).length > 0),
1483
- states: entity.states,
1484
- initialState: entity.initialState
1485
- };
1486
- }
1487
- function toCamelCase(text) {
1488
- return text.toLowerCase().split(/\s+/).map(
1489
- (word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
1490
- ).join("");
1491
- }
1492
- function toKebabCase(text) {
1493
- return text.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
1494
- }
1495
- function parseValue(text) {
1496
- text = text.trim();
1497
- if (text.toLowerCase() === "true") return true;
1498
- if (text.toLowerCase() === "false") return false;
1499
- const num = parseFloat(text);
1500
- if (!isNaN(num)) return num;
1501
- return text.replace(/^["']|["']$/g, "");
1502
- }
1503
- function mapFieldTypeToSchema(fieldType) {
1504
- const mapping = {
1505
- "text": "string",
1506
- "long text": "string",
1507
- "number": "number",
1508
- "currency": "number",
1509
- "date": "date",
1510
- "timestamp": "timestamp",
1511
- "datetime": "datetime",
1512
- "yes/no": "boolean",
1513
- "enum": "enum",
1514
- "list": "array",
1515
- "object": "object",
1516
- "relation": "relation"
1517
- };
1518
- return mapping[fieldType] || "string";
1519
- }
1520
-
1521
- // src/orbitals/domain-language/parsers/page-parser.ts
1522
- function parsePage(text) {
1523
- const ctx = { errors: [], warnings: [] };
1524
- const lexer = new Lexer(text);
1525
- const tokens = lexer.tokenize();
1526
- let pos = 0;
1527
- const current = () => tokens[pos] || { type: "EOF" /* EOF */, value: "", line: 0, column: 0, offset: 0 };
1528
- const advance = () => tokens[pos++];
1529
- const isAtEnd = () => current().type === "EOF" /* EOF */;
1530
- const skip = (type) => {
1531
- while (current().type === type) advance();
1532
- };
1533
- skip("NEWLINE" /* NEWLINE */);
1534
- const pageHeader = parsePageHeader();
1535
- if (!pageHeader) {
1536
- return {
1537
- success: false,
1538
- errors: ctx.errors.length > 0 ? ctx.errors : [{
1539
- message: 'Expected page definition starting with "The [PageName] shows..."'
1540
- }],
1541
- warnings: []
1542
- };
1543
- }
1544
- const page = {
1545
- type: "page",
1546
- name: pageHeader.name,
1547
- description: pageHeader.description,
1548
- purpose: "",
1549
- url: pageHeader.url || "",
1550
- primaryEntity: pageHeader.primaryEntity,
1551
- traitName: pageHeader.traitName,
1552
- sections: [],
1553
- actions: []
1554
- };
1555
- skip("NEWLINE" /* NEWLINE */);
1556
- while (!isAtEnd()) {
1557
- skip("NEWLINE" /* NEWLINE */);
1558
- if (isAtEnd()) break;
1559
- const parsed = parsePageSection(page);
1560
- if (!parsed) {
1561
- advance();
1562
- }
1563
- }
1564
- if (!page.url) {
1565
- page.url = "/" + toKebabCase2(page.name.replace(/Page$/i, ""));
1566
- }
1567
- return {
1568
- success: true,
1569
- data: page,
1570
- errors: ctx.errors,
1571
- warnings: ctx.warnings
1572
- };
1573
- function parsePageHeader() {
1574
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
1575
- const firstToken = current().value;
1576
- advance();
1577
- const nameParts2 = [firstToken];
1578
- while (current().type === "IDENTIFIER" /* IDENTIFIER */ && current().value.toLowerCase() !== "at") {
1579
- nameParts2.push(current().value);
1580
- advance();
1581
- }
1582
- if (current().type === "IDENTIFIER" /* IDENTIFIER */ && current().value.toLowerCase() === "at") {
1583
- advance();
1584
- const pathParts = [];
1585
- while (!isAtEnd() && current().type !== "COLON" /* COLON */ && current().type !== "NEWLINE" /* NEWLINE */) {
1586
- pathParts.push(current().value);
1587
- advance();
1588
- }
1589
- const url = pathParts.join("");
1590
- if (current().type === "COLON" /* COLON */) {
1591
- advance();
1592
- }
1593
- let name2 = nameParts2.map((p) => toPascalCase(p)).join("");
1594
- if (!name2.toLowerCase().endsWith("page")) {
1595
- name2 += "Page";
1596
- }
1597
- skip("NEWLINE" /* NEWLINE */);
1598
- let primaryEntity;
1599
- let traitName;
1600
- if (current().type === "INDENT" /* INDENT */) {
1601
- advance();
1602
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
1603
- skip("NEWLINE" /* NEWLINE */);
1604
- if (current().type === "DEDENT" /* DEDENT */) break;
1605
- if (current().type === "DASH" /* DASH */) {
1606
- advance();
1607
- }
1608
- if (current().type === "SHOWS" /* SHOWS */) {
1609
- advance();
1610
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
1611
- primaryEntity = current().value;
1612
- advance();
1613
- }
1614
- if (current().type === "IDENTIFIER" /* IDENTIFIER */ && current().value.toLowerCase() === "using") {
1615
- advance();
1616
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
1617
- traitName = current().value;
1618
- advance();
1619
- }
1620
- }
1621
- }
1622
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */ && current().type !== "DEDENT" /* DEDENT */) {
1623
- advance();
1624
- }
1625
- skip("NEWLINE" /* NEWLINE */);
1626
- }
1627
- if (current().type === "DEDENT" /* DEDENT */) {
1628
- advance();
1629
- }
1630
- }
1631
- return {
1632
- name: name2,
1633
- description: `Page at ${url}`,
1634
- url,
1635
- primaryEntity,
1636
- traitName
1637
- };
1638
- }
1639
- }
1640
- if (current().type !== "THE" /* THE */) {
1641
- ctx.errors.push({ message: 'Expected "The" at start of page definition' });
1642
- return null;
1643
- }
1644
- advance();
1645
- const nameParts = [];
1646
- while (current().type === "IDENTIFIER" /* IDENTIFIER */) {
1647
- nameParts.push(current().value);
1648
- advance();
1649
- }
1650
- if (nameParts.length === 0) {
1651
- ctx.errors.push({ message: 'Expected page name after "The"' });
1652
- return null;
1653
- }
1654
- let name = nameParts.map((p) => toPascalCase(p)).join("");
1655
- if (!name.toLowerCase().endsWith("page")) {
1656
- name += "Page";
1657
- }
1658
- if (current().type !== "SHOWS" /* SHOWS */) {
1659
- ctx.errors.push({ message: `Expected "shows" after page name "${name}"` });
1660
- return null;
1661
- }
1662
- advance();
1663
- const descParts = [];
1664
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */) {
1665
- descParts.push(current().value);
1666
- advance();
1667
- }
1668
- return {
1669
- name,
1670
- description: descParts.join(" ").trim()
1671
- };
1672
- }
1673
- function parsePageSection(page2) {
1674
- const token = current();
1675
- if (token.type === "ENTITY" /* ENTITY */) {
1676
- advance();
1677
- skip("COLON" /* COLON */);
1678
- page2.primaryEntity = collectUntilNewline();
1679
- return true;
1680
- }
1681
- if (token.type === "PURPOSE" /* PURPOSE */) {
1682
- advance();
1683
- skip("COLON" /* COLON */);
1684
- page2.purpose = collectUntilNewline();
1685
- return true;
1686
- }
1687
- if (token.type === "URL" /* URL */) {
1688
- advance();
1689
- skip("COLON" /* COLON */);
1690
- page2.url = collectUntilNewline();
1691
- return true;
1692
- }
1693
- if (token.type === "IT" /* IT */) {
1694
- advance();
1695
- if (current().type === "DISPLAYS" /* DISPLAYS */) {
1696
- advance();
1697
- skip("COLON" /* COLON */);
1698
- skip("NEWLINE" /* NEWLINE */);
1699
- parseDisplaysSection(page2);
1700
- return true;
1701
- }
1702
- }
1703
- if (token.type === "USERS" /* USERS */) {
1704
- advance();
1705
- if (current().type === "CAN" /* CAN */) {
1706
- advance();
1707
- skip("COLON" /* COLON */);
1708
- skip("NEWLINE" /* NEWLINE */);
1709
- parseActionsSection(page2);
1710
- return true;
1711
- }
1712
- }
1713
- if (token.type === "WHEN" /* WHEN */) {
1714
- advance();
1715
- if (current().type === "ACCESSED" /* ACCESSED */) {
1716
- advance();
1717
- skip("COLON" /* COLON */);
1718
- page2.onAccess = collectUntilNewline();
1719
- return true;
1720
- }
1721
- }
1722
- return false;
1723
- }
1724
- function parseDisplaysSection(page2) {
1725
- if (current().type !== "INDENT" /* INDENT */) {
1726
- return;
1727
- }
1728
- advance();
1729
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
1730
- skip("NEWLINE" /* NEWLINE */);
1731
- if (current().type === "DEDENT" /* DEDENT */) break;
1732
- const section = parseSectionLine();
1733
- if (section) {
1734
- page2.sections.push(section);
1735
- }
1736
- skip("NEWLINE" /* NEWLINE */);
1737
- }
1738
- if (current().type === "DEDENT" /* DEDENT */) {
1739
- advance();
1740
- }
1741
- }
1742
- function parseSectionLine() {
1743
- if (current().type === "DASH" /* DASH */) {
1744
- advance();
1745
- }
1746
- const description = collectUntilNewline();
1747
- if (!description) {
1748
- return null;
1749
- }
1750
- return {
1751
- type: "page_section",
1752
- description
1753
- };
1754
- }
1755
- function parseActionsSection(page2) {
1756
- if (current().type !== "INDENT" /* INDENT */) {
1757
- return;
1758
- }
1759
- advance();
1760
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
1761
- skip("NEWLINE" /* NEWLINE */);
1762
- if (current().type === "DEDENT" /* DEDENT */) break;
1763
- const action = parseActionLine();
1764
- if (action) {
1765
- page2.actions.push(action);
1766
- }
1767
- skip("NEWLINE" /* NEWLINE */);
1768
- }
1769
- if (current().type === "DEDENT" /* DEDENT */) {
1770
- advance();
1771
- }
1772
- }
1773
- function parseActionLine() {
1774
- if (current().type === "DASH" /* DASH */) {
1775
- advance();
1776
- }
1777
- const fullText = collectUntilNewline();
1778
- if (!fullText) {
1779
- return null;
1780
- }
1781
- const toIndex = fullText.toLowerCase().indexOf(" to ");
1782
- if (toIndex === -1) {
1783
- return {
1784
- type: "page_action",
1785
- trigger: fullText,
1786
- action: ""
1787
- };
1788
- }
1789
- return {
1790
- type: "page_action",
1791
- trigger: fullText.slice(0, toIndex).trim(),
1792
- action: fullText.slice(toIndex + 4).trim()
1793
- };
1794
- }
1795
- function collectUntilNewline() {
1796
- const parts = [];
1797
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */) {
1798
- parts.push(current().value);
1799
- advance();
1800
- }
1801
- return parts.join(" ").trim();
1802
- }
1803
- }
1804
- function formatPageToSchema(page) {
1805
- const viewType = inferViewTypeFromPageName(page.name);
1806
- const primaryEntity = page.primaryEntity;
1807
- return {
1808
- name: page.name,
1809
- path: page.url.startsWith("/") ? page.url : `/${page.url}`,
1810
- purpose: page.purpose || page.description,
1811
- sections: page.sections.map((section, index) => {
1812
- const pattern = inferPatternFromDescription(section.description);
1813
- const patternConfig = inferPatternConfigFromDescription(section.description, page.primaryEntity);
1814
- return {
1815
- id: `section-${index}`,
1816
- purpose: section.description,
1817
- order: index,
1818
- estimatedSize: "medium",
1819
- pattern: {
1820
- type: pattern,
1821
- ...patternConfig
1822
- }
1823
- };
1824
- }),
1825
- traits: [],
1826
- primaryEntity,
1827
- viewType,
1828
- isInitial: page.url === "/" || page.url === "/dashboard"
1829
- };
1830
- }
1831
- function inferViewTypeFromPageName(name) {
1832
- const lower = name.toLowerCase();
1833
- if (lower.includes("list") || lower.includes("index")) {
1834
- return "list";
1835
- }
1836
- if (lower.includes("detail") || lower.includes("view")) {
1837
- return "detail";
1838
- }
1839
- if (lower.includes("create") || lower.includes("new")) {
1840
- return "create";
1841
- }
1842
- if (lower.includes("edit")) {
1843
- return "edit";
1844
- }
1845
- if (lower.includes("dashboard")) {
1846
- return "dashboard";
1847
- }
1848
- return "list";
1849
- }
1850
- function inferPatternConfigFromDescription(description, primaryEntity) {
1851
- const lower = description.toLowerCase();
1852
- const entityName = primaryEntity || "Item";
1853
- if (lower.includes("header") || lower.includes("title")) {
1854
- const titleMatch = description.match(/title\s*["']?([^"']+)["']?/i) || description.match(/["']([^"']+)["']/);
1855
- return {
1856
- title: titleMatch ? titleMatch[1] : "Page Title"
1857
- };
1858
- }
1859
- if (lower.includes("list") || lower.includes("table")) {
1860
- return {
1861
- entity: entityName,
1862
- fieldNames: ["id", "name", "createdAt"]
1863
- };
1864
- }
1865
- if (lower.includes("detail")) {
1866
- return {
1867
- entity: entityName,
1868
- fieldNames: ["id", "name", "description", "createdAt"]
1869
- };
1870
- }
1871
- if (lower.includes("form")) {
1872
- return {
1873
- entity: entityName,
1874
- fields: [
1875
- { field: "name", label: "Name", type: "text", required: true },
1876
- { field: "description", label: "Description", type: "textarea" }
1877
- ]
1878
- };
1879
- }
1880
- if (lower.includes("stats") || lower.includes("statistics") || lower.includes("summary") || lower.includes("overview")) {
1881
- return {
1882
- entity: entityName,
1883
- fieldNames: ["id", "name", "value"]
1884
- };
1885
- }
1886
- return {
1887
- entity: entityName,
1888
- fieldNames: ["id", "name", "createdAt"]
1889
- };
1890
- }
1891
- function toPascalCase(text) {
1892
- if (/^[A-Z][a-zA-Z]*$/.test(text)) {
1893
- return text;
1894
- }
1895
- if (text.includes(" ")) {
1896
- return text.split(" ").map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
1897
- }
1898
- return text.charAt(0).toUpperCase() + text.slice(1);
1899
- }
1900
- function toKebabCase2(text) {
1901
- return text.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/\s+/g, "-").toLowerCase();
1902
- }
1903
- function inferPatternFromDescription(description) {
1904
- const lower = description.toLowerCase();
1905
- if (lower.includes("header")) {
1906
- return "page-header";
1907
- }
1908
- if (lower.includes("list") || lower.includes("table")) {
1909
- return "entity-list";
1910
- }
1911
- if (lower.includes("statistics") || lower.includes("stats") || lower.includes("summary") || lower.includes("overview")) {
1912
- return "entity-list";
1913
- }
1914
- if (lower.includes("form") || lower.includes("input") || lower.includes("edit")) {
1915
- return "form-section";
1916
- }
1917
- if (lower.includes("detail") || lower.includes("view")) {
1918
- return "entity-detail";
1919
- }
1920
- if (lower.includes("chart") || lower.includes("graph")) {
1921
- return "entity-list";
1922
- }
1923
- if (lower.includes("timeline") || lower.includes("history") || lower.includes("activity")) {
1924
- return "entity-list";
1925
- }
1926
- if (lower.includes("title")) {
1927
- return "page-header";
1928
- }
1929
- return "entity-list";
1930
- }
1931
-
1932
- // src/orbitals/domain-language/parsers/guard-parser.ts
1933
- function parseGuard(text, entityName) {
1934
- const ctx = { entityName, errors: [] };
1935
- let expression = text.trim();
1936
- if (expression.toLowerCase().startsWith("if ")) {
1937
- expression = expression.slice(3).trim();
1938
- }
1939
- const condition = parseCondition(expression, ctx);
1940
- if (!condition) {
1941
- return {
1942
- success: false,
1943
- errors: ctx.errors.length > 0 ? ctx.errors : [{
1944
- message: `Failed to parse guard expression: "${text}"`
1945
- }],
1946
- warnings: []
1947
- };
1948
- }
1949
- return {
1950
- success: true,
1951
- data: {
1952
- type: "guard",
1953
- condition,
1954
- raw: text
1955
- },
1956
- errors: [],
1957
- warnings: []
1958
- };
1959
- }
1960
- function parseCondition(text, ctx) {
1961
- text = text.trim();
1962
- const andMatch = splitAtTopLevel(text, " AND ");
1963
- if (andMatch) {
1964
- const left = parseCondition(andMatch.left, ctx);
1965
- const right = parseCondition(andMatch.right, ctx);
1966
- if (left && right) {
1967
- return {
1968
- type: "logical",
1969
- operator: "AND",
1970
- left,
1971
- right
1972
- };
1973
- }
1974
- }
1975
- const orMatch = splitAtTopLevel(text, " OR ");
1976
- if (orMatch) {
1977
- const left = parseCondition(orMatch.left, ctx);
1978
- const right = parseCondition(orMatch.right, ctx);
1979
- if (left && right) {
1980
- return {
1981
- type: "logical",
1982
- operator: "OR",
1983
- left,
1984
- right
1985
- };
1986
- }
1987
- }
1988
- return parseUserCheck(text) || parseFieldCheck(text, ctx) || parseComparison(text, ctx);
1989
- }
1990
- function parseUserCheck(text, ctx) {
1991
- const lowerText = text.toLowerCase();
1992
- const roleMatch = lowerText.match(/^user\s+is\s+(\w+)$/);
1993
- if (roleMatch) {
1994
- return {
1995
- type: "user_check",
1996
- check: "is_role",
1997
- role: roleMatch[1]
1998
- };
1999
- }
2000
- if (lowerText === "user owns this") {
2001
- return {
2002
- type: "user_check",
2003
- check: "owns_this",
2004
- ownerField: "ownerId"
2005
- };
2006
- }
2007
- return null;
2008
- }
2009
- function parseFieldCheck(text, ctx) {
2010
- text.toLowerCase();
2011
- const providedMatch = text.match(/^(.+?)\s+is\s+provided$/i);
2012
- if (providedMatch) {
2013
- const field = parseFieldReference(providedMatch[1], ctx);
2014
- if (field) {
2015
- return {
2016
- type: "field_check",
2017
- field,
2018
- check: "provided"
2019
- };
2020
- }
2021
- }
2022
- const emptyMatch = text.match(/^(.+?)\s+is\s+empty$/i);
2023
- if (emptyMatch) {
2024
- const field = parseFieldReference(emptyMatch[1], ctx);
2025
- if (field) {
2026
- return {
2027
- type: "field_check",
2028
- field,
2029
- check: "empty"
2030
- };
2031
- }
2032
- }
2033
- const notEqualsMatch = text.match(/^(.+?)\s+is\s+not\s+(.+)$/i);
2034
- if (notEqualsMatch) {
2035
- return null;
2036
- }
2037
- const equalsMatch = text.match(/^(.+?)\s+is\s+(.+)$/i);
2038
- if (equalsMatch) {
2039
- const fieldName = equalsMatch[1].trim();
2040
- const value = equalsMatch[2].trim();
2041
- if (["provided", "empty", "not"].includes(value.toLowerCase())) {
2042
- return null;
2043
- }
2044
- const field = parseFieldReference(fieldName, ctx);
2045
- if (field) {
2046
- return {
2047
- type: "field_check",
2048
- field,
2049
- check: "equals",
2050
- value: parseValue2(value)
2051
- };
2052
- }
2053
- }
2054
- return null;
2055
- }
2056
- function parseComparison(text, ctx) {
2057
- const operators = [
2058
- { pattern: /^(.+?)\s*>=\s*(.+)$/, operator: ">=" },
2059
- { pattern: /^(.+?)\s*<=\s*(.+)$/, operator: "<=" },
2060
- { pattern: /^(.+?)\s*!=\s*(.+)$/, operator: "!=" },
2061
- { pattern: /^(.+?)\s*==\s*(.+)$/, operator: "==" },
2062
- { pattern: /^(.+?)\s*>\s*(.+)$/, operator: ">" },
2063
- { pattern: /^(.+?)\s*<\s*(.+)$/, operator: "<" }
2064
- ];
2065
- const isNotMatch = text.match(/^(.+?)\s+is\s+not\s+(.+)$/i);
2066
- if (isNotMatch) {
2067
- const field = parseFieldReference(isNotMatch[1], ctx);
2068
- if (field) {
2069
- return {
2070
- type: "comparison",
2071
- field,
2072
- operator: "!=",
2073
- value: parseValue2(isNotMatch[2])
2074
- };
2075
- }
2076
- }
2077
- for (const { pattern, operator } of operators) {
2078
- const match = text.match(pattern);
2079
- if (match) {
2080
- const field = parseFieldReference(match[1], ctx);
2081
- if (field) {
2082
- return {
2083
- type: "comparison",
2084
- field,
2085
- operator,
2086
- value: parseValue2(match[2])
2087
- };
2088
- }
2089
- }
2090
- }
2091
- return null;
2092
- }
2093
- function parseFieldReference(text, ctx) {
2094
- text = text.trim();
2095
- const dotMatch = text.match(/^(\w+)\.(\w+)$/);
2096
- if (dotMatch) {
2097
- return {
2098
- type: "field_reference",
2099
- entityName: dotMatch[1],
2100
- fieldName: dotMatch[2]
2101
- };
2102
- }
2103
- const fieldName = toCamelCase2(text);
2104
- return {
2105
- type: "field_reference",
2106
- entityName: ctx.entityName,
2107
- fieldName
2108
- };
2109
- }
2110
- function parseValue2(text) {
2111
- text = text.trim();
2112
- if (text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'")) {
2113
- return text.slice(1, -1);
2114
- }
2115
- if (text.toLowerCase() === "true") return true;
2116
- if (text.toLowerCase() === "false") return false;
2117
- const num = parseFloat(text);
2118
- if (!isNaN(num)) return num;
2119
- return text;
2120
- }
2121
- function toCamelCase2(text) {
2122
- return text.toLowerCase().split(/\s+/).map(
2123
- (word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
2124
- ).join("");
2125
- }
2126
- function splitAtTopLevel(text, separator) {
2127
- const separatorLower = separator.toLowerCase();
2128
- const textLower = text.toLowerCase();
2129
- let depth = 0;
2130
- for (let i = 0; i <= textLower.length - separatorLower.length; i++) {
2131
- const char = text[i];
2132
- if (char === "(") depth++;
2133
- else if (char === ")") depth--;
2134
- else if (depth === 0 && textLower.slice(i, i + separatorLower.length) === separatorLower) {
2135
- return {
2136
- left: text.slice(0, i),
2137
- right: text.slice(i + separator.length)
2138
- };
2139
- }
2140
- }
2141
- return null;
2142
- }
2143
- function isKnownPattern(pattern) {
2144
- return getPatternDefinition(pattern) !== null;
2145
- }
2146
- function parseDomainGuard(text, entityName) {
2147
- let expr = text.trim();
2148
- if (expr.toLowerCase().startsWith("if ")) {
2149
- expr = expr.slice(3).trim();
2150
- }
2151
- return parseExpression(expr);
2152
- }
2153
- function parseDomainEffect(text, entityName) {
2154
- let expr = text.trim();
2155
- if (expr.toLowerCase().startsWith("then ")) {
2156
- expr = expr.slice(5).trim();
2157
- }
2158
- return parseEffect(expr);
2159
- }
2160
- function isInlineSExpr(text) {
2161
- const trimmed = text.trim();
2162
- return trimmed.startsWith("[") || trimmed.startsWith("{") || trimmed.startsWith("(");
2163
- }
2164
- function parseInlineSExpr(text) {
2165
- const trimmed = text.trim();
2166
- if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
2167
- try {
2168
- return JSON.parse(trimmed);
2169
- } catch {
2170
- return parseRelaxedJson(trimmed);
2171
- }
2172
- }
2173
- if (trimmed.startsWith("(")) {
2174
- return parseLispSExpr(trimmed);
2175
- }
2176
- throw new Error(`Invalid inline S-Expression: ${text}`);
2177
- }
2178
- function parseRelaxedJson(text) {
2179
- let normalized = text;
2180
- normalized = normalized.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, '"$1"');
2181
- normalized = normalized.replace(/(\{|,)\s*(\w+)\s*:/g, '$1"$2":');
2182
- try {
2183
- return JSON.parse(normalized);
2184
- } catch (e) {
2185
- throw new Error(`Failed to parse relaxed JSON: ${text}`);
2186
- }
2187
- }
2188
- function parseLispSExpr(text) {
2189
- const trimmed = text.trim();
2190
- if (!trimmed.startsWith("(") || !trimmed.endsWith(")")) {
2191
- throw new Error(`Invalid Lisp S-Expression: ${text}`);
2192
- }
2193
- const inner = trimmed.slice(1, -1).trim();
2194
- const tokens = tokenizeLisp(inner);
2195
- if (tokens.length === 0) {
2196
- return [];
2197
- }
2198
- return tokens.map(parseLispToken);
2199
- }
2200
- function tokenizeLisp(text) {
2201
- const tokens = [];
2202
- let current = "";
2203
- let depth = 0;
2204
- let inString = false;
2205
- let stringChar = "";
2206
- for (let i = 0; i < text.length; i++) {
2207
- const char = text[i];
2208
- if (inString) {
2209
- current += char;
2210
- if (char === stringChar && text[i - 1] !== "\\") {
2211
- inString = false;
2212
- }
2213
- continue;
2214
- }
2215
- if (char === '"' || char === "'") {
2216
- inString = true;
2217
- stringChar = char;
2218
- current += char;
2219
- continue;
2220
- }
2221
- if (char === "(" || char === "[" || char === "{") {
2222
- depth++;
2223
- current += char;
2224
- continue;
2225
- }
2226
- if (char === ")" || char === "]" || char === "}") {
2227
- depth--;
2228
- current += char;
2229
- continue;
2230
- }
2231
- if (char === " " || char === " " || char === "\n") {
2232
- if (depth === 0 && current.trim()) {
2233
- tokens.push(current.trim());
2234
- current = "";
2235
- } else if (depth > 0) {
2236
- current += char;
2237
- }
2238
- continue;
2239
- }
2240
- current += char;
2241
- }
2242
- if (current.trim()) {
2243
- tokens.push(current.trim());
2244
- }
2245
- return tokens;
2246
- }
2247
- function parseLispToken(token) {
2248
- const trimmed = token.trim();
2249
- if (trimmed.startsWith("(")) {
2250
- return parseLispSExpr(trimmed);
2251
- }
2252
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
2253
- return parseRelaxedJson(trimmed);
2254
- }
2255
- if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
2256
- return trimmed.slice(1, -1);
2257
- }
2258
- if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
2259
- return parseFloat(trimmed);
2260
- }
2261
- if (trimmed === "true") return true;
2262
- if (trimmed === "false") return false;
2263
- if (trimmed === "null") return null;
2264
- if (trimmed.startsWith("@")) {
2265
- return trimmed;
2266
- }
2267
- return trimmed;
2268
- }
2269
- function parseExpression(text, ctx) {
2270
- text = text.trim();
2271
- if (isInlineSExpr(text)) {
2272
- return parseInlineSExpr(text);
2273
- }
2274
- const andMatch = splitAtTopLevel2(text, " and ");
2275
- if (andMatch) {
2276
- const left = parseExpression(andMatch.left);
2277
- const right = parseExpression(andMatch.right);
2278
- return ["and", left, right];
2279
- }
2280
- const orMatch = splitAtTopLevel2(text, " or ");
2281
- if (orMatch) {
2282
- const left = parseExpression(orMatch.left);
2283
- const right = parseExpression(orMatch.right);
2284
- return ["or", left, right];
2285
- }
2286
- if (text.toLowerCase().startsWith("not ")) {
2287
- const inner = parseExpression(text.slice(4).trim());
2288
- return ["not", inner];
2289
- }
2290
- if (text.startsWith("(") && text.endsWith(")")) {
2291
- return parseExpression(text.slice(1, -1));
2292
- }
2293
- return parseComparison2(text);
2294
- }
2295
- function parseComparison2(text, ctx) {
2296
- const atLeastMatch = text.match(/^(.+?)\s+is\s+at\s+least\s+(.+)$/i);
2297
- if (atLeastMatch) {
2298
- const field = parseFieldRef(atLeastMatch[1]);
2299
- const value = parseValue3(atLeastMatch[2]);
2300
- return [">=", field, value];
2301
- }
2302
- const atMostMatch = text.match(/^(.+?)\s+is\s+at\s+most\s+(.+)$/i);
2303
- if (atMostMatch) {
2304
- const field = parseFieldRef(atMostMatch[1]);
2305
- const value = parseValue3(atMostMatch[2]);
2306
- return ["<=", field, value];
2307
- }
2308
- const greaterThanMatch = text.match(/^(.+?)\s+is\s+greater\s+than\s+(.+)$/i);
2309
- if (greaterThanMatch) {
2310
- const field = parseFieldRef(greaterThanMatch[1]);
2311
- const value = parseValue3(greaterThanMatch[2]);
2312
- return [">", field, value];
2313
- }
2314
- const lessThanMatch = text.match(/^(.+?)\s+is\s+less\s+than\s+(.+)$/i);
2315
- if (lessThanMatch) {
2316
- const field = parseFieldRef(lessThanMatch[1]);
2317
- const value = parseValue3(lessThanMatch[2]);
2318
- return ["<", field, value];
2319
- }
2320
- const isNotMatch = text.match(/^(.+?)\s+is\s+not\s+(.+)$/i);
2321
- if (isNotMatch) {
2322
- const field = parseFieldRef(isNotMatch[1]);
2323
- const value = parseValue3(isNotMatch[2]);
2324
- return ["!=", field, value];
2325
- }
2326
- const isMatch = text.match(/^(.+?)\s+is\s+(.+)$/i);
2327
- if (isMatch) {
2328
- const field = parseFieldRef(isMatch[1]);
2329
- const value = parseValue3(isMatch[2]);
2330
- return ["=", field, value];
2331
- }
2332
- const opPatterns = [
2333
- { pattern: /^(.+?)\s*>=\s*(.+)$/, op: ">=" },
2334
- { pattern: /^(.+?)\s*<=\s*(.+)$/, op: "<=" },
2335
- { pattern: /^(.+?)\s*!=\s*(.+)$/, op: "!=" },
2336
- { pattern: /^(.+?)\s*==\s*(.+)$/, op: "=" },
2337
- { pattern: /^(.+?)\s*>\s*(.+)$/, op: ">" },
2338
- { pattern: /^(.+?)\s*<\s*(.+)$/, op: "<" }
2339
- ];
2340
- for (const { pattern, op } of opPatterns) {
2341
- const match = text.match(pattern);
2342
- if (match) {
2343
- const left = parseFieldRef(match[1]);
2344
- const right = parseValue3(match[2]);
2345
- return [op, left, right];
2346
- }
2347
- }
2348
- return parseFieldRef(text);
2349
- }
2350
- function parseEffect(text, ctx) {
2351
- text = text.trim();
2352
- if (isInlineSExpr(text)) {
2353
- return parseInlineSExpr(text);
2354
- }
2355
- const updateMatch = text.match(/^update\s+(.+?)\s+to\s+(.+)$/i);
2356
- if (updateMatch) {
2357
- const field = parseFieldRef(updateMatch[1]);
2358
- const value = parseEffectValue(updateMatch[2]);
2359
- return ["set", field, value];
2360
- }
2361
- const emitWithMatch = text.match(/^emit\s+(\S+)\s+with\s+(.+)$/i);
2362
- if (emitWithMatch) {
2363
- const event = emitWithMatch[1];
2364
- const payload = parseEffectValue(emitWithMatch[2]);
2365
- return ["emit", event, payload];
2366
- }
2367
- const emitMatch = text.match(/^emit\s+(\S+)$/i);
2368
- if (emitMatch) {
2369
- return ["emit", emitMatch[1]];
2370
- }
2371
- const renderNullMatch = text.match(/^render\s+null\s+to\s+(\S+)$/i);
2372
- if (renderNullMatch) {
2373
- return ["render-ui", renderNullMatch[1], null];
2374
- }
2375
- const renderFullMatch = text.match(/^render\s+(\S+)\s+to\s+(\S+)\s+for\s+(\S+)\s+with\s+(.+)$/i);
2376
- if (renderFullMatch) {
2377
- const pattern = renderFullMatch[1];
2378
- const slot = renderFullMatch[2];
2379
- const entity = renderFullMatch[3];
2380
- const propsText = renderFullMatch[4];
2381
- const props = parseRenderProps(propsText);
2382
- validatePatternType(pattern);
2383
- return ["render-ui", slot, { type: pattern, entity, ...props }];
2384
- }
2385
- const renderEntityMatch = text.match(/^render\s+(\S+)\s+to\s+(\S+)\s+for\s+(\S+)$/i);
2386
- if (renderEntityMatch) {
2387
- const pattern = renderEntityMatch[1];
2388
- const slot = renderEntityMatch[2];
2389
- const entity = renderEntityMatch[3];
2390
- validatePatternType(pattern);
2391
- return ["render-ui", slot, { type: pattern, entity }];
2392
- }
2393
- const renderPropsMatch = text.match(/^render\s+(\S+)\s+to\s+(\S+)\s+with\s+(.+)$/i);
2394
- if (renderPropsMatch) {
2395
- const pattern = renderPropsMatch[1];
2396
- const slot = renderPropsMatch[2];
2397
- const propsText = renderPropsMatch[3];
2398
- const props = parseRenderProps(propsText);
2399
- validatePatternType(pattern);
2400
- return ["render-ui", slot, { type: pattern, ...props }];
2401
- }
2402
- const renderMatch = text.match(/^render\s+(\S+)\s+to\s+(\S+)$/i);
2403
- if (renderMatch) {
2404
- const pattern = renderMatch[1];
2405
- const slot = renderMatch[2];
2406
- validatePatternType(pattern);
2407
- return ["render-ui", slot, { type: pattern }];
2408
- }
2409
- const renderSlotMatch = text.match(/^render\s+to\s+(\S+)$/i);
2410
- if (renderSlotMatch) {
2411
- return ["render-ui", renderSlotMatch[1]];
2412
- }
2413
- const navWithMatch = text.match(/^navigate\s+to\s+(.+?)\s+with\s+(.+)$/i);
2414
- if (navWithMatch) {
2415
- const path6 = navWithMatch[1];
2416
- const params = parseEffectValue(navWithMatch[2]);
2417
- return ["navigate", path6, params];
2418
- }
2419
- const navMatch = text.match(/^navigate\s+to\s+(.+)$/i);
2420
- if (navMatch) {
2421
- return ["navigate", navMatch[1]];
2422
- }
2423
- const showNotifyMatch = text.match(/^show\s+(\w+)\s+notification\s+"(.+)"$/i);
2424
- if (showNotifyMatch) {
2425
- return ["notify", showNotifyMatch[2], showNotifyMatch[1]];
2426
- }
2427
- const notifyMatch = text.match(/^notify\s+"(.+)"$/i);
2428
- if (notifyMatch) {
2429
- return ["notify", notifyMatch[1]];
2430
- }
2431
- const persistWithMatch = text.match(/^persist\s+(\w+)\s+(.+)$/i);
2432
- if (persistWithMatch) {
2433
- const action = persistWithMatch[1];
2434
- const data = parseFieldRef(persistWithMatch[2]);
2435
- return ["persist", action, data];
2436
- }
2437
- const persistMatch = text.match(/^persist\s+(\w+)$/i);
2438
- if (persistMatch) {
2439
- return ["persist", persistMatch[1]];
2440
- }
2441
- const spawnWithMatch = text.match(/^spawn\s+(\S+)\s+with\s+(.+)$/i);
2442
- if (spawnWithMatch) {
2443
- const entityType = spawnWithMatch[1];
2444
- const props = parseEffectValue(spawnWithMatch[2]);
2445
- return ["spawn", entityType, props];
2446
- }
2447
- const spawnMatch = text.match(/^spawn\s+(\S+)$/i);
2448
- if (spawnMatch) {
2449
- return ["spawn", spawnMatch[1]];
2450
- }
2451
- const despawnIdMatch = text.match(/^despawn\s+(.+)$/i);
2452
- if (despawnIdMatch && despawnIdMatch[1] !== "this") {
2453
- return ["despawn", parseFieldRef(despawnIdMatch[1])];
2454
- }
2455
- if (text.toLowerCase() === "despawn" || text.toLowerCase() === "despawn this") {
2456
- return ["despawn"];
2457
- }
2458
- const callServiceMatch = text.match(/^call\s+(\w+)\.(\w+)$/i);
2459
- if (callServiceMatch) {
2460
- return ["call-service", callServiceMatch[1], callServiceMatch[2]];
2461
- }
2462
- const callMatch = text.match(/^call\s+(\S+)$/i);
2463
- if (callMatch) {
2464
- return ["call-service", callMatch[1]];
2465
- }
2466
- return text;
2467
- }
2468
- function parseFieldRef(text, ctx) {
2469
- text = text.trim();
2470
- if (text.startsWith("@")) {
2471
- return text;
2472
- }
2473
- if (text.toLowerCase() === "current state") {
2474
- return "@state";
2475
- }
2476
- if (text.toLowerCase() === "current time") {
2477
- return "@now";
2478
- }
2479
- if (text.toLowerCase() === "entity") {
2480
- return "@entity";
2481
- }
2482
- if (text.toLowerCase() === "payload") {
2483
- return "@payload";
2484
- }
2485
- if (text.toLowerCase().startsWith("incoming ")) {
2486
- const field = toCamelCase3(text.slice(9).trim());
2487
- return `@payload.${field}`;
2488
- }
2489
- const possessiveMatch = text.match(/^(\w+)'s\s+(.+)$/);
2490
- if (possessiveMatch) {
2491
- const entity = possessiveMatch[1];
2492
- const field = toCamelCase3(possessiveMatch[2]);
2493
- return `@${entity}.${field}`;
2494
- }
2495
- if (text.includes(".")) {
2496
- const parts = text.split(".");
2497
- if (parts[0] && /^[A-Z]/.test(parts[0])) {
2498
- return `@${text}`;
2499
- }
2500
- return `@entity.${text}`;
2501
- }
2502
- const fieldName = toCamelCase3(text);
2503
- return `@entity.${fieldName}`;
2504
- }
2505
- function parseValue3(text) {
2506
- text = text.trim();
2507
- if (text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'")) {
2508
- return text.slice(1, -1);
2509
- }
2510
- if (text.toLowerCase() === "nothing" || text.toLowerCase() === "null") {
2511
- return null;
2512
- }
2513
- if (text.toLowerCase() === "true") return true;
2514
- if (text.toLowerCase() === "false") return false;
2515
- const num = parseFloat(text);
2516
- if (!isNaN(num) && text.match(/^-?\d+(\.\d+)?$/)) {
2517
- return num;
2518
- }
2519
- if (text.startsWith("@")) {
2520
- return text;
2521
- }
2522
- if (/^[a-zA-Z_]\w*$/.test(text)) {
2523
- return text;
2524
- }
2525
- return text;
2526
- }
2527
- function parseEffectValue(text, ctx) {
2528
- text = text.trim();
2529
- if (text.startsWith("{") && text.endsWith("}")) {
2530
- try {
2531
- const processed = text.replace(/@[\w.]+/g, (match) => `"${match}"`);
2532
- const obj = JSON.parse(processed);
2533
- return processBindingsInObject(obj);
2534
- } catch {
2535
- return text;
2536
- }
2537
- }
2538
- const arithmeticMatch = text.match(/^\((.+?)\s+(plus|minus|times|divided by)\s+(.+)\)$/i);
2539
- if (arithmeticMatch) {
2540
- const left = parseEffectValue(arithmeticMatch[1]);
2541
- const right = parseEffectValue(arithmeticMatch[3]);
2542
- const opMap = {
2543
- "plus": "+",
2544
- "minus": "-",
2545
- "times": "*",
2546
- "divided by": "/"
2547
- };
2548
- return [opMap[arithmeticMatch[2].toLowerCase()] || arithmeticMatch[2], left, right];
2549
- }
2550
- if (text.includes(".") || text.toLowerCase().startsWith("incoming ") || text.match(/^\w+'s\s+/) || /^[a-z]/.test(text)) {
2551
- return parseFieldRef(text);
2552
- }
2553
- return parseValue3(text);
2554
- }
2555
- function processBindingsInObject(obj) {
2556
- if (obj === null || obj === void 0) {
2557
- return null;
2558
- }
2559
- if (typeof obj === "string") {
2560
- if (obj.startsWith("@")) {
2561
- return obj;
2562
- }
2563
- return obj;
2564
- }
2565
- if (typeof obj !== "object") {
2566
- return obj;
2567
- }
2568
- if (Array.isArray(obj)) {
2569
- return obj.map(processBindingsInObject);
2570
- }
2571
- const result = {};
2572
- for (const [key, value] of Object.entries(obj)) {
2573
- result[key] = processBindingsInObject(value);
2574
- }
2575
- return result;
2576
- }
2577
- function splitAtTopLevel2(text, separator) {
2578
- const separatorLower = separator.toLowerCase();
2579
- const textLower = text.toLowerCase();
2580
- let depth = 0;
2581
- for (let i = 0; i <= textLower.length - separatorLower.length; i++) {
2582
- const char = text[i];
2583
- if (char === "(" || char === "[" || char === "{") depth++;
2584
- else if (char === ")" || char === "]" || char === "}") depth--;
2585
- else if (depth === 0 && textLower.slice(i, i + separatorLower.length) === separatorLower) {
2586
- return {
2587
- left: text.slice(0, i).trim(),
2588
- right: text.slice(i + separator.length).trim()
2589
- };
2590
- }
2591
- }
2592
- return null;
2593
- }
2594
- function toCamelCase3(text) {
2595
- const words = text.split(/\s+/);
2596
- if (words.length === 1) {
2597
- return words[0];
2598
- }
2599
- return words.map(
2600
- (word, index) => index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
2601
- ).join("");
2602
- }
2603
- function splitArrayItems(content) {
2604
- const items = [];
2605
- let current = "";
2606
- let inQuote = false;
2607
- let quoteChar = "";
2608
- for (let i = 0; i < content.length; i++) {
2609
- const char = content[i];
2610
- if ((char === "'" || char === '"') && (i === 0 || content[i - 1] !== "\\")) {
2611
- if (!inQuote) {
2612
- inQuote = true;
2613
- quoteChar = char;
2614
- } else if (char === quoteChar) {
2615
- inQuote = false;
2616
- }
2617
- current += char;
2618
- } else if (char === "," && !inQuote) {
2619
- if (current.trim()) {
2620
- items.push(current.trim());
2621
- }
2622
- current = "";
2623
- } else {
2624
- current += char;
2625
- }
2626
- }
2627
- if (current.trim()) {
2628
- items.push(current.trim());
2629
- }
2630
- return items;
2631
- }
2632
- function unquote(value) {
2633
- const trimmed = value.trim();
2634
- if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith('"') && trimmed.endsWith('"')) {
2635
- return trimmed.slice(1, -1);
2636
- }
2637
- return trimmed;
2638
- }
2639
- function parseArrayItem(item) {
2640
- const trimmed = item.trim();
2641
- const tabMatch = trimmed.match(/^(\w+):\s*['"]([^'"]+)['"]\s*->\s*(\w+)$/);
2642
- if (tabMatch) {
2643
- return { id: tabMatch[1], label: tabMatch[2], event: tabMatch[3] };
2644
- }
2645
- const actionVariantMatch = trimmed.match(/^([^->]+)\s*->\s*(\w+):(\w+)$/);
2646
- if (actionVariantMatch) {
2647
- return {
2648
- label: actionVariantMatch[1].trim(),
2649
- event: actionVariantMatch[2],
2650
- variant: actionVariantMatch[3]
2651
- };
2652
- }
2653
- const actionMatch = trimmed.match(/^(['"]?)([^'"->]+)\1\s*->\s*(\w+)$/);
2654
- if (actionMatch) {
2655
- return { label: actionMatch[2].trim(), event: actionMatch[3] };
2656
- }
2657
- const cellSpanMatch = trimmed.match(/^(\w+)\s*@\s*(\d+)x(\d+)$/);
2658
- if (cellSpanMatch) {
2659
- return {
2660
- section: cellSpanMatch[1],
2661
- colSpan: parseInt(cellSpanMatch[2], 10),
2662
- rowSpan: parseInt(cellSpanMatch[3], 10)
2663
- };
2664
- }
2665
- const cellMatch = trimmed.match(/^(\w+)\s*@\s*(\d+)$/);
2666
- if (cellMatch) {
2667
- return {
2668
- section: cellMatch[1],
2669
- colSpan: parseInt(cellMatch[2], 10)
2670
- };
2671
- }
2672
- const asMatch = trimmed.match(/^(\w+)\s+as\s+['"]([^'"]+)['"]$/);
2673
- if (asMatch) {
2674
- return { field: asMatch[1], label: asMatch[2] };
2675
- }
2676
- return unquote(trimmed);
2677
- }
2678
- function parseArrayProp(content) {
2679
- const items = splitArrayItems(content);
2680
- return items.map(parseArrayItem);
2681
- }
2682
- function hasComplexSyntax(content) {
2683
- return /\s+as\s+['"]/.test(content) || // "as" pattern
2684
- /->\s*\w+/.test(content) || // "->" pattern
2685
- /\w+\s*@\s*\d+/.test(content);
2686
- }
2687
- function parseRenderProps(text) {
2688
- const props = {};
2689
- const propsRegex = /(\w+)\s+(?:'([^']+)'|"([^"]+)"|\[([^\]]+)\]|(\S+))/g;
2690
- let match;
2691
- while ((match = propsRegex.exec(text)) !== null) {
2692
- const key = match[1];
2693
- const singleQuoted = match[2];
2694
- const doubleQuoted = match[3];
2695
- const arrayContent = match[4];
2696
- const bareValue = match[5];
2697
- if (singleQuoted !== void 0) {
2698
- props[key] = singleQuoted;
2699
- } else if (doubleQuoted !== void 0) {
2700
- props[key] = doubleQuoted;
2701
- } else if (arrayContent !== void 0) {
2702
- if (hasComplexSyntax(arrayContent)) {
2703
- props[key] = parseArrayProp(arrayContent);
2704
- } else {
2705
- props[key] = arrayContent.split(/\s*,\s*/).map((s) => s.trim());
2706
- }
2707
- } else if (bareValue !== void 0) {
2708
- if (bareValue === "true") props[key] = true;
2709
- else if (bareValue === "false") props[key] = false;
2710
- else if (/^\d+(\.\d+)?$/.test(bareValue)) props[key] = parseFloat(bareValue);
2711
- else props[key] = bareValue;
2712
- }
2713
- }
2714
- return props;
2715
- }
2716
- function validatePatternType(pattern) {
2717
- if (!isKnownPattern(pattern)) ;
2718
- }
2719
-
2720
- // src/orbitals/domain-language/parsers/behavior-parser.ts
2721
- function parseBehavior(text, entityName) {
2722
- const ctx = { entityName, errors: [], warnings: [] };
2723
- const lexer = new Lexer(text);
2724
- const tokens = lexer.tokenize();
2725
- let pos = 0;
2726
- const current = () => tokens[pos] || { type: "EOF" /* EOF */, value: "", line: 0, column: 0, offset: 0 };
2727
- const advance = () => tokens[pos++];
2728
- const isAtEnd = () => current().type === "EOF" /* EOF */;
2729
- const skip = (type) => {
2730
- while (current().type === type) advance();
2731
- };
2732
- skip("NEWLINE" /* NEWLINE */);
2733
- const behaviorName = parseBehaviorName();
2734
- if (!behaviorName) {
2735
- return {
2736
- success: false,
2737
- errors: ctx.errors.length > 0 ? ctx.errors : [{
2738
- message: "Expected behavior name at start of definition"
2739
- }],
2740
- warnings: []
2741
- };
2742
- }
2743
- const behavior = {
2744
- type: "behavior",
2745
- name: behaviorName,
2746
- entityName,
2747
- states: [],
2748
- initialState: "",
2749
- transitions: [],
2750
- ticks: [],
2751
- rules: []
2752
- };
2753
- skip("NEWLINE" /* NEWLINE */);
2754
- while (!isAtEnd()) {
2755
- skip("NEWLINE" /* NEWLINE */);
2756
- if (isAtEnd()) break;
2757
- const parsed = parseBehaviorSection(behavior);
2758
- if (!parsed) {
2759
- advance();
2760
- }
2761
- }
2762
- if (!behavior.initialState && behavior.states.length > 0) {
2763
- behavior.initialState = behavior.states[0];
2764
- }
2765
- return {
2766
- success: true,
2767
- data: behavior,
2768
- errors: ctx.errors,
2769
- warnings: ctx.warnings
2770
- };
2771
- function parseBehaviorName() {
2772
- const nameParts = [];
2773
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */) {
2774
- if (current().type === "IDENTIFIER" /* IDENTIFIER */ && current().value.toLowerCase() === "entity") {
2775
- advance();
2776
- if (current().type === "COLON" /* COLON */) {
2777
- advance();
2778
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
2779
- ctx.entityName = current().value;
2780
- advance();
2781
- }
2782
- }
2783
- continue;
2784
- }
2785
- if (current().type === "LPAREN" /* LPAREN */ || current().type === "RPAREN" /* RPAREN */) {
2786
- advance();
2787
- continue;
2788
- }
2789
- if (current().type === "IDENTIFIER" /* IDENTIFIER */ || current().type === "LIFECYCLE" /* LIFECYCLE */ || current().type === "AUTO" /* AUTO */) {
2790
- nameParts.push(current().value);
2791
- }
2792
- advance();
2793
- }
2794
- if (nameParts.length === 0) {
2795
- return null;
2796
- }
2797
- return nameParts.join(" ");
2798
- }
2799
- function parseBehaviorSection(behavior2) {
2800
- const token = current();
2801
- if (token.type === "IDENTIFIER" /* IDENTIFIER */ && token.value.toLowerCase() === "entity") {
2802
- advance();
2803
- skip("COLON" /* COLON */);
2804
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
2805
- ctx.entityName = current().value;
2806
- behavior2.entityName = current().value;
2807
- advance();
2808
- }
2809
- return true;
2810
- }
2811
- if (token.type === "STATES" /* STATES */) {
2812
- advance();
2813
- skip("COLON" /* COLON */);
2814
- parseStatesLine(behavior2);
2815
- return true;
2816
- }
2817
- if (token.type === "INITIAL" /* INITIAL */) {
2818
- advance();
2819
- skip("COLON" /* COLON */);
2820
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
2821
- behavior2.initialState = current().value;
2822
- advance();
2823
- }
2824
- return true;
2825
- }
2826
- if (token.type === "TRANSITIONS" /* TRANSITIONS */) {
2827
- advance();
2828
- skip("COLON" /* COLON */);
2829
- skip("NEWLINE" /* NEWLINE */);
2830
- parseTransitionsSection(behavior2);
2831
- return true;
2832
- }
2833
- if (token.type === "RULES" /* RULES */) {
2834
- advance();
2835
- skip("COLON" /* COLON */);
2836
- skip("NEWLINE" /* NEWLINE */);
2837
- parseRulesSection(behavior2);
2838
- return true;
2839
- }
2840
- if (token.type === "EVERY" /* EVERY */) {
2841
- advance();
2842
- parseTick(behavior2);
2843
- return true;
2844
- }
2845
- if (token.type === "LIFECYCLE" /* LIFECYCLE */) {
2846
- advance();
2847
- return true;
2848
- }
2849
- return false;
2850
- }
2851
- function parseStatesLine(behavior2) {
2852
- const states = [];
2853
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */) {
2854
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
2855
- states.push(current().value);
2856
- }
2857
- advance();
2858
- }
2859
- behavior2.states = states;
2860
- }
2861
- function parseTransitionsSection(behavior2) {
2862
- if (current().type === "INDENT" /* INDENT */) {
2863
- advance();
2864
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
2865
- skip("NEWLINE" /* NEWLINE */);
2866
- if (current().type === "DEDENT" /* DEDENT */) break;
2867
- if (current().type === "DASH" /* DASH */ || current().type === "FROM" /* FROM */) {
2868
- const transition = parseTransitionLine();
2869
- if (transition) {
2870
- behavior2.transitions.push(transition);
2871
- }
2872
- } else {
2873
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */ && current().type !== "DEDENT" /* DEDENT */) {
2874
- advance();
2875
- }
2876
- }
2877
- }
2878
- if (current().type === "DEDENT" /* DEDENT */) {
2879
- advance();
2880
- }
2881
- } else {
2882
- while (!isAtEnd() && current().type !== "RULES" /* RULES */ && current().type !== "EVERY" /* EVERY */) {
2883
- skip("NEWLINE" /* NEWLINE */);
2884
- if (current().type === "RULES" /* RULES */ || current().type === "EVERY" /* EVERY */ || isAtEnd()) break;
2885
- if (current().type === "DASH" /* DASH */ || current().type === "FROM" /* FROM */) {
2886
- const transition = parseTransitionLine();
2887
- if (transition) {
2888
- behavior2.transitions.push(transition);
2889
- }
2890
- } else {
2891
- advance();
2892
- }
2893
- }
2894
- }
2895
- }
2896
- function parseTransitionLine() {
2897
- if (current().type === "DASH" /* DASH */) {
2898
- advance();
2899
- }
2900
- if (current().type !== "FROM" /* FROM */) {
2901
- return null;
2902
- }
2903
- advance();
2904
- let fromState = "";
2905
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
2906
- fromState = current().value;
2907
- advance();
2908
- }
2909
- if (fromState.toLowerCase() === "any") {
2910
- fromState = "*";
2911
- }
2912
- if (current().type !== "TO" /* TO */) {
2913
- ctx.errors.push({ message: `Expected "to" in transition from "${fromState}"` });
2914
- return null;
2915
- }
2916
- advance();
2917
- let toState = "";
2918
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
2919
- toState = current().value;
2920
- advance();
2921
- }
2922
- let event = "";
2923
- if (current().type === "WHEN" /* WHEN */) {
2924
- advance();
2925
- if (current().type === "IDENTIFIER" /* IDENTIFIER */) {
2926
- event = current().value;
2927
- advance();
2928
- }
2929
- }
2930
- const transition = {
2931
- type: "transition",
2932
- fromState,
2933
- toState,
2934
- event: event.toUpperCase(),
2935
- guards: [],
2936
- effects: []
2937
- };
2938
- skip("NEWLINE" /* NEWLINE */);
2939
- if (current().type === "INDENT" /* INDENT */) {
2940
- advance();
2941
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
2942
- skip("NEWLINE" /* NEWLINE */);
2943
- if (current().type === "DEDENT" /* DEDENT */) break;
2944
- if (current().type === "IF" /* IF */) {
2945
- const guardText = collectLine();
2946
- const guardResult = parseGuard(guardText, entityName);
2947
- if (guardResult.success && guardResult.data) {
2948
- transition.guards.push(guardResult.data);
2949
- }
2950
- continue;
2951
- }
2952
- if (current().type === "THEN" /* THEN */) {
2953
- advance();
2954
- const effectText = collectUntilNewline();
2955
- const effect = parseEffectText(effectText);
2956
- if (effect) {
2957
- transition.effects.push(effect);
2958
- }
2959
- continue;
2960
- }
2961
- if (current().type !== "NEWLINE" /* NEWLINE */ && current().type !== "DEDENT" /* DEDENT */) {
2962
- advance();
2963
- }
2964
- skip("NEWLINE" /* NEWLINE */);
2965
- }
2966
- if (current().type === "DEDENT" /* DEDENT */) {
2967
- advance();
2968
- }
2969
- } else {
2970
- if (current().type === "IF" /* IF */) {
2971
- const guardText = collectLine();
2972
- const guardResult = parseGuard(guardText, entityName);
2973
- if (guardResult.success && guardResult.data) {
2974
- transition.guards.push(guardResult.data);
2975
- }
2976
- }
2977
- }
2978
- return transition;
2979
- }
2980
- function parseRulesSection(behavior2) {
2981
- if (current().type === "INDENT" /* INDENT */) {
2982
- advance();
2983
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
2984
- skip("NEWLINE" /* NEWLINE */);
2985
- if (current().type === "DEDENT" /* DEDENT */) break;
2986
- if (current().type === "DASH" /* DASH */) {
2987
- advance();
2988
- }
2989
- const rule = collectUntilNewline();
2990
- if (rule) {
2991
- behavior2.rules.push(rule);
2992
- }
2993
- skip("NEWLINE" /* NEWLINE */);
2994
- }
2995
- if (current().type === "DEDENT" /* DEDENT */) {
2996
- advance();
2997
- }
2998
- }
2999
- }
3000
- function parseTick(behavior2) {
3001
- const intervalParts = [];
3002
- while (!isAtEnd() && current().type !== "COLON" /* COLON */ && current().type !== "NEWLINE" /* NEWLINE */) {
3003
- intervalParts.push(current().value);
3004
- advance();
3005
- }
3006
- const interval = intervalParts.join(" ");
3007
- skip("COLON" /* COLON */);
3008
- skip("NEWLINE" /* NEWLINE */);
3009
- const tick = {
3010
- type: "tick",
3011
- name: `tick_${interval.replace(/\s+/g, "_").toLowerCase()}`,
3012
- interval,
3013
- intervalMs: parseInterval(interval),
3014
- effects: []
3015
- };
3016
- if (current().type === "INDENT" /* INDENT */) {
3017
- advance();
3018
- while (!isAtEnd() && current().type !== "DEDENT" /* DEDENT */) {
3019
- skip("NEWLINE" /* NEWLINE */);
3020
- if (current().type === "DEDENT" /* DEDENT */) break;
3021
- if (current().type === "IF" /* IF */) {
3022
- const guardText = collectLine();
3023
- const guardResult = parseGuard(guardText, entityName);
3024
- if (guardResult.success && guardResult.data) {
3025
- tick.guard = guardResult.data;
3026
- }
3027
- }
3028
- if (current().type === "THEN" /* THEN */) {
3029
- advance();
3030
- }
3031
- if (current().type === "DASH" /* DASH */) {
3032
- advance();
3033
- }
3034
- const effectText = collectUntilNewline();
3035
- if (effectText && current().type !== "IF" /* IF */) {
3036
- const effect = parseEffectText(effectText);
3037
- if (effect) {
3038
- tick.effects.push(effect);
3039
- }
3040
- }
3041
- skip("NEWLINE" /* NEWLINE */);
3042
- }
3043
- if (current().type === "DEDENT" /* DEDENT */) {
3044
- advance();
3045
- }
3046
- }
3047
- behavior2.ticks.push(tick);
3048
- }
3049
- function collectLine() {
3050
- return collectTokensSmartJoin();
3051
- }
3052
- function collectUntilNewline() {
3053
- return collectTokensSmartJoin();
3054
- }
3055
- function collectTokensSmartJoin() {
3056
- const parts = [];
3057
- while (!isAtEnd() && current().type !== "NEWLINE" /* NEWLINE */) {
3058
- parts.push({ value: current().value, type: current().type });
3059
- advance();
3060
- }
3061
- const startsWithBracket = parts.length > 0 && parts[0].type === "LBRACKET" /* LBRACKET */;
3062
- let result = "";
3063
- for (let i = 0; i < parts.length; i++) {
3064
- const part = parts[i];
3065
- const prev = parts[i - 1];
3066
- const noSpaceBefore = part.type === "DASH" /* DASH */ || part.type === "DOT" /* DOT */ || part.type === "COMMA" /* COMMA */ || part.type === "COLON" /* COLON */ || part.type === "RBRACKET" /* RBRACKET */ || part.type === "RPAREN" /* RPAREN */;
3067
- const prevNoSpaceAfter = prev && (prev.type === "DASH" /* DASH */ || prev.type === "DOT" /* DOT */ || prev.type === "LBRACKET" /* LBRACKET */ || prev.type === "LPAREN" /* LPAREN */);
3068
- if (i > 0 && !noSpaceBefore && !prevNoSpaceAfter) {
3069
- result += " ";
3070
- }
3071
- if (startsWithBracket && part.type === "STRING" /* STRING */) {
3072
- result += `"${part.value}"`;
3073
- } else {
3074
- result += part.value;
3075
- }
3076
- }
3077
- return result.trim();
3078
- }
3079
- function parseEffectText(text2) {
3080
- const trimmed = text2.trim();
3081
- const lower = trimmed.toLowerCase();
3082
- if (trimmed.startsWith("[")) {
3083
- try {
3084
- const parsed = JSON.parse(trimmed);
3085
- if (Array.isArray(parsed) && parsed.length > 0) {
3086
- const effectOp = String(parsed[0]);
3087
- return {
3088
- type: "effect",
3089
- effectType: effectOp,
3090
- description: trimmed,
3091
- config: { _rawSExpr: parsed }
3092
- // Store parsed S-expression
3093
- };
3094
- }
3095
- } catch {
3096
- }
3097
- }
3098
- const persistMatch = text2.match(/^persist\s+(\w+)(?:\s+(\w+))?$/i);
3099
- if (persistMatch) {
3100
- return {
3101
- type: "effect",
3102
- effectType: "persist",
3103
- description: text2,
3104
- config: {
3105
- dataAction: persistMatch[1].toLowerCase(),
3106
- entity: persistMatch[2] || void 0
3107
- }
3108
- };
3109
- }
3110
- const sendInAppMatch = text2.match(/^send_in_app\s+to\s+(.+?)\s+title\s+"([^"]+)"\s+message\s+"([^"]+)"(?:\s+type\s+(\w+))?$/i);
3111
- if (sendInAppMatch) {
3112
- return {
3113
- type: "effect",
3114
- effectType: "notify",
3115
- description: text2,
3116
- config: {
3117
- userId: sendInAppMatch[1],
3118
- title: sendInAppMatch[2],
3119
- message: sendInAppMatch[3],
3120
- type: sendInAppMatch[4] || "info"
3121
- }
3122
- };
3123
- }
3124
- const jsonMatch = text2.match(/^(\w+):(\{.+\})$/);
3125
- if (jsonMatch) {
3126
- try {
3127
- const config = JSON.parse(jsonMatch[2]);
3128
- return {
3129
- type: "effect",
3130
- effectType: jsonMatch[1],
3131
- description: text2,
3132
- config
3133
- };
3134
- } catch {
3135
- }
3136
- }
3137
- const notifyWithRecipientMatch = text2.match(/^notify\s+(\S+)\s+"([^"]+)"$/i);
3138
- if (notifyWithRecipientMatch) {
3139
- return {
3140
- type: "effect",
3141
- effectType: "notify",
3142
- description: text2,
3143
- config: {
3144
- recipient: notifyWithRecipientMatch[1],
3145
- message: notifyWithRecipientMatch[2]
3146
- }
3147
- };
3148
- }
3149
- if (lower.startsWith("notify")) {
3150
- const remaining = text2.slice(6).trim();
3151
- const quotedMatch = remaining.match(/^"([^"]+)"$/);
3152
- return {
3153
- type: "effect",
3154
- effectType: "notify",
3155
- description: text2,
3156
- config: {
3157
- message: quotedMatch ? quotedMatch[1] : remaining
3158
- }
3159
- };
3160
- }
3161
- const updateMatch = text2.match(/^update\s+(.+?)\s+to\s+(.+)$/i);
3162
- if (updateMatch) {
3163
- return {
3164
- type: "effect",
3165
- effectType: "set",
3166
- description: text2,
3167
- config: {
3168
- field: preserveTemplateVars(updateMatch[1]),
3169
- value: updateMatch[2]
3170
- }
3171
- };
3172
- }
3173
- const navMatch = text2.match(/^navigate\s+to\s+(.+)$/i);
3174
- if (navMatch) {
3175
- return {
3176
- type: "effect",
3177
- effectType: "navigate",
3178
- description: text2,
3179
- config: {
3180
- path: navMatch[1]
3181
- }
3182
- };
3183
- }
3184
- const emitMatch = text2.match(/^emit\s+(.+)$/i);
3185
- if (emitMatch) {
3186
- return {
3187
- type: "effect",
3188
- effectType: "emit",
3189
- description: text2,
3190
- config: {
3191
- eventKey: emitMatch[1].toUpperCase().replace(/\s+/g, "_")
3192
- }
3193
- };
3194
- }
3195
- const callMatch = text2.match(/^call\s+(.+)$/i);
3196
- if (callMatch) {
3197
- return {
3198
- type: "effect",
3199
- effectType: "call-service",
3200
- description: text2,
3201
- config: {
3202
- service: callMatch[1]
3203
- }
3204
- };
3205
- }
3206
- if (lower.startsWith("render ")) {
3207
- try {
3208
- const result = parseDomainEffect(text2);
3209
- if (Array.isArray(result) && result[0] === "render-ui") {
3210
- return {
3211
- type: "effect",
3212
- effectType: "render-ui",
3213
- description: text2,
3214
- config: {
3215
- slot: result[1],
3216
- pattern: result[2]
3217
- }
3218
- };
3219
- }
3220
- } catch {
3221
- }
3222
- }
3223
- ctx.warnings.push({
3224
- message: `Unknown effect syntax: "${text2}". Effect will be ignored. Use a valid effect format like "update X to Y", "emit EVENT", "navigate to /path", "notify 'message'", or "render pattern to slot".`
3225
- });
3226
- return null;
3227
- }
3228
- function parseInterval(text2) {
3229
- const lower = text2.toLowerCase();
3230
- const match = lower.match(/(\d+)\s*(second|minute|hour|day|week)s?/);
3231
- if (match) {
3232
- const value = parseInt(match[1], 10);
3233
- const unit = match[2];
3234
- switch (unit) {
3235
- case "second":
3236
- return value * 1e3;
3237
- case "minute":
3238
- return value * 60 * 1e3;
3239
- case "hour":
3240
- return value * 60 * 60 * 1e3;
3241
- case "day":
3242
- return value * 24 * 60 * 60 * 1e3;
3243
- case "week":
3244
- return value * 7 * 24 * 60 * 60 * 1e3;
3245
- }
3246
- }
3247
- return 60 * 60 * 1e3;
3248
- }
3249
- }
3250
- function formatBehaviorToSchema(behavior) {
3251
- const trait = {
3252
- // Just remove spaces, preserve the casing from the source
3253
- // Trust the LLM/author to use consistent PascalCase naming
3254
- name: behavior.name.replace(/\s+/g, ""),
3255
- description: behavior.name
3256
- };
3257
- if (behavior.states.length > 0) {
3258
- trait.stateMachine = {
3259
- states: behavior.states.map((state, index) => ({
3260
- name: state,
3261
- isInitial: state === behavior.initialState || index === 0 && !behavior.initialState
3262
- })),
3263
- events: extractEventsFromTransitions(behavior.transitions),
3264
- transitions: behavior.transitions.map((t) => {
3265
- const transition = {
3266
- from: t.fromState,
3267
- to: t.toState,
3268
- event: t.event
3269
- };
3270
- if (t.guards.length > 0) {
3271
- const guardExprs = t.guards.map((g) => {
3272
- if (g.raw) {
3273
- return parseDomainGuard(g.raw, behavior.entityName);
3274
- }
3275
- return parseDomainGuard(formatGuardToCondition(g), behavior.entityName);
3276
- });
3277
- transition.guard = guardExprs.length === 1 ? guardExprs[0] : ["and", ...guardExprs];
3278
- }
3279
- if (t.effects.length > 0) {
3280
- transition.effects = t.effects.map((e) => {
3281
- if (e.config && "_rawSExpr" in e.config && Array.isArray(e.config._rawSExpr)) {
3282
- return e.config._rawSExpr;
3283
- }
3284
- return parseDomainEffect(e.description, behavior.entityName);
3285
- });
3286
- }
3287
- return transition;
3288
- })
3289
- };
3290
- }
3291
- if (behavior.ticks.length > 0) {
3292
- trait.ticks = behavior.ticks.map((t) => ({
3293
- name: toPascalCase2(t.name.replace(/\s+/g, "")),
3294
- interval: t.intervalMs,
3295
- // Direct number in ms
3296
- guard: t.guard ? parseDomainGuard(t.guard.raw || formatGuardToCondition(t.guard), behavior.entityName) : void 0,
3297
- effects: t.effects.map((e) => {
3298
- if (e.config && "_rawSExpr" in e.config && Array.isArray(e.config._rawSExpr)) {
3299
- return e.config._rawSExpr;
3300
- }
3301
- return parseDomainEffect(e.description, behavior.entityName);
3302
- })
3303
- }));
3304
- }
3305
- return trait;
3306
- }
3307
- function toCamelCase4(text) {
3308
- return text.toLowerCase().split(/\s+/).map(
3309
- (word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
3310
- ).join("");
3311
- }
3312
- function toPascalCase2(text) {
3313
- if (!text.includes(" ")) {
3314
- return text.charAt(0).toUpperCase() + text.slice(1);
3315
- }
3316
- return text.split(/\s+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
3317
- }
3318
- function extractEventsFromTransitions(transitions) {
3319
- const events = /* @__PURE__ */ new Map();
3320
- for (const t of transitions) {
3321
- if (t.event && !events.has(t.event)) {
3322
- events.set(t.event, toTitleCase(t.event.replace(/_/g, " ")));
3323
- }
3324
- }
3325
- return Array.from(events.entries()).map(([key, name]) => ({ key, name }));
3326
- }
3327
- function toTitleCase(text) {
3328
- return text.toLowerCase().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3329
- }
3330
- function formatGuardToCondition(guard) {
3331
- if (guard.raw) {
3332
- return guard.raw.replace(/^if\s+/i, "");
3333
- }
3334
- return "true";
3335
- }
3336
- function preserveTemplateVars(text) {
3337
- if (text.includes("{") && text.includes("}")) {
3338
- return text.replace(/\s*\.\s*/g, ".");
3339
- }
3340
- const cleanedText = text.replace(/\s*\.\s*/g, ".");
3341
- if (cleanedText.includes(".")) {
3342
- return cleanedText;
3343
- }
3344
- return toCamelCase4(text);
3345
- }
3346
- function getPageName(page) {
3347
- if (isPageReferenceString(page)) {
3348
- const parts = page.split(".");
3349
- return parts[parts.length - 1];
3350
- }
3351
- if (isPageReferenceObject(page)) {
3352
- const parts = page.ref.split(".");
3353
- return parts[parts.length - 1];
3354
- }
3355
- return page.name;
3356
- }
3357
- function convertDomainToSchema(domainText, baseSchema) {
3358
- const errors = [];
3359
- const warnings = [];
3360
- const mappings = [];
3361
- const schema = baseSchema ? {
3362
- ...baseSchema,
3363
- orbitals: [...baseSchema.orbitals || []]
3364
- } : {
3365
- name: "Application",
3366
- orbitals: []
3367
- };
3368
- const sections = splitDomainDocument(domainText);
3369
- const parsedEntities = [];
3370
- const parsedPages = [];
3371
- const parsedTraits = [];
3372
- for (const text of sections.entities) {
3373
- const result = parseEntity(text);
3374
- if (result.success && result.data) {
3375
- const entityRecord = formatEntityToSchema(result.data);
3376
- const entity = {
3377
- name: entityRecord.name,
3378
- fields: entityRecord.fields || [],
3379
- persistence: "persistent"
3380
- };
3381
- parsedEntities.push({
3382
- name: result.data.name,
3383
- entity,
3384
- text
3385
- });
3386
- } else {
3387
- errors.push(...result.errors);
3388
- }
3389
- warnings.push(...result.warnings);
3390
- }
3391
- for (const text of sections.pages) {
3392
- const result = parsePage(text);
3393
- if (result.success && result.data) {
3394
- const pageRecord = formatPageToSchema(result.data);
3395
- const page = {
3396
- name: pageRecord.name,
3397
- path: pageRecord.path || "/",
3398
- primaryEntity: pageRecord.primaryEntity
3399
- };
3400
- if (result.data.traitName) {
3401
- page.traits = [{
3402
- ref: result.data.traitName,
3403
- linkedEntity: result.data.primaryEntity
3404
- }];
3405
- }
3406
- const forEntity = result.data.primaryEntity || page.primaryEntity;
3407
- parsedPages.push({
3408
- name: result.data.name,
3409
- page,
3410
- forEntity,
3411
- text
3412
- });
3413
- } else {
3414
- errors.push(...result.errors);
3415
- }
3416
- warnings.push(...result.warnings);
3417
- }
3418
- for (const text of sections.behaviors) {
3419
- if (text.trim().toLowerCase().startsWith("every")) {
3420
- parseTickFromDomain(text);
3421
- continue;
3422
- }
3423
- const result = parseBehavior(text, "");
3424
- if (result.success && result.data) {
3425
- const traitRecord = formatBehaviorToSchema(result.data);
3426
- const trait = {
3427
- name: traitRecord.name,
3428
- stateMachine: traitRecord.stateMachine
3429
- };
3430
- const forEntity = result.data.entityName || void 0;
3431
- parsedTraits.push({
3432
- name: result.data.name,
3433
- trait,
3434
- forEntity,
3435
- text
3436
- });
3437
- } else {
3438
- errors.push(...result.errors);
3439
- }
3440
- warnings.push(...result.warnings);
3441
- }
3442
- console.log(`[DomainToSchema] Parsed: ${parsedEntities.length} entities, ${parsedPages.length} pages, ${parsedTraits.length} traits`);
3443
- if (parsedEntities.length > 0) {
3444
- console.log(`[DomainToSchema] Entities: ${parsedEntities.map((e) => e.name).join(", ")}`);
3445
- }
3446
- if (parsedTraits.length > 0) {
3447
- console.log(`[DomainToSchema] Traits: ${parsedTraits.map((t) => t.name).join(", ")}`);
3448
- }
3449
- const traitToEntityMap = /* @__PURE__ */ new Map();
3450
- for (const { page } of parsedPages) {
3451
- if (page.traits && page.primaryEntity) {
3452
- for (const traitRef of page.traits) {
3453
- if (traitRef.ref) {
3454
- const linkedEntity = traitRef.linkedEntity || page.primaryEntity;
3455
- if (linkedEntity) {
3456
- traitToEntityMap.set(traitRef.ref.toLowerCase(), linkedEntity);
3457
- }
3458
- }
3459
- }
3460
- }
3461
- }
3462
- if (traitToEntityMap.size > 0) {
3463
- console.log(`[DomainToSchema] Page trait references: ${Array.from(traitToEntityMap.entries()).map(([t, e]) => `${t}\u2192${e}`).join(", ")}`);
3464
- } else {
3465
- console.log(`[DomainToSchema] No page trait references found (pages may be missing primaryEntity or traits)`);
3466
- }
3467
- const orbitals = [];
3468
- for (let i = 0; i < parsedEntities.length; i++) {
3469
- const { name, entity, text } = parsedEntities[i];
3470
- const entityPages = parsedPages.filter((p) => p.forEntity === name || p.page.primaryEntity === name).map((p) => p.page);
3471
- const entityTraits = parsedTraits.filter((t) => {
3472
- const traitNameLower = t.name.toLowerCase();
3473
- if (t.forEntity === name) {
3474
- console.log(`[DomainToSchema] \u2713 Trait "${t.name}" \u2192 Entity "${name}" (explicit Entity: syntax)`);
3475
- return true;
3476
- }
3477
- const linkedEntity = traitToEntityMap.get(traitNameLower);
3478
- if (linkedEntity === name) {
3479
- console.log(`[DomainToSchema] \u2713 Trait "${t.name}" \u2192 Entity "${name}" (page trait reference)`);
3480
- return true;
3481
- }
3482
- return false;
3483
- }).map((t) => t.trait.name);
3484
- const orbital = {
3485
- name,
3486
- entity,
3487
- traits: entityTraits,
3488
- pages: entityPages
3489
- };
3490
- orbitals.push(orbital);
3491
- mappings.push({
3492
- sectionId: `entity_${name}`,
3493
- sectionType: "entity",
3494
- schemaPath: `orbitals[${i}].entity`,
3495
- domainText: text
3496
- });
3497
- }
3498
- for (const { name, forEntity, text } of parsedPages) {
3499
- const orbitalIndex = parsedEntities.findIndex((e) => e.name === forEntity);
3500
- const pageIndex = orbitalIndex >= 0 ? orbitals[orbitalIndex].pages.findIndex((p) => getPageName(p) === name) : -1;
3501
- mappings.push({
3502
- sectionId: `page_${name}`,
3503
- sectionType: "page",
3504
- schemaPath: orbitalIndex >= 0 && pageIndex >= 0 ? `orbitals[${orbitalIndex}].pages[${pageIndex}]` : `orphanedPages`,
3505
- domainText: text
3506
- });
3507
- }
3508
- for (const { name, forEntity, text } of parsedTraits) {
3509
- const orbitalIndex = parsedEntities.findIndex((e) => e.name === forEntity);
3510
- const traitIndex = orbitalIndex >= 0 ? orbitals[orbitalIndex].traits.findIndex((t) => getTraitName(t) === name) : -1;
3511
- mappings.push({
3512
- sectionId: `behavior_${name}`,
3513
- sectionType: "behavior",
3514
- schemaPath: orbitalIndex >= 0 && traitIndex >= 0 ? `orbitals[${orbitalIndex}].traits[${traitIndex}]` : `traits`,
3515
- domainText: text
3516
- });
3517
- }
3518
- const assignedTraitNames = /* @__PURE__ */ new Set();
3519
- for (const orbital of orbitals) {
3520
- for (const traitRef of orbital.traits) {
3521
- const traitName = getTraitName(traitRef);
3522
- assignedTraitNames.add(traitName.toLowerCase());
3523
- }
3524
- }
3525
- console.log(`[DomainToSchema] ${parsedTraits.length} traits parsed, ${assignedTraitNames.size} assigned to orbitals`);
3526
- for (const orbital of orbitals) {
3527
- if (orbital.traits.length > 0) {
3528
- console.log(`[DomainToSchema] Orbital ${orbital.name}: ${orbital.traits.length} traits - ${orbital.traits.join(", ")}`);
3529
- }
3530
- }
3531
- schema.orbitals = orbitals;
3532
- return {
3533
- success: errors.length === 0,
3534
- schema,
3535
- errors,
3536
- warnings,
3537
- mappings
3538
- };
3539
- }
3540
- function splitDomainDocument(text) {
3541
- const result = {
3542
- entities: [],
3543
- pages: [],
3544
- behaviors: []
3545
- };
3546
- const normalizedText = text.replace(/\r\n/g, "\n");
3547
- let currentSection = null;
3548
- let currentContent = [];
3549
- const lines = normalizedText.split("\n");
3550
- for (const line of lines) {
3551
- const trimmed = line.trim().toLowerCase();
3552
- if (trimmed === "# entities" || trimmed === "#entities") {
3553
- flushSection();
3554
- currentSection = "entities";
3555
- continue;
3556
- }
3557
- if (trimmed === "# pages" || trimmed === "#pages") {
3558
- flushSection();
3559
- currentSection = "pages";
3560
- continue;
3561
- }
3562
- if (trimmed === "# behaviors" || trimmed === "#behaviors" || trimmed === "# traits" || trimmed === "#traits") {
3563
- flushSection();
3564
- currentSection = "behaviors";
3565
- continue;
3566
- }
3567
- currentContent.push(line);
3568
- }
3569
- flushSection();
3570
- if (result.entities.length === 0 && result.pages.length === 0 && result.behaviors.length === 0) {
3571
- return autoDetectSections(normalizedText);
3572
- }
3573
- return result;
3574
- function flushSection() {
3575
- if (!currentSection || currentContent.length === 0) {
3576
- currentContent = [];
3577
- return;
3578
- }
3579
- const content = currentContent.join("\n").trim();
3580
- if (content) {
3581
- let items = content.split(/\n---\n/).map((s) => s.trim()).filter((s) => s);
3582
- if (currentSection === "entities") {
3583
- items = splitEntitiesByPattern(items);
3584
- } else if (currentSection === "pages") {
3585
- items = splitPagesByPattern(items);
3586
- } else if (currentSection === "behaviors") {
3587
- items = splitBehaviorsByPattern(items);
3588
- }
3589
- result[currentSection].push(...items);
3590
- }
3591
- currentContent = [];
3592
- }
3593
- }
3594
- function splitEntitiesByPattern(items) {
3595
- const result = [];
3596
- for (const item of items) {
3597
- const lines = item.split("\n");
3598
- let currentEntity = [];
3599
- for (const line of lines) {
3600
- const trimmed = line.trim();
3601
- if (/^an?\s+\w+\s+is\s/i.test(trimmed) && currentEntity.length > 0) {
3602
- result.push(currentEntity.join("\n").trim());
3603
- currentEntity = [line];
3604
- } else {
3605
- currentEntity.push(line);
3606
- }
3607
- }
3608
- if (currentEntity.length > 0) {
3609
- result.push(currentEntity.join("\n").trim());
3610
- }
3611
- }
3612
- return result.filter((s) => s);
3613
- }
3614
- function splitPagesByPattern(items) {
3615
- const result = [];
3616
- for (const item of items) {
3617
- const lines = item.split("\n");
3618
- let currentPage = [];
3619
- for (const line of lines) {
3620
- const trimmed = line.trim();
3621
- if (/^\w+(?:page)?\s+at\s+\//i.test(trimmed) && currentPage.length > 0) {
3622
- result.push(currentPage.join("\n").trim());
3623
- currentPage = [line];
3624
- } else {
3625
- currentPage.push(line);
3626
- }
3627
- }
3628
- if (currentPage.length > 0) {
3629
- result.push(currentPage.join("\n").trim());
3630
- }
3631
- }
3632
- return result.filter((s) => s);
3633
- }
3634
- function splitBehaviorsByPattern(items) {
3635
- const result = [];
3636
- for (const item of items) {
3637
- const lines = item.split("\n");
3638
- let currentBehavior = [];
3639
- for (const line of lines) {
3640
- const trimmed = line.trim();
3641
- if ((/^\w+\s+behavior:?$/i.test(trimmed) || /^every\s+/i.test(trimmed)) && currentBehavior.length > 0) {
3642
- result.push(currentBehavior.join("\n").trim());
3643
- currentBehavior = [line];
3644
- } else {
3645
- currentBehavior.push(line);
3646
- }
3647
- }
3648
- if (currentBehavior.length > 0) {
3649
- result.push(currentBehavior.join("\n").trim());
3650
- }
3651
- }
3652
- return result.filter((s) => s);
3653
- }
3654
- function autoDetectSections(text) {
3655
- const result = {
3656
- entities: [],
3657
- pages: [],
3658
- behaviors: []
3659
- };
3660
- const sections = text.split(/\n---\n/).map((s) => s.trim()).filter((s) => s);
3661
- for (const section of sections) {
3662
- const firstLine = section.split("\n")[0].toLowerCase();
3663
- if (firstLine.match(/^an?\s+\w+\s+is/)) {
3664
- result.entities.push(section);
3665
- } else if (firstLine.match(/^the\s+\w+.*shows/)) {
3666
- result.pages.push(section);
3667
- } else if (firstLine.match(/^every\s+/)) {
3668
- result.behaviors.push(section);
3669
- } else if (section.toLowerCase().includes("states:") || section.toLowerCase().includes("transitions:")) {
3670
- result.behaviors.push(section);
3671
- } else if (section.toLowerCase().includes("it has:")) {
3672
- result.entities.push(section);
3673
- } else if (section.toLowerCase().includes("it displays:")) {
3674
- result.pages.push(section);
3675
- } else {
3676
- result.entities.push(section);
3677
- }
3678
- }
3679
- return result;
3680
- }
3681
- function parseTickFromDomain(text) {
3682
- const lines = text.trim().split("\n");
3683
- if (lines.length === 0) return null;
3684
- const firstLine = lines[0].trim();
3685
- const intervalMatch = firstLine.match(/^every\s+(.+?):?$/i);
3686
- if (!intervalMatch) return null;
3687
- const intervalStr = intervalMatch[1];
3688
- const intervalMs = parseIntervalToMs(intervalStr);
3689
- const tickName = intervalStr.split(/\s+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
3690
- const tick = {
3691
- name: tickName,
3692
- interval: intervalMs,
3693
- effects: []
3694
- // S-Expression arrays
3695
- };
3696
- for (let i = 1; i < lines.length; i++) {
3697
- const line = lines[i].trim();
3698
- if (line.toLowerCase().startsWith("if ")) {
3699
- tick.guard = line.slice(3).trim();
3700
- } else if (line.startsWith("-")) {
3701
- const effectText = line.slice(1).trim();
3702
- const effect = parseEffectFromText(effectText);
3703
- if (effect) {
3704
- tick.effects.push(effect);
3705
- }
3706
- }
3707
- }
3708
- return tick;
3709
- }
3710
- function parseIntervalToMs(text) {
3711
- const lower = text.toLowerCase();
3712
- const match = lower.match(/(\d+)\s*(second|minute|hour|day|week)s?/);
3713
- if (match) {
3714
- const value = parseInt(match[1], 10);
3715
- const unit = match[2];
3716
- switch (unit) {
3717
- case "second":
3718
- return value * 1e3;
3719
- case "minute":
3720
- return value * 60 * 1e3;
3721
- case "hour":
3722
- return value * 60 * 60 * 1e3;
3723
- case "day":
3724
- return value * 24 * 60 * 60 * 1e3;
3725
- case "week":
3726
- return value * 7 * 24 * 60 * 60 * 1e3;
3727
- }
3728
- }
3729
- return 60 * 60 * 1e3;
3730
- }
3731
- function parseEffectFromText(text) {
3732
- try {
3733
- const result = parseDomainEffect(text);
3734
- if (Array.isArray(result)) {
3735
- return result;
3736
- }
3737
- return null;
3738
- } catch {
3739
- return null;
730
+ success: false,
731
+ error: error instanceof Error ? error.message : String(error),
732
+ stats
733
+ };
3740
734
  }
3741
735
  }
3742
736
 
737
+ // src/orbitals/domain-language/index.ts
738
+ var domain_language_exports = {};
739
+ __export(domain_language_exports, {
740
+ ODL_EXAMPLES: () => ODL_EXAMPLES,
741
+ ODL_PATTERNS: () => ODL_PATTERNS,
742
+ ODL_SYNTAX_REFERENCE: () => ODL_SYNTAX_REFERENCE,
743
+ ODL_TO_SCHEMA_MAPPING: () => ODL_TO_SCHEMA_MAPPING
744
+ });
745
+ __reExport(domain_language_exports, domain_language_star);
746
+
747
+ // src/orbitals/domain-language/prompts/odl-syntax.ts
748
+ var ODL_SYNTAX_REFERENCE = `
749
+ ## Domain Language Syntax
750
+
751
+ ### Entity Definition
752
+
753
+ \`\`\`
754
+ A [EntityName] is a [persistence] entity that:
755
+ - has [fieldName] as [type] (required|optional)
756
+ - has [fieldName] as [type] with default [value]
757
+ - belongs to [RelatedEntity]
758
+ - belongs to [RelatedEntity] as [alias]
759
+ - has many [RelatedEntity]s
760
+ \`\`\`
761
+
762
+ **Persistence types:**
763
+ - \`persistent\` - Stored in database (default)
764
+ - \`runtime\` - Memory only, for games/UI state
765
+ - \`singleton\` - Single instance, for config
766
+
767
+ **Field types:**
768
+ - \`text\` / \`long text\` - String fields
769
+ - \`number\` / \`currency\` - Numeric fields
770
+ - \`yes/no\` - Boolean fields
771
+ - \`date\` / \`timestamp\` / \`datetime\` - Date fields
772
+ - \`enum [val1, val2]\` - Enumeration
773
+ - \`list\` / \`object\` - Complex types
774
+
775
+ ### Page Definition
776
+
777
+ \`\`\`
778
+ [PageName] at /[path]:
779
+ - shows [Entity] using [TraitName]
780
+ - view type: [list|detail|create|edit|dashboard]
781
+ - is initial page
782
+ \`\`\`
783
+
784
+ ### Behavior Definition
785
+
786
+ \`\`\`
787
+ [BehaviorName] behavior:
788
+ States: [State1], [State2], [State3]
789
+ Initial: [State1]
790
+
791
+ Transitions:
792
+ - From [State1] to [State2] on [EVENT_NAME]
793
+ if [guard condition]
794
+ then [effect1]
795
+ then [effect2]
796
+ \`\`\`
797
+
798
+ ### Section Headers
799
+
800
+ Domain Language text is organized into sections:
801
+
802
+ \`\`\`
803
+ # Entities
804
+ [entity definitions]
805
+
806
+ # Pages
807
+ [page definitions]
808
+
809
+ # Behaviors
810
+ [behavior/trait definitions]
811
+ \`\`\`
812
+ `;
813
+
814
+ // src/orbitals/domain-language/prompts/odl-examples.ts
815
+ var ODL_EXAMPLES = `
816
+ ## Complete Example
817
+
818
+ ### Entity
819
+
820
+ \`\`\`
821
+ A Task is a persistent entity that:
822
+ - has title as text (required)
823
+ - has description as long text
824
+ - has status as enum [todo, in_progress, done] with default "todo"
825
+ - has dueDate as date
826
+ - belongs to User as assignee
827
+ - belongs to Project
828
+ \`\`\`
829
+
830
+ ### Page
831
+
832
+ \`\`\`
833
+ TasksPage at /tasks:
834
+ - shows Task using TaskManagement
835
+ - view type: list
836
+ - is initial page
837
+ \`\`\`
838
+
839
+ ### Behavior
840
+
841
+ \`\`\`
842
+ TaskManagement behavior:
843
+ States: Viewing, Editing, Creating
844
+ Initial: Viewing
845
+
846
+ Transitions:
847
+ - From Viewing to Viewing on INIT
848
+ then render page-header to main with title 'Tasks'
849
+ then render entity-table to center for Task
850
+
851
+ - From Viewing to Editing on EDIT_TASK
852
+ then render form-section to modal for Task
853
+
854
+ - From Editing to Viewing on SAVE_TASK
855
+ then persist update Task
856
+ then render null to modal
857
+ then emit TASK_UPDATED
858
+ \`\`\`
859
+
860
+ ## Multi-Entity Example
861
+
862
+ \`\`\`
863
+ # Entities
864
+
865
+ A User is a persistent entity that:
866
+ - has name as text (required)
867
+ - has email as text (required)
868
+ - has role as enum [admin, user, guest] with default "user"
869
+
870
+ A Project is a persistent entity that:
871
+ - has name as text (required)
872
+ - has description as long text
873
+ - has status as enum [active, archived] with default "active"
874
+ - belongs to User as owner
875
+
876
+ A Task is a persistent entity that:
877
+ - has title as text (required)
878
+ - has status as enum [todo, in_progress, done] with default "todo"
879
+ - has priority as number with default 0
880
+ - belongs to Project
881
+ - belongs to User as assignee
882
+
883
+ # Pages
884
+
885
+ ProjectsPage at /projects:
886
+ - shows Project using ProjectManagement
887
+ - view type: list
888
+ - is initial page
889
+
890
+ TasksPage at /projects/:projectId/tasks:
891
+ - shows Task using TaskManagement
892
+ - view type: list
893
+
894
+ # Behaviors
895
+
896
+ ProjectManagement behavior:
897
+ States: List, Detail
898
+ Initial: List
899
+
900
+ Transitions:
901
+ - From List to List on INIT
902
+ then render entity-table to main for Project
903
+
904
+ - From List to Detail on VIEW_PROJECT
905
+ then render entity-detail to drawer for Project
906
+
907
+ TaskManagement behavior:
908
+ States: Viewing, Editing
909
+ Initial: Viewing
910
+
911
+ Transitions:
912
+ - From Viewing to Viewing on INIT
913
+ then ["render-ui", "main", {"type": "entity-table", "entity": "Task", "itemActions": [{"label": "Edit", "event": "EDIT_TASK"}, {"label": "Delete", "event": "DELETE_TASK", "variant": "danger"}]}]
914
+
915
+ - From Viewing to Editing on EDIT_TASK
916
+ then render form-section to modal for Task
917
+
918
+ - From Editing to Viewing on SAVE_TASK
919
+ then persist update Task
920
+ then render null to modal
921
+ \`\`\`
922
+
923
+ ## Dashboard Example (Complex Patterns)
924
+
925
+ \`\`\`
926
+ Dashboard behavior:
927
+ States: Viewing
928
+ Initial: Viewing
929
+
930
+ Transitions:
931
+ - From Viewing to Viewing on INIT
932
+ then render page-header to main with title 'Dashboard'
933
+ then ["render-ui", "center", {"type": "stats", "metrics": [{"field": "totalTasks", "label": "Total Tasks"}, {"field": "completedTasks", "label": "Completed"}, {"field": "overdueTasks", "label": "Overdue"}]}]
934
+ then ["render-ui", "bottom", {"type": "tabs", "tabs": [{"id": "overview", "label": "Overview", "event": "VIEW_OVERVIEW"}, {"id": "recent", "label": "Recent Activity", "event": "VIEW_RECENT"}]}]
935
+ \`\`\`
936
+
937
+ **Note**: Use inline JSON for \`render-ui\` effects with complex props (metrics, tabs, itemActions, cells).
938
+ `;
939
+
940
+ // src/orbitals/domain-language/prompts/odl-patterns.ts
941
+ var ODL_PATTERNS = `
942
+ ## Common Patterns
943
+
944
+ ### CRUD Pattern
945
+
946
+ \`\`\`
947
+ [Entity]Management behavior:
948
+ States: List, Detail, Create, Edit
949
+ Initial: List
950
+
951
+ Transitions:
952
+ - From List to List on INIT
953
+ then render entity-table to main for [Entity]
954
+
955
+ - From List to Create on CREATE_[ENTITY]
956
+ then render form-section to modal for [Entity]
957
+
958
+ - From Create to List on SUBMIT_CREATE
959
+ then persist create [Entity]
960
+ then render null to modal
961
+
962
+ - From List to Detail on VIEW_[ENTITY]
963
+ then render entity-detail to drawer for [Entity]
964
+
965
+ - From Detail to Edit on EDIT_[ENTITY]
966
+ then render form-section to modal for [Entity]
967
+
968
+ - From Edit to Detail on SUBMIT_EDIT
969
+ then persist update [Entity]
970
+ then render null to modal
971
+
972
+ - From Detail to List on DELETE_[ENTITY]
973
+ then persist delete [Entity]
974
+ then render null to drawer
975
+ \`\`\`
976
+
977
+ ### Game Health Pattern
978
+
979
+ \`\`\`
980
+ Health behavior:
981
+ States: Healthy, Wounded, Critical, Dead
982
+ Initial: Healthy
983
+
984
+ Transitions:
985
+ - From Healthy to Wounded on TAKE_DAMAGE
986
+ if health >= 30 and health < 70
987
+ then update status to 'wounded'
988
+
989
+ - From Wounded to Critical on TAKE_DAMAGE
990
+ if health < 30
991
+ then update status to 'critical'
992
+
993
+ - From Critical to Dead on TAKE_DAMAGE
994
+ if health <= 0
995
+ then update isAlive to false
996
+ then emit ENTITY_DIED
997
+ \`\`\`
998
+
999
+ ### Approval Workflow Pattern
1000
+
1001
+ \`\`\`
1002
+ ApprovalWorkflow behavior:
1003
+ States: Draft, Pending, Approved, Rejected
1004
+ Initial: Draft
1005
+
1006
+ Transitions:
1007
+ - From Draft to Pending on SUBMIT
1008
+ then update status to 'pending'
1009
+ then emit APPROVAL_REQUESTED
1010
+
1011
+ - From Pending to Approved on APPROVE
1012
+ then update status to 'approved'
1013
+ then update approvedAt to now
1014
+ then emit APPROVAL_GRANTED
1015
+
1016
+ - From Pending to Rejected on REJECT
1017
+ then update status to 'rejected'
1018
+ then emit APPROVAL_DENIED
1019
+
1020
+ - From Rejected to Draft on REVISE
1021
+ then update status to 'draft'
1022
+ \`\`\`
1023
+
1024
+ ### Form Validation Pattern
1025
+
1026
+ \`\`\`
1027
+ FormValidation behavior:
1028
+ States: Idle, Validating, Valid, Invalid
1029
+ Initial: Idle
1030
+
1031
+ Transitions:
1032
+ - From Idle to Validating on VALIDATE
1033
+ then update isValidating to true
1034
+
1035
+ - From Validating to Valid on VALIDATION_SUCCESS
1036
+ then update isValidating to false
1037
+ then update errors to null
1038
+
1039
+ - From Validating to Invalid on VALIDATION_FAILED
1040
+ then update isValidating to false
1041
+
1042
+ - From Invalid to Validating on VALIDATE
1043
+ then update isValidating to true
1044
+
1045
+ - From Valid to Idle on RESET
1046
+ then update errors to null
1047
+ \`\`\`
1048
+
1049
+ ### Dashboard Pattern
1050
+
1051
+ \`\`\`
1052
+ DashboardView behavior:
1053
+ States: Loading, Ready
1054
+ Initial: Loading
1055
+
1056
+ Transitions:
1057
+ - From Loading to Ready on DATA_LOADED
1058
+ then render stats to main
1059
+ then render entity-cards to center
1060
+
1061
+ - From Ready to Loading on REFRESH
1062
+ then emit FETCH_DASHBOARD_DATA
1063
+ \`\`\`
1064
+ `;
1065
+
1066
+ // src/orbitals/domain-language/prompts/odl-to-schema.ts
1067
+ var ODL_TO_SCHEMA_MAPPING = `
1068
+ ## Domain Language -> OrbitalSchema Mapping
1069
+
1070
+ | Domain Language | OrbitalSchema |
1071
+ |-----------------|---------------|
1072
+ | "A Task is..." | \`{ "entity": { "name": "Task", ... } }\` |
1073
+ | "persistent entity" | \`"persistence": "persistent"\` |
1074
+ | "runtime entity" | \`"persistence": "runtime"\` |
1075
+ | "singleton entity" | \`"persistence": "singleton"\` |
1076
+ | "has X as text" | \`{ "name": "X", "type": "string" }\` |
1077
+ | "has X as number" | \`{ "name": "X", "type": "number" }\` |
1078
+ | "has X as yes/no" | \`{ "name": "X", "type": "boolean" }\` |
1079
+ | "has X as enum [a, b]" | \`{ "name": "X", "type": "enum", "values": ["a", "b"] }\` |
1080
+ | "belongs to Y" | \`{ "type": "relation", "relation": { "entity": "Y" } }\` |
1081
+ | "belongs to Y as alias" | \`{ "name": "alias", "type": "relation", "relation": { "entity": "Y" } }\` |
1082
+ | "States: A, B" | \`"states": [{ "name": "A" }, { "name": "B" }]\` |
1083
+ | "Initial: A" | \`{ "name": "A", "isInitial": true }\` |
1084
+ | "From X to Y on Z" | \`{ "from": "X", "to": "Y", "event": "Z" }\` |
1085
+ | "if health >= 0" | \`"guard": [">=", "@entity.health", 0]\` |
1086
+ | "then update status to 'done'" | \`"effects": [["set", "@entity.status", "done"]]\` |
1087
+ | "then emit EVENT" | \`"effects": [["emit", "EVENT"]]\` |
1088
+ | "then render X to slot" | \`"effects": [["render-ui", "slot", { "type": "X" }]]\` |
1089
+ | "then persist create Task" | \`"effects": [["persist", "create", "Task"]]\` |
1090
+ | "then navigate to /path" | \`"effects": [["navigate", "/path"]]\` |
1091
+
1092
+ ## Conversion Process
1093
+
1094
+ 1. **Parse Domain Language** - Split into sections (Entities, Pages, Behaviors)
1095
+ 2. **Extract entities** - Parse "A X is..." definitions
1096
+ 3. **Extract pages** - Parse "[Name] at /path" definitions
1097
+ 4. **Extract behaviors** - Parse "[Name] behavior:" definitions
1098
+ 5. **Parse guards** - Convert human-readable conditions to S-expressions
1099
+ 6. **Parse effects** - Convert human-readable effects to S-expressions
1100
+ 7. **Assemble schema** - Build OrbitalSchema JSON structure
1101
+
1102
+ ## CLI Commands
1103
+
1104
+ \`\`\`bash
1105
+ # Convert Domain Language to OrbitalSchema
1106
+ npx kflow domain:to-schema input.txt -o output.orb
1107
+
1108
+ # Convert OrbitalSchema to Domain Language
1109
+ npx kflow domain:to-text input.orb -o output.txt
1110
+
1111
+ # Validate round-trip conversion
1112
+ npx kflow domain:validate input.orb --verbose
1113
+ \`\`\`
1114
+ `;
1115
+
3743
1116
  // src/tools/finish-task.ts
3744
1117
  var execAsync2 = promisify(exec);
3745
1118
  async function collectOrbitalsFromDir(workDir) {
@@ -3816,7 +1189,7 @@ function createFinishTaskTool(workDir) {
3816
1189
  if (domainText.trim()) {
3817
1190
  source = "domain";
3818
1191
  const appName = input.summary?.match(/Generated \d+ orbitals?:?\s*(.+)/)?.[1] || "Application";
3819
- const domainResult = convertDomainToSchema(domainText, {
1192
+ const domainResult = (0, domain_language_exports.convertDomainToSchema)(domainText, {
3820
1193
  name: appName,
3821
1194
  orbitals: []
3822
1195
  });
@@ -4271,7 +1644,7 @@ function createConstructCombinedDomainTool(options = {}) {
4271
1644
  role: "assistant",
4272
1645
  isComplete: false
4273
1646
  });
4274
- const schemaResult = convertDomainToSchema(domainText, {
1647
+ const schemaResult = (0, domain_language_exports.convertDomainToSchema)(domainText, {
4275
1648
  name: appName,
4276
1649
  orbitals: []
4277
1650
  });
@@ -5405,6 +2778,282 @@ function createSchemaChunkingTools(workDir) {
5405
2778
  applyChunk: createApplyChunkTool(workDir)
5406
2779
  };
5407
2780
  }
2781
+ var designCache = /* @__PURE__ */ new Map();
2782
+ var CACHE_TTL_MS2 = 24 * 60 * 60 * 1e3;
2783
+ var CACHE_VERSION2 = 1;
2784
+ function generateFingerprint2(input) {
2785
+ const normalized = JSON.stringify({
2786
+ version: CACHE_VERSION2,
2787
+ ...input
2788
+ });
2789
+ return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 16);
2790
+ }
2791
+ function getCached2(fingerprint) {
2792
+ const entry = designCache.get(fingerprint);
2793
+ if (!entry) return null;
2794
+ if (Date.now() - entry.timestamp > CACHE_TTL_MS2) {
2795
+ designCache.delete(fingerprint);
2796
+ return null;
2797
+ }
2798
+ return entry;
2799
+ }
2800
+ var STATIC_DESIGN_PROMPT = null;
2801
+ function getDesignSystemPrompt() {
2802
+ if (!STATIC_DESIGN_PROMPT) {
2803
+ const skill = generateKflowDesignSkill();
2804
+ STATIC_DESIGN_PROMPT = skill.content;
2805
+ }
2806
+ return STATIC_DESIGN_PROMPT;
2807
+ }
2808
+ function getPatternRecommendations(input) {
2809
+ const recContext = buildRecommendationContext({
2810
+ state: input.from,
2811
+ event: input.event,
2812
+ slot: input.slot,
2813
+ domainCategory: input.domainCategory,
2814
+ entityFields: input.entityFields
2815
+ });
2816
+ return recommendPatterns(recContext, 8);
2817
+ }
2818
+ function buildDesignUserPrompt(input) {
2819
+ const fieldList = input.entityFields.map((f) => {
2820
+ let desc = ` - ${f.name}: ${f.type}`;
2821
+ if (f.values) desc += ` (values: ${f.values.join(", ")})`;
2822
+ return desc;
2823
+ }).join("\n");
2824
+ const hints = [];
2825
+ if (input.designStyle) hints.push(`Style: ${input.designStyle}`);
2826
+ if (input.flowPattern) hints.push(`Flow: ${input.flowPattern}`);
2827
+ if (input.listPattern) hints.push(`List: ${input.listPattern}`);
2828
+ if (input.formPattern) hints.push(`Form: ${input.formPattern}`);
2829
+ if (input.detailPattern) hints.push(`Detail: ${input.detailPattern}`);
2830
+ const vocab = input.vocabulary ? Object.entries(input.vocabulary).map(([k, v]) => ` ${k} \u2192 "${v}"`).join("\n") : "";
2831
+ return `Design render-ui effects for this transition:
2832
+
2833
+ ## Transition
2834
+ - **From**: ${input.from}
2835
+ - **To**: ${input.to}
2836
+ - **Event**: ${input.event}
2837
+ - **Slot**: ${input.slot}
2838
+
2839
+ ## Entity: ${input.entityName}
2840
+ ${fieldList}
2841
+
2842
+ ## Domain
2843
+ - **Category**: ${input.domainCategory || "business"}
2844
+ ${vocab ? `- **Vocabulary**:
2845
+ ${vocab}` : ""}
2846
+ ${hints.length > 0 ? `- **Design Hints**: ${hints.join(", ")}` : ""}
2847
+
2848
+ ${input.recommendationsSection || ""}
2849
+
2850
+ ${input.existingEffects ? `## Existing Effects (enhance these)
2851
+ \`\`\`json
2852
+ ${JSON.stringify(input.existingEffects, null, 2)}
2853
+ \`\`\`` : ""}
2854
+
2855
+ Return ONLY the JSON array of render-ui effect tuples.`;
2856
+ }
2857
+ var DesignTransitionSchema = z.object({
2858
+ from: z.string().describe('Source state name (e.g., "Browsing")'),
2859
+ to: z.string().describe('Target state name (e.g., "Browsing" for self-loop, "Creating" for transition)'),
2860
+ event: z.string().describe('Event name (e.g., "INIT", "CREATE", "VIEW", "EDIT", "DELETE", "SAVE", "CANCEL")'),
2861
+ slot: z.string().describe('UI slot to render into: "main", "modal", "drawer", "sidebar", "overlay"'),
2862
+ entityName: z.string().describe('Entity name (e.g., "Task", "Order")'),
2863
+ entityFields: z.array(z.object({
2864
+ name: z.string(),
2865
+ type: z.string(),
2866
+ values: z.array(z.string()).optional()
2867
+ })).describe("Entity fields with types and optional enum values"),
2868
+ domainCategory: z.enum(["business", "game", "dashboard", "form", "content", "social", "e-commerce", "workflow"]).optional().describe("Domain category for pattern selection"),
2869
+ vocabulary: z.record(z.string(), z.string()).optional().describe('Domain vocabulary mapping (e.g., { "create": "Place Order", "item": "Order" })'),
2870
+ designStyle: z.enum(["minimal", "modern", "playful", "data-driven", "immersive"]).optional().describe("Visual style hint"),
2871
+ flowPattern: z.enum(["hub-spoke", "master-detail", "crud-cycle", "linear", "role-based"]).optional().describe("Application flow pattern"),
2872
+ listPattern: z.enum(["entity-table", "entity-cards", "entity-list"]).optional().describe("Preferred list pattern"),
2873
+ formPattern: z.enum(["modal", "drawer", "page"]).optional().describe("Preferred form pattern"),
2874
+ detailPattern: z.enum(["drawer", "page", "split"]).optional().describe("Preferred detail view pattern"),
2875
+ existingEffects: z.array(z.any()).optional().describe("Existing render-ui effects to enhance (for refinement passes)")
2876
+ });
2877
+ function createDesignTransitionTool(options = {}) {
2878
+ let eventCallback = options.onEvent;
2879
+ const setEventCallback = (callback) => {
2880
+ eventCallback = callback;
2881
+ };
2882
+ const emitEvent = (transitionId, type, data) => {
2883
+ if (eventCallback) {
2884
+ eventCallback(transitionId, {
2885
+ type,
2886
+ data,
2887
+ timestamp: Date.now()
2888
+ });
2889
+ }
2890
+ };
2891
+ const designTransitionTool = tool(
2892
+ async (input) => {
2893
+ const transitionId = `${input.entityName}:${input.from}->${input.to}:${input.event}`;
2894
+ const fingerprint = generateFingerprint2({
2895
+ from: input.from,
2896
+ to: input.to,
2897
+ event: input.event,
2898
+ slot: input.slot,
2899
+ entityName: input.entityName,
2900
+ entityFields: input.entityFields,
2901
+ domainCategory: input.domainCategory,
2902
+ designStyle: input.designStyle,
2903
+ flowPattern: input.flowPattern,
2904
+ listPattern: input.listPattern
2905
+ });
2906
+ try {
2907
+ emitEvent(transitionId, "message", {
2908
+ content: `Designing UI for ${transitionId}`,
2909
+ role: "assistant",
2910
+ isComplete: false
2911
+ });
2912
+ const cached = getCached2(fingerprint);
2913
+ if (cached) {
2914
+ emitEvent(transitionId, "generation_log", {
2915
+ level: "info",
2916
+ message: `Design cache HIT for ${transitionId}`,
2917
+ data: { fingerprint }
2918
+ });
2919
+ return JSON.stringify({
2920
+ success: true,
2921
+ transitionId,
2922
+ effects: cached.effects,
2923
+ cached: true,
2924
+ usage: cached.usage
2925
+ });
2926
+ }
2927
+ const recommendations = getPatternRecommendations(input);
2928
+ const recommendationsSection = formatRecommendationsForPrompt(recommendations);
2929
+ const systemPrompt = getDesignSystemPrompt();
2930
+ const userPrompt = buildDesignUserPrompt({
2931
+ ...input,
2932
+ recommendationsSection
2933
+ });
2934
+ emitEvent(transitionId, "tool_call", {
2935
+ tool: "llm_design_transition",
2936
+ args: {
2937
+ transition: transitionId,
2938
+ slot: input.slot,
2939
+ domain: input.domainCategory
2940
+ }
2941
+ });
2942
+ const client = new LLMClient({
2943
+ provider: "anthropic",
2944
+ model: "claude-sonnet-4-20250514",
2945
+ temperature: 0.1
2946
+ // Slight creativity for design
2947
+ });
2948
+ const response = await client.callWithCache({
2949
+ systemPrompt: "",
2950
+ systemBlocks: [{
2951
+ type: "text",
2952
+ text: systemPrompt,
2953
+ cache_control: { type: "ephemeral" }
2954
+ }],
2955
+ userPrompt,
2956
+ maxTokens: 4096,
2957
+ rawText: true
2958
+ });
2959
+ const rawText = (response.raw || String(response.data) || "").trim();
2960
+ let effects;
2961
+ try {
2962
+ const jsonText = rawText.replace(/^```(?:json)?\n?/m, "").replace(/\n?```$/m, "").trim();
2963
+ effects = JSON.parse(jsonText);
2964
+ if (!Array.isArray(effects)) {
2965
+ effects = [effects];
2966
+ }
2967
+ } catch {
2968
+ return JSON.stringify({
2969
+ success: false,
2970
+ transitionId,
2971
+ error: "Failed to parse design output as JSON",
2972
+ rawOutput: rawText
2973
+ });
2974
+ }
2975
+ const usage = {
2976
+ inputTokens: response.usage?.promptTokens || 0,
2977
+ outputTokens: response.usage?.completionTokens || 0,
2978
+ totalTokens: response.usage?.totalTokens || 0
2979
+ };
2980
+ designCache.set(fingerprint, {
2981
+ effects,
2982
+ timestamp: Date.now(),
2983
+ usage
2984
+ });
2985
+ emitEvent(transitionId, "tool_result", {
2986
+ tool: "llm_design_transition",
2987
+ result: { fingerprint, effectCount: effects.length, usage },
2988
+ success: true
2989
+ });
2990
+ emitEvent(transitionId, "message", {
2991
+ content: `Designed ${effects.length} effect(s) for ${transitionId} (${usage.totalTokens} tokens)`,
2992
+ role: "assistant",
2993
+ isComplete: true
2994
+ });
2995
+ return JSON.stringify({
2996
+ success: true,
2997
+ transitionId,
2998
+ effects,
2999
+ cached: false,
3000
+ usage,
3001
+ recommendedPatterns: recommendations.map((r) => r.pattern)
3002
+ });
3003
+ } catch (error) {
3004
+ const errorMessage = error instanceof Error ? error.message : String(error);
3005
+ emitEvent(transitionId, "error", {
3006
+ error: errorMessage,
3007
+ code: "DESIGN_TRANSITION_ERROR"
3008
+ });
3009
+ return JSON.stringify({
3010
+ success: false,
3011
+ transitionId,
3012
+ error: errorMessage
3013
+ });
3014
+ }
3015
+ },
3016
+ {
3017
+ name: "design_transition",
3018
+ description: `Design rich render-ui effects for a single orbital transition.
3019
+
3020
+ Takes the transition context (from/to state, event, entity, domain) and produces
3021
+ polished render-ui effects using the full pattern catalog.
3022
+
3023
+ USE THIS TOOL WHEN:
3024
+ - Generating INIT transitions (always compose header + stats + content)
3025
+ - Generating CREATE/EDIT transitions (form with proper fields)
3026
+ - Generating VIEW transitions (detail with tabs for related entities)
3027
+ - Enhancing existing render-ui effects that are too basic
3028
+
3029
+ The tool uses a specialized design skill with pattern catalog, layout composition,
3030
+ and domain-aware pattern selection to produce rich UI.
3031
+
3032
+ RETURNS: { success, effects: [["render-ui", slot, config], ...], transitionId, usage }
3033
+
3034
+ The effects array contains ONLY render-ui tuples. Non-UI effects (persist, emit, set)
3035
+ are NOT included \u2014 you must preserve those from the original transition.
3036
+
3037
+ INTEGRATION: After calling this tool, use extract_chunk to get the orbital,
3038
+ replace the render-ui effects in the target transition (keep persist/emit/set effects),
3039
+ then apply_chunk to merge back into schema.json.`,
3040
+ schema: DesignTransitionSchema
3041
+ }
3042
+ );
3043
+ return {
3044
+ tool: designTransitionTool,
3045
+ setEventCallback
3046
+ };
3047
+ }
3048
+ function createDesignEventWrapper(writeEvent) {
3049
+ return (transitionId, event) => {
3050
+ writeEvent({
3051
+ type: "subagent_event",
3052
+ timestamp: Date.now(),
3053
+ data: { transitionId, event }
3054
+ });
3055
+ };
3056
+ }
5408
3057
  function createGitHubTools(config) {
5409
3058
  const { token, owner = "", repo = "", workDir } = config;
5410
3059
  const integrationConfig = {
@@ -5774,10 +3423,12 @@ function createAgentTools(workDir) {
5774
3423
  // Domain tools (now use internal functions)
5775
3424
  domainOrbitalTools: createDomainOrbitalTools({ workDir }),
5776
3425
  // Chunking tools
5777
- schemaChunking: createSchemaChunkingTools(workDir)
3426
+ schemaChunking: createSchemaChunkingTools(workDir),
3427
+ // Design tool
3428
+ designTransition: createDesignTransitionTool()
5778
3429
  };
5779
3430
  }
5780
3431
 
5781
- export { createAgentTools, createApplyChunkTool, createCombineSchemasTool, createConstructCombinedDomainTool, createDomainOrbitalTools, createExecuteTool, createExtractChunkTool, createFinishTaskTool, createGenerateOrbitalDomainTool, createGenerateSchemaTool, createGitHubTools, createGitHubToolsArray, createOrbitalSubagentTool, createQuerySchemaStructureTool, createSchemaChunkingTools, createSubagentEventWrapper, createTraitEventWrapper, createTraitSubagentTool, createValidateSchemaTool, validateCommandPaths };
3432
+ export { createAgentTools, createApplyChunkTool, createCombineSchemasTool, createConstructCombinedDomainTool, createDesignEventWrapper, createDesignTransitionTool, createDomainOrbitalTools, createExecuteTool, createExtractChunkTool, createFinishTaskTool, createGenerateOrbitalDomainTool, createGenerateSchemaTool, createGitHubTools, createGitHubToolsArray, createOrbitalSubagentTool, createQuerySchemaStructureTool, createSchemaChunkingTools, createSubagentEventWrapper, createTraitEventWrapper, createTraitSubagentTool, createValidateSchemaTool, validateCommandPaths };
5782
3433
  //# sourceMappingURL=index.js.map
5783
3434
  //# sourceMappingURL=index.js.map