@orcalang/orca-lang 0.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.
Files changed (183) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +128 -0
  3. package/dist/auth/index.d.ts +6 -0
  4. package/dist/auth/index.d.ts.map +1 -0
  5. package/dist/auth/index.js +6 -0
  6. package/dist/auth/index.js.map +1 -0
  7. package/dist/auth/lock.d.ts +2 -0
  8. package/dist/auth/lock.d.ts.map +1 -0
  9. package/dist/auth/lock.js +59 -0
  10. package/dist/auth/lock.js.map +1 -0
  11. package/dist/auth/providers/anthropic.d.ts +14 -0
  12. package/dist/auth/providers/anthropic.d.ts.map +1 -0
  13. package/dist/auth/providers/anthropic.js +145 -0
  14. package/dist/auth/providers/anthropic.js.map +1 -0
  15. package/dist/auth/providers/index.d.ts +3 -0
  16. package/dist/auth/providers/index.d.ts.map +1 -0
  17. package/dist/auth/providers/index.js +3 -0
  18. package/dist/auth/providers/index.js.map +1 -0
  19. package/dist/auth/providers/minimax.d.ts +6 -0
  20. package/dist/auth/providers/minimax.d.ts.map +1 -0
  21. package/dist/auth/providers/minimax.js +65 -0
  22. package/dist/auth/providers/minimax.js.map +1 -0
  23. package/dist/auth/refresh.d.ts +8 -0
  24. package/dist/auth/refresh.d.ts.map +1 -0
  25. package/dist/auth/refresh.js +104 -0
  26. package/dist/auth/refresh.js.map +1 -0
  27. package/dist/auth/store.d.ts +11 -0
  28. package/dist/auth/store.d.ts.map +1 -0
  29. package/dist/auth/store.js +63 -0
  30. package/dist/auth/store.js.map +1 -0
  31. package/dist/auth/types.d.ts +51 -0
  32. package/dist/auth/types.d.ts.map +1 -0
  33. package/dist/auth/types.js +2 -0
  34. package/dist/auth/types.js.map +1 -0
  35. package/dist/compiler/mermaid.d.ts +3 -0
  36. package/dist/compiler/mermaid.d.ts.map +1 -0
  37. package/dist/compiler/mermaid.js +86 -0
  38. package/dist/compiler/mermaid.js.map +1 -0
  39. package/dist/compiler/xstate.d.ts +15 -0
  40. package/dist/compiler/xstate.d.ts.map +1 -0
  41. package/dist/compiler/xstate.js +542 -0
  42. package/dist/compiler/xstate.js.map +1 -0
  43. package/dist/config/index.d.ts +3 -0
  44. package/dist/config/index.d.ts.map +1 -0
  45. package/dist/config/index.js +3 -0
  46. package/dist/config/index.js.map +1 -0
  47. package/dist/config/loader.d.ts +4 -0
  48. package/dist/config/loader.d.ts.map +1 -0
  49. package/dist/config/loader.js +109 -0
  50. package/dist/config/loader.js.map +1 -0
  51. package/dist/config/types.d.ts +13 -0
  52. package/dist/config/types.d.ts.map +1 -0
  53. package/dist/config/types.js +8 -0
  54. package/dist/config/types.js.map +1 -0
  55. package/dist/generators/index.d.ts +5 -0
  56. package/dist/generators/index.d.ts.map +1 -0
  57. package/dist/generators/index.js +5 -0
  58. package/dist/generators/index.js.map +1 -0
  59. package/dist/generators/registry.d.ts +12 -0
  60. package/dist/generators/registry.d.ts.map +1 -0
  61. package/dist/generators/registry.js +15 -0
  62. package/dist/generators/registry.js.map +1 -0
  63. package/dist/generators/typescript.d.ts +9 -0
  64. package/dist/generators/typescript.d.ts.map +1 -0
  65. package/dist/generators/typescript.js +55 -0
  66. package/dist/generators/typescript.js.map +1 -0
  67. package/dist/index.d.ts +10 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +630 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/llm/anthropic.d.ts +14 -0
  72. package/dist/llm/anthropic.d.ts.map +1 -0
  73. package/dist/llm/anthropic.js +87 -0
  74. package/dist/llm/anthropic.js.map +1 -0
  75. package/dist/llm/grok.d.ts +13 -0
  76. package/dist/llm/grok.d.ts.map +1 -0
  77. package/dist/llm/grok.js +60 -0
  78. package/dist/llm/grok.js.map +1 -0
  79. package/dist/llm/index.d.ts +11 -0
  80. package/dist/llm/index.d.ts.map +1 -0
  81. package/dist/llm/index.js +23 -0
  82. package/dist/llm/index.js.map +1 -0
  83. package/dist/llm/ollama.d.ts +11 -0
  84. package/dist/llm/ollama.d.ts.map +1 -0
  85. package/dist/llm/ollama.js +51 -0
  86. package/dist/llm/ollama.js.map +1 -0
  87. package/dist/llm/openai.d.ts +13 -0
  88. package/dist/llm/openai.d.ts.map +1 -0
  89. package/dist/llm/openai.js +61 -0
  90. package/dist/llm/openai.js.map +1 -0
  91. package/dist/llm/provider.d.ts +32 -0
  92. package/dist/llm/provider.d.ts.map +1 -0
  93. package/dist/llm/provider.js +2 -0
  94. package/dist/llm/provider.js.map +1 -0
  95. package/dist/parser/ast-to-markdown.d.ts +3 -0
  96. package/dist/parser/ast-to-markdown.d.ts.map +1 -0
  97. package/dist/parser/ast-to-markdown.js +209 -0
  98. package/dist/parser/ast-to-markdown.js.map +1 -0
  99. package/dist/parser/ast.d.ts +183 -0
  100. package/dist/parser/ast.d.ts.map +1 -0
  101. package/dist/parser/ast.js +3 -0
  102. package/dist/parser/ast.js.map +1 -0
  103. package/dist/parser/markdown-parser.d.ts +8 -0
  104. package/dist/parser/markdown-parser.d.ts.map +1 -0
  105. package/dist/parser/markdown-parser.js +838 -0
  106. package/dist/parser/markdown-parser.js.map +1 -0
  107. package/dist/runtime/effects.d.ts +17 -0
  108. package/dist/runtime/effects.d.ts.map +1 -0
  109. package/dist/runtime/effects.js +28 -0
  110. package/dist/runtime/effects.js.map +1 -0
  111. package/dist/runtime/machine.d.ts +8 -0
  112. package/dist/runtime/machine.d.ts.map +1 -0
  113. package/dist/runtime/machine.js +158 -0
  114. package/dist/runtime/machine.js.map +1 -0
  115. package/dist/runtime/types.d.ts +37 -0
  116. package/dist/runtime/types.d.ts.map +1 -0
  117. package/dist/runtime/types.js +3 -0
  118. package/dist/runtime/types.js.map +1 -0
  119. package/dist/skills.d.ts +114 -0
  120. package/dist/skills.d.ts.map +1 -0
  121. package/dist/skills.js +1103 -0
  122. package/dist/skills.js.map +1 -0
  123. package/dist/tools.d.ts +18 -0
  124. package/dist/tools.d.ts.map +1 -0
  125. package/dist/tools.js +124 -0
  126. package/dist/tools.js.map +1 -0
  127. package/dist/verifier/completeness.d.ts +4 -0
  128. package/dist/verifier/completeness.d.ts.map +1 -0
  129. package/dist/verifier/completeness.js +82 -0
  130. package/dist/verifier/completeness.js.map +1 -0
  131. package/dist/verifier/determinism.d.ts +17 -0
  132. package/dist/verifier/determinism.d.ts.map +1 -0
  133. package/dist/verifier/determinism.js +301 -0
  134. package/dist/verifier/determinism.js.map +1 -0
  135. package/dist/verifier/properties.d.ts +6 -0
  136. package/dist/verifier/properties.d.ts.map +1 -0
  137. package/dist/verifier/properties.js +404 -0
  138. package/dist/verifier/properties.js.map +1 -0
  139. package/dist/verifier/structural.d.ts +50 -0
  140. package/dist/verifier/structural.d.ts.map +1 -0
  141. package/dist/verifier/structural.js +692 -0
  142. package/dist/verifier/structural.js.map +1 -0
  143. package/dist/verifier/types.d.ts +40 -0
  144. package/dist/verifier/types.d.ts.map +1 -0
  145. package/dist/verifier/types.js +2 -0
  146. package/dist/verifier/types.js.map +1 -0
  147. package/package.json +49 -0
  148. package/src/auth/index.ts +5 -0
  149. package/src/auth/lock.ts +71 -0
  150. package/src/auth/providers/anthropic.ts +192 -0
  151. package/src/auth/providers/index.ts +17 -0
  152. package/src/auth/providers/minimax.ts +100 -0
  153. package/src/auth/refresh.ts +138 -0
  154. package/src/auth/store.ts +75 -0
  155. package/src/auth/types.ts +62 -0
  156. package/src/compiler/mermaid.ts +109 -0
  157. package/src/compiler/xstate.ts +615 -0
  158. package/src/config/index.ts +2 -0
  159. package/src/config/loader.ts +122 -0
  160. package/src/config/types.ts +21 -0
  161. package/src/generators/index.ts +6 -0
  162. package/src/generators/registry.ts +27 -0
  163. package/src/generators/typescript.ts +67 -0
  164. package/src/index.ts +671 -0
  165. package/src/llm/anthropic.ts +102 -0
  166. package/src/llm/grok.ts +73 -0
  167. package/src/llm/index.ts +29 -0
  168. package/src/llm/ollama.ts +62 -0
  169. package/src/llm/openai.ts +74 -0
  170. package/src/llm/provider.ts +35 -0
  171. package/src/parser/ast-to-markdown.ts +220 -0
  172. package/src/parser/ast.ts +236 -0
  173. package/src/parser/markdown-parser.ts +844 -0
  174. package/src/runtime/effects.ts +48 -0
  175. package/src/runtime/machine.ts +201 -0
  176. package/src/runtime/types.ts +44 -0
  177. package/src/skills.ts +1339 -0
  178. package/src/tools.ts +144 -0
  179. package/src/verifier/completeness.ts +89 -0
  180. package/src/verifier/determinism.ts +328 -0
  181. package/src/verifier/properties.ts +507 -0
  182. package/src/verifier/structural.ts +803 -0
  183. package/src/verifier/types.ts +45 -0
