@orcalang/orca-lang 0.1.19 → 0.1.24

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 (53) hide show
  1. package/dist/compiler/dt-compiler.d.ts +4 -0
  2. package/dist/compiler/dt-compiler.d.ts.map +1 -1
  3. package/dist/compiler/dt-compiler.js +354 -4
  4. package/dist/compiler/dt-compiler.js.map +1 -1
  5. package/dist/health-check.js +75 -0
  6. package/dist/health-check.js.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +5 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/parser/ast-to-markdown.d.ts.map +1 -1
  11. package/dist/parser/ast-to-markdown.js +3 -1
  12. package/dist/parser/ast-to-markdown.js.map +1 -1
  13. package/dist/parser/ast.d.ts +1 -0
  14. package/dist/parser/ast.d.ts.map +1 -1
  15. package/dist/parser/dt-ast.d.ts +11 -1
  16. package/dist/parser/dt-ast.d.ts.map +1 -1
  17. package/dist/parser/dt-parser.d.ts.map +1 -1
  18. package/dist/parser/dt-parser.js +40 -8
  19. package/dist/parser/dt-parser.js.map +1 -1
  20. package/dist/parser/markdown-parser.d.ts.map +1 -1
  21. package/dist/parser/markdown-parser.js +14 -4
  22. package/dist/parser/markdown-parser.js.map +1 -1
  23. package/dist/skills.d.ts +3 -2
  24. package/dist/skills.d.ts.map +1 -1
  25. package/dist/skills.js +486 -28
  26. package/dist/skills.js.map +1 -1
  27. package/dist/tools.js +4 -4
  28. package/dist/tools.js.map +1 -1
  29. package/dist/verifier/dt-verifier.d.ts +28 -1
  30. package/dist/verifier/dt-verifier.d.ts.map +1 -1
  31. package/dist/verifier/dt-verifier.js +591 -32
  32. package/dist/verifier/dt-verifier.js.map +1 -1
  33. package/dist/verifier/properties.d.ts +4 -0
  34. package/dist/verifier/properties.d.ts.map +1 -1
  35. package/dist/verifier/properties.js +56 -20
  36. package/dist/verifier/properties.js.map +1 -1
  37. package/dist/verifier/structural.d.ts.map +1 -1
  38. package/dist/verifier/structural.js +6 -1
  39. package/dist/verifier/structural.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/compiler/dt-compiler.ts +374 -4
  42. package/src/health-check.ts +79 -0
  43. package/src/index.ts +5 -1
  44. package/src/parser/ast-to-markdown.ts +2 -1
  45. package/src/parser/ast.ts +1 -0
  46. package/src/parser/dt-ast.ts +4 -2
  47. package/src/parser/dt-parser.ts +46 -8
  48. package/src/parser/markdown-parser.ts +11 -3
  49. package/src/skills.ts +520 -30
  50. package/src/tools.ts +4 -4
  51. package/src/verifier/dt-verifier.ts +639 -30
  52. package/src/verifier/properties.ts +78 -23
  53. package/src/verifier/structural.ts +5 -1
package/dist/skills.js CHANGED
@@ -6,8 +6,8 @@ import { checkDeterminism } from './verifier/determinism.js';
6
6
  import { checkProperties } from './verifier/properties.js';
7
7
  import { compileToXState } from './compiler/xstate.js';
8
8
  import { compileToMermaid } from './compiler/mermaid.js';
9
- import { verifyDecisionTables } from './verifier/dt-verifier.js';
10
- import { compileDecisionTableToTypeScript, compileDecisionTableToJSON } from './compiler/dt-compiler.js';
9
+ import { verifyDecisionTables, checkFileContextAlignment, checkDTMachineIntegration, computeAlignedDTOutputDomain } from './verifier/dt-verifier.js';
10
+ import { compileDecisionTableToTypeScript, compileDecisionTableToPython, compileDecisionTableToGo, compileDecisionTableToRust, compileDecisionTableToJSON, toSnakeCase, } from './compiler/dt-compiler.js';
11
11
  import { loadConfig } from './config/index.js';
12
12
  import { createProvider } from './llm/index.js';
13
13
  import { getCodeGenerator } from './generators/index.js';
