foodforthought-cli 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl

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 (46) hide show
  1. ate/__init__.py +1 -1
  2. ate/bridge_server.py +622 -0
  3. ate/cli.py +2625 -242
  4. ate/compatibility.py +580 -0
  5. ate/generators/__init__.py +19 -0
  6. ate/generators/docker_generator.py +461 -0
  7. ate/generators/hardware_config.py +469 -0
  8. ate/generators/ros2_generator.py +617 -0
  9. ate/generators/skill_generator.py +783 -0
  10. ate/marketplace.py +524 -0
  11. ate/mcp_server.py +1341 -107
  12. ate/primitives.py +1016 -0
  13. ate/robot_setup.py +2222 -0
  14. ate/skill_schema.py +537 -0
  15. ate/telemetry/__init__.py +33 -0
  16. ate/telemetry/cli.py +455 -0
  17. ate/telemetry/collector.py +444 -0
  18. ate/telemetry/context.py +318 -0
  19. ate/telemetry/fleet_agent.py +419 -0
  20. ate/telemetry/formats/__init__.py +18 -0
  21. ate/telemetry/formats/hdf5_serializer.py +503 -0
  22. ate/telemetry/formats/mcap_serializer.py +457 -0
  23. ate/telemetry/types.py +334 -0
  24. foodforthought_cli-0.2.3.dist-info/METADATA +300 -0
  25. foodforthought_cli-0.2.3.dist-info/RECORD +44 -0
  26. foodforthought_cli-0.2.3.dist-info/top_level.txt +6 -0
  27. mechdog_labeled/__init__.py +3 -0
  28. mechdog_labeled/primitives.py +113 -0
  29. mechdog_labeled/servo_map.py +209 -0
  30. mechdog_output/__init__.py +3 -0
  31. mechdog_output/primitives.py +59 -0
  32. mechdog_output/servo_map.py +203 -0
  33. test_autodetect/__init__.py +3 -0
  34. test_autodetect/primitives.py +113 -0
  35. test_autodetect/servo_map.py +209 -0
  36. test_full_auto/__init__.py +3 -0
  37. test_full_auto/primitives.py +113 -0
  38. test_full_auto/servo_map.py +209 -0
  39. test_smart_detect/__init__.py +3 -0
  40. test_smart_detect/primitives.py +113 -0
  41. test_smart_detect/servo_map.py +209 -0
  42. foodforthought_cli-0.2.1.dist-info/METADATA +0 -151
  43. foodforthought_cli-0.2.1.dist-info/RECORD +0 -9
  44. foodforthought_cli-0.2.1.dist-info/top_level.txt +0 -1
  45. {foodforthought_cli-0.2.1.dist-info → foodforthought_cli-0.2.3.dist-info}/WHEEL +0 -0
  46. {foodforthought_cli-0.2.1.dist-info → foodforthought_cli-0.2.3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,783 @@
1
+ """
2
+ Skill Code Generator - Generate Python skill implementations from specifications.
3
+
4
+ This generator creates:
5
+ - skill.py: Main skill class with execute() method
6
+ - primitives.py: Hardware-abstracted primitive wrappers
7
+ - validators.py: Input/output validation
8
+ - state_machine.py: Execution flow state machine
9
+ """
10
+
11
+ import re
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ from ..skill_schema import SkillSpecification, SkillParameter, PrimitiveCall
17
+ from ..primitives import PRIMITIVE_REGISTRY, get_primitive
18
+
19
+
20
+ def to_pascal_case(name: str) -> str:
21
+ """Convert snake_case or kebab-case to PascalCase."""
22
+ # Replace hyphens with underscores
23
+ name = name.replace("-", "_")
24
+ # Split on underscores and capitalize each word
25
+ return "".join(word.capitalize() for word in name.split("_"))
26
+
27
+
28
+ def to_snake_case(name: str) -> str:
29
+ """Convert PascalCase or kebab-case to snake_case."""
30
+ # Replace hyphens with underscores
31
+ name = name.replace("-", "_")
32
+ # Insert underscore before uppercase letters
33
+ name = re.sub(r"(?<!^)(?=[A-Z])", "_", name)
34
+ return name.lower()
35
+
36
+
37
+ def python_type(param_type: str) -> str:
38
+ """Convert skill parameter type to Python type annotation."""
39
+ type_map = {
40
+ "Pose": "Dict[str, Any]",
41
+ "float": "float",
42
+ "int": "int",
43
+ "bool": "bool",
44
+ "string": "str",
45
+ "array": "List[Any]",
46
+ "JointState": "List[float]",
47
+ "Trajectory": "List[Dict[str, Any]]",
48
+ "Point": "Tuple[float, float, float]",
49
+ "Quaternion": "Tuple[float, float, float, float]",
50
+ "Transform": "Dict[str, Any]",
51
+ }
52
+ return type_map.get(param_type, "Any")
53
+
54
+
55
+ class SkillCodeGenerator:
56
+ """
57
+ Generate Python code for a skill from its specification.
58
+
59
+ This generator creates a complete Python package structure with:
60
+ - Main skill class
61
+ - Primitive wrappers
62
+ - Input/output validators
63
+ - State machine for execution flow
64
+ """
65
+
66
+ def __init__(self, spec: SkillSpecification):
67
+ """
68
+ Initialize the generator.
69
+
70
+ Args:
71
+ spec: The skill specification to generate code for
72
+ """
73
+ self.spec = spec
74
+ self.class_name = to_pascal_case(spec.name)
75
+ self.module_name = to_snake_case(spec.name)
76
+
77
+ def generate_skill_class(self) -> str:
78
+ """
79
+ Generate the main skill.py file.
80
+
81
+ Returns:
82
+ Python source code for the skill class
83
+ """
84
+ lines = [
85
+ '"""',
86
+ f'{self.spec.name} - {self.spec.description}',
87
+ '',
88
+ f'Generated by Skill Compiler v1.0.0',
89
+ f'Generated at: {datetime.now().isoformat()}',
90
+ '',
91
+ 'Do not edit manually - regenerate from skill.yaml',
92
+ '"""',
93
+ '',
94
+ 'from dataclasses import dataclass',
95
+ 'from typing import Any, Dict, List, Optional, Tuple',
96
+ 'import logging',
97
+ '',
98
+ f'from .primitives import PrimitiveExecutor',
99
+ f'from .validators import validate_{self.module_name}_input, validate_{self.module_name}_output',
100
+ '',
101
+ '',
102
+ 'logger = logging.getLogger(__name__)',
103
+ '',
104
+ ]
105
+
106
+ # Generate input dataclass
107
+ lines.extend(self._generate_input_class())
108
+ lines.append('')
109
+
110
+ # Generate output dataclass
111
+ lines.extend(self._generate_output_class())
112
+ lines.append('')
113
+
114
+ # Generate main skill class
115
+ lines.extend(self._generate_skill_class_body())
116
+
117
+ return '\n'.join(lines)
118
+
119
+ def _generate_input_class(self) -> List[str]:
120
+ """Generate the input dataclass."""
121
+ lines = [
122
+ '@dataclass',
123
+ f'class {self.class_name}Input:',
124
+ f' """Input parameters for {self.spec.name} skill."""',
125
+ ]
126
+
127
+ if not self.spec.parameters:
128
+ lines.append(' pass')
129
+ return lines
130
+
131
+ for param in self.spec.parameters:
132
+ type_annotation = python_type(param.type)
133
+ if param.default is not None:
134
+ default_str = repr(param.default)
135
+ lines.append(f' {param.name}: {type_annotation} = {default_str}')
136
+ elif not param.required:
137
+ lines.append(f' {param.name}: Optional[{type_annotation}] = None')
138
+ else:
139
+ lines.append(f' {param.name}: {type_annotation}')
140
+
141
+ return lines
142
+
143
+ def _generate_output_class(self) -> List[str]:
144
+ """Generate the output dataclass."""
145
+ lines = [
146
+ '@dataclass',
147
+ f'class {self.class_name}Output:',
148
+ f' """Output from {self.spec.name} skill execution."""',
149
+ ' success: bool',
150
+ ' message: str',
151
+ ' error_code: Optional[str] = None',
152
+ ' execution_time: float = 0.0',
153
+ ]
154
+
155
+ # Add success criteria as output fields
156
+ for criterion in self.spec.success_criteria:
157
+ lines.append(f' {criterion.name}: bool = False')
158
+
159
+ return lines
160
+
161
+ def _generate_skill_class_body(self) -> List[str]:
162
+ """Generate the main skill class."""
163
+ lines = [
164
+ f'class {self.class_name}Skill:',
165
+ f' """',
166
+ f' {self.spec.description}',
167
+ f' ',
168
+ f' Version: {self.spec.version}',
169
+ ]
170
+
171
+ if self.spec.author:
172
+ lines.append(f' Author: {self.spec.author}')
173
+
174
+ lines.extend([
175
+ f' """',
176
+ '',
177
+ ' def __init__(self, hardware_config: Dict[str, Any]):',
178
+ ' """',
179
+ ' Initialize the skill with hardware configuration.',
180
+ ' ',
181
+ ' Args:',
182
+ ' hardware_config: Hardware mapping configuration',
183
+ ' """',
184
+ ' self.config = hardware_config',
185
+ ' self.primitives = PrimitiveExecutor(hardware_config)',
186
+ ' self._setup_hardware()',
187
+ '',
188
+ ' def _setup_hardware(self) -> None:',
189
+ ' """Initialize hardware components."""',
190
+ ])
191
+
192
+ # Add hardware setup for each requirement
193
+ for req in self.spec.hardware_requirements:
194
+ comp_type = req.component_type
195
+ lines.extend([
196
+ f' self.{comp_type} = self.config.get("{comp_type}")',
197
+ f' if not self.{comp_type}:',
198
+ f' logger.warning("No {comp_type} configured")',
199
+ ])
200
+
201
+ lines.extend([
202
+ '',
203
+ f' def execute(self, input: {self.class_name}Input) -> {self.class_name}Output:',
204
+ ' """',
205
+ ' Execute the skill.',
206
+ ' ',
207
+ ' Args:',
208
+ ' input: Skill input parameters',
209
+ ' ',
210
+ ' Returns:',
211
+ ' Skill execution output with success status',
212
+ ' """',
213
+ ' import time',
214
+ ' start_time = time.time()',
215
+ '',
216
+ ' # Validate input',
217
+ f' validation_errors = validate_{self.module_name}_input(input)',
218
+ ' if validation_errors:',
219
+ f' return {self.class_name}Output(',
220
+ ' success=False,',
221
+ ' message=f"Input validation failed: {validation_errors}",',
222
+ ' error_code="INPUT_VALIDATION_ERROR"',
223
+ ' )',
224
+ '',
225
+ ' try:',
226
+ ' # Execute skill logic',
227
+ ' result = self._execute_impl(input)',
228
+ ' execution_time = time.time() - start_time',
229
+ '',
230
+ ' # Build output',
231
+ f' output = {self.class_name}Output(',
232
+ ' success=result.get("success", False),',
233
+ ' message=result.get("message", ""),',
234
+ ' execution_time=execution_time,',
235
+ ])
236
+
237
+ # Add success criteria outputs
238
+ for criterion in self.spec.success_criteria:
239
+ lines.append(f' {criterion.name}=result.get("{criterion.name}", False),')
240
+
241
+ lines.extend([
242
+ ' )',
243
+ '',
244
+ ' # Validate output',
245
+ f' output_errors = validate_{self.module_name}_output(output)',
246
+ ' if output_errors:',
247
+ ' logger.warning(f"Output validation warnings: {output_errors}")',
248
+ '',
249
+ ' return output',
250
+ '',
251
+ ' except Exception as e:',
252
+ ' execution_time = time.time() - start_time',
253
+ ' logger.error(f"Skill execution failed: {e}")',
254
+ f' return {self.class_name}Output(',
255
+ ' success=False,',
256
+ ' message=str(e),',
257
+ ' error_code="EXECUTION_ERROR",',
258
+ ' execution_time=execution_time',
259
+ ' )',
260
+ '',
261
+ ])
262
+
263
+ # Generate implementation method
264
+ lines.extend(self._generate_execute_impl())
265
+
266
+ return lines
267
+
268
+ def _generate_execute_impl(self) -> List[str]:
269
+ """Generate the _execute_impl method with skill logic."""
270
+ lines = [
271
+ f' def _execute_impl(self, input: {self.class_name}Input) -> Dict[str, Any]:',
272
+ ' """',
273
+ ' Internal skill implementation.',
274
+ ' ',
275
+ ' Override this method to customize skill behavior.',
276
+ ' """',
277
+ ' result = {"success": True, "message": ""}',
278
+ '',
279
+ ]
280
+
281
+ # Generate execution flow from primitives
282
+ if self.spec.execution_flow:
283
+ lines.append(' # Execute primitive sequence')
284
+ for i, call in enumerate(self.spec.execution_flow):
285
+ lines.extend(self._generate_primitive_call(call, i))
286
+ elif self.spec.primitives:
287
+ # Default execution: call each primitive in order
288
+ lines.append(' # Default execution sequence')
289
+ for i, prim_name in enumerate(self.spec.primitives):
290
+ call = PrimitiveCall(primitive=prim_name)
291
+ lines.extend(self._generate_primitive_call(call, i))
292
+ else:
293
+ lines.extend([
294
+ ' # TODO: Implement skill logic',
295
+ ' logger.warning("Skill implementation not complete")',
296
+ ])
297
+
298
+ # Check success criteria
299
+ lines.append('')
300
+ lines.append(' # Evaluate success criteria')
301
+ for criterion in self.spec.success_criteria:
302
+ lines.extend([
303
+ f' result["{criterion.name}"] = self._check_{criterion.name}(input)',
304
+ ])
305
+
306
+ lines.extend([
307
+ '',
308
+ ' # Overall success is all criteria met',
309
+ ' all_criteria = [',
310
+ ])
311
+ for criterion in self.spec.success_criteria:
312
+ lines.append(f' result.get("{criterion.name}", False),')
313
+ lines.extend([
314
+ ' ]',
315
+ ' result["success"] = all(all_criteria) if all_criteria else True',
316
+ ' result["message"] = "Skill completed successfully" if result["success"] else "Some criteria not met"',
317
+ '',
318
+ ' return result',
319
+ '',
320
+ ])
321
+
322
+ # Generate success criterion check methods
323
+ for criterion in self.spec.success_criteria:
324
+ lines.extend([
325
+ f' def _check_{criterion.name}(self, input: {self.class_name}Input) -> bool:',
326
+ f' """Check if {criterion.name} criterion is met."""',
327
+ f' # Condition: {criterion.condition}',
328
+ ])
329
+ if criterion.tolerance is not None:
330
+ lines.append(f' # Tolerance: {criterion.tolerance}')
331
+ lines.extend([
332
+ ' # TODO: Implement criterion check',
333
+ ' return True',
334
+ '',
335
+ ])
336
+
337
+ return lines
338
+
339
+ def _generate_primitive_call(self, call: PrimitiveCall, index: int) -> List[str]:
340
+ """Generate code for a single primitive call."""
341
+ lines = []
342
+
343
+ # Add condition check if present
344
+ if call.condition:
345
+ lines.append(f' if {call.condition}:')
346
+ indent = ' '
347
+ else:
348
+ indent = ' '
349
+
350
+ # Generate the primitive call
351
+ prim = get_primitive(call.primitive)
352
+ if prim:
353
+ # Build parameter string
354
+ params = []
355
+ for param_name, param_value in call.parameters.items():
356
+ if isinstance(param_value, str) and param_value.startswith('input.'):
357
+ params.append(f'{param_name}={param_value}')
358
+ else:
359
+ params.append(f'{param_name}={repr(param_value)}')
360
+
361
+ param_str = ', '.join(params)
362
+ lines.append(f'{indent}step_{index}_result = self.primitives.{call.primitive}({param_str})')
363
+ else:
364
+ lines.append(f'{indent}# Unknown primitive: {call.primitive}')
365
+ lines.append(f'{indent}step_{index}_result = True')
366
+
367
+ # Handle failure
368
+ if call.on_failure:
369
+ lines.extend([
370
+ f'{indent}if not step_{index}_result:',
371
+ ])
372
+ if call.on_failure == 'abort':
373
+ lines.append(f'{indent} raise RuntimeError("Primitive {call.primitive} failed")')
374
+ elif call.on_failure == 'retry' and call.retries > 0:
375
+ lines.extend([
376
+ f'{indent} for retry in range({call.retries}):',
377
+ f'{indent} step_{index}_result = self.primitives.{call.primitive}()',
378
+ f'{indent} if step_{index}_result:',
379
+ f'{indent} break',
380
+ ])
381
+ # 'continue' just continues execution
382
+
383
+ lines.append('')
384
+ return lines
385
+
386
+ def generate_primitives_wrapper(self) -> str:
387
+ """
388
+ Generate primitives.py with hardware-abstracted primitive calls.
389
+
390
+ Returns:
391
+ Python source code for the primitives wrapper
392
+ """
393
+ lines = [
394
+ '"""',
395
+ f'Primitives wrapper for {self.spec.name} skill.',
396
+ '',
397
+ 'Provides hardware-abstracted calls to robot primitives.',
398
+ f'Generated by Skill Compiler v1.0.0',
399
+ '"""',
400
+ '',
401
+ 'from typing import Any, Dict, List, Optional',
402
+ 'import logging',
403
+ '',
404
+ '',
405
+ 'logger = logging.getLogger(__name__)',
406
+ '',
407
+ '',
408
+ 'class PrimitiveExecutor:',
409
+ ' """',
410
+ ' Execute primitives with hardware abstraction.',
411
+ ' ',
412
+ ' Wraps low-level hardware calls and provides a consistent interface.',
413
+ ' """',
414
+ '',
415
+ ' def __init__(self, hardware_config: Dict[str, Any]):',
416
+ ' """Initialize with hardware configuration."""',
417
+ ' self.config = hardware_config',
418
+ ' self._setup_drivers()',
419
+ '',
420
+ ' def _setup_drivers(self) -> None:',
421
+ ' """Setup hardware drivers based on config."""',
422
+ ]
423
+
424
+ # Setup hardware drivers
425
+ for req in self.spec.hardware_requirements:
426
+ comp_type = req.component_type
427
+ lines.extend([
428
+ f' self.{comp_type}_driver = self._get_{comp_type}_driver()',
429
+ ])
430
+
431
+ lines.append('')
432
+
433
+ # Generate driver getter methods
434
+ for req in self.spec.hardware_requirements:
435
+ comp_type = req.component_type
436
+ lines.extend([
437
+ f' def _get_{comp_type}_driver(self):',
438
+ f' """Get or create {comp_type} hardware driver."""',
439
+ f' config = self.config.get("{comp_type}", {{}})',
440
+ f' driver_type = config.get("driver", "mock")',
441
+ ' # TODO: Implement driver factory',
442
+ ' return MockDriver()',
443
+ '',
444
+ ])
445
+
446
+ # Generate primitive methods
447
+ for prim_name in self.spec.primitives:
448
+ prim = get_primitive(prim_name)
449
+ if prim:
450
+ lines.extend(self._generate_primitive_method(prim))
451
+ else:
452
+ lines.extend([
453
+ f' def {prim_name}(self, **kwargs) -> bool:',
454
+ f' """Unknown primitive: {prim_name}"""',
455
+ f' logger.warning("Primitive {prim_name} not implemented")',
456
+ ' return False',
457
+ '',
458
+ ])
459
+
460
+ # Add mock driver class
461
+ lines.extend([
462
+ '',
463
+ 'class MockDriver:',
464
+ ' """Mock hardware driver for testing."""',
465
+ '',
466
+ ' def __getattr__(self, name):',
467
+ ' def mock_method(*args, **kwargs):',
468
+ ' logger.info(f"Mock call: {name}({args}, {kwargs})")',
469
+ ' return True',
470
+ ' return mock_method',
471
+ ])
472
+
473
+ return '\n'.join(lines)
474
+
475
+ def _generate_primitive_method(self, prim) -> List[str]:
476
+ """Generate a method for a single primitive."""
477
+ lines = []
478
+
479
+ # Build parameter list
480
+ params = ['self']
481
+ for param_name, param_def in prim.parameters.items():
482
+ type_str = python_type(param_def.type)
483
+ if param_def.required:
484
+ params.append(f'{param_name}: {type_str}')
485
+ else:
486
+ default_val = repr(param_def.default)
487
+ params.append(f'{param_name}: {type_str} = {default_val}')
488
+
489
+ param_str = ', '.join(params)
490
+ return_type = python_type(prim.returns)
491
+
492
+ lines.extend([
493
+ f' def {prim.name}({param_str}) -> {return_type}:',
494
+ f' """',
495
+ f' {prim.description}',
496
+ f' ',
497
+ ])
498
+
499
+ # Document parameters
500
+ for param_name, param_def in prim.parameters.items():
501
+ lines.append(f' Args:')
502
+ lines.append(f' {param_name}: {param_def.description}')
503
+
504
+ lines.extend([
505
+ f' ',
506
+ f' Returns:',
507
+ f' {prim.returns}: Success status',
508
+ f' """',
509
+ ' try:',
510
+ ])
511
+
512
+ # Generate hardware call based on primitive hardware requirements
513
+ if 'arm' in prim.hardware:
514
+ lines.append(' driver = self.arm_driver')
515
+ elif 'gripper' in prim.hardware:
516
+ lines.append(' driver = self.gripper_driver')
517
+ elif prim.hardware:
518
+ lines.append(f' driver = self.{prim.hardware[0]}_driver')
519
+ else:
520
+ lines.append(' driver = None')
521
+
522
+ # Generate the actual call
523
+ param_names = list(prim.parameters.keys())
524
+ call_args = ', '.join(f'{p}={p}' for p in param_names)
525
+
526
+ lines.extend([
527
+ f' result = driver.{prim.name}({call_args})',
528
+ ' return result',
529
+ ' except Exception as e:',
530
+ f' logger.error(f"Primitive {prim.name} failed: {{e}}")',
531
+ ' return False' if prim.returns == 'bool' else ' return None',
532
+ '',
533
+ ])
534
+
535
+ return lines
536
+
537
+ def generate_validators(self) -> str:
538
+ """
539
+ Generate validators.py for input/output validation.
540
+
541
+ Returns:
542
+ Python source code for validators
543
+ """
544
+ lines = [
545
+ '"""',
546
+ f'Validators for {self.spec.name} skill.',
547
+ '',
548
+ 'Provides input and output validation.',
549
+ f'Generated by Skill Compiler v1.0.0',
550
+ '"""',
551
+ '',
552
+ 'from typing import Any, Dict, List, Optional',
553
+ '',
554
+ '',
555
+ ]
556
+
557
+ # Import the input/output classes
558
+ lines.extend([
559
+ f'def validate_{self.module_name}_input(input) -> List[str]:',
560
+ ' """',
561
+ ' Validate skill input parameters.',
562
+ ' ',
563
+ ' Args:',
564
+ ' input: Input dataclass instance',
565
+ ' ',
566
+ ' Returns:',
567
+ ' List of validation errors (empty if valid)',
568
+ ' """',
569
+ ' errors = []',
570
+ '',
571
+ ])
572
+
573
+ # Generate validation for each parameter
574
+ for param in self.spec.parameters:
575
+ if param.required and param.default is None:
576
+ lines.extend([
577
+ f' # Validate {param.name}',
578
+ f' if input.{param.name} is None:',
579
+ f' errors.append("{param.name} is required")',
580
+ ])
581
+
582
+ if param.range:
583
+ min_val, max_val = param.range
584
+ lines.extend([
585
+ f' if input.{param.name} is not None:',
586
+ f' if input.{param.name} < {min_val} or input.{param.name} > {max_val}:',
587
+ f' errors.append("{param.name} must be between {min_val} and {max_val}")',
588
+ ])
589
+
590
+ lines.extend([
591
+ '',
592
+ ' return errors',
593
+ '',
594
+ '',
595
+ ])
596
+
597
+ # Output validation
598
+ lines.extend([
599
+ f'def validate_{self.module_name}_output(output) -> List[str]:',
600
+ ' """',
601
+ ' Validate skill output.',
602
+ ' ',
603
+ ' Args:',
604
+ ' output: Output dataclass instance',
605
+ ' ',
606
+ ' Returns:',
607
+ ' List of validation warnings',
608
+ ' """',
609
+ ' warnings = []',
610
+ '',
611
+ ' if output.success and not output.message:',
612
+ ' warnings.append("Success output should have a message")',
613
+ '',
614
+ ' if not output.success and not output.error_code:',
615
+ ' warnings.append("Failed output should have an error code")',
616
+ '',
617
+ ' return warnings',
618
+ ])
619
+
620
+ return '\n'.join(lines)
621
+
622
+ def generate_state_machine(self) -> str:
623
+ """
624
+ Generate state_machine.py for execution flow.
625
+
626
+ Returns:
627
+ Python source code for state machine
628
+ """
629
+ lines = [
630
+ '"""',
631
+ f'State machine for {self.spec.name} skill execution.',
632
+ '',
633
+ 'Manages execution flow and state transitions.',
634
+ f'Generated by Skill Compiler v1.0.0',
635
+ '"""',
636
+ '',
637
+ 'from enum import Enum, auto',
638
+ 'from typing import Any, Callable, Dict, Optional',
639
+ 'import logging',
640
+ '',
641
+ '',
642
+ 'logger = logging.getLogger(__name__)',
643
+ '',
644
+ '',
645
+ f'class {self.class_name}State(Enum):',
646
+ ' """States for skill execution."""',
647
+ ' IDLE = auto()',
648
+ ' INITIALIZING = auto()',
649
+ ]
650
+
651
+ # Add states for each primitive
652
+ for i, prim in enumerate(self.spec.primitives):
653
+ state_name = prim.upper().replace('-', '_')
654
+ lines.append(f' {state_name} = auto()')
655
+
656
+ lines.extend([
657
+ ' VALIDATING = auto()',
658
+ ' COMPLETED = auto()',
659
+ ' FAILED = auto()',
660
+ '',
661
+ '',
662
+ f'class {self.class_name}StateMachine:',
663
+ ' """',
664
+ ' State machine for managing skill execution flow.',
665
+ ' """',
666
+ '',
667
+ ' def __init__(self):',
668
+ f' self.state = {self.class_name}State.IDLE',
669
+ ' self.error: Optional[str] = None',
670
+ ' self._transitions: Dict[tuple, Callable] = {}',
671
+ ' self._setup_transitions()',
672
+ '',
673
+ ' def _setup_transitions(self) -> None:',
674
+ ' """Define valid state transitions."""',
675
+ f' S = {self.class_name}State',
676
+ '',
677
+ ' # Define transitions',
678
+ ' self._transitions = {',
679
+ ' (S.IDLE, "start"): S.INITIALIZING,',
680
+ ])
681
+
682
+ # Add transitions between primitives
683
+ prev_state = 'INITIALIZING'
684
+ for prim in self.spec.primitives:
685
+ state_name = prim.upper().replace('-', '_')
686
+ lines.append(f' (S.{prev_state}, "next"): S.{state_name},')
687
+ prev_state = state_name
688
+
689
+ lines.extend([
690
+ f' (S.{prev_state}, "next"): S.VALIDATING,',
691
+ ' (S.VALIDATING, "success"): S.COMPLETED,',
692
+ ' (S.VALIDATING, "failure"): S.FAILED,',
693
+ ' }',
694
+ '',
695
+ ' def transition(self, event: str) -> bool:',
696
+ ' """',
697
+ ' Attempt to transition to a new state.',
698
+ ' ',
699
+ ' Args:',
700
+ ' event: Event triggering the transition',
701
+ ' ',
702
+ ' Returns:',
703
+ ' True if transition succeeded',
704
+ ' """',
705
+ ' key = (self.state, event)',
706
+ ' if key in self._transitions:',
707
+ ' old_state = self.state',
708
+ ' self.state = self._transitions[key]',
709
+ ' logger.info(f"State transition: {old_state} -> {self.state}")',
710
+ ' return True',
711
+ ' else:',
712
+ ' logger.warning(f"Invalid transition: {self.state} + {event}")',
713
+ ' return False',
714
+ '',
715
+ ' def reset(self) -> None:',
716
+ ' """Reset state machine to initial state."""',
717
+ f' self.state = {self.class_name}State.IDLE',
718
+ ' self.error = None',
719
+ '',
720
+ ' @property',
721
+ ' def is_complete(self) -> bool:',
722
+ ' """Check if execution is complete."""',
723
+ f' return self.state in ({self.class_name}State.COMPLETED, {self.class_name}State.FAILED)',
724
+ '',
725
+ ' @property',
726
+ ' def is_success(self) -> bool:',
727
+ ' """Check if execution succeeded."""',
728
+ f' return self.state == {self.class_name}State.COMPLETED',
729
+ ])
730
+
731
+ return '\n'.join(lines)
732
+
733
+ def generate(self, output_dir: Path) -> Dict[str, str]:
734
+ """
735
+ Generate all skill code files.
736
+
737
+ Args:
738
+ output_dir: Directory to write files to
739
+
740
+ Returns:
741
+ Dict mapping filenames to generated content
742
+ """
743
+ output_dir = Path(output_dir)
744
+ skill_dir = output_dir / self.module_name
745
+
746
+ files = {
747
+ 'skill.py': self.generate_skill_class(),
748
+ 'primitives.py': self.generate_primitives_wrapper(),
749
+ 'validators.py': self.generate_validators(),
750
+ 'state_machine.py': self.generate_state_machine(),
751
+ '__init__.py': self._generate_init(),
752
+ }
753
+
754
+ # Create directory and write files
755
+ skill_dir.mkdir(parents=True, exist_ok=True)
756
+ for filename, content in files.items():
757
+ (skill_dir / filename).write_text(content)
758
+
759
+ return files
760
+
761
+ def _generate_init(self) -> str:
762
+ """Generate __init__.py for the skill package."""
763
+ return f'''"""
764
+ {self.spec.name} skill package.
765
+
766
+ {self.spec.description}
767
+ """
768
+
769
+ from .skill import {self.class_name}Skill, {self.class_name}Input, {self.class_name}Output
770
+ from .primitives import PrimitiveExecutor
771
+ from .state_machine import {self.class_name}State, {self.class_name}StateMachine
772
+
773
+ __all__ = [
774
+ "{self.class_name}Skill",
775
+ "{self.class_name}Input",
776
+ "{self.class_name}Output",
777
+ "PrimitiveExecutor",
778
+ "{self.class_name}State",
779
+ "{self.class_name}StateMachine",
780
+ ]
781
+
782
+ __version__ = "{self.spec.version}"
783
+ '''