Skip to content

Instantly share code, notes, and snippets.

@fry69

fry69/roast.md Secret

Last active July 4, 2025 09:08
Show Gist options
  • Select an option

  • Save fry69/ad94b5f8bd608697dbbeb8f08ab6a212 to your computer and use it in GitHub Desktop.

Select an option

Save fry69/ad94b5f8bd608697dbbeb8f08ab6a212 to your computer and use it in GitHub Desktop.
Roast + Claude Code

Yes, it is entirely possible and indeed one of Roast's core design goals to integrate Claude Code and implement a workflow similar to Mario Zechner's "Port Java to X" approach, including robust state management and resumability. Roast provides the structured framework that tames agentic AI, turning ad-hoc interactions into reproducible engineering workflows.

Integrating Claude Code and Zechner's Workflow with Roast

Roast directly supports Claude Code through its CodingAgent built-in tool. This tool is considered "Roast's Secret Weapon" and enables a full integration with Claude Code.

Here's how you can structure a Roast workflow to mimic Mario Zechner's "Port Java to X" process:

1. workflow.yml (The "Program")

This YAML file defines the overall structure, control flow, and tool access for your porting workflow, much like Zechner's port.md file serves as his "program".

# workflow.yml - Simulating Mario Zechner's "Port Java to X"
name: Port Java to X
model: anthropic/claude-3-opus-20240229 # Use a Claude model via OpenRouter as recommended for Claude Code
api_provider: openrouter
api_token: $(echo $OPENROUTER_API_KEY) # Dynamically fetch API token

tools:
  - Roast::Tools::ReadFile # To read source files, convention files, and porting notes
  - Roast::Tools::WriteFile # To write conventions, porting notes, and updated plan
  - Roast::Tools::Cmd: # To execute jq commands and compile-cpp.js
      allowed_commands:
        - jq
        - git
        - ./compile-cpp.js # Assuming this script is in your path and executable
        - ls # For directory exploration if needed by agent
  - Roast::Tools::CodingAgent: # The Claude Code integration
      coding_agent_command: claude --model opus # Customize Claude Code CLI command if needed
  - Roast::Tools::Input # For human checkpoints and confirmations

# Pre-processing steps: Run once before any targets are processed
pre_processing:
  - prepare_initial_state:
      description: Read or generate the porting-plan.json metadata.
  - load_conventions_and_notes:
      description: Load target language conventions and porting notes.

# Main workflow steps: Loop through each type to port
steps:
  - port_types_loop:
      each: "{{ (workflow.output.prepare_initial_state.portingOrder | filter_by('types', {'portingState': 'pending'})).map(&:javaSourcePath).uniq }}"
      # Zechner's jq command for next pending type:
      # jq -r '.portingOrder[] | {file: .javaSourcePath, types: .types[] | select(.portingState == "pending")} | "\(.file)|\(.types.name)|\(.types.kind)|\(.types.startLine)|\(.types.endLine)|\(.types.candidateFiles | join(","))"' porting-plan.json | head -1
      # A Ruby helper function or custom tool might be better for complex parsing/filtering.
      # For this example, let's assume 'prepare_initial_state' sets a `workflow.output.pending_types` array.
      # Or, a custom Ruby step could read the JSON and prepare the 'each' collection.
      # Simpler approach for demo: Assume a file `pending_java_files.txt` lists files to process.
      # each: "$(cat pending_java_files.txt)" # Iterate over lines from a command
      # Let's use a simpler iteration over a mock list for clarity:
      each: "{{ ['path/to/java/TypeA.java', 'path/to/java/TypeB.java'] }}" # Replace with dynamic source
      as: current_java_file
      steps:
        - get_current_type_details:
            description: Extract details for the current_java_file from porting-plan.json.
            prompt: "Using 'jq', extract the name, kind, startLine, endLine, and candidateFiles for the type in '{{current_java_file}}' that has 'portingState == \"pending\"' from 'porting-plan.json'. Output as JSON."
            # This step would need its own prompt.md or an inline prompt, and potentially a custom Ruby step or Cmd step with output parsing.
            # Example: $(jq --arg file "{{current_java_file}}" '.portingOrder[] | select(.javaSourcePath == $file) | .types[] | select(.portingState == "pending") | {name, kind, startLine, endLine, candidateFiles}' porting-plan.json)
        - confirm_port_type:
            prompt: "Do you want to port the type '{{output.get_current_type_details.name}}' from file '{{current_java_file}}' now? (yes/no)"
            type: confirm # Human checkpoint
            if: "{{ output.confirm_port_type == 'yes' }}" # Conditional execution
            then:
              - ^perform_porting: # Agent step: Delegate to Claude Code
                  description: "Port the Java type '{{output.get_current_type_details.name}}' from '{{current_java_file}}' (lines {{output.get_current_type_details.startLine}}-{{output.get_current_type_details.endLine}}) to the target language as per 'targetRuntime-conventions.md' and 'porting-notes.md'. Use '{{output.get_current_type_details.candidateFiles | join(', ')}}' as candidate files. Ensure functional parity and idiomatic differences are handled. Use Bash tools for compile testing (`./compile-cpp.js {{candidate_file}}`) if target is C++. Respond with the diff and a summary."
                  # This prompt would typically be in perform_porting/prompt.md and could be quite detailed
              - update_porting_plan:
                  description: Mark the type as 'done' in porting-plan.json.
                  $(jq --arg file "{{current_java_file}}" --arg type "{{output.get_current_type_details.name}}" \
                      '(.portingOrder[] | select(.javaSourcePath == $file) | .types[] | select(.name == $type) | .portingState) = "done"' \
                      porting-plan.json > tmp.json && mv tmp.json porting-plan.json) # Update state
              - review_and_continue:
                  prompt: "Review the changes made. Mark as done and continue to next type? (yes/no)"
                  type: confirm # Another human checkpoint
                  # This step would implicitly update the porting-plan.json on 'yes'
                  # For a real scenario, you'd integrate git diff here to show changes:
                  # ^show_diff: prompt: "Show me the git diff for changes related to {{output.get_current_type_details.name}}."
              - update_porting_notes:
                  prompt: "Are there any new porting patterns or special cases discovered for '{{output.get_current_type_details.name}}'? If so, add them to 'porting-notes.md'."
                  type: text # Allow user to add notes directly
                  # The response from this input step could be used by a WriteFile tool to append to porting-notes.md.
                  # e.g., if: "{{output.update_porting_notes | present}}" then: write_file(path: "porting-notes.md", content: "{{output.update_porting_notes}}", append: true)

