@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,325 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/advanced.ts
|
|
2
|
+
// Advanced Next.js Patterns (Parallel Routes, PPR, Optimistic Updates)
|
|
3
|
+
/**
|
|
4
|
+
* Advanced rules detect missed opportunities for Next.js advanced features
|
|
5
|
+
* like parallel routes, partial prerendering, and optimistic updates.
|
|
6
|
+
*/
|
|
7
|
+
export const ADVANCED_RULES = [
|
|
8
|
+
{
|
|
9
|
+
id: "advanced-parallel-routes",
|
|
10
|
+
category: "advanced",
|
|
11
|
+
impact: "LOW-MEDIUM",
|
|
12
|
+
// Detects: layout with multiple independent data-fetching sections
|
|
13
|
+
pattern: /export\s+(?:default\s+)?(?:async\s+)?function\s+Layout[\s\S]*?(?:await|fetch\()[\s\S]*?(?:await|fetch\()/,
|
|
14
|
+
antiPattern: /@\w+|parallel/i,
|
|
15
|
+
message: "Layout with multiple data sources may benefit from Parallel Routes",
|
|
16
|
+
suggestion: "Use @folder convention for independent loading states",
|
|
17
|
+
fileFilter: /layout\.(tsx?|jsx?)$/,
|
|
18
|
+
examples: {
|
|
19
|
+
bad: `// app/dashboard/layout.tsx
|
|
20
|
+
export default async function Layout({ children }) {
|
|
21
|
+
const user = await getUser();
|
|
22
|
+
const notifications = await getNotifications();
|
|
23
|
+
return (
|
|
24
|
+
<div>
|
|
25
|
+
<Sidebar user={user} />
|
|
26
|
+
<NotificationPanel data={notifications} />
|
|
27
|
+
{children}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}`,
|
|
31
|
+
good: `// app/dashboard/layout.tsx
|
|
32
|
+
export default function Layout({
|
|
33
|
+
children,
|
|
34
|
+
sidebar, // @sidebar/page.tsx
|
|
35
|
+
notifications // @notifications/page.tsx
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
{sidebar}
|
|
40
|
+
{notifications}
|
|
41
|
+
{children}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}`
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "advanced-intercepting-routes",
|
|
49
|
+
category: "advanced",
|
|
50
|
+
impact: "LOW-MEDIUM",
|
|
51
|
+
// Detects: modal/dialog pattern without intercepting routes
|
|
52
|
+
pattern: /<(?:Modal|Dialog|Sheet|Drawer)[^>]*>[\s\S]*?<\/(?:Modal|Dialog|Sheet|Drawer)>/,
|
|
53
|
+
antiPattern: /\(\.\)|\.{2,}|intercepting/i,
|
|
54
|
+
message: "Modal pattern may benefit from Intercepting Routes",
|
|
55
|
+
suggestion: "Use (.) or (..) convention for URL-backed modals",
|
|
56
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
57
|
+
examples: {
|
|
58
|
+
bad: `// Basic modal loses URL context
|
|
59
|
+
function PhotoGallery({ photos }) {
|
|
60
|
+
const [selectedPhoto, setSelectedPhoto] = useState(null);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
{photos.map(p => (
|
|
65
|
+
<img onClick={() => setSelectedPhoto(p)} />
|
|
66
|
+
))}
|
|
67
|
+
{selectedPhoto && (
|
|
68
|
+
<Modal onClose={() => setSelectedPhoto(null)}>
|
|
69
|
+
<PhotoDetail photo={selectedPhoto} />
|
|
70
|
+
</Modal>
|
|
71
|
+
)}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}`,
|
|
75
|
+
good: `// app/photos/(..)photo/[id]/page.tsx (intercepts)
|
|
76
|
+
// Enables: shareable URL, back button works, soft navigation
|
|
77
|
+
|
|
78
|
+
// app/photos/page.tsx
|
|
79
|
+
function PhotoGallery({ photos }) {
|
|
80
|
+
return photos.map(p => (
|
|
81
|
+
<Link href={\`/photo/\${p.id}\`}>
|
|
82
|
+
<img src={p.thumbnail} />
|
|
83
|
+
</Link>
|
|
84
|
+
));
|
|
85
|
+
}`
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "advanced-generatestaticparams",
|
|
90
|
+
category: "advanced",
|
|
91
|
+
impact: "LOW-MEDIUM",
|
|
92
|
+
// Detects: dynamic route page without generateStaticParams
|
|
93
|
+
pattern: /export\s+(?:default\s+)?(?:async\s+)?function\s+Page\s*\(\s*\{\s*params/,
|
|
94
|
+
antiPattern: /generateStaticParams|dynamic\s*=\s*['"]force-dynamic['"]/,
|
|
95
|
+
message: "Dynamic route may benefit from generateStaticParams",
|
|
96
|
+
suggestion: "Add generateStaticParams for static generation of known paths",
|
|
97
|
+
fileFilter: /\[.*\].*page\.(tsx?|jsx?)$/,
|
|
98
|
+
examples: {
|
|
99
|
+
bad: `// app/blog/[slug]/page.tsx
|
|
100
|
+
export default async function Page({ params }) {
|
|
101
|
+
const post = await getPost(params.slug);
|
|
102
|
+
return <Article post={post} />;
|
|
103
|
+
}`,
|
|
104
|
+
good: `// app/blog/[slug]/page.tsx
|
|
105
|
+
export async function generateStaticParams() {
|
|
106
|
+
const posts = await getAllPosts();
|
|
107
|
+
return posts.map(post => ({ slug: post.slug }));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default async function Page({ params }) {
|
|
111
|
+
const post = await getPost(params.slug);
|
|
112
|
+
return <Article post={post} />;
|
|
113
|
+
}`
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "advanced-route-segment-config",
|
|
118
|
+
category: "advanced",
|
|
119
|
+
impact: "LOW-MEDIUM",
|
|
120
|
+
// Detects: page with specific caching needs without config
|
|
121
|
+
pattern: /export\s+(?:default\s+)?(?:async\s+)?function\s+Page[\s\S]*?(?:revalidate|cache|unstable_cache)/,
|
|
122
|
+
antiPattern: /export\s+const\s+(?:dynamic|revalidate|fetchCache|runtime)/,
|
|
123
|
+
message: "Page may benefit from Route Segment Config",
|
|
124
|
+
suggestion: "Add export const revalidate/dynamic/fetchCache for explicit caching control",
|
|
125
|
+
fileFilter: /page\.(tsx?|jsx?)$/,
|
|
126
|
+
examples: {
|
|
127
|
+
bad: `export default async function Page() {
|
|
128
|
+
const data = await fetch(url, {
|
|
129
|
+
next: { revalidate: 3600 }
|
|
130
|
+
});
|
|
131
|
+
}`,
|
|
132
|
+
good: `export const revalidate = 3600; // Applies to all fetches
|
|
133
|
+
|
|
134
|
+
export default async function Page() {
|
|
135
|
+
const data = await fetch(url);
|
|
136
|
+
}`
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "advanced-metadata-missing",
|
|
141
|
+
category: "advanced",
|
|
142
|
+
impact: "MEDIUM-HIGH",
|
|
143
|
+
// Detects: page.tsx without metadata export
|
|
144
|
+
pattern: /export\s+(?:default\s+)?(?:async\s+)?function\s+(?:Page|default)/,
|
|
145
|
+
antiPattern: /export\s+(?:const|async\s+function)\s+(?:metadata|generateMetadata)/,
|
|
146
|
+
message: "Page lacks Metadata API for SEO optimization",
|
|
147
|
+
suggestion: "Add metadata export or generateMetadata function",
|
|
148
|
+
fileFilter: /page\.(tsx?|jsx?)$/,
|
|
149
|
+
examples: {
|
|
150
|
+
bad: `export default function Page() {
|
|
151
|
+
return <div>My Page</div>;
|
|
152
|
+
}`,
|
|
153
|
+
good: `export const metadata = {
|
|
154
|
+
title: 'My Page',
|
|
155
|
+
description: 'Page description for SEO'
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export default function Page() {
|
|
159
|
+
return <div>My Page</div>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Or dynamic metadata
|
|
163
|
+
export async function generateMetadata({ params }) {
|
|
164
|
+
const post = await getPost(params.slug);
|
|
165
|
+
return { title: post.title };
|
|
166
|
+
}`
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "advanced-use-optimistic",
|
|
171
|
+
category: "advanced",
|
|
172
|
+
impact: "LOW-MEDIUM",
|
|
173
|
+
// Detects: form with Server Action but no optimistic update
|
|
174
|
+
pattern: /action=\{(?:async\s*)?\([^)]*\)\s*=>|formAction|useFormState/,
|
|
175
|
+
antiPattern: /useOptimistic|optimistic/i,
|
|
176
|
+
message: "Form action may benefit from optimistic updates",
|
|
177
|
+
suggestion: "Use useOptimistic hook for instant UI feedback",
|
|
178
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
179
|
+
examples: {
|
|
180
|
+
bad: `function TodoList({ todos }) {
|
|
181
|
+
async function addTodo(formData) {
|
|
182
|
+
'use server';
|
|
183
|
+
await db.todos.create(formData);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<form action={addTodo}>
|
|
188
|
+
<input name="title" />
|
|
189
|
+
<button>Add</button> {/* Waits for server response */}
|
|
190
|
+
</form>
|
|
191
|
+
);
|
|
192
|
+
}`,
|
|
193
|
+
good: `function TodoList({ todos }) {
|
|
194
|
+
const [optimisticTodos, addOptimistic] = useOptimistic(
|
|
195
|
+
todos,
|
|
196
|
+
(state, newTodo) => [...state, { ...newTodo, pending: true }]
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
async function addTodo(formData) {
|
|
200
|
+
addOptimistic({ title: formData.get('title') });
|
|
201
|
+
await createTodoAction(formData);
|
|
202
|
+
}
|
|
203
|
+
}`
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "advanced-server-actions-inline",
|
|
208
|
+
category: "advanced",
|
|
209
|
+
impact: "LOW-MEDIUM",
|
|
210
|
+
// Detects: multiple inline 'use server' functions
|
|
211
|
+
pattern: /(?:async\s+function\s+\w+[^{]*\{[^}]*['"]use server['"]\s*;[\s\S]*?){2,}/,
|
|
212
|
+
message: "Multiple inline Server Actions may hurt maintainability",
|
|
213
|
+
suggestion: "Extract Server Actions to separate actions.ts file",
|
|
214
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
215
|
+
examples: {
|
|
216
|
+
bad: `export default function Page() {
|
|
217
|
+
async function createPost() {
|
|
218
|
+
'use server';
|
|
219
|
+
// action 1
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function deletePost() {
|
|
223
|
+
'use server';
|
|
224
|
+
// action 2
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function updatePost() {
|
|
228
|
+
'use server';
|
|
229
|
+
// action 3
|
|
230
|
+
}
|
|
231
|
+
}`,
|
|
232
|
+
good: `// app/posts/actions.ts
|
|
233
|
+
'use server'
|
|
234
|
+
|
|
235
|
+
export async function createPost(formData) { ... }
|
|
236
|
+
export async function deletePost(id) { ... }
|
|
237
|
+
export async function updatePost(id, formData) { ... }
|
|
238
|
+
|
|
239
|
+
// app/posts/page.tsx
|
|
240
|
+
import { createPost, deletePost } from './actions';`
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "advanced-partial-prerendering",
|
|
245
|
+
category: "advanced",
|
|
246
|
+
impact: "LOW-MEDIUM",
|
|
247
|
+
// Detects: page with mix of static and dynamic content
|
|
248
|
+
pattern: /(?:export\s+const\s+revalidate|cache:\s*['"]no-store['"])[\s\S]*?(?:<[A-Z]\w+|<div)/,
|
|
249
|
+
antiPattern: /Suspense|experimental_ppr/i,
|
|
250
|
+
message: "Page with mixed content may benefit from Partial Prerendering",
|
|
251
|
+
suggestion: "Enable PPR and wrap dynamic parts with Suspense",
|
|
252
|
+
fileFilter: /page\.(tsx?|jsx?)$/,
|
|
253
|
+
examples: {
|
|
254
|
+
bad: `// Entire page is dynamic
|
|
255
|
+
export const dynamic = 'force-dynamic';
|
|
256
|
+
|
|
257
|
+
export default async function Page() {
|
|
258
|
+
const user = await getUser(); // Dynamic
|
|
259
|
+
return (
|
|
260
|
+
<div>
|
|
261
|
+
<StaticHeader /> {/* Could be prerendered */}
|
|
262
|
+
<UserProfile user={user} />
|
|
263
|
+
<StaticFooter /> {/* Could be prerendered */}
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}`,
|
|
267
|
+
good: `// next.config.js: experimental: { ppr: true }
|
|
268
|
+
|
|
269
|
+
export default function Page() {
|
|
270
|
+
return (
|
|
271
|
+
<div>
|
|
272
|
+
<StaticHeader /> {/* Prerendered */}
|
|
273
|
+
<Suspense fallback={<ProfileSkeleton />}>
|
|
274
|
+
<UserProfile /> {/* Streams in */}
|
|
275
|
+
</Suspense>
|
|
276
|
+
<StaticFooter /> {/* Prerendered */}
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}`
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "advanced-use-transition",
|
|
284
|
+
category: "advanced",
|
|
285
|
+
impact: "LOW-MEDIUM",
|
|
286
|
+
// Detects: heavy state update without useTransition
|
|
287
|
+
pattern: /set\w+\s*\(\s*(?:items|data|list|results|filtered|sorted)[\s\S]{0,50}(?:filter|map|sort)\s*\(/,
|
|
288
|
+
antiPattern: /useTransition|startTransition/,
|
|
289
|
+
message: "Heavy state update may block UI - consider useTransition",
|
|
290
|
+
suggestion: "Wrap non-urgent updates with startTransition for responsive UI",
|
|
291
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
292
|
+
examples: {
|
|
293
|
+
bad: `function SearchResults({ items }) {
|
|
294
|
+
const [query, setQuery] = useState('');
|
|
295
|
+
const [filteredItems, setFilteredItems] = useState(items);
|
|
296
|
+
|
|
297
|
+
function handleSearch(e) {
|
|
298
|
+
const q = e.target.value;
|
|
299
|
+
setQuery(q);
|
|
300
|
+
// Expensive filter blocks typing
|
|
301
|
+
setFilteredItems(items.filter(item =>
|
|
302
|
+
item.name.toLowerCase().includes(q.toLowerCase())
|
|
303
|
+
));
|
|
304
|
+
}
|
|
305
|
+
}`,
|
|
306
|
+
good: `function SearchResults({ items }) {
|
|
307
|
+
const [query, setQuery] = useState('');
|
|
308
|
+
const [filteredItems, setFilteredItems] = useState(items);
|
|
309
|
+
const [isPending, startTransition] = useTransition();
|
|
310
|
+
|
|
311
|
+
function handleSearch(e) {
|
|
312
|
+
const q = e.target.value;
|
|
313
|
+
setQuery(q); // Urgent: update input immediately
|
|
314
|
+
|
|
315
|
+
startTransition(() => {
|
|
316
|
+
// Non-urgent: can be interrupted
|
|
317
|
+
setFilteredItems(items.filter(item =>
|
|
318
|
+
item.name.toLowerCase().includes(q.toLowerCase())
|
|
319
|
+
));
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}`
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/async.ts
|
|
2
|
+
// Async/Await Anti-patterns (Waterfall Detection)
|
|
3
|
+
/**
|
|
4
|
+
* Async rules detect common async/await anti-patterns that cause
|
|
5
|
+
* waterfall requests and unnecessary wait times.
|
|
6
|
+
*
|
|
7
|
+
* Based on Vercel agent-skills react-best-practices
|
|
8
|
+
*/
|
|
9
|
+
export const ASYNC_RULES = [
|
|
10
|
+
{
|
|
11
|
+
id: "async-sequential-await",
|
|
12
|
+
category: "async",
|
|
13
|
+
impact: "CRITICAL",
|
|
14
|
+
// Detects: await foo(); await bar(); (sequential awaits on separate lines)
|
|
15
|
+
pattern: /await\s+\w+\([^)]*\)\s*;?\s*\n\s*(?:const\s+\w+\s*=\s*)?await\s+\w+\(/,
|
|
16
|
+
message: "Sequential await statements cause waterfall requests",
|
|
17
|
+
suggestion: "Use Promise.all() or Promise.allSettled() for parallel execution",
|
|
18
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
19
|
+
examples: {
|
|
20
|
+
bad: `const user = await getUser(id);
|
|
21
|
+
const posts = await getPosts(userId);`,
|
|
22
|
+
good: `const [user, posts] = await Promise.all([
|
|
23
|
+
getUser(id),
|
|
24
|
+
getPosts(userId)
|
|
25
|
+
]);`
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "async-await-in-loop",
|
|
30
|
+
category: "async",
|
|
31
|
+
impact: "CRITICAL",
|
|
32
|
+
// Detects: for/while loops with await inside
|
|
33
|
+
pattern: /(?:for|while)\s*\([^)]*\)\s*\{[^}]*await\s+/,
|
|
34
|
+
message: "await inside loop causes N sequential requests",
|
|
35
|
+
suggestion: "Use Promise.all() with map() for parallel execution",
|
|
36
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
37
|
+
examples: {
|
|
38
|
+
bad: `for (const id of ids) {
|
|
39
|
+
const data = await fetch(id);
|
|
40
|
+
}`,
|
|
41
|
+
good: `const results = await Promise.all(
|
|
42
|
+
ids.map(id => fetch(id))
|
|
43
|
+
);`
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "async-defer-await",
|
|
48
|
+
category: "async",
|
|
49
|
+
impact: "HIGH",
|
|
50
|
+
// Detects: await before conditional that might not need the result
|
|
51
|
+
pattern: /const\s+\w+\s*=\s*await\s+\w+\([^)]*\)[\s\S]{0,100}if\s*\([^)]*!\s*\w+/,
|
|
52
|
+
message: "await executed before conditional check may be unnecessary",
|
|
53
|
+
suggestion: "Move await inside the branch that actually needs the data",
|
|
54
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
55
|
+
examples: {
|
|
56
|
+
bad: `const user = await getUser();
|
|
57
|
+
if (!needsUser) return;
|
|
58
|
+
// use user`,
|
|
59
|
+
good: `if (!needsUser) return;
|
|
60
|
+
const user = await getUser();
|
|
61
|
+
// use user`
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "async-no-parallel-fetch",
|
|
66
|
+
category: "async",
|
|
67
|
+
impact: "HIGH",
|
|
68
|
+
// Detects: multiple fetch calls without Promise.all
|
|
69
|
+
pattern: /fetch\([^)]+\)[\s\S]{0,50}fetch\([^)]+\)/,
|
|
70
|
+
antiPattern: /Promise\.all/,
|
|
71
|
+
message: "Multiple fetch calls may be running sequentially",
|
|
72
|
+
suggestion: "Consider Promise.all() if fetches are independent",
|
|
73
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
74
|
+
examples: {
|
|
75
|
+
bad: `const res1 = await fetch('/api/users');
|
|
76
|
+
const res2 = await fetch('/api/posts');`,
|
|
77
|
+
good: `const [res1, res2] = await Promise.all([
|
|
78
|
+
fetch('/api/users'),
|
|
79
|
+
fetch('/api/posts')
|
|
80
|
+
]);`
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "async-use-effect-async",
|
|
85
|
+
category: "async",
|
|
86
|
+
impact: "MEDIUM-HIGH",
|
|
87
|
+
// Detects: async function directly in useEffect
|
|
88
|
+
pattern: /useEffect\s*\(\s*async\s*\(/,
|
|
89
|
+
message: "useEffect callback cannot be async directly",
|
|
90
|
+
suggestion: "Define async function inside useEffect and call it",
|
|
91
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
92
|
+
examples: {
|
|
93
|
+
bad: `useEffect(async () => {
|
|
94
|
+
const data = await fetchData();
|
|
95
|
+
}, []);`,
|
|
96
|
+
good: `useEffect(() => {
|
|
97
|
+
const fetchAndSet = async () => {
|
|
98
|
+
const data = await fetchData();
|
|
99
|
+
};
|
|
100
|
+
fetchAndSet();
|
|
101
|
+
}, []);`
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
];
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/bundle.ts
|
|
2
|
+
// Bundle Size Anti-patterns (Barrel Imports, Tree Shaking)
|
|
3
|
+
/**
|
|
4
|
+
* Bundle rules detect import patterns that increase bundle size
|
|
5
|
+
* and hurt initial load performance.
|
|
6
|
+
*
|
|
7
|
+
* Based on Vercel agent-skills react-best-practices
|
|
8
|
+
*/
|
|
9
|
+
export const BUNDLE_RULES = [
|
|
10
|
+
{
|
|
11
|
+
id: "bundle-barrel-import",
|
|
12
|
+
category: "bundle",
|
|
13
|
+
impact: "CRITICAL",
|
|
14
|
+
// Detects: import { x } from '@/components' (barrel import from index)
|
|
15
|
+
pattern: /import\s+\{[^}]+\}\s+from\s+['"][^'"]*\/(?:components|utils|lib|hooks)['"](?!\/)$/m,
|
|
16
|
+
message: "Barrel import may include entire module tree in bundle",
|
|
17
|
+
suggestion: "Import directly from the specific file: import { X } from '@/components/X'",
|
|
18
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
19
|
+
examples: {
|
|
20
|
+
bad: `import { Button, Input } from '@/components';`,
|
|
21
|
+
good: `import { Button } from '@/components/Button';
|
|
22
|
+
import { Input } from '@/components/Input';`
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "bundle-lodash-full",
|
|
27
|
+
category: "bundle",
|
|
28
|
+
impact: "CRITICAL",
|
|
29
|
+
// Detects: import _ from 'lodash' or import { x } from 'lodash'
|
|
30
|
+
pattern: /import\s+(?:_|\{[^}]+\})\s+from\s+['"]lodash['"]/,
|
|
31
|
+
antiPattern: /lodash\//,
|
|
32
|
+
message: "Full lodash import adds ~70KB to bundle",
|
|
33
|
+
suggestion: "Use lodash-es or import specific functions: import debounce from 'lodash/debounce'",
|
|
34
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
35
|
+
examples: {
|
|
36
|
+
bad: `import { debounce, throttle } from 'lodash';`,
|
|
37
|
+
good: `import debounce from 'lodash/debounce';
|
|
38
|
+
import throttle from 'lodash/throttle';`
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "bundle-moment",
|
|
43
|
+
category: "bundle",
|
|
44
|
+
impact: "HIGH",
|
|
45
|
+
// Detects: import moment from 'moment'
|
|
46
|
+
pattern: /import\s+\w+\s+from\s+['"]moment['"]/,
|
|
47
|
+
message: "moment.js is 300KB+ with locales",
|
|
48
|
+
suggestion: "Use date-fns or dayjs instead (much smaller bundle)",
|
|
49
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
50
|
+
examples: {
|
|
51
|
+
bad: `import moment from 'moment';`,
|
|
52
|
+
good: `import { format, parseISO } from 'date-fns';`
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "bundle-no-dynamic-import",
|
|
57
|
+
category: "bundle",
|
|
58
|
+
impact: "MEDIUM-HIGH",
|
|
59
|
+
// Detects: large component imports that could be dynamic
|
|
60
|
+
pattern: /import\s+\w+\s+from\s+['"][^'"]*(?:Modal|Dialog|Chart|Editor|Map)['"]/,
|
|
61
|
+
antiPattern: /dynamic|lazy/,
|
|
62
|
+
message: "Large component imported statically",
|
|
63
|
+
suggestion: "Use dynamic import for components not needed on initial render",
|
|
64
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
65
|
+
examples: {
|
|
66
|
+
bad: `import ChartComponent from './ChartComponent';`,
|
|
67
|
+
good: `const ChartComponent = dynamic(() => import('./ChartComponent'), {
|
|
68
|
+
loading: () => <Loading />
|
|
69
|
+
});`
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "bundle-icon-library",
|
|
74
|
+
category: "bundle",
|
|
75
|
+
impact: "MEDIUM-HIGH",
|
|
76
|
+
// Detects: import * from icon libraries
|
|
77
|
+
pattern: /import\s+\*\s+as\s+\w+\s+from\s+['"](?:@heroicons|lucide-react|react-icons)['"]/,
|
|
78
|
+
message: "Importing entire icon library increases bundle size",
|
|
79
|
+
suggestion: "Import only specific icons: import { IconName } from 'library/category'",
|
|
80
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
81
|
+
examples: {
|
|
82
|
+
bad: `import * as Icons from 'lucide-react';`,
|
|
83
|
+
good: `import { Search, Menu } from 'lucide-react';`
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "bundle-dev-dependency",
|
|
88
|
+
category: "bundle",
|
|
89
|
+
impact: "HIGH",
|
|
90
|
+
// Detects: imports that are typically dev-only
|
|
91
|
+
pattern: /import\s+.*from\s+['"](?:faker|@faker-js|msw|@testing-library)['"]/,
|
|
92
|
+
message: "Development dependency may be included in production bundle",
|
|
93
|
+
suggestion: "Ensure this import is only in test/mock files or use dynamic import with env check",
|
|
94
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
95
|
+
examples: {
|
|
96
|
+
bad: `import { faker } from '@faker-js/faker';`,
|
|
97
|
+
good: `// Only in *.test.ts or *.mock.ts files
|
|
98
|
+
import { faker } from '@faker-js/faker';`
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
];
|