-
Notifications
You must be signed in to change notification settings - Fork 0
Customization Guide
BatraXPankaj edited this page Nov 13, 2025
·
1 revision
Tailor Smart Issue Analyzer to your specific workflow needs.
Current: Only runs on new issues
Add issue editing:
on:
issues:
types: [opened, edited]Add manual trigger:
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to analyze'
required: trueRun on comments:
on:
issue_comment:
types: [created]Add custom topic labels:
// In classification system prompt:
Available labels:
- bug, enhancement, documentation
- YOUR_CUSTOM_LABEL_1
- YOUR_CUSTOM_LABEL_2Modify priority levels:
// Change from P0-P3 to Critical/High/Medium/Low:
"priority": "critical" // instead of "P0"
// Update label application:
labels.push(`priority-${classifyResult.priority.toLowerCase()}`);Change size scale:
// T-shirt sizes to story points:
"size": "3" // instead of "M"
// Or use hours:
"estimatedHours": 8Increase response length:
// For more detailed analysis:
callLLM(systemPrompt, userPrompt, 500) // was 300Adjust creativity:
// More consistent (less creative):
temperature: 0.1
// More creative (less consistent):
temperature: 0.7Change model:
model: 'gpt-4o' // More powerful
model: 'gpt-3.5-turbo' // Faster
model: 'gpt-4o-mini' // Current (balanced)Example: Code Quality Analysis
// Add 5th parallel call:
const [dup, classify, contextAnalysis, translation, codeQuality] = await Promise.all([
// ... existing calls ...
callLLM(
'You are a code quality analyzer.',
`Analyze this issue for code quality concerns:
Title: ${issueTitle}
Body: ${issueBody}
Return JSON:
{
"hasCodeSnippet": true/false,
"qualityIssues": ["naming", "performance", "security"],
"complexity": "low/medium/high"
}`,
200
)
]);
// Parse and use results:
const codeQualityResult = JSON.parse(codeQuality.match(/\{[\s\S]*\}/)?.[0] || '{}');
if (codeQualityResult.hasCodeSnippet) {
labels.push('has-code');
}Add multiple languages:
const translations = await Promise.all([
// Spanish
callLLM('Translator', `Translate to Spanish:\n${issueBody}`, 400),
// French
callLLM('Translator', `Translate to French:\n${issueBody}`, 400),
// German
callLLM('Translator', `Translate to German:\n${issueBody}`, 400)
]);
const [spanish, french, german] = translations.map(t =>
JSON.parse(t.match(/\{[\s\S]*\}/)?.[0] || '{}')
);
// Add to comment:
commentBody += `
### 🌐 Translations
**Spanish:** ${spanish.title}
**French:** ${french.title}
**German:** ${german.title}
`;Map labels to teams:
const teamMapping = {
'bug': ['backend-team', 'qa-team'],
'ui/ux': ['design-team', 'frontend-team'],
'security': ['security-team'],
'performance': ['devops-team', 'backend-team'],
'documentation': ['tech-writers']
};
// Get teams for this issue:
const suggestedTeams = classifyResult.labels
.flatMap(label => teamMapping[label] || [])
.filter((team, index, self) => self.indexOf(team) === index); // Unique
// Auto-assign:
if (suggestedTeams.length > 0) {
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
assignees: suggestedTeams
});
}// After high-priority issue detection:
if (classifyResult.priority === 'P0' || labels.includes('security')) {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `🚨 Critical Issue Created`,
blocks: [{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${issueTitle}*\nPriority: ${classifyResult.priority}\n<${issueUrl}|View Issue>`
}
}]
})
});
}// Create Jira ticket for high-priority issues:
if (classifyResult.priority === 'P0') {
await fetch('https://your-domain.atlassian.net/rest/api/3/issue', {
method: 'POST',
headers: {
'Authorization': `Basic ${Buffer.from(
`${process.env.JIRA_EMAIL}:${process.env.JIRA_API_TOKEN}`
).toString('base64')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
fields: {
project: { key: 'PROJ' },
summary: issueTitle,
description: issueBody,
issuetype: { name: 'Bug' },
priority: { name: 'Highest' }
}
})
});
}// Using SendGrid:
if (contextAnalysis.sentiment === 'negative') {
await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
personalizations: [{
to: [{ email: 'support@example.com' }],
subject: `Frustrated user: ${issueTitle}`
}],
from: { email: 'noreply@example.com' },
content: [{
type: 'text/plain',
value: `A user submitted an issue with negative sentiment:\n\n${issueBody}`
}]
})
});
}Instead of auto-closing:
// Just flag as potential duplicate:
if (dupResult.isDuplicate) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['potential-duplicate']
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `⚠️ This may be a duplicate of #${dupResult.duplicateOf}. Please review before closing.`
});
// Don't auto-close
}Merge duplicates:
if (dupResult.isDuplicate) {
// Copy comments from new issue to original
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
for (const comment of comments.data) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: dupResult.duplicateOf,
body: `> From duplicate #${context.issue.number}:\n\n${comment.body}`
});
}
// Then close
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'closed'
});
}Auto-create checklist:
if (contextAnalysis.missingInfo?.length > 0) {
const checklist = contextAnalysis.missingInfo
.map(item => `- [ ] ${item}`)
.join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `### ⚠️ Please provide the following information:\n\n${checklist}\n\n*Check items as you add them to the issue description.*`
});
// Apply "needs-more-info" label
labels.push('needs-more-info');
}Add to project board column:
// Get project board:
const projects = await github.rest.projects.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo
});
const projectId = projects.data[0]?.id; // First project
if (projectId) {
// Get columns:
const columns = await github.rest.projects.listColumns({ project_id: projectId });
// Determine column based on priority:
const columnMap = {
'P0': 'Urgent',
'P1': 'High Priority',
'P2': 'Backlog',
'P3': 'Nice to Have'
};
const targetColumn = columns.data.find(c =>
c.name === columnMap[classifyResult.priority]
);
if (targetColumn) {
// Create card:
await github.rest.projects.createCard({
column_id: targetColumn.id,
content_id: context.issue.id,
content_type: 'Issue'
});
}
}Escalate old issues:
// Check issue age:
const createdAt = new Date(context.payload.issue.created_at);
const now = new Date();
const ageHours = (now - createdAt) / (1000 * 60 * 60);
if (ageHours > 24 && classifyResult.priority === 'P1') {
// Escalate to P0 if not addressed in 24h:
await github.rest.issues.addLabels({
labels: ['priority-p0', 'escalated']
});
await github.rest.issues.createComment({
body: '⚠️ Escalated to P0 due to age (24+ hours without response)'
});
}Track analysis metrics:
// At end of workflow:
const metrics = {
timestamp: new Date().toISOString(),
issue_number: context.issue.number,
analysis_time: duration,
labels_applied: labels.length,
priority: classifyResult.priority,
size: classifyResult.size,
is_duplicate: dupResult.isDuplicate,
sentiment: contextAnalysis.sentiment
};
// Send to analytics service:
await fetch('https://your-analytics.com/api/metrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metrics)
});
// Or store in GitHub as artifact:
const fs = require('fs');
fs.writeFileSync('metrics.json', JSON.stringify(metrics));Test different prompts:
// Randomly select prompt version:
const promptVersion = Math.random() < 0.5 ? 'v1' : 'v2';
const systemPromptV1 = 'You are a classifier. Return JSON.';
const systemPromptV2 = 'You are an expert GitHub issue triager with 10 years experience. Analyze carefully and return structured JSON.';
const systemPrompt = promptVersion === 'v1' ? systemPromptV1 : systemPromptV2;
// Log which version was used:
console.log(`Using prompt version: ${promptVersion}`);
// Track results to see which performs betterSkip analysis for certain users:
// Don't analyze issues from bots:
if (context.payload.issue.user.type === 'Bot') {
console.log('Skipping bot-created issue');
return;
}
// Skip issues with specific labels:
const skipLabels = ['wontfix', 'invalid', 'spam'];
if (context.payload.issue.labels.some(l => skipLabels.includes(l.name))) {
console.log('Skipping issue with exclusion label');
return;
}Instead of hardcoding, use config file:
.github/issue-analyzer-config.json:
{
"models": {
"classification": "gpt-4o-mini",
"translation": "gpt-4o-mini",
"temperature": 0.3
},
"labels": {
"topics": ["bug", "enhancement", "documentation"],
"priorities": ["P0", "P1", "P2", "P3"],
"sizes": ["XS", "S", "M", "L", "XL"]
},
"teams": {
"bug": ["backend-team"],
"ui/ux": ["frontend-team", "design-team"]
},
"autoClose": {
"duplicates": true,
"spam": true
},
"notifications": {
"slack": {
"enabled": true,
"priorityThreshold": "P1"
}
}
}Load in workflow:
const fs = require('fs');
const config = JSON.parse(
fs.readFileSync('.github/issue-analyzer-config.json', 'utf8')
);
// Use config values:
const model = config.models.classification;
const temperature = config.models.temperature;
const validLabels = config.labels.topics;name: Test Custom Analysis
on:
workflow_dispatch:
inputs:
test_issue:
description: 'Test issue number'
required: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
script: |
// Your custom code here
console.log('Testing with issue:', '${{ github.event.inputs.test_issue }}');- Fork repository
- Test changes there first
- Verify results before deploying to production
const DRY_RUN = process.env.DRY_RUN === 'true';
if (DRY_RUN) {
console.log('DRY RUN: Would apply labels:', labels);
console.log('DRY RUN: Would post comment:', commentBody);
} else {
await github.rest.issues.addLabels({...});
await github.rest.issues.createComment({...});
}- Examples - See customizations in action
- Best Practices - Optimization tips
- API Reference - Function documentation
- Architecture - Understand the system