From 09e3a4ee61422e5d7e0bc43334d1b9ffb71954dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:29:26 +0000 Subject: [PATCH 1/4] Initial plan From a6be3e4b999961c1ffa26ea761ef52002de7868e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:09:06 +0000 Subject: [PATCH 2/4] Enable more Go linters and fix all reported issues Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .golangci.yml | 16 +++++ cmd/gh-aw/main.go | 13 ++-- pkg/cli/access_log.go | 2 +- pkg/cli/actionlint.go | 14 +++-- pkg/cli/actions_build_command.go | 17 +++--- pkg/cli/add_command.go | 17 +++--- pkg/cli/add_interactive_auth.go | 7 ++- pkg/cli/add_interactive_engine.go | 4 +- pkg/cli/add_interactive_git.go | 12 ++-- pkg/cli/add_interactive_orchestrator.go | 5 +- pkg/cli/add_workflow_pr.go | 14 ++--- pkg/cli/add_workflow_resolution.go | 7 ++- pkg/cli/audit.go | 13 ++-- pkg/cli/audit_report_analysis.go | 8 +-- pkg/cli/audit_report_render.go | 13 ++-- pkg/cli/audit_test.go | 14 ++--- pkg/cli/codemod_bots.go | 2 +- pkg/cli/codemod_mcp_network.go | 6 +- pkg/cli/codemod_roles.go | 2 +- pkg/cli/commands.go | 12 ++-- pkg/cli/compile_batch_operations.go | 4 +- pkg/cli/compile_helpers.go | 8 +-- pkg/cli/compile_orchestration.go | 7 ++- pkg/cli/compile_post_processing.go | 2 +- pkg/cli/compile_stats.go | 7 ++- pkg/cli/compile_validation.go | 7 ++- pkg/cli/compile_watch.go | 5 +- pkg/cli/compile_workflow_processor.go | 4 +- pkg/cli/copilot_agent.go | 2 +- pkg/cli/copilot_agents.go | 12 ++-- pkg/cli/copilot_setup.go | 11 ++-- pkg/cli/deps_outdated.go | 7 ++- pkg/cli/deps_security.go | 4 +- pkg/cli/docker_images.go | 2 +- pkg/cli/download_workflow.go | 2 +- pkg/cli/enable.go | 4 +- pkg/cli/engine_secrets.go | 31 +++++----- pkg/cli/error_formatting_test.go | 5 +- pkg/cli/file_tracker.go | 8 +-- pkg/cli/firewall_log.go | 6 +- pkg/cli/fix_command.go | 4 +- pkg/cli/frontmatter_editor.go | 15 +++-- pkg/cli/gateway_logs.go | 5 +- pkg/cli/generate_action_metadata_command.go | 13 ++-- pkg/cli/git.go | 25 ++++---- pkg/cli/hash_command.go | 2 +- pkg/cli/health_command.go | 11 ++-- pkg/cli/health_metrics.go | 3 +- pkg/cli/imports.go | 4 +- pkg/cli/init.go | 5 +- pkg/cli/interactive.go | 7 ++- pkg/cli/log_aggregation.go | 4 +- pkg/cli/logs_cache.go | 2 +- pkg/cli/logs_display.go | 31 +++++----- pkg/cli/logs_download.go | 11 ++-- pkg/cli/logs_github_api.go | 12 ++-- pkg/cli/logs_metrics.go | 20 +++---- pkg/cli/logs_parallel_test.go | 3 +- pkg/cli/logs_parsing_core.go | 4 +- pkg/cli/logs_parsing_firewall.go | 2 +- pkg/cli/logs_parsing_javascript.go | 2 +- pkg/cli/logs_report.go | 2 +- pkg/cli/logs_utils.go | 4 +- pkg/cli/mcp_add.go | 15 ++--- pkg/cli/mcp_config_file.go | 2 +- pkg/cli/mcp_inspect.go | 7 ++- pkg/cli/mcp_inspect_inspector.go | 2 +- pkg/cli/mcp_inspect_list.go | 3 +- pkg/cli/mcp_inspect_mcp.go | 10 ++-- pkg/cli/mcp_inspect_safe_inputs_files.go | 2 +- pkg/cli/mcp_inspect_safe_inputs_inspector.go | 11 ++-- pkg/cli/mcp_inspect_safe_inputs_server.go | 7 ++- pkg/cli/mcp_list.go | 9 +-- pkg/cli/mcp_registry.go | 4 +- pkg/cli/mcp_registry_improvements_test.go | 2 +- pkg/cli/mcp_registry_list.go | 4 +- pkg/cli/mcp_secrets.go | 2 +- pkg/cli/mcp_server_command.go | 6 +- pkg/cli/mcp_server_helpers.go | 7 ++- pkg/cli/mcp_server_http.go | 2 +- pkg/cli/mcp_tools_privileged.go | 12 ++-- pkg/cli/mcp_tools_readonly.go | 4 +- pkg/cli/mcp_validation.go | 11 ++-- pkg/cli/packages.go | 4 +- pkg/cli/poutine.go | 11 ++-- pkg/cli/pr_automerge.go | 14 +++-- pkg/cli/pr_command.go | 36 +++++------ pkg/cli/preconditions.go | 13 ++-- pkg/cli/project_command.go | 59 ++++++++++--------- pkg/cli/redacted_domains.go | 7 ++- pkg/cli/remote_workflow.go | 13 ++-- pkg/cli/remove_command.go | 10 ++-- pkg/cli/resolver.go | 2 +- pkg/cli/run_interactive.go | 15 ++--- pkg/cli/run_push.go | 15 ++--- pkg/cli/run_workflow_execution.go | 32 +++++----- pkg/cli/run_workflow_execution_test.go | 5 +- pkg/cli/run_workflow_tracking.go | 7 ++- pkg/cli/run_workflow_validation.go | 17 +++--- pkg/cli/secret_set_command.go | 2 +- pkg/cli/secrets.go | 6 +- pkg/cli/shell_completion.go | 25 ++++---- pkg/cli/signal_aware_poll.go | 3 +- pkg/cli/signal_aware_poll_test.go | 6 +- pkg/cli/spec.go | 13 ++-- pkg/cli/status_command.go | 10 ++-- pkg/cli/tokens_bootstrap.go | 3 +- pkg/cli/trial_command.go | 28 +++++---- pkg/cli/trial_dry_run_test.go | 4 +- pkg/cli/trial_repository.go | 30 +++++----- pkg/cli/trial_support.go | 2 +- pkg/cli/update_actions.go | 21 +++---- pkg/cli/update_merge.go | 4 +- pkg/cli/update_workflows.go | 27 +++++---- pkg/cli/validation_output_test.go | 5 +- pkg/cli/workflows.go | 12 ++-- pkg/cli/zizmor.go | 8 ++- pkg/console/console.go | 3 +- pkg/console/form.go | 5 +- pkg/console/form_test.go | 4 +- pkg/console/input.go | 10 ++-- pkg/console/input_test.go | 4 +- pkg/console/list.go | 5 +- pkg/console/render.go | 12 ++-- pkg/console/select.go | 10 ++-- pkg/fileutil/fileutil.go | 3 +- pkg/parser/frontmatter_content.go | 3 +- pkg/parser/frontmatter_hash.go | 3 +- pkg/parser/frontmatter_helpers_test.go | 4 +- pkg/parser/frontmatter_syntax_errors_test.go | 5 +- pkg/parser/github.go | 3 +- pkg/parser/github_urls.go | 25 ++++---- pkg/parser/import_cache.go | 3 +- pkg/parser/import_error.go | 5 +- pkg/parser/import_processor.go | 15 ++--- pkg/parser/json_path_locator.go | 4 +- pkg/parser/remote_fetch.go | 11 ++-- pkg/parser/schedule_parser.go | 55 ++++++++--------- pkg/parser/schema_compiler.go | 6 +- pkg/parser/schema_errors.go | 4 +- pkg/parser/schema_suggestions.go | 2 +- pkg/parser/workflow_update.go | 2 +- pkg/parser/yaml_import.go | 17 +++--- pkg/stringutil/pat_validation.go | 8 +-- pkg/workflow/add_comment.go | 3 +- pkg/workflow/add_labels.go | 4 +- pkg/workflow/agent_validation.go | 2 +- pkg/workflow/agentic_output_test.go | 5 +- pkg/workflow/artifact_manager.go | 9 +-- pkg/workflow/bundler_file_mode.go | 2 +- pkg/workflow/cache.go | 16 ++--- pkg/workflow/claude_engine.go | 17 +++--- pkg/workflow/claude_engine_tools_test.go | 3 +- pkg/workflow/claude_logs.go | 8 ++- pkg/workflow/claude_tools.go | 6 +- pkg/workflow/codex_engine.go | 11 ++-- pkg/workflow/codex_logs.go | 2 +- pkg/workflow/command.go | 5 +- pkg/workflow/command_test.go | 2 +- pkg/workflow/compiler.go | 10 ++-- pkg/workflow/compiler_activation_jobs.go | 12 ++-- .../compiler_error_formatting_test.go | 10 ++-- pkg/workflow/compiler_jobs.go | 2 +- pkg/workflow/compiler_orchestrator_engine.go | 6 +- .../compiler_orchestrator_frontmatter.go | 7 ++- pkg/workflow/compiler_safe_outputs.go | 3 +- pkg/workflow/compiler_safe_outputs_steps.go | 8 +-- pkg/workflow/compiler_string_api.go | 3 +- pkg/workflow/compiler_unlock_job.go | 3 +- pkg/workflow/compiler_yaml_test.go | 6 +- pkg/workflow/concurrency_validation.go | 12 ++-- pkg/workflow/copilot_engine_execution.go | 9 +-- pkg/workflow/copilot_engine_installation.go | 4 +- pkg/workflow/copilot_installer.go | 14 ++--- pkg/workflow/create_agent_session.go | 3 +- pkg/workflow/create_code_scanning_alert.go | 3 +- pkg/workflow/create_discussion.go | 3 +- pkg/workflow/create_issue.go | 3 +- pkg/workflow/create_pr_review_comment.go | 3 +- pkg/workflow/create_pull_request.go | 3 +- pkg/workflow/dependabot.go | 5 +- pkg/workflow/dispatch_workflow_validation.go | 3 +- pkg/workflow/engine.go | 5 +- pkg/workflow/engine_firewall_support.go | 5 +- pkg/workflow/engine_helpers.go | 2 +- pkg/workflow/engine_helpers_shared_test.go | 3 +- pkg/workflow/engine_validation.go | 2 +- pkg/workflow/error_aggregation_test.go | 47 ++++++++------- pkg/workflow/error_wrapping_test.go | 3 +- pkg/workflow/expression_extraction.go | 8 +-- pkg/workflow/expression_nodes.go | 2 +- pkg/workflow/expression_parser.go | 13 ++-- pkg/workflow/expression_validation.go | 4 +- pkg/workflow/expressions_test.go | 6 +- pkg/workflow/frontmatter_extraction_yaml.go | 7 ++- pkg/workflow/frontmatter_types.go | 8 ++- .../github_toolset_validation_error.go | 4 +- pkg/workflow/inputs.go | 7 ++- pkg/workflow/jobs.go | 3 +- pkg/workflow/known_needs_expressions.go | 11 ++-- pkg/workflow/label_trigger_parser.go | 6 +- pkg/workflow/maintenance_workflow.go | 3 +- pkg/workflow/markdown_security_scanner.go | 6 +- pkg/workflow/mcp_gateway_mounts_test.go | 4 +- pkg/workflow/mcp_github_config.go | 7 ++- pkg/workflow/mcp_setup_generator.go | 11 ++-- pkg/workflow/missing_data.go | 3 +- pkg/workflow/missing_tool.go | 3 +- pkg/workflow/nodejs.go | 6 +- pkg/workflow/notify_comment.go | 4 +- pkg/workflow/npm_validation.go | 2 +- pkg/workflow/permissions_operations.go | 2 +- pkg/workflow/permissions_validation.go | 2 +- pkg/workflow/pip_validation.go | 4 +- pkg/workflow/plugin_installation.go | 12 ++-- pkg/workflow/plugin_installation_test.go | 14 +++-- pkg/workflow/publish_assets.go | 3 +- pkg/workflow/publish_assets_test.go | 5 +- pkg/workflow/repo_memory.go | 17 +++--- .../repo_memory_path_consistency_test.go | 24 ++++---- .../repository_features_validation.go | 6 +- pkg/workflow/runtime_overrides.go | 9 ++- pkg/workflow/runtime_setup_test.go | 8 ++- pkg/workflow/runtime_step_generator.go | 29 ++++----- pkg/workflow/runtime_validation.go | 2 +- pkg/workflow/safe_jobs.go | 8 +-- pkg/workflow/safe_outputs_config_helpers.go | 4 +- .../safe_outputs_default_create_issue_test.go | 2 +- pkg/workflow/safe_outputs_env.go | 3 +- pkg/workflow/safe_outputs_runs_on_test.go | 9 ++- pkg/workflow/schedule_preprocessing.go | 6 +- pkg/workflow/schema_validation.go | 5 +- pkg/workflow/secrets_validation.go | 4 +- pkg/workflow/shared_workflow_test.go | 17 +++--- pkg/workflow/slash_command_parser.go | 4 +- pkg/workflow/step_order_validation.go | 3 +- pkg/workflow/step_types.go | 3 +- pkg/workflow/stop_after.go | 17 +++--- pkg/workflow/strict_mode_validation.go | 13 ++-- pkg/workflow/strings.go | 2 +- pkg/workflow/threat_detection.go | 3 +- pkg/workflow/threat_detection_test.go | 5 +- pkg/workflow/time_delta.go | 7 ++- pkg/workflow/tools_parser.go | 5 +- pkg/workflow/tools_validation.go | 6 +- pkg/workflow/trigger_parser.go | 13 ++-- pkg/workflow/validation_helpers.go | 5 +- 247 files changed, 1110 insertions(+), 945 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 46982ae4e5..f7db66ce4b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,6 +17,22 @@ linters: - testifylint # Enforce testify best practices - intrange # Suggest integer range in for loops - modernize # Modernize Go code using modern language features + - errorlint # Find error wrapping issues (type assertions, comparisons) + - usestdlibvars # Use standard library variables/constants (e.g. http.MethodGet) + - mirror # Avoid unnecessary allocations in bytes/strings operations + - nolintlint # Report ill-formed or insufficient nolint directives + - wastedassign # Find wasted assignment statements + - makezero # Find slice declarations with non-zero initial length followed by append + - perfsprint # Replace fmt.Sprintf with faster alternatives where possible + - bodyclose # Check HTTP response bodies are closed + - gocheckcompilerdirectives # Validate go compiler directive comments + - recvcheck # Check receiver type consistency + - copyloopvar # Detect loop variable copies + - exptostd # Replace golang.org/x/exp functions with std equivalents + - durationcheck # Check for two durations multiplied together + - fatcontext # Detect nested contexts in loops and function literals + - nosprintfhostport # Check for misuse of Sprintf to construct host:port URLs + - reassign # Check that package variables are not reassigned disable: - errcheck # Disabled due to exclude-functions not working properly in golangci-lint v2 - gocritic # Disabled due to disabled-checks not working properly in golangci-lint v2 diff --git a/cmd/gh-aw/main.go b/cmd/gh-aw/main.go index 01aed77d7b..590aace28a 100644 --- a/cmd/gh-aw/main.go +++ b/cmd/gh-aw/main.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "os" "sort" @@ -132,7 +133,7 @@ Examples: if len(args) == 0 || interactiveFlag { // Check if running in CI environment if cli.IsRunningInCI() { - return fmt.Errorf("interactive mode cannot be used in CI environments. Please provide a workflow name") + return errors.New("interactive mode cannot be used in CI environments. Please provide a workflow name") } // Use default workflow name for interactive mode @@ -380,18 +381,18 @@ Examples: if len(args) == 0 { // Check if running in CI environment if cli.IsRunningInCI() { - return fmt.Errorf("interactive mode cannot be used in CI environments. Please provide a workflow name") + return errors.New("interactive mode cannot be used in CI environments. Please provide a workflow name") } // Interactive mode doesn't support repeat or enable flags if repeatCount > 0 { - return fmt.Errorf("--repeat flag is not supported in interactive mode") + return errors.New("--repeat flag is not supported in interactive mode") } if enable { - return fmt.Errorf("--enable-if-needed flag is not supported in interactive mode") + return errors.New("--enable-if-needed flag is not supported in interactive mode") } if len(inputs) > 0 { - return fmt.Errorf("workflow inputs cannot be specified in interactive mode (they will be collected interactively)") + return errors.New("workflow inputs cannot be specified in interactive mode (they will be collected interactively)") } return cli.RunWorkflowInteractively(cmd.Context(), verboseFlag, repoOverride, refOverride, autoMergePRs, push, engineOverride, dryRun) @@ -463,7 +464,7 @@ func init() { rootCmd.SilenceErrors = true // Set version template to match the version subcommand format - rootCmd.SetVersionTemplate(fmt.Sprintf("%s version {{.Version}}\n", string(constants.CLIExtensionPrefix))) + rootCmd.SetVersionTemplate(string(constants.CLIExtensionPrefix) + " version {{.Version}}\n") // Create custom help command that supports "all" subcommand customHelpCmd := &cobra.Command{ diff --git a/pkg/cli/access_log.go b/pkg/cli/access_log.go index e2878748b7..b28ae770ef 100644 --- a/pkg/cli/access_log.go +++ b/pkg/cli/access_log.go @@ -171,7 +171,7 @@ func analyzeAccessLogs(runDir string, verbose bool) (*DomainAnalysis, error) { // No access logs found accessLogLog.Printf("No access logs directory found in: %s", runDir) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No access logs found in %s", runDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No access logs found in "+runDir)) } return nil, nil } diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index 24cc92b96a..e5d7d96796 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -50,7 +51,7 @@ func getActionlintDocsURL(kind string) string { } } - return fmt.Sprintf("https://github.com/rhysd/actionlint/blob/main/docs/checks.md#%s", anchor) + return "https://github.com/rhysd/actionlint/blob/main/docs/checks.md#" + anchor } // actionlintStats tracks aggregate statistics across all actionlint validations @@ -159,7 +160,7 @@ func getActionlintVersion() (string, error) { // We only want the first line which contains the version number lines := strings.Split(strings.TrimSpace(string(output)), "\n") if len(lines) == 0 { - return "", fmt.Errorf("no version output from actionlint") + return "", errors.New("no version output from actionlint") } version := strings.TrimSpace(lines[0]) actionlintVersion = version @@ -183,7 +184,7 @@ func runActionlintOnFile(lockFiles []string, verbose bool, strict bool) error { // Log error but continue - version display is not critical actionlintLog.Printf("Could not fetch actionlint version: %v", err) } else { - fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Using actionlint %s", version))) + fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Using actionlint "+version)) } } @@ -214,7 +215,7 @@ func runActionlintOnFile(lockFiles []string, verbose bool, strict bool) error { dockerArgs := []string{ "run", "--rm", - "-v", fmt.Sprintf("%s:/workdir", gitRoot), + "-v", gitRoot + ":/workdir", "-w", "/workdir", "rhysd/actionlint:latest", "-format", "{{json .}}", @@ -225,7 +226,7 @@ func runActionlintOnFile(lockFiles []string, verbose bool, strict bool) error { // Always show that actionlint is running (regular verbosity) if len(lockFiles) == 1 { - fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Running actionlint (includes shellcheck & pyflakes) on %s", relPaths[0]))) + fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Running actionlint (includes shellcheck & pyflakes) on "+relPaths[0])) } else { fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Running actionlint (includes shellcheck & pyflakes) on %d files", len(lockFiles)))) } @@ -286,7 +287,8 @@ func runActionlintOnFile(lockFiles []string, verbose bool, strict bool) error { // Exit code 0 = no errors // Exit code 1 = errors found // Other codes = actual errors - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode := exitErr.ExitCode() actionlintLog.Printf("Actionlint exited with code %d, found %d errors", exitCode, totalErrors) // Exit code 1 indicates errors were found diff --git a/pkg/cli/actions_build_command.go b/pkg/cli/actions_build_command.go index 1ee1c964fd..f535858d4f 100644 --- a/pkg/cli/actions_build_command.go +++ b/pkg/cli/actions_build_command.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -77,7 +78,7 @@ func ActionsValidateCommand() error { } if !allValid { - return fmt.Errorf("validation failed for one or more actions") + return errors.New("validation failed for one or more actions") } fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("✨ All actions valid")) @@ -128,7 +129,7 @@ func ActionsCleanCommand() error { // getActionDirectories returns a sorted list of action directory names func getActionDirectories(actionsDir string) ([]string, error) { if _, err := os.Stat(actionsDir); os.IsNotExist(err) { - return nil, fmt.Errorf("actions/ directory does not exist") + return nil, errors.New("actions/ directory does not exist") } entries, err := os.ReadDir(actionsDir) @@ -164,7 +165,7 @@ func validateActionYml(actionPath string) error { ymlPath := filepath.Join(actionPath, "action.yml") if _, err := os.Stat(ymlPath); os.IsNotExist(err) { - return fmt.Errorf("action.yml not found") + return errors.New("action.yml not found") } content, err := os.ReadFile(ymlPath) @@ -187,7 +188,7 @@ func validateActionYml(actionPath string) error { isComposite := strings.Contains(contentStr, "using: 'composite'") || strings.Contains(contentStr, "using: \"composite\"") if !isNode20 && !isComposite { - return fmt.Errorf("action must use either 'node20' or 'composite' runtime") + return errors.New("action must use either 'node20' or 'composite' runtime") } return nil @@ -197,7 +198,7 @@ func validateActionYml(actionPath string) error { func buildAction(actionsDir, actionName string) error { actionsBuildLog.Printf("Building action: %s", actionName) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\nšŸ“¦ Building action: %s", actionName))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nšŸ“¦ Building action: "+actionName)) actionPath := filepath.Join(actionsDir, actionName) @@ -249,9 +250,9 @@ func buildAction(actionsDir, actionName string) error { for _, dep := range dependencies { if content, ok := sources[dep]; ok { files[dep] = content - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", dep))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" - "+dep)) } else { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf(" ⚠ Warning: Could not find %s", dep))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(" ⚠ Warning: Could not find "+dep)) } } @@ -275,7 +276,7 @@ func buildAction(actionsDir, actionName string) error { return fmt.Errorf("failed to write output file: %w", err) } - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" āœ“ Built %s", outputPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" āœ“ Built "+outputPath)) fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" āœ“ Embedded %d files", len(files)))) return nil diff --git a/pkg/cli/add_command.go b/pkg/cli/add_command.go index 5dad23a2a9..1f2ae6dd8f 100644 --- a/pkg/cli/add_command.go +++ b/pkg/cli/add_command.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "path/filepath" @@ -228,12 +229,12 @@ func AddResolvedWorkflows(workflowStrings []string, resolved *ResolvedWorkflows, if opts.CreatePR { // Check if GitHub CLI is available if !isGHCLIAvailable() { - return nil, fmt.Errorf("GitHub CLI (gh) is required for PR creation but not available") + return nil, errors.New("GitHub CLI (gh) is required for PR creation but not available") } // Check if we're in a git repository if !isGitRepo() { - return nil, fmt.Errorf("not in a git repository - PR creation requires a git repository") + return nil, errors.New("not in a git repository - PR creation requires a git repository") } // Check no other changes are present @@ -337,7 +338,7 @@ func addWorkflowsWithTracking(workflows []*ResolvedWorkflow, tracker *FileTracke // Create commit message var commitMessage string if len(workflows) == 1 { - commitMessage = fmt.Sprintf("chore: add workflow %s", workflows[0].Spec.WorkflowName) + commitMessage = "chore: add workflow " + workflows[0].Spec.WorkflowName } else { commitMessage = fmt.Sprintf("chore: add %d workflows", len(workflows)) } @@ -380,7 +381,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o sourceInfo := resolved.SourceInfo if opts.Verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Adding workflow: %s", workflowSpec.String()))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Adding workflow: "+workflowSpec.String())) if opts.Force { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Force flag enabled: will overwrite existing files")) } @@ -479,7 +480,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Destination file '%s' already exists, skipping.", destFile))) return nil } - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Overwriting existing file: %s", destFile))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Overwriting existing file: "+destFile)) } content := string(sourceContent) @@ -548,7 +549,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o } else { content = updatedContent if opts.Verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Set stop-after field to: %s", opts.StopAfter))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Set stop-after field to: "+opts.StopAfter)) } } } @@ -563,7 +564,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o } else { content = updatedContent if opts.Verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Set engine field to: %s", opts.EngineOverride))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Set engine field to: "+opts.EngineOverride)) } } } @@ -592,7 +593,7 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o // Show output if !opts.Quiet { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Added workflow: %s", destFile))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Added workflow: "+destFile)) if description := ExtractWorkflowDescription(content); description != "" { fmt.Fprintln(os.Stderr, "") diff --git a/pkg/cli/add_interactive_auth.go b/pkg/cli/add_interactive_auth.go index f66af43199..4b57f3a792 100644 --- a/pkg/cli/add_interactive_auth.go +++ b/pkg/cli/add_interactive_auth.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "strings" @@ -27,7 +28,7 @@ func (c *AddInteractiveConfig) checkGitRepository() error { fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" git init")) fmt.Fprintln(os.Stderr, "") - return fmt.Errorf("not in a git repository") + return errors.New("not in a git repository") } // Try to get the repository slug @@ -49,7 +50,7 @@ func (c *AddInteractiveConfig) checkGitRepository() error { Validate(func(s string) error { parts := strings.Split(s, "/") if len(parts) != 2 || parts[0] == "" || parts[1] == "" { - return fmt.Errorf("please enter in format 'owner/repo'") + return errors.New("please enter in format 'owner/repo'") } return nil }), @@ -66,7 +67,7 @@ func (c *AddInteractiveConfig) checkGitRepository() error { c.RepoOverride = repoSlug } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Target repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Target repository: "+repoSlug)) addInteractiveLog.Printf("Target repository: %s", repoSlug) // Check if repository is public or private diff --git a/pkg/cli/add_interactive_engine.go b/pkg/cli/add_interactive_engine.go index cfdad05604..16a83ff331 100644 --- a/pkg/cli/add_interactive_engine.go +++ b/pkg/cli/add_interactive_engine.go @@ -77,7 +77,7 @@ func (c *AddInteractiveConfig) selectAIEngineAndKey() error { // Inform user if workflow specifies an engine if workflowSpecifiedEngine != "" { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow specifies engine: %s", workflowSpecifiedEngine))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Workflow specifies engine: "+workflowSpecifiedEngine)) } // Build engine options with notes about existing secrets and workflow specification @@ -124,7 +124,7 @@ func (c *AddInteractiveConfig) selectAIEngineAndKey() error { } c.EngineOverride = selectedEngine - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Selected engine: %s", selectedEngine))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Selected engine: "+selectedEngine)) return c.collectAPIKey(selectedEngine) } diff --git a/pkg/cli/add_interactive_git.go b/pkg/cli/add_interactive_git.go index 0732a5e4fd..cf774a2f2b 100644 --- a/pkg/cli/add_interactive_git.go +++ b/pkg/cli/add_interactive_git.go @@ -2,9 +2,11 @@ package cli import ( "context" + "errors" "fmt" "os" "os/exec" + "strconv" "strings" "github.com/charmbracelet/huh" @@ -51,7 +53,7 @@ func (c *AddInteractiveConfig) applyChanges(ctx context.Context, workflowFiles, if err := c.mergePullRequest(result.PRNumber); err != nil { // Check if already merged if strings.Contains(err.Error(), "already merged") || strings.Contains(err.Error(), "MERGED") { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Merged pull request %s", result.PRURL))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Merged pull request "+result.PRURL)) } else { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to merge PR: %v", err))) fmt.Fprintln(os.Stderr, "Please merge the PR manually from the GitHub web interface.") @@ -76,11 +78,11 @@ func (c *AddInteractiveConfig) applyChanges(ctx context.Context, workflowFiles, if !continueAfterMerge { fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Stopped. You can continue later by merging the PR and running the workflow manually.")) - return fmt.Errorf("user chose to stop after merge failure") + return errors.New("user chose to stop after merge failure") } } } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Merged pull request %s", result.PRURL))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Merged pull request "+result.PRURL)) } } @@ -170,7 +172,7 @@ func (c *AddInteractiveConfig) checkCleanWorkingDirectory() error { fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" git stash # Temporarily stash changes")) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" git add -A && git commit -m 'wip' # Commit changes")) fmt.Fprintln(os.Stderr, "") - return fmt.Errorf("working directory is not clean") + return errors.New("working directory is not clean") } fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Working directory is clean")) @@ -179,7 +181,7 @@ func (c *AddInteractiveConfig) checkCleanWorkingDirectory() error { // mergePullRequest merges the specified PR func (c *AddInteractiveConfig) mergePullRequest(prNumber int) error { - output, err := workflow.RunGHCombined("Merging pull request...", "pr", "merge", fmt.Sprintf("%d", prNumber), "--repo", c.RepoOverride, "--merge") + output, err := workflow.RunGHCombined("Merging pull request...", "pr", "merge", strconv.Itoa(prNumber), "--repo", c.RepoOverride, "--merge") if err != nil { return fmt.Errorf("merge failed: %w (output: %s)", err, string(output)) } diff --git a/pkg/cli/add_interactive_orchestrator.go b/pkg/cli/add_interactive_orchestrator.go index 1956bf5c5d..93fbc880b5 100644 --- a/pkg/cli/add_interactive_orchestrator.go +++ b/pkg/cli/add_interactive_orchestrator.go @@ -2,6 +2,7 @@ package cli import ( "context" + "errors" "fmt" "os" @@ -48,7 +49,7 @@ func RunAddInteractive(ctx context.Context, workflowSpecs []string, verbose bool // Assert this function is not running in automated unit tests or CI if os.Getenv("GO_TEST_MODE") == "true" || os.Getenv("CI") != "" { - return fmt.Errorf("interactive add cannot be used in automated tests or CI environments") + return errors.New("interactive add cannot be used in automated tests or CI environments") } config := &AddInteractiveConfig{ @@ -209,7 +210,7 @@ func (c *AddInteractiveConfig) confirmChanges(workflowFiles, initFiles []string, if !confirmed { fmt.Fprintln(os.Stderr, "Operation cancelled.") - return fmt.Errorf("user cancelled the operation") + return errors.New("user cancelled the operation") } return nil diff --git a/pkg/cli/add_workflow_pr.go b/pkg/cli/add_workflow_pr.go index a592030a38..21c1ce19f9 100644 --- a/pkg/cli/add_workflow_pr.go +++ b/pkg/cli/add_workflow_pr.go @@ -113,17 +113,17 @@ func addWorkflowsWithPR(workflows []*ResolvedWorkflow, opts AddOptions) (int, st var commitMessage, prTitle, prBody, joinedNames string if len(workflows) == 1 { joinedNames = workflows[0].Spec.WorkflowName - commitMessage = fmt.Sprintf("Add agentic workflow %s", joinedNames) - prTitle = fmt.Sprintf("Add agentic workflow %s", joinedNames) - prBody = fmt.Sprintf("Add agentic workflow %s", joinedNames) + commitMessage = "Add agentic workflow " + joinedNames + prTitle = "Add agentic workflow " + joinedNames + prBody = "Add agentic workflow " + joinedNames } else { workflowNames := sliceutil.Map(workflows, func(wf *ResolvedWorkflow) string { return wf.Spec.WorkflowName }) joinedNames = strings.Join(workflowNames, ", ") - commitMessage = fmt.Sprintf("Add agentic workflows: %s", joinedNames) - prTitle = fmt.Sprintf("Add agentic workflows: %s", joinedNames) - prBody = fmt.Sprintf("Add agentic workflows: %s", joinedNames) + commitMessage = "Add agentic workflows: " + joinedNames + prTitle = "Add agentic workflows: " + joinedNames + prBody = "Add agentic workflows: " + joinedNames } if err := commitChanges(commitMessage, opts.Verbose); err != nil { @@ -161,6 +161,6 @@ func addWorkflowsWithPR(workflows []*ResolvedWorkflow, opts AddOptions) (int, st return prNumber, prURL, fmt.Errorf("failed to switch back to branch %s: %w", currentBranch, err) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Created pull request %s", prURL))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created pull request "+prURL)) return prNumber, prURL, nil } diff --git a/pkg/cli/add_workflow_resolution.go b/pkg/cli/add_workflow_resolution.go index 9c03ca9fda..e552fc5f31 100644 --- a/pkg/cli/add_workflow_resolution.go +++ b/pkg/cli/add_workflow_resolution.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "path/filepath" @@ -46,7 +47,7 @@ func ResolveWorkflows(workflows []string, verbose bool) (*ResolvedWorkflows, err resolutionLog.Printf("Resolving workflows: count=%d", len(workflows)) if len(workflows) == 0 { - return nil, fmt.Errorf("at least one workflow name is required") + return nil, errors.New("at least one workflow name is required") } for i, workflow := range workflows { @@ -166,7 +167,7 @@ func expandLocalWildcardWorkflows(specs []*WorkflowSpec, verbose bool) ([]*Workf } if len(discovered) == 0 { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("No workflows found matching %s", spec.WorkflowPath))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("No workflows found matching "+spec.WorkflowPath)) } else { if verbose { fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Found %d workflow(s)", len(discovered)))) @@ -179,7 +180,7 @@ func expandLocalWildcardWorkflows(specs []*WorkflowSpec, verbose bool) ([]*Workf } if len(expandedWorkflows) == 0 { - return nil, fmt.Errorf("no workflows to add after expansion") + return nil, errors.New("no workflows to add after expansion") } return expandedWorkflows, nil diff --git a/pkg/cli/audit.go b/pkg/cli/audit.go index 66276147c4..7339155707 100644 --- a/pkg/cli/audit.go +++ b/pkg/cli/audit.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "time" @@ -415,7 +416,7 @@ func AuditWorkflowRun(ctx context.Context, runID int64, owner, repo, hostname st // Display logs location (only for console output) if !jsonOutput { absOutputDir, _ := filepath.Abs(runOutputDir) - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Audit complete. Logs saved to %s", absOutputDir))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Audit complete. Logs saved to "+absOutputDir)) } return nil @@ -444,11 +445,11 @@ func auditJobRun(runID int64, jobID int64, stepNumber int, owner, repo, hostname args = append(args, "-R", fmt.Sprintf("%s/%s", owner, repo)) } - args = append(args, "--job", fmt.Sprintf("%d", jobID), "--log") + args = append(args, "--job", strconv.FormatInt(jobID, 10), "--log") if verbose { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Fetching logs for job %d...", jobID))) - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Executing: gh %s", strings.Join(args, " ")))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Executing: gh "+strings.Join(args, " "))) } output, err := workflow.RunGHCombined("Fetching job logs...", args...) @@ -465,7 +466,7 @@ func auditJobRun(runID int64, jobID int64, stepNumber int, owner, repo, hostname } if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Job log saved to %s", jobLogPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Job log saved to "+jobLogPath)) } // If step number is specified, extract that step's output @@ -503,7 +504,7 @@ func auditJobRun(runID int64, jobID int64, stepNumber int, owner, repo, hostname // Display summary if !jsonOutput { absOutputDir, _ := filepath.Abs(outputDir) - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Job audit complete. Logs saved to %s", absOutputDir))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Job audit complete. Logs saved to "+absOutputDir)) // Display file locations fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nDownloaded files:")) @@ -624,7 +625,7 @@ func fetchWorkflowRunMetadata(runID int64, owner, repo, hostname string, verbose ) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Executing: gh %s", strings.Join(args, " ")))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Executing: gh "+strings.Join(args, " "))) } output, err := workflow.RunGHCombined("Fetching run metadata...", args...) diff --git a/pkg/cli/audit_report_analysis.go b/pkg/cli/audit_report_analysis.go index 45993fa2d7..89de341e15 100644 --- a/pkg/cli/audit_report_analysis.go +++ b/pkg/cli/audit_report_analysis.go @@ -98,7 +98,7 @@ func generateFindings(processedRun ProcessedRun, metrics MetricsData, errors []E Category: "tooling", Severity: "high", Title: "MCP Server Failures", - Description: fmt.Sprintf("Failed MCP servers: %s", strings.Join(serverNames, ", ")), + Description: "Failed MCP servers: " + strings.Join(serverNames, ", "), Impact: "Missing tools may limit workflow capabilities", }) } @@ -109,7 +109,7 @@ func generateFindings(processedRun ProcessedRun, metrics MetricsData, errors []E for i := 0; i < len(processedRun.MissingTools) && i < 3; i++ { toolNames = append(toolNames, processedRun.MissingTools[i].Tool) } - desc := fmt.Sprintf("Missing tools: %s", strings.Join(toolNames, ", ")) + desc := "Missing tools: " + strings.Join(toolNames, ", ") if len(processedRun.MissingTools) > 3 { desc += fmt.Sprintf(" (and %d more)", len(processedRun.MissingTools)-3) } @@ -205,7 +205,7 @@ func generateRecommendations(processedRun ProcessedRun, metrics MetricsData, fin Priority: "medium", Action: "Add missing tools to workflow configuration", Reason: "Missing tools limit agent capabilities and may cause failures", - Example: fmt.Sprintf("Add tools configuration for: %s", processedRun.MissingTools[0].Tool), + Example: "Add tools configuration for: " + processedRun.MissingTools[0].Tool, }) } @@ -274,7 +274,7 @@ func generateFailureAnalysis(processedRun ProcessedRun, errors []ErrorInfo) *Fai // Attempt to identify root cause rootCause := "" if len(processedRun.MCPFailures) > 0 { - rootCause = fmt.Sprintf("MCP server failure: %s", processedRun.MCPFailures[0].ServerName) + rootCause = "MCP server failure: " + processedRun.MCPFailures[0].ServerName } else if len(errors) > 0 { // Look for common error patterns firstError := errors[0].Message diff --git a/pkg/cli/audit_report_render.go b/pkg/cli/audit_report_render.go index 0d607c6ef3..31d040b93b 100644 --- a/pkg/cli/audit_report_render.go +++ b/pkg/cli/audit_report_render.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/stringutil" @@ -266,7 +267,7 @@ func renderToolUsageTable(toolUsage []ToolUsageInfo) { row := []string{ stringutil.Truncate(tool.Name, 40), - fmt.Sprintf("%d", tool.CallCount), + strconv.Itoa(tool.CallCount), inputStr, outputStr, durationStr, @@ -298,15 +299,15 @@ func renderMCPToolUsageTable(mcpData *MCPToolUsageData) { if durationStr == "" { durationStr = "N/A" } - errorStr := fmt.Sprintf("%d", server.ErrorCount) + errorStr := strconv.Itoa(server.ErrorCount) if server.ErrorCount == 0 { errorStr = "-" } row := []string{ stringutil.Truncate(server.ServerName, 25), - fmt.Sprintf("%d", server.RequestCount), - fmt.Sprintf("%d", server.ToolCallCount), + strconv.Itoa(server.RequestCount), + strconv.Itoa(server.ToolCallCount), inputStr, outputStr, durationStr, @@ -338,7 +339,7 @@ func renderMCPToolUsageTable(mcpData *MCPToolUsageData) { row := []string{ stringutil.Truncate(tool.ServerName, 20), stringutil.Truncate(tool.ToolName, 30), - fmt.Sprintf("%d", tool.CallCount), + strconv.Itoa(tool.CallCount), totalInStr, totalOutStr, maxInStr, @@ -410,7 +411,7 @@ func renderCreatedItemsTable(items []CreatedItemReport) { for _, item := range items { numberStr := "" if item.Number > 0 { - numberStr = fmt.Sprintf("%d", item.Number) + numberStr = strconv.Itoa(item.Number) } row := []string{ diff --git a/pkg/cli/audit_test.go b/pkg/cli/audit_test.go index ba0f85b66c..9e5e8e54de 100644 --- a/pkg/cli/audit_test.go +++ b/pkg/cli/audit_test.go @@ -4,7 +4,7 @@ package cli import ( "encoding/json" - "fmt" + "errors" "io" "os" "path/filepath" @@ -224,32 +224,32 @@ func TestIsPermissionError(t *testing.T) { }, { name: "Authentication required error", - err: fmt.Errorf("authentication required"), + err: errors.New("authentication required"), expected: true, }, { name: "Exit status 4 error", - err: fmt.Errorf("exit status 4"), + err: errors.New("exit status 4"), expected: true, }, { name: "GitHub CLI authentication error", - err: fmt.Errorf("GitHub CLI authentication required"), + err: errors.New("GitHub CLI authentication required"), expected: true, }, { name: "Permission denied error", - err: fmt.Errorf("permission denied"), + err: errors.New("permission denied"), expected: true, }, { name: "GH_TOKEN error", - err: fmt.Errorf("GH_TOKEN environment variable not set"), + err: errors.New("GH_TOKEN environment variable not set"), expected: true, }, { name: "Other error", - err: fmt.Errorf("some other error"), + err: errors.New("some other error"), expected: false, }, } diff --git a/pkg/cli/codemod_bots.go b/pkg/cli/codemod_bots.go index 9a7ce5dd26..d2dcef92d1 100644 --- a/pkg/cli/codemod_bots.go +++ b/pkg/cli/codemod_bots.go @@ -182,7 +182,7 @@ func getBotsToOnBotsCodemod() Codemod { if len(parts) == 2 { result = append(result, fmt.Sprintf("%sbots:%s", onItemIndent, parts[1])) } else { - result = append(result, fmt.Sprintf("%sbots:", onItemIndent)) + result = append(result, onItemIndent+"bots:") } } else { // Array item line (e.g., "- dependabot") diff --git a/pkg/cli/codemod_mcp_network.go b/pkg/cli/codemod_mcp_network.go index cf045aa9ce..75b967c003 100644 --- a/pkg/cli/codemod_mcp_network.go +++ b/pkg/cli/codemod_mcp_network.go @@ -276,7 +276,7 @@ func addTopLevelNetwork(lines []string, domains []string) []string { networkLines = append(networkLines, "network:") networkLines = append(networkLines, " allowed:") for _, domain := range domains { - networkLines = append(networkLines, fmt.Sprintf(" - %s", domain)) + networkLines = append(networkLines, " - "+domain) } // Insert at the determined position @@ -393,7 +393,7 @@ func addAllowedToNetwork(lines []string, domains []string) []string { if insertIndex > 0 { // Insert allowed before the next top-level block allowedLines := []string{ - fmt.Sprintf("%s allowed:", networkIndent), + networkIndent + " allowed:", } for _, domain := range domains { allowedLines = append(allowedLines, fmt.Sprintf("%s - %s", networkIndent, domain)) @@ -411,7 +411,7 @@ func addAllowedToNetwork(lines []string, domains []string) []string { break } } - result = append(result, fmt.Sprintf("%s allowed:", networkIndentStr)) + result = append(result, networkIndentStr+" allowed:") for _, domain := range domains { result = append(result, fmt.Sprintf("%s - %s", networkIndentStr, domain)) } diff --git a/pkg/cli/codemod_roles.go b/pkg/cli/codemod_roles.go index 591f194f36..3d20365e82 100644 --- a/pkg/cli/codemod_roles.go +++ b/pkg/cli/codemod_roles.go @@ -182,7 +182,7 @@ func getRolesToOnRolesCodemod() Codemod { if len(parts) == 2 { result = append(result, fmt.Sprintf("%sroles:%s", onItemIndent, parts[1])) } else { - result = append(result, fmt.Sprintf("%sroles:", onItemIndent)) + result = append(result, onItemIndent+"roles:") } } else { // Array item line (e.g., "- admin") diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index d276df347a..c6b2f13fa4 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -53,7 +53,7 @@ func downloadAgentFileFromGitHub(verbose bool) (string, error) { ref = GetVersion() commandsLog.Printf("Using release tag: %s", ref) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Using release version: %s", ref))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Using release version: "+ref)) } } else { commandsLog.Print("Using main branch for dev build") @@ -107,7 +107,7 @@ func downloadAgentFileFromGitHub(verbose bool) (string, error) { // Patch URLs to match the current version/ref patchedContent := patchAgentFileURLs(contentStr, ref) if patchedContent != contentStr && verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Patched URLs to use ref: %s", ref))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Patched URLs to use ref: "+ref)) } commandsLog.Printf("Successfully downloaded agent file (%d bytes)", len(patchedContent)) @@ -134,7 +134,7 @@ func patchAgentFileURLs(content, ref string) string { // (e.g., for private repositories accessed from codespaces). func downloadAgentFileViaGHCLI(ref string) (string, error) { output, err := workflow.RunGH("Downloading agent file...", "api", - fmt.Sprintf("/repos/github/gh-aw/contents/.github/agents/agentic-workflows.agent.md?ref=%s", url.QueryEscape(ref)), + "/repos/github/gh-aw/contents/.github/agents/agentic-workflows.agent.md?ref="+url.QueryEscape(ref), "--header", "Accept: application/vnd.github.raw") if err != nil { return "", fmt.Errorf("gh api download failed: %w", err) @@ -168,7 +168,7 @@ func resolveWorkflowFileInDir(fileOrWorkflowName string, verbose bool, workflowD // First, try to use it as a direct file path if _, err := os.Stat(fileOrWorkflowName); err == nil { commandsLog.Printf("Found workflow file at path: %s", fileOrWorkflowName) - console.LogVerbose(verbose, fmt.Sprintf("Found workflow file at path: %s", fileOrWorkflowName)) + console.LogVerbose(verbose, "Found workflow file at path: "+fileOrWorkflowName) // Return absolute path absPath, err := filepath.Abs(fileOrWorkflowName) if err != nil { @@ -234,7 +234,7 @@ func NewWorkflow(workflowName string, verbose bool, force bool) error { workflowName = strings.TrimSuffix(workflowName, ".md") commandsLog.Printf("Normalized workflow name: %s", workflowName) - console.LogVerbose(verbose, fmt.Sprintf("Creating new workflow: %s", workflowName)) + console.LogVerbose(verbose, "Creating new workflow: "+workflowName) // Get current working directory for .github/workflows workingDir, err := os.Getwd() @@ -284,7 +284,7 @@ func NewWorkflow(workflowName string, verbose bool, force bool) error { return fmt.Errorf("failed to write workflow file '%s': %w", destFile, err) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Created new workflow: %s", destFile))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created new workflow: "+destFile)) fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Edit the file to customize your workflow, then run '%s compile' to generate the GitHub Actions workflow", string(constants.CLIExtensionPrefix)))) return nil diff --git a/pkg/cli/compile_batch_operations.go b/pkg/cli/compile_batch_operations.go index 9d16755f3b..b4841503cc 100644 --- a/pkg/cli/compile_batch_operations.go +++ b/pkg/cli/compile_batch_operations.go @@ -142,7 +142,7 @@ func purgeOrphanedLockFiles(workflowsDir string, expectedLockFiles []string, ver if err := os.Remove(orphanedFile); err != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to remove orphaned lock file %s: %v", filepath.Base(orphanedFile), err))) } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed orphaned lock file: %s", filepath.Base(orphanedFile)))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed orphaned lock file: "+filepath.Base(orphanedFile))) } } if verbose { @@ -181,7 +181,7 @@ func purgeInvalidFiles(workflowsDir string, verbose bool) error { if err := os.Remove(invalidFile); err != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to remove invalid file %s: %v", filepath.Base(invalidFile), err))) } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed invalid file: %s", filepath.Base(invalidFile)))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed invalid file: "+filepath.Base(invalidFile))) } } diff --git a/pkg/cli/compile_helpers.go b/pkg/cli/compile_helpers.go index 2424fcd385..6bb8126cf4 100644 --- a/pkg/cli/compile_helpers.go +++ b/pkg/cli/compile_helpers.go @@ -95,7 +95,7 @@ func compileSingleFile(compiler *workflow.Compiler, file string, stats *Compilat // Regular workflow file - compile normally compileHelpersLog.Printf("Compiling as regular workflow: %s", file) if verbose { - fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Compiling: %s", file))) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage("Compiling: "+file)) } if err := CompileWorkflowWithValidation(compiler, file, verbose, false, false, false, false, false); err != nil { @@ -132,7 +132,7 @@ func compileAllWorkflowFiles(compiler *workflow.Compiler, workflowsDir string, v if len(mdFiles) == 0 { compileHelpersLog.Printf("No markdown files found in %s", workflowsDir) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No markdown files found in %s", workflowsDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No markdown files found in "+workflowsDir)) } return stats, nil } @@ -161,7 +161,7 @@ func compileAllWorkflowFiles(compiler *workflow.Compiler, workflowsDir string, v } else { compileHelpersLog.Print("Action cache saved successfully") if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Action cache saved to %s", actionCache.GetCachePath()))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Action cache saved to "+actionCache.GetCachePath())) } } } @@ -337,7 +337,7 @@ func handleFileDeleted(mdFile string, verbose bool) { } } else { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Removed corresponding lock file: %s", lockFile))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Removed corresponding lock file: "+lockFile)) } } } diff --git a/pkg/cli/compile_orchestration.go b/pkg/cli/compile_orchestration.go index e8ddf6c6b0..549537bdcf 100644 --- a/pkg/cli/compile_orchestration.go +++ b/pkg/cli/compile_orchestration.go @@ -22,6 +22,7 @@ package cli import ( + "errors" "fmt" "os" "path/filepath" @@ -176,7 +177,7 @@ func compileSpecificFiles( // Don't return the detailed error message here since it's already printed in the summary // Returning a simple error prevents duplication in the output if errorCount > 0 { - return workflowDataList, fmt.Errorf("compilation failed") + return workflowDataList, errors.New("compilation failed") } return workflowDataList, nil @@ -205,7 +206,7 @@ func compileAllFilesInDirectory( compileOrchestrationLog.Printf("Scanning for markdown files in %s", workflowsDir) if config.Verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Scanning for markdown files in %s", workflowsDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Scanning for markdown files in "+workflowsDir)) } // Find all markdown files @@ -339,7 +340,7 @@ func compileAllFilesInDirectory( // Return error if any compilations failed if errorCount > 0 { - return workflowDataList, fmt.Errorf("compilation failed") + return workflowDataList, errors.New("compilation failed") } return workflowDataList, nil diff --git a/pkg/cli/compile_post_processing.go b/pkg/cli/compile_post_processing.go index 46db94c0e8..b2afc519c2 100644 --- a/pkg/cli/compile_post_processing.go +++ b/pkg/cli/compile_post_processing.go @@ -145,7 +145,7 @@ func saveActionCache(actionCache *workflow.ActionCache, verbose bool) error { compilePostProcessingLog.Print("Action cache saved successfully") if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Action cache saved to %s", actionCache.GetCachePath()))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Action cache saved to "+actionCache.GetCachePath())) } return nil diff --git a/pkg/cli/compile_stats.go b/pkg/cli/compile_stats.go index b64dd38246..f3c76c594e 100644 --- a/pkg/cli/compile_stats.go +++ b/pkg/cli/compile_stats.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "sort" + "strconv" "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" @@ -150,9 +151,9 @@ func displayStatsTable(statsList []*WorkflowStats) { rows = append(rows, []string{ workflowName, fileSize, - fmt.Sprintf("%d", stats.Jobs), - fmt.Sprintf("%d", stats.Steps), - fmt.Sprintf("%d", stats.ScriptCount), + strconv.Itoa(stats.Jobs), + strconv.Itoa(stats.Steps), + strconv.Itoa(stats.ScriptCount), }) } diff --git a/pkg/cli/compile_validation.go b/pkg/cli/compile_validation.go index 47d834edb2..ed646fdbcb 100644 --- a/pkg/cli/compile_validation.go +++ b/pkg/cli/compile_validation.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "path/filepath" @@ -197,18 +198,18 @@ func validateCompileConfig(config CompileConfig) error { if config.Dependabot { if len(config.MarkdownFiles) > 0 { compileValidationLog.Print("Config validation failed: dependabot flag with specific files") - return fmt.Errorf("--dependabot flag cannot be used with specific workflow files") + return errors.New("--dependabot flag cannot be used with specific workflow files") } if config.WorkflowDir != "" && config.WorkflowDir != ".github/workflows" { compileValidationLog.Printf("Config validation failed: dependabot with custom dir: %s", config.WorkflowDir) - return fmt.Errorf("--dependabot flag cannot be used with custom --dir") + return errors.New("--dependabot flag cannot be used with custom --dir") } } // Validate purge flag usage if config.Purge && len(config.MarkdownFiles) > 0 { compileValidationLog.Print("Config validation failed: purge flag with specific files") - return fmt.Errorf("--purge flag can only be used when compiling all markdown files (no specific files specified)") + return errors.New("--purge flag can only be used when compiling all markdown files (no specific files specified)") } // Validate workflow directory path diff --git a/pkg/cli/compile_watch.go b/pkg/cli/compile_watch.go index d7f85d2cf7..f263ec62d5 100644 --- a/pkg/cli/compile_watch.go +++ b/pkg/cli/compile_watch.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "os/signal" @@ -157,7 +158,7 @@ func watchAndCompileWorkflows(markdownFile string, compiler *workflow.Compiler, select { case event, ok := <-watcher.Events: if !ok { - return fmt.Errorf("watcher channel closed") + return errors.New("watcher channel closed") } // Filter out Chmod events (noisy and usually not useful for workflow changes) @@ -214,7 +215,7 @@ func watchAndCompileWorkflows(markdownFile string, compiler *workflow.Compiler, case err, ok := <-watcher.Errors: if !ok { - return fmt.Errorf("watcher error channel closed") + return errors.New("watcher error channel closed") } compileWatchLog.Printf("Watcher error: %v", err) if verbose { diff --git a/pkg/cli/compile_workflow_processor.go b/pkg/cli/compile_workflow_processor.go index 11780f9e54..c78ac3fa74 100644 --- a/pkg/cli/compile_workflow_processor.go +++ b/pkg/cli/compile_workflow_processor.go @@ -22,6 +22,7 @@ package cli import ( + "errors" "fmt" "os" "path/filepath" @@ -98,7 +99,8 @@ func compileWorkflowFile( workflowData, err := compiler.ParseWorkflowFile(resolvedFile) if err != nil { // Check if this is a shared workflow (not an error, just info) - if sharedErr, ok := err.(*workflow.SharedWorkflowError); ok { + var sharedErr *workflow.SharedWorkflowError + if errors.As(err, &sharedErr) { if !jsonOutput { // Print info message instead of error fmt.Fprintln(os.Stderr, console.FormatInfoMessage(sharedErr.Error())) diff --git a/pkg/cli/copilot_agent.go b/pkg/cli/copilot_agent.go index 42752e2bba..81f16c5813 100644 --- a/pkg/cli/copilot_agent.go +++ b/pkg/cli/copilot_agent.go @@ -154,7 +154,7 @@ func (d *CopilotCodingAgentDetector) hasAgentArtifacts() bool { if _, err := os.Stat(artifactPath); err == nil { if d.verbose { fmt.Fprintln(os.Stderr, console.FormatInfoMessage( - fmt.Sprintf("Found agent artifact: %s", artifactName))) + "Found agent artifact: "+artifactName)) } return true } diff --git a/pkg/cli/copilot_agents.go b/pkg/cli/copilot_agents.go index fcd638b872..f3943d3e44 100644 --- a/pkg/cli/copilot_agents.go +++ b/pkg/cli/copilot_agents.go @@ -52,7 +52,7 @@ func ensureAgenticWorkflowsDispatcher(verbose bool, skipInstructions bool) error if strings.TrimSpace(existingContent) == expectedContent { copilotAgentsLog.Printf("Dispatcher agent is up-to-date: %s", targetPath) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Dispatcher agent is up-to-date: %s", targetPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Dispatcher agent is up-to-date: "+targetPath)) } return nil } @@ -67,12 +67,12 @@ func ensureAgenticWorkflowsDispatcher(verbose bool, skipInstructions bool) error if existingContent == "" { copilotAgentsLog.Printf("Created dispatcher agent: %s", targetPath) if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Created dispatcher agent: %s", targetPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created dispatcher agent: "+targetPath)) } } else { copilotAgentsLog.Printf("Updated dispatcher agent: %s", targetPath) if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Updated dispatcher agent: %s", targetPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Updated dispatcher agent: "+targetPath)) } } @@ -94,7 +94,7 @@ func cleanupOldPromptFile(promptFileName string, verbose bool) error { return fmt.Errorf("failed to remove old prompt file: %w", err) } if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Removed old prompt file: %s", oldPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Removed old prompt file: "+oldPath)) } } @@ -161,7 +161,7 @@ func deleteOldTemplateFiles(verbose bool) error { } removedCount++ if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Removed old template file: %s", path))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Removed old template file: "+path)) } } } @@ -174,7 +174,7 @@ func deleteOldTemplateFiles(verbose bool) error { return fmt.Errorf("failed to remove empty templates directory: %w", err) } if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Removed empty templates directory: %s", templatesDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Removed empty templates directory: "+templatesDir)) } } } diff --git a/pkg/cli/copilot_setup.go b/pkg/cli/copilot_setup.go index 07b3bce88b..41a81b24de 100644 --- a/pkg/cli/copilot_setup.go +++ b/pkg/cli/copilot_setup.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "path/filepath" @@ -245,7 +246,7 @@ func renderCopilotSetupUpdateInstructions(filePath string, actionMode workflow.A fmt.Fprintln(os.Stderr) fmt.Fprintf(os.Stderr, "%s %s\n", "ℹ", - fmt.Sprintf("Existing file detected: %s", filePath)) + "Existing file detected: "+filePath) fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "To enable GitHub Copilot Agent integration, please add the following steps") fmt.Fprintln(os.Stderr, "to the 'copilot-setup-steps' job in your .github/workflows/copilot-setup-steps.yml file:") @@ -277,7 +278,7 @@ func upgradeSetupCliVersion(workflow *Workflow, actionMode workflow.ActionMode, // Find the copilot-setup-steps job job, exists := workflow.Jobs["copilot-setup-steps"] if !exists { - return false, fmt.Errorf("copilot-setup-steps job not found in workflow") + return false, errors.New("copilot-setup-steps job not found in workflow") } upgraded := false @@ -293,7 +294,7 @@ func upgradeSetupCliVersion(workflow *Workflow, actionMode workflow.ActionMode, oldUses := step.Uses if actionMode.IsRelease() { // Update to the new version tag - newUses := fmt.Sprintf("github/gh-aw/actions/setup-cli%s", actionRef) + newUses := "github/gh-aw/actions/setup-cli" + actionRef step.Uses = newUses // Update the with.version parameter @@ -330,7 +331,7 @@ func injectExtensionInstallStep(workflow *Workflow, actionMode workflow.ActionMo } installStep = CopilotWorkflowStep{ Name: "Install gh-aw extension", - Uses: fmt.Sprintf("github/gh-aw/actions/setup-cli%s", actionRef), + Uses: "github/gh-aw/actions/setup-cli" + actionRef, With: map[string]any{ "version": version, }, @@ -346,7 +347,7 @@ func injectExtensionInstallStep(workflow *Workflow, actionMode workflow.ActionMo // Find the copilot-setup-steps job job, exists := workflow.Jobs["copilot-setup-steps"] if !exists { - return fmt.Errorf("copilot-setup-steps job not found in workflow") + return errors.New("copilot-setup-steps job not found in workflow") } // Insert the extension install step at the beginning diff --git a/pkg/cli/deps_outdated.go b/pkg/cli/deps_outdated.go index b3719f8b17..394c6ad3c0 100644 --- a/pkg/cli/deps_outdated.go +++ b/pkg/cli/deps_outdated.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "io" "net/http" @@ -50,7 +51,7 @@ func CheckOutdatedDependencies(verbose bool) ([]OutdatedDependency, error) { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Reading go.mod from: %s", goModPath))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Reading go.mod from: "+goModPath)) } // Parse go.mod to get dependencies @@ -145,12 +146,12 @@ func findGoMod() (string, error) { // Try git root root, err := findGitRoot() if err != nil { - return "", fmt.Errorf("not in a Go module (no go.mod found)") + return "", errors.New("not in a Go module (no go.mod found)") } goModPath := filepath.Join(root, "go.mod") if _, err := os.Stat(goModPath); err != nil { - return "", fmt.Errorf("not in a Go module (no go.mod found)") + return "", errors.New("not in a Go module (no go.mod found)") } return goModPath, nil diff --git a/pkg/cli/deps_security.go b/pkg/cli/deps_security.go index 9be3f69886..f9f7707daf 100644 --- a/pkg/cli/deps_security.go +++ b/pkg/cli/deps_security.go @@ -57,7 +57,7 @@ func CheckSecurityAdvisories(verbose bool) ([]SecurityAdvisory, error) { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Reading go.mod from: %s", goModPath))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Reading go.mod from: "+goModPath)) } // Parse go.mod to get dependencies @@ -135,7 +135,7 @@ func querySecurityAdvisories(depVersions map[string]string, verbose bool) ([]Sec url := "https://api.github.com/advisories?ecosystem=go&per_page=100" client := &http.Client{Timeout: 30 * time.Second} - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } diff --git a/pkg/cli/docker_images.go b/pkg/cli/docker_images.go index 05184ee072..3e37772b07 100644 --- a/pkg/cli/docker_images.go +++ b/pkg/cli/docker_images.go @@ -249,7 +249,7 @@ func ResetDockerPullState() { // that require static analysis tools func ValidateMCPServerDockerAvailability() error { if !isDockerAvailable() { - return fmt.Errorf("docker is not available - required for zizmor, poutine, and actionlint static analysis tools") + return errors.New("docker is not available - required for zizmor, poutine, and actionlint static analysis tools") } return nil } diff --git a/pkg/cli/download_workflow.go b/pkg/cli/download_workflow.go index 087e15badc..9716aa3f0f 100644 --- a/pkg/cli/download_workflow.go +++ b/pkg/cli/download_workflow.go @@ -164,7 +164,7 @@ func downloadWorkflowContent(repo, path, ref string, verbose bool) ([]byte, erro // Try fallback using git commands content, gitErr := downloadWorkflowContentViaGit(repo, path, ref, verbose) if gitErr != nil { - return nil, fmt.Errorf("failed to fetch file content via GitHub API and git: API error: %w, Git error: %v", err, gitErr) + return nil, fmt.Errorf("failed to fetch file content via GitHub API and git: API error: %w, Git error: %w", err, gitErr) } return content, nil } diff --git a/pkg/cli/enable.go b/pkg/cli/enable.go index 2c5f913bb6..ca8d87815c 100644 --- a/pkg/cli/enable.go +++ b/pkg/cli/enable.go @@ -84,7 +84,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st // Check if gh CLI is available if !isGHCLIAvailable() { - return fmt.Errorf("GitHub CLI (gh) is required but not available") + return errors.New("GitHub CLI (gh) is required but not available") } // Get the core set of workflows from markdown files in .github/workflows @@ -197,7 +197,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st } return errors.New(console.FormatErrorWithSuggestions( - fmt.Sprintf("workflows not found: %s", strings.Join(notFoundNames, ", ")), + "workflows not found: "+strings.Join(notFoundNames, ", "), suggestions, )) } diff --git a/pkg/cli/engine_secrets.go b/pkg/cli/engine_secrets.go index ddde7b8a32..fab901d00a 100644 --- a/pkg/cli/engine_secrets.go +++ b/pkg/cli/engine_secrets.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "strings" @@ -240,7 +241,7 @@ func promptForCopilotPATUnified(req SecretRequirement, config EngineSecretConfig fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Classic PATs (ghp_...) are not supported. You must use a fine-grained PAT (github_pat_...).")) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Please create a token at:") - fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf(" %s", req.KeyURL))) + fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" "+req.KeyURL)) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Configure the token with:") fmt.Fprintln(os.Stderr, " • Token name: Agentic Workflows Copilot") @@ -260,7 +261,7 @@ func promptForCopilotPATUnified(req SecretRequirement, config EngineSecretConfig Value(&token). Validate(func(s string) error { if len(s) < 10 { - return fmt.Errorf("token appears to be too short") + return errors.New("token appears to be too short") } return stringutil.ValidateCopilotPAT(s) }), @@ -291,8 +292,8 @@ func promptForSystemTokenUnified(req SecretRequirement, config EngineSecretConfi fmt.Fprintln(os.Stderr, "") fmt.Fprintf(os.Stderr, "%s requires a GitHub Personal Access Token (PAT).\n", req.Name) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("When needed: %s", req.WhenNeeded))) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Recommended scopes: %s", req.Description))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("When needed: "+req.WhenNeeded)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Recommended scopes: "+req.Description)) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Create a token at:") fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" https://github.com/settings/personal-access-tokens/new")) @@ -308,7 +309,7 @@ func promptForSystemTokenUnified(req SecretRequirement, config EngineSecretConfi Value(&token). Validate(func(s string) error { if len(s) < 10 { - return fmt.Errorf("token appears to be too short") + return errors.New("token appears to be too short") } return nil }), @@ -321,7 +322,7 @@ func promptForSystemTokenUnified(req SecretRequirement, config EngineSecretConfi // Store in environment for later use _ = os.Setenv(req.Name, token) - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("%s token received", req.Name))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(req.Name+" token received")) // Upload to repository if we have a repo slug if config.RepoSlug != "" { @@ -347,7 +348,7 @@ func promptForGenericAPIKeyUnified(req SecretRequirement, config EngineSecretCon fmt.Fprintln(os.Stderr, "") if req.KeyURL != "" { fmt.Fprintln(os.Stderr, "Get your API key from:") - fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf(" %s", req.KeyURL))) + fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" "+req.KeyURL)) fmt.Fprintln(os.Stderr, "") } @@ -361,7 +362,7 @@ func promptForGenericAPIKeyUnified(req SecretRequirement, config EngineSecretCon Value(&apiKey). Validate(func(s string) error { if len(s) < 10 { - return fmt.Errorf("API key appears to be too short") + return errors.New("API key appears to be too short") } return nil }), @@ -374,7 +375,7 @@ func promptForGenericAPIKeyUnified(req SecretRequirement, config EngineSecretCon // Store in environment for later use _ = os.Setenv(req.Name, apiKey) - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("%s API key received", label))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(label+" API key received")) // Upload to repository if we have a repo slug if config.RepoSlug != "" { @@ -402,7 +403,7 @@ func checkOptionalSecret(req SecretRequirement, config EngineSecretConfig) error return nil } - return fmt.Errorf("not configured") + return errors.New("not configured") } // uploadSecretToRepo uploads a secret to the repository if it doesn't already exist @@ -555,9 +556,9 @@ func displayMissingSecrets(requirements []SecretRequirement, repoSlug string, ex fmt.Fprintln(os.Stderr, console.FormatErrorMessage("Required secrets are missing:")) for _, req := range requiredMissing { fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Secret: %s", req.Name))) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("When needed: %s", req.WhenNeeded))) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Recommended scopes: %s", req.Description))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Secret: "+req.Name)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("When needed: "+req.WhenNeeded)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Recommended scopes: "+req.Description)) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf("gh aw secrets set %s --owner %s --repo %s", req.Name, cmdOwner, cmdRepo))) } } @@ -568,8 +569,8 @@ func displayMissingSecrets(requirements []SecretRequirement, repoSlug string, ex for _, req := range optionalMissing { fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Secret: %s (optional)", req.Name))) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("When needed: %s", req.WhenNeeded))) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Recommended scopes: %s", req.Description))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("When needed: "+req.WhenNeeded)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Recommended scopes: "+req.Description)) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf("gh aw secrets set %s --owner %s --repo %s", req.Name, cmdOwner, cmdRepo))) } } diff --git a/pkg/cli/error_formatting_test.go b/pkg/cli/error_formatting_test.go index f4eee9a5b8..7b2c6019ff 100644 --- a/pkg/cli/error_formatting_test.go +++ b/pkg/cli/error_formatting_test.go @@ -4,6 +4,7 @@ package cli import ( "bytes" + "errors" "fmt" "io" "os" @@ -20,7 +21,7 @@ import ( func TestCompileErrorFormatting(t *testing.T) { // Create a temporary test workflow with invalid frontmatter tempDir := t.TempDir() - invalidWorkflow := fmt.Sprintf("%s/invalid.md", tempDir) + invalidWorkflow := tempDir + "/invalid.md" // Write invalid workflow (missing closing frontmatter delimiter) err := os.WriteFile(invalidWorkflow, []byte(`--- @@ -163,7 +164,7 @@ func TestErrorMessagePatterns(t *testing.T) { { name: "wrapped error maintains context", errorCreator: func() error { - baseErr := fmt.Errorf("base error") + baseErr := errors.New("base error") return fmt.Errorf("failed to compile: %w", baseErr) }, shouldContain: []string{ diff --git a/pkg/cli/file_tracker.go b/pkg/cli/file_tracker.go index f1a968c809..584765d39e 100644 --- a/pkg/cli/file_tracker.go +++ b/pkg/cli/file_tracker.go @@ -89,7 +89,7 @@ func (ft *FileTracker) StageAllFiles(verbose bool) error { console.LogVerbose(verbose, fmt.Sprintf("Staging %d files...", len(allFiles))) if verbose { for _, file := range allFiles { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf(" - %s", file))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(" - "+file)) } } @@ -117,7 +117,7 @@ func (ft *FileTracker) RollbackCreatedFiles(verbose bool) error { var errors []string for _, file := range ft.CreatedFiles { - console.LogVerbose(verbose, fmt.Sprintf(" - Deleting %s", file)) + console.LogVerbose(verbose, " - Deleting "+file) if err := os.Remove(file); err != nil && !os.IsNotExist(err) { fileTrackerLog.Printf("Failed to delete %s: %v", file, err) errors = append(errors, fmt.Sprintf("failed to delete %s: %v", file, err)) @@ -142,7 +142,7 @@ func (ft *FileTracker) RollbackModifiedFiles(verbose bool) error { var errors []string for _, file := range ft.ModifiedFiles { - console.LogVerbose(verbose, fmt.Sprintf(" - Restoring %s", file)) + console.LogVerbose(verbose, " - Restoring "+file) // Restore original content if we have it if originalContent, exists := ft.OriginalContent[file]; exists { @@ -152,7 +152,7 @@ func (ft *FileTracker) RollbackModifiedFiles(verbose bool) error { } } else { if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("No original content stored for %s", file))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("No original content stored for "+file)) } } } diff --git a/pkg/cli/firewall_log.go b/pkg/cli/firewall_log.go index fd4269ae14..2da160a60a 100644 --- a/pkg/cli/firewall_log.go +++ b/pkg/cli/firewall_log.go @@ -339,7 +339,7 @@ func analyzeFirewallLogs(runDir string, verbose bool) (*FirewallAnalysis, error) logsDir := filepath.Join(runDir, name) firewallLogLog.Printf("Found firewall logs directory: %s", name) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found firewall logs directory: %s", name))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found firewall logs directory: "+name)) } return analyzeMultipleFirewallLogs(logsDir, verbose) } @@ -364,7 +364,7 @@ func analyzeFirewallLogs(runDir string, verbose bool) (*FirewallAnalysis, error) if len(firewallLogs) == 0 { firewallLogLog.Print("No firewall logs found") if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No firewall logs found in %s", runDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No firewall logs found in "+runDir)) } return nil, nil } @@ -372,7 +372,7 @@ func analyzeFirewallLogs(runDir string, verbose bool) (*FirewallAnalysis, error) // Parse the first firewall log file found firewallLogLog.Printf("Found %d firewall log files, analyzing first: %s", len(firewallLogs), filepath.Base(firewallLogs[0])) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Analyzing firewall log: %s", filepath.Base(firewallLogs[0])))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Analyzing firewall log: "+filepath.Base(firewallLogs[0]))) } return parseFirewallLog(firewallLogs[0], verbose) diff --git a/pkg/cli/fix_command.go b/pkg/cli/fix_command.go index 057e10b37c..966b15a0ca 100644 --- a/pkg/cli/fix_command.go +++ b/pkg/cli/fix_command.go @@ -331,12 +331,12 @@ func processWorkflowFileWithInfo(filePath string, codemods []Codemod, write bool return false, nil, fmt.Errorf("failed to write file: %w", err) } - fmt.Fprintf(os.Stderr, "%s\n", console.FormatSuccessMessage(fmt.Sprintf("āœ“ %s", fileName))) + fmt.Fprintf(os.Stderr, "%s\n", console.FormatSuccessMessage("āœ“ "+fileName)) for _, codemodName := range appliedCodemods { fmt.Fprintf(os.Stderr, " • %s\n", codemodName) } } else { - fmt.Fprintf(os.Stderr, "%s\n", console.FormatWarningMessage(fmt.Sprintf("⚠ %s", fileName))) + fmt.Fprintf(os.Stderr, "%s\n", console.FormatWarningMessage("⚠ "+fileName)) for _, codemodName := range appliedCodemods { fmt.Fprintf(os.Stderr, " • %s\n", codemodName) } diff --git a/pkg/cli/frontmatter_editor.go b/pkg/cli/frontmatter_editor.go index 94bb1a12f9..0550fdd6ca 100644 --- a/pkg/cli/frontmatter_editor.go +++ b/pkg/cli/frontmatter_editor.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "strings" @@ -30,8 +31,7 @@ func UpdateFieldInFrontmatter(content, fieldName, fieldValue string) (string, er frontmatterEditorLog.Printf("Using raw frontmatter lines for field update (%d lines)", len(result.FrontmatterLines)) // Look for existing field in the raw lines fieldUpdated := false - frontmatterLines := make([]string, len(result.FrontmatterLines)) - copy(frontmatterLines, result.FrontmatterLines) + frontmatterLines := append([]string(nil), result.FrontmatterLines...) // Try to find and update the field in place for i, line := range frontmatterLines { @@ -139,8 +139,7 @@ func addFieldToFrontmatter(content, fieldName, fieldValue string) (string, error } // Field doesn't exist, add it manually to preserve formatting - frontmatterLines := make([]string, len(result.FrontmatterLines)) - copy(frontmatterLines, result.FrontmatterLines) + frontmatterLines := append([]string(nil), result.FrontmatterLines...) // Add field at the end of the frontmatter, preserving original formatting newField := fmt.Sprintf("%s: %s", fieldName, fieldValue) @@ -302,7 +301,7 @@ func SetFieldInOnTrigger(content, fieldName, fieldValue string) (string, error) // Check if frontmatter exists if result.Frontmatter == nil { // No frontmatter, cannot set nested field without 'on' block - return "", fmt.Errorf("no frontmatter found, cannot set field in 'on' trigger") + return "", errors.New("no frontmatter found, cannot set field in 'on' trigger") } // Check if 'on' field exists @@ -334,14 +333,14 @@ func SetFieldInOnTrigger(content, fieldName, fieldValue string) (string, error) } // No frontmatter lines, cannot create 'on' block - return "", fmt.Errorf("no frontmatter found, cannot set field in 'on' trigger") + return "", errors.New("no frontmatter found, cannot set field in 'on' trigger") } // Check if 'on' is an object (map) _, isMap := onValue.(map[string]any) if !isMap { // 'on' is not a map (might be a string), cannot set field - return "", fmt.Errorf("'on' field is not an object, cannot set nested field") + return "", errors.New("'on' field is not an object, cannot set nested field") } // Work with raw frontmatter lines to preserve formatting @@ -451,5 +450,5 @@ func SetFieldInOnTrigger(content, fieldName, fieldValue string) (string, error) // This should rarely happen since we already checked for frontmatter existence frontmatterEditorLog.Printf("No raw frontmatter lines available") - return "", fmt.Errorf("no frontmatter lines available to modify") + return "", errors.New("no frontmatter lines available to modify") } diff --git a/pkg/cli/gateway_logs.go b/pkg/cli/gateway_logs.go index 831e6df002..1e3f5bee26 100644 --- a/pkg/cli/gateway_logs.go +++ b/pkg/cli/gateway_logs.go @@ -13,6 +13,7 @@ package cli import ( "bufio" "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -92,7 +93,7 @@ func parseGatewayLogs(logDir string, verbose bool) (*GatewayMetrics, error) { mcpLogsPath := filepath.Join(logDir, "mcp-logs", "gateway.jsonl") if _, err := os.Stat(mcpLogsPath); os.IsNotExist(err) { gatewayLogsLog.Printf("gateway.jsonl not found at: %s or %s", gatewayLogPath, mcpLogsPath) - return nil, fmt.Errorf("gateway.jsonl not found") + return nil, errors.New("gateway.jsonl not found") } gatewayLogPath = mcpLogsPath gatewayLogsLog.Printf("Found gateway.jsonl in mcp-logs subdirectory") @@ -406,7 +407,7 @@ func extractMCPToolUsageData(logDir string, verbose bool) (*MCPToolUsageData, er // Try mcp-logs subdirectory (new path after artifact download) mcpLogsPath := filepath.Join(logDir, "mcp-logs", "gateway.jsonl") if _, err := os.Stat(mcpLogsPath); os.IsNotExist(err) { - return nil, fmt.Errorf("gateway.jsonl not found") + return nil, errors.New("gateway.jsonl not found") } gatewayLogPath = mcpLogsPath } diff --git a/pkg/cli/generate_action_metadata_command.go b/pkg/cli/generate_action_metadata_command.go index fd01fb7250..b9b0fadde9 100644 --- a/pkg/cli/generate_action_metadata_command.go +++ b/pkg/cli/generate_action_metadata_command.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "path/filepath" @@ -69,7 +70,7 @@ func GenerateActionMetadataCommand() error { } content := string(contentBytes) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\nšŸ“¦ Processing: %s", filename))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nšŸ“¦ Processing: "+filename)) // Extract metadata metadata, err := extractActionMetadata(filename, content) @@ -92,14 +93,14 @@ func GenerateActionMetadataCommand() error { // Generate action.yml if err := generateActionYml(actionDir, metadata); err != nil { - fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("āœ— Failed to generate action.yml: %s", err.Error()))) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage("āœ— Failed to generate action.yml: "+err.Error())) continue } fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" āœ“ Generated action.yml")) // Generate README.md if err := generateReadme(actionDir, metadata); err != nil { - fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("āœ— Failed to generate README.md: %s", err.Error()))) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage("āœ— Failed to generate README.md: "+err.Error())) continue } fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" āœ“ Generated README.md")) @@ -115,7 +116,7 @@ func GenerateActionMetadataCommand() error { } if generatedCount == 0 { - return fmt.Errorf("no actions were generated") + return errors.New("no actions were generated") } fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("\n✨ Successfully generated %d action(s)", generatedCount))) @@ -207,7 +208,7 @@ func extractInputs(content string) []ActionInput { if !seen[inputName] { inputs = append(inputs, ActionInput{ Name: inputName, - Description: fmt.Sprintf("Input parameter: %s", inputName), + Description: "Input parameter: " + inputName, Required: false, Default: "", }) @@ -239,7 +240,7 @@ func extractOutputs(content string) []ActionOutput { if !seen[outputName] { outputs = append(outputs, ActionOutput{ Name: outputName, - Description: fmt.Sprintf("Output parameter: %s", outputName), + Description: "Output parameter: " + outputName, }) seen[outputName] = true } diff --git a/pkg/cli/git.go b/pkg/cli/git.go index 4ebca436c1..dd52b22e85 100644 --- a/pkg/cli/git.go +++ b/pkg/cli/git.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "os/exec" @@ -301,7 +302,7 @@ func getCurrentBranch() (string, error) { branch := strings.TrimSpace(string(output)) if branch == "" { gitLog.Print("Could not determine current branch") - return "", fmt.Errorf("could not determine current branch") + return "", errors.New("could not determine current branch") } gitLog.Printf("Current branch: %s", branch) @@ -310,7 +311,7 @@ func getCurrentBranch() (string, error) { // createAndSwitchBranch creates a new branch and switches to it func createAndSwitchBranch(branchName string, verbose bool) error { - console.LogVerbose(verbose, fmt.Sprintf("Creating and switching to branch: %s", branchName)) + console.LogVerbose(verbose, "Creating and switching to branch: "+branchName) cmd := exec.Command("git", "checkout", "-b", branchName) if err := cmd.Run(); err != nil { @@ -322,7 +323,7 @@ func createAndSwitchBranch(branchName string, verbose bool) error { // switchBranch switches to the specified branch func switchBranch(branchName string, verbose bool) error { - console.LogVerbose(verbose, fmt.Sprintf("Switching to branch: %s", branchName)) + console.LogVerbose(verbose, "Switching to branch: "+branchName) cmd := exec.Command("git", "checkout", branchName) if err := cmd.Run(); err != nil { @@ -334,7 +335,7 @@ func switchBranch(branchName string, verbose bool) error { // commitChanges commits all staged changes with the given message func commitChanges(message string, verbose bool) error { - console.LogVerbose(verbose, fmt.Sprintf("Committing changes with message: %s", message)) + console.LogVerbose(verbose, "Committing changes with message: "+message) cmd := exec.Command("git", "commit", "-m", message) if err := cmd.Run(); err != nil { @@ -346,7 +347,7 @@ func commitChanges(message string, verbose bool) error { // pushBranch pushes the specified branch to origin func pushBranch(branchName string, verbose bool) error { - console.LogVerbose(verbose, fmt.Sprintf("Pushing branch: %s", branchName)) + console.LogVerbose(verbose, "Pushing branch: "+branchName) cmd := exec.Command("git", "push", "-u", "origin", branchName) if err := cmd.Run(); err != nil { @@ -367,7 +368,7 @@ func checkCleanWorkingDirectory(verbose bool) error { } if len(strings.TrimSpace(string(output))) > 0 { - return fmt.Errorf("working directory has uncommitted changes, please commit or stash them first") + return errors.New("working directory has uncommitted changes, please commit or stash them first") } console.LogVerbose(verbose, "Working directory is clean") @@ -522,7 +523,7 @@ func checkWorkflowFileStatus(workflowPath string) (*WorkflowFileStatus, error) { gitLog.Printf("Upstream branch: %s", upstream) // Check if there are commits in the current branch that affect this file and aren't in upstream - cmd = exec.Command("git", "-C", gitRoot, "log", fmt.Sprintf("%s..HEAD", upstream), "--oneline", "--", relPath) + cmd = exec.Command("git", "-C", gitRoot, "log", upstream+"..HEAD", "--oneline", "--", relPath) output, err = cmd.Output() if err != nil { gitLog.Printf("Failed to check unpushed commits: %v", err) @@ -658,7 +659,7 @@ func getDefaultBranch() (string, error) { repoSlug := getRepositorySlugFromRemote() if repoSlug == "" { gitLog.Print("No remote repository configured, cannot determine default branch") - return "", fmt.Errorf("no remote repository configured") + return "", errors.New("no remote repository configured") } // Parse owner and repo from slug @@ -681,7 +682,7 @@ func getDefaultBranch() (string, error) { defaultBranch := strings.TrimSpace(string(output)) if defaultBranch == "" { gitLog.Print("Empty default branch returned") - return "", fmt.Errorf("could not determine default branch") + return "", errors.New("could not determine default branch") } gitLog.Printf("Default branch: %s", defaultBranch) @@ -705,7 +706,7 @@ func checkOnDefaultBranch(verbose bool) error { // If no remote is configured, fail the push operation if strings.Contains(err.Error(), "no remote repository configured") { gitLog.Print("No remote configured, cannot push") - return fmt.Errorf("--push requires a remote repository to be configured") + return errors.New("--push requires a remote repository to be configured") } return fmt.Errorf("failed to get default branch: %w", err) } @@ -718,7 +719,7 @@ func checkOnDefaultBranch(verbose bool) error { gitLog.Printf("On default branch: %s", currentBranch) if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("āœ“ On default branch: %s", currentBranch))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("āœ“ On default branch: "+currentBranch)) } return nil } @@ -758,7 +759,7 @@ func confirmPushOperation(verbose bool) error { if !confirmed { gitLog.Print("User declined push operation") - return fmt.Errorf("push operation cancelled by user") + return errors.New("push operation cancelled by user") } gitLog.Print("User confirmed push operation") diff --git a/pkg/cli/hash_command.go b/pkg/cli/hash_command.go index 72f7e9fd74..bc42896e02 100644 --- a/pkg/cli/hash_command.go +++ b/pkg/cli/hash_command.go @@ -46,7 +46,7 @@ func RunHashFrontmatter(workflowPath string) error { // Check if file exists if _, err := os.Stat(workflowPath); os.IsNotExist(err) { - fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("workflow file not found: %s", workflowPath))) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage("workflow file not found: "+workflowPath)) return fmt.Errorf("workflow file not found: %s", workflowPath) } diff --git a/pkg/cli/health_command.go b/pkg/cli/health_command.go index 2e37f06d7a..4085ffedda 100644 --- a/pkg/cli/health_command.go +++ b/pkg/cli/health_command.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "strconv" "strings" "time" @@ -101,7 +102,7 @@ func RunHealth(config HealthConfig) error { startDate := time.Now().AddDate(0, 0, -config.Days).Format("2006-01-02") if config.Verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Fetching workflow runs since %s", startDate))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Fetching workflow runs since "+startDate)) } // Fetch workflow runs from GitHub @@ -250,14 +251,14 @@ func displayDetailedHealth(runs []WorkflowRun, config HealthConfig) error { } details := []DetailedHealth{ - {"Total Runs", fmt.Sprintf("%d", health.TotalRuns)}, - {"Successful", fmt.Sprintf("%d", health.SuccessCount)}, - {"Failed", fmt.Sprintf("%d", health.FailureCount)}, + {"Total Runs", strconv.Itoa(health.TotalRuns)}, + {"Successful", strconv.Itoa(health.SuccessCount)}, + {"Failed", strconv.Itoa(health.FailureCount)}, {"Success Rate", health.DisplayRate}, {"Trend", health.Trend}, {"Avg Duration", health.DisplayDur}, {"Avg Tokens", health.DisplayTokens}, - {"Avg Cost", fmt.Sprintf("$%s", health.DisplayCost)}, + {"Avg Cost", "$" + health.DisplayCost}, {"Total Cost", fmt.Sprintf("$%.3f", health.TotalCost)}, } diff --git a/pkg/cli/health_metrics.go b/pkg/cli/health_metrics.go index cacdf2b1af..76959caf13 100644 --- a/pkg/cli/health_metrics.go +++ b/pkg/cli/health_metrics.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "strconv" "time" "github.com/github/gh-aw/pkg/logger" @@ -281,7 +282,7 @@ func formatTokens(tokens int) string { return "-" } if tokens < 1000 { - return fmt.Sprintf("%d", tokens) + return strconv.Itoa(tokens) } if tokens < 1000000 { return fmt.Sprintf("%.1fK", float64(tokens)/1000) diff --git a/pkg/cli/imports.go b/pkg/cli/imports.go index 171b6b0506..cd2f0124ba 100644 --- a/pkg/cli/imports.go +++ b/pkg/cli/imports.go @@ -194,7 +194,7 @@ func processIncludesWithWorkflowSpec(content string, workflow *WorkflowSpec, com // Skip if filePath is empty (e.g., section-only reference like "#Section") if filePath == "" { if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Skipping include with empty file path: %s", line))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Skipping include with empty file path: "+line)) } result.WriteString(line + "\n") continue @@ -347,7 +347,7 @@ func processIncludesInContent(content string, workflow *WorkflowSpec, commitSHA // Skip if filePath is empty (e.g., section-only reference like "#Section") if filePath == "" { if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Skipping include with empty file path: %s", line))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Skipping include with empty file path: "+line)) } result.WriteString(line + "\n") continue diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 6cc3ded600..fe5f1027f4 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "math/rand" "os" @@ -54,14 +55,14 @@ func InitRepository(opts InitOptions) error { // If creating a PR, check GitHub CLI is available if opts.CreatePR { if !isGHCLIAvailable() { - return fmt.Errorf("GitHub CLI (gh) is required for PR creation but not available") + return errors.New("GitHub CLI (gh) is required for PR creation but not available") } } // Ensure we're in a git repository if !isGitRepo() { initLog.Print("Not in a git repository, initialization failed") - return fmt.Errorf("not in a git repository") + return errors.New("not in a git repository") } initLog.Print("Verified git repository") diff --git a/pkg/cli/interactive.go b/pkg/cli/interactive.go index c7778d4762..e25e0f4f74 100644 --- a/pkg/cli/interactive.go +++ b/pkg/cli/interactive.go @@ -2,6 +2,7 @@ package cli import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -50,7 +51,7 @@ func CreateWorkflowInteractively(ctx context.Context, workflowName string, verbo // Assert this function is not running in automated unit tests if os.Getenv("GO_TEST_MODE") == "true" || os.Getenv("CI") != "" { - return fmt.Errorf("interactive workflow creation cannot be used in automated tests or CI environments") + return errors.New("interactive workflow creation cannot be used in automated tests or CI environments") } if verbose { @@ -148,7 +149,7 @@ func (b *InteractiveWorkflowBuilder) promptForConfiguration() error { } if len(detectedNetworks) > 0 { // Build a custom option that reflects what was auto-detected - label := fmt.Sprintf("detected - Auto-detected ecosystems: %s", strings.Join(detectedNetworks, ", ")) + label := "detected - Auto-detected ecosystems: " + strings.Join(detectedNetworks, ", ") networkOptions = append([]huh.Option[string]{huh.NewOption(label, strings.Join(append([]string{"defaults"}, detectedNetworks...), ","))}, networkOptions...) } @@ -270,7 +271,7 @@ func (b *InteractiveWorkflowBuilder) generateWorkflow(force bool) error { } if !overwrite { - return fmt.Errorf("workflow creation cancelled") + return errors.New("workflow creation cancelled") } } diff --git a/pkg/cli/log_aggregation.go b/pkg/cli/log_aggregation.go index 531ef3b181..f7d692ed69 100644 --- a/pkg/cli/log_aggregation.go +++ b/pkg/cli/log_aggregation.go @@ -51,7 +51,7 @@ func aggregateLogFiles[T LogAnalysis]( if len(files) == 0 { logAggregationLog.Printf("No log files found matching pattern '%s' in %s", globPattern, logsDir) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No log files found in %s", logsDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No log files found in "+logsDir)) } return zero, nil } @@ -72,7 +72,7 @@ func aggregateLogFiles[T LogAnalysis]( // Parse each file and aggregate results for _, file := range files { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Parsing %s", filepath.Base(file)))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Parsing "+filepath.Base(file))) } analysis, err := parser(file, verbose) diff --git a/pkg/cli/logs_cache.go b/pkg/cli/logs_cache.go index e9695d4371..a13f21d395 100644 --- a/pkg/cli/logs_cache.go +++ b/pkg/cli/logs_cache.go @@ -83,7 +83,7 @@ func saveRunSummary(outputDir string, summary *RunSummary, verbose bool) error { logsCacheLog.Printf("Successfully saved run summary cache: path=%s", summaryPath) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Saved run summary to %s", summaryPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Saved run summary to "+summaryPath)) } return nil diff --git a/pkg/cli/logs_display.go b/pkg/cli/logs_display.go index a829ca56e2..06df28487d 100644 --- a/pkg/cli/logs_display.go +++ b/pkg/cli/logs_display.go @@ -13,6 +13,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "time" @@ -73,16 +74,16 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) { // Format turns turnsStr := "" if run.Turns > 0 { - turnsStr = fmt.Sprintf("%d", run.Turns) + turnsStr = strconv.Itoa(run.Turns) totalTurns += run.Turns } // Format errors - errorsStr := fmt.Sprintf("%d", run.ErrorCount) + errorsStr := strconv.Itoa(run.ErrorCount) totalErrors += run.ErrorCount // Format warnings - warningsStr := fmt.Sprintf("%d", run.WarningCount) + warningsStr := strconv.Itoa(run.WarningCount) totalWarnings += run.WarningCount // Format missing tools @@ -100,7 +101,7 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) { } } else { // In normal mode, just show the count - missingToolsStr = fmt.Sprintf("%d", run.MissingToolCount) + missingToolsStr = strconv.Itoa(run.MissingToolCount) } totalMissingTools += run.MissingToolCount @@ -119,7 +120,7 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) { } } else { // In normal mode, just show the count - missingDataStr = fmt.Sprintf("%d", run.MissingDataCount) + missingDataStr = strconv.Itoa(run.MissingDataCount) } totalMissingData += run.MissingDataCount @@ -142,12 +143,12 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) { } } else { // In normal mode, just show the count - noopsStr = fmt.Sprintf("%d", run.NoopCount) + noopsStr = strconv.Itoa(run.NoopCount) } totalNoops += run.NoopCount // Format safe items count - safeItemsStr := fmt.Sprintf("%d", run.SafeItemsCount) + safeItemsStr := strconv.Itoa(run.SafeItemsCount) totalSafeItems += run.SafeItemsCount // Truncate workflow name if too long @@ -166,7 +167,7 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) { } row := []string{ - fmt.Sprintf("%d", run.DatabaseID), + strconv.FormatInt(run.DatabaseID, 10), workflowName, statusStr, durationStr, @@ -193,13 +194,13 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) { timeutil.FormatDuration(totalDuration), console.FormatNumber(totalTokens), fmt.Sprintf("%.3f", totalCost), - fmt.Sprintf("%d", totalTurns), - fmt.Sprintf("%d", totalErrors), - fmt.Sprintf("%d", totalWarnings), - fmt.Sprintf("%d", totalMissingTools), - fmt.Sprintf("%d", totalMissingData), - fmt.Sprintf("%d", totalNoops), - fmt.Sprintf("%d", totalSafeItems), + strconv.Itoa(totalTurns), + strconv.Itoa(totalErrors), + strconv.Itoa(totalWarnings), + strconv.Itoa(totalMissingTools), + strconv.Itoa(totalMissingData), + strconv.Itoa(totalNoops), + strconv.Itoa(totalSafeItems), "", "", } diff --git a/pkg/cli/logs_download.go b/pkg/cli/logs_download.go index 9b83905523..3319b11286 100644 --- a/pkg/cli/logs_download.go +++ b/pkg/cli/logs_download.go @@ -12,6 +12,7 @@ package cli import ( "archive/zip" + "errors" "fmt" "io" "os" @@ -304,7 +305,7 @@ func downloadWorkflowRunLogs(runID int64, outputDir string, verbose bool) error if err != nil { // Check for authentication errors if strings.Contains(err.Error(), "exit status 4") { - return fmt.Errorf("GitHub CLI authentication required. Run 'gh auth login' first") + return errors.New("GitHub CLI authentication required. Run 'gh auth login' first") } // If logs are not found or run has no logs, this is not a critical error if strings.Contains(string(output), "not found") || strings.Contains(err.Error(), "410") { @@ -333,7 +334,7 @@ func downloadWorkflowRunLogs(runID int64, outputDir string, verbose bool) error } if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Downloaded and extracted workflow run logs to %s", workflowLogsDir))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Downloaded and extracted workflow run logs to "+workflowLogsDir)) } return nil @@ -377,7 +378,7 @@ func extractZipFile(f *zip.File, destDir string, verbose bool) (extractErr error } if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Extracting: %s", cleanName))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Extracting: "+cleanName)) } // Create directory if it's a directory entry @@ -495,7 +496,7 @@ func downloadRunArtifacts(runID int64, outputDir string, verbose bool) error { return fmt.Errorf("failed to create run output directory: %w", err) } if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Created output directory %s", outputDir))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Created output directory "+outputDir)) } if verbose { @@ -533,7 +534,7 @@ func downloadRunArtifacts(runID int64, outputDir string, verbose bool) error { } // Check for authentication errors if strings.Contains(err.Error(), "exit status 4") { - return fmt.Errorf("GitHub CLI authentication required. Run 'gh auth login' first") + return errors.New("GitHub CLI authentication required. Run 'gh auth login' first") } return fmt.Errorf("failed to download artifacts for run %d: %w (output: %s)", runID, err, string(output)) } diff --git a/pkg/cli/logs_github_api.go b/pkg/cli/logs_github_api.go index 28ed1d76c3..ae3f527653 100644 --- a/pkg/cli/logs_github_api.go +++ b/pkg/cli/logs_github_api.go @@ -11,6 +11,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -53,7 +54,7 @@ func fetchJobStatuses(runID int64, verbose bool) (int, error) { var job JobInfo if err := json.Unmarshal([]byte(line), &job); err != nil { if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Failed to parse job info: %s", line))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Failed to parse job info: "+line)) } continue } @@ -98,7 +99,7 @@ func fetchJobDetails(runID int64, verbose bool) ([]JobInfoWithDuration, error) { var job JobInfo if err := json.Unmarshal([]byte(line), &job); err != nil { if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Failed to parse job info: %s", line))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Failed to parse job info: "+line)) } continue } @@ -182,7 +183,7 @@ func listWorkflowRunsWithPagination(opts ListWorkflowRunsOptions) ([]WorkflowRun } if opts.Verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Executing: gh %s", strings.Join(args, " ")))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Executing: gh "+strings.Join(args, " "))) } // Start spinner for network operation @@ -203,7 +204,8 @@ func listWorkflowRunsWithPagination(opts ListWorkflowRunsOptions) ([]WorkflowRun // Extract detailed error information including exit code var exitCode int - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode = exitErr.ExitCode() logsGitHubAPILog.Printf("gh run list command failed with exit code %d. Command: gh %v", exitCode, args) logsGitHubAPILog.Printf("combined output: %s", string(output)) @@ -235,7 +237,7 @@ func listWorkflowRunsWithPagination(opts ListWorkflowRunsOptions) ([]WorkflowRun strings.Contains(combinedMsg, "To use GitHub CLI in a GitHub Actions workflow") || strings.Contains(combinedMsg, "authentication required") || strings.Contains(outputMsg, "gh auth login") { - return nil, 0, fmt.Errorf("GitHub CLI authentication required. Run 'gh auth login' first") + return nil, 0, errors.New("GitHub CLI authentication required. Run 'gh auth login' first") } if len(output) > 0 { diff --git a/pkg/cli/logs_metrics.go b/pkg/cli/logs_metrics.go index 7101530976..3d7c129791 100644 --- a/pkg/cli/logs_metrics.go +++ b/pkg/cli/logs_metrics.go @@ -38,7 +38,7 @@ func extractLogMetrics(logDir string, verbose bool, workflowPath ...string) (Log logsMetricsLog.Printf("Extracting log metrics from: %s", logDir) var metrics LogMetrics if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Beginning metric extraction in %s", logDir))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Beginning metric extraction in "+logDir)) } // First check if this is a GitHub Copilot coding agent run (not Copilot CLI) @@ -66,7 +66,7 @@ func extractLogMetrics(logDir string, verbose bool, workflowPath ...string) (Log detectedEngine = engine logsMetricsLog.Printf("Detected engine: %s", engine.GetID()) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Detected engine from aw_info.json: %s", engine.GetID()))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Detected engine from aw_info.json: "+engine.GetID())) } } else { logsMetricsLog.Print("Failed to extract engine from aw_info.json") @@ -77,7 +77,7 @@ func extractLogMetrics(logDir string, verbose bool, workflowPath ...string) (Log } else { logsMetricsLog.Printf("No aw_info.json found at %s: %v", infoFilePath, err) if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("No aw_info.json found at %s", infoFilePath))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("No aw_info.json found at "+infoFilePath)) } } @@ -241,7 +241,7 @@ func extractMissingToolsFromRun(runDir string, run WorkflowRun, verbose bool) ([ if _, nestedErr := os.Stat(nested); nestedErr == nil { resolvedAgentOutputFile = nested if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("agent_output.json is a directory; using nested file %s", nested))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("agent_output.json is a directory; using nested file "+nested)) } } else if verbose { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("agent_output.json directory present but nested file missing: %v", nestedErr))) @@ -255,7 +255,7 @@ func extractMissingToolsFromRun(runDir string, run WorkflowRun, verbose bool) ([ if found, ok := findAgentOutputFile(runDir); ok { resolvedAgentOutputFile = found if verbose && found != agentOutputPath { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found agent_output.json at %s", found))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found agent_output.json at "+found)) } } } @@ -367,7 +367,7 @@ func extractNoopsFromRun(runDir string, run WorkflowRun, verbose bool) ([]NoopRe if _, nestedErr := os.Stat(nested); nestedErr == nil { resolvedAgentOutputFile = nested if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("agent_output.json is a directory; using nested file %s", nested))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("agent_output.json is a directory; using nested file "+nested)) } } else if verbose { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("agent_output.json directory present but nested file missing: %v", nestedErr))) @@ -381,7 +381,7 @@ func extractNoopsFromRun(runDir string, run WorkflowRun, verbose bool) ([]NoopRe if found, ok := findAgentOutputFile(runDir); ok { resolvedAgentOutputFile = found if verbose && found != agentOutputPath { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found agent_output.json at %s", found))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found agent_output.json at "+found)) } } } @@ -439,7 +439,7 @@ func extractNoopsFromRun(runDir string, run WorkflowRun, verbose bool) ([]NoopRe noops = append(noops, noop) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found noop entry: %s", item.Message))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found noop entry: "+item.Message)) } } } @@ -489,7 +489,7 @@ func extractMissingDataFromRun(runDir string, run WorkflowRun, verbose bool) ([] if _, nestedErr := os.Stat(nested); nestedErr == nil { resolvedAgentOutputFile = nested if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("agent_output.json is a directory; using nested file %s", nested))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("agent_output.json is a directory; using nested file "+nested)) } } else if verbose { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("agent_output.json directory present but nested file missing: %v", nestedErr))) @@ -503,7 +503,7 @@ func extractMissingDataFromRun(runDir string, run WorkflowRun, verbose bool) ([] if found, ok := findAgentOutputFile(runDir); ok { resolvedAgentOutputFile = found if verbose && found != agentOutputPath { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found agent_output.json at %s", found))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found agent_output.json at "+found)) } } } diff --git a/pkg/cli/logs_parallel_test.go b/pkg/cli/logs_parallel_test.go index 53e3341bb1..898560aa4d 100644 --- a/pkg/cli/logs_parallel_test.go +++ b/pkg/cli/logs_parallel_test.go @@ -4,6 +4,7 @@ package cli import ( "context" + "errors" "fmt" "os" "strings" @@ -295,7 +296,7 @@ func TestDownloadRunArtifactsParallelWithCancellation(t *testing.T) { if !result.Skipped { t.Errorf("Expected result for run %d to be skipped due to cancelled context", result.Run.DatabaseID) } - if result.Error != context.Canceled { + if !errors.Is(result.Error, context.Canceled) { t.Errorf("Expected error to be context.Canceled for run %d, got %v", result.Run.DatabaseID, result.Error) } } diff --git a/pkg/cli/logs_parsing_core.go b/pkg/cli/logs_parsing_core.go index fa67f99b70..6ad17dd773 100644 --- a/pkg/cli/logs_parsing_core.go +++ b/pkg/cli/logs_parsing_core.go @@ -49,7 +49,7 @@ func parseAwInfo(infoFilePath string, verbose bool) (*AwInfo, error) { // It's a directory - look for nested aw_info.json nestedPath := filepath.Join(cleanPath, "aw_info.json") if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("aw_info.json is a directory, trying nested file: %s", nestedPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("aw_info.json is a directory, trying nested file: "+nestedPath)) } data, err = os.ReadFile(nestedPath) } else { @@ -99,7 +99,7 @@ func extractEngineFromAwInfo(infoFilePath string, verbose bool) workflow.CodingA if err != nil { logsParsingCoreLog.Printf("Unknown engine: %s", info.EngineID) if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Unknown engine in aw_info.json: %s", info.EngineID))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Unknown engine in aw_info.json: "+info.EngineID)) } return nil } diff --git a/pkg/cli/logs_parsing_firewall.go b/pkg/cli/logs_parsing_firewall.go index 67030e08d9..27de75317f 100644 --- a/pkg/cli/logs_parsing_firewall.go +++ b/pkg/cli/logs_parsing_firewall.go @@ -62,7 +62,7 @@ func parseFirewallLogs(runDir string, verbose bool) error { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found firewall logs in %s", logsDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found firewall logs in "+logsDir)) } // Create a temporary directory for running the parser diff --git a/pkg/cli/logs_parsing_javascript.go b/pkg/cli/logs_parsing_javascript.go index cbf4d20e5b..3d10bf6396 100644 --- a/pkg/cli/logs_parsing_javascript.go +++ b/pkg/cli/logs_parsing_javascript.go @@ -52,7 +52,7 @@ func parseAgentLog(runDir string, engine workflow.CodingAgentEngine, verbose boo jsScript := workflow.GetLogParserScript(parserScriptName) if jsScript == "" { if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to get log parser script %s", parserScriptName))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Failed to get log parser script "+parserScriptName)) } return nil } diff --git a/pkg/cli/logs_report.go b/pkg/cli/logs_report.go index 17d93d6e72..0fd1dd251f 100644 --- a/pkg/cli/logs_report.go +++ b/pkg/cli/logs_report.go @@ -896,7 +896,7 @@ func writeSummaryFile(path string, data LogsData, verbose bool) error { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Wrote summary to %s", path))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Wrote summary to "+path)) } reportLog.Printf("Successfully wrote summary file: %s", path) diff --git a/pkg/cli/logs_utils.go b/pkg/cli/logs_utils.go index 596d685f5c..2b6019e9b7 100644 --- a/pkg/cli/logs_utils.go +++ b/pkg/cli/logs_utils.go @@ -40,7 +40,7 @@ func getAgenticWorkflowNames(verbose bool) ([]string, error) { for _, file := range files { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Reading workflow file: %s", file))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Reading workflow file: "+file)) } content, err := os.ReadFile(file) @@ -65,7 +65,7 @@ func getAgenticWorkflowNames(verbose bool) ([]string, error) { if name != "" { workflowNames = append(workflowNames, name) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found agentic workflow: %s", name))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found agentic workflow: "+name)) } break } diff --git a/pkg/cli/mcp_add.go b/pkg/cli/mcp_add.go index d9ecb918db..0a140801f6 100644 --- a/pkg/cli/mcp_add.go +++ b/pkg/cli/mcp_add.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "strings" @@ -92,7 +93,7 @@ func AddMCPTool(workflowFile string, mcpServerID string, registryURL string, tra if verbose { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Selected server: %s (Transport: %s)", selectedServer.Name, selectedServer.Transport))) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Will add as tool ID: %s", toolID))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Will add as tool ID: "+toolID)) } // Read the workflow file @@ -172,7 +173,7 @@ func createMCPToolConfig(server *MCPRegistryServerForProcessing, preferredTransp case "stdio", "http", "docker": transport = preferredTransport if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Using preferred transport: %s", transport))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Using preferred transport: "+transport)) } default: return nil, fmt.Errorf("unsupported transport type: %s (supported: stdio, http, docker)", preferredTransport) @@ -244,7 +245,7 @@ func createMCPToolConfig(server *MCPRegistryServerForProcessing, preferredTransp if url, hasURL := server.Config["url"]; hasURL { mcpSection["url"] = url } else { - return nil, fmt.Errorf("HTTP transport requires URL configuration") + return nil, errors.New("HTTP transport requires URL configuration") } // Add headers if present @@ -252,7 +253,7 @@ func createMCPToolConfig(server *MCPRegistryServerForProcessing, preferredTransp mcpSection["headers"] = headers } } else { - return nil, fmt.Errorf("HTTP transport requires configuration") + return nil, errors.New("HTTP transport requires configuration") } case "docker": @@ -261,7 +262,7 @@ func createMCPToolConfig(server *MCPRegistryServerForProcessing, preferredTransp if container, hasContainer := server.Config["container"]; hasContainer { mcpSection["container"] = container } else { - return nil, fmt.Errorf("docker transport requires container configuration") + return nil, errors.New("docker transport requires container configuration") } // Add environment variables if present @@ -269,7 +270,7 @@ func createMCPToolConfig(server *MCPRegistryServerForProcessing, preferredTransp mcpSection["env"] = convertToGitHubActionsEnv(env, server.EnvironmentVariables) } } else { - return nil, fmt.Errorf("docker transport requires configuration") + return nil, errors.New("docker transport requires configuration") } default: @@ -348,7 +349,7 @@ Registry URL defaults to: https://api.mcp.github.com/v0.1`, // If only workflow ID/file is provided, show error (need both workflow and server) if len(args) == 1 { - return fmt.Errorf("both workflow ID/file and server name are required to add an MCP tool\nUse 'gh aw mcp add' to list available servers") + return errors.New("both workflow ID/file and server name are required to add an MCP tool\nUse 'gh aw mcp add' to list available servers") } // If both arguments are provided, add the MCP tool diff --git a/pkg/cli/mcp_config_file.go b/pkg/cli/mcp_config_file.go index f6aaba67e1..5d652fbef8 100644 --- a/pkg/cli/mcp_config_file.go +++ b/pkg/cli/mcp_config_file.go @@ -100,7 +100,7 @@ func renderMCPConfigUpdateInstructions(filePath, serverName string, serverConfig fmt.Fprintln(os.Stderr) fmt.Fprintf(os.Stderr, "%s %s\n", "ℹ", - fmt.Sprintf("Existing file detected: %s", filePath)) + "Existing file detected: "+filePath) fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "To enable GitHub Copilot Agent MCP server integration, please add the following") fmt.Fprintln(os.Stderr, "to the \"servers\" section of your .vscode/mcp.json file:") diff --git a/pkg/cli/mcp_inspect.go b/pkg/cli/mcp_inspect.go index d68ea11fd6..a468121319 100644 --- a/pkg/cli/mcp_inspect.go +++ b/pkg/cli/mcp_inspect.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "os/exec" @@ -44,7 +45,7 @@ func InspectWorkflowMCP(workflowFile string, serverFilter string, toolFilter str } if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Inspecting MCP servers in: %s", workflowPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Inspecting MCP servers in: "+workflowPath)) } // Use the compiler to parse the workflow file @@ -55,7 +56,7 @@ func InspectWorkflowMCP(workflowFile string, serverFilter string, toolFilter str workflowData, err := compiler.ParseWorkflowFile(workflowPath) if err != nil { // Handle shared workflow error separately (not a fatal error for inspection) - if _, isSharedWorkflow := err.(*workflow.SharedWorkflowError); isSharedWorkflow { + if errors.As(err, new(*workflow.SharedWorkflowError)) { fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Cannot inspect shared/imported workflows directly - they must be imported by a main workflow")) return nil } @@ -216,7 +217,7 @@ The command will: // Validate that tool flag requires server flag if toolFilter != "" && serverFilter == "" { - return fmt.Errorf("--tool flag requires --server flag to be specified") + return errors.New("--tool flag requires --server flag to be specified") } // Handle spawn inspector flag diff --git a/pkg/cli/mcp_inspect_inspector.go b/pkg/cli/mcp_inspect_inspector.go index 9059376a2a..9967cacf1c 100644 --- a/pkg/cli/mcp_inspect_inspector.go +++ b/pkg/cli/mcp_inspect_inspector.go @@ -91,7 +91,7 @@ func spawnMCPInspector(workflowFile string, serverFilter string, verbose bool) e for _, config := range stdioServers { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Starting server: %s", config.Name))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Starting server: "+config.Name)) } // Create the command for the MCP server diff --git a/pkg/cli/mcp_inspect_list.go b/pkg/cli/mcp_inspect_list.go index 6df0a47887..f3f463b5e9 100644 --- a/pkg/cli/mcp_inspect_list.go +++ b/pkg/cli/mcp_inspect_list.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" @@ -34,7 +35,7 @@ func listWorkflowsWithMCP(workflowsDir string, verbose bool) error { if os.IsNotExist(err) { errMsg := "no .github/workflows directory found" fmt.Fprintln(os.Stderr, console.FormatErrorMessage(errMsg)) - return fmt.Errorf("no .github/workflows directory found") + return errors.New("no .github/workflows directory found") } return err } diff --git a/pkg/cli/mcp_inspect_mcp.go b/pkg/cli/mcp_inspect_mcp.go index bef7fb0d7c..247e0ab2d1 100644 --- a/pkg/cli/mcp_inspect_mcp.go +++ b/pkg/cli/mcp_inspect_mcp.go @@ -96,12 +96,12 @@ func buildConnectionString(config parser.MCPServerConfig) string { switch config.Type { case "stdio": if config.Container != "" { - return fmt.Sprintf("docker: %s", config.Container) + return "docker: " + config.Container } if len(config.Args) > 0 { return fmt.Sprintf("cmd: %s %s", config.Command, strings.Join(config.Args, " ")) } - return fmt.Sprintf("cmd: %s", config.Command) + return "cmd: " + config.Command case "http": return config.URL default: @@ -224,7 +224,7 @@ func connectStdioMCPServer(ctx context.Context, config parser.MCPServerConfig, v // connectHTTPMCPServer connects to an HTTP-based MCP server using the Go SDK func connectHTTPMCPServer(ctx context.Context, config parser.MCPServerConfig, verbose bool) (*parser.MCPServerInfo, error) { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Connecting to HTTP MCP server: %s", config.URL))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Connecting to HTTP MCP server: "+config.URL)) } // Create MCP client with logger for better debugging @@ -455,7 +455,7 @@ func displayDetailedToolInfo(info *parser.MCPServerInfo, toolName string) { isAllowed = true } - fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatSectionHeader(fmt.Sprintf("šŸ› ļø Tool Details: %s", foundTool.Name))) + fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatSectionHeader("šŸ› ļø Tool Details: "+foundTool.Name)) // Display basic information fmt.Fprintf(os.Stderr, "šŸ“‹ **Name:** %s\n", foundTool.Name) @@ -580,7 +580,7 @@ func displayToolAllowanceHint(info *parser.MCPServerInfo) { fmt.Fprintf(os.Stderr, "```\n") if len(blockedTools) > 3 { - fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage(fmt.Sprintf("šŸ“‹ All blocked tools: %s", strings.Join(blockedTools, ", ")))) + fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage("šŸ“‹ All blocked tools: "+strings.Join(blockedTools, ", "))) } } else if len(info.Config.Allowed) == 0 { // No explicit allowed list - all tools are allowed by default diff --git a/pkg/cli/mcp_inspect_safe_inputs_files.go b/pkg/cli/mcp_inspect_safe_inputs_files.go index fb905d013e..edbe6455a9 100644 --- a/pkg/cli/mcp_inspect_safe_inputs_files.go +++ b/pkg/cli/mcp_inspect_safe_inputs_files.go @@ -45,7 +45,7 @@ func writeSafeInputsFiles(dir string, safeInputsConfig *workflow.SafeInputsConfi return fmt.Errorf("failed to write %s: %w", jsFile.name, err) } if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Wrote %s", jsFile.name))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Wrote "+jsFile.name)) } } diff --git a/pkg/cli/mcp_inspect_safe_inputs_inspector.go b/pkg/cli/mcp_inspect_safe_inputs_inspector.go index eb0569608d..386c513579 100644 --- a/pkg/cli/mcp_inspect_safe_inputs_inspector.go +++ b/pkg/cli/mcp_inspect_safe_inputs_inspector.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "os/exec" @@ -39,7 +40,7 @@ func spawnSafeInputsInspector(workflowFile string, verbose bool) error { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Inspecting safe-inputs from: %s", workflowPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Inspecting safe-inputs from: "+workflowPath)) } // Use the workflow compiler to parse the file and resolve imports @@ -56,7 +57,7 @@ func spawnSafeInputsInspector(workflowFile string, verbose bool) error { // This includes both direct and imported safe-inputs configurations safeInputsConfig := workflowData.SafeInputs if safeInputsConfig == nil || len(safeInputsConfig.Tools) == 0 { - return fmt.Errorf("no safe-inputs configuration found in workflow") + return errors.New("no safe-inputs configuration found in workflow") } fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d safe-input tool(s) to configure", len(safeInputsConfig.Tools)))) @@ -73,7 +74,7 @@ func spawnSafeInputsInspector(workflowFile string, verbose bool) error { }() if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Created temporary directory: %s", tmpDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Created temporary directory: "+tmpDir)) } // Write safe-inputs files to temporary directory @@ -84,7 +85,7 @@ func spawnSafeInputsInspector(workflowFile string, verbose bool) error { // Find an available port for the HTTP server port := findAvailablePort(safeInputsStartPort, verbose) if port == 0 { - return fmt.Errorf("failed to find an available port for the HTTP server") + return errors.New("failed to find an available port for the HTTP server") } if verbose { @@ -111,7 +112,7 @@ func spawnSafeInputsInspector(workflowFile string, verbose bool) error { // Wait for the server to start up if !waitForServerReady(port, 5*time.Second, verbose) { - return fmt.Errorf("safe-inputs HTTP server failed to start within timeout") + return errors.New("safe-inputs HTTP server failed to start within timeout") } fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Safe-inputs HTTP server started successfully")) diff --git a/pkg/cli/mcp_inspect_safe_inputs_server.go b/pkg/cli/mcp_inspect_safe_inputs_server.go index 140bac3165..11ad0b7d88 100644 --- a/pkg/cli/mcp_inspect_safe_inputs_server.go +++ b/pkg/cli/mcp_inspect_safe_inputs_server.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "net" "net/http" @@ -120,7 +121,7 @@ func startSafeInputsServer(safeInputsConfig *workflow.SafeInputsConfig, verbose } if verbose { - if _, err := fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Created temporary directory: %s", tmpDir))); err != nil { + if _, err := fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Created temporary directory: "+tmpDir)); err != nil { mcpInspectLog.Printf("Warning: failed to write to stderr: %v", err) } } @@ -144,7 +145,7 @@ func startSafeInputsServer(safeInputsConfig *workflow.SafeInputsConfig, verbose } errMsg := "failed to find an available port for the HTTP server" fmt.Fprintln(os.Stderr, console.FormatErrorMessage(errMsg)) - return nil, nil, "", fmt.Errorf("failed to find an available port for the HTTP server") + return nil, nil, "", errors.New("failed to find an available port for the HTTP server") } if verbose { @@ -172,7 +173,7 @@ func startSafeInputsServer(safeInputsConfig *workflow.SafeInputsConfig, verbose if err := os.RemoveAll(tmpDir); err != nil && verbose { mcpInspectLog.Printf("Warning: failed to clean up temporary directory %s: %v", tmpDir, err) } - return nil, nil, "", fmt.Errorf("safe-inputs HTTP server failed to start within timeout") + return nil, nil, "", errors.New("safe-inputs HTTP server failed to start within timeout") } if verbose { diff --git a/pkg/cli/mcp_list.go b/pkg/cli/mcp_list.go index 0c57e04be9..8c4b20f598 100644 --- a/pkg/cli/mcp_list.go +++ b/pkg/cli/mcp_list.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "slices" + "strconv" "strings" "github.com/github/gh-aw/pkg/console" @@ -88,7 +89,7 @@ func ListWorkflowMCP(workflowFile string, verbose bool) error { } tableConfig := console.TableConfig{ - Title: fmt.Sprintf("MCP servers in %s", filepath.Base(workflowPath)), + Title: "MCP servers in " + filepath.Base(workflowPath), Headers: headers, Rows: rows, } @@ -112,7 +113,7 @@ func ListWorkflowMCP(workflowFile string, verbose bool) error { } tableConfig := console.TableConfig{ - Title: fmt.Sprintf("MCP servers in %s", filepath.Base(workflowPath)), + Title: "MCP servers in " + filepath.Base(workflowPath), Headers: headers, Rows: rows, } @@ -191,7 +192,7 @@ func listWorkflowsWithMCPServers(workflowsDir string, verbose bool) error { rows = append(rows, []string{ workflow.name, - fmt.Sprintf("%d", workflow.serverCount), + strconv.Itoa(workflow.serverCount), serverList, }) } @@ -209,7 +210,7 @@ func listWorkflowsWithMCPServers(workflowsDir string, verbose bool) error { for _, workflow := range workflowData { rows = append(rows, []string{ workflow.name, - fmt.Sprintf("%d", workflow.serverCount), + strconv.Itoa(workflow.serverCount), }) } diff --git a/pkg/cli/mcp_registry.go b/pkg/cli/mcp_registry.go index 5730315393..a81b88e9d8 100644 --- a/pkg/cli/mcp_registry.go +++ b/pkg/cli/mcp_registry.go @@ -70,7 +70,7 @@ func (c *MCPRegistryClient) SearchServers(query string) ([]MCPRegistryServerForP mcpRegistryLog.Printf("Searching MCP servers: query=%q", query) // Always use servers endpoint for listing all servers - searchURL := fmt.Sprintf("%s/servers", c.registryURL) + searchURL := c.registryURL + "/servers" // Create HTTP request with proper headers req, err := c.createRegistryRequest("GET", searchURL) @@ -264,7 +264,7 @@ func (c *MCPRegistryClient) GetServer(serverName string) (*MCPRegistryServerForP mcpRegistryLog.Printf("Getting MCP server: name=%s", serverName) // Use the servers endpoint and filter locally, just like SearchServers - serversURL := fmt.Sprintf("%s/servers", c.registryURL) + serversURL := c.registryURL + "/servers" // Create HTTP request with proper headers req, err := c.createRegistryRequest("GET", serversURL) diff --git a/pkg/cli/mcp_registry_improvements_test.go b/pkg/cli/mcp_registry_improvements_test.go index ae814d5b12..ec9ad57237 100644 --- a/pkg/cli/mcp_registry_improvements_test.go +++ b/pkg/cli/mcp_registry_improvements_test.go @@ -128,7 +128,7 @@ func TestMCPRegistryClient_FlexibleValidation(t *testing.T) { // Create a test server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) w.Write([]byte(response)) })) defer server.Close() diff --git a/pkg/cli/mcp_registry_list.go b/pkg/cli/mcp_registry_list.go index ff3167ebf5..8063efede4 100644 --- a/pkg/cli/mcp_registry_list.go +++ b/pkg/cli/mcp_registry_list.go @@ -18,7 +18,7 @@ func listAvailableServers(registryURL string, verbose bool) error { // Search for all servers (empty query) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Fetching available MCP servers from registry: %s", registryClient.registryURL))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Fetching available MCP servers from registry: "+registryClient.registryURL)) } servers, err := registryClient.SearchServers("") @@ -70,7 +70,7 @@ func listAvailableServers(registryURL string, verbose bool) error { // Create and render table tableConfig := console.TableConfig{ - Title: fmt.Sprintf("MCP registry: %s", registryClient.registryURL), + Title: "MCP registry: " + registryClient.registryURL, Headers: headers, Rows: rows, ShowTotal: true, diff --git a/pkg/cli/mcp_secrets.go b/pkg/cli/mcp_secrets.go index 4d3164e2aa..821a412fcb 100644 --- a/pkg/cli/mcp_secrets.go +++ b/pkg/cli/mcp_secrets.go @@ -66,7 +66,7 @@ func checkAndSuggestSecrets(toolConfig map[string]any, verbose bool) error { fmt.Fprintln(os.Stderr, console.FormatWarningMessage("The following secrets are required but not found in the repository:")) for _, secretName := range missingSecrets { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("To add %s secret:", secretName))) - fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf("gh secret set %s", secretName))) + fmt.Fprintln(os.Stderr, console.FormatCommandMessage("gh secret set "+secretName)) } } else if verbose { mcpSecretsLog.Print("All required secrets are available in repository") diff --git a/pkg/cli/mcp_server_command.go b/pkg/cli/mcp_server_command.go index 8489c95a41..62da18e461 100644 --- a/pkg/cli/mcp_server_command.go +++ b/pkg/cli/mcp_server_command.go @@ -89,7 +89,7 @@ func checkAndLogGHVersion() { // Extract just the first line for cleaner logging to stderr firstLine := strings.Split(versionOutput, "\n")[0] - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("gh CLI: %s", firstLine))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("gh CLI: "+firstLine)) } // runMCPServer starts the MCP server on stdio or HTTP transport @@ -104,7 +104,7 @@ func runMCPServer(port int, cmdPath string, validateActor bool) error { if actor != "" { mcpLog.Printf("Using actor: %s", actor) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Actor: %s", actor))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Actor: "+actor)) } else { mcpLog.Print("No actor specified (GITHUB_ACTOR environment variable)") if validateActor { @@ -135,7 +135,7 @@ func runMCPServer(port int, cmdPath string, validateActor bool) error { // Log current working directory if cwd, err := os.Getwd(); err == nil { mcpLog.Printf("Current working directory: %s", cwd) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Current working directory: %s", cwd))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Current working directory: "+cwd)) } else { mcpLog.Printf("WARNING: Failed to get current working directory: %v", err) fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to get current working directory: %v", err))) diff --git a/pkg/cli/mcp_server_helpers.go b/pkg/cli/mcp_server_helpers.go index 27e647bc62..7d684c0794 100644 --- a/pkg/cli/mcp_server_helpers.go +++ b/pkg/cli/mcp_server_helpers.go @@ -3,6 +3,7 @@ package cli import ( "context" "encoding/json" + "errors" "fmt" "os" "strings" @@ -60,7 +61,7 @@ func getRepository() (string, error) { repo = strings.TrimSpace(string(output)) if repo == "" { - return "", fmt.Errorf("repository not found") + return "", errors.New("repository not found") } mcpLog.Printf("Got repository from gh repo view: %s", repo) @@ -73,10 +74,10 @@ func getRepository() (string, error) { // Results are cached for 1 hour to avoid excessive API calls. func queryActorRole(ctx context.Context, actor string, repo string) (string, error) { if actor == "" { - return "", fmt.Errorf("actor not specified") + return "", errors.New("actor not specified") } if repo == "" { - return "", fmt.Errorf("repository not specified") + return "", errors.New("repository not specified") } // Check cache first diff --git a/pkg/cli/mcp_server_http.go b/pkg/cli/mcp_server_http.go index 16686ca6dd..417fc6fdba 100644 --- a/pkg/cli/mcp_server_http.go +++ b/pkg/cli/mcp_server_http.go @@ -82,7 +82,7 @@ func runHTTPServer(server *mcp.Server, port int) error { ReadHeaderTimeout: MCPServerHTTPTimeout, } - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Starting MCP server on http://localhost%s", addr))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Starting MCP server on http://localhost"+addr)) mcpLog.Printf("HTTP server listening on %s", addr) // Run the HTTP server diff --git a/pkg/cli/mcp_tools_privileged.go b/pkg/cli/mcp_tools_privileged.go index d9f97a80f3..cb5f53ba46 100644 --- a/pkg/cli/mcp_tools_privileged.go +++ b/pkg/cli/mcp_tools_privileged.go @@ -2,7 +2,7 @@ package cli import ( "context" - "fmt" + "errors" "os/exec" "strconv" "strings" @@ -171,7 +171,8 @@ return a schema description instead of the full output. Adjust the 'max_tokens' // Try to get stderr and exit code for detailed error reporting var stderr string var exitCode int - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { stderr = string(exitErr.Stderr) exitCode = exitErr.ExitCode() } @@ -192,7 +193,7 @@ return a schema description instead of the full output. Adjust the 'max_tokens' return nil, nil, &jsonrpc.Error{ Code: jsonrpc.CodeInternalError, - Message: fmt.Sprintf("failed to download workflow logs: %s", err.Error()), + Message: "failed to download workflow logs: " + err.Error(), Data: mcpErrorData(errorData), } } @@ -298,7 +299,8 @@ Returns JSON with the following structure: // Try to get stderr and exit code for detailed error reporting var stderr string var exitCode int - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { stderr = string(exitErr.Stderr) exitCode = exitErr.ExitCode() } @@ -317,7 +319,7 @@ Returns JSON with the following structure: return nil, nil, &jsonrpc.Error{ Code: jsonrpc.CodeInternalError, - Message: fmt.Sprintf("failed to audit workflow run: %s", err.Error()), + Message: "failed to audit workflow run: " + err.Error(), Data: mcpErrorData(errorData), } } diff --git a/pkg/cli/mcp_tools_readonly.go b/pkg/cli/mcp_tools_readonly.go index 297fb95424..9af39e2267 100644 --- a/pkg/cli/mcp_tools_readonly.go +++ b/pkg/cli/mcp_tools_readonly.go @@ -3,6 +3,7 @@ package cli import ( "context" "encoding/json" + "errors" "os/exec" "github.com/modelcontextprotocol/go-sdk/jsonrpc" @@ -213,7 +214,8 @@ Returns JSON array with validation results for each workflow: if len(outputStr) == 0 { // Try to get stderr for error details var stderr string - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { stderr = string(exitErr.Stderr) } return nil, nil, &jsonrpc.Error{ diff --git a/pkg/cli/mcp_validation.go b/pkg/cli/mcp_validation.go index 9aeeacad0f..0cb3bcaf28 100644 --- a/pkg/cli/mcp_validation.go +++ b/pkg/cli/mcp_validation.go @@ -5,6 +5,7 @@ package cli import ( "context" + "errors" "fmt" "os" "os/exec" @@ -60,7 +61,7 @@ func logAndValidateBinaryPath() (string, error) { if _, err := os.Stat(binaryPath); err != nil { if os.IsNotExist(err) { mcpValidationLog.Printf("ERROR: binary file does not exist at path: %s", binaryPath) - fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("ERROR: binary file does not exist at path: %s", binaryPath))) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage("ERROR: binary file does not exist at path: "+binaryPath)) return "", fmt.Errorf("binary file does not exist at path: %s", binaryPath) } mcpValidationLog.Printf("Warning: failed to stat binary file at %s: %v", binaryPath, err) @@ -70,7 +71,7 @@ func logAndValidateBinaryPath() (string, error) { // Log the binary path for debugging mcpValidationLog.Printf("gh-aw binary path: %s", binaryPath) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("gh-aw binary path: %s", binaryPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("gh-aw binary path: "+binaryPath)) return binaryPath, nil } @@ -126,7 +127,7 @@ func validateServerSecrets(config parser.MCPServerConfig, verbose bool, useActio } if strings.Contains(value, "GH_TOKEN") || strings.Contains(value, "GITHUB_TOKEN") || strings.Contains(value, "GITHUB_PERSONAL_ACCESS_TOKEN") { if token, err := parser.GetGitHubToken(); err != nil { - return fmt.Errorf("GitHub token not found in environment (set GH_TOKEN or GITHUB_TOKEN)") + return errors.New("GitHub token not found in environment (set GH_TOKEN or GITHUB_TOKEN)") } else { config.Env[key] = token } @@ -211,7 +212,7 @@ func validateServerSecrets(config parser.MCPServerConfig, verbose bool, useActio mcpValidationLog.Printf("Found %d missing secrets", len(missingSecrets)) fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("āš ļø %d required secret(s) not found:", len(missingSecrets)))) for _, secret := range missingSecrets { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf(" āœ— %s", secret.Name))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(" āœ— "+secret.Name)) } } @@ -257,7 +258,7 @@ func validateMCPServerConfiguration(cmdPath string) error { mcpValidationLog.Print("Status command timed out") errMsg := "status command timed out - this may indicate a configuration issue" fmt.Fprintln(os.Stderr, console.FormatErrorMessage(errMsg)) - return fmt.Errorf("status command timed out - this may indicate a configuration issue") + return errors.New("status command timed out - this may indicate a configuration issue") } mcpValidationLog.Printf("Status command failed: %v", err) diff --git a/pkg/cli/packages.go b/pkg/cli/packages.go index 9dd8083a64..c4ffc4e436 100644 --- a/pkg/cli/packages.go +++ b/pkg/cli/packages.go @@ -66,7 +66,7 @@ func collectLocalIncludeDependencies(content, packagePath string, verbose bool) seen := make(map[string]bool) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Collecting package dependencies from: %s", packagePath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Collecting package dependencies from: "+packagePath)) } err := collectLocalIncludeDependenciesRecursive(content, packagePath, &dependencies, seen, verbose) @@ -191,7 +191,7 @@ func copyIncludeDependenciesFromPackageWithForce(dependencies []IncludeDependenc } // Force is enabled, overwrite - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Overwriting existing include file: %s", dep.TargetPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Overwriting existing include file: "+dep.TargetPath)) } // Track the file based on whether it existed before (if tracker is available) diff --git a/pkg/cli/poutine.go b/pkg/cli/poutine.go index 1b6f194f23..b50e0e83c1 100644 --- a/pkg/cli/poutine.go +++ b/pkg/cli/poutine.go @@ -3,6 +3,7 @@ package cli import ( "bytes" "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -96,7 +97,7 @@ func runPoutineOnDirectory(workflowDir string, verbose bool, strict bool) error "docker", "run", "--rm", - "-v", fmt.Sprintf("%s:/workdir", gitRoot), + "-v", gitRoot+":/workdir", "-w", "/workdir", "ghcr.io/boostsecurityio/poutine:latest", "analyze_local", @@ -139,7 +140,8 @@ func runPoutineOnDirectory(workflowDir string, verbose bool, strict bool) error // Check if the error is due to findings or actual failure if err != nil { // poutine exits with non-zero code when findings are present - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode := exitErr.ExitCode() poutineLog.Printf("Poutine exited with code %d (warnings=%d)", exitCode, totalWarnings) // Exit code 1 typically indicates findings in the repository @@ -196,7 +198,7 @@ func runPoutineOnFile(lockFile string, verbose bool, strict bool) error { "docker", "run", "--rm", - "-v", fmt.Sprintf("%s:/workdir", gitRoot), + "-v", gitRoot+":/workdir", "-w", "/workdir", "ghcr.io/boostsecurityio/poutine:latest", "analyze_local", @@ -239,7 +241,8 @@ func runPoutineOnFile(lockFile string, verbose bool, strict bool) error { // Check if the error is due to findings or actual failure if err != nil { // poutine exits with non-zero code when findings are present - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode := exitErr.ExitCode() poutineLog.Printf("Poutine exited with code %d (warnings=%d)", exitCode, totalWarnings) // Exit code 1 typically indicates findings in the repository diff --git a/pkg/cli/pr_automerge.go b/pkg/cli/pr_automerge.go index 991f8d2d90..8a0468a9dd 100644 --- a/pkg/cli/pr_automerge.go +++ b/pkg/cli/pr_automerge.go @@ -2,8 +2,10 @@ package cli import ( "encoding/json" + "errors" "fmt" "os" + "strconv" "strings" "time" @@ -65,7 +67,7 @@ func AutoMergePullRequestsCreatedAfter(repoSlug string, createdAfter time.Time, if len(eligiblePRs) == 0 { prAutomergeLog.Print("No eligible PRs found for auto-merge") if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No pull requests found created after %s", createdAfter.Format(time.RFC3339)))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No pull requests found created after "+createdAfter.Format(time.RFC3339))) } return nil } @@ -82,7 +84,7 @@ func AutoMergePullRequestsCreatedAfter(repoSlug string, createdAfter time.Time, // Convert from draft to non-draft if necessary if pr.IsDraft { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Converting PR #%d from draft to ready for review", pr.Number))) - if output, err := workflow.RunGHCombined("Converting draft to ready...", "pr", "ready", fmt.Sprintf("%d", pr.Number), "--repo", repoSlug); err != nil { + if output, err := workflow.RunGHCombined("Converting draft to ready...", "pr", "ready", strconv.Itoa(pr.Number), "--repo", repoSlug); err != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to convert PR #%d from draft: %v (output: %s)", pr.Number, err, string(output)))) continue } @@ -96,7 +98,7 @@ func AutoMergePullRequestsCreatedAfter(repoSlug string, createdAfter time.Time, // Auto-merge the PR fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Auto-merging PR #%d", pr.Number))) - if output, err := workflow.RunGHCombined("Auto-merging pull request...", "pr", "merge", fmt.Sprintf("%d", pr.Number), "--repo", repoSlug, "--auto", "--squash"); err != nil { + if output, err := workflow.RunGHCombined("Auto-merging pull request...", "pr", "merge", strconv.Itoa(pr.Number), "--repo", repoSlug, "--auto", "--squash"); err != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to auto-merge PR #%d: %v (output: %s)", pr.Number, err, string(output)))) continue } @@ -137,11 +139,11 @@ func WaitForWorkflowCompletion(repoSlug, runID string, timeoutMinutes int, verbo if strings.Contains(status, `"conclusion":"success"`) { return PollSuccess, nil } else if strings.Contains(status, `"conclusion":"failure"`) { - return PollFailure, fmt.Errorf("workflow failed") + return PollFailure, errors.New("workflow failed") } else if strings.Contains(status, `"conclusion":"cancelled"`) { - return PollFailure, fmt.Errorf("workflow was cancelled") + return PollFailure, errors.New("workflow was cancelled") } else { - return PollFailure, fmt.Errorf("workflow completed with unknown conclusion") + return PollFailure, errors.New("workflow completed with unknown conclusion") } } diff --git a/pkg/cli/pr_command.go b/pkg/cli/pr_command.go index ee1f44c1a1..83a8d636b5 100644 --- a/pkg/cli/pr_command.go +++ b/pkg/cli/pr_command.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -158,7 +159,7 @@ func createForkIfNeeded(targetOwner, targetRepo string, verbose bool) (forkOwner checkCmd := workflow.ExecGH("repo", "view", forkRepoSpec, "--json", "name") if checkCmd.Run() == nil { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Fork already exists: %s", forkRepoSpec))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Fork already exists: "+forkRepoSpec)) } return currentUser, targetRepo, nil } @@ -170,7 +171,7 @@ func createForkIfNeeded(targetOwner, targetRepo string, verbose bool) (forkOwner } if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Successfully created fork: %s", forkRepoSpec))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Successfully created fork: "+forkRepoSpec)) } return currentUser, targetRepo, nil @@ -219,13 +220,13 @@ func createPatchFromPR(sourceOwner, sourceRepo string, prInfo *PRInfo, verbose b patchFile := filepath.Join(tempDir, "pr.patch") // Use gh pr diff command directly - this is the most reliable method - diffContent, err := workflow.RunGH("Fetching pull request diff...", "pr", "diff", fmt.Sprintf("%d", prInfo.Number), "--repo", fmt.Sprintf("%s/%s", sourceOwner, sourceRepo)) + diffContent, err := workflow.RunGH("Fetching pull request diff...", "pr", "diff", strconv.Itoa(prInfo.Number), "--repo", fmt.Sprintf("%s/%s", sourceOwner, sourceRepo)) if err != nil { return "", fmt.Errorf("failed to get PR diff: %w", err) } if len(diffContent) == 0 { - return "", fmt.Errorf("PR diff is empty") + return "", errors.New("PR diff is empty") } // Create proper mailbox format patch that git am expects @@ -294,7 +295,7 @@ func applyPatchToRepo(patchFile string, prInfo *PRInfo, targetOwner, targetRepo // Create a new branch for the transfer based on the updated default branch branchName := fmt.Sprintf("transfer-pr-%d-%d", prInfo.Number, time.Now().Unix()) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Creating branch: %s", branchName))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Creating branch: "+branchName)) } cmd = exec.Command("git", "checkout", "-b", branchName) @@ -312,7 +313,7 @@ func applyPatchToRepo(patchFile string, prInfo *PRInfo, targetOwner, targetRepo lines := strings.Split(string(patchContent), "\n") fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Patch file has %d lines", len(lines)))) if len(lines) > 0 { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("First line: %s", lines[0]))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("First line: "+lines[0])) } } } @@ -396,7 +397,7 @@ func applyPatchToRepo(patchFile string, prInfo *PRInfo, targetOwner, targetRepo commitMsg += "\n\n" + prInfo.Body } commitMsg += fmt.Sprintf("\n\nOriginal-PR: %s#%d", prInfo.SourceRepo, prInfo.Number) - commitMsg += fmt.Sprintf("\nOriginal-Author: %s", prInfo.AuthorLogin) + commitMsg += "\nOriginal-Author: " + prInfo.AuthorLogin cmd = exec.Command("git", "commit", "-m", commitMsg) if err := cmd.Run(); err != nil { @@ -441,7 +442,7 @@ func createTransferPR(targetOwner, targetRepo string, prInfo *PRInfo, branchName if checkRemoteCmd.Run() != nil { // Remote doesn't exist, add it if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Adding fork remote: %s", forkRepoURL))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Adding fork remote: "+forkRepoURL)) } addRemoteCmd := exec.Command("git", "remote", "add", remoteName, forkRepoURL) if err := addRemoteCmd.Run(); err != nil { @@ -461,7 +462,7 @@ func createTransferPR(targetOwner, targetRepo string, prInfo *PRInfo, branchName if err != nil { // Remote doesn't exist, add it if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Adding upstream remote: %s", targetRepoURL))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Adding upstream remote: "+targetRepoURL)) } addUpstreamCmd := exec.Command("git", "remote", "add", upstreamRemote, targetRepoURL) if err := addUpstreamCmd.Run(); err != nil { @@ -470,7 +471,7 @@ func createTransferPR(targetOwner, targetRepo string, prInfo *PRInfo, branchName } else { // Remote exists but points to wrong repo, update it if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Updating upstream remote: %s", targetRepoURL))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Updating upstream remote: "+targetRepoURL)) } setUpstreamCmd := exec.Command("git", "remote", "set-url", upstreamRemote, targetRepoURL) if err := setUpstreamCmd.Run(); err != nil { @@ -509,7 +510,7 @@ func createTransferPR(targetOwner, targetRepo string, prInfo *PRInfo, branchName prBody += "\n\n---\n\n" } prBody += fmt.Sprintf("**Transferred from:** %s#%d\n", prInfo.SourceRepo, prInfo.Number) - prBody += fmt.Sprintf("**Original Author:** @%s", prInfo.AuthorLogin) + prBody += "**Original Author:** @" + prInfo.AuthorLogin // Create the PR repoFlag := fmt.Sprintf("%s/%s", targetOwner, targetRepo) @@ -533,7 +534,7 @@ func createTransferPR(targetOwner, targetRepo string, prInfo *PRInfo, branchName if needsFork { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("PR created from fork %s/%s to %s/%s", forkOwner, forkRepo, targetOwner, targetRepo))) } - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("URL: %s", strings.TrimSpace(string(output))))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("URL: "+strings.TrimSpace(string(output)))) return nil } @@ -567,7 +568,7 @@ func transferPR(prURL, targetRepo string, verbose bool) error { } parts := strings.SplitN(repoSpec.RepoSlug, "/", 2) if len(parts) != 2 { - return fmt.Errorf("invalid target repository format, expected: owner/repo") + return errors.New("invalid target repository format, expected: owner/repo") } targetOwner, targetRepoName = parts[0], parts[1] } else { @@ -591,7 +592,7 @@ func transferPR(prURL, targetRepo string, verbose bool) error { // Check if source and target are the same if sourceOwner == targetOwner && sourceRepoName == targetRepoName { prLog.Print("Source and target repositories are the same - aborting") - return fmt.Errorf("source and target repositories cannot be the same") + return errors.New("source and target repositories cannot be the same") } // Ensure we're in the correct git repository @@ -703,7 +704,7 @@ func transferPR(prURL, targetRepo string, verbose bool) error { } else { // Using current repository as target if !isGitRepo() { - return fmt.Errorf("not in a git repository") + return errors.New("not in a git repository") } workingDir = "." } @@ -768,7 +769,7 @@ func transferPR(prURL, targetRepo string, verbose bool) error { // createPR creates a pull request using GitHub CLI and returns the PR number func createPR(branchName, title, body string, verbose bool) (int, string, error) { if verbose { - fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Creating PR: %s", title))) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage("Creating PR: "+title)) } // Get the current repository info to ensure PR is created in the correct repo @@ -794,7 +795,8 @@ func createPR(branchName, title, body string, verbose bool) (int, string, error) output, err := workflow.RunGH("Creating pull request...", "pr", "create", "--repo", repoSpec, "--title", title, "--body", body, "--head", branchName) if err != nil { // Try to get stderr for better error reporting - if exitError, ok := err.(*exec.ExitError); ok { + var exitError *exec.ExitError + if errors.As(err, &exitError) { return 0, "", fmt.Errorf("failed to create PR: %w\nOutput: %s\nError: %s", err, string(output), string(exitError.Stderr)) } return 0, "", fmt.Errorf("failed to create PR: %w", err) diff --git a/pkg/cli/preconditions.go b/pkg/cli/preconditions.go index 6a374bdf7d..5cc49ec342 100644 --- a/pkg/cli/preconditions.go +++ b/pkg/cli/preconditions.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "os" "strings" @@ -72,7 +73,7 @@ func checkGHAuthStatusShared(verbose bool) error { fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" gh auth login")) fmt.Fprintln(os.Stderr, "") - return fmt.Errorf("not authenticated with GitHub CLI") + return errors.New("not authenticated with GitHub CLI") } if verbose { @@ -95,7 +96,7 @@ func checkGitRepositoryShared(verbose bool) (string, error) { fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" git init")) fmt.Fprintln(os.Stderr, "") - return "", fmt.Errorf("not in a git repository") + return "", errors.New("not in a git repository") } // Try to get the repository slug @@ -112,7 +113,7 @@ func checkGitRepositoryShared(verbose bool) (string, error) { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Target repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Target repository: "+repoSlug)) } preconditionsLog.Printf("Target repository: %s", repoSlug) @@ -173,7 +174,7 @@ func checkActionsEnabledShared(repoSlug string, verbose bool) error { fmt.Fprintln(os.Stderr, "Note: For organization repositories, this setting may be controlled at the org level.") fmt.Fprintln(os.Stderr, "Contact an organization owner if you cannot change this setting.") fmt.Fprintln(os.Stderr, "") - return fmt.Errorf("") // Error already displayed above + return errors.New("") // Error already displayed above case "selected": // Selected actions - need to check if GitHub-owned actions are allowed if err := checkSelectedActionsPermissions(permissions.SelectedActionsURL, verbose); err != nil { @@ -225,7 +226,7 @@ func checkSelectedActionsPermissions(selectedActionsURL string, verbose bool) er fmt.Fprintln(os.Stderr, "Note: For organization repositories, this setting may be controlled at the org level.") fmt.Fprintln(os.Stderr, "Contact an organization owner if you cannot change this setting.") fmt.Fprintln(os.Stderr, "") - return fmt.Errorf("") // Error already displayed above + return errors.New("") // Error already displayed above } if verbose { @@ -275,7 +276,7 @@ func checkRepoVisibilityShared(repoSlug string) bool { preconditionsLog.Print("Checking repository visibility") // Use gh api to check repository visibility - output, err := workflow.RunGH("Checking repository visibility...", "api", fmt.Sprintf("/repos/%s", repoSlug), "--jq", ".visibility") + output, err := workflow.RunGH("Checking repository visibility...", "api", "/repos/"+repoSlug, "--jq", ".visibility") if err != nil { preconditionsLog.Printf("Could not check repository visibility: %v", err) // Default to public if we can't determine diff --git a/pkg/cli/project_command.go b/pkg/cli/project_command.go index d68e433381..5fe7300e4e 100644 --- a/pkg/cli/project_command.go +++ b/pkg/cli/project_command.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "os" "strconv" @@ -89,7 +90,7 @@ Examples: withProjectSetup, _ := cmd.Flags().GetBool("with-project-setup") if owner == "" { - return fmt.Errorf("--owner flag is required. Use '@me' for current user or specify org name") + return errors.New("--owner flag is required. Use '@me' for current user or specify org name") } config := ProjectConfig{ @@ -127,7 +128,7 @@ func RunProjectNew(ctx context.Context, config ProjectConfig) error { return fmt.Errorf("failed to get current user: %w", err) } ownerLogin = currentUser - console.LogVerbose(config.Verbose, fmt.Sprintf("Resolved @me to user: %s", ownerLogin)) + console.LogVerbose(config.Verbose, "Resolved @me to user: "+ownerLogin) } config.OwnerType = ownerType @@ -165,12 +166,12 @@ func RunProjectNew(ctx context.Context, config ProjectConfig) error { // Create views and fields if requested projectURL, ok := project["url"].(string) if !ok || projectURL == "" { - return fmt.Errorf("failed to get project URL from response") + return errors.New("failed to get project URL from response") } projectNumberFloat, ok := project["number"].(float64) if !ok || projectNumberFloat <= 0 { - return fmt.Errorf("failed to get valid project number from response") + return errors.New("failed to get valid project number from response") } projectNumber := int(projectNumberFloat) @@ -217,7 +218,7 @@ func getCurrentUser(ctx context.Context) (string, error) { login := strings.TrimSpace(string(output)) if login == "" { - return "", fmt.Errorf("failed to get current user login") + return "", errors.New("failed to get current user login") } return login, nil @@ -235,7 +236,7 @@ func validateOwner(ctx context.Context, ownerType, owner string, verbose bool) e query = fmt.Sprintf(`query { user(login: "%s") { id login } }`, escapeGraphQLString(owner)) } - _, err := workflow.RunGH("Validating owner...", "api", "graphql", "-f", fmt.Sprintf("query=%s", query)) + _, err := workflow.RunGH("Validating owner...", "api", "graphql", "-f", "query="+query) if err != nil { if ownerType == "org" { return fmt.Errorf("organization '%s' not found or not accessible", owner) @@ -270,24 +271,24 @@ func getOwnerNodeId(ctx context.Context, ownerType, owner string, verbose bool) jqPath = ".data.user.id" } - output, err := workflow.RunGH("Getting owner ID...", "api", "graphql", "-f", fmt.Sprintf("query=%s", query), "--jq", jqPath) + output, err := workflow.RunGH("Getting owner ID...", "api", "graphql", "-f", "query="+query, "--jq", jqPath) if err != nil { return "", fmt.Errorf("failed to get owner node ID: %w", err) } nodeId := strings.TrimSpace(string(output)) if nodeId == "" { - return "", fmt.Errorf("failed to get owner node ID from response") + return "", errors.New("failed to get owner node ID from response") } - console.LogVerbose(verbose, fmt.Sprintf("āœ“ Got node ID: %s", nodeId)) + console.LogVerbose(verbose, "āœ“ Got node ID: "+nodeId) return nodeId, nil } // createProject creates a GitHub Project V2 func createProject(ctx context.Context, ownerId, title string, verbose bool) (map[string]any, error) { projectLog.Printf("Creating project: ownerId=%s, title=%s", ownerId, title) - console.LogVerbose(verbose, fmt.Sprintf("Creating project with owner ID: %s", ownerId)) + console.LogVerbose(verbose, "Creating project with owner ID: "+ownerId) mutation := fmt.Sprintf(`mutation { createProjectV2(input: { ownerId: "%s", title: "%s" }) { @@ -300,11 +301,11 @@ func createProject(ctx context.Context, ownerId, title string, verbose bool) (ma } }`, ownerId, escapeGraphQLString(title)) - output, err := workflow.RunGH("Creating project...", "api", "graphql", "-f", fmt.Sprintf("query=%s", mutation)) + output, err := workflow.RunGH("Creating project...", "api", "graphql", "-f", "query="+mutation) if err != nil { // Check for permission errors if strings.Contains(err.Error(), "INSUFFICIENT_SCOPES") || strings.Contains(err.Error(), "NOT_FOUND") { - return nil, fmt.Errorf("insufficient permissions. You need a PAT with Projects access (classic: 'project' scope, fine-grained: Organization → Projects: Read & Write). Set GH_AW_PROJECT_GITHUB_TOKEN or configure gh CLI with a suitable token") + return nil, errors.New("insufficient permissions. You need a PAT with Projects access (classic: 'project' scope, fine-grained: Organization → Projects: Read & Write). Set GH_AW_PROJECT_GITHUB_TOKEN or configure gh CLI with a suitable token") } return nil, fmt.Errorf("GraphQL mutation failed: %w", err) } @@ -318,17 +319,17 @@ func createProject(ctx context.Context, ownerId, title string, verbose bool) (ma // Extract project data data, ok := response["data"].(map[string]any) if !ok { - return nil, fmt.Errorf("invalid response: missing 'data' field") + return nil, errors.New("invalid response: missing 'data' field") } createResult, ok := data["createProjectV2"].(map[string]any) if !ok { - return nil, fmt.Errorf("invalid response: missing 'createProjectV2' field") + return nil, errors.New("invalid response: missing 'createProjectV2' field") } project, ok := createResult["projectV2"].(map[string]any) if !ok { - return nil, fmt.Errorf("invalid response: missing 'projectV2' field") + return nil, errors.New("invalid response: missing 'projectV2' field") } console.LogVerbose(verbose, fmt.Sprintf("āœ“ Project created: #%v", project["number"])) @@ -338,7 +339,7 @@ func createProject(ctx context.Context, ownerId, title string, verbose bool) (ma // linkProjectToRepo links a project to a repository func linkProjectToRepo(ctx context.Context, projectId, repoSlug string, verbose bool) error { projectLog.Printf("Linking project %s to repository %s", projectId, repoSlug) - console.LogVerbose(verbose, fmt.Sprintf("Linking project to repository: %s", repoSlug)) + console.LogVerbose(verbose, "Linking project to repository: "+repoSlug) // Parse repo slug parts := strings.Split(repoSlug, "/") @@ -350,14 +351,14 @@ func linkProjectToRepo(ctx context.Context, projectId, repoSlug string, verbose // Get repository ID query := fmt.Sprintf(`query { repository(owner: "%s", name: "%s") { id } }`, escapeGraphQLString(repoOwner), escapeGraphQLString(repoName)) - output, err := workflow.RunGH("Getting repository ID...", "api", "graphql", "-f", fmt.Sprintf("query=%s", query), "--jq", ".data.repository.id") + output, err := workflow.RunGH("Getting repository ID...", "api", "graphql", "-f", "query="+query, "--jq", ".data.repository.id") if err != nil { return fmt.Errorf("repository '%s' not found: %w", repoSlug, err) } repoId := strings.TrimSpace(string(output)) if repoId == "" { - return fmt.Errorf("failed to get repository ID") + return errors.New("failed to get repository ID") } // Link project to repository @@ -369,12 +370,12 @@ func linkProjectToRepo(ctx context.Context, projectId, repoSlug string, verbose } }`, projectId, repoId) - _, err = workflow.RunGH("Linking project to repository...", "api", "graphql", "-f", fmt.Sprintf("query=%s", mutation)) + _, err = workflow.RunGH("Linking project to repository...", "api", "graphql", "-f", "query="+mutation) if err != nil { return fmt.Errorf("failed to link project to repository: %w", err) } - console.LogVerbose(verbose, fmt.Sprintf("āœ“ Linked project to repository %s", repoSlug)) + console.LogVerbose(verbose, "āœ“ Linked project to repository "+repoSlug) return nil } @@ -401,7 +402,7 @@ func parseProjectURL(projectURL string) (projectURLInfo, error) { // Expected format: https://github.com/orgs/myorg/projects/123 or https://github.com/users/myuser/projects/123 parts := strings.Split(projectURL, "/") if len(parts) < 6 { - return projectURLInfo{}, fmt.Errorf("invalid project URL format") + return projectURLInfo{}, errors.New("invalid project URL format") } var scope, ownerLogin, numberStr string @@ -417,7 +418,7 @@ func parseProjectURL(projectURL string) (projectURLInfo, error) { } if scope == "" { - return projectURLInfo{}, fmt.Errorf("invalid project URL: could not find orgs/users segment") + return projectURLInfo{}, errors.New("invalid project URL: could not find orgs/users segment") } projectNumber, err := strconv.Atoi(numberStr) @@ -516,7 +517,7 @@ func createStandardFields(ctx context.Context, projectURL string, projectNumber if err := createField(ctx, projectNumber, owner, field.name, field.dataType, field.options, verbose); err != nil { return fmt.Errorf("failed to create field '%s': %w", field.name, err) } - console.LogVerbose(verbose, fmt.Sprintf("Created field: %s", field.name)) + console.LogVerbose(verbose, "Created field: "+field.name) } return nil @@ -527,7 +528,7 @@ func createField(ctx context.Context, projectNumber int, owner, name, dataType s projectLog.Printf("Creating field: name=%s, type=%s", name, dataType) args := []string{ - "project", "field-create", fmt.Sprintf("%d", projectNumber), + "project", "field-create", strconv.Itoa(projectNumber), "--owner", owner, "--name", name, "--data-type", dataType, @@ -572,7 +573,7 @@ func ensureStatusOption(ctx context.Context, projectURL, optionName string, verb ) if !changed { - console.LogVerbose(verbose, fmt.Sprintf("Status option already present and ordered: %s", optionName)) + console.LogVerbose(verbose, "Status option already present and ordered: "+optionName) return nil } @@ -581,7 +582,7 @@ func ensureStatusOption(ctx context.Context, projectURL, optionName string, verb return fmt.Errorf("failed to update Status field: %w", err) } - console.LogVerbose(verbose, fmt.Sprintf("Status option added before 'Done': %s", optionName)) + console.LogVerbose(verbose, "Status option added before 'Done': "+optionName) return nil } @@ -645,14 +646,14 @@ func getStatusField(ctx context.Context, info projectURLInfo, verbose bool) (sta } // Get project ID - projectIDOutput, err := workflow.RunGH("Getting project info...", "api", "graphql", "-f", fmt.Sprintf("query=%s", query), "--jq", jqProjectID) + projectIDOutput, err := workflow.RunGH("Getting project info...", "api", "graphql", "-f", "query="+query, "--jq", jqProjectID) if err != nil { return statusFieldInfo{}, fmt.Errorf("failed to get project ID: %w", err) } projectID := strings.TrimSpace(string(projectIDOutput)) // Get fields - fieldsOutput, err := workflow.RunGH("Getting project fields...", "api", "graphql", "-f", fmt.Sprintf("query=%s", query), "--jq", jqFields) + fieldsOutput, err := workflow.RunGH("Getting project fields...", "api", "graphql", "-f", "query="+query, "--jq", jqFields) if err != nil { return statusFieldInfo{}, fmt.Errorf("failed to get project fields: %w", err) } @@ -695,7 +696,7 @@ func getStatusField(ctx context.Context, info projectURLInfo, verbose bool) (sta } } - return statusFieldInfo{}, fmt.Errorf("status field not found in project") + return statusFieldInfo{}, errors.New("status field not found in project") } // getString safely extracts a string value from a map diff --git a/pkg/cli/redacted_domains.go b/pkg/cli/redacted_domains.go index e43b73f417..2cc100fc54 100644 --- a/pkg/cli/redacted_domains.go +++ b/pkg/cli/redacted_domains.go @@ -2,6 +2,7 @@ package cli import ( "bufio" + "errors" "fmt" "os" "path/filepath" @@ -126,7 +127,7 @@ func analyzeRedactedDomains(runDir string, verbose bool) (*RedactedDomainsAnalys } if !info.IsDir() && info.Name() == "redacted-urls.log" { foundPath = path - return fmt.Errorf("found") // Stop walking + return errors.New("found") // Stop walking } return nil }) @@ -134,7 +135,7 @@ func analyzeRedactedDomains(runDir string, verbose bool) (*RedactedDomainsAnalys if foundPath != "" { redactedDomainsLog.Printf("Found redacted-urls.log via recursive search: %s", foundPath) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found redacted-urls.log at %s", foundPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found redacted-urls.log at "+foundPath)) } return parseRedactedDomainsLog(foundPath, verbose) } @@ -142,7 +143,7 @@ func analyzeRedactedDomains(runDir string, verbose bool) (*RedactedDomainsAnalys // No redacted domains log found - this is not an error, just means no URLs were redacted redactedDomainsLog.Print("No redacted-urls.log found") if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No redacted-urls.log found in %s", runDir))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No redacted-urls.log found in "+runDir)) } return nil, nil } diff --git a/pkg/cli/remote_workflow.go b/pkg/cli/remote_workflow.go index 1d49dfcdad..061b594891 100644 --- a/pkg/cli/remote_workflow.go +++ b/pkg/cli/remote_workflow.go @@ -2,6 +2,7 @@ package cli import ( "bufio" + "errors" "fmt" "os" "path/filepath" @@ -45,7 +46,7 @@ func FetchWorkflowFromSource(spec *WorkflowSpec, verbose bool) (*FetchedWorkflow // fetchLocalWorkflow reads a workflow file from the local filesystem func fetchLocalWorkflow(spec *WorkflowSpec, verbose bool) (*FetchedWorkflow, error) { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Reading local workflow: %s", spec.WorkflowPath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Reading local workflow: "+spec.WorkflowPath)) } content, err := os.ReadFile(spec.WorkflowPath) @@ -94,7 +95,7 @@ func fetchRemoteWorkflow(spec *WorkflowSpec, verbose bool) (*FetchedWorkflow, er } else { remoteWorkflowLog.Printf("Resolved ref %s to SHA: %s", ref, commitSHA) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Resolved to commit: %s", commitSHA[:7]))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Resolved to commit: "+commitSHA[:7])) } } @@ -183,7 +184,7 @@ func FetchIncludeFromSource(includePath string, baseSpec *WorkflowSpec, verbose // Parse path: owner/repo/path/to/file.md slashParts := strings.Split(pathPart, "/") if len(slashParts) < 3 { - return nil, section, fmt.Errorf("invalid workflowspec: must be owner/repo/path[@ref]") + return nil, section, errors.New("invalid workflowspec: must be owner/repo/path[@ref]") } owner := slashParts[0] @@ -279,7 +280,7 @@ func fetchAndSaveRemoteIncludes(content string, spec *WorkflowSpec, targetDir st if err != nil { if isOptional { if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Optional include not found: %s", includePath))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Optional include not found: "+includePath)) } continue } @@ -312,7 +313,7 @@ func fetchAndSaveRemoteIncludes(content string, spec *WorkflowSpec, targetDir st fileExists = true if !force { if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Include file already exists, skipping: %s", targetPath))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Include file already exists, skipping: "+targetPath)) } continue } @@ -324,7 +325,7 @@ func fetchAndSaveRemoteIncludes(content string, spec *WorkflowSpec, targetDir st } if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Fetched include: %s", targetPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Fetched include: "+targetPath)) } // Track the file diff --git a/pkg/cli/remove_command.go b/pkg/cli/remove_command.go index b8dce7f394..d6a59ac123 100644 --- a/pkg/cli/remove_command.go +++ b/pkg/cli/remove_command.go @@ -76,7 +76,7 @@ func RemoveWorkflows(pattern string, keepOrphans bool) error { if len(filesToRemove) == 0 { removeLog.Printf("No workflows matched pattern: %q", pattern) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No workflows found matching pattern: %s", pattern))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No workflows found matching pattern: "+pattern)) return nil } @@ -138,7 +138,7 @@ func RemoveWorkflows(pattern string, keepOrphans bool) error { if err := os.Remove(file); err != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to remove %s: %v", file, err))) } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed: %s", filepath.Base(file)))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed: "+filepath.Base(file))) removedFiles = append(removedFiles, file) } @@ -148,7 +148,7 @@ func RemoveWorkflows(pattern string, keepOrphans bool) error { if err := os.Remove(lockFile); err != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to remove %s: %v", lockFile, err))) } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed: %s", filepath.Base(lockFile)))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed: "+filepath.Base(lockFile))) } } } @@ -248,7 +248,7 @@ func cleanupOrphanedIncludes(verbose bool) error { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to remove orphaned include %s: %v", include, err))) } } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed orphaned include: %s", include))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed orphaned include: "+include)) } } } @@ -374,7 +374,7 @@ func cleanupAllIncludes(verbose bool) error { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to remove include %s: %v", relPath, err))) } } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed include: %s", relPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed include: "+relPath)) } } } diff --git a/pkg/cli/resolver.go b/pkg/cli/resolver.go index 6730cb0d19..70643b294d 100644 --- a/pkg/cli/resolver.go +++ b/pkg/cli/resolver.go @@ -57,7 +57,7 @@ func ResolveWorkflowPath(workflowFile string) (string, error) { } return "", errors.New(console.FormatErrorWithSuggestions( - fmt.Sprintf("workflow file not found: %s", workflowPath), + "workflow file not found: "+workflowPath, suggestions, )) } diff --git a/pkg/cli/run_interactive.go b/pkg/cli/run_interactive.go index 6773cf4f24..3012ee58b2 100644 --- a/pkg/cli/run_interactive.go +++ b/pkg/cli/run_interactive.go @@ -2,6 +2,7 @@ package cli import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -31,7 +32,7 @@ func RunWorkflowInteractively(ctx context.Context, verbose bool, repoOverride st // Check if running in CI environment if IsRunningInCI() { - return fmt.Errorf("interactive mode cannot be used in CI environments") + return errors.New("interactive mode cannot be used in CI environments") } if verbose { @@ -45,7 +46,7 @@ func RunWorkflowInteractively(ctx context.Context, verbose bool, repoOverride st } if len(workflows) == 0 { - return fmt.Errorf("no runnable workflows found. Workflows must have 'workflow_dispatch' trigger") + return errors.New("no runnable workflows found. Workflows must have 'workflow_dispatch' trigger") } // Step 2: Let user select a workflow @@ -74,7 +75,7 @@ func RunWorkflowInteractively(ctx context.Context, verbose bool, repoOverride st // Step 6: Build command string for display cmdStr := buildCommandString(selectedWorkflow.Name, inputValues, repoOverride, refOverride, autoMergePRs, push, engineOverride) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nRunning workflow...")) - fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf("Equivalent command: %s", cmdStr))) + fmt.Fprintln(os.Stderr, console.FormatCommandMessage("Equivalent command: "+cmdStr)) fmt.Fprintln(os.Stderr, "") // Step 7: Execute the workflow @@ -234,7 +235,7 @@ func selectWorkflowNonInteractive(workflows []WorkflowOption) (*WorkflowOption, // showWorkflowInfo displays information about the selected workflow func showWorkflowInfo(wf *WorkflowOption) { fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow: %s", wf.Name))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Workflow: "+wf.Name)) if len(wf.Inputs) > 0 { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nWorkflow Inputs:")) @@ -245,7 +246,7 @@ func showWorkflowInfo(wf *WorkflowOption) { } desc := "" if input.Description != "" { - desc = fmt.Sprintf(" - %s", input.Description) + desc = " - " + input.Description } defaultVal := "" if input.Default != "" { @@ -303,7 +304,7 @@ func collectInputsWithMap(inputs map[string]*workflow.InputDefinition) ([]string if inputDef.Required { field = field.Validate(func(s string) error { if s == "" { - return fmt.Errorf("this input is required") + return errors.New("this input is required") } return nil }) @@ -413,7 +414,7 @@ func RunSpecificWorkflowInteractively(ctx context.Context, workflowName string, // Build command string for display cmdStr := buildCommandString(workflowName, inputValues, repoOverride, refOverride, autoMergePRs, push, engineOverride) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nRunning workflow...")) - fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf("Equivalent command: %s", cmdStr))) + fmt.Fprintln(os.Stderr, console.FormatCommandMessage("Equivalent command: "+cmdStr)) fmt.Fprintln(os.Stderr, "") // Execute the workflow diff --git a/pkg/cli/run_push.go b/pkg/cli/run_push.go index 9ee88e433f..d90f3743cd 100644 --- a/pkg/cli/run_push.go +++ b/pkg/cli/run_push.go @@ -2,6 +2,7 @@ package cli import ( "context" + "errors" "fmt" "os" "os/exec" @@ -101,7 +102,7 @@ func collectWorkflowFiles(ctx context.Context, workflowPath string, verbose bool runPushLog.Printf("Added lock file: %s", lockFilePath) } else if verbose { runPushLog.Printf("Lock file not found after compilation: %s", lockFilePath) - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Lock file not found after compilation: %s", lockFilePath))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Lock file not found after compilation: "+lockFilePath)) } // Collect transitive closure of imported files @@ -307,7 +308,7 @@ func collectImports(workflowPath string, files map[string]bool, visited map[stri if resolvedPath == "" { runPushLog.Printf("Could not resolve import path: %s", importPath) if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Could not resolve import: %s", importPath))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Could not resolve import: "+importPath)) } continue } @@ -327,7 +328,7 @@ func collectImports(workflowPath string, files map[string]bool, visited map[stri if _, err := os.Stat(absImportPath); err != nil { runPushLog.Printf("Import file not found: %s (error: %v)", absImportPath, err) if verbose { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Import file not found: %s", absImportPath))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Import file not found: "+absImportPath)) } continue } @@ -459,7 +460,7 @@ func pushWorkflowFiles(workflowName string, files []string, refOverride string, runPushLog.Printf("Current branch matches --ref value: %s", currentBranch) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Verified current branch matches --ref: %s", currentBranch))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Verified current branch matches --ref: "+currentBranch)) } } @@ -528,12 +529,12 @@ func pushWorkflowFiles(workflowName string, files []string, refOverride string, fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please commit or unstage these files before using --push")) fmt.Fprintln(os.Stderr, "") - return fmt.Errorf("git has staged files not part of workflow - commit or unstage them before using --push") + return errors.New("git has staged files not part of workflow - commit or unstage them before using --push") } runPushLog.Printf("No extra staged files detected - all staged files are part of our workflow") // Create commit message - commitMessage := fmt.Sprintf("Updated agentic workflow %s", workflowName) + commitMessage := "Updated agentic workflow " + workflowName runPushLog.Printf("Creating commit with message: %s", commitMessage) // Show what will be committed and ask for confirmation using console helper @@ -560,7 +561,7 @@ func pushWorkflowFiles(workflowName string, files []string, refOverride string, if !confirmed { runPushLog.Print("Push cancelled by user") - return fmt.Errorf("push cancelled by user") + return errors.New("push cancelled by user") } runPushLog.Printf("User confirmed - proceeding with commit and push") diff --git a/pkg/cli/run_workflow_execution.go b/pkg/cli/run_workflow_execution.go index 695fd2e1ab..5d31c484ab 100644 --- a/pkg/cli/run_workflow_execution.go +++ b/pkg/cli/run_workflow_execution.go @@ -2,6 +2,7 @@ package cli import ( "context" + "errors" "fmt" "os" "os/exec" @@ -47,7 +48,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO } if workflowIdOrName == "" { - return fmt.Errorf("workflow name or ID is required") + return errors.New("workflow name or ID is required") } // Validate input format early before attempting workflow validation @@ -63,12 +64,12 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO } if opts.Verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Running workflow on GitHub Actions: %s", workflowIdOrName))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Running workflow on GitHub Actions: "+workflowIdOrName)) } // Check if gh CLI is available if !isGHCLIAvailable() { - return fmt.Errorf("GitHub CLI (gh) is required but not available") + return errors.New("GitHub CLI (gh) is required but not available") } // Validate workflow exists and is runnable @@ -157,7 +158,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO executionLog.Printf("Failed to enable workflow %s: %v", workflowIdOrName, err) return fmt.Errorf("failed to enable workflow '%s': %w", workflowIdOrName, err) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Enabled workflow: %s", workflowIdOrName))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Enabled workflow: "+workflowIdOrName)) } else { executionLog.Printf("Workflow %s is already enabled (state=%s)", workflowIdOrName, wf.State) } @@ -201,7 +202,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO // Recompile workflow if engine override is provided (only for local workflows) if opts.EngineOverride != "" && opts.RepoOverride == "" { if opts.Verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Recompiling workflow with engine override: %s", opts.EngineOverride))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Recompiling workflow with engine override: "+opts.EngineOverride)) } workflowMarkdownPath := stringutil.LockFileToMarkdown(lockFilePath) @@ -224,7 +225,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO } if opts.Verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Successfully recompiled workflow with engine: %s", opts.EngineOverride))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Successfully recompiled workflow with engine: "+opts.EngineOverride)) } } else if opts.EngineOverride != "" && opts.RepoOverride != "" { if opts.Verbose { @@ -233,7 +234,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO } if opts.Verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Using lock file: %s", lockFileName))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Using lock file: "+lockFileName)) } // Check for missing or outdated lock files (when not using --push) @@ -254,7 +255,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO if opts.Push { // Only valid for local workflows if opts.RepoOverride != "" { - return fmt.Errorf("--push flag is only supported for local workflows, not remote repositories") + return errors.New("--push flag is only supported for local workflows, not remote repositories") } if opts.Verbose { @@ -363,7 +364,8 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO if err != nil { // If there's an error, try to get stderr for better error reporting var stderrOutput string - if exitError, ok := err.(*exec.ExitError); ok { + var exitError *exec.ExitError + if errors.As(err, &exitError) { stderrOutput = string(exitError.Stderr) fmt.Fprintf(os.Stderr, "%s", exitError.Stderr) } @@ -378,7 +380,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO if isRunningInCodespace() && is403PermissionError(errorMsg) { // Show specialized error message for codespace users fmt.Fprint(os.Stderr, getCodespacePermissionErrorMessage()) - return fmt.Errorf("failed to run workflow on GitHub Actions: permission denied (403)") + return errors.New("failed to run workflow on GitHub Actions: permission denied (403)") } return fmt.Errorf("failed to run workflow on GitHub Actions: %w", err) @@ -390,14 +392,14 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO fmt.Fprintln(os.Stderr, console.FormatInfoMessage(output)) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Successfully triggered workflow: %s", lockFileName))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Successfully triggered workflow: "+lockFileName)) executionLog.Printf("Workflow triggered successfully: %s", lockFileName) // Try to get the latest run for this workflow to show a direct link // Add a delay to allow GitHub Actions time to register the new workflow run runInfo, runErr := getLatestWorkflowRunWithRetry(lockFileName, opts.RepoOverride, opts.Verbose) if runErr == nil && runInfo.URL != "" { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("šŸ”— View workflow run: %s", runInfo.URL))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("šŸ”— View workflow run: "+runInfo.URL)) executionLog.Printf("Workflow run URL: %s (ID: %d)", runInfo.URL, runInfo.DatabaseID) // Suggest audit command for analysis @@ -436,7 +438,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Waiting for workflow completion...")) } - runIDStr := fmt.Sprintf("%d", runInfo.DatabaseID) + runIDStr := strconv.FormatInt(runInfo.DatabaseID, 10) if err := WaitForWorkflowCompletion(targetRepo, runIDStr, 30, opts.Verbose); err != nil { if opts.AutoMergePRs { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Workflow did not complete successfully, skipping auto-merge: %v", err))) @@ -466,7 +468,7 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO // RunWorkflowsOnGitHub runs multiple agentic workflows on GitHub Actions, optionally repeating a specified number of times func RunWorkflowsOnGitHub(ctx context.Context, workflowNames []string, opts RunOptions) error { if len(workflowNames) == 0 { - return fmt.Errorf("at least one workflow name or ID is required") + return errors.New("at least one workflow name or ID is required") } // Check context cancellation at the start @@ -480,7 +482,7 @@ func RunWorkflowsOnGitHub(ctx context.Context, workflowNames []string, opts RunO // Validate all workflows exist and are runnable before starting for _, workflowName := range workflowNames { if workflowName == "" { - return fmt.Errorf("workflow name cannot be empty") + return errors.New("workflow name cannot be empty") } // Validate workflow exists diff --git a/pkg/cli/run_workflow_execution_test.go b/pkg/cli/run_workflow_execution_test.go index 737d4456c2..d754b86eaf 100644 --- a/pkg/cli/run_workflow_execution_test.go +++ b/pkg/cli/run_workflow_execution_test.go @@ -4,6 +4,7 @@ package cli import ( "context" + "errors" "strings" "testing" ) @@ -106,7 +107,7 @@ func TestRunWorkflowOnGitHub_ContextCancellation(t *testing.T) { } // Check that it's a context cancellation error - if !strings.Contains(err.Error(), "context canceled") && err != context.Canceled { + if !strings.Contains(err.Error(), "context canceled") && !errors.Is(err, context.Canceled) { t.Errorf("Expected context cancellation error, got: %v", err) } } @@ -181,7 +182,7 @@ func TestRunWorkflowsOnGitHub_ContextCancellation(t *testing.T) { } // Check that it's a context cancellation error - if !strings.Contains(err.Error(), "context canceled") && err != context.Canceled { + if !strings.Contains(err.Error(), "context canceled") && !errors.Is(err, context.Canceled) { t.Errorf("Expected context cancellation error, got: %v", err) } } diff --git a/pkg/cli/run_workflow_tracking.go b/pkg/cli/run_workflow_tracking.go index 1669eed9af..c8be4bf21a 100644 --- a/pkg/cli/run_workflow_tracking.go +++ b/pkg/cli/run_workflow_tracking.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -91,7 +92,7 @@ func getLatestWorkflowRunWithRetry(lockFileName string, repo string, verbose boo } if len(output) == 0 || string(output) == "[]" { - lastErr = fmt.Errorf("no runs found for workflow") + lastErr = errors.New("no runs found for workflow") runWorkflowTrackingLog.Printf("Attempt %d/%d: no runs found, output empty or []", attempt+1, maxRetries) console.LogVerbose(verbose, fmt.Sprintf("Attempt %d/%d: no runs found yet", attempt+1, maxRetries)) continue @@ -115,7 +116,7 @@ func getLatestWorkflowRunWithRetry(lockFileName string, repo string, verbose boo } if len(runs) == 0 { - lastErr = fmt.Errorf("no runs found") + lastErr = errors.New("no runs found") console.LogVerbose(verbose, fmt.Sprintf("Attempt %d/%d: no runs in parsed JSON", attempt+1, maxRetries)) continue } @@ -161,7 +162,7 @@ func getLatestWorkflowRunWithRetry(lockFileName string, repo string, verbose boo // For the first few attempts, if we have a run but it's too old, keep trying if attempt < 3 { - lastErr = fmt.Errorf("workflow run appears to be from a previous execution") + lastErr = errors.New("workflow run appears to be from a previous execution") continue } diff --git a/pkg/cli/run_workflow_validation.go b/pkg/cli/run_workflow_validation.go index cd1a8df2ca..0c3d56f2e5 100644 --- a/pkg/cli/run_workflow_validation.go +++ b/pkg/cli/run_workflow_validation.go @@ -38,7 +38,7 @@ func IsRunnable(markdownPath string) (bool, error) { // Check if the lock file exists if _, err := os.Stat(cleanLockPath); os.IsNotExist(err) { validationLog.Printf("Lock file does not exist: %s", cleanLockPath) - return false, fmt.Errorf("workflow has not been compiled yet - run 'gh aw compile' first") + return false, errors.New("workflow has not been compiled yet - run 'gh aw compile' first") } // Read the lock file - path is sanitized using filepath.Clean() to prevent path traversal attacks. @@ -91,7 +91,7 @@ func getWorkflowInputs(markdownPath string) (map[string]*workflow.InputDefinitio // Check if the lock file exists if _, err := os.Stat(cleanLockPath); os.IsNotExist(err) { validationLog.Printf("Lock file does not exist: %s", cleanLockPath) - return nil, fmt.Errorf("workflow has not been compiled yet - run 'gh aw compile' first") + return nil, errors.New("workflow has not been compiled yet - run 'gh aw compile' first") } // Read the lock file - path is sanitized using filepath.Clean() to prevent path traversal attacks. @@ -222,11 +222,11 @@ func validateWorkflowInputs(markdownPath string, providedInputs []string) error var errorParts []string if len(missingInputs) > 0 { - errorParts = append(errorParts, fmt.Sprintf("Missing required input(s): %s", strings.Join(missingInputs, ", "))) + errorParts = append(errorParts, "Missing required input(s): "+strings.Join(missingInputs, ", ")) } if len(typos) > 0 { - errorParts = append(errorParts, fmt.Sprintf("Invalid input name(s):\n %s", strings.Join(suggestions, "\n "))) + errorParts = append(errorParts, "Invalid input name(s):\n "+strings.Join(suggestions, "\n ")) } // Add helpful information about valid inputs @@ -239,11 +239,11 @@ func validateWorkflowInputs(markdownPath string, providedInputs []string) error } desc := "" if def.Description != "" { - desc = fmt.Sprintf(": %s", def.Description) + desc = ": " + def.Description } inputDescriptions = append(inputDescriptions, fmt.Sprintf(" %s%s%s", name, required, desc)) } - errorParts = append(errorParts, fmt.Sprintf("\nValid inputs:\n%s", strings.Join(inputDescriptions, "\n"))) + errorParts = append(errorParts, "\nValid inputs:\n"+strings.Join(inputDescriptions, "\n")) } return fmt.Errorf("%s", strings.Join(errorParts, "\n\n")) @@ -267,7 +267,7 @@ func validateWorkflowInputs(markdownPath string, providedInputs []string) error // This follows the principle that domain-specific validation belongs in domain files. func validateRemoteWorkflow(workflowName string, repoOverride string, verbose bool) error { if repoOverride == "" { - return fmt.Errorf("repository must be specified for remote workflow validation") + return errors.New("repository must be specified for remote workflow validation") } // Normalize workflow ID to handle both "workflow-name" and ".github/workflows/workflow-name.md" formats @@ -283,7 +283,8 @@ func validateRemoteWorkflow(workflowName string, repoOverride string, verbose bo // Use gh CLI to list workflows in the target repository output, err := workflow.RunGH("Listing workflows...", "workflow", "list", "--repo", repoOverride, "--json", "name,path,state") if err != nil { - if exitError, ok := err.(*exec.ExitError); ok { + var exitError *exec.ExitError + if errors.As(err, &exitError) { return fmt.Errorf("failed to list workflows in repository '%s': %s", repoOverride, string(exitError.Stderr)) } return fmt.Errorf("failed to list workflows in repository '%s': %w", repoOverride, err) diff --git a/pkg/cli/secret_set_command.go b/pkg/cli/secret_set_command.go index 284ba28b6e..523762f029 100644 --- a/pkg/cli/secret_set_command.go +++ b/pkg/cli/secret_set_command.go @@ -71,7 +71,7 @@ Examples: if flagOwner != "" || flagRepo != "" { // Both must be provided together when overriding the target repository if flagOwner == "" || flagRepo == "" { - return fmt.Errorf("both --owner and --repo must be specified together when overriding the target repository") + return errors.New("both --owner and --repo must be specified together when overriding the target repository") } owner, repo = flagOwner, flagRepo secretSetLog.Printf("Using explicit repository: %s/%s", owner, repo) diff --git a/pkg/cli/secrets.go b/pkg/cli/secrets.go index 1472705a89..080e4ea6cf 100644 --- a/pkg/cli/secrets.go +++ b/pkg/cli/secrets.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -31,9 +32,10 @@ func checkSecretExists(secretName string) (bool, error) { output, err := workflow.RunGH("Listing secrets...", "secret", "list", "--json", "name") if err != nil { // Check if it's a 403 error by examining the error - if exitError, ok := err.(*exec.ExitError); ok { + var exitError *exec.ExitError + if errors.As(err, &exitError) { if strings.Contains(string(exitError.Stderr), "403") { - return false, fmt.Errorf("403 access denied") + return false, errors.New("403 access denied") } } return false, fmt.Errorf("failed to list secrets: %w", err) diff --git a/pkg/cli/shell_completion.go b/pkg/cli/shell_completion.go index 9b3b9667f4..62ad66d3c7 100644 --- a/pkg/cli/shell_completion.go +++ b/pkg/cli/shell_completion.go @@ -2,6 +2,7 @@ package cli import ( "bytes" + "errors" "fmt" "os" "os/exec" @@ -91,14 +92,14 @@ func InstallShellCompletion(verbose bool, rootCmd CommandProvider) error { // For now, we only use the CommandProvider interface methods cmd, ok := rootCmd.(*cobra.Command) if !ok { - return fmt.Errorf("rootCmd must be a *cobra.Command") + return errors.New("rootCmd must be a *cobra.Command") } shellType := DetectShell() shellCompletionLog.Printf("Detected shell type: %s", shellType) if shellType == ShellUnknown { - return fmt.Errorf("could not detect shell type. Please install completions manually using: gh aw completion ") + return errors.New("could not detect shell type. Please install completions manually using: gh aw completion ") } fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Detected shell: %s", shellType))) @@ -191,7 +192,7 @@ func installBashCompletion(verbose bool, cmd *cobra.Command) error { return fmt.Errorf("failed to write completion file: %w", err) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Installed bash completion to: %s", completionPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Installed bash completion to: "+completionPath)) // Check if .bashrc sources completions bashrcPath := filepath.Join(homeDir, ".bashrc") @@ -265,7 +266,7 @@ func installZshCompletion(verbose bool, cmd *cobra.Command) error { return fmt.Errorf("failed to write completion file: %w", err) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Installed zsh completion to: %s", completionPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Installed zsh completion to: "+completionPath)) // Check if .zshrc configures fpath zshrcPath := filepath.Join(homeDir, ".zshrc") @@ -332,7 +333,7 @@ func installFishCompletion(verbose bool, cmd *cobra.Command) error { return fmt.Errorf("failed to write completion file: %w", err) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Installed fish completion to: %s", completionPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Installed fish completion to: "+completionPath)) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Fish will automatically load completions on next shell start")) return nil @@ -358,7 +359,7 @@ func installPowerShellCompletion(verbose bool, cmd *cobra.Command) error { profilePath := strings.TrimSpace(profileBuf.String()) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("PowerShell profile path: %s", profilePath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("PowerShell profile path: "+profilePath)) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To enable completions, add the following to your PowerShell profile:")) fmt.Fprintln(os.Stderr, "") @@ -385,7 +386,7 @@ func UninstallShellCompletion(verbose bool) error { shellCompletionLog.Printf("Detected shell type: %s", shellType) if shellType == ShellUnknown { - return fmt.Errorf("could not detect shell type. Please uninstall completions manually") + return errors.New("could not detect shell type. Please uninstall completions manually") } fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Detected shell: %s", shellType))) @@ -449,13 +450,13 @@ func uninstallBashCompletion(verbose bool) error { lastErr = err continue } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed bash completion from: %s", path))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed bash completion from: "+path)) removed = true } } if !removed { - return fmt.Errorf("no bash completion file found to remove") + return errors.New("no bash completion file found to remove") } if lastErr != nil { @@ -489,7 +490,7 @@ func uninstallZshCompletion(verbose bool) error { return fmt.Errorf("failed to remove completion file: %w", err) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed zsh completion from: %s", completionPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed zsh completion from: "+completionPath)) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please restart your shell for changes to take effect")) return nil @@ -516,7 +517,7 @@ func uninstallFishCompletion(verbose bool) error { return fmt.Errorf("failed to remove completion file: %w", err) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed fish completion from: %s", completionPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Removed fish completion from: "+completionPath)) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Fish will automatically detect the removal on next shell start")) return nil @@ -542,7 +543,7 @@ func uninstallPowerShellCompletion(verbose bool) error { profilePath := strings.TrimSpace(profileBuf.String()) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("PowerShell profile path: %s", profilePath))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("PowerShell profile path: "+profilePath)) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To uninstall completions, remove the following line from your PowerShell profile:")) fmt.Fprintln(os.Stderr, "") diff --git a/pkg/cli/signal_aware_poll.go b/pkg/cli/signal_aware_poll.go index 7910df5476..101a499f00 100644 --- a/pkg/cli/signal_aware_poll.go +++ b/pkg/cli/signal_aware_poll.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "os/signal" @@ -81,7 +82,7 @@ func PollWithSignalHandling(options PollOptions) error { case <-sigChan: pollLog.Print("Received interrupt signal") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Received interrupt signal, stopping wait...")) - return fmt.Errorf("interrupted by user") + return errors.New("interrupted by user") case <-ticker.C: // Check if timeout exceeded diff --git a/pkg/cli/signal_aware_poll_test.go b/pkg/cli/signal_aware_poll_test.go index 3a4a08bdbc..44dac376f3 100644 --- a/pkg/cli/signal_aware_poll_test.go +++ b/pkg/cli/signal_aware_poll_test.go @@ -3,7 +3,7 @@ package cli import ( - "fmt" + "errors" "testing" "time" ) @@ -33,7 +33,7 @@ func TestPollWithSignalHandling_Success(t *testing.T) { } func TestPollWithSignalHandling_Failure(t *testing.T) { - expectedErr := fmt.Errorf("poll failed") + expectedErr := errors.New("poll failed") err := PollWithSignalHandling(PollOptions{ PollInterval: 10 * time.Millisecond, Timeout: 1 * time.Second, @@ -47,7 +47,7 @@ func TestPollWithSignalHandling_Failure(t *testing.T) { t.Error("Expected error, got nil") } - if err != expectedErr { + if !errors.Is(err, expectedErr) { t.Errorf("Expected error %v, got %v", expectedErr, err) } } diff --git a/pkg/cli/spec.go b/pkg/cli/spec.go index 0731d3bfa2..8f9a5c2079 100644 --- a/pkg/cli/spec.go +++ b/pkg/cli/spec.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "net/url" "path/filepath" @@ -148,7 +149,7 @@ func parseRepoSpec(repoSpec string) (*RepoSpec, error) { // Validate repository format (org/repo) repoParts := strings.Split(repo, "/") if len(repoParts) != 2 || repoParts[0] == "" || repoParts[1] == "" { - return nil, fmt.Errorf("repository must be in format 'owner/repo'. Example: github/gh-aw") + return nil, errors.New("repository must be in format 'owner/repo'. Example: github/gh-aw") } } @@ -182,7 +183,7 @@ func parseGitHubURL(spec string) (*WorkflowSpec, error) { // Must be a GitHub URL if parsedURL.Host != "github.com" && parsedURL.Host != "raw.githubusercontent.com" { specLog.Printf("Invalid host: %s", parsedURL.Host) - return nil, fmt.Errorf("URL must be from github.com or raw.githubusercontent.com") + return nil, errors.New("URL must be from github.com or raw.githubusercontent.com") } owner, repo, ref, filePath, err := parser.ParseRepoFileURL(spec) @@ -195,7 +196,7 @@ func parseGitHubURL(spec string) (*WorkflowSpec, error) { // Ensure the file path ends with .md if !strings.HasSuffix(filePath, ".md") { - return nil, fmt.Errorf("GitHub URL must point to a .md file") + return nil, errors.New("GitHub URL must point to a .md file") } // Validate owner and repo @@ -261,7 +262,7 @@ func parseWorkflowSpec(spec string) (*WorkflowSpec, error) { // Must have at least 3 parts: owner/repo/workflow-path if len(slashParts) < 3 { - return nil, fmt.Errorf("workflow specification must be in format 'owner/repo/workflow-name[@version]'") + return nil, errors.New("workflow specification must be in format 'owner/repo/workflow-name[@version]'") } owner := slashParts[0] @@ -287,7 +288,7 @@ func parseWorkflowSpec(spec string) (*WorkflowSpec, error) { // Validate owner and repo parts are not empty if owner == "" || repo == "" { - return nil, fmt.Errorf("invalid workflow specification: owner and repo cannot be empty") + return nil, errors.New("invalid workflow specification: owner and repo cannot be empty") } // Basic validation that owner and repo look like GitHub identifiers @@ -363,7 +364,7 @@ func parseSourceSpec(source string) (*SourceSpec, error) { // Parse path: owner/repo/path/to/workflow.md slashParts := strings.Split(pathPart, "/") if len(slashParts) < 3 { - return nil, fmt.Errorf("invalid source format: must be owner/repo/path[@ref]") + return nil, errors.New("invalid source format: must be owner/repo/path[@ref]") } spec := &SourceSpec{ diff --git a/pkg/cli/status_command.go b/pkg/cli/status_command.go index b926983242..0c5a332ddd 100644 --- a/pkg/cli/status_command.go +++ b/pkg/cli/status_command.go @@ -2,6 +2,7 @@ package cli import ( "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -182,7 +183,7 @@ func StatusWorkflows(pattern string, verbose bool, jsonOutput bool, ref string, if verbose && !jsonOutput { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Checking status of workflow files")) if pattern != "" { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Filtering by pattern: %s", pattern))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Filtering by pattern: "+pattern)) } } @@ -295,7 +296,8 @@ func fetchLatestRunsByRef(ref string, repoOverride string, verbose bool) (map[st // Extract detailed error information including exit code and stderr var exitCode int var stderr string - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode = exitErr.ExitCode() stderr = string(exitErr.Stderr) statusLog.Printf("gh run list command failed with exit code %d. Command: gh %v", exitCode, args) @@ -324,7 +326,7 @@ func fetchLatestRunsByRef(ref string, repoOverride string, verbose bool) (map[st if !verbose { spinner.Stop() } - return nil, fmt.Errorf("gh run list returned empty output") + return nil, errors.New("gh run list returned empty output") } // Validate JSON before unmarshaling @@ -332,7 +334,7 @@ func fetchLatestRunsByRef(ref string, repoOverride string, verbose bool) (map[st if !verbose { spinner.Stop() } - return nil, fmt.Errorf("gh run list returned invalid JSON") + return nil, errors.New("gh run list returned invalid JSON") } var runs []WorkflowRun diff --git a/pkg/cli/tokens_bootstrap.go b/pkg/cli/tokens_bootstrap.go index d3d3392175..659312b818 100644 --- a/pkg/cli/tokens_bootstrap.go +++ b/pkg/cli/tokens_bootstrap.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" @@ -143,7 +144,7 @@ func getSecretRequirements(engineFilter string) ([]SecretRequirement, error) { } if len(workflowFiles) == 0 { - return nil, fmt.Errorf("no workflow files found in .github/workflows/") + return nil, errors.New("no workflow files found in .github/workflows/") } tokensBootstrapLog.Printf("Found %d workflow files, extracting secrets", len(workflowFiles)) diff --git a/pkg/cli/trial_command.go b/pkg/cli/trial_command.go index b5dcc11eb4..0e5096050b 100644 --- a/pkg/cli/trial_command.go +++ b/pkg/cli/trial_command.go @@ -3,11 +3,13 @@ package cli import ( "context" "encoding/json" + "errors" "fmt" "os" "os/exec" "path/filepath" "regexp" + "strconv" "strings" "time" @@ -258,7 +260,7 @@ func RunWorkflowTrials(ctx context.Context, workflowSpecs []string, opts TrialOp logicalRepoSlug = logicalRepo.RepoSlug directTrialMode = false trialLog.Printf("Using logical-repo mode: %s", logicalRepoSlug) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Target repository (specified): %s", logicalRepoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Target repository (specified): "+logicalRepoSlug)) } else { // No --clone-repo or --logical-repo specified // If --repo is specified without simulation flags, it's direct trial mode @@ -278,7 +280,7 @@ func RunWorkflowTrials(ctx context.Context, workflowSpecs []string, opts TrialOp return fmt.Errorf("failed to determine simulated host repository: %w", err) } directTrialMode = false - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Target repository (current): %s", logicalRepoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Target repository (current): "+logicalRepoSlug)) } } @@ -299,9 +301,9 @@ func RunWorkflowTrials(ctx context.Context, workflowSpecs []string, opts TrialOp if err != nil { return fmt.Errorf("failed to get GitHub username for default trial repo: %w", err) } - hostRepoSlug = fmt.Sprintf("%s/gh-aw-trial", username) + hostRepoSlug = username + "/gh-aw-trial" trialLog.Printf("Using default host repository: %s", hostRepoSlug) - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Host repository (default): %s", hostRepoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Host repository (default): "+hostRepoSlug)) } // Step 1.5: Show confirmation unless quiet mode @@ -530,7 +532,7 @@ func RunWorkflowTrials(ctx context.Context, workflowSpecs []string, opts TrialOp fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("=== Additional Artifacts Available from %s (%d files) ===", parsedSpec.WorkflowName, len(artifacts.AdditionalArtifacts)))) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Trial completed for workflow: %s", parsedSpec.WorkflowName))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Trial completed for workflow: "+parsedSpec.WorkflowName)) } // Step 6: Save combined results for multi-workflow trials @@ -550,7 +552,7 @@ func RunWorkflowTrials(ctx context.Context, workflowSpecs []string, opts TrialOp if err := saveTrialResult(combinedFilename, combinedResult, opts.Verbose); err != nil { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to save combined trial result: %v", err))) } - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Combined results saved to: %s", combinedFilename))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Combined results saved to: "+combinedFilename)) } // Step 6.5: Copy trial results to host repository and commit them @@ -593,7 +595,7 @@ func getCurrentGitHubUsername() (string, error) { username := strings.TrimSpace(string(output)) if username == "" { - return "", fmt.Errorf("GitHub username is empty") + return "", errors.New("GitHub username is empty") } return username, nil @@ -810,7 +812,7 @@ func showTrialConfirmation(parsedSpecs []*WorkflowSpec, logicalRepoSlug, cloneRe } if !confirmed { - return fmt.Errorf("trial cancelled by user") + return errors.New("trial cancelled by user") } return nil @@ -818,11 +820,11 @@ func showTrialConfirmation(parsedSpecs []*WorkflowSpec, logicalRepoSlug, cloneRe func triggerWorkflowRun(repoSlug, workflowName string, triggerContext string, verbose bool) (string, error) { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Triggering workflow run for: %s", workflowName))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Triggering workflow run for: "+workflowName)) } // Trigger workflow using gh CLI - lockFileName := fmt.Sprintf("%s.lock.yml", workflowName) + lockFileName := workflowName + ".lock.yml" // Build the command args args := []string{"workflow", "run", lockFileName, "--repo", repoSlug} @@ -831,7 +833,7 @@ func triggerWorkflowRun(repoSlug, workflowName string, triggerContext string, ve if triggerContext != "" { issueNumber := parseIssueSpec(triggerContext) if issueNumber != "" { - args = append(args, "--field", fmt.Sprintf("issue_number=%s", issueNumber)) + args = append(args, "--field", "issue_number="+issueNumber) if verbose { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Using issue number %s from trigger context", issueNumber))) } @@ -852,7 +854,7 @@ func triggerWorkflowRun(repoSlug, workflowName string, triggerContext string, ve return "", fmt.Errorf("failed to get workflow run ID: %w", err) } - runID := fmt.Sprintf("%d", runInfo.DatabaseID) + runID := strconv.FormatInt(runInfo.DatabaseID, 10) if verbose { fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Workflow run started with ID: %s (status: %s)", runID, runInfo.Status))) @@ -902,7 +904,7 @@ func saveTrialResult(filename string, result any, verbose bool) error { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Saved trial result to: %s", filename))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Saved trial result to: "+filename)) } return nil diff --git a/pkg/cli/trial_dry_run_test.go b/pkg/cli/trial_dry_run_test.go index 226f32a4ca..633fba47b3 100644 --- a/pkg/cli/trial_dry_run_test.go +++ b/pkg/cli/trial_dry_run_test.go @@ -4,8 +4,8 @@ package cli import ( "bytes" - "fmt" "os" + "strconv" "strings" "testing" @@ -248,7 +248,7 @@ func TestDryRunNoActualAPICallsForCreate(t *testing.T) { // This test documents that in dry-run mode, we should not make actual GitHub API calls // This is a behavioral test - actual integration would require mocking gh CLI - repoSlug := "test-owner/test-repo-" + fmt.Sprintf("%d", os.Getpid()) + repoSlug := "test-owner/test-repo-" + strconv.Itoa(os.Getpid()) dryRun := true verbose := true diff --git a/pkg/cli/trial_repository.go b/pkg/cli/trial_repository.go index 832b064ab7..368253993f 100644 --- a/pkg/cli/trial_repository.go +++ b/pkg/cli/trial_repository.go @@ -50,17 +50,17 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos if forceDeleteHostRepo { // Force delete mode: delete the existing repository first if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Force deleting existing host repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Force deleting existing host repository: "+repoSlug)) } if dryRun { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("[DRY RUN] Would delete repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("[DRY RUN] Would delete repository: "+repoSlug)) } else { if deleteOutput, deleteErr := workflow.RunGHCombined("Deleting repository...", "repo", "delete", repoSlug, "--yes"); deleteErr != nil { return fmt.Errorf("failed to force delete existing host repository %s: %w (output: %s)", repoSlug, deleteErr, string(deleteOutput)) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Force deleted existing host repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Force deleted existing host repository: "+repoSlug)) } // Continue to create the repository below @@ -71,7 +71,7 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos if cloneRepoSlug != "" { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Reusing existing host repository: %s (contents will be force-pushed)", repoSlug))) } else { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Reusing existing host repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Reusing existing host repository: "+repoSlug)) } } prefix := "" @@ -96,7 +96,7 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos fmt.Fprintln(os.Stderr, console.FormatInfoMessage("[DRY RUN] Would create repository with description: 'GitHub Agentic Workflows host repository'")) fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("[DRY RUN] Would enable GitHub Actions permissions at: https://github.com/%s/settings/actions", repoSlug))) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("[DRY RUN] Would enable discussions")) - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("[DRY RUN] Would create host repository: https://github.com/%s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("[DRY RUN] Would create host repository: https://github.com/"+repoSlug)) return nil } @@ -109,16 +109,16 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos if strings.Contains(outputStr, "name already exists") { // Repository exists but gh repo view failed earlier - this is okay, reuse it if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Repository already exists (detected via create error): %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Repository already exists (detected via create error): "+repoSlug)) } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Using existing host repository: https://github.com/%s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Using existing host repository: https://github.com/"+repoSlug)) return nil } return fmt.Errorf("failed to create host repository: %w (output: %s)", err, string(output)) } // Show host repository creation message with URL - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Created host repository: https://github.com/%s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created host repository: https://github.com/"+repoSlug)) // Prompt user to enable GitHub Actions permissions fmt.Fprintln(os.Stderr, console.FormatInfoMessage("")) @@ -136,7 +136,7 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos // Enable discussions in the repository as most workflows use them if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Enabling discussions in repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Enabling discussions in repository: "+repoSlug)) } if discussionsOutput, discussionsErr := workflow.RunGHCombined("Enabling discussions...", "repo", "edit", repoSlug, "--enable-discussions"); discussionsErr != nil { @@ -154,7 +154,7 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos func cleanupTrialRepository(repoSlug string, verbose bool) error { if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Cleaning up host repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Cleaning up host repository: "+repoSlug)) } // Use gh CLI to delete the repository with proper username/repo format @@ -165,7 +165,7 @@ func cleanupTrialRepository(repoSlug string, verbose bool) error { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Deleted host repository: %s", repoSlug))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Deleted host repository: "+repoSlug)) } return nil @@ -264,9 +264,9 @@ func installWorkflowInTrialMode(ctx context.Context, tempDir string, parsedSpec if opts.Verbose { if fetched.IsLocal { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Copied local workflow to %s", result.DestPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Copied local workflow to "+result.DestPath)) } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Fetched remote workflow to %s", result.DestPath))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Fetched remote workflow to "+result.DestPath)) } } @@ -392,7 +392,7 @@ func modifyWorkflowForTrialMode(tempDir, workflowName, logicalRepoSlug string, v } // Find the workflow markdown file - workflowPath := filepath.Join(tempDir, constants.GetWorkflowDir(), fmt.Sprintf("%s.md", workflowName)) + workflowPath := filepath.Join(tempDir, constants.GetWorkflowDir(), workflowName+".md") // Validate workflow path workflowPath, err := fileutil.ValidateAbsolutePath(workflowPath) @@ -427,7 +427,7 @@ func modifyWorkflowForTrialMode(tempDir, workflowName, logicalRepoSlug string, v // Add the original uses line newLines = append(newLines, fmt.Sprintf("%s%s%s", indentation, usesLine, remainder)) // Add the with clause at the same indentation level as uses - newLines = append(newLines, fmt.Sprintf("%swith:", indentation)) + newLines = append(newLines, indentation+"with:") newLines = append(newLines, fmt.Sprintf("%s repository: %s", indentation, logicalRepoSlug)) } else { newLines = append(newLines, line) diff --git a/pkg/cli/trial_support.go b/pkg/cli/trial_support.go index edd9ff6f7c..e906e95d33 100644 --- a/pkg/cli/trial_support.go +++ b/pkg/cli/trial_support.go @@ -140,7 +140,7 @@ func parseJSONArtifact(filePath string, verbose bool) map[string]any { } if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Parsed JSON artifact: %s", filepath.Base(filePath)))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Parsed JSON artifact: "+filepath.Base(filePath))) } return parsed diff --git a/pkg/cli/update_actions.go b/pkg/cli/update_actions.go index 20c82a4411..2da787df20 100644 --- a/pkg/cli/update_actions.go +++ b/pkg/cli/update_actions.go @@ -3,6 +3,7 @@ package cli import ( "bytes" "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -42,7 +43,7 @@ func UpdateActions(allowMajor, verbose bool) error { // Check if the file exists if _, err := os.Stat(actionsLockPath); os.IsNotExist(err) { if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Actions lock file not found: %s", actionsLockPath))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Actions lock file not found: "+actionsLockPath)) } return nil // Not an error, just skip } @@ -171,7 +172,7 @@ func getLatestActionRelease(repo, currentVersion string, allowMajor, verbose boo // Try fallback using git ls-remote latestRelease, latestSHA, gitErr := getLatestActionReleaseViaGit(repo, currentVersion, allowMajor, verbose) if gitErr != nil { - return "", "", fmt.Errorf("failed to fetch releases via GitHub API and git: API error: %w, Git error: %v", err, gitErr) + return "", "", fmt.Errorf("failed to fetch releases via GitHub API and git: API error: %w, Git Error: %w", err, gitErr) } return latestRelease, latestSHA, nil } @@ -180,7 +181,7 @@ func getLatestActionRelease(repo, currentVersion string, allowMajor, verbose boo releases := strings.Split(strings.TrimSpace(string(output)), "\n") if len(releases) == 0 || releases[0] == "" { - return "", "", fmt.Errorf("no releases found") + return "", "", errors.New("no releases found") } // Parse current version @@ -203,7 +204,7 @@ func getLatestActionRelease(repo, currentVersion string, allowMajor, verbose boo } if len(validReleases) == 0 { - return "", "", fmt.Errorf("no valid semantic version releases found") + return "", "", errors.New("no valid semantic version releases found") } // Sort releases by semver in descending order (highest first) @@ -249,7 +250,7 @@ func getLatestActionRelease(repo, currentVersion string, allowMajor, verbose boo } if latestCompatible == "" { - return "", "", fmt.Errorf("no compatible release found") + return "", "", errors.New("no compatible release found") } // Get the SHA for the latest compatible release @@ -302,7 +303,7 @@ func getLatestActionReleaseViaGit(repo, currentVersion string, allowMajor, verbo } if len(releases) == 0 { - return "", "", fmt.Errorf("no releases found") + return "", "", errors.New("no releases found") } // Parse current version @@ -325,7 +326,7 @@ func getLatestActionReleaseViaGit(repo, currentVersion string, allowMajor, verbo } if len(validReleases) == 0 { - return "", "", fmt.Errorf("no valid semantic version releases found") + return "", "", errors.New("no valid semantic version releases found") } // Sort releases by semver in descending order (highest first) @@ -371,7 +372,7 @@ func getLatestActionReleaseViaGit(repo, currentVersion string, allowMajor, verbo } if latestCompatible == "" { - return "", "", fmt.Errorf("no compatible release found") + return "", "", errors.New("no compatible release found") } sha := tagToSHA[latestCompatible] @@ -394,7 +395,7 @@ func getActionSHAForTag(repo, tag string) (string, error) { sha := strings.TrimSpace(string(output)) if sha == "" { - return "", fmt.Errorf("empty SHA returned for tag") + return "", errors.New("empty SHA returned for tag") } // Validate SHA format (should be 40 hex characters) @@ -435,7 +436,7 @@ func marshalActionsLockSorted(actionsLock *actionsLockFile) ([]byte, error) { // Write the key-value pair with proper indentation buf.WriteString(" ") - buf.WriteString(string(keyJSON)) + buf.Write(keyJSON) buf.WriteString(": ") // Pretty-print the entry JSON with proper indentation diff --git a/pkg/cli/update_merge.go b/pkg/cli/update_merge.go index 8ef25ae75d..deee71961a 100644 --- a/pkg/cli/update_merge.go +++ b/pkg/cli/update_merge.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "os" "os/exec" @@ -162,7 +163,8 @@ func MergeWorkflowContent(base, current, new, oldSourceSpec, newRef string, verb // The exit code can be >1 for multiple conflicts, not just errors hasConflicts := false if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode := exitErr.ExitCode() if exitCode > 0 && exitCode < 128 { // Conflicts found (exit codes 1-127 indicate conflicts) diff --git a/pkg/cli/update_workflows.go b/pkg/cli/update_workflows.go index 3379b8c19f..8f866186e3 100644 --- a/pkg/cli/update_workflows.go +++ b/pkg/cli/update_workflows.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "net/url" "os" @@ -31,9 +32,9 @@ func UpdateWorkflows(workflowNames []string, allowMajor, force, verbose bool, en if len(workflows) == 0 { if len(workflowNames) > 0 { - return fmt.Errorf("no workflows found matching the specified names with source field") + return errors.New("no workflows found matching the specified names with source field") } - return fmt.Errorf("no workflows found with source field") + return errors.New("no workflows found with source field") } fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d workflow(s) to update", len(workflows)))) @@ -61,7 +62,7 @@ func UpdateWorkflows(workflowNames []string, allowMajor, force, verbose bool, en showUpdateSummary(successfulUpdates, failedUpdates) if len(successfulUpdates) == 0 { - return fmt.Errorf("no workflows were successfully updated") + return errors.New("no workflows were successfully updated") } return nil @@ -224,7 +225,7 @@ func resolveLatestCommitFromDefaultBranch(repo, currentSHA string, verbose bool) // getRepoDefaultBranch fetches the default branch name for a repository. func getRepoDefaultBranch(repo string) (string, error) { - output, err := workflow.RunGH("Fetching repo info...", "api", fmt.Sprintf("/repos/%s", repo), "--jq", ".default_branch") + output, err := workflow.RunGH("Fetching repo info...", "api", "/repos/"+repo, "--jq", ".default_branch") if err != nil { return "", err } @@ -269,7 +270,7 @@ func resolveLatestRelease(repo, currentRef string, allowMajor, verbose bool) (st releases := strings.Split(strings.TrimSpace(string(output)), "\n") if len(releases) == 0 || releases[0] == "" { - return "", fmt.Errorf("no releases found") + return "", errors.New("no releases found") } // Parse current version @@ -278,7 +279,7 @@ func resolveLatestRelease(repo, currentRef string, allowMajor, verbose bool) (st // If current version is not a valid semantic version, just return the latest release latestRelease := releases[0] if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Current version is not valid, using latest release: %s", latestRelease))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Current version is not valid, using latest release: "+latestRelease)) } return latestRelease, nil } @@ -306,11 +307,11 @@ func resolveLatestRelease(repo, currentRef string, allowMajor, verbose bool) (st } if latestCompatible == "" { - return "", fmt.Errorf("no compatible release found") + return "", errors.New("no compatible release found") } if verbose && latestCompatible != currentRef { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found newer release: %s", latestCompatible))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found newer release: "+latestCompatible)) } return latestCompatible, nil @@ -321,8 +322,8 @@ func updateWorkflow(wf *workflowWithSource, allowMajor, force, verbose bool, eng updateLog.Printf("Updating workflow: name=%s, source=%s, force=%v, noMerge=%v", wf.Name, wf.SourceSpec, force, noMerge) if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\nUpdating workflow: %s", wf.Name))) - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Source: %s", wf.SourceSpec))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nUpdating workflow: "+wf.Name)) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Source: "+wf.SourceSpec)) } // Parse source spec @@ -353,8 +354,8 @@ func updateWorkflow(wf *workflowWithSource, allowMajor, force, verbose bool, eng } if verbose { - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Current ref: %s", currentRef))) - fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Latest ref: %s", latestRef))) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Current ref: "+currentRef)) + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Latest ref: "+latestRef)) } // Check if update is needed @@ -524,7 +525,7 @@ func updateWorkflow(wf *workflowWithSource, allowMajor, force, verbose bool, eng } else { finalContent = updatedContent if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Set stop-after field to: %s", stopAfter))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Set stop-after field to: "+stopAfter)) } } } diff --git a/pkg/cli/validation_output_test.go b/pkg/cli/validation_output_test.go index 1aa8e7d61f..167e36bf0e 100644 --- a/pkg/cli/validation_output_test.go +++ b/pkg/cli/validation_output_test.go @@ -4,7 +4,6 @@ package cli import ( "errors" - "fmt" "strings" "testing" @@ -75,7 +74,7 @@ func TestFormatValidationError(t *testing.T) { }, { name: "error with formatting characters", - err: fmt.Errorf("path must be relative, got: /absolute/path"), + err: errors.New("path must be relative, got: /absolute/path"), mustContain: []string{ "path must be relative", "/absolute/path", @@ -211,7 +210,7 @@ func TestFormatValidationErrorContentIntegrity(t *testing.T) { } for _, msg := range errorMessages { - t.Run(fmt.Sprintf("content_integrity_%s", strings.ReplaceAll(msg, "\n", "_")), func(t *testing.T) { + t.Run("content_integrity_"+strings.ReplaceAll(msg, "\n", "_"), func(t *testing.T) { err := errors.New(msg) result := FormatValidationError(err) diff --git a/pkg/cli/workflows.go b/pkg/cli/workflows.go index 76e5ab4cf5..ef4d46c6d3 100644 --- a/pkg/cli/workflows.go +++ b/pkg/cli/workflows.go @@ -78,7 +78,8 @@ func fetchGitHubWorkflows(repoOverride string, verbose bool) (map[string]*GitHub // Extract detailed error information including exit code and stderr var exitCode int var stderr string - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode = exitErr.ExitCode() stderr = string(exitErr.Stderr) workflowsLog.Printf("gh workflow list command failed with exit code %d. Command: gh %v", exitCode, args) @@ -97,7 +98,7 @@ func fetchGitHubWorkflows(repoOverride string, verbose bool) (map[string]*GitHub if !verbose { spinner.Stop() } - return nil, fmt.Errorf("gh workflow list returned empty output - check if repository has workflows and gh CLI is authenticated") + return nil, errors.New("gh workflow list returned empty output - check if repository has workflows and gh CLI is authenticated") } // Validate JSON before unmarshaling @@ -105,7 +106,7 @@ func fetchGitHubWorkflows(repoOverride string, verbose bool) (map[string]*GitHub if !verbose { spinner.Stop() } - return nil, fmt.Errorf("gh workflow list returned invalid JSON - this may be due to network issues or authentication problems") + return nil, errors.New("gh workflow list returned invalid JSON - this may be due to network issues or authentication problems") } var workflows []GitHubWorkflow @@ -201,7 +202,8 @@ func restoreWorkflowState(workflowIdOrName string, workflowID int64, repoOverrid // Extract detailed error information including exit code and stderr var exitCode int var stderr string - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode = exitErr.ExitCode() stderr = string(exitErr.Stderr) workflowsLog.Printf("gh workflow disable command failed with exit code %d. Command: gh %v", exitCode, args) @@ -212,7 +214,7 @@ func restoreWorkflowState(workflowIdOrName string, workflowID int64, repoOverrid fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to restore workflow '%s' to disabled state: %v", workflowIdOrName, err))) } } else { - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Restored workflow to disabled state: %s", workflowIdOrName))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Restored workflow to disabled state: "+workflowIdOrName)) } } diff --git a/pkg/cli/zizmor.go b/pkg/cli/zizmor.go index fe0815a104..10f5b028e0 100644 --- a/pkg/cli/zizmor.go +++ b/pkg/cli/zizmor.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -78,7 +79,7 @@ func runZizmorOnFiles(lockFiles []string, verbose bool, strict bool) error { dockerArgs := []string{ "run", "--rm", - "-v", fmt.Sprintf("%s:/workdir", gitRoot), + "-v", gitRoot + ":/workdir", "-w", "/workdir", "ghcr.io/zizmorcore/zizmor:latest", "--format", "json", @@ -92,7 +93,7 @@ func runZizmorOnFiles(lockFiles []string, verbose bool, strict bool) error { // Always show that zizmor is running (regular verbosity) if len(lockFiles) == 1 { - fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Running zizmor security scanner on %s", relPaths[0]))) + fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Running zizmor security scanner on "+relPaths[0])) } else { fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Running zizmor security scanner on %d files", len(lockFiles)))) } @@ -132,7 +133,8 @@ func runZizmorOnFiles(lockFiles []string, verbose bool, strict bool) error { // 10-13 = findings at different severity levels // 14 = findings with mixed severities // Other codes = actual errors - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode := exitErr.ExitCode() zizmorLog.Printf("Zizmor exited with code %d (warnings=%d)", exitCode, totalWarnings) // Exit codes 10-14 indicate findings diff --git a/pkg/console/console.go b/pkg/console/console.go index cb88b01d28..e0f9430e0c 100644 --- a/pkg/console/console.go +++ b/pkg/console/console.go @@ -5,6 +5,7 @@ package console import ( "fmt" "os" + "strconv" "strings" "github.com/charmbracelet/lipgloss" @@ -80,7 +81,7 @@ func renderContext(err CompilerError) string { var output strings.Builder maxLineNum := err.Position.Line + len(err.Context)/2 - lineNumWidth := len(fmt.Sprintf("%d", maxLineNum)) + lineNumWidth := len(strconv.Itoa(maxLineNum)) for i, line := range err.Context { lineNum := err.Position.Line - len(err.Context)/2 + i diff --git a/pkg/console/form.go b/pkg/console/form.go index c630206f40..91078e0e0e 100644 --- a/pkg/console/form.go +++ b/pkg/console/form.go @@ -3,6 +3,7 @@ package console import ( + "errors" "fmt" "github.com/charmbracelet/huh" @@ -14,7 +15,7 @@ import ( func RunForm(fields []FormField) error { // Validate inputs first before checking TTY if len(fields) == 0 { - return fmt.Errorf("no form fields provided") + return errors.New("no form fields provided") } // Validate field configurations before checking TTY @@ -29,7 +30,7 @@ func RunForm(fields []FormField) error { // Check if stdin is a TTY - if not, we can't show interactive forms if !tty.IsStderrTerminal() { - return fmt.Errorf("interactive forms not available (not a TTY)") + return errors.New("interactive forms not available (not a TTY)") } // Build form fields diff --git a/pkg/console/form_test.go b/pkg/console/form_test.go index 65216de476..64efed30b9 100644 --- a/pkg/console/form_test.go +++ b/pkg/console/form_test.go @@ -3,7 +3,7 @@ package console import ( - "fmt" + "errors" "testing" "github.com/stretchr/testify/assert" @@ -136,7 +136,7 @@ func TestRunForm(t *testing.T) { Value: &name, Validate: func(s string) error { if len(s) < 3 { - return fmt.Errorf("must be at least 3 characters") + return errors.New("must be at least 3 characters") } return nil }, diff --git a/pkg/console/input.go b/pkg/console/input.go index ad01247c65..c6e87ba2fe 100644 --- a/pkg/console/input.go +++ b/pkg/console/input.go @@ -3,7 +3,7 @@ package console import ( - "fmt" + "errors" "github.com/charmbracelet/huh" "github.com/github/gh-aw/pkg/tty" @@ -14,7 +14,7 @@ import ( func PromptInput(title, description, placeholder string) (string, error) { // Check if stdin is a TTY - if not, we can't show interactive forms if !tty.IsStderrTerminal() { - return "", fmt.Errorf("interactive input not available (not a TTY)") + return "", errors.New("interactive input not available (not a TTY)") } var value string @@ -42,7 +42,7 @@ func PromptInput(title, description, placeholder string) (string, error) { func PromptSecretInput(title, description string) (string, error) { // Check if stdin is a TTY - if not, we can't show interactive forms if !tty.IsStderrTerminal() { - return "", fmt.Errorf("interactive input not available (not a TTY)") + return "", errors.New("interactive input not available (not a TTY)") } var value string @@ -55,7 +55,7 @@ func PromptSecretInput(title, description string) (string, error) { EchoMode(huh.EchoModePassword). // Masks input for security Validate(func(s string) error { if len(s) == 0 { - return fmt.Errorf("value cannot be empty") + return errors.New("value cannot be empty") } return nil }). @@ -75,7 +75,7 @@ func PromptSecretInput(title, description string) (string, error) { func PromptInputWithValidation(title, description, placeholder string, validate func(string) error) (string, error) { // Check if stdin is a TTY - if not, we can't show interactive forms if !tty.IsStderrTerminal() { - return "", fmt.Errorf("interactive input not available (not a TTY)") + return "", errors.New("interactive input not available (not a TTY)") } var value string diff --git a/pkg/console/input_test.go b/pkg/console/input_test.go index 9039be49ba..6db522d6da 100644 --- a/pkg/console/input_test.go +++ b/pkg/console/input_test.go @@ -3,7 +3,7 @@ package console import ( - "fmt" + "errors" "testing" "github.com/stretchr/testify/assert" @@ -64,7 +64,7 @@ func TestPromptInputWithValidation(t *testing.T) { placeholder := "Enter value" validator := func(s string) error { if len(s) < 3 { - return fmt.Errorf("must be at least 3 characters") + return errors.New("must be at least 3 characters") } return nil } diff --git a/pkg/console/list.go b/pkg/console/list.go index 7e14eb5a84..2e0f6471f1 100644 --- a/pkg/console/list.go +++ b/pkg/console/list.go @@ -3,6 +3,7 @@ package console import ( + "errors" "fmt" "io" "os" @@ -133,7 +134,7 @@ func ShowInteractiveList(title string, items []ListItem) (string, error) { listLog.Printf("Showing interactive list: title=%s, items=%d", title, len(items)) if len(items) == 0 { - return "", fmt.Errorf("no items to display") + return "", errors.New("no items to display") } // Check if we're in a TTY environment @@ -181,7 +182,7 @@ func ShowInteractiveList(title string, items []ListItem) (string, error) { // Check if user cancelled result := finalModel.(listModel) if result.quitting && result.choice == "" { - return "", fmt.Errorf("selection cancelled") + return "", errors.New("selection cancelled") } listLog.Printf("Selected item: %s", result.choice) diff --git a/pkg/console/render.go b/pkg/console/render.go index cb6e0f8e6a..9bd6b751bc 100644 --- a/pkg/console/render.go +++ b/pkg/console/render.go @@ -381,9 +381,9 @@ func formatFieldValue(val reflect.Value) string { // Fallback for unexported fields switch val.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return fmt.Sprintf("%d", val.Int()) + return strconv.FormatInt(val.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return fmt.Sprintf("%d", val.Uint()) + return strconv.FormatUint(val.Uint(), 10) case reflect.Float32, reflect.Float64: return fmt.Sprintf("%f", val.Float()) } @@ -415,11 +415,11 @@ func formatFieldValue(val reflect.Value) string { // For unexported fields, try to format based on kind switch val.Kind() { case reflect.Bool: - return fmt.Sprintf("%t", val.Bool()) + return strconv.FormatBool(val.Bool()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return fmt.Sprintf("%d", val.Int()) + return strconv.FormatInt(val.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return fmt.Sprintf("%d", val.Uint()) + return strconv.FormatUint(val.Uint(), 10) case reflect.Float32, reflect.Float64: return fmt.Sprintf("%f", val.Float()) case reflect.String: @@ -543,7 +543,7 @@ func FormatNumber(n int) string { f := float64(n) if f < 1000 { - return fmt.Sprintf("%d", n) + return strconv.Itoa(n) } else if f < 1000000 { // Format as thousands (k) k := f / 1000 diff --git a/pkg/console/select.go b/pkg/console/select.go index 028ad8eebe..0d2a94a0ba 100644 --- a/pkg/console/select.go +++ b/pkg/console/select.go @@ -3,7 +3,7 @@ package console import ( - "fmt" + "errors" "github.com/charmbracelet/huh" "github.com/github/gh-aw/pkg/tty" @@ -14,12 +14,12 @@ import ( func PromptSelect(title, description string, options []SelectOption) (string, error) { // Validate inputs first if len(options) == 0 { - return "", fmt.Errorf("no options provided") + return "", errors.New("no options provided") } // Check if stdin is a TTY - if not, we can't show interactive forms if !tty.IsStderrTerminal() { - return "", fmt.Errorf("interactive selection not available (not a TTY)") + return "", errors.New("interactive selection not available (not a TTY)") } var selected string @@ -52,12 +52,12 @@ func PromptSelect(title, description string, options []SelectOption) (string, er func PromptMultiSelect(title, description string, options []SelectOption, limit int) ([]string, error) { // Validate inputs first if len(options) == 0 { - return nil, fmt.Errorf("no options provided") + return nil, errors.New("no options provided") } // Check if stdin is a TTY - if not, we can't show interactive forms if !tty.IsStderrTerminal() { - return nil, fmt.Errorf("interactive selection not available (not a TTY)") + return nil, errors.New("interactive selection not available (not a TTY)") } var selected []string diff --git a/pkg/fileutil/fileutil.go b/pkg/fileutil/fileutil.go index 54e5bd4ad0..5ff45b23db 100644 --- a/pkg/fileutil/fileutil.go +++ b/pkg/fileutil/fileutil.go @@ -2,6 +2,7 @@ package fileutil import ( + "errors" "fmt" "io" "os" @@ -32,7 +33,7 @@ import ( func ValidateAbsolutePath(path string) (string, error) { // Check for empty path if path == "" { - return "", fmt.Errorf("path cannot be empty") + return "", errors.New("path cannot be empty") } // Sanitize the filepath to prevent path traversal attacks diff --git a/pkg/parser/frontmatter_content.go b/pkg/parser/frontmatter_content.go index 203ad073c6..fbaa30c9c4 100644 --- a/pkg/parser/frontmatter_content.go +++ b/pkg/parser/frontmatter_content.go @@ -3,6 +3,7 @@ package parser import ( "bufio" "bytes" + "errors" "fmt" "os" "path/filepath" @@ -48,7 +49,7 @@ func ExtractFrontmatterFromContent(content string) (*FrontmatterResult, error) { } if endIndex == -1 { - return nil, fmt.Errorf("frontmatter not properly closed") + return nil, errors.New("frontmatter not properly closed") } // Extract frontmatter YAML diff --git a/pkg/parser/frontmatter_hash.go b/pkg/parser/frontmatter_hash.go index 35c12b4be4..9448c07d66 100644 --- a/pkg/parser/frontmatter_hash.go +++ b/pkg/parser/frontmatter_hash.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -438,7 +439,7 @@ func extractFrontmatterAndBodyText(content string) (string, string, error) { } if endIndex == -1 { - return "", "", fmt.Errorf("frontmatter not properly closed") + return "", "", errors.New("frontmatter not properly closed") } // Extract frontmatter text (lines between --- delimiters) diff --git a/pkg/parser/frontmatter_helpers_test.go b/pkg/parser/frontmatter_helpers_test.go index 6a69ed93ce..c32905708f 100644 --- a/pkg/parser/frontmatter_helpers_test.go +++ b/pkg/parser/frontmatter_helpers_test.go @@ -3,7 +3,7 @@ package parser import ( - "fmt" + "errors" "os" "path/filepath" "strings" @@ -116,7 +116,7 @@ tools: {} --- # Test Workflow`, updateFunc: func(frontmatter map[string]any) error { - return fmt.Errorf("test error") + return errors.New("test error") }, expectedContent: "", expectError: true, diff --git a/pkg/parser/frontmatter_syntax_errors_test.go b/pkg/parser/frontmatter_syntax_errors_test.go index 36867b74be..17d4bf2539 100644 --- a/pkg/parser/frontmatter_syntax_errors_test.go +++ b/pkg/parser/frontmatter_syntax_errors_test.go @@ -3,7 +3,6 @@ package parser import ( - "fmt" "os" "path/filepath" "strings" @@ -373,7 +372,7 @@ This workflow may have schema validation errors.`, Column: column, }, Type: "error", - Message: fmt.Sprintf("frontmatter parsing failed: %s", message), + Message: "frontmatter parsing failed: " + message, } formattedError := console.FormatError(compilerError) @@ -572,7 +571,7 @@ The error is on line 5 where there's an unclosed bracket.` Column: column, }, Type: "error", - Message: fmt.Sprintf("frontmatter parsing failed: %s", message), + Message: "frontmatter parsing failed: " + message, } // Test that error formatting works diff --git a/pkg/parser/github.go b/pkg/parser/github.go index 10bbd6c020..fc5bcc3096 100644 --- a/pkg/parser/github.go +++ b/pkg/parser/github.go @@ -3,6 +3,7 @@ package parser import ( + "errors" "fmt" "os" "os/exec" @@ -93,7 +94,7 @@ func GetGitHubToken() (string, error) { token := strings.TrimSpace(string(output)) if token == "" { githubLog.Print("gh auth token returned empty token") - return "", fmt.Errorf("GITHUB_TOKEN environment variable not set and 'gh auth token' returned empty token") + return "", errors.New("GITHUB_TOKEN environment variable not set and 'gh auth token' returned empty token") } githubLog.Print("Successfully retrieved token from gh auth token") diff --git a/pkg/parser/github_urls.go b/pkg/parser/github_urls.go index 87fd6b963a..25733921c7 100644 --- a/pkg/parser/github_urls.go +++ b/pkg/parser/github_urls.go @@ -1,6 +1,7 @@ package parser import ( + "errors" "fmt" "net/url" "path/filepath" @@ -65,7 +66,7 @@ func ParseGitHubURL(urlStr string) (*GitHubURLComponents, error) { // Check if it's a GitHub-like host host := parsedURL.Host if host == "" { - return nil, fmt.Errorf("URL must include a host") + return nil, errors.New("URL must include a host") } urlLog.Printf("Detected host: %s", host) @@ -81,7 +82,7 @@ func ParseGitHubURL(urlStr string) (*GitHubURLComponents, error) { // Need at least owner and repo if len(pathParts) < 2 { - return nil, fmt.Errorf("invalid GitHub URL format: path too short") + return nil, errors.New("invalid GitHub URL format: path too short") } owner := pathParts[0] @@ -89,7 +90,7 @@ func ParseGitHubURL(urlStr string) (*GitHubURLComponents, error) { // Validate owner and repo are not empty if owner == "" || repo == "" { - return nil, fmt.Errorf("invalid GitHub URL: owner and repo cannot be empty") + return nil, errors.New("invalid GitHub URL: owner and repo cannot be empty") } // Determine the type based on path structure @@ -187,7 +188,7 @@ func ParseGitHubURL(urlStr string) (*GitHubURLComponents, error) { } } - return nil, fmt.Errorf("unrecognized GitHub URL format") + return nil, errors.New("unrecognized GitHub URL format") } // parseRunURL parses the run ID portion of a GitHub Actions URL @@ -198,7 +199,7 @@ func ParseGitHubURL(urlStr string) (*GitHubURLComponents, error) { // - /runs/12345678/attempts/2 func parseRunURL(host, owner, repo string, parts []string) (*GitHubURLComponents, error) { if len(parts) == 0 { - return nil, fmt.Errorf("missing run ID") + return nil, errors.New("missing run ID") } runID, err := strconv.ParseInt(parts[0], 10, 64) @@ -262,7 +263,7 @@ func parseRawGitHubContentURL(parsedURL *url.URL) (*GitHubURLComponents, error) // Need at least: owner, repo, ref-or-sha, and filename if len(pathParts) < 4 { - return nil, fmt.Errorf("invalid raw.githubusercontent.com URL format: path too short") + return nil, errors.New("invalid raw.githubusercontent.com URL format: path too short") } owner := pathParts[0] @@ -276,7 +277,7 @@ func parseRawGitHubContentURL(parsedURL *url.URL) (*GitHubURLComponents, error) // Format: /owner/repo/refs/heads/branch/path/to/file // or /owner/repo/refs/tags/tag/path/to/file if len(pathParts) < 5 { - return nil, fmt.Errorf("invalid raw.githubusercontent.com URL format: refs path too short") + return nil, errors.New("invalid raw.githubusercontent.com URL format: refs path too short") } // pathParts[3] is "heads" or "tags" ref = pathParts[4] // branch or tag name @@ -289,7 +290,7 @@ func parseRawGitHubContentURL(parsedURL *url.URL) (*GitHubURLComponents, error) // Validate owner and repo if owner == "" || repo == "" { - return nil, fmt.Errorf("invalid raw.githubusercontent.com URL: owner and repo cannot be empty") + return nil, errors.New("invalid raw.githubusercontent.com URL: owner and repo cannot be empty") } return &GitHubURLComponents{ @@ -325,7 +326,7 @@ func ParseRunURL(input string) (runID int64, owner, repo, hostname string, err e } if components.Type != URLTypeRun { - return 0, "", "", "", fmt.Errorf("URL is not a GitHub Actions run URL") + return 0, "", "", "", errors.New("URL is not a GitHub Actions run URL") } return components.Number, components.Owner, components.Repo, components.Host, nil @@ -349,7 +350,7 @@ func ParseRunURLExtended(input string) (*GitHubURLComponents, error) { } if components.Type != URLTypeRun { - return nil, fmt.Errorf("URL is not a GitHub Actions run URL") + return nil, errors.New("URL is not a GitHub Actions run URL") } return components, nil @@ -364,7 +365,7 @@ func ParsePRURL(prURL string) (owner, repo string, prNumber int, err error) { } if components.Type != URLTypePullRequest { - return "", "", 0, fmt.Errorf("URL is not a GitHub PR URL") + return "", "", 0, errors.New("URL is not a GitHub PR URL") } // Validate that Number fits in int range (important for 32-bit systems) @@ -395,7 +396,7 @@ func ParseRepoFileURL(fileURL string) (owner, repo, ref, filePath string, err er case URLTypeBlob, URLTypeTree, URLTypeRaw, URLTypeRawContent: return components.Owner, components.Repo, components.Ref, components.Path, nil default: - return "", "", "", "", fmt.Errorf("URL is not a GitHub file URL") + return "", "", "", "", errors.New("URL is not a GitHub file URL") } } diff --git a/pkg/parser/import_cache.go b/pkg/parser/import_cache.go index 5f9056a631..92bd727b19 100644 --- a/pkg/parser/import_cache.go +++ b/pkg/parser/import_cache.go @@ -1,6 +1,7 @@ package parser import ( + "errors" "fmt" "os" "path/filepath" @@ -34,7 +35,7 @@ func validatePathComponents(owner, repo, path, sha string) error { // Check for empty components if comp == "" { importCacheLog.Print("Path validation failed: empty component detected") - return fmt.Errorf("empty component in path") + return errors.New("empty component in path") } // Check for path traversal attempts if strings.Contains(comp, "..") { diff --git a/pkg/parser/import_error.go b/pkg/parser/import_error.go index 39ef0d35fe..4d5e4c21a9 100644 --- a/pkg/parser/import_error.go +++ b/pkg/parser/import_error.go @@ -1,6 +1,7 @@ package parser import ( + "errors" "fmt" "strings" @@ -40,7 +41,7 @@ func (e *ImportCycleError) Error() string { if len(e.Chain) == 0 { return "circular import detected" } - return fmt.Sprintf("circular import detected: %s", strings.Join(e.Chain, " → ")) + return "circular import detected: " + strings.Join(e.Chain, " → ") } // FormatImportCycleError formats an import cycle error with a delightful multiline indented display @@ -48,7 +49,7 @@ func FormatImportCycleError(err *ImportCycleError) error { importErrorLog.Printf("Formatting import cycle error: chain=%v, workflow=%s", err.Chain, err.WorkflowFile) if len(err.Chain) < 2 { - return fmt.Errorf("circular import detected (invalid chain)") + return errors.New("circular import detected (invalid chain)") } // Build a multiline, indented representation of the import chain diff --git a/pkg/parser/import_processor.go b/pkg/parser/import_processor.go index a9d0ff95f0..723dfb5d51 100644 --- a/pkg/parser/import_processor.go +++ b/pkg/parser/import_processor.go @@ -2,6 +2,7 @@ package parser import ( "encoding/json" + "errors" "fmt" "maps" "path" @@ -200,23 +201,23 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a // Object import with path and optional inputs pathValue, hasPath := importItem["path"] if !hasPath { - return nil, fmt.Errorf("import object must have a 'path' field") + return nil, errors.New("import object must have a 'path' field") } pathStr, ok := pathValue.(string) if !ok { - return nil, fmt.Errorf("import 'path' must be a string") + return nil, errors.New("import 'path' must be a string") } var inputs map[string]any if inputsValue, hasInputs := importItem["inputs"]; hasInputs { if inputsMap, ok := inputsValue.(map[string]any); ok { inputs = inputsMap } else { - return nil, fmt.Errorf("import 'inputs' must be an object") + return nil, errors.New("import 'inputs' must be an object") } } importSpecs = append(importSpecs, ImportSpec{Path: pathStr, Inputs: inputs}) default: - return nil, fmt.Errorf("import item must be a string or an object with 'path' field") + return nil, errors.New("import item must be a string or an object with 'path' field") } } case []string: @@ -224,7 +225,7 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a importSpecs = append(importSpecs, ImportSpec{Path: s}) } default: - return nil, fmt.Errorf("imports field must be an array of strings or objects") + return nil, errors.New("imports field must be an array of strings or objects") } if len(importSpecs) == 0 { @@ -322,7 +323,7 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a FilePath: workflowFilePath, Line: line, Column: column, - Cause: fmt.Errorf("cannot import .lock.yml files. Lock files are compiled outputs from gh-aw. Import the source .md file instead"), + Cause: errors.New("cannot import .lock.yml files. Lock files are compiled outputs from gh-aw. Import the source .md file instead"), } return nil, FormatImportError(importErr, yamlContent) } @@ -1061,7 +1062,7 @@ func topologicalSortImports(imports []string, baseDir string, cache *ImportCache } // Fallback error if we couldn't construct the path (shouldn't happen) - return nil, fmt.Errorf("circular import detected but could not determine cycle path") + return nil, errors.New("circular import detected but could not determine cycle path") } return result, nil diff --git a/pkg/parser/json_path_locator.go b/pkg/parser/json_path_locator.go index c88cb0fd34..87ce8ed5f1 100644 --- a/pkg/parser/json_path_locator.go +++ b/pkg/parser/json_path_locator.go @@ -1,6 +1,7 @@ package parser import ( + "errors" "regexp" "strconv" "strings" @@ -22,7 +23,8 @@ type JSONPathLocation struct { func ExtractJSONPathFromValidationError(err error) []JSONPathInfo { var paths []JSONPathInfo - if validationError, ok := err.(*jsonschema.ValidationError); ok { + var validationError *jsonschema.ValidationError + if errors.As(err, &validationError) { // Process each cause (individual validation error) for _, cause := range validationError.Causes { path := JSONPathInfo{ diff --git a/pkg/parser/remote_fetch.go b/pkg/parser/remote_fetch.go index ad05432824..c334dd0235 100644 --- a/pkg/parser/remote_fetch.go +++ b/pkg/parser/remote_fetch.go @@ -5,6 +5,7 @@ package parser import ( "encoding/base64" "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -222,7 +223,7 @@ func downloadIncludeFromWorkflowSpec(spec string, cache *ImportCache) (string, e slashParts := strings.Split(pathPart, "/") if len(slashParts) < 3 { remoteLog.Printf("Invalid workflowspec format: %s", spec) - return "", fmt.Errorf("invalid workflowspec: must be owner/repo/path[@ref]") + return "", errors.New("invalid workflowspec: must be owner/repo/path[@ref]") } owner := slashParts[0] @@ -337,7 +338,7 @@ func resolveRefToSHAViaGit(owner, repo, ref string) (string, error) { // Extract SHA from the first line parts := strings.Fields(lines[0]) if len(parts) < 1 { - return "", fmt.Errorf("invalid git ls-remote output format") + return "", errors.New("invalid git ls-remote output format") } sha := parts[0] @@ -371,7 +372,7 @@ func resolveRefToSHA(owner, repo, ref string) (string, error) { sha, gitErr := resolveRefToSHAViaGit(owner, repo, ref) if gitErr != nil { // If git fallback also fails, return both errors - return "", fmt.Errorf("failed to resolve ref via GitHub API (auth error) and git ls-remote: API error: %w, Git error: %v", err, gitErr) + return "", fmt.Errorf("failed to resolve ref via GitHub API (auth error) and git ls-remote: API error: %w, Git error: %w", err, gitErr) } return sha, nil } @@ -648,7 +649,7 @@ func downloadFileFromGitHubWithDepth(owner, repo, path, ref string, symlinkDepth content, gitErr := downloadFileViaGit(owner, repo, path, ref) if gitErr != nil { // If git fallback also fails, return both errors - return nil, fmt.Errorf("failed to fetch file content via GitHub API (auth error) and git fallback: API error: %w, Git error: %v", err, gitErr) + return nil, fmt.Errorf("failed to fetch file content via GitHub API (auth error) and git fallback: API error: %w, Git error: %w", err, gitErr) } return content, nil } @@ -712,7 +713,7 @@ func ListWorkflowFiles(owner, repo, ref, workflowPath string) ([]string, error) files, gitErr := listWorkflowFilesViaGit(owner, repo, ref, workflowPath) if gitErr != nil { // If git fallback also fails, return both errors - return nil, fmt.Errorf("failed to list workflow files via GitHub API (auth error) and git fallback: API error: %w, Git error: %v", err, gitErr) + return nil, fmt.Errorf("failed to list workflow files via GitHub API (auth error) and git fallback: API error: %w, Git error: %w", err, gitErr) } return files, nil } diff --git a/pkg/parser/schedule_parser.go b/pkg/parser/schedule_parser.go index b33f686a2d..97f0ed7a90 100644 --- a/pkg/parser/schedule_parser.go +++ b/pkg/parser/schedule_parser.go @@ -1,6 +1,7 @@ package parser import ( + "errors" "fmt" "regexp" "strconv" @@ -24,7 +25,7 @@ func ParseSchedule(input string) (cron string, original string, err error) { scheduleLog.Printf("Parsing schedule expression: %s", input) input = strings.TrimSpace(input) if input == "" { - return "", "", fmt.Errorf("schedule expression cannot be empty") + return "", "", errors.New("schedule expression cannot be empty") } // If it's already a cron expression (5 fields separated by spaces), return as-is @@ -62,7 +63,7 @@ func (p *ScheduleParser) tokenize() error { // Split on whitespace tokens := strings.Fields(input) if len(tokens) == 0 { - return fmt.Errorf("empty schedule expression") + return errors.New("empty schedule expression") } p.tokens = tokens @@ -73,7 +74,7 @@ func (p *ScheduleParser) tokenize() error { // parse parses the tokens into a cron expression func (p *ScheduleParser) parse() (string, error) { if len(p.tokens) == 0 { - return "", fmt.Errorf("no tokens to parse") + return "", errors.New("no tokens to parse") } // Check for interval-based schedules: "every N minutes|hours" @@ -88,7 +89,7 @@ func (p *ScheduleParser) parse() (string, error) { // parseInterval parses interval-based schedules like "every 10 minutes" or "every 2h" func (p *ScheduleParser) parseInterval() (string, error) { if len(p.tokens) < 2 { - return "", fmt.Errorf("invalid interval format, expected 'every N unit' or 'every Nunit'") + return "", errors.New("invalid interval format, expected 'every N unit' or 'every Nunit'") } // Check if "on weekdays" suffix is present at the end @@ -115,7 +116,7 @@ func (p *ScheduleParser) parseInterval() (string, error) { if len(p.tokens) > 2 { for i := 2; i < endPos; i++ { if p.tokens[i] == "at" { - return "", fmt.Errorf("interval schedules cannot have 'at time' clause") + return "", errors.New("interval schedules cannot have 'at time' clause") } } } @@ -144,7 +145,7 @@ func (p *ScheduleParser) parseInterval() (string, error) { // every Nm -> */N * * * * (minute intervals don't need scattering) // Minute intervals with weekdays not supported (would run every N minutes only on weekdays) if hasWeekdaysSuffix { - return "", fmt.Errorf("minute intervals with 'on weekdays' are not supported") + return "", errors.New("minute intervals with 'on weekdays' are not supported") } return fmt.Sprintf("*/%d * * * *", interval), nil case "h": @@ -190,7 +191,7 @@ func (p *ScheduleParser) parseInterval() (string, error) { minTokens = 5 } if len(p.tokens) < minTokens { - return "", fmt.Errorf("invalid interval format, expected 'every N unit' or 'every Nunit' (e.g., 'every 2h')") + return "", errors.New("invalid interval format, expected 'every N unit' or 'every Nunit' (e.g., 'every 2h')") } // Parse the interval number @@ -215,7 +216,7 @@ func (p *ScheduleParser) parseInterval() (string, error) { // Look for "at" keyword for i := 3; i < endPos; i++ { if p.tokens[i] == "at" { - return "", fmt.Errorf("interval schedules cannot have 'at time' clause") + return "", errors.New("interval schedules cannot have 'at time' clause") } } } @@ -245,7 +246,7 @@ func (p *ScheduleParser) parseInterval() (string, error) { // every N minutes -> */N * * * * (minute intervals don't need scattering) // Minute intervals with weekdays not supported if hasWeekdaysSuffix { - return "", fmt.Errorf("minute intervals with 'on weekdays' are not supported") + return "", errors.New("minute intervals with 'on weekdays' are not supported") } return fmt.Sprintf("*/%d * * * *", interval), nil case "hours": @@ -270,7 +271,7 @@ func (p *ScheduleParser) parseInterval() (string, error) { // parseBase parses base schedules like "daily", "weekly on monday", etc. func (p *ScheduleParser) parseBase() (string, error) { if len(p.tokens) == 0 { - return "", fmt.Errorf("empty schedule") + return "", errors.New("empty schedule") } baseType := p.tokens[0] @@ -308,7 +309,7 @@ func (p *ScheduleParser) parseBase() (string, error) { // Parse: "daily between START and END" // We need at least: daily between TIME and TIME (5 tokens minimum) if len(p.tokens) < 5 { - return "", fmt.Errorf("invalid 'between' format, expected 'daily between START and END'") + return "", errors.New("invalid 'between' format, expected 'daily between START and END'") } // Find the "and" keyword to split start and end times @@ -325,7 +326,7 @@ func (p *ScheduleParser) parseBase() (string, error) { } } if andIndex == -1 { - return "", fmt.Errorf("missing 'and' keyword in 'between' clause") + return "", errors.New("missing 'and' keyword in 'between' clause") } // Extract start time (tokens between "between" and "and") @@ -349,7 +350,7 @@ func (p *ScheduleParser) parseBase() (string, error) { // Allow ranges that cross midnight (e.g., 22:00 to 02:00) // We'll handle this in the scattering logic if startMinutes == endMinutes { - return "", fmt.Errorf("start and end times cannot be the same in 'between' clause") + return "", errors.New("start and end times cannot be the same in 'between' clause") } // Return fuzzy between format with optional weekdays suffix @@ -374,7 +375,7 @@ func (p *ScheduleParser) parseBase() (string, error) { return fmt.Sprintf("FUZZY:DAILY_AROUND:%s:%s * * *", hour, minute), nil } // Reject "daily at TIME" pattern - use cron directly for fixed times - return "", fmt.Errorf("'daily at