diff --git a/.github/scripts/label-issue.js b/.github/scripts/label-issue.js deleted file mode 100644 index b872d231..00000000 --- a/.github/scripts/label-issue.js +++ /dev/null @@ -1,112 +0,0 @@ -import * as core from '@actions/core'; -import * as github from '@actions/github'; - -const GH_TOKEN = process.env.GITHUB_TOKEN; - -const pkgLabel = { - 'Core (@videojs/core)': 'pkg:core', - 'Icons (@videojs/icons)': 'pkg:icons', - 'HTML (@videojs/html)': 'pkg:html', - 'React (@videojs/react)': 'pkg:react', - 'Utils (@videojs/utils)': 'pkg:utils', - 'HTML Example': 'example:html', - 'React Example': 'example:react', - 'Next.js Example': 'example:next.js', - Website: 'site', -}; - -const envLabel = { - // Browsers - chrome: 'browser:chrome', - chromium: 'browser:chrome', - firefox: 'browser:firefox', - safari: 'browser:safari', - edge: 'browser:edge', - msedge: 'browser:edge', - // OS - macos: 'os:mac', - 'mac os': 'os:mac', - darwin: 'os:mac', - windows: 'os:windows', - win32: 'os:windows', - win10: 'os:windows', - linux: 'os:linux', - ubuntu: 'os:linux', - debian: 'os:linux', - android: 'os:android', - ios: 'os:ios', - iphone: 'os:ios', - ipad: 'os:ios', -}; - -async function run() { - try { - const { issue } = github.context.payload; - - if (!issue || !issue.body) { - console.log('No issue body found, skipping.'); - return; - } - - const labels = new Set(); - - const pkg = extractSection(issue, '### Which package(s) or projects are affected?'); - if (pkg) { - for (const [option, label] of Object.entries(pkgLabel)) { - if (pkg.includes(option)) { - labels.add(label); - } - } - } - - const env = extractSection(issue, '### Browser/OS/Node environment'); - if (env) { - const envText = env.toLowerCase(); - for (const [keyword, label] of Object.entries(envLabel)) { - if (envText.includes(keyword)) { - labels.add(label); - } - } - } - - if (labels.size === 0) { - console.log('No matching labels found.'); - return; - } - - console.log(`Matched labels: ${Array.from(labels).join(', ')}`); - - if (process.env.DRY_RUN === 'true') { - console.log('⚠️ DRY RUN: Skipping API call.'); - return; - } - - if (!GH_TOKEN) { - throw new Error('GITHUB_TOKEN is not set'); - } - - const octokit = github.getOctokit(GH_TOKEN); - - await octokit.rest.issues.addLabels({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - issue_number: issue.number, - labels: Array.from(labels), - }); - } catch (error) { - core.setFailed(`Action failed with error: ${error}`); - } -} - -/** - * @param {*} issue - * @param {string} header - */ -function extractSection(issue, header) { - const escapedHeader = header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const regex = new RegExp(`${escapedHeader}\\r?\\n([\\s\\S]*?)(?=\\r?\\n###|$)`, 'i'); - const match = issue.body.match(regex); - return match ? match[1].trim() : null; -}; - -run(); diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml new file mode 100644 index 00000000..3f66cbb8 --- /dev/null +++ b/.github/workflows/issue-triage.yml @@ -0,0 +1,84 @@ +name: Issue Triage +on: + issues: + types: [opened, edited, reopened] + +permissions: + actions: read + contents: read + issues: write + id-token: write + +concurrency: + group: issue-triage-${{ github.event.issue.number }} + cancel-in-progress: true + +jobs: + triage: + if: github.event.issue.pull_request == null + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Run Claude issue triager + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + claude_args: | + --model sonnet + --max-turns 12 + --allowedTools "Bash(gh:*)" "WebSearch" "WebFetch" + prompt: | + You are the issue triage agent for this repository. + + Issue context: + - Number: #${{ github.event.issue.number }} + - Title: ${{ github.event.issue.title }} + - URL: ${{ github.event.issue.html_url }} + - Trigger: ${{ github.event.action }} + - Current labels: + ${{ toJson(github.event.issue.labels) }} + - Body: + ${{ github.event.issue.body }} + + Triage responsibilities: + 1. Classify and label: + - Determine best-fit labels from issue content and template sections. + - Review existing repository labels first and align to established taxonomy. + - Review labels used on related/cross-referenced issues before deciding. + - Add missing useful labels and remove clearly incorrect labels. + - Do not create new labels unless absolutely necessary; prefer no label over inventing a new one. + 2. Detect probable duplicates: + - Search for existing issues with the same bug/request symptoms, stack traces, repro steps, or scope. + - If a likely duplicate is found, leave one concise comment linking the likely canonical issue and why it appears duplicated. + - If confidence is not high, do not mark as duplicate; instead add `triage` label for human review. + - Do not auto-close duplicates unless there is very high confidence and no meaningful unique information. + 3. Escalate uncertainty: + - Add `triage` label when classification is ambiguous, conflicting, or needs human judgment. + 4. Suggest docs/resources when helpful: + - Use https://v10.videojs.org/llms.txt as the primary source to discover relevant docs links. + - If likely already documented, suggest 1-3 relevant docs links with a short reason. + - Prioritize repository/site docs over random sources. + 5. Ask for missing critical info only when needed: + - If reproduction or environment details are insufficient, post a short targeted follow-up. + 6. Help users unblock when issue is about usage: + - If the issue suggests the user is struggling to achieve something with the library, provide practical guidance. + - Suggest the most relevant documented approach with 1-3 links (from https://v10.videojs.org/llms.txt discovery). + - Include concise next steps or a minimal example/snippet when it materially helps. + - If docs appear incomplete or unclear, call that out and add `triage` for human follow-up. + 7. Planning recommendations (comment-only): + - Review roadmap context at https://github.com/orgs/videojs/projects/7 and current repository milestones. + - Identify likely related epics/issues and related open PRs. + - Recommend whether this issue should be added to the roadmap. + - Recommend a milestone if one is a strong fit. + - Recommend related epic linkage if one clearly fits. + - Recommend obvious `blocked by` relationships when clearly implied. + - Recommend other existing issues that should be included in project planning alongside this one when relevant (for example, skin integration, docs API reference coverage, or other adjacent planning gaps). + - Do not directly add the issue to roadmap/projects, milestones, epics, or blocked-by links. + - Instead, leave one concise comment with recommendations only when meaningful. + 8. Avoid noise: + - Do not comment if there is no meaningful action or new information. + - Keep comments concise and action-oriented. diff --git a/.github/workflows/label-issue.yml b/.github/workflows/label-issue.yml deleted file mode 100644 index a019e23a..00000000 --- a/.github/workflows/label-issue.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Label Issue -on: - issues: - types: [opened, edited] - -permissions: - issues: write - contents: read - -jobs: - label: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v5 - with: - node-version: 22 - cache: pnpm - - - name: Install dependencies - run: pnpm install - - - name: Run - run: node .github/scripts/label-issue.js - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}