Skip to content

Commit 4b2fedb

Browse files
author
Anil Maktala
committed
updated duplicate detection logic
1 parent 24985df commit 4b2fedb

9 files changed

Lines changed: 827 additions & 97 deletions

scripts/assign_labels.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export async function assignLabels(
8181
}
8282

8383
/**
84-
* Add duplicate label to an issue
84+
* Add duplicate label to an issue and remove pending-triage label
8585
*/
8686
export async function addDuplicateLabel(
8787
owner: string,
@@ -94,6 +94,7 @@ export async function addDuplicateLabel(
9494

9595
console.log(`Adding duplicate label to issue #${issueNumber}`);
9696

97+
// Add duplicate label
9798
await retryWithBackoff(async () => {
9899
await client.issues.addLabels({
99100
owner,
@@ -104,6 +105,24 @@ export async function addDuplicateLabel(
104105
});
105106

106107
console.log(`Successfully added duplicate label to issue #${issueNumber}`);
108+
109+
// Remove pending-triage label
110+
try {
111+
console.log(`Removing pending-triage label from issue #${issueNumber}`);
112+
await retryWithBackoff(async () => {
113+
await client.issues.removeLabel({
114+
owner,
115+
repo,
116+
issue_number: issueNumber,
117+
name: "pending-triage",
118+
});
119+
});
120+
console.log(`Successfully removed pending-triage label from issue #${issueNumber}`);
121+
} catch (error) {
122+
// Label might not exist on the issue, which is fine
123+
console.log(`Note: Could not remove pending-triage label (may not exist): ${error}`);
124+
}
125+
107126
return true;
108127
} catch (error) {
109128
console.error(

scripts/detect_duplicates.ts

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { retryWithBackoff } from "./retry_utils.js";
1414
const MODEL_ID = "us.anthropic.claude-sonnet-4-20250514-v1:0";
1515
const SIMILARITY_THRESHOLD = 0.8;
1616
const BATCH_SIZE = 10;
17-
const DAYS_TO_SEARCH = 90;
1817

1918
// Security: Maximum lengths for input validation
2019
const MAX_TITLE_LENGTH = 500;
@@ -65,7 +64,8 @@ function sanitizePromptInput(input: string, maxLength: number): string {
6564
}
6665

6766
/**
68-
* Fetch existing open issues from repository
67+
* Fetch existing open issues from repository with Bug or Feature type
68+
* Falls back to bug/feature labels if issue types are not configured
6969
*/
7070
export async function fetchExistingIssues(
7171
owner: string,
@@ -74,38 +74,81 @@ export async function fetchExistingIssues(
7474
githubToken: string
7575
): Promise<IssueData[]> {
7676
const client = new Octokit({ auth: githubToken });
77-
const cutoffDate = new Date();
78-
cutoffDate.setDate(cutoffDate.getDate() - DAYS_TO_SEARCH);
7977

8078
try {
81-
const { data: issues } = await client.issues.listForRepo({
82-
owner,
83-
repo,
84-
state: "open",
85-
per_page: 100,
86-
sort: "created",
87-
direction: "desc",
79+
// Fetch all open issues (up to 1000 for better duplicate detection)
80+
// GitHub API allows max 100 per page, so we'll fetch multiple pages
81+
const allIssues: any[] = [];
82+
let page = 1;
83+
const perPage = 100;
84+
const maxPages = 10; // Fetch up to 1000 issues
85+
86+
while (page <= maxPages) {
87+
const { data: pageIssues } = await client.issues.listForRepo({
88+
owner,
89+
repo,
90+
state: "open",
91+
per_page: perPage,
92+
page: page,
93+
sort: "created",
94+
direction: "desc",
95+
});
96+
97+
if (pageIssues.length === 0) {
98+
break; // No more issues
99+
}
100+
101+
allIssues.push(...pageIssues);
102+
103+
if (pageIssues.length < perPage) {
104+
break; // Last page
105+
}
106+
107+
page++;
108+
}
109+
110+
// Filter for Bug or Feature types, or bug/feature labels
111+
const filteredIssues = allIssues.filter((issue: any) => {
112+
// Exclude current issue and pull requests
113+
if (issue.number === currentIssueNumber || issue.pull_request) {
114+
return false;
115+
}
116+
117+
// Check if issue has Bug or Feature type (type is an object with a name property)
118+
if (issue.type && typeof issue.type === 'object' && issue.type.name) {
119+
if (issue.type.name === "Bug" || issue.type.name === "Feature") {
120+
return true;
121+
}
122+
}
123+
124+
// Fallback: Check for bug or feature labels
125+
const labelNames = issue.labels.map((l: any) =>
126+
typeof l === "string" ? l.toLowerCase() : (l.name || "").toLowerCase()
127+
);
128+
return labelNames.includes("bug") || labelNames.includes("feature");
88129
});
89130

90-
return issues
91-
.filter(
92-
(issue) =>
93-
issue.number !== currentIssueNumber &&
94-
!issue.pull_request &&
95-
new Date(issue.created_at) >= cutoffDate
96-
)
97-
.map((issue) => ({
98-
number: issue.number,
99-
title: issue.title,
100-
body: issue.body || "",
101-
created_at: new Date(issue.created_at),
102-
updated_at: new Date(issue.updated_at),
103-
labels: issue.labels.map((l) =>
104-
typeof l === "string" ? l : l.name || ""
105-
),
106-
url: issue.html_url,
107-
state: issue.state,
108-
}));
131+
const hasTypes = allIssues.some(i => i.type && i.type.name);
132+
const filterMethod = hasTypes
133+
? "issue types (Bug/Feature)"
134+
: "labels (bug/feature)";
135+
136+
console.log(
137+
`Filtered ${filteredIssues.length} issues with Bug/Feature type (from ${allIssues.length} total) using ${filterMethod}`
138+
);
139+
140+
return filteredIssues.map((issue: any) => ({
141+
number: issue.number,
142+
title: issue.title,
143+
body: issue.body || "",
144+
created_at: new Date(issue.created_at),
145+
updated_at: new Date(issue.updated_at),
146+
labels: issue.labels.map((l: any) =>
147+
typeof l === "string" ? l : l.name || ""
148+
),
149+
url: issue.html_url,
150+
state: issue.state,
151+
}));
109152
} catch (error) {
110153
console.error("Error fetching existing issues:", error);
111154
return [];
@@ -330,28 +373,30 @@ export function generateDuplicateComment(duplicates: DuplicateMatch[]): string {
330373
return "";
331374
}
332375

333-
const header = `## Potential Duplicate Issues Detected
334-
335-
This issue appears to be similar to the following existing issue(s):
376+
const DUPLICATE_CLOSE_DAYS = 3;
336377

337-
`;
338-
339-
const issueList = duplicates
378+
const duplicateList = duplicates
340379
.map(
341380
(dup) =>
342-
`- [#${dup.issue_number}: ${dup.issue_title}](${dup.url}) (${(
381+
`\n- [#${dup.issue_number}: ${dup.issue_title}](${dup.url}) (${(
343382
dup.similarity_score * 100
344-
).toFixed(0)}% similar)\n ${dup.reasoning}`
383+
).toFixed(0)}% similar)`
345384
)
346-
.join("\n\n");
385+
.join("");
386+
387+
const comment = `🤖 **Potential Duplicate Detected**
347388
348-
const footer = `
389+
This issue appears to be similar to:${duplicateList}
349390
350-
---
391+
**What happens next?**
392+
- ⏰ This issue will be automatically closed in ${DUPLICATE_CLOSE_DAYS} days
393+
- 🏷️ Remove the \`duplicate\` label if this is NOT a duplicate
394+
- 💬 Comment on the original issue if you have additional information
351395
352-
If you believe this is not a duplicate, please provide additional details to help us understand the difference. A maintainer will review and remove the duplicate label if appropriate.`;
396+
**Why is this marked as duplicate?**
397+
${duplicates[0].reasoning}`;
353398

354-
return header + issueList + footer;
399+
return comment;
355400
}
356401

357402
/**

scripts/test/TEST_LOCALLY.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Testing GitHub Issue Triage Workflow Locally
2+
3+
This guide explains how to test the complete issue triage workflow on real GitHub issues from your local machine.
4+
5+
## Prerequisites
6+
7+
1. **Environment variables configured** in `scripts/.env`:
8+
- `AWS_ACCESS_KEY_ID` - AWS credentials for Bedrock
9+
- `AWS_SECRET_ACCESS_KEY` - AWS credentials for Bedrock
10+
- `AWS_REGION` - AWS region (default: us-east-1)
11+
- `GITHUB_TOKEN` - GitHub personal access token with repo access
12+
- `REPOSITORY_OWNER` - GitHub repository owner (default: kirodotdev)
13+
- `REPOSITORY_NAME` - GitHub repository name (default: Kiro)
14+
15+
2. **Dependencies installed**:
16+
```bash
17+
cd scripts
18+
npm install
19+
```
20+
21+
## Method 1: Using the Shell Script (Recommended)
22+
23+
The easiest way to test:
24+
25+
```bash
26+
cd scripts/test
27+
./test-real-issue.sh <issue_number>
28+
```
29+
30+
Example:
31+
```bash
32+
./test-real-issue.sh 5066
33+
```
34+
35+
This will:
36+
1. Load environment variables from `.env`
37+
2. Build the TypeScript code
38+
3. Fetch the issue details from GitHub
39+
4. Run the complete triage workflow
40+
5. Show results
41+
42+
## Method 2: Using Node Directly
43+
44+
```bash
45+
cd scripts
46+
47+
# Build TypeScript
48+
npm run build
49+
50+
# Load env vars and run test (excluding ISSUE_NUMBER from .env)
51+
export $(cat .env | grep -v '^#' | grep -v '^ISSUE_NUMBER' | xargs)
52+
export TEST_ISSUE_NUMBER=5066
53+
node dist/test/test-real-issue.js
54+
```
55+
56+
## Method 3: Manual Workflow Execution
57+
58+
To run the triage script directly (like GitHub Actions does):
59+
60+
```bash
61+
cd scripts
62+
63+
# Build
64+
npm run build
65+
66+
# Set environment variables
67+
export $(cat .env | grep -v '^#' | xargs)
68+
export ISSUE_NUMBER=5066
69+
export ISSUE_TITLE="Your issue title"
70+
export ISSUE_BODY="Your issue body"
71+
72+
# Run triage
73+
node dist/triage_issue.js
74+
```
75+
76+
## What Gets Tested
77+
78+
The workflow performs these steps:
79+
80+
1. **Classification** - Uses AWS Bedrock Claude to classify the issue and recommend labels
81+
2. **Label Assignment** - Assigns recommended labels to the issue
82+
3. **Duplicate Detection** - Searches all open issues for potential duplicates
83+
4. **Duplicate Comment** - Posts a comment if duplicates are found (≥80% similarity)
84+
5. **Duplicate Label** - Adds "duplicate" label and removes "pending-triage" label
85+
86+
## Verifying Results
87+
88+
After running the test, check the GitHub issue to verify:
89+
90+
1. **Labels added**: Check if recommended labels were applied
91+
2. **Duplicate comment**: Look for "Potential Duplicate Issues Detected" comment
92+
3. **Duplicate label**: If duplicates found, "duplicate" label should be added
93+
4. **Pending-triage removed**: If duplicate label added, "pending-triage" should be removed
94+
95+
## Example Output
96+
97+
```
98+
╔════════════════════════════════════════════════════════════╗
99+
║ Real Issue Triage Test (Local Simulation) ║
100+
╚════════════════════════════════════════════════════════════╝
101+
102+
Repository: kirodotdev/kiro
103+
Issue Number: #5066
104+
105+
=== Triaging Issue #5066 ===
106+
Title: Getting Authorization error - unable to proceed
107+
108+
Step 1: Classifying issue with AWS Bedrock...
109+
Recommended labels: auth, os: mac, theme:unexpected-error, pending-triage
110+
111+
Step 2: Assigning labels...
112+
✅ Successfully assigned labels
113+
114+
Step 3: Detecting duplicate issues...
115+
Found 2 potential duplicates
116+
117+
Step 4: Posting duplicate comment...
118+
✅ Successfully posted duplicate comment
119+
120+
Step 5: Adding duplicate label...
121+
✅ Successfully added duplicate label
122+
✅ Successfully removed pending-triage label
123+
124+
=== Triage Complete ===
125+
```
126+
127+
## Troubleshooting
128+
129+
### "GITHUB_TOKEN not set"
130+
Make sure your `.env` file has a valid GitHub token with repo access.
131+
132+
### "AWS credentials not set"
133+
Ensure AWS credentials are configured in `.env` file.
134+
135+
### "Issue not found"
136+
- Verify the issue number exists
137+
- Check you have access to the repository
138+
- Ensure the repository owner/name are correct
139+
140+
### "Label does not exist"
141+
This is normal if the issue doesn't have the "pending-triage" label. The script handles this gracefully.
142+
143+
## Notes
144+
145+
- The script makes real changes to GitHub issues (adds labels, posts comments)
146+
- Use test issues or your own repository for testing
147+
- The duplicate detection searches the last 100 open issues
148+
- Similarity threshold is 80% for duplicate detection

0 commit comments

Comments
 (0)