diff --git a/pkg/cli/add_command.go b/pkg/cli/add_command.go index 0ee137003db..b4da84b96af 100644 --- a/pkg/cli/add_command.go +++ b/pkg/cli/add_command.go @@ -45,6 +45,7 @@ The --force flag overwrites existing workflow files.`, nameFlag, _ := cmd.Flags().GetString("name") prFlag, _ := cmd.Flags().GetBool("pr") forceFlag, _ := cmd.Flags().GetBool("force") + appendText, _ := cmd.Flags().GetString("append") verbose, _ := cmd.Flags().GetBool("verbose") // If no arguments provided and not in CI, automatically use interactive mode @@ -66,12 +67,12 @@ The --force flag overwrites existing workflow files.`, // Handle normal mode if prFlag { - if err := AddWorkflows(workflows, numberFlag, verbose, engineOverride, nameFlag, forceFlag, true); err != nil { + if err := AddWorkflows(workflows, numberFlag, verbose, engineOverride, nameFlag, forceFlag, appendText, true); err != nil { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error())) os.Exit(1) } } else { - if err := AddWorkflows(workflows, numberFlag, verbose, engineOverride, nameFlag, forceFlag, false); err != nil { + if err := AddWorkflows(workflows, numberFlag, verbose, engineOverride, nameFlag, forceFlag, appendText, false); err != nil { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error())) os.Exit(1) } @@ -97,12 +98,15 @@ The --force flag overwrites existing workflow files.`, // Add force flag to add command cmd.Flags().Bool("force", false, "Overwrite existing workflow files") + // Add append flag to add command + cmd.Flags().String("append", "", "Append extra content to the end of agentic workflow on installation") + return cmd } // AddWorkflows adds one or more workflows from components to .github/workflows // with optional repository installation and PR creation -func AddWorkflows(workflows []string, number int, verbose bool, engineOverride string, name string, force bool, createPR bool) error { +func AddWorkflows(workflows []string, number int, verbose bool, engineOverride string, name string, force bool, appendText string, createPR bool) error { if len(workflows) == 0 { return fmt.Errorf("at least one workflow name is required") } @@ -170,15 +174,15 @@ func AddWorkflows(workflows []string, number int, verbose bool, engineOverride s // Handle PR creation workflow if createPR { - return addWorkflowsWithPR(processedWorkflows, number, verbose, engineOverride, name, force) + return addWorkflowsWithPR(processedWorkflows, number, verbose, engineOverride, name, force, appendText) } // Handle normal workflow addition - return addWorkflowsNormal(processedWorkflows, number, verbose, engineOverride, name, force) + return addWorkflowsNormal(processedWorkflows, number, verbose, engineOverride, name, force, appendText) } // addWorkflowsNormal handles normal workflow addition without PR creation -func addWorkflowsNormal(workflows []*WorkflowSpec, number int, verbose bool, engineOverride string, name string, force bool) error { +func addWorkflowsNormal(workflows []*WorkflowSpec, number int, verbose bool, engineOverride string, name string, force bool, appendText string) error { // Create file tracker for all operations tracker, err := NewFileTracker() if err != nil { @@ -205,7 +209,7 @@ func addWorkflowsNormal(workflows []*WorkflowSpec, number int, verbose bool, eng currentName = name } - if err := addWorkflowWithTracking(workflow, number, verbose, engineOverride, currentName, force, tracker); err != nil { + if err := addWorkflowWithTracking(workflow, number, verbose, engineOverride, currentName, force, appendText, tracker); err != nil { return fmt.Errorf("failed to add workflow '%s': %w", workflow.String(), err) } } @@ -218,7 +222,7 @@ func addWorkflowsNormal(workflows []*WorkflowSpec, number int, verbose bool, eng } // addWorkflowsWithPR handles workflow addition with PR creation -func addWorkflowsWithPR(workflows []*WorkflowSpec, number int, verbose bool, engineOverride string, name string, force bool) error { +func addWorkflowsWithPR(workflows []*WorkflowSpec, number int, verbose bool, engineOverride string, name string, force bool, appendText string) error { // Get current branch for restoration later currentBranch, err := getCurrentBranch() if err != nil { @@ -247,7 +251,7 @@ func addWorkflowsWithPR(workflows []*WorkflowSpec, number int, verbose bool, eng }() // Add workflows using the normal function logic - if err := addWorkflowsNormal(workflows, number, verbose, engineOverride, name, force); err != nil { + if err := addWorkflowsNormal(workflows, number, verbose, engineOverride, name, force, appendText); err != nil { // Rollback on error if rollbackErr := tracker.RollbackAllFiles(verbose); rollbackErr != nil && verbose { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to rollback files: %v", rollbackErr))) @@ -326,7 +330,7 @@ func addWorkflowsWithPR(workflows []*WorkflowSpec, number int, verbose bool, eng } // addWorkflowWithTracking adds a workflow from components to .github/workflows with file tracking -func addWorkflowWithTracking(workflow *WorkflowSpec, number int, verbose bool, engineOverride string, name string, force bool, tracker *FileTracker) error { +func addWorkflowWithTracking(workflow *WorkflowSpec, number int, verbose bool, engineOverride string, name string, force bool, appendText string, tracker *FileTracker) error { if verbose { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Adding workflow: %s", workflow.String()))) fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Number of copies: %d", number))) @@ -472,6 +476,15 @@ func addWorkflowWithTracking(workflow *WorkflowSpec, number int, verbose bool, e } } + // Append text if provided + if appendText != "" { + // Ensure we have a newline before appending + if !strings.HasSuffix(content, "\n") { + content += "\n" + } + content += "\n" + appendText + } + // Track the file based on whether it existed before (if tracker is available) if tracker != nil { if fileExists { diff --git a/pkg/cli/local_workflow_trial_test.go b/pkg/cli/local_workflow_trial_test.go index 413ae06c425..329b687c0a3 100644 --- a/pkg/cli/local_workflow_trial_test.go +++ b/pkg/cli/local_workflow_trial_test.go @@ -60,7 +60,7 @@ This is a test workflow. } // Test the local installation function - err = installLocalWorkflowInTrialMode(originalDir, tempDir, spec, false) + err = installLocalWorkflowInTrialMode(originalDir, tempDir, spec, "", false) if err != nil { t.Fatalf("Failed to install local workflow: %v", err) } diff --git a/pkg/cli/trial_command.go b/pkg/cli/trial_command.go index 35056bfd80b..ffeb495283a 100644 --- a/pkg/cli/trial_command.go +++ b/pkg/cli/trial_command.go @@ -109,6 +109,7 @@ Trial results are saved both locally (in trials/ directory) and in the host repo repeatCount, _ := cmd.Flags().GetInt("repeat") autoMergePRs, _ := cmd.Flags().GetBool("auto-merge-prs") engineOverride, _ := cmd.Flags().GetString("engine") + appendText, _ := cmd.Flags().GetString("append") verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose") if err := validateEngine(engineOverride); err != nil { @@ -116,7 +117,7 @@ Trial results are saved both locally (in trials/ directory) and in the host repo os.Exit(1) } - if err := RunWorkflowTrials(workflowSpecs, logicalRepoSpec, cloneRepoSpec, hostRepoSpec, deleteHostRepo, forceDeleteHostRepo, yes, timeout, triggerContext, repeatCount, autoMergePRs, engineOverride, verbose); err != nil { + if err := RunWorkflowTrials(workflowSpecs, logicalRepoSpec, cloneRepoSpec, hostRepoSpec, deleteHostRepo, forceDeleteHostRepo, yes, timeout, triggerContext, repeatCount, autoMergePRs, engineOverride, appendText, verbose); err != nil { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error())) os.Exit(1) } @@ -143,12 +144,13 @@ Trial results are saved both locally (in trials/ directory) and in the host repo cmd.Flags().Int("repeat", 0, "Number of times to repeat running workflows (0 = run once)") cmd.Flags().Bool("auto-merge-prs", false, "Auto-merge any pull requests created during the trial (requires --clone-repo)") cmd.Flags().StringP("engine", "a", "", "Override AI engine (claude, codex, copilot, custom)") + cmd.Flags().String("append", "", "Append extra content to the end of agentic workflow on installation") return cmd } // RunWorkflowTrials executes the main logic for trialing one or more workflows -func RunWorkflowTrials(workflowSpecs []string, logicalRepoSpec string, cloneRepoSpec string, hostRepoSpec string, deleteHostRepo, forceDeleteHostRepo, quiet bool, timeoutMinutes int, triggerContext string, repeatCount int, autoMergePRs bool, engineOverride string, verbose bool) error { +func RunWorkflowTrials(workflowSpecs []string, logicalRepoSpec string, cloneRepoSpec string, hostRepoSpec string, deleteHostRepo, forceDeleteHostRepo, quiet bool, timeoutMinutes int, triggerContext string, repeatCount int, autoMergePRs bool, engineOverride string, appendText string, verbose bool) error { // Parse all workflow specifications var parsedSpecs []*WorkflowSpec for _, spec := range workflowSpecs { @@ -296,7 +298,7 @@ func RunWorkflowTrials(workflowSpecs []string, logicalRepoSpec string, cloneRepo fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("=== Running trial for workflow: %s ===", parsedSpec.WorkflowName))) // Install workflow with trial mode compilation - if err := installWorkflowInTrialMode(tempDir, parsedSpec, logicalRepoSlug, cloneRepoSlug, hostRepoSlug, secretTracker, engineOverride, verbose); err != nil { + if err := installWorkflowInTrialMode(tempDir, parsedSpec, logicalRepoSlug, cloneRepoSlug, hostRepoSlug, secretTracker, engineOverride, appendText, verbose); err != nil { return fmt.Errorf("failed to install workflow '%s' in trial mode: %w", parsedSpec.WorkflowName, err) } @@ -699,7 +701,7 @@ func cloneTrialHostRepository(repoSlug string, verbose bool) (string, error) { } // installWorkflowInTrialMode installs a workflow in trial mode using a parsed spec -func installWorkflowInTrialMode(tempDir string, parsedSpec *WorkflowSpec, logicalRepoSlug, cloneRepoSlug, hostRepoSlug string, secretTracker *TrialSecretTracker, engineOverride string, verbose bool) error { +func installWorkflowInTrialMode(tempDir string, parsedSpec *WorkflowSpec, logicalRepoSlug, cloneRepoSlug, hostRepoSlug string, secretTracker *TrialSecretTracker, engineOverride string, appendText string, verbose bool) error { // Change to temp directory originalDir, err := os.Getwd() if err != nil { @@ -718,7 +720,7 @@ func installWorkflowInTrialMode(tempDir string, parsedSpec *WorkflowSpec, logica } // For local workflows, copy the file directly from the filesystem - if err := installLocalWorkflowInTrialMode(originalDir, tempDir, parsedSpec, verbose); err != nil { + if err := installLocalWorkflowInTrialMode(originalDir, tempDir, parsedSpec, appendText, verbose); err != nil { return fmt.Errorf("failed to install local workflow: %w", err) } } else { @@ -732,7 +734,7 @@ func installWorkflowInTrialMode(tempDir string, parsedSpec *WorkflowSpec, logica } // Add the workflow from the installed package - if err := AddWorkflows([]string{parsedSpec.String()}, 1, verbose, "", "", true, false); err != nil { + if err := AddWorkflows([]string{parsedSpec.String()}, 1, verbose, "", "", true, appendText, false); err != nil { return fmt.Errorf("failed to add workflow: %w", err) } } @@ -779,7 +781,7 @@ func installWorkflowInTrialMode(tempDir string, parsedSpec *WorkflowSpec, logica } // installLocalWorkflowInTrialMode installs a local workflow file for trial mode -func installLocalWorkflowInTrialMode(originalDir, tempDir string, parsedSpec *WorkflowSpec, verbose bool) error { +func installLocalWorkflowInTrialMode(originalDir, tempDir string, parsedSpec *WorkflowSpec, appendText string, verbose bool) error { // Construct the source path (relative to original directory) sourcePath := filepath.Join(originalDir, parsedSpec.WorkflowPath) @@ -803,6 +805,17 @@ func installLocalWorkflowInTrialMode(originalDir, tempDir string, parsedSpec *Wo return fmt.Errorf("failed to read local workflow file: %w", err) } + // Append text if provided + if appendText != "" { + contentStr := string(content) + // Ensure we have a newline before appending + if !strings.HasSuffix(contentStr, "\n") { + contentStr += "\n" + } + contentStr += "\n" + appendText + content = []byte(contentStr) + } + // Write the content to the destination if err := os.WriteFile(destPath, content, 0644); err != nil { return fmt.Errorf("failed to write workflow to destination: %w", err)