@almadar/agent 3.5.6 → 3.5.11

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,99 +1,16 @@
1
- import { extractJsonFromText, createOpenRouterClient, createDeepSeekClient } from '@almadar/llm';
2
- import { loadGoldenOrb, getAllBehaviors } from '@almadar/std';
3
1
  import fs3 from 'fs';
4
2
  import path3 from 'path';
3
+ import { extractJsonFromText, createAnthropicClient, createOpenRouterClient, createDeepSeekClient } from '@almadar/llm';
5
4
  import { execSync } from 'child_process';
6
- import { getDecompositionCompact, getBindingsCompact, getBindingContextRules, getOrbRenderUIGuide } from '@almadar/skills';
5
+ import { getDecompositionCompact, getBindingsCompact, getBindingContextRules, getOrbRenderUIGuideFiltered, getOrbRenderUIGuide } from '@almadar/skills';
6
+ import { loadGoldenOrb, getAllBehaviors } from '@almadar/std';
7
+ import { getOrbAllowedPatterns } from '@almadar/patterns';
7
8
  import { tool } from '@langchain/core/tools';
8
9
  import { z } from 'zod';
10
+ import * as stdBehaviorFunctions from '@almadar/std/behaviors/functions';
11
+ import { composeBehaviors } from '@almadar/core/builders';
9
12
 
10
- var __defProp = Object.defineProperty;
11
- var __getOwnPropNames = Object.getOwnPropertyNames;
12
- var __esm = (fn, res) => function __init() {
13
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
- };
15
- var __export = (target, all) => {
16
- for (var name in all)
17
- __defProp(target, name, { get: all[name], enumerable: true });
18
- };
19
-
20
- // src/gates/gate05-behavior-match.ts
21
- var gate05_behavior_match_exports = {};
22
- __export(gate05_behavior_match_exports, {
23
- loadGoldenOrbByName: () => loadGoldenOrbByName,
24
- runGate05: () => runGate05
25
- });
26
- function getBehaviorCatalog() {
27
- if (cachedCatalog) return cachedCatalog;
28
- const all = getAllBehaviors();
29
- const lines = all.map((b) => {
30
- const name = String(b.name || "");
31
- const desc = String(b.description || "");
32
- const states = Array.isArray(b.states) ? b.states.length : 0;
33
- const events = Array.isArray(b.events) ? b.events.length : 0;
34
- return `- ${name}: ${desc} (${states} states, ${events} events)`;
35
- });
36
- cachedCatalog = lines.join("\n");
37
- return cachedCatalog;
38
- }
39
- function loadGoldenOrbByName(behaviorName) {
40
- return loadGoldenOrb(behaviorName);
41
- }
42
- function buildMatchSystemPrompt() {
43
- return `You are a behavior matching assistant. Given an application description and its orbital structure, select the most relevant golden behavior from the catalog below.
44
-
45
- The catalog contains 107 standard behaviors. Each behavior is a proven, production-quality .orb program that handles a specific interaction pattern (CRUD lists, forms, search, cart, booking, etc.).
46
-
47
- Your task: pick the ONE behavior that best matches the application's primary interaction pattern. If no behavior is a close match, respond with "none".
48
-
49
- Respond with ONLY valid JSON: { "match": "std-behavior-name" } or { "match": "none" }
50
- No markdown, no explanation.`;
51
- }
52
- function buildMatchUserPrompt(prompt, orb) {
53
- const orbitalNames = orb.orbitals.map((o) => {
54
- const name = o.name;
55
- return name;
56
- });
57
- return `Application: ${prompt}
58
-
59
- Orbitals produced by Gate 0: ${orbitalNames.join(", ")}
60
-
61
- Behavior catalog:
62
- ${getBehaviorCatalog()}`;
63
- }
64
- async function runGate05(client, prompt, orb, opts = {}) {
65
- const systemPrompt = buildMatchSystemPrompt();
66
- const userPrompt = buildMatchUserPrompt(prompt, orb);
67
- const raw = await client.callRaw({
68
- systemPrompt,
69
- userPrompt,
70
- maxTokens: opts.maxTokens ?? 256
71
- });
72
- const parsed = extractJsonFromText(raw);
73
- if (!parsed) {
74
- console.warn("[Gate 0.5] Failed to parse match response, falling back to pure mode");
75
- return { matchedName: null, goldenOrb: null };
76
- }
77
- const obj = JSON.parse(parsed);
78
- const matchName = String(obj.match || "none");
79
- if (matchName === "none") {
80
- console.log("[Gate 0.5] No behavior match found, using pure mode");
81
- return { matchedName: null, goldenOrb: null };
82
- }
83
- const goldenOrb = loadGoldenOrbByName(matchName);
84
- if (!goldenOrb) {
85
- console.warn(`[Gate 0.5] Matched "${matchName}" but could not load .orb file`);
86
- return { matchedName: matchName, goldenOrb: null };
87
- }
88
- console.log(`[Gate 0.5] Matched behavior: ${matchName}`);
89
- return { matchedName: matchName, goldenOrb };
90
- }
91
- var cachedCatalog;
92
- var init_gate05_behavior_match = __esm({
93
- "src/gates/gate05-behavior-match.ts"() {
94
- cachedCatalog = null;
95
- }
96
- });
13
+ // src/gates/orchestrator.ts
97
14
  var ORBITAL_BINARY = "/home/osamah/bin/orbital";
