"
+ }
+ `;
+
+ try {
+ const responseResult = await callGitHubModels(responsePrompt);
+ const parsed = JSON.parse(responseResult);
+ suggestedResponse = parsed.suggested_response || '';
+ console.log('Suggested customer response generated');
+ } catch (e) {
+ console.log(`Suggested response generation failed: ${e.message}`);
+ suggestedResponse = '';
+ }
+
// --- Store outputs ---
core.setOutput('category', analysis.category);
core.setOutput('confidence', analysis.confidence.toString());
@@ -256,6 +473,9 @@ jobs:
core.setOutput('keywords', analysis.keywords.join(', '));
core.setOutput('code_analysis', codeAnalysis);
core.setOutput('engineer_guidance', engineerGuidance);
+ core.setOutput('suggested_response', suggestedResponse);
+ core.setOutput('similar_issues', similarIssuesAnalysis);
+ core.setOutput('workaround', workaroundAnalysis);
core.setOutput('issue_number', issueNumber.toString());
core.setOutput('issue_title', issueTitle);
core.setOutput('issue_url', issue.html_url);
@@ -271,6 +491,9 @@ jobs:
keywords: ${{ steps.triage.outputs.keywords }}
code_analysis: ${{ steps.triage.outputs.code_analysis }}
engineer_guidance: ${{ steps.triage.outputs.engineer_guidance }}
+ suggested_response: ${{ steps.triage.outputs.suggested_response }}
+ similar_issues: ${{ steps.triage.outputs.similar_issues }}
+ workaround: ${{ steps.triage.outputs.workaround }}
issue_number: ${{ steps.triage.outputs.issue_number }}
issue_title: ${{ steps.triage.outputs.issue_title }}
issue_url: ${{ steps.triage.outputs.issue_url }}
@@ -289,6 +512,9 @@ jobs:
keywords: ${{ needs.triage.outputs.keywords }}
code_analysis: ${{ needs.triage.outputs.code_analysis }}
engineer_guidance: ${{ needs.triage.outputs.engineer_guidance }}
+ suggested_response: ${{ needs.triage.outputs.suggested_response }}
+ similar_issues: ${{ needs.triage.outputs.similar_issues }}
+ workaround: ${{ needs.triage.outputs.workaround }}
issue_number: ${{ needs.triage.outputs.issue_number }}
issue_title: ${{ needs.triage.outputs.issue_title }}
issue_url: ${{ needs.triage.outputs.issue_url }}
diff --git a/test-triage-local.js b/test-triage-local.js
index f67eb142..1f0f71fc 100644
--- a/test-triage-local.js
+++ b/test-triage-local.js
@@ -94,7 +94,7 @@ async function fetchFileContent(filePath) {
}
// --- Helper: Send Teams notification ---
-async function sendTeamsNotification(analysis, codeAnalysis, engineerGuidance, issue) {
+async function sendTeamsNotification(analysis, codeAnalysis, engineerGuidance, suggestedResponse, similarIssuesAnalysis, workaroundAnalysis, issue) {
const category = analysis.category;
const severity = analysis.severity;
@@ -171,7 +171,6 @@ async function sendTeamsNotification(analysis, codeAnalysis, engineerGuidance, i
}
if (parsed.implementation_approach) parts.push(`Implementation Approach: ${escVal(parsed.implementation_approach)}`);
if (parsed.risks_and_tradeoffs) parts.push(`Risks & Tradeoffs: ${escVal(parsed.risks_and_tradeoffs)}`);
- if (parsed.suggested_response) parts.push(`Suggested Response to User:
${escVal(parsed.suggested_response)}`);
if (parsed.related_considerations && parsed.related_considerations.length > 0) {
parts.push(`Related Considerations:
${parsed.related_considerations.map((s, i) => ` ${i + 1}. ${escVal(s)}`).join("
")}`);
}
@@ -184,6 +183,53 @@ async function sendTeamsNotification(analysis, codeAnalysis, engineerGuidance, i
}
}
+ // Format similar issues analysis
+ let similarIssuesText = "";
+ if (similarIssuesAnalysis) {
+ try {
+ const parsed = JSON.parse(similarIssuesAnalysis);
+ const escVal = (s) => String(s).replace(/&/g, "&").replace(//g, ">");
+ const parts = [];
+ if (parsed.summary) parts.push(`Summary: ${escVal(parsed.summary)}`);
+ if (parsed.duplicate_issues && parsed.duplicate_issues.length > 0) {
+ parts.push(`Similar/Duplicate Issues:
${parsed.duplicate_issues.filter(d => /^\d+$/.test(String(d.issue_number))).map(d =>
+ ` • #${Number(d.issue_number)} ${escVal(d.title)} [${escVal(d.state)}] — ${escVal(d.similarity)}: ${escVal(d.explanation)}`
+ ).join("
")}`);
+ }
+ if (parsed.recently_fixed && parsed.recently_fixed.length > 0) {
+ parts.push(`Recently Fixed:
${parsed.recently_fixed.filter(f => /^\d+$/.test(String(f.issue_number))).map(f =>
+ ` • #${Number(f.issue_number)} ${escVal(f.title)} (closed ${escVal(f.closed_at)}) — ${escVal(f.relevance)}`
+ ).join("
")}`);
+ }
+ similarIssuesText = parts.join("
");
+ } catch (e) {
+ similarIssuesText = esc(similarIssuesAnalysis);
+ }
+ }
+
+ // Format workaround analysis
+ let workaroundText = "";
+ if (workaroundAnalysis) {
+ try {
+ const parsed = JSON.parse(workaroundAnalysis);
+ const escVal = (s) => String(s).replace(/&/g, "&").replace(//g, ">");
+ const parts = [];
+ if (parsed.summary) parts.push(`Summary: ${escVal(parsed.summary)}`);
+ if (parsed.has_workaround && parsed.workarounds && parsed.workarounds.length > 0) {
+ const validConfidence = ['high', 'medium', 'low'];
+ parts.push(`Workarounds:
${parsed.workarounds.map((w, i) =>
+ ` ${i + 1}. ${escVal(w.description)} [${validConfidence.includes(w.confidence) ? w.confidence : 'unknown'} confidence]
Limitations: ${escVal(w.limitations)}${w.code_snippet ? `
Code: ${escVal(w.code_snippet)}` : ''}`
+ ).join("
")}`);
+ }
+ if (parsed.can_downgrade && parsed.downgrade_version) {
+ parts.push(`⬇️ Safe to downgrade to: ${escVal(parsed.downgrade_version)}`);
+ }
+ workaroundText = parts.join("
");
+ } catch (e) {
+ workaroundText = esc(workaroundAnalysis);
+ }
+ }
+
const htmlMessage = [
`${emoji} mssql-python Issue Triage
`,
`${esc(categoryDisplay)} | `,
@@ -201,8 +247,16 @@ async function sendTeamsNotification(analysis, codeAnalysis, engineerGuidance, i
`
${esc(analysis.summary_for_maintainers)}
`,
`🔍 Code Analysis
`,
`${codeAnalysisText}
`,
+ similarIssuesText ? `🔄 Similar Issues & Recent Fixes
` : '',
+ similarIssuesText ? `${similarIssuesText}
` : '',
+ workaroundText ? `🛠️ Workarounds
` : '',
+ workaroundText ? `${workaroundText}
` : '',
engineerGuidanceText ? `💡 Engineer Guidance
` : '',
engineerGuidanceText ? `${engineerGuidanceText}
` : '',
+ suggestedResponse ? `
` : '',
+ suggestedResponse ? `✉️ Suggested Response to Customer
` : '',
+ suggestedResponse ? `Copy-paste or edit the response below and post it on the issue:
` : '',
+ suggestedResponse ? `${esc(suggestedResponse)}
` : '',
`
`,
`⚡ Action Required: ${esc(action)}
`,
`⚠️ AI-generated analysis — verified against source code but may contain inaccuracies. Review before acting.
`,
@@ -393,7 +447,6 @@ Respond in JSON:
"implementation_approach": "",
"effort_estimate": "small|medium|large|epic",
"risks_and_tradeoffs": "",
- "suggested_response": "",
"related_considerations": [""]
}
@@ -415,10 +468,219 @@ IMPORTANT: If your technical_assessment does not identify any actual issue or ga
}
}
+ // --- Search for similar/duplicate issues and recent fixes ---
+ console.log(`\n🔄 Searching for similar issues and recent fixes...`);
+ let similarIssuesAnalysis = '';
+
+ try {
+ const headers = { "Accept": "application/vnd.github.v3+json", "User-Agent": "triage-test" };
+ if (process.env.GH_TOKEN) headers["Authorization"] = `token ${process.env.GH_TOKEN}`;
+
+ const openRes = await fetch(`https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/issues?state=open&per_page=30&sort=created&direction=desc`, { headers });
+ const openIssues = await openRes.json();
+
+ const closedRes = await fetch(`https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/issues?state=closed&per_page=30&sort=updated&direction=desc`, { headers });
+ const closedIssues = await closedRes.json();
+
+ const allIssues = [...openIssues, ...closedIssues]
+ .filter(i => i.number !== issueNumber && !i.pull_request)
+ .map(i => ({
+ number: i.number,
+ title: i.title,
+ state: i.state,
+ labels: i.labels.map(l => l.name).join(', '),
+ created_at: i.created_at,
+ closed_at: i.closed_at,
+ body_preview: (i.body || '').slice(0, 300)
+ }));
+
+ console.log(` Found ${allIssues.length} issues to compare against`);
+
+ const similarPrompt = `
+You are an expert triage system for the mssql-python repository.
+A new issue has been filed. Your job is to check if any similar or duplicate issues
+already exist (open or recently closed/fixed).
+
+NEW ISSUE:
+Title: ${issue.title}
+Body: ${(issue.body || '').slice(0, 2000)}
+Keywords: ${analysis.keywords.join(', ')}
+
+EXISTING ISSUES (recent open + recently closed):
+${JSON.stringify(allIssues, null, 2).slice(0, 6000)}
+
+Analyze and respond in JSON:
+{
+ "has_duplicates": true/false,
+ "has_similar": true/false,
+ "duplicate_issues": [
+ {
+ "issue_number": ,
+ "title": "",
+ "state": "open|closed",
+ "similarity": "duplicate|highly_similar|related",
+ "explanation": ""
+ }
+ ],
+ "recently_fixed": [
+ {
+ "issue_number": ,
+ "title": "",
+ "closed_at": "",
+ "relevance": ""
+ }
+ ],
+ "summary": "<1-3 sentence summary: are there duplicates? was this recently fixed? should the new issue be closed as duplicate?>"
+}
+
+IMPORTANT:
+- Only flag as duplicate if the core problem is truly the same, not just topically related
+- For recently_fixed, only include issues that were closed AND directly address the same problem
+- If no duplicates or similar issues exist, set has_duplicates and has_similar to false with empty arrays
+`;
+
+ similarIssuesAnalysis = await callGitHubModels(similarPrompt);
+ const parsed = JSON.parse(similarIssuesAnalysis);
+ console.log(` Duplicates: ${parsed.has_duplicates}, Similar: ${parsed.has_similar}`);
+ console.log(` Summary: ${parsed.summary}`);
+ } catch (e) {
+ console.log(` ⚠️ Similar issues search failed: ${e.message}`);
+ }
+
+ // --- Generate workaround suggestions (BUG/BREAK_FIX only) ---
+ let workaroundAnalysis = '';
+
+ if (["BUG", "BREAK_FIX"].includes(analysis.category)) {
+ console.log(`\n🛠️ Generating workaround suggestions...`);
+
+ const workaroundCtx = codeAnalysis ? `Code Analysis:\n${codeAnalysis}` : '';
+
+ const workaroundPrompt = `
+You are a senior support engineer on the mssql-python team — Microsoft's Python driver for SQL Server.
+A user has reported a ${analysis.category === "BREAK_FIX" ? "regression/break-fix" : "bug"}.
+Your job is to suggest practical workarounds the user can apply RIGHT NOW while the team works on a proper fix.
+
+Issue Title: ${issue.title}
+Issue Body:
+${(issue.body || "").slice(0, 2000)}
+
+${workaroundCtx}
+${codeContext}
+
+Provide workaround suggestions in JSON:
+{
+ "has_workaround": true/false,
+ "workarounds": [
+ {
+ "description": "",
+ "code_snippet": "",
+ "limitations": "",
+ "confidence": "high|medium|low"
+ }
+ ],
+ "can_downgrade": true/false,
+ "downgrade_version": "",
+ "summary": "<1-2 sentence summary of available workarounds>"
+}
+
+IMPORTANT:
+- Only suggest workarounds you are confident about based on the code and issue description
+- Workarounds should be practical and safe for production use
+- If no reliable workaround exists, set has_workaround to false
+- For BREAK_FIX issues, always consider if downgrading to a previous version is viable
+- Code snippets should be complete and copy-pasteable
+`;
+
+ try {
+ workaroundAnalysis = await callGitHubModels(workaroundPrompt);
+ const parsed = JSON.parse(workaroundAnalysis);
+ console.log(` Has workaround: ${parsed.has_workaround}`);
+ console.log(` Summary: ${parsed.summary}`);
+ if (parsed.workarounds && parsed.workarounds.length > 0) {
+ for (const w of parsed.workarounds) {
+ console.log(` • ${w.description} [${w.confidence}]`);
+ }
+ }
+ } catch (e) {
+ console.log(` ⚠️ Workaround generation failed: ${e.message}`);
+ }
+ }
+
+ // --- Generate suggested customer response (all categories) ---
+ console.log(`\n✉️ Generating suggested customer response...`);
+ let suggestedResponse = '';
+
+ const analysisContext = codeAnalysis
+ ? `Code Analysis:\n${codeAnalysis}`
+ : engineerGuidance
+ ? `Engineer Guidance:\n${engineerGuidance}`
+ : '';
+
+ const workaroundResponseCtx = workaroundAnalysis
+ ? `Workaround Analysis:\n${workaroundAnalysis}`
+ : '';
+
+ const similarResponseCtx = similarIssuesAnalysis
+ ? `Similar Issues Analysis:\n${similarIssuesAnalysis}`
+ : '';
+
+ const responsePrompt = `
+You are a senior support engineer on the mssql-python team — Microsoft's Python driver for SQL Server.
+A customer filed a GitHub issue and you need to craft a helpful, professional, and empathetic response
+that an engineer can copy-paste (or lightly edit) and post on the issue.
+
+Issue Category: ${analysis.category}
+Severity: ${analysis.severity}
+Issue Title: ${issue.title}
+Issue Author: @${issue.user.login}
+Issue Body:
+${(issue.body || '').slice(0, 3000)}
+
+${analysisContext}
+
+${workaroundResponseCtx}
+
+${similarResponseCtx}
+
+Write a suggested response following these guidelines:
+- Address the author by their GitHub username (@username)
+- Thank them for filing the issue
+- Acknowledge the specific problem or request they described
+- For BUG/BREAK_FIX: Let them know the team is investigating; ask for OS, Python version,
+ mssql-python version, SQL Server version, and a minimal repro script if not already provided;
+ if workarounds are available, mention them briefly
+- For FEATURE_REQUEST: Acknowledge the value of the request; mention the team will evaluate it;
+ ask for use-case details or code examples showing desired behavior if not provided
+- For DISCUSSION: Provide helpful guidance or clarification based on the analysis;
+ point to relevant docs or code if applicable
+- If similar or duplicate issues were found, mention them (e.g., "This looks related to #123")
+- Always ask for a minimal reproduction script/code snippet if the user hasn't provided one
+- Keep the tone warm, professional, and collaborative
+- Use Markdown formatting suitable for GitHub comments
+- Do NOT promise timelines or specific fixes
+- Do NOT reveal internal triage details or AI involvement
+- Keep it concise (under 200 words)
+
+Respond in JSON:
+{
+ "suggested_response": ""
+}
+`;
+
+ try {
+ const responseResult = await callGitHubModels(responsePrompt);
+ const parsed = JSON.parse(responseResult);
+ suggestedResponse = parsed.suggested_response || '';
+ console.log(` ✅ Suggested response generated`);
+ console.log(`\n✉️ Suggested Response:\n${suggestedResponse}`);
+ } catch (e) {
+ console.log(` ⚠️ Suggested response generation failed: ${e.message}`);
+ }
+
// --- Send Teams notification ---
console.log(`\n📤 Sending Teams notification...`);
try {
- const status = await sendTeamsNotification(analysis, codeAnalysis, engineerGuidance, issue);
+ const status = await sendTeamsNotification(analysis, codeAnalysis, engineerGuidance, suggestedResponse, similarIssuesAnalysis, workaroundAnalysis, issue);
console.log(` ✅ Teams notification sent (HTTP ${status})`);
} catch (e) {
console.error(` ❌ Teams notification failed: ${e.message}`);