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