@@ -136,8 +136,15 @@ export async function verifySkill(input) {
136
136
  const source = resolveSource(input);
137
137
  const label = resolveLabel(input);
138
138
  let machine;
139
+ let fileDecisionTables = [];
139
140
  try {
140
- machine = parseSource(label, source);
141
+ const { file } = parseMarkdown(source);
142
+ if (file.machines.length === 0)
143
+ throw new Error(`${label} contains no machine definition.`);
144
+ if (file.machines.length > 1)
145
+ throw new Error(`${label} contains multiple machines.`);
146
+ machine = file.machines[0];
147
+ fileDecisionTables = file.decisionTables;
141
148
  }
142
149
  catch (err) {
143
150
  // Parse error - return as verification error
@@ -159,7 +166,16 @@ export async function verifySkill(input) {
159
166
  const structural = checkStructural(machine);
160
167
  const completeness = checkCompleteness(machine);
161
168
  const determinism = checkDeterminism(machine);
162
- const properties = checkProperties(machine);
169
+ // Check co-located decision table alignment and machine integration (single-machine files only)
170
+ const orcaFile = { machines: [machine], decisionTables: fileDecisionTables };
171
+ const dtOutputDomain = fileDecisionTables.length > 0 ? computeAlignedDTOutputDomain(orcaFile) : undefined;
172
+ const properties = checkProperties(machine, { dtOutputDomain });
173
+ const dtAlignment = fileDecisionTables.length > 0
174
+ ? checkFileContextAlignment(orcaFile)
175
+ : [];
176
+ const dtIntegration = fileDecisionTables.length > 0
177
+ ? checkDTMachineIntegration(orcaFile)
178
+ : [];
163
179
  const mapError = (e) => ({
164
180
  code: e.code,
165
181
  message: e.message,
@@ -167,6 +183,9 @@ export async function verifySkill(input) {
167
183
  location: e.location ? {
168
184
  state: e.location.state,
169
185
  event: e.location.event,
186
+ decisionTable: e.location.decisionTable,
187
+ condition: e.location.condition,
188
+ action: e.location.action,
170
189
  } : undefined,
171
190
  suggestion: e.suggestion,
172
191
  });
@@ -175,6 +194,8 @@ export async function verifySkill(input) {
175
194
  ...completeness.errors.map(mapError),
176
195
  ...determinism.errors.map(mapError),
177
196
  ...properties.errors.map(mapError),
197
+ ...dtAlignment.map(mapError),
198
+ ...dtIntegration.map(mapError),
178
199
  ];
179
200
  return {
180
201
  status: allErrors.some(e => e.severity === 'error') ? 'invalid' : 'valid',
@@ -221,7 +242,17 @@ export async function compileSkill(input, target) {
221
242
  }
222
243
  export async function generateActionsSkill(input, language = 'typescript', useLLM = false, configPath, generateTests = false) {
223
244
  const source = resolveSource(input);
224
- const machine = parseSource(resolveLabel(input), source);
245
+ // Parse the full file to get both the machine and any co-located decision tables
246
+ const { file } = parseMarkdown(source);
247
+ const label = resolveLabel(input);
248
+ if (file.machines.length === 0) {
249
+ throw new Error(`${label} contains no machine definition.`);
250
+ }
251
+ if (file.machines.length > 1) {
252
+ throw new Error(`${label} contains multiple machines. Use a single-machine file for action generation.`);
253
+ }
254
+ const machine = file.machines[0];
255
+ const decisionTables = file.decisionTables;
225
256
  const actions = machine.actions.map(action => ({
226
257
  name: action.name,
227
258
  signature: `${action.name}(${action.parameters.join(', ')}) -> ${action.returnType}${action.hasEffect ? ` + Effect<${action.effectType}>` : ''}`,
@@ -231,6 +262,22 @@ export async function generateActionsSkill(input, language = 'typescript', useLL
231
262
  effectType: action.effectType,
232
263
  context_used: extractContextFields(machine, action.name),
233
264
  }));
265
+ // Compile decision table evaluator code for each DT in the file
266
+ const decisionTableCode = {};
267
+ for (const dt of decisionTables) {
268
+ if (language === 'python') {
269
+ decisionTableCode[dt.name] = compileDecisionTableToPython(dt);
270
+ }
271
+ else if (language === 'go') {
272
+ decisionTableCode[dt.name] = compileDecisionTableToGo(dt);
273
+ }
274
+ else if (language === 'rust') {
275
+ decisionTableCode[dt.name] = compileDecisionTableToRust(dt);
276
+ }
277
+ else {
278
+ decisionTableCode[dt.name] = compileDecisionTableToTypeScript(dt);
279
+ }
280
+ }
234
281
  let scaffolds = {};
235
282
  let tests = {};
236
283
  if (useLLM) {
@@ -243,7 +290,7 @@ export async function generateActionsSkill(input, language = 'typescript', useLL
243
290
  max_tokens: config.max_tokens,
244
291
  temperature: config.temperature,
245
292
  });
246
- scaffolds = await generateWithLLM(provider, actions, machine, language);
293
+ scaffolds = await generateWithLLM(provider, actions, machine, language, decisionTables);
247
294
  if (generateTests) {
248
295
  tests = await generateUnitTests(provider, actions, machine, language);
249
296
  }
@@ -251,7 +298,14 @@ export async function generateActionsSkill(input, language = 'typescript', useLL
251
298
  else {
252
299
  // Use template-based scaffold generation
253
300
  for (const action of machine.actions) {
254
- scaffolds[action.name] = generateActionScaffold(action, machine, language);
301
+ const matchedDT = findMatchingDT(action.name, decisionTables);
302
+ if (matchedDT && !action.hasEffect && isDTFullyAligned(matchedDT, machine)) {
303
+ // All DT conditions and outputs exist in context — generate fully wired code
304
+ scaffolds[action.name] = generateFullyWiredActionScaffold(action, machine, language, matchedDT);
305
+ }
306
+ else {
307
+ scaffolds[action.name] = generateActionScaffold(action, machine, language, matchedDT ?? undefined);
308
+ }
255
309
  }
256
310
  if (generateTests) {
257
311
  tests = generateTemplateTests(actions, machine, language);
@@ -262,22 +316,31 @@ export async function generateActionsSkill(input, language = 'typescript', useLL
262
316
  machine: machine.name,
263
317
  actions,
264
318
  scaffolds,
319
+ decisionTableCode: Object.keys(decisionTableCode).length > 0 ? decisionTableCode : undefined,
265
320
  tests: Object.keys(tests).length > 0 ? tests : undefined,
266
321
  };
267
322
  }
268
- async function generateWithLLM(provider, actions, machine, language) {
323
+ async function generateWithLLM(provider, actions, machine, language, decisionTables = []) {
269
324
  const generator = getCodeGenerator(language);
270
325
  const scaffolds = {};
326
+ const dtContext = decisionTables.length > 0
327
+ ? `\nDecision tables available:\n${decisionTables.map(dt => `- ${dt.name}: conditions=[${dt.conditions.map(c => c.name).join(', ')}] outputs=[${dt.actions.map(a => a.name).join(', ')}]`).join('\n')}`
328
+ : '';
271
329
  const systemPrompt = `You are an expert ${language} developer specializing in state machine action implementations.
272
330
  Given a machine definition and action signatures, generate complete action implementations.
273
331
  Follow the type signatures exactly. Use the provided context fields.
274
- If an action has an effect, return [newContext, effect] tuple.`;
332
+ If an action has an effect, return [newContext, effect] tuple.
333
+ If decision tables are listed, use their evaluator functions (e.g. evaluate${language === 'go' ? 'DtName' : 'DtName'}) when appropriate.`;
275
334
  for (const action of actions) {
335
+ const matchedDT = findMatchingDT(action.name, decisionTables);
336
+ const dtHint = matchedDT
337
+ ? `\nThis action should use the ${matchedDT.name} decision table evaluator.`
338
+ : '';
276
339
  const userPrompt = `Machine: ${machine.name}
277
- Context fields: ${machine.context.map(f => `${f.name}: ${f.type || 'unknown'}`).join(', ')}
340
+ Context fields: ${machine.context.map(f => `${f.name}: ${f.type || 'unknown'}`).join(', ')}${dtContext}
278
341
 
279
342
  Action: ${action.signature}
280
- Description: ${action.name}${action.hasEffect ? ` (effect type: ${action.effectType})` : ''}
343
+ Description: ${action.name}${action.hasEffect ? ` (effect type: ${action.effectType})` : ''}${dtHint}
281
344
 
282
345
  Generate the implementation:`;
283
346
  try {
@@ -295,7 +358,7 @@ Generate the implementation:`;
295
358
  catch (err) {
296
359
  console.error(`LLM error for action ${action.name}: ${err instanceof Error ? err.message : String(err)}`);
297
360
  // Fall back to scaffold on error
298
- scaffolds[action.name] = generateActionScaffold(action, machine, language);
361
+ scaffolds[action.name] = generateActionScaffold(action, machine, language, matchedDT ?? undefined);
299
362
  }
300
363
  }
301
364
  return scaffolds;
@@ -352,6 +415,9 @@ function generateTemplateTestsForAction(action, machine, language = 'typescript'
352
415
  if (language === 'go') {
353
416
  return generateGoTestScaffold(action, machine);
354
417
  }
418
+ if (language === 'rust') {
419
+ return generateRustTestScaffold(action, machine);
420
+ }
355
421
  return generateTypeScriptTestScaffold(action, machine);
356
422
  }
357
423
  function generateTypeScriptTestScaffold(action, machine) {
@@ -522,6 +588,60 @@ ${preserved.map(f => `\tif result["${f}"] != ctx["${f}"] {\n\t\tt.Errorf("${f}:
522
588
  }
523
589
  `;
524
590
  }
591
+ function generateRustTestScaffold(action, machine) {
592
+ const ctxFields = machine.context.map(f => {
593
+ return ` "${f.name}": ${getDefaultValueForType(f.type, 'rust')}`;
594
+ }).join(',\n');
595
+ const contextUsed = action.context_used.length > 0 ? action.context_used : machine.context.map(f => f.name);
596
+ const preserved = contextUsed.filter(f => !actionModifiesField(action, f));
597
+ if (action.hasEffect) {
598
+ return `// Tests for ${action.name}
599
+ #[cfg(test)]
600
+ mod tests {
601
+ use super::*;
602
+ use serde_json::json;
603
+
604
+ #[test]
605
+ fn test_${action.name}_executes_effect() {
606
+ let ctx = json!({
607
+ ${ctxFields}
608
+ });
609
+ let event = json!({"type": "test"});
610
+ let (result, effect) = ${action.name}(&ctx, &event);
611
+ assert!(result.is_object());
612
+ assert_eq!(effect.effect_type, "${action.effectType}");
613
+ }
614
+ }
615
+ `;
616
+ }
617
+ return `// Tests for ${action.name}
618
+ #[cfg(test)]
619
+ mod tests {
620
+ use super::*;
621
+ use serde_json::json;
622
+
623
+ #[test]
624
+ fn test_${action.name}_transforms_context() {
625
+ let ctx = json!({
626
+ ${ctxFields}
627
+ });
628
+ let event = json!({"type": "test"});
629
+ let result = ${action.name}(&ctx, &event);
630
+ assert!(result.is_object());
631
+ }
632
+
633
+ #[test]
634
+ fn test_${action.name}_preserves_fields() {
635
+ let ctx = json!({
636
+ ${ctxFields}
637
+ });
638
+ let event = json!({"type": "test"});
639
+ let result = ${action.name}(&ctx, &event);
640
+ ${preserved.map(f => ` assert_eq!(result["${f}"], ctx["${f}"]);`).join('\n')}
641
+ }
642
+ }
643
+ `;
644
+ }
525
645
  function actionModifiesField(action, fieldName) {
526
646
  // Heuristic: if the action name suggests modification of a field, it likely modifies it
527
647
  const modifiers = ['increment', 'decrement', 'set', 'update', 'add', 'remove', 'clear', 'reset', 'toggle'];
@@ -539,7 +659,7 @@ function actionModifiesField(action, fieldName) {
539
659
  }
540
660
  function getDefaultValueForType(type, language = 'typescript') {
541
661
  if (!type) {
542
- return language === 'python' ? '""' : language === 'go' ? '""' : "''";
662
+ return language === 'python' ? '""' : language === 'go' ? '""' : language === 'rust' ? '"".to_string()' : "''";
543
663
  }
544
664
  if (typeof type === 'object' && 'kind' in type) {
545
665
  if (language === 'python') {
@@ -566,6 +686,25 @@ function getDefaultValueForType(type, language = 'typescript') {
566
686
  case 'custom': return 'nil';
567
687
  }
568
688
  }
689
+ else if (language === 'rust') {
690
+ switch (type.kind) {
691
+ case 'string': return '"".to_string()';
692
+ case 'int': return '0';
693
+ case 'decimal': return '0.0';
694
+ case 'bool': return 'false';
695
+ case 'optional': return 'null';
696
+ case 'array': return 'vec![]';
697
+ case 'map': return 'HashMap::new()';
698
+ case 'custom': {
699
+ // Handle common type aliases
700
+ if (type.name === 'float' || type.name === 'double')
701
+ return '0.0';
702
+ if (type.name === 'integer' || type.name === 'long')
703
+ return '0';
704
+ return 'null';
705
+ }
706
+ }
707
+ }
569
708
  else {
570
709
  switch (type.kind) {
571
710
  case 'string': return "''";
@@ -579,7 +718,7 @@ function getDefaultValueForType(type, language = 'typescript') {
579
718
  }
580
719
  }
581
720
  }
582
- return language === 'python' ? 'None' : language === 'go' ? 'nil' : 'null';
721
+ return language === 'python' ? 'None' : language === 'go' ? 'nil' : language === 'rust' ? 'null' : 'null';
583
722
  }
584
723
  function extractContextFields(machine, actionName) {
585
724
  const fields = [];
@@ -602,7 +741,270 @@ function extractContextFields(machine, actionName) {
602
741
  function toPascalCase(snake) {
603
742
  return snake.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
604
743
  }
605
- function generateActionScaffold(action, machine, language) {
744
+ /**
745
+ * Find a decision table whose name tokens overlap with the action name tokens.
746
+ * E.g. action "apply_routing_decision" matches DT "PaymentRouting" via "routing".
747
+ */
748
+ function findMatchingDT(actionName, dts) {
749
+ if (dts.length === 0)
750
+ return null;
751
+ const actionTokens = new Set(actionName.toLowerCase().split('_').filter(t => t.length > 2));
752
+ for (const dt of dts) {
753
+ const dtTokens = dt.name
754
+ .replace(/([A-Z])/g, ' $1')
755
+ .trim()
756
+ .toLowerCase()
757
+ .split(/\s+/)
758
+ .filter(t => t.length > 2);
759
+ if (dtTokens.some(t => actionTokens.has(t)))
760
+ return dt;
761
+ }
762
+ return null;
763
+ }
764
+ /**
765
+ * Generate a commented example DT call block to include in an action stub.
766
+ */
767
+ function generateDTCallComment(dt, language) {
768
+ const dtName = dt.name;
769
+ if (language === 'python') {
770
+ const fnName = `evaluate_${toSnakeCase(dtName)}`;
771
+ const inputClass = `${toPascalCase(dtName)}Input`;
772
+ const inputArgs = dt.conditions.map(c => {
773
+ const hint = c.type === 'enum' && c.values.length > 0
774
+ ? `str (${c.values.join(', ')})`
775
+ : c.type === 'bool' ? 'bool' : 'str';
776
+ return ` # ${c.name}=..., # ${hint} — map from ctx`;
777
+ }).join('\n');
778
+ const outputFields = dt.actions.map(a => ` # # dt_result.${a.name} → ctx['${a.name}']`).join('\n');
779
+ return [
780
+ ` # Call ${fnName} to evaluate ${dtName} rules:`,
781
+ ` # dt_result = ${fnName}(${inputClass}(`,
782
+ inputArgs,
783
+ ` # ))`,
784
+ ` # if dt_result is not None:`,
785
+ outputFields,
786
+ ].join('\n');
787
+ }
788
+ if (language === 'go') {
789
+ const fnName = `Evaluate${toPascalCase(dtName)}`;
790
+ const inputStruct = `${toPascalCase(dtName)}Input`;
791
+ const inputArgs = dt.conditions.map(c => {
792
+ const goField = toPascalCase(c.name);
793
+ const hint = c.type === 'enum' && c.values.length > 0
794
+ ? `string (${c.values.join(', ')})`
795
+ : c.type === 'bool' ? 'bool' : 'string';
796
+ return `\t// \t${goField}: ..., // ${hint} — map from ctx`;
797
+ }).join('\n');
798
+ const outputFields = dt.actions.map(a => `\t// \tresult["${a.name}"] = dtResult.${toPascalCase(a.name)}`).join('\n');
799
+ return [
800
+ `\t// Call ${fnName} to evaluate ${dtName} rules:`,
801
+ `\t// dtResult := ${fnName}(${inputStruct}{`,
802
+ inputArgs,
803
+ `\t// })`,
804
+ `\t// if dtResult != nil {`,
805
+ outputFields,
806
+ `\t// }`,
807
+ ].join('\n');
808
+ }
809
+ if (language === 'rust') {
810
+ const fnName = `evaluate_${toSnakeCase(dtName)}`;
811
+ const inputStruct = `${toPascalCase(dtName)}Input`;
812
+ const inputArgs = dt.conditions.map(c => {
813
+ const hint = c.type === 'enum' && c.values.length > 0
814
+ ? `String (${c.values.join(', ')})`
815
+ : c.type === 'bool' ? 'bool' : c.type === 'int_range' ? 'i64' : 'String';
816
+ return ` // ${c.name}: ..., // ${hint} — map from ctx`;
817
+ }).join('\n');
818
+ const outputFields = dt.actions.map(a => ` // result["${a.name}"] = Value::String(dt_result.${a.name}.clone());`).join('\n');
819
+ return [
820
+ ` // Call ${fnName} to evaluate ${dtName} rules:`,
821
+ ` // let dt_input = ${inputStruct} {`,
822
+ inputArgs,
823
+ ` // };`,
824
+ ` // if let Some(dt_result) = ${fnName}(&dt_input) {`,
825
+ outputFields,
826
+ ` // }`,
827
+ ].join('\n');
828
+ }
829
+ // TypeScript (default)
830
+ const fnName = `evaluate${toPascalCase(dtName)}`;
831
+ const inputType = `${toPascalCase(dtName)}Input`;
832
+ const inputArgs = dt.conditions.map(c => {
833
+ const hint = c.type === 'enum' && c.values.length > 0
834
+ ? `enum: ${c.values.join(', ')}`
835
+ : c.type;
836
+ return ` // ${c.name}: /* ctx.? */ as ${inputType}['${c.name}'], // ${hint} — map from ctx`;
837
+ }).join('\n');
838
+ const outputFields = dt.actions.map(a => ` // ${a.name}: dtResult.${a.name},`).join('\n');
839
+ return [
840
+ ` // Call ${fnName} to evaluate ${dtName} rules:`,
841
+ ` // const dtResult = ${fnName}({`,
842
+ inputArgs,
843
+ ` // });`,
844
+ ` // if (dtResult !== null) {`,
845
+ ` // return { ...ctx,`,
846
+ outputFields,
847
+ ` // };`,
848
+ ` // }`,
849
+ ].join('\n');
850
+ }
851
+ /**
852
+ * Returns true when every DT condition name and output name exists as a
853
+ * context field in the machine — the full co-location contract is satisfied.
854
+ */
855
+ function isDTFullyAligned(dt, machine) {
856
+ const contextNames = new Set(machine.context.map(f => f.name));
857
+ return dt.conditions.every(c => contextNames.has(c.name)) &&
858
+ dt.actions.every(a => contextNames.has(a.name));
859
+ }
860
+ /** Generate a Go type assertion for reading a context value. */
861
+ function goCtxRead(fieldName, condType) {
862
+ if (condType === 'bool')
863
+ return `ctx["${fieldName}"].(bool)`;
864
+ if (condType === 'int_range')
865
+ return `ctx["${fieldName}"].(int)`;
866
+ return `ctx["${fieldName}"].(string)`;
867
+ }
868
+ /** Generate a Rust serde_json value extraction for reading a context value. */
869
+ function rustCtxRead(fieldName, condType) {
870
+ if (condType === 'bool')
871
+ return `ctx["${fieldName}"].as_bool().unwrap_or_default()`;
872
+ if (condType === 'int_range')
873
+ return `ctx["${fieldName}"].as_i64().unwrap_or_default()`;
874
+ if (condType === 'decimal_range')
875
+ return `ctx["${fieldName}"].as_f64().unwrap_or_default()`;
876
+ return `ctx["${fieldName}"].as_str().unwrap_or_default().to_string()`;
877
+ }
878
+ function generateFullyWiredActionScaffold(action, machine, language, dt) {
879
+ if (language === 'python') {
880
+ const dtFnName = `evaluate_${toSnakeCase(dt.name)}`;
881
+ const inputClass = `${toPascalCase(dt.name)}Input`;
882
+ const inputArgs = dt.conditions
883
+ .map(c => ` ${c.name}=ctx['${c.name}'],`)
884
+ .join('\n');
885
+ const outputAssigns = dt.actions
886
+ .map(a => ` '${a.name}': dt_result.${a.name},`)
887
+ .join('\n');
888
+ return `# Action: ${action.name}
889
+ # Decision table: ${dt.name}
890
+ # Register via: machine.register_action("${action.name}", ${action.name})
891
+
892
+ from typing import Any
893
+
894
+ async def ${action.name}(ctx: dict[str, Any], event: Any = None) -> dict[str, Any]:
895
+ dt_result = ${dtFnName}(${inputClass}(
896
+ ${inputArgs}
897
+ ))
898
+ if dt_result is not None:
899
+ return {**ctx,
900
+ ${outputAssigns}
901
+ }
902
+ return dict(ctx)
903
+ `;
904
+ }
905
+ if (language === 'go') {
906
+ const fnName = toPascalCase(action.name);
907
+ const dtFnName = `Evaluate${toPascalCase(dt.name)}`;
908
+ const inputStruct = `${toPascalCase(dt.name)}Input`;
909
+ const ctxReads = dt.conditions.map(c => {
910
+ const varName = toPascalCase(c.name).charAt(0).toLowerCase() + toPascalCase(c.name).slice(1);
911
+ return `\t${varName}, _ := ${goCtxRead(c.name, c.type)}`;
912
+ }).join('\n');
913
+ const inputFields = dt.conditions.map(c => {
914
+ const goField = toPascalCase(c.name);
915
+ const varName = goField.charAt(0).toLowerCase() + goField.slice(1);
916
+ return `\t\t${goField}: ${varName},`;
917
+ }).join('\n');
918
+ const outputAssigns = dt.actions
919
+ .map(a => `\t\tresult["${a.name}"] = dtResult.${toPascalCase(a.name)}`)
920
+ .join('\n');
921
+ return `// Action: ${action.name}
922
+ // Decision table: ${dt.name}
923
+ // Register via: machine.RegisterAction("${action.name}", ${fnName})
924
+
925
+ func ${fnName}(ctx orca.Context, event map[string]any) map[string]any {
926
+ ${ctxReads}
927
+ \tdtResult := ${dtFnName}(${inputStruct}{
928
+ ${inputFields}
929
+ \t})
930
+ \tresult := make(orca.Context)
931
+ \tfor k, v := range ctx {
932
+ \t\tresult[k] = v
933
+ \t}
934
+ \tif dtResult != nil {
935
+ ${outputAssigns}
936
+ \t}
937
+ \treturn result
938
+ }
939
+ `;
940
+ }
941
+ if (language === 'rust') {
942
+ const dtFnName = `evaluate_${toSnakeCase(dt.name)}`;
943
+ const inputStruct = `${toPascalCase(dt.name)}Input`;
944
+ const ctxReads = dt.conditions
945
+ .map(c => ` let ${c.name} = ${rustCtxRead(c.name, c.type)};`)
946
+ .join('\n');
947
+ const inputFields = dt.conditions
948
+ .map(c => ` ${c.name},`)
949
+ .join('\n');
950
+ const outputAssigns = dt.actions
951
+ .map(a => {
952
+ const aType = a.type;
953
+ if (aType === 'bool')
954
+ return ` result["${a.name}"] = Value::Bool(dt_result.${a.name});`;
955
+ if (aType === 'int_range')
956
+ return ` result["${a.name}"] = json!(dt_result.${a.name});`;
957
+ if (aType === 'decimal_range')
958
+ return ` result["${a.name}"] = json!(dt_result.${a.name});`;
959
+ return ` result["${a.name}"] = Value::String(dt_result.${a.name}.clone());`;
960
+ })
961
+ .join('\n');
962
+ return `// Action: ${action.name}
963
+ // Decision table: ${dt.name}
964
+ // Register via: machine.register_action_rust("${action.name}", Box::new(${action.name}))
965
+
966
+ use serde_json::{Value, json};
967
+
968
+ fn ${action.name}(ctx: &Value, event: &Value) -> Value {
969
+ ${ctxReads}
970
+ let dt_input = ${inputStruct} {
971
+ ${inputFields}
972
+ };
973
+ if let Some(dt_result) = ${dtFnName}(&dt_input) {
974
+ let mut result = ctx.clone();
975
+ ${outputAssigns}
976
+ return result;
977
+ }
978
+ ctx.clone()
979
+ }
980
+ `;
981
+ }
982
+ // TypeScript (default)
983
+ const dtFnName = `evaluate${toPascalCase(dt.name)}`;
984
+ const inputArgs = dt.conditions
985
+ .map(c => ` ${c.name}: ctx.${c.name},`)
986
+ .join('\n');
987
+ const outputAssigns = dt.actions
988
+ .map(a => ` ${a.name}: dtResult.${a.name},`)
989
+ .join('\n');
990
+ return `// Action: ${action.name}
991
+ // Decision table: ${dt.name}
992
+
993
+ export function ${action.name}(ctx: Context): Context {
994
+ const dtResult = ${dtFnName}({
995
+ ${inputArgs}
996
+ });
997
+ if (dtResult !== null) {
998
+ return {
999
+ ...ctx,
1000
+ ${outputAssigns}
1001
+ };
1002
+ }
1003
+ return { ...ctx };
1004
+ }
1005
+ `;
1006
+ }
1007
+ function generateActionScaffold(action, machine, language, matchedDT) {
606
1008
  if (language === 'python') {
607
1009
  if (action.hasEffect) {
608
1010
  return `# Action: ${action.name}
@@ -619,13 +1021,17 @@ async def ${action.name}(effect: Effect) -> EffectResult:
619
1021
  return EffectResult(status=EffectStatus.SUCCESS, data={})
620
1022
  `;
621
1023
  }
622
- return `# Action: ${action.name}
1024
+ const pyDTComment = matchedDT ? '\n' + generateDTCallComment(matchedDT, 'python') + '\n' : '';
1025
+ const pyTodo = matchedDT
1026
+ ? ` # TODO: Implement action using ${matchedDT.name} decision table`
1027
+ : ' # TODO: Implement action';
1028
+ return `# Action: ${action.name}${matchedDT ? `\n# Decision table: ${matchedDT.name}` : ''}
623
1029
  # Register via: machine.register_action("${action.name}", ${action.name})
624
1030
 
625
1031
  from typing import Any
626
1032
 
627
- async def ${action.name}(ctx: dict[str, Any], event: Any = None) -> dict[str, Any]:
628
- # TODO: Implement action
1033
+ async def ${action.name}(ctx: dict[str, Any], event: Any = None) -> dict[str, Any]:${pyDTComment}
1034
+ ${pyTodo}
629
1035
  return dict(ctx)
630
1036
  `;
631
1037
  }
@@ -645,17 +1051,50 @@ func ${fnName}(effect orca.Effect) orca.EffectResult {
645
1051
  }
646
1052
  `;
647
1053
  }
648
- return `// Action: ${action.name}
1054
+ const goDTComment = matchedDT ? '\n' + generateDTCallComment(matchedDT, 'go') + '\n' : '';
1055
+ const goTodo = matchedDT
1056
+ ? `\t// TODO: Implement action using ${matchedDT.name} decision table`
1057
+ : '\t// TODO: Implement action';
1058
+ return `// Action: ${action.name}${matchedDT ? `\n// Decision table: ${matchedDT.name}` : ''}
649
1059
  // Register via: machine.RegisterAction("${action.name}", ${fnName})
650
1060
 
651
- func ${fnName}(ctx orca.Context, event map[string]any) map[string]any {
652
- \t// TODO: Implement action
1061
+ func ${fnName}(ctx orca.Context, event map[string]any) map[string]any {${goDTComment}
1062
+ ${goTodo}
653
1063
  \tresult := make(orca.Context)
654
1064
  \tfor k, v := range ctx {
655
1065
  \t\tresult[k] = v
656
1066
  \t}
657
1067
  \treturn result
658
1068
  }
1069
+ `;
1070
+ }
1071
+ if (language === 'rust') {
1072
+ if (action.hasEffect) {
1073
+ return `// Action: ${action.name}
1074
+ // Effect: ${action.effectType}
1075
+ // Register via: machine.register_action_rust("${action.name}", Box::new(${action.name}))
1076
+
1077
+ use serde_json::Value;
1078
+
1079
+ fn ${action.name}(ctx: &Value, event: &Value) -> (Value, Effect) {
1080
+ // TODO: Implement effect
1081
+ (ctx.clone(), Effect { effect_type: "${action.effectType}".to_string(), payload: Value::Null })
1082
+ }
1083
+ `;
1084
+ }
1085
+ const rustDTComment = matchedDT ? '\n' + generateDTCallComment(matchedDT, 'rust') + '\n' : '';
1086
+ const rustTodo = matchedDT
1087
+ ? ` // TODO: Implement action using ${matchedDT.name} decision table`
1088
+ : ' // TODO: Implement action';
1089
+ return `// Action: ${action.name}${matchedDT ? `\n// Decision table: ${matchedDT.name}` : ''}
1090
+ // Register via: machine.register_action_rust("${action.name}", Box::new(${action.name}))
1091
+
1092
+ use serde_json::Value;
1093
+
1094
+ fn ${action.name}(ctx: &Value, event: &Value) -> Value {${rustDTComment}
1095
+ ${rustTodo}
1096
+ ctx.clone()
1097
+ }
659
1098
  `;
660
1099
  }
661
1100
  // TypeScript (default)
@@ -681,10 +1120,14 @@ export function ${action.name}(${params}): [Context, Effect<${action.effectType}
681
1120
  // }
682
1121
  `;
683
1122
  }
684
- return `// Action: ${action.name}
685
-
686
- export function ${action.name}(ctx: Context): Context {
687
- // TODO: Implement action
1123
+ const tsDTComment = matchedDT ? '\n' + generateDTCallComment(matchedDT, 'typescript') + '\n' : '';
1124
+ const tsTodo = matchedDT
1125
+ ? ` // TODO: Implement action using ${matchedDT.name} decision table`
1126
+ : ' // TODO: Implement action';
1127
+ return `// Action: ${action.name}${matchedDT ? `\n// Decision table: ${matchedDT.name}` : ''}
1128
+
1129
+ export function ${action.name}(ctx: Context): Context {${tsDTComment}
1130
+ ${tsTodo}
688
1131
  return { ...ctx };
689
1132
  }
690
1133
  `;
@@ -1502,9 +1945,24 @@ export function compileDTSkill(input, target = 'typescript') {
1502
1945
  };
1503
1946
  }
1504
1947
  const dt = file.decisionTables[0];
1505
- const output = target === 'json'
1506
- ? compileDecisionTableToJSON(dt)
1507
- : compileDecisionTableToTypeScript(dt);
1948
+ let output;
1949
+ switch (target) {
1950
+ case 'json':
1951
+ output = compileDecisionTableToJSON(dt);
1952
+ break;
1953
+ case 'python':
1954
+ output = compileDecisionTableToPython(dt);
1955
+ break;
1956
+ case 'go':
1957
+ output = compileDecisionTableToGo(dt);
1958
+ break;
1959
+ case 'rust':
1960
+ output = compileDecisionTableToRust(dt);
1961
+ break;
1962
+ default:
1963
+ output = compileDecisionTableToTypeScript(dt);
1964
+ break;
1965
+ }
1508
1966
  return { status: 'success', target, output, warnings: [] };
1509
1967
  }
1510
1968
  catch (err) {