# Post-processing steps: Run once after all targets have been processed
post_processing:
  - generate_final_report:
      description: "Generate a summary report based on the updated porting-plan.json and collected notes."
      output.txt: "summary_report/output.txt" # Custom output template for final report

2. Example Step Files

Roast uses Markdown files for prompts and ERB templates for output.

pre_processing/prepare_initial_state/prompt.md

# Prepare Initial Porting State

First, we need to ensure the 'porting-plan.json' file exists and load its metadata.
If the file doesn't exist, please instruct the user to generate it using the external script: `./generate-porting-plan.js <prevBranch> <currentBranch> <spineRuntimesDir> <targetRuntime>`

Otherwise, read the 'porting-plan.json' file and extract the following metadata using 'jq':
- `prevBranch`
- `currentBranch`
- `spineRuntimesDir`
- `targetRuntime`
- `targetRuntimePath`
- `targetRuntimeLanguage`

Output these as a JSON object.

Note: In Zechner's actual workflow, generate-porting-plan.js is run externally to create the JSON state. This Roast step would simulate loading or verifying that state. Roast's Cmd tool could run jq to read this directly.

pre_processing/load_conventions_and_notes/prompt.md

# Load Conventions and Porting Notes

Read the contents of the '{{workflow.output.prepare_initial_state.targetRuntime}}-conventions.md' file. If it does not exist, use the 'CodingAgent' to analyze the codebase at '{{workflow.output.prepare_initial_state.targetRuntimePath}}' and generate this file documenting the conventions as described in our overall workflow.

Also, read the 'porting-notes.md' file. If it doesn't exist, create it with initial content: "# Porting Notes".

Make sure both contents are available in the conversation context.

This step demonstrates the dynamic file creation/reading based on workflow.output from previous steps. An inline ^ (Agent step) could be used here if the conventions file is missing, allowing Claude Code to generate it.

perform_porting/prompt.md (This is the core Claude Code prompt)

# Perform Type Porting

You are an expert C++ developer (or whatever {{workflow.output.prepare_initial_state.targetRuntimeLanguage}} expert). Your task is to port the following Java type to the target language, ensuring functional equivalence and idiomatic style as per the provided conventions and notes.

