diff --git a/.github/prompts/create-agentic-workflow.prompt.md b/.github/agents/create-agentic-workflow.md similarity index 96% rename from .github/prompts/create-agentic-workflow.prompt.md rename to .github/agents/create-agentic-workflow.md index f2aff576e7..12bec76552 100644 --- a/.github/prompts/create-agentic-workflow.prompt.md +++ b/.github/agents/create-agentic-workflow.md @@ -1,7 +1,15 @@ --- +name: create-agentic-workflow description: Design agentic workflows using GitHub Agentic Workflows (gh-aw) extension with interactive guidance on triggers, tools, and security best practices. -tools: ['runInTerminal', 'getTerminalOutput', 'createFile', 'createDirectory', 'editFiles', 'search', 'changes', 'githubRepo'] -model: GPT-5 +tools: + - runInTerminal + - getTerminalOutput + - createFile + - createDirectory + - editFiles + - search + - changes + - githubRepo --- This file will configure the agent into a mode to create agentic workflows. Read the ENTIRE content of this file carefully before proceeding. Follow the instructions precisely. @@ -130,4 +138,4 @@ DO NOT ask all these questions at once; instead, engage in a back-and-forth conv - Use the `gh aw compile --strict` command to validate syntax. - Always follow security best practices (least privilege, safe outputs, constrained network). - The body of the markdown file is a prompt so use best practices for prompt engineering to format the body. -- skip the summary at the end, keep it short. \ No newline at end of file +- skip the summary at the end, keep it short. diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 07e4b38944..0c8bd30d6b 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -846,7 +846,7 @@ jobs: - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 with: cache: npm cache-dependency-path: docs/package-lock.json diff --git a/Makefile b/Makefile index 569f2dc4b3..e47e897c5c 100644 --- a/Makefile +++ b/Makefile @@ -204,7 +204,7 @@ sync-templates: @echo "Syncing templates from .github to pkg/cli/templates..." @mkdir -p pkg/cli/templates @cp .github/instructions/github-agentic-workflows.instructions.md pkg/cli/templates/ - @cp .github/prompts/create-agentic-workflow.prompt.md pkg/cli/templates/ + @cp .github/agents/create-agentic-workflow.md pkg/cli/templates/ @cp .github/prompts/create-shared-agentic-workflow.prompt.md pkg/cli/templates/ @cp .github/prompts/setup-agentic-workflows.prompt.md pkg/cli/templates/ @echo "✓ Templates synced successfully" diff --git a/pkg/cli/add_command.go b/pkg/cli/add_command.go index 138ed3890f..bc05c07819 100644 --- a/pkg/cli/add_command.go +++ b/pkg/cli/add_command.go @@ -671,9 +671,37 @@ func ensureCopilotInstructions(verbose bool, skipInstructions bool) error { return nil } -// ensureAgenticWorkflowPrompt ensures that .github/prompts/create-agentic-workflow.prompt.md contains the agentic workflow creation prompt +// ensureAgenticWorkflowPrompt removes the old agentic workflow prompt file if it exists func ensureAgenticWorkflowPrompt(verbose bool, skipInstructions bool) error { - return ensurePromptFromTemplate("create-agentic-workflow.prompt.md", agenticWorkflowPromptTemplate, verbose, skipInstructions) + // This function now removes the old prompt file since we've migrated to agent format + if skipInstructions { + return nil + } + + gitRoot, err := findGitRoot() + if err != nil { + return err // Not in a git repository, skip + } + + promptsDir := filepath.Join(gitRoot, ".github", "prompts") + oldPromptPath := filepath.Join(promptsDir, "create-agentic-workflow.prompt.md") + + // Check if the old prompt file exists and remove it + if _, err := os.Stat(oldPromptPath); err == nil { + if err := os.Remove(oldPromptPath); err != nil { + return fmt.Errorf("failed to remove old prompt file: %w", err) + } + if verbose { + fmt.Printf("Removed old prompt file: %s\n", oldPromptPath) + } + } + + return nil +} + +// ensureAgenticWorkflowAgent ensures that .github/agents/create-agentic-workflow.md contains the agentic workflow creation agent +func ensureAgenticWorkflowAgent(verbose bool, skipInstructions bool) error { + return ensureAgentFromTemplate("create-agentic-workflow.md", agenticWorkflowAgentTemplate, verbose, skipInstructions) } // ensureSharedAgenticWorkflowPrompt ensures that .github/prompts/create-shared-agentic-workflow.prompt.md contains the shared workflow creation prompt diff --git a/pkg/cli/agentic_workflow_prompt_test.go b/pkg/cli/agentic_workflow_prompt_test.go index 32f5f71543..03d6cdbbe4 100644 --- a/pkg/cli/agentic_workflow_prompt_test.go +++ b/pkg/cli/agentic_workflow_prompt_test.go @@ -8,26 +8,26 @@ import ( "testing" ) -func TestEnsureAgenticWorkflowPrompt(t *testing.T) { +func TestEnsureAgenticWorkflowAgent(t *testing.T) { tests := []struct { name string existingContent string expectedContent string }{ { - name: "creates new agentic workflow prompt file", + name: "creates new agentic workflow agent file", existingContent: "", - expectedContent: strings.TrimSpace(agenticWorkflowPromptTemplate), + expectedContent: strings.TrimSpace(agenticWorkflowAgentTemplate), }, { name: "does not modify existing correct file", - existingContent: agenticWorkflowPromptTemplate, - expectedContent: strings.TrimSpace(agenticWorkflowPromptTemplate), + existingContent: agenticWorkflowAgentTemplate, + expectedContent: strings.TrimSpace(agenticWorkflowAgentTemplate), }, { name: "updates modified file", - existingContent: "# Modified Agentic Workflow Prompt\n\nThis is a modified version.", - expectedContent: strings.TrimSpace(agenticWorkflowPromptTemplate), + existingContent: "# Modified Agentic Workflow Agent\n\nThis is a modified version.", + expectedContent: strings.TrimSpace(agenticWorkflowAgentTemplate), }, } @@ -51,34 +51,34 @@ func TestEnsureAgenticWorkflowPrompt(t *testing.T) { t.Fatalf("Failed to init git repo: %v", err) } - promptsDir := filepath.Join(tempDir, ".github", "prompts") - agenticWorkflowPromptPath := filepath.Join(promptsDir, "create-agentic-workflow.prompt.md") + agentsDir := filepath.Join(tempDir, ".github", "agents") + agenticWorkflowAgentPath := filepath.Join(agentsDir, "create-agentic-workflow.md") // Create initial content if specified if tt.existingContent != "" { - if err := os.MkdirAll(promptsDir, 0755); err != nil { - t.Fatalf("Failed to create prompts directory: %v", err) + if err := os.MkdirAll(agentsDir, 0755); err != nil { + t.Fatalf("Failed to create agents directory: %v", err) } - if err := os.WriteFile(agenticWorkflowPromptPath, []byte(tt.existingContent), 0644); err != nil { - t.Fatalf("Failed to create initial agentic workflow prompt: %v", err) + if err := os.WriteFile(agenticWorkflowAgentPath, []byte(tt.existingContent), 0644); err != nil { + t.Fatalf("Failed to create initial agentic workflow agent: %v", err) } } // Call the function with skipInstructions=false to test the functionality - err = ensureAgenticWorkflowPrompt(false, false) + err = ensureAgenticWorkflowAgent(false, false) if err != nil { - t.Fatalf("ensureAgenticWorkflowPrompt() returned error: %v", err) + t.Fatalf("ensureAgenticWorkflowAgent() returned error: %v", err) } // Check that file exists - if _, err := os.Stat(agenticWorkflowPromptPath); os.IsNotExist(err) { - t.Fatalf("Expected agentic workflow prompt file to exist") + if _, err := os.Stat(agenticWorkflowAgentPath); os.IsNotExist(err) { + t.Fatalf("Expected agentic workflow agent file to exist") } // Check content - content, err := os.ReadFile(agenticWorkflowPromptPath) + content, err := os.ReadFile(agenticWorkflowAgentPath) if err != nil { - t.Fatalf("Failed to read agentic workflow prompt: %v", err) + t.Fatalf("Failed to read agentic workflow agent: %v", err) } contentStr := strings.TrimSpace(string(content)) @@ -93,7 +93,7 @@ func TestEnsureAgenticWorkflowPrompt(t *testing.T) { } } -func TestEnsureAgenticWorkflowPrompt_WithSkipInstructionsTrue(t *testing.T) { +func TestEnsureAgenticWorkflowAgent_WithSkipInstructionsTrue(t *testing.T) { // Create a temporary directory for testing tempDir := t.TempDir() @@ -113,15 +113,62 @@ func TestEnsureAgenticWorkflowPrompt_WithSkipInstructionsTrue(t *testing.T) { } // Call the function with skipInstructions=true - err = ensureAgenticWorkflowPrompt(false, true) + err = ensureAgenticWorkflowAgent(false, true) if err != nil { - t.Fatalf("ensureAgenticWorkflowPrompt() returned error: %v", err) + t.Fatalf("ensureAgenticWorkflowAgent() returned error: %v", err) } // Check that file was NOT created + agentsDir := filepath.Join(tempDir, ".github", "agents") + agenticWorkflowAgentPath := filepath.Join(agentsDir, "create-agentic-workflow.md") + if _, err := os.Stat(agenticWorkflowAgentPath); !os.IsNotExist(err) { + t.Fatalf("Expected agentic workflow agent file to NOT exist when skipInstructions=true") + } +} + +func TestEnsureAgenticWorkflowPrompt_RemovesOldFile(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + + // Change to temp directory and initialize git repo for findGitRoot to work + oldWd, _ := os.Getwd() + defer func() { + _ = os.Chdir(oldWd) + }() + err := os.Chdir(tempDir) + if err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + // Initialize git repo + if err := exec.Command("git", "init").Run(); err != nil { + t.Fatalf("Failed to init git repo: %v", err) + } + + // Create the old prompt file promptsDir := filepath.Join(tempDir, ".github", "prompts") - agenticWorkflowPromptPath := filepath.Join(promptsDir, "create-agentic-workflow.prompt.md") - if _, err := os.Stat(agenticWorkflowPromptPath); !os.IsNotExist(err) { - t.Fatalf("Expected agentic workflow prompt file to NOT exist when skipInstructions=true") + oldPromptPath := filepath.Join(promptsDir, "create-agentic-workflow.prompt.md") + + if err := os.MkdirAll(promptsDir, 0755); err != nil { + t.Fatalf("Failed to create prompts directory: %v", err) + } + if err := os.WriteFile(oldPromptPath, []byte("old content"), 0644); err != nil { + t.Fatalf("Failed to create old prompt file: %v", err) + } + + // Verify old file exists + if _, err := os.Stat(oldPromptPath); os.IsNotExist(err) { + t.Fatalf("Old prompt file should exist before test") + } + + // Call the function to remove old prompt + err = ensureAgenticWorkflowPrompt(false, false) + if err != nil { + t.Fatalf("ensureAgenticWorkflowPrompt() returned error: %v", err) + } + + // Check that old file was removed + if _, err := os.Stat(oldPromptPath); !os.IsNotExist(err) { + t.Fatalf("Expected old prompt file to be removed") } } diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index e67d2248b1..51a472f2ee 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -22,8 +22,8 @@ var ( //go:embed templates/github-agentic-workflows.instructions.md var copilotInstructionsTemplate string -//go:embed templates/create-agentic-workflow.prompt.md -var agenticWorkflowPromptTemplate string +//go:embed templates/create-agentic-workflow.md +var agenticWorkflowAgentTemplate string //go:embed templates/create-shared-agentic-workflow.prompt.md var sharedAgenticWorkflowPromptTemplate string diff --git a/pkg/cli/compile_instructions_test.go b/pkg/cli/compile_instructions_test.go index e313afb121..ad1953ee35 100644 --- a/pkg/cli/compile_instructions_test.go +++ b/pkg/cli/compile_instructions_test.go @@ -67,7 +67,7 @@ This is a test workflow for compilation. // Define paths for instruction files copilotInstructionsPath := filepath.Join(tempDir, ".github", "instructions", "github-agentic-workflows.instructions.md") - agenticWorkflowPromptPath := filepath.Join(tempDir, ".github", "prompts", "create-agentic-workflow.prompt.md") + agenticWorkflowAgentPath := filepath.Join(tempDir, ".github", "agents", "create-agentic-workflow.md") sharedAgenticWorkflowPromptPath := filepath.Join(tempDir, ".github", "prompts", "create-shared-agentic-workflow.prompt.md") // Compile the workflow @@ -102,12 +102,12 @@ This is a test workflow for compilation. t.Errorf("Expected copilot instructions file NOT to exist, but it was created at %s", copilotInstructionsPath) } - if _, err := os.Stat(agenticWorkflowPromptPath); !os.IsNotExist(err) { - t.Errorf("Expected agentic workflow prompt file NOT to exist, but it was created at %s", agenticWorkflowPromptPath) + if _, err := os.Stat(agenticWorkflowAgentPath); !os.IsNotExist(err) { + t.Errorf("Expected agentic workflow agent file NOT to exist, but it was created at %s", agenticWorkflowAgentPath) } if _, err := os.Stat(sharedAgenticWorkflowPromptPath); !os.IsNotExist(err) { - t.Errorf("Expected shared agentic workflow prompt file NOT to exist, but it was created at %s", sharedAgenticWorkflowPromptPath) + t.Errorf("Expected shared agentic workflow agent file NOT to exist, but it was created at %s", sharedAgenticWorkflowPromptPath) } } @@ -171,7 +171,7 @@ This is a test workflow for compilation. // Define paths for instruction files copilotInstructionsPath := filepath.Join(tempDir, ".github", "instructions", "github-agentic-workflows.instructions.md") - agenticWorkflowPromptPath := filepath.Join(tempDir, ".github", "prompts", "create-agentic-workflow.prompt.md") + agenticWorkflowAgentPath := filepath.Join(tempDir, ".github", "agents", "create-agentic-workflow.md") sharedAgenticWorkflowPromptPath := filepath.Join(tempDir, ".github", "prompts", "create-shared-agentic-workflow.prompt.md") // Compile all workflows (no specific files) @@ -207,11 +207,11 @@ This is a test workflow for compilation. t.Errorf("Expected copilot instructions file NOT to exist, but it was created at %s", copilotInstructionsPath) } - if _, err := os.Stat(agenticWorkflowPromptPath); !os.IsNotExist(err) { - t.Errorf("Expected agentic workflow prompt file NOT to exist, but it was created at %s", agenticWorkflowPromptPath) + if _, err := os.Stat(agenticWorkflowAgentPath); !os.IsNotExist(err) { + t.Errorf("Expected agentic workflow agent file NOT to exist, but it was created at %s", agenticWorkflowAgentPath) } if _, err := os.Stat(sharedAgenticWorkflowPromptPath); !os.IsNotExist(err) { - t.Errorf("Expected shared agentic workflow prompt file NOT to exist, but it was created at %s", sharedAgenticWorkflowPromptPath) + t.Errorf("Expected shared agentic workflow agent file NOT to exist, but it was created at %s", sharedAgenticWorkflowPromptPath) } } diff --git a/pkg/cli/copilot-prompts.go b/pkg/cli/copilot-prompts.go index 61fd87ae1a..5ad7df1a6f 100644 --- a/pkg/cli/copilot-prompts.go +++ b/pkg/cli/copilot-prompts.go @@ -56,3 +56,53 @@ func ensurePromptFromTemplate(promptFileName, templateContent string, verbose bo return nil } + +// ensureAgentFromTemplate ensures that an agent file exists and matches the embedded template +func ensureAgentFromTemplate(agentFileName, templateContent string, verbose bool, skipInstructions bool) error { + if skipInstructions { + return nil // Skip writing agent if flag is set + } + + gitRoot, err := findGitRoot() + if err != nil { + return err // Not in a git repository, skip + } + + agentsDir := filepath.Join(gitRoot, ".github", "agents") + agentPath := filepath.Join(agentsDir, agentFileName) + + // Ensure the .github/agents directory exists + if err := os.MkdirAll(agentsDir, 0755); err != nil { + return fmt.Errorf("failed to create .github/agents directory: %w", err) + } + + // Check if the agent file already exists and matches the template + existingContent := "" + if content, err := os.ReadFile(agentPath); err == nil { + existingContent = string(content) + } + + // Check if content matches our expected template + expectedContent := strings.TrimSpace(templateContent) + if strings.TrimSpace(existingContent) == expectedContent { + if verbose { + fmt.Printf("Agent is up-to-date: %s\n", agentPath) + } + return nil + } + + // Write the agent file + if err := os.WriteFile(agentPath, []byte(templateContent), 0644); err != nil { + return fmt.Errorf("failed to write agent file: %w", err) + } + + if verbose { + if existingContent == "" { + fmt.Printf("Created agent: %s\n", agentPath) + } else { + fmt.Printf("Updated agent: %s\n", agentPath) + } + } + + return nil +} diff --git a/pkg/cli/init.go b/pkg/cli/init.go index ae81b32cf0..fe4053007b 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -42,14 +42,21 @@ func InitRepository(verbose bool, mcp bool) error { fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created GitHub Copilot instructions")) } - // Write agentic workflow prompt - initLog.Print("Writing agentic workflow prompt") + // Remove old agentic workflow prompt if it exists + initLog.Print("Removing old agentic workflow prompt") if err := ensureAgenticWorkflowPrompt(verbose, false); err != nil { - initLog.Printf("Failed to write agentic workflow prompt: %v", err) - return fmt.Errorf("failed to write agentic workflow prompt: %w", err) + initLog.Printf("Failed to remove old agentic workflow prompt: %v", err) + return fmt.Errorf("failed to remove old agentic workflow prompt: %w", err) + } + + // Write agentic workflow agent + initLog.Print("Writing agentic workflow agent") + if err := ensureAgenticWorkflowAgent(verbose, false); err != nil { + initLog.Printf("Failed to write agentic workflow agent: %v", err) + return fmt.Errorf("failed to write agentic workflow agent: %w", err) } if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created /create-agentic-workflow command")) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created custom agent for workflow creation")) } // Write shared agentic workflow prompt @@ -105,9 +112,9 @@ func InitRepository(verbose bool, mcp bool) error { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("✓ GitHub Copilot Agent MCP integration configured")) fmt.Fprintln(os.Stderr, "") } - fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Start a chat and copy the following prompt to create a new workflow:")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Start a chat and use the custom agent to create a new workflow:")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, " activate @.github/prompts/create-agentic-workflow.prompt.md") + fmt.Fprintln(os.Stderr, " @.github/agents/create-agentic-workflow.md") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Or add workflows from the catalog: "+constants.CLIExtensionPrefix+" add ")) fmt.Fprintln(os.Stderr, "") diff --git a/pkg/cli/init_command.go b/pkg/cli/init_command.go index 5422f51ff4..71b0298674 100644 --- a/pkg/cli/init_command.go +++ b/pkg/cli/init_command.go @@ -19,14 +19,15 @@ func NewInitCommand() *cobra.Command { This command: - Configures .gitattributes to mark .lock.yml files as generated - Creates GitHub Copilot custom instructions at .github/instructions/github-agentic-workflows.instructions.md -- Creates the /create-agentic-workflow prompt at .github/prompts/create-agentic-workflow.prompt.md +- Creates the custom agent for workflow creation at .github/agents/create-agentic-workflow.md +- Removes the old /create-agentic-workflow prompt if it exists With --mcp flag: - Creates .github/workflows/copilot-setup-steps.yml with gh-aw installation steps - Creates .vscode/mcp.json with gh-aw MCP server configuration After running this command, you can: -- Use GitHub Copilot Chat with /create-agentic-workflow to create workflows interactively +- Use GitHub Copilot Chat with @.github/agents/create-agentic-workflow.md to create workflows interactively - Add workflows from the catalog with: ` + constants.CLIExtensionPrefix + ` add - Create new workflows from scratch with: ` + constants.CLIExtensionPrefix + ` new diff --git a/pkg/cli/init_test.go b/pkg/cli/init_test.go index 1c8a1a5420..0e6aecfd83 100644 --- a/pkg/cli/init_test.go +++ b/pkg/cli/init_test.go @@ -78,10 +78,10 @@ func TestInitRepository(t *testing.T) { t.Errorf("Expected copilot instructions file to exist") } - // Verify agentic workflow prompt was created - agenticWorkflowPromptPath := filepath.Join(tempDir, ".github", "prompts", "create-agentic-workflow.prompt.md") - if _, err := os.Stat(agenticWorkflowPromptPath); os.IsNotExist(err) { - t.Errorf("Expected agentic workflow prompt file to exist") + // Verify agentic workflow agent was created + agenticWorkflowAgentPath := filepath.Join(tempDir, ".github", "agents", "create-agentic-workflow.md") + if _, err := os.Stat(agenticWorkflowAgentPath); os.IsNotExist(err) { + t.Errorf("Expected agentic workflow agent file to exist") } // Verify .gitattributes contains the correct entry @@ -141,9 +141,9 @@ func TestInitRepository_Idempotent(t *testing.T) { t.Errorf("Expected copilot instructions file to exist after second call") } - agenticWorkflowPromptPath := filepath.Join(tempDir, ".github", "prompts", "create-agentic-workflow.prompt.md") - if _, err := os.Stat(agenticWorkflowPromptPath); os.IsNotExist(err) { - t.Errorf("Expected agentic workflow prompt file to exist after second call") + agenticWorkflowAgentPath := filepath.Join(tempDir, ".github", "agents", "create-agentic-workflow.md") + if _, err := os.Stat(agenticWorkflowAgentPath); os.IsNotExist(err) { + t.Errorf("Expected agentic workflow agent file to exist after second call") } } diff --git a/pkg/cli/templates/create-agentic-workflow.prompt.md b/pkg/cli/templates/create-agentic-workflow.md similarity index 96% rename from pkg/cli/templates/create-agentic-workflow.prompt.md rename to pkg/cli/templates/create-agentic-workflow.md index f2aff576e7..12bec76552 100644 --- a/pkg/cli/templates/create-agentic-workflow.prompt.md +++ b/pkg/cli/templates/create-agentic-workflow.md @@ -1,7 +1,15 @@ --- +name: create-agentic-workflow description: Design agentic workflows using GitHub Agentic Workflows (gh-aw) extension with interactive guidance on triggers, tools, and security best practices. -tools: ['runInTerminal', 'getTerminalOutput', 'createFile', 'createDirectory', 'editFiles', 'search', 'changes', 'githubRepo'] -model: GPT-5 +tools: + - runInTerminal + - getTerminalOutput + - createFile + - createDirectory + - editFiles + - search + - changes + - githubRepo --- This file will configure the agent into a mode to create agentic workflows. Read the ENTIRE content of this file carefully before proceeding. Follow the instructions precisely. @@ -130,4 +138,4 @@ DO NOT ask all these questions at once; instead, engage in a back-and-forth conv - Use the `gh aw compile --strict` command to validate syntax. - Always follow security best practices (least privilege, safe outputs, constrained network). - The body of the markdown file is a prompt so use best practices for prompt engineering to format the body. -- skip the summary at the end, keep it short. \ No newline at end of file +- skip the summary at the end, keep it short.