claude-self-reflect 3.3.1 โ 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/claude-self-reflect-test.md +107 -8
- package/.claude/agents/csr-validator.md +151 -0
- package/.claude/agents/open-source-maintainer.md +46 -7
- package/.claude/agents/quality-fixer.md +314 -0
- package/.claude/agents/reflection-specialist.md +40 -1
- package/mcp-server/run-mcp.sh +20 -6
- package/mcp-server/src/code_reload_tool.py +271 -0
- package/mcp-server/src/embedding_manager.py +60 -26
- package/mcp-server/src/enhanced_tool_registry.py +407 -0
- package/mcp-server/src/mode_switch_tool.py +181 -0
- package/mcp-server/src/parallel_search.py +14 -4
- package/mcp-server/src/project_resolver.py +20 -2
- package/mcp-server/src/reflection_tools.py +50 -8
- package/mcp-server/src/rich_formatting.py +103 -0
- package/mcp-server/src/search_tools.py +98 -39
- package/mcp-server/src/security_patches.py +555 -0
- package/mcp-server/src/server.py +318 -240
- package/mcp-server/src/status.py +13 -8
- package/mcp-server/src/status_unified.py +286 -0
- package/mcp-server/src/test_quality.py +153 -0
- package/package.json +1 -1
- package/scripts/ast_grep_final_analyzer.py +5 -2
- package/scripts/ast_grep_unified_registry.py +170 -16
- package/scripts/csr-status +190 -45
- package/scripts/import-conversations-unified.py +106 -104
- package/scripts/session_quality_tracker.py +221 -41
- package/scripts/streaming-watcher.py +113 -158
|
@@ -59,6 +59,9 @@ class UnifiedASTGrepRegistry:
|
|
|
59
59
|
# JavaScript patterns (shared with TS)
|
|
60
60
|
patterns.update(self._load_javascript_patterns())
|
|
61
61
|
|
|
62
|
+
# Shell script patterns
|
|
63
|
+
patterns.update(self._load_shell_patterns())
|
|
64
|
+
|
|
62
65
|
return patterns
|
|
63
66
|
|
|
64
67
|
def _load_python_patterns(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
@@ -224,6 +227,41 @@ class UnifiedASTGrepRegistry:
|
|
|
224
227
|
"quality": "bad",
|
|
225
228
|
"weight": -4,
|
|
226
229
|
"language": "python"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "sync-voyage-embed",
|
|
233
|
+
"pattern": "$CLIENT.embed($$$)",
|
|
234
|
+
"description": "Blocking Voyage embed in async context",
|
|
235
|
+
"quality": "bad",
|
|
236
|
+
"weight": -5,
|
|
237
|
+
"language": "python",
|
|
238
|
+
"inside": "async def $FUNC($$$): $$$"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"id": "thread-join-async",
|
|
242
|
+
"pattern": "$THREAD.join($$$)",
|
|
243
|
+
"description": "Thread join blocking async context",
|
|
244
|
+
"quality": "bad",
|
|
245
|
+
"weight": -5,
|
|
246
|
+
"language": "python",
|
|
247
|
+
"inside": "async def $FUNC($$$): $$$"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"id": "invalid-env-var-hyphen",
|
|
251
|
+
"pattern": "os.getenv('$VAR')",
|
|
252
|
+
"description": "Environment variable with hyphen (invalid in shells)",
|
|
253
|
+
"quality": "bad",
|
|
254
|
+
"weight": -3,
|
|
255
|
+
"language": "python",
|
|
256
|
+
"constraint": "$VAR matches .*-.*"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"id": "dotenv-override-runtime",
|
|
260
|
+
"pattern": "load_dotenv($$$, override=True)",
|
|
261
|
+
"description": "Runtime environment mutation in MCP",
|
|
262
|
+
"quality": "bad",
|
|
263
|
+
"weight": -3,
|
|
264
|
+
"language": "python"
|
|
227
265
|
}
|
|
228
266
|
],
|
|
229
267
|
"python_qdrant": [
|
|
@@ -268,6 +306,50 @@ class UnifiedASTGrepRegistry:
|
|
|
268
306
|
"quality": "good",
|
|
269
307
|
"weight": 5,
|
|
270
308
|
"language": "python"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"id": "missing-embedding-guard",
|
|
312
|
+
"pattern": "query_embedding = await $MGR.generate_embedding($$$)\n$$$\nawait $CLIENT.search($$$, query_vector=query_embedding, $$$)",
|
|
313
|
+
"description": "Missing None check after embedding generation",
|
|
314
|
+
"quality": "bad",
|
|
315
|
+
"weight": -4,
|
|
316
|
+
"language": "python"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"id": "attr-vs-api",
|
|
320
|
+
"pattern": "$MGR.model_name",
|
|
321
|
+
"description": "Accessing non-existent attribute instead of API",
|
|
322
|
+
"quality": "bad",
|
|
323
|
+
"weight": -3,
|
|
324
|
+
"language": "python",
|
|
325
|
+
"note": "Use get_model_info() instead"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"id": "duplicate-import",
|
|
329
|
+
"pattern": "import $MODULE\n$$$\ndef $FUNC($$$):\n $$$\n import $MODULE",
|
|
330
|
+
"description": "Duplicate import inside function",
|
|
331
|
+
"quality": "bad",
|
|
332
|
+
"weight": -2,
|
|
333
|
+
"language": "python"
|
|
334
|
+
}
|
|
335
|
+
],
|
|
336
|
+
"python_runtime_modification": [
|
|
337
|
+
{
|
|
338
|
+
"id": "singleton-state-change",
|
|
339
|
+
"pattern": "$SINGLETON.$ATTR = $VALUE",
|
|
340
|
+
"description": "Runtime singleton state modification",
|
|
341
|
+
"quality": "neutral",
|
|
342
|
+
"weight": 0,
|
|
343
|
+
"language": "python",
|
|
344
|
+
"note": "Can be good for mode switching, bad if uncontrolled"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"id": "public-init-exposure",
|
|
348
|
+
"pattern": "def try_initialize_$TYPE(self): $$$",
|
|
349
|
+
"description": "Public initialization method for runtime config",
|
|
350
|
+
"quality": "neutral",
|
|
351
|
+
"weight": 0,
|
|
352
|
+
"language": "python"
|
|
271
353
|
}
|
|
272
354
|
]
|
|
273
355
|
}
|
|
@@ -386,6 +468,48 @@ class UnifiedASTGrepRegistry:
|
|
|
386
468
|
]
|
|
387
469
|
}
|
|
388
470
|
|
|
471
|
+
def _load_shell_patterns(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
472
|
+
"""Shell script patterns."""
|
|
473
|
+
return {
|
|
474
|
+
"shell_env_handling": [
|
|
475
|
+
{
|
|
476
|
+
"id": "unused-shell-var",
|
|
477
|
+
"pattern": "$VAR=\"$VALUE\"",
|
|
478
|
+
"description": "Assigned but never referenced variable",
|
|
479
|
+
"quality": "bad",
|
|
480
|
+
"weight": -2,
|
|
481
|
+
"language": "bash",
|
|
482
|
+
"note": "Check if variable is used later"
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"id": "unsafe-var-check",
|
|
486
|
+
"pattern": "[ ! -z \"$VAR\" ]",
|
|
487
|
+
"description": "Unsafe variable check (breaks with set -u)",
|
|
488
|
+
"quality": "bad",
|
|
489
|
+
"weight": -3,
|
|
490
|
+
"language": "bash",
|
|
491
|
+
"fix": "[ -n \"${VAR:-}\" ]"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"id": "redundant-export",
|
|
495
|
+
"pattern": "export $VAR=\"$VAR\"",
|
|
496
|
+
"description": "Redundant export of same value",
|
|
497
|
+
"quality": "bad",
|
|
498
|
+
"weight": -2,
|
|
499
|
+
"language": "bash"
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
"id": "missing-safety-flags",
|
|
503
|
+
"pattern": "#!/bin/bash",
|
|
504
|
+
"description": "Missing safety flags",
|
|
505
|
+
"quality": "bad",
|
|
506
|
+
"weight": -3,
|
|
507
|
+
"language": "bash",
|
|
508
|
+
"note": "Add 'set -euo pipefail' after shebang"
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
}
|
|
512
|
+
|
|
389
513
|
def _load_javascript_patterns(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
390
514
|
"""JavaScript patterns (subset of TypeScript)."""
|
|
391
515
|
return {
|
|
@@ -466,26 +590,56 @@ class UnifiedASTGrepRegistry:
|
|
|
466
590
|
"""Get only bad quality patterns (anti-patterns)."""
|
|
467
591
|
return [p for p in self.get_all_patterns() if p.get('quality') == 'bad']
|
|
468
592
|
|
|
469
|
-
def calculate_quality_score(self, matches: List[Dict]) -> float:
|
|
593
|
+
def calculate_quality_score(self, matches: List[Dict], loc: int = 1000) -> float:
|
|
470
594
|
"""
|
|
471
|
-
Calculate quality score based
|
|
472
|
-
|
|
595
|
+
Calculate quality score using penalty-based approach.
|
|
596
|
+
Issues dominate the score; good patterns provide minimal bonus.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
matches: List of pattern matches with weight and count
|
|
600
|
+
loc: Lines of code (for normalization)
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
Score from 0.0 to 1.0
|
|
473
604
|
"""
|
|
474
|
-
|
|
475
|
-
|
|
605
|
+
import math
|
|
606
|
+
|
|
607
|
+
# Normalize to KLOC (thousands of lines)
|
|
608
|
+
kloc = max(1.0, loc / 1000.0)
|
|
609
|
+
|
|
610
|
+
# Separate issues (bad) from good patterns
|
|
611
|
+
issues = [m for m in matches if m.get('quality') == 'bad']
|
|
612
|
+
good_patterns = [m for m in matches if m.get('quality') == 'good']
|
|
613
|
+
|
|
614
|
+
# Calculate severity-weighted issue density
|
|
615
|
+
total_issues = 0
|
|
616
|
+
for issue in issues:
|
|
617
|
+
severity = abs(issue.get('weight', 1)) # Use weight as severity
|
|
618
|
+
count = issue.get('count', 0)
|
|
619
|
+
total_issues += severity * count
|
|
620
|
+
|
|
621
|
+
issues_per_kloc = total_issues / kloc
|
|
622
|
+
|
|
623
|
+
# Penalty calculation (logarithmic to avoid linear dominance)
|
|
624
|
+
# Calibrated so 50 issues/KLOC = ~50% penalty
|
|
625
|
+
penalty = min(0.7, 0.15 * math.log1p(issues_per_kloc))
|
|
626
|
+
|
|
627
|
+
# Small bonus for good patterns (capped at 5%)
|
|
628
|
+
good_score = 0
|
|
629
|
+
if good_patterns:
|
|
630
|
+
for pattern in good_patterns:
|
|
631
|
+
weight = pattern.get('weight', 1)
|
|
632
|
+
count = pattern.get('count', 0)
|
|
633
|
+
# Cap contribution per pattern type
|
|
634
|
+
normalized_count = min(count / kloc, 50) # Max 50 per KLOC
|
|
635
|
+
good_score += weight * normalized_count / 1000
|
|
476
636
|
|
|
477
|
-
|
|
478
|
-
weight = match.get('weight', 0)
|
|
479
|
-
count = match.get('count', 0)
|
|
480
|
-
total_weight += weight * count
|
|
481
|
-
total_count += abs(weight) * count
|
|
637
|
+
bonus = min(0.05, good_score) # Cap at 5% bonus
|
|
482
638
|
|
|
483
|
-
|
|
484
|
-
|
|
639
|
+
# Final score: start at 100%, subtract penalty, add small bonus
|
|
640
|
+
score = max(0.0, min(1.0, 1.0 - penalty + bonus))
|
|
485
641
|
|
|
486
|
-
|
|
487
|
-
normalized = (total_weight + 100) / 200
|
|
488
|
-
return max(0.0, min(1.0, normalized))
|
|
642
|
+
return score
|
|
489
643
|
|
|
490
644
|
def export_to_json(self, path: str):
|
|
491
645
|
"""Export registry to JSON file."""
|
|
@@ -545,7 +699,7 @@ if __name__ == "__main__":
|
|
|
545
699
|
print(f" - {category}: {count} patterns")
|
|
546
700
|
|
|
547
701
|
# Export to JSON
|
|
548
|
-
export_path = "
|
|
702
|
+
export_path = Path(__file__).parent / "unified_registry.json"
|
|
549
703
|
registry.export_to_json(export_path)
|
|
550
704
|
print(f"\nโ
Exported unified registry to {export_path}")
|
|
551
705
|
|
package/scripts/csr-status
CHANGED
|
@@ -55,9 +55,120 @@ def get_import_status():
|
|
|
55
55
|
return "๐ CSR: Error"
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def categorize_issues(file_reports):
|
|
59
|
+
"""
|
|
60
|
+
Categorize issues from AST analysis into critical/medium/low.
|
|
61
|
+
"""
|
|
62
|
+
critical = 0
|
|
63
|
+
medium = 0
|
|
64
|
+
low = 0
|
|
65
|
+
|
|
66
|
+
for file_path, report in file_reports.items():
|
|
67
|
+
# Only use top_issues for accurate counting (avoid double-counting from recommendations)
|
|
68
|
+
for issue in report.get('top_issues', []):
|
|
69
|
+
severity = issue.get('severity', 'medium')
|
|
70
|
+
count = issue.get('count', 0)
|
|
71
|
+
issue_id = issue.get('id', '').lower()
|
|
72
|
+
|
|
73
|
+
if severity == 'high' or severity == 'critical':
|
|
74
|
+
critical += count
|
|
75
|
+
elif severity == 'medium':
|
|
76
|
+
# Console.log and print statements are low severity
|
|
77
|
+
if 'print' in issue_id or 'console' in issue_id:
|
|
78
|
+
low += count
|
|
79
|
+
else:
|
|
80
|
+
medium += count
|
|
81
|
+
else:
|
|
82
|
+
low += count
|
|
83
|
+
|
|
84
|
+
return critical, medium, low
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_quality_icon(critical=0, medium=0, low=0):
|
|
88
|
+
"""
|
|
89
|
+
Determine quality icon based on issue severity counts.
|
|
90
|
+
"""
|
|
91
|
+
# Icon selection based on highest severity present
|
|
92
|
+
if critical > 0:
|
|
93
|
+
if critical >= 10:
|
|
94
|
+
return "๐ด" # Red circle - Critical issues need immediate attention
|
|
95
|
+
else:
|
|
96
|
+
return "๐ " # Orange circle - Some critical issues
|
|
97
|
+
elif medium > 0:
|
|
98
|
+
if medium >= 50:
|
|
99
|
+
return "๐ก" # Yellow circle - Many medium issues
|
|
100
|
+
else:
|
|
101
|
+
return "๐ข" # Green circle - Few medium issues
|
|
102
|
+
elif low > 0:
|
|
103
|
+
if low >= 100:
|
|
104
|
+
return "โช" # White circle - Many minor issues (prints)
|
|
105
|
+
else:
|
|
106
|
+
return "โ
" # Check mark - Only minor issues
|
|
107
|
+
else:
|
|
108
|
+
return "โจ" # Sparkles - Perfect, no issues
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def format_statusline_quality(critical=0, medium=0, low=0):
|
|
112
|
+
"""
|
|
113
|
+
Format statusline with colored dot and labeled numbers.
|
|
114
|
+
"""
|
|
115
|
+
import os
|
|
116
|
+
icon = get_quality_icon(critical, medium, low)
|
|
117
|
+
|
|
118
|
+
# Check if we should use colors (when in a TTY)
|
|
119
|
+
use_colors = os.isatty(sys.stdout.fileno()) if hasattr(sys.stdout, 'fileno') else False
|
|
120
|
+
|
|
121
|
+
# Build count display with colors if supported
|
|
122
|
+
counts = []
|
|
123
|
+
if critical > 0:
|
|
124
|
+
if use_colors:
|
|
125
|
+
# Use bright red for critical
|
|
126
|
+
counts.append(f"\033[1;31mC:{critical}\033[0m")
|
|
127
|
+
else:
|
|
128
|
+
counts.append(f"C:{critical}")
|
|
129
|
+
if medium > 0:
|
|
130
|
+
if use_colors:
|
|
131
|
+
# Use bright yellow for medium
|
|
132
|
+
counts.append(f"\033[1;33mM:{medium}\033[0m")
|
|
133
|
+
else:
|
|
134
|
+
counts.append(f"M:{medium}")
|
|
135
|
+
if low > 0:
|
|
136
|
+
if use_colors:
|
|
137
|
+
# Use bright white/gray for low
|
|
138
|
+
counts.append(f"\033[1;37mL:{low}\033[0m")
|
|
139
|
+
else:
|
|
140
|
+
counts.append(f"L:{low}")
|
|
141
|
+
|
|
142
|
+
if counts:
|
|
143
|
+
return f"{icon} {' '.join(counts)}"
|
|
144
|
+
else:
|
|
145
|
+
return f"{icon}" # Perfect - no counts needed
|
|
146
|
+
|
|
147
|
+
|
|
58
148
|
def get_session_health():
|
|
59
|
-
"""Get cached session health."""
|
|
60
|
-
|
|
149
|
+
"""Get cached session health with icon-based quality display."""
|
|
150
|
+
# Check for session edit tracker to show appropriate label
|
|
151
|
+
tracker_file = Path.home() / ".claude-self-reflect" / "current_session_edits.json"
|
|
152
|
+
|
|
153
|
+
# Get quality cache file for current project
|
|
154
|
+
project_name = Path.cwd().name
|
|
155
|
+
cache_file = Path.home() / ".claude-self-reflect" / "quality_cache" / f"{project_name}.json"
|
|
156
|
+
|
|
157
|
+
# Default label prefix
|
|
158
|
+
label_prefix = ""
|
|
159
|
+
|
|
160
|
+
# Check if we have a session tracker with edited files
|
|
161
|
+
if tracker_file.exists():
|
|
162
|
+
try:
|
|
163
|
+
with open(tracker_file, 'r') as f:
|
|
164
|
+
tracker_data = json.load(f)
|
|
165
|
+
edited_files = tracker_data.get('edited_files', [])
|
|
166
|
+
if edited_files:
|
|
167
|
+
# Show session label with file count
|
|
168
|
+
file_count = len(edited_files)
|
|
169
|
+
label_prefix = f"Session ({file_count} file{'s' if file_count > 1 else ''}): "
|
|
170
|
+
except:
|
|
171
|
+
pass
|
|
61
172
|
|
|
62
173
|
if not cache_file.exists():
|
|
63
174
|
# Fall back to import status if no health data
|
|
@@ -79,22 +190,26 @@ def get_session_health():
|
|
|
79
190
|
# Fall back to import status if no session
|
|
80
191
|
return get_import_status()
|
|
81
192
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
193
|
+
# Extract issue counts by severity
|
|
194
|
+
file_reports = data.get('file_reports', {})
|
|
195
|
+
critical, medium, low = categorize_issues(file_reports)
|
|
85
196
|
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
emoji = '๐ข'
|
|
89
|
-
elif grade in ['B', 'C']:
|
|
90
|
-
emoji = '๐ก'
|
|
91
|
-
else:
|
|
92
|
-
emoji = '๐ด'
|
|
197
|
+
# Use the icon-based display with optional label
|
|
198
|
+
quality_display = format_statusline_quality(critical, medium, low)
|
|
93
199
|
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
200
|
+
# Add session label if we have one
|
|
201
|
+
if data.get('scope_label') == 'Session':
|
|
202
|
+
# For session scope, always show the label with counts
|
|
203
|
+
if label_prefix:
|
|
204
|
+
if critical == 0 and medium == 0 and low == 0:
|
|
205
|
+
return f"{label_prefix}0 0 0 {quality_display}"
|
|
206
|
+
else:
|
|
207
|
+
return f"{label_prefix}{critical} {medium} {low} {quality_display}"
|
|
208
|
+
else:
|
|
209
|
+
# Fallback if no tracker file
|
|
210
|
+
return f"Session: {critical} {medium} {low} {quality_display}"
|
|
211
|
+
|
|
212
|
+
return quality_display
|
|
98
213
|
|
|
99
214
|
except Exception:
|
|
100
215
|
return get_import_status()
|
|
@@ -257,6 +372,14 @@ def get_compact_status():
|
|
|
257
372
|
cache_dir = Path.home() / ".claude-self-reflect" / "quality_cache"
|
|
258
373
|
cache_file = cache_dir / f"{safe_project_name}.json"
|
|
259
374
|
|
|
375
|
+
# If the exact cache file doesn't exist, try to find one that ends with this project name
|
|
376
|
+
# This handles cases like "metafora-Atlas-gold.json" for project "Atlas-gold"
|
|
377
|
+
if not cache_file.exists():
|
|
378
|
+
# Look for files ending with the project name
|
|
379
|
+
possible_files = list(cache_dir.glob(f"*-{safe_project_name}.json"))
|
|
380
|
+
if possible_files:
|
|
381
|
+
cache_file = possible_files[0] # Use the first match
|
|
382
|
+
|
|
260
383
|
# Validate cache file path stays within cache directory
|
|
261
384
|
if cache_file.exists() and not str(cache_file.resolve()).startswith(str(cache_dir.resolve())):
|
|
262
385
|
# Security issue - return placeholder
|
|
@@ -273,30 +396,38 @@ def get_compact_status():
|
|
|
273
396
|
mtime = datetime.fromtimestamp(cache_file.stat().st_mtime)
|
|
274
397
|
age = datetime.now() - mtime
|
|
275
398
|
|
|
276
|
-
# Use quality data up to
|
|
277
|
-
if age < timedelta(
|
|
399
|
+
# Use quality data up to 30 minutes old for fresher results
|
|
400
|
+
if age < timedelta(minutes=30):
|
|
278
401
|
with open(cache_file, 'r') as f:
|
|
279
402
|
data = json.load(f)
|
|
280
403
|
|
|
281
|
-
if data.get('status') == '
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
#
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
404
|
+
if data.get('status') == 'non-code':
|
|
405
|
+
# Non-code project - show documentation indicator
|
|
406
|
+
grade_str = "[๐:Docs]"
|
|
407
|
+
quality_valid = True
|
|
408
|
+
elif data.get('status') == 'success':
|
|
409
|
+
# Extract issue counts by severity for icon display
|
|
410
|
+
file_reports = data.get('file_reports', {})
|
|
411
|
+
critical, medium, low = categorize_issues(file_reports)
|
|
412
|
+
|
|
413
|
+
# Get icon based on severity
|
|
414
|
+
icon = get_quality_icon(critical, medium, low)
|
|
415
|
+
|
|
416
|
+
# Build compact display with ANSI colors for each severity level
|
|
417
|
+
colored_parts = []
|
|
418
|
+
if critical > 0:
|
|
419
|
+
colored_parts.append(f"\033[31m{critical}\033[0m") # Standard red for critical
|
|
420
|
+
if medium > 0:
|
|
421
|
+
colored_parts.append(f"\033[33m{medium}\033[0m") # Standard yellow for medium
|
|
422
|
+
if low > 0:
|
|
423
|
+
colored_parts.append(f"\033[37m{low}\033[0m") # White/light gray for low
|
|
424
|
+
|
|
425
|
+
# Join with middle dot separator
|
|
426
|
+
if colored_parts:
|
|
427
|
+
grade_str = f"[{icon}:{'ยท'.join(colored_parts)}]"
|
|
295
428
|
else:
|
|
296
|
-
|
|
429
|
+
grade_str = f"[{icon}]"
|
|
297
430
|
|
|
298
|
-
# Simple, clear display without confusing scope labels
|
|
299
|
-
grade_str = f"[{emoji}:{grade}/{issues}]"
|
|
300
431
|
quality_valid = True
|
|
301
432
|
except:
|
|
302
433
|
pass
|
|
@@ -308,17 +439,31 @@ def get_compact_status():
|
|
|
308
439
|
if cache_file.exists():
|
|
309
440
|
with open(cache_file, 'r') as f:
|
|
310
441
|
old_data = json.load(f)
|
|
311
|
-
if old_data.get('status') == '
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
442
|
+
if old_data.get('status') == 'non-code':
|
|
443
|
+
# Non-code project - show documentation indicator
|
|
444
|
+
grade_str = "[๐:Docs]"
|
|
445
|
+
elif old_data.get('status') == 'success':
|
|
446
|
+
# Extract issue counts by severity for icon display
|
|
447
|
+
file_reports = old_data.get('file_reports', {})
|
|
448
|
+
critical, medium, low = categorize_issues(file_reports)
|
|
449
|
+
|
|
450
|
+
# Get icon based on severity
|
|
451
|
+
icon = get_quality_icon(critical, medium, low)
|
|
452
|
+
|
|
453
|
+
# Build compact display with ANSI colors for each severity level
|
|
454
|
+
colored_parts = []
|
|
455
|
+
if critical > 0:
|
|
456
|
+
colored_parts.append(f"\033[31m{critical}\033[0m") # Standard red for critical
|
|
457
|
+
if medium > 0:
|
|
458
|
+
colored_parts.append(f"\033[33m{medium}\033[0m") # Standard yellow for medium
|
|
459
|
+
if low > 0:
|
|
460
|
+
colored_parts.append(f"\033[37m{low}\033[0m") # White/light gray for low
|
|
461
|
+
|
|
462
|
+
# Join with middle dot separator
|
|
463
|
+
if colored_parts:
|
|
464
|
+
grade_str = f"[{icon}:{'ยท'.join(colored_parts)}]"
|
|
319
465
|
else:
|
|
320
|
-
|
|
321
|
-
grade_str = f"[{emoji}:{old_grade}/{old_issues}]"
|
|
466
|
+
grade_str = f"[{icon}]"
|
|
322
467
|
else:
|
|
323
468
|
grade_str = "[...]"
|
|
324
469
|
else:
|