**Current Java File:** {{workflow.current_java_file}}
**Type to Port:** {{output.get_current_type_details.name}} (Kind: {{output.get_current_type_details.kind}})
**Java Source Content (relevant section from lines {{output.get_current_type_details.startLine}} to {{output.get_current_type_details.endLine}}):**
```java
{{ read_file(path: workflow.current_java_file, offset: output.get_current_type_details.startLine, limit: (output.get_current_type_details.endLine - output.get_current_type_details.startLine + 1)) }}

Target Language Conventions ({{workflow.output.prepare_initial_state.targetRuntime}}-conventions.md):

{{ read_file(path: workflow.output.prepare_initial_state.targetRuntime + '-conventions.md') }}

Porting Notes (porting-notes.md):

{{ read_file(path: 'porting-notes.md') }}

Candidate Target Files: {{output.get_current_type_details.candidateFiles | join(', ')}} Existing Target File Content (if applicable, for each candidate file): <% output.get_current_type_details.candidateFiles.each do |file_path| %> File: <%= file_path %>

<%= read_file(path: file_path) rescue "File does not exist or is empty." %>

<% end %>

Instructions:

  1. Analyze the Java source and compare it with the existing target files. Identify differences due to new/changed functionality in Java and those due to idiomatic differences in the target language.
  2. Make necessary changes to the candidate target files. If a candidate file doesn't exist, create it. Use the update_files tool to apply a unified diff.
  3. Ensure 100% functional parity. The ported code must behave identically to the Java reference.
  4. Adhere strictly to the targetLanguage-conventions.md and porting-notes.md. Pay special attention to naming conventions, memory management, and method differences (e.g., Posed.reset() vs resetConstrained()).
  5. For C++ ({{workflow.output.prepare_initial_state.targetRuntimeLanguage == 'cpp'}}): After making changes to a .cpp file, run the ./compile-cpp.js command on that file to check for compilation errors. If there are errors, fix them iteratively.
  6. Provide a summary of the changes made and any notable decisions (e.g., how a specific Java construct was translated idiomatically).
  7. Respond with the generated diff(s) using the update_files tool format.

This robust prompt demonstrates how the CodingAgent (Claude Code) receives all necessary context and tools to perform the complex porting task iteratively and adaptively. The read_file tool calls within the prompt directly pull in the relevant file contents.

post_processing/generate_final_report/output.txt

=== Porting Workflow Summary Report ===
Generated at: <%= Time.now.strftime("%Y-%m-%d %H:%M:%S") %>
Source Branch: <%= pre_processing.prepare_initial_state.prevBranch %>
Target Branch: <%= pre_processing.prepare_initial_state.currentBranch %>
Target Runtime: <%= pre_processing.prepare_initial_state.targetRuntime %>
Total files processed: <%= targets.size %>

Summary of Porting Progress:
<% targets.each do |file_path, target| %>
- <%= File.basename(file_path) %>: Ported type <%= target.output.get_current_type_details.name %> (Status: <%= target.output.update_porting_plan.status || 'completed' %>)
  Details: <%= target.output.perform_porting.summary %>
<% end %>

Overall Assessment:
<%= output.generate_report.response %>

===============================

This ERB template leverages the pre_processing and targets hashes to aggregate results from all individual file processing steps, creating a final, comprehensive report, similar to how Zechner discusses outputs.

Core Parts of Zechner's Workflow Implemented in Roast:

  • "Program" as Prompts/YAML: The workflow.yml and prompt.md files define the structured instructions for the LLM, similar to Zechner's port.md.
  • Structured Inputs: Roast's target option and explicit ReadFile tools allow precise control over what information (source code, conventions, notes) goes into the LLM's context.
  • Persistent State:
    • The porting-plan.json serves as the central state file, explicitly managed by Cmd steps running jq commands to mark types as done.
    • porting-notes.md is another state file, updated via WriteFile or an interactive Input step.
    • This directly aligns with Zechner's concept of serializing state to disk using JSON and Markdown files.
  • Function Library/Tools: Roast's built-in tools (ReadFile, WriteFile, Cmd, CodingAgent) and the ability to define custom Ruby tools and MCP tools provide the "function library" for the LLM to interact with the environment. The example shows jq, git, and ./compile-cpp.js as allowed Cmd functions.
  • Control Flow: Roast explicitly supports each loops for iteration, if/unless for conditionals, and Input steps for human checkpoints. This formalizes Zechner's "loops, conditionals, and human checkpoints".
  • Human Checkpoints: The Input step (confirm_port_type, review_and_continue) directly implements human intervention points where the workflow pauses for user confirmation or input.

Resuming a Workflow and Using State with Roast

Yes, resuming a workflow at any point is definitely possible with Roast, and it's a "killer feature". Roast achieves this through its Session Replay capabilities and explicit state management:

  1. Session Storage: Roast automatically saves each step's state, including conversation transcripts and outputs, to an SQLite database (by default, or filesystem). This aligns perfectly with Zechner's emphasis on persistent state.
  2. Session Replay Command: You can use the -r or --replay option with roast execute to resume a workflow from a specific step, optionally even from a specific session timestamp.
    roast execute workflow.yml -r review_and_continue
    # Or to resume a specific past session
    roast execute workflow.yml -r 20250507_123456_789:review_and_continue
    This dramatically speeds up development and debugging by avoiding the need to rerun expensive AI operations or long initial steps.
  3. Retrieving and Using State (Implicit and Explicit):
    • Implicit (Conversation Context): For most AI steps, the LLM naturally remembers the entire conversation history, including previous prompts and responses. This is often sufficient for steps to build upon earlier results without explicit configuration.
    • Explicit (Output Hash): Each step's result is stored in the workflow.output hash, using the step name as the key. You can programmatically access this hash within subsequent steps, prompts, or commands using interpolation {{output.step_name.key}}. This is crucial for passing structured data between non-LLM steps or for conditional logic based on previous results.
    • External Files (Porting Plan, Notes): As demonstrated in the example workflow, Roast can explicitly read from and write to external files like porting-plan.json and porting-notes.md using tools like ReadFile, WriteFile, and Cmd (with jq). This allows the persistent state to reside outside Roast's internal session storage, which can then be picked up by any subsequent run, whether a full run or a resumed one. Zechner explicitly states that his JSON and Markdown files allow for resuming from any point with a fresh context.

By combining Roast's structured workflow capabilities, its direct Claude Code integration, and its robust state management and session replay features, you can effectively implement and manage complex agentic engineering tasks, aligning perfectly with Mario Zechner's vision of taming agentic AI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment