diff --git a/.github/instructions/github-script.instructions.md b/.github/instructions/github-script.instructions.md new file mode 100644 index 00000000000..027e8c08e74 --- /dev/null +++ b/.github/instructions/github-script.instructions.md @@ -0,0 +1,49 @@ +--- +description: GitHub Action Script Source Code +applyTo: "pkg/workflow/js/*.cjs" +--- + +This JavaScript file will be run using the GitHub Action `actions/github-script@v7` which provides the `@actions/core`, `@actions/github` packages for logging errors and setting action status. + +- do not add import or require for `@actions/core` +- reference: + - https://github.com/actions/toolkit/blob/main/packages/core/README.md + - https://github.com/actions/toolkit/blob/main/packages/github/README.md + +## Best practices + +- use `core.info`, `core.warning`, `core.error` for logging, not `console.log` or `console.error` +- use `core.debug` for verbose debug logging, which can be enabled in action settings +- use `core.setOutput` to set action outputs +- use `core.exportVariable` to set environment variables for subsequent steps +- use `core.getInput` to get action inputs, with `required: true` for mandatory inputs +- use `core.setFailed` to mark the action as failed with an error message + +## Step summary + +Use `core.summary.*` function to write output the step summary file. + +- use `core.summary.addRaw()` to add raw Markdown content (GitHub Flavored Markdown supported) +- make sure to call `core.summary.write()` to flush pending writes +- summary function calls can be chained, e.g. `core.summary.addRaw(...).addRaw(...).write()` + +## Common errors + +- avoid `any` type as much as possible, use specific types or `unknown` instead +- catch handler: check if error is an instance of Error before accessing message property + +```js +catch (error) { + core.setFailed(error instanceof Error ? error : String(error)); +} +``` + +- `core.setFailed` also calls `core.error`, so do not call both + +## Typechecking + +Run `make js` to run the typescript compiler. + +Run `make lint-cjs` to lint the files. + +Run `make fmt-cjs` after editing to format the file. \ No newline at end of file diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 534c08c6db9..23cc041ce76 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -617,7 +617,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -667,6 +667,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -741,9 +742,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -751,33 +760,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -788,6 +798,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -838,8 +849,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -875,8 +887,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -887,14 +900,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1202,10 +1221,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1219,7 +1239,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1232,11 +1252,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1280,11 +1301,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -1292,10 +1313,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -1435,9 +1461,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -1524,6 +1557,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -1536,6 +1574,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -1550,6 +1593,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -1569,6 +1617,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; @@ -1617,27 +1671,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } // Find all create-issue items @@ -1645,10 +1698,10 @@ jobs: /** @param {any} item */ item => item.type === "create-issue" ); if (createIssueItems.length === 0) { - console.log("No create-issue items found in agent output"); + core.info("No create-issue items found in agent output"); return; } - console.log(`Found ${createIssueItems.length} create-issue item(s)`); + core.info(`Found ${createIssueItems.length} create-issue item(s)`); // If in staged mode, emit step summary instead of creating issues if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Create Issues Preview\n\n"; @@ -1668,7 +1721,7 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Issue creation preview written to step summary"); + core.info("📝 Issue creation preview written to step summary"); return; } // Check if we're in an issue context (triggered by an issue event) @@ -1685,9 +1738,8 @@ jobs: // Process each create-issue item for (let i = 0; i < createIssueItems.length; i++) { const createIssueItem = createIssueItems[i]; - console.log( - `Processing create-issue item ${i + 1}/${createIssueItems.length}:`, - { title: createIssueItem.title, bodyLength: createIssueItem.body.length } + core.info( + `Processing create-issue item ${i + 1}/${createIssueItems.length}: title=${createIssueItem.title}, bodyLength=${createIssueItem.body.length}` ); // Merge environment labels with item-specific labels let labels = [...envLabels]; @@ -1707,7 +1759,7 @@ jobs: title = titlePrefix + title; } if (parentIssueNumber) { - console.log("Detected issue context, parent issue #" + parentIssueNumber); + core.info("Detected issue context, parent issue #" + parentIssueNumber); // Add reference to parent issue in the child issue body bodyLines.push(`Related to #${parentIssueNumber}`); } @@ -1725,9 +1777,9 @@ jobs: ); // Prepare the body content const body = bodyLines.join("\n").trim(); - console.log("Creating issue with title:", title); - console.log("Labels:", labels); - console.log("Body length:", body.length); + core.info(`Creating issue with title: ${title}`); + core.info(`Labels: ${labels}`); + core.info(`Body length: ${body.length}`); try { // Create the issue using GitHub API const { data: issue } = await github.rest.issues.create({ @@ -1737,7 +1789,7 @@ jobs: body: body, labels: labels, }); - console.log("Created issue #" + issue.number + ": " + issue.html_url); + core.info("Created issue #" + issue.number + ": " + issue.html_url); createdIssues.push(issue); // If we have a parent issue, add a comment to it referencing the new child issue if (parentIssueNumber) { @@ -1748,11 +1800,10 @@ jobs: issue_number: parentIssueNumber, body: `Created related issue: #${issue.number}`, }); - console.log("Added comment to parent issue #" + parentIssueNumber); + core.info("Added comment to parent issue #" + parentIssueNumber); } catch (error) { - console.log( - "Warning: Could not add comment to parent issue:", - error instanceof Error ? error.message : String(error) + core.info( + `Warning: Could not add comment to parent issue: ${error instanceof Error ? error.message : String(error)}` ); } } @@ -1768,10 +1819,10 @@ jobs: if ( errorMessage.includes("Issues has been disabled in this repository") ) { - console.log( + core.info( `⚠ Cannot create issue "${title}": Issues are disabled for this repository` ); - console.log( + core.info( "Consider enabling issues in repository settings if you want to create issues automatically" ); continue; // Skip this issue but continue processing others @@ -1788,7 +1839,7 @@ jobs: } await core.summary.addRaw(summaryContent).write(); } - console.log(`Successfully created ${createdIssues.length} issue(s)`); + core.info(`Successfully created ${createdIssues.length} issue(s)`); } await main(); @@ -1818,27 +1869,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } // Find all add-issue-comment items @@ -1846,10 +1896,10 @@ jobs: /** @param {any} item */ item => item.type === "add-issue-comment" ); if (commentItems.length === 0) { - console.log("No add-issue-comment items found in agent output"); + core.info("No add-issue-comment items found in agent output"); return; } - console.log(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-issue-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n"; @@ -1868,12 +1918,12 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Comment creation preview written to step summary"); + core.info("📝 Comment creation preview written to step summary"); return; } // Get the target configuration from environment variable const commentTarget = process.env.GITHUB_AW_COMMENT_TARGET || "triggering"; - console.log(`Comment target configuration: ${commentTarget}`); + core.info(`Comment target configuration: ${commentTarget}`); // Check if we're in an issue or pull request context const isIssueContext = context.eventName === "issues" || context.eventName === "issue_comment"; @@ -1883,7 +1933,7 @@ jobs: context.eventName === "pull_request_review_comment"; // Validate context based on target configuration if (commentTarget === "triggering" && !isIssueContext && !isPRContext) { - console.log( + core.info( 'Target is "triggering" but not running in issue or pull request context, skipping comment creation' ); return; @@ -1892,9 +1942,8 @@ jobs: // Process each comment item for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; - console.log( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}:`, - { bodyLength: commentItem.body.length } + core.info( + `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment let issueNumber; @@ -1904,14 +1953,14 @@ jobs: if (commentItem.issue_number) { issueNumber = parseInt(commentItem.issue_number, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number specified: ${commentItem.issue_number}` ); continue; } commentEndpoint = "issues"; } else { - console.log( + core.info( 'Target is "*" but no issue_number specified in comment item' ); continue; @@ -1920,7 +1969,7 @@ jobs: // Explicit issue number specified in target issueNumber = parseInt(commentTarget, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number in target configuration: ${commentTarget}` ); continue; @@ -1933,7 +1982,7 @@ jobs: issueNumber = context.payload.issue.number; commentEndpoint = "issues"; } else { - console.log("Issue context detected but no issue found in payload"); + core.info("Issue context detected but no issue found in payload"); continue; } } else if (isPRContext) { @@ -1941,7 +1990,7 @@ jobs: issueNumber = context.payload.pull_request.number; commentEndpoint = "issues"; // PR comments use the issues API endpoint } else { - console.log( + core.info( "Pull request context detected but no pull request found in payload" ); continue; @@ -1949,7 +1998,7 @@ jobs: } } if (!issueNumber) { - console.log("Could not determine issue or pull request number"); + core.info("Could not determine issue or pull request number"); continue; } // Extract body from the JSON item @@ -1960,8 +2009,8 @@ jobs: ? `${context.payload.repository.html_url}/actions/runs/${runId}` : `https://github.com/actions/runs/${runId}`; body += `\n\n> Generated by Agentic Workflow [Run](${runUrl})\n`; - console.log(`Creating comment on ${commentEndpoint} #${issueNumber}`); - console.log("Comment content length:", body.length); + core.info(`Creating comment on ${commentEndpoint} #${issueNumber}`); + core.info(`Comment content length: ${body.length}`); try { // Create the comment using GitHub API const { data: comment } = await github.rest.issues.createComment({ @@ -1970,7 +2019,7 @@ jobs: issue_number: issueNumber, body: body, }); - console.log("Created comment #" + comment.id + ": " + comment.html_url); + core.info("Created comment #" + comment.id + ": " + comment.html_url); createdComments.push(comment); // Set output for the last created comment (for backward compatibility) if (i === commentItems.length - 1) { @@ -1992,7 +2041,7 @@ jobs: } await core.summary.addRaw(summaryContent).write(); } - console.log(`Successfully created ${createdComments.length} comment(s)`); + core.info(`Successfully created ${createdComments.length} comment(s)`); return createdComments; } await main(); diff --git a/.github/workflows/test-safe-output-add-issue-comment.lock.yml b/.github/workflows/test-safe-output-add-issue-comment.lock.yml index b33ef09f785..c3f57a6ffea 100644 --- a/.github/workflows/test-safe-output-add-issue-comment.lock.yml +++ b/.github/workflows/test-safe-output-add-issue-comment.lock.yml @@ -39,7 +39,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -52,17 +52,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -70,32 +68,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -450,7 +448,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -500,6 +498,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -574,9 +573,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -584,33 +591,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -621,6 +629,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -671,8 +680,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -708,8 +718,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -720,14 +731,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1035,10 +1052,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1052,7 +1070,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1065,11 +1083,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1125,27 +1144,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } // Find all add-issue-comment items @@ -1153,10 +1171,10 @@ jobs: /** @param {any} item */ item => item.type === "add-issue-comment" ); if (commentItems.length === 0) { - console.log("No add-issue-comment items found in agent output"); + core.info("No add-issue-comment items found in agent output"); return; } - console.log(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-issue-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n"; @@ -1175,12 +1193,12 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Comment creation preview written to step summary"); + core.info("📝 Comment creation preview written to step summary"); return; } // Get the target configuration from environment variable const commentTarget = process.env.GITHUB_AW_COMMENT_TARGET || "triggering"; - console.log(`Comment target configuration: ${commentTarget}`); + core.info(`Comment target configuration: ${commentTarget}`); // Check if we're in an issue or pull request context const isIssueContext = context.eventName === "issues" || context.eventName === "issue_comment"; @@ -1190,7 +1208,7 @@ jobs: context.eventName === "pull_request_review_comment"; // Validate context based on target configuration if (commentTarget === "triggering" && !isIssueContext && !isPRContext) { - console.log( + core.info( 'Target is "triggering" but not running in issue or pull request context, skipping comment creation' ); return; @@ -1199,9 +1217,8 @@ jobs: // Process each comment item for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; - console.log( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}:`, - { bodyLength: commentItem.body.length } + core.info( + `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment let issueNumber; @@ -1211,14 +1228,14 @@ jobs: if (commentItem.issue_number) { issueNumber = parseInt(commentItem.issue_number, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number specified: ${commentItem.issue_number}` ); continue; } commentEndpoint = "issues"; } else { - console.log( + core.info( 'Target is "*" but no issue_number specified in comment item' ); continue; @@ -1227,7 +1244,7 @@ jobs: // Explicit issue number specified in target issueNumber = parseInt(commentTarget, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number in target configuration: ${commentTarget}` ); continue; @@ -1240,7 +1257,7 @@ jobs: issueNumber = context.payload.issue.number; commentEndpoint = "issues"; } else { - console.log("Issue context detected but no issue found in payload"); + core.info("Issue context detected but no issue found in payload"); continue; } } else if (isPRContext) { @@ -1248,7 +1265,7 @@ jobs: issueNumber = context.payload.pull_request.number; commentEndpoint = "issues"; // PR comments use the issues API endpoint } else { - console.log( + core.info( "Pull request context detected but no pull request found in payload" ); continue; @@ -1256,7 +1273,7 @@ jobs: } } if (!issueNumber) { - console.log("Could not determine issue or pull request number"); + core.info("Could not determine issue or pull request number"); continue; } // Extract body from the JSON item @@ -1267,8 +1284,8 @@ jobs: ? `${context.payload.repository.html_url}/actions/runs/${runId}` : `https://github.com/actions/runs/${runId}`; body += `\n\n> Generated by Agentic Workflow [Run](${runUrl})\n`; - console.log(`Creating comment on ${commentEndpoint} #${issueNumber}`); - console.log("Comment content length:", body.length); + core.info(`Creating comment on ${commentEndpoint} #${issueNumber}`); + core.info(`Comment content length: ${body.length}`); try { // Create the comment using GitHub API const { data: comment } = await github.rest.issues.createComment({ @@ -1277,7 +1294,7 @@ jobs: issue_number: issueNumber, body: body, }); - console.log("Created comment #" + comment.id + ": " + comment.html_url); + core.info("Created comment #" + comment.id + ": " + comment.html_url); createdComments.push(comment); // Set output for the last created comment (for backward compatibility) if (i === commentItems.length - 1) { @@ -1299,7 +1316,7 @@ jobs: } await core.summary.addRaw(summaryContent).write(); } - console.log(`Successfully created ${createdComments.length} comment(s)`); + core.info(`Successfully created ${createdComments.length} comment(s)`); return createdComments; } await main(); diff --git a/.github/workflows/test-safe-output-add-issue-label.lock.yml b/.github/workflows/test-safe-output-add-issue-label.lock.yml index bff150a66fd..9b5f44ff451 100644 --- a/.github/workflows/test-safe-output-add-issue-label.lock.yml +++ b/.github/workflows/test-safe-output-add-issue-label.lock.yml @@ -41,7 +41,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -54,17 +54,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -72,32 +70,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -454,7 +452,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -504,6 +502,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -578,9 +577,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -588,33 +595,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -625,6 +633,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -675,8 +684,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -712,8 +722,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -724,14 +735,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1039,10 +1056,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1056,7 +1074,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1069,11 +1087,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1127,27 +1146,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.debug(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.warning("No valid items found in agent output"); return; } // Find the add-issue-label item @@ -1155,12 +1173,12 @@ jobs: /** @param {any} item */ item => item.type === "add-issue-label" ); if (!labelsItem) { - console.log("No add-issue-label item found in agent output"); + core.warning("No add-issue-label item found in agent output"); return; } - console.log("Found add-issue-label item:", { - labelsCount: labelsItem.labels.length, - }); + core.debug( + `Found add-issue-label item with ${labelsItem.labels.length} labels` + ); // If in staged mode, emit step summary instead of adding labels if (process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true") { let summaryContent = "## 🎭 Staged Mode: Add Labels Preview\n\n"; @@ -1176,7 +1194,7 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Label addition preview written to step summary"); + core.info("📝 Label addition preview written to step summary"); return; } // Read the allowed labels from environment variable (optional) @@ -1192,9 +1210,9 @@ jobs: } } if (allowedLabels) { - console.log("Allowed labels:", allowedLabels); + core.debug(`Allowed labels: ${JSON.stringify(allowedLabels)}`); } else { - console.log("No label restrictions - any labels are allowed"); + core.debug("No label restrictions - any labels are allowed"); } // Read the max limit from environment variable (default: 3) const maxCountEnv = process.env.GITHUB_AW_LABELS_MAX_COUNT; @@ -1205,7 +1223,7 @@ jobs: ); return; } - console.log("Max count:", maxCount); + core.debug(`Max count: ${maxCount}`); // Check if we're in an issue or pull request context const isIssueContext = context.eventName === "issues" || context.eventName === "issue_comment"; @@ -1247,7 +1265,7 @@ jobs: } // Extract labels from the JSON item const requestedLabels = labelsItem.labels || []; - console.log("Requested labels:", requestedLabels); + core.debug(`Requested labels: ${JSON.stringify(requestedLabels)}`); // Check for label removal attempts (labels starting with '-') for (const label of requestedLabels) { if (label.startsWith("-")) { @@ -1271,11 +1289,11 @@ jobs: let uniqueLabels = [...new Set(validLabels)]; // Enforce max limit if (uniqueLabels.length > maxCount) { - console.log(`too many labels, keep ${maxCount}`); + core.debug(`too many labels, keep ${maxCount}`); uniqueLabels = uniqueLabels.slice(0, maxCount); } if (uniqueLabels.length === 0) { - console.log("No labels to add"); + core.info("No labels to add"); core.setOutput("labels_added", ""); await core.summary .addRaw( @@ -1287,9 +1305,8 @@ jobs: .write(); return; } - console.log( - `Adding ${uniqueLabels.length} labels to ${contextType} #${issueNumber}:`, - uniqueLabels + core.info( + `Adding ${uniqueLabels.length} labels to ${contextType} #${issueNumber}: ${JSON.stringify(uniqueLabels)}` ); try { // Add labels using GitHub API @@ -1299,7 +1316,7 @@ jobs: issue_number: issueNumber, labels: uniqueLabels, }); - console.log( + core.info( `Successfully added ${uniqueLabels.length} labels to ${contextType} #${issueNumber}` ); // Set output for other jobs to use diff --git a/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml b/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml index fee2be52feb..c2ed36a411a 100644 --- a/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml +++ b/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml @@ -43,7 +43,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -56,17 +56,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -74,32 +72,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -476,7 +474,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -526,6 +524,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -600,9 +599,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -610,33 +617,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -647,6 +655,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -697,8 +706,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -734,8 +744,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -746,14 +757,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1061,10 +1078,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1078,7 +1096,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1091,11 +1109,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1152,27 +1171,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } // Find all create-code-scanning-alert items @@ -1180,12 +1198,10 @@ jobs: /** @param {any} item */ item => item.type === "create-code-scanning-alert" ); if (securityItems.length === 0) { - console.log("No create-code-scanning-alert items found in agent output"); + core.info("No create-code-scanning-alert items found in agent output"); return; } - console.log( - `Found ${securityItems.length} create-code-scanning-alert item(s)` - ); + core.info(`Found ${securityItems.length} create-code-scanning-alert item(s)`); // If in staged mode, emit step summary instead of creating code scanning alerts if (process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true") { let summaryContent = @@ -1203,7 +1219,7 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log( + core.info( "📝 Code scanning alert creation preview written to step summary" ); return; @@ -1212,37 +1228,28 @@ jobs: const maxFindings = process.env.GITHUB_AW_SECURITY_REPORT_MAX ? parseInt(process.env.GITHUB_AW_SECURITY_REPORT_MAX) : 0; // 0 means unlimited - console.log( + core.info( `Max findings configuration: ${maxFindings === 0 ? "unlimited" : maxFindings}` ); // Get the driver configuration from environment variable const driverName = process.env.GITHUB_AW_SECURITY_REPORT_DRIVER || "GitHub Agentic Workflows Security Scanner"; - console.log(`Driver name: ${driverName}`); + core.info(`Driver name: ${driverName}`); // Get the workflow filename for rule ID prefix const workflowFilename = process.env.GITHUB_AW_WORKFLOW_FILENAME || "workflow"; - console.log(`Workflow filename for rule ID prefix: ${workflowFilename}`); + core.info(`Workflow filename for rule ID prefix: ${workflowFilename}`); const validFindings = []; // Process each security item and validate the findings for (let i = 0; i < securityItems.length; i++) { const securityItem = securityItems[i]; - console.log( - `Processing create-code-scanning-alert item ${i + 1}/${securityItems.length}:`, - { - file: securityItem.file, - line: securityItem.line, - severity: securityItem.severity, - messageLength: securityItem.message - ? securityItem.message.length - : "undefined", - ruleIdSuffix: securityItem.ruleIdSuffix || "not specified", - } + core.info( + `Processing create-code-scanning-alert item ${i + 1}/${securityItems.length}: file=${securityItem.file}, line=${securityItem.line}, severity=${securityItem.severity}, messageLength=${securityItem.message ? securityItem.message.length : "undefined"}, ruleIdSuffix=${securityItem.ruleIdSuffix || "not specified"}` ); // Validate required fields if (!securityItem.file) { - console.log('Missing required field "file" in code scanning alert item'); + core.info('Missing required field "file" in code scanning alert item'); continue; } if ( @@ -1250,19 +1257,19 @@ jobs: (typeof securityItem.line !== "number" && typeof securityItem.line !== "string") ) { - console.log( + core.info( 'Missing or invalid required field "line" in code scanning alert item' ); continue; } if (!securityItem.severity || typeof securityItem.severity !== "string") { - console.log( + core.info( 'Missing or invalid required field "severity" in code scanning alert item' ); continue; } if (!securityItem.message || typeof securityItem.message !== "string") { - console.log( + core.info( 'Missing or invalid required field "message" in code scanning alert item' ); continue; @@ -1270,7 +1277,7 @@ jobs: // Parse line number const line = parseInt(securityItem.line, 10); if (isNaN(line) || line <= 0) { - console.log(`Invalid line number: ${securityItem.line}`); + core.info(`Invalid line number: ${securityItem.line}`); continue; } // Parse optional column number @@ -1280,14 +1287,14 @@ jobs: typeof securityItem.column !== "number" && typeof securityItem.column !== "string" ) { - console.log( + core.info( 'Invalid field "column" in code scanning alert item (must be number or string)' ); continue; } const parsedColumn = parseInt(securityItem.column, 10); if (isNaN(parsedColumn) || parsedColumn <= 0) { - console.log(`Invalid column number: ${securityItem.column}`); + core.info(`Invalid column number: ${securityItem.column}`); continue; } column = parsedColumn; @@ -1296,7 +1303,7 @@ jobs: let ruleIdSuffix = null; if (securityItem.ruleIdSuffix !== undefined) { if (typeof securityItem.ruleIdSuffix !== "string") { - console.log( + core.info( 'Invalid field "ruleIdSuffix" in code scanning alert item (must be string)' ); continue; @@ -1304,14 +1311,14 @@ jobs: // Validate that the suffix doesn't contain invalid characters const trimmedSuffix = securityItem.ruleIdSuffix.trim(); if (trimmedSuffix.length === 0) { - console.log( + core.info( 'Invalid field "ruleIdSuffix" in code scanning alert item (cannot be empty)' ); continue; } // Check for characters that would be problematic in rule IDs if (!/^[a-zA-Z0-9_-]+$/.test(trimmedSuffix)) { - console.log( + core.info( `Invalid ruleIdSuffix "${trimmedSuffix}" (must contain only alphanumeric characters, hyphens, and underscores)` ); continue; @@ -1319,6 +1326,7 @@ jobs: ruleIdSuffix = trimmedSuffix; } // Validate severity level and map to SARIF level + /** @type {Record} */ const severityMap = { error: "error", warning: "warning", @@ -1327,7 +1335,7 @@ jobs: }; const normalizedSeverity = securityItem.severity.toLowerCase(); if (!severityMap[normalizedSeverity]) { - console.log( + core.info( `Invalid severity level: ${securityItem.severity} (must be error, warning, info, or note)` ); continue; @@ -1345,15 +1353,15 @@ jobs: }); // Check if we've reached the max limit if (maxFindings > 0 && validFindings.length >= maxFindings) { - console.log(`Reached maximum findings limit: ${maxFindings}`); + core.info(`Reached maximum findings limit: ${maxFindings}`); break; } } if (validFindings.length === 0) { - console.log("No valid security findings to report"); + core.info("No valid security findings to report"); return; } - console.log(`Processing ${validFindings.length} valid security finding(s)`); + core.info(`Processing ${validFindings.length} valid security finding(s)`); // Generate SARIF file const sarifContent = { $schema: @@ -1396,8 +1404,8 @@ jobs: const sarifFilePath = path.join(process.cwd(), sarifFileName); try { fs.writeFileSync(sarifFilePath, JSON.stringify(sarifContent, null, 2)); - console.log(`✓ Created SARIF file: ${sarifFilePath}`); - console.log(`SARIF file size: ${fs.statSync(sarifFilePath).size} bytes`); + core.info(`✓ Created SARIF file: ${sarifFilePath}`); + core.info(`SARIF file size: ${fs.statSync(sarifFilePath).size} bytes`); // Set outputs for the GitHub Action core.setOutput("sarif_file", sarifFilePath); core.setOutput("findings_count", validFindings.length); @@ -1424,7 +1432,7 @@ jobs: ); throw error; } - console.log( + core.info( `Successfully created code scanning alert with ${validFindings.length} finding(s)` ); return { diff --git a/.github/workflows/test-safe-output-create-discussion.lock.yml b/.github/workflows/test-safe-output-create-discussion.lock.yml index dc242b54875..2a2b39fc853 100644 --- a/.github/workflows/test-safe-output-create-discussion.lock.yml +++ b/.github/workflows/test-safe-output-create-discussion.lock.yml @@ -38,7 +38,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -51,17 +51,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -69,32 +67,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -441,7 +439,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -491,6 +489,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -565,9 +564,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -575,33 +582,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -612,6 +620,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -662,8 +671,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -699,8 +709,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -711,14 +722,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1026,10 +1043,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1043,7 +1061,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1056,11 +1074,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1112,27 +1131,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.debug(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.warning("No valid items found in agent output"); return; } // Find all create-discussion items @@ -1140,12 +1158,10 @@ jobs: /** @param {any} item */ item => item.type === "create-discussion" ); if (createDiscussionItems.length === 0) { - console.log("No create-discussion items found in agent output"); + core.warning("No create-discussion items found in agent output"); return; } - console.log( - `Found ${createDiscussionItems.length} create-discussion item(s)` - ); + core.debug(`Found ${createDiscussionItems.length} create-discussion item(s)`); // If in staged mode, emit step summary instead of creating discussions if (process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true") { let summaryContent = "## 🎭 Staged Mode: Create Discussions Preview\n\n"; @@ -1165,7 +1181,7 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Discussion creation preview written to step summary"); + core.info("📝 Discussion creation preview written to step summary"); return; } // Get repository ID and discussion categories using GraphQL API @@ -1194,9 +1210,12 @@ jobs: repositoryId = queryResult.repository.id; discussionCategories = queryResult.repository.discussionCategories.nodes || []; - console.log( - "Available categories:", - discussionCategories.map(cat => ({ name: cat.name, id: cat.id })) + core.info( + `Available categories: ${JSON.stringify( + discussionCategories.map( + /** @param {any} cat */ cat => ({ name: cat.name, id: cat.id }) + ) + )}` ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -1206,10 +1225,10 @@ jobs: errorMessage.includes("not found") || errorMessage.includes("Could not resolve to a Repository") ) { - console.log( + core.info( "⚠ Cannot create discussions: Discussions are not enabled for this repository" ); - console.log( + core.info( "Consider enabling discussions in repository settings if you want to create discussions automatically" ); return; // Exit gracefully without creating discussions @@ -1222,7 +1241,7 @@ jobs: if (!categoryId && discussionCategories.length > 0) { // Default to the first category if none specified categoryId = discussionCategories[0].id; - console.log( + core.info( `No category-id specified, using default category: ${discussionCategories[0].name} (${categoryId})` ); } @@ -1240,12 +1259,8 @@ jobs: // Process each create-discussion item for (let i = 0; i < createDiscussionItems.length; i++) { const createDiscussionItem = createDiscussionItems[i]; - console.log( - `Processing create-discussion item ${i + 1}/${createDiscussionItems.length}:`, - { - title: createDiscussionItem.title, - bodyLength: createDiscussionItem.body.length, - } + core.info( + `Processing create-discussion item ${i + 1}/${createDiscussionItems.length}: title=${createDiscussionItem.title}, bodyLength=${createDiscussionItem.body.length}` ); // Extract title and body from the JSON item let title = createDiscussionItem.title @@ -1274,9 +1289,9 @@ jobs: ); // Prepare the body content const body = bodyLines.join("\n").trim(); - console.log("Creating discussion with title:", title); - console.log("Category ID:", categoryId); - console.log("Body length:", body.length); + core.info(`Creating discussion with title: ${title}`); + core.info(`Category ID: ${categoryId}`); + core.info(`Body length: ${body.length}`); try { // Create the discussion using GraphQL API with parameterized mutation const createDiscussionMutation = ` @@ -1303,7 +1318,7 @@ jobs: body: body, }); const discussion = mutationResult.createDiscussion.discussion; - console.log( + core.info( "Created discussion #" + discussion.number + ": " + discussion.url ); createdDiscussions.push(discussion); @@ -1327,9 +1342,7 @@ jobs: } await core.summary.addRaw(summaryContent).write(); } - console.log( - `Successfully created ${createdDiscussions.length} discussion(s)` - ); + core.info(`Successfully created ${createdDiscussions.length} discussion(s)`); } await main(); diff --git a/.github/workflows/test-safe-output-create-issue.lock.yml b/.github/workflows/test-safe-output-create-issue.lock.yml index 7ed149ea4fa..64b5e310426 100644 --- a/.github/workflows/test-safe-output-create-issue.lock.yml +++ b/.github/workflows/test-safe-output-create-issue.lock.yml @@ -35,7 +35,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -48,17 +48,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -66,32 +64,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -445,7 +443,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -495,6 +493,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -569,9 +568,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -579,33 +586,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -616,6 +624,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -666,8 +675,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -703,8 +713,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -715,14 +726,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1030,10 +1047,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1047,7 +1065,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1060,11 +1078,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1116,7 +1135,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -1129,17 +1148,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -1147,32 +1164,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -1192,27 +1209,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } // Find all create-issue items @@ -1220,10 +1236,10 @@ jobs: /** @param {any} item */ item => item.type === "create-issue" ); if (createIssueItems.length === 0) { - console.log("No create-issue items found in agent output"); + core.info("No create-issue items found in agent output"); return; } - console.log(`Found ${createIssueItems.length} create-issue item(s)`); + core.info(`Found ${createIssueItems.length} create-issue item(s)`); // If in staged mode, emit step summary instead of creating issues if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Create Issues Preview\n\n"; @@ -1243,7 +1259,7 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Issue creation preview written to step summary"); + core.info("📝 Issue creation preview written to step summary"); return; } // Check if we're in an issue context (triggered by an issue event) @@ -1260,9 +1276,8 @@ jobs: // Process each create-issue item for (let i = 0; i < createIssueItems.length; i++) { const createIssueItem = createIssueItems[i]; - console.log( - `Processing create-issue item ${i + 1}/${createIssueItems.length}:`, - { title: createIssueItem.title, bodyLength: createIssueItem.body.length } + core.info( + `Processing create-issue item ${i + 1}/${createIssueItems.length}: title=${createIssueItem.title}, bodyLength=${createIssueItem.body.length}` ); // Merge environment labels with item-specific labels let labels = [...envLabels]; @@ -1282,7 +1297,7 @@ jobs: title = titlePrefix + title; } if (parentIssueNumber) { - console.log("Detected issue context, parent issue #" + parentIssueNumber); + core.info("Detected issue context, parent issue #" + parentIssueNumber); // Add reference to parent issue in the child issue body bodyLines.push(`Related to #${parentIssueNumber}`); } @@ -1300,9 +1315,9 @@ jobs: ); // Prepare the body content const body = bodyLines.join("\n").trim(); - console.log("Creating issue with title:", title); - console.log("Labels:", labels); - console.log("Body length:", body.length); + core.info(`Creating issue with title: ${title}`); + core.info(`Labels: ${labels}`); + core.info(`Body length: ${body.length}`); try { // Create the issue using GitHub API const { data: issue } = await github.rest.issues.create({ @@ -1312,7 +1327,7 @@ jobs: body: body, labels: labels, }); - console.log("Created issue #" + issue.number + ": " + issue.html_url); + core.info("Created issue #" + issue.number + ": " + issue.html_url); createdIssues.push(issue); // If we have a parent issue, add a comment to it referencing the new child issue if (parentIssueNumber) { @@ -1323,11 +1338,10 @@ jobs: issue_number: parentIssueNumber, body: `Created related issue: #${issue.number}`, }); - console.log("Added comment to parent issue #" + parentIssueNumber); + core.info("Added comment to parent issue #" + parentIssueNumber); } catch (error) { - console.log( - "Warning: Could not add comment to parent issue:", - error instanceof Error ? error.message : String(error) + core.info( + `Warning: Could not add comment to parent issue: ${error instanceof Error ? error.message : String(error)}` ); } } @@ -1343,10 +1357,10 @@ jobs: if ( errorMessage.includes("Issues has been disabled in this repository") ) { - console.log( + core.info( `⚠ Cannot create issue "${title}": Issues are disabled for this repository` ); - console.log( + core.info( "Consider enabling issues in repository settings if you want to create issues automatically" ); continue; // Skip this issue but continue processing others @@ -1363,7 +1377,7 @@ jobs: } await core.summary.addRaw(summaryContent).write(); } - console.log(`Successfully created ${createdIssues.length} issue(s)`); + core.info(`Successfully created ${createdIssues.length} issue(s)`); } await main(); diff --git a/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml b/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml index 4c51a1c98a5..176cbf2159d 100644 --- a/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml +++ b/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml @@ -37,7 +37,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -50,17 +50,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -68,32 +66,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -458,7 +456,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -508,6 +506,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -582,9 +581,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -592,33 +599,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -629,6 +637,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -679,8 +688,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -716,8 +726,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -728,14 +739,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1043,10 +1060,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1060,7 +1078,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1073,11 +1091,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1130,27 +1149,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } // Find all create-pull-request-review-comment items @@ -1159,12 +1177,12 @@ jobs: item.type === "create-pull-request-review-comment" ); if (reviewCommentItems.length === 0) { - console.log( + core.info( "No create-pull-request-review-comment items found in agent output" ); return; } - console.log( + core.info( `Found ${reviewCommentItems.length} create-pull-request-review-comment item(s)` ); // If in staged mode, emit step summary instead of creating review comments @@ -1187,14 +1205,12 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log( - "📝 PR review comment creation preview written to step summary" - ); + core.info("📝 PR review comment creation preview written to step summary"); return; } // Get the side configuration from environment variable const defaultSide = process.env.GITHUB_AW_PR_REVIEW_COMMENT_SIDE || "RIGHT"; - console.log(`Default comment side configuration: ${defaultSide}`); + core.info(`Default comment side configuration: ${defaultSide}`); // Check if we're in a pull request context, or an issue comment context on a PR const isPRContext = context.eventName === "pull_request" || @@ -1204,7 +1220,7 @@ jobs: context.payload.issue && context.payload.issue.pull_request); if (!isPRContext) { - console.log( + core.info( "Not running in pull request context, skipping review comment creation" ); return; @@ -1213,7 +1229,7 @@ jobs: if (!pullRequest) { //No, github.event.issue.pull_request does not contain the full pull request data like head.sha. It only includes a minimal object with a url pointing to the pull request API resource. //To get full PR details (like head.sha, base.ref, etc.), you need to make an API call using that URL. - if (context.payload.issue.pull_request) { + if (context.payload.issue && context.payload.issue.pull_request) { // Fetch full pull request details using the GitHub API const prUrl = context.payload.issue.pull_request.url; try { @@ -1223,46 +1239,39 @@ jobs: }, }); pullRequest = fullPR; - console.log("Fetched full pull request details from API"); + core.info("Fetched full pull request details from API"); } catch (error) { - console.log( - "Failed to fetch full pull request details:", - error instanceof Error ? error.message : String(error) + core.info( + `Failed to fetch full pull request details: ${error instanceof Error ? error.message : String(error)}` ); return; } } else { - console.log( + core.info( "Pull request data not found in payload - cannot create review comments" ); return; } } // Check if we have the commit SHA needed for creating review comments - if (!pullRequest.head || !pullRequest.head.sha) { - console.log( + if (!pullRequest || !pullRequest.head || !pullRequest.head.sha) { + core.info( "Pull request head commit SHA not found in payload - cannot create review comments" ); return; } const pullRequestNumber = pullRequest.number; - console.log(`Creating review comments on PR #${pullRequestNumber}`); + core.info(`Creating review comments on PR #${pullRequestNumber}`); const createdComments = []; // Process each review comment item for (let i = 0; i < reviewCommentItems.length; i++) { const commentItem = reviewCommentItems[i]; - console.log( - `Processing create-pull-request-review-comment item ${i + 1}/${reviewCommentItems.length}:`, - { - bodyLength: commentItem.body ? commentItem.body.length : "undefined", - path: commentItem.path, - line: commentItem.line, - startLine: commentItem.start_line, - } + core.info( + `Processing create-pull-request-review-comment item ${i + 1}/${reviewCommentItems.length}: bodyLength=${commentItem.body ? commentItem.body.length : "undefined"}, path=${commentItem.path}, line=${commentItem.line}, startLine=${commentItem.start_line}` ); // Validate required fields if (!commentItem.path) { - console.log('Missing required field "path" in review comment item'); + core.info('Missing required field "path" in review comment item'); continue; } if ( @@ -1270,13 +1279,13 @@ jobs: (typeof commentItem.line !== "number" && typeof commentItem.line !== "string") ) { - console.log( + core.info( 'Missing or invalid required field "line" in review comment item' ); continue; } if (!commentItem.body || typeof commentItem.body !== "string") { - console.log( + core.info( 'Missing or invalid required field "body" in review comment item' ); continue; @@ -1284,14 +1293,14 @@ jobs: // Parse line numbers const line = parseInt(commentItem.line, 10); if (isNaN(line) || line <= 0) { - console.log(`Invalid line number: ${commentItem.line}`); + core.info(`Invalid line number: ${commentItem.line}`); continue; } let startLine = undefined; if (commentItem.start_line) { startLine = parseInt(commentItem.start_line, 10); if (isNaN(startLine) || startLine <= 0 || startLine > line) { - console.log( + core.info( `Invalid start_line number: ${commentItem.start_line} (must be <= line: ${line})` ); continue; @@ -1300,7 +1309,7 @@ jobs: // Determine side (LEFT or RIGHT) const side = commentItem.side || defaultSide; if (side !== "LEFT" && side !== "RIGHT") { - console.log(`Invalid side value: ${side} (must be LEFT or RIGHT)`); + core.info(`Invalid side value: ${side} (must be LEFT or RIGHT)`); continue; } // Extract body from the JSON item @@ -1311,19 +1320,20 @@ jobs: ? `${context.payload.repository.html_url}/actions/runs/${runId}` : `https://github.com/actions/runs/${runId}`; body += `\n\n> Generated by Agentic Workflow [Run](${runUrl})\n`; - console.log( + core.info( `Creating review comment on PR #${pullRequestNumber} at ${commentItem.path}:${line}${startLine ? ` (lines ${startLine}-${line})` : ""} [${side}]` ); - console.log("Comment content length:", body.length); + core.info(`Comment content length: ${body.length}`); try { // Prepare the request parameters + /** @type {any} */ const requestParams = { owner: context.repo.owner, repo: context.repo.repo, pull_number: pullRequestNumber, body: body, path: commentItem.path, - commit_id: pullRequest.head.sha, // Required for creating review comments + commit_id: pullRequest && pullRequest.head ? pullRequest.head.sha : "", // Required for creating review comments line: line, side: side, }; @@ -1335,7 +1345,7 @@ jobs: // Create the review comment using GitHub API const { data: comment } = await github.rest.pulls.createReviewComment(requestParams); - console.log( + core.info( "Created review comment #" + comment.id + ": " + comment.html_url ); createdComments.push(comment); @@ -1359,9 +1369,7 @@ jobs: } await core.summary.addRaw(summaryContent).write(); } - console.log( - `Successfully created ${createdComments.length} review comment(s)` - ); + core.info(`Successfully created ${createdComments.length} review comment(s)`); return createdComments; } await main(); diff --git a/.github/workflows/test-safe-output-create-pull-request.lock.yml b/.github/workflows/test-safe-output-create-pull-request.lock.yml index 4b5a8933cf2..c1f83311956 100644 --- a/.github/workflows/test-safe-output-create-pull-request.lock.yml +++ b/.github/workflows/test-safe-output-create-pull-request.lock.yml @@ -36,7 +36,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -49,17 +49,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -67,32 +65,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -474,7 +472,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -524,6 +522,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -598,9 +597,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -608,33 +615,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -645,6 +653,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -695,8 +704,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -732,8 +742,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -744,14 +755,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1059,10 +1076,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1076,7 +1094,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1089,11 +1107,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1297,7 +1316,7 @@ jobs: } const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT || ""; if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); } const ifNoChanges = process.env.GITHUB_AW_PR_IF_NO_CHANGES || "warn"; // Check if patch file exists and has valid content @@ -1313,7 +1332,7 @@ jobs: summaryContent += `**Message:** ${message}\n\n`; // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log( + core.info( "📝 Pull request creation preview written to step summary (no patch file)" ); return; @@ -1326,7 +1345,7 @@ jobs: return; case "warn": default: - console.log(message); + core.warning(message); return; } } @@ -1344,7 +1363,7 @@ jobs: summaryContent += `**Message:** ${message}\n\n`; // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log( + core.info( "📝 Pull request creation preview written to step summary (patch error)" ); return; @@ -1357,7 +1376,7 @@ jobs: return; case "warn": default: - console.log(message); + core.warning(message); return; } } @@ -1376,29 +1395,28 @@ jobs: return; case "warn": default: - console.log(message); + core.warning(message); return; } } - console.log("Agent output content length:", outputContent.length); + core.debug(`Agent output content length: ${outputContent.length}`); if (!isEmpty) { - console.log("Patch content validation passed"); + core.info("Patch content validation passed"); } else { - console.log("Patch file is empty - processing noop operation"); + core.info("Patch file is empty - processing noop operation"); } // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.warning("No valid items found in agent output"); return; } // Find the create-pull-request item @@ -1406,13 +1424,12 @@ jobs: /** @param {any} item */ item => item.type === "create-pull-request" ); if (!pullRequestItem) { - console.log("No create-pull-request item found in agent output"); + core.warning("No create-pull-request item found in agent output"); return; } - console.log("Found create-pull-request item:", { - title: pullRequestItem.title, - bodyLength: pullRequestItem.body.length, - }); + core.debug( + `Found create-pull-request item: title="${pullRequestItem.title}", bodyLength=${pullRequestItem.body.length}` + ); // If in staged mode, emit step summary instead of creating PR if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n"; @@ -1435,7 +1452,7 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Pull request creation preview written to step summary"); + core.info("📝 Pull request creation preview written to step summary"); return; } // Extract title, body, and branch from the JSON item @@ -1477,50 +1494,48 @@ jobs: // Parse draft setting from environment variable (defaults to true) const draftEnv = process.env.GITHUB_AW_PR_DRAFT; const draft = draftEnv ? draftEnv.toLowerCase() === "true" : true; - console.log("Creating pull request with title:", title); - console.log("Labels:", labels); - console.log("Draft:", draft); - console.log("Body length:", body.length); + core.info(`Creating pull request with title: ${title}`); + core.debug(`Labels: ${JSON.stringify(labels)}`); + core.debug(`Draft: ${draft}`); + core.debug(`Body length: ${body.length}`); const randomHex = crypto.randomBytes(8).toString("hex"); // Use branch name from JSONL if provided, otherwise generate unique branch name if (!branchName) { - console.log( + core.debug( "No branch name provided in JSONL, generating unique branch name" ); // Generate unique branch name using cryptographic random hex branchName = `${workflowId}-${randomHex}`; } else { branchName = `${branchName}-${randomHex}`; - console.log("Using branch name from JSONL with added salt:", branchName); + core.debug(`Using branch name from JSONL with added salt: ${branchName}`); } - console.log("Generated branch name:", branchName); - console.log("Base branch:", baseBranch); + core.info(`Generated branch name: ${branchName}`); + core.debug(`Base branch: ${baseBranch}`); // Create a new branch using git CLI, ensuring it's based on the correct base branch // First, fetch latest changes and checkout the base branch - console.log( - "Fetching latest changes and checking out base branch:", - baseBranch + core.debug( + `Fetching latest changes and checking out base branch: ${baseBranch}` ); execSync("git fetch origin", { stdio: "inherit" }); execSync(`git checkout ${baseBranch}`, { stdio: "inherit" }); // Handle branch creation/checkout - console.log( - "Branch should not exist locally, creating new branch from base:", - branchName + core.debug( + `Branch should not exist locally, creating new branch from base: ${branchName}` ); execSync(`git checkout -b ${branchName}`, { stdio: "inherit" }); - console.log("Created new branch from base:", branchName); + core.info(`Created new branch from base: ${branchName}`); // Apply the patch using git CLI (skip if empty) if (!isEmpty) { - console.log("Applying patch..."); + core.info("Applying patch..."); // Patches are created with git format-patch, so use git am to apply them execSync("git am /tmp/aw.patch", { stdio: "inherit" }); - console.log("Patch applied successfully"); + core.info("Patch applied successfully"); // Push the applied commits to the branch execSync(`git push origin ${branchName}`, { stdio: "inherit" }); - console.log("Changes pushed to branch"); + core.info("Changes pushed to branch"); } else { - console.log("Skipping patch application (empty patch)"); + core.info("Skipping patch application (empty patch)"); // For empty patches, handle if-no-changes configuration const message = "No changes to apply - noop operation completed successfully"; @@ -1534,7 +1549,7 @@ jobs: return; case "warn": default: - console.log(message); + core.warning(message); return; } } @@ -1548,8 +1563,8 @@ jobs: base: baseBranch, draft: draft, }); - console.log( - "Created pull request #" + pullRequest.number + ": " + pullRequest.html_url + core.info( + `Created pull request #${pullRequest.number}: ${pullRequest.html_url}` ); // Add labels if specified if (labels.length > 0) { @@ -1559,7 +1574,7 @@ jobs: issue_number: pullRequest.number, labels: labels, }); - console.log("Added labels to pull request:", labels); + core.info(`Added labels to pull request: ${JSON.stringify(labels)}`); } // Set output for other jobs to use core.setOutput("pull_request_number", pullRequest.number); diff --git a/.github/workflows/test-safe-output-missing-tool.lock.yml b/.github/workflows/test-safe-output-missing-tool.lock.yml index b0396cfb60e..4d5ff39ac94 100644 --- a/.github/workflows/test-safe-output-missing-tool.lock.yml +++ b/.github/workflows/test-safe-output-missing-tool.lock.yml @@ -381,7 +381,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -431,6 +431,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -505,9 +506,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -515,33 +524,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -552,6 +562,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -602,8 +613,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -639,8 +651,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -651,14 +664,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -966,10 +985,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -983,7 +1003,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -996,11 +1016,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1060,6 +1081,7 @@ jobs: if (maxReports) { core.info(`Maximum reports allowed: ${maxReports}`); } + /** @type {any[]} */ const missingTools = []; // Return early if no agent output if (!agentOutput.trim()) { @@ -1073,7 +1095,7 @@ jobs: try { validatedOutput = JSON.parse(agentOutput); } catch (error) { - core.error( + core.setFailed( `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; diff --git a/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml b/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml index bfda88579f3..5885d4e3334 100644 --- a/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml +++ b/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml @@ -37,7 +37,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -50,17 +50,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -68,32 +66,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -478,7 +476,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -528,6 +526,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -602,9 +601,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -612,33 +619,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -649,6 +657,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -699,8 +708,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -736,8 +746,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -748,14 +759,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1063,10 +1080,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1080,7 +1098,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1093,11 +1111,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1287,7 +1306,7 @@ jobs: // Environment validation - fail early if required variables are missing const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT || ""; if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } const target = process.env.GITHUB_AW_PUSH_TARGET || "triggering"; @@ -1304,7 +1323,7 @@ jobs: return; case "warn": default: - console.log(message); + core.info(message); return; } } @@ -1322,7 +1341,7 @@ jobs: return; case "warn": default: - console.log(message); + core.info(message); return; } } @@ -1342,28 +1361,27 @@ jobs: break; case "warn": default: - console.log(message); + core.info(message); break; } } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); if (!isEmpty) { - console.log("Patch content validation passed"); + core.info("Patch content validation passed"); } - console.log("Target configuration:", target); + core.info(`Target configuration: ${target}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } // Find the push-to-pr-branch item @@ -1371,10 +1389,10 @@ jobs: /** @param {any} item */ item => item.type === "push-to-pr-branch" ); if (!pushItem) { - console.log("No push-to-pr-branch item found in agent output"); + core.info("No push-to-pr-branch item found in agent output"); return; } - console.log("Found push-to-pr-branch item"); + core.info("Found push-to-pr-branch item"); // If in staged mode, emit step summary instead of pushing changes if (process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true") { let summaryContent = "## 🎭 Staged Mode: Push to PR Branch Preview\n\n"; @@ -1395,7 +1413,7 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Push to PR branch preview written to step summary"); + core.info("📝 Push to PR branch preview written to step summary"); return; } // Validate target configuration for pull request context @@ -1443,18 +1461,18 @@ jobs: throw new Error("No head branch found for PR"); } } catch (error) { - console.log( - `Warning: Could not fetch PR ${pullNumber} details: ${error.message}` + core.info( + `Warning: Could not fetch PR ${pullNumber} details: ${error instanceof Error ? error.message : String(error)}` ); // Exit with failure if we cannot determine the branch name core.setFailed(`Failed to determine branch name for PR ${pullNumber}`); return; } - console.log("Target branch:", branchName); + core.info(`Target branch: ${branchName}`); // Check if patch has actual changes (not just empty) const hasChanges = !isEmpty; // Switch to or create the target branch - console.log("Switching to branch:", branchName); + core.info(`Switching to branch: ${branchName}`); try { // Try to checkout existing branch first execSync("git fetch origin", { stdio: "inherit" }); @@ -1467,32 +1485,31 @@ jobs: execSync(`git checkout -B ${branchName} origin/${branchName}`, { stdio: "inherit", }); - console.log("Checked out existing branch from origin:", branchName); + core.info(`Checked out existing branch from origin: ${branchName}`); } catch (originError) { // Branch doesn't exist on origin, check if it exists locally try { execSync(`git rev-parse --verify ${branchName}`, { stdio: "pipe" }); // Branch exists locally, check it out execSync(`git checkout ${branchName}`, { stdio: "inherit" }); - console.log("Checked out existing local branch:", branchName); + core.info(`Checked out existing local branch: ${branchName}`); } catch (localError) { // Branch doesn't exist locally or on origin, create it from default branch - console.log( - "Branch does not exist, creating new branch from default branch:", - branchName + core.info( + `Branch does not exist, creating new branch from default branch: ${branchName}` ); // Get the default branch name const defaultBranch = execSync( "git remote show origin | grep 'HEAD branch' | cut -d' ' -f5", { encoding: "utf8" } ).trim(); - console.log("Default branch:", defaultBranch); + core.info(`Default branch: ${defaultBranch}`); // Ensure we have the latest default branch execSync(`git checkout ${defaultBranch}`, { stdio: "inherit" }); execSync(`git pull origin ${defaultBranch}`, { stdio: "inherit" }); // Create new branch from default branch execSync(`git checkout -b ${branchName}`, { stdio: "inherit" }); - console.log("Created new branch from default branch:", branchName); + core.info(`Created new branch from default branch: ${branchName}`); } } } catch (error) { @@ -1503,14 +1520,14 @@ jobs: } // Apply the patch using git CLI (skip if empty) if (!isEmpty) { - console.log("Applying patch..."); + core.info("Applying patch..."); try { // Patches are created with git format-patch, so use git am to apply them execSync("git am /tmp/aw.patch", { stdio: "inherit" }); - console.log("Patch applied successfully"); + core.info("Patch applied successfully"); // Push the applied commits to the branch execSync(`git push origin ${branchName}`, { stdio: "inherit" }); - console.log("Changes committed and pushed to branch:", branchName); + core.info(`Changes committed and pushed to branch: ${branchName}`); } catch (error) { core.error( `Failed to apply patch: ${error instanceof Error ? error.message : String(error)}` @@ -1519,7 +1536,7 @@ jobs: return; } } else { - console.log("Skipping patch application (empty patch)"); + core.info("Skipping patch application (empty patch)"); // Handle if-no-changes configuration for empty patches const message = "No changes to apply - noop operation completed successfully"; @@ -1534,7 +1551,7 @@ jobs: break; case "warn": default: - console.log(message); + core.info(message); break; } } diff --git a/.github/workflows/test-safe-output-update-issue.lock.yml b/.github/workflows/test-safe-output-update-issue.lock.yml index be62dddc14c..3a438007098 100644 --- a/.github/workflows/test-safe-output-update-issue.lock.yml +++ b/.github/workflows/test-safe-output-update-issue.lock.yml @@ -36,7 +36,7 @@ jobs: // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } const actor = context.actor; @@ -49,17 +49,15 @@ jobs: core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ owner: owner, @@ -67,32 +65,32 @@ jobs: username: actor, }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { if ( permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } // Cancel the job when permission check fails core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } @@ -450,7 +448,7 @@ jobs: /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -500,6 +498,7 @@ jobs: // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -574,9 +573,17 @@ jobs: return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -584,33 +591,34 @@ jobs: const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } // Parse JSONL content @@ -621,6 +629,7 @@ jobs: const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line if (item === undefined) { @@ -671,8 +680,9 @@ jobs: item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -708,8 +718,9 @@ jobs: } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -720,14 +731,20 @@ jobs: ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": // Check that at least one updateable field is provided @@ -1035,10 +1052,11 @@ jobs: errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } // Report validation results @@ -1052,7 +1070,7 @@ jobs: // For now, we'll continue with valid items but log the errors // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { items: parsedItems, @@ -1065,11 +1083,12 @@ jobs: // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); core.setOutput("raw_output", outputContent); @@ -1127,27 +1146,26 @@ jobs: // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } // Find all update-issue items @@ -1155,10 +1173,10 @@ jobs: /** @param {any} item */ item => item.type === "update-issue" ); if (updateItems.length === 0) { - console.log("No update-issue items found in agent output"); + core.info("No update-issue items found in agent output"); return; } - console.log(`Found ${updateItems.length} update-issue item(s)`); + core.info(`Found ${updateItems.length} update-issue item(s)`); // If in staged mode, emit step summary instead of updating issues if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Update Issues Preview\n\n"; @@ -1185,7 +1203,7 @@ jobs: } // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Issue update preview written to step summary"); + core.info("📝 Issue update preview written to step summary"); return; } // Get the configuration from environment variables @@ -1193,8 +1211,8 @@ jobs: const canUpdateStatus = process.env.GITHUB_AW_UPDATE_STATUS === "true"; const canUpdateTitle = process.env.GITHUB_AW_UPDATE_TITLE === "true"; const canUpdateBody = process.env.GITHUB_AW_UPDATE_BODY === "true"; - console.log(`Update target configuration: ${updateTarget}`); - console.log( + core.info(`Update target configuration: ${updateTarget}`); + core.info( `Can update status: ${canUpdateStatus}, title: ${canUpdateTitle}, body: ${canUpdateBody}` ); // Check if we're in an issue context @@ -1202,7 +1220,7 @@ jobs: context.eventName === "issues" || context.eventName === "issue_comment"; // Validate context based on target configuration if (updateTarget === "triggering" && !isIssueContext) { - console.log( + core.info( 'Target is "triggering" but not running in issue context, skipping issue update' ); return; @@ -1211,7 +1229,7 @@ jobs: // Process each update item for (let i = 0; i < updateItems.length; i++) { const updateItem = updateItems[i]; - console.log(`Processing update-issue item ${i + 1}/${updateItems.length}`); + core.info(`Processing update-issue item ${i + 1}/${updateItems.length}`); // Determine the issue number for this update let issueNumber; if (updateTarget === "*") { @@ -1219,22 +1237,20 @@ jobs: if (updateItem.issue_number) { issueNumber = parseInt(updateItem.issue_number, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number specified: ${updateItem.issue_number}` ); continue; } } else { - console.log( - 'Target is "*" but no issue_number specified in update item' - ); + core.info('Target is "*" but no issue_number specified in update item'); continue; } } else if (updateTarget && updateTarget !== "triggering") { // Explicit issue number specified in target issueNumber = parseInt(updateTarget, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number in target configuration: ${updateTarget}` ); continue; @@ -1245,20 +1261,21 @@ jobs: if (context.payload.issue) { issueNumber = context.payload.issue.number; } else { - console.log("Issue context detected but no issue found in payload"); + core.info("Issue context detected but no issue found in payload"); continue; } } else { - console.log("Could not determine issue number"); + core.info("Could not determine issue number"); continue; } } if (!issueNumber) { - console.log("Could not determine issue number"); + core.info("Could not determine issue number"); continue; } - console.log(`Updating issue #${issueNumber}`); + core.info(`Updating issue #${issueNumber}`); // Build the update object based on allowed fields and provided values + /** @type {any} */ const updateData = {}; let hasUpdates = false; if (canUpdateStatus && updateItem.status !== undefined) { @@ -1266,9 +1283,9 @@ jobs: if (updateItem.status === "open" || updateItem.status === "closed") { updateData.state = updateItem.status; hasUpdates = true; - console.log(`Will update status to: ${updateItem.status}`); + core.info(`Will update status to: ${updateItem.status}`); } else { - console.log( + core.info( `Invalid status value: ${updateItem.status}. Must be 'open' or 'closed'` ); } @@ -1280,22 +1297,22 @@ jobs: ) { updateData.title = updateItem.title.trim(); hasUpdates = true; - console.log(`Will update title to: ${updateItem.title.trim()}`); + core.info(`Will update title to: ${updateItem.title.trim()}`); } else { - console.log("Invalid title value: must be a non-empty string"); + core.info("Invalid title value: must be a non-empty string"); } } if (canUpdateBody && updateItem.body !== undefined) { if (typeof updateItem.body === "string") { updateData.body = updateItem.body; hasUpdates = true; - console.log(`Will update body (length: ${updateItem.body.length})`); + core.info(`Will update body (length: ${updateItem.body.length})`); } else { - console.log("Invalid body value: must be a string"); + core.info("Invalid body value: must be a string"); } } if (!hasUpdates) { - console.log("No valid updates to apply for this item"); + core.info("No valid updates to apply for this item"); continue; } try { @@ -1306,7 +1323,7 @@ jobs: issue_number: issueNumber, ...updateData, }); - console.log("Updated issue #" + issue.number + ": " + issue.html_url); + core.info("Updated issue #" + issue.number + ": " + issue.html_url); updatedIssues.push(issue); // Set output for the last updated issue (for backward compatibility) if (i === updateItems.length - 1) { @@ -1328,7 +1345,7 @@ jobs: } await core.summary.addRaw(summaryContent).write(); } - console.log(`Successfully updated ${updatedIssues.length} issue(s)`); + core.info(`Successfully updated ${updatedIssues.length} issue(s)`); return updatedIssues; } await main(); diff --git a/pkg/cli/workflows/test-ai-inference-github-models.lock.yml b/pkg/cli/workflows/test-ai-inference-github-models.lock.yml index 116108a0a7f..0399e8e6e57 100644 --- a/pkg/cli/workflows/test-ai-inference-github-models.lock.yml +++ b/pkg/cli/workflows/test-ai-inference-github-models.lock.yml @@ -316,11 +316,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -328,10 +328,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -471,9 +476,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -560,6 +572,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -572,6 +589,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -586,6 +608,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -605,6 +632,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-add-issue-comment.lock.yml b/pkg/cli/workflows/test-claude-add-issue-comment.lock.yml index 1a26818fc9f..b16bf6d9dce 100644 --- a/pkg/cli/workflows/test-claude-add-issue-comment.lock.yml +++ b/pkg/cli/workflows/test-claude-add-issue-comment.lock.yml @@ -313,11 +313,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -325,10 +325,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -468,9 +473,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -557,6 +569,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -569,6 +586,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -583,6 +605,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -602,6 +629,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-add-issue-labels.lock.yml b/pkg/cli/workflows/test-claude-add-issue-labels.lock.yml index c028c79e6a9..e0a221349d6 100644 --- a/pkg/cli/workflows/test-claude-add-issue-labels.lock.yml +++ b/pkg/cli/workflows/test-claude-add-issue-labels.lock.yml @@ -313,11 +313,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -325,10 +325,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -468,9 +473,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -557,6 +569,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -569,6 +586,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -583,6 +605,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -602,6 +629,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-command.lock.yml b/pkg/cli/workflows/test-claude-command.lock.yml index 0684114a273..d9ff0cd94e5 100644 --- a/pkg/cli/workflows/test-claude-command.lock.yml +++ b/pkg/cli/workflows/test-claude-command.lock.yml @@ -313,11 +313,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -325,10 +325,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -468,9 +473,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -557,6 +569,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -569,6 +586,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -583,6 +605,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -602,6 +629,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-create-issue.lock.yml b/pkg/cli/workflows/test-claude-create-issue.lock.yml index 10891bc8ef0..2736c23fd6e 100644 --- a/pkg/cli/workflows/test-claude-create-issue.lock.yml +++ b/pkg/cli/workflows/test-claude-create-issue.lock.yml @@ -313,11 +313,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -325,10 +325,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -468,9 +473,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -557,6 +569,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -569,6 +586,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -583,6 +605,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -602,6 +629,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-create-pull-request-review-comment.lock.yml b/pkg/cli/workflows/test-claude-create-pull-request-review-comment.lock.yml index d79287e8af2..21b26a8fb40 100644 --- a/pkg/cli/workflows/test-claude-create-pull-request-review-comment.lock.yml +++ b/pkg/cli/workflows/test-claude-create-pull-request-review-comment.lock.yml @@ -313,11 +313,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -325,10 +325,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -468,9 +473,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -557,6 +569,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -569,6 +586,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -583,6 +605,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -602,6 +629,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-create-pull-request.lock.yml b/pkg/cli/workflows/test-claude-create-pull-request.lock.yml index 3dad6655270..16b613bba25 100644 --- a/pkg/cli/workflows/test-claude-create-pull-request.lock.yml +++ b/pkg/cli/workflows/test-claude-create-pull-request.lock.yml @@ -318,11 +318,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -330,10 +330,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -473,9 +478,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -562,6 +574,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -574,6 +591,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -588,6 +610,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -607,6 +634,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-create-repository-security-advisory.lock.yml b/pkg/cli/workflows/test-claude-create-repository-security-advisory.lock.yml index d6b169aea2e..1d23175a97f 100644 --- a/pkg/cli/workflows/test-claude-create-repository-security-advisory.lock.yml +++ b/pkg/cli/workflows/test-claude-create-repository-security-advisory.lock.yml @@ -316,11 +316,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -328,10 +328,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -471,9 +476,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -560,6 +572,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -572,6 +589,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -586,6 +608,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -605,6 +632,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-mcp.lock.yml b/pkg/cli/workflows/test-claude-mcp.lock.yml index 6b11c8eb98c..463be71180d 100644 --- a/pkg/cli/workflows/test-claude-mcp.lock.yml +++ b/pkg/cli/workflows/test-claude-mcp.lock.yml @@ -316,11 +316,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -328,10 +328,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -471,9 +476,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -560,6 +572,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -572,6 +589,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -586,6 +608,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -605,6 +632,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-push-to-pr-branch.lock.yml b/pkg/cli/workflows/test-claude-push-to-pr-branch.lock.yml index 86673712683..4d79c5cc5de 100644 --- a/pkg/cli/workflows/test-claude-push-to-pr-branch.lock.yml +++ b/pkg/cli/workflows/test-claude-push-to-pr-branch.lock.yml @@ -318,11 +318,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -330,10 +330,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -473,9 +478,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -562,6 +574,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -574,6 +591,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -588,6 +610,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -607,6 +634,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-claude-update-issue.lock.yml b/pkg/cli/workflows/test-claude-update-issue.lock.yml index 414cc3131cc..e683f16bc20 100644 --- a/pkg/cli/workflows/test-claude-update-issue.lock.yml +++ b/pkg/cli/workflows/test-claude-update-issue.lock.yml @@ -316,11 +316,11 @@ jobs: // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const logContent = fs.readFileSync(logFile, "utf8"); @@ -328,10 +328,15 @@ jobs: // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } + /** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -471,9 +476,16 @@ jobs: } return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } + /** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -560,6 +572,11 @@ jobs: } return markdown; } + /** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -572,6 +589,11 @@ jobs: } return toolName; } + /** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -586,6 +608,11 @@ jobs: } return paramStrs.join(", "); } + /** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -605,6 +632,12 @@ jobs: } return formatted; } + /** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-codex-add-issue-comment.lock.yml b/pkg/cli/workflows/test-codex-add-issue-comment.lock.yml index d47e523d1dd..d8768b05a6c 100644 --- a/pkg/cli/workflows/test-codex-add-issue-comment.lock.yml +++ b/pkg/cli/workflows/test-codex-add-issue-comment.lock.yml @@ -137,25 +137,30 @@ jobs: try { const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const content = fs.readFileSync(logFile, "utf8"); const parsedLog = parseCodexLog(content); if (parsedLog) { core.summary.addRaw(parsedLog).write(); - console.log("Codex log parsed successfully"); + core.info("Codex log parsed successfully"); } else { core.error("Failed to parse Codex log"); } } catch (error) { - core.setFailed(error.message); + core.setFailed(error instanceof Error ? error : String(error)); } } + /** + * Parse codex log content and format as markdown + * @param {string} logContent - The raw log content to parse + * @returns {string} Formatted markdown content + */ function parseCodexLog(logContent) { try { const lines = logContent.split("\n"); @@ -237,8 +242,11 @@ jobs: const tokenMatches = logContent.match(/tokens used: (\d+)/g); if (tokenMatches) { for (const match of tokenMatches) { - const tokens = parseInt(match.match(/(\d+)/)[1]); - totalTokens += tokens; + const numberMatch = match.match(/(\d+)/); + if (numberMatch) { + const tokens = parseInt(numberMatch[1]); + totalTokens += tokens; + } } } if (totalTokens > 0) { @@ -353,6 +361,11 @@ jobs: return "## 🤖 Commands and Tools\n\nError parsing log content.\n\n## 🤖 Reasoning\n\nUnable to parse reasoning from log.\n\n"; } } + /** + * Format bash command for display + * @param {string} command - The command to format + * @returns {string} Formatted command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -372,6 +385,12 @@ jobs: } return formatted; } + /** + * Truncate string to maximum length + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum length allowed + * @returns {string} Truncated string + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; @@ -421,11 +440,13 @@ jobs: if (hasErrors) { core.setFailed("Errors detected in agent logs - failing workflow step"); } else { - console.log("Error validation completed successfully"); + core.info("Error validation completed successfully"); } } catch (error) { console.debug(error); - core.setFailed(`Error validating log: ${error.message}`); + core.setFailed( + `Error validating log: ${error instanceof Error ? error.message : String(error)}` + ); } } function getErrorPatternsFromEnv() { @@ -443,10 +464,15 @@ jobs: return patterns; } catch (e) { throw new Error( - `Failed to parse GITHUB_AW_ERROR_PATTERNS as JSON: ${e.message}` + `Failed to parse GITHUB_AW_ERROR_PATTERNS as JSON: ${e instanceof Error ? e.message : String(e)}` ); } } + /** + * @param {string} logContent + * @param {any[]} patterns + * @returns {boolean} + */ function validateErrors(logContent, patterns) { const lines = logContent.split("\n"); let hasErrors = false; @@ -470,6 +496,11 @@ jobs: } return hasErrors; } + /** + * @param {any} match + * @param {any} pattern + * @returns {string} + */ function extractLevel(match, pattern) { if ( pattern.level_group && @@ -487,6 +518,12 @@ jobs: } return "unknown"; } + /** + * @param {any} match + * @param {any} pattern + * @param {any} fullLine + * @returns {string} + */ function extractMessage(match, pattern, fullLine) { if ( pattern.message_group && @@ -498,6 +535,11 @@ jobs: // Fallback to the full match or line return match[0] || fullLine.trim(); } + /** + * @param {any} str + * @param {any} maxLength + * @returns {string} + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-codex-add-issue-labels.lock.yml b/pkg/cli/workflows/test-codex-add-issue-labels.lock.yml index 1729380f519..3266ae21564 100644 --- a/pkg/cli/workflows/test-codex-add-issue-labels.lock.yml +++ b/pkg/cli/workflows/test-codex-add-issue-labels.lock.yml @@ -137,25 +137,30 @@ jobs: try { const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const content = fs.readFileSync(logFile, "utf8"); const parsedLog = parseCodexLog(content); if (parsedLog) { core.summary.addRaw(parsedLog).write(); - console.log("Codex log parsed successfully"); + core.info("Codex log parsed successfully"); } else { core.error("Failed to parse Codex log"); } } catch (error) { - core.setFailed(error.message); + core.setFailed(error instanceof Error ? error : String(error)); } } + /** + * Parse codex log content and format as markdown + * @param {string} logContent - The raw log content to parse + * @returns {string} Formatted markdown content + */ function parseCodexLog(logContent) { try { const lines = logContent.split("\n"); @@ -237,8 +242,11 @@ jobs: const tokenMatches = logContent.match(/tokens used: (\d+)/g); if (tokenMatches) { for (const match of tokenMatches) { - const tokens = parseInt(match.match(/(\d+)/)[1]); - totalTokens += tokens; + const numberMatch = match.match(/(\d+)/); + if (numberMatch) { + const tokens = parseInt(numberMatch[1]); + totalTokens += tokens; + } } } if (totalTokens > 0) { @@ -353,6 +361,11 @@ jobs: return "## 🤖 Commands and Tools\n\nError parsing log content.\n\n## 🤖 Reasoning\n\nUnable to parse reasoning from log.\n\n"; } } + /** + * Format bash command for display + * @param {string} command - The command to format + * @returns {string} Formatted command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -372,6 +385,12 @@ jobs: } return formatted; } + /** + * Truncate string to maximum length + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum length allowed + * @returns {string} Truncated string + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; @@ -421,11 +440,13 @@ jobs: if (hasErrors) { core.setFailed("Errors detected in agent logs - failing workflow step"); } else { - console.log("Error validation completed successfully"); + core.info("Error validation completed successfully"); } } catch (error) { console.debug(error); - core.setFailed(`Error validating log: ${error.message}`); + core.setFailed( + `Error validating log: ${error instanceof Error ? error.message : String(error)}` + ); } } function getErrorPatternsFromEnv() { @@ -443,10 +464,15 @@ jobs: return patterns; } catch (e) { throw new Error( - `Failed to parse GITHUB_AW_ERROR_PATTERNS as JSON: ${e.message}` + `Failed to parse GITHUB_AW_ERROR_PATTERNS as JSON: ${e instanceof Error ? e.message : String(e)}` ); } } + /** + * @param {string} logContent + * @param {any[]} patterns + * @returns {boolean} + */ function validateErrors(logContent, patterns) { const lines = logContent.split("\n"); let hasErrors = false; @@ -470,6 +496,11 @@ jobs: } return hasErrors; } + /** + * @param {any} match + * @param {any} pattern + * @returns {string} + */ function extractLevel(match, pattern) { if ( pattern.level_group && @@ -487,6 +518,12 @@ jobs: } return "unknown"; } + /** + * @param {any} match + * @param {any} pattern + * @param {any} fullLine + * @returns {string} + */ function extractMessage(match, pattern, fullLine) { if ( pattern.message_group && @@ -498,6 +535,11 @@ jobs: // Fallback to the full match or line return match[0] || fullLine.trim(); } + /** + * @param {any} str + * @param {any} maxLength + * @returns {string} + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/cli/workflows/test-codex-command.lock.yml b/pkg/cli/workflows/test-codex-command.lock.yml index 97a68429415..bca20703e84 100644 --- a/pkg/cli/workflows/test-codex-command.lock.yml +++ b/pkg/cli/workflows/test-codex-command.lock.yml @@ -137,25 +137,30 @@ jobs: try { const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } const content = fs.readFileSync(logFile, "utf8"); const parsedLog = parseCodexLog(content); if (parsedLog) { core.summary.addRaw(parsedLog).write(); - console.log("Codex log parsed successfully"); + core.info("Codex log parsed successfully"); } else { core.error("Failed to parse Codex log"); } } catch (error) { - core.setFailed(error.message); + core.setFailed(error instanceof Error ? error : String(error)); } } + /** + * Parse codex log content and format as markdown + * @param {string} logContent - The raw log content to parse + * @returns {string} Formatted markdown content + */ function parseCodexLog(logContent) { try { const lines = logContent.split("\n"); @@ -237,8 +242,11 @@ jobs: const tokenMatches = logContent.match(/tokens used: (\d+)/g); if (tokenMatches) { for (const match of tokenMatches) { - const tokens = parseInt(match.match(/(\d+)/)[1]); - totalTokens += tokens; + const numberMatch = match.match(/(\d+)/); + if (numberMatch) { + const tokens = parseInt(numberMatch[1]); + totalTokens += tokens; + } } } if (totalTokens > 0) { @@ -353,6 +361,11 @@ jobs: return "## 🤖 Commands and Tools\n\nError parsing log content.\n\n## 🤖 Reasoning\n\nUnable to parse reasoning from log.\n\n"; } } + /** + * Format bash command for display + * @param {string} command - The command to format + * @returns {string} Formatted command string + */ function formatBashCommand(command) { if (!command) return ""; // Convert multi-line commands to single line by replacing newlines with spaces @@ -372,6 +385,12 @@ jobs: } return formatted; } + /** + * Truncate string to maximum length + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum length allowed + * @returns {string} Truncated string + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; @@ -421,11 +440,13 @@ jobs: if (hasErrors) { core.setFailed("Errors detected in agent logs - failing workflow step"); } else { - console.log("Error validation completed successfully"); + core.info("Error validation completed successfully"); } } catch (error) { console.debug(error); - core.setFailed(`Error validating log: ${error.message}`); + core.setFailed( + `Error validating log: ${error instanceof Error ? error.message : String(error)}` + ); } } function getErrorPatternsFromEnv() { @@ -443,10 +464,15 @@ jobs: return patterns; } catch (e) { throw new Error( - `Failed to parse GITHUB_AW_ERROR_PATTERNS as JSON: ${e.message}` + `Failed to parse GITHUB_AW_ERROR_PATTERNS as JSON: ${e instanceof Error ? e.message : String(e)}` ); } } + /** + * @param {string} logContent + * @param {any[]} patterns + * @returns {boolean} + */ function validateErrors(logContent, patterns) { const lines = logContent.split("\n"); let hasErrors = false; @@ -470,6 +496,11 @@ jobs: } return hasErrors; } + /** + * @param {any} match + * @param {any} pattern + * @returns {string} + */ function extractLevel(match, pattern) { if ( pattern.level_group && @@ -487,6 +518,12 @@ jobs: } return "unknown"; } + /** + * @param {any} match + * @param {any} pattern + * @param {any} fullLine + * @returns {string} + */ function extractMessage(match, pattern, fullLine) { if ( pattern.message_group && @@ -498,6 +535,11 @@ jobs: // Fallback to the full match or line return match[0] || fullLine.trim(); } + /** + * @param {any} str + * @param {any} maxLength + * @returns {string} + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/workflow/js/add_labels.cjs b/pkg/workflow/js/add_labels.cjs index 6868949ca5b..cfc5fd6bc88 100644 --- a/pkg/workflow/js/add_labels.cjs +++ b/pkg/workflow/js/add_labels.cjs @@ -2,31 +2,30 @@ async function main() { // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.debug(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.warning("No valid items found in agent output"); return; } @@ -35,13 +34,13 @@ async function main() { /** @param {any} item */ item => item.type === "add-issue-label" ); if (!labelsItem) { - console.log("No add-issue-label item found in agent output"); + core.warning("No add-issue-label item found in agent output"); return; } - console.log("Found add-issue-label item:", { - labelsCount: labelsItem.labels.length, - }); + core.debug( + `Found add-issue-label item with ${labelsItem.labels.length} labels` + ); // If in staged mode, emit step summary instead of adding labels if (process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true") { @@ -61,7 +60,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Label addition preview written to step summary"); + core.info("📝 Label addition preview written to step summary"); return; } @@ -80,9 +79,9 @@ async function main() { } if (allowedLabels) { - console.log("Allowed labels:", allowedLabels); + core.debug(`Allowed labels: ${JSON.stringify(allowedLabels)}`); } else { - console.log("No label restrictions - any labels are allowed"); + core.debug("No label restrictions - any labels are allowed"); } // Read the max limit from environment variable (default: 3) @@ -95,7 +94,7 @@ async function main() { return; } - console.log("Max count:", maxCount); + core.debug(`Max count: ${maxCount}`); // Check if we're in an issue or pull request context const isIssueContext = @@ -143,7 +142,7 @@ async function main() { // Extract labels from the JSON item const requestedLabels = labelsItem.labels || []; - console.log("Requested labels:", requestedLabels); + core.debug(`Requested labels: ${JSON.stringify(requestedLabels)}`); // Check for label removal attempts (labels starting with '-') for (const label of requestedLabels) { @@ -171,12 +170,12 @@ async function main() { // Enforce max limit if (uniqueLabels.length > maxCount) { - console.log(`too many labels, keep ${maxCount}`); + core.debug(`too many labels, keep ${maxCount}`); uniqueLabels = uniqueLabels.slice(0, maxCount); } if (uniqueLabels.length === 0) { - console.log("No labels to add"); + core.info("No labels to add"); core.setOutput("labels_added", ""); await core.summary .addRaw( @@ -190,9 +189,8 @@ No labels were added (no valid labels found in agent output). return; } - console.log( - `Adding ${uniqueLabels.length} labels to ${contextType} #${issueNumber}:`, - uniqueLabels + core.info( + `Adding ${uniqueLabels.length} labels to ${contextType} #${issueNumber}: ${JSON.stringify(uniqueLabels)}` ); try { @@ -204,7 +202,7 @@ No labels were added (no valid labels found in agent output). labels: uniqueLabels, }); - console.log( + core.info( `Successfully added ${uniqueLabels.length} labels to ${contextType} #${issueNumber}` ); diff --git a/pkg/workflow/js/add_reaction.cjs b/pkg/workflow/js/add_reaction.cjs index a4594fa6f7a..ca824af5f80 100644 --- a/pkg/workflow/js/add_reaction.cjs +++ b/pkg/workflow/js/add_reaction.cjs @@ -2,7 +2,7 @@ async function main() { // Read inputs from environment variables const reaction = process.env.GITHUB_AW_REACTION || "eyes"; - console.log("Reaction type:", reaction); + core.info(`Reaction type: ${reaction}`); // Validate reaction type const validReactions = [ @@ -73,7 +73,7 @@ async function main() { return; } - console.log("API endpoint:", endpoint); + core.info(`API endpoint: ${endpoint}`); await addReaction(endpoint, reaction); } catch (error) { @@ -98,10 +98,10 @@ async function addReaction(endpoint, reaction) { const reactionId = response.data?.id; if (reactionId) { - console.log(`Successfully added reaction: ${reaction} (id: ${reactionId})`); + core.info(`Successfully added reaction: ${reaction} (id: ${reactionId})`); core.setOutput("reaction-id", reactionId.toString()); } else { - console.log(`Successfully added reaction: ${reaction}`); + core.info(`Successfully added reaction: ${reaction}`); core.setOutput("reaction-id", ""); } } diff --git a/pkg/workflow/js/add_reaction_and_edit_comment.cjs b/pkg/workflow/js/add_reaction_and_edit_comment.cjs index 398fc83be48..d4a48fb10ae 100644 --- a/pkg/workflow/js/add_reaction_and_edit_comment.cjs +++ b/pkg/workflow/js/add_reaction_and_edit_comment.cjs @@ -7,10 +7,10 @@ async function main() { ? `${context.payload.repository.html_url}/actions/runs/${runId}` : `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; - console.log("Reaction type:", reaction); - console.log("Command name:", command || "none"); - console.log("Run ID:", runId); - console.log("Run URL:", runUrl); + core.info(`Reaction type: ${reaction}`); + core.info(`Command name: ${command || "none"}`); + core.info(`Run ID: ${runId}`); + core.info(`Run URL: ${runUrl}`); // Validate reaction type const validReactions = [ @@ -92,22 +92,22 @@ async function main() { return; } - console.log("Reaction API endpoint:", reactionEndpoint); + core.info(`Reaction API endpoint: ${reactionEndpoint}`); // Add reaction first await addReaction(reactionEndpoint, reaction); // Then edit comment if applicable and if it's a comment event if (shouldEditComment && commentUpdateEndpoint) { - console.log("Comment update endpoint:", commentUpdateEndpoint); + core.info(`Comment update endpoint: ${commentUpdateEndpoint}`); await editCommentWithWorkflowLink(commentUpdateEndpoint, runUrl); } else { if (!command && commentUpdateEndpoint) { - console.log( + core.info( "Skipping comment edit - only available for command workflows" ); } else { - console.log("Skipping comment edit for event type:", eventName); + core.info(`Skipping comment edit for event type: ${eventName}`); } } } catch (error) { @@ -134,10 +134,10 @@ async function addReaction(endpoint, reaction) { const reactionId = response.data?.id; if (reactionId) { - console.log(`Successfully added reaction: ${reaction} (id: ${reactionId})`); + core.info(`Successfully added reaction: ${reaction} (id: ${reactionId})`); core.setOutput("reaction-id", reactionId.toString()); } else { - console.log(`Successfully added reaction: ${reaction}`); + core.info(`Successfully added reaction: ${reaction}`); core.setOutput("reaction-id", ""); } } @@ -161,9 +161,7 @@ async function editCommentWithWorkflowLink(endpoint, runUrl) { // Check if we've already added a workflow link to avoid duplicates if (originalBody.includes("*🤖 [Workflow run](")) { - console.log( - "Comment already contains a workflow run link, skipping edit" - ); + core.info("Comment already contains a workflow run link, skipping edit"); return; } @@ -177,14 +175,14 @@ async function editCommentWithWorkflowLink(endpoint, runUrl) { }, }); - console.log(`Successfully updated comment with workflow link`); - console.log(`Comment ID: ${updateResponse.data.id}`); + core.info(`Successfully updated comment with workflow link`); + core.info(`Comment ID: ${updateResponse.data.id}`); } catch (error) { // Don't fail the entire job if comment editing fails - just log it const errorMessage = error instanceof Error ? error.message : String(error); core.warning( - "Failed to edit comment with workflow link (This is not critical - the reaction was still added successfully):", - errorMessage + "Failed to edit comment with workflow link (This is not critical - the reaction was still added successfully): " + + errorMessage ); } } diff --git a/pkg/workflow/js/check_permissions.cjs b/pkg/workflow/js/check_permissions.cjs index e2ec20060bb..176271d4653 100644 --- a/pkg/workflow/js/check_permissions.cjs +++ b/pkg/workflow/js/check_permissions.cjs @@ -4,7 +4,7 @@ async function main() { // skip check for safe events const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); + core.info(`✅ Event ${eventName} does not require validation`); return; } @@ -19,18 +19,16 @@ async function main() { core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); + core.setFailed("Configuration error: Required permissions not specified"); return; } // Check if the actor has the required repository permissions try { - console.log( + core.debug( `Checking if user '${actor}' has required permissions for ${owner}/${repo}` ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); + core.debug(`Required permissions: ${requiredPermissions.join(", ")}`); const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ @@ -40,7 +38,7 @@ async function main() { }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); // Check if user has one of the required permission levels for (const requiredPerm of requiredPermissions) { @@ -48,19 +46,19 @@ async function main() { permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain") ) { - console.log(`✅ User has ${permission} access to repository`); + core.info(`✅ User has ${permission} access to repository`); return; } } - console.log( + core.warning( `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` ); } catch (repoError) { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + core.setFailed(`Repository permission check failed: ${errorMessage}`); return; } @@ -68,7 +66,7 @@ async function main() { core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + core.setFailed( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); } diff --git a/pkg/workflow/js/check_team_member.cjs b/pkg/workflow/js/check_team_member.cjs index 3a342bae627..ca8a1c43334 100644 --- a/pkg/workflow/js/check_team_member.cjs +++ b/pkg/workflow/js/check_team_member.cjs @@ -4,7 +4,7 @@ async function main() { // Check if the actor has repository access (admin, maintain permissions) try { - console.log( + core.info( `Checking if user '${actor}' is admin or maintainer of ${owner}/${repo}` ); @@ -16,10 +16,10 @@ async function main() { }); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.info(`Repository permission level: ${permission}`); if (permission === "admin" || permission === "maintain") { - console.log(`User has ${permission} access to repository`); + core.info(`User has ${permission} access to repository`); core.setOutput("is_team_member", "true"); return; } diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index c1210a2a4eb..0f0a1da0ecc 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -149,7 +149,7 @@ async function main() { /** * Gets the maximum allowed count for a given output type * @param {string} itemType - The output item type - * @param {Object} config - The safe-outputs configuration + * @param {any} config - The safe-outputs configuration * @returns {number} The maximum allowed count */ function getMaxAllowedForType(itemType, config) { @@ -202,6 +202,7 @@ async function main() { // U+0014 (DC4) — represented here as "\u0014" // Escape control characters not allowed in JSON strings (U+0000 through U+001F) // Preserve common JSON escapes for \b, \f, \n, \r, \t and use \uXXXX for the rest. + /** @type {Record} */ const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { const c = ch.charCodeAt(0); @@ -288,9 +289,17 @@ async function main() { return JSON.parse(repairedJson); } catch (repairError) { // If repair also fails, throw the error - console.log(`invalid input json: ${jsonStr}`); + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = + originalError instanceof Error + ? originalError.message + : String(originalError); + const repairMsg = + repairError instanceof Error + ? repairError.message + : String(repairError); throw new Error( - `JSON parsing failed. Original: ${originalError.message}. After attempted repair: ${repairError.message}` + `JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}` ); } } @@ -300,37 +309,38 @@ async function main() { const safeOutputsConfig = process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); return; } - console.log("Raw output content length:", outputContent.length); + core.info(`Raw output content length: ${outputContent.length}`); // Parse the safe-outputs configuration + /** @type {any} */ let expectedOutputTypes = {}; if (safeOutputsConfig) { try { expectedOutputTypes = JSON.parse(safeOutputsConfig); - console.log("Expected output types:", Object.keys(expectedOutputTypes)); - } catch (error) { - console.log( - "Warning: Could not parse safe-outputs config:", - error.message + core.info( + `Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}` ); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); } } @@ -343,6 +353,7 @@ async function main() { const line = lines[i].trim(); if (line === "") continue; // Skip empty lines try { + /** @type {any} */ const item = parseJsonWithRepair(line); // If item is undefined (failed to parse), add error and process next line @@ -398,8 +409,9 @@ async function main() { item.body = sanitizeContent(item.body); // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -437,8 +449,9 @@ async function main() { } // Sanitize labels if present if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => - typeof label === "string" ? sanitizeContent(label) : label + item.labels = item.labels.map( + /** @param {any} label */ label => + typeof label === "string" ? sanitizeContent(label) : label ); } break; @@ -450,14 +463,20 @@ async function main() { ); continue; } - if (item.labels.some(label => typeof label !== "string")) { + if ( + item.labels.some( + /** @param {any} label */ label => typeof label !== "string" + ) + ) { errors.push( `Line ${i + 1}: add-issue-label labels array must contain only strings` ); continue; } // Sanitize label strings - item.labels = item.labels.map(label => sanitizeContent(label)); + item.labels = item.labels.map( + /** @param {any} label */ label => sanitizeContent(label) + ); break; case "update-issue": @@ -775,10 +794,11 @@ async function main() { continue; } - console.log(`Line ${i + 1}: Valid ${itemType} item`); + core.info(`Line ${i + 1}: Valid ${itemType} item`); parsedItems.push(item); } catch (error) { - errors.push(`Line ${i + 1}: Invalid JSON - ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); } } @@ -796,7 +816,7 @@ async function main() { // In the future, we might want to fail the workflow for invalid items } - console.log(`Successfully parsed ${parsedItems.length} valid output items`); + core.info(`Successfully parsed ${parsedItems.length} valid output items`); // Set the parsed and validated items as output const validatedOutput = { @@ -812,12 +832,13 @@ async function main() { // Ensure the /tmp directory exists fs.mkdirSync("/tmp", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - console.log(`Stored validated output to: ${agentOutputFile}`); + core.info(`Stored validated output to: ${agentOutputFile}`); // Set the environment variable GITHUB_AW_AGENT_OUTPUT to the file path core.exportVariable("GITHUB_AW_AGENT_OUTPUT", agentOutputFile); } catch (error) { - core.error(`Failed to write agent output file: ${error.message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); } core.setOutput("output", JSON.stringify(validatedOutput)); diff --git a/pkg/workflow/js/compute_text.cjs b/pkg/workflow/js/compute_text.cjs index d0100a81e45..e9b3f4a0392 100644 --- a/pkg/workflow/js/compute_text.cjs +++ b/pkg/workflow/js/compute_text.cjs @@ -200,7 +200,7 @@ async function main() { ); const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); + core.debug(`Repository permission level: ${permission}`); if (permission !== "admin" && permission !== "maintain") { core.setOutput("text", ""); @@ -267,7 +267,7 @@ async function main() { const sanitizedText = sanitizeContent(text); // Display sanitized text in logs - console.log(`text: ${sanitizedText}`); + core.debug(`text: ${sanitizedText}`); // Set the sanitized text as output core.setOutput("text", sanitizedText); diff --git a/pkg/workflow/js/create_code_scanning_alert.cjs b/pkg/workflow/js/create_code_scanning_alert.cjs index 248f6f877c0..86460b25ac9 100644 --- a/pkg/workflow/js/create_code_scanning_alert.cjs +++ b/pkg/workflow/js/create_code_scanning_alert.cjs @@ -2,31 +2,30 @@ async function main() { // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } @@ -35,13 +34,11 @@ async function main() { /** @param {any} item */ item => item.type === "create-code-scanning-alert" ); if (securityItems.length === 0) { - console.log("No create-code-scanning-alert items found in agent output"); + core.info("No create-code-scanning-alert items found in agent output"); return; } - console.log( - `Found ${securityItems.length} create-code-scanning-alert item(s)` - ); + core.info(`Found ${securityItems.length} create-code-scanning-alert item(s)`); // If in staged mode, emit step summary instead of creating code scanning alerts if (process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true") { @@ -62,7 +59,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log( + core.info( "📝 Code scanning alert creation preview written to step summary" ); return; @@ -72,7 +69,7 @@ async function main() { const maxFindings = process.env.GITHUB_AW_SECURITY_REPORT_MAX ? parseInt(process.env.GITHUB_AW_SECURITY_REPORT_MAX) : 0; // 0 means unlimited - console.log( + core.info( `Max findings configuration: ${maxFindings === 0 ? "unlimited" : maxFindings}` ); @@ -80,34 +77,25 @@ async function main() { const driverName = process.env.GITHUB_AW_SECURITY_REPORT_DRIVER || "GitHub Agentic Workflows Security Scanner"; - console.log(`Driver name: ${driverName}`); + core.info(`Driver name: ${driverName}`); // Get the workflow filename for rule ID prefix const workflowFilename = process.env.GITHUB_AW_WORKFLOW_FILENAME || "workflow"; - console.log(`Workflow filename for rule ID prefix: ${workflowFilename}`); + core.info(`Workflow filename for rule ID prefix: ${workflowFilename}`); const validFindings = []; // Process each security item and validate the findings for (let i = 0; i < securityItems.length; i++) { const securityItem = securityItems[i]; - console.log( - `Processing create-code-scanning-alert item ${i + 1}/${securityItems.length}:`, - { - file: securityItem.file, - line: securityItem.line, - severity: securityItem.severity, - messageLength: securityItem.message - ? securityItem.message.length - : "undefined", - ruleIdSuffix: securityItem.ruleIdSuffix || "not specified", - } + core.info( + `Processing create-code-scanning-alert item ${i + 1}/${securityItems.length}: file=${securityItem.file}, line=${securityItem.line}, severity=${securityItem.severity}, messageLength=${securityItem.message ? securityItem.message.length : "undefined"}, ruleIdSuffix=${securityItem.ruleIdSuffix || "not specified"}` ); // Validate required fields if (!securityItem.file) { - console.log('Missing required field "file" in code scanning alert item'); + core.info('Missing required field "file" in code scanning alert item'); continue; } @@ -116,21 +104,21 @@ async function main() { (typeof securityItem.line !== "number" && typeof securityItem.line !== "string") ) { - console.log( + core.info( 'Missing or invalid required field "line" in code scanning alert item' ); continue; } if (!securityItem.severity || typeof securityItem.severity !== "string") { - console.log( + core.info( 'Missing or invalid required field "severity" in code scanning alert item' ); continue; } if (!securityItem.message || typeof securityItem.message !== "string") { - console.log( + core.info( 'Missing or invalid required field "message" in code scanning alert item' ); continue; @@ -139,7 +127,7 @@ async function main() { // Parse line number const line = parseInt(securityItem.line, 10); if (isNaN(line) || line <= 0) { - console.log(`Invalid line number: ${securityItem.line}`); + core.info(`Invalid line number: ${securityItem.line}`); continue; } @@ -150,14 +138,14 @@ async function main() { typeof securityItem.column !== "number" && typeof securityItem.column !== "string" ) { - console.log( + core.info( 'Invalid field "column" in code scanning alert item (must be number or string)' ); continue; } const parsedColumn = parseInt(securityItem.column, 10); if (isNaN(parsedColumn) || parsedColumn <= 0) { - console.log(`Invalid column number: ${securityItem.column}`); + core.info(`Invalid column number: ${securityItem.column}`); continue; } column = parsedColumn; @@ -167,7 +155,7 @@ async function main() { let ruleIdSuffix = null; if (securityItem.ruleIdSuffix !== undefined) { if (typeof securityItem.ruleIdSuffix !== "string") { - console.log( + core.info( 'Invalid field "ruleIdSuffix" in code scanning alert item (must be string)' ); continue; @@ -175,14 +163,14 @@ async function main() { // Validate that the suffix doesn't contain invalid characters const trimmedSuffix = securityItem.ruleIdSuffix.trim(); if (trimmedSuffix.length === 0) { - console.log( + core.info( 'Invalid field "ruleIdSuffix" in code scanning alert item (cannot be empty)' ); continue; } // Check for characters that would be problematic in rule IDs if (!/^[a-zA-Z0-9_-]+$/.test(trimmedSuffix)) { - console.log( + core.info( `Invalid ruleIdSuffix "${trimmedSuffix}" (must contain only alphanumeric characters, hyphens, and underscores)` ); continue; @@ -191,6 +179,7 @@ async function main() { } // Validate severity level and map to SARIF level + /** @type {Record} */ const severityMap = { error: "error", warning: "warning", @@ -200,7 +189,7 @@ async function main() { const normalizedSeverity = securityItem.severity.toLowerCase(); if (!severityMap[normalizedSeverity]) { - console.log( + core.info( `Invalid severity level: ${securityItem.severity} (must be error, warning, info, or note)` ); continue; @@ -221,17 +210,17 @@ async function main() { // Check if we've reached the max limit if (maxFindings > 0 && validFindings.length >= maxFindings) { - console.log(`Reached maximum findings limit: ${maxFindings}`); + core.info(`Reached maximum findings limit: ${maxFindings}`); break; } } if (validFindings.length === 0) { - console.log("No valid security findings to report"); + core.info("No valid security findings to report"); return; } - console.log(`Processing ${validFindings.length} valid security finding(s)`); + core.info(`Processing ${validFindings.length} valid security finding(s)`); // Generate SARIF file const sarifContent = { @@ -277,8 +266,8 @@ async function main() { try { fs.writeFileSync(sarifFilePath, JSON.stringify(sarifContent, null, 2)); - console.log(`✓ Created SARIF file: ${sarifFilePath}`); - console.log(`SARIF file size: ${fs.statSync(sarifFilePath).size} bytes`); + core.info(`✓ Created SARIF file: ${sarifFilePath}`); + core.info(`SARIF file size: ${fs.statSync(sarifFilePath).size} bytes`); // Set outputs for the GitHub Action core.setOutput("sarif_file", sarifFilePath); @@ -311,7 +300,7 @@ async function main() { throw error; } - console.log( + core.info( `Successfully created code scanning alert with ${validFindings.length} finding(s)` ); return { diff --git a/pkg/workflow/js/create_comment.cjs b/pkg/workflow/js/create_comment.cjs index 7ed0fe27663..67693c46a9a 100644 --- a/pkg/workflow/js/create_comment.cjs +++ b/pkg/workflow/js/create_comment.cjs @@ -5,31 +5,30 @@ async function main() { // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } @@ -38,11 +37,11 @@ async function main() { /** @param {any} item */ item => item.type === "add-issue-comment" ); if (commentItems.length === 0) { - console.log("No add-issue-comment items found in agent output"); + core.info("No add-issue-comment items found in agent output"); return; } - console.log(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-issue-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { @@ -64,13 +63,13 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Comment creation preview written to step summary"); + core.info("📝 Comment creation preview written to step summary"); return; } // Get the target configuration from environment variable const commentTarget = process.env.GITHUB_AW_COMMENT_TARGET || "triggering"; - console.log(`Comment target configuration: ${commentTarget}`); + core.info(`Comment target configuration: ${commentTarget}`); // Check if we're in an issue or pull request context const isIssueContext = @@ -82,7 +81,7 @@ async function main() { // Validate context based on target configuration if (commentTarget === "triggering" && !isIssueContext && !isPRContext) { - console.log( + core.info( 'Target is "triggering" but not running in issue or pull request context, skipping comment creation' ); return; @@ -93,9 +92,8 @@ async function main() { // Process each comment item for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; - console.log( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}:`, - { bodyLength: commentItem.body.length } + core.info( + `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment @@ -107,14 +105,14 @@ async function main() { if (commentItem.issue_number) { issueNumber = parseInt(commentItem.issue_number, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number specified: ${commentItem.issue_number}` ); continue; } commentEndpoint = "issues"; } else { - console.log( + core.info( 'Target is "*" but no issue_number specified in comment item' ); continue; @@ -123,7 +121,7 @@ async function main() { // Explicit issue number specified in target issueNumber = parseInt(commentTarget, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number in target configuration: ${commentTarget}` ); continue; @@ -136,7 +134,7 @@ async function main() { issueNumber = context.payload.issue.number; commentEndpoint = "issues"; } else { - console.log("Issue context detected but no issue found in payload"); + core.info("Issue context detected but no issue found in payload"); continue; } } else if (isPRContext) { @@ -144,7 +142,7 @@ async function main() { issueNumber = context.payload.pull_request.number; commentEndpoint = "issues"; // PR comments use the issues API endpoint } else { - console.log( + core.info( "Pull request context detected but no pull request found in payload" ); continue; @@ -153,7 +151,7 @@ async function main() { } if (!issueNumber) { - console.log("Could not determine issue or pull request number"); + core.info("Could not determine issue or pull request number"); continue; } @@ -166,8 +164,8 @@ async function main() { : `https://github.com/actions/runs/${runId}`; body += `\n\n> Generated by Agentic Workflow [Run](${runUrl})\n`; - console.log(`Creating comment on ${commentEndpoint} #${issueNumber}`); - console.log("Comment content length:", body.length); + core.info(`Creating comment on ${commentEndpoint} #${issueNumber}`); + core.info(`Comment content length: ${body.length}`); try { // Create the comment using GitHub API @@ -178,7 +176,7 @@ async function main() { body: body, }); - console.log("Created comment #" + comment.id + ": " + comment.html_url); + core.info("Created comment #" + comment.id + ": " + comment.html_url); createdComments.push(comment); // Set output for the last created comment (for backward compatibility) @@ -203,7 +201,7 @@ async function main() { await core.summary.addRaw(summaryContent).write(); } - console.log(`Successfully created ${createdComments.length} comment(s)`); + core.info(`Successfully created ${createdComments.length} comment(s)`); return createdComments; } await main(); diff --git a/pkg/workflow/js/create_discussion.cjs b/pkg/workflow/js/create_discussion.cjs index 673561a2940..a4feaf4a386 100644 --- a/pkg/workflow/js/create_discussion.cjs +++ b/pkg/workflow/js/create_discussion.cjs @@ -2,30 +2,29 @@ async function main() { // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.debug(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.warning("No valid items found in agent output"); return; } @@ -34,13 +33,11 @@ async function main() { /** @param {any} item */ item => item.type === "create-discussion" ); if (createDiscussionItems.length === 0) { - console.log("No create-discussion items found in agent output"); + core.warning("No create-discussion items found in agent output"); return; } - console.log( - `Found ${createDiscussionItems.length} create-discussion item(s)` - ); + core.debug(`Found ${createDiscussionItems.length} create-discussion item(s)`); // If in staged mode, emit step summary instead of creating discussions if (process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true") { @@ -63,7 +60,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Discussion creation preview written to step summary"); + core.info("📝 Discussion creation preview written to step summary"); return; } @@ -95,9 +92,12 @@ async function main() { repositoryId = queryResult.repository.id; discussionCategories = queryResult.repository.discussionCategories.nodes || []; - console.log( - "Available categories:", - discussionCategories.map(cat => ({ name: cat.name, id: cat.id })) + core.info( + `Available categories: ${JSON.stringify( + discussionCategories.map( + /** @param {any} cat */ cat => ({ name: cat.name, id: cat.id }) + ) + )}` ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -108,10 +108,10 @@ async function main() { errorMessage.includes("not found") || errorMessage.includes("Could not resolve to a Repository") ) { - console.log( + core.info( "⚠ Cannot create discussions: Discussions are not enabled for this repository" ); - console.log( + core.info( "Consider enabling discussions in repository settings if you want to create discussions automatically" ); return; // Exit gracefully without creating discussions @@ -126,7 +126,7 @@ async function main() { if (!categoryId && discussionCategories.length > 0) { // Default to the first category if none specified categoryId = discussionCategories[0].id; - console.log( + core.info( `No category-id specified, using default category: ${discussionCategories[0].name} (${categoryId})` ); } @@ -147,12 +147,8 @@ async function main() { // Process each create-discussion item for (let i = 0; i < createDiscussionItems.length; i++) { const createDiscussionItem = createDiscussionItems[i]; - console.log( - `Processing create-discussion item ${i + 1}/${createDiscussionItems.length}:`, - { - title: createDiscussionItem.title, - bodyLength: createDiscussionItem.body.length, - } + core.info( + `Processing create-discussion item ${i + 1}/${createDiscussionItems.length}: title=${createDiscussionItem.title}, bodyLength=${createDiscussionItem.body.length}` ); // Extract title and body from the JSON item @@ -187,9 +183,9 @@ async function main() { // Prepare the body content const body = bodyLines.join("\n").trim(); - console.log("Creating discussion with title:", title); - console.log("Category ID:", categoryId); - console.log("Body length:", body.length); + core.info(`Creating discussion with title: ${title}`); + core.info(`Category ID: ${categoryId}`); + core.info(`Body length: ${body.length}`); try { // Create the discussion using GraphQL API with parameterized mutation @@ -220,7 +216,7 @@ async function main() { const discussion = mutationResult.createDiscussion.discussion; - console.log( + core.info( "Created discussion #" + discussion.number + ": " + discussion.url ); createdDiscussions.push(discussion); @@ -247,8 +243,6 @@ async function main() { await core.summary.addRaw(summaryContent).write(); } - console.log( - `Successfully created ${createdDiscussions.length} discussion(s)` - ); + core.info(`Successfully created ${createdDiscussions.length} discussion(s)`); } await main(); diff --git a/pkg/workflow/js/create_issue.cjs b/pkg/workflow/js/create_issue.cjs index fa4deef9ec5..7399b26aef3 100644 --- a/pkg/workflow/js/create_issue.cjs +++ b/pkg/workflow/js/create_issue.cjs @@ -5,30 +5,29 @@ async function main() { // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } @@ -37,11 +36,11 @@ async function main() { /** @param {any} item */ item => item.type === "create-issue" ); if (createIssueItems.length === 0) { - console.log("No create-issue items found in agent output"); + core.info("No create-issue items found in agent output"); return; } - console.log(`Found ${createIssueItems.length} create-issue item(s)`); + core.info(`Found ${createIssueItems.length} create-issue item(s)`); // If in staged mode, emit step summary instead of creating issues if (isStaged) { @@ -64,7 +63,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Issue creation preview written to step summary"); + core.info("📝 Issue creation preview written to step summary"); return; } @@ -85,9 +84,8 @@ async function main() { // Process each create-issue item for (let i = 0; i < createIssueItems.length; i++) { const createIssueItem = createIssueItems[i]; - console.log( - `Processing create-issue item ${i + 1}/${createIssueItems.length}:`, - { title: createIssueItem.title, bodyLength: createIssueItem.body.length } + core.info( + `Processing create-issue item ${i + 1}/${createIssueItems.length}: title=${createIssueItem.title}, bodyLength=${createIssueItem.body.length}` ); // Merge environment labels with item-specific labels @@ -112,7 +110,7 @@ async function main() { } if (parentIssueNumber) { - console.log("Detected issue context, parent issue #" + parentIssueNumber); + core.info("Detected issue context, parent issue #" + parentIssueNumber); // Add reference to parent issue in the child issue body bodyLines.push(`Related to #${parentIssueNumber}`); @@ -134,9 +132,9 @@ async function main() { // Prepare the body content const body = bodyLines.join("\n").trim(); - console.log("Creating issue with title:", title); - console.log("Labels:", labels); - console.log("Body length:", body.length); + core.info(`Creating issue with title: ${title}`); + core.info(`Labels: ${labels}`); + core.info(`Body length: ${body.length}`); try { // Create the issue using GitHub API @@ -148,7 +146,7 @@ async function main() { labels: labels, }); - console.log("Created issue #" + issue.number + ": " + issue.html_url); + core.info("Created issue #" + issue.number + ": " + issue.html_url); createdIssues.push(issue); // If we have a parent issue, add a comment to it referencing the new child issue @@ -160,11 +158,10 @@ async function main() { issue_number: parentIssueNumber, body: `Created related issue: #${issue.number}`, }); - console.log("Added comment to parent issue #" + parentIssueNumber); + core.info("Added comment to parent issue #" + parentIssueNumber); } catch (error) { - console.log( - "Warning: Could not add comment to parent issue:", - error instanceof Error ? error.message : String(error) + core.info( + `Warning: Could not add comment to parent issue: ${error instanceof Error ? error.message : String(error)}` ); } } @@ -182,10 +179,10 @@ async function main() { if ( errorMessage.includes("Issues has been disabled in this repository") ) { - console.log( + core.info( `⚠ Cannot create issue "${title}": Issues are disabled for this repository` ); - console.log( + core.info( "Consider enabling issues in repository settings if you want to create issues automatically" ); continue; // Skip this issue but continue processing others @@ -205,6 +202,6 @@ async function main() { await core.summary.addRaw(summaryContent).write(); } - console.log(`Successfully created ${createdIssues.length} issue(s)`); + core.info(`Successfully created ${createdIssues.length} issue(s)`); } await main(); diff --git a/pkg/workflow/js/create_pr_review_comment.cjs b/pkg/workflow/js/create_pr_review_comment.cjs index 2fb02685299..e743a50baaf 100644 --- a/pkg/workflow/js/create_pr_review_comment.cjs +++ b/pkg/workflow/js/create_pr_review_comment.cjs @@ -2,31 +2,30 @@ async function main() { // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } @@ -36,13 +35,13 @@ async function main() { item.type === "create-pull-request-review-comment" ); if (reviewCommentItems.length === 0) { - console.log( + core.info( "No create-pull-request-review-comment items found in agent output" ); return; } - console.log( + core.info( `Found ${reviewCommentItems.length} create-pull-request-review-comment item(s)` ); @@ -68,15 +67,13 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log( - "📝 PR review comment creation preview written to step summary" - ); + core.info("📝 PR review comment creation preview written to step summary"); return; } // Get the side configuration from environment variable const defaultSide = process.env.GITHUB_AW_PR_REVIEW_COMMENT_SIDE || "RIGHT"; - console.log(`Default comment side configuration: ${defaultSide}`); + core.info(`Default comment side configuration: ${defaultSide}`); // Check if we're in a pull request context, or an issue comment context on a PR const isPRContext = @@ -88,7 +85,7 @@ async function main() { context.payload.issue.pull_request); if (!isPRContext) { - console.log( + core.info( "Not running in pull request context, skipping review comment creation" ); return; @@ -99,7 +96,7 @@ async function main() { //No, github.event.issue.pull_request does not contain the full pull request data like head.sha. It only includes a minimal object with a url pointing to the pull request API resource. //To get full PR details (like head.sha, base.ref, etc.), you need to make an API call using that URL. - if (context.payload.issue.pull_request) { + if (context.payload.issue && context.payload.issue.pull_request) { // Fetch full pull request details using the GitHub API const prUrl = context.payload.issue.pull_request.url; try { @@ -109,16 +106,15 @@ async function main() { }, }); pullRequest = fullPR; - console.log("Fetched full pull request details from API"); + core.info("Fetched full pull request details from API"); } catch (error) { - console.log( - "Failed to fetch full pull request details:", - error instanceof Error ? error.message : String(error) + core.info( + `Failed to fetch full pull request details: ${error instanceof Error ? error.message : String(error)}` ); return; } } else { - console.log( + core.info( "Pull request data not found in payload - cannot create review comments" ); return; @@ -126,34 +122,28 @@ async function main() { } // Check if we have the commit SHA needed for creating review comments - if (!pullRequest.head || !pullRequest.head.sha) { - console.log( + if (!pullRequest || !pullRequest.head || !pullRequest.head.sha) { + core.info( "Pull request head commit SHA not found in payload - cannot create review comments" ); return; } const pullRequestNumber = pullRequest.number; - console.log(`Creating review comments on PR #${pullRequestNumber}`); + core.info(`Creating review comments on PR #${pullRequestNumber}`); const createdComments = []; // Process each review comment item for (let i = 0; i < reviewCommentItems.length; i++) { const commentItem = reviewCommentItems[i]; - console.log( - `Processing create-pull-request-review-comment item ${i + 1}/${reviewCommentItems.length}:`, - { - bodyLength: commentItem.body ? commentItem.body.length : "undefined", - path: commentItem.path, - line: commentItem.line, - startLine: commentItem.start_line, - } + core.info( + `Processing create-pull-request-review-comment item ${i + 1}/${reviewCommentItems.length}: bodyLength=${commentItem.body ? commentItem.body.length : "undefined"}, path=${commentItem.path}, line=${commentItem.line}, startLine=${commentItem.start_line}` ); // Validate required fields if (!commentItem.path) { - console.log('Missing required field "path" in review comment item'); + core.info('Missing required field "path" in review comment item'); continue; } @@ -162,14 +152,14 @@ async function main() { (typeof commentItem.line !== "number" && typeof commentItem.line !== "string") ) { - console.log( + core.info( 'Missing or invalid required field "line" in review comment item' ); continue; } if (!commentItem.body || typeof commentItem.body !== "string") { - console.log( + core.info( 'Missing or invalid required field "body" in review comment item' ); continue; @@ -178,7 +168,7 @@ async function main() { // Parse line numbers const line = parseInt(commentItem.line, 10); if (isNaN(line) || line <= 0) { - console.log(`Invalid line number: ${commentItem.line}`); + core.info(`Invalid line number: ${commentItem.line}`); continue; } @@ -186,7 +176,7 @@ async function main() { if (commentItem.start_line) { startLine = parseInt(commentItem.start_line, 10); if (isNaN(startLine) || startLine <= 0 || startLine > line) { - console.log( + core.info( `Invalid start_line number: ${commentItem.start_line} (must be <= line: ${line})` ); continue; @@ -196,7 +186,7 @@ async function main() { // Determine side (LEFT or RIGHT) const side = commentItem.side || defaultSide; if (side !== "LEFT" && side !== "RIGHT") { - console.log(`Invalid side value: ${side} (must be LEFT or RIGHT)`); + core.info(`Invalid side value: ${side} (must be LEFT or RIGHT)`); continue; } @@ -210,20 +200,21 @@ async function main() { : `https://github.com/actions/runs/${runId}`; body += `\n\n> Generated by Agentic Workflow [Run](${runUrl})\n`; - console.log( + core.info( `Creating review comment on PR #${pullRequestNumber} at ${commentItem.path}:${line}${startLine ? ` (lines ${startLine}-${line})` : ""} [${side}]` ); - console.log("Comment content length:", body.length); + core.info(`Comment content length: ${body.length}`); try { // Prepare the request parameters + /** @type {any} */ const requestParams = { owner: context.repo.owner, repo: context.repo.repo, pull_number: pullRequestNumber, body: body, path: commentItem.path, - commit_id: pullRequest.head.sha, // Required for creating review comments + commit_id: pullRequest && pullRequest.head ? pullRequest.head.sha : "", // Required for creating review comments line: line, side: side, }; @@ -238,7 +229,7 @@ async function main() { const { data: comment } = await github.rest.pulls.createReviewComment(requestParams); - console.log( + core.info( "Created review comment #" + comment.id + ": " + comment.html_url ); createdComments.push(comment); @@ -265,9 +256,7 @@ async function main() { await core.summary.addRaw(summaryContent).write(); } - console.log( - `Successfully created ${createdComments.length} review comment(s)` - ); + core.info(`Successfully created ${createdComments.length} review comment(s)`); return createdComments; } await main(); diff --git a/pkg/workflow/js/create_pull_request.cjs b/pkg/workflow/js/create_pull_request.cjs index 591bb9623d0..2e97b2deede 100644 --- a/pkg/workflow/js/create_pull_request.cjs +++ b/pkg/workflow/js/create_pull_request.cjs @@ -21,7 +21,7 @@ async function main() { const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT || ""; if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); } const ifNoChanges = process.env.GITHUB_AW_PR_IF_NO_CHANGES || "warn"; @@ -41,7 +41,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log( + core.info( "📝 Pull request creation preview written to step summary (no patch file)" ); return; @@ -55,7 +55,7 @@ async function main() { return; case "warn": default: - console.log(message); + core.warning(message); return; } } @@ -77,7 +77,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log( + core.info( "📝 Pull request creation preview written to step summary (patch error)" ); return; @@ -91,7 +91,7 @@ async function main() { return; case "warn": default: - console.log(message); + core.warning(message); return; } } @@ -112,16 +112,16 @@ async function main() { return; case "warn": default: - console.log(message); + core.warning(message); return; } } - console.log("Agent output content length:", outputContent.length); + core.debug(`Agent output content length: ${outputContent.length}`); if (!isEmpty) { - console.log("Patch content validation passed"); + core.info("Patch content validation passed"); } else { - console.log("Patch file is empty - processing noop operation"); + core.info("Patch file is empty - processing noop operation"); } // Parse the validated output JSON @@ -129,15 +129,14 @@ async function main() { try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.warning("No valid items found in agent output"); return; } @@ -146,14 +145,13 @@ async function main() { /** @param {any} item */ item => item.type === "create-pull-request" ); if (!pullRequestItem) { - console.log("No create-pull-request item found in agent output"); + core.warning("No create-pull-request item found in agent output"); return; } - console.log("Found create-pull-request item:", { - title: pullRequestItem.title, - bodyLength: pullRequestItem.body.length, - }); + core.debug( + `Found create-pull-request item: title="${pullRequestItem.title}", bodyLength=${pullRequestItem.body.length}` + ); // If in staged mode, emit step summary instead of creating PR if (isStaged) { @@ -181,7 +179,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Pull request creation preview written to step summary"); + core.info("📝 Pull request creation preview written to step summary"); return; } @@ -231,57 +229,55 @@ async function main() { const draftEnv = process.env.GITHUB_AW_PR_DRAFT; const draft = draftEnv ? draftEnv.toLowerCase() === "true" : true; - console.log("Creating pull request with title:", title); - console.log("Labels:", labels); - console.log("Draft:", draft); - console.log("Body length:", body.length); + core.info(`Creating pull request with title: ${title}`); + core.debug(`Labels: ${JSON.stringify(labels)}`); + core.debug(`Draft: ${draft}`); + core.debug(`Body length: ${body.length}`); const randomHex = crypto.randomBytes(8).toString("hex"); // Use branch name from JSONL if provided, otherwise generate unique branch name if (!branchName) { - console.log( + core.debug( "No branch name provided in JSONL, generating unique branch name" ); // Generate unique branch name using cryptographic random hex branchName = `${workflowId}-${randomHex}`; } else { branchName = `${branchName}-${randomHex}`; - console.log("Using branch name from JSONL with added salt:", branchName); + core.debug(`Using branch name from JSONL with added salt: ${branchName}`); } - console.log("Generated branch name:", branchName); - console.log("Base branch:", baseBranch); + core.info(`Generated branch name: ${branchName}`); + core.debug(`Base branch: ${baseBranch}`); // Create a new branch using git CLI, ensuring it's based on the correct base branch // First, fetch latest changes and checkout the base branch - console.log( - "Fetching latest changes and checking out base branch:", - baseBranch + core.debug( + `Fetching latest changes and checking out base branch: ${baseBranch}` ); execSync("git fetch origin", { stdio: "inherit" }); execSync(`git checkout ${baseBranch}`, { stdio: "inherit" }); // Handle branch creation/checkout - console.log( - "Branch should not exist locally, creating new branch from base:", - branchName + core.debug( + `Branch should not exist locally, creating new branch from base: ${branchName}` ); execSync(`git checkout -b ${branchName}`, { stdio: "inherit" }); - console.log("Created new branch from base:", branchName); + core.info(`Created new branch from base: ${branchName}`); // Apply the patch using git CLI (skip if empty) if (!isEmpty) { - console.log("Applying patch..."); + core.info("Applying patch..."); // Patches are created with git format-patch, so use git am to apply them execSync("git am /tmp/aw.patch", { stdio: "inherit" }); - console.log("Patch applied successfully"); + core.info("Patch applied successfully"); // Push the applied commits to the branch execSync(`git push origin ${branchName}`, { stdio: "inherit" }); - console.log("Changes pushed to branch"); + core.info("Changes pushed to branch"); } else { - console.log("Skipping patch application (empty patch)"); + core.info("Skipping patch application (empty patch)"); // For empty patches, handle if-no-changes configuration const message = @@ -297,7 +293,7 @@ async function main() { return; case "warn": default: - console.log(message); + core.warning(message); return; } } @@ -313,8 +309,8 @@ async function main() { draft: draft, }); - console.log( - "Created pull request #" + pullRequest.number + ": " + pullRequest.html_url + core.info( + `Created pull request #${pullRequest.number}: ${pullRequest.html_url}` ); // Add labels if specified @@ -325,7 +321,7 @@ async function main() { issue_number: pullRequest.number, labels: labels, }); - console.log("Added labels to pull request:", labels); + core.info(`Added labels to pull request: ${JSON.stringify(labels)}`); } // Set output for other jobs to use diff --git a/pkg/workflow/js/missing_tool.cjs b/pkg/workflow/js/missing_tool.cjs index 144e1db6ed2..4cb48a1ba51 100644 --- a/pkg/workflow/js/missing_tool.cjs +++ b/pkg/workflow/js/missing_tool.cjs @@ -13,6 +13,7 @@ async function main() { core.info(`Maximum reports allowed: ${maxReports}`); } + /** @type {any[]} */ const missingTools = []; // Return early if no agent output @@ -28,7 +29,7 @@ async function main() { try { validatedOutput = JSON.parse(agentOutput); } catch (error) { - core.error( + core.setFailed( `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; diff --git a/pkg/workflow/js/parse_claude_log.cjs b/pkg/workflow/js/parse_claude_log.cjs index 3288b3a7911..b04e3e60885 100644 --- a/pkg/workflow/js/parse_claude_log.cjs +++ b/pkg/workflow/js/parse_claude_log.cjs @@ -5,12 +5,12 @@ function main() { // Get the log file path from environment const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } @@ -20,11 +20,16 @@ function main() { // Append to GitHub step summary core.summary.addRaw(markdown).write(); } catch (error) { - core.error(`Error parsing Claude log: ${error.message}`); - core.setFailed(error.message); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(errorMessage); } } +/** + * Parses Claude log content and converts it to markdown format + * @param {string} logContent - The raw log content as a string + * @returns {string} Formatted markdown content + */ function parseClaudeLog(logContent) { try { const logEntries = JSON.parse(logContent); @@ -180,10 +185,17 @@ function parseClaudeLog(logContent) { return markdown; } catch (error) { - return `## Agent Log Summary\n\nError parsing Claude log: ${error.message}\n`; + const errorMessage = error instanceof Error ? error.message : String(error); + return `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`; } } +/** + * Formats a tool use entry with its result into markdown + * @param {any} toolUse - The tool use object containing name, input, etc. + * @param {any} toolResult - The corresponding tool result object + * @returns {string} Formatted markdown string + */ function formatToolUse(toolUse, toolResult) { const toolName = toolUse.name; const input = toolUse.input || {}; @@ -285,6 +297,11 @@ function formatToolUse(toolUse, toolResult) { return markdown; } +/** + * Formats MCP tool name from internal format to display format + * @param {string} toolName - The raw tool name (e.g., mcp__github__search_issues) + * @returns {string} Formatted tool name (e.g., github::search_issues) + */ function formatMcpName(toolName) { // Convert mcp__github__search_issues to github::search_issues if (toolName.startsWith("mcp__")) { @@ -298,6 +315,11 @@ function formatMcpName(toolName) { return toolName; } +/** + * Formats MCP parameters into a human-readable string + * @param {Record} input - The input object containing parameters + * @returns {string} Formatted parameters string + */ function formatMcpParameters(input) { const keys = Object.keys(input); if (keys.length === 0) return ""; @@ -316,6 +338,11 @@ function formatMcpParameters(input) { return paramStrs.join(", "); } +/** + * Formats a bash command by normalizing whitespace and escaping + * @param {string} command - The raw bash command string + * @returns {string} Formatted and escaped command string + */ function formatBashCommand(command) { if (!command) return ""; @@ -340,6 +367,12 @@ function formatBashCommand(command) { return formatted; } +/** + * Truncates a string to a maximum length with ellipsis + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum allowed length + * @returns {string} Truncated string with ellipsis if needed + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/workflow/js/parse_codex_log.cjs b/pkg/workflow/js/parse_codex_log.cjs index 8db41e27968..4f97a15e51f 100644 --- a/pkg/workflow/js/parse_codex_log.cjs +++ b/pkg/workflow/js/parse_codex_log.cjs @@ -4,12 +4,12 @@ function main() { try { const logFile = process.env.GITHUB_AW_AGENT_OUTPUT; if (!logFile) { - console.log("No agent log file specified"); + core.info("No agent log file specified"); return; } if (!fs.existsSync(logFile)) { - console.log(`Log file not found: ${logFile}`); + core.info(`Log file not found: ${logFile}`); return; } @@ -18,15 +18,20 @@ function main() { if (parsedLog) { core.summary.addRaw(parsedLog).write(); - console.log("Codex log parsed successfully"); + core.info("Codex log parsed successfully"); } else { core.error("Failed to parse Codex log"); } } catch (error) { - core.setFailed(error.message); + core.setFailed(error instanceof Error ? error : String(error)); } } +/** + * Parse codex log content and format as markdown + * @param {string} logContent - The raw log content to parse + * @returns {string} Formatted markdown content + */ function parseCodexLog(logContent) { try { const lines = logContent.split("\n"); @@ -118,8 +123,11 @@ function parseCodexLog(logContent) { const tokenMatches = logContent.match(/tokens used: (\d+)/g); if (tokenMatches) { for (const match of tokenMatches) { - const tokens = parseInt(match.match(/(\d+)/)[1]); - totalTokens += tokens; + const numberMatch = match.match(/(\d+)/); + if (numberMatch) { + const tokens = parseInt(numberMatch[1]); + totalTokens += tokens; + } } } @@ -252,6 +260,11 @@ function parseCodexLog(logContent) { } } +/** + * Format bash command for display + * @param {string} command - The command to format + * @returns {string} Formatted command string + */ function formatBashCommand(command) { if (!command) return ""; @@ -276,6 +289,12 @@ function formatBashCommand(command) { return formatted; } +/** + * Truncate string to maximum length + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum length allowed + * @returns {string} Truncated string + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/workflow/js/push_to_pr_branch.cjs b/pkg/workflow/js/push_to_pr_branch.cjs index e6aed1cc9c4..13357efe0d4 100644 --- a/pkg/workflow/js/push_to_pr_branch.cjs +++ b/pkg/workflow/js/push_to_pr_branch.cjs @@ -6,7 +6,7 @@ async function main() { // Environment validation - fail early if required variables are missing const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT || ""; if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } @@ -26,7 +26,7 @@ async function main() { return; case "warn": default: - console.log(message); + core.info(message); return; } } @@ -47,7 +47,7 @@ async function main() { return; case "warn": default: - console.log(message); + core.info(message); return; } } @@ -69,31 +69,30 @@ async function main() { break; case "warn": default: - console.log(message); + core.info(message); break; } } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); if (!isEmpty) { - console.log("Patch content validation passed"); + core.info("Patch content validation passed"); } - console.log("Target configuration:", target); + core.info(`Target configuration: ${target}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } @@ -102,11 +101,11 @@ async function main() { /** @param {any} item */ item => item.type === "push-to-pr-branch" ); if (!pushItem) { - console.log("No push-to-pr-branch item found in agent output"); + core.info("No push-to-pr-branch item found in agent output"); return; } - console.log("Found push-to-pr-branch item"); + core.info("Found push-to-pr-branch item"); // If in staged mode, emit step summary instead of pushing changes if (process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true") { @@ -132,7 +131,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Push to PR branch preview written to step summary"); + core.info("📝 Push to PR branch preview written to step summary"); return; } @@ -184,21 +183,21 @@ async function main() { throw new Error("No head branch found for PR"); } } catch (error) { - console.log( - `Warning: Could not fetch PR ${pullNumber} details: ${error.message}` + core.info( + `Warning: Could not fetch PR ${pullNumber} details: ${error instanceof Error ? error.message : String(error)}` ); // Exit with failure if we cannot determine the branch name core.setFailed(`Failed to determine branch name for PR ${pullNumber}`); return; } - console.log("Target branch:", branchName); + core.info(`Target branch: ${branchName}`); // Check if patch has actual changes (not just empty) const hasChanges = !isEmpty; // Switch to or create the target branch - console.log("Switching to branch:", branchName); + core.info(`Switching to branch: ${branchName}`); try { // Try to checkout existing branch first execSync("git fetch origin", { stdio: "inherit" }); @@ -212,19 +211,18 @@ async function main() { execSync(`git checkout -B ${branchName} origin/${branchName}`, { stdio: "inherit", }); - console.log("Checked out existing branch from origin:", branchName); + core.info(`Checked out existing branch from origin: ${branchName}`); } catch (originError) { // Branch doesn't exist on origin, check if it exists locally try { execSync(`git rev-parse --verify ${branchName}`, { stdio: "pipe" }); // Branch exists locally, check it out execSync(`git checkout ${branchName}`, { stdio: "inherit" }); - console.log("Checked out existing local branch:", branchName); + core.info(`Checked out existing local branch: ${branchName}`); } catch (localError) { // Branch doesn't exist locally or on origin, create it from default branch - console.log( - "Branch does not exist, creating new branch from default branch:", - branchName + core.info( + `Branch does not exist, creating new branch from default branch: ${branchName}` ); // Get the default branch name @@ -232,7 +230,7 @@ async function main() { "git remote show origin | grep 'HEAD branch' | cut -d' ' -f5", { encoding: "utf8" } ).trim(); - console.log("Default branch:", defaultBranch); + core.info(`Default branch: ${defaultBranch}`); // Ensure we have the latest default branch execSync(`git checkout ${defaultBranch}`, { stdio: "inherit" }); @@ -240,7 +238,7 @@ async function main() { // Create new branch from default branch execSync(`git checkout -b ${branchName}`, { stdio: "inherit" }); - console.log("Created new branch from default branch:", branchName); + core.info(`Created new branch from default branch: ${branchName}`); } } } catch (error) { @@ -252,15 +250,15 @@ async function main() { // Apply the patch using git CLI (skip if empty) if (!isEmpty) { - console.log("Applying patch..."); + core.info("Applying patch..."); try { // Patches are created with git format-patch, so use git am to apply them execSync("git am /tmp/aw.patch", { stdio: "inherit" }); - console.log("Patch applied successfully"); + core.info("Patch applied successfully"); // Push the applied commits to the branch execSync(`git push origin ${branchName}`, { stdio: "inherit" }); - console.log("Changes committed and pushed to branch:", branchName); + core.info(`Changes committed and pushed to branch: ${branchName}`); } catch (error) { core.error( `Failed to apply patch: ${error instanceof Error ? error.message : String(error)}` @@ -269,7 +267,7 @@ async function main() { return; } } else { - console.log("Skipping patch application (empty patch)"); + core.info("Skipping patch application (empty patch)"); // Handle if-no-changes configuration for empty patches const message = @@ -286,7 +284,7 @@ async function main() { break; case "warn": default: - console.log(message); + core.info(message); break; } } diff --git a/pkg/workflow/js/sanitize_output.cjs b/pkg/workflow/js/sanitize_output.cjs index 7d4166d3b11..5e65c4d92ba 100644 --- a/pkg/workflow/js/sanitize_output.cjs +++ b/pkg/workflow/js/sanitize_output.cjs @@ -188,27 +188,25 @@ async function main() { const fs = require("fs"); const outputFile = process.env.GITHUB_AW_SAFE_OUTPUTS; if (!outputFile) { - console.log("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); + core.info("GITHUB_AW_SAFE_OUTPUTS not set, no output to collect"); core.setOutput("output", ""); return; } if (!fs.existsSync(outputFile)) { - console.log("Output file does not exist:", outputFile); + core.info(`Output file does not exist: ${outputFile}`); core.setOutput("output", ""); return; } const outputContent = fs.readFileSync(outputFile, "utf8"); if (outputContent.trim() === "") { - console.log("Output file is empty"); + core.info("Output file is empty"); core.setOutput("output", ""); } else { const sanitizedContent = sanitizeContent(outputContent); - console.log( - "Collected agentic output (sanitized):", - sanitizedContent.substring(0, 200) + - (sanitizedContent.length > 200 ? "..." : "") + core.info( + `Collected agentic output (sanitized): ${sanitizedContent.substring(0, 200)}${sanitizedContent.length > 200 ? "..." : ""}` ); core.setOutput("output", sanitizedContent); } diff --git a/pkg/workflow/js/update_issue.cjs b/pkg/workflow/js/update_issue.cjs index 3ba6e75142d..14cff6e31ae 100644 --- a/pkg/workflow/js/update_issue.cjs +++ b/pkg/workflow/js/update_issue.cjs @@ -5,31 +5,30 @@ async function main() { // Read the validated output content from environment variable const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT; if (!outputContent) { - console.log("No GITHUB_AW_AGENT_OUTPUT environment variable found"); + core.info("No GITHUB_AW_AGENT_OUTPUT environment variable found"); return; } if (outputContent.trim() === "") { - console.log("Agent output content is empty"); + core.info("Agent output content is empty"); return; } - console.log("Agent output content length:", outputContent.length); + core.info(`Agent output content length: ${outputContent.length}`); // Parse the validated output JSON let validatedOutput; try { validatedOutput = JSON.parse(outputContent); } catch (error) { - console.log( - "Error parsing agent output JSON:", - error instanceof Error ? error.message : String(error) + core.setFailed( + `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}` ); return; } if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { - console.log("No valid items found in agent output"); + core.info("No valid items found in agent output"); return; } @@ -38,11 +37,11 @@ async function main() { /** @param {any} item */ item => item.type === "update-issue" ); if (updateItems.length === 0) { - console.log("No update-issue items found in agent output"); + core.info("No update-issue items found in agent output"); return; } - console.log(`Found ${updateItems.length} update-issue item(s)`); + core.info(`Found ${updateItems.length} update-issue item(s)`); // If in staged mode, emit step summary instead of updating issues if (isStaged) { @@ -73,7 +72,7 @@ async function main() { // Write to step summary await core.summary.addRaw(summaryContent).write(); - console.log("📝 Issue update preview written to step summary"); + core.info("📝 Issue update preview written to step summary"); return; } @@ -83,8 +82,8 @@ async function main() { const canUpdateTitle = process.env.GITHUB_AW_UPDATE_TITLE === "true"; const canUpdateBody = process.env.GITHUB_AW_UPDATE_BODY === "true"; - console.log(`Update target configuration: ${updateTarget}`); - console.log( + core.info(`Update target configuration: ${updateTarget}`); + core.info( `Can update status: ${canUpdateStatus}, title: ${canUpdateTitle}, body: ${canUpdateBody}` ); @@ -94,7 +93,7 @@ async function main() { // Validate context based on target configuration if (updateTarget === "triggering" && !isIssueContext) { - console.log( + core.info( 'Target is "triggering" but not running in issue context, skipping issue update' ); return; @@ -105,7 +104,7 @@ async function main() { // Process each update item for (let i = 0; i < updateItems.length; i++) { const updateItem = updateItems[i]; - console.log(`Processing update-issue item ${i + 1}/${updateItems.length}`); + core.info(`Processing update-issue item ${i + 1}/${updateItems.length}`); // Determine the issue number for this update let issueNumber; @@ -115,22 +114,20 @@ async function main() { if (updateItem.issue_number) { issueNumber = parseInt(updateItem.issue_number, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number specified: ${updateItem.issue_number}` ); continue; } } else { - console.log( - 'Target is "*" but no issue_number specified in update item' - ); + core.info('Target is "*" but no issue_number specified in update item'); continue; } } else if (updateTarget && updateTarget !== "triggering") { // Explicit issue number specified in target issueNumber = parseInt(updateTarget, 10); if (isNaN(issueNumber) || issueNumber <= 0) { - console.log( + core.info( `Invalid issue number in target configuration: ${updateTarget}` ); continue; @@ -141,23 +138,24 @@ async function main() { if (context.payload.issue) { issueNumber = context.payload.issue.number; } else { - console.log("Issue context detected but no issue found in payload"); + core.info("Issue context detected but no issue found in payload"); continue; } } else { - console.log("Could not determine issue number"); + core.info("Could not determine issue number"); continue; } } if (!issueNumber) { - console.log("Could not determine issue number"); + core.info("Could not determine issue number"); continue; } - console.log(`Updating issue #${issueNumber}`); + core.info(`Updating issue #${issueNumber}`); // Build the update object based on allowed fields and provided values + /** @type {any} */ const updateData = {}; let hasUpdates = false; @@ -166,9 +164,9 @@ async function main() { if (updateItem.status === "open" || updateItem.status === "closed") { updateData.state = updateItem.status; hasUpdates = true; - console.log(`Will update status to: ${updateItem.status}`); + core.info(`Will update status to: ${updateItem.status}`); } else { - console.log( + core.info( `Invalid status value: ${updateItem.status}. Must be 'open' or 'closed'` ); } @@ -181,9 +179,9 @@ async function main() { ) { updateData.title = updateItem.title.trim(); hasUpdates = true; - console.log(`Will update title to: ${updateItem.title.trim()}`); + core.info(`Will update title to: ${updateItem.title.trim()}`); } else { - console.log("Invalid title value: must be a non-empty string"); + core.info("Invalid title value: must be a non-empty string"); } } @@ -191,14 +189,14 @@ async function main() { if (typeof updateItem.body === "string") { updateData.body = updateItem.body; hasUpdates = true; - console.log(`Will update body (length: ${updateItem.body.length})`); + core.info(`Will update body (length: ${updateItem.body.length})`); } else { - console.log("Invalid body value: must be a string"); + core.info("Invalid body value: must be a string"); } } if (!hasUpdates) { - console.log("No valid updates to apply for this item"); + core.info("No valid updates to apply for this item"); continue; } @@ -211,7 +209,7 @@ async function main() { ...updateData, }); - console.log("Updated issue #" + issue.number + ": " + issue.html_url); + core.info("Updated issue #" + issue.number + ": " + issue.html_url); updatedIssues.push(issue); // Set output for the last updated issue (for backward compatibility) @@ -236,7 +234,7 @@ async function main() { await core.summary.addRaw(summaryContent).write(); } - console.log(`Successfully updated ${updatedIssues.length} issue(s)`); + core.info(`Successfully updated ${updatedIssues.length} issue(s)`); return updatedIssues; } await main(); diff --git a/pkg/workflow/js/validate_errors.cjs b/pkg/workflow/js/validate_errors.cjs index ebeb88901e6..8bce08765d6 100644 --- a/pkg/workflow/js/validate_errors.cjs +++ b/pkg/workflow/js/validate_errors.cjs @@ -27,11 +27,13 @@ function main() { if (hasErrors) { core.setFailed("Errors detected in agent logs - failing workflow step"); } else { - console.log("Error validation completed successfully"); + core.info("Error validation completed successfully"); } } catch (error) { console.debug(error); - core.setFailed(`Error validating log: ${error.message}`); + core.setFailed( + `Error validating log: ${error instanceof Error ? error.message : String(error)}` + ); } } @@ -51,11 +53,16 @@ function getErrorPatternsFromEnv() { return patterns; } catch (e) { throw new Error( - `Failed to parse GITHUB_AW_ERROR_PATTERNS as JSON: ${e.message}` + `Failed to parse GITHUB_AW_ERROR_PATTERNS as JSON: ${e instanceof Error ? e.message : String(e)}` ); } } +/** + * @param {string} logContent + * @param {any[]} patterns + * @returns {boolean} + */ function validateErrors(logContent, patterns) { const lines = logContent.split("\n"); let hasErrors = false; @@ -86,6 +93,11 @@ function validateErrors(logContent, patterns) { return hasErrors; } +/** + * @param {any} match + * @param {any} pattern + * @returns {string} + */ function extractLevel(match, pattern) { if ( pattern.level_group && @@ -106,6 +118,12 @@ function extractLevel(match, pattern) { return "unknown"; } +/** + * @param {any} match + * @param {any} pattern + * @param {any} fullLine + * @returns {string} + */ function extractMessage(match, pattern, fullLine) { if ( pattern.message_group && @@ -119,6 +137,11 @@ function extractMessage(match, pattern, fullLine) { return match[0] || fullLine.trim(); } +/** + * @param {any} str + * @param {any} maxLength + * @returns {string} + */ function truncateString(str, maxLength) { if (!str) return ""; if (str.length <= maxLength) return str; diff --git a/pkg/workflow/log_parser_snapshot_test.go b/pkg/workflow/log_parser_snapshot_test.go index fb72b5520b4..aed26a18810 100644 --- a/pkg/workflow/log_parser_snapshot_test.go +++ b/pkg/workflow/log_parser_snapshot_test.go @@ -24,12 +24,6 @@ func TestLogParserSnapshots(t *testing.T) { logFile: "sample_claude_log.txt", expectedFile: "expected_claude_baseline.md", }, - { - name: "Codex log parsing", - engine: "codex", - logFile: "sample_codex_log.txt", - expectedFile: "expected_codex_baseline.md", - }, } for _, tt := range tests { diff --git a/pkg/workflow/log_parser_test.go b/pkg/workflow/log_parser_test.go index 5baaf56ac5f..62fe43d3826 100644 --- a/pkg/workflow/log_parser_test.go +++ b/pkg/workflow/log_parser_test.go @@ -149,83 +149,3 @@ func TestParseClaudeLogSmoke(t *testing.T) { t.Error("Expected error message for empty Claude log") } } - -func TestParseCodexLogSmoke(t *testing.T) { - script := GetLogParserScript("parse_codex_log") - if script == "" { - t.Skip("parse_codex_log script not available") - } - - // Test with minimal valid Codex log - minimalCodexLog := `[2025-08-31T12:37:08] OpenAI Codex v0.27.0 (research preview) --------- -workdir: /home/runner/work/test/test -model: o4-mini -provider: openai -approval: never -sandbox: workspace-write [workdir, /tmp] -reasoning effort: medium -reasoning summaries: auto --------- -[2025-08-31T12:37:08] User instructions: -Test task for the agent. - -[2025-08-31T12:37:09] thinking -Let me help with this test task. - -[2025-08-31T12:37:10] tool get_current_time() -[2025-08-31T12:37:10] success in 0.2s - -[2025-08-31T12:37:11] exec echo "Hello from Codex" in working directory -[2025-08-31T12:37:11] success in 0.1s - -tokens used: 250 - -[2025-08-31T12:37:12] Final response: Task completed successfully.` - - result, err := runJSLogParser(script, minimalCodexLog) - if err != nil { - t.Fatalf("Failed to parse minimal Codex log: %v", err) - } - - // Verify essential sections are present - if !strings.Contains(result, "🤖 Commands and Tools") { - t.Error("Expected Codex log output to contain Commands and Tools section") - } - if !strings.Contains(result, "🤖 Reasoning") { - t.Error("Expected Codex log output to contain Reasoning section") - } - if !strings.Contains(result, "get_current_time") { - t.Error("Expected Codex log output to contain tool usage") - } - if !strings.Contains(result, "echo") { - t.Error("Expected Codex log output to contain exec command") - } - if !strings.Contains(result, "Total Tokens Used") { - t.Error("Expected Codex log output to contain token usage") - } - - // Test with empty input - result, err = runJSLogParser(script, "") - if err != nil { - t.Fatalf("Failed to parse empty Codex log: %v", err) - } - // Codex parser should handle empty input gracefully - if !strings.Contains(result, "🤖 Commands and Tools") { - t.Error("Expected Codex log output to contain Commands and Tools section even for empty input") - } - - // Test with malformed log entries - malformedLog := `[2025-08-31T12:37:08] Invalid log format -Some random text that doesn't follow expected patterns -More unstructured content` - - result, err = runJSLogParser(script, malformedLog) - if err != nil { - t.Fatalf("Failed to parse malformed Codex log: %v", err) - } - // Should still produce basic structure - if !strings.Contains(result, "🤖 Commands and Tools") { - t.Error("Expected Codex log output to contain Commands and Tools section even for malformed input") - } -} diff --git a/tsconfig.json b/tsconfig.json index 9fe8bd76711..13c27d751b8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,11 +10,11 @@ "outDir": "./dist/js", "rootDir": "./pkg/workflow/js", "strict": true, - "noImplicitAny": true, + "noImplicitAny": false, "strictNullChecks": true, - "strictFunctionTypes": false, - "noImplicitThis": false, - "noImplicitReturns": false, + "strictFunctionTypes": true, + "noImplicitThis": true, + "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "moduleResolution": "bundler", "allowSyntheticDefaultImports": true,