-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherror-analysis.py
More file actions
285 lines (246 loc) · 9.07 KB
/
error-analysis.py
File metadata and controls
285 lines (246 loc) · 9.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#!/usr/bin/env python3
"""error-analysis.py — On-demand error pattern analysis from session-knowledge DB.
Usage:
python3 error-analysis.py # Full analysis summary
python3 error-analysis.py --type syntax # Filter by error type
python3 error-analysis.py --recurring # Show only recurring mistakes
python3 error-analysis.py --top 10 # Top N errors by recurrence
python3 error-analysis.py --export json # JSON output
python3 error-analysis.py --root-causes # Show root cause breakdown
When invoked from CLI, produces a structured error analysis report.
Can be delegated to Copilot CLI for deeper investigation.
"""
import json
import os
import sqlite3
import sys
from pathlib import Path
if os.name == "nt":
sys.stdout.reconfigure(encoding="utf-8")
DB_PATH = Path(os.environ.get("SK_DB", Path.home() / ".copilot" / "session-state" / "knowledge.db"))
def get_db():
if not DB_PATH.exists():
print(f"ERROR: Database not found at {DB_PATH}", file=sys.stderr)
sys.exit(1)
db = sqlite3.connect(str(DB_PATH), timeout=10)
db.row_factory = sqlite3.Row
return db
def col_exists(db, table: str, col: str) -> bool:
try:
info = db.execute(f"PRAGMA table_info({table})").fetchall()
return any(r["name"] == col for r in info)
except Exception:
return False
def analyze_error_types(db, error_type_filter=None):
"""Error type distribution."""
if not col_exists(db, "knowledge_entries", "error_type"):
return []
where = " AND error_type = ?" if error_type_filter else ""
params = [error_type_filter] if error_type_filter else []
rows = db.execute(
f"""
SELECT COALESCE(error_type, 'unclassified') as error_type,
COUNT(*) as count,
COALESCE(AVG(CASE severity
WHEN 'critical' THEN 4
WHEN 'high' THEN 3
WHEN 'medium' THEN 2
WHEN 'low' THEN 1
ELSE 0 END), 0) as avg_severity
FROM knowledge_entries
WHERE category = 'mistake'{where}
GROUP BY error_type
ORDER BY count DESC
""",
params,
).fetchall()
return [dict(r) for r in rows]
def analyze_recurring(db, top_n=20):
"""Mistakes that recur after being briefed."""
if not col_exists(db, "knowledge_entries", "recurrence_after_briefing"):
return []
rows = db.execute(
"""
SELECT id, title, error_type, severity, root_cause,
COALESCE(recurrence_after_briefing, 0) as recurrence,
tags, first_seen
FROM knowledge_entries
WHERE category = 'mistake'
AND COALESCE(recurrence_after_briefing, 0) > 0
ORDER BY recurrence_after_briefing DESC
LIMIT ?
""",
(top_n,),
).fetchall()
return [dict(r) for r in rows]
def analyze_root_causes(db):
"""Root cause breakdown."""
if not col_exists(db, "knowledge_entries", "root_cause"):
return []
rows = db.execute(
"""
SELECT COALESCE(root_cause, 'unknown') as root_cause,
COUNT(*) as count,
GROUP_CONCAT(DISTINCT error_type) as error_types
FROM knowledge_entries
WHERE category = 'mistake'
AND root_cause IS NOT NULL AND root_cause != ''
GROUP BY root_cause
ORDER BY count DESC
LIMIT 20
"""
).fetchall()
return [dict(r) for r in rows]
def analyze_severity(db):
"""Severity distribution."""
if not col_exists(db, "knowledge_entries", "severity"):
return []
rows = db.execute(
"""
SELECT COALESCE(severity, 'unknown') as severity, COUNT(*) as count
FROM knowledge_entries
WHERE category = 'mistake'
GROUP BY severity
ORDER BY CASE severity
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
WHEN 'low' THEN 4
ELSE 5 END
"""
).fetchall()
return [dict(r) for r in rows]
def analyze_trends(db):
"""Weekly mistake trend."""
rows = db.execute(
"""
SELECT strftime('%Y-W%W', first_seen) as week,
COUNT(*) as count
FROM knowledge_entries
WHERE category = 'mistake' AND first_seen IS NOT NULL
GROUP BY week
ORDER BY week DESC
LIMIT 12
"""
).fetchall()
return [dict(r) for r in rows]
def full_report(db, error_type_filter=None, top_n=20):
"""Generate complete error analysis report."""
return {
"error_types": analyze_error_types(db, error_type_filter),
"severity": analyze_severity(db),
"recurring": analyze_recurring(db, top_n),
"root_causes": analyze_root_causes(db),
"trends": analyze_trends(db),
}
def print_report(report):
"""Pretty-print the analysis report."""
# Error types
print("\n═══ Error Type Distribution ═══")
for item in report.get("error_types", []):
bar = "█" * min(item["count"], 40)
print(f" {item['error_type']:20s} {item['count']:4d} {bar}")
# Severity
print("\n═══ Severity Breakdown ═══")
icons = {"critical": "🔴", "high": "🟠", "medium": "🟡", "low": "🟢", "unknown": "⚪"}
for item in report.get("severity", []):
icon = icons.get(item["severity"], "⚪")
print(f" {icon} {item['severity']:12s} {item['count']:4d}")
# Recurring mistakes
recurring = report.get("recurring", [])
if recurring:
print(f"\n═══ Top Recurring Mistakes ({len(recurring)}) ═══")
for item in recurring[:10]:
etype = item.get("error_type") or "?"
sev = item.get("severity") or "?"
print(f" [{item['recurrence']}x] {item['title'][:60]}")
print(f" type={etype} severity={sev}")
if item.get("root_cause"):
print(f" cause: {item['root_cause'][:60]}")
# Root causes
root_causes = report.get("root_causes", [])
if root_causes:
print(f"\n═══ Root Causes ({len(root_causes)}) ═══")
for item in root_causes[:10]:
print(f" [{item['count']:3d}] {item['root_cause'][:60]}")
if item.get("error_types"):
print(f" types: {item['error_types'][:60]}")
# Trends
trends = report.get("trends", [])
if trends:
print("\n═══ Weekly Trend (last 12 weeks) ═══")
for item in trends:
bar = "▓" * min(item["count"], 40)
print(f" {item['week']} {item['count']:3d} {bar}")
# Summary
total_mistakes = sum(item["count"] for item in report.get("error_types", []))
total_recurring = sum(item.get("recurrence", 0) for item in recurring)
print("\n═══ Summary ═══")
print(f" Total mistakes: {total_mistakes}")
print(f" Total recurrences: {total_recurring}")
print(f" Unique root causes: {len(root_causes)}")
print()
def main():
args = sys.argv[1:]
error_type_filter = None
top_n = 20
export_fmt = None
show_recurring_only = False
show_root_causes_only = False
i = 0
while i < len(args):
if args[i] == "--type" and i + 1 < len(args):
error_type_filter = args[i + 1]
i += 2
elif args[i] == "--top" and i + 1 < len(args):
top_n = int(args[i + 1])
i += 2
elif args[i] == "--export" and i + 1 < len(args):
export_fmt = args[i + 1]
i += 2
elif args[i] == "--recurring":
show_recurring_only = True
i += 1
elif args[i] == "--root-causes":
show_root_causes_only = True
i += 1
elif args[i] in ("--help", "-h"):
print(__doc__)
return
else:
i += 1
db = get_db()
if show_recurring_only:
recurring = analyze_recurring(db, top_n)
if export_fmt == "json":
print(json.dumps(recurring, indent=2, default=str))
elif recurring:
print(f"\n═══ Recurring Mistakes ({len(recurring)}) ═══")
for item in recurring:
print(f" [{item['recurrence']}x] {item['title'][:60]}")
if item.get("root_cause"):
print(f" cause: {item['root_cause'][:60]}")
else:
print("No recurring mistakes found.")
db.close()
return
if show_root_causes_only:
causes = analyze_root_causes(db)
if export_fmt == "json":
print(json.dumps(causes, indent=2, default=str))
elif causes:
print(f"\n═══ Root Causes ({len(causes)}) ═══")
for item in causes:
print(f" [{item['count']:3d}] {item['root_cause'][:60]}")
else:
print("No root causes found.")
db.close()
return
report = full_report(db, error_type_filter, top_n)
db.close()
if export_fmt == "json":
print(json.dumps(report, indent=2, default=str))
else:
print_report(report)
if __name__ == "__main__":
main()