Skip to content

Commit baefa15

Browse files
committed
Merge branch 'main' into concurrent-refresh
2 parents 5b9fecc + e39881a commit baefa15

41 files changed

Lines changed: 2204 additions & 271 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.
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
name: Similar Issue Search & Discussion Suggestion
2+
on:
3+
issues:
4+
types: [opened]
5+
pull_request:
6+
types: [opened]
7+
8+
jobs:
9+
analyze:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
issues: write
13+
pull-requests: write
14+
15+
steps:
16+
- name: Checkout deja-view
17+
uses: actions/checkout@v4
18+
with:
19+
repository: 'bdougie/deja-view'
20+
# Option 1: Pin to specific commit (most secure)
21+
ref: '45e7696' # Pin to specific commit for stability
22+
23+
# Option 2: Use a tag (recommended after creating releases)
24+
# ref: 'v1.0.0'
25+
26+
# Option 3: Use main branch (least secure, gets latest changes)
27+
# ref: 'main'
28+
29+
path: deja-view
30+
31+
- name: Setup Python
32+
uses: actions/setup-python@v5
33+
with:
34+
python-version: '3.11'
35+
36+
- name: Install dependencies
37+
run: |
38+
cd deja-view
39+
pip install -r requirements.txt
40+
41+
- name: Index repository if needed
42+
env:
43+
CHROMA_API_KEY: ${{ secrets.CHROMA_CLOUD_API_KEY }}
44+
CHROMA_TENANT: ${{ secrets.CHROMA_TENANT }}
45+
CHROMA_DATABASE: ${{ secrets.CHROMA_DATABASE }}
46+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47+
run: |
48+
cd deja-view
49+
50+
# Index the repository (this will update existing index)
51+
python cli.py index "${{ github.repository }}" --max-issues 200 || true
52+
53+
- name: Search for similar issues
54+
id: search
55+
env:
56+
CHROMA_API_KEY: ${{ secrets.CHROMA_CLOUD_API_KEY }}
57+
CHROMA_TENANT: ${{ secrets.CHROMA_TENANT }}
58+
CHROMA_DATABASE: ${{ secrets.CHROMA_DATABASE }}
59+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60+
run: |
61+
cd deja-view
62+
63+
# Determine if this is an issue or PR
64+
if [ "${{ github.event_name }}" = "pull_request" ]; then
65+
ISSUE_URL="https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
66+
THRESHOLD="0.85"
67+
TOP_K="5"
68+
else
69+
ISSUE_URL="https://github.com/${{ github.repository }}/issues/${{ github.event.issue.number }}"
70+
THRESHOLD="0.7"
71+
TOP_K="5"
72+
fi
73+
74+
# Create a Python script to search and format as JSON
75+
cat << 'PYTHON_SCRIPT' > search_similar.py
76+
import sys
77+
import json
78+
import os
79+
from github_similarity_service import SimilarityService
80+
81+
issue_url = sys.argv[1]
82+
threshold = float(sys.argv[2])
83+
top_k = int(sys.argv[3])
84+
85+
# Parse issue number from URL
86+
issue_number = int(issue_url.split('/')[-1])
87+
repo_parts = issue_url.split('/')
88+
owner = repo_parts[3]
89+
repo = repo_parts[4]
90+
91+
service = SimilarityService()
92+
93+
# Find similar issues
94+
similar = service.find_similar_issues(
95+
owner=owner,
96+
repo=repo,
97+
issue_number=issue_number,
98+
top_k=top_k,
99+
min_similarity=threshold
100+
)
101+
102+
# Format as JSON
103+
print(json.dumps(similar))
104+
PYTHON_SCRIPT
105+
106+
# Search for similar issues
107+
SIMILAR_ISSUES=$(python search_similar.py "$ISSUE_URL" "$THRESHOLD" "$TOP_K")
108+
109+
# Save to output
110+
echo "similar_issues<<EOF" >> $GITHUB_OUTPUT
111+
echo "$SIMILAR_ISSUES" >> $GITHUB_OUTPUT
112+
echo "EOF" >> $GITHUB_OUTPUT
113+
114+
# Check if any similar issues found
115+
if [ "$(echo "$SIMILAR_ISSUES" | jq '. | length')" -gt 0 ]; then
116+
echo "has_similar=true" >> $GITHUB_OUTPUT
117+
else
118+
echo "has_similar=false" >> $GITHUB_OUTPUT
119+
fi
120+
121+
- name: Check if issue should be a discussion
122+
id: discussion_check
123+
if: github.event_name == 'issues'
124+
env:
125+
CHROMA_API_KEY: ${{ secrets.CHROMA_CLOUD_API_KEY }}
126+
CHROMA_TENANT: ${{ secrets.CHROMA_TENANT }}
127+
CHROMA_DATABASE: ${{ secrets.CHROMA_DATABASE }}
128+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
129+
run: |
130+
cd deja-view
131+
132+
# Create a Python script to analyze the issue
133+
cat << 'PYTHON_SCRIPT' > check_discussion.py
134+
import os
135+
import json
136+
import re
137+
from github_similarity_service import SimilarityService
138+
139+
# Get issue details from environment
140+
issue_title = os.environ.get('ISSUE_TITLE', '')
141+
issue_body = os.environ.get('ISSUE_BODY', '')
142+
issue_labels = json.loads(os.environ.get('ISSUE_LABELS', '[]'))
143+
144+
# Mock Issue object for analysis
145+
class MockIssue:
146+
def __init__(self):
147+
self.title = issue_title
148+
self.body = issue_body
149+
self.labels = issue_labels
150+
self.state = 'open'
151+
self.number = int(os.environ.get('ISSUE_NUMBER', 0))
152+
self.created_at = ''
153+
self.updated_at = ''
154+
self.url = ''
155+
156+
service = SimilarityService()
157+
issue = MockIssue()
158+
159+
score, reasons = service._calculate_discussion_score(issue)
160+
161+
result = {
162+
'score': score,
163+
'reasons': reasons,
164+
'should_be_discussion': score >= 0.5,
165+
'confidence': 'high' if score >= 0.7 else 'medium' if score >= 0.5 else 'low'
166+
}
167+
168+
print(json.dumps(result))
169+
PYTHON_SCRIPT
170+
171+
# Run the analysis
172+
DISCUSSION_ANALYSIS=$(ISSUE_TITLE="${{ github.event.issue.title }}" \
173+
ISSUE_BODY='${{ github.event.issue.body }}' \
174+
ISSUE_LABELS='${{ toJson(github.event.issue.labels.*.name) }}' \
175+
ISSUE_NUMBER="${{ github.event.issue.number }}" \
176+
python check_discussion.py)
177+
178+
echo "analysis<<EOF" >> $GITHUB_OUTPUT
179+
echo "$DISCUSSION_ANALYSIS" >> $GITHUB_OUTPUT
180+
echo "EOF" >> $GITHUB_OUTPUT
181+
182+
# Extract whether it should be a discussion
183+
SHOULD_BE_DISCUSSION=$(echo "$DISCUSSION_ANALYSIS" | jq -r '.should_be_discussion')
184+
echo "should_be_discussion=$SHOULD_BE_DISCUSSION" >> $GITHUB_OUTPUT
185+
186+
# Extract confidence level
187+
CONFIDENCE=$(echo "$DISCUSSION_ANALYSIS" | jq -r '.confidence')
188+
echo "confidence=$CONFIDENCE" >> $GITHUB_OUTPUT
189+
190+
- name: Comment with analysis
191+
uses: actions/github-script@v7
192+
with:
193+
script: |
194+
let comment = '';
195+
const hasSimilar = '${{ steps.search.outputs.has_similar }}' === 'true';
196+
const isPR = '${{ github.event_name }}' === 'pull_request';
197+
const shouldBeDiscussion = !isPR && '${{ steps.discussion_check.outputs.should_be_discussion }}' === 'true';
198+
199+
// Add similar issues section if found
200+
if (hasSimilar) {
201+
const similarIssues = JSON.parse(`${{ steps.search.outputs.similar_issues }}`);
202+
203+
if (isPR) {
204+
// Filter only open issues for PRs
205+
const openIssues = similarIssues.filter(issue => issue.state === 'open');
206+
207+
if (openIssues.length > 0) {
208+
// Simple table format for PRs
209+
comment += '## 📋 Related Open Issues\n\n';
210+
comment += 'These open issues appear to be related to this PR (≥85% similarity):\n\n';
211+
comment += '| # | Title | Similarity |\n';
212+
comment += '|---|-------|------------|\n';
213+
214+
openIssues.forEach(issue => {
215+
const similarity = (issue.similarity * 100).toFixed(0);
216+
const title = issue.title.length > 50 ? issue.title.substring(0, 47) + '...' : issue.title;
217+
comment += `| [#${issue.number}](${issue.url}) | ${title} | ${similarity}% |\n`;
218+
});
219+
220+
comment += '\n';
221+
comment += '_Consider closing related issues if this PR addresses them._\n';
222+
} else {
223+
// Don't add any comment if no open issues found
224+
return;
225+
}
226+
} else {
227+
// Original format for issues
228+
comment += '## 🔍 Similar Issues Found\n\n';
229+
comment += 'I found some existing issues that might be related:\n\n';
230+
231+
similarIssues.forEach((issue, index) => {
232+
const similarity = (issue.similarity * 100).toFixed(1);
233+
const state = issue.state === 'open' ? '🟢' : '🔴';
234+
235+
comment += `${index + 1}. ${state} [#${issue.number}: ${issue.title}](${issue.url}) (${similarity}% similar)\n`;
236+
237+
if (issue.summary) {
238+
comment += ` > ${issue.summary.substring(0, 150)}${issue.summary.length > 150 ? '...' : ''}\n`;
239+
}
240+
comment += '\n';
241+
});
242+
243+
comment += '\n';
244+
}
245+
}
246+
247+
// Add discussion suggestion if applicable
248+
if (shouldBeDiscussion) {
249+
const analysis = JSON.parse(`${{ steps.discussion_check.outputs.analysis }}`);
250+
const confidence = '${{ steps.discussion_check.outputs.confidence }}';
251+
252+
if (hasSimilar) {
253+
comment += '---\n\n';
254+
}
255+
256+
comment += '## 💬 Discussion Suggestion\n\n';
257+
258+
const confidenceEmoji = {
259+
'high': '🟢',
260+
'medium': '🟡',
261+
'low': '🔴'
262+
};
263+
264+
comment += `This issue might be better suited as a **GitHub Discussion** ${confidenceEmoji[confidence] || ''}\n\n`;
265+
comment += `**Confidence**: ${confidence.charAt(0).toUpperCase() + confidence.slice(1)} (${(analysis.score * 100).toFixed(0)}%)\n\n`;
266+
267+
if (analysis.reasons && analysis.reasons.length > 0) {
268+
comment += '**Reasons**:\n';
269+
analysis.reasons.forEach(reason => {
270+
comment += `- ${reason}\n`;
271+
});
272+
comment += '\n';
273+
}
274+
275+
comment += 'GitHub Discussions are great for:\n';
276+
comment += '- Questions and help requests\n';
277+
comment += '- Feature requests and ideas\n';
278+
comment += '- General feedback and brainstorming\n';
279+
comment += '- Community conversations\n\n';
280+
281+
comment += '_Consider converting this to a discussion if it\'s not a bug report or actionable task._\n';
282+
}
283+
284+
// Only comment if we have something to say
285+
if (comment) {
286+
comment += '\n---\n';
287+
comment += '_This analysis was automatically generated by [deja-view](https://github.com/bdougie/deja-view)._';
288+
289+
if ('${{ github.event_name }}' === 'pull_request') {
290+
await github.rest.issues.createComment({
291+
owner: context.repo.owner,
292+
repo: context.repo.repo,
293+
issue_number: context.payload.pull_request.number,
294+
body: comment
295+
});
296+
} else {
297+
await github.rest.issues.createComment({
298+
owner: context.repo.owner,
299+
repo: context.repo.repo,
300+
issue_number: context.issue.number,
301+
body: comment
302+
});
303+
}
304+
}
305+
306+
- name: Add labels
307+
if: github.event_name == 'issues'
308+
uses: actions/github-script@v7
309+
with:
310+
script: |
311+
const labels = [];
312+
313+
// Check for duplicate
314+
if ('${{ steps.search.outputs.has_similar }}' === 'true') {
315+
const similarIssues = JSON.parse(`${{ steps.search.outputs.similar_issues }}`);
316+
const hasDuplicate = similarIssues.some(issue => issue.similarity > 0.9);
317+
318+
if (hasDuplicate) {
319+
labels.push('potential-duplicate');
320+
}
321+
}
322+
323+
// Check for discussion suggestion
324+
if ('${{ steps.discussion_check.outputs.should_be_discussion }}' === 'true') {
325+
const confidence = '${{ steps.discussion_check.outputs.confidence }}';
326+
327+
if (confidence === 'high') {
328+
labels.push('should-be-discussion');
329+
} else if (confidence === 'medium') {
330+
labels.push('discussion');
331+
}
332+
}
333+
334+
// Add labels if any
335+
if (labels.length > 0) {
336+
await github.rest.issues.addLabels({
337+
owner: context.repo.owner,
338+
repo: context.repo.repo,
339+
issue_number: context.issue.number,
340+
labels: labels
341+
});
342+
}

0 commit comments

Comments
 (0)