98
15
  function validateWithOrbitalCLI(schema, workDir) {
99
16
  if (!fs3.existsSync(ORBITAL_BINARY)) {
@@ -270,7 +187,7 @@ Your output must be a JSON object with:
270
187
  - category: "interaction"
271
188
  - stateMachine:
272
189
  - states: [{ name: camelCase, isInitial?: true }]
273
- - events: [{ key: "UPPER_SNAKE_CASE", name: "Human Label", payloadSchema?: [{ name, type }] }]
190
+ - events: [{ key: "UPPER_SNAKE_CASE", name: "Human Label", payloadSchema: [{ name, type }] }]
274
191
  - transitions: [{ from, event, to, effects: [] }]
275
192
  - ui: { "stateName": { "presentation": "inline" | "modal" }, ... }
276
193
 
@@ -281,6 +198,26 @@ State Machine Rules:
281
198
  - The initial state MUST have an INIT self-loop transition (idle->INIT->idle or browsing->INIT->browsing)
282
199
  - Leave effects as empty arrays (Gate 3 fills these)
283
200
 
201
+ Payload Schema Rules (MANDATORY - validation fails without these):
202
+ - EVERY event except INIT, CLOSE, and CANCEL MUST have a payloadSchema. This is NOT optional.
203
+ - SAVE: { "key": "SAVE", "name": "Save", "payloadSchema": [{ "name": "data", "type": "object" }] }
204
+ - EDIT: { "key": "EDIT", "name": "Edit", "payloadSchema": [{ "name": "id", "type": "string" }] }
205
+ - DELETE: { "key": "DELETE", "name": "Delete", "payloadSchema": [{ "name": "id", "type": "string" }] }
206
+ - VIEW: { "key": "VIEW", "name": "View", "payloadSchema": [{ "name": "id", "type": "string" }] }
207
+ - CREATE: { "key": "CREATE", "name": "Create" } (no payload needed, opens empty form)
208
+ - Any event with a verb that acts on an entity needs payloadSchema with at least "id" or "data"
209
+
210
+ Example events array:
211
+ [
212
+ { "key": "INIT", "name": "Initialize" },
213
+ { "key": "CREATE", "name": "Create Item" },
214
+ { "key": "EDIT", "name": "Edit Item", "payloadSchema": [{ "name": "id", "type": "string" }] },
215
+ { "key": "SAVE", "name": "Save", "payloadSchema": [{ "name": "data", "type": "object" }] },
216
+ { "key": "DELETE", "name": "Delete Item", "payloadSchema": [{ "name": "id", "type": "string" }] },
217
+ { "key": "CANCEL", "name": "Cancel" },
218
+ { "key": "CLOSE", "name": "Close" }
219
+ ]
220
+
284
221
  UI Slot Assignment Rules:
285
222
  - Assign "inline" to primary view states (idle, browsing, listing, viewing)
286
223
  - Assign "modal" to overlay action states (creating, editing, deleting, confirming)
@@ -345,6 +282,13 @@ Rules:
345
282
  - set target MUST start with @entity (never @payload)
346
283
  - Do NOT include render-ui effects (Gate 4 handles those)
347
284
 
285
+ Guard Rules (CRITICAL):
286
+ - Do NOT wrap a single condition in "and". Use "and" ONLY for 2+ conditions.
287
+ WRONG: ["and", ["eq", "@entity.status", "active"]]
288
+ RIGHT: ["eq", "@entity.status", "active"]
289
+ - ONLY reference fields that exist on the entity shown in the user prompt. Do NOT invent fields.
290
+ If the entity has fields [id, name, status], you can use @entity.name, @entity.status. Do NOT use @entity.items or @entity.data if they are not listed.
291
+
348
292
  Respond with ONLY valid JSON. No markdown, no explanation.`;
349
293
  }
350
294
  function buildGate3UserPrompt(transition, entity, sm, guidedTransition) {
@@ -362,8 +306,8 @@ ${JSON.stringify(guidedTransition, null, 2)}`;
362
306
  }
363
307
  return userPrompt;
364
308
  }
