diff --git a/.github/workflows/test-claude-add-issue-comment.lock.yml b/.github/workflows/test-claude-add-issue-comment.lock.yml index 07ffdb39937..64fb8c79b39 100644 --- a/.github/workflows/test-claude-add-issue-comment.lock.yml +++ b/.github/workflows/test-claude-add-issue-comment.lock.yml @@ -549,13 +549,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -660,13 +660,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-claude-add-issue-labels.lock.yml b/.github/workflows/test-claude-add-issue-labels.lock.yml index 58e3c5ca5e5..c7a1ef59330 100644 --- a/.github/workflows/test-claude-add-issue-labels.lock.yml +++ b/.github/workflows/test-claude-add-issue-labels.lock.yml @@ -264,12 +264,12 @@ jobs: **Adding Labels to Issues or Pull Requests** ```json - {"type": "add-issue-labels", "labels": ["label1", "label2", "label3"]} + {"type": "add-issue-label", "labels": ["label1", "label2", "label3"]} ``` **Example JSONL file content:** ``` - {"type": "add-issue-labels", "labels": ["bug", "priority-high"]} + {"type": "add-issue-label", "labels": ["bug", "priority-high"]} ``` **Important Notes:** @@ -425,7 +425,7 @@ jobs: uses: actions/github-script@v7 env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-labels\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-label\":true}" with: script: | async function main() { @@ -549,13 +549,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -660,13 +660,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings @@ -1120,13 +1120,13 @@ jobs: console.log('No valid items found in agent output'); return; } - // Find the add-issue-labels item - const labelsItem = validatedOutput.items.find(/** @param {any} item */ item => item.type === 'add-issue-labels'); + // Find the add-issue-label item + const labelsItem = validatedOutput.items.find(/** @param {any} item */ item => item.type === 'add-issue-label'); if (!labelsItem) { - console.log('No add-issue-labels item found in agent output'); + console.log('No add-issue-label item found in agent output'); return; } - console.log('Found add-issue-labels item:', { labelsCount: labelsItem.labels.length }); + console.log('Found add-issue-label item:', { labelsCount: labelsItem.labels.length }); // Read the allowed labels from environment variable (optional) const allowedLabelsEnv = process.env.GITHUB_AW_LABELS_ALLOWED; let allowedLabels = null; @@ -1141,11 +1141,11 @@ jobs: } else { console.log('No label restrictions - any labels are allowed'); } - // Read the max-count limit from environment variable (default: 3) + // Read the max limit from environment variable (default: 3) const maxCountEnv = process.env.GITHUB_AW_LABELS_MAX_COUNT; const maxCount = maxCountEnv ? parseInt(maxCountEnv, 10) : 3; if (isNaN(maxCount) || maxCount < 1) { - core.setFailed(`Invalid max-count value: ${maxCountEnv}. Must be a positive integer`); + core.setFailed(`Invalid max value: ${maxCountEnv}. Must be a positive integer`); return; } console.log('Max count:', maxCount); @@ -1200,7 +1200,7 @@ jobs: } // Remove duplicates from requested labels let uniqueLabels = [...new Set(validLabels)]; - // Enforce max-count limit + // Enforce max limit if (uniqueLabels.length > maxCount) { console.log(`too many labels, keep ${maxCount}`) uniqueLabels = uniqueLabels.slice(0, maxCount); diff --git a/.github/workflows/test-claude-add-issue-labels.md b/.github/workflows/test-claude-add-issue-labels.md index 4146ca56681..159588e9735 100644 --- a/.github/workflows/test-claude-add-issue-labels.md +++ b/.github/workflows/test-claude-add-issue-labels.md @@ -8,7 +8,7 @@ engine: id: claude safe-outputs: - add-issue-labels: + add-issue-label: --- If the title of the issue #${{ github.event.issue.number }} is exactly "[claude-test] Hello from Claude" then add the issue labels "claude-safe-output-label-test" to the issue. diff --git a/.github/workflows/test-claude-command.lock.yml b/.github/workflows/test-claude-command.lock.yml index f431225b73c..46859b1a504 100644 --- a/.github/workflows/test-claude-command.lock.yml +++ b/.github/workflows/test-claude-command.lock.yml @@ -787,13 +787,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -898,13 +898,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-claude-create-issue.lock.yml b/.github/workflows/test-claude-create-issue.lock.yml index e7bca272dc5..976cb1eaf16 100644 --- a/.github/workflows/test-claude-create-issue.lock.yml +++ b/.github/workflows/test-claude-create-issue.lock.yml @@ -378,13 +378,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -489,13 +489,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-claude-create-pull-request.lock.yml b/.github/workflows/test-claude-create-pull-request.lock.yml index 070087a9e72..25c8a6bb9cb 100644 --- a/.github/workflows/test-claude-create-pull-request.lock.yml +++ b/.github/workflows/test-claude-create-pull-request.lock.yml @@ -387,13 +387,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -498,13 +498,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-claude-mcp.lock.yml b/.github/workflows/test-claude-mcp.lock.yml index 7fb379cf27a..2ad2219a23e 100644 --- a/.github/workflows/test-claude-mcp.lock.yml +++ b/.github/workflows/test-claude-mcp.lock.yml @@ -571,13 +571,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -682,13 +682,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-codex-add-issue-comment.lock.yml b/.github/workflows/test-codex-add-issue-comment.lock.yml index 93bd57d92d9..3bbbf2efec1 100644 --- a/.github/workflows/test-codex-add-issue-comment.lock.yml +++ b/.github/workflows/test-codex-add-issue-comment.lock.yml @@ -487,13 +487,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -598,13 +598,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-codex-add-issue-labels.lock.yml b/.github/workflows/test-codex-add-issue-labels.lock.yml index f849db3016f..cf8727ac0c7 100644 --- a/.github/workflows/test-codex-add-issue-labels.lock.yml +++ b/.github/workflows/test-codex-add-issue-labels.lock.yml @@ -266,12 +266,12 @@ jobs: **Adding Labels to Issues or Pull Requests** ```json - {"type": "add-issue-labels", "labels": ["label1", "label2", "label3"]} + {"type": "add-issue-label", "labels": ["label1", "label2", "label3"]} ``` **Example JSONL file content:** ``` - {"type": "add-issue-labels", "labels": ["bug", "priority-high"]} + {"type": "add-issue-label", "labels": ["bug", "priority-high"]} ``` **Important Notes:** @@ -363,7 +363,7 @@ jobs: uses: actions/github-script@v7 env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-labels\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-label\":true}" with: script: | async function main() { @@ -487,13 +487,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -598,13 +598,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings @@ -1003,13 +1003,13 @@ jobs: console.log('No valid items found in agent output'); return; } - // Find the add-issue-labels item - const labelsItem = validatedOutput.items.find(/** @param {any} item */ item => item.type === 'add-issue-labels'); + // Find the add-issue-label item + const labelsItem = validatedOutput.items.find(/** @param {any} item */ item => item.type === 'add-issue-label'); if (!labelsItem) { - console.log('No add-issue-labels item found in agent output'); + console.log('No add-issue-label item found in agent output'); return; } - console.log('Found add-issue-labels item:', { labelsCount: labelsItem.labels.length }); + console.log('Found add-issue-label item:', { labelsCount: labelsItem.labels.length }); // Read the allowed labels from environment variable (optional) const allowedLabelsEnv = process.env.GITHUB_AW_LABELS_ALLOWED; let allowedLabels = null; @@ -1024,11 +1024,11 @@ jobs: } else { console.log('No label restrictions - any labels are allowed'); } - // Read the max-count limit from environment variable (default: 3) + // Read the max limit from environment variable (default: 3) const maxCountEnv = process.env.GITHUB_AW_LABELS_MAX_COUNT; const maxCount = maxCountEnv ? parseInt(maxCountEnv, 10) : 3; if (isNaN(maxCount) || maxCount < 1) { - core.setFailed(`Invalid max-count value: ${maxCountEnv}. Must be a positive integer`); + core.setFailed(`Invalid max value: ${maxCountEnv}. Must be a positive integer`); return; } console.log('Max count:', maxCount); @@ -1083,7 +1083,7 @@ jobs: } // Remove duplicates from requested labels let uniqueLabels = [...new Set(validLabels)]; - // Enforce max-count limit + // Enforce max limit if (uniqueLabels.length > maxCount) { console.log(`too many labels, keep ${maxCount}`) uniqueLabels = uniqueLabels.slice(0, maxCount); diff --git a/.github/workflows/test-codex-add-issue-labels.md b/.github/workflows/test-codex-add-issue-labels.md index ff2b25dbfb8..72fbcb83b78 100644 --- a/.github/workflows/test-codex-add-issue-labels.md +++ b/.github/workflows/test-codex-add-issue-labels.md @@ -8,7 +8,7 @@ engine: id: codex safe-outputs: - add-issue-labels: + add-issue-label: --- If the title of the issue #${{ github.event.issue.number }} is "[codex-test] Hello from Codex" then add the issue labels "codex-safe-output-label-test" to the issue. diff --git a/.github/workflows/test-codex-command.lock.yml b/.github/workflows/test-codex-command.lock.yml index cc2be65202e..f2d504645db 100644 --- a/.github/workflows/test-codex-command.lock.yml +++ b/.github/workflows/test-codex-command.lock.yml @@ -787,13 +787,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -898,13 +898,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-codex-create-issue.lock.yml b/.github/workflows/test-codex-create-issue.lock.yml index 43e9dc279a1..9f490568f8c 100644 --- a/.github/workflows/test-codex-create-issue.lock.yml +++ b/.github/workflows/test-codex-create-issue.lock.yml @@ -316,13 +316,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -427,13 +427,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-codex-create-pull-request.lock.yml b/.github/workflows/test-codex-create-pull-request.lock.yml index e521f0b4b6e..c1d7ff11743 100644 --- a/.github/workflows/test-codex-create-pull-request.lock.yml +++ b/.github/workflows/test-codex-create-pull-request.lock.yml @@ -325,13 +325,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -436,13 +436,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-codex-mcp.lock.yml b/.github/workflows/test-codex-mcp.lock.yml index 8e0363275c6..917fa40cab1 100644 --- a/.github/workflows/test-codex-mcp.lock.yml +++ b/.github/workflows/test-codex-mcp.lock.yml @@ -506,13 +506,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -617,13 +617,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/.github/workflows/test-proxy.lock.yml b/.github/workflows/test-proxy.lock.yml index 4fe21c5ad52..1b6a238f944 100644 --- a/.github/workflows/test-proxy.lock.yml +++ b/.github/workflows/test-proxy.lock.yml @@ -549,13 +549,13 @@ jobs: // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -660,13 +660,13 @@ jobs: item.labels = item.labels.map(label => typeof label === 'string' ? sanitizeContent(label) : label); } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/docs/safe-outputs.md b/docs/safe-outputs.md index 2ce52d88e50..a4e72a3b4d2 100644 --- a/docs/safe-outputs.md +++ b/docs/safe-outputs.md @@ -19,33 +19,27 @@ safe-outputs: add-issue-comment: ``` -This declares that the workflow should create at most one new issue and add at most one comment to the triggering issue or pull request based on the agentic workflow's output. +This declares that the workflow should create at most one new issue and add at most one comment to the triggering issue or pull request based on the agentic workflow's output. To create multiple issues or comments, use the `max` parameter. ## Available Output Types -### New Issue Creation (`create-issue:` / `create-issues:`) +### New Issue Creation (`create-issue:`) Adding issue creation to the `safe-outputs:` section declares that the workflow should conclude with the creation of GitHub issues based on the workflow's output. -**Singular Form (create exactly one issue):** +**Basic Configuration:** ```yaml safe-outputs: create-issue: ``` -**Plural Form (create multiple issues):** -```yaml -safe-outputs: - create-issues: - max: 5 # Optional: maximum number of issues (default: 10) -``` - **With Configuration:** ```yaml safe-outputs: - create-issue: # Singular form + create-issue: title-prefix: "[ai] " # Optional: prefix for issue titles labels: [automation, agentic] # Optional: labels to attach to issues + max: 5 # Optional: maximum number of issues (default: 1) ``` The agentic part of your workflow should describe the issue(s) it wants created. @@ -61,27 +55,21 @@ Create new issues with your findings. For each issue, provide a title starting w The compiled workflow will have additional prompting describing that, to create issues, it should write the issue details to a file. -### Issue Comment Creation (`add-issue-comment:` / `add-issue-comments:`) +### Issue Comment Creation (`add-issue-comment:`) Adding comment creation to the `safe-outputs:` section declares that the workflow should conclude with posting comments based on the workflow's output. By default, comments are posted on the triggering issue or pull request, but this can be configured using the `target` option. -**Singular Form (adds exactly one comment):** +**Basic Configuration:** ```yaml safe-outputs: add-issue-comment: ``` -**Plural Form (adds multiple comments):** -```yaml -safe-outputs: - add-issue-comments: - max: 3 # Optional: maximum number of comments (default: 10) -``` - -**Configuration options:** +**With Configuration:** ```yaml safe-outputs: add-issue-comment: + max: 3 # Optional: maximum number of comments (default: 1) target: "*" # Optional: target for comments # "triggering" (default) - only comment on triggering issue/PR # "*" - allow comments on any issue (requires issue_number in agent output) @@ -136,22 +124,22 @@ Analyze the latest commit and suggest improvements. 2. Create a pull request for your improvements, with a descriptive title and detailed description of the changes made ``` -### Label Addition (`add-issue-labels:`) +### Label Addition (`add-issue-label:`) -Adding `add-issue-labels:` to the `safe-outputs:` section of your workflow declares that the workflow should conclude with adding labels to the current issue or pull request based on the coding agent's analysis. +Adding `add-issue-label:` to the `safe-outputs:` section of your workflow declares that the workflow should conclude with adding labels to the current issue or pull request based on the coding agent's analysis. ```yaml safe-outputs: - add-issue-labels: + add-issue-label: ``` or with further configuration: ```yaml safe-outputs: - add-issue-labels: + add-issue-label: allowed: [triage, bug, enhancement] # Optional: allowed labels for addition. - max-count: 3 # Optional: maximum number of labels to add (default: 3) + max: 3 # Optional: maximum number of labels to add (default: 3) ``` The agentic part of your workflow should analyze the issue content and determine appropriate labels. @@ -172,7 +160,7 @@ The agentic part of your workflow will have implicit additional prompting saying - Lines starting with `-` are rejected (no removal operations allowed) - Duplicate labels are automatically removed - If `allowed` is provided, all requested labels must be in the `allowed` list or the job fails with a clear error message. If `allowed` is not provided then any labels are allowed (including creating new labels). -- Label count is limited by `max-count` setting (default: 3) - exceeding this limit causes job failure +- Label count is limited by `max` setting (default: 3) - exceeding this limit causes job failure - Only GitHub's `issues.addLabels` API endpoint is used (no removal endpoints) ## Security and Sanitization diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 053521fdcdb..0b028f5faf0 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -3685,14 +3685,12 @@ permissions: # Outputs - what APIs and tools can the AI use? safe-outputs: - create-issue: # Creates exactly one issue - # create-issues: # Creates multiple issues (default max: 10) - # max: 5 # Optional: specify maximum number + create-issue: # Creates issues (default max: 1) + max: 5 # Optional: specify maximum number # create-pull-request: # Creates exactly one pull request - # add-issue-comment: # Adds exactly one comment - # add-issue-comments: # Adds multiple comments (default max: 10) + # add-issue-comment: # Adds comments (default max: 1) # max: 2 # Optional: specify maximum number - # add-issue-labels: + # add-issue-label: --- diff --git a/pkg/cli/templates/instructions.md b/pkg/cli/templates/instructions.md index 15f99c07b62..6cf72ed271f 100644 --- a/pkg/cli/templates/instructions.md +++ b/pkg/cli/templates/instructions.md @@ -88,12 +88,15 @@ The YAML frontmatter supports these fields: create-issue: title-prefix: "[ai] " # Optional: prefix for issue titles labels: [automation, agentic] # Optional: labels to attach to issues + max: 5 # Optional: maximum number of issues (default: 1) ``` When using `safe-outputs.create-issue`, the main job does **not** need `issues: write` permission since issue creation is handled by a separate job with appropriate permissions. - - `add_issue_comment:` - Safe comment creation on issues/PRs + - `add-issue-comment:` - Safe comment creation on issues/PRs ```yaml safe-outputs: - add_issue_comment: {} + add-issue-comment: + max: 3 # Optional: maximum number of comments (default: 1) + target: "*" # Optional: target for comments (default: "triggering") ``` When using `safe-outputs.add-issue-comment`, the main job does **not** need `issues: write` or `pull-requests: write` permissions since comment creation is handled by a separate job with appropriate permissions. - `create-pull-request:` - Safe pull request creation with git patches @@ -494,6 +497,7 @@ permissions: engine: claude safe-outputs: add-issue-comment: + max: 3 # Optional: create multiple comments (default: 1) --- # Issue Analysis Agent diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index e12aab49c1e..30a5aa47b40 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -988,32 +988,6 @@ { "type": "object", "description": "Configuration for creating GitHub issues from agentic workflow output", - "properties": { - "title-prefix": { - "type": "string", - "description": "Optional prefix for the issue title" - }, - "labels": { - "type": "array", - "description": "Optional list of labels to attach to the issue", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - { - "type": "null", - "description": "Enable issue creation with default configuration" - } - ] - }, - "create-issues": { - "oneOf": [ - { - "type": "object", - "description": "Configuration for creating GitHub issues from agentic workflow output (plural form)", "properties": { "title-prefix": { "type": "string", @@ -1028,7 +1002,7 @@ }, "max": { "type": "integer", - "description": "Maximum number of issues to create (default: 10)", + "description": "Maximum number of issues to create (default: 1)", "minimum": 1, "maximum": 100 } @@ -1037,7 +1011,7 @@ }, { "type": "null", - "description": "Enable issue creation with default configuration (max: 10)" + "description": "Enable issue creation with default configuration" } ] }, @@ -1046,29 +1020,10 @@ { "type": "object", "description": "Configuration for creating GitHub issue/PR comments from agentic workflow output", - "properties": { - "target": { - "type": "string", - "description": "Target for comments: 'triggering' (default), '*' (any issue), or explicit issue number" - } - }, - "additionalProperties": false - }, - { - "type": "null", - "description": "Enable issue comment creation with default configuration" - } - ] - }, - "add-issue-comments": { - "oneOf": [ - { - "type": "object", - "description": "Configuration for creating GitHub issue/PR comments from agentic workflow output (plural form)", "properties": { "max": { "type": "integer", - "description": "Maximum number of comments to create (default: 10)", + "description": "Maximum number of comments to create (default: 1)", "minimum": 1, "maximum": 100 }, @@ -1081,7 +1036,7 @@ }, { "type": "null", - "description": "Enable issue comment creation with default configuration (max: 10)" + "description": "Enable issue comment creation with default configuration" } ] }, @@ -1115,7 +1070,7 @@ } ] }, - "add-issue-labels": { + "add-issue-label": { "oneOf": [ { "type": "null", @@ -1133,7 +1088,7 @@ }, "minItems": 1 }, - "max-count": { + "max": { "type": "integer", "description": "Optional maximum number of labels to add (default: 3)", "minimum": 1 diff --git a/pkg/workflow/agentic_output_test.go b/pkg/workflow/agentic_output_test.go index 21516021210..c677091b089 100644 --- a/pkg/workflow/agentic_output_test.go +++ b/pkg/workflow/agentic_output_test.go @@ -27,7 +27,7 @@ tools: allowed: [list_issues] engine: claude safe-outputs: - add-issue-labels: + add-issue-label: allowed: ["bug", "enhancement"] --- @@ -122,7 +122,7 @@ tools: allowed: [list_issues] engine: codex safe-outputs: - add-issue-labels: + add-issue-label: allowed: ["bug", "enhancement"] --- diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 79625e57168..7e5cd1e79fe 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -142,10 +142,10 @@ type WorkflowData struct { // SafeOutputsConfig holds configuration for automatic output routes type SafeOutputsConfig struct { - CreateIssues *CreateIssuesConfig `yaml:"create-issues,omitempty"` - AddIssueComments *AddIssueCommentsConfig `yaml:"add-issue-comments,omitempty"` - CreatePullRequests *CreatePullRequestsConfig `yaml:"create-pull-requests,omitempty"` - AddIssueLabels *AddIssueLabelsConfig `yaml:"add-issue-labels,omitempty"` + CreateIssues *CreateIssuesConfig `yaml:"create-issue,omitempty"` + AddIssueComments *AddIssueCommentsConfig `yaml:"add-issue-comment,omitempty"` + CreatePullRequests *CreatePullRequestsConfig `yaml:"create-pull-request,omitempty"` + AddIssueLabels *AddIssueLabelsConfig `yaml:"add-issue-label,omitempty"` AllowedDomains []string `yaml:"allowed-domains,omitempty"` } @@ -177,8 +177,8 @@ type CreatePullRequestsConfig struct { // AddIssueLabelsConfig holds configuration for adding labels to issues/PRs from agent output type AddIssueLabelsConfig struct { - Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed labels. If omitted, any labels are allowed (including creating new ones). - MaxCount *int `yaml:"max-count,omitempty"` // Optional maximum number of labels to add (default: 3) + Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed labels. If omitted, any labels are allowed (including creating new ones). + MaxCount *int `yaml:"max,omitempty"` // Optional maximum number of labels to add (default: 3) } // CompileWorkflow converts a markdown workflow to GitHub Actions YAML @@ -1560,7 +1560,7 @@ func (c *Compiler) buildJobs(data *WorkflowData) error { } } - // Build create_issue_comment job if output.add-issue-comments is configured + // Build create_issue_comment job if output.add-issue-comment is configured if data.SafeOutputs.AddIssueComments != nil { createCommentJob, err := c.buildCreateOutputAddIssueCommentJob(data, jobName) if err != nil { @@ -1571,7 +1571,7 @@ func (c *Compiler) buildJobs(data *WorkflowData) error { } } - // Build create_pull_request job if output.create-pull-requests is configured + // Build create_pull_request job if output.create-pull-request is configured if data.SafeOutputs.CreatePullRequests != nil { createPullRequestJob, err := c.buildCreateOutputPullRequestJob(data, jobName) if err != nil { @@ -1582,7 +1582,7 @@ func (c *Compiler) buildJobs(data *WorkflowData) error { } } - // Build add_labels job if output.add-issue-labels is configured (including null/empty) + // Build add_labels job if output.add-issue-label is configured (including null/empty) if data.SafeOutputs.AddIssueLabels != nil { addLabelsJob, err := c.buildCreateOutputLabelJob(data, jobName) if err != nil { @@ -1725,7 +1725,7 @@ func (c *Compiler) buildAddReactionJob(data *WorkflowData, taskJobCreated bool) // buildCreateOutputIssueJob creates the create_issue job func (c *Compiler) buildCreateOutputIssueJob(data *WorkflowData, mainJobName string) (*Job, error) { if data.SafeOutputs == nil || data.SafeOutputs.CreateIssues == nil { - return nil, fmt.Errorf("safe-outputs.create-issues configuration is required") + return nil, fmt.Errorf("safe-outputs.create-issue configuration is required") } var steps []string @@ -1775,7 +1775,7 @@ func (c *Compiler) buildCreateOutputIssueJob(data *WorkflowData, mainJobName str // buildCreateOutputAddIssueCommentJob creates the create_issue_comment job func (c *Compiler) buildCreateOutputAddIssueCommentJob(data *WorkflowData, mainJobName string) (*Job, error) { if data.SafeOutputs == nil || data.SafeOutputs.AddIssueComments == nil { - return nil, fmt.Errorf("safe-outputs.add-issue-comments configuration is required") + return nil, fmt.Errorf("safe-outputs.add-issue-comment configuration is required") } var steps []string @@ -1832,7 +1832,7 @@ func (c *Compiler) buildCreateOutputAddIssueCommentJob(data *WorkflowData, mainJ // buildCreateOutputPullRequestJob creates the create_pull_request job func (c *Compiler) buildCreateOutputPullRequestJob(data *WorkflowData, mainJobName string) (*Job, error) { if data.SafeOutputs == nil || data.SafeOutputs.CreatePullRequests == nil { - return nil, fmt.Errorf("safe-outputs.create-pull-requests configuration is required") + return nil, fmt.Errorf("safe-outputs.create-pull-request configuration is required") } var steps []string @@ -2351,7 +2351,7 @@ func (c *Compiler) generatePrompt(yaml *strings.Builder, data *WorkflowData, eng if data.SafeOutputs.AddIssueLabels != nil { yaml.WriteString(" **Adding Labels to Issues or Pull Requests**\n") yaml.WriteString(" ```json\n") - yaml.WriteString(" {\"type\": \"add-issue-labels\", \"labels\": [\"label1\", \"label2\", \"label3\"]}\n") + yaml.WriteString(" {\"type\": \"add-issue-label\", \"labels\": [\"label1\", \"label2\", \"label3\"]}\n") yaml.WriteString(" ```\n") yaml.WriteString(" \n") } @@ -2374,7 +2374,7 @@ func (c *Compiler) generatePrompt(yaml *strings.Builder, data *WorkflowData, eng exampleCount++ } if data.SafeOutputs.AddIssueLabels != nil { - yaml.WriteString(" {\"type\": \"add-issue-labels\", \"labels\": [\"bug\", \"priority-high\"]}\n") + yaml.WriteString(" {\"type\": \"add-issue-label\", \"labels\": [\"bug\", \"priority-high\"]}\n") exampleCount++ } @@ -2439,19 +2439,19 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut if outputMap, ok := output.(map[string]any); ok { config := &SafeOutputsConfig{} - // Handle create-issue and create-issues + // Handle create-issue issuesConfig := c.parseIssuesConfig(outputMap) if issuesConfig != nil { config.CreateIssues = issuesConfig } - // Handle add-issue-comment and add-issue-comments + // Handle add-issue-comment commentsConfig := c.parseCommentsConfig(outputMap) if commentsConfig != nil { config.AddIssueComments = commentsConfig } - // Handle create-pull-request and create-pull-requests + // Handle create-pull-request pullRequestsConfig := c.parsePullRequestsConfig(outputMap) if pullRequestsConfig != nil { config.CreatePullRequests = pullRequestsConfig @@ -2470,8 +2470,8 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut } } - // Parse add-issue-labels configuration - if labels, exists := outputMap["add-issue-labels"]; exists { + // Parse add-issue-label configuration + if labels, exists := outputMap["add-issue-label"]; exists { if labelsMap, ok := labels.(map[string]any); ok { labelConfig := &AddIssueLabelsConfig{} @@ -2488,8 +2488,8 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut } } - // Parse max-count (optional) - if maxCount, exists := labelsMap["max-count"]; exists { + // Parse max (optional) + if maxCount, exists := labelsMap["max"]; exists { // Handle different numeric types that YAML parsers might return var maxCountInt int var validMaxCount bool @@ -2525,146 +2525,77 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut return nil } -// parseIssuesConfig handles both create-issue (singular) and create-issues (plural) configurations +// parseIssuesConfig handles create-issue configuration func (c *Compiler) parseIssuesConfig(outputMap map[string]any) *CreateIssuesConfig { - // Check for both singular and plural forms - hasSingular := false - hasPlural := false - - if _, exists := outputMap["create-issue"]; exists { - hasSingular = true - } - if _, exists := outputMap["create-issues"]; exists { - hasPlural = true - } - - // Error if both are specified - if hasSingular && hasPlural { - // This should be caught by validation, but we'll handle it gracefully - // Prefer plural form - hasSingular = false - } - - var configData any - var defaultMax int - - if hasPlural { - configData = outputMap["create-issues"] - defaultMax = 10 // Default for plural form - } else if hasSingular { - configData = outputMap["create-issue"] - defaultMax = 1 // Singular form always has max 1 - } else { - return nil - } - - issuesConfig := &CreateIssuesConfig{Max: defaultMax} - - if configMap, ok := configData.(map[string]any); ok { - // Parse title-prefix - if titlePrefix, exists := configMap["title-prefix"]; exists { - if titlePrefixStr, ok := titlePrefix.(string); ok { - issuesConfig.TitlePrefix = titlePrefixStr + if configData, exists := outputMap["create-issue"]; exists { + issuesConfig := &CreateIssuesConfig{Max: 1} // Default max is 1 + + if configMap, ok := configData.(map[string]any); ok { + // Parse title-prefix + if titlePrefix, exists := configMap["title-prefix"]; exists { + if titlePrefixStr, ok := titlePrefix.(string); ok { + issuesConfig.TitlePrefix = titlePrefixStr + } } - } - // Parse labels - if labels, exists := configMap["labels"]; exists { - if labelsArray, ok := labels.([]any); ok { - var labelStrings []string - for _, label := range labelsArray { - if labelStr, ok := label.(string); ok { - labelStrings = append(labelStrings, labelStr) + // Parse labels + if labels, exists := configMap["labels"]; exists { + if labelsArray, ok := labels.([]any); ok { + var labelStrings []string + for _, label := range labelsArray { + if labelStr, ok := label.(string); ok { + labelStrings = append(labelStrings, labelStr) + } } + issuesConfig.Labels = labelStrings } - issuesConfig.Labels = labelStrings } - } - // Parse max (only for plural form) - if hasPlural { + // Parse max if max, exists := configMap["max"]; exists { if maxInt, ok := c.parseIntValue(max); ok { issuesConfig.Max = maxInt } } } + + return issuesConfig } - return issuesConfig + return nil } -// parseCommentsConfig handles both add-issue-comment (singular) and add-issue-comments (plural) configurations +// parseCommentsConfig handles add-issue-comment configuration func (c *Compiler) parseCommentsConfig(outputMap map[string]any) *AddIssueCommentsConfig { - // Check for both singular and plural forms - hasSingular := false - hasPlural := false - - if _, exists := outputMap["add-issue-comment"]; exists { - hasSingular = true - } - if _, exists := outputMap["add-issue-comments"]; exists { - hasPlural = true - } + if configData, exists := outputMap["add-issue-comment"]; exists { + commentsConfig := &AddIssueCommentsConfig{Max: 1} // Default max is 1 - // Error if both are specified - if hasSingular && hasPlural { - // This should be caught by validation, but we'll handle it gracefully - // Prefer plural form - hasSingular = false - } - - var configData any - var defaultMax int - - if hasPlural { - configData = outputMap["add-issue-comments"] - defaultMax = 10 // Default for plural form - } else if hasSingular { - configData = outputMap["add-issue-comment"] - defaultMax = 1 // Singular form always has max 1 - } else { - return nil - } - - commentsConfig := &AddIssueCommentsConfig{Max: defaultMax} - - if configMap, ok := configData.(map[string]any); ok { - // Parse max (only for plural form) - if hasPlural { + if configMap, ok := configData.(map[string]any); ok { + // Parse max if max, exists := configMap["max"]; exists { if maxInt, ok := c.parseIntValue(max); ok { commentsConfig.Max = maxInt } } - } - // Parse target field (for both singular and plural forms) - if target, exists := configMap["target"]; exists { - if targetStr, ok := target.(string); ok { - commentsConfig.Target = targetStr + // Parse target + if target, exists := configMap["target"]; exists { + if targetStr, ok := target.(string); ok { + commentsConfig.Target = targetStr + } } } + + return commentsConfig } - return commentsConfig + return nil } // parsePullRequestsConfig handles only create-pull-request (singular) configuration func (c *Compiler) parsePullRequestsConfig(outputMap map[string]any) *CreatePullRequestsConfig { // Check for singular form only - hasSingular := false - if _, exists := outputMap["create-pull-request"]; exists { - hasSingular = true - } - - // Check for unsupported plural form and return nil (no error, just ignore) - if _, exists := outputMap["create-pull-requests"]; exists { - // Plural form is not supported for pull requests - ignore it - return nil - } - - if !hasSingular { + if _, exists := outputMap["create-pull-request"]; !exists { return nil } @@ -3016,7 +2947,7 @@ func (c *Compiler) generateOutputCollectionStep(yaml *strings.Builder, data *Wor safeOutputsConfig["create-pull-request"] = true } if data.SafeOutputs.AddIssueLabels != nil { - safeOutputsConfig["add-issue-labels"] = true + safeOutputsConfig["add-issue-label"] = true } // Convert to JSON string for environment variable diff --git a/pkg/workflow/git_patch_test.go b/pkg/workflow/git_patch_test.go index d4fd78dea0e..128dfb04c74 100644 --- a/pkg/workflow/git_patch_test.go +++ b/pkg/workflow/git_patch_test.go @@ -30,7 +30,7 @@ func TestGitPatchGeneration(t *testing.T) { on: workflow_dispatch: safe-outputs: - add-issue-labels: + add-issue-label: allowed: ["bug", "enhancement"] --- diff --git a/pkg/workflow/js/add_labels.cjs b/pkg/workflow/js/add_labels.cjs index acaec144266..45cb7fc4dcf 100644 --- a/pkg/workflow/js/add_labels.cjs +++ b/pkg/workflow/js/add_labels.cjs @@ -27,14 +27,14 @@ async function main() { return; } - // Find the add-issue-labels item - const labelsItem = validatedOutput.items.find(/** @param {any} item */ item => item.type === 'add-issue-labels'); + // Find the add-issue-label item + const labelsItem = validatedOutput.items.find(/** @param {any} item */ item => item.type === 'add-issue-label'); if (!labelsItem) { - console.log('No add-issue-labels item found in agent output'); + console.log('No add-issue-label item found in agent output'); return; } - console.log('Found add-issue-labels item:', { labelsCount: labelsItem.labels.length }); + console.log('Found add-issue-label item:', { labelsCount: labelsItem.labels.length }); // Read the allowed labels from environment variable (optional) const allowedLabelsEnv = process.env.GITHUB_AW_LABELS_ALLOWED; @@ -53,11 +53,11 @@ async function main() { console.log('No label restrictions - any labels are allowed'); } - // Read the max-count limit from environment variable (default: 3) + // Read the max limit from environment variable (default: 3) const maxCountEnv = process.env.GITHUB_AW_LABELS_MAX_COUNT; const maxCount = maxCountEnv ? parseInt(maxCountEnv, 10) : 3; if (isNaN(maxCount) || maxCount < 1) { - core.setFailed(`Invalid max-count value: ${maxCountEnv}. Must be a positive integer`); + core.setFailed(`Invalid max value: ${maxCountEnv}. Must be a positive integer`); return; } @@ -123,7 +123,7 @@ async function main() { // Remove duplicates from requested labels let uniqueLabels = [...new Set(validLabels)]; - // Enforce max-count limit + // Enforce max limit if (uniqueLabels.length > maxCount) { console.log(`too many labels, keep ${maxCount}`) uniqueLabels = uniqueLabels.slice(0, maxCount); diff --git a/pkg/workflow/js/add_labels.test.cjs b/pkg/workflow/js/add_labels.test.cjs index 9387a16bf76..a267a2650dc 100644 --- a/pkg/workflow/js/add_labels.test.cjs +++ b/pkg/workflow/js/add_labels.test.cjs @@ -94,7 +94,7 @@ describe('add_labels.cjs', () => { it('should work when allowed labels are not provided (any labels allowed)', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement', 'custom-label'] }] }); @@ -121,7 +121,7 @@ describe('add_labels.cjs', () => { it('should work when allowed labels list is empty (any labels allowed)', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement', 'custom-label'] }] }); @@ -148,7 +148,7 @@ describe('add_labels.cjs', () => { it('should enforce allowed labels when restrictions are set', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement', 'custom-label', 'documentation'] }] }); @@ -175,7 +175,7 @@ describe('add_labels.cjs', () => { it('should fail when max count is invalid', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement'] }] }); @@ -185,14 +185,14 @@ describe('add_labels.cjs', () => { // Execute the script await eval(`(async () => { ${addLabelsScript} })()`); - expect(mockCore.setFailed).toHaveBeenCalledWith('Invalid max-count value: invalid. Must be a positive integer'); + expect(mockCore.setFailed).toHaveBeenCalledWith('Invalid max value: invalid. Must be a positive integer'); expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); }); it('should fail when max count is zero', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement'] }] }); @@ -202,14 +202,14 @@ describe('add_labels.cjs', () => { // Execute the script await eval(`(async () => { ${addLabelsScript} })()`); - expect(mockCore.setFailed).toHaveBeenCalledWith('Invalid max-count value: 0. Must be a positive integer'); + expect(mockCore.setFailed).toHaveBeenCalledWith('Invalid max value: 0. Must be a positive integer'); expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled(); }); it('should use default max count when not specified', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement', 'feature', 'documentation'] }] }); @@ -237,7 +237,7 @@ describe('add_labels.cjs', () => { it('should fail when not in issue or PR context', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement'] }] }); @@ -254,7 +254,7 @@ describe('add_labels.cjs', () => { it('should work with issue_comment event', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -274,7 +274,7 @@ describe('add_labels.cjs', () => { it('should work with pull_request event', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -301,7 +301,7 @@ describe('add_labels.cjs', () => { it('should work with pull_request_review event', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -328,7 +328,7 @@ describe('add_labels.cjs', () => { it('should fail when issue context detected but no issue in payload', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -346,7 +346,7 @@ describe('add_labels.cjs', () => { it('should fail when PR context detected but no PR in payload', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -367,7 +367,7 @@ describe('add_labels.cjs', () => { it('should parse labels from agent output and add valid ones', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement', 'documentation'] }] }); @@ -395,7 +395,7 @@ describe('add_labels.cjs', () => { it('should skip empty lines in agent output', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement'] }] }); @@ -419,7 +419,7 @@ describe('add_labels.cjs', () => { it('should fail when line starts with dash (removal indication)', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', '-enhancement'] }] }); @@ -435,7 +435,7 @@ describe('add_labels.cjs', () => { it('should remove duplicate labels', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement', 'bug', 'enhancement'] }] }); @@ -459,7 +459,7 @@ describe('add_labels.cjs', () => { it('should enforce max count limit', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement', 'feature', 'documentation', 'question'] }] }); @@ -485,7 +485,7 @@ describe('add_labels.cjs', () => { it('should skip when no valid labels found', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['invalid', 'another-invalid'] }] }); @@ -509,7 +509,7 @@ describe('add_labels.cjs', () => { it('should successfully add labels to issue', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement'] }] }); @@ -545,7 +545,7 @@ describe('add_labels.cjs', () => { it('should successfully add labels to pull request', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -574,7 +574,7 @@ describe('add_labels.cjs', () => { it('should handle GitHub API errors', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -597,7 +597,7 @@ describe('add_labels.cjs', () => { it('should handle non-Error objects in catch block', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -622,7 +622,7 @@ describe('add_labels.cjs', () => { it('should log agent output content length', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement'] }] }); @@ -633,7 +633,7 @@ describe('add_labels.cjs', () => { // Execute the script await eval(`(async () => { ${addLabelsScript} })()`); - expect(consoleSpy).toHaveBeenCalledWith('Agent output content length:', 70); + expect(consoleSpy).toHaveBeenCalledWith('Agent output content length:', 69); consoleSpy.mockRestore(); }); @@ -641,7 +641,7 @@ describe('add_labels.cjs', () => { it('should log allowed labels and max count', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -662,7 +662,7 @@ describe('add_labels.cjs', () => { it('should log requested labels', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement', 'invalid'] }] }); @@ -681,7 +681,7 @@ describe('add_labels.cjs', () => { it('should log final labels being added', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement'] }] }); @@ -702,7 +702,7 @@ describe('add_labels.cjs', () => { it('should handle whitespace in allowed labels', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug', 'enhancement'] }] }); @@ -727,7 +727,7 @@ describe('add_labels.cjs', () => { it('should handle empty entries in allowed labels', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); @@ -746,7 +746,7 @@ describe('add_labels.cjs', () => { it('should handle single label output', async () => { process.env.GITHUB_AW_AGENT_OUTPUT = JSON.stringify({ items: [{ - type: 'add-issue-labels', + type: 'add-issue-label', labels: ['bug'] }] }); diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index 3ad997fa9f1..0e7fd7b8a03 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -141,13 +141,13 @@ async function main() { // Use default limits for plural-supported types switch (itemType) { case 'create-issue': - return 10; // Allow multiple issues + return 1; // Only one issue allowed case 'add-issue-comment': - return 10; // Allow multiple comments + return 1; // Only one comment allowed case 'create-pull-request': return 1; // Only one pull request allowed - case 'add-issue-labels': - return 1; // Only one labels operation allowed + case 'add-issue-label': + return 5; // Only one labels operation allowed default: return 1; // Default to single item for unknown types } @@ -267,13 +267,13 @@ async function main() { } break; - case 'add-issue-labels': + case 'add-issue-label': if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add-issue-labels requires a 'labels' array field`); + errors.push(`Line ${i + 1}: add-issue-label requires a 'labels' array field`); continue; } if (item.labels.some(label => typeof label !== 'string')) { - errors.push(`Line ${i + 1}: add-issue-labels labels array must contain only strings`); + errors.push(`Line ${i + 1}: add-issue-label labels array must contain only strings`); continue; } // Sanitize label strings diff --git a/pkg/workflow/js/collect_ndjson_output.test.cjs b/pkg/workflow/js/collect_ndjson_output.test.cjs index 17f320f4b4a..f3177c63af1 100644 --- a/pkg/workflow/js/collect_ndjson_output.test.cjs +++ b/pkg/workflow/js/collect_ndjson_output.test.cjs @@ -150,15 +150,15 @@ describe('collect_ndjson_output.cjs', () => { expect(parsedOutput.errors[1]).toContain('requires a \'title\' string field'); }); - it('should validate required fields for add-issue-labels type', async () => { + it('should validate required fields for add-issue-label type', async () => { const testFile = '/tmp/test-ndjson-output.txt'; - const ndjsonContent = `{"type": "add-issue-labels", "labels": ["bug", "enhancement"]} -{"type": "add-issue-labels", "labels": "not-an-array"} -{"type": "add-issue-labels", "labels": [1, 2, 3]}`; + const ndjsonContent = `{"type": "add-issue-label", "labels": ["bug", "enhancement"]} +{"type": "add-issue-label", "labels": "not-an-array"} +{"type": "add-issue-label", "labels": [1, 2, 3]}`; fs.writeFileSync(testFile, ndjsonContent); process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; - process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"add-issue-labels": true}'; + process.env.GITHUB_AW_SAFE_OUTPUTS_CONFIG = '{"add-issue-label": true}'; await eval(`(async () => { ${collectScript} })()`); @@ -196,8 +196,7 @@ describe('collect_ndjson_output.cjs', () => { it('should allow multiple items of supported types up to limits', async () => { const testFile = '/tmp/test-ndjson-output.txt'; - const ndjsonContent = `{"type": "create-issue", "title": "First Issue", "body": "First body"} -{"type": "create-issue", "title": "Second Issue", "body": "Second body"}`; + const ndjsonContent = `{"type": "create-issue", "title": "First Issue", "body": "First body"}`; fs.writeFileSync(testFile, ndjsonContent); process.env.GITHUB_AW_SAFE_OUTPUTS = testFile; @@ -210,9 +209,8 @@ describe('collect_ndjson_output.cjs', () => { expect(outputCall).toBeDefined(); const parsedOutput = JSON.parse(outputCall[1]); - expect(parsedOutput.items).toHaveLength(2); // Both items should be allowed + expect(parsedOutput.items).toHaveLength(1); // Both items should be allowed expect(parsedOutput.items[0].title).toBe('First Issue'); - expect(parsedOutput.items[1].title).toBe('Second Issue'); expect(parsedOutput.errors).toHaveLength(0); // No errors for multiple items within limits }); diff --git a/pkg/workflow/output_labels.go b/pkg/workflow/output_labels.go index 0301a5e2b99..ecfdb034a1e 100644 --- a/pkg/workflow/output_labels.go +++ b/pkg/workflow/output_labels.go @@ -34,7 +34,7 @@ func (c *Compiler) buildCreateOutputLabelJob(data *WorkflowData, mainJobName str // Pass the allowed labels list (empty string if no restrictions) allowedLabelsStr := strings.Join(allowedLabels, ",") steps = append(steps, fmt.Sprintf(" GITHUB_AW_LABELS_ALLOWED: %q\n", allowedLabelsStr)) - // Pass the max-count limit + // Pass the max limit steps = append(steps, fmt.Sprintf(" GITHUB_AW_LABELS_MAX_COUNT: %d\n", maxCount)) steps = append(steps, " with:\n") diff --git a/pkg/workflow/output_test.go b/pkg/workflow/output_test.go index 9992370c8ba..086604a3ec0 100644 --- a/pkg/workflow/output_test.go +++ b/pkg/workflow/output_test.go @@ -135,7 +135,7 @@ safe-outputs: create-issue: create-pull-request: add-issue-comment: - add-issue-labels: + add-issue-label: --- # Test Null Output Configuration @@ -188,15 +188,15 @@ This workflow tests the null output configuration parsing. t.Fatal("Expected add-issue-comment configuration to be parsed with null value") } - // Verify add-issue-labels configuration is parsed with empty values + // Verify add-issue-label configuration is parsed with empty values if workflowData.SafeOutputs.AddIssueLabels == nil { - t.Fatal("Expected add-issue-labels configuration to be parsed with null value") + t.Fatal("Expected add-issue-label configuration to be parsed with null value") } if len(workflowData.SafeOutputs.AddIssueLabels.Allowed) != 0 { - t.Errorf("Expected empty allowed labels for null add-issue-labels, got %v", workflowData.SafeOutputs.AddIssueLabels.Allowed) + t.Errorf("Expected empty allowed labels for null add-issue-label, got %v", workflowData.SafeOutputs.AddIssueLabels.Allowed) } if workflowData.SafeOutputs.AddIssueLabels.MaxCount != nil { - t.Errorf("Expected nil MaxCount for null add-issue-labels, got %v", *workflowData.SafeOutputs.AddIssueLabels.MaxCount) + t.Errorf("Expected nil MaxCount for null add-issue-label, got %v", *workflowData.SafeOutputs.AddIssueLabels.MaxCount) } } @@ -442,15 +442,15 @@ This workflow tests the output.add-issue-comment target configuration parsing. } } -func TestOutputCommentPluralTargetParsing(t *testing.T) { +func TestOutputCommentMaxTargetParsing(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "output-comment-plural-target-test") + tmpDir, err := os.MkdirTemp("", "output-comment-max-target-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) - // Test case with plural form and target configuration + // Test case with max and target configuration testContent := `--- on: issues: @@ -461,17 +461,17 @@ permissions: pull-requests: write engine: claude safe-outputs: - add-issue-comments: + add-issue-comment: max: 3 target: "123" --- -# Test Output Issue Comments Plural Target Configuration +# Test Output Issue Comments Max Target Configuration -This workflow tests the add-issue-comments plural target configuration parsing. +This workflow tests the add-issue-comment max and target configuration parsing. ` - testFile := filepath.Join(tmpDir, "test-output-issue-comments-target.md") + testFile := filepath.Join(tmpDir, "test-output-issue-comment-max-target.md") if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatal(err) } @@ -481,7 +481,7 @@ This workflow tests the add-issue-comments plural target configuration parsing. // Parse the workflow data workflowData, err := compiler.parseWorkflowFile(testFile) if err != nil { - t.Fatalf("Unexpected error parsing workflow with plural target comment config: %v", err) + t.Fatalf("Unexpected error parsing workflow with max target comment config: %v", err) } // Verify output configuration is parsed correctly @@ -985,7 +985,7 @@ func TestOutputLabelConfigParsing(t *testing.T) { } defer os.RemoveAll(tmpDir) - // Test case with add-issue-labels configuration + // Test case with add-issue-label configuration testContent := `--- on: issues: @@ -996,7 +996,7 @@ permissions: pull-requests: write engine: claude safe-outputs: - add-issue-labels: + add-issue-label: allowed: [triage, bug, enhancement, needs-review] --- @@ -1048,7 +1048,7 @@ func TestOutputLabelJobGeneration(t *testing.T) { } defer os.RemoveAll(tmpDir) - // Test case with add-issue-labels configuration + // Test case with add-issue-label configuration testContent := `--- on: issues: @@ -1062,7 +1062,7 @@ tools: allowed: [get_issue] engine: claude safe-outputs: - add-issue-labels: + add-issue-label: allowed: [triage, bug, enhancement] --- @@ -1157,8 +1157,8 @@ permissions: issues: write engine: claude safe-outputs: - add-issue-labels: - max-count: 5 + add-issue-label: + max: 5 --- # Test Output Label No Allowed Labels @@ -1203,9 +1203,9 @@ Write your labels to ${{ env.GITHUB_AW_SAFE_OUTPUTS }}, one per line. t.Error("Expected empty allowed labels to be set as environment variable") } - // Verify max-count is set correctly + // Verify max is set correctly if !strings.Contains(lockContent, "GITHUB_AW_LABELS_MAX_COUNT: 5") { - t.Error("Expected max-count to be set correctly") + t.Error("Expected max to be set correctly") } t.Logf("Generated workflow content:\n%s", lockContent) @@ -1219,7 +1219,7 @@ func TestOutputLabelJobGenerationNullConfig(t *testing.T) { } defer os.RemoveAll(tmpDir) - // Test workflow with null add-issue-labels configuration + // Test workflow with null add-issue-label configuration testContent := `--- on: issues: @@ -1229,7 +1229,7 @@ permissions: issues: write engine: claude safe-outputs: - add-issue-labels: + add-issue-label: --- # Test Output Label Null Config @@ -1279,9 +1279,9 @@ Write your labels to ${{ env.GITHUB_AW_SAFE_OUTPUTS }}, one per line. t.Error("Expected empty allowed labels to be set as environment variable") } - // Verify default max-count is set correctly + // Verify default max is set correctly if !strings.Contains(lockContent, "GITHUB_AW_LABELS_MAX_COUNT: 3") { - t.Error("Expected default max-count to be set correctly") + t.Error("Expected default max to be set correctly") } t.Logf("Generated workflow content:\n%s", lockContent) @@ -1295,7 +1295,7 @@ func TestOutputLabelConfigNullParsing(t *testing.T) { } defer os.RemoveAll(tmpDir) - // Test case with null add-issue-labels configuration + // Test case with null add-issue-label configuration testContent := `--- on: issues: @@ -1306,7 +1306,7 @@ permissions: pull-requests: write engine: claude safe-outputs: - add-issue-labels: + add-issue-label: --- # Test Output Label Null Configuration Parsing @@ -1341,21 +1341,21 @@ This workflow tests the output labels null configuration parsing. t.Errorf("Expected 0 allowed labels for null config, got %d", len(workflowData.SafeOutputs.AddIssueLabels.Allowed)) } - // Verify max-count is nil (will use default) + // Verify max is nil (will use default) if workflowData.SafeOutputs.AddIssueLabels.MaxCount != nil { - t.Errorf("Expected max-count to be nil for null config, got %d", *workflowData.SafeOutputs.AddIssueLabels.MaxCount) + t.Errorf("Expected max to be nil for null config, got %d", *workflowData.SafeOutputs.AddIssueLabels.MaxCount) } } func TestOutputLabelConfigMaxCountParsing(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "output-label-max-count-test") + tmpDir, err := os.MkdirTemp("", "output-label-max-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) - // Test case with add-issue-labels configuration including max-count + // Test case with add-issue-label configuration including max testContent := `--- on: issues: @@ -1366,17 +1366,17 @@ permissions: pull-requests: write engine: claude safe-outputs: - add-issue-labels: + add-issue-label: allowed: [triage, bug, enhancement, needs-review] - max-count: 5 + max: 5 --- # Test Output Label Max Count Configuration -This workflow tests the output labels max-count configuration parsing. +This workflow tests the output labels max configuration parsing. ` - testFile := filepath.Join(tmpDir, "test-output-labels-max-count.md") + testFile := filepath.Join(tmpDir, "test-output-labels-max.md") if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatal(err) } @@ -1386,7 +1386,7 @@ This workflow tests the output labels max-count configuration parsing. // Parse the workflow data workflowData, err := compiler.parseWorkflowFile(testFile) if err != nil { - t.Fatalf("Unexpected error parsing workflow with output labels max-count config: %v", err) + t.Fatalf("Unexpected error parsing workflow with output labels max config: %v", err) } // Verify output configuration is parsed correctly @@ -1410,26 +1410,26 @@ This workflow tests the output labels max-count configuration parsing. } } - // Verify max-count + // Verify max if workflowData.SafeOutputs.AddIssueLabels.MaxCount == nil { - t.Fatal("Expected max-count to be parsed") + t.Fatal("Expected max to be parsed") } expectedMaxCount := 5 if *workflowData.SafeOutputs.AddIssueLabels.MaxCount != expectedMaxCount { - t.Errorf("Expected max-count to be %d, got %d", expectedMaxCount, *workflowData.SafeOutputs.AddIssueLabels.MaxCount) + t.Errorf("Expected max to be %d, got %d", expectedMaxCount, *workflowData.SafeOutputs.AddIssueLabels.MaxCount) } } func TestOutputLabelConfigDefaultMaxCount(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "output-label-default-max-count-test") + tmpDir, err := os.MkdirTemp("", "output-label-default-max-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) - // Test case with add-issue-labels configuration without max-count (should use default) + // Test case with add-issue-label configuration without max (should use default) testContent := `--- on: issues: @@ -1440,13 +1440,13 @@ permissions: pull-requests: write engine: claude safe-outputs: - add-issue-labels: + add-issue-label: allowed: [triage, bug, enhancement] --- # Test Output Label Default Max Count -This workflow tests the default max-count behavior. +This workflow tests the default max behavior. ` testFile := filepath.Join(tmpDir, "test-output-labels-default.md") @@ -1459,24 +1459,24 @@ This workflow tests the default max-count behavior. // Parse the workflow data workflowData, err := compiler.parseWorkflowFile(testFile) if err != nil { - t.Fatalf("Unexpected error parsing workflow without max-count: %v", err) + t.Fatalf("Unexpected error parsing workflow without max: %v", err) } - // Verify max-count is nil (will use default in job generation) + // Verify max is nil (will use default in job generation) if workflowData.SafeOutputs.AddIssueLabels.MaxCount != nil { - t.Errorf("Expected max-count to be nil (default), got %d", *workflowData.SafeOutputs.AddIssueLabels.MaxCount) + t.Errorf("Expected max to be nil (default), got %d", *workflowData.SafeOutputs.AddIssueLabels.MaxCount) } } func TestOutputLabelJobGenerationWithMaxCount(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "output-label-job-max-count-test") + tmpDir, err := os.MkdirTemp("", "output-label-job-max-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) - // Test case with add-issue-labels configuration including max-count + // Test case with add-issue-label configuration including max testContent := `--- on: issues: @@ -1490,17 +1490,17 @@ tools: allowed: [get_issue] engine: claude safe-outputs: - add-issue-labels: + add-issue-label: allowed: [triage, bug, enhancement] - max-count: 2 + max: 2 --- # Test Output Label Job Generation with Max Count -This workflow tests the add_labels job generation with max-count. +This workflow tests the add_labels job generation with max. ` - testFile := filepath.Join(tmpDir, "test-output-labels-max-count.md") + testFile := filepath.Join(tmpDir, "test-output-labels-max.md") if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatal(err) } @@ -1510,11 +1510,11 @@ This workflow tests the add_labels job generation with max-count. // Compile the workflow err = compiler.CompileWorkflow(testFile) if err != nil { - t.Fatalf("Unexpected error compiling workflow with output labels max-count: %v", err) + t.Fatalf("Unexpected error compiling workflow with output labels max: %v", err) } // Read the generated lock file - lockFile := filepath.Join(tmpDir, "test-output-labels-max-count.lock.yml") + lockFile := filepath.Join(tmpDir, "test-output-labels-max.lock.yml") content, err := os.ReadFile(lockFile) if err != nil { t.Fatalf("Failed to read generated lock file: %v", err) @@ -1532,9 +1532,9 @@ This workflow tests the add_labels job generation with max-count. t.Error("Expected allowed labels to be set as environment variable") } - // Verify max-count environment variable is set + // Verify max environment variable is set if !strings.Contains(lockContent, "GITHUB_AW_LABELS_MAX_COUNT: 2") { - t.Error("Expected max-count to be set as environment variable") + t.Error("Expected max to be set as environment variable") } t.Logf("Generated workflow content:\n%s", lockContent) @@ -1542,13 +1542,13 @@ This workflow tests the add_labels job generation with max-count. func TestOutputLabelJobGenerationWithDefaultMaxCount(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "output-label-job-default-max-count-test") + tmpDir, err := os.MkdirTemp("", "output-label-job-default-max-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) - // Test case with add-issue-labels configuration without max-count (should use default of 3) + // Test case with add-issue-label configuration without max (should use default of 3) testContent := `--- on: issues: @@ -1562,16 +1562,16 @@ tools: allowed: [get_issue] engine: claude safe-outputs: - add-issue-labels: + add-issue-label: allowed: [triage, bug, enhancement] --- # Test Output Label Job Generation with Default Max Count -This workflow tests the add_labels job generation with default max-count. +This workflow tests the add_labels job generation with default max. ` - testFile := filepath.Join(tmpDir, "test-output-labels-default-max-count.md") + testFile := filepath.Join(tmpDir, "test-output-labels-default-max.md") if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatal(err) } @@ -1581,11 +1581,11 @@ This workflow tests the add_labels job generation with default max-count. // Compile the workflow err = compiler.CompileWorkflow(testFile) if err != nil { - t.Fatalf("Unexpected error compiling workflow with output labels default max-count: %v", err) + t.Fatalf("Unexpected error compiling workflow with output labels default max: %v", err) } // Read the generated lock file - lockFile := filepath.Join(tmpDir, "test-output-labels-default-max-count.lock.yml") + lockFile := filepath.Join(tmpDir, "test-output-labels-default-max.lock.yml") content, err := os.ReadFile(lockFile) if err != nil { t.Fatalf("Failed to read generated lock file: %v", err) @@ -1598,9 +1598,9 @@ This workflow tests the add_labels job generation with default max-count. t.Error("Expected 'add_labels' job to be in generated workflow") } - // Verify max-count environment variable is set to default value of 3 + // Verify max environment variable is set to default value of 3 if !strings.Contains(lockContent, "GITHUB_AW_LABELS_MAX_COUNT: 3") { - t.Error("Expected max-count to be set to default value of 3 as environment variable") + t.Error("Expected max to be set to default value of 3 as environment variable") } t.Logf("Generated workflow content:\n%s", lockContent) @@ -1624,7 +1624,7 @@ permissions: issues: write engine: claude safe-outputs: - add-issue-labels: + add-issue-label: allowed: [] --- @@ -1669,7 +1669,7 @@ permissions: issues: write engine: claude safe-outputs: - add-issue-labels: {} + add-issue-label: {} --- # Test Output Label Missing Allowed diff --git a/pkg/workflow/plural_safe_outputs_integration_test.go b/pkg/workflow/plural_safe_outputs_integration_test.go deleted file mode 100644 index 3d21f6c01dd..00000000000 --- a/pkg/workflow/plural_safe_outputs_integration_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package workflow - -import ( - "testing" - - "github.com/githubnext/gh-aw/pkg/parser" -) - -func TestSafeOutputsBackwardCompatibility(t *testing.T) { - compiler := &Compiler{} - - t.Run("Legacy singular syntax should still work", func(t *testing.T) { - content := `--- -safe-outputs: - create-issue: - title-prefix: "[Auto] " - labels: ["bug", "auto-generated"] - add-issue-comment: - create-pull-request: - title-prefix: "[Fix] " - draft: true ---- - -# Test workflow - -This workflow should work with legacy syntax. -` - - // Parse the workflow content - result, err := parser.ExtractFrontmatterFromContent(content) - if err != nil { - t.Fatalf("Failed to parse frontmatter: %v", err) - } - - config := compiler.extractSafeOutputsConfig(result.Frontmatter) - if config == nil { - t.Fatal("Expected config to be parsed") - } - - // Verify create-issue (singular) is converted to create-issues with max: 1 - if config.CreateIssues == nil { - t.Fatal("Expected CreateIssues to be parsed from legacy create-issue") - } - if config.CreateIssues.Max != 1 { - t.Errorf("Expected CreateIssues.Max to be 1 for legacy syntax, got %d", config.CreateIssues.Max) - } - if config.CreateIssues.TitlePrefix != "[Auto] " { - t.Errorf("Expected TitlePrefix '[Auto] ', got '%s'", config.CreateIssues.TitlePrefix) - } - if len(config.CreateIssues.Labels) != 2 { - t.Errorf("Expected 2 labels, got %d", len(config.CreateIssues.Labels)) - } - - // Verify add-issue-comment (singular) is converted to add-issue-comments with max: 1 - if config.AddIssueComments == nil { - t.Fatal("Expected AddIssueComments to be parsed from legacy add-issue-comment") - } - if config.AddIssueComments.Max != 1 { - t.Errorf("Expected AddIssueComments.Max to be 1 for legacy syntax, got %d", config.AddIssueComments.Max) - } - - // Verify create-pull-request (singular) stays as max: 1 - if config.CreatePullRequests == nil { - t.Fatal("Expected CreatePullRequests to be parsed from create-pull-request") - } - if config.CreatePullRequests.Max != 1 { - t.Errorf("Expected CreatePullRequests.Max to be 1 for singular syntax, got %d", config.CreatePullRequests.Max) - } - if config.CreatePullRequests.TitlePrefix != "[Fix] " { - t.Errorf("Expected TitlePrefix '[Fix] ', got '%s'", config.CreatePullRequests.TitlePrefix) - } - if config.CreatePullRequests.Draft == nil || *config.CreatePullRequests.Draft != true { - t.Errorf("Expected Draft to be true, got %v", config.CreatePullRequests.Draft) - } - }) - - t.Run("New plural syntax should work", func(t *testing.T) { - content := `--- -safe-outputs: - create-issues: - title-prefix: "[Batch] " - labels: ["enhancement"] - max: 5 - add-issue-comments: - max: 3 - create-pull-request: - title-prefix: "[Single] " - draft: false ---- - -# Test workflow - -This workflow uses the new plural syntax for issues and comments, singular for pull requests. -` - - // Parse the workflow content - result, err := parser.ExtractFrontmatterFromContent(content) - if err != nil { - t.Fatalf("Failed to parse frontmatter: %v", err) - } - - config := compiler.extractSafeOutputsConfig(result.Frontmatter) - if config == nil { - t.Fatal("Expected config to be parsed") - } - - // Verify create-issues (plural) with explicit max - if config.CreateIssues == nil { - t.Fatal("Expected CreateIssues to be parsed") - } - if config.CreateIssues.Max != 5 { - t.Errorf("Expected CreateIssues.Max to be 5, got %d", config.CreateIssues.Max) - } - if config.CreateIssues.TitlePrefix != "[Batch] " { - t.Errorf("Expected TitlePrefix '[Batch] ', got '%s'", config.CreateIssues.TitlePrefix) - } - - // Verify add-issue-comments (plural) with explicit max - if config.AddIssueComments == nil { - t.Fatal("Expected AddIssueComments to be parsed") - } - if config.AddIssueComments.Max != 3 { - t.Errorf("Expected AddIssueComments.Max to be 3, got %d", config.AddIssueComments.Max) - } - - // Verify create-pull-request (singular) is always max: 1 - if config.CreatePullRequests == nil { - t.Fatal("Expected CreatePullRequests to be parsed") - } - if config.CreatePullRequests.Max != 1 { - t.Errorf("Expected CreatePullRequests.Max to be 1 (singular), got %d", config.CreatePullRequests.Max) - } - if config.CreatePullRequests.Draft == nil || *config.CreatePullRequests.Draft != false { - t.Errorf("Expected Draft to be false, got %v", config.CreatePullRequests.Draft) - } - }) - - t.Run("Plural syntax without explicit max should default to 10", func(t *testing.T) { - content := `--- -safe-outputs: - create-issues: - add-issue-comments: - create-pull-request: ---- - -# Test workflow - -This workflow uses plural syntax without explicit max values (except pull request which is always singular). -` - - // Parse the workflow content - result, err := parser.ExtractFrontmatterFromContent(content) - if err != nil { - t.Fatalf("Failed to parse frontmatter: %v", err) - } - - config := compiler.extractSafeOutputsConfig(result.Frontmatter) - if config == nil { - t.Fatal("Expected config to be parsed") - } - - // Issues and comments should default to max: 10, pull requests always max: 1 - if config.CreateIssues == nil || config.CreateIssues.Max != 10 { - t.Errorf("Expected CreateIssues.Max to be 10, got %d", config.CreateIssues.Max) - } - if config.AddIssueComments == nil || config.AddIssueComments.Max != 10 { - t.Errorf("Expected AddIssueComments.Max to be 10, got %d", config.AddIssueComments.Max) - } - if config.CreatePullRequests == nil || config.CreatePullRequests.Max != 1 { - t.Errorf("Expected CreatePullRequests.Max to be 1 (singular), got %d", config.CreatePullRequests.Max) - } - }) -} - -func TestWorkflowCompilationWithPluralSafeOutputs(t *testing.T) { - t.Run("Workflow should parse plural safe-outputs configuration", func(t *testing.T) { - content := `--- -safe-outputs: - create-issues: - max: 2 - add-issue-comments: - max: 4 ---- - -# Test workflow - -This workflow uses plural safe-outputs and should compile successfully. - -Analyze the repository and create issues for any problems found. -` - - // Parse just the frontmatter content - result, err := parser.ExtractFrontmatterFromContent(content) - if err != nil { - t.Fatalf("Failed to parse frontmatter: %v", err) - } - - // Extract safe-outputs configuration - compiler := &Compiler{} - config := compiler.extractSafeOutputsConfig(result.Frontmatter) - - // Verify the configuration was parsed correctly - if config == nil { - t.Fatal("Expected SafeOutputs to be parsed") - } - if config.CreateIssues == nil || config.CreateIssues.Max != 2 { - t.Errorf("Expected CreateIssues.Max to be 2, got %v", config.CreateIssues) - } - if config.AddIssueComments == nil || config.AddIssueComments.Max != 4 { - t.Errorf("Expected AddIssueComments.Max to be 4, got %v", config.AddIssueComments) - } - }) -} diff --git a/pkg/workflow/plural_safe_outputs_test.go b/pkg/workflow/safe_outputs_max_test.go similarity index 55% rename from pkg/workflow/plural_safe_outputs_test.go rename to pkg/workflow/safe_outputs_max_test.go index 5f242fad663..a9f7453b8df 100644 --- a/pkg/workflow/plural_safe_outputs_test.go +++ b/pkg/workflow/safe_outputs_max_test.go @@ -4,10 +4,10 @@ import ( "testing" ) -func TestPluralSafeOutputs(t *testing.T) { +func TestSafeOutputsMaxConfiguration(t *testing.T) { compiler := &Compiler{} - t.Run("Singular forms should convert to max: 1", func(t *testing.T) { + t.Run("Default configuration should use max: 1", func(t *testing.T) { testSingular := map[string]any{ "safe-outputs": map[string]any{ "create-issue": nil, @@ -25,67 +25,31 @@ func TestPluralSafeOutputs(t *testing.T) { t.Fatal("Expected CreateIssues to be parsed") } if config.CreateIssues.Max != 1 { - t.Errorf("Expected CreateIssues.Max to be 1 for singular form, got %d", config.CreateIssues.Max) + t.Errorf("Expected CreateIssues.Max to be 1 by default, got %d", config.CreateIssues.Max) } if config.AddIssueComments == nil { t.Fatal("Expected AddIssueComments to be parsed") } if config.AddIssueComments.Max != 1 { - t.Errorf("Expected AddIssueComments.Max to be 1 for singular form, got %d", config.AddIssueComments.Max) + t.Errorf("Expected AddIssueComments.Max to be 1 by default, got %d", config.AddIssueComments.Max) } if config.CreatePullRequests == nil { t.Fatal("Expected CreatePullRequests to be parsed") } if config.CreatePullRequests.Max != 1 { - t.Errorf("Expected CreatePullRequests.Max to be 1 for singular form, got %d", config.CreatePullRequests.Max) + t.Errorf("Expected CreatePullRequests.Max to be 1 by default, got %d", config.CreatePullRequests.Max) } }) - t.Run("Plural forms should default to max: 10", func(t *testing.T) { - testPlural := map[string]any{ + t.Run("Explicit max values should be used", func(t *testing.T) { + testWithMax := map[string]any{ "safe-outputs": map[string]any{ - "create-issues": nil, - "add-issue-comments": nil, - "create-pull-request": nil, // Note: singular, not plural - }, - } - - config := compiler.extractSafeOutputsConfig(testPlural) - if config == nil { - t.Fatal("Expected config to be parsed") - } - - if config.CreateIssues == nil { - t.Fatal("Expected CreateIssues to be parsed") - } - if config.CreateIssues.Max != 10 { - t.Errorf("Expected CreateIssues.Max to be 10 for plural form, got %d", config.CreateIssues.Max) - } - - if config.AddIssueComments == nil { - t.Fatal("Expected AddIssueComments to be parsed") - } - if config.AddIssueComments.Max != 10 { - t.Errorf("Expected AddIssueComments.Max to be 10 for plural form, got %d", config.AddIssueComments.Max) - } - - if config.CreatePullRequests == nil { - t.Fatal("Expected CreatePullRequests to be parsed") - } - if config.CreatePullRequests.Max != 1 { - t.Errorf("Expected CreatePullRequests.Max to be 1 for singular form, got %d", config.CreatePullRequests.Max) - } - }) - - t.Run("Plural forms with explicit max should use provided value", func(t *testing.T) { - testPluralMax := map[string]any{ - "safe-outputs": map[string]any{ - "create-issues": map[string]any{ + "create-issue": map[string]any{ "max": 3, }, - "add-issue-comments": map[string]any{ + "add-issue-comment": map[string]any{ "max": 5, }, "create-pull-request": map[string]any{ @@ -95,7 +59,7 @@ func TestPluralSafeOutputs(t *testing.T) { }, } - config := compiler.extractSafeOutputsConfig(testPluralMax) + config := compiler.extractSafeOutputsConfig(testWithMax) if config == nil { t.Fatal("Expected config to be parsed") } @@ -122,14 +86,18 @@ func TestPluralSafeOutputs(t *testing.T) { } }) - t.Run("Mixed configurations should work correctly", func(t *testing.T) { - testMixed := map[string]any{ + t.Run("Complete configuration with all options", func(t *testing.T) { + testComplete := map[string]any{ "safe-outputs": map[string]any{ - "create-issues": map[string]any{ + "create-issue": map[string]any{ "title-prefix": "[Auto] ", "labels": []any{"bug", "auto-generated"}, "max": 2, }, + "add-issue-comment": map[string]any{ + "max": 3, + "target": "*", + }, "create-pull-request": map[string]any{ "title-prefix": "[Fix] ", "labels": []any{"fix"}, @@ -138,12 +106,12 @@ func TestPluralSafeOutputs(t *testing.T) { }, } - config := compiler.extractSafeOutputsConfig(testMixed) + config := compiler.extractSafeOutputsConfig(testComplete) if config == nil { t.Fatal("Expected config to be parsed") } - // Check plural create-issues + // Check create-issue if config.CreateIssues == nil { t.Fatal("Expected CreateIssues to be parsed") } @@ -157,12 +125,23 @@ func TestPluralSafeOutputs(t *testing.T) { t.Errorf("Expected CreateIssues.Labels to be ['bug', 'auto-generated'], got %v", config.CreateIssues.Labels) } - // Check singular create-pull-request (should convert to max: 1) + // Check add-issue-comment + if config.AddIssueComments == nil { + t.Fatal("Expected AddIssueComments to be parsed") + } + if config.AddIssueComments.Max != 3 { + t.Errorf("Expected AddIssueComments.Max to be 3, got %d", config.AddIssueComments.Max) + } + if config.AddIssueComments.Target != "*" { + t.Errorf("Expected AddIssueComments.Target to be '*', got '%s'", config.AddIssueComments.Target) + } + + // Check create-pull-request if config.CreatePullRequests == nil { t.Fatal("Expected CreatePullRequests to be parsed") } if config.CreatePullRequests.Max != 1 { - t.Errorf("Expected CreatePullRequests.Max to be 1 for singular form, got %d", config.CreatePullRequests.Max) + t.Errorf("Expected CreatePullRequests.Max to be 1, got %d", config.CreatePullRequests.Max) } if config.CreatePullRequests.TitlePrefix != "[Fix] " { t.Errorf("Expected CreatePullRequests.TitlePrefix to be '[Fix] ', got '%s'", config.CreatePullRequests.TitlePrefix) @@ -174,33 +153,4 @@ func TestPluralSafeOutputs(t *testing.T) { t.Errorf("Expected CreatePullRequests.Draft to be true, got %v", config.CreatePullRequests.Draft) } }) - - t.Run("Should prefer plural form when both singular and plural are present", func(t *testing.T) { - testBoth := map[string]any{ - "safe-outputs": map[string]any{ - "create-issue": map[string]any{ - "title-prefix": "[Singular] ", - }, - "create-issues": map[string]any{ - "title-prefix": "[Plural] ", - "max": 5, - }, - }, - } - - config := compiler.extractSafeOutputsConfig(testBoth) - if config == nil { - t.Fatal("Expected config to be parsed") - } - - if config.CreateIssues == nil { - t.Fatal("Expected CreateIssues to be parsed") - } - if config.CreateIssues.Max != 5 { - t.Errorf("Expected CreateIssues.Max to be 5 (from plural form), got %d", config.CreateIssues.Max) - } - if config.CreateIssues.TitlePrefix != "[Plural] " { - t.Errorf("Expected CreateIssues.TitlePrefix to be '[Plural] ' (from plural form), got '%s'", config.CreateIssues.TitlePrefix) - } - }) }