package/dist/skills.js ADDED
@@ -0,0 +1,1103 @@
1
+ import { readFileSync } from 'fs';
2
+ import { parseMarkdown } from './parser/markdown-parser.js';
3
+ import { checkStructural, analyzeFile } from './verifier/structural.js';
4
+ import { checkCompleteness } from './verifier/completeness.js';
5
+ import { checkDeterminism } from './verifier/determinism.js';
6
+ import { checkProperties } from './verifier/properties.js';
7
+ import { compileToXState } from './compiler/xstate.js';
8
+ import { compileToMermaid } from './compiler/mermaid.js';
9
+ import { loadConfig } from './config/index.js';
10
+ import { createProvider } from './llm/index.js';
11
+ import { getCodeGenerator } from './generators/index.js';
12
+ function resolveSource(input) {
13
+ if (input.source !== undefined)
14
+ return input.source;
15
+ if (input.file !== undefined)
16
+ return readFileSync(input.file, 'utf-8');
17
+ throw new Error('SkillInput requires either source or file');
18
+ }
19
+ function resolveLabel(input) {
20
+ return input.file ?? '<source>';
21
+ }
22
+ /** Parse an Orca machine definition (single-machine only). */
23
+ function parseSource(label, source) {
24
+ const { file } = parseMarkdown(source);
25
+ if (file.machines.length > 1) {
26
+ throw new Error(`${label} contains multiple machines.`);
27
+ }
28
+ return file.machines[0];
29
+ }
30
+ function collectStateNames(states) {
31
+ const names = [];
32
+ for (const s of states) {
33
+ names.push(s.name);
34
+ if (s.contains)
35
+ names.push(...collectStateNames(s.contains));
36
+ if (s.parallel) {
37
+ for (const region of s.parallel.regions) {
38
+ names.push(...collectStateNames(region.states));
39
+ }
40
+ }
41
+ }
42
+ return names;
43
+ }
44
+ function serializeType(type) {
45
+ switch (type.kind) {
46
+ case 'string': return 'string';
47
+ case 'int': return 'int';
48
+ case 'decimal': return 'decimal';
49
+ case 'bool': return 'bool';
50
+ case 'array': return `${type.elementType}[]`;
51
+ case 'map': return `Map<${type.keyType}, ${type.valueType}>`;
52
+ case 'optional': return `${type.innerType}?`;
53
+ case 'custom': return type.name;
54
+ }
55
+ }
56
+ function serializeGuardExpression(expr) {
57
+ switch (expr.kind) {
58
+ case 'true': return 'true';
59
+ case 'false': return 'false';
60
+ case 'not': return `!(${serializeGuardExpression(expr.expr)})`;
61
+ case 'and': return `(${serializeGuardExpression(expr.left)} && ${serializeGuardExpression(expr.right)})`;
62
+ case 'or': return `(${serializeGuardExpression(expr.left)} || ${serializeGuardExpression(expr.right)})`;
63
+ case 'compare': {
64
+ const opMap = { eq: '==', ne: '!=', lt: '<', gt: '>', le: '<=', ge: '>=' };
65
+ const leftPath = expr.left.path.join('.');
66
+ const left = leftPath.startsWith('ctx.') ? leftPath : `ctx.${leftPath}`;
67
+ const right = expr.right.type === 'string'
68
+ ? `"${expr.right.value}"`
69
+ : String(expr.right.value);
70
+ return `${left} ${opMap[expr.op]} ${right}`;
71
+ }
72
+ case 'nullcheck': {
73
+ const rawPath = expr.expr.path.join('.');
74
+ const path = rawPath.startsWith('ctx.') ? rawPath : `ctx.${rawPath}`;
75
+ return expr.isNull ? `${path} == null` : `${path} != null`;
76
+ }
77
+ }
78
+ }
79
+ function machineToParseResult(machine) {
80
+ return {
81
+ name: machine.name,
82
+ states: collectStateNames(machine.states),
83
+ events: machine.events.map(e => e.name),
84
+ transitions: machine.transitions.map(t => ({
85
+ source: t.source,
86
+ event: t.event,
87
+ guard: t.guard
88
+ ? (t.guard.negated ? `!${t.guard.name}` : t.guard.name)
89
+ : undefined,
90
+ target: t.target,
91
+ action: t.action,
92
+ })),
93
+ guards: machine.guards.map(g => ({
94
+ name: g.name,
95
+ expression: serializeGuardExpression(g.expression),
96
+ })),
97
+ actions: machine.actions.map(a => ({
98
+ name: a.name,
99
+ hasEffect: a.hasEffect,
100
+ effectType: a.effectType,
101
+ })),
102
+ effects: machine.effects?.map(e => ({
103
+ name: e.name,
104
+ input: e.input,
105
+ output: e.output,
106
+ })),
107
+ context: machine.context.map(f => ({
108
+ name: f.name,
109
+ type: serializeType(f.type),
110
+ default: f.defaultValue,
111
+ })),
112
+ };
113
+ }
114
+ export function parseSkill(input) {
115
+ const source = resolveSource(input);
116
+ try {
117
+ const { file } = parseMarkdown(source);
118
+ const machines = file.machines.map(machineToParseResult);
119
+ return {
120
+ status: 'success',
121
+ machines,
122
+ machine: machines[0],
123
+ };
124
+ }
125
+ catch (err) {
126
+ return {
127
+ status: 'error',
128
+ error: err instanceof Error ? err.message : String(err),
129
+ };
130
+ }
131
+ }
132
+ // ── /verify-machine ───────────────────────────────────────────────────────────
133
+ export async function verifySkill(input) {
134
+ const source = resolveSource(input);
135
+ const label = resolveLabel(input);
136
+ let machine;
137
+ try {
138
+ machine = parseSource(label, source);
139
+ }
140
+ catch (err) {
141
+ // Parse error - return as verification error
142
+ const message = err instanceof Error ? err.message : String(err);
143
+ return {
144
+ status: 'invalid',
145
+ machine: extractMachineNameFromSource(source) || 'unknown',
146
+ states: 0,
147
+ events: 0,
148
+ transitions: 0,
149
+ errors: [{
150
+ code: 'PARSE_ERROR',
151
+ message,
152
+ severity: 'error',
153
+ suggestion: 'Check Orca syntax - ensure proper formatting for .orca or .orca.md files',
154
+ }],
155
+ };
156
+ }
157
+ const structural = checkStructural(machine);
158
+ const completeness = checkCompleteness(machine);
159
+ const determinism = checkDeterminism(machine);
160
+ const properties = checkProperties(machine);
161
+ const mapError = (e) => ({
162
+ code: e.code,
163
+ message: e.message,
164
+ severity: e.severity,
165
+ location: e.location ? {
166
+ state: e.location.state,
167
+ event: e.location.event,
168
+ } : undefined,
169
+ suggestion: e.suggestion,
170
+ });
171
+ const allErrors = [
172
+ ...structural.errors.map(mapError),
173
+ ...completeness.errors.map(mapError),
174
+ ...determinism.errors.map(mapError),
175
+ ...properties.errors.map(mapError),
176
+ ];
177
+ return {
178
+ status: allErrors.some(e => e.severity === 'error') ? 'invalid' : 'valid',
179
+ machine: machine.name,
180
+ states: machine.states.length,
181
+ events: machine.events.length,
182
+ transitions: machine.transitions.length,
183
+ errors: allErrors,
184
+ };
185
+ }
186
+ export async function compileSkill(input, target) {
187
+ const source = resolveSource(input);
188
+ const machine = parseSource(resolveLabel(input), source);
189
+ // Run verification to get warnings
190
+ const structural = checkStructural(machine);
191
+ const completeness = checkCompleteness(machine);
192
+ const determinism = checkDeterminism(machine);
193
+ const warnings = [
194
+ ...structural.errors.filter(e => e.severity === 'warning').map(e => ({
195
+ code: e.code,
196
+ message: e.message,
197
+ severity: e.severity,
198
+ })),
199
+ ...completeness.errors.filter(e => e.severity === 'warning').map(e => ({
200
+ code: e.code,
201
+ message: e.message,
202
+ severity: e.severity,
203
+ })),
204
+ ...determinism.errors.filter(e => e.severity === 'warning').map(e => ({
205
+ code: e.code,
206
+ message: e.message,
207
+ severity: e.severity,
208
+ })),
209
+ ];
210
+ const output = target === 'xstate'
211
+ ? compileToXState(machine)
212
+ : compileToMermaid(machine);
213
+ return {
214
+ status: 'success',
215
+ target,
216
+ output,
217
+ warnings,
218
+ };
219
+ }
220
+ export async function generateActionsSkill(input, language = 'typescript', useLLM = false, configPath, generateTests = false) {
221
+ const source = resolveSource(input);
222
+ const machine = parseSource(resolveLabel(input), source);
223
+ const actions = machine.actions.map(action => ({
224
+ name: action.name,
225
+ signature: `${action.name}(${action.parameters.join(', ')}) -> ${action.returnType}${action.hasEffect ? ` + Effect<${action.effectType}>` : ''}`,
226
+ parameters: action.parameters,
227
+ returnType: action.returnType,
228
+ hasEffect: action.hasEffect,
229
+ effectType: action.effectType,
230
+ context_used: extractContextFields(machine, action.name),
231
+ }));
232
+ let scaffolds = {};
233
+ let tests = {};
234
+ if (useLLM) {
235
+ // Use LLM to generate action implementations
236
+ const config = loadConfig(configPath);
237
+ const provider = createProvider(config.provider, {
238
+ api_key: config.api_key,
239
+ base_url: config.base_url,
240
+ model: config.model,
241
+ max_tokens: config.max_tokens,
242
+ temperature: config.temperature,
243
+ });
244
+ scaffolds = await generateWithLLM(provider, actions, machine, language);
245
+ if (generateTests) {
246
+ tests = await generateUnitTests(provider, actions, machine, language);
247
+ }
248
+ }
249
+ else {
250
+ // Use template-based scaffold generation
251
+ for (const action of machine.actions) {
252
+ scaffolds[action.name] = generateActionScaffold(action, machine, language);
253
+ }
254
+ if (generateTests) {
255
+ tests = generateTemplateTests(actions, machine, language);
256
+ }
257
+ }
258
+ return {
259
+ status: 'success',
260
+ machine: machine.name,
261
+ actions,
262
+ scaffolds,
263
+ tests: Object.keys(tests).length > 0 ? tests : undefined,
264
+ };
265
+ }
266
+ async function generateWithLLM(provider, actions, machine, language) {
267
+ const generator = getCodeGenerator(language);
268
+ const scaffolds = {};
269
+ const systemPrompt = `You are an expert ${language} developer specializing in state machine action implementations.
270
+ Given a machine definition and action signatures, generate complete action implementations.
271
+ Follow the type signatures exactly. Use the provided context fields.
272
+ If an action has an effect, return [newContext, effect] tuple.`;
273
+ for (const action of actions) {
274
+ const userPrompt = `Machine: ${machine.name}
275
+ Context fields: ${machine.context.map(f => `${f.name}: ${f.type || 'unknown'}`).join(', ')}
276
+
277
+ Action: ${action.signature}
278
+ Description: ${action.name}${action.hasEffect ? ` (effect type: ${action.effectType})` : ''}
279
+
280
+ Generate the implementation:`;
281
+ try {
282
+ const response = await provider.complete({
283
+ messages: [
284
+ { role: 'system', content: systemPrompt },
285
+ { role: 'user', content: userPrompt },
286
+ ],
287
+ model: '',
288
+ max_tokens: 2048,
289
+ temperature: 0.5,
290
+ });
291
+ scaffolds[action.name] = response.content;
292
+ }
293
+ catch (err) {
294
+ console.error(`LLM error for action ${action.name}: ${err instanceof Error ? err.message : String(err)}`);
295
+ // Fall back to scaffold on error
296
+ scaffolds[action.name] = generateActionScaffold(action, machine, language);
297
+ }
298
+ }
299
+ return scaffolds;
300
+ }
301
+ async function generateUnitTests(provider, actions, machine, language) {
302
+ const tests = {};
303
+ const systemPrompt = `You are an expert in ${language} testing. Generate unit tests for state machine actions.
304
+ Each test should verify the action's behavior with specific input contexts.
305
+ Use a testing framework appropriate for ${language}.`;
306
+ for (const action of actions) {
307
+ const contextFields = machine.context.map(f => `${f.name}: ${f.type || 'unknown'}`).join(', ');
308
+ const userPrompt = `Machine: ${machine.name}
309
+ Context type: { ${contextFields} }
310
+ Action: ${action.signature}
311
+ Context fields used: ${action.context_used.join(', ') || 'all fields'}
312
+
313
+ Generate unit tests that verify:
314
+ 1. The action transforms context correctly
315
+ 2. All context fields are preserved (if not modified)
316
+ 3. Edge cases for the specific action
317
+
318
+ Format: Provide test code in a code fence.`;
319
+ try {
320
+ const response = await provider.complete({
321
+ messages: [
322
+ { role: 'system', content: systemPrompt },
323
+ { role: 'user', content: userPrompt },
324
+ ],
325
+ model: '',
326
+ max_tokens: 2048,
327
+ temperature: 0.3,
328
+ });
329
+ tests[action.name] = response.content;
330
+ }
331
+ catch (err) {
332
+ console.error(`Test generation error for action ${action.name}: ${err instanceof Error ? err.message : String(err)}`);
333
+ // Fall back to template tests on error
334
+ tests[action.name] = generateTemplateTestsForAction(action, machine);
335
+ }
336
+ }
337
+ return tests;
338
+ }
339
+ function generateTemplateTests(actions, machine, language = 'typescript') {
340
+ const tests = {};
341
+ for (const action of actions) {
342
+ tests[action.name] = generateTemplateTestsForAction(action, machine, language);
343
+ }
344
+ return tests;
345
+ }
346
+ function generateTemplateTestsForAction(action, machine, language = 'typescript') {
347
+ if (language === 'python') {
348
+ return generatePythonTestScaffold(action, machine);
349
+ }
350
+ if (language === 'go') {
351
+ return generateGoTestScaffold(action, machine);
352
+ }
353
+ return generateTypeScriptTestScaffold(action, machine);
354
+ }
355
+ function generateTypeScriptTestScaffold(action, machine) {
356
+ const ctxFields = machine.context.map(f => {
357
+ return ` ${f.name}: ${getDefaultValueForType(f.type, 'typescript')}`;
358
+ }).join(',\n');
359
+ const contextUsed = action.context_used.length > 0 ? action.context_used : machine.context.map(f => f.name);
360
+ if (action.hasEffect) {
361
+ return `// Tests for ${action.name}
362
+ describe('${action.name}', () => {
363
+ const defaultContext = {
364
+ ${ctxFields}
365
+ };
366
+
367
+ it('should return updated context and emit effect', () => {
368
+ const ctx = { ...defaultContext };
369
+ const result = ${action.name}(ctx);
370
+
371
+ expect(result).toBeDefined();
372
+ expect(Array.isArray(result)).toBe(true);
373
+ expect(result[1]).toHaveProperty('type');
374
+ expect(result[1].type).toBe('${action.effectType}');
375
+ });
376
+
377
+ it('should preserve unmodified context fields', () => {
378
+ const ctx = { ...defaultContext };
379
+ const [newCtx] = ${action.name}(ctx);
380
+
381
+ ${contextUsed.filter(f => !actionModifiesField(action, f)).map(f => `expect(newCtx.${f}).toBe(ctx.${f});`).join('\n ')}
382
+ });
383
+ });
384
+ `;
385
+ }
386
+ return `// Tests for ${action.name}
387
+ describe('${action.name}', () => {
388
+ const defaultContext = {
389
+ ${ctxFields}
390
+ };
391
+
392
+ it('should transform context correctly', () => {
393
+ const ctx = { ...defaultContext };
394
+ const result = ${action.name}(ctx);
395
+
396
+ expect(result).toBeDefined();
397
+ expect(typeof result).toBe('object');
398
+ });
399
+
400
+ it('should preserve unmodified context fields', () => {
401
+ const ctx = { ...defaultContext };
402
+ const result = ${action.name}(ctx);
403
+
404
+ ${contextUsed.filter(f => !actionModifiesField(action, f)).map(f => `expect(result.${f}).toBe(ctx.${f});`).join('\n ')}
405
+ });
406
+ });
407
+ `;
408
+ }
409
+ function generatePythonTestScaffold(action, machine) {
410
+ const ctxFields = machine.context.map(f => {
411
+ return ` "${f.name}": ${getDefaultValueForType(f.type, 'python')}`;
412
+ }).join(',\n');
413
+ const contextUsed = action.context_used.length > 0 ? action.context_used : machine.context.map(f => f.name);
414
+ const preserved = contextUsed.filter(f => !actionModifiesField(action, f));
415
+ if (action.hasEffect) {
416
+ return `# Tests for ${action.name}
417
+ import pytest
418
+ from orca_runtime_python import Effect, EffectResult, EffectStatus
419
+
420
+
421
+ def make_context():
422
+ return {
423
+ ${ctxFields}
424
+ }
425
+
426
+
427
+ @pytest.mark.asyncio
428
+ async def test_${action.name}_executes_effect():
429
+ effect = Effect(
430
+ type="${action.effectType}",
431
+ payload={"action": "${action.name}", "context": make_context(), "event": None},
432
+ )
433
+ result = await ${action.name}(effect)
434
+ assert result.status == EffectStatus.SUCCESS
435
+
436
+
437
+ @pytest.mark.asyncio
438
+ async def test_${action.name}_returns_effect_result():
439
+ effect = Effect(
440
+ type="${action.effectType}",
441
+ payload={"action": "${action.name}", "context": make_context(), "event": None},
442
+ )
443
+ result = await ${action.name}(effect)
444
+ assert isinstance(result, EffectResult)
445
+ `;
446
+ }
447
+ return `# Tests for ${action.name}
448
+ import pytest
449
+
450
+
451
+ def make_context():
452
+ return {
453
+ ${ctxFields}
454
+ }
455
+
456
+
457
+ @pytest.mark.asyncio
458
+ async def test_${action.name}_transforms_context():
459
+ ctx = make_context()
460
+ result = await ${action.name}(ctx)
461
+ assert isinstance(result, dict)
462
+
463
+
464
+ @pytest.mark.asyncio
465
+ async def test_${action.name}_preserves_fields():
466
+ ctx = make_context()
467
+ result = await ${action.name}(ctx)
468
+ ${preserved.map(f => ` assert result["${f}"] == ctx["${f}"]`).join('\n')}
469
+ `;
470
+ }
471
+ function generateGoTestScaffold(action, machine) {
472
+ const fnName = toPascalCase(action.name);
473
+ const ctxFields = machine.context.map(f => {
474
+ return `\t\t"${f.name}": ${getDefaultValueForType(f.type, 'go')}`;
475
+ }).join(',\n');
476
+ const contextUsed = action.context_used.length > 0 ? action.context_used : machine.context.map(f => f.name);
477
+ const preserved = contextUsed.filter(f => !actionModifiesField(action, f));
478
+ if (action.hasEffect) {
479
+ return `// Tests for ${action.name}
480
+ package actions_test
481
+
482
+ import (
483
+ \t"testing"
484
+ \torca "github.com/jascal/orca-lang/packages/runtime-go/orca_runtime_go"
485
+ )
486
+
487
+ func Test${fnName}(t *testing.T) {
488
+ \tctx := orca.Context{
489
+ ${ctxFields},
490
+ \t}
491
+ \teffect := orca.Effect{
492
+ \t\tType: "${action.effectType}",
493
+ \t\tPayload: map[string]any{"action": "${action.name}", "context": ctx},
494
+ \t}
495
+ \tresult := ${fnName}(effect)
496
+ \tif result.Status != orca.EffectStatusSuccess {
497
+ \t\tt.Errorf("expected success, got %v: %s", result.Status, result.Error)
498
+ \t}
499
+ }
500
+ `;
501
+ }
502
+ return `// Tests for ${action.name}
503
+ package actions_test
504
+
505
+ import (
506
+ \t"testing"
507
+ \torca "github.com/jascal/orca-lang/packages/runtime-go/orca_runtime_go"
508
+ )
509
+
510
+ func Test${fnName}(t *testing.T) {
511
+ \tctx := orca.Context{
512
+ ${ctxFields},
513
+ \t}
514
+ \tevent := map[string]any{"type": "test"}
515
+ \tresult := ${fnName}(ctx, event)
516
+ \tif result == nil {
517
+ \t\tt.Fatal("${fnName} returned nil context")
518
+ \t}
519
+ ${preserved.map(f => `\tif result["${f}"] != ctx["${f}"] {\n\t\tt.Errorf("${f}: got %v, want %v", result["${f}"], ctx["${f}"])\n\t}`).join('\n')}
520
+ }
521
+ `;
522
+ }
523
+ function actionModifiesField(action, fieldName) {
524
+ // Heuristic: if the action name suggests modification of a field, it likely modifies it
525
+ const modifiers = ['increment', 'decrement', 'set', 'update', 'add', 'remove', 'clear', 'reset', 'toggle'];
526
+ const name = action.name.toLowerCase();
527
+ for (const mod of modifiers) {
528
+ if (name.includes(mod) && name.includes(fieldName)) {
529
+ return true;
530
+ }
531
+ }
532
+ // Check if it's a setter-style action
533
+ if (name.startsWith('set_') && fieldName === name.replace('set_', '')) {
534
+ return true;
535
+ }
536
+ return false;
537
+ }
538
+ function getDefaultValueForType(type, language = 'typescript') {
539
+ if (!type) {
540
+ return language === 'python' ? '""' : language === 'go' ? '""' : "''";
541
+ }
542
+ if (typeof type === 'object' && 'kind' in type) {
543
+ if (language === 'python') {
544
+ switch (type.kind) {
545
+ case 'string': return '""';
546
+ case 'int': return '0';
547
+ case 'decimal': return '0.0';
548
+ case 'bool': return 'False';
549
+ case 'optional': return 'None';
550
+ case 'array': return '[]';
551
+ case 'map': return '{}';
552
+ case 'custom': return 'None';
553
+ }
554
+ }
555
+ else if (language === 'go') {
556
+ switch (type.kind) {
557
+ case 'string': return '""';
558
+ case 'int': return '0';
559
+ case 'decimal': return '0.0';
560
+ case 'bool': return 'false';
561
+ case 'optional': return 'nil';
562
+ case 'array': return 'nil';
563
+ case 'map': return 'nil';
564
+ case 'custom': return 'nil';
565
+ }
566
+ }
567
+ else {
568
+ switch (type.kind) {
569
+ case 'string': return "''";
570
+ case 'int':
571
+ case 'decimal': return '0';
572
+ case 'bool': return 'false';
573
+ case 'optional': return 'null';
574
+ case 'array': return '[]';
575
+ case 'map': return '{}';
576
+ case 'custom': return 'null';
577
+ }
578
+ }
579
+ }
580
+ return language === 'python' ? 'None' : language === 'go' ? 'nil' : 'null';
581
+ }
582
+ function extractContextFields(machine, actionName) {
583
+ const fields = [];
584
+ // Check transitions for context usage
585
+ for (const t of machine.transitions) {
586
+ if (t.action === actionName) {
587
+ // In a real implementation, this would analyze the guard expressions
588
+ // For now, just return all context fields
589
+ return machine.context.map(f => f.name);
590
+ }
591
+ }
592
+ // Check on_entry/on_exit
593
+ for (const state of machine.states) {
594
+ if (state.onEntry === actionName || state.onExit === actionName) {
595
+ return machine.context.map(f => f.name);
596
+ }
597
+ }
598
+ return fields;
599
+ }
600
+ function toPascalCase(snake) {
601
+ return snake.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
602
+ }
603
+ function generateActionScaffold(action, machine, language) {
604
+ if (language === 'python') {
605
+ if (action.hasEffect) {
606
+ return `# Action: ${action.name}
607
+ # Effect: ${action.effectType}
608
+ # Register via: bus.register_effect_handler("${action.effectType}", ${action.name})
609
+
610
+ from typing import Any
611
+ from orca_runtime_python import Effect, EffectResult, EffectStatus
612
+
613
+ async def ${action.name}(effect: Effect) -> EffectResult:
614
+ # effect.payload contains {"action": "${action.name}", "context": {...}, "event": None}
615
+ ctx = effect.payload.get("context", {})
616
+ # TODO: Implement effect
617
+ return EffectResult(status=EffectStatus.SUCCESS, data={})
618
+ `;
619
+ }
620
+ return `# Action: ${action.name}
621
+ # Register via: machine.register_action("${action.name}", ${action.name})
622
+
623
+ from typing import Any
624
+
625
+ async def ${action.name}(ctx: dict[str, Any], event: Any = None) -> dict[str, Any]:
626
+ # TODO: Implement action
627
+ return dict(ctx)
628
+ `;
629
+ }
630
+ if (language === 'go') {
631
+ const fnName = toPascalCase(action.name);
632
+ if (action.hasEffect) {
633
+ return `// Action: ${action.name}
634
+ // Effect: ${action.effectType}
635
+ // Register via: bus.SetEffectHandler(DispatchEffects)
636
+ // Add a case for "${action.effectType}" in your effect dispatcher.
637
+
638
+ func ${fnName}(effect orca.Effect) orca.EffectResult {
639
+ \t// effect.Type == "${action.effectType}"
640
+ \t// effect.Payload contains {"action": "${action.name}", "context": map[string]any{...}}
641
+ \t// TODO: Implement effect
642
+ \treturn orca.EffectResult{Status: orca.EffectStatusSuccess, Data: map[string]any{}}
643
+ }
644
+ `;
645
+ }
646
+ return `// Action: ${action.name}
647
+ // Register via: machine.RegisterAction("${action.name}", ${fnName})
648
+
649
+ func ${fnName}(ctx orca.Context, event map[string]any) map[string]any {
650
+ \t// TODO: Implement action
651
+ \tresult := make(orca.Context)
652
+ \tfor k, v := range ctx {
653
+ \t\tresult[k] = v
654
+ \t}
655
+ \treturn result
656
+ }
657
+ `;
658
+ }
659
+ // TypeScript (default)
660
+ const params = action.parameters.map(p => {
661
+ if (p === 'ctx' || p === 'Context')
662
+ return 'ctx: Context';
663
+ if (p.startsWith('event:'))
664
+ return p;
665
+ return `ctx: Context, ${p}`;
666
+ }).join(', ');
667
+ if (action.hasEffect) {
668
+ return `// Action: ${action.name}
669
+ // Effect: ${action.effectType}
670
+
671
+ export function ${action.name}(${params}): [Context, Effect<${action.effectType}>] {
672
+ // TODO: Implement action
673
+ return [ctx, { type: '${action.effectType}', payload: {} }];
674
+ }
675
+
676
+ // Guard helper (if needed):
677
+ // export function ${action.name}_guard(ctx: Context): boolean {
678
+ // return true;
679
+ // }
680
+ `;
681
+ }
682
+ return `// Action: ${action.name}
683
+
684
+ export function ${action.name}(ctx: Context): Context {
685
+ // TODO: Implement action
686
+ return { ...ctx };
687
+ }
688
+ `;
689
+ }
690
+ export async function refineSkill(input, errors, configPath, maxIterations = 3) {
691
+ const config = loadConfig(configPath);
692
+ const provider = createProvider(config.provider, {
693
+ api_key: config.api_key,
694
+ base_url: config.base_url,
695
+ model: config.model,
696
+ max_tokens: config.max_tokens,
697
+ temperature: config.temperature,
698
+ });
699
+ const initialSource = resolveSource(input);
700
+ const initialMachine = parseSource(resolveLabel(input), initialSource);
701
+ const isMd = input.source !== undefined || (input.file !== undefined && (input.file.endsWith('.orca.md') || input.file.endsWith('.md')));
702
+ const systemPrompt = `You are an expert in Orca state machine language. Given verification errors, fix the machine definition.
703
+ Output ONLY the corrected Orca machine definition in ${isMd ? 'markdown (.orca.md)' : 'DSL (.orca)'} format, no explanations.`;
704
+ let currentSource = initialSource;
705
+ let currentErrors = errors;
706
+ let machineName = initialMachine.name;
707
+ let machineStates = initialMachine.states.map(s => s.name).join(', ');
708
+ let machineEvents = initialMachine.events.join(', ');
709
+ for (let i = 0; i < maxIterations; i++) {
710
+ const errorList = currentErrors.map(e => `[${e.severity.toUpperCase()}] ${e.code}: ${e.message}`).join('\n');
711
+ const userPrompt = `Machine: ${machineName}
712
+ States: ${machineStates}
713
+ Events: ${machineEvents}
714
+
715
+ Verification Errors:
716
+ ${errorList}
717
+
718
+ Machine Definition:
719
+ ${currentSource}
720
+
721
+ Provide the corrected machine definition:`;
722
+ try {
723
+ const response = await provider.complete({
724
+ messages: [
725
+ { role: 'system', content: systemPrompt },
726
+ { role: 'user', content: userPrompt },
727
+ ],
728
+ model: '',
729
+ max_tokens: 4096,
730
+ temperature: 0.3,
731
+ });
732
+ currentSource = stripCodeFence(response.content);
733
+ const verification = await verifySkill({ source: currentSource });
734
+ if (verification.status === 'valid') {
735
+ return {
736
+ status: 'success',
737
+ corrected: currentSource,
738
+ verification,
739
+ iterations: i + 1,
740
+ changes: [`Corrected after ${i + 1} iteration(s)`],
741
+ };
742
+ }
743
+ currentErrors = verification.errors.filter(e => e.severity === 'error');
744
+ // Update machine metadata for next iteration prompt
745
+ try {
746
+ const m = parseSource('<refined>', currentSource);
747
+ machineName = m.name;
748
+ machineStates = m.states.map(s => s.name).join(', ');
749
+ machineEvents = m.events.join(', ');
750
+ }
751
+ catch {
752
+ // parse error — keep previous metadata, errors already include PARSE_ERROR
753
+ }
754
+ }
755
+ catch (err) {
756
+ return {
757
+ status: 'error',
758
+ changes: [],
759
+ error: err instanceof Error ? err.message : String(err),
760
+ };
761
+ }
762
+ }
763
+ const finalVerification = await verifySkill({ source: currentSource });
764
+ return {
765
+ status: 'requires_refinement',
766
+ corrected: currentSource,
767
+ verification: finalVerification,
768
+ iterations: maxIterations,
769
+ changes: [`${maxIterations} iteration(s) attempted but errors remain`],
770
+ };
771
+ }
772
+ const ORCA_SYNTAX_REFERENCE = `Orca State Machine Markdown Syntax Reference (.orca.md):
773
+
774
+ The machine definition uses standard markdown: headings, tables, bullet lists, and blockquotes.
775
+
776
+ # machine MachineName
777
+
778
+ ## context
779
+
780
+ | Field | Type | Default |
781
+ |--------|---------|---------|
782
+ | field1 | string | |
783
+ | field2 | int | 0 |
784
+ | field3 | string? | |
785
+ | field4 | bool | |
786
+
787
+ ## events
788
+
789
+ - event1
790
+ - event2
791
+ - event3
792
+
793
+ ## state idle [initial]
794
+ > Description of this state
795
+ - on_entry: action_name
796
+ - on_exit: action_name
797
+ - timeout: 5s -> target_state
798
+ - ignore: event1, event2
799
+
800
+ ## state done [final]
801
+ > Terminal state
802
+
803
+ ## transitions
804
+
805
+ | Source | Event | Guard | Target | Action |
806
+ |--------|--------|--------|--------|---------|
807
+ | idle | event1 | | active | action1 |
808
+ | active | event2 | guard1 | done | action2 |
809
+ | active | event2 | !guard1| idle | |
810
+
811
+ ## guards
812
+
813
+ | Name | Expression |
814
+ |--------|---------------------------|
815
+ | guard1 | \`ctx.field2 > 10\` |
816
+ | guard2 | \`ctx.status == "active"\` |
817
+
818
+ NOTE: Guards support ONLY: comparisons (< > == != <= >=), null checks (== null, != null), boolean operators (and, or, not). NO method calls like .contains(), .includes(), etc.
819
+
820
+ ## actions
821
+
822
+ | Name | Signature |
823
+ |---------|--------------------------------------------|
824
+ | action1 | \`(ctx) -> Context\` |
825
+ | action2 | \`(ctx, event) -> Context\` |
826
+ | action3 | \`(ctx) -> Context + Effect<EffectType>\` |
827
+
828
+ Key syntax notes:
829
+ - [initial] marks the initial state (exactly one required)
830
+ - [final] marks terminal states (zero or more allowed)
831
+ - Empty action column means "no action" (transition only changes state)
832
+ - Guard column uses guard name from guards table; prefix with ! to negate
833
+ - Guard expressions in backticks in the guards table
834
+ - Action signatures in backticks in the actions table
835
+ - Effect<T> in return type means the action emits an effect
836
+ - ctx.field accesses context fields (read-only in guards)
837
+ - timeout: 5s -> target means 5 second timeout transition
838
+ - Action BODIES are NOT written in Orca - only signatures in the actions table
839
+ - Transitions reference actions by name; actions are implemented separately
840
+ - States are headings (## for top-level, ### for nested children)
841
+ - State descriptions use blockquotes (> text)
842
+ - File extension should be .orca.md`;
843
+ export async function generateOrcaSkill(naturalLanguageSpec, configPath, maxIterations = 3) {
844
+ const config = loadConfig(configPath);
845
+ // Check if LLM is available
846
+ if (!config.api_key && !process.env.ANTHROPIC_API_KEY && !process.env.MINIMAX_API_KEY) {
847
+ return {
848
+ status: 'error',
849
+ error: 'No API key available. Set ANTHROPIC_API_KEY or MINIMAX_API_KEY in .env',
850
+ };
851
+ }
852
+ const provider = createProvider(config.provider, {
853
+ api_key: config.api_key,
854
+ base_url: config.base_url,
855
+ model: config.model,
856
+ max_tokens: config.max_tokens,
857
+ temperature: config.temperature,
858
+ });
859
+ const systemPrompt = `You are an expert in Orca state machine design. Generate Orca machine definitions in markdown (.orca.md) format from natural language descriptions.
860
+
861
+ ${ORCA_SYNTAX_REFERENCE}
862
+
863
+ IMPORTANT - Guard Restrictions:
864
+ - Guards support ONLY: comparisons (< > == != <= >=), null checks, boolean operators
865
+ - NO method calls: do NOT use .contains(), .includes(), .length(), etc.
866
+ - For arrays, check .length > 0 or compare to null
867
+ - If you need complex logic, compute it in an action and store a boolean flag in context
868
+
869
+ IMPORTANT - Action Syntax:
870
+ - The actions table declares ONLY SIGNATURES (names and types), not implementations
871
+ - Write signatures in backticks: \`(ctx) -> Context\`
872
+ - NEVER write action bodies
873
+ - Transitions reference actions by name only
874
+
875
+ Important rules:
876
+ - Always include exactly ONE initial state marked with [initial]
877
+ - Final states should be marked with [final]
878
+ - Every (state, event) pair must have a transition OR the event must be ignored
879
+ - Use guards for conditional transitions
880
+ - Context should contain all data needed for guards and actions
881
+ - For effects (API calls, I/O), use Effect<T> return type in the signature
882
+
883
+ Output ONLY the Orca machine definition in .orca.md markdown format, wrapped in a code fence, with no additional text.`;
884
+ let currentOrca = '';
885
+ let lastErrors = [];
886
+ let iteration = 0;
887
+ while (iteration < maxIterations) {
888
+ const userPrompt = iteration === 0
889
+ ? `Generate an Orca state machine for:\n${naturalLanguageSpec}`
890
+ : `The previous Orca machine had verification errors. Fix them:\n\nPrevious Orca:\n${currentOrca}\n\nVerification errors:\n${JSON.stringify(lastErrors, null, 2)}\n\nProvide the corrected Orca machine definition:`;
891
+ try {
892
+ const response = await provider.complete({
893
+ messages: [
894
+ { role: 'system', content: systemPrompt },
895
+ { role: 'user', content: userPrompt },
896
+ ],
897
+ model: '',
898
+ max_tokens: 4096,
899
+ temperature: 0.5,
900
+ });
901
+ currentOrca = stripCodeFence(response.content);
902
+ const verification = await verifySkill({ source: currentOrca });
903
+ if (verification.status === 'valid') {
904
+ return {
905
+ status: 'success',
906
+ machine: verification.machine,
907
+ orca: currentOrca,
908
+ verification,
909
+ };
910
+ }
911
+ lastErrors = verification.errors;
912
+ iteration++;
913
+ }
914
+ catch (err) {
915
+ return {
916
+ status: 'error',
917
+ error: err instanceof Error ? err.message : String(err),
918
+ };
919
+ }
920
+ }
921
+ return {
922
+ status: 'requires_refinement',
923
+ machine: extractMachineNameFromSource(currentOrca),
924
+ orca: currentOrca,
925
+ verification: await verifySkill({ source: currentOrca }).catch(() => ({
926
+ status: 'invalid',
927
+ machine: 'unknown',
928
+ states: 0,
929
+ events: 0,
930
+ transitions: 0,
931
+ errors: [],
932
+ })),
933
+ };
934
+ }
935
+ // ── /generate-orca-multi ─────────────────────────────────────────────────────
936
+ const MULTI_MACHINE_SYNTAX_ADDENDUM = `
937
+ Multi-machine files
938
+ -------------------
939
+ Separate machines with a line containing only three dashes (---).
940
+
941
+ A state can invoke another machine in the same file:
942
+
943
+ ## state coordinatingState [initial]
944
+ - invoke: ChildMachineName
945
+ - on_done: nextState
946
+ - on_error: errorState
947
+
948
+ Rules:
949
+ - invoke: must name a machine defined in the same file
950
+ - on_done: fires when the invoked machine reaches a [final] state
951
+ - on_error: fires if the invoked machine cannot proceed
952
+ - Each state may invoke at most one child machine
953
+ - Circular invocations are not allowed (A invokes B invokes A)
954
+ - The coordinator machine should have a clear entry point and delegate work to child machines
955
+ - Child machines should have at least one [final] state so the coordinator's on_done can fire
956
+ `;
957
+ function verifyMultiSource(source) {
958
+ const { file } = parseMarkdown(source);
959
+ const allErrors = [];
960
+ // Cross-machine checks (cycle detection, unknown machines, input mappings)
961
+ const fileAnalysis = analyzeFile(file);
962
+ for (const e of [...fileAnalysis.errors, ...fileAnalysis.warnings]) {
963
+ allErrors.push({ code: e.code, message: e.message, severity: e.severity, suggestion: e.suggestion });
964
+ }
965
+ // Per-machine checks
966
+ for (const machine of file.machines) {
967
+ const structural = checkStructural(machine);
968
+ const completeness = checkCompleteness(machine);
969
+ const determinism = checkDeterminism(machine);
970
+ const properties = checkProperties(machine);
971
+ for (const e of [
972
+ ...structural.errors,
973
+ ...completeness.errors,
974
+ ...determinism.errors,
975
+ ...properties.errors,
976
+ ]) {
977
+ allErrors.push({
978
+ code: e.code,
979
+ message: `[${machine.name}] ${e.message}`,
980
+ severity: e.severity,
981
+ location: e.location ? { state: e.location.state, event: e.location.event } : undefined,
982
+ suggestion: e.suggestion,
983
+ });
984
+ }
985
+ }
986
+ return {
987
+ valid: !allErrors.some(e => e.severity === 'error'),
988
+ errors: allErrors,
989
+ machines: file.machines.map(m => m.name),
990
+ };
991
+ }
992
+ export async function generateOrcaMultiSkill(naturalLanguageSpec, configPath, maxIterations = 3) {
993
+ const config = loadConfig(configPath);
994
+ if (!config.api_key && !process.env.ANTHROPIC_API_KEY && !process.env.MINIMAX_API_KEY) {
995
+ return {
996
+ status: 'error',
997
+ error: 'No API key available. Set ANTHROPIC_API_KEY or MINIMAX_API_KEY in .env',
998
+ };
999
+ }
1000
+ const provider = createProvider(config.provider, {
1001
+ api_key: config.api_key,
1002
+ base_url: config.base_url,
1003
+ model: config.model,
1004
+ max_tokens: config.max_tokens,
1005
+ temperature: config.temperature,
1006
+ });
1007
+ const systemPrompt = `You are an expert in Orca state machine design. Generate coordinated multi-machine Orca definitions in markdown (.orca.md) format from natural language descriptions.
1008
+
1009
+ ${ORCA_SYNTAX_REFERENCE}
1010
+ ${MULTI_MACHINE_SYNTAX_ADDENDUM}
1011
+
1012
+ IMPORTANT - Guard Restrictions:
1013
+ - Guards support ONLY: comparisons (< > == != <= >=), null checks, boolean operators
1014
+ - NO method calls: do NOT use .contains(), .includes(), .length(), etc.
1015
+ - If you need complex logic, compute it in an action and store a boolean flag in context
1016
+
1017
+ IMPORTANT - Action Syntax:
1018
+ - The actions table declares ONLY SIGNATURES (names and types), not implementations
1019
+ - Write signatures in backticks: \`(ctx) -> Context\`
1020
+ - NEVER write action bodies
1021
+
1022
+ Multi-machine design principles:
1023
+ - Design a clear coordinator machine that delegates to focused child machines
1024
+ - Each child machine should have a single responsibility and at least one [final] state
1025
+ - Use invoke: in coordinator states to call child machines
1026
+ - Separate machines with --- on its own line
1027
+ - Name machines clearly (e.g. OrderCoordinator, PaymentProcessor, NotificationSender)
1028
+
1029
+ Output ONLY the complete multi-machine Orca definition in .orca.md markdown format, wrapped in a code fence, with no additional text.`;
1030
+ let currentOrca = '';
1031
+ let lastErrors = [];
1032
+ let iteration = 0;
1033
+ while (iteration < maxIterations) {
1034
+ const userPrompt = iteration === 0
1035
+ ? `Generate a multi-machine Orca state machine system for:\n${naturalLanguageSpec}\n\nDesign at least 2 coordinated machines separated by ---`
1036
+ : `The previous multi-machine Orca definition had verification errors. Fix them:\n\nPrevious Orca:\n${currentOrca}\n\nVerification errors:\n${JSON.stringify(lastErrors, null, 2)}\n\nProvide the corrected multi-machine Orca definition:`;
1037
+ try {
1038
+ const response = await provider.complete({
1039
+ messages: [
1040
+ { role: 'system', content: systemPrompt },
1041
+ { role: 'user', content: userPrompt },
1042
+ ],
1043
+ model: '',
1044
+ max_tokens: 8192,
1045
+ temperature: 0.5,
1046
+ });
1047
+ currentOrca = stripCodeFence(response.content);
1048
+ let verification;
1049
+ try {
1050
+ verification = verifyMultiSource(currentOrca);
1051
+ }
1052
+ catch (parseErr) {
1053
+ lastErrors = [{
1054
+ code: 'PARSE_ERROR',
1055
+ message: parseErr instanceof Error ? parseErr.message : String(parseErr),
1056
+ severity: 'error',
1057
+ }];
1058
+ iteration++;
1059
+ continue;
1060
+ }
1061
+ if (verification.valid) {
1062
+ return {
1063
+ status: 'success',
1064
+ machines: verification.machines,
1065
+ orca: currentOrca,
1066
+ };
1067
+ }
1068
+ lastErrors = verification.errors.filter(e => e.severity === 'error');
1069
+ iteration++;
1070
+ }
1071
+ catch (err) {
1072
+ return {
1073
+ status: 'error',
1074
+ error: err instanceof Error ? err.message : String(err),
1075
+ };
1076
+ }
1077
+ }
1078
+ let finalMachines = [];
1079
+ try {
1080
+ const { file } = parseMarkdown(currentOrca);
1081
+ finalMachines = file.machines.map(m => m.name);
1082
+ }
1083
+ catch { /* ignore */ }
1084
+ return {
1085
+ status: 'requires_refinement',
1086
+ machines: finalMachines,
1087
+ orca: currentOrca,
1088
+ errors: lastErrors,
1089
+ };
1090
+ }
1091
+ function stripCodeFence(code) {
1092
+ return code
1093
+ .replace(/^```(?:orca|markdown|md|orca\.md)?\n/, '')
1094
+ .replace(/^```\n/, '')
1095
+ .replace(/\n```$/, '')
1096
+ .trim();
1097
+ }
1098
+ function extractMachineNameFromSource(orca) {
1099
+ // Support both DSL format (machine Name) and markdown format (# machine Name)
1100
+ const match = orca.match(/^(?:#\s+)?machine\s+(\w+)/m);
1101
+ return match ? match[1] : 'Unknown';
1102
+ }
1103
+ //# sourceMappingURL=skills.js.map