365
- function buildGate4SystemPrompt() {
366
- const orbGuide = getOrbRenderUIGuide();
309
+ function buildGate4SystemPrompt(matchedPatterns) {
310
+ const orbGuide = matchedPatterns ? getOrbRenderUIGuideFiltered(matchedPatterns) : getOrbRenderUIGuide();
367
311
  return `You are a UI designer. Given a transition with its effects, produce the render-ui pattern tree.
368
312
 
369
313
  ${orbGuide}
@@ -375,19 +319,27 @@ Your output must be a JSON object with:
375
319
 
376
320
  Slot Rules:
377
321
  - Use the slot shown in the user prompt for the target state (main or modal)
378
- - When exiting a modal state (the user prompt will say so), you MUST include:
322
+ - When exiting a modal state (the user prompt will say so), you MUST include BOTH:
379
323
  1. { "slot": "modal", "tree": null } to clear the modal
380
- 2. { "slot": "main", "tree": { ... } } to render the main view
324
+ 2. { "slot": "main", "tree": { ... } } to re-render the main view
325
+ WITHOUT BOTH, the main view shows stale content after closing the modal. This is a validation error.
381
326
 
382
327
  Binding Rules:
383
328
  - Bindings are ONLY paths: @entity.name, @entity.price (NO expressions like @entity.x <= 0)
384
329
  - Each prop value must contain at most ONE binding. NEVER concatenate: "@entity.price @entity.currency" is INVALID
385
330
  - For combined display, use separate typography elements or static labels
386
331
  - button event props must match state machine event keys
332
+ - ONLY bind to entity fields shown in the user prompt. Do NOT invent fields.
333
+ If entity has fields [id, name, status, price], you can use @entity.name, @entity.price.
334
+ Do NOT use @entity.items, @entity.data, or any field not listed. This is a validation error.
387
335
 
388
336
  Pattern Rules:
389
337
  - Use ONLY props that exist on the pattern. Do NOT invent props.
390
- - data-list: entity, itemActions, emptyState (NOT emptyIcon, emptyTitle)
338
+ - data-list fields MUST be a string array: ["name", "status", "price"]. NEVER use objects or nested trees for fields.
339
+ WRONG: "fields": [{ "type": "stack", "children": [...] }]
340
+ WRONG: "fields": [{ "field": "name", "label": "Name" }]
341
+ RIGHT: "fields": ["name", "status", "price"]
342
+ - data-list: entity, fields, itemActions (NOT emptyIcon, emptyTitle)
391
343
  - button: label, variant, event, icon, size (NOT disabled)
392
344
  - card: children (NOT padding, bg, border, use box for those)
393
345
 
@@ -519,9 +471,72 @@ async function runGate0(client, prompt, opts = {}, behaviorData) {
519
471
  });
520
472
  return parseGate0Response(raw);
521
473
  }
474
+ var cachedCatalog = null;
475
+ function getBehaviorCatalog() {
476
+ if (cachedCatalog) return cachedCatalog;
477
+ const all = getAllBehaviors();
478
+ const lines = all.map((b) => {
479
+ const name = String(b.name || "");
480
+ const desc = String(b.description || "");
481
+ const states = Array.isArray(b.states) ? b.states.length : 0;
482
+ const events = Array.isArray(b.events) ? b.events.length : 0;
483
+ return `- ${name}: ${desc} (${states} states, ${events} events)`;
484
+ });
485
+ cachedCatalog = lines.join("\n");
486
+ return cachedCatalog;
487
+ }
488
+ function loadGoldenOrbByName(behaviorName) {
489
+ return loadGoldenOrb(behaviorName);
490
+ }
491
+ function buildMatchSystemPrompt() {
492
+ return `You are a behavior matching assistant. Given an application description and its orbital structure, select the most relevant golden behavior from the catalog below.
522
493
 
523
- // src/gates/orchestrator.ts
524
- init_gate05_behavior_match();
494
+ The catalog contains 107 standard behaviors. Each behavior is a proven, production-quality .orb program that handles a specific interaction pattern (CRUD lists, forms, search, cart, booking, etc.).
495
+
496
+ Your task: pick the ONE behavior that best matches the application's primary interaction pattern. If no behavior is a close match, respond with "none".
497
+
498
+ Respond with ONLY valid JSON: { "match": "std-behavior-name" } or { "match": "none" }
499
+ No markdown, no explanation.`;
500
+ }
501
+ function buildMatchUserPrompt(prompt, orb) {
502
+ const orbitalNames = orb.orbitals.map((o) => {
503
+ const name = o.name;
504
+ return name;
505
+ });
506
+ return `Application: ${prompt}
507
+
508
+ Orbitals produced by Gate 0: ${orbitalNames.join(", ")}
509
+
510
+ Behavior catalog:
511
+ ${getBehaviorCatalog()}`;
512
+ }
513
+ async function runGate05(client, prompt, orb, opts = {}) {
514
+ const systemPrompt = buildMatchSystemPrompt();
515
+ const userPrompt = buildMatchUserPrompt(prompt, orb);
516
+ const raw = await client.callRaw({
517
+ systemPrompt,
518
+ userPrompt,
519
+ maxTokens: opts.maxTokens ?? 256
520
+ });
521
+ const parsed = extractJsonFromText(raw);
522
+ if (!parsed) {
523
+ console.warn("[Gate 0.5] Failed to parse match response, falling back to pure mode");
524
+ return { matchedName: null, goldenOrb: null };
525
+ }
526
+ const obj = JSON.parse(parsed);
527
+ const matchName = String(obj.match || "none");
528
+ if (matchName === "none") {
529
+ console.log("[Gate 0.5] No behavior match found, using pure mode");
530
+ return { matchedName: null, goldenOrb: null };
531
+ }
532
+ const goldenOrb = loadGoldenOrbByName(matchName);
533
+ if (!goldenOrb) {
534
+ console.warn(`[Gate 0.5] Matched "${matchName}" but could not load .orb file`);
535
+ return { matchedName: matchName, goldenOrb: null };
536
+ }
537
+ console.log(`[Gate 0.5] Matched behavior: ${matchName}`);
538
+ return { matchedName: matchName, goldenOrb };
539
+ }
525
540
 
526
541
  // src/gates/merge.ts
527
542
  function deepCloneOrb(orb) {
@@ -823,6 +838,296 @@ function findGuidedTransition(goldenOrb, traitName, from, event) {
823
838
  }
824
839
  return void 0;
825
840
  }
841
+ var CORE_PATTERNS = [
842
+ "stack",
843
+ "box",
844
+ "typography",
845
+ "divider",
846
+ "icon",
847
+ "button"
848
+ ];
849
+ var GAME_EVENTS = /* @__PURE__ */ new Set([
850
+ "ATTACK",
851
+ "DEFEND",
852
+ "MOVE",
853
+ "SPAWN",
854
+ "DAMAGE",
855
+ "HEAL",
856
+ "CAST",
857
+ "LEVEL_UP",
858
+ "GAME_OVER",
859
+ "START_GAME",
860
+ "PAUSE",
861
+ "RESUME",
862
+ "NEXT_TURN",
863
+ "END_TURN",
864
+ "COLLECT",
865
+ "EQUIP",
866
+ "UNEQUIP",
867
+ "CRAFT",
868
+ "TICK",
869
+ "STEP",
870
+ "SIMULATE"
871
+ ]);
872
+ var GAME_PATTERN_NAMES = /* @__PURE__ */ new Set([
873
+ "health-bar",
874
+ "score-display",
875
+ "control-button",
876
+ "d-pad",
877
+ "action-button",
878
+ "choice-button",
879
+ "combo-counter",
880
+ "damage-number",
881
+ "mini-map",
882
+ "resource-counter",
883
+ "timer-display",
884
+ "turn-indicator",
885
+ "waypoint-marker",
886
+ "x-p-bar",
887
+ "crafting-recipe",
888
+ "enemy-plate",
889
+ "health-panel",
890
+ "quest-tracker",
891
+ "resource-bar",
892
+ "turn-panel",
893
+ "unit-command-bar",
894
+ "powerup-slots",
895
+ "item-slot",
896
+ "status-effect",
897
+ // display: game/simulation
898
+ "model-loader",
899
+ "feature-renderer",
900
+ "unit-renderer",
901
+ "state-indicator",
902
+ "simulation-controls",
903
+ "simulation-graph",
904
+ "event-log",
905
+ "object-rule-panel",
906
+ "rule-editor",
907
+ "action-palette",
908
+ "sequence-bar",
909
+ "state-node",
910
+ "transition-arrow",
911
+ "variable-panel"
912
+ ]);
913
+ var SEARCH_PATTERNS = /* @__PURE__ */ new Set(["search-input"]);
914
+ var WIZARD_PATTERNS = /* @__PURE__ */ new Set(["wizard-progress", "wizard-navigation"]);
915
+ var SPECIALIZED_PATTERNS = /* @__PURE__ */ new Set([
916
+ "law-reference-tooltip",
917
+ "violation-alert",
918
+ "scaled-diagram",
919
+ "quiz-block",
920
+ "code-block",
921
+ "code-view",
922
+ "markdown-content",
923
+ "flip-card",
924
+ "flip-container",
925
+ "confetti-effect",
926
+ "typewriter-text",
927
+ "pull-to-refresh",
928
+ "swipeable-row",
929
+ "lightbox",
930
+ "carousel",
931
+ "calendar-grid",
932
+ "time-slot-cell",
933
+ "day-cell",
934
+ "line-chart",
935
+ "chart-legend",
936
+ "graph-view",
937
+ "split-pane",
938
+ "master-detail",
939
+ "tabbed-container",
940
+ // Aliases (typography already covers these)
941
+ "heading",
942
+ "text",
943
+ // Alias for button
944
+ "button-pattern",
945
+ // Niche/unlikely for most apps
946
+ "conditional-wrapper",
947
+ "text-highlight",
948
+ "theme-toggle",
949
+ "flex",
950
+ "floating-action-button",
951
+ "animated-counter",
952
+ "infinite-scroll-sentinel",
953
+ "progress-dots",
954
+ "map-view",
955
+ "range-slider",
956
+ "number-stepper",
957
+ "star-rating",
958
+ "upload-drop-zone",
959
+ "collapsible-section",
960
+ "stat-badge",
961
+ "status-dot",
962
+ "sortable-list",
963
+ "trend-indicator",
964
+ // Container patterns (compiler handles these, not render-ui)
965
+ "modal",
966
+ "drawer",
967
+ "overlay",
968
+ // Form sub-patterns (form-section is enough)
969
+ "repeatable-form-section",
970
+ "form-field",
971
+ "form-section-header",
972
+ "input-group",
973
+ "relation-select",
974
+ "date-range-selector",
975
+ // Layout extras (stack/box cover these)
976
+ "grid",
977
+ "center",
978
+ "spacer",
979
+ // Error boundary (runtime handles this)
980
+ "error-boundary"
981
+ ]);
982
+ function analyzeApp(orb) {
983
+ const signals = {
984
+ hasModalStates: false,
985
+ hasInlineStates: false,
986
+ hasCreateEdit: false,
987
+ hasDelete: false,
988
+ hasSearch: false,
989
+ hasWizard: false,
990
+ isGame: false,
991
+ eventNames: /* @__PURE__ */ new Set()
992
+ };
993
+ for (const orbital of orb.orbitals) {
994
+ for (const traitRef of orbital.traits) {
995
+ if (typeof traitRef === "string" || "ref" in traitRef) continue;
996
+ const trait = traitRef;
997
+ if (!trait.stateMachine) continue;
998
+ const ui = trait.ui;
999
+ if (ui) {
1000
+ for (const val of Object.values(ui)) {
1001
+ if (val.presentation === "modal") signals.hasModalStates = true;
1002
+ if (val.presentation === "inline") signals.hasInlineStates = true;
1003
+ }
1004
+ }
1005
+ for (const state of trait.stateMachine.states) {
1006
+ const name = state.name.toLowerCase();
1007
+ if (name.includes("creat") || name.includes("edit")) signals.hasCreateEdit = true;
1008
+ if (name.includes("delet") || name.includes("confirm")) signals.hasDelete = true;
1009
+ if (name.includes("search") || name.includes("filter")) signals.hasSearch = true;
1010
+ if (name.includes("step") || name.includes("wizard")) signals.hasWizard = true;
1011
+ }
1012
+ for (const event of trait.stateMachine.events) {
1013
+ signals.eventNames.add(event.key);
1014
+ if (GAME_EVENTS.has(event.key)) signals.isGame = true;
1015
+ if (event.key === "SEARCH" || event.key === "FILTER") signals.hasSearch = true;
1016
+ }
1017
+ }
1018
+ }
1019
+ return signals;
1020
+ }
1021
+ function preFilterPatterns(signals) {
1022
+ const grouped = getOrbAllowedPatterns();
1023
+ const allowed = [];
1024
+ for (const items of Object.values(grouped)) {
1025
+ for (const item of items) {
1026
+ if (CORE_PATTERNS.includes(item.name)) continue;
1027
+ if (!signals.isGame && GAME_PATTERN_NAMES.has(item.name)) continue;
1028
+ if (!signals.hasSearch && SEARCH_PATTERNS.has(item.name)) continue;
1029
+ if (!signals.hasWizard && WIZARD_PATTERNS.has(item.name)) continue;
1030
+ if (!signals.isGame && SPECIALIZED_PATTERNS.has(item.name)) continue;
1031
+ allowed.push(item.name);
1032
+ }
1033
+ }
1034
+ return allowed;
1035
+ }
1036
+ function buildSlimCatalog(patternNames) {
1037
+ const grouped = getOrbAllowedPatterns();
1038
+ const nameSet = new Set(patternNames);
1039
+ const lines = [];
1040
+ for (const [cat, items] of Object.entries(grouped).sort()) {
1041
+ const filtered = items.filter((i) => nameSet.has(i.name));
1042
+ if (filtered.length === 0) continue;
1043
+ lines.push(`## ${cat}`);
1044
+ for (const item of filtered) {
1045
+ const desc = item.description.split("\n")[0].slice(0, 80);
1046
+ lines.push(`- ${item.name}: ${desc}`);
1047
+ }
1048
+ }
1049
+ return lines.join("\n");
1050
+ }
1051
+ function buildSystemPrompt(preFiltered, signals) {
1052
+ const catalog = buildSlimCatalog(preFiltered);
1053
+ return `You are a UI pattern selector. Pick 5-15 patterns from the catalog below to render this application's UI.
1054
+
1055
+ Core layout patterns are ALWAYS included (do not list them):
1056
+ ${CORE_PATTERNS.join(", ")}
1057
+
1058
+ Select from each applicable category:
1059
+
1060
+ **Content (pick 1-3)**: data-list or data-grid for lists, card for grouping, stat-display for metrics, badge for status
1061
+ **Forms${signals.hasCreateEdit ? " (REQUIRED - app has create/edit states)" : ""}**: form-section for create/edit forms
1062
+ **Feedback (pick 1-2)**: empty-state, loading-state, error-state, notification
1063
+ **Navigation (if needed)**: tabs, pagination, breadcrumb
1064
+
1065
+ Pick at most 15 patterns total. Only pick patterns you are confident the app needs.
1066
+
1067
+ Catalog (${preFiltered.length} patterns):
1068
+ ${catalog}
1069
+
1070
+ Respond with ONLY valid JSON: { "patterns": ["pattern-name", ...] }`;
1071
+ }
1072
+ function buildUserPrompt(orb) {
1073
+ const lines = [];
1074
+ for (const orbital of orb.orbitals) {
1075
+ const entity = orbital.entity;
1076
+ const fields = entity.fields?.map((f) => `${f.name}:${f.type}`).join(", ") || "none";
1077
+ lines.push(`Entity: ${entity.name} (${fields})`);
1078
+ for (const traitRef of orbital.traits) {
1079
+ if (typeof traitRef === "string" || "ref" in traitRef) continue;
1080
+ const trait = traitRef;
1081
+ if (!trait.stateMachine) continue;
1082
+ const states = trait.stateMachine.states.map((s) => s.name).join(", ");
1083
+ const events = trait.stateMachine.events.map((e) => e.key).join(", ");
1084
+ const ui = trait.ui;
1085
+ const slots = ui ? Object.entries(ui).map(([s, v]) => `${s}(${v.presentation})`).join(", ") : "none";
1086
+ lines.push(`Trait: ${trait.name} | States: ${states} | Events: ${events} | Slots: ${slots}`);
1087
+ }
1088
+ }
1089
+ return `Select UI patterns for this application:
1090
+
1091
+ ${lines.join("\n")}`;
1092
+ }
1093
+ async function runGate35(client, orb, _opts = {}) {
1094
+ const signals = analyzeApp(orb);
1095
+ const preFiltered = preFilterPatterns(signals);
1096
+ console.log(`[Gate 3.5] Pre-filtered: ${preFiltered.length} patterns (game=${signals.isGame}, createEdit=${signals.hasCreateEdit})`);
1097
+ const systemPrompt = buildSystemPrompt(preFiltered, signals);
1098
+ const userPrompt = buildUserPrompt(orb);
1099
+ try {
1100
+ const raw = await client.callRaw({
1101
+ systemPrompt,
1102
+ userPrompt,
1103
+ maxTokens: 256
1104
+ });
1105
+ const jsonStr = extractJsonFromText(raw);
1106
+ if (!jsonStr) {
1107
+ console.warn("[Gate 3.5] No JSON in response, using pre-filtered set");
1108
+ return [.../* @__PURE__ */ new Set([...CORE_PATTERNS, ...preFiltered])];
1109
+ }
1110
+ const parsed = JSON.parse(jsonStr);
1111
+ const selected = Array.isArray(parsed.patterns) ? parsed.patterns : [];
1112
+ const preFilteredSet = new Set(preFiltered);
1113
+ const validated = selected.filter((p) => preFilteredSet.has(p));
1114
+ const result = [.../* @__PURE__ */ new Set([...CORE_PATTERNS, ...validated])];
1115
+ if (result.length > 20) {
1116
+ return [...CORE_PATTERNS, ...validated.slice(0, 14)];
1117
+ }
1118
+ if (result.length < 8) {
1119
+ console.warn(`[Gate 3.5] Only ${result.length} patterns, adding essentials`);
1120
+ const essentials = ["badge", "stat-display", "data-list", "form-section", "empty-state", "card"];
1121
+ for (const p of essentials) {
1122
+ if (!result.includes(p) && preFilteredSet.has(p)) result.push(p);
1123
+ }
1124
+ }
1125
+ return result;
1126
+ } catch (error) {
1127
+ console.warn(`[Gate 3.5] Error: ${error instanceof Error ? error.message : error}, using pre-filtered set`);
1128
+ return [.../* @__PURE__ */ new Set([...CORE_PATTERNS, ...preFiltered])];
1129
+ }
1130
+ }
826
1131
  function buildSlotContext(sm, traitUi) {
827
1132
  const slots = {};
828
1133
  for (const state of sm.states) {
@@ -850,8 +1155,8 @@ function parseGate4Response(raw) {
850
1155
  tree: r.tree === null ? null : r.tree || { type: "typography", variant: "body", content: "Empty" }
851
1156
  }));
