feat(workflows): add --dry-run flag to preview spec/plan output without AI invocation#2704
feat(workflows): add --dry-run flag to preview spec/plan output without AI invocation#2704fuleinist wants to merge 2 commits into
Conversation
…ut AI invocation Implements GitHub issue github#2661. - Add dry_run field to StepContext (workflows/base.py) - Add dry_run parameter to WorkflowEngine.execute() (workflows/engine.py) - Add --dry-run to 'specify workflow run' CLI command - Add 'specify specify' and 'specify plan' CLI commands with --dry-run support - CommandStep: in dry-run mode, renders the command/integration/model and returns COMPLETED without spawning the integration CLI subprocess - GateStep: in dry-run mode, skips interactive prompt and returns COMPLETED - Add tests for dry-run in TestCommandStep, TestGateStep, and TestWorkflowEngine Usage: specify specify --spec 'Build a kanban board' --dry-run specify plan --spec 'Build a kanban board' --dry-run specify workflow run speckit --input spec='Build kanban' --dry-run
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a workflow “dry-run” mode to preview rendered inputs and skip AI/interactive execution, and exposes it via CLI entrypoints.
Changes:
- Introduces
dry_runonWorkflowEngine.execute()and propagates it throughStepContext. - Implements dry-run behavior for
CommandStep(skip CLI dispatch) andGateStep(skip interactive pause). - Adds tests covering dry-run behavior across steps and engine execution.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_workflows.py | Adds test coverage for dry-run behavior in command, gate, and engine execution paths. |
| src/specify_cli/workflows/steps/gate/init.py | Skips interactive gating and returns COMPLETED during dry-run. |
| src/specify_cli/workflows/steps/command/init.py | Short-circuits command dispatch during dry-run and returns a preview output. |
| src/specify_cli/workflows/engine.py | Adds dry_run parameter to execute() and passes it to StepContext. |
| src/specify_cli/workflows/base.py | Extends StepContext with a dry_run flag. |
| src/specify_cli/init.py | Adds dry-run CLI options and new direct “specify/plan” CLI commands. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| invoke_str = f"{command} {args_str}".strip() | ||
| output["dispatched"] = False | ||
| output["dry_run"] = True | ||
| output["exit_code"] = None |
There was a problem hiding this comment.
Fixed: changed to in dry-run mode. Since the step returns , exit_code=0 accurately reflects that the step succeeded — and it avoids breaking expression evaluation in downstream steps that expect an integer.
| self, | ||
| definition: WorkflowDefinition, | ||
| inputs: dict[str, Any] | None = None, | ||
| run_id: str | None = None, | ||
| dry_run: bool = False, | ||
| ) -> RunState: | ||
| """Execute a workflow definition. |
There was a problem hiding this comment.
Fixed: added comprehensive docstring documentation for the dry_run parameter in WorkflowEngine.execute(), covering what is skipped, what side-effects remain (run persistence), and expected status behavior.
| ) | ||
| console.print(f"\n[{status_color}]Status: {state.status.value}[/{status_color}]") | ||
| if dry_run: | ||
| console.print("[dim]Run with --dry-run to see step details. Run without --dry-run to execute.[/dim]") |
There was a problem hiding this comment.
Fixed: changed message from 'Run with --dry-run to see step details. Run without --dry-run to execute.' to simply 'Run without --dry-run to execute.' — the confusing 'Run with --dry-run' part was contradictory since the message only appears inside the dry-run block.
| ) | ||
| console.print(f"\n[{status_color}]Status: {state.status.value}[/{status_color}]") | ||
| if dry_run: | ||
| console.print("[dim]Run with --dry-run to see step details. Run without --dry-run to execute.[/dim]") |
There was a problem hiding this comment.
Same fix as above for the plan command: changed message from 'Run with --dry-run to see step details. Run without --dry-run to execute.' to simply 'Run without --dry-run to execute.'.
- Set exit_code=0 in dry-run mode (CommandStep) instead of None, matching the COMPLETED status and not breaking expression evaluation - Add dry_run parameter documentation to WorkflowEngine.execute() docstring - Fix contradictory 'Run with --dry-run' hint messages in specify specify/plan commands (the message appeared inside the dry-run block itself)
Summary
Implements GitHub issue #2661 — add a
--dry-runflag tospecify specifyandspecify plancommands to preview rendered prompts without invoking the AI.Changes
Core engine
src/specify_cli/workflows/base.py: Adddry_run: bool = Falsefield toStepContextdataclasssrc/specify_cli/workflows/engine.py: Adddry_run: bool = Falseparameter toWorkflowEngine.execute(); passes flag toStepContextCLI commands
src/specify_cli/__init__.py:--dry-runoption tospecify workflow runcommandspecify specifycommand:specify specify --spec "..." [--dry-run]specify plancommand:specify plan --spec "..." [--dry-run]Step behavior
CommandStep(workflows/steps/command/__init__.py): Whencontext.dry_run=True, renders the resolved command/integration/model and returnsCOMPLETEDwithout spawning the integration CLI subprocessGateStep(workflows/steps/gate/__init__.py): Whencontext.dry_run=True, skips interactive prompt and returnsCOMPLETEDimmediatelyTests
tests/test_workflows.py: Add dry-run tests forTestCommandStep,TestGateStep, andTestWorkflowEngineUsage examples
Dry-run output shows the resolved command, integration, model, and arguments without making any AI API calls — suitable for CI/template iteration.
Acceptance criteria
--dry-runflag prints rendered prompt/inputs without AI API callsspecify specify --dry-run --spec ...worksspecify plan --dry-run --spec ...worksspecify workflow run ... --dry-runworksCloses #2661