diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..16a8f522 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: true + +contact_links: + - name: "Has your issue already been reported?" + url: "https://github.com/issues?q=repo%3Aruncat-dev%2FRunCat365" + about: "Please check the list of existing issues before opening a new one to avoid duplicates." \ No newline at end of file diff --git a/.github/scripts/check-duplicates.cjs b/.github/scripts/check-duplicates.cjs new file mode 100644 index 00000000..ff18ab59 --- /dev/null +++ b/.github/scripts/check-duplicates.cjs @@ -0,0 +1,67 @@ +module.exports = async ({ github, context }) => { + const issue = context.payload.issue; + const owner = context.repo.owner; + const repo = context.repo.repo; + const title = issue.title; + + // Clean special characters but keep the natural phrasing + const cleanTitle = title + .replace(/[^\w\s]/gi, ' ') + .replace(/\s+/g, ' ') + .trim(); + + const words = cleanTitle.split(/\s+/); + + if (cleanTitle.length < 10 || words.length < 3) { + console.log("Title is too short or lacks context to search for duplicates. Skipping."); + return; + } + + const query = `repo:${owner}/${repo} in:title ${cleanTitle} -number:${issue.number}`; + console.log(`Searching with hybrid query: "${query}"`); + + try { + const response = await github.rest.search.issuesAndPullRequests({ + q: query, + search_type: 'hybrid', + per_page: 5 + }); + + const items = response.data.items; + + if (items && items.length > 0) { + let commentBody = `### 🔍 Potential Duplicate Issues or PRs Found\n\n`; + commentBody += `Hello @${issue.user.login},\n\n`; + commentBody += `We found some existing issues or Pull Requests that might cover a similar topic. Please review them to see if they already address your concern:\n\n`; + + for (const item of items) { + const type = item.pull_request ? 'Pull Request' : 'Issue'; + const state = item.state === 'open' ? '🟢 Open' : '🔴 Closed'; + commentBody += `- [${type} #${item.number}](${item.html_url}) - *${item.title}* (${state})\n`; + } + + commentBody += `\n---\n`; + commentBody += `*This is an automated check. Maintainers will review this issue and decide whether to close it as a duplicate or keep it open.*`; + + await github.rest.issues.createComment({ + owner: owner, + repo: repo, + issue_number: issue.number, + body: commentBody + }); + + await github.rest.issues.addLabels({ + owner: owner, + repo: repo, + issue_number: issue.number, + labels: ['potential-duplicate'] + }); + + console.log(`Found ${items.length} potential duplicates and commented.`); + } else { + console.log("No potential duplicates found."); + } + } catch (error) { + console.error("Error searching for duplicates:", error); + } +}; diff --git a/.github/workflows/check-duplicates.yml b/.github/workflows/check-duplicates.yml new file mode 100644 index 00000000..00b2a2c1 --- /dev/null +++ b/.github/workflows/check-duplicates.yml @@ -0,0 +1,23 @@ +name: Check Duplicate Issues and PRs + +on: + issues: + types: [opened] + +permissions: + issues: write + pull-requests: read + +jobs: + check-duplicates: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Search and notify duplicates + uses: actions/github-script@v7 + with: + script: | + const script = require('./.github/scripts/check-duplicates.cjs'); + await script({ github, context });