852
1157
  }
853
- async function runGate4(client, orb, opts = {}, _behaviorData) {
854
- const systemPrompt = buildGate4SystemPrompt();
1158
+ async function runGate4(client, orb, opts = {}, _behaviorData, matchedPatterns) {
1159
+ const systemPrompt = buildGate4SystemPrompt(matchedPatterns);
855
1160
  let result = orb;
856
1161
  for (const orbital of orb.orbitals) {
857
1162
  const entity = orbital.entity;
@@ -968,6 +1273,9 @@ function createClientForProvider(config) {
968
1273
  case "mistral-medium":
969
1274
  client = createOpenRouterClient({ model: "mistralai/mistral-medium-3.1", ...opts });
970
1275
  break;
1276
+ case "anthropic":
1277
+ client = createAnthropicClient(opts);
1278
+ break;
971
1279
  default: {
972
1280
  const _exhaustive = config.provider;
973
1281
  throw new Error(`Unknown provider: ${_exhaustive}`);
@@ -986,8 +1294,29 @@ ${opts2.userPrompt}` });
986
1294
  function timeMs() {
987
1295
  return performance.now();
988
1296
  }
1297
+ function withCallCounter(client) {
1298
+ let calls = 0;
1299
+ const proxy = new Proxy(client, {
1300
+ get(target, prop, receiver) {
1301
+ if (prop === "calls") return calls;
1302
+ if (prop === "reset") return () => {
1303
+ calls = 0;
1304
+ };
1305
+ const value = Reflect.get(target, prop, receiver);
1306
+ if (prop === "callRaw" || prop === "callRawWithMetadata") {
1307
+ return (...args) => {
1308
+ calls++;
1309
+ return value.apply(target, args);
1310
+ };
1311
+ }
1312
+ return value;
1313
+ }
1314
+ });
1315
+ return proxy;
1316
+ }
989
1317
  async function runGatePipeline(prompt, config, behaviorData, goldenOrb) {
990
- const client = createClientForProvider(config);
1318
+ const counter = withCallCounter(createClientForProvider(config));
1319
+ const client = counter;
991
1320
  const maxTokens = config.maxTokens || 4096;
992
1321
  const pipelineStart = timeMs();
993
1322
  const opts = {
@@ -995,41 +1324,60 @@ async function runGatePipeline(prompt, config, behaviorData, goldenOrb) {
995
1324
  goldenOrb,
996
1325
  workDir: config.workDir
997
1326
  };
1327
+ counter.reset();
998
1328
  const g0Start = timeMs();
999
1329
  const orbAfterGate0 = await runGate0(client, prompt, opts, behaviorData);
1000
1330
  const g0Ms = timeMs() - g0Start;
1001
- console.log(`[Gate 0] ${orbAfterGate0.name}: ${orbAfterGate0.orbitals.length} orbital(s) (${g0Ms.toFixed(0)}ms)`);
1331
+ const g0Calls = counter.calls;
1332
+ console.log(`[Gate 0] ${orbAfterGate0.name}: ${orbAfterGate0.orbitals.length} orbital(s) (${g0Ms.toFixed(0)}ms, ${g0Calls} LLM call(s))`);
1002
1333
  maybeValidate(config, orbAfterGate0, 0);
1334
+ let g05Calls = 0;
1003
1335
  if (config.mode === "guided" && !goldenOrb) {
1336
+ counter.reset();
1004
1337
  const g05Start = timeMs();
1005
1338
  const matchResult = await runGate05(client, prompt, orbAfterGate0, opts);
1006
1339
  const g05Ms = timeMs() - g05Start;
1340
+ g05Calls = counter.calls;
1007
1341
  if (matchResult.goldenOrb) {
1008
1342
  opts.goldenOrb = matchResult.goldenOrb;
1009
- console.log(`[Gate 0.5] Matched: ${matchResult.matchedName} (${g05Ms.toFixed(0)}ms)`);
1343
+ console.log(`[Gate 0.5] Matched: ${matchResult.matchedName} (${g05Ms.toFixed(0)}ms, ${g05Calls} LLM call(s))`);
1010
1344
  } else {
1011
- console.log(`[Gate 0.5] No match, continuing in pure mode (${g05Ms.toFixed(0)}ms)`);
1345
+ console.log(`[Gate 0.5] No match, continuing in pure mode (${g05Ms.toFixed(0)}ms, ${g05Calls} LLM call(s))`);
1012
1346
  }
1013
1347
  }
1348
+ counter.reset();
1014
1349
  const g1Start = timeMs();
1015
1350
  const orbAfterGate1 = await runGate1(client, orbAfterGate0, opts);
1016
1351
  const g1Ms = timeMs() - g1Start;
1017
- console.log(`[Gate 1] Enriched ${orbAfterGate1.orbitals.length} orbital(s) (${g1Ms.toFixed(0)}ms)`);
1352
+ const g1Calls = counter.calls;
1353
+ console.log(`[Gate 1] Enriched ${orbAfterGate1.orbitals.length} orbital(s) (${g1Ms.toFixed(0)}ms, ${g1Calls} LLM call(s))`);
1018
1354
  maybeValidate(config, orbAfterGate1, 1);
1355
+ counter.reset();
1019
1356
  const g2Start = timeMs();
1020
1357
  const orbAfterGate2 = await runGate2(client, orbAfterGate1, opts);
1021
1358
  const g2Ms = timeMs() - g2Start;
1022
- console.log(`[Gate 2] State machines filled (${g2Ms.toFixed(0)}ms)`);
1359
+ const g2Calls = counter.calls;
1360
+ console.log(`[Gate 2] State machines filled (${g2Ms.toFixed(0)}ms, ${g2Calls} LLM call(s))`);
1023
1361
  maybeValidate(config, orbAfterGate2, 2);
1362
+ counter.reset();
1024
1363
  const g3Start = timeMs();
1025
1364
  const orbAfterGate3 = await runGate3(client, orbAfterGate2, opts);
1026
1365
  const g3Ms = timeMs() - g3Start;
1027
- console.log(`[Gate 3] Guards + effects filled (${g3Ms.toFixed(0)}ms)`);
1366
+ const g3Calls = counter.calls;
1367
+ console.log(`[Gate 3] Guards + effects filled (${g3Ms.toFixed(0)}ms, ${g3Calls} LLM call(s))`);
1028
1368
  maybeValidate(config, orbAfterGate3, 3);
1369
+ counter.reset();
1370
+ const g35Start = timeMs();
1371
+ const matchedPatterns = await runGate35(client, orbAfterGate3, opts);
1372
+ const g35Ms = timeMs() - g35Start;
1373
+ const g35Calls = counter.calls;
1374
+ console.log(`[Gate 3.5] Matched ${matchedPatterns.length} patterns (${g35Ms.toFixed(0)}ms, ${g35Calls} LLM call(s))`);
1375
+ counter.reset();
1029
1376
  const g4Start = timeMs();
1030
- const orbFinal = await runGate4(client, orbAfterGate3, opts);
1377
+ const orbFinal = await runGate4(client, orbAfterGate3, opts, behaviorData, matchedPatterns);
1031
1378
  const g4Ms = timeMs() - g4Start;
1032
- console.log(`[Gate 4] Render-ui appended (${g4Ms.toFixed(0)}ms)`);
1379
+ const g4Calls = counter.calls;
1380
+ console.log(`[Gate 4] Render-ui appended (${g4Ms.toFixed(0)}ms, ${g4Calls} LLM call(s))`);
1033
1381
  fs3.mkdirSync(config.workDir, { recursive: true });
1034
1382
  const validation = validateWithOrbitalCLI(orbFinal, config.workDir);
1035
1383
  console.log(`[Validate] valid=${validation.valid}, errors=${(validation.errors || []).length}`);
@@ -1038,12 +1386,14 @@ async function runGatePipeline(prompt, config, behaviorData, goldenOrb) {
1038
1386
  verify = await runVerify(orbFinal, config);
1039
1387
  }
1040
1388
  const totalMs = timeMs() - pipelineStart;
1041
- console.log(`[Pipeline] Total: ${(totalMs / 1e3).toFixed(1)}s`);
1389
+ const totalCalls = g0Calls + g05Calls + g1Calls + g2Calls + g3Calls + g35Calls + g4Calls;
1390
+ console.log(`[Pipeline] Total: ${(totalMs / 1e3).toFixed(1)}s, ${totalCalls} LLM calls`);
1042
1391
  const timings = {
1043
1392
  gate0Ms: g0Ms,
1044
1393
  gate1Ms: [g1Ms],
1045
1394
  gate2Ms: [g2Ms],
1046
1395
  gate3Ms: [g3Ms],
1396
+ gate35Ms: g35Ms,
1047
1397
  gate4Ms: [g4Ms],
1048
1398
  totalMs
1049
1399
  };
@@ -1109,9 +1459,6 @@ function maybeValidate(config, orb, gate) {
1109
1459
  }
1110
1460
  }
1111
1461
 
1112
- // src/gates/index.ts
1113
- init_gate05_behavior_match();
1114
-
1115
1462
  // src/gates/behavior-extract.ts
1116
1463
  function asArray(val) {
1117
1464
  if (!val) return [];
@@ -1486,7 +1833,6 @@ function countNodes(tree) {
1486
1833
  }
1487
1834
  return count;
1488
1835
  }
1489
- init_gate05_behavior_match();
1490
1836
  function createGateClient(provider = "mistral-small") {
1491
1837
  let client;
1492
1838
  switch (provider) {
@@ -1586,8 +1932,7 @@ function createBuildOrbitalTool(workDir, gateProvider) {
1586
1932
  }
1587
1933
  let goldenOrb;
1588
1934
  if (input.goldenBehavior) {
1589
- const { loadGoldenOrbByName: loadGoldenOrbByName2 } = await Promise.resolve().then(() => (init_gate05_behavior_match(), gate05_behavior_match_exports));
1590
- goldenOrb = loadGoldenOrbByName2(input.goldenBehavior) ?? void 0;
1935
+ goldenOrb = loadGoldenOrbByName(input.goldenBehavior) ?? void 0;
1591
1936
  }
1592
1937
  const opts = {
1593
1938
  goldenOrb
@@ -1696,10 +2041,10 @@ function createVerifyAppTool(workDir) {
1696
2041
  );
1697
2042
  }
1698
2043
  var BEHAVIOR_FUNCTIONS = {};
1699
- async function loadBehaviorFunctions() {
2044
+ function loadBehaviorFunctions() {
1700
2045
  if (Object.keys(BEHAVIOR_FUNCTIONS).length > 0) return;
1701
2046
  try {
1702
- const fns = await import('@almadar/std/behaviors/functions');
2047
+ const fns = stdBehaviorFunctions;
1703
2048
  const mapping = {
1704
2049
  // Molecules: CRUD/Data
1705
2050
  "std-list": "stdList",
@@ -1779,7 +2124,7 @@ async function loadBehaviorFunctions() {
1779
2124
  function createUseBehaviorTool(workDir) {
1780
2125
  return tool(
1781
2126
  async (input) => {
1782
- await loadBehaviorFunctions();
2127
+ loadBehaviorFunctions();
1783
2128
  const newEntityName = input.entityName;
1784
2129
  const newFields = JSON.parse(input.fieldsJson);
1785
2130
  let finalOrbital;
@@ -1799,7 +2144,6 @@ function createUseBehaviorTool(workDir) {
1799
2144
  finalOrbital = orbital;
1800
2145
  } else {
1801
2146
  console.log(`[use_behavior] Falling back to substitution for ${input.behaviorName}`);
1802
- const { loadGoldenOrb } = await import('@almadar/std');
1803
2147
  const behavior = loadGoldenOrb(input.behaviorName);
1804
2148
  if (!behavior) {
1805
2149
  return { success: false, error: `Behavior "${input.behaviorName}" not found` };
@@ -1995,43 +2339,29 @@ function createComposeTool(workDir) {
1995
2339
  if (orbitals.length === 0) {
1996
2340
  return { success: false, error: "No orbitals found in .orbitals/" };
1997
2341
  }
1998
- let pages;
1999
- if (input.pagesJson) {
2000
- pages = JSON.parse(input.pagesJson);
2001
- } else {
2002
- pages = orbitals.map((o, i) => {
2003
- const oName = String(o.name || `Orbital${i}`);
2004
- const entityName = oName.replace(/Orbital$/i, "");
2005
- const traits = o.traits || [];
2006
- return {
2007
- name: `${entityName}Page`,
2008
- path: `/${entityName.toLowerCase()}s`,
2009
- ...i === 0 ? { isInitial: true } : {},
2010
- traits: traits.filter((t) => typeof t !== "string" && !("ref" in t)).map((t) => ({ ref: String(t.name) }))
2011
- };
2012
- });
2342
+ let eventWiring;
2343
+ if (input.eventWiringJson) {
2344
+ eventWiring = JSON.parse(input.eventWiringJson);
2013
2345
  }
2014
- for (const orbital of orbitals) {
2015
- String(orbital.name || "");
2016
- const orbitalTraitNames = (orbital.traits || []).filter((t) => typeof t !== "string" && !("ref" in t)).map((t) => String(t.name));
2017
- orbital.pages = pages.filter((p) => {
2018
- const pageTraits = p.traits || [];
2019
- return pageTraits.some((pt) => orbitalTraitNames.includes(pt.ref));
2020
- });
2346
+ let entityMappings;
2347
+ if (input.entityMappingsJson) {
2348
+ entityMappings = JSON.parse(input.entityMappingsJson);
2021
2349
  }
2022
- const schema = {
2023
- name: input.appName || "Application",
2024
- version: "1.0.0",
2025
- orbitals
2026
- };
2350
+ const result = composeBehaviors({
2351
+ appName: input.appName || "Application",
2352
+ orbitals,
2353
+ layoutStrategy: input.layoutStrategy ?? "auto",
2354
+ eventWiring,
2355
+ entityMappings
2356
+ });
2027
2357
  const schemaPath = path3.join(workDir, "schema.orb");
2028
- fs3.writeFileSync(schemaPath, JSON.stringify(schema, null, 2));
2029
- const validation = validateWithOrbitalCLI(schema, workDir);
2358
+ fs3.writeFileSync(schemaPath, JSON.stringify(result.schema, null, 2));
2359
+ const validation = validateWithOrbitalCLI(result.schema, workDir);
2030
2360
  return {
2031
2361
  success: true,
2032
- appName: schema.name,
2362
+ appName: result.schema.name,
2033
2363
  orbitalCount: orbitals.length,
2034
- pageCount: pages.length,
2364
+ pageCount: result.layout.pageCount,
2035
2365
  schemaPath,
2036
2366
  validation: {
2037
2367
  valid: validation.valid ?? false,
@@ -2041,12 +2371,14 @@ function createComposeTool(workDir) {
2041
2371
  };
2042
2372
  },
2043
2373
  {
2044
- name: "compose_app",
2045
- description: "Compose multiple orbitals from .orbitals/ into a final multi-page application schema. Writes schema.orb and validates.",
2374
+ name: "compose_behaviors",
2375
+ description: "Compose multiple orbitals from .orbitals/ into a final application schema using composeBehaviors. Handles layout strategy detection, event wiring, and page generation. Writes schema.orb and validates.",
2046
2376
  schema: z.object({
2047
2377
  appName: z.string().optional().describe("Application name"),
2048
2378
  orbitalFiles: z.string().optional().describe("JSON array of orbital filenames. Defaults to all .json files in .orbitals/"),
2049
- pagesJson: z.string().optional().describe("JSON array of page definitions: [{ name, path, isInitial?, traits: [{ ref }] }]. Auto-generated if omitted.")
2379
+ layoutStrategy: z.enum(["sidebar", "tabs", "dashboard", "wizard-flow", "auto"]).optional().describe("Layout strategy. Defaults to auto."),
2380
+ eventWiringJson: z.string().optional().describe("JSON array of event wiring entries: [{ from, event, to, triggers }]"),
2381
+ entityMappingsJson: z.string().optional().describe('JSON object of entity mappings: { "Entity.field": "OtherEntity.field" }')
2050
2382
  })
2051
2383
  }
2052
2384
  );