@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,186 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/client.ts
|
|
2
|
+
// Client Component Anti-patterns (use client, hydration, SSR)
|
|
3
|
+
/**
|
|
4
|
+
* Client rules detect issues with Client Components including
|
|
5
|
+
* overuse of 'use client', hydration mismatches, and SSR problems.
|
|
6
|
+
*/
|
|
7
|
+
export const CLIENT_RULES = [
|
|
8
|
+
{
|
|
9
|
+
id: "client-use-client-overuse",
|
|
10
|
+
category: "client",
|
|
11
|
+
impact: "CRITICAL",
|
|
12
|
+
// Detects: 'use client' in files with only static content or simple props
|
|
13
|
+
pattern: /['"]use client['"][\s\S]*?export\s+(?:default\s+)?function\s+\w+\([^)]*\)\s*\{[\s\S]*?return\s*\(/,
|
|
14
|
+
antiPattern: /useState|useEffect|useReducer|useContext|onClick|onChange|onSubmit|addEventListener/,
|
|
15
|
+
message: "'use client' may be unnecessary - no client-side interactivity detected",
|
|
16
|
+
suggestion: "Remove 'use client' if component doesn't use hooks or event handlers",
|
|
17
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
18
|
+
examples: {
|
|
19
|
+
bad: `'use client'
|
|
20
|
+
export function StaticCard({ title, description }) {
|
|
21
|
+
return (
|
|
22
|
+
<div>
|
|
23
|
+
<h2>{title}</h2>
|
|
24
|
+
<p>{description}</p>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}`,
|
|
28
|
+
good: `// No 'use client' needed for static components
|
|
29
|
+
export function StaticCard({ title, description }) {
|
|
30
|
+
return (
|
|
31
|
+
<div>
|
|
32
|
+
<h2>{title}</h2>
|
|
33
|
+
<p>{description}</p>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}`
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "client-no-children-prop",
|
|
41
|
+
category: "client",
|
|
42
|
+
impact: "LOW-MEDIUM",
|
|
43
|
+
// Detects: Client component that wraps content but doesn't use children
|
|
44
|
+
pattern: /['"]use client['"][\s\S]*?export\s+(?:default\s+)?function\s+\w+\([^)]*\)\s*\{[\s\S]*?<[A-Z]\w+/,
|
|
45
|
+
antiPattern: /children|{props\.children}|\{children\}/,
|
|
46
|
+
message: "Client Component wrapper may benefit from children prop pattern",
|
|
47
|
+
suggestion: "Use children prop to keep nested Server Components on server",
|
|
48
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
49
|
+
examples: {
|
|
50
|
+
bad: `'use client'
|
|
51
|
+
export function AnimatedSection({ content }) {
|
|
52
|
+
const ref = useRef();
|
|
53
|
+
// Animation logic...
|
|
54
|
+
return <section ref={ref}>{content}</section>;
|
|
55
|
+
}`,
|
|
56
|
+
good: `'use client'
|
|
57
|
+
export function AnimatedSection({ children }) {
|
|
58
|
+
const ref = useRef();
|
|
59
|
+
// Animation logic...
|
|
60
|
+
return <section ref={ref}>{children}</section>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Usage: children stay as Server Components
|
|
64
|
+
<AnimatedSection>
|
|
65
|
+
<ServerRenderedContent />
|
|
66
|
+
</AnimatedSection>`
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "client-window-check-missing",
|
|
71
|
+
category: "client",
|
|
72
|
+
impact: "CRITICAL",
|
|
73
|
+
// Detects: direct window/document access without typeof check
|
|
74
|
+
pattern: /(?:window\.|document\.)(?!addEventListener)(?:\w+)/,
|
|
75
|
+
antiPattern: /typeof\s+(?:window|document)\s*[!=]==?\s*['"]undefined['"]/,
|
|
76
|
+
message: "Direct window/document access may cause SSR errors",
|
|
77
|
+
suggestion: "Check typeof window !== 'undefined' or use useEffect for client-only code",
|
|
78
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
79
|
+
examples: {
|
|
80
|
+
bad: `function Component() {
|
|
81
|
+
const width = window.innerWidth;
|
|
82
|
+
return <div style={{ width }} />;
|
|
83
|
+
}`,
|
|
84
|
+
good: `function Component() {
|
|
85
|
+
const [width, setWidth] = useState(0);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
setWidth(window.innerWidth);
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
return <div style={{ width }} />;
|
|
92
|
+
}`
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "client-useeffect-data-fetch",
|
|
97
|
+
category: "client",
|
|
98
|
+
impact: "MEDIUM-HIGH",
|
|
99
|
+
// Detects: useEffect with fetch or data loading
|
|
100
|
+
pattern: /useEffect\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*\{[\s\S]*?(?:fetch\(|axios\.|useSWR|useQuery)/,
|
|
101
|
+
message: "Data fetching in useEffect causes client-side waterfall",
|
|
102
|
+
suggestion: "Prefer Server Components for data fetching, or use React Query/SWR with suspense",
|
|
103
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
104
|
+
examples: {
|
|
105
|
+
bad: `'use client'
|
|
106
|
+
function UserProfile({ id }) {
|
|
107
|
+
const [user, setUser] = useState(null);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
fetch(\`/api/users/\${id}\`)
|
|
111
|
+
.then(res => res.json())
|
|
112
|
+
.then(setUser);
|
|
113
|
+
}, [id]);
|
|
114
|
+
|
|
115
|
+
return user ? <div>{user.name}</div> : <Loading />;
|
|
116
|
+
}`,
|
|
117
|
+
good: `// Server Component (preferred)
|
|
118
|
+
async function UserProfile({ id }) {
|
|
119
|
+
const user = await getUser(id);
|
|
120
|
+
return <div>{user.name}</div>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Or with React Query suspense
|
|
124
|
+
const { data } = useSuspenseQuery({
|
|
125
|
+
queryKey: ['user', id],
|
|
126
|
+
queryFn: () => getUser(id)
|
|
127
|
+
});`
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "client-heavy-import",
|
|
132
|
+
category: "client",
|
|
133
|
+
impact: "MEDIUM-HIGH",
|
|
134
|
+
// Detects: large libraries imported in 'use client' files
|
|
135
|
+
pattern: /['"]use client['"][\s\S]*?import\s+.*from\s+['"](?:chart\.js|d3|three|@react-three|gsap|framer-motion|@dnd-kit)['"]/,
|
|
136
|
+
antiPattern: /dynamic\s*\(/,
|
|
137
|
+
message: "Heavy library imported in Client Component increases bundle size",
|
|
138
|
+
suggestion: "Use dynamic import with { ssr: false } for heavy client libraries",
|
|
139
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
140
|
+
examples: {
|
|
141
|
+
bad: `'use client'
|
|
142
|
+
import { Chart } from 'chart.js';
|
|
143
|
+
import { motion } from 'framer-motion';`,
|
|
144
|
+
good: `'use client'
|
|
145
|
+
import dynamic from 'next/dynamic';
|
|
146
|
+
|
|
147
|
+
const Chart = dynamic(
|
|
148
|
+
() => import('chart.js').then(mod => mod.Chart),
|
|
149
|
+
{ ssr: false }
|
|
150
|
+
);`
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "client-state-url-sync",
|
|
155
|
+
category: "client",
|
|
156
|
+
impact: "LOW-MEDIUM",
|
|
157
|
+
// Detects: useState for filter/sort/pagination without URL sync
|
|
158
|
+
pattern: /const\s+\[\s*(?:filter|sort|page|query|search|tab)\w*\s*,/,
|
|
159
|
+
antiPattern: /useSearchParams|useRouter|router\.push|searchParams/,
|
|
160
|
+
message: "UI state may benefit from URL synchronization",
|
|
161
|
+
suggestion: "Consider syncing filter/sort/pagination state with URL for shareability",
|
|
162
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
163
|
+
examples: {
|
|
164
|
+
bad: `'use client'
|
|
165
|
+
function ProductList() {
|
|
166
|
+
const [sortBy, setSortBy] = useState('price');
|
|
167
|
+
const [filterCategory, setFilterCategory] = useState('all');
|
|
168
|
+
// State lost on refresh, can't share URL
|
|
169
|
+
}`,
|
|
170
|
+
good: `'use client'
|
|
171
|
+
function ProductList() {
|
|
172
|
+
const searchParams = useSearchParams();
|
|
173
|
+
const router = useRouter();
|
|
174
|
+
|
|
175
|
+
const sortBy = searchParams.get('sort') || 'price';
|
|
176
|
+
const filterCategory = searchParams.get('category') || 'all';
|
|
177
|
+
|
|
178
|
+
const updateParams = (key, value) => {
|
|
179
|
+
const params = new URLSearchParams(searchParams);
|
|
180
|
+
params.set(key, value);
|
|
181
|
+
router.push(\`?\${params.toString()}\`);
|
|
182
|
+
};
|
|
183
|
+
}`
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/index.ts
|
|
2
|
+
// Rule Registry and Loader
|
|
3
|
+
import { ASYNC_RULES } from "./async.js";
|
|
4
|
+
import { BUNDLE_RULES } from "./bundle.js";
|
|
5
|
+
import { SERVER_RULES } from "./server.js";
|
|
6
|
+
import { RENDERING_RULES } from "./rendering.js";
|
|
7
|
+
import { CLIENT_RULES } from "./client.js";
|
|
8
|
+
import { RERENDER_RULES } from "./rerender.js";
|
|
9
|
+
import { ADVANCED_RULES } from "./advanced.js";
|
|
10
|
+
import { JS_RULES } from "./js.js";
|
|
11
|
+
// ============================================================
|
|
12
|
+
// Rule Registry
|
|
13
|
+
// ============================================================
|
|
14
|
+
/**
|
|
15
|
+
* All available rules organized by category
|
|
16
|
+
*/
|
|
17
|
+
export const RULE_REGISTRY = {
|
|
18
|
+
async: ASYNC_RULES,
|
|
19
|
+
bundle: BUNDLE_RULES,
|
|
20
|
+
server: SERVER_RULES,
|
|
21
|
+
rendering: RENDERING_RULES,
|
|
22
|
+
client: CLIENT_RULES,
|
|
23
|
+
rerender: RERENDER_RULES,
|
|
24
|
+
advanced: ADVANCED_RULES,
|
|
25
|
+
js: JS_RULES
|
|
26
|
+
};
|
|
27
|
+
// ============================================================
|
|
28
|
+
// Rule Loader Functions
|
|
29
|
+
// ============================================================
|
|
30
|
+
/**
|
|
31
|
+
* Get all rules from all categories
|
|
32
|
+
*/
|
|
33
|
+
export function getAllRules() {
|
|
34
|
+
return Object.values(RULE_REGISTRY).flat();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get rules for specific categories
|
|
38
|
+
*/
|
|
39
|
+
export function getRulesByCategories(categories) {
|
|
40
|
+
return categories.flatMap(cat => RULE_REGISTRY[cat] || []);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get a specific rule by ID
|
|
44
|
+
*/
|
|
45
|
+
export function getRuleById(id) {
|
|
46
|
+
return getAllRules().find(r => r.id === id);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get rule count by category
|
|
50
|
+
*/
|
|
51
|
+
export function getRuleCountByCategory() {
|
|
52
|
+
const counts = {};
|
|
53
|
+
for (const [cat, rules] of Object.entries(RULE_REGISTRY)) {
|
|
54
|
+
counts[cat] = rules.length;
|
|
55
|
+
}
|
|
56
|
+
return counts;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get total rule count
|
|
60
|
+
*/
|
|
61
|
+
export function getTotalRuleCount() {
|
|
62
|
+
return getAllRules().length;
|
|
63
|
+
}
|
|
64
|
+
// ============================================================
|
|
65
|
+
// Re-export Individual Rule Sets
|
|
66
|
+
// ============================================================
|
|
67
|
+
export { ASYNC_RULES } from "./async.js";
|
|
68
|
+
export { BUNDLE_RULES } from "./bundle.js";
|
|
69
|
+
export { SERVER_RULES } from "./server.js";
|
|
70
|
+
export { RENDERING_RULES } from "./rendering.js";
|
|
71
|
+
export { CLIENT_RULES } from "./client.js";
|
|
72
|
+
export { RERENDER_RULES } from "./rerender.js";
|
|
73
|
+
export { ADVANCED_RULES } from "./advanced.js";
|
|
74
|
+
export { JS_RULES } from "./js.js";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/js.ts
|
|
2
|
+
// General JavaScript Anti-patterns (Bundle, Safety, Performance)
|
|
3
|
+
/**
|
|
4
|
+
* JS rules detect general JavaScript anti-patterns that affect
|
|
5
|
+
* bundle size, safety, and runtime performance.
|
|
6
|
+
*/
|
|
7
|
+
export const JS_RULES = [
|
|
8
|
+
{
|
|
9
|
+
id: "js-lodash-full-import",
|
|
10
|
+
category: "js",
|
|
11
|
+
impact: "CRITICAL",
|
|
12
|
+
// Detects: import _ from 'lodash' or import { x } from 'lodash'
|
|
13
|
+
pattern: /import\s+(?:_|\{[^}]+\})\s+from\s+['"]lodash['"]/,
|
|
14
|
+
antiPattern: /lodash\/|lodash-es/,
|
|
15
|
+
message: "Full lodash import adds ~70KB to bundle",
|
|
16
|
+
suggestion: "Use lodash-es or import from 'lodash/function'",
|
|
17
|
+
fileFilter: /\.(tsx?|jsx?|mjs)$/,
|
|
18
|
+
examples: {
|
|
19
|
+
bad: `import { debounce, throttle } from 'lodash';
|
|
20
|
+
import _ from 'lodash';`,
|
|
21
|
+
good: `import debounce from 'lodash/debounce';
|
|
22
|
+
import throttle from 'lodash/throttle';
|
|
23
|
+
// Or use lodash-es for tree-shaking
|
|
24
|
+
import { debounce } from 'lodash-es';`
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "js-moment-usage",
|
|
29
|
+
category: "js",
|
|
30
|
+
impact: "MEDIUM-HIGH",
|
|
31
|
+
// Detects: import moment from 'moment'
|
|
32
|
+
pattern: /import\s+\w*\s*(?:,\s*\{[^}]*\})?\s*from\s+['"]moment['"]/,
|
|
33
|
+
message: "moment.js is 300KB+ with locales - consider alternatives",
|
|
34
|
+
suggestion: "Use date-fns (~10KB), dayjs (~2KB), or native Intl API",
|
|
35
|
+
fileFilter: /\.(tsx?|jsx?|mjs)$/,
|
|
36
|
+
examples: {
|
|
37
|
+
bad: `import moment from 'moment';
|
|
38
|
+
const formatted = moment(date).format('YYYY-MM-DD');`,
|
|
39
|
+
good: `// date-fns (tree-shakeable)
|
|
40
|
+
import { format } from 'date-fns';
|
|
41
|
+
const formatted = format(date, 'yyyy-MM-dd');
|
|
42
|
+
|
|
43
|
+
// dayjs (2KB)
|
|
44
|
+
import dayjs from 'dayjs';
|
|
45
|
+
const formatted = dayjs(date).format('YYYY-MM-DD');
|
|
46
|
+
|
|
47
|
+
// Native (0KB)
|
|
48
|
+
const formatted = new Intl.DateTimeFormat('en-CA').format(date);`
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "js-async-loop",
|
|
53
|
+
category: "js",
|
|
54
|
+
impact: "CRITICAL",
|
|
55
|
+
// Detects: for/forEach with await inside
|
|
56
|
+
pattern: /(?:for\s*\(|\.forEach\s*\()[^)]*\)\s*(?:=>)?\s*\{?[^}]*await\s+/,
|
|
57
|
+
message: "await in loop causes sequential execution (N round trips)",
|
|
58
|
+
suggestion: "Use Promise.all() with map() for parallel execution",
|
|
59
|
+
fileFilter: /\.(tsx?|jsx?|mjs)$/,
|
|
60
|
+
examples: {
|
|
61
|
+
bad: `// Sequential: 10 items = 10 round trips
|
|
62
|
+
for (const id of ids) {
|
|
63
|
+
const result = await fetchItem(id);
|
|
64
|
+
results.push(result);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Same problem with forEach
|
|
68
|
+
ids.forEach(async (id) => {
|
|
69
|
+
await processItem(id);
|
|
70
|
+
});`,
|
|
71
|
+
good: `// Parallel: 10 items = 1 round trip (batched)
|
|
72
|
+
const results = await Promise.all(
|
|
73
|
+
ids.map(id => fetchItem(id))
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// With concurrency limit if needed
|
|
77
|
+
import pLimit from 'p-limit';
|
|
78
|
+
const limit = pLimit(5);
|
|
79
|
+
const results = await Promise.all(
|
|
80
|
+
ids.map(id => limit(() => fetchItem(id)))
|
|
81
|
+
);`
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "js-console-log",
|
|
86
|
+
category: "js",
|
|
87
|
+
impact: "LOW-MEDIUM",
|
|
88
|
+
// Detects: console.log (but not console.error/warn)
|
|
89
|
+
pattern: /console\.log\s*\(/,
|
|
90
|
+
antiPattern: /\/\/.*console|\/\*[\s\S]*?console[\s\S]*?\*\/|debug|development/i,
|
|
91
|
+
message: "console.log may remain in production code",
|
|
92
|
+
suggestion: "Remove or replace with proper logging/debugging tools",
|
|
93
|
+
fileFilter: /\.(tsx?|jsx?|mjs)$/,
|
|
94
|
+
examples: {
|
|
95
|
+
bad: `function processData(data) {
|
|
96
|
+
console.log('data:', data); // Ships to production
|
|
97
|
+
return transform(data);
|
|
98
|
+
}`,
|
|
99
|
+
good: `// Use environment check
|
|
100
|
+
if (process.env.NODE_ENV === 'development') {
|
|
101
|
+
console.log('data:', data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Or use a logger that respects environment
|
|
105
|
+
import { logger } from '@/lib/logger';
|
|
106
|
+
logger.debug('data:', data);
|
|
107
|
+
|
|
108
|
+
// Or remove entirely for production`
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "js-json-parse-unsafe",
|
|
113
|
+
category: "js",
|
|
114
|
+
impact: "MEDIUM-HIGH",
|
|
115
|
+
// Detects: JSON.parse without try-catch
|
|
116
|
+
pattern: /(?:const|let|var)\s+\w+\s*=\s*JSON\.parse\s*\(/,
|
|
117
|
+
antiPattern: /try\s*\{|\.catch\(|safeJson|parseJSON/i,
|
|
118
|
+
message: "JSON.parse without error handling may crash on invalid input",
|
|
119
|
+
suggestion: "Wrap in try-catch or use safe parsing utility",
|
|
120
|
+
fileFilter: /\.(tsx?|jsx?|mjs)$/,
|
|
121
|
+
examples: {
|
|
122
|
+
bad: `function loadSettings() {
|
|
123
|
+
const stored = localStorage.getItem('settings');
|
|
124
|
+
const settings = JSON.parse(stored); // Crashes if null or invalid
|
|
125
|
+
return settings;
|
|
126
|
+
}`,
|
|
127
|
+
good: `function loadSettings() {
|
|
128
|
+
const stored = localStorage.getItem('settings');
|
|
129
|
+
try {
|
|
130
|
+
return stored ? JSON.parse(stored) : defaultSettings;
|
|
131
|
+
} catch {
|
|
132
|
+
console.error('Invalid settings JSON');
|
|
133
|
+
return defaultSettings;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Or create a utility
|
|
138
|
+
function safeJsonParse<T>(str: string | null, fallback: T): T {
|
|
139
|
+
if (!str) return fallback;
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(str);
|
|
142
|
+
} catch {
|
|
143
|
+
return fallback;
|
|
144
|
+
}
|
|
145
|
+
}`
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
];
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/rendering.ts
|
|
2
|
+
// Rendering Pattern Anti-patterns (Suspense, Streaming, Error Boundaries)
|
|
3
|
+
/**
|
|
4
|
+
* Rendering rules detect patterns related to React's concurrent features,
|
|
5
|
+
* Suspense boundaries, and streaming rendering in Next.js.
|
|
6
|
+
*/
|
|
7
|
+
export const RENDERING_RULES = [
|
|
8
|
+
{
|
|
9
|
+
id: "rendering-suspense-boundary",
|
|
10
|
+
category: "rendering",
|
|
11
|
+
impact: "MEDIUM-HIGH",
|
|
12
|
+
// Detects: async component without Suspense wrapper nearby
|
|
13
|
+
pattern: /(?:async\s+function|async\s+\()\s*\w*\s*\([^)]*\)\s*(?::\s*[^{]+)?\s*\{[\s\S]*?(?:await|fetch\()/,
|
|
14
|
+
antiPattern: /Suspense|loading\.tsx/,
|
|
15
|
+
message: "Async Server Component may lack Suspense boundary",
|
|
16
|
+
suggestion: "Wrap async components with <Suspense fallback={...}> or add loading.tsx",
|
|
17
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
18
|
+
examples: {
|
|
19
|
+
bad: `async function UserProfile() {
|
|
20
|
+
const user = await getUser();
|
|
21
|
+
return <div>{user.name}</div>;
|
|
22
|
+
}`,
|
|
23
|
+
good: `// In page.tsx
|
|
24
|
+
<Suspense fallback={<UserSkeleton />}>
|
|
25
|
+
<UserProfile />
|
|
26
|
+
</Suspense>
|
|
27
|
+
|
|
28
|
+
// Or add loading.tsx in same folder`
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "rendering-loading-ui",
|
|
33
|
+
category: "rendering",
|
|
34
|
+
impact: "LOW-MEDIUM",
|
|
35
|
+
// Detects: page.tsx with data fetching but no loading.tsx in hints
|
|
36
|
+
pattern: /export\s+(?:default\s+)?(?:async\s+)?function\s+(?:Page|default)[\s\S]*?(?:await|fetch\()/,
|
|
37
|
+
antiPattern: /loading\s*[=:]/,
|
|
38
|
+
message: "Page with data fetching may lack loading UI",
|
|
39
|
+
suggestion: "Create loading.tsx alongside page.tsx for automatic loading states",
|
|
40
|
+
fileFilter: /page\.(tsx?|jsx?)$/,
|
|
41
|
+
examples: {
|
|
42
|
+
bad: `// app/users/page.tsx only
|
|
43
|
+
export default async function Page() {
|
|
44
|
+
const users = await getUsers();
|
|
45
|
+
return <UserList users={users} />;
|
|
46
|
+
}`,
|
|
47
|
+
good: `// app/users/loading.tsx
|
|
48
|
+
export default function Loading() {
|
|
49
|
+
return <UserListSkeleton />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// app/users/page.tsx
|
|
53
|
+
export default async function Page() {
|
|
54
|
+
const users = await getUsers();
|
|
55
|
+
return <UserList users={users} />;
|
|
56
|
+
}`
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "rendering-streaming-disabled",
|
|
61
|
+
category: "rendering",
|
|
62
|
+
impact: "MEDIUM-HIGH",
|
|
63
|
+
// Detects: dynamic = 'force-static' or revalidate = 0 on pages with heavy data
|
|
64
|
+
pattern: /(?:export\s+const\s+dynamic\s*=\s*['"]force-static['"]|export\s+const\s+revalidate\s*=\s*0)[\s\S]*?(?:getMany|findAll|select\s*\*)/,
|
|
65
|
+
message: "Static rendering with heavy data fetching prevents streaming benefits",
|
|
66
|
+
suggestion: "Consider streaming with Suspense for large datasets",
|
|
67
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
68
|
+
examples: {
|
|
69
|
+
bad: `export const dynamic = 'force-static';
|
|
70
|
+
|
|
71
|
+
async function Page() {
|
|
72
|
+
const allPosts = await db.posts.findAll(); // Could be slow
|
|
73
|
+
}`,
|
|
74
|
+
good: `// Let Next.js stream the response
|
|
75
|
+
async function Page() {
|
|
76
|
+
return (
|
|
77
|
+
<Suspense fallback={<PostsSkeleton />}>
|
|
78
|
+
<PostsList />
|
|
79
|
+
</Suspense>
|
|
80
|
+
);
|
|
81
|
+
}`
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "rendering-nested-suspense",
|
|
86
|
+
category: "rendering",
|
|
87
|
+
impact: "LOW-MEDIUM",
|
|
88
|
+
// Detects: multiple async components at same level without individual Suspense
|
|
89
|
+
pattern: /<(?:[A-Z]\w+)\s*\/?>[\s\S]{0,50}<(?:[A-Z]\w+)\s*\/?>[\s\S]{0,50}<(?:[A-Z]\w+)\s*\/?>/,
|
|
90
|
+
antiPattern: /Suspense/,
|
|
91
|
+
message: "Multiple components may benefit from nested Suspense boundaries",
|
|
92
|
+
suggestion: "Use nested Suspense to stream independent sections progressively",
|
|
93
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
94
|
+
examples: {
|
|
95
|
+
bad: `// All components load together
|
|
96
|
+
<Page>
|
|
97
|
+
<Header />
|
|
98
|
+
<MainContent />
|
|
99
|
+
<Sidebar />
|
|
100
|
+
</Page>`,
|
|
101
|
+
good: `// Independent loading states
|
|
102
|
+
<Page>
|
|
103
|
+
<Header />
|
|
104
|
+
<Suspense fallback={<ContentSkeleton />}>
|
|
105
|
+
<MainContent />
|
|
106
|
+
</Suspense>
|
|
107
|
+
<Suspense fallback={<SidebarSkeleton />}>
|
|
108
|
+
<Sidebar />
|
|
109
|
+
</Suspense>
|
|
110
|
+
</Page>`
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "rendering-error-boundary",
|
|
115
|
+
category: "rendering",
|
|
116
|
+
impact: "MEDIUM-HIGH",
|
|
117
|
+
// Detects: async data fetching without error.tsx or ErrorBoundary
|
|
118
|
+
pattern: /(?:await\s+fetch|await\s+db\.|await\s+prisma\.)/,
|
|
119
|
+
antiPattern: /ErrorBoundary|error\.tsx|try\s*\{/,
|
|
120
|
+
message: "Data fetching may lack error boundary",
|
|
121
|
+
suggestion: "Add error.tsx or wrap with ErrorBoundary for graceful error handling",
|
|
122
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
123
|
+
examples: {
|
|
124
|
+
bad: `async function UserProfile() {
|
|
125
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
126
|
+
return <div>{user.name}</div>;
|
|
127
|
+
}`,
|
|
128
|
+
good: `// Add error.tsx in same folder
|
|
129
|
+
'use client'
|
|
130
|
+
export default function Error({ error, reset }) {
|
|
131
|
+
return (
|
|
132
|
+
<div>
|
|
133
|
+
<h2>Something went wrong!</h2>
|
|
134
|
+
<button onClick={() => reset()}>Try again</button>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}`
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: "rendering-sequential-fetch",
|
|
142
|
+
category: "rendering",
|
|
143
|
+
impact: "CRITICAL",
|
|
144
|
+
// Detects: multiple awaits for data fetching in sequence
|
|
145
|
+
pattern: /const\s+\w+\s*=\s*await\s+(?:fetch|db\.|prisma\.)[^;]+;\s*\n\s*const\s+\w+\s*=\s*await\s+(?:fetch|db\.|prisma\.)/,
|
|
146
|
+
message: "Sequential data fetching in Server Component causes waterfall",
|
|
147
|
+
suggestion: "Use Promise.all() for parallel fetching or split into separate components with Suspense",
|
|
148
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
149
|
+
examples: {
|
|
150
|
+
bad: `async function Dashboard() {
|
|
151
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
152
|
+
const posts = await db.posts.findMany({ where: { userId: id } });
|
|
153
|
+
const analytics = await fetch('/api/analytics');
|
|
154
|
+
}`,
|
|
155
|
+
good: `async function Dashboard() {
|
|
156
|
+
const [user, posts, analytics] = await Promise.all([
|
|
157
|
+
db.user.findUnique({ where: { id } }),
|
|
158
|
+
db.posts.findMany({ where: { userId: id } }),
|
|
159
|
+
fetch('/api/analytics')
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Or split with Suspense for progressive loading`
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
];
|