Skip to content

Commit 290e24c

Browse files
committed
feat: add automated bug triage utility
1 parent 791deb4 commit 290e24c

1 file changed

Lines changed: 134 additions & 0 deletions

File tree

bug_triage.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""
2+
Automated Bug Triage Tool
3+
This script scans markdown bug reports, classifies them by severity based on
4+
keywords, and generates a summarized triage report in Markdown format.
5+
"""
6+
7+
import glob
8+
import os
9+
from datetime import datetime
10+
from typing import Dict, List
11+
from pathlib import Path
12+
13+
# Configuration: Adjust these paths based on your local environment
14+
BASE_DIR = Path(__file__).parent
15+
BUG_PATH = str(BASE_DIR / "production" / "qa" / "bugs" / "*.md")
16+
OUTPUT_PATH = str(BASE_DIR / "production" / "qa")
17+
18+
def classify_severity(content: str) -> str:
19+
"""
20+
Classifies bug severity based on specific keywords found in the content.
21+
Returns S1 (Critical) through S4 (Minor).
22+
"""
23+
content = content.lower()
24+
25+
if any(k in content for k in ["crash", "data loss", "cannot start", "fatal"]):
26+
return "S1"
27+
if any(k in content for k in ["broken", "not working", "fail"]):
28+
return "S2"
29+
if any(k in content for k in ["slow", "incorrect", "glitch"]):
30+
return "S3"
31+
return "S4"
32+
33+
34+
def classify_priority(severity: str) -> str:
35+
"""
36+
Maps the technical severity level to a business priority level.
37+
"""
38+
priority_map = {
39+
"S1": "P1",
40+
"S2": "P2",
41+
"S3": "P3"
42+
}
43+
return priority_map.get(severity, "P4")
44+
45+
46+
def read_bugs() -> List[Dict]:
47+
"""
48+
Reads all markdown files in the BUG_PATH and extracts metadata.
49+
"""
50+
files = glob.glob(BUG_PATH)
51+
bugs = []
52+
53+
# Sorting files ensures consistent BUG-ID assignment across runs
54+
for i, file_path in enumerate(sorted(files)):
55+
try:
56+
with open(file_path, "r", encoding="utf-8") as f:
57+
content = f.read()
58+
59+
severity = classify_severity(content)
60+
priority = classify_priority(severity)
61+
62+
bugs.append({
63+
"id": f"BUG-{i+1:03}",
64+
"file": file_path,
65+
"severity": severity,
66+
"priority": priority,
67+
# Extract first line as summary, capped at 80 chars
68+
"summary": content.strip().split("\n")[0][:80]
69+
})
70+
except IOError as e:
71+
print(f"⚠️ Could not read file {file_path}: {e}")
72+
73+
return bugs
74+
75+
76+
def generate_report(bugs: List[Dict]) -> None:
77+
"""
78+
Groups bugs by priority and writes a summarized Markdown report.
79+
"""
80+
date = datetime.now().strftime("%Y-%m-%d")
81+
82+
# Ensure the output directory exists
83+
if not os.path.exists(OUTPUT_PATH):
84+
os.makedirs(OUTPUT_PATH)
85+
86+
output_file = os.path.join(OUTPUT_PATH, f"bug-triage-{date}.md")
87+
88+
# Filter bugs into priority buckets
89+
p1 = [b for b in bugs if b["priority"] == "P1"]
90+
p2 = [b for b in bugs if b["priority"] == "P2"]
91+
p3 = [b for b in bugs if b["priority"] == "P3"]
92+
p4 = [b for b in bugs if b["priority"] == "P4"]
93+
94+
report_content = [
95+
"# Bug Triage Report",
96+
f"**Date**: {date} ",
97+
f"**Open bugs processed**: {len(bugs)}",
98+
"\n---\n",
99+
"## Triage Summary\n",
100+
"| Priority | Count |",
101+
"|----------|-------|",
102+
f"| P1 | {len(p1)} |",
103+
f"| P2 | {len(p2)} |",
104+
f"| P3 | {len(p3)} |",
105+
f"| P4 | {len(p4)} |",
106+
"\n---\n",
107+
"## P1 Bugs (Critical)"
108+
]
109+
110+
for b in p1:
111+
report_content.append(f"- {b['id']} | {b['severity']} | {b['summary']}")
112+
113+
report_content.append("\n## P2 Bugs (High)")
114+
for b in p2:
115+
report_content.append(f"- {b['id']} | {b['severity']} | {b['summary']}")
116+
117+
report_content.append("\n## Backlog (P3/P4)")
118+
for b in p3 + p4:
119+
report_content.append(f"- {b['id']} | {b['severity']} | {b['summary']}")
120+
121+
with open(output_file, "w", encoding="utf-8") as f:
122+
f.write("\n".join(report_content))
123+
124+
print(f"✅ Report successfully generated at: {output_file}")
125+
126+
127+
if __name__ == "__main__":
128+
extracted_bugs = read_bugs()
129+
130+
if not extracted_bugs:
131+
print(f"❌ No bug files found in: {BUG_PATH}")
132+
print("Tip: Ensure the directory exists and contains .md files.")
133+
else:
134+
generate_report(extracted_bugs)

0 commit comments

Comments
 (0)