mirror of
				https://github.com/community-scripts/ProxmoxVE.git
				synced 2025-11-04 10:22:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			231 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
		
			Generated
		
	
	
			
		
		
	
	
			231 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
		
			Generated
		
	
	
name: Auto Label Pull Requests
 | 
						||
 | 
						||
on:
 | 
						||
  workflow_dispatch:
 | 
						||
  pull_request_target:
 | 
						||
    branches: ["main"]
 | 
						||
    types: [opened, synchronize, reopened, edited]
 | 
						||
 | 
						||
jobs:
 | 
						||
  autolabeler:
 | 
						||
    if: github.repository == 'community-scripts/ProxmoxVE'
 | 
						||
    runs-on: ubuntu-latest
 | 
						||
    permissions:
 | 
						||
      pull-requests: write
 | 
						||
    env:
 | 
						||
      CONFIG_PATH: .github/autolabeler-config.json
 | 
						||
    steps:
 | 
						||
      - name: Checkout repository
 | 
						||
        uses: actions/checkout@v4
 | 
						||
 | 
						||
      - name: Install dependencies
 | 
						||
        run: npm install minimatch
 | 
						||
 | 
						||
      - name: Label PR based on file changes and PR template
 | 
						||
        uses: actions/github-script@v7
 | 
						||
        with:
 | 
						||
          script: |
 | 
						||
            const fs = require('fs').promises;
 | 
						||
            const path = require('path');
 | 
						||
            const { minimatch } = require('minimatch');
 | 
						||
 | 
						||
            const configPath = path.resolve(process.env.CONFIG_PATH);
 | 
						||
            const fileContent = await fs.readFile(configPath, 'utf-8');
 | 
						||
            const autolabelerConfig = JSON.parse(fileContent);
 | 
						||
 | 
						||
            const prNumber = context.payload.pull_request.number;
 | 
						||
            const prBody = context.payload.pull_request.body || "";
 | 
						||
 | 
						||
            let labelsToAdd = new Set();
 | 
						||
 | 
						||
            const prListFilesResponse = await github.rest.pulls.listFiles({
 | 
						||
              owner: context.repo.owner,
 | 
						||
              repo: context.repo.repo,
 | 
						||
              pull_number: prNumber,
 | 
						||
            });
 | 
						||
            const prFiles = prListFilesResponse.data;
 | 
						||
 | 
						||
            for (const [label, rules] of Object.entries(autolabelerConfig)) {
 | 
						||
              const shouldAddLabel = prFiles.some((prFile) => {
 | 
						||
                return rules.some((rule) => {
 | 
						||
                  const isFileStatusMatch = rule.fileStatus ? rule.fileStatus === prFile.status : true;
 | 
						||
                  const isIncludeGlobMatch = rule.includeGlobs.some((glob) => minimatch(prFile.filename, glob));
 | 
						||
                  const isExcludeGlobMatch = rule.excludeGlobs.some((glob) => minimatch(prFile.filename, glob));
 | 
						||
                  return isFileStatusMatch && isIncludeGlobMatch && !isExcludeGlobMatch;
 | 
						||
                });
 | 
						||
              });
 | 
						||
 | 
						||
              if (shouldAddLabel) {
 | 
						||
                labelsToAdd.add(label);
 | 
						||
                if (label === "update script") {
 | 
						||
                  for (const prFile of prFiles) {
 | 
						||
                    const filename = prFile.filename;
 | 
						||
                    if (filename.startsWith("vm/")) labelsToAdd.add("vm");
 | 
						||
                    if (filename.startsWith("tools/addon/")) labelsToAdd.add("addon");
 | 
						||
                    if (filename.startsWith("tools/pve/")) labelsToAdd.add("pve-tool");
 | 
						||
                  }
 | 
						||
                }
 | 
						||
              }
 | 
						||
            }
 | 
						||
 | 
						||
            if (labelsToAdd.size < 2) {
 | 
						||
              const templateLabelMappings = {
 | 
						||
                "🐞 **Bug fix**": "bugfix",
 | 
						||
                "✨ **New feature**": "feature",
 | 
						||
                "💥 **Breaking change**": "breaking change",
 | 
						||
                "🆕 **New script**": "new script",
 | 
						||
                "🌍 **Website update**": "website", // handled special
 | 
						||
                "🔧 **Refactoring / Code Cleanup**": "refactor",
 | 
						||
                "📝 **Documentation update**": "documentation" // mapped to maintenance
 | 
						||
              };
 | 
						||
 | 
						||
              for (const [checkbox, label] of Object.entries(templateLabelMappings)) {
 | 
						||
                const escapedCheckbox = checkbox.replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$1");
 | 
						||
                const regex = new RegExp(`- \\[(x|X)\\]\\s*${escapedCheckbox}`, "i");
 | 
						||
 | 
						||
                if (regex.test(prBody)) {
 | 
						||
                  if (label === "website") {
 | 
						||
                    const hasJson = prFiles.some((f) => f.filename.startsWith("frontend/public/json/"));
 | 
						||
                    const hasUpdateScript = labelsToAdd.has("update script");
 | 
						||
                    const hasContentLabel = ["bugfix", "feature", "refactor"].some((l) => labelsToAdd.has(l));
 | 
						||
 | 
						||
                    if (!(hasUpdateScript && hasContentLabel)) {
 | 
						||
                      labelsToAdd.add(hasJson ? "json" : "website");
 | 
						||
                    }
 | 
						||
                  } else if (label === "documentation") {
 | 
						||
                    labelsToAdd.add("maintenance");
 | 
						||
                  } else {
 | 
						||
                    labelsToAdd.add(label);
 | 
						||
                  }
 | 
						||
                }
 | 
						||
              }
 | 
						||
            }
 | 
						||
            if (labelsToAdd.size === 0) {
 | 
						||
              labelsToAdd.add("needs triage");
 | 
						||
            }
 | 
						||
 | 
						||
            if (labelsToAdd.size > 0) {
 | 
						||
              await github.rest.issues.addLabels({
 | 
						||
                owner: context.repo.owner,
 | 
						||
                repo: context.repo.repo,
 | 
						||
                issue_number: prNumber,
 | 
						||
                labels: Array.from(labelsToAdd),
 | 
						||
              });
 | 
						||
            }
 | 
						||
  ai-check:
 | 
						||
    needs: autolabeler
 | 
						||
    runs-on: ubuntu-latest
 | 
						||
    permissions:
 | 
						||
      pull-requests: write
 | 
						||
    steps:
 | 
						||
      - name: Checkout repository
 | 
						||
        uses: actions/checkout@v4
 | 
						||
 | 
						||
      - name: Load priority config
 | 
						||
        run: |
 | 
						||
          echo "PRIORITY_JSON=$(jq -c . .github/label-priority.json)" >> $GITHUB_ENV
 | 
						||
 | 
						||
      - name: Fetch PR metadata
 | 
						||
        id: pr
 | 
						||
        uses: actions/github-script@v7
 | 
						||
        with:
 | 
						||
          script: |
 | 
						||
            const pr = context.payload.pull_request;
 | 
						||
            const files = await github.rest.pulls.listFiles({
 | 
						||
              owner: context.repo.owner,
 | 
						||
              repo: context.repo.repo,
 | 
						||
              pull_number: pr.number
 | 
						||
            });
 | 
						||
            const prData = {
 | 
						||
              title: pr.title || "",
 | 
						||
              body: pr.body || "",
 | 
						||
              files: files.data.map(f => f.filename)
 | 
						||
            };
 | 
						||
            require('fs').writeFileSync(process.env.GITHUB_ENV, `PR_DATA=${JSON.stringify(prData)}\n`, {flag: 'a'});
 | 
						||
 | 
						||
      - name: AI Label Review
 | 
						||
        id: ai
 | 
						||
        uses: actions/github-script@v7
 | 
						||
        env:
 | 
						||
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
 | 
						||
        with:
 | 
						||
          script: |
 | 
						||
            const prData = JSON.parse(process.env.PR_DATA);
 | 
						||
 | 
						||
            const prompt = `
 | 
						||
            You are a GitHub labeling bot.
 | 
						||
            Task:
 | 
						||
            - Analyze PR title, body, and file list.
 | 
						||
            - For each possible label, return a confidence score (0–1).
 | 
						||
            - If both bugfix and refactor apply, prefer refactor.
 | 
						||
            - Output JSON: {"labels":[{"name":"bugfix","score":0.9},{"name":"refactor","score":0.6}]}
 | 
						||
 | 
						||
            Valid labels: [new script, update script, delete script, bugfix, feature, maintenance, refactor, website, json, api, core, github, addon, pve-tool, vm].
 | 
						||
 | 
						||
            PR data: ${JSON.stringify(prData)}
 | 
						||
            `;
 | 
						||
 | 
						||
            const response = await fetch("https://api.openai.com/v1/chat/completions", {
 | 
						||
              method: "POST",
 | 
						||
              headers: {
 | 
						||
                "Content-Type": "application/json",
 | 
						||
                "Authorization": "Bearer " + process.env.OPENAI_API_KEY,
 | 
						||
              },
 | 
						||
              body: JSON.stringify({
 | 
						||
                model: "gpt-4.1-mini",
 | 
						||
                messages: [{ role: "user", content: prompt }],
 | 
						||
                temperature: 0
 | 
						||
              })
 | 
						||
            });
 | 
						||
 | 
						||
            const data = await response.json();
 | 
						||
            const labels = JSON.parse(data.choices[0].message.content).labels;
 | 
						||
            core.setOutput("labels", JSON.stringify(labels));
 | 
						||
 | 
						||
      - name: Apply AI Labels
 | 
						||
        uses: actions/github-script@v7
 | 
						||
        with:
 | 
						||
          script: |
 | 
						||
            const raw = JSON.parse('${{ steps.ai.outputs.labels }}');
 | 
						||
            const prNumber = context.payload.pull_request.number;
 | 
						||
            const config = JSON.parse(process.env.PRIORITY_JSON);
 | 
						||
 | 
						||
            let toApply = [];
 | 
						||
            let toSuggest = [];
 | 
						||
 | 
						||
            raw.forEach(l => {
 | 
						||
              if (l.score >= 0.8) {
 | 
						||
                const conflicts = config.conflicts[l.name] || [];
 | 
						||
                const hasStrongerConflict = conflicts.some(c =>
 | 
						||
                  raw.some(x =>
 | 
						||
                    x.name === c &&
 | 
						||
                    x.score >= 0.6 &&
 | 
						||
                    (config.priorities[c] || 0) >= (config.priorities[l.name] || 0)
 | 
						||
                  )
 | 
						||
                );
 | 
						||
                if (!hasStrongerConflict) {
 | 
						||
                  toApply.push(l.name);
 | 
						||
                }
 | 
						||
              } else if (l.score >= 0.5) {
 | 
						||
                toSuggest.push(`${l.name} (${Math.round(l.score*100)}%)`);
 | 
						||
              }
 | 
						||
            });
 | 
						||
 | 
						||
            if (toApply.length > 0) {
 | 
						||
              await github.rest.issues.addLabels({
 | 
						||
                owner: context.repo.owner,
 | 
						||
                repo: context.repo.repo,
 | 
						||
                issue_number: prNumber,
 | 
						||
                labels: toApply
 | 
						||
              });
 | 
						||
            }
 | 
						||
 | 
						||
            if (toSuggest.length > 0) {
 | 
						||
              await github.rest.issues.createComment({
 | 
						||
                owner: context.repo.owner,
 | 
						||
                repo: context.repo.repo,
 | 
						||
                issue_number: prNumber,
 | 
						||
                body: `🤖 AI suggests these possible labels (uncertain): ${toSuggest.join(", ")}`
 | 
						||
              });
 | 
						||
            }
 |