diff --git a/docs/src/content/docs/status.mdx b/docs/src/content/docs/status.mdx index 1371fa1026..334f70bfe8 100644 --- a/docs/src/content/docs/status.mdx +++ b/docs/src/content/docs/status.mdx @@ -15,7 +15,7 @@ Status of all agentic workflows. [Browse source files](https://github.com/github | [Basic Research Agent](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/research.md) | copilot | [![Basic Research Agent](https://github.com/githubnext/gh-aw/actions/workflows/research.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/research.lock.yml) | - | - | | [Blog Auditor](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/blog-auditor.md) | claude | [![Blog Auditor](https://github.com/githubnext/gh-aw/actions/workflows/blog-auditor.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/blog-auditor.lock.yml) | `0 12 * * 3` | - | | [Brave Web Search Agent](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/brave.md) | copilot | [![Brave Web Search Agent](https://github.com/githubnext/gh-aw/actions/workflows/brave.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/brave.lock.yml) | - | `/brave` | -| [Changeset Generator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/changeset.md) | copilot | [![Changeset Generator](https://github.com/githubnext/gh-aw/actions/workflows/changeset.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/changeset.lock.yml) | - | - | +| [Changeset Generator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/changeset.md) | copilot | [![Changeset Generator](https://github.com/githubnext/gh-aw/actions/workflows/changeset.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/changeset.lock.yml) | `0 */2 * * *` | - | | [CI Failure Doctor](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/ci-doctor.md) | copilot | [![CI Failure Doctor](https://github.com/githubnext/gh-aw/actions/workflows/ci-doctor.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/ci-doctor.lock.yml) | - | - | | [CLI Consistency Checker](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/cli-consistency-checker.md) | copilot | [![CLI Consistency Checker](https://github.com/githubnext/gh-aw/actions/workflows/cli-consistency-checker.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/cli-consistency-checker.lock.yml) | `0 13 * * 1-5` | - | | [CLI Version Checker](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/cli-version-checker.md) | copilot | [![CLI Version Checker](https://github.com/githubnext/gh-aw/actions/workflows/cli-version-checker.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/cli-version-checker.lock.yml) | `0 15 * * *` | - | diff --git a/pkg/cli/add_command.go b/pkg/cli/add_command.go index 9966c16716..f528f37260 100644 --- a/pkg/cli/add_command.go +++ b/pkg/cli/add_command.go @@ -1,11 +1,9 @@ package cli import ( - "encoding/json" "fmt" "math/rand" "os" - "os/exec" "path/filepath" "strings" @@ -815,164 +813,6 @@ func compileWorkflowWithTracking(filePath string, verbose bool, engineOverride s return nil } -// ensureCopilotInstructions ensures that .github/instructions/github-agentic-workflows.md contains the copilot instructions -func ensureCopilotInstructions(verbose bool, skipInstructions bool) error { - if skipInstructions { - return nil // Skip writing instructions if flag is set - } - - gitRoot, err := findGitRoot() - if err != nil { - return err // Not in a git repository, skip - } - - copilotDir := filepath.Join(gitRoot, ".github", "instructions") - copilotInstructionsPath := filepath.Join(copilotDir, "github-agentic-workflows.instructions.md") - - // Ensure the .github/instructions directory exists - if err := os.MkdirAll(copilotDir, 0755); err != nil { - return fmt.Errorf("failed to create .github/instructions directory: %w", err) - } - - // Check if the instructions file already exists and matches the template - existingContent := "" - if content, err := os.ReadFile(copilotInstructionsPath); err == nil { - existingContent = string(content) - } - - // Check if content matches our expected template - expectedContent := strings.TrimSpace(copilotInstructionsTemplate) - if strings.TrimSpace(existingContent) == expectedContent { - if verbose { - fmt.Printf("Copilot instructions are up-to-date: %s\n", copilotInstructionsPath) - } - return nil - } - - // Write the copilot instructions file - if err := os.WriteFile(copilotInstructionsPath, []byte(copilotInstructionsTemplate), 0644); err != nil { - return fmt.Errorf("failed to write copilot instructions: %w", err) - } - - if verbose { - if existingContent == "" { - fmt.Printf("Created copilot instructions: %s\n", copilotInstructionsPath) - } else { - fmt.Printf("Updated copilot instructions: %s\n", copilotInstructionsPath) - } - } - - return nil -} - -// ensureAgenticWorkflowPrompt removes the old agentic workflow prompt file if it exists -func ensureAgenticWorkflowPrompt(verbose bool, skipInstructions bool) error { - // 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) -} - -// ensureSharedAgenticWorkflowAgent ensures that .github/agents/create-shared-agentic-workflow.md contains the shared workflow creation agent -func ensureSharedAgenticWorkflowAgent(verbose bool, skipInstructions bool) error { - return ensureAgentFromTemplate("create-shared-agentic-workflow.md", sharedAgenticWorkflowAgentTemplate, verbose, skipInstructions) -} - -// ensureSetupAgenticWorkflowsAgent ensures that .github/agents/setup-agentic-workflows.md contains the setup guide agent -func ensureSetupAgenticWorkflowsAgent(verbose bool, skipInstructions bool) error { - return ensureAgentFromTemplate("setup-agentic-workflows.md", setupAgenticWorkflowsAgentTemplate, verbose, skipInstructions) -} - -// checkCleanWorkingDirectory checks if there are uncommitted changes -func checkCleanWorkingDirectory(verbose bool) error { - if verbose { - fmt.Printf("Checking for uncommitted changes...\n") - } - - cmd := exec.Command("git", "status", "--porcelain") - output, err := cmd.Output() - if err != nil { - return fmt.Errorf("failed to check git status: %w", err) - } - - if len(strings.TrimSpace(string(output))) > 0 { - return fmt.Errorf("working directory has uncommitted changes, please commit or stash them first") - } - - if verbose { - fmt.Printf("Working directory is clean\n") - } - return nil -} - -// createPR creates a pull request using GitHub CLI -func createPR(branchName, title, body string, verbose bool) error { - if verbose { - fmt.Printf("Creating PR: %s\n", title) - } - - // Get the current repository info to ensure PR is created in the correct repo - cmd := exec.Command("gh", "repo", "view", "--json", "owner,name") - repoOutput, err := cmd.Output() - if err != nil { - return fmt.Errorf("failed to get current repository info: %w", err) - } - - var repoInfo struct { - Owner struct { - Login string `json:"login"` - } `json:"owner"` - Name string `json:"name"` - } - - if err := json.Unmarshal(repoOutput, &repoInfo); err != nil { - return fmt.Errorf("failed to parse repository info: %w", err) - } - - repoSpec := fmt.Sprintf("%s/%s", repoInfo.Owner.Login, repoInfo.Name) - - // Explicitly specify the repository to ensure PR is created in the current repo (not upstream) - cmd = exec.Command("gh", "pr", "create", "--repo", repoSpec, "--title", title, "--body", body, "--head", branchName) - output, err := cmd.Output() - if err != nil { - // Try to get stderr for better error reporting - if exitError, ok := err.(*exec.ExitError); ok { - return fmt.Errorf("failed to create PR: %w\nOutput: %s\nError: %s", err, string(output), string(exitError.Stderr)) - } - return fmt.Errorf("failed to create PR: %w", err) - } - - prURL := strings.TrimSpace(string(output)) - fmt.Printf("📢 Pull Request created: %s\n", prURL) - - return nil -} - // addSourceToWorkflow adds the source field to the workflow's frontmatter func addSourceToWorkflow(content, source string) (string, error) { // Use shared frontmatter logic that preserves formatting diff --git a/pkg/cli/copilot-agents.go b/pkg/cli/copilot-agents.go index 081de6fc4b..b5ddc550c1 100644 --- a/pkg/cli/copilot-agents.go +++ b/pkg/cli/copilot-agents.go @@ -56,3 +56,96 @@ func ensureAgentFromTemplate(agentFileName, templateContent string, verbose bool return nil } + +// ensureCopilotInstructions ensures that .github/instructions/github-agentic-workflows.md contains the copilot instructions +func ensureCopilotInstructions(verbose bool, skipInstructions bool) error { + if skipInstructions { + return nil // Skip writing instructions if flag is set + } + + gitRoot, err := findGitRoot() + if err != nil { + return err // Not in a git repository, skip + } + + copilotDir := filepath.Join(gitRoot, ".github", "instructions") + copilotInstructionsPath := filepath.Join(copilotDir, "github-agentic-workflows.instructions.md") + + // Ensure the .github/instructions directory exists + if err := os.MkdirAll(copilotDir, 0755); err != nil { + return fmt.Errorf("failed to create .github/instructions directory: %w", err) + } + + // Check if the instructions file already exists and matches the template + existingContent := "" + if content, err := os.ReadFile(copilotInstructionsPath); err == nil { + existingContent = string(content) + } + + // Check if content matches our expected template + expectedContent := strings.TrimSpace(copilotInstructionsTemplate) + if strings.TrimSpace(existingContent) == expectedContent { + if verbose { + fmt.Printf("Copilot instructions are up-to-date: %s\n", copilotInstructionsPath) + } + return nil + } + + // Write the copilot instructions file + if err := os.WriteFile(copilotInstructionsPath, []byte(copilotInstructionsTemplate), 0644); err != nil { + return fmt.Errorf("failed to write copilot instructions: %w", err) + } + + if verbose { + if existingContent == "" { + fmt.Printf("Created copilot instructions: %s\n", copilotInstructionsPath) + } else { + fmt.Printf("Updated copilot instructions: %s\n", copilotInstructionsPath) + } + } + + return nil +} + +// ensureAgenticWorkflowPrompt removes the old agentic workflow prompt file if it exists +func ensureAgenticWorkflowPrompt(verbose bool, skipInstructions bool) error { + // 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) +} + +// ensureSharedAgenticWorkflowAgent ensures that .github/agents/create-shared-agentic-workflow.md contains the shared workflow creation agent +func ensureSharedAgenticWorkflowAgent(verbose bool, skipInstructions bool) error { + return ensureAgentFromTemplate("create-shared-agentic-workflow.md", sharedAgenticWorkflowAgentTemplate, verbose, skipInstructions) +} + +// ensureSetupAgenticWorkflowsAgent ensures that .github/agents/setup-agentic-workflows.md contains the setup guide agent +func ensureSetupAgenticWorkflowsAgent(verbose bool, skipInstructions bool) error { + return ensureAgentFromTemplate("setup-agentic-workflows.md", setupAgenticWorkflowsAgentTemplate, verbose, skipInstructions) +} diff --git a/pkg/cli/git.go b/pkg/cli/git.go index c33efbd36a..79967997a2 100644 --- a/pkg/cli/git.go +++ b/pkg/cli/git.go @@ -188,3 +188,25 @@ func pushBranch(branchName string, verbose bool) error { return nil } + +// checkCleanWorkingDirectory checks if there are uncommitted changes +func checkCleanWorkingDirectory(verbose bool) error { + if verbose { + fmt.Printf("Checking for uncommitted changes...\n") + } + + cmd := exec.Command("git", "status", "--porcelain") + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("failed to check git status: %w", err) + } + + if len(strings.TrimSpace(string(output))) > 0 { + return fmt.Errorf("working directory has uncommitted changes, please commit or stash them first") + } + + if verbose { + fmt.Printf("Working directory is clean\n") + } + return nil +} diff --git a/pkg/cli/pr_command.go b/pkg/cli/pr_command.go index 415edd9e20..fc72f371e6 100644 --- a/pkg/cli/pr_command.go +++ b/pkg/cli/pr_command.go @@ -739,3 +739,46 @@ func transferPR(prURL, targetRepo string, verbose bool) error { return nil } + +// createPR creates a pull request using GitHub CLI +func createPR(branchName, title, body string, verbose bool) error { + if verbose { + fmt.Printf("Creating PR: %s\n", title) + } + + // Get the current repository info to ensure PR is created in the correct repo + cmd := exec.Command("gh", "repo", "view", "--json", "owner,name") + repoOutput, err := cmd.Output() + if err != nil { + return fmt.Errorf("failed to get current repository info: %w", err) + } + + var repoInfo struct { + Owner struct { + Login string `json:"login"` + } `json:"owner"` + Name string `json:"name"` + } + + if err := json.Unmarshal(repoOutput, &repoInfo); err != nil { + return fmt.Errorf("failed to parse repository info: %w", err) + } + + repoSpec := fmt.Sprintf("%s/%s", repoInfo.Owner.Login, repoInfo.Name) + + // Explicitly specify the repository to ensure PR is created in the current repo (not upstream) + cmd = exec.Command("gh", "pr", "create", "--repo", repoSpec, "--title", title, "--body", body, "--head", branchName) + output, err := cmd.Output() + if err != nil { + // Try to get stderr for better error reporting + if exitError, ok := err.(*exec.ExitError); ok { + return fmt.Errorf("failed to create PR: %w\nOutput: %s\nError: %s", err, string(output), string(exitError.Stderr)) + } + return fmt.Errorf("failed to create PR: %w", err) + } + + prURL := strings.TrimSpace(string(output)) + fmt.Printf("📢 Pull Request created: %s\n", prURL) + + return nil +}