From 60c331d0d456c45e29286b77a55814144f5b2e8a Mon Sep 17 00:00:00 2001 From: Don Syme Date: Sat, 11 Oct 2025 19:47:45 +0100 Subject: [PATCH 1/4] extended update command --- pkg/cli/update_command.go | 200 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 192 insertions(+), 8 deletions(-) diff --git a/pkg/cli/update_command.go b/pkg/cli/update_command.go index 19e5b4119a3..07b27c2af54 100644 --- a/pkg/cli/update_command.go +++ b/pkg/cli/update_command.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "math/rand" "os" "os/exec" "path/filepath" @@ -18,33 +19,38 @@ import ( func NewUpdateCommand(validateEngine func(string) error) *cobra.Command { cmd := &cobra.Command{ Use: "update [workflow-name]...", - Short: "Update workflows from their source repositories", - Long: `Update one or more workflows from their source repositories. + Short: "Update workflows from their source repositories and check for gh-aw updates", + Long: `Update one or more workflows from their source repositories and check for gh-aw updates. -The command uses the 'source' field in the workflow frontmatter to determine -the source repository and version. It then fetches the latest version based on -the current ref: +The command: +1. Checks if a newer version of gh-aw is available +2. Updates workflows using the 'source' field in the workflow frontmatter +3. Recompiles all workflows + +For workflow updates, it fetches the latest version based on the current ref: - If the ref is a tag, it updates to the latest release (use --major for major version updates) - If the ref is a branch, it fetches the latest commit from that branch - Otherwise, it fetches the latest commit from the default branch Examples: - ` + constants.CLIExtensionPrefix + ` update # Update all workflows with source field - ` + constants.CLIExtensionPrefix + ` update ci-doctor # Update specific workflow + ` + constants.CLIExtensionPrefix + ` update # Check gh-aw updates and update all workflows + ` + constants.CLIExtensionPrefix + ` update ci-doctor # Check gh-aw updates and update specific workflow ` + constants.CLIExtensionPrefix + ` update ci-doctor --major # Allow major version updates + ` + constants.CLIExtensionPrefix + ` update --pr # Create PR with changes ` + constants.CLIExtensionPrefix + ` update --force # Force update even if no changes`, Run: func(cmd *cobra.Command, args []string) { majorFlag, _ := cmd.Flags().GetBool("major") forceFlag, _ := cmd.Flags().GetBool("force") engineOverride, _ := cmd.Flags().GetString("engine") verbose, _ := cmd.Flags().GetBool("verbose") + prFlag, _ := cmd.Flags().GetBool("pr") if err := validateEngine(engineOverride); err != nil { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error())) os.Exit(1) } - if err := UpdateWorkflows(args, majorFlag, forceFlag, verbose, engineOverride); err != nil { + if err := UpdateWorkflowsWithExtensionCheck(args, majorFlag, forceFlag, verbose, engineOverride, prFlag); err != nil { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error())) os.Exit(1) } @@ -54,10 +60,188 @@ Examples: cmd.Flags().Bool("major", false, "Allow major version updates when updating tagged releases") cmd.Flags().Bool("force", false, "Force update even if no changes detected") cmd.Flags().StringP("engine", "a", "", "Override AI engine (claude, codex, copilot)") + cmd.Flags().Bool("pr", false, "Create a pull request with the workflow changes") return cmd } +// checkExtensionUpdate checks if a newer version of gh-aw is available +func checkExtensionUpdate(verbose bool) error { + if verbose { + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Checking for gh-aw extension updates...")) + } + + // Run gh extension upgrade --dry-run to check for updates + cmd := exec.Command("gh", "extension", "upgrade", "githubnext/gh-aw", "--dry-run") + output, err := cmd.CombinedOutput() + if err != nil { + if verbose { + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to check for extension updates: %v", err))) + } + return nil // Don't fail the whole command if update check fails + } + + outputStr := strings.TrimSpace(string(output)) + if verbose { + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Extension update check output: %s", outputStr))) + } + + // Parse the output to see if an update is available + // Expected format: "[aw]: would have upgraded from v0.14.0 to v0.18.1" + lines := strings.Split(outputStr, "\n") + for _, line := range lines { + if strings.Contains(line, "[aw]: would have upgraded from") { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(line)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Run 'gh extension upgrade githubnext/gh-aw' to update")) + return nil + } + } + + if strings.Contains(outputStr, "✓ Successfully checked extension upgrades") { + if verbose { + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("gh-aw extension is up to date")) + } + } + + return nil +} + +// runCompileWorkflows runs the compile command to recompile all workflows +func runCompileWorkflows(verbose bool, engineOverride string) error { + if verbose { + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Compiling workflows...")) + } + + // Create a compiler instance similar to how compile command does it + compiler := workflow.NewCompiler(verbose, engineOverride, GetVersion()) + + // Compile all workflows in the workflows directory + workflowsDir := getWorkflowsDir() + err := compileAllWorkflowFiles(compiler, workflowsDir, verbose) + if err != nil { + return fmt.Errorf("failed to compile workflows: %w", err) + } + + if verbose { + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Successfully compiled all workflows")) + } + return nil +} + +// UpdateWorkflowsWithExtensionCheck performs the complete update process: +// 1. Check for gh-aw extension updates +// 2. Update workflows from source repositories +// 3. Compile all workflows +// 4. Optionally create a PR +func UpdateWorkflowsWithExtensionCheck(workflowNames []string, allowMajor, force, verbose bool, engineOverride string, createPR bool) error { + // Step 1: Check for gh-aw extension updates + if err := checkExtensionUpdate(verbose); err != nil { + return fmt.Errorf("extension update check failed: %w", err) + } + + // Step 2: Update workflows from source repositories + if err := UpdateWorkflows(workflowNames, allowMajor, force, verbose, engineOverride); err != nil { + return fmt.Errorf("workflow update failed: %w", err) + } + + // Step 3: Compile all workflows + if err := runCompileWorkflows(verbose, engineOverride); err != nil { + return fmt.Errorf("compile failed: %w", err) + } + + // Step 4: Optionally create PR if flag is set + if createPR { + if err := createUpdatePR(verbose); err != nil { + return fmt.Errorf("failed to create PR: %w", err) + } + } + + return nil +} + +// hasGitChanges checks if there are any uncommitted changes +func hasGitChanges() (bool, error) { + cmd := exec.Command("git", "status", "--porcelain") + output, err := cmd.Output() + if err != nil { + return false, fmt.Errorf("failed to check git status: %w", err) + } + + return len(strings.TrimSpace(string(output))) > 0, nil +} + +// runGitCommand runs a git command with the specified arguments +func runGitCommand(args ...string) error { + cmd := exec.Command("git", args...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("git %s failed: %w", strings.Join(args, " "), err) + } + return nil +} + +// createUpdatePR creates a pull request with the workflow changes +func createUpdatePR(verbose bool) error { + // Check if GitHub CLI is available + if !isGHCLIAvailable() { + return fmt.Errorf("GitHub CLI (gh) is required for PR creation but not found in PATH") + } + + // Check if there are any changes to commit + hasChanges, err := hasGitChanges() + if err != nil { + return fmt.Errorf("failed to check git status: %w", err) + } + + if !hasChanges { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No changes to create PR for")) + return nil + } + + if verbose { + fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Creating pull request with workflow updates...")) + } + + // Create a branch name with timestamp + randomNum := rand.Intn(9000) + 1000 // Generate number between 1000-9999 + branchName := fmt.Sprintf("update-workflows-%d", randomNum) + + // Create and checkout new branch + if err := runGitCommand("checkout", "-b", branchName); err != nil { + return fmt.Errorf("failed to create branch: %w", err) + } + + // Add all changes + if err := runGitCommand("add", "."); err != nil { + return fmt.Errorf("failed to add changes: %w", err) + } + + // Commit changes + commitMsg := "Update workflows and recompile" + if err := runGitCommand("commit", "-m", commitMsg); err != nil { + return fmt.Errorf("failed to commit changes: %w", err) + } + + // Push branch + if err := runGitCommand("push", "-u", "origin", branchName); err != nil { + return fmt.Errorf("failed to push branch: %w", err) + } + + // Create PR + cmd := exec.Command("gh", "pr", "create", + "--title", "Update workflows and recompile", + "--body", "This PR updates workflows from their source repositories and recompiles them.\n\nGenerated by `gh aw update --pr`") + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to create PR: %w\nOutput: %s", err, string(output)) + } + + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Successfully created pull request")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(strings.TrimSpace(string(output)))) + + return nil +} + // UpdateWorkflows updates workflows from their source repositories func UpdateWorkflows(workflowNames []string, allowMajor, force, verbose bool, engineOverride string) error { workflowsDir := getWorkflowsDir() From 7ec192b1edd561cee0a578a1f3d96695a0831a1e Mon Sep 17 00:00:00 2001 From: Don Syme Date: Sat, 11 Oct 2025 20:05:00 +0100 Subject: [PATCH 2/4] preserve format on update --- pkg/cli/add_command.go | 36 +++++++++++++++ pkg/cli/add_source_test.go | 55 +++++++++++++++++++++++ pkg/cli/format_preservation_test.go | 69 +++++++++++++++++++++++++++++ pkg/cli/update_command.go | 4 +- 4 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 pkg/cli/format_preservation_test.go diff --git a/pkg/cli/add_command.go b/pkg/cli/add_command.go index 1b92107c885..91e359907fe 100644 --- a/pkg/cli/add_command.go +++ b/pkg/cli/add_command.go @@ -804,6 +804,42 @@ func addSourceToWorkflow(content, source string) (string, error) { return "", fmt.Errorf("failed to parse frontmatter: %w", err) } + // Try to preserve original frontmatter formatting by manually inserting the source field + if len(result.FrontmatterLines) > 0 { + // Check if source field already exists + if result.Frontmatter != nil { + if _, exists := result.Frontmatter["source"]; exists { + // Source field exists, replace it by parsing and re-marshaling (fallback behavior) + return addSourceToWorkflowFallback(result, source) + } + } + + // Source field doesn't exist, insert it manually to preserve formatting + frontmatterLines := make([]string, len(result.FrontmatterLines)) + copy(frontmatterLines, result.FrontmatterLines) + + // Add source field at the end of the frontmatter, preserving original formatting + sourceField := fmt.Sprintf("source: %s", source) + frontmatterLines = append(frontmatterLines, sourceField) + + // Reconstruct the file with preserved formatting + var lines []string + lines = append(lines, "---") + lines = append(lines, frontmatterLines...) + lines = append(lines, "---") + if result.Markdown != "" { + lines = append(lines, result.Markdown) + } + + return strings.Join(lines, "\n"), nil + } + + // Fallback to original behavior if no frontmatter lines are available + return addSourceToWorkflowFallback(result, source) +} + +// addSourceToWorkflowFallback implements the original behavior as a fallback +func addSourceToWorkflowFallback(result *parser.FrontmatterResult, source string) (string, error) { // Initialize frontmatter if it doesn't exist if result.Frontmatter == nil { result.Frontmatter = make(map[string]any) diff --git a/pkg/cli/add_source_test.go b/pkg/cli/add_source_test.go index 2b213f62917..739fb5614de 100644 --- a/pkg/cli/add_source_test.go +++ b/pkg/cli/add_source_test.go @@ -79,6 +79,41 @@ This workflow has complex 'on' triggers.`, expectError: false, checkSource: true, }, + { + name: "preserve_formatting_with_comments_and_blank_lines", + content: `--- +on: + workflow_dispatch: + + schedule: + # Run daily at 2am UTC, all days except Saturday and Sunday + - cron: "0 2 * * 1-5" + + stop-after: +48h # workflow will no longer trigger after 48 hours + +timeout_minutes: 30 + +permissions: read-all + +network: defaults + +engine: claude + +tools: + # Web tools for testing + web-search: null + + # Memory cache + cache-memory: true +--- + +# Well Formatted Workflow + +This workflow has proper formatting with comments and blank lines.`, + source: "githubnext/agentics/workflows/formatted.md@v1.0.0", + expectError: false, + checkSource: true, + }, } for _, tt := range tests { @@ -113,11 +148,31 @@ This workflow has complex 'on' triggers.`, if strings.Contains(tt.content, "# Test Workflow") && !strings.Contains(result, "# Test Workflow") { t.Errorf("addSourceToWorkflow() result does not preserve markdown content") } + if strings.Contains(tt.content, "# Well Formatted Workflow") && !strings.Contains(result, "# Well Formatted Workflow") { + t.Errorf("addSourceToWorkflow() result does not preserve markdown content") + } // Verify that "on" keyword is not quoted if strings.Contains(result, `"on":`) { t.Errorf("addSourceToWorkflow() result contains quoted 'on' keyword, should be unquoted. Result:\n%s", result) } + + // For the formatting preservation test, verify that comments and blank lines are preserved + if tt.name == "preserve_formatting_with_comments_and_blank_lines" { + if !strings.Contains(result, "# Run daily at 2am UTC, all days except Saturday and Sunday") { + t.Errorf("addSourceToWorkflow() result does not preserve comments") + } + if !strings.Contains(result, "stop-after: +48h # workflow will no longer trigger") { + t.Errorf("addSourceToWorkflow() result does not preserve inline comments") + } + if !strings.Contains(result, " # Web tools for testing") { + t.Errorf("addSourceToWorkflow() result does not preserve indented comments") + } + // Check that there are still blank lines by checking for consecutive newlines + if !strings.Contains(result, "\n\n") { + t.Errorf("addSourceToWorkflow() result does not preserve blank lines") + } + } } }) } diff --git a/pkg/cli/format_preservation_test.go b/pkg/cli/format_preservation_test.go new file mode 100644 index 00000000000..e5d4e6a241c --- /dev/null +++ b/pkg/cli/format_preservation_test.go @@ -0,0 +1,69 @@ +package cli + +import ( + "fmt" + "strings" + "testing" +) + +func TestFormatPreservationManual(t *testing.T) { + content := `--- +on: + workflow_dispatch: + + schedule: + # Daily run at 9 AM UTC on weekdays + - cron: "0 9 * * 1-5" + + # Auto-stop workflow after 2 hours + stop-after: +2h + +timeout_minutes: 45 + +permissions: read-all + +network: defaults + +engine: claude + +tools: + # Enable web search functionality + web-search: null + + # Memory caching for better performance + cache-memory: true + +--- + +# Test Formatting Preservation + +This workflow is designed to test whether the formatting is preserved.` + + result, err := addSourceToWorkflow(content, "test/repo/workflow.md@v1.0.0") + if err != nil { + t.Fatalf("Error adding source: %v", err) + } + + fmt.Printf("=== ORIGINAL ===\n%s\n\n", content) + fmt.Printf("=== RESULT ===\n%s\n", result) + + // Check if comments are preserved + if !strings.Contains(result, "# Daily run at 9 AM UTC on weekdays") { + t.Error("Comments were not preserved") + } + + // Check if blank lines are preserved (look for consecutive newlines) + if !strings.Contains(result, "\n\n") { + t.Error("Blank lines were not preserved") + } + + // Check if inline comments are preserved + if !strings.Contains(result, "stop-after: +2h") { + t.Error("Inline comments were not preserved") + } + + // Check if indentation is preserved (4-space indentation) + if !strings.Contains(result, " workflow_dispatch:") { + t.Error("4-space indentation was not preserved") + } +} \ No newline at end of file diff --git a/pkg/cli/update_command.go b/pkg/cli/update_command.go index 07b27c2af54..38929e72b36 100644 --- a/pkg/cli/update_command.go +++ b/pkg/cli/update_command.go @@ -227,10 +227,10 @@ func createUpdatePR(verbose bool) error { } // Create PR - cmd := exec.Command("gh", "pr", "create", + cmd := exec.Command("gh", "pr", "create", "--title", "Update workflows and recompile", "--body", "This PR updates workflows from their source repositories and recompiles them.\n\nGenerated by `gh aw update --pr`") - + output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to create PR: %w\nOutput: %s", err, string(output)) From d166dabaa3bef1cb9cc142314959b76a4cb69f3b Mon Sep 17 00:00:00 2001 From: Don Syme Date: Sat, 11 Oct 2025 20:08:32 +0100 Subject: [PATCH 3/4] format code --- .github/workflows/lockfile-stats.lock.yml | 144 +++++++++------------- pkg/cli/add_command.go | 2 +- pkg/cli/format_preservation_test.go | 2 +- 3 files changed, 61 insertions(+), 87 deletions(-) diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 09a87ca0ae3..0ff403e3ce2 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -213,7 +213,7 @@ jobs: with: node-version: '24' - name: Install Claude Code CLI - run: npm install -g @anthropic-ai/claude-code@2.0.14 + run: npm install -g @anthropic-ai/claude-code@2.0.13 - name: Generate Claude Settings run: | mkdir -p /tmp/gh-aw/.claude @@ -250,7 +250,6 @@ jobs: import re # Domain allow-list (populated during generation) - # JSON array safely embedded as Python list literal ALLOWED_DOMAINS = ["crl3.digicert.com","crl4.digicert.com","ocsp.digicert.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","crl.geotrust.com","ocsp.geotrust.com","crl.thawte.com","ocsp.thawte.com","crl.verisign.com","ocsp.verisign.com","crl.globalsign.com","ocsp.globalsign.com","crls.ssl.com","ocsp.ssl.com","crl.identrust.com","ocsp.identrust.com","crl.sectigo.com","ocsp.sectigo.com","crl.usertrust.com","ocsp.usertrust.com","s.symcb.com","s.symcd.com","json-schema.org","json.schemastore.org","archive.ubuntu.com","security.ubuntu.com","ppa.launchpad.net","keyserver.ubuntu.com","azure.archive.ubuntu.com","api.snapcraft.io","packagecloud.io","packages.cloud.google.com","packages.microsoft.com"] def extract_domain(url_or_query): @@ -621,7 +620,7 @@ jobs: }, { name: "add_comment", - description: "Add a comment to a GitHub issue, pull request, or discussion", + description: "Add a comment to a GitHub issue or pull request", inputSchema: { type: "object", required: ["body"], @@ -629,15 +628,7 @@ jobs: body: { type: "string", description: "Comment body/content" }, issue_number: { type: "number", - description: "Issue number (optional for current context)", - }, - pull_number: { - type: "number", - description: "Pull request number (optional, alternative to issue_number)", - }, - discussion_number: { - type: "number", - description: "Discussion number for discussion comments (optional, alternative to issue_number)", + description: "Issue or PR number (optional for current context)", }, }, additionalProperties: false, @@ -1012,7 +1003,7 @@ jobs: GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-discussion\":{\"max\":1},\"missing-tool\":{}}" run: | mkdir -p /tmp/gh-aw/mcp-config - cat > /tmp/gh-aw/mcp-config/mcp-servers.json << EOF + cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' { "mcpServers": { "github": { @@ -1023,9 +1014,7 @@ jobs: "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", - "-e", - "GITHUB_TOOLSETS=all", - "ghcr.io/github/github-mcp-server:v0.18.0" + "ghcr.io/github/github-mcp-server:sha-09deac4" ], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" @@ -1460,6 +1449,15 @@ jobs: To report a missing tool use the missing-tool tool from the safe-outputs MCP. EOF + - name: Print prompt to step summary + env: + GITHUB_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: | + echo "## Generated Prompt" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```markdown' >> $GITHUB_STEP_SUMMARY + cat $GITHUB_AW_PROMPT >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY - name: Capture agent version run: | VERSION_OUTPUT=$(claude --version 2>&1 || echo "unknown") @@ -1546,7 +1544,6 @@ jobs: # - mcp__github__get_issue # - mcp__github__get_issue_comments # - mcp__github__get_job_logs - # - mcp__github__get_label # - mcp__github__get_latest_release # - mcp__github__get_me # - mcp__github__get_notification_details @@ -1571,7 +1568,6 @@ jobs: # - mcp__github__list_discussions # - mcp__github__list_issue_types # - mcp__github__list_issues - # - mcp__github__list_label # - mcp__github__list_notifications # - mcp__github__list_pull_requests # - mcp__github__list_releases @@ -1583,7 +1579,6 @@ jobs: # - mcp__github__list_workflow_run_artifacts # - mcp__github__list_workflow_runs # - mcp__github__list_workflows - # - mcp__github__pull_request_read # - mcp__github__search_code # - mcp__github__search_issues # - mcp__github__search_orgs @@ -1594,7 +1589,7 @@ jobs: run: | set -o pipefail # Execute Claude Code CLI with prompt from file - claude --print --mcp-config /tmp/gh-aw/mcp-config/mcp-servers.json --allowed-tools "Bash(cat),Bash(date),Bash(echo),Bash(grep),Bash(head),Bash(ls),Bash(pwd),Bash(sort),Bash(tail),Bash(uniq),Bash(wc),BashOutput,Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookRead,Read,Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/gh-aw/cache-memory/*),mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_sub_issues,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users" --debug --verbose --permission-mode bypassPermissions --output-format stream-json --settings /tmp/gh-aw/.claude/settings.json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee /tmp/gh-aw/agent-stdio.log + claude --print --mcp-config /tmp/gh-aw/mcp-config/mcp-servers.json --allowed-tools "Bash(cat),Bash(date),Bash(echo),Bash(grep),Bash(head),Bash(ls),Bash(pwd),Bash(sort),Bash(tail),Bash(uniq),Bash(wc),BashOutput,Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookRead,Read,Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/gh-aw/cache-memory/*),mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_sub_issues,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users" --debug --verbose --permission-mode bypassPermissions --output-format stream-json --settings /tmp/gh-aw/.claude/settings.json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} DISABLE_TELEMETRY: "1" @@ -2050,39 +2045,16 @@ jobs: if (item.labels && Array.isArray(item.labels)) { item.labels = item.labels.map(label => (typeof label === "string" ? sanitizeContent(label) : label)); } - if (item.parent !== undefined) { - const parentValidation = validateIssueOrPRNumber(item.parent, "create_issue 'parent'", i + 1); - if (!parentValidation.isValid) { - if (parentValidation.error) errors.push(parentValidation.error); - continue; - } - } break; case "add-comment": if (!item.body || typeof item.body !== "string") { errors.push(`Line ${i + 1}: add_comment requires a 'body' string field`); continue; } - if (item.issue_number !== undefined) { - const issueNumValidation = validateIssueOrPRNumber(item.issue_number, "add_comment 'issue_number'", i + 1); - if (!issueNumValidation.isValid) { - if (issueNumValidation.error) errors.push(issueNumValidation.error); - continue; - } - } - if (item.discussion_number !== undefined) { - const discussionNumValidation = validateIssueOrPRNumber(item.discussion_number, "add_comment 'discussion_number'", i + 1); - if (!discussionNumValidation.isValid) { - if (discussionNumValidation.error) errors.push(discussionNumValidation.error); - continue; - } - } - if (item.pull_number !== undefined) { - const pullNumValidation = validateIssueOrPRNumber(item.pull_number, "add_comment 'pull_number'", i + 1); - if (!pullNumValidation.isValid) { - if (pullNumValidation.error) errors.push(pullNumValidation.error); - continue; - } + const issueNumValidation = validateIssueOrPRNumber(item.issue_number, "add_comment 'issue_number'", i + 1); + if (!issueNumValidation.isValid) { + if (issueNumValidation.error) errors.push(issueNumValidation.error); + continue; } item.body = sanitizeContent(item.body); break; @@ -2373,6 +2345,18 @@ jobs: const outputTypes = Array.from(new Set(parsedItems.map(item => item.type))); core.info(`output_types: ${outputTypes.join(", ")}`); core.setOutput("output_types", outputTypes.join(",")); + try { + await core.summary + .addRaw("## Processed Output\n\n") + .addRaw("```json\n") + .addRaw(JSON.stringify(validatedOutput)) + .addRaw("\n```\n") + .write(); + core.info("Successfully wrote processed output to step summary"); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.warning(`Failed to write to step summary: ${errorMsg}`); + } } await main(); - name: Upload sanitized agent output @@ -2465,16 +2449,6 @@ jobs: mcpFailures: [], }; } - const toolUsePairs = new Map(); - for (const entry of logEntries) { - if (entry.type === "user" && entry.message?.content) { - for (const content of entry.message.content) { - if (content.type === "tool_result" && content.tool_use_id) { - toolUsePairs.set(content.tool_use_id, content); - } - } - } - } let markdown = ""; const mcpFailures = []; const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init"); @@ -2485,27 +2459,18 @@ jobs: mcpFailures.push(...initResult.mcpFailures); markdown += "\n"; } - markdown += "\n## 🤖 Reasoning\n\n"; + markdown += "## 🤖 Commands and Tools\n\n"; + const toolUsePairs = new Map(); + const commandSummary = []; for (const entry of logEntries) { - if (entry.type === "assistant" && entry.message?.content) { + if (entry.type === "user" && entry.message?.content) { for (const content of entry.message.content) { - if (content.type === "text" && content.text) { - const text = content.text.trim(); - if (text && text.length > 0) { - markdown += text + "\n\n"; - } - } else if (content.type === "tool_use") { - const toolResult = toolUsePairs.get(content.id); - const toolMarkdown = formatToolUse(content, toolResult); - if (toolMarkdown) { - markdown += toolMarkdown; - } + if (content.type === "tool_result" && content.tool_use_id) { + toolUsePairs.set(content.tool_use_id, content); } } } } - markdown += "## 🤖 Commands and Tools\n\n"; - const commandSummary = []; for (const entry of logEntries) { if (entry.type === "assistant" && entry.message?.content) { for (const content of entry.message.content) { @@ -2570,6 +2535,25 @@ jobs: markdown += `**Permission Denials:** ${lastEntry.permission_denials.length}\n\n`; } } + markdown += "\n## 🤖 Reasoning\n\n"; + for (const entry of logEntries) { + if (entry.type === "assistant" && entry.message?.content) { + for (const content of entry.message.content) { + if (content.type === "text" && content.text) { + const text = content.text.trim(); + if (text && text.length > 0) { + markdown += text + "\n\n"; + } + } else if (content.type === "tool_use") { + const toolResult = toolUsePairs.get(content.id); + const toolMarkdown = formatToolUse(content, toolResult); + if (toolMarkdown) { + markdown += toolMarkdown; + } + } + } + } + } return { markdown, mcpFailures }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -2786,15 +2770,6 @@ jobs: }; } main(); - - name: Print prompt to step summary - env: - GITHUB_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - run: | - echo "## Generated Prompt" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```markdown' >> $GITHUB_STEP_SUMMARY - cat $GITHUB_AW_PROMPT >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - name: Upload Agent Stdio if: always() uses: actions/upload-artifact@v4 @@ -2807,7 +2782,7 @@ jobs: uses: actions/github-script@v8 env: GITHUB_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log - GITHUB_AW_ERROR_PATTERNS: "[{\"pattern\":\"access denied.*only authorized.*can trigger.*workflow\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied - workflow access restriction\"},{\"pattern\":\"access denied.*user.*not authorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied - user not authorized\"},{\"pattern\":\"repository permission check failed\",\"level_group\":0,\"message_group\":0,\"description\":\"Repository permission check failure\"},{\"pattern\":\"configuration error.*required permissions not specified\",\"level_group\":0,\"message_group\":0,\"description\":\"Configuration error - missing permissions\"},{\"pattern\":\"\\\\berror\\\\b.*permission.*denied\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied error (requires error context)\"},{\"pattern\":\"\\\\berror\\\\b.*unauthorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Unauthorized error (requires error context)\"},{\"pattern\":\"\\\\berror\\\\b.*forbidden\",\"level_group\":0,\"message_group\":0,\"description\":\"Forbidden error (requires error context)\"},{\"pattern\":\"\\\\berror\\\\b.*access.*restricted\",\"level_group\":0,\"message_group\":0,\"description\":\"Access restricted error (requires error context)\"},{\"pattern\":\"\\\\berror\\\\b.*insufficient.*permission\",\"level_group\":0,\"message_group\":0,\"description\":\"Insufficient permissions error (requires error context)\"}]" + GITHUB_AW_ERROR_PATTERNS: "[{\"pattern\":\"access denied.*only authorized.*can trigger.*workflow\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied - workflow access restriction\"},{\"pattern\":\"access denied.*user.*not authorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied - user not authorized\"},{\"pattern\":\"repository permission check failed\",\"level_group\":0,\"message_group\":0,\"description\":\"Repository permission check failure\"},{\"pattern\":\"configuration error.*required permissions not specified\",\"level_group\":0,\"message_group\":0,\"description\":\"Configuration error - missing permissions\"},{\"pattern\":\"error.*permission.*denied\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied error (requires error context)\"},{\"pattern\":\"error.*unauthorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Unauthorized error (requires error context)\"},{\"pattern\":\"error.*forbidden\",\"level_group\":0,\"message_group\":0,\"description\":\"Forbidden error (requires error context)\"},{\"pattern\":\"error.*access.*restricted\",\"level_group\":0,\"message_group\":0,\"description\":\"Access restricted error (requires error context)\"},{\"pattern\":\"error.*insufficient.*permission\",\"level_group\":0,\"message_group\":0,\"description\":\"Insufficient permissions error (requires error context)\"}]" with: script: | function main() { @@ -3095,7 +3070,7 @@ jobs: with: node-version: '24' - name: Install Claude Code CLI - run: npm install -g @anthropic-ai/claude-code@2.0.14 + run: npm install -g @anthropic-ai/claude-code@2.0.13 - name: Execute Claude Code CLI id: agentic_execution # Allowed tools (sorted): @@ -3330,10 +3305,9 @@ jobs: } const workflowName = process.env.GITHUB_AW_WORKFLOW_NAME || "Workflow"; const runId = context.runId; - const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; const runUrl = context.payload.repository ? `${context.payload.repository.html_url}/actions/runs/${runId}` - : `${githubServer}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; + : `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; bodyLines.push(``, ``, `> AI generated by [${workflowName}](${runUrl})`, ""); const body = bodyLines.join("\n").trim(); core.info(`Creating discussion with title: ${title}`); diff --git a/pkg/cli/add_command.go b/pkg/cli/add_command.go index 91e359907fe..1f44c806fef 100644 --- a/pkg/cli/add_command.go +++ b/pkg/cli/add_command.go @@ -817,7 +817,7 @@ func addSourceToWorkflow(content, source string) (string, error) { // Source field doesn't exist, insert it manually to preserve formatting frontmatterLines := make([]string, len(result.FrontmatterLines)) copy(frontmatterLines, result.FrontmatterLines) - + // Add source field at the end of the frontmatter, preserving original formatting sourceField := fmt.Sprintf("source: %s", source) frontmatterLines = append(frontmatterLines, sourceField) diff --git a/pkg/cli/format_preservation_test.go b/pkg/cli/format_preservation_test.go index e5d4e6a241c..b64fbff8611 100644 --- a/pkg/cli/format_preservation_test.go +++ b/pkg/cli/format_preservation_test.go @@ -66,4 +66,4 @@ This workflow is designed to test whether the formatting is preserved.` if !strings.Contains(result, " workflow_dispatch:") { t.Error("4-space indentation was not preserved") } -} \ No newline at end of file +} From 33d6f365b59be8d51ce11a519c491a654fd5ba06 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Sat, 11 Oct 2025 20:09:56 +0100 Subject: [PATCH 4/4] format code --- .../workflows/cli-version-checker.lock.yml | 31 ++-- .github/workflows/lockfile-stats.lock.yml | 144 +++++++++++------- .github/workflows/poem-bot.lock.yml | 31 ++-- .github/workflows/security-fix-pr.lock.yml | 31 ++-- .../workflows/technical-doc-writer.lock.yml | 31 ++-- .github/workflows/tidy.lock.yml | 31 ++-- .github/workflows/unbloat-docs.lock.yml | 31 ++-- 7 files changed, 199 insertions(+), 131 deletions(-) diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 294506cfce3..2b8f9094a22 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -3013,19 +3013,26 @@ jobs: script: | const fs = require("fs"); const crypto = require("crypto"); + function generatePatchPreview(patchContent) { + if (!patchContent || !patchContent.trim()) { + return ""; + } + const lines = patchContent.split("\n"); + const maxLines = 500; + const maxChars = 2000; + let preview = lines.length <= maxLines ? patchContent : lines.slice(0, maxLines).join("\n"); + const lineTruncated = lines.length > maxLines; + const charTruncated = preview.length > maxChars; + if (charTruncated) { + preview = preview.slice(0, maxChars); + } + const truncated = lineTruncated || charTruncated; + const summary = truncated + ? `Show patch preview (${Math.min(maxLines, lines.length)} of ${lines.length} lines)` + : `Show patch (${lines.length} lines)`; + return `\n\n
${summary}\n\n\`\`\`diff\n${preview}${truncated ? "\n... (truncated)" : ""}\n\`\`\`\n\n
`; + } async function main() { - function generatePatchPreview(patchContent) { - if (!patchContent || !patchContent.trim()) { - return ""; - } - const lines = patchContent.split("\n"); - const maxLines = 500; - if (lines.length <= maxLines) { - return `\n\n
Show patch (${lines.length} lines)\n\n\`\`\`diff\n${patchContent}\n\`\`\`\n\n
`; - } - const preview = lines.slice(0, maxLines).join("\n"); - return `\n\n
Show patch preview (${maxLines} of ${lines.length} lines)\n\n\`\`\`diff\n${preview}\n... (truncated)\n\`\`\`\n\n
`; - } const isStaged = process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true"; const workflowId = process.env.GITHUB_AW_WORKFLOW_ID; if (!workflowId) { diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 0ff403e3ce2..09a87ca0ae3 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -213,7 +213,7 @@ jobs: with: node-version: '24' - name: Install Claude Code CLI - run: npm install -g @anthropic-ai/claude-code@2.0.13 + run: npm install -g @anthropic-ai/claude-code@2.0.14 - name: Generate Claude Settings run: | mkdir -p /tmp/gh-aw/.claude @@ -250,6 +250,7 @@ jobs: import re # Domain allow-list (populated during generation) + # JSON array safely embedded as Python list literal ALLOWED_DOMAINS = ["crl3.digicert.com","crl4.digicert.com","ocsp.digicert.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","crl.geotrust.com","ocsp.geotrust.com","crl.thawte.com","ocsp.thawte.com","crl.verisign.com","ocsp.verisign.com","crl.globalsign.com","ocsp.globalsign.com","crls.ssl.com","ocsp.ssl.com","crl.identrust.com","ocsp.identrust.com","crl.sectigo.com","ocsp.sectigo.com","crl.usertrust.com","ocsp.usertrust.com","s.symcb.com","s.symcd.com","json-schema.org","json.schemastore.org","archive.ubuntu.com","security.ubuntu.com","ppa.launchpad.net","keyserver.ubuntu.com","azure.archive.ubuntu.com","api.snapcraft.io","packagecloud.io","packages.cloud.google.com","packages.microsoft.com"] def extract_domain(url_or_query): @@ -620,7 +621,7 @@ jobs: }, { name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", + description: "Add a comment to a GitHub issue, pull request, or discussion", inputSchema: { type: "object", required: ["body"], @@ -628,7 +629,15 @@ jobs: body: { type: "string", description: "Comment body/content" }, issue_number: { type: "number", - description: "Issue or PR number (optional for current context)", + description: "Issue number (optional for current context)", + }, + pull_number: { + type: "number", + description: "Pull request number (optional, alternative to issue_number)", + }, + discussion_number: { + type: "number", + description: "Discussion number for discussion comments (optional, alternative to issue_number)", }, }, additionalProperties: false, @@ -1003,7 +1012,7 @@ jobs: GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-discussion\":{\"max\":1},\"missing-tool\":{}}" run: | mkdir -p /tmp/gh-aw/mcp-config - cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' + cat > /tmp/gh-aw/mcp-config/mcp-servers.json << EOF { "mcpServers": { "github": { @@ -1014,7 +1023,9 @@ jobs: "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:sha-09deac4" + "-e", + "GITHUB_TOOLSETS=all", + "ghcr.io/github/github-mcp-server:v0.18.0" ], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" @@ -1449,15 +1460,6 @@ jobs: To report a missing tool use the missing-tool tool from the safe-outputs MCP. EOF - - name: Print prompt to step summary - env: - GITHUB_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - run: | - echo "## Generated Prompt" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```markdown' >> $GITHUB_STEP_SUMMARY - cat $GITHUB_AW_PROMPT >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - name: Capture agent version run: | VERSION_OUTPUT=$(claude --version 2>&1 || echo "unknown") @@ -1544,6 +1546,7 @@ jobs: # - mcp__github__get_issue # - mcp__github__get_issue_comments # - mcp__github__get_job_logs + # - mcp__github__get_label # - mcp__github__get_latest_release # - mcp__github__get_me # - mcp__github__get_notification_details @@ -1568,6 +1571,7 @@ jobs: # - mcp__github__list_discussions # - mcp__github__list_issue_types # - mcp__github__list_issues + # - mcp__github__list_label # - mcp__github__list_notifications # - mcp__github__list_pull_requests # - mcp__github__list_releases @@ -1579,6 +1583,7 @@ jobs: # - mcp__github__list_workflow_run_artifacts # - mcp__github__list_workflow_runs # - mcp__github__list_workflows + # - mcp__github__pull_request_read # - mcp__github__search_code # - mcp__github__search_issues # - mcp__github__search_orgs @@ -1589,7 +1594,7 @@ jobs: run: | set -o pipefail # Execute Claude Code CLI with prompt from file - claude --print --mcp-config /tmp/gh-aw/mcp-config/mcp-servers.json --allowed-tools "Bash(cat),Bash(date),Bash(echo),Bash(grep),Bash(head),Bash(ls),Bash(pwd),Bash(sort),Bash(tail),Bash(uniq),Bash(wc),BashOutput,Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookRead,Read,Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/gh-aw/cache-memory/*),mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_sub_issues,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users" --debug --verbose --permission-mode bypassPermissions --output-format stream-json --settings /tmp/gh-aw/.claude/settings.json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee /tmp/gh-aw/agent-stdio.log + claude --print --mcp-config /tmp/gh-aw/mcp-config/mcp-servers.json --allowed-tools "Bash(cat),Bash(date),Bash(echo),Bash(grep),Bash(head),Bash(ls),Bash(pwd),Bash(sort),Bash(tail),Bash(uniq),Bash(wc),BashOutput,Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookRead,Read,Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/gh-aw/cache-memory/*),mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_sub_issues,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users" --debug --verbose --permission-mode bypassPermissions --output-format stream-json --settings /tmp/gh-aw/.claude/settings.json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} DISABLE_TELEMETRY: "1" @@ -2045,16 +2050,39 @@ jobs: if (item.labels && Array.isArray(item.labels)) { item.labels = item.labels.map(label => (typeof label === "string" ? sanitizeContent(label) : label)); } + if (item.parent !== undefined) { + const parentValidation = validateIssueOrPRNumber(item.parent, "create_issue 'parent'", i + 1); + if (!parentValidation.isValid) { + if (parentValidation.error) errors.push(parentValidation.error); + continue; + } + } break; case "add-comment": if (!item.body || typeof item.body !== "string") { errors.push(`Line ${i + 1}: add_comment requires a 'body' string field`); continue; } - const issueNumValidation = validateIssueOrPRNumber(item.issue_number, "add_comment 'issue_number'", i + 1); - if (!issueNumValidation.isValid) { - if (issueNumValidation.error) errors.push(issueNumValidation.error); - continue; + if (item.issue_number !== undefined) { + const issueNumValidation = validateIssueOrPRNumber(item.issue_number, "add_comment 'issue_number'", i + 1); + if (!issueNumValidation.isValid) { + if (issueNumValidation.error) errors.push(issueNumValidation.error); + continue; + } + } + if (item.discussion_number !== undefined) { + const discussionNumValidation = validateIssueOrPRNumber(item.discussion_number, "add_comment 'discussion_number'", i + 1); + if (!discussionNumValidation.isValid) { + if (discussionNumValidation.error) errors.push(discussionNumValidation.error); + continue; + } + } + if (item.pull_number !== undefined) { + const pullNumValidation = validateIssueOrPRNumber(item.pull_number, "add_comment 'pull_number'", i + 1); + if (!pullNumValidation.isValid) { + if (pullNumValidation.error) errors.push(pullNumValidation.error); + continue; + } } item.body = sanitizeContent(item.body); break; @@ -2345,18 +2373,6 @@ jobs: const outputTypes = Array.from(new Set(parsedItems.map(item => item.type))); core.info(`output_types: ${outputTypes.join(", ")}`); core.setOutput("output_types", outputTypes.join(",")); - try { - await core.summary - .addRaw("## Processed Output\n\n") - .addRaw("```json\n") - .addRaw(JSON.stringify(validatedOutput)) - .addRaw("\n```\n") - .write(); - core.info("Successfully wrote processed output to step summary"); - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - core.warning(`Failed to write to step summary: ${errorMsg}`); - } } await main(); - name: Upload sanitized agent output @@ -2449,6 +2465,16 @@ jobs: mcpFailures: [], }; } + const toolUsePairs = new Map(); + for (const entry of logEntries) { + if (entry.type === "user" && entry.message?.content) { + for (const content of entry.message.content) { + if (content.type === "tool_result" && content.tool_use_id) { + toolUsePairs.set(content.tool_use_id, content); + } + } + } + } let markdown = ""; const mcpFailures = []; const initEntry = logEntries.find(entry => entry.type === "system" && entry.subtype === "init"); @@ -2459,18 +2485,27 @@ jobs: mcpFailures.push(...initResult.mcpFailures); markdown += "\n"; } - markdown += "## 🤖 Commands and Tools\n\n"; - const toolUsePairs = new Map(); - const commandSummary = []; + markdown += "\n## 🤖 Reasoning\n\n"; for (const entry of logEntries) { - if (entry.type === "user" && entry.message?.content) { + if (entry.type === "assistant" && entry.message?.content) { for (const content of entry.message.content) { - if (content.type === "tool_result" && content.tool_use_id) { - toolUsePairs.set(content.tool_use_id, content); + if (content.type === "text" && content.text) { + const text = content.text.trim(); + if (text && text.length > 0) { + markdown += text + "\n\n"; + } + } else if (content.type === "tool_use") { + const toolResult = toolUsePairs.get(content.id); + const toolMarkdown = formatToolUse(content, toolResult); + if (toolMarkdown) { + markdown += toolMarkdown; + } } } } } + markdown += "## 🤖 Commands and Tools\n\n"; + const commandSummary = []; for (const entry of logEntries) { if (entry.type === "assistant" && entry.message?.content) { for (const content of entry.message.content) { @@ -2535,25 +2570,6 @@ jobs: markdown += `**Permission Denials:** ${lastEntry.permission_denials.length}\n\n`; } } - markdown += "\n## 🤖 Reasoning\n\n"; - for (const entry of logEntries) { - if (entry.type === "assistant" && entry.message?.content) { - for (const content of entry.message.content) { - if (content.type === "text" && content.text) { - const text = content.text.trim(); - if (text && text.length > 0) { - markdown += text + "\n\n"; - } - } else if (content.type === "tool_use") { - const toolResult = toolUsePairs.get(content.id); - const toolMarkdown = formatToolUse(content, toolResult); - if (toolMarkdown) { - markdown += toolMarkdown; - } - } - } - } - } return { markdown, mcpFailures }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -2770,6 +2786,15 @@ jobs: }; } main(); + - name: Print prompt to step summary + env: + GITHUB_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: | + echo "## Generated Prompt" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```markdown' >> $GITHUB_STEP_SUMMARY + cat $GITHUB_AW_PROMPT >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY - name: Upload Agent Stdio if: always() uses: actions/upload-artifact@v4 @@ -2782,7 +2807,7 @@ jobs: uses: actions/github-script@v8 env: GITHUB_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log - GITHUB_AW_ERROR_PATTERNS: "[{\"pattern\":\"access denied.*only authorized.*can trigger.*workflow\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied - workflow access restriction\"},{\"pattern\":\"access denied.*user.*not authorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied - user not authorized\"},{\"pattern\":\"repository permission check failed\",\"level_group\":0,\"message_group\":0,\"description\":\"Repository permission check failure\"},{\"pattern\":\"configuration error.*required permissions not specified\",\"level_group\":0,\"message_group\":0,\"description\":\"Configuration error - missing permissions\"},{\"pattern\":\"error.*permission.*denied\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied error (requires error context)\"},{\"pattern\":\"error.*unauthorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Unauthorized error (requires error context)\"},{\"pattern\":\"error.*forbidden\",\"level_group\":0,\"message_group\":0,\"description\":\"Forbidden error (requires error context)\"},{\"pattern\":\"error.*access.*restricted\",\"level_group\":0,\"message_group\":0,\"description\":\"Access restricted error (requires error context)\"},{\"pattern\":\"error.*insufficient.*permission\",\"level_group\":0,\"message_group\":0,\"description\":\"Insufficient permissions error (requires error context)\"}]" + GITHUB_AW_ERROR_PATTERNS: "[{\"pattern\":\"access denied.*only authorized.*can trigger.*workflow\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied - workflow access restriction\"},{\"pattern\":\"access denied.*user.*not authorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied - user not authorized\"},{\"pattern\":\"repository permission check failed\",\"level_group\":0,\"message_group\":0,\"description\":\"Repository permission check failure\"},{\"pattern\":\"configuration error.*required permissions not specified\",\"level_group\":0,\"message_group\":0,\"description\":\"Configuration error - missing permissions\"},{\"pattern\":\"\\\\berror\\\\b.*permission.*denied\",\"level_group\":0,\"message_group\":0,\"description\":\"Permission denied error (requires error context)\"},{\"pattern\":\"\\\\berror\\\\b.*unauthorized\",\"level_group\":0,\"message_group\":0,\"description\":\"Unauthorized error (requires error context)\"},{\"pattern\":\"\\\\berror\\\\b.*forbidden\",\"level_group\":0,\"message_group\":0,\"description\":\"Forbidden error (requires error context)\"},{\"pattern\":\"\\\\berror\\\\b.*access.*restricted\",\"level_group\":0,\"message_group\":0,\"description\":\"Access restricted error (requires error context)\"},{\"pattern\":\"\\\\berror\\\\b.*insufficient.*permission\",\"level_group\":0,\"message_group\":0,\"description\":\"Insufficient permissions error (requires error context)\"}]" with: script: | function main() { @@ -3070,7 +3095,7 @@ jobs: with: node-version: '24' - name: Install Claude Code CLI - run: npm install -g @anthropic-ai/claude-code@2.0.13 + run: npm install -g @anthropic-ai/claude-code@2.0.14 - name: Execute Claude Code CLI id: agentic_execution # Allowed tools (sorted): @@ -3305,9 +3330,10 @@ jobs: } const workflowName = process.env.GITHUB_AW_WORKFLOW_NAME || "Workflow"; const runId = context.runId; + const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; const runUrl = context.payload.repository ? `${context.payload.repository.html_url}/actions/runs/${runId}` - : `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; + : `${githubServer}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; bodyLines.push(``, ``, `> AI generated by [${workflowName}](${runUrl})`, ""); const body = bodyLines.join("\n").trim(); core.info(`Creating discussion with title: ${title}`); diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index a6e468fa508..b036d5c6240 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -4524,19 +4524,26 @@ jobs: script: | const fs = require("fs"); const crypto = require("crypto"); - async function main() { - function generatePatchPreview(patchContent) { - if (!patchContent || !patchContent.trim()) { - return ""; - } - const lines = patchContent.split("\n"); - const maxLines = 500; - if (lines.length <= maxLines) { - return `\n\n
Show patch (${lines.length} lines)\n\n\`\`\`diff\n${patchContent}\n\`\`\`\n\n
`; - } - const preview = lines.slice(0, maxLines).join("\n"); - return `\n\n
Show patch preview (${maxLines} of ${lines.length} lines)\n\n\`\`\`diff\n${preview}\n... (truncated)\n\`\`\`\n\n
`; + function generatePatchPreview(patchContent) { + if (!patchContent || !patchContent.trim()) { + return ""; } + const lines = patchContent.split("\n"); + const maxLines = 500; + const maxChars = 2000; + let preview = lines.length <= maxLines ? patchContent : lines.slice(0, maxLines).join("\n"); + const lineTruncated = lines.length > maxLines; + const charTruncated = preview.length > maxChars; + if (charTruncated) { + preview = preview.slice(0, maxChars); + } + const truncated = lineTruncated || charTruncated; + const summary = truncated + ? `Show patch preview (${Math.min(maxLines, lines.length)} of ${lines.length} lines)` + : `Show patch (${lines.length} lines)`; + return `\n\n
${summary}\n\n\`\`\`diff\n${preview}${truncated ? "\n... (truncated)" : ""}\n\`\`\`\n\n
`; + } + async function main() { const isStaged = process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true"; const workflowId = process.env.GITHUB_AW_WORKFLOW_ID; if (!workflowId) { diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index 4aabaa1ace1..113104e0792 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -3101,19 +3101,26 @@ jobs: script: | const fs = require("fs"); const crypto = require("crypto"); + function generatePatchPreview(patchContent) { + if (!patchContent || !patchContent.trim()) { + return ""; + } + const lines = patchContent.split("\n"); + const maxLines = 500; + const maxChars = 2000; + let preview = lines.length <= maxLines ? patchContent : lines.slice(0, maxLines).join("\n"); + const lineTruncated = lines.length > maxLines; + const charTruncated = preview.length > maxChars; + if (charTruncated) { + preview = preview.slice(0, maxChars); + } + const truncated = lineTruncated || charTruncated; + const summary = truncated + ? `Show patch preview (${Math.min(maxLines, lines.length)} of ${lines.length} lines)` + : `Show patch (${lines.length} lines)`; + return `\n\n
${summary}\n\n\`\`\`diff\n${preview}${truncated ? "\n... (truncated)" : ""}\n\`\`\`\n\n
`; + } async function main() { - function generatePatchPreview(patchContent) { - if (!patchContent || !patchContent.trim()) { - return ""; - } - const lines = patchContent.split("\n"); - const maxLines = 500; - if (lines.length <= maxLines) { - return `\n\n
Show patch (${lines.length} lines)\n\n\`\`\`diff\n${patchContent}\n\`\`\`\n\n
`; - } - const preview = lines.slice(0, maxLines).join("\n"); - return `\n\n
Show patch preview (${maxLines} of ${lines.length} lines)\n\n\`\`\`diff\n${preview}\n... (truncated)\n\`\`\`\n\n
`; - } const isStaged = process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true"; const workflowId = process.env.GITHUB_AW_WORKFLOW_ID; if (!workflowId) { diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index f05ea1fb8c4..feaffdc3e8a 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -3478,19 +3478,26 @@ jobs: script: | const fs = require("fs"); const crypto = require("crypto"); - async function main() { - function generatePatchPreview(patchContent) { - if (!patchContent || !patchContent.trim()) { - return ""; - } - const lines = patchContent.split("\n"); - const maxLines = 500; - if (lines.length <= maxLines) { - return `\n\n
Show patch (${lines.length} lines)\n\n\`\`\`diff\n${patchContent}\n\`\`\`\n\n
`; - } - const preview = lines.slice(0, maxLines).join("\n"); - return `\n\n
Show patch preview (${maxLines} of ${lines.length} lines)\n\n\`\`\`diff\n${preview}\n... (truncated)\n\`\`\`\n\n
`; + function generatePatchPreview(patchContent) { + if (!patchContent || !patchContent.trim()) { + return ""; } + const lines = patchContent.split("\n"); + const maxLines = 500; + const maxChars = 2000; + let preview = lines.length <= maxLines ? patchContent : lines.slice(0, maxLines).join("\n"); + const lineTruncated = lines.length > maxLines; + const charTruncated = preview.length > maxChars; + if (charTruncated) { + preview = preview.slice(0, maxChars); + } + const truncated = lineTruncated || charTruncated; + const summary = truncated + ? `Show patch preview (${Math.min(maxLines, lines.length)} of ${lines.length} lines)` + : `Show patch (${lines.length} lines)`; + return `\n\n
${summary}\n\n\`\`\`diff\n${preview}${truncated ? "\n... (truncated)" : ""}\n\`\`\`\n\n
`; + } + async function main() { const isStaged = process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true"; const workflowId = process.env.GITHUB_AW_WORKFLOW_ID; if (!workflowId) { diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index db9557c0945..32b70668b0a 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -3493,19 +3493,26 @@ jobs: script: | const fs = require("fs"); const crypto = require("crypto"); - async function main() { - function generatePatchPreview(patchContent) { - if (!patchContent || !patchContent.trim()) { - return ""; - } - const lines = patchContent.split("\n"); - const maxLines = 500; - if (lines.length <= maxLines) { - return `\n\n
Show patch (${lines.length} lines)\n\n\`\`\`diff\n${patchContent}\n\`\`\`\n\n
`; - } - const preview = lines.slice(0, maxLines).join("\n"); - return `\n\n
Show patch preview (${maxLines} of ${lines.length} lines)\n\n\`\`\`diff\n${preview}\n... (truncated)\n\`\`\`\n\n
`; + function generatePatchPreview(patchContent) { + if (!patchContent || !patchContent.trim()) { + return ""; } + const lines = patchContent.split("\n"); + const maxLines = 500; + const maxChars = 2000; + let preview = lines.length <= maxLines ? patchContent : lines.slice(0, maxLines).join("\n"); + const lineTruncated = lines.length > maxLines; + const charTruncated = preview.length > maxChars; + if (charTruncated) { + preview = preview.slice(0, maxChars); + } + const truncated = lineTruncated || charTruncated; + const summary = truncated + ? `Show patch preview (${Math.min(maxLines, lines.length)} of ${lines.length} lines)` + : `Show patch (${lines.length} lines)`; + return `\n\n
${summary}\n\n\`\`\`diff\n${preview}${truncated ? "\n... (truncated)" : ""}\n\`\`\`\n\n
`; + } + async function main() { const isStaged = process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true"; const workflowId = process.env.GITHUB_AW_WORKFLOW_ID; if (!workflowId) { diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 501ef36a8a4..bf70d08ebab 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -3715,19 +3715,26 @@ jobs: script: | const fs = require("fs"); const crypto = require("crypto"); - async function main() { - function generatePatchPreview(patchContent) { - if (!patchContent || !patchContent.trim()) { - return ""; - } - const lines = patchContent.split("\n"); - const maxLines = 500; - if (lines.length <= maxLines) { - return `\n\n
Show patch (${lines.length} lines)\n\n\`\`\`diff\n${patchContent}\n\`\`\`\n\n
`; - } - const preview = lines.slice(0, maxLines).join("\n"); - return `\n\n
Show patch preview (${maxLines} of ${lines.length} lines)\n\n\`\`\`diff\n${preview}\n... (truncated)\n\`\`\`\n\n
`; + function generatePatchPreview(patchContent) { + if (!patchContent || !patchContent.trim()) { + return ""; } + const lines = patchContent.split("\n"); + const maxLines = 500; + const maxChars = 2000; + let preview = lines.length <= maxLines ? patchContent : lines.slice(0, maxLines).join("\n"); + const lineTruncated = lines.length > maxLines; + const charTruncated = preview.length > maxChars; + if (charTruncated) { + preview = preview.slice(0, maxChars); + } + const truncated = lineTruncated || charTruncated; + const summary = truncated + ? `Show patch preview (${Math.min(maxLines, lines.length)} of ${lines.length} lines)` + : `Show patch (${lines.length} lines)`; + return `\n\n
${summary}\n\n\`\`\`diff\n${preview}${truncated ? "\n... (truncated)" : ""}\n\`\`\`\n\n
`; + } + async function main() { const isStaged = process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true"; const workflowId = process.env.GITHUB_AW_WORKFLOW_ID; if (!workflowId) {