Skip to content

Commit 35e07a4

Browse files
authored
Merge branch 'main' into test/api-og-route-mouse-interactivity-4671
2 parents b14e3cd + 31b8454 commit 35e07a4

808 files changed

Lines changed: 93787 additions & 5404 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.env.local.example‎

Lines changed: 0 additions & 50 deletions
This file was deleted.

‎.github/scripts/check-duplicates.js‎

Lines changed: 112 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,172 @@
11
const github = require('@actions/github');
22
const { GoogleGenerativeAI } = require('@google/generative-ai');
33

4-
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5-
64
function cosineSimilarity(vecA, vecB) {
75
let dotProduct = 0.0;
86
let normA = 0.0;
97
let normB = 0.0;
8+
109
for (let i = 0; i < vecA.length; i++) {
1110
dotProduct += vecA[i] * vecB[i];
1211
normA += vecA[i] * vecA[i];
1312
normB += vecB[i] * vecB[i];
1413
}
15-
if (normA === 0 || normB === 0) return 0;
14+
15+
if (normA === 0 || normB === 0) {
16+
return 0;
17+
}
18+
1619
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
1720
}
1821

1922
async function run() {
2023
const geminiApiKey = process.env.GEMINI_API_KEY;
21-
const githubToken = process.env.GITHUB_TOKEN;
24+
const githubToken = process.env.GITHUB_PAT || process.env.GITHUB_TOKEN;
2225

2326
if (!geminiApiKey) {
2427
throw new Error('❌ Missing GEMINI_API_KEY environment variable.');
2528
}
29+
2630
if (!githubToken) {
27-
throw new Error('❌ Missing GITHUB_TOKEN environment variable.');
31+
throw new Error('❌ Missing GITHUB_PAT or GITHUB_TOKEN environment variable.');
2832
}
2933

3034
const octokit = github.getOctokit(githubToken);
3135
const { owner, repo } = github.context.repo;
36+
3237
console.log(`🤖 Starting semantic scan for duplicates in ${owner}/${repo}...`);
3338

34-
// 1. Fetch all open issues in the repository
39+
const currentIssueNumber = github.context.payload.issue?.number;
40+
41+
// Prevent failures when executed from pull_request,
42+
// workflow_dispatch, schedule, etc.
43+
if (!currentIssueNumber) {
44+
console.log('ℹ️ No issue payload detected. Skipping duplicate scan.');
45+
return;
46+
}
47+
3548
console.log('Fetching all open issues...');
49+
3650
const allIssues = await octokit.paginate(octokit.rest.issues.listForRepo, {
3751
owner,
3852
repo,
3953
state: 'open',
4054
per_page: 100,
4155
});
4256

43-
// Filter out Pull Requests
4457
const openIssues = allIssues.filter((issue) => !issue.pull_request);
58+
4559
console.log(`Found ${openIssues.length} open issues (excluding Pull Requests).`);
4660

4761
if (openIssues.length < 2) {
4862
console.log('ℹ️ Not enough issues to compare. Ending scan.');
4963
return;
5064
}
5165

52-
// 2. Initialize Gemini API
66+
const currentIssue = openIssues.find((issue) => issue.number === currentIssueNumber);
67+
68+
if (!currentIssue) {
69+
console.log(`ℹ️ Triggering issue #${currentIssueNumber} is not open. Skipping scan.`);
70+
return;
71+
}
72+
5373
const genAI = new GoogleGenerativeAI(geminiApiKey);
54-
const model = genAI.getGenerativeModel({ model: 'gemini-embedding-001' });
74+
const model = genAI.getGenerativeModel({
75+
model: 'gemini-embedding-001',
76+
});
5577

56-
// 3. Generate embeddings with 4.1s rate limit delay
57-
console.log('Generating semantic embeddings using gemini-embedding-001...');
58-
for (let i = 0; i < openIssues.length; i++) {
59-
const issue = openIssues[i];
60-
const textToEmbed = `Title: ${issue.title}\nBody: ${issue.body || ''}`.slice(0, 3000);
78+
console.log(`Generating embedding for Issue #${currentIssue.number}...`);
6179

62-
console.log(
63-
`[${i + 1}/${openIssues.length}] Embedding Issue #${issue.number}: "${issue.title.substring(0, 50)}..."`
64-
);
80+
const currentText = `Title: ${currentIssue.title}\nBody: ${currentIssue.body || ''}`.slice(
81+
0,
82+
3000
83+
);
84+
85+
const currentResult = await model.embedContent(currentText);
86+
87+
const currentEmbedding = currentResult.embedding.values;
88+
89+
const { data: existingComments } = await octokit.rest.issues.listComments({
90+
owner,
91+
repo,
92+
issue_number: currentIssue.number,
93+
});
94+
95+
const candidateIssues = openIssues.filter((issue) => issue.number !== currentIssue.number);
96+
97+
console.log(`Comparing against ${candidateIssues.length} existing issues...`);
98+
99+
let duplicatesCount = 0;
100+
101+
for (const candidateIssue of candidateIssues) {
102+
const candidateText =
103+
`Title: ${candidateIssue.title}\nBody: ${candidateIssue.body || ''}`.slice(0, 3000);
104+
105+
let candidateEmbedding;
65106

66107
try {
67-
const result = await model.embedContent(textToEmbed);
68-
issue.embedding = result.embedding.values;
69-
} catch (err) {
70-
console.error(`❌ Failed to generate embedding for Issue #${issue.number}:`, err.message);
71-
throw err;
108+
const candidateResult = await model.embedContent(candidateText);
109+
110+
candidateEmbedding = candidateResult.embedding.values;
111+
} catch (error) {
112+
console.warn(
113+
`⚠️ Failed to generate embedding for Issue #${candidateIssue.number}:`,
114+
error.message
115+
);
116+
continue;
72117
}
73118

74-
if (i < openIssues.length - 1) {
75-
console.log('Sleeping for 4.1 seconds to avoid rate limits...');
76-
await delay(4100);
119+
const similarity = cosineSimilarity(currentEmbedding, candidateEmbedding);
120+
121+
if (similarity < 0.85) {
122+
continue;
77123
}
78-
}
79124

80-
// 4. Perform pairwise comparisons
81-
console.log('\nComparing issues for semantic similarity...');
82-
let duplicatesCount = 0;
125+
const similarityPercent = (similarity * 100).toFixed(1);
83126

84-
for (let i = 0; i < openIssues.length; i++) {
85-
for (let j = i + 1; j < openIssues.length; j++) {
86-
const issueA = openIssues[i];
87-
const issueB = openIssues[j];
88-
89-
const similarity = cosineSimilarity(issueA.embedding, issueB.embedding);
90-
91-
// Flag if similarity is 85% or higher
92-
if (similarity >= 0.85) {
93-
const newerIssue = issueA.number > issueB.number ? issueA : issueB;
94-
const olderIssue = issueA.number < issueB.number ? issueA : issueB;
95-
const similarityPercent = (similarity * 100).toFixed(1);
96-
97-
console.log(
98-
`⚠️ Possible Duplicate: #${newerIssue.number} and #${olderIssue.number} similarity: ${similarityPercent}%`
99-
);
100-
101-
// Check if bot already commented on the newer issue
102-
console.log(`Checking comments on newer issue #${newerIssue.number}...`);
103-
const { data: comments } = await octokit.rest.issues.listComments({
104-
owner,
105-
repo,
106-
issue_number: newerIssue.number,
107-
});
108-
109-
const alreadyFlagged = comments.some(
110-
(comment) =>
111-
comment.body &&
112-
comment.body.includes(
113-
`My semantic scan detected that this issue might be a duplicate of #${olderIssue.number}`
114-
)
115-
);
116-
117-
if (alreadyFlagged) {
118-
console.log(
119-
`ℹ️ Already flagged Issue #${newerIssue.number} as duplicate of #${olderIssue.number}. Skipping comment.`
120-
);
121-
continue;
122-
}
123-
124-
// Post duplicate warning comment
125-
const author = newerIssue.user.login;
126-
const commentBody = `Hey @${author}! 🤖\n\nMy semantic scan detected that this issue might be a duplicate of #${olderIssue.number} (Similarity: **${similarityPercent}%**).\n\nPlease check between these issues and close this one if it is a duplicate.`;
127-
128-
console.log(`Posting duplicate warning comment on newer issue #${newerIssue.number}...`);
129-
await octokit.rest.issues.createComment({
130-
owner,
131-
repo,
132-
issue_number: newerIssue.number,
133-
body: commentBody,
134-
});
135-
136-
// Add 'possible-duplicate' label to the newer issue
137-
try {
138-
console.log(`Adding 'possible-duplicate' label to issue #${newerIssue.number}...`);
139-
await octokit.rest.issues.addLabels({
140-
owner,
141-
repo,
142-
issue_number: newerIssue.number,
143-
labels: ['possible-duplicate'],
144-
});
145-
} catch (labelErr) {
146-
console.warn(
147-
`⚠️ Warning: Could not add label 'possible-duplicate' to Issue #${newerIssue.number}:`,
148-
labelErr.message
149-
);
150-
}
151-
152-
duplicatesCount++;
153-
}
127+
console.log(
128+
`⚠️ Possible duplicate: #${currentIssue.number} ↔ #${candidateIssue.number} (${similarityPercent}%)`
129+
);
130+
131+
const duplicateMessage = `My semantic scan detected that this issue might be a duplicate of #${candidateIssue.number}`;
132+
133+
const alreadyFlagged = existingComments.some(
134+
(comment) => comment.body && comment.body.includes(duplicateMessage)
135+
);
136+
137+
if (alreadyFlagged) {
138+
continue;
154139
}
140+
141+
const author = currentIssue.user.login;
142+
143+
await octokit.rest.issues.createComment({
144+
owner,
145+
repo,
146+
issue_number: currentIssue.number,
147+
body:
148+
`Hey @${author}! 🤖\n\n` +
149+
`${duplicateMessage} (Similarity: **${similarityPercent}%**).\n\n` +
150+
'Please review both issues and close this one if it is a duplicate.',
151+
});
152+
153+
try {
154+
await octokit.rest.issues.addLabels({
155+
owner,
156+
repo,
157+
issue_number: currentIssue.number,
158+
labels: ['possible-duplicate'],
159+
});
160+
} catch (error) {
161+
console.warn(`⚠️ Failed to add label to Issue #${currentIssue.number}:`, error.message);
162+
}
163+
164+
duplicatesCount++;
155165
}
156166

157-
console.log(`\n🎉 Semantic duplicate scan complete! Flagged ${duplicatesCount} new duplicates.`);
167+
console.log(
168+
`🎉 Semantic duplicate scan complete! Flagged ${duplicatesCount} potential duplicates.`
169+
);
158170
}
159171

160172
run().catch((error) => {

‎.github/scripts/issue-management/assign-handler.js‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ async function findExistingAssignments(github, owner, repo, username, currentIss
1010
return issues.filter((issue) => !issue.pull_request && issue.number !== currentIssueNumber);
1111
}
1212

13-
const MAX_ASSIGNED_ISSUES = 3;
13+
const MAX_ASSIGNED_ISSUES = 5;
1414

1515
async function handleAssign({ github, context, username, hasWriteAccess }) {
1616
const { owner, repo } = context.repo;
@@ -64,7 +64,13 @@ async function handleAssign({ github, context, username, hasWriteAccess }) {
6464
throw error;
6565
}
6666

67-
const currentAssignees = context.payload.issue.assignees.map((a) => a.login.toLowerCase());
67+
// Re-fetch to avoid stale assignee data from the webhook payload
68+
const { data: freshIssue } = await github.rest.issues.get({
69+
owner,
70+
repo,
71+
issue_number: issueNumber,
72+
});
73+
const currentAssignees = freshIssue.assignees.map((a) => a.login.toLowerCase());
6874
if (currentAssignees.includes(username.toLowerCase())) {
6975
await github.rest.issues.createComment({
7076
owner,

‎.github/scripts/issue-management/claim-handler.js‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ async function findExistingAssignments(github, owner, repo, username, currentIss
1010
return issues.filter((issue) => !issue.pull_request && issue.number !== currentIssueNumber);
1111
}
1212

13-
const MAX_ASSIGNED_ISSUES = 3;
13+
const MAX_ASSIGNED_ISSUES = 5;
1414

1515
async function handleClaim({ github, context }) {
1616
const { owner, repo } = context.repo;
@@ -43,7 +43,13 @@ async function handleClaim({ github, context }) {
4343
return;
4444
}
4545

46-
const currentAssignees = context.payload.issue.assignees.map((a) => a.login.toLowerCase());
46+
// Re-fetch to avoid stale assignee data from the webhook payload
47+
const { data: freshIssue } = await github.rest.issues.get({
48+
owner,
49+
repo,
50+
issue_number: issueNumber,
51+
});
52+
const currentAssignees = freshIssue.assignees.map((a) => a.login.toLowerCase());
4753

4854
if (currentAssignees.length > 0) {
4955
if (currentAssignees.includes(commenter.toLowerCase())) {

‎.github/scripts/issue-management/main.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ module.exports = async ({ github, context, core }) => {
6868
owner,
6969
repo,
7070
issue_number: issueNumber,
71-
body: `⚠️ An unexpected error occurred while processing your command. Please try again or contact a maintainer.\n\n> \`${error.message}\``,
71+
body: `⚠️ An unexpected error occurred while processing your command. Please try again or contact a maintainer.`,
7272
});
7373
} catch (_) {}
7474
core.setFailed(error.message);

0 commit comments

Comments
 (0)