@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.
Files changed (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +269 -0
  3. package/build/auth/gate.js +225 -0
  4. package/build/auth/index.js +55 -0
  5. package/build/auth/public_key.js +27 -0
  6. package/build/auth/token_cache.js +122 -0
  7. package/build/auth/token_verifier.js +103 -0
  8. package/build/bootstrap/doctor.js +115 -0
  9. package/build/bootstrap/installer.js +673 -0
  10. package/build/bootstrap/lock.js +37 -0
  11. package/build/bootstrap/platform.js +26 -0
  12. package/build/bootstrap/registry.js +37 -0
  13. package/build/cache/index.js +147 -0
  14. package/build/cli.js +101 -0
  15. package/build/contracts.js +22 -0
  16. package/build/control_plane/gate.js +161 -0
  17. package/build/control_plane/index.js +6 -0
  18. package/build/dx/activity.js +139 -0
  19. package/build/engine.js +106 -0
  20. package/build/errors.js +171 -0
  21. package/build/generated/activate_input.js +2 -0
  22. package/build/generated/activate_output.js +57 -0
  23. package/build/generated/advisory_review_input.js +2 -0
  24. package/build/generated/advisory_review_output.js +35 -0
  25. package/build/generated/auth_token_file.js +2 -0
  26. package/build/generated/briefing_input.js +2 -0
  27. package/build/generated/briefing_output.js +2 -0
  28. package/build/generated/clinic_bridge_file.js +13 -0
  29. package/build/generated/contracts_bundle_info.js +5 -0
  30. package/build/generated/create_work_order_input.js +2 -0
  31. package/build/generated/create_work_order_output.js +2 -0
  32. package/build/generated/current_work_order_file.js +2 -0
  33. package/build/generated/doctor_input.js +2 -0
  34. package/build/generated/doctor_output.js +24 -0
  35. package/build/generated/execution_result.js +2 -0
  36. package/build/generated/execution_task.js +2 -0
  37. package/build/generated/export_output_input.js +2 -0
  38. package/build/generated/export_output_output.js +2 -0
  39. package/build/generated/finalize_work_input.js +2 -0
  40. package/build/generated/finalize_work_output.js +2 -0
  41. package/build/generated/gate_input.js +2 -0
  42. package/build/generated/gate_output.js +2 -0
  43. package/build/generated/gate_result_v1.js +2 -0
  44. package/build/generated/get_decision_input.js +2 -0
  45. package/build/generated/get_decision_output.js +13 -0
  46. package/build/generated/handoff_to_clinic.js +2 -0
  47. package/build/generated/index.js +75 -0
  48. package/build/generated/inspect_code_input.js +2 -0
  49. package/build/generated/inspect_code_output.js +13 -0
  50. package/build/generated/memory_retrieve_output.js +2 -0
  51. package/build/generated/memory_state_file.js +2 -0
  52. package/build/generated/memory_status_input.js +2 -0
  53. package/build/generated/memory_status_output.js +13 -0
  54. package/build/generated/memory_sync_input.js +2 -0
  55. package/build/generated/memory_sync_output.js +13 -0
  56. package/build/generated/plugin_result.js +2 -0
  57. package/build/generated/react_perf_check_patterns_input.js +2 -0
  58. package/build/generated/react_perf_check_patterns_output.js +2 -0
  59. package/build/generated/react_perf_generate_report_input.js +2 -0
  60. package/build/generated/react_perf_generate_report_output.js +2 -0
  61. package/build/generated/repair_plan_input.js +2 -0
  62. package/build/generated/repair_plan_output.js +2 -0
  63. package/build/generated/run_app_input.js +2 -0
  64. package/build/generated/run_app_output.js +2 -0
  65. package/build/generated/run_state_file.js +13 -0
  66. package/build/generated/scaffold_input.js +2 -0
  67. package/build/generated/scaffold_output.js +2 -0
  68. package/build/generated/search_oss_input.js +2 -0
  69. package/build/generated/search_oss_output.js +2 -0
  70. package/build/generated/selection_validation_result.js +2 -0
  71. package/build/generated/signal_agent_input.js +2 -0
  72. package/build/generated/spec_high_ask_queue_items_file.js +2 -0
  73. package/build/generated/spec_high_clinic_bridge_output.js +2 -0
  74. package/build/generated/spec_high_decision_draft_output.js +2 -0
  75. package/build/generated/spec_high_validate_output.js +2 -0
  76. package/build/generated/status_input.js +2 -0
  77. package/build/generated/status_output.js +2 -0
  78. package/build/generated/submit_decision_input.js +2 -0
  79. package/build/generated/submit_decision_output.js +2 -0
  80. package/build/generated/tool_error_output.js +2 -0
  81. package/build/generated/undo_last_task_input.js +2 -0
  82. package/build/generated/undo_last_task_output.js +2 -0
  83. package/build/generated/update_input.js +2 -0
  84. package/build/generated/update_output.js +2 -0
  85. package/build/generated/vibe_pm_inspection_result.js +2 -0
  86. package/build/generated/vibe_pm_report_markdown.js +2 -0
  87. package/build/generated/vibe_pm_verdict.js +2 -0
  88. package/build/generated/vibe_repo_config.js +2 -0
  89. package/build/generated/vibecoding_helper_answer_output.js +2 -0
  90. package/build/generated/vibecoding_helper_one_loop_selection_output.js +2 -0
  91. package/build/generated/vibecoding_helper_show_ask_queue_output.js +2 -0
  92. package/build/generated/work_order_v1.js +2 -0
  93. package/build/generated/zoekt_evidence_input.js +2 -0
  94. package/build/generated/zoekt_evidence_output.js +2 -0
  95. package/build/index.js +111 -0
  96. package/build/legacy_alias.js +65 -0
  97. package/build/local-mode/bash.js +61 -0
  98. package/build/local-mode/config.js +171 -0
  99. package/build/local-mode/git.js +33 -0
  100. package/build/local-mode/init.js +110 -0
  101. package/build/local-mode/paths.js +24 -0
  102. package/build/local-mode/templates.js +856 -0
  103. package/build/local-mode/work-order.js +41 -0
  104. package/build/resources/index.js +246 -0
  105. package/build/security/input-validator.js +119 -0
  106. package/build/security/path-policy.js +289 -0
  107. package/build/security/sandbox.js +228 -0
  108. package/build/tools/react_perf/check_patterns.js +172 -0
  109. package/build/tools/react_perf/generate_report.js +337 -0
  110. package/build/tools/react_perf/index.js +119 -0
  111. package/build/tools/react_perf/rules/advanced.js +325 -0
  112. package/build/tools/react_perf/rules/async.js +104 -0
  113. package/build/tools/react_perf/rules/bundle.js +101 -0
  114. package/build/tools/react_perf/rules/client.js +186 -0
  115. package/build/tools/react_perf/rules/index.js +74 -0
  116. package/build/tools/react_perf/rules/js.js +148 -0
  117. package/build/tools/react_perf/rules/rendering.js +166 -0
  118. package/build/tools/react_perf/rules/rerender.js +161 -0
  119. package/build/tools/react_perf/rules/server.js +141 -0
  120. package/build/tools/react_perf/types.js +127 -0
  121. package/build/tools/vibe_pm/activate.js +102 -0
  122. package/build/tools/vibe_pm/advisory_review.js +77 -0
  123. package/build/tools/vibe_pm/briefing.js +178 -0
  124. package/build/tools/vibe_pm/context.js +439 -0
  125. package/build/tools/vibe_pm/create_work_order.js +271 -0
  126. package/build/tools/vibe_pm/doc_status_gate.js +370 -0
  127. package/build/tools/vibe_pm/doctor.js +262 -0
  128. package/build/tools/vibe_pm/entity_gate/preflight.js +78 -0
  129. package/build/tools/vibe_pm/export_output.js +135 -0
  130. package/build/tools/vibe_pm/finalize_work.js +393 -0
  131. package/build/tools/vibe_pm/gate.js +33 -0
  132. package/build/tools/vibe_pm/get_decision.js +281 -0
  133. package/build/tools/vibe_pm/index.js +593 -0
  134. package/build/tools/vibe_pm/inspect_code.js +828 -0
  135. package/build/tools/vibe_pm/intent/generator.js +294 -0
  136. package/build/tools/vibe_pm/intent/index.js +5 -0
  137. package/build/tools/vibe_pm/intent/prompt_density.js +227 -0
  138. package/build/tools/vibe_pm/intent/types.js +70 -0
  139. package/build/tools/vibe_pm/intent/verifier.js +237 -0
  140. package/build/tools/vibe_pm/kce/doc_usage.js +51 -0
  141. package/build/tools/vibe_pm/kce/on_finalize.js +11 -0
  142. package/build/tools/vibe_pm/kce/preflight.js +232 -0
  143. package/build/tools/vibe_pm/local_memory.js +26 -0
  144. package/build/tools/vibe_pm/memory_status.js +82 -0
  145. package/build/tools/vibe_pm/memory_sync.js +134 -0
  146. package/build/tools/vibe_pm/modules/decision_snapshot.js +29 -0
  147. package/build/tools/vibe_pm/modules/ensure.js +100 -0
  148. package/build/tools/vibe_pm/modules/fingerprint.js +30 -0
  149. package/build/tools/vibe_pm/modules/fix_dependencies.js +394 -0
  150. package/build/tools/vibe_pm/modules/planning_v1.js +110 -0
  151. package/build/tools/vibe_pm/modules/repo_context.js +56 -0
  152. package/build/tools/vibe_pm/modules/research_v1.js +114 -0
  153. package/build/tools/vibe_pm/modules/skills_v1.js +100 -0
  154. package/build/tools/vibe_pm/pm_language.js +222 -0
  155. package/build/tools/vibe_pm/repair_plan.js +199 -0
  156. package/build/tools/vibe_pm/run_app.js +597 -0
  157. package/build/tools/vibe_pm/run_app_podman.js +64 -0
  158. package/build/tools/vibe_pm/scaffold.js +550 -0
  159. package/build/tools/vibe_pm/search_oss.js +124 -0
  160. package/build/tools/vibe_pm/status.js +153 -0
  161. package/build/tools/vibe_pm/submit_decision.js +87 -0
  162. package/build/tools/vibe_pm/system_design/issue_mapping.js +47 -0
  163. package/build/tools/vibe_pm/system_design/rulebook.js +112 -0
  164. package/build/tools/vibe_pm/system_design/semgrep.js +132 -0
  165. package/build/tools/vibe_pm/types.js +229 -0
  166. package/build/tools/vibe_pm/undo_last_task.js +163 -0
  167. package/build/tools/vibe_pm/update.js +146 -0
  168. package/build/tools/vibe_pm/zoekt_evidence.js +96 -0
  169. package/build/tools.js +269 -0
  170. package/build/version-check.js +239 -0
  171. package/build/vibe-cli.js +631 -0
  172. 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
+ ];