@vibecodetown/mcp-server 2.1.0
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/LICENSE +21 -0
- package/README.md +269 -0
- package/build/auth/gate.js +225 -0
- package/build/auth/index.js +55 -0
- package/build/auth/public_key.js +27 -0
- package/build/auth/token_cache.js +122 -0
- package/build/auth/token_verifier.js +103 -0
- package/build/bootstrap/doctor.js +115 -0
- package/build/bootstrap/installer.js +673 -0
- package/build/bootstrap/lock.js +37 -0
- package/build/bootstrap/platform.js +26 -0
- package/build/bootstrap/registry.js +37 -0
- package/build/cache/index.js +147 -0
- package/build/cli.js +101 -0
- package/build/contracts.js +22 -0
- package/build/control_plane/gate.js +161 -0
- package/build/control_plane/index.js +6 -0
- package/build/dx/activity.js +139 -0
- package/build/engine.js +106 -0
- package/build/errors.js +171 -0
- package/build/generated/activate_input.js +2 -0
- package/build/generated/activate_output.js +57 -0
- package/build/generated/advisory_review_input.js +2 -0
- package/build/generated/advisory_review_output.js +35 -0
- package/build/generated/auth_token_file.js +2 -0
- package/build/generated/briefing_input.js +2 -0
- package/build/generated/briefing_output.js +2 -0
- package/build/generated/clinic_bridge_file.js +13 -0
- package/build/generated/contracts_bundle_info.js +5 -0
- package/build/generated/create_work_order_input.js +2 -0
- package/build/generated/create_work_order_output.js +2 -0
- package/build/generated/current_work_order_file.js +2 -0
- package/build/generated/doctor_input.js +2 -0
- package/build/generated/doctor_output.js +24 -0
- package/build/generated/execution_result.js +2 -0
- package/build/generated/execution_task.js +2 -0
- package/build/generated/export_output_input.js +2 -0
- package/build/generated/export_output_output.js +2 -0
- package/build/generated/finalize_work_input.js +2 -0
- package/build/generated/finalize_work_output.js +2 -0
- package/build/generated/gate_input.js +2 -0
- package/build/generated/gate_output.js +2 -0
- package/build/generated/gate_result_v1.js +2 -0
- package/build/generated/get_decision_input.js +2 -0
- package/build/generated/get_decision_output.js +13 -0
- package/build/generated/handoff_to_clinic.js +2 -0
- package/build/generated/index.js +75 -0
- package/build/generated/inspect_code_input.js +2 -0
- package/build/generated/inspect_code_output.js +13 -0
- package/build/generated/memory_retrieve_output.js +2 -0
- package/build/generated/memory_state_file.js +2 -0
- package/build/generated/memory_status_input.js +2 -0
- package/build/generated/memory_status_output.js +13 -0
- package/build/generated/memory_sync_input.js +2 -0
- package/build/generated/memory_sync_output.js +13 -0
- package/build/generated/plugin_result.js +2 -0
- package/build/generated/react_perf_check_patterns_input.js +2 -0
- package/build/generated/react_perf_check_patterns_output.js +2 -0
- package/build/generated/react_perf_generate_report_input.js +2 -0
- package/build/generated/react_perf_generate_report_output.js +2 -0
- package/build/generated/repair_plan_input.js +2 -0
- package/build/generated/repair_plan_output.js +2 -0
- package/build/generated/run_app_input.js +2 -0
- package/build/generated/run_app_output.js +2 -0
- package/build/generated/run_state_file.js +13 -0
- package/build/generated/scaffold_input.js +2 -0
- package/build/generated/scaffold_output.js +2 -0
- package/build/generated/search_oss_input.js +2 -0
- package/build/generated/search_oss_output.js +2 -0
- package/build/generated/selection_validation_result.js +2 -0
- package/build/generated/signal_agent_input.js +2 -0
- package/build/generated/spec_high_ask_queue_items_file.js +2 -0
- package/build/generated/spec_high_clinic_bridge_output.js +2 -0
- package/build/generated/spec_high_decision_draft_output.js +2 -0
- package/build/generated/spec_high_validate_output.js +2 -0
- package/build/generated/status_input.js +2 -0
- package/build/generated/status_output.js +2 -0
- package/build/generated/submit_decision_input.js +2 -0
- package/build/generated/submit_decision_output.js +2 -0
- package/build/generated/tool_error_output.js +2 -0
- package/build/generated/undo_last_task_input.js +2 -0
- package/build/generated/undo_last_task_output.js +2 -0
- package/build/generated/update_input.js +2 -0
- package/build/generated/update_output.js +2 -0
- package/build/generated/vibe_pm_inspection_result.js +2 -0
- package/build/generated/vibe_pm_report_markdown.js +2 -0
- package/build/generated/vibe_pm_verdict.js +2 -0
- package/build/generated/vibe_repo_config.js +2 -0
- package/build/generated/vibecoding_helper_answer_output.js +2 -0
- package/build/generated/vibecoding_helper_one_loop_selection_output.js +2 -0
- package/build/generated/vibecoding_helper_show_ask_queue_output.js +2 -0
- package/build/generated/work_order_v1.js +2 -0
- package/build/generated/zoekt_evidence_input.js +2 -0
- package/build/generated/zoekt_evidence_output.js +2 -0
- package/build/index.js +111 -0
- package/build/legacy_alias.js +65 -0
- package/build/local-mode/bash.js +61 -0
- package/build/local-mode/config.js +171 -0
- package/build/local-mode/git.js +33 -0
- package/build/local-mode/init.js +110 -0
- package/build/local-mode/paths.js +24 -0
- package/build/local-mode/templates.js +856 -0
- package/build/local-mode/work-order.js +41 -0
- package/build/resources/index.js +246 -0
- package/build/security/input-validator.js +119 -0
- package/build/security/path-policy.js +289 -0
- package/build/security/sandbox.js +228 -0
- package/build/tools/react_perf/check_patterns.js +172 -0
- package/build/tools/react_perf/generate_report.js +337 -0
- package/build/tools/react_perf/index.js +119 -0
- package/build/tools/react_perf/rules/advanced.js +325 -0
- package/build/tools/react_perf/rules/async.js +104 -0
- package/build/tools/react_perf/rules/bundle.js +101 -0
- package/build/tools/react_perf/rules/client.js +186 -0
- package/build/tools/react_perf/rules/index.js +74 -0
- package/build/tools/react_perf/rules/js.js +148 -0
- package/build/tools/react_perf/rules/rendering.js +166 -0
- package/build/tools/react_perf/rules/rerender.js +161 -0
- package/build/tools/react_perf/rules/server.js +141 -0
- package/build/tools/react_perf/types.js +127 -0
- package/build/tools/vibe_pm/activate.js +102 -0
- package/build/tools/vibe_pm/advisory_review.js +77 -0
- package/build/tools/vibe_pm/briefing.js +178 -0
- package/build/tools/vibe_pm/context.js +439 -0
- package/build/tools/vibe_pm/create_work_order.js +271 -0
- package/build/tools/vibe_pm/doc_status_gate.js +370 -0
- package/build/tools/vibe_pm/doctor.js +262 -0
- package/build/tools/vibe_pm/entity_gate/preflight.js +78 -0
- package/build/tools/vibe_pm/export_output.js +135 -0
- package/build/tools/vibe_pm/finalize_work.js +393 -0
- package/build/tools/vibe_pm/gate.js +33 -0
- package/build/tools/vibe_pm/get_decision.js +281 -0
- package/build/tools/vibe_pm/index.js +593 -0
- package/build/tools/vibe_pm/inspect_code.js +828 -0
- package/build/tools/vibe_pm/intent/generator.js +294 -0
- package/build/tools/vibe_pm/intent/index.js +5 -0
- package/build/tools/vibe_pm/intent/prompt_density.js +227 -0
- package/build/tools/vibe_pm/intent/types.js +70 -0
- package/build/tools/vibe_pm/intent/verifier.js +237 -0
- package/build/tools/vibe_pm/kce/doc_usage.js +51 -0
- package/build/tools/vibe_pm/kce/on_finalize.js +11 -0
- package/build/tools/vibe_pm/kce/preflight.js +232 -0
- package/build/tools/vibe_pm/local_memory.js +26 -0
- package/build/tools/vibe_pm/memory_status.js +82 -0
- package/build/tools/vibe_pm/memory_sync.js +134 -0
- package/build/tools/vibe_pm/modules/decision_snapshot.js +29 -0
- package/build/tools/vibe_pm/modules/ensure.js +100 -0
- package/build/tools/vibe_pm/modules/fingerprint.js +30 -0
- package/build/tools/vibe_pm/modules/fix_dependencies.js +394 -0
- package/build/tools/vibe_pm/modules/planning_v1.js +110 -0
- package/build/tools/vibe_pm/modules/repo_context.js +56 -0
- package/build/tools/vibe_pm/modules/research_v1.js +114 -0
- package/build/tools/vibe_pm/modules/skills_v1.js +100 -0
- package/build/tools/vibe_pm/pm_language.js +222 -0
- package/build/tools/vibe_pm/repair_plan.js +199 -0
- package/build/tools/vibe_pm/run_app.js +597 -0
- package/build/tools/vibe_pm/run_app_podman.js +64 -0
- package/build/tools/vibe_pm/scaffold.js +550 -0
- package/build/tools/vibe_pm/search_oss.js +124 -0
- package/build/tools/vibe_pm/status.js +153 -0
- package/build/tools/vibe_pm/submit_decision.js +87 -0
- package/build/tools/vibe_pm/system_design/issue_mapping.js +47 -0
- package/build/tools/vibe_pm/system_design/rulebook.js +112 -0
- package/build/tools/vibe_pm/system_design/semgrep.js +132 -0
- package/build/tools/vibe_pm/types.js +229 -0
- package/build/tools/vibe_pm/undo_last_task.js +163 -0
- package/build/tools/vibe_pm/update.js +146 -0
- package/build/tools/vibe_pm/zoekt_evidence.js +96 -0
- package/build/tools.js +269 -0
- package/build/version-check.js +239 -0
- package/build/vibe-cli.js +631 -0
- package/package.json +76 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/generate_report.ts
|
|
2
|
+
// Main implementation for react_perf.generate_report
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { impactToSeverity, calculateScore } from "./types.js";
|
|
6
|
+
import { checkPatterns } from "./check_patterns.js";
|
|
7
|
+
import { getTotalRuleCount, getRuleCountByCategory } from "./rules/index.js";
|
|
8
|
+
// ============================================================
|
|
9
|
+
// Report Generators
|
|
10
|
+
// ============================================================
|
|
11
|
+
function generateMarkdownReport(summary, violations, includeRecommendations) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
// Header
|
|
14
|
+
lines.push("# React Performance Analysis Report");
|
|
15
|
+
lines.push("");
|
|
16
|
+
lines.push(`Generated: ${new Date().toISOString()}`);
|
|
17
|
+
lines.push("");
|
|
18
|
+
// Score Badge
|
|
19
|
+
const scoreEmoji = summary.score >= 80 ? "🟢" : summary.score >= 50 ? "🟡" : "🔴";
|
|
20
|
+
lines.push(`## Score: ${scoreEmoji} ${summary.score}/100`);
|
|
21
|
+
lines.push("");
|
|
22
|
+
// Summary Table
|
|
23
|
+
lines.push("## Summary");
|
|
24
|
+
lines.push("");
|
|
25
|
+
lines.push("| Metric | Value |");
|
|
26
|
+
lines.push("|--------|-------|");
|
|
27
|
+
lines.push(`| Files Scanned | ${summary.total_files_scanned} |`);
|
|
28
|
+
lines.push(`| Total Issues | ${summary.total_issues} |`);
|
|
29
|
+
lines.push(`| Errors | ${summary.by_severity.error} |`);
|
|
30
|
+
lines.push(`| Warnings | ${summary.by_severity.warning} |`);
|
|
31
|
+
lines.push(`| Info | ${summary.by_severity.info} |`);
|
|
32
|
+
lines.push("");
|
|
33
|
+
// By Category
|
|
34
|
+
lines.push("## Issues by Category");
|
|
35
|
+
lines.push("");
|
|
36
|
+
lines.push("| Category | Count |");
|
|
37
|
+
lines.push("|----------|-------|");
|
|
38
|
+
for (const [category, count] of Object.entries(summary.by_category)) {
|
|
39
|
+
if (count > 0) {
|
|
40
|
+
lines.push(`| ${category} | ${count} |`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
lines.push("");
|
|
44
|
+
// Rule Coverage
|
|
45
|
+
const ruleCount = getTotalRuleCount();
|
|
46
|
+
const categoryCount = getRuleCountByCategory();
|
|
47
|
+
lines.push("## Rule Coverage");
|
|
48
|
+
lines.push("");
|
|
49
|
+
lines.push(`Total rules: ${ruleCount}`);
|
|
50
|
+
lines.push("");
|
|
51
|
+
lines.push("| Category | Rules |");
|
|
52
|
+
lines.push("|----------|-------|");
|
|
53
|
+
for (const [cat, count] of Object.entries(categoryCount)) {
|
|
54
|
+
lines.push(`| ${cat} | ${count} |`);
|
|
55
|
+
}
|
|
56
|
+
lines.push("");
|
|
57
|
+
// Violations Detail
|
|
58
|
+
if (violations.length > 0) {
|
|
59
|
+
lines.push("## Violations");
|
|
60
|
+
lines.push("");
|
|
61
|
+
// Group by severity
|
|
62
|
+
const errors = violations.filter(v => impactToSeverity(v.impact) === "error");
|
|
63
|
+
const warnings = violations.filter(v => impactToSeverity(v.impact) === "warning");
|
|
64
|
+
const infos = violations.filter(v => impactToSeverity(v.impact) === "info");
|
|
65
|
+
if (errors.length > 0) {
|
|
66
|
+
lines.push("### 🔴 Errors");
|
|
67
|
+
lines.push("");
|
|
68
|
+
for (const v of errors) {
|
|
69
|
+
lines.push(`- **${v.rule_id}** in \`${v.file}:${v.line}\``);
|
|
70
|
+
lines.push(` - ${v.message}`);
|
|
71
|
+
if (includeRecommendations) {
|
|
72
|
+
lines.push(` - 💡 ${v.suggestion}`);
|
|
73
|
+
}
|
|
74
|
+
if (v.code_snippet) {
|
|
75
|
+
lines.push(` - \`${v.code_snippet}\``);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
lines.push("");
|
|
79
|
+
}
|
|
80
|
+
if (warnings.length > 0) {
|
|
81
|
+
lines.push("### 🟡 Warnings");
|
|
82
|
+
lines.push("");
|
|
83
|
+
for (const v of warnings) {
|
|
84
|
+
lines.push(`- **${v.rule_id}** in \`${v.file}:${v.line}\``);
|
|
85
|
+
lines.push(` - ${v.message}`);
|
|
86
|
+
if (includeRecommendations) {
|
|
87
|
+
lines.push(` - 💡 ${v.suggestion}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
lines.push("");
|
|
91
|
+
}
|
|
92
|
+
if (infos.length > 0) {
|
|
93
|
+
lines.push("### 🔵 Info");
|
|
94
|
+
lines.push("");
|
|
95
|
+
for (const v of infos) {
|
|
96
|
+
lines.push(`- **${v.rule_id}** in \`${v.file}:${v.line}\``);
|
|
97
|
+
lines.push(` - ${v.message}`);
|
|
98
|
+
if (includeRecommendations) {
|
|
99
|
+
lines.push(` - 💡 ${v.suggestion}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
lines.push("");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
lines.push("## ✅ No violations found!");
|
|
107
|
+
lines.push("");
|
|
108
|
+
lines.push("Great job! Your code follows React performance best practices.");
|
|
109
|
+
lines.push("");
|
|
110
|
+
}
|
|
111
|
+
// Recommendations
|
|
112
|
+
if (includeRecommendations && violations.length > 0) {
|
|
113
|
+
lines.push("## Top 5 Recommendations");
|
|
114
|
+
lines.push("");
|
|
115
|
+
// Get unique suggestions by rule_id
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const topSuggestions = [];
|
|
118
|
+
for (const v of violations) {
|
|
119
|
+
if (!seen.has(v.rule_id) && topSuggestions.length < 5) {
|
|
120
|
+
seen.add(v.rule_id);
|
|
121
|
+
topSuggestions.push({ impact: v.impact, suggestion: v.suggestion });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (let i = 0; i < topSuggestions.length; i++) {
|
|
125
|
+
const s = topSuggestions[i];
|
|
126
|
+
lines.push(`${i + 1}. **[${s.impact}]** ${s.suggestion}`);
|
|
127
|
+
}
|
|
128
|
+
lines.push("");
|
|
129
|
+
}
|
|
130
|
+
return lines.join("\n");
|
|
131
|
+
}
|
|
132
|
+
function generateHtmlReport(summary, violations, includeRecommendations) {
|
|
133
|
+
const scoreColor = summary.score >= 80 ? "#22c55e" : summary.score >= 50 ? "#eab308" : "#ef4444";
|
|
134
|
+
return `<!DOCTYPE html>
|
|
135
|
+
<html lang="en">
|
|
136
|
+
<head>
|
|
137
|
+
<meta charset="UTF-8">
|
|
138
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
139
|
+
<title>React Performance Report</title>
|
|
140
|
+
<style>
|
|
141
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
142
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; padding: 2rem; max-width: 1200px; margin: 0 auto; background: #f5f5f5; }
|
|
143
|
+
h1 { color: #1a1a1a; margin-bottom: 1rem; }
|
|
144
|
+
h2 { color: #333; margin: 2rem 0 1rem; border-bottom: 2px solid #ddd; padding-bottom: 0.5rem; }
|
|
145
|
+
h3 { color: #555; margin: 1.5rem 0 0.5rem; }
|
|
146
|
+
.card { background: white; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
147
|
+
.score { font-size: 3rem; font-weight: bold; color: ${scoreColor}; }
|
|
148
|
+
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; }
|
|
149
|
+
.summary-item { text-align: center; }
|
|
150
|
+
.summary-item .value { font-size: 2rem; font-weight: bold; }
|
|
151
|
+
.summary-item .label { color: #666; font-size: 0.9rem; }
|
|
152
|
+
table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
|
153
|
+
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #eee; }
|
|
154
|
+
th { background: #f9f9f9; font-weight: 600; }
|
|
155
|
+
.error { color: #dc2626; }
|
|
156
|
+
.warning { color: #ca8a04; }
|
|
157
|
+
.info { color: #2563eb; }
|
|
158
|
+
.violation { padding: 1rem; margin: 0.5rem 0; border-left: 4px solid; background: white; border-radius: 4px; }
|
|
159
|
+
.violation.error { border-color: #dc2626; }
|
|
160
|
+
.violation.warning { border-color: #ca8a04; }
|
|
161
|
+
.violation.info { border-color: #2563eb; }
|
|
162
|
+
.violation-title { font-weight: 600; }
|
|
163
|
+
.violation-file { color: #666; font-size: 0.9rem; font-family: monospace; }
|
|
164
|
+
.violation-message { margin: 0.5rem 0; }
|
|
165
|
+
.violation-suggestion { color: #059669; font-size: 0.9rem; }
|
|
166
|
+
code { background: #f3f4f6; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9rem; }
|
|
167
|
+
.timestamp { color: #888; font-size: 0.9rem; }
|
|
168
|
+
</style>
|
|
169
|
+
</head>
|
|
170
|
+
<body>
|
|
171
|
+
<h1>🚀 React Performance Analysis Report</h1>
|
|
172
|
+
<p class="timestamp">Generated: ${new Date().toISOString()}</p>
|
|
173
|
+
|
|
174
|
+
<div class="card">
|
|
175
|
+
<h2 style="margin-top: 0;">Score</h2>
|
|
176
|
+
<div class="score">${summary.score}/100</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div class="card">
|
|
180
|
+
<h2 style="margin-top: 0;">Summary</h2>
|
|
181
|
+
<div class="summary-grid">
|
|
182
|
+
<div class="summary-item">
|
|
183
|
+
<div class="value">${summary.total_files_scanned}</div>
|
|
184
|
+
<div class="label">Files Scanned</div>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="summary-item">
|
|
187
|
+
<div class="value">${summary.total_issues}</div>
|
|
188
|
+
<div class="label">Total Issues</div>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="summary-item">
|
|
191
|
+
<div class="value error">${summary.by_severity.error}</div>
|
|
192
|
+
<div class="label">Errors</div>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="summary-item">
|
|
195
|
+
<div class="value warning">${summary.by_severity.warning}</div>
|
|
196
|
+
<div class="label">Warnings</div>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="summary-item">
|
|
199
|
+
<div class="value info">${summary.by_severity.info}</div>
|
|
200
|
+
<div class="label">Info</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div class="card">
|
|
206
|
+
<h2 style="margin-top: 0;">Issues by Category</h2>
|
|
207
|
+
<table>
|
|
208
|
+
<thead>
|
|
209
|
+
<tr><th>Category</th><th>Count</th></tr>
|
|
210
|
+
</thead>
|
|
211
|
+
<tbody>
|
|
212
|
+
${Object.entries(summary.by_category)
|
|
213
|
+
.filter(([, count]) => count > 0)
|
|
214
|
+
.map(([cat, count]) => `<tr><td>${cat}</td><td>${count}</td></tr>`)
|
|
215
|
+
.join("\n ")}
|
|
216
|
+
</tbody>
|
|
217
|
+
</table>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
${violations.length > 0 ? `
|
|
221
|
+
<div class="card">
|
|
222
|
+
<h2 style="margin-top: 0;">Violations</h2>
|
|
223
|
+
${violations.map(v => {
|
|
224
|
+
const severity = impactToSeverity(v.impact);
|
|
225
|
+
return `
|
|
226
|
+
<div class="violation ${severity}">
|
|
227
|
+
<div class="violation-title">${v.rule_id}</div>
|
|
228
|
+
<div class="violation-file">${v.file}:${v.line}</div>
|
|
229
|
+
<div class="violation-message">${v.message}</div>
|
|
230
|
+
${includeRecommendations ? `<div class="violation-suggestion">💡 ${v.suggestion}</div>` : ""}
|
|
231
|
+
${v.code_snippet ? `<div><code>${escapeHtml(v.code_snippet)}</code></div>` : ""}
|
|
232
|
+
</div>`;
|
|
233
|
+
}).join("")}
|
|
234
|
+
</div>
|
|
235
|
+
` : `
|
|
236
|
+
<div class="card">
|
|
237
|
+
<h2 style="margin-top: 0;">✅ No violations found!</h2>
|
|
238
|
+
<p>Great job! Your code follows React performance best practices.</p>
|
|
239
|
+
</div>
|
|
240
|
+
`}
|
|
241
|
+
</body>
|
|
242
|
+
</html>`;
|
|
243
|
+
}
|
|
244
|
+
function escapeHtml(text) {
|
|
245
|
+
return text
|
|
246
|
+
.replace(/&/g, "&")
|
|
247
|
+
.replace(/</g, "<")
|
|
248
|
+
.replace(/>/g, ">")
|
|
249
|
+
.replace(/"/g, """)
|
|
250
|
+
.replace(/'/g, "'");
|
|
251
|
+
}
|
|
252
|
+
function generateJsonReport(summary, violations, includeRecommendations) {
|
|
253
|
+
const report = {
|
|
254
|
+
generated_at: new Date().toISOString(),
|
|
255
|
+
summary,
|
|
256
|
+
violations: violations.map(v => ({
|
|
257
|
+
rule_id: v.rule_id,
|
|
258
|
+
category: v.category,
|
|
259
|
+
file: v.file,
|
|
260
|
+
line: v.line,
|
|
261
|
+
column: v.column,
|
|
262
|
+
impact: v.impact,
|
|
263
|
+
severity: impactToSeverity(v.impact),
|
|
264
|
+
message: v.message,
|
|
265
|
+
...(includeRecommendations ? { suggestion: v.suggestion } : {}),
|
|
266
|
+
...(v.code_snippet ? { code_snippet: v.code_snippet } : {})
|
|
267
|
+
})),
|
|
268
|
+
rules: {
|
|
269
|
+
total: getTotalRuleCount(),
|
|
270
|
+
by_category: getRuleCountByCategory()
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
return JSON.stringify(report, null, 2);
|
|
274
|
+
}
|
|
275
|
+
// ============================================================
|
|
276
|
+
// Main Entry Point
|
|
277
|
+
// ============================================================
|
|
278
|
+
export async function generateReport(input) {
|
|
279
|
+
// Run check_patterns to get violations
|
|
280
|
+
const checkResult = await checkPatterns({
|
|
281
|
+
target_paths: input.target_paths,
|
|
282
|
+
min_impact: "LOW" // Get all violations for the report
|
|
283
|
+
});
|
|
284
|
+
// Calculate severity counts
|
|
285
|
+
const bySeverity = {
|
|
286
|
+
error: 0,
|
|
287
|
+
warning: 0,
|
|
288
|
+
info: 0
|
|
289
|
+
};
|
|
290
|
+
const byCategory = {};
|
|
291
|
+
for (const v of checkResult.violations) {
|
|
292
|
+
const severity = impactToSeverity(v.impact);
|
|
293
|
+
bySeverity[severity]++;
|
|
294
|
+
byCategory[v.category] = (byCategory[v.category] || 0) + 1;
|
|
295
|
+
}
|
|
296
|
+
// Build summary
|
|
297
|
+
const summary = {
|
|
298
|
+
total_files_scanned: checkResult.total_files_scanned,
|
|
299
|
+
total_issues: checkResult.total_violations,
|
|
300
|
+
by_severity: bySeverity,
|
|
301
|
+
by_category: byCategory,
|
|
302
|
+
score: calculateScore(bySeverity)
|
|
303
|
+
};
|
|
304
|
+
// Generate report content
|
|
305
|
+
const format = input.format || "markdown";
|
|
306
|
+
const includeRecommendations = input.include_recommendations !== false;
|
|
307
|
+
let reportContent;
|
|
308
|
+
switch (format) {
|
|
309
|
+
case "html":
|
|
310
|
+
reportContent = generateHtmlReport(summary, checkResult.violations, includeRecommendations);
|
|
311
|
+
break;
|
|
312
|
+
case "json":
|
|
313
|
+
reportContent = generateJsonReport(summary, checkResult.violations, includeRecommendations);
|
|
314
|
+
break;
|
|
315
|
+
case "markdown":
|
|
316
|
+
default:
|
|
317
|
+
reportContent = generateMarkdownReport(summary, checkResult.violations, includeRecommendations);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
// Save to file if output_path is specified
|
|
321
|
+
let savedTo;
|
|
322
|
+
if (input.output_path) {
|
|
323
|
+
const outputPath = path.resolve(input.output_path);
|
|
324
|
+
const dir = path.dirname(outputPath);
|
|
325
|
+
if (!fs.existsSync(dir)) {
|
|
326
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
327
|
+
}
|
|
328
|
+
fs.writeFileSync(outputPath, reportContent, "utf-8");
|
|
329
|
+
savedTo = outputPath;
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
status: "OK",
|
|
333
|
+
summary,
|
|
334
|
+
report_content: reportContent,
|
|
335
|
+
...(savedTo ? { saved_to: savedTo } : {})
|
|
336
|
+
};
|
|
337
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/index.ts
|
|
2
|
+
// react_perf.* MCP tools registration and export
|
|
3
|
+
import { checkPatternsInputSchema, checkPatternsOutputSchema, generateReportInputSchema, generateReportOutputSchema } from "./types.js";
|
|
4
|
+
import { checkPatterns } from "./check_patterns.js";
|
|
5
|
+
import { generateReport } from "./generate_report.js";
|
|
6
|
+
import { getTotalRuleCount, getRuleCountByCategory } from "./rules/index.js";
|
|
7
|
+
import { ToolErrorOutputSchema } from "../../generated/tool_error_output.js";
|
|
8
|
+
import { decorateToolOutputText, notifyDesktopBestEffort } from "../../dx/activity.js";
|
|
9
|
+
function toolResult(data) {
|
|
10
|
+
// react_perf tools are surfaced through VibePM as well; keep output decoration consistent.
|
|
11
|
+
const toolName = "react_perf";
|
|
12
|
+
const jsonText = JSON.stringify(data, null, 2);
|
|
13
|
+
void notifyDesktopBestEffort({ toolName, data, outcome: "success" });
|
|
14
|
+
return {
|
|
15
|
+
content: decorateToolOutputText(toolName, jsonText, data, "success"),
|
|
16
|
+
structuredContent: data
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function err(toolName, reason, extra) {
|
|
20
|
+
const out = ToolErrorOutputSchema.parse({
|
|
21
|
+
status: "ERROR",
|
|
22
|
+
reason,
|
|
23
|
+
message: extra?.message ?? "요청을 처리하는 중 문제가 생겼습니다.",
|
|
24
|
+
recovery: extra?.recovery,
|
|
25
|
+
is_retryable: extra?.is_retryable,
|
|
26
|
+
details: extra?.details,
|
|
27
|
+
debug: extra?.debug
|
|
28
|
+
});
|
|
29
|
+
const jsonText = JSON.stringify(out, null, 2);
|
|
30
|
+
void notifyDesktopBestEffort({ toolName, data: out, outcome: "error" });
|
|
31
|
+
return {
|
|
32
|
+
content: decorateToolOutputText(toolName, jsonText, out, "error"),
|
|
33
|
+
structuredContent: out,
|
|
34
|
+
isError: true
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// ============================================================
|
|
38
|
+
// Tool Handlers (with error handling)
|
|
39
|
+
// ============================================================
|
|
40
|
+
async function reactPerfCheckPatterns(input) {
|
|
41
|
+
const startedAt = Date.now();
|
|
42
|
+
try {
|
|
43
|
+
const result = await checkPatterns(input);
|
|
44
|
+
const validated = checkPatternsOutputSchema.parse(result);
|
|
45
|
+
const durationMs = Date.now() - startedAt;
|
|
46
|
+
const jsonText = JSON.stringify(validated, null, 2);
|
|
47
|
+
void notifyDesktopBestEffort({ toolName: "react_perf.check_patterns", data: validated, outcome: "success", durationMs });
|
|
48
|
+
return {
|
|
49
|
+
content: decorateToolOutputText("react_perf.check_patterns", jsonText, validated, "success", durationMs),
|
|
50
|
+
structuredContent: validated
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
55
|
+
return err("react_perf.check_patterns", "check_patterns_failed", { message: msg });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function reactPerfGenerateReport(input) {
|
|
59
|
+
const startedAt = Date.now();
|
|
60
|
+
try {
|
|
61
|
+
const result = await generateReport(input);
|
|
62
|
+
const validated = generateReportOutputSchema.parse(result);
|
|
63
|
+
const durationMs = Date.now() - startedAt;
|
|
64
|
+
const jsonText = JSON.stringify(validated, null, 2);
|
|
65
|
+
void notifyDesktopBestEffort({ toolName: "react_perf.generate_report", data: validated, outcome: "success", durationMs });
|
|
66
|
+
return {
|
|
67
|
+
content: decorateToolOutputText("react_perf.generate_report", jsonText, validated, "success", durationMs),
|
|
68
|
+
structuredContent: validated
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
73
|
+
return err("react_perf.generate_report", "generate_report_failed", { message: msg });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ============================================================
|
|
77
|
+
// Export
|
|
78
|
+
// ============================================================
|
|
79
|
+
export function defineReactPerfTools() {
|
|
80
|
+
return {
|
|
81
|
+
// Input schemas (for validation)
|
|
82
|
+
checkPatternsInputSchema,
|
|
83
|
+
generateReportInputSchema,
|
|
84
|
+
// Tool handlers
|
|
85
|
+
reactPerfCheckPatterns,
|
|
86
|
+
reactPerfGenerateReport,
|
|
87
|
+
// Rule info (for introspection)
|
|
88
|
+
getTotalRuleCount,
|
|
89
|
+
getRuleCountByCategory
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// ============================================================
|
|
93
|
+
// Tool Descriptions (for MCP registration)
|
|
94
|
+
// ============================================================
|
|
95
|
+
export const REACT_PERF_TOOL_DESCRIPTIONS = {
|
|
96
|
+
"react_perf.check_patterns": `React/Next.js 코드에서 성능 anti-pattern을 탐지합니다.
|
|
97
|
+
|
|
98
|
+
탐지 가능한 패턴 (48개 규칙):
|
|
99
|
+
- async: 순차 await (waterfall), 루프 내 await
|
|
100
|
+
- bundle: barrel import, 대용량 라이브러리 import
|
|
101
|
+
- server: Server Action 인증/검증 누락
|
|
102
|
+
- rendering: Suspense 바운더리, 스트리밍, Error Boundary
|
|
103
|
+
- client: 과도한 'use client', window 객체 접근, hydration
|
|
104
|
+
- rerender: 인라인 객체/함수, Context value, memo
|
|
105
|
+
- advanced: Parallel Routes, PPR, Optimistic Updates
|
|
106
|
+
- js: lodash 전체 import, moment.js, 안전하지 않은 JSON.parse
|
|
107
|
+
|
|
108
|
+
사용 시점: 성능 최적화가 필요하거나 코드 리뷰 시`,
|
|
109
|
+
"react_perf.generate_report": `React/Next.js 성능 분석 리포트를 생성합니다.
|
|
110
|
+
|
|
111
|
+
출력 형식:
|
|
112
|
+
- markdown: 마크다운 형식 (기본값)
|
|
113
|
+
- html: HTML 형식 (시각적 대시보드)
|
|
114
|
+
- json: JSON 형식 (자동화용)
|
|
115
|
+
|
|
116
|
+
점수 계산: 100 - (error × 10) - (warning × 3) - (info × 1)
|
|
117
|
+
|
|
118
|
+
사용 시점: 팀 공유용 리포트 생성, CI/CD 품질 게이트`
|
|
119
|
+
};
|