Default step type — invokes a Spec Kit command via the integration CLI. The command files (skills, markdown, TOML) are already installed in the integration's directory on disk. This step tells the CLI to execute the command by name (e.g. ``/speckit.specify`` or ``/speckit-specify``
| 11 | |
| 12 | |
| 13 | class CommandStep(StepBase): |
| 14 | """Default step type — invokes a Spec Kit command via the integration CLI. |
| 15 | |
| 16 | The command files (skills, markdown, TOML) are already installed in |
| 17 | the integration's directory on disk. This step tells the CLI to |
| 18 | execute the command by name (e.g. ``/speckit.specify`` or |
| 19 | ``/speckit-specify``) rather than reading the file contents. |
| 20 | |
| 21 | .. note:: |
| 22 | |
| 23 | CLI output is streamed to the terminal for live progress. |
| 24 | ``output.exit_code`` is always captured and can be referenced |
| 25 | by later steps (e.g. ``{{ steps.specify.output.exit_code }}``). |
| 26 | Full ``stdout``/``stderr`` capture is a planned enhancement. |
| 27 | """ |
| 28 | |
| 29 | type_key = "command" |
| 30 | |
| 31 | def execute(self, config: dict[str, Any], context: StepContext) -> StepResult: |
| 32 | command = config.get("command", "") |
| 33 | input_data = config.get("input", {}) |
| 34 | |
| 35 | # Resolve expressions in input |
| 36 | resolved_input: dict[str, Any] = {} |
| 37 | for key, value in input_data.items(): |
| 38 | resolved_input[key] = evaluate_expression(value, context) |
| 39 | |
| 40 | # Resolve integration (step → workflow default → project default) |
| 41 | integration = config.get("integration") or context.default_integration |
| 42 | if integration and isinstance(integration, str) and "{{" in integration: |
| 43 | integration = evaluate_expression(integration, context) |
| 44 | |
| 45 | # Resolve model |
| 46 | model = config.get("model") or context.default_model |
| 47 | if model and isinstance(model, str) and "{{" in model: |
| 48 | model = evaluate_expression(model, context) |
| 49 | |
| 50 | # Merge options (workflow defaults ← step overrides) |
| 51 | options = dict(context.default_options) |
| 52 | step_options = config.get("options", {}) |
| 53 | if step_options: |
| 54 | options.update(step_options) |
| 55 | |
| 56 | # Attempt CLI dispatch |
| 57 | args_str = str(resolved_input.get("args", "")) |
| 58 | dispatch_result = self._try_dispatch( |
| 59 | command, integration, model, args_str, context |
| 60 | ) |
| 61 | |
| 62 | output: dict[str, Any] = { |
| 63 | "command": command, |
| 64 | "integration": integration, |
| 65 | "model": model, |
| 66 | "options": options, |
| 67 | "input": resolved_input, |
| 68 | } |
| 69 | |
| 70 | if dispatch_result is not None: |
no outgoing calls