diff --git a/.github/workflows/ai-triage-campaign.lock.yml b/.github/workflows/ai-triage-campaign.lock.yml index ba71c6a992..b55a139fe7 100644 --- a/.github/workflows/ai-triage-campaign.lock.yml +++ b/.github/workflows/ai-triage-campaign.lock.yml @@ -1919,6 +1919,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1937,6 +1938,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2361,6 +2364,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index bd00f378a6..8b1f59b0b2 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -2991,6 +2991,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3009,6 +3010,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3433,6 +3436,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index 872f613fe6..ff4cc41e11 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -1817,6 +1817,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1835,6 +1836,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2259,6 +2262,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 0cd41d04e0..ae5613b9ab 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -3232,6 +3232,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3250,6 +3251,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3674,6 +3677,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index 6a37190fce..2ada1a86be 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -2361,6 +2361,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2379,6 +2380,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2803,6 +2806,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 8a41dcf193..baf3c9428f 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -2748,6 +2748,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2766,6 +2767,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3190,6 +3193,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 086b66b106..93e6225cc7 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -2599,6 +2599,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2617,6 +2618,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3041,6 +3044,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 5198580260..030237f3fe 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -2273,6 +2273,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2291,6 +2292,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2715,6 +2718,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 8eb491f11c..6236f1111b 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -1841,6 +1841,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1859,6 +1860,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2283,6 +2286,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index b3d945b327..51fe5fdf64 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -2170,6 +2170,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2188,6 +2189,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2612,6 +2615,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 773e1dfd71..665a0407fd 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -3442,6 +3442,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3460,6 +3461,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3884,6 +3887,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index 1d6d46da95..ebb4a52a20 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -2260,6 +2260,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2278,6 +2279,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2702,6 +2705,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index abb91a640c..09dbd767c9 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -2996,6 +2996,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3014,6 +3015,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3438,6 +3441,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 0c757a99ee..b5d5d0559f 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -3310,6 +3310,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3328,6 +3329,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3752,6 +3755,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index bdc2d77d9e..05e523ee38 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -2479,6 +2479,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2497,6 +2498,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2921,6 +2924,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index c84896d3eb..02e39a9e91 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -4699,6 +4699,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -4717,6 +4718,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -5141,6 +5144,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 1d02ee9a67..821f368963 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -3050,6 +3050,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3068,6 +3069,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3492,6 +3495,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index 60dfe534a0..6805ca97b4 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -3393,6 +3393,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3411,6 +3412,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3835,6 +3838,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 635d16e522..4e8a0aed5e 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -2043,6 +2043,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2061,6 +2062,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2485,6 +2488,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index 8d7ab1d1be..6607524ecc 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -2129,6 +2129,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2147,6 +2148,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2571,6 +2574,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 16974e2e68..85e5f461ad 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -2807,6 +2807,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2825,6 +2826,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3249,6 +3252,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index 02ac897d18..dfbb5d90cd 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -1901,6 +1901,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1919,6 +1920,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2343,6 +2346,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 3e45211aea..2401580772 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -3175,6 +3175,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3193,6 +3194,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3617,6 +3620,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index d75416732b..2bb57152b7 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -2902,6 +2902,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2920,6 +2921,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3344,6 +3347,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index 49de9a2528..3cefb6e7b9 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -1664,6 +1664,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1682,6 +1683,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2106,6 +2109,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { @@ -4500,7 +4519,7 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - timeout-minutes: 5 + timeout-minutes: 10 outputs: tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index 4af3537e39..015a7b0147 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -2310,6 +2310,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2328,6 +2329,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2752,6 +2755,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index b70f15ee61..7e265b62b1 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -2117,6 +2117,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2135,6 +2136,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2559,6 +2562,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 7e77bf6dcf..538d8bbb83 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1502,6 +1502,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1520,6 +1521,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -1944,6 +1947,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 4c73777cb8..6bb167523c 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -3091,6 +3091,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3109,6 +3110,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3533,6 +3536,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index f100614865..0bc5ff0b8f 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -1776,6 +1776,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1794,6 +1795,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2218,6 +2221,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index aaab8a7b9d..a862798a74 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -1847,6 +1847,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1865,6 +1866,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2289,6 +2292,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index bbac089d90..04acadca8e 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -1934,6 +1934,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1952,6 +1953,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2376,6 +2379,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index 30b4f1f8f5..e0d402928f 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -1840,6 +1840,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1858,6 +1859,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2282,6 +2285,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index 719cd01c90..8dd89bbc33 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -2785,6 +2785,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2803,6 +2804,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3227,6 +3230,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index e808912118..eb26832e1a 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -2848,6 +2848,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2866,6 +2867,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3290,6 +3293,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 53e97b0c47..0ae082b2c6 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -2261,6 +2261,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2279,6 +2280,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2703,6 +2706,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index 02b9a01109..f5c8ca8f15 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -1913,6 +1913,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1931,6 +1932,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2355,6 +2358,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/go.mod b/.github/workflows/go.mod index 189adb4402..0eed60d348 100644 --- a/.github/workflows/go.mod +++ b/.github/workflows/go.mod @@ -1,3 +1,5 @@ module github.com/githubnext/gh-aw-workflows-deps - go 1.21 + +require ( +) diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index 6ba86a4763..f412cd8640 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -2827,6 +2827,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2845,6 +2846,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3269,6 +3272,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 92e526d712..7ef578f2c5 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -2040,6 +2040,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2058,6 +2059,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2482,6 +2485,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 477375177d..e675f95697 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -2407,6 +2407,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2425,6 +2426,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2849,6 +2852,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 321a33d36d..2a4cfae8d3 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -2500,6 +2500,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2518,6 +2519,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2942,6 +2945,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 0bf885dda6..b99dc2c1d3 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -2787,6 +2787,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2805,6 +2806,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3229,6 +3232,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index bdfad48ffc..83c7fa6f0e 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -2474,6 +2474,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2492,6 +2493,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2916,6 +2919,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index 77c5a41c9e..05c984f5dc 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -1522,6 +1522,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1540,6 +1541,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -1964,6 +1967,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index e6d0432375..b568ff6792 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -2895,6 +2895,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2913,6 +2914,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3337,6 +3340,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 1d7537be6c..8edb72dfaf 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -2349,6 +2349,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2367,6 +2368,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2791,6 +2794,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 4b6c2b83ed..a1a614c376 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -3066,6 +3066,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3084,6 +3085,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3508,6 +3511,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index 7c10883c29..3ccffb39fd 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -3191,6 +3191,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3209,6 +3210,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3633,6 +3636,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 8d34e5a371..6898eea239 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -3702,6 +3702,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3720,6 +3721,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -4144,6 +4147,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 0948045eaa..3eff3da894 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -3546,6 +3546,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3564,6 +3565,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3988,6 +3991,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 606d70a086..5bf1e70723 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -3467,6 +3467,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3485,6 +3486,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3909,6 +3912,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/release-highlights.lock.yml b/.github/workflows/release-highlights.lock.yml index 4dc3d5198d..48f28a77fc 100644 --- a/.github/workflows/release-highlights.lock.yml +++ b/.github/workflows/release-highlights.lock.yml @@ -1922,6 +1922,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1940,6 +1941,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2364,6 +2367,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index 2a90410b0c..d327f6f689 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -1856,6 +1856,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1874,6 +1875,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2298,6 +2301,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index eb7611c2fb..70282da654 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -2795,6 +2795,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2813,6 +2814,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3237,6 +3240,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 074027b264..784fc2e26f 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -1702,6 +1702,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1720,6 +1721,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2144,6 +2147,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 3000041533..1f85da6365 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -2724,6 +2724,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2742,6 +2743,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3166,6 +3169,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 9ff2027d37..31b08b9fc5 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -2510,6 +2510,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2528,6 +2529,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2952,6 +2955,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index ee76535c26..6f05abc45a 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -3562,6 +3562,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3580,6 +3581,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -4004,6 +4007,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index 5a722e0bf0..c530d5d605 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -1986,6 +1986,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2004,6 +2005,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2428,6 +2431,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 351a5c3c27..97ccc422c2 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -2747,6 +2747,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2765,6 +2766,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3189,6 +3192,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index e1b09d2acb..ff01806bb5 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -2374,6 +2374,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2392,6 +2393,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2816,6 +2819,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 82d6312080..6f623b8279 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1938,6 +1938,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1956,6 +1957,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2380,6 +2383,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 96c56016f3..0c2a89d936 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1969,6 +1969,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1987,6 +1988,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2411,6 +2414,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index 42100402fa..a318707a7f 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -3128,6 +3128,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3146,6 +3147,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3570,6 +3573,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index 04d58bb99c..83a62eb248 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -2530,6 +2530,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2548,6 +2549,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2972,6 +2975,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index ec6a079459..840b851614 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -1930,6 +1930,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1948,6 +1949,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2372,6 +2375,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 75a8e0d091..c8b5a1b0f1 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -2808,6 +2808,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2826,6 +2827,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3250,6 +3253,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/test-assign-milestone-allowed.lock.yml b/.github/workflows/test-assign-milestone-allowed.lock.yml index 1cd8ced14e..a5a02b97c7 100644 --- a/.github/workflows/test-assign-milestone-allowed.lock.yml +++ b/.github/workflows/test-assign-milestone-allowed.lock.yml @@ -1627,6 +1627,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1645,6 +1646,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2069,6 +2072,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/test-claude-assign-milestone.lock.yml b/.github/workflows/test-claude-assign-milestone.lock.yml index eaf6ccebd8..2ac0635b59 100644 --- a/.github/workflows/test-claude-assign-milestone.lock.yml +++ b/.github/workflows/test-claude-assign-milestone.lock.yml @@ -1615,6 +1615,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1633,6 +1634,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2057,6 +2060,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/test-close-discussion.lock.yml b/.github/workflows/test-close-discussion.lock.yml index a847896811..46309cf476 100644 --- a/.github/workflows/test-close-discussion.lock.yml +++ b/.github/workflows/test-close-discussion.lock.yml @@ -1493,6 +1493,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1511,6 +1512,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -1935,6 +1938,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/test-codex-assign-milestone.lock.yml b/.github/workflows/test-codex-assign-milestone.lock.yml index 63cd477f98..a779eea6ab 100644 --- a/.github/workflows/test-codex-assign-milestone.lock.yml +++ b/.github/workflows/test-codex-assign-milestone.lock.yml @@ -1433,6 +1433,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1451,6 +1452,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -1875,6 +1878,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/test-copilot-assign-milestone.lock.yml b/.github/workflows/test-copilot-assign-milestone.lock.yml index 065e879fe4..2df7ce4966 100644 --- a/.github/workflows/test-copilot-assign-milestone.lock.yml +++ b/.github/workflows/test-copilot-assign-milestone.lock.yml @@ -1456,6 +1456,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1474,6 +1475,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -1898,6 +1901,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/test-ollama-threat-detection.lock.yml b/.github/workflows/test-ollama-threat-detection.lock.yml index 06bbbbcdf8..b2b056690e 100644 --- a/.github/workflows/test-ollama-threat-detection.lock.yml +++ b/.github/workflows/test-ollama-threat-detection.lock.yml @@ -1466,6 +1466,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -1484,6 +1485,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -1908,6 +1911,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 10866c5b72..6190c6baca 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -2060,6 +2060,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2078,6 +2079,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2502,6 +2505,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index e2db22a03f..dd442a671f 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -2805,6 +2805,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2823,6 +2824,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3247,6 +3250,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 90bfeb4926..0517658d30 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -3215,6 +3215,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -3233,6 +3234,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3657,6 +3660,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index 976776da2e..53ac5265d3 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -2016,6 +2016,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2034,6 +2035,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -2458,6 +2461,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 63cd68b1f7..4128250b0b 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -2765,6 +2765,7 @@ jobs: } } const maxBodyLength = 65000; + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -2783,6 +2784,8 @@ jobs: return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -3207,6 +3210,22 @@ jobs: } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index d687265d42..3edbf6639a 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -1458,6 +1458,43 @@ safe-outputs: # (optional) # This field supports multiple formats (oneOf): + # Option 1: Configuration for closing GitHub issues with comment from agentic + # workflow output + close-issue: + # Only close issues that have all of these labels + # (optional) + required-labels: [] + # Array of strings + + # Only close issues with this title prefix + # (optional) + required-title-prefix: "example-value" + + # Target for closing: 'triggering' (default, current issue), or '*' (any issue + # with issue_number field) + # (optional) + target: "example-value" + + # Maximum number of issues to close (default: 1) + # (optional) + max: 1 + + # Target repository in format 'owner/repo' for cross-repository operations. Takes + # precedence over trial target repo settings. + # (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 issue closing with default configuration + close-issue: null + + # (optional) + # This field supports multiple formats (oneOf): + # Option 1: Configuration for automatically creating GitHub issue or pull request # comments from AI workflow output. The main job does not need write permissions. add-comment: @@ -1638,6 +1675,39 @@ safe-outputs: # (optional) # This field supports multiple formats (oneOf): + # Option 1: Null configuration allows any reviewers + add-reviewer: null + + # Option 2: Configuration for adding reviewers to pull requests from agentic + # workflow output + add-reviewer: + # Optional list of allowed reviewers. If omitted, any reviewers are allowed. + # (optional) + reviewers: [] + # Array of strings + + # Optional maximum number of reviewers to add (default: 3) + # (optional) + max: 1 + + # Target for reviewers: 'triggering' (default), '*' (any PR), or explicit PR + # number + # (optional) + target: "example-value" + + # Target repository in format 'owner/repo' for cross-repository reviewer addition. + # Takes precedence over trial target repo settings. + # (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 }}" + + # (optional) + # This field supports multiple formats (oneOf): + # Option 1: Null configuration allows assigning any milestones assign-milestone: null diff --git a/pkg/cli/workflows/test-copilot-add-reviewer.md b/pkg/cli/workflows/test-copilot-add-reviewer.md new file mode 100644 index 0000000000..6af272c492 --- /dev/null +++ b/pkg/cli/workflows/test-copilot-add-reviewer.md @@ -0,0 +1,23 @@ +--- +on: + pull_request: + types: [opened] +permissions: + contents: read + actions: read +engine: copilot +safe-outputs: + add-reviewer: + max: 3 +timeout-minutes: 5 +--- + +# Test Add Reviewer Safe Output + +Test the add-reviewer safe output functionality. + +Add reviewers to the pull request using the add_reviewer tool: +- Add "octocat" as a reviewer +- Add "github" as a reviewer + +Output as JSONL format. diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index d934330e08..1836c75bb3 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2837,6 +2837,46 @@ } ] }, + "add-reviewer": { + "oneOf": [ + { + "type": "null", + "description": "Null configuration allows any reviewers" + }, + { + "type": "object", + "description": "Configuration for adding reviewers to pull requests from agentic workflow output", + "properties": { + "reviewers": { + "type": "array", + "description": "Optional list of allowed reviewers. If omitted, any reviewers are allowed.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "max": { + "type": "integer", + "description": "Optional maximum number of reviewers to add (default: 3)", + "minimum": 1 + }, + "target": { + "type": "string", + "description": "Target for reviewers: 'triggering' (default), '*' (any PR), or explicit PR number" + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository reviewer addition. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + } + ] + }, "assign-milestone": { "oneOf": [ { diff --git a/pkg/workflow/add_reviewer.go b/pkg/workflow/add_reviewer.go new file mode 100644 index 0000000000..a86f07fe9f --- /dev/null +++ b/pkg/workflow/add_reviewer.go @@ -0,0 +1,139 @@ +package workflow + +import ( + "fmt" + "strings" +) + +// AddReviewerConfig holds configuration for adding reviewers to PRs from agent output +type AddReviewerConfig struct { + Reviewers []string `yaml:"reviewers,omitempty"` // Optional list of allowed reviewers. If omitted, any reviewers are allowed. + Max int `yaml:"max,omitempty"` // Optional maximum number of reviewers to add (default: 3) + GitHubToken string `yaml:"github-token,omitempty"` // GitHub token for this specific output type + Target string `yaml:"target,omitempty"` // Target for reviewers: "triggering" (default), "*" (any PR), or explicit PR number + TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository reviewers +} + +// buildAddReviewerJob creates the add_reviewer job +func (c *Compiler) buildAddReviewerJob(data *WorkflowData, mainJobName string) (*Job, error) { + if data.SafeOutputs == nil || data.SafeOutputs.AddReviewer == nil { + return nil, fmt.Errorf("safe-outputs configuration is required") + } + + // Handle case where AddReviewer configuration is provided + var allowedReviewers []string + maxCount := 3 + + if data.SafeOutputs.AddReviewer != nil { + allowedReviewers = data.SafeOutputs.AddReviewer.Reviewers + if data.SafeOutputs.AddReviewer.Max > 0 { + maxCount = data.SafeOutputs.AddReviewer.Max + } + } + + // Build custom environment variables specific to add-reviewer + var customEnvVars []string + // Pass the allowed reviewers list (empty string if no restrictions) + allowedReviewersStr := strings.Join(allowedReviewers, ",") + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_REVIEWERS_ALLOWED: %q\n", allowedReviewersStr)) + // Pass the max limit + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_REVIEWERS_MAX_COUNT: %d\n", maxCount)) + + // Pass the target configuration + if data.SafeOutputs.AddReviewer != nil && data.SafeOutputs.AddReviewer.Target != "" { + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_REVIEWERS_TARGET: %q\n", data.SafeOutputs.AddReviewer.Target)) + } + + // Add standard environment variables (metadata + staged/target repo) + customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, data.SafeOutputs.AddReviewer.TargetRepoSlug)...) + + // Create outputs for the job + outputs := map[string]string{ + "reviewers_added": "${{ steps.add_reviewer.outputs.reviewers_added }}", + } + + var jobCondition = BuildSafeOutputType("add_reviewer") + if data.SafeOutputs.AddReviewer.Target == "" { + // Only run if in PR context when target is not specified + prCondition := BuildPropertyAccess("github.event.pull_request.number") + jobCondition = buildAnd(jobCondition, prCondition) + } + + // Use the shared builder function to create the job + return c.buildSafeOutputJob(data, SafeOutputJobConfig{ + JobName: "add_reviewer", + StepName: "Add Reviewers", + StepID: "add_reviewer", + MainJobName: mainJobName, + CustomEnvVars: customEnvVars, + Script: getAddReviewerScript(), + Permissions: NewPermissionsContentsReadPRWrite(), + Outputs: outputs, + Condition: jobCondition, + Token: data.SafeOutputs.AddReviewer.GitHubToken, + TargetRepoSlug: data.SafeOutputs.AddReviewer.TargetRepoSlug, + }) +} + +// parseAddReviewerConfig handles add-reviewer configuration +func (c *Compiler) parseAddReviewerConfig(outputMap map[string]any) *AddReviewerConfig { + if configData, exists := outputMap["add-reviewer"]; exists { + addReviewerConfig := &AddReviewerConfig{} + addReviewerConfig.Max = 3 // Default max is 3 + + if configMap, ok := configData.(map[string]any); ok { + // Parse reviewers (supports both string and array) + if reviewers, exists := configMap["reviewers"]; exists { + if reviewerStr, ok := reviewers.(string); ok { + // Single string format + addReviewerConfig.Reviewers = []string{reviewerStr} + } else if reviewersArray, ok := reviewers.([]any); ok { + // Array format + var reviewerStrings []string + for _, reviewer := range reviewersArray { + if reviewerStr, ok := reviewer.(string); ok { + reviewerStrings = append(reviewerStrings, reviewerStr) + } + } + addReviewerConfig.Reviewers = reviewerStrings + } + } + + // Parse max + if max, exists := configMap["max"]; exists { + if maxInt, ok := max.(int); ok { + addReviewerConfig.Max = maxInt + } + } + + // Parse github-token + if token, exists := configMap["github-token"]; exists { + if tokenStr, ok := token.(string); ok { + addReviewerConfig.GitHubToken = tokenStr + } + } + + // Parse target + if target, exists := configMap["target"]; exists { + if targetStr, ok := target.(string); ok { + addReviewerConfig.Target = targetStr + } + } + + // Parse target-repo using shared helper with validation + targetRepoSlug, isInvalid := parseTargetRepoWithValidation(configMap) + if isInvalid { + return nil // Invalid configuration, return nil to cause validation error + } + addReviewerConfig.TargetRepoSlug = targetRepoSlug + } else { + // If configData is nil or not a map (e.g., "add-reviewer:" with no value), + // still set the default max + addReviewerConfig.Max = 3 + } + + return addReviewerConfig + } + + return nil +} diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 13f91f940e..60a9b8d8e4 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -267,6 +267,7 @@ type SafeOutputsConfig struct { CreatePullRequestReviewComments *CreatePullRequestReviewCommentsConfig `yaml:"create-pull-request-review-comments,omitempty"` CreateCodeScanningAlerts *CreateCodeScanningAlertsConfig `yaml:"create-code-scanning-alerts,omitempty"` AddLabels *AddLabelsConfig `yaml:"add-labels,omitempty"` + AddReviewer *AddReviewerConfig `yaml:"add-reviewer,omitempty"` AssignMilestone *AssignMilestoneConfig `yaml:"assign-milestone,omitempty"` UpdateIssues *UpdateIssuesConfig `yaml:"update-issues,omitempty"` PushToPullRequestBranch *PushToPullRequestBranchConfig `yaml:"push-to-pull-request-branch,omitempty"` diff --git a/pkg/workflow/compiler_jobs.go b/pkg/workflow/compiler_jobs.go index 56c2f9d45b..8e1f48a889 100644 --- a/pkg/workflow/compiler_jobs.go +++ b/pkg/workflow/compiler_jobs.go @@ -321,6 +321,24 @@ func (c *Compiler) buildSafeOutputsJobs(data *WorkflowData, jobName, markdownPat safeOutputJobNames = append(safeOutputJobNames, addLabelsJob.Name) } + // Build add_reviewer job if output.add-reviewer is configured + if data.SafeOutputs.AddReviewer != nil { + addReviewerJob, err := c.buildAddReviewerJob(data, jobName) + if err != nil { + return fmt.Errorf("failed to build add_reviewer job: %w", err) + } + // Safe-output jobs should depend on agent job (always) AND detection job (if enabled) + if threatDetectionEnabled { + addReviewerJob.Needs = append(addReviewerJob.Needs, constants.DetectionJobName) + // Add detection success check to the job condition + addReviewerJob.If = AddDetectionSuccessCheck(addReviewerJob.If) + } + if err := c.jobManager.AddJob(addReviewerJob); err != nil { + return fmt.Errorf("failed to add add_reviewer job: %w", err) + } + safeOutputJobNames = append(safeOutputJobNames, addReviewerJob.Name) + } + // Build assign_milestone job if output.assign-milestone is configured if data.SafeOutputs.AssignMilestone != nil { assignMilestoneJob, err := c.buildAssignMilestoneJob(data, jobName) diff --git a/pkg/workflow/js/add_reviewer.cjs b/pkg/workflow/js/add_reviewer.cjs new file mode 100644 index 0000000000..bb60d531d2 --- /dev/null +++ b/pkg/workflow/js/add_reviewer.cjs @@ -0,0 +1,216 @@ +// @ts-check +/// + +const { loadAgentOutput } = require("./load_agent_output.cjs"); +const { generateStagedPreview } = require("./staged_preview.cjs"); + +// GitHub Copilot reviewer bot username +const COPILOT_REVIEWER_BOT = "copilot-pull-request-reviewer[bot]"; + +async function main() { + const result = loadAgentOutput(); + if (!result.success) { + return; + } + + const reviewerItem = result.items.find(item => item.type === "add_reviewer"); + if (!reviewerItem) { + core.warning("No add-reviewer item found in agent output"); + return; + } + core.info(`Found add-reviewer item with ${reviewerItem.reviewers.length} reviewers`); + + if (process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true") { + await generateStagedPreview({ + title: "Add Reviewers", + description: "The following reviewers would be added if staged mode was disabled:", + items: [reviewerItem], + renderItem: item => { + let content = ""; + if (item.pull_request_number) { + content += `**Target Pull Request:** #${item.pull_request_number}\n\n`; + } else { + content += `**Target:** Current pull request\n\n`; + } + if (item.reviewers && item.reviewers.length > 0) { + content += `**Reviewers to add:** ${item.reviewers.join(", ")}\n\n`; + } + return content; + }, + }); + return; + } + + // Parse allowed reviewers from environment (if configured) + const allowedReviewersEnv = process.env.GH_AW_REVIEWERS_ALLOWED?.trim(); + const allowedReviewers = allowedReviewersEnv + ? allowedReviewersEnv + .split(",") + .map(reviewer => reviewer.trim()) + .filter(reviewer => reviewer) + : undefined; + + if (allowedReviewers) { + core.info(`Allowed reviewers: ${JSON.stringify(allowedReviewers)}`); + } else { + core.info("No reviewer restrictions - any reviewers are allowed"); + } + + // Parse max count from environment + const maxCountEnv = process.env.GH_AW_REVIEWERS_MAX_COUNT; + const maxCount = maxCountEnv ? parseInt(maxCountEnv, 10) : 3; + if (isNaN(maxCount) || maxCount < 1) { + core.setFailed(`Invalid max value: ${maxCountEnv}. Must be a positive integer`); + return; + } + core.info(`Max count: ${maxCount}`); + + // Parse target configuration + const reviewersTarget = process.env.GH_AW_REVIEWERS_TARGET || "triggering"; + core.info(`Reviewers target configuration: ${reviewersTarget}`); + + // Check if we're in a PR context + const isPRContext = + context.eventName === "pull_request" || + context.eventName === "pull_request_review" || + context.eventName === "pull_request_review_comment"; + + if (reviewersTarget === "triggering" && !isPRContext) { + core.info('Target is "triggering" but not running in pull request context, skipping reviewer addition'); + return; + } + + // Determine the pull request number + let prNumber; + if (reviewersTarget === "*") { + if (reviewerItem.pull_request_number) { + prNumber = + typeof reviewerItem.pull_request_number === "number" + ? reviewerItem.pull_request_number + : parseInt(String(reviewerItem.pull_request_number), 10); + if (isNaN(prNumber) || prNumber <= 0) { + core.setFailed(`Invalid pull_request_number specified: ${reviewerItem.pull_request_number}`); + return; + } + } else { + core.setFailed('Target is "*" but no pull_request_number specified in reviewer item'); + return; + } + } else if (reviewersTarget && reviewersTarget !== "triggering") { + prNumber = parseInt(reviewersTarget, 10); + if (isNaN(prNumber) || prNumber <= 0) { + core.setFailed(`Invalid pull request number in target configuration: ${reviewersTarget}`); + return; + } + } else { + // Use triggering PR + if (isPRContext) { + if (context.payload.pull_request) { + prNumber = context.payload.pull_request.number; + } else { + core.setFailed("Pull request context detected but no pull request found in payload"); + return; + } + } + } + + if (!prNumber) { + core.setFailed("Could not determine pull request number"); + return; + } + + const requestedReviewers = reviewerItem.reviewers || []; + core.info(`Requested reviewers: ${JSON.stringify(requestedReviewers)}`); + + // Filter by allowed reviewers if configured + let validReviewers; + if (allowedReviewers) { + validReviewers = requestedReviewers.filter(reviewer => allowedReviewers.includes(reviewer)); + } else { + validReviewers = requestedReviewers; + } + + // Sanitize and deduplicate reviewers + let uniqueReviewers = validReviewers + .filter(reviewer => reviewer != null && reviewer !== false && reviewer !== 0) + .map(reviewer => String(reviewer).trim()) + .filter(reviewer => reviewer) + .filter((reviewer, index, arr) => arr.indexOf(reviewer) === index); + + // Apply max count limit + if (uniqueReviewers.length > maxCount) { + core.info(`Too many reviewers, keeping ${maxCount}`); + uniqueReviewers = uniqueReviewers.slice(0, maxCount); + } + + if (uniqueReviewers.length === 0) { + core.info("No reviewers to add"); + core.setOutput("reviewers_added", ""); + await core.summary + .addRaw( + ` +## Reviewer Addition + +No reviewers were added (no valid reviewers found in agent output). +` + ) + .write(); + return; + } + + core.info(`Adding ${uniqueReviewers.length} reviewers to PR #${prNumber}: ${JSON.stringify(uniqueReviewers)}`); + + try { + // Special handling for "copilot" reviewer - separate it from other reviewers in a single pass + const hasCopilot = uniqueReviewers.includes("copilot"); + const otherReviewers = hasCopilot ? uniqueReviewers.filter(r => r !== "copilot") : uniqueReviewers; + + // Add non-copilot reviewers first + if (otherReviewers.length > 0) { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + reviewers: otherReviewers, + }); + core.info(`Successfully added ${otherReviewers.length} reviewer(s) to PR #${prNumber}`); + } + + // Add copilot reviewer separately if requested + if (hasCopilot) { + try { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + reviewers: [COPILOT_REVIEWER_BOT], + }); + core.info(`Successfully added copilot as reviewer to PR #${prNumber}`); + } catch (copilotError) { + core.warning(`Failed to add copilot as reviewer: ${copilotError instanceof Error ? copilotError.message : String(copilotError)}`); + // Don't fail the whole step if copilot reviewer fails + } + } + + core.setOutput("reviewers_added", uniqueReviewers.join("\n")); + + const reviewersListMarkdown = uniqueReviewers.map(reviewer => `- \`${reviewer}\``).join("\n"); + await core.summary + .addRaw( + ` +## Reviewer Addition + +Successfully added ${uniqueReviewers.length} reviewer(s) to PR #${prNumber}: + +${reviewersListMarkdown} +` + ) + .write(); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + core.error(`Failed to add reviewers: ${errorMessage}`); + core.setFailed(`Failed to add reviewers: ${errorMessage}`); + } +} + +await main(); diff --git a/pkg/workflow/js/add_reviewer.test.cjs b/pkg/workflow/js/add_reviewer.test.cjs new file mode 100644 index 0000000000..0471defb8d --- /dev/null +++ b/pkg/workflow/js/add_reviewer.test.cjs @@ -0,0 +1,292 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import fs from "fs"; +import path from "path"; +import os from "os"; + +// Mock the global objects that GitHub Actions provides +const mockCore = { + debug: vi.fn(), + info: vi.fn(), + notice: 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: { + pulls: { + requestReviewers: vi.fn().mockResolvedValue({}), + }, + }, +}; + +const mockContext = { + eventName: "pull_request", + repo: { + owner: "testowner", + repo: "testrepo", + }, + payload: { + pull_request: { + number: 123, + }, + }, +}; + +// Set up global mocks before importing the module +global.core = mockCore; +global.github = mockGithub; +global.context = mockContext; + +describe("add_reviewer", () => { + let tempDir; + let outputFile; + + beforeEach(() => { + // Create a temporary directory for test files + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "add-reviewer-test-")); + outputFile = path.join(tempDir, "agent-output.json"); + + // Reset all mocks before each test + vi.clearAllMocks(); + vi.resetModules(); // Reset module cache to allow fresh imports + + // Clear environment variables + delete process.env.GH_AW_SAFE_OUTPUTS_STAGED; + delete process.env.GH_AW_REVIEWERS_ALLOWED; + delete process.env.GH_AW_REVIEWERS_MAX_COUNT; + delete process.env.GH_AW_REVIEWERS_TARGET; + + // Reset context to default + global.context = { + eventName: "pull_request", + repo: { + owner: "testowner", + repo: "testrepo", + }, + payload: { + pull_request: { + number: 123, + }, + }, + }; + }); + + afterEach(() => { + // Clean up temporary files + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + it("should handle missing GH_AW_AGENT_OUTPUT", async () => { + delete process.env.GH_AW_AGENT_OUTPUT; + + await import("./add_reviewer.cjs"); + + expect(mockCore.info).toHaveBeenCalledWith("No GH_AW_AGENT_OUTPUT environment variable found"); + }); + + it("should handle missing add_reviewer item", async () => { + const agentOutput = { + items: [{ type: "create_issue", title: "Test", body: "Body" }], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + + await import("./add_reviewer.cjs"); + + expect(mockCore.warning).toHaveBeenCalledWith("No add-reviewer item found in agent output"); + }); + + it("should add reviewers to PR in non-staged mode", async () => { + const agentOutput = { + items: [ + { + type: "add_reviewer", + reviewers: ["octocat", "github"], + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + process.env.GH_AW_REVIEWERS_MAX_COUNT = "3"; + process.env.GH_AW_REVIEWERS_TARGET = "triggering"; + + await import("./add_reviewer.cjs"); + + expect(mockGithub.rest.pulls.requestReviewers).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + pull_number: 123, + reviewers: ["octocat", "github"], + }); + expect(mockCore.setOutput).toHaveBeenCalledWith("reviewers_added", "octocat\ngithub"); + }); + + it("should generate staged preview in staged mode", async () => { + const agentOutput = { + items: [ + { + type: "add_reviewer", + reviewers: ["octocat", "github"], + pull_request_number: 123, + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "true"; + + await import("./add_reviewer.cjs"); + + expect(mockCore.summary.addRaw).toHaveBeenCalled(); + expect(mockCore.summary.write).toHaveBeenCalled(); + expect(mockGithub.rest.pulls.requestReviewers).not.toHaveBeenCalled(); + }); + + it("should filter by allowed reviewers", async () => { + const agentOutput = { + items: [ + { + type: "add_reviewer", + reviewers: ["octocat", "github", "unauthorized"], + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + process.env.GH_AW_REVIEWERS_ALLOWED = "octocat,github"; + process.env.GH_AW_REVIEWERS_MAX_COUNT = "3"; + + await import("./add_reviewer.cjs"); + + expect(mockGithub.rest.pulls.requestReviewers).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + pull_number: 123, + reviewers: ["octocat", "github"], + }); + }); + + it("should enforce max count limit", async () => { + const agentOutput = { + items: [ + { + type: "add_reviewer", + reviewers: ["user1", "user2", "user3", "user4", "user5"], + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + process.env.GH_AW_REVIEWERS_MAX_COUNT = "2"; + + await import("./add_reviewer.cjs"); + + expect(mockGithub.rest.pulls.requestReviewers).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + pull_number: 123, + reviewers: ["user1", "user2"], + }); + }); + + it("should handle non-PR context gracefully", async () => { + const agentOutput = { + items: [ + { + type: "add_reviewer", + reviewers: ["octocat"], + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + global.context = { + ...mockContext, + eventName: "issues", // Not a PR context + payload: { + issue: { + number: 123, + }, + }, + }; + + await import("./add_reviewer.cjs"); + + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining('Target is "triggering" but not running in pull request context')); + expect(mockGithub.rest.pulls.requestReviewers).not.toHaveBeenCalled(); + }); + + it("should handle explicit PR number with * target", async () => { + const agentOutput = { + items: [ + { + type: "add_reviewer", + reviewers: ["octocat"], + pull_request_number: 456, + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + process.env.GH_AW_REVIEWERS_TARGET = "*"; + + await import("./add_reviewer.cjs"); + + expect(mockGithub.rest.pulls.requestReviewers).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + pull_number: 456, + reviewers: ["octocat"], + }); + }); + + it("should handle API errors gracefully", async () => { + const agentOutput = { + items: [ + { + type: "add_reviewer", + reviewers: ["octocat"], + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + mockGithub.rest.pulls.requestReviewers.mockRejectedValueOnce(new Error("API Error")); + + await import("./add_reviewer.cjs"); + + expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("Failed to add reviewers")); + expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("Failed to add reviewers")); + }); + + it("should deduplicate reviewers", async () => { + const agentOutput = { + items: [ + { + type: "add_reviewer", + reviewers: ["octocat", "github", "octocat", "github"], + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(agentOutput)); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + + await import("./add_reviewer.cjs"); + + expect(mockGithub.rest.pulls.requestReviewers).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + pull_number: 123, + reviewers: ["octocat", "github"], + }); + }); +}); diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index 3a4e5c3ec8..9c7af3e26f 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -5,6 +5,9 @@ async function main() { const fs = require("fs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const maxBodyLength = 65000; + // Maximum length for GitHub usernames (39 characters) + // Reference: https://github.com/dead-claudia/github-limits + const MAX_GITHUB_USERNAME_LENGTH = 39; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { @@ -23,6 +26,8 @@ async function main() { return 1; case "add_labels": return 5; + case "add_reviewer": + return 3; case "assign_milestone": return 1; case "update_issue": @@ -455,6 +460,23 @@ async function main() { } item.labels = item.labels.map(label => sanitizeContent(label, 128)); break; + case "add_reviewer": + if (!item.reviewers || !Array.isArray(item.reviewers)) { + errors.push(`Line ${i + 1}: add_reviewer requires a 'reviewers' array field`); + continue; + } + if (item.reviewers.some(reviewer => typeof reviewer !== "string")) { + errors.push(`Line ${i + 1}: add_reviewer reviewers array must contain only strings`); + continue; + } + const reviewerPRNumberValidation = validateIssueOrPRNumber(item.pull_request_number, "add_reviewer 'pull_request_number'", i + 1); + if (!reviewerPRNumberValidation.isValid) { + if (reviewerPRNumberValidation.error) errors.push(reviewerPRNumberValidation.error); + continue; + } + // Sanitize reviewer usernames (limit to MAX_GITHUB_USERNAME_LENGTH) + item.reviewers = item.reviewers.map(reviewer => sanitizeContent(reviewer, MAX_GITHUB_USERNAME_LENGTH)); + break; case "update_issue": const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; if (!hasValidField) { diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 0923c6a726..7bb06e6dfd 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -6,11 +6,19 @@ "type": "object", "required": ["title", "body"], "properties": { - "title": { "type": "string", "description": "Issue title" }, - "body": { "type": "string", "description": "Issue body/description" }, + "title": { + "type": "string", + "description": "Issue title" + }, + "body": { + "type": "string", + "description": "Issue body/description" + }, "labels": { "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "description": "Issue labels" }, "parent": { @@ -28,7 +36,10 @@ "type": "object", "required": ["body"], "properties": { - "body": { "type": "string", "description": "Task description/instructions for the agent" } + "body": { + "type": "string", + "description": "Task description/instructions for the agent" + } }, "additionalProperties": false } @@ -40,9 +51,18 @@ "type": "object", "required": ["title", "body"], "properties": { - "title": { "type": "string", "description": "Discussion title" }, - "body": { "type": "string", "description": "Discussion body/content" }, - "category": { "type": "string", "description": "Discussion category" } + "title": { + "type": "string", + "description": "Discussion title" + }, + "body": { + "type": "string", + "description": "Discussion body/content" + }, + "category": { + "type": "string", + "description": "Discussion category" + } }, "additionalProperties": false } @@ -97,7 +117,10 @@ "type": "object", "required": ["body", "item_number"], "properties": { - "body": { "type": "string", "description": "Comment body/content" }, + "body": { + "type": "string", + "description": "Comment body/content" + }, "item_number": { "type": "number", "description": "Issue, pull request or discussion number" @@ -113,7 +136,10 @@ "type": "object", "required": ["title", "body"], "properties": { - "title": { "type": "string", "description": "Pull request title" }, + "title": { + "type": "string", + "description": "Pull request title" + }, "body": { "type": "string", "description": "Pull request body/description" @@ -124,7 +150,9 @@ }, "labels": { "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "description": "Optional labels to add to the PR" } }, @@ -146,7 +174,10 @@ "type": ["number", "string"], "description": "Line number for the comment" }, - "body": { "type": "string", "description": "Comment body content" }, + "body": { + "type": "string", + "description": "Comment body content" + }, "start_line": { "type": ["number", "string"], "description": "Optional start line for multi-line comments" @@ -205,7 +236,9 @@ "properties": { "labels": { "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "description": "Labels to add" }, "item_number": { @@ -216,6 +249,28 @@ "additionalProperties": false } }, + { + "name": "add_reviewer", + "description": "Add reviewers to a GitHub pull request", + "inputSchema": { + "type": "object", + "required": ["reviewers"], + "properties": { + "reviewers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "GitHub usernames to add as reviewers" + }, + "pull_request_number": { + "type": ["number", "string"], + "description": "Pull request number (optional - uses triggering PR if not provided)" + } + }, + "additionalProperties": false + } + }, { "name": "assign_milestone", "description": "Assign an issue to a milestone", @@ -246,8 +301,14 @@ "enum": ["open", "closed"], "description": "Optional new issue status" }, - "title": { "type": "string", "description": "Optional new issue title" }, - "body": { "type": "string", "description": "Optional new issue body" }, + "title": { + "type": "string", + "description": "Optional new issue title" + }, + "body": { + "type": "string", + "description": "Optional new issue body" + }, "issue_number": { "type": ["number", "string"], "description": "Optional issue number for target '*'" @@ -267,7 +328,10 @@ "type": "string", "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." }, - "message": { "type": "string", "description": "Commit message" }, + "message": { + "type": "string", + "description": "Commit message" + }, "pull_request_number": { "type": ["number", "string"], "description": "Optional pull request number for target '*'" @@ -322,8 +386,14 @@ "type": "object", "required": ["tool", "reason"], "properties": { - "tool": { "type": "string", "description": "Name of the missing tool (max 128 characters)" }, - "reason": { "type": "string", "description": "Why this tool is needed (max 256 characters)" }, + "tool": { + "type": "string", + "description": "Name of the missing tool (max 128 characters)" + }, + "reason": { + "type": "string", + "description": "Why this tool is needed (max 256 characters)" + }, "alternatives": { "type": "string", "description": "Possible alternatives or workarounds (max 256 characters)" diff --git a/pkg/workflow/js/types/safe-outputs-config.d.ts b/pkg/workflow/js/types/safe-outputs-config.d.ts index 49555ea513..63e9465dbf 100644 --- a/pkg/workflow/js/types/safe-outputs-config.d.ts +++ b/pkg/workflow/js/types/safe-outputs-config.d.ts @@ -73,6 +73,14 @@ interface AddLabelsConfig extends SafeOutputConfig { allowed?: string[]; } +/** + * Configuration for adding reviewers to pull requests + */ +interface AddReviewerConfig extends SafeOutputConfig { + reviewers?: string[]; + target?: string; +} + /** * Configuration for updating issues */ @@ -174,6 +182,7 @@ type SpecificSafeOutputConfig = | CreatePullRequestReviewCommentConfig | CreateCodeScanningAlertConfig | AddLabelsConfig + | AddReviewerConfig | UpdateIssueConfig | PushToPullRequestBranchConfig | UploadAssetConfig @@ -197,6 +206,7 @@ export { CreatePullRequestReviewCommentConfig, CreateCodeScanningAlertConfig, AddLabelsConfig, + AddReviewerConfig, UpdateIssueConfig, PushToPullRequestBranchConfig, UploadAssetConfig, diff --git a/pkg/workflow/js/types/safe-outputs.d.ts b/pkg/workflow/js/types/safe-outputs.d.ts index 791c3ddbea..4433effd73 100644 --- a/pkg/workflow/js/types/safe-outputs.d.ts +++ b/pkg/workflow/js/types/safe-outputs.d.ts @@ -123,6 +123,17 @@ interface AddLabelsItem extends BaseSafeOutputItem { issue_number?: number; } +/** + * JSONL item for adding reviewers to a pull request + */ +interface AddReviewerItem extends BaseSafeOutputItem { + type: "add_reviewer"; + /** Array of GitHub usernames to add as reviewers */ + reviewers: string[]; + /** Pull request number (optional - uses triggering PR if not provided) */ + pull_request_number?: number | string; +} + /** * JSONL item for updating an issue */ @@ -216,6 +227,7 @@ type SafeOutputItem = | CreatePullRequestReviewCommentItem | CreateCodeScanningAlertItem | AddLabelsItem + | AddReviewerItem | UpdateIssueItem | PushToPrBranchItem | MissingToolItem @@ -243,6 +255,7 @@ export { CreatePullRequestReviewCommentItem, CreateCodeScanningAlertItem, AddLabelsItem, + AddReviewerItem, UpdateIssueItem, PushToPrBranchItem, MissingToolItem, diff --git a/pkg/workflow/safe_outputs.go b/pkg/workflow/safe_outputs.go index be85a68fec..fcfe37907b 100644 --- a/pkg/workflow/safe_outputs.go +++ b/pkg/workflow/safe_outputs.go @@ -39,6 +39,7 @@ func HasSafeOutputsEnabled(safeOutputs *SafeOutputsConfig) bool { safeOutputs.CreatePullRequestReviewComments != nil || safeOutputs.CreateCodeScanningAlerts != nil || safeOutputs.AddLabels != nil || + safeOutputs.AddReviewer != nil || safeOutputs.AssignMilestone != nil || safeOutputs.UpdateIssues != nil || safeOutputs.PushToPullRequestBranch != nil || @@ -107,6 +108,14 @@ func generateSafeOutputsPromptSection(yaml *strings.Builder, safeOutputs *SafeOu written = true } + if safeOutputs.AddReviewer != nil { + if written { + yaml.WriteString(", ") + } + yaml.WriteString("Adding Reviewers to Pull Requests") + written = true + } + if safeOutputs.UpdateIssues != nil { if written { yaml.WriteString(", ") @@ -192,6 +201,13 @@ func generateSafeOutputsPromptSection(yaml *strings.Builder, safeOutputs *SafeOu yaml.WriteString(" \n") } + if safeOutputs.AddReviewer != nil { + yaml.WriteString(" **Adding Reviewers to Pull Requests**\n") + yaml.WriteString(" \n") + yaml.WriteString(fmt.Sprintf(" To add reviewers to a pull request, use the add-reviewer tool from %s\n", constants.SafeOutputsMCPServerID)) + yaml.WriteString(" \n") + } + if safeOutputs.UpdateIssues != nil { yaml.WriteString(" **Updating an Issue**\n") yaml.WriteString(" \n") @@ -400,6 +416,12 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut } } + // Parse add-reviewer configuration + addReviewerConfig := c.parseAddReviewerConfig(outputMap) + if addReviewerConfig != nil { + config.AddReviewer = addReviewerConfig + } + // Parse assign-milestone configuration if milestone, exists := outputMap["assign-milestone"]; exists { if milestoneMap, ok := milestone.(map[string]any); ok { @@ -1139,6 +1161,9 @@ func generateFilteredToolsJSON(data *WorkflowData) (string, error) { if data.SafeOutputs.AddLabels != nil { enabledTools["add_labels"] = true } + if data.SafeOutputs.AddReviewer != nil { + enabledTools["add_reviewer"] = true + } if data.SafeOutputs.AssignMilestone != nil { enabledTools["assign_milestone"] = true } diff --git a/pkg/workflow/safe_outputs_tools_test.go b/pkg/workflow/safe_outputs_tools_test.go index 3448d2771b..3a25cc90d2 100644 --- a/pkg/workflow/safe_outputs_tools_test.go +++ b/pkg/workflow/safe_outputs_tools_test.go @@ -150,6 +150,7 @@ func TestGenerateFilteredToolsJSON(t *testing.T) { CreatePullRequestReviewComments: &CreatePullRequestReviewCommentsConfig{BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 5}}, CreateCodeScanningAlerts: &CreateCodeScanningAlertsConfig{BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 100}}, AddLabels: &AddLabelsConfig{Max: 3}, + AddReviewer: &AddReviewerConfig{Max: 3}, UpdateIssues: &UpdateIssuesConfig{BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 3}}, PushToPullRequestBranch: &PushToPullRequestBranchConfig{BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}}, UploadAssets: &UploadAssetsConfig{BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 10}}, @@ -164,6 +165,7 @@ func TestGenerateFilteredToolsJSON(t *testing.T) { "create_pull_request_review_comment", "create_code_scanning_alert", "add_labels", + "add_reviewer", "update_issue", "push_to_pull_request_branch", "upload_asset", @@ -276,6 +278,7 @@ func TestGetSafeOutputsToolsJSON(t *testing.T) { "create_pull_request_review_comment", "create_code_scanning_alert", "add_labels", + "add_reviewer", "assign_milestone", "update_issue", "push_to_pull_request_branch", diff --git a/pkg/workflow/scripts.go b/pkg/workflow/scripts.go index a49b6823bd..3f7dc9af6e 100644 --- a/pkg/workflow/scripts.go +++ b/pkg/workflow/scripts.go @@ -26,6 +26,9 @@ var createIssueScriptSource string //go:embed js/add_labels.cjs var addLabelsScriptSource string +//go:embed js/add_reviewer.cjs +var addReviewerScriptSource string + //go:embed js/assign_milestone.cjs var assignMilestoneScriptSource string @@ -99,6 +102,9 @@ var ( addLabelsScript string addLabelsScriptOnce sync.Once + addReviewerScript string + addReviewerScriptOnce sync.Once + assignMilestoneScript string assignMilestoneScriptOnce sync.Once @@ -244,6 +250,24 @@ func getAddLabelsScript() string { return addLabelsScript } +// getAddReviewerScript returns the bundled add_reviewer script +// Bundling is performed on first access and cached for subsequent calls +func getAddReviewerScript() string { + addReviewerScriptOnce.Do(func() { + scriptsLog.Print("Bundling add_reviewer script") + sources := GetJavaScriptSources() + bundled, err := BundleJavaScriptFromSources(addReviewerScriptSource, sources, "") + if err != nil { + scriptsLog.Printf("Bundling failed for add_reviewer, using source as-is: %v", err) + // If bundling fails, use the source as-is + addReviewerScript = addReviewerScriptSource + } else { + addReviewerScript = bundled + } + }) + return addReviewerScript +} + // getAssignMilestoneScript returns the bundled assign_milestone script // Bundling is performed on first access and cached for subsequent calls func getAssignMilestoneScript() string { diff --git a/schemas/agent-output.json b/schemas/agent-output.json index 25710934ea..17ca87e0b7 100644 --- a/schemas/agent-output.json +++ b/schemas/agent-output.json @@ -31,6 +31,7 @@ {"$ref": "#/$defs/AddCommentOutput"}, {"$ref": "#/$defs/CreatePullRequestOutput"}, {"$ref": "#/$defs/AddLabelsOutput"}, + {"$ref": "#/$defs/AddReviewerOutput"}, {"$ref": "#/$defs/UpdateIssueOutput"}, {"$ref": "#/$defs/PushToPullRequestBranchOutput"}, {"$ref": "#/$defs/CreatePullRequestReviewCommentOutput"}, @@ -143,6 +144,33 @@ "required": ["type", "labels"], "additionalProperties": false }, + "AddReviewerOutput": { + "title": "Add Reviewer Output", + "description": "Output for adding reviewers to a pull request", + "type": "object", + "properties": { + "type": { + "const": "add_reviewer" + }, + "reviewers": { + "type": "array", + "description": "Array of GitHub usernames to add as reviewers", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "pull_request_number": { + "oneOf": [ + {"type": "number"}, + {"type": "string"} + ], + "description": "Pull request number (optional - uses triggering PR if not provided)" + } + }, + "required": ["type", "reviewers"], + "additionalProperties": false + }, "UpdateIssueOutput": { "title": "Update Issue Output", "description": "Output for updating an existing issue. Note: The JavaScript validation ensures at least one of status, title, or body is provided.",