Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
ffc7b28
Add AI-powered issue triage workflow
kenjpais Oct 23, 2025
3daa2e5
Add AI triage setup documentation
kenjpais Oct 23, 2025
1412784
Add Slack notifications with AI triage and summary
kenjpais Oct 23, 2025
2c22b99
Add Slack integration documentation
kenjpais Oct 23, 2025
0c3362a
Trigger workflow on issue edits and reopens
kenjpais Oct 23, 2025
e45a8e1
Fix workflow: correct label check syntax and cleanup code
kenjpais Nov 3, 2025
c618762
Updated workflows
kenjpais Nov 3, 2025
0687259
Deleted unnecessary files
kenjpais Nov 3, 2025
88394d0
Restore ISSUE_TEMPLATE files from remote master
kenjpais Nov 3, 2025
3a083de
Fixed duplicate comment issue
kenjpais Nov 3, 2025
fbe2778
Merge pull request #27 from kenjpais/ai-triage
kenjpais Nov 3, 2025
f596fd3
Removed label trigger
kenjpais Nov 3, 2025
16ef195
Merge pull request #34 from kenjpais/ai-triage
kenjpais Nov 3, 2025
606ac21
Workfflow will run even if needs-triage label is not applied
kenjpais Nov 4, 2025
360fc77
Merge pull request #41 from kenjpais/ai-triage
kenjpais Nov 4, 2025
c6d3e9d
Updated workflow to run when kind/bug label is applied
kenjpais Nov 4, 2025
c188b9e
Merge pull request #55 from kenjpais/ai-triage
kenjpais Nov 4, 2025
901d624
Automated adding triage/needs-triage label
kenjpais Nov 4, 2025
9967a12
Merge pull request #57 from kenjpais/ai-triage
kenjpais Nov 4, 2025
aaf05c8
Fixed label format
kenjpais Nov 4, 2025
206fea8
Merge pull request #59 from kenjpais/ai-triage
kenjpais Nov 4, 2025
dc1e851
Added delay such that label is added reliably
kenjpais Nov 4, 2025
6320ef3
Merge pull request #61 from kenjpais/ai-triage
kenjpais Nov 4, 2025
1dc1a01
Testing fix
kenjpais Nov 4, 2025
28d76de
Merge pull request #63 from kenjpais/ai-triage
kenjpais Nov 4, 2025
81d7bd9
Applied document suggested label trigger
kenjpais Nov 4, 2025
ac496a3
Merge pull request #65 from kenjpais/ai-triage
kenjpais Nov 4, 2025
8e8851f
Testing fix
kenjpais Nov 4, 2025
b6d5edb
Merge pull request #67 from kenjpais/ai-triage
kenjpais Nov 4, 2025
3c3418a
Updated condn
kenjpais Nov 4, 2025
bc7412c
Merge pull request #69 from kenjpais/ai-triage
kenjpais Nov 4, 2025
8385f0d
Testing new condn
kenjpais Nov 4, 2025
d7b84ac
Merge pull request #71 from kenjpais/ai-triage
kenjpais Nov 4, 2025
4872519
Simp condn
kenjpais Nov 4, 2025
2e6110d
Merge pull request #73 from kenjpais/ai-triage
kenjpais Nov 4, 2025
c2de94e
Added sequential check
kenjpais Nov 4, 2025
3e8740f
Merge pull request #75 from kenjpais/ai-triage
kenjpais Nov 4, 2025
d7cb0c0
Added ensure-label step
kenjpais Nov 4, 2025
348c05e
Merge pull request #77 from kenjpais/ai-triage
kenjpais Nov 4, 2025
c11eff8
Further delay
kenjpais Nov 4, 2025
6156a09
Merge pull request #81 from kenjpais/ai-triage
kenjpais Nov 4, 2025
1693bb6
Increased wait
kenjpais Nov 4, 2025
6639c37
Merge pull request #83 from kenjpais/ai-triage
kenjpais Nov 4, 2025
7f50ba4
New verification
kenjpais Nov 4, 2025
17bcabd
Merge pull request #85 from kenjpais/ai-triage
kenjpais Nov 4, 2025
cbeeb78
Disabled concurrency
kenjpais Nov 4, 2025
cb5d5f3
Merge pull request #87 from kenjpais/ai-triage
kenjpais Nov 4, 2025
a9c94a8
Simplified entire workflow with triage/needs-triage trigger
kenjpais Nov 4, 2025
82e494c
Merge pull request #89 from kenjpais/ai-triage
kenjpais Nov 4, 2025
9bca602
Updated workflow with api call with force
kenjpais Nov 4, 2025
46a35d3
Merge pull request #113 from kenjpais/ai-triage
kenjpais Nov 4, 2025
047aa1c
API call with delay
kenjpais Nov 4, 2025
e1fced4
Merge pull request #116 from kenjpais/ai-triage
kenjpais Nov 4, 2025
a22b4c0
Changed triggger to labeled
kenjpais Nov 4, 2025
85c3838
Merge pull request #118 from kenjpais/ai-triage
kenjpais Nov 4, 2025
020f0c3
Use trigger kind/bug label
kenjpais Nov 4, 2025
8ecb802
Merge pull request #120 from kenjpais/ai-triage
kenjpais Nov 4, 2025
3bf7b5e
fixed issue in condn
kenjpais Nov 4, 2025
bc15843
Merge pull request #122 from kenjpais/ai-triage
kenjpais Nov 4, 2025
44e4e00
Added step to reapply kind/label
kenjpais Nov 4, 2025
dd5bcbe
Merge pull request #124 from kenjpais/ai-triage
kenjpais Nov 4, 2025
72db48c
Added priority check
kenjpais Nov 4, 2025
0895b71
Merge pull request #129 from kenjpais/ai-triage
kenjpais Nov 4, 2025
e18faa1
FIX simplified workflow
kenjpais Nov 4, 2025
3ec01ca
Merge pull request #131 from kenjpais/ai-triage
kenjpais Nov 4, 2025
e0f924b
Updated labels_to_prompts_mapping
kenjpais Nov 4, 2025
6f74bcc
Merge pull request #133 from kenjpais/ai-triage
kenjpais Nov 4, 2025
060fe59
Trying consolidated prompts
kenjpais Nov 4, 2025
773af5d
Merge pull request #135 from kenjpais/ai-triage
kenjpais Nov 4, 2025
70f916b
Testing prompt
kenjpais Nov 4, 2025
fe71f65
Merge pull request #137 from kenjpais/ai-triage
kenjpais Nov 4, 2025
e81edd4
Testing diff model
kenjpais Nov 4, 2025
eb00845
Merge pull request #139 from kenjpais/ai-triage
kenjpais Nov 4, 2025
8be3ded
Fixed header
kenjpais Nov 4, 2025
92c47c1
Merge pull request #141 from kenjpais/ai-triage
kenjpais Nov 4, 2025
c4f3c5f
Fixed substr issue in notify-slack
kenjpais Nov 4, 2025
025b88a
Merge pull request #143 from kenjpais/ai-triage
kenjpais Nov 4, 2025
d11d69b
Added mirroring workflow to copy existing issues on upstream repo to …
kenjpais Nov 7, 2025
4b537ef
Merge pull request #169 from kenjpais/ai-triage
kenjpais Nov 7, 2025
f3843ed
Update mirrored issues tracking: 60 new issues
github-actions[bot] Nov 7, 2025
bc5f341
Revert "Update mirrored issues tracking: 60 new issues"
kenjpais Nov 7, 2025
4e8397d
Revert "Merge pull request #169 from kenjpais/ai-triage"
kenjpais Nov 7, 2025
f844adc
Refactored code and moved scripts
kenjpais Nov 10, 2025
496deb3
Fixed bugs
kenjpais Nov 10, 2025
ef4bade
Fixed secret access issue
kenjpais Nov 10, 2025
e430d69
Fixed bug
kenjpais Nov 10, 2025
a159097
Added debug log
kenjpais Nov 10, 2025
3200111
Fixed issue in scripts
kenjpais Nov 10, 2025
d6d8986
Fixed debug issue
kenjpais Nov 10, 2025
e3100a0
Added slack formatting
kenjpais Nov 10, 2025
c4b5558
Modularized scripts
kenjpais Nov 10, 2025
996b2ee
Simplified workflows
kenjpais Nov 10, 2025
be18319
Added read
kenjpais Nov 10, 2025
331c690
Fixed read:
kenjpais Nov 10, 2025
00f27fd
Updated model name
kenjpais Nov 10, 2025
80a576a
Fixed model naming
kenjpais Nov 10, 2025
0dc38f4
Refactored and reduced code
kenjpais Nov 10, 2025
047d513
Removed unnecessary workflows for push to original repo
kenjpais Nov 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/prompts/bug-triage.prompt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
messages:
- role: system
content: >+
You are an expert software triage engineer analyzing bug reports for OKD (The Community Distribution of Kubernetes).
OKD is the community distribution of Kubernetes that powers Red Hat's OpenShift, optimized for continuous application
development and multi-tenant deployment.

Your task is to assess whether a bug report is complete and actionable. Analyze the bug report for the following key elements:

1. Problem Description: Is there a clear description of what went wrong and what was expected?
2. Reproduction Steps: Are there specific steps provided to reproduce the issue?
3. Environment Information: Is cluster version, platform (AWS/bare metal/etc), and relevant context provided?
4. Log Output: Are there logs, error messages, or diagnostic output (if applicable)?

Rate the overall bug report as:
- Ready for Review - All critical information is present, the bug is clearly described, and can be worked on immediately
- Missing Details - Important information is missing or unclear (specify what's needed)
- Needs Clarification - The report is confusing or contradictory and requires clarification from the reporter
The severity and component output MUST be a single word enclosed in the header "### AI Assessment:". Concatenate the severity and component with a hyphen.

Rate the issue's severity based on its description of impact:
- critical: System is down, major data loss, or core functionality completely broken.
- high: Significant disruption, major feature broken, or common user workflow blocked.
- medium: Minor inconvenience, visual bug, or easily worked around issue.
- low: Cosmetic issue, documentation error, or non-critical feature improvement
Also, determine the primary component affected from this list:
- CoreAPI: Kubernetes/OpenShift API server and controllers
- Networking: SDN, ingress/routes, CNI configuration
- Installation: Bare metal, AWS, or Azure install process and configuration
- Storage: Persistent Volumes, storage classes, or volume mounting
- WebConsole: UI and user experience issues
- Documentation: Errors in guides or reference material

Example Output: `### AI Assessment: high-Networking`
Example Output: `### AI Assessment: critical-CoreAPI`

Response Format:
Start your response with: `AI Assessment: [Severity]-[Component]`
(For example: `AI Assessment: high-Networking` or `AI Assessment: critical-CoreAPI`)

Then provide:
1. A brief analysis of each key element (1-2 sentences each)
2. What specific information is missing (if any)
3. Overall recommendation for next steps

Keep your response under 200 words and be specific about what's missing or unclear.
- role: user
content: '{{input}}'
model: openai/gpt-4o
modelParameters:
max_tokens: 300
testData: []
evaluators: []
26 changes: 26 additions & 0 deletions .github/scripts/add-label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Adds the kind/bug label to an issue
*/

async function addLabel(github, context, core, issueNumber) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: ['kind/bug'],
});
core.info(`Added kind/bug label to issue #${issueNumber} after AI assessment`);
} catch (error) {
// Label might already exist, which is fine
if (error.status === 422) {
core.info(`Label kind/bug already exists on issue #${issueNumber}`);
} else {
core.warning(`Failed to add label to issue #${issueNumber}: ${error.message}`);
throw error;
}
}
}

module.exports = { addLabel };

38 changes: 38 additions & 0 deletions .github/scripts/format-message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Formats a simple message for both GitHub comments and Slack
* Uses plain text format that works for both platforms
*/

function parseAssessments(assessmentOutput, core) {
let assessments = [];
try {
assessments = JSON.parse(assessmentOutput || '[]');
} catch (e) {
if (core) {
core.warning(`Failed to parse assessment output: ${e.message}`);
}
}
return assessments;
}

function formatMessage(issue, assessments) {
let message = `OKD Issue #${issue.number}: ${issue.title}\n`;
message += `${issue.html_url}\n\n`;

if (!assessments || assessments.length === 0) {
message += 'No triage assessment available';
} else {
for (const assessment of assessments) {
const label = assessment.assessmentLabel || 'N/A';
const response = assessment.response || 'No response';

message += `Label: ${label}\n`;
message += `Assessment: ${response}\n`;
}
}

return message.trim();
}

module.exports = { formatMessage, parseAssessments };

62 changes: 62 additions & 0 deletions .github/scripts/format-slack-message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Formats messages specifically for Slack
* Converts GitHub markdown formatting to Slack's format
*/

/**
* Converts markdown text to Slack-compatible formatting
*/
function convertMarkdownToSlack(text) {
if (!text) return '';

let converted = text;

// Convert markdown headers (### Header) to bold text
converted = converted.replace(/^###\s+(.+)$/gm, '*$1*');
converted = converted.replace(/^##\s+(.+)$/gm, '*$1*');
converted = converted.replace(/^#\s+(.+)$/gm, '*$1*');

// Convert markdown bold (**text** or __text__) to Slack bold (*text*)
converted = converted.replace(/\*\*(.+?)\*\*/g, '*$1*');
converted = converted.replace(/__(.+?)__/g, '*$1*');

// Convert markdown links [text](url) to Slack links <url|text>
converted = converted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<$2|$1>');

// Convert markdown code blocks ``` to plain text (Slack handles these differently)
converted = converted.replace(/```[\s\S]*?```/g, (match) => {
return match.replace(/```/g, '');
});

// Convert inline code `text` to Slack inline code (same format)
// No change needed, Slack uses the same format

return converted;
}

/**
* Formats a message specifically for Slack
*/
function formatSlackMessage(issue, assessments) {
let message = `*OKD Issue #${issue.number}*: ${issue.title}\n`;
message += `<${issue.html_url}|View Issue>\n\n`;

if (!assessments || assessments.length === 0) {
message += '_No triage assessment available_';
} else {
for (const assessment of assessments) {
const label = assessment.assessmentLabel || 'N/A';
const response = assessment.response || 'No response';

// Convert markdown in the response to Slack format
const slackFormattedResponse = convertMarkdownToSlack(response);

message += `*Label:* ${label}\n`;
message += `*Assessment:*\n${slackFormattedResponse}\n`;
}
}

return message.trim();
}

module.exports = { formatSlackMessage, convertMarkdownToSlack };
23 changes: 23 additions & 0 deletions .github/scripts/get-issues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Issue-related utilities: getting issue details
*/

/**
* Gets issue details and sets outputs
*/
async function getIssueDetails(github, context, core, issueNumber, owner, repo) {
const { data: issue } = await github.rest.issues.get({
owner: owner,
repo: repo,
issue_number: issueNumber,
});

core.setOutput('issue_number', issue.number);
core.setOutput('issue_title', issue.title);
core.setOutput('issue_body', issue.body || '');
core.setOutput('issue_url', issue.html_url);
core.setOutput('repo_name', repo);
}

module.exports = { getIssueDetails };

22 changes: 22 additions & 0 deletions .github/scripts/process-assessments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Processes assessment output and formats messages for GitHub summary
*/

const { formatMessage, parseAssessments } = require('./format-message');

async function processAssessments(assessmentOutput, issueNumber, issueTitle, issueUrl, core) {
const assessments = parseAssessments(assessmentOutput, core);

const issue = {
number: parseInt(issueNumber),
title: issueTitle,
html_url: issueUrl
};

const message = formatMessage(issue, assessments);
core.summary.addRaw(message);
await core.summary.write();
}

module.exports = { processAssessments };

49 changes: 49 additions & 0 deletions .github/scripts/send-slack-message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Sends a Slack-formatted message to Slack
*/

const { parseAssessments } = require('./format-message');
const { formatSlackMessage } = require('./format-slack-message');

async function sendSlackMessage(issueNumber, issueTitle, issueUrl, assessmentsJson, webhookUrl, core) {
if (!webhookUrl) {
core.warning('No Slack webhook URL provided, skipping notification');
return;
}

const assessments = typeof assessmentsJson === 'string'
? parseAssessments(assessmentsJson, core)
: assessmentsJson;

const issue = {
number: parseInt(issueNumber),
title: issueTitle,
html_url: issueUrl
};

const message = formatSlackMessage(issue, assessments);

const payload = {
text: message
};

try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});

if (!response.ok) {
throw new Error(`Slack API returned ${response.status}: ${response.statusText}`);
}

core.info('Slack notification sent successfully');
} catch (err) {
core.warning(`Slack notification failed: ${err.message}`);
throw err;
}
}

module.exports = { sendSlackMessage };

97 changes: 97 additions & 0 deletions .github/workflows/issue-opened.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: "Triage New Issue"
run-name: "AI Triage for Issue #${{ github.event.issue.number }}"

on:
issues:
types: [opened, edited]

concurrency:
group: ai-triage-issue-${{ github.event.issue.number }}
cancel-in-progress: false

permissions:
issues: write
contents: read
actions: read
models: read

jobs:
triage-issues:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Get Issue Details
id: get-issue
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { getIssueDetails } = require('./.github/scripts/get-issues.js');
await getIssueDetails(github, context, core, ${{ github.event.issue.number }}, context.repo.owner, context.repo.repo);

- name: AI Issue Assessment
id: ai-assessment
uses: github/[email protected]
with:
token: ${{ secrets.GITHUB_TOKEN }}
issue_number: ${{ steps.get-issue.outputs.issue_number }}
issue_body: ${{ steps.get-issue.outputs.issue_body }}
repo_name: ${{ steps.get-issue.outputs.repo_name }}
owner: ${{ github.repository_owner }}
ai_review_label: 'kind/bug'
prompts_directory: './.github/prompts'
labels_to_prompts_mapping: 'kind/bug,bug-triage.prompt.yml'
max_tokens: 300

- name: Add kind/bug label
if: always()
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { addLabel } = require('./.github/scripts/add-label.js');
await addLabel(github, context, core, ${{ steps.get-issue.outputs.issue_number }});

- name: Process Assessments
id: process-assessments
if: always()
uses: actions/github-script@v7
env:
ASSESSMENT_OUTPUT: ${{ steps.ai-assessment.outputs.ai_assessments }}
ISSUE_NUMBER: ${{ steps.get-issue.outputs.issue_number }}
ISSUE_TITLE: ${{ steps.get-issue.outputs.issue_title }}
ISSUE_URL: ${{ steps.get-issue.outputs.issue_url }}
with:
script: |
const { processAssessments } = require('./.github/scripts/process-assessments.js');
await processAssessments(
process.env.ASSESSMENT_OUTPUT,
process.env.ISSUE_NUMBER,
process.env.ISSUE_TITLE,
process.env.ISSUE_URL,
core
);

- name: Send to Slack
if: always()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
ASSESSMENT_OUTPUT: ${{ steps.ai-assessment.outputs.ai_assessments }}
ISSUE_NUMBER: ${{ steps.get-issue.outputs.issue_number }}
ISSUE_TITLE: ${{ steps.get-issue.outputs.issue_title }}
ISSUE_URL: ${{ steps.get-issue.outputs.issue_url }}
uses: actions/github-script@v7
continue-on-error: true
with:
script: |
const { sendSlackMessage } = require('./.github/scripts/send-slack-message.js');
await sendSlackMessage(
process.env.ISSUE_NUMBER,
process.env.ISSUE_TITLE,
process.env.ISSUE_URL,
process.env.ASSESSMENT_OUTPUT,
process.env.SLACK_WEBHOOK_URL,
core
);