diff --git a/.github/workflows/ai-triage-campaign.lock.yml b/.github/workflows/ai-triage-campaign.lock.yml index 5685e35535..034dfe89cc 100644 --- a/.github/workflows/ai-triage-campaign.lock.yml +++ b/.github/workflows/ai-triage-campaign.lock.yml @@ -260,7 +260,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"missing_tool":{},"update_project":{"max":20}} + {"missing_tool":{},"noop":{"max":1},"update_project":{"max":20}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1701,6 +1701,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2245,6 +2247,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2359,7 +2383,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 13d8c96583..d077442ea8 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -420,6 +420,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1290,7 +1334,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"missing_tool":{}} + {"add_comment":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2771,6 +2815,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3315,6 +3361,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3429,7 +3497,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index df1acb41b4..e24c43083f 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -265,7 +265,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1647,6 +1647,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2191,6 +2193,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2305,7 +2329,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index acc3ea3f43..541382c6bc 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -447,7 +447,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2690,6 +2690,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3234,6 +3236,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3348,7 +3372,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index 4bd7278de4..f5f2ed8978 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -368,7 +368,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2035,6 +2035,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2579,6 +2581,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2693,7 +2717,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 6f7c34283b..c42f27d188 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -401,6 +401,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1253,7 +1297,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"missing_tool":{}} + {"add_comment":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2618,6 +2662,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3162,6 +3208,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3276,7 +3344,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 5070e0af9f..be080dbd4e 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -409,6 +409,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -866,7 +910,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"missing_tool":{},"push_to_pull_request_branch":{}} + {"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Push changes to a pull request branch","inputSchema":{"additionalProperties":false,"properties":{"branch":{"description":"Optional branch name. Do not provide this parameter if you want to push changes from the current branch. If not provided, the current branch will be used.","type":"string"},"message":{"description":"Commit message","type":"string"},"pull_request_number":{"description":"Optional pull request number for target '*'","type":["number","string"]}},"required":["message"],"type":"object"},"name":"push_to_pull_request_branch"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2315,6 +2359,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2859,6 +2905,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2973,7 +3041,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 0e4cef2a97..e9136a994e 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -717,7 +717,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"create_issue":{"max":1},"missing_tool":{}} + {"add_comment":{"max":1},"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2132,6 +2132,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2676,6 +2678,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2790,7 +2814,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 0f56355a7c..0a1d05ee9e 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -266,7 +266,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":5},"missing_tool":{}} + {"create_issue":{"max":5},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1685,6 +1685,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2229,6 +2231,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2343,7 +2367,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 5ccab34582..e3addf9ff3 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -293,7 +293,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1875,6 +1875,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2419,6 +2421,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2533,7 +2557,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index ae28028da5..bb8dab84a4 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -451,6 +451,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1465,7 +1509,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"create_pull_request":{},"missing_tool":{},"push_to_pull_request_branch":{}} + {"add_comment":{"max":1},"create_pull_request":{},"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Push changes to a pull request branch","inputSchema":{"additionalProperties":false,"properties":{"branch":{"description":"Optional branch name. Do not provide this parameter if you want to push changes from the current branch. If not provided, the current branch will be used.","type":"string"},"message":{"description":"Commit message","type":"string"},"pull_request_number":{"description":"Optional pull request number for target '*'","type":["number","string"]}},"required":["message"],"type":"object"},"name":"push_to_pull_request_branch"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -3165,6 +3209,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3709,6 +3755,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3823,7 +3891,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index 031c697381..9c1986b2a9 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -371,7 +371,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1966,6 +1966,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2510,6 +2512,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2624,7 +2648,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index cb0a31419f..91289b5c6d 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -403,7 +403,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2329,6 +2329,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2873,6 +2875,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2987,7 +3011,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index e8ce2c6394..6e324929e9 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -332,7 +332,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2422,6 +2422,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2966,6 +2968,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3080,7 +3104,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 874628de86..2e0106382d 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -302,7 +302,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1988,6 +1988,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2532,6 +2534,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2646,7 +2670,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 7220b91b25..4cf365e5d9 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -443,7 +443,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -3239,6 +3239,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3783,6 +3785,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3897,7 +3921,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 2f81636638..b1968c7e02 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -402,6 +402,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1258,7 +1302,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"missing_tool":{},"push_to_pull_request_branch":{}} + {"add_comment":{"max":1},"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Push changes to a pull request branch","inputSchema":{"additionalProperties":false,"properties":{"branch":{"description":"Optional branch name. Do not provide this parameter if you want to push changes from the current branch. If not provided, the current branch will be used.","type":"string"},"message":{"description":"Commit message","type":"string"},"pull_request_number":{"description":"Optional pull request number for target '*'","type":["number","string"]}},"required":["message"],"type":"object"},"name":"push_to_pull_request_branch"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2772,6 +2816,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3316,6 +3362,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3430,7 +3498,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index 296a7d1b15..8a5753b0f0 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -388,7 +388,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2309,6 +2309,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2853,6 +2855,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2967,7 +2991,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index f30b9128d8..fb48e30c0c 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -387,7 +387,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_pull_request":{},"missing_tool":{}} + {"create_pull_request":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1895,6 +1895,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2439,6 +2441,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2553,7 +2577,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index 4e50b38aa4..5811cbb113 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -289,7 +289,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1801,6 +1801,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2345,6 +2347,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2459,7 +2483,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 37889fe423..5489526e64 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -330,7 +330,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2406,6 +2406,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2950,6 +2952,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3064,7 +3088,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index 7dfb4684b9..518bcf8901 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -376,7 +376,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1820,6 +1820,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2364,6 +2366,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2478,7 +2502,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 76fa48c0bd..da59218433 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -336,7 +336,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2415,6 +2415,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2959,6 +2961,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3073,7 +3097,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index 31360bd840..fc97379378 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -323,7 +323,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2259,6 +2259,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2803,6 +2805,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2917,7 +2941,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index 322193e923..2c51cb6f49 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -271,7 +271,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1578,6 +1578,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2122,6 +2124,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2236,7 +2260,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index dd48096a0a..77f04aa3c7 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -260,7 +260,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":5},"missing_tool":{}} + {"create_issue":{"max":5},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1719,6 +1719,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2263,6 +2265,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2377,7 +2401,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index 033d1de988..55164ca6c8 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -684,7 +684,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1,"target":"*"},"missing_tool":{}} + {"add_comment":{"max":1,"target":"*"},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2022,6 +2022,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2566,6 +2568,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2680,7 +2704,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 880231f13d..e08c75f601 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -14,12 +14,16 @@ # detection["detection"] # missing_tool["missing_tool"] # noop["noop"] +# pre_activation["pre_activation"] # push_to_pull_request_branch["push_to_pull_request_branch"] +# update_release["update_release"] +# pre_activation --> activation # activation --> agent # agent --> conclusion # activation --> conclusion # push_to_pull_request_branch --> conclusion # missing_tool --> conclusion +# update_release --> conclusion # noop --> conclusion # agent --> detection # agent --> missing_tool @@ -29,6 +33,8 @@ # agent --> push_to_pull_request_branch # activation --> push_to_pull_request_branch # detection --> push_to_pull_request_branch +# agent --> update_release +# detection --> update_release # ``` # # Pinned GitHub Actions: @@ -45,7 +51,21 @@ name: "Dev" "on": - workflow_dispatch: null + release: + types: + - created + - edited + - published + workflow_dispatch: + inputs: + release_id: + description: Release ID + required: false + type: string + release_url: + description: Release URL (e.g., https://github.com/owner/repo/releases/tag/v1.0.0) + required: false + type: string permissions: contents: read @@ -60,9 +80,13 @@ run-name: "Dev" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: contents: read + outputs: + text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -151,6 +175,289 @@ jobs: main().catch(error => { core.setFailed(error instanceof Error ? error.message : String(error)); }); + - name: Compute current body text + id: compute-text + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + function extractDomainsFromUrl(url) { + if (!url || typeof url !== "string") { + return []; + } + try { + const urlObj = new URL(url); + const hostname = urlObj.hostname.toLowerCase(); + const domains = [hostname]; + if (hostname === "github.com") { + domains.push("api.github.com"); + domains.push("raw.githubusercontent.com"); + domains.push("*.githubusercontent.com"); + } + else if (!hostname.startsWith("api.")) { + domains.push("api." + hostname); + domains.push("raw." + hostname); + } + return domains; + } catch (e) { + return []; + } + } + function sanitizeContent(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; + } + const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; + const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; + let allowedDomains = allowedDomainsEnv + ? allowedDomainsEnv + .split(",") + .map(d => d.trim()) + .filter(d => d) + : defaultAllowedDomains; + const githubServerUrl = process.env.GITHUB_SERVER_URL; + const githubApiUrl = process.env.GITHUB_API_URL; + if (githubServerUrl) { + const serverDomains = extractDomainsFromUrl(githubServerUrl); + allowedDomains = allowedDomains.concat(serverDomains); + } + if (githubApiUrl) { + const apiDomains = extractDomainsFromUrl(githubApiUrl); + allowedDomains = allowedDomains.concat(apiDomains); + } + allowedDomains = [...new Set(allowedDomains)]; + let sanitized = content; + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized); + const lines = sanitized.split("\n"); + const maxLines = 65000; + maxLength = maxLength || 524288; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + sanitized = truncatedLines; + } + } else if (sanitized.length > maxLength) { + sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; + } + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); + function sanitizeUrlDomains(s) { + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); + const isAllowed = allowedDomains.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); + }); + if (isAllowed) { + return match; + } + const domain = hostname; + const truncated = domain.length > 12 ? domain.substring(0, 12) + "..." : domain; + core.info(`Redacted URL: ${truncated}`); + core.debug(`Redacted URL (full): ${match}`); + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; + }); + return s; + } + function sanitizeUrlProtocols(s) { + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + const domainMatch = match.match(/^[^:]+:\/\/([^\/\s?#]+)/); + const domain = domainMatch ? domainMatch[1] : match; + const truncated = domain.length > 12 ? domain.substring(0, 12) + "..." : domain; + core.info(`Redacted URL: ${truncated}`); + core.debug(`Redacted URL (full): ${match}`); + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; + core.info(`Redacted URL: ${truncated}`); + core.debug(`Redacted URL (full): ${match}`); + return "(redacted)"; + } + return match; + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeMentions(s) { + return s.replace( + /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, + (_m, p1, p2) => `${p1}\`@${p2}\`` + ); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + } + async function main() { + let text = ""; + const actor = context.actor; + const { owner, repo } = context.repo; + const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: owner, + repo: repo, + username: actor, + }); + const permission = repoPermission.data.permission; + core.info(`Repository permission level: ${permission}`); + if (permission !== "admin" && permission !== "maintain") { + core.setOutput("text", ""); + return; + } + switch (context.eventName) { + case "issues": + if (context.payload.issue) { + const title = context.payload.issue.title || ""; + const body = context.payload.issue.body || ""; + text = `${title}\n\n${body}`; + } + break; + case "pull_request": + if (context.payload.pull_request) { + const title = context.payload.pull_request.title || ""; + const body = context.payload.pull_request.body || ""; + text = `${title}\n\n${body}`; + } + break; + case "pull_request_target": + if (context.payload.pull_request) { + const title = context.payload.pull_request.title || ""; + const body = context.payload.pull_request.body || ""; + text = `${title}\n\n${body}`; + } + break; + case "issue_comment": + if (context.payload.comment) { + text = context.payload.comment.body || ""; + } + break; + case "pull_request_review_comment": + if (context.payload.comment) { + text = context.payload.comment.body || ""; + } + break; + case "pull_request_review": + if (context.payload.review) { + text = context.payload.review.body || ""; + } + break; + case "discussion": + if (context.payload.discussion) { + const title = context.payload.discussion.title || ""; + const body = context.payload.discussion.body || ""; + text = `${title}\n\n${body}`; + } + break; + case "discussion_comment": + if (context.payload.comment) { + text = context.payload.comment.body || ""; + } + break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; + default: + text = ""; + break; + } + const sanitizedText = sanitizeContent(text); + core.info(`text: ${sanitizedText}`); + core.setOutput("text", sanitizedText); + } + await main(); agent: needs: activation @@ -255,10 +562,10 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"missing_tool":{},"push_to_pull_request_branch":{}} + {"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{},"update_release":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' - [{"description":"Push changes to a pull request branch","inputSchema":{"additionalProperties":false,"properties":{"branch":{"description":"Optional branch name. Do not provide this parameter if you want to push changes from the current branch. If not provided, the current branch will be used.","type":"string"},"message":{"description":"Commit message","type":"string"},"pull_request_number":{"description":"Optional pull request number for target '*'","type":["number","string"]}},"required":["message"],"type":"object"},"name":"push_to_pull_request_branch"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] + [{"description":"Push changes to a pull request branch","inputSchema":{"additionalProperties":false,"properties":{"branch":{"description":"Optional branch name. Do not provide this parameter if you want to push changes from the current branch. If not provided, the current branch will be used.","type":"string"},"message":{"description":"Commit message","type":"string"},"pull_request_number":{"description":"Optional pull request number for target '*'","type":["number","string"]}},"required":["message"],"type":"object"},"name":"push_to_pull_request_branch"},{"description":"Update a GitHub release description","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Release body content to set, append, or prepend","type":"string"},"operation":{"description":"Update operation: 'replace' (full replacement), 'append' (add at end with separator), or 'prepend' (add at start with separator)","enum":["replace","append","prepend"],"type":"string"},"tag":{"description":"Release tag name (optional - inferred from event context if omitted)","type":"string"}},"required":["operation","body"],"type":"object"},"name":"update_release"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] EOF cat > /tmp/gh-aw/safeoutputs/mcp-server.cjs << 'EOF' const fs = require("fs"); @@ -883,38 +1190,22 @@ jobs: mkdir -p "$PROMPT_DIR" # shellcheck disable=SC2006,SC2287 cat > "$GH_AW_PROMPT" << 'PROMPT_EOF' - # Generate a Poem - - Create or update a `poem.md` file with a creative poem about GitHub Agentic Workflows and push the changes to the pull request branch. - - **Instructions**: - - Use the `edit` tool to either create a new `poem.md` file or update the existing one if it already exists. Write a creative, engaging poem that celebrates the power and capabilities of GitHub Agentic Workflows. + # Release Summary Prepender - The poem should be: - - Creative and fun - - Related to automation, AI agents, or GitHub workflows - - At least 8 lines long - - Written in a poetic style (rhyming, rhythm, or free verse) + **Context:** This workflow is triggered when a release is created/edited or manually dispatched with a release URL/ID. - Commit your changes. + The release content is available in the context: "${GH_AW_EXPR_0BABF60D}" - Call the `push-to-pull-request-branch` tool after making your changes. + **Task:** Analyze the release description and prepend a concise AI-generated summary to it. - **Example poem file structure:** - ```markdown - # Poem for GitHub Agentic Workflows + **Instructions:** - In the realm of code where automation flows, - An agent awakens, its purpose it knows. - Through pull requests and issues it goes, - Analyzing, creating, whatever it shows. + 1. Read the release content from the context above + 2. Generate a clear, concise summary (2-4 sentences) highlighting the key changes or features + 3. Use the `update_release` safe output with the **prepend** operation to add your summary at the top + 4. **CRITICAL:** If the `update_release` tool is not available, fail immediately with an error message - With LlamaGuard watching for threats in the night, - And Ollama scanning to keep things right. - The workflows are running, efficient and bright, - GitHub Agentic magic, a developer's delight. - ``` + **Note:** The tag field is optional and will be automatically inferred from the release event context (`github.event.release.tag_name`) or from workflow dispatch inputs (`release_url` or `release_id`). The summary will be prepended with a horizontal line separator and AI attribution footer. PROMPT_EOF - name: Append XPIA security instructions to prompt @@ -1048,6 +1339,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_0BABF60D: ${{ needs.activation.outputs.text }} with: script: | const fs = require("fs"); @@ -1544,6 +1836,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2088,6 +2382,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2202,7 +2518,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); @@ -3535,6 +3851,7 @@ jobs: - activation - push_to_pull_request_branch - missing_tool + - update_release - noop if: (always()) && (needs.agent.result != 'skipped') runs-on: ubuntu-slim @@ -4440,6 +4757,86 @@ jobs: } await main(); + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + steps: + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_REQUIRED_ROLES: admin,maintainer,write + with: + script: | + async function main() { + const { eventName } = context; + const actor = context.actor; + const { owner, repo } = context.repo; + const requiredPermissionsEnv = process.env.GH_AW_REQUIRED_ROLES; + const requiredPermissions = requiredPermissionsEnv ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") : []; + if (eventName === "workflow_dispatch") { + const hasWriteRole = requiredPermissions.includes("write"); + if (hasWriteRole) { + core.info(`✅ Event ${eventName} does not require validation (write role allowed)`); + core.setOutput("is_team_member", "true"); + core.setOutput("result", "safe_event"); + return; + } + core.info(`Event ${eventName} requires validation (write role not allowed)`); + } + const safeEvents = ["schedule"]; + if (safeEvents.includes(eventName)) { + core.info(`✅ Event ${eventName} does not require validation`); + core.setOutput("is_team_member", "true"); + core.setOutput("result", "safe_event"); + return; + } + if (!requiredPermissions || requiredPermissions.length === 0) { + core.warning("❌ Configuration error: Required permissions not specified. Contact repository administrator."); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "config_error"); + core.setOutput("error_message", "Configuration error: Required permissions not specified"); + return; + } + try { + core.info(`Checking if user '${actor}' has required permissions for ${owner}/${repo}`); + core.info(`Required permissions: ${requiredPermissions.join(", ")}`); + const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: owner, + repo: repo, + username: actor, + }); + const permission = repoPermission.data.permission; + core.info(`Repository permission level: ${permission}`); + for (const requiredPerm of requiredPermissions) { + if (permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain")) { + core.info(`✅ User has ${permission} access to repository`); + core.setOutput("is_team_member", "true"); + core.setOutput("result", "authorized"); + core.setOutput("user_permission", permission); + return; + } + } + core.warning(`User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "insufficient_permissions"); + core.setOutput("user_permission", permission); + core.setOutput( + "error_message", + `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` + ); + } catch (repoError) { + const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); + core.warning(`Repository permission check failed: ${errorMessage}`); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "api_error"); + core.setOutput("error_message", `Repository permission check failed: ${errorMessage}`); + return; + } + } + await main(); + push_to_pull_request_branch: needs: - agent @@ -4943,3 +5340,215 @@ jobs: } await main(); + update_release: + needs: + - agent + - detection + if: > + (((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'update_release'))) && + (needs.detection.outputs.success == 'true') + runs-on: ubuntu-slim + permissions: + contents: write + timeout-minutes: 10 + outputs: + release_id: ${{ steps.update_release.outputs.release_id }} + release_tag: ${{ steps.update_release.outputs.release_tag }} + release_url: ${{ steps.update_release.outputs.release_url }} + steps: + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent_output.json + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Update Release + id: update_release + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Dev" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const fs = require("fs"); + function loadAgentOutput() { + const agentOutputFile = process.env.GH_AW_AGENT_OUTPUT; + if (!agentOutputFile) { + core.info("No GH_AW_AGENT_OUTPUT environment variable found"); + return { success: false }; + } + let outputContent; + try { + outputContent = fs.readFileSync(agentOutputFile, "utf8"); + } catch (error) { + const errorMessage = `Error reading agent output file: ${error instanceof Error ? error.message : String(error)}`; + core.error(errorMessage); + return { success: false, error: errorMessage }; + } + if (outputContent.trim() === "") { + core.info("Agent output content is empty"); + return { success: false }; + } + core.info(`Agent output content length: ${outputContent.length}`); + let validatedOutput; + try { + validatedOutput = JSON.parse(outputContent); + } catch (error) { + const errorMessage = `Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}`; + core.error(errorMessage); + return { success: false, error: errorMessage }; + } + if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) { + core.info("No valid items found in agent output"); + return { success: false }; + } + return { success: true, items: validatedOutput.items }; + } + async function generateStagedPreview(options) { + const { title, description, items, renderItem } = options; + let summaryContent = `## 🎭 Staged Mode: ${title} Preview\n\n`; + summaryContent += `${description}\n\n`; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + summaryContent += renderItem(item, i); + summaryContent += "---\n\n"; + } + try { + await core.summary.addRaw(summaryContent).write(); + core.info(summaryContent); + core.info(`📝 ${title} preview written to step summary`); + } catch (error) { + core.setFailed(error instanceof Error ? error : String(error)); + } + } + async function main() { + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + const result = loadAgentOutput(); + if (!result.success) { + return; + } + const updateItems = result.items.filter( item => item.type === "update_release"); + if (updateItems.length === 0) { + core.info("No update-release items found in agent output"); + return; + } + core.info(`Found ${updateItems.length} update-release item(s)`); + if (isStaged) { + await generateStagedPreview({ + title: "Update Releases", + description: "The following release updates would be applied if staged mode was disabled:", + items: updateItems, + renderItem: (item, index) => { + let content = `### Release Update ${index + 1}\n`; + content += `**Tag:** ${item.tag || "(inferred from event context)"}\n`; + content += `**Operation:** ${item.operation}\n\n`; + content += `**Body Content:**\n${item.body}\n\n`; + return content; + }, + }); + return; + } + const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + const updatedReleases = []; + for (let i = 0; i < updateItems.length; i++) { + const updateItem = updateItems[i]; + core.info(`Processing update-release item ${i + 1}/${updateItems.length}`); + try { + let releaseTag = updateItem.tag; + if (!releaseTag) { + if (context.eventName === "release" && context.payload.release && context.payload.release.tag_name) { + releaseTag = context.payload.release.tag_name; + core.info(`Inferred release tag from event context: ${releaseTag}`); + } else if (context.eventName === "workflow_dispatch" && context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/[^\/]+\/[^\/]+\/releases\/tag\/([^\/\?#]+)/); + if (urlMatch && urlMatch[1]) { + releaseTag = decodeURIComponent(urlMatch[1]); + core.info(`Inferred release tag from release_url input: ${releaseTag}`); + } + } + if (!releaseTag && context.payload.inputs.release_id) { + const releaseId = context.payload.inputs.release_id; + core.info(`Fetching release with ID: ${releaseId}`); + const { data: release } = await github.rest.repos.getRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: parseInt(releaseId, 10), + }); + releaseTag = release.tag_name; + core.info(`Inferred release tag from release_id input: ${releaseTag}`); + } + } + if (!releaseTag) { + core.error("No tag provided and unable to infer from event context"); + core.setFailed("Release tag is required but not provided and cannot be inferred from event context"); + return; + } + } + core.info(`Fetching release with tag: ${releaseTag}`); + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: releaseTag, + }); + core.info(`Found release: ${release.name || release.tag_name} (ID: ${release.id})`); + let newBody; + if (updateItem.operation === "replace") { + newBody = updateItem.body; + core.info("Operation: replace (full body replacement)"); + } else if (updateItem.operation === "prepend") { + const aiFooter = `\n\n> AI generated by [${workflowName}](${runUrl})`; + const prependSection = `${updateItem.body}${aiFooter}\n\n---\n\n`; + newBody = prependSection + (release.body || ""); + core.info("Operation: prepend (add to start with separator)"); + } else { + const aiFooter = `\n\n> AI generated by [${workflowName}](${runUrl})`; + const appendSection = `\n\n---\n\n${updateItem.body}${aiFooter}`; + newBody = (release.body || "") + appendSection; + core.info("Operation: append (add to end with separator)"); + } + const { data: updatedRelease } = await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id, + body: newBody, + }); + core.info(`Successfully updated release: ${updatedRelease.html_url}`); + updatedReleases.push({ + tag: releaseTag, + url: updatedRelease.html_url, + id: updatedRelease.id, + }); + if (i === 0) { + core.setOutput("release_id", updatedRelease.id); + core.setOutput("release_url", updatedRelease.html_url); + core.setOutput("release_tag", updatedRelease.tag_name); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + const tagInfo = updateItem.tag || "inferred from context"; + core.error(`Failed to update release with tag ${tagInfo}: ${errorMessage}`); + if (errorMessage.includes("Not Found")) { + core.error(`Release with tag '${tagInfo}' not found. Please ensure the tag exists.`); + } + core.setFailed(`Failed to update release: ${errorMessage}`); + return; + } + } + let summaryContent = `## ✅ Release Updates Complete\n\n`; + summaryContent += `Updated ${updatedReleases.length} release(s):\n\n`; + for (const rel of updatedReleases) { + summaryContent += `- **${rel.tag}**: [View Release](${rel.url})\n`; + } + await core.summary.addRaw(summaryContent).write(); + } + await main(); + diff --git a/.github/workflows/dev.md b/.github/workflows/dev.md index f437d972b1..6c5f5dd4dd 100644 --- a/.github/workflows/dev.md +++ b/.github/workflows/dev.md @@ -1,6 +1,17 @@ --- on: workflow_dispatch: + inputs: + release_url: + description: 'Release URL (e.g., https://github.com/owner/repo/releases/tag/v1.0.0)' + required: false + type: string + release_id: + description: 'Release ID' + required: false + type: string + release: + types: [created, edited, published] concurrency: group: dev-workflow-${{ github.ref }} cancel-in-progress: true @@ -14,6 +25,8 @@ permissions: tools: edit: safe-outputs: + update-release: + max: 1 threat-detection: engine: false steps: @@ -332,35 +345,19 @@ safe-outputs: timeout-minutes: 20 --- -# Generate a Poem +# Release Summary Prepender -Create or update a `poem.md` file with a creative poem about GitHub Agentic Workflows and push the changes to the pull request branch. +**Context:** This workflow is triggered when a release is created/edited or manually dispatched with a release URL/ID. -**Instructions**: +The release content is available in the context: "${{ needs.activation.outputs.text }}" -Use the `edit` tool to either create a new `poem.md` file or update the existing one if it already exists. Write a creative, engaging poem that celebrates the power and capabilities of GitHub Agentic Workflows. +**Task:** Analyze the release description and prepend a concise AI-generated summary to it. -The poem should be: -- Creative and fun -- Related to automation, AI agents, or GitHub workflows -- At least 8 lines long -- Written in a poetic style (rhyming, rhythm, or free verse) +**Instructions:** -Commit your changes. +1. Read the release content from the context above +2. Generate a clear, concise summary (2-4 sentences) highlighting the key changes or features +3. Use the `update_release` safe output with the **prepend** operation to add your summary at the top +4. **CRITICAL:** If the `update_release` tool is not available, fail immediately with an error message -Call the `push-to-pull-request-branch` tool after making your changes. - -**Example poem file structure:** -```markdown -# Poem for GitHub Agentic Workflows - -In the realm of code where automation flows, -An agent awakens, its purpose it knows. -Through pull requests and issues it goes, -Analyzing, creating, whatever it shows. - -With LlamaGuard watching for threats in the night, -And Ollama scanning to keep things right. -The workflows are running, efficient and bright, -GitHub Agentic magic, a developer's delight. -``` +**Note:** The tag field is optional and will be automatically inferred from the release event context (`github.event.release.tag_name`) or from workflow dispatch inputs (`release_url` or `release_id`). The summary will be prepended with a horizontal line separator and AI attribution footer. diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index b4a5d97a5a..85a499cbfd 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -423,7 +423,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"create_pull_request":{},"missing_tool":{}} + {"create_discussion":{"max":1},"create_pull_request":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2447,6 +2447,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2991,6 +2993,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3105,7 +3129,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index a1f2639fe9..d1cccc2f72 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -260,7 +260,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_pull_request":{},"missing_tool":{}} + {"create_pull_request":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1642,6 +1642,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2186,6 +2188,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2300,7 +2324,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index d734a28728..37e382365c 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -270,7 +270,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1699,6 +1699,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2243,6 +2245,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2357,7 +2381,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index ce00c9116f..d4867c2b46 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -283,7 +283,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":3},"missing_tool":{}} + {"create_issue":{"max":3},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1716,6 +1716,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2260,6 +2262,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2374,7 +2398,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index 1d39e20cb2..06b20d7f24 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -377,7 +377,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1748,6 +1748,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2292,6 +2294,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2406,7 +2430,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index ee95debefc..55dfca368e 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -399,7 +399,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"create_pull_request":{},"missing_tool":{}} + {"create_discussion":{"max":1},"create_pull_request":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2269,6 +2269,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2813,6 +2815,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2927,7 +2951,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 37d4267468..d47343a76e 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -404,7 +404,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_pull_request":{},"missing_tool":{}} + {"create_pull_request":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2014,6 +2014,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2558,6 +2560,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2672,7 +2696,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index 69c44edbe1..b672d5dc8b 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -374,7 +374,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1788,6 +1788,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2332,6 +2334,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2446,7 +2470,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index 54fe4dfbc1..221eb252a9 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -407,6 +407,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1277,7 +1321,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"create_pull_request_review_comment":{"max":5},"missing_tool":{}} + {"add_comment":{"max":1},"create_pull_request_review_comment":{"max":5},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Create a review comment on a GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body content","type":"string"},"line":{"description":"Line number for the comment","type":["number","string"]},"path":{"description":"File path for the review comment","type":"string"},"side":{"description":"Optional side of the diff: LEFT or RIGHT","enum":["LEFT","RIGHT"],"type":"string"},"start_line":{"description":"Optional start line for multi-line comments","type":["number","string"]}},"required":["path","line","body"],"type":"object"},"name":"create_pull_request_review_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2673,6 +2717,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3217,6 +3263,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3331,7 +3399,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index d206a3bc1d..70167f1a25 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -387,7 +387,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_pull_request":{},"missing_tool":{}} + {"create_pull_request":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1893,6 +1893,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2437,6 +2439,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2551,7 +2575,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 8937930b67..aee68150b0 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -398,6 +398,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1091,7 +1135,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_labels":{"allowed":["bug","feature","enhancement","documentation"],"max":1},"missing_tool":{}} + {"add_labels":{"allowed":["bug","feature","enhancement","documentation"],"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add labels to a GitHub issue or pull request","inputSchema":{"additionalProperties":false,"properties":{"item_number":{"description":"Issue or PR number (optional for current context)","type":"number"},"labels":{"description":"Labels to add","items":{"type":"string"},"type":"array"}},"required":["labels"],"type":"object"},"name":"add_labels"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2320,6 +2364,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2864,6 +2910,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2978,7 +3046,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 0a9ce3407a..c434f65ae2 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -390,7 +390,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2103,6 +2103,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2647,6 +2649,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2761,7 +2785,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index a794388f8b..6eed714963 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -357,7 +357,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"notion-add-comment":{"description":"Add a comment to a Notion page","inputs":{"comment":{"description":"The comment text to add","required":true,"type":"string"}},"output":"Comment added to Notion successfully!"},"post-to-slack-channel":{"description":"Post a message to a Slack channel. Message must be 200 characters or less. Supports basic Slack markdown: *bold*, _italic_, ~strike~, `code`, ```code block```, \u003equote, and links \u003curl|text\u003e. Requires GH_AW_SLACK_CHANNEL_ID environment variable to be set.","inputs":{"message":{"description":"The message to post (max 200 characters, supports Slack markdown)","required":true,"type":"string"}},"output":"Message posted to Slack successfully!"}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"notion-add-comment":{"description":"Add a comment to a Notion page","inputs":{"comment":{"description":"The comment text to add","required":true,"type":"string"}},"output":"Comment added to Notion successfully!"},"post-to-slack-channel":{"description":"Post a message to a Slack channel. Message must be 200 characters or less. Supports basic Slack markdown: *bold*, _italic_, ~strike~, `code`, ```code block```, \u003equote, and links \u003curl|text\u003e. Requires GH_AW_SLACK_CHANNEL_ID environment variable to be set.","inputs":{"message":{"description":"The message to post (max 200 characters, supports Slack markdown)","required":true,"type":"string"}},"output":"Message posted to Slack successfully!"}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2220,6 +2220,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2764,6 +2766,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2878,7 +2902,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 00c23b15f2..5e9a76e348 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -603,7 +603,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"missing_tool":{},"push_to_pull_request_branch":{}} + {"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Push changes to a pull request branch","inputSchema":{"additionalProperties":false,"properties":{"branch":{"description":"Optional branch name. Do not provide this parameter if you want to push changes from the current branch. If not provided, the current branch will be used.","type":"string"},"message":{"description":"Commit message","type":"string"},"pull_request_number":{"description":"Optional pull request number for target '*'","type":["number","string"]}},"required":["message"],"type":"object"},"name":"push_to_pull_request_branch"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2196,6 +2196,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2740,6 +2742,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2854,7 +2878,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index 9133e98164..db5b40d7af 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -1495,6 +1495,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2039,6 +2041,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2153,7 +2177,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 413b6c14f0..444c3f2d78 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -423,6 +423,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1301,7 +1345,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"missing_tool":{}} + {"add_comment":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2724,6 +2768,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3268,6 +3314,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3382,7 +3450,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 1c6cc43f68..b6e81a239e 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -402,6 +402,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -845,7 +889,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":5},"missing_tool":{}} + {"create_issue":{"max":5},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2199,6 +2243,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2743,6 +2789,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2857,7 +2925,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 440285ee72..7b0701e403 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -438,6 +438,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1597,7 +1641,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":3,"target":"*"},"add_labels":{"allowed":["poetry","creative","automation","ai-generated","epic","haiku","sonnet","limerick"],"max":5},"create_issue":{"max":2},"create_pull_request":{},"create_pull_request_review_comment":{"max":2},"missing_tool":{},"push_to_pull_request_branch":{},"update_issue":{"max":2},"upload_asset":{}} + {"add_comment":{"max":3,"target":"*"},"add_labels":{"allowed":["poetry","creative","automation","ai-generated","epic","haiku","sonnet","limerick"],"max":5},"create_issue":{"max":2},"create_pull_request":{},"create_pull_request_review_comment":{"max":2},"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{},"update_issue":{"max":2},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Create a review comment on a GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body content","type":"string"},"line":{"description":"Line number for the comment","type":["number","string"]},"path":{"description":"File path for the review comment","type":"string"},"side":{"description":"Optional side of the diff: LEFT or RIGHT","enum":["LEFT","RIGHT"],"type":"string"},"start_line":{"description":"Optional start line for multi-line comments","type":["number","string"]}},"required":["path","line","body"],"type":"object"},"name":"create_pull_request_review_comment"},{"description":"Add labels to a GitHub issue or pull request","inputSchema":{"additionalProperties":false,"properties":{"item_number":{"description":"Issue or PR number (optional for current context)","type":"number"},"labels":{"description":"Labels to add","items":{"type":"string"},"type":"array"}},"required":["labels"],"type":"object"},"name":"add_labels"},{"description":"Update a GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Optional new issue body","type":"string"},"issue_number":{"description":"Optional issue number for target '*'","type":["number","string"]},"status":{"description":"Optional new issue status","enum":["open","closed"],"type":"string"},"title":{"description":"Optional new issue title","type":"string"}},"type":"object"},"name":"update_issue"},{"description":"Push changes to a pull request branch","inputSchema":{"additionalProperties":false,"properties":{"branch":{"description":"Optional branch name. Do not provide this parameter if you want to push changes from the current branch. If not provided, the current branch will be used.","type":"string"},"message":{"description":"Commit message","type":"string"},"pull_request_number":{"description":"Optional pull request number for target '*'","type":["number","string"]}},"required":["message"],"type":"object"},"name":"push_to_pull_request_branch"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2995,6 +3039,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3539,6 +3585,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3653,7 +3721,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index d3e5a1d744..fb6f62225f 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -1074,7 +1074,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":3},"create_discussion":{"max":1},"create_pull_request_review_comment":{"max":10},"missing_tool":{}} + {"add_comment":{"max":3},"create_discussion":{"max":1},"create_pull_request_review_comment":{"max":10},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Create a review comment on a GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body content","type":"string"},"line":{"description":"Line number for the comment","type":["number","string"]},"path":{"description":"File path for the review comment","type":"string"},"side":{"description":"Optional side of the diff: LEFT or RIGHT","enum":["LEFT","RIGHT"],"type":"string"},"start_line":{"description":"Optional start line for multi-line comments","type":["number","string"]}},"required":["path","line","body"],"type":"object"},"name":"create_pull_request_review_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2776,6 +2776,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3320,6 +3322,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3434,7 +3458,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 568145a3f4..8a9b351b69 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -441,7 +441,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2443,6 +2443,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2987,6 +2989,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3101,7 +3125,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 84e823064c..c2892369df 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -328,7 +328,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2579,6 +2579,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3123,6 +3125,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3237,7 +3261,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 1f84150496..d9514909fa 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -446,6 +446,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1347,7 +1391,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"create_pull_request":{},"missing_tool":{}} + {"add_comment":{"max":1},"create_pull_request":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -3065,6 +3109,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3609,6 +3655,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3723,7 +3791,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index 9ad62acf43..d0236c9083 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -259,7 +259,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1677,6 +1677,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2221,6 +2223,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2335,7 +2359,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index be9dc67da1..4910bfbb7b 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -306,7 +306,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2195,6 +2195,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2739,6 +2741,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2853,7 +2877,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 58c28f69ac..7d2f07b293 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -271,7 +271,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1611,6 +1611,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2155,6 +2157,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2269,7 +2293,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 0b00d3c0d1..9aaa97b0c5 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -416,7 +416,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2236,6 +2236,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2780,6 +2782,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2894,7 +2918,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 4d24ae464a..3988774ebc 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -390,7 +390,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2109,6 +2109,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2653,6 +2655,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2767,7 +2791,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 2b5213ecfe..60e4ce1424 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -449,6 +449,50 @@ jobs: text = context.payload.comment.body || ""; } break; + case "release": + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + case "workflow_dispatch": + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; default: text = ""; break; @@ -1440,7 +1484,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"missing_tool":{}} + {"add_comment":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -3179,6 +3223,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3723,6 +3769,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3837,7 +3905,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index 1543e722be..3c85be360c 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -385,7 +385,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_pull_request":{},"missing_tool":{}} + {"create_pull_request":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1841,6 +1841,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2385,6 +2387,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2499,7 +2523,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 40795a0845..e2c409a3af 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -393,7 +393,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2197,6 +2197,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2741,6 +2743,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2855,7 +2879,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index cc2bd27ac9..de5315b261 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -378,7 +378,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1756,6 +1756,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2300,6 +2302,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2414,7 +2438,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 86575b9a45..1a7e17fbbc 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -265,7 +265,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1458,6 +1458,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2002,6 +2004,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2116,7 +2140,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index ce98c2aa77..18f64cc449 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -273,7 +273,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1528,6 +1528,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2072,6 +2074,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2186,7 +2210,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index a33f730d55..e0f86b94dc 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -1191,7 +1191,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1,"target":"*"},"create_issue":{"max":1},"missing_tool":{}} + {"add_comment":{"max":1,"target":"*"},"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2822,6 +2822,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3366,6 +3368,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3480,7 +3504,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index 255fcaedd9..cb68ba540d 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -409,7 +409,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2124,6 +2124,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2668,6 +2670,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2782,7 +2806,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index a368dea512..a4d59b1f2a 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -296,7 +296,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1750,6 +1750,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2294,6 +2296,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2408,7 +2432,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index adf3dfe88e..385a3e77b0 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -735,7 +735,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"create_pull_request":{},"missing_tool":{},"upload_asset":{}} + {"add_comment":{"max":1},"create_pull_request":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2423,6 +2423,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2967,6 +2969,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3081,7 +3105,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/test-ollama-threat-detection.lock.yml b/.github/workflows/test-ollama-threat-detection.lock.yml index cabb5c638a..8605044aa6 100644 --- a/.github/workflows/test-ollama-threat-detection.lock.yml +++ b/.github/workflows/test-ollama-threat-detection.lock.yml @@ -255,7 +255,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1473,6 +1473,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2017,6 +2019,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2131,7 +2155,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 3a19751463..7bd82c5552 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -631,7 +631,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_pull_request":{},"missing_tool":{},"push_to_pull_request_branch":{}} + {"create_pull_request":{},"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Push changes to a pull request branch","inputSchema":{"additionalProperties":false,"properties":{"branch":{"description":"Optional branch name. Do not provide this parameter if you want to push changes from the current branch. If not provided, the current branch will be used.","type":"string"},"message":{"description":"Commit message","type":"string"},"pull_request_number":{"description":"Optional pull request number for target '*'","type":["number","string"]}},"required":["message"],"type":"object"},"name":"push_to_pull_request_branch"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1999,6 +1999,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2543,6 +2545,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2657,7 +2681,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index b4b0c2857b..53de5bcfba 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -393,7 +393,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2267,6 +2267,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2811,6 +2813,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2925,7 +2949,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 40573a2993..c14e31dacc 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -1178,7 +1178,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"create_pull_request":{},"missing_tool":{},"upload_asset":{}} + {"add_comment":{"max":1},"create_pull_request":{},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Add a comment to a GitHub issue, pull request, or discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Comment body/content","type":"string"},"item_number":{"description":"Issue, pull request or discussion number","type":"number"}},"required":["body","item_number"],"type":"object"},"name":"add_comment"},{"description":"Create a new GitHub pull request","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Pull request body/description","type":"string"},"branch":{"description":"Optional branch name. If not provided, the current branch will be used.","type":"string"},"labels":{"description":"Optional labels to add to the PR","items":{"type":"string"},"type":"array"},"title":{"description":"Pull request title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_pull_request"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2918,6 +2918,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -3462,6 +3464,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -3576,7 +3600,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index 66603ae481..b9da591f0e 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -270,7 +270,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_issue":{"max":1},"missing_tool":{}} + {"create_issue":{"max":1},"missing_tool":{},"noop":{"max":1}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub issue","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Issue body/description","type":"string"},"labels":{"description":"Issue labels","items":{"type":"string"},"type":"array"},"parent":{"description":"Parent issue number to create this issue as a sub-issue of","type":"number"},"title":{"description":"Issue title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_issue"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -1764,6 +1764,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2308,6 +2310,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2422,7 +2446,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index d340000d25..76616cf63e 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -276,7 +276,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/safeoutputs cat > /tmp/gh-aw/safeoutputs/config.json << 'EOF' - {"create_discussion":{"max":1},"missing_tool":{},"upload_asset":{}} + {"create_discussion":{"max":1},"missing_tool":{},"noop":{"max":1},"upload_asset":{}} EOF cat > /tmp/gh-aw/safeoutputs/tools.json << 'EOF' [{"description":"Create a new GitHub discussion","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Discussion body/content","type":"string"},"category":{"description":"Discussion category","type":"string"},"title":{"description":"Discussion title","type":"string"}},"required":["title","body"],"type":"object"},"name":"create_discussion"},{"description":"Publish a file as a URL-addressable asset to an orphaned git branch","inputSchema":{"additionalProperties":false,"properties":{"path":{"description":"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.","type":"string"}},"required":["path"],"type":"object"},"name":"upload_asset"},{"description":"Report a missing tool or functionality needed to complete tasks","inputSchema":{"additionalProperties":false,"properties":{"alternatives":{"description":"Possible alternatives or workarounds (max 256 characters)","type":"string"},"reason":{"description":"Why this tool is needed (max 256 characters)","type":"string"},"tool":{"description":"Name of the missing tool (max 128 characters)","type":"string"}},"required":["tool","reason"],"type":"object"},"name":"missing_tool"},{"description":"Log a message for transparency when no significant actions are needed. Use this to ensure workflows produce human-visible artifacts even when no other actions are taken (e.g., 'Analysis complete - no issues found').","inputSchema":{"additionalProperties":false,"properties":{"message":{"description":"Message to log for transparency","type":"string"}},"required":["message"],"type":"object"},"name":"noop"}] @@ -2167,6 +2167,8 @@ jobs: return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; default: @@ -2711,6 +2713,28 @@ jobs: item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -2825,7 +2849,7 @@ jobs: const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index d3ef4f9e52..ae03889de1 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -1707,6 +1707,28 @@ safe-outputs: # Option 2: Enable asset publishing with default configuration upload-assets: null + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: Configuration for updating GitHub release descriptions + update-release: + # Maximum number of releases to update (default: 1) + # (optional) + max: 1 + + # Target repository for cross-repo release updates (format: owner/repo). If not + # specified, updates releases in the workflow's repository. + # (optional) + target-repo: "example-value" + + # GitHub token to use for this specific output type. Overrides global github-token + # if specified. + # (optional) + github-token: "${{ secrets.GITHUB_TOKEN }}" + + # Option 2: Enable release updates with default configuration + update-release: null + # If true, emit step summary messages instead of making GitHub API calls (preview # mode) # (optional) diff --git a/pkg/cli/workflows/test-claude-update-release.md b/pkg/cli/workflows/test-claude-update-release.md new file mode 100644 index 0000000000..2ab53810e8 --- /dev/null +++ b/pkg/cli/workflows/test-claude-update-release.md @@ -0,0 +1,34 @@ +--- +on: + workflow_dispatch: +permissions: + contents: read + actions: read +engine: claude +safe-outputs: + update-release: + max: 1 +timeout-minutes: 5 +--- + +# Test Claude Update Release + +Test the update-release safe output with the Claude engine. + +Find the latest release in this repository and update its description using the **append** operation. + +Add this content to the release notes: + +## Test Update from Claude + +This section was added by an automated test workflow to verify the update-release functionality. + +**Test Details:** +- Engine: Claude +- Operation: append +- Timestamp: Current date and time + +Output as JSONL format: +``` +{"type": "update_release", "tag": "", "operation": "append", "body": ""} +``` diff --git a/pkg/cli/workflows/test-codex-update-release.md b/pkg/cli/workflows/test-codex-update-release.md new file mode 100644 index 0000000000..83e56ad872 --- /dev/null +++ b/pkg/cli/workflows/test-codex-update-release.md @@ -0,0 +1,34 @@ +--- +on: + workflow_dispatch: +permissions: + contents: read + actions: read +engine: codex +safe-outputs: + update-release: + max: 1 +timeout-minutes: 5 +--- + +# Test Codex Update Release + +Test the update-release safe output with the Codex engine. + +Find the latest release in this repository and update its description using the **replace** operation. + +Replace the release notes with this content: + +## Updated Release Notes (Codex Test) + +This release description was updated by an automated test workflow to verify the update-release functionality. + +**Test Configuration:** +- Engine: Codex +- Operation: replace +- Timestamp: Current date and time + +Output as JSONL format: +``` +{"type": "update_release", "tag": "", "operation": "replace", "body": ""} +``` diff --git a/pkg/cli/workflows/test-copilot-update-release.md b/pkg/cli/workflows/test-copilot-update-release.md new file mode 100644 index 0000000000..42d3bdf19d --- /dev/null +++ b/pkg/cli/workflows/test-copilot-update-release.md @@ -0,0 +1,34 @@ +--- +on: + workflow_dispatch: +permissions: + contents: read + actions: read +engine: copilot +safe-outputs: + update-release: + max: 1 +timeout-minutes: 5 +--- + +# Test Copilot Update Release + +Test the update-release safe output with the Copilot engine. + +Find the latest release in this repository and update its description using the **append** operation. + +Add this content to the release notes: + +## Test Update from Copilot + +This section was added by an automated test workflow to verify the update-release functionality. + +**Test Information:** +- Engine: Copilot +- Operation: append +- Timestamp: Current date and time + +Output as JSONL format: +``` +{"type": "update_release", "tag": "", "operation": "append", "body": ""} +``` diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 7b01b1573e..f852970a0f 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2906,6 +2906,37 @@ } ] }, + "update-release": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for updating GitHub release descriptions", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of releases to update (default: 1)", + "minimum": 1, + "maximum": 10, + "default": 1 + }, + "target-repo": { + "type": "string", + "description": "Target repository for cross-repo release updates (format: owner/repo). If not specified, updates releases in the workflow's repository.", + "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$" + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable release updates with default configuration" + } + ] + }, "staged": { "type": "boolean", "description": "If true, emit step summary messages instead of making GitHub API calls (preview mode)", diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index aaeb66a0bf..89f2c5a88d 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -262,6 +262,7 @@ type SafeOutputsConfig struct { UpdateIssues *UpdateIssuesConfig `yaml:"update-issues,omitempty"` PushToPullRequestBranch *PushToPullRequestBranchConfig `yaml:"push-to-pull-request-branch,omitempty"` UploadAssets *UploadAssetsConfig `yaml:"upload-assets,omitempty"` + UpdateRelease *UpdateReleaseConfig `yaml:"update-release,omitempty"` // Update GitHub release descriptions CreateAgentTasks *CreateAgentTaskConfig `yaml:"create-agent-task,omitempty"` // Create GitHub Copilot agent tasks UpdateProjects *UpdateProjectConfig `yaml:"update-project,omitempty"` // Smart project board management (create/add/update) MissingTool *MissingToolConfig `yaml:"missing-tool,omitempty"` // Optional for reporting missing functionality diff --git a/pkg/workflow/compiler_jobs.go b/pkg/workflow/compiler_jobs.go index 211c11d3ac..ab8bfd4334 100644 --- a/pkg/workflow/compiler_jobs.go +++ b/pkg/workflow/compiler_jobs.go @@ -358,6 +358,24 @@ func (c *Compiler) buildSafeOutputsJobs(data *WorkflowData, jobName, markdownPat safeOutputJobNames = append(safeOutputJobNames, uploadAssetsJob.Name) } + // Build update_release job if output.update-release is configured + if data.SafeOutputs.UpdateRelease != nil { + updateReleaseJob, err := c.buildCreateOutputUpdateReleaseJob(data, jobName) + if err != nil { + return fmt.Errorf("failed to build update_release job: %w", err) + } + // Safe-output jobs should depend on agent job (always) AND detection job (if enabled) + if threatDetectionEnabled { + updateReleaseJob.Needs = append(updateReleaseJob.Needs, constants.DetectionJobName) + // Add detection success check to the job condition + updateReleaseJob.If = AddDetectionSuccessCheck(updateReleaseJob.If) + } + if err := c.jobManager.AddJob(updateReleaseJob); err != nil { + return fmt.Errorf("failed to add update_release job: %w", err) + } + safeOutputJobNames = append(safeOutputJobNames, updateReleaseJob.Name) + } + // Build create_agent_task job if output.create-agent-task is configured if data.SafeOutputs.CreateAgentTasks != nil { createAgentTaskJob, err := c.buildCreateOutputAgentTaskJob(data, jobName) diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index f06c10fc20..f53b2b1596 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -35,6 +35,8 @@ async function main() { return 40; case "upload_asset": return 10; + case "update_release": + return 1; case "noop": return 1; // Default max for noop messages default: @@ -587,6 +589,32 @@ async function main() { item.alternatives = sanitizeContent(item.alternatives, 512); } break; + case "update_release": + // Validate tag (optional - will be inferred from context if missing) + if (item.tag !== undefined && typeof item.tag !== "string") { + errors.push(`Line ${i + 1}: update_release 'tag' must be a string if provided`); + continue; + } + // Validate operation + if (!item.operation || typeof item.operation !== "string") { + errors.push(`Line ${i + 1}: update_release requires an 'operation' string field`); + continue; + } + if (item.operation !== "replace" && item.operation !== "append" && item.operation !== "prepend") { + errors.push(`Line ${i + 1}: update_release 'operation' must be 'replace', 'append', or 'prepend'`); + continue; + } + // Validate body + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_release requires a 'body' string field`); + continue; + } + // Sanitize content + if (item.tag) { + item.tag = sanitizeContent(item.tag, 256); + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; case "upload_asset": if (!item.path || typeof item.path !== "string") { errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); @@ -701,7 +729,7 @@ async function main() { const agentOutputFile = "/tmp/gh-aw/agent_output.json"; const validatedOutputJson = JSON.stringify(validatedOutput); try { - fs.mkdirSync("/tmp", { recursive: true }); + fs.mkdirSync("/tmp/gh-aw", { recursive: true }); fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); core.info(`Stored validated output to: ${agentOutputFile}`); core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); diff --git a/pkg/workflow/js/collect_ndjson_output.test.cjs b/pkg/workflow/js/collect_ndjson_output.test.cjs index a5c8b57b85..080ac85499 100644 --- a/pkg/workflow/js/collect_ndjson_output.test.cjs +++ b/pkg/workflow/js/collect_ndjson_output.test.cjs @@ -448,6 +448,42 @@ describe("collect_ndjson_output.cjs", () => { expect(parsedOutput.errors[0]).toContain("side' must be 'LEFT' or 'RIGHT'"); }); + it("should validate required fields for update_release type", async () => { + const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; + const ndjsonContent = `{"type": "update_release", "tag": "v1.0.0", "operation": "replace", "body": "New release notes"} +{"type": "update_release", "tag": "v1.0.0", "operation": "prepend", "body": "Prepended notes"} +{"type": "update_release", "operation": "replace", "body": "Tag omitted - will be inferred"} +{"type": "update_release", "tag": "v1.0.0", "operation": "invalid", "body": "Notes"} +{"type": "update_release", "tag": "v1.0.0", "body": "Missing operation"} +{"type": "update_release", "tag": "v1.0.0", "operation": "append"}`; + + fs.writeFileSync(testFile, ndjsonContent); + process.env.GH_AW_SAFE_OUTPUTS = testFile; + const __config = '{"update_release": {"max": 10}}'; + const configPath = "/tmp/gh-aw/safeoutputs/config.json"; + fs.mkdirSync("/tmp/gh-aw/safeoutputs", { recursive: true }); + fs.writeFileSync(configPath, __config); + + await eval(`(async () => { ${collectScript} })()`); + + const setOutputCalls = mockCore.setOutput.mock.calls; + const outputCall = setOutputCalls.find(call => call[0] === "output"); + expect(outputCall).toBeDefined(); + + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.items).toHaveLength(3); // Valid replace, prepend, and tag-omitted items + expect(parsedOutput.items[0].tag).toBe("v1.0.0"); + expect(parsedOutput.items[0].operation).toBe("replace"); + expect(parsedOutput.items[1].operation).toBe("prepend"); + expect(parsedOutput.items[2].tag).toBeUndefined(); // Tag omitted + expect(parsedOutput.items[2].operation).toBe("replace"); + expect(parsedOutput.items[0].body).toBeDefined(); + expect(parsedOutput.errors).toHaveLength(3); // 3 invalid items + expect(parsedOutput.errors.some(e => e.includes("operation' must be 'replace', 'append', or 'prepend'"))).toBe(true); + expect(parsedOutput.errors.some(e => e.includes("requires an 'operation' string field"))).toBe(true); + expect(parsedOutput.errors.some(e => e.includes("requires a 'body' string field"))).toBe(true); + }); + it("should respect max limits for create-pull-request-review-comment from config", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; const items = []; diff --git a/pkg/workflow/js/compute_text.cjs b/pkg/workflow/js/compute_text.cjs index 9a8d7065cf..ea057a396e 100644 --- a/pkg/workflow/js/compute_text.cjs +++ b/pkg/workflow/js/compute_text.cjs @@ -95,6 +95,57 @@ async function main() { } break; + case "release": + // For releases: name + body + if (context.payload.release) { + const name = context.payload.release.name || context.payload.release.tag_name || ""; + const body = context.payload.release.body || ""; + text = `${name}\n\n${body}`; + } + break; + + case "workflow_dispatch": + // For workflow dispatch: check for release_url or release_id in inputs + if (context.payload.inputs) { + const releaseUrl = context.payload.inputs.release_url; + const releaseId = context.payload.inputs.release_id; + + // If release_url is provided, extract owner/repo/tag + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/releases\/tag\/([^\/]+)/); + if (urlMatch) { + const [, urlOwner, urlRepo, tag] = urlMatch; + try { + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: urlOwner, + repo: urlRepo, + tag: tag, + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release from URL: ${error instanceof Error ? error.message : String(error)}`); + } + } + } else if (releaseId) { + // If release_id is provided, fetch the release + try { + const { data: release } = await github.rest.repos.getRelease({ + owner: owner, + repo: repo, + release_id: parseInt(releaseId, 10), + }); + const name = release.name || release.tag_name || ""; + const body = release.body || ""; + text = `${name}\n\n${body}`; + } catch (error) { + core.warning(`Failed to fetch release by ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + } + break; + default: // Default: empty text text = ""; diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 25d6740b63..cfd0dc8c03 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -229,6 +229,30 @@ "additionalProperties": false } }, + { + "name": "update_release", + "description": "Update a GitHub release description", + "inputSchema": { + "type": "object", + "required": ["operation", "body"], + "properties": { + "tag": { + "type": "string", + "description": "Release tag name (optional - inferred from event context if omitted)" + }, + "operation": { + "type": "string", + "enum": ["replace", "append", "prepend"], + "description": "Update operation: 'replace' (full replacement), 'append' (add at end with separator), or 'prepend' (add at start with separator)" + }, + "body": { + "type": "string", + "description": "Release body content to set, append, or prepend" + } + }, + "additionalProperties": false + } + }, { "name": "missing_tool", "description": "Report a missing tool or functionality needed to complete tasks", diff --git a/pkg/workflow/js/types/safe-outputs-config.d.ts b/pkg/workflow/js/types/safe-outputs-config.d.ts index 837a1c4ff6..cadc674d11 100644 --- a/pkg/workflow/js/types/safe-outputs-config.d.ts +++ b/pkg/workflow/js/types/safe-outputs-config.d.ts @@ -92,6 +92,13 @@ interface UploadAssetConfig extends SafeOutputConfig { "allowed-exts"?: string[]; } +/** + * Configuration for updating releases + */ +interface UpdateReleaseConfig extends SafeOutputConfig { + target?: string; +} + /** * Configuration for no-op output */ @@ -151,6 +158,7 @@ type SpecificSafeOutputConfig = | UpdateIssueConfig | PushToPullRequestBranchConfig | UploadAssetConfig + | UpdateReleaseConfig | NoOpConfig | MissingToolConfig | ThreatDetectionConfig; @@ -171,6 +179,7 @@ export { UpdateIssueConfig, PushToPullRequestBranchConfig, UploadAssetConfig, + UpdateReleaseConfig, NoOpConfig, MissingToolConfig, ThreatDetectionConfig, diff --git a/pkg/workflow/js/types/safe-outputs.d.ts b/pkg/workflow/js/types/safe-outputs.d.ts index 3e11a9314b..ebb40ffbed 100644 --- a/pkg/workflow/js/types/safe-outputs.d.ts +++ b/pkg/workflow/js/types/safe-outputs.d.ts @@ -158,6 +158,19 @@ interface UploadAssetItem extends BaseSafeOutputItem { file_path: string; } +/** + * JSONL item for updating a release + */ +interface UpdateReleaseItem extends BaseSafeOutputItem { + type: "update_release"; + /** Tag name of the release to update (optional - inferred from context if missing) */ + tag?: string; + /** Update operation: 'replace', 'append', or 'prepend' */ + operation: "replace" | "append" | "prepend"; + /** Content to set or append to the release body */ + body: string; +} + /** * JSONL item for no-op (logging only) */ @@ -182,6 +195,7 @@ type SafeOutputItem = | PushToPrBranchItem | MissingToolItem | UploadAssetItem + | UpdateReleaseItem | NoOpItem; /** @@ -206,6 +220,7 @@ export { PushToPrBranchItem, MissingToolItem, UploadAssetItem, + UpdateReleaseItem, NoOpItem, SafeOutputItem, SafeOutputItems, diff --git a/pkg/workflow/js/update_release.cjs b/pkg/workflow/js/update_release.cjs new file mode 100644 index 0000000000..dccde38ed5 --- /dev/null +++ b/pkg/workflow/js/update_release.cjs @@ -0,0 +1,171 @@ +// @ts-check +/// + +const { loadAgentOutput } = require("./load_agent_output.cjs"); +const { generateStagedPreview } = require("./staged_preview.cjs"); + +async function main() { + // Check if we're in staged mode + const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true"; + + const result = loadAgentOutput(); + if (!result.success) { + return; + } + + // Find all update-release items + const updateItems = result.items.filter(/** @param {any} item */ item => item.type === "update_release"); + if (updateItems.length === 0) { + core.info("No update-release items found in agent output"); + return; + } + + core.info(`Found ${updateItems.length} update-release item(s)`); + + // If in staged mode, emit step summary instead of updating releases + if (isStaged) { + await generateStagedPreview({ + title: "Update Releases", + description: "The following release updates would be applied if staged mode was disabled:", + items: updateItems, + renderItem: (item, index) => { + let content = `### Release Update ${index + 1}\n`; + content += `**Tag:** ${item.tag || "(inferred from event context)"}\n`; + content += `**Operation:** ${item.operation}\n\n`; + content += `**Body Content:**\n${item.body}\n\n`; + return content; + }, + }); + return; + } + + // Get workflow run URL for AI attribution + const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow"; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + + const updatedReleases = []; + + // Process each update item + for (let i = 0; i < updateItems.length; i++) { + const updateItem = updateItems[i]; + core.info(`Processing update-release item ${i + 1}/${updateItems.length}`); + + try { + // Infer tag from event context if not provided + let releaseTag = updateItem.tag; + if (!releaseTag) { + // Try to get tag from release event context + if (context.eventName === "release" && context.payload.release && context.payload.release.tag_name) { + releaseTag = context.payload.release.tag_name; + core.info(`Inferred release tag from event context: ${releaseTag}`); + } else if (context.eventName === "workflow_dispatch" && context.payload.inputs) { + // Try to extract from release_url input + const releaseUrl = context.payload.inputs.release_url; + if (releaseUrl) { + const urlMatch = releaseUrl.match(/github\.com\/[^\/]+\/[^\/]+\/releases\/tag\/([^\/\?#]+)/); + if (urlMatch && urlMatch[1]) { + releaseTag = decodeURIComponent(urlMatch[1]); + core.info(`Inferred release tag from release_url input: ${releaseTag}`); + } + } + // Try to fetch from release_id input + if (!releaseTag && context.payload.inputs.release_id) { + const releaseId = context.payload.inputs.release_id; + core.info(`Fetching release with ID: ${releaseId}`); + const { data: release } = await github.rest.repos.getRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: parseInt(releaseId, 10), + }); + releaseTag = release.tag_name; + core.info(`Inferred release tag from release_id input: ${releaseTag}`); + } + } + + if (!releaseTag) { + core.error("No tag provided and unable to infer from event context"); + core.setFailed("Release tag is required but not provided and cannot be inferred from event context"); + return; + } + } + + // Get the release by tag + core.info(`Fetching release with tag: ${releaseTag}`); + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: releaseTag, + }); + + core.info(`Found release: ${release.name || release.tag_name} (ID: ${release.id})`); + + // Determine new body based on operation + let newBody; + if (updateItem.operation === "replace") { + // Replace: just use the new content + newBody = updateItem.body; + core.info("Operation: replace (full body replacement)"); + } else if (updateItem.operation === "prepend") { + // Prepend: add content, AI footer, and horizontal line at the start + const aiFooter = `\n\n> AI generated by [${workflowName}](${runUrl})`; + const prependSection = `${updateItem.body}${aiFooter}\n\n---\n\n`; + newBody = prependSection + (release.body || ""); + core.info("Operation: prepend (add to start with separator)"); + } else { + // Append: add horizontal line, content, and AI footer at the end + const aiFooter = `\n\n> AI generated by [${workflowName}](${runUrl})`; + const appendSection = `\n\n---\n\n${updateItem.body}${aiFooter}`; + newBody = (release.body || "") + appendSection; + core.info("Operation: append (add to end with separator)"); + } + + // Update the release + const { data: updatedRelease } = await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id, + body: newBody, + }); + + core.info(`Successfully updated release: ${updatedRelease.html_url}`); + + updatedReleases.push({ + tag: releaseTag, + url: updatedRelease.html_url, + id: updatedRelease.id, + }); + + // Set outputs for the first release + if (i === 0) { + core.setOutput("release_id", updatedRelease.id); + core.setOutput("release_url", updatedRelease.html_url); + core.setOutput("release_tag", updatedRelease.tag_name); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + const tagInfo = updateItem.tag || "inferred from context"; + core.error(`Failed to update release with tag ${tagInfo}: ${errorMessage}`); + + // Check for specific error cases + if (errorMessage.includes("Not Found")) { + core.error(`Release with tag '${tagInfo}' not found. Please ensure the tag exists.`); + } + + core.setFailed(`Failed to update release: ${errorMessage}`); + return; + } + } + + // Generate step summary + let summaryContent = `## ✅ Release Updates Complete\n\n`; + summaryContent += `Updated ${updatedReleases.length} release(s):\n\n`; + + for (const rel of updatedReleases) { + summaryContent += `- **${rel.tag}**: [View Release](${rel.url})\n`; + } + + await core.summary.addRaw(summaryContent).write(); +} + +// Call the main function +await main(); diff --git a/pkg/workflow/js/update_release.test.cjs b/pkg/workflow/js/update_release.test.cjs new file mode 100644 index 0000000000..5c35a08cdd --- /dev/null +++ b/pkg/workflow/js/update_release.test.cjs @@ -0,0 +1,401 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import fs from "fs"; +import path from "path"; + +// Mock the global objects that GitHub Actions provides +const mockCore = { + debug: vi.fn(), + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + setFailed: vi.fn(), + setOutput: vi.fn(), + summary: { + addRaw: vi.fn().mockReturnThis(), + write: vi.fn().mockResolvedValue(), + }, +}; + +const mockGithub = { + rest: { + repos: { + getReleaseByTag: vi.fn(), + updateRelease: vi.fn(), + }, + }, +}; + +const mockContext = { + repo: { + owner: "test-owner", + repo: "test-repo", + }, + serverUrl: "https://github.com", + runId: 123456, +}; + +// Set up global mocks before importing the module +global.core = mockCore; +global.github = mockGithub; +global.context = mockContext; + +describe("update_release", () => { + let updateReleaseScript; + let tempFilePath; + + // Helper function to set agent output via file + const setAgentOutput = data => { + tempFilePath = path.join("/tmp", `test_agent_output_${Date.now()}_${Math.random().toString(36).slice(2)}.json`); + const content = typeof data === "string" ? data : JSON.stringify(data); + fs.writeFileSync(tempFilePath, content); + process.env.GH_AW_AGENT_OUTPUT = tempFilePath; + }; + + beforeEach(() => { + // Reset mocks before each test + vi.clearAllMocks(); + delete process.env.GH_AW_SAFE_OUTPUTS_STAGED; + delete process.env.GH_AW_AGENT_OUTPUT; + delete process.env.GH_AW_WORKFLOW_NAME; + + // Read the script + const scriptPath = path.join(__dirname, "update_release.cjs"); + updateReleaseScript = fs.readFileSync(scriptPath, "utf8"); + }); + + afterEach(() => { + // Clean up temporary file + if (tempFilePath && fs.existsSync(tempFilePath)) { + fs.unlinkSync(tempFilePath); + tempFilePath = undefined; + } + }); + + it("should handle empty agent output", async () => { + setAgentOutput({ items: [], errors: [] }); + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockCore.info).toHaveBeenCalledWith("No update-release items found in agent output"); + expect(mockGithub.rest.repos.getReleaseByTag).not.toHaveBeenCalled(); + }); + + it("should handle replace operation", async () => { + const mockRelease = { + id: 1, + tag_name: "v1.0.0", + name: "Release v1.0.0", + body: "Old release notes", + html_url: "https://github.com/test-owner/test-repo/releases/tag/v1.0.0", + }; + + const mockUpdatedRelease = { + ...mockRelease, + body: "New release notes", + }; + + mockGithub.rest.repos.getReleaseByTag.mockResolvedValue({ data: mockRelease }); + mockGithub.rest.repos.updateRelease.mockResolvedValue({ data: mockUpdatedRelease }); + + setAgentOutput({ + items: [ + { + type: "update_release", + tag: "v1.0.0", + operation: "replace", + body: "New release notes", + }, + ], + errors: [], + }); + process.env.GH_AW_WORKFLOW_NAME = "Test Workflow"; + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockGithub.rest.repos.getReleaseByTag).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + tag: "v1.0.0", + }); + + expect(mockGithub.rest.repos.updateRelease).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + release_id: 1, + body: "New release notes", + }); + + expect(mockCore.setOutput).toHaveBeenCalledWith("release_id", 1); + expect(mockCore.setOutput).toHaveBeenCalledWith("release_url", mockUpdatedRelease.html_url); + expect(mockCore.setOutput).toHaveBeenCalledWith("release_tag", "v1.0.0"); + expect(mockCore.summary.addRaw).toHaveBeenCalled(); + }); + + it("should handle append operation", async () => { + const mockRelease = { + id: 2, + tag_name: "v2.0.0", + name: "Release v2.0.0", + body: "Original release notes", + html_url: "https://github.com/test-owner/test-repo/releases/tag/v2.0.0", + }; + + const expectedBody = + "Original release notes\n\n---\n\nAdditional notes\n\n> AI generated by [Test Workflow](https://github.com/test-owner/test-repo/actions/runs/123456)"; + + const mockUpdatedRelease = { + ...mockRelease, + body: expectedBody, + }; + + mockGithub.rest.repos.getReleaseByTag.mockResolvedValue({ data: mockRelease }); + mockGithub.rest.repos.updateRelease.mockResolvedValue({ data: mockUpdatedRelease }); + + setAgentOutput({ + items: [ + { + type: "update_release", + tag: "v2.0.0", + operation: "append", + body: "Additional notes", + }, + ], + errors: [], + }); + process.env.GH_AW_WORKFLOW_NAME = "Test Workflow"; + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockGithub.rest.repos.updateRelease).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + release_id: 2, + body: expectedBody, + }); + + expect(mockCore.info).toHaveBeenCalledWith("Operation: append (add to end with separator)"); + }); + + it("should handle prepend operation", async () => { + const mockRelease = { + id: 3, + tag_name: "v3.0.0", + name: "Release v3.0.0", + body: "Existing release notes", + html_url: "https://github.com/test-owner/test-repo/releases/tag/v3.0.0", + }; + + const expectedBody = + "Prepended notes\n\n> AI generated by [Test Workflow](https://github.com/test-owner/test-repo/actions/runs/123456)\n\n---\n\nExisting release notes"; + + const mockUpdatedRelease = { + ...mockRelease, + body: expectedBody, + }; + + mockGithub.rest.repos.getReleaseByTag.mockResolvedValue({ data: mockRelease }); + mockGithub.rest.repos.updateRelease.mockResolvedValue({ data: mockUpdatedRelease }); + + setAgentOutput({ + items: [ + { + type: "update_release", + tag: "v3.0.0", + operation: "prepend", + body: "Prepended notes", + }, + ], + errors: [], + }); + process.env.GH_AW_WORKFLOW_NAME = "Test Workflow"; + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockGithub.rest.repos.updateRelease).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + release_id: 3, + body: expectedBody, + }); + + expect(mockCore.info).toHaveBeenCalledWith("Operation: prepend (add to start with separator)"); + }); + + it("should handle staged mode", async () => { + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "true"; + setAgentOutput({ + items: [ + { + type: "update_release", + tag: "v1.0.0", + operation: "replace", + body: "New notes", + }, + ], + errors: [], + }); + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockCore.summary.addRaw).toHaveBeenCalled(); + expect(mockGithub.rest.repos.getReleaseByTag).not.toHaveBeenCalled(); + expect(mockGithub.rest.repos.updateRelease).not.toHaveBeenCalled(); + }); + + it("should handle release not found error", async () => { + mockGithub.rest.repos.getReleaseByTag.mockRejectedValue(new Error("Not Found")); + + setAgentOutput({ + items: [ + { + type: "update_release", + tag: "v99.99.99", + operation: "replace", + body: "New notes", + }, + ], + errors: [], + }); + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("Failed to update release")); + expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("not found")); + expect(mockCore.setFailed).toHaveBeenCalled(); + }); + + it("should handle multiple release updates", async () => { + const mockRelease1 = { + id: 1, + tag_name: "v1.0.0", + body: "Release 1", + html_url: "https://github.com/test-owner/test-repo/releases/tag/v1.0.0", + }; + + const mockRelease2 = { + id: 2, + tag_name: "v2.0.0", + body: "Release 2", + html_url: "https://github.com/test-owner/test-repo/releases/tag/v2.0.0", + }; + + mockGithub.rest.repos.getReleaseByTag.mockResolvedValueOnce({ data: mockRelease1 }).mockResolvedValueOnce({ data: mockRelease2 }); + + mockGithub.rest.repos.updateRelease + .mockResolvedValueOnce({ data: { ...mockRelease1, body: "Updated 1" } }) + .mockResolvedValueOnce({ data: { ...mockRelease2, body: "Updated 2" } }); + + setAgentOutput({ + items: [ + { + type: "update_release", + tag: "v1.0.0", + operation: "replace", + body: "Updated 1", + }, + { + type: "update_release", + tag: "v2.0.0", + operation: "replace", + body: "Updated 2", + }, + ], + errors: [], + }); + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockGithub.rest.repos.getReleaseByTag).toHaveBeenCalledTimes(2); + expect(mockGithub.rest.repos.updateRelease).toHaveBeenCalledTimes(2); + expect(mockCore.summary.addRaw).toHaveBeenCalledWith(expect.stringContaining("Updated 2 release(s)")); + }); + + it("should infer tag from release event context", async () => { + // Set up release event context + mockContext.eventName = "release"; + mockContext.payload = { + release: { + tag_name: "v1.5.0", + name: "Version 1.5.0", + body: "Original release body", + }, + }; + + const mockRelease = { + id: 1, + tag_name: "v1.5.0", + body: "Original release body", + html_url: "https://github.com/test-owner/test-repo/releases/tag/v1.5.0", + }; + + mockGithub.rest.repos.getReleaseByTag.mockResolvedValue({ data: mockRelease }); + mockGithub.rest.repos.updateRelease.mockResolvedValue({ data: { ...mockRelease, body: "Updated body" } }); + + // Agent output without tag field + setAgentOutput({ + items: [ + { + type: "update_release", + operation: "replace", + body: "Updated body", + }, + ], + errors: [], + }); + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Inferred release tag from event context: v1.5.0")); + expect(mockGithub.rest.repos.getReleaseByTag).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + tag: "v1.5.0", + }); + expect(mockGithub.rest.repos.updateRelease).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + release_id: 1, + body: "Updated body", + }); + + // Clean up + delete mockContext.eventName; + delete mockContext.payload; + }); + + it("should fail gracefully when tag is missing and cannot be inferred", async () => { + // Set up context without release info + mockContext.eventName = "push"; + mockContext.payload = {}; + + // Agent output without tag field + setAgentOutput({ + items: [ + { + type: "update_release", + operation: "replace", + body: "Updated body", + }, + ], + errors: [], + }); + + // Execute the script + await eval(`(async () => { ${updateReleaseScript} })()`); + + expect(mockCore.error).toHaveBeenCalledWith("No tag provided and unable to infer from event context"); + expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("Release tag is required")); + + // Clean up + delete mockContext.eventName; + delete mockContext.payload; + }); +}); diff --git a/pkg/workflow/safe_outputs.go b/pkg/workflow/safe_outputs.go index a3b6987369..bb29a61d3a 100644 --- a/pkg/workflow/safe_outputs.go +++ b/pkg/workflow/safe_outputs.go @@ -403,6 +403,12 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut config.UploadAssets = uploadAssetsConfig } + // Handle update-release + updateReleaseConfig := c.parseUpdateReleaseConfig(outputMap) + if updateReleaseConfig != nil { + config.UpdateRelease = updateReleaseConfig + } + // Handle missing-tool (parse configuration if present, or enable by default) missingToolConfig := c.parseMissingToolConfig(outputMap) if missingToolConfig != nil { @@ -834,6 +840,20 @@ func generateSafeOutputsConfig(data *WorkflowData) string { } safeOutputsConfig["update_project"] = updateProjectConfig } + if data.SafeOutputs.UpdateRelease != nil { + updateReleaseConfig := map[string]any{} + if data.SafeOutputs.UpdateRelease.Max > 0 { + updateReleaseConfig["max"] = data.SafeOutputs.UpdateRelease.Max + } + safeOutputsConfig["update_release"] = updateReleaseConfig + } + if data.SafeOutputs.NoOp != nil { + noopConfig := map[string]any{} + if data.SafeOutputs.NoOp.Max > 0 { + noopConfig["max"] = data.SafeOutputs.NoOp.Max + } + safeOutputsConfig["noop"] = noopConfig + } } // Add safe-jobs configuration from SafeOutputs.Jobs @@ -937,6 +957,9 @@ func generateFilteredToolsJSON(data *WorkflowData) (string, error) { if data.SafeOutputs.MissingTool != nil { enabledTools["missing_tool"] = true } + if data.SafeOutputs.UpdateRelease != nil { + enabledTools["update_release"] = true + } if data.SafeOutputs.NoOp != nil { enabledTools["noop"] = true } diff --git a/pkg/workflow/safe_outputs_tools_test.go b/pkg/workflow/safe_outputs_tools_test.go index 2bb66531bc..843f75f336 100644 --- a/pkg/workflow/safe_outputs_tools_test.go +++ b/pkg/workflow/safe_outputs_tools_test.go @@ -277,6 +277,7 @@ func TestGetSafeOutputsToolsJSON(t *testing.T) { "update_issue", "push_to_pull_request_branch", "upload_asset", + "update_release", "missing_tool", "noop", } diff --git a/pkg/workflow/scripts.go b/pkg/workflow/scripts.go index 587c796528..f5613567be 100644 --- a/pkg/workflow/scripts.go +++ b/pkg/workflow/scripts.go @@ -32,6 +32,9 @@ var createDiscussionScriptSource string //go:embed js/update_issue.cjs var updateIssueScriptSource string +//go:embed js/update_release.cjs +var updateReleaseScriptSource string + //go:embed js/create_code_scanning_alert.cjs var createCodeScanningAlertScriptSource string @@ -90,6 +93,9 @@ var ( updateIssueScript string updateIssueScriptOnce sync.Once + updateReleaseScript string + updateReleaseScriptOnce sync.Once + createCodeScanningAlertScript string createCodeScanningAlertScriptOnce sync.Once @@ -265,6 +271,23 @@ func getUpdateIssueScript() string { return updateIssueScript } +// getUpdateReleaseScript returns the bundled update_release script +// Bundling is performed on first access and cached for subsequent calls +func getUpdateReleaseScript() string { + updateReleaseScriptOnce.Do(func() { + sources := GetJavaScriptSources() + bundled, err := BundleJavaScriptFromSources(updateReleaseScriptSource, sources, "") + if err != nil { + scriptsLog.Printf("Bundling failed for update_release, using source as-is: %v", err) + // If bundling fails, use the source as-is + updateReleaseScript = updateReleaseScriptSource + } else { + updateReleaseScript = bundled + } + }) + return updateReleaseScript +} + // getCreateCodeScanningAlertScript returns the bundled create_code_scanning_alert script // Bundling is performed on first access and cached for subsequent calls func getCreateCodeScanningAlertScript() string { diff --git a/pkg/workflow/update_release.go b/pkg/workflow/update_release.go new file mode 100644 index 0000000000..27ced9ee2d --- /dev/null +++ b/pkg/workflow/update_release.go @@ -0,0 +1,81 @@ +package workflow + +import ( + "fmt" +) + +// UpdateReleaseConfig holds configuration for updating GitHub releases from agent output +type UpdateReleaseConfig struct { + BaseSafeOutputConfig `yaml:",inline"` + TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository for cross-repo operations +} + +// buildCreateOutputUpdateReleaseJob creates the update_release job using the shared builder +func (c *Compiler) buildCreateOutputUpdateReleaseJob(data *WorkflowData, mainJobName string) (*Job, error) { + if data.SafeOutputs == nil || data.SafeOutputs.UpdateRelease == nil { + return nil, fmt.Errorf("safe-outputs.update-release configuration is required") + } + + // Build custom environment variables specific to update-release + var customEnvVars []string + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_WORKFLOW_NAME: %q\n", data.Name)) + + // Add common safe output job environment variables (staged/target repo) + customEnvVars = append(customEnvVars, buildSafeOutputJobEnvVars( + c.trialMode, + c.trialLogicalRepoSlug, + data.SafeOutputs.Staged, + data.SafeOutputs.UpdateRelease.TargetRepoSlug, + )...) + + // Get token from config + var token string + if data.SafeOutputs.UpdateRelease != nil { + token = data.SafeOutputs.UpdateRelease.GitHubToken + } + + // Create outputs for the job + outputs := map[string]string{ + "release_id": "${{ steps.update_release.outputs.release_id }}", + "release_url": "${{ steps.update_release.outputs.release_url }}", + "release_tag": "${{ steps.update_release.outputs.release_tag }}", + } + + // Use the shared builder function to create the job + return c.buildSafeOutputJob(data, SafeOutputJobConfig{ + JobName: "update_release", + StepName: "Update Release", + StepID: "update_release", + MainJobName: mainJobName, + CustomEnvVars: customEnvVars, + Script: getUpdateReleaseScript(), + Permissions: NewPermissionsContentsWrite(), + Outputs: outputs, + Token: token, + TargetRepoSlug: data.SafeOutputs.UpdateRelease.TargetRepoSlug, + }) +} + +// parseUpdateReleaseConfig handles update-release configuration +func (c *Compiler) parseUpdateReleaseConfig(outputMap map[string]any) *UpdateReleaseConfig { + if configData, exists := outputMap["update-release"]; exists { + updateReleaseConfig := &UpdateReleaseConfig{} + updateReleaseConfig.Max = 1 // Default max is 1 + + if configMap, ok := configData.(map[string]any); ok { + // Parse common base fields with default max of 1 + c.parseBaseSafeOutputConfig(configMap, &updateReleaseConfig.BaseSafeOutputConfig, 1) + + // Parse target-repo using shared helper + targetRepoSlug := parseTargetRepoFromConfig(configMap) + updateReleaseConfig.TargetRepoSlug = targetRepoSlug + } else { + // If configData is nil or not a map, still set the default max + updateReleaseConfig.Max = 1 + } + + return updateReleaseConfig + } + + return nil +} diff --git a/schemas/agent-output.json b/schemas/agent-output.json index f8de40955b..7a6e03f83b 100644 --- a/schemas/agent-output.json +++ b/schemas/agent-output.json @@ -38,6 +38,7 @@ {"$ref": "#/$defs/MissingToolOutput"}, {"$ref": "#/$defs/CreateCodeScanningAlertOutput"}, {"$ref": "#/$defs/UpdateProjectOutput"}, + {"$ref": "#/$defs/UpdateReleaseOutput"}, {"$ref": "#/$defs/NoOpOutput"} ] }, @@ -359,6 +360,33 @@ "required": ["type", "project"], "additionalProperties": false }, + "UpdateReleaseOutput": { + "title": "Update Release Output", + "description": "Output for updating a GitHub release description", + "type": "object", + "properties": { + "type": { + "const": "update_release" + }, + "tag": { + "type": "string", + "description": "Tag name of the release to update", + "minLength": 1 + }, + "operation": { + "type": "string", + "description": "Update operation: 'replace', 'append', or 'prepend'", + "enum": ["replace", "append", "prepend"] + }, + "body": { + "type": "string", + "description": "Content to set or append to the release body", + "minLength": 1 + } + }, + "required": ["type", "operation", "body"], + "additionalProperties": false + }, "NoOpOutput": { "title": "No-Op Output", "description": "Output for logging a message without taking any GitHub actions. Always available as a fallback to ensure human-visible artifacts.",