Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,956 changes: 1,956 additions & 0 deletions .github/workflows/agent-cook.lock.yml

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions .github/workflows/agent-cook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
name: Agent Cook
on:
workflow_dispatch:
inputs:
trigger_specification:
description: 'Specify when the workflow should trigger (e.g., "on issues opened", "on push to main", "manually via workflow_dispatch")'
required: true
type: string
task_specification:
description: 'Describe what the workflow should do (e.g., "analyze the issue and provide suggestions", "run tests and create summary")'
required: true
type: string
output_specification:
description: 'Specify the desired output (e.g., "create issue comment", "create pull request", "add labels")'
required: true
type: string

permissions:
contents: read
actions: read

safe-outputs:
create-pull-request:
title-prefix: "[agent-cook] "
labels: [agentic-workflow, automation]
draft: true

engine: claude
---

# Agent Cook - Agentic Workflow Generator

You are the "Agent Cook" - an AI assistant that helps users create agentic workflows for GitHub repositories. Your task is to generate a new agentic workflow based on the user's specifications.

## User's Requirements

<trigger_specification>${{ github.event.inputs.trigger_specification }}</trigger_specification>

<task_specification>${{ github.event.inputs.task_specification }}</task_specification>

<output_specification>${{ github.event.inputs.output_specification }}</output_specification>

## Your Task

1. **Parse the Requirements**: Analyze the user's trigger, task, and output specifications to understand what they want to achieve.

2. **Design the Workflow**: Create a complete agentic workflow markdown file with:
- Appropriate `on:` trigger configuration based on the trigger specification
- Proper `permissions:` section for the required GitHub API access
- Correct `safe-outputs:` configuration based on the output specification
- Clear, actionable instructions in natural language for the AI to execute

3. **Validate the Syntax**: Use the gh-aw compiler to validate your generated workflow:
```bash
# Create a temporary workflow file to test
echo "YOUR_GENERATED_WORKFLOW_CONTENT" > /tmp/test-workflow.md

# Validate the syntax without generating lock files
gh-aw compile /tmp/test-workflow.md --no-emit --validate --verbose
```

4. **Generate the Final Workflow**: Create a properly formatted agentic workflow file with:
- A meaningful name (derived from the task specification)
- Complete frontmatter configuration
- Clear, step-by-step instructions for the AI
- Proper use of GitHub context expressions where needed

5. **Create Pull Request**: Save your final workflow as `.github/workflows/[generated-name].md` and create a pull request with:
- **Title**: "Add [workflow-name] agentic workflow"
- **Body**:
```
## Generated Agentic Workflow

This workflow was generated by Agent Cook based on the following specifications:

**Trigger**: `${{ github.event.inputs.trigger_specification }}`
**Task**: `${{ github.event.inputs.task_specification }}`
**Output**: `${{ github.event.inputs.output_specification }}`

## Workflow Details

[Include a brief explanation of what the workflow does and how it works]

## Usage

[Include any special instructions for using the workflow]
```

## Guidelines

- Follow the agentic workflow format with YAML frontmatter and markdown content
- Use appropriate GitHub Actions triggers (issues, pull_request, workflow_dispatch, schedule, etc.)
- Include only necessary permissions - follow principle of least privilege
- Use safe-outputs for GitHub API interactions (create-issue, add-issue-comment, create-pull-request, etc.)
- Write clear, specific instructions that an AI can follow
- Include relevant GitHub context expressions (e.g., `${{ github.event.issue.number }}`)
- Ensure the workflow name is descriptive and follows kebab-case convention
- Test your generated workflow with the compiler before creating the PR
- For detailed formatting and configuration options, reference the comprehensive agentic workflow documentation

Remember: You are creating a workflow FILE, not just explaining how to create one. The output should be a complete, working agentic workflow that can be compiled and executed.
4 changes: 3 additions & 1 deletion cmd/gh-aw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,12 @@ Examples:
validate, _ := cmd.Flags().GetBool("validate")
watch, _ := cmd.Flags().GetBool("watch")
instructions, _ := cmd.Flags().GetBool("instructions")
noEmit, _ := cmd.Flags().GetBool("no-emit")
if err := validateEngine(engineOverride); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
if err := cli.CompileWorkflows(args, verbose, engineOverride, validate, watch, instructions); err != nil {
if err := cli.CompileWorkflows(args, verbose, engineOverride, validate, watch, instructions, noEmit); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
Expand Down Expand Up @@ -346,6 +347,7 @@ func init() {
compileCmd.Flags().Bool("validate", false, "Enable GitHub Actions workflow schema validation")
compileCmd.Flags().BoolP("watch", "w", false, "Watch for changes to workflow files and recompile automatically")
compileCmd.Flags().Bool("instructions", false, "Generate or update GitHub Copilot instructions file")
compileCmd.Flags().Bool("no-emit", false, "Validate workflow without generating lock files")

// Add flags to remove command
removeCmd.Flags().Bool("keep-orphans", false, "Skip removal of orphaned include files that are no longer referenced by any workflow")
Expand Down
5 changes: 4 additions & 1 deletion pkg/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,13 +568,16 @@ func AddWorkflowWithTracking(workflow string, number int, verbose bool, engineOv
}

// CompileWorkflows compiles markdown files into GitHub Actions workflow files
func CompileWorkflows(markdownFiles []string, verbose bool, engineOverride string, validate bool, watch bool, writeInstructions bool) error {
func CompileWorkflows(markdownFiles []string, verbose bool, engineOverride string, validate bool, watch bool, writeInstructions bool, noEmit bool) error {
// Create compiler with verbose flag and AI engine override
compiler := workflow.NewCompiler(verbose, engineOverride, GetVersion())

// Set validation based on the validate flag (false by default for compatibility)
compiler.SetSkipValidation(!validate)

// Set noEmit flag to validate without generating lock files
compiler.SetNoEmit(noEmit)

if watch {
// Watch mode: watch for file changes and recompile automatically
// For watch mode, we only support a single file for now
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/commands_compile_workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ This is a test workflow for backward compatibility.
if tt.workflowID != "" {
args = []string{tt.workflowID}
}
err = CompileWorkflows(args, false, "", false, false, false)
err = CompileWorkflows(args, false, "", false, false, false, false)

if tt.expectError {
if err == nil {
Expand Down
56 changes: 54 additions & 2 deletions pkg/cli/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func TestCompileWorkflows(t *testing.T) {
if tt.markdownFile != "" {
args = []string{tt.markdownFile}
}
err := CompileWorkflows(args, false, "", false, false, false)
err := CompileWorkflows(args, false, "", false, false, false, false)

if tt.expectError && err == nil {
t.Errorf("Expected error for test '%s', got nil", tt.name)
Expand All @@ -119,6 +119,58 @@ func TestCompileWorkflows(t *testing.T) {
}
}

func TestCompileWorkflowsWithNoEmit(t *testing.T) {
defer os.RemoveAll(".github")

// Create test directory and workflow
err := os.MkdirAll(".github/workflows", 0755)
if err != nil {
t.Fatalf("Failed to create test directory: %v", err)
}

// Create a simple test workflow
workflowContent := `---
on:
workflow_dispatch:
permissions:
contents: read
---

# Test Workflow for No Emit

This is a test workflow to verify the --no-emit flag functionality.`

err = os.WriteFile(".github/workflows/no-emit-test.md", []byte(workflowContent), 0644)
if err != nil {
t.Fatalf("Failed to create test workflow file: %v", err)
}

// Test compilation with noEmit = false (should create lock file)
err = CompileWorkflows([]string{"no-emit-test"}, false, "", false, false, false, false)
if err != nil {
t.Errorf("CompileWorkflows with noEmit=false should not error, got: %v", err)
}

// Verify lock file was created
if _, err := os.Stat(".github/workflows/no-emit-test.lock.yml"); os.IsNotExist(err) {
t.Error("Lock file should have been created when noEmit=false")
}

// Remove lock file
os.Remove(".github/workflows/no-emit-test.lock.yml")

// Test compilation with noEmit = true (should NOT create lock file)
err = CompileWorkflows([]string{"no-emit-test"}, false, "", false, false, false, true)
if err != nil {
t.Errorf("CompileWorkflows with noEmit=true should not error, got: %v", err)
}

// Verify lock file was NOT created
if _, err := os.Stat(".github/workflows/no-emit-test.lock.yml"); !os.IsNotExist(err) {
t.Error("Lock file should NOT have been created when noEmit=true")
}
}

func TestRemoveWorkflows(t *testing.T) {
err := RemoveWorkflows("test-pattern", false)

Expand Down Expand Up @@ -239,7 +291,7 @@ func TestAllCommandsExist(t *testing.T) {
}{
{func() error { return ListWorkflows(false) }, false, "ListWorkflows"},
{func() error { return AddWorkflowWithTracking("", 1, false, "", "", false, nil) }, false, "AddWorkflowWithTracking (empty name)"}, // Shows help when empty, doesn't error
{func() error { return CompileWorkflows([]string{}, false, "", false, false, false) }, false, "CompileWorkflows"}, // Should compile existing markdown files successfully
{func() error { return CompileWorkflows([]string{}, false, "", false, false, false, false) }, false, "CompileWorkflows"}, // Should compile existing markdown files successfully
{func() error { return RemoveWorkflows("test", false) }, false, "RemoveWorkflows"}, // Should handle missing directory gracefully
{func() error { return StatusWorkflows("test", false) }, false, "StatusWorkflows"}, // Should handle missing directory gracefully
{func() error { return EnableWorkflows("test") }, false, "EnableWorkflows"}, // Should handle missing directory gracefully
Expand Down
19 changes: 18 additions & 1 deletion pkg/cli/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type WorkflowRun struct {
Duration time.Duration
TokenUsage int
EstimatedCost float64
Turns int
LogsPath string
}

Expand Down Expand Up @@ -343,6 +344,7 @@ func DownloadWorkflowLogs(workflowName string, count int, startDate, endDate, ou
run := result.Run
run.TokenUsage = result.Metrics.TokenUsage
run.EstimatedCost = result.Metrics.EstimatedCost
run.Turns = result.Metrics.Turns
run.LogsPath = result.LogsPath

// Store access analysis for later display (we'll access it via the result)
Expand Down Expand Up @@ -741,6 +743,11 @@ func extractLogMetrics(logDir string, verbose bool) (LogMetrics, error) {
metrics.EstimatedCost += fileMetrics.EstimatedCost
metrics.ErrorCount += fileMetrics.ErrorCount
metrics.WarningCount += fileMetrics.WarningCount
if fileMetrics.Turns > metrics.Turns {
// For turns, take the maximum rather than summing, since turns represent
// the total conversation turns for the entire workflow run
metrics.Turns = fileMetrics.Turns
}
}

return nil
Expand Down Expand Up @@ -858,12 +865,13 @@ func displayLogsOverview(runs []WorkflowRun) {
}

// Prepare table data
headers := []string{"Run ID", "Workflow", "Status", "Duration", "Tokens", "Cost ($)", "Created", "Logs Path"}
headers := []string{"Run ID", "Workflow", "Status", "Duration", "Tokens", "Cost ($)", "Turns", "Created", "Logs Path"}
var rows [][]string

var totalTokens int
var totalCost float64
var totalDuration time.Duration
var totalTurns int

for _, run := range runs {
// Format duration
Expand All @@ -887,6 +895,13 @@ func displayLogsOverview(runs []WorkflowRun) {
totalTokens += run.TokenUsage
}

// Format turns
turnsStr := "N/A"
if run.Turns > 0 {
turnsStr = fmt.Sprintf("%d", run.Turns)
totalTurns += run.Turns
}

// Truncate workflow name if too long
workflowName := run.WorkflowName
if len(workflowName) > 20 {
Expand All @@ -903,6 +918,7 @@ func displayLogsOverview(runs []WorkflowRun) {
durationStr,
tokensStr,
costStr,
turnsStr,
run.CreatedAt.Format("2006-01-02"),
relPath,
}
Expand All @@ -917,6 +933,7 @@ func displayLogsOverview(runs []WorkflowRun) {
formatDuration(totalDuration),
formatNumber(totalTokens),
fmt.Sprintf("%.3f", totalCost),
fmt.Sprintf("%d", totalTurns),
"",
"",
}
Expand Down
Loading