Skip to content

feat: add symphony workflow preview command#95

Merged
chihsuan merged 9 commits into
mainfrom
worktree-workflow-preview
May 27, 2026
Merged

feat: add symphony workflow preview command#95
chihsuan merged 9 commits into
mainfrom
worktree-workflow-preview

Conversation

@chihsuan
Copy link
Copy Markdown
Member

Context

Authors editing WORKFLOW.md had no way to see the final prompt an agent receives; partials and variables were only resolved at runtime, so template mistakes surfaced mid-run.

TL;DR

Add symphony workflow preview to print the assembled base-issue prompt from a local WORKFLOW.md.

Summary

  • Add symphony workflow preview [--file WORKFLOW.md] [--agent codex|claude] escript subcommand.
  • Render the exact base-issue prompt with deterministic sample data — no Linear, network, or orchestrator.
  • Reuse PromptBuilder.build_prompt/2 via a new optional :workflow opt so preview can't drift from runtime.
  • Add SymphonyElixir.WorkflowPreview (render/1, sample_issue/0); surface bad partials/vars as errors (lint).
  • Document the command in README.md; add a non-rendering front-matter tip in WORKFLOW.md.

Alternatives

  • Static partial-expansion only (variables left as placeholders): rejected — does not show what the agent actually receives.
  • Fetch a real Linear issue for variable values: rejected — needs auth/network and is non-deterministic; sample data is enough to verify structure.
  • Mix task instead of escript subcommand: rejected — end users run the shipped binary, not the source checkout.

Test Plan

  • make all
  • mix test test/symphony_elixir/workflow_preview_test.exs test/symphony_elixir/cli_test.exs test/symphony_elixir/prompt_builder_test.exs
  • ./bin/symphony workflow preview renders the repo WORKFLOW.md; unknown {% render %} partial reports an error

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new CLI subcommand to preview the fully assembled base-issue agent prompt from a local WORKFLOW.md, using deterministic sample issue data and the same prompt-building path as runtime to avoid drift.

Changes:

  • Add symphony workflow preview CLI subcommand to print the rendered base-issue prompt (with sample issue context) without needing Linear/network/orchestrator.
  • Introduce SymphonyElixir.WorkflowPreview and a :workflow seam in PromptBuilder.build_prompt/2 to reuse the runtime prompt assembly code path.
  • Add tests for preview behavior and update docs (README.md, WORKFLOW.md) to advertise the command.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
WORKFLOW.md Adds a front-matter tip about the new preview command without affecting rendered prompt content.
README.md Documents symphony workflow preview usage and intent as a lint/preview tool.
lib/symphony_elixir/workflow_preview.ex New module that loads a workflow file and renders the assembled prompt with sample issue data.
lib/symphony_elixir/prompt_builder.ex Adds :workflow opt handling so preview can reuse the same prompt assembly logic as runtime.
lib/symphony_elixir/cli.ex Adds workflow preview command wiring and usage text.
test/symphony_elixir/workflow_preview_test.exs New unit tests for preview rendering and error handling.
test/symphony_elixir/prompt_builder_test.exs Verifies PromptBuilder.build_prompt/2 supports pre-loaded workflows via :workflow.
test/symphony_elixir/cli_test.exs Adds CLI-level tests for preview output and usage/error behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +320 to +326
defp resolved_workflow(opts, workflow_source) do
case Keyword.get(opts, :workflow) do
{:ok, _workflow} = loaded -> loaded
%{} = workflow -> {:ok, workflow}
nil -> workflow_for_prompt(workflow_source)
end
end
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2d5a8e5. resolved_workflow/2 now matches {:error, _reason} and propagates it, so a Workflow.load/1 error flows to prompt_template!/2 and raises the existing workflow_unavailable: ... error instead of a CaseClauseError. Added tests covering the {:ok, workflow} and {:error, reason} shapes.

Comment on lines +37 to +43
defp load_error_message(file, {:missing_workflow_file, _path, posix}) do
"Workflow file not found: `#{file}` (#{:file.format_error(posix)})"
end

defp load_error_message(file, reason) do
"Could not read workflow file `#{file}`: #{inspect(reason)}"
end
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2d5a8e5. load_error_message/2 now uses :file.format_error/1 for the POSIX reason, so :enoent and :eacces read differently ("no such file or directory" vs "permission denied"), and adds a dedicated {:workflow_parse_error, _} clause that reports a readable "Could not parse front matter" message. The generic fallback is clearly labeled "Could not load workflow file". Added tests for missing-file, malformed-YAML, and non-map front matter.

@chihsuan chihsuan merged commit 911f4bb into main May 27, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants