@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,161 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/rerender.ts
|
|
2
|
+
// Re-render Anti-patterns (Inline Props, Missing Memo, Context)
|
|
3
|
+
/**
|
|
4
|
+
* Rerender rules detect patterns that cause unnecessary re-renders
|
|
5
|
+
* including inline objects/functions and missing memoization.
|
|
6
|
+
*/
|
|
7
|
+
export const RERENDER_RULES = [
|
|
8
|
+
{
|
|
9
|
+
id: "rerender-inline-object",
|
|
10
|
+
category: "rerender",
|
|
11
|
+
impact: "MEDIUM-HIGH",
|
|
12
|
+
// Detects: inline object literal as prop (style={{}} excluded by antiPattern)
|
|
13
|
+
pattern: /<[A-Z]\w+[^>]*\s(?:options|config|data|params|settings|initialValue)=\{\{/,
|
|
14
|
+
message: "Inline object prop creates new reference on every render",
|
|
15
|
+
suggestion: "Extract object to useMemo or move outside component",
|
|
16
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
17
|
+
examples: {
|
|
18
|
+
bad: `function Parent() {
|
|
19
|
+
return <Chart options={{ responsive: true, scales: {...} }} />;
|
|
20
|
+
}`,
|
|
21
|
+
good: `function Parent() {
|
|
22
|
+
const chartOptions = useMemo(() => ({
|
|
23
|
+
responsive: true,
|
|
24
|
+
scales: {...}
|
|
25
|
+
}), []);
|
|
26
|
+
|
|
27
|
+
return <Chart options={chartOptions} />;
|
|
28
|
+
}`
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "rerender-inline-function",
|
|
33
|
+
category: "rerender",
|
|
34
|
+
impact: "MEDIUM-HIGH",
|
|
35
|
+
// Detects: inline arrow function as prop (excluding common safe patterns)
|
|
36
|
+
pattern: /<[A-Z]\w+[^>]*\s(?:on[A-Z]\w+|handler|callback|render\w*)=\{\s*\([^)]*\)\s*=>/,
|
|
37
|
+
antiPattern: /useCallback|memo\(/,
|
|
38
|
+
message: "Inline function prop creates new reference on every render",
|
|
39
|
+
suggestion: "Wrap with useCallback if passed to memoized child",
|
|
40
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
41
|
+
examples: {
|
|
42
|
+
bad: `function Parent() {
|
|
43
|
+
return <MemoizedList onItemClick={(id) => handleClick(id)} />;
|
|
44
|
+
}`,
|
|
45
|
+
good: `function Parent() {
|
|
46
|
+
const handleItemClick = useCallback((id) => {
|
|
47
|
+
handleClick(id);
|
|
48
|
+
}, [handleClick]);
|
|
49
|
+
|
|
50
|
+
return <MemoizedList onItemClick={handleItemClick} />;
|
|
51
|
+
}`
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "rerender-context-value",
|
|
56
|
+
category: "rerender",
|
|
57
|
+
impact: "CRITICAL",
|
|
58
|
+
// Detects: Context Provider with inline value object
|
|
59
|
+
pattern: /<\w+Context\.Provider\s+value=\{\{/,
|
|
60
|
+
message: "Inline context value causes all consumers to re-render",
|
|
61
|
+
suggestion: "Memoize context value or split into separate contexts",
|
|
62
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
63
|
+
examples: {
|
|
64
|
+
bad: `function AppProvider({ children }) {
|
|
65
|
+
const [user, setUser] = useState(null);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<AppContext.Provider value={{ user, setUser }}>
|
|
69
|
+
{children}
|
|
70
|
+
</AppContext.Provider>
|
|
71
|
+
);
|
|
72
|
+
}`,
|
|
73
|
+
good: `function AppProvider({ children }) {
|
|
74
|
+
const [user, setUser] = useState(null);
|
|
75
|
+
|
|
76
|
+
const value = useMemo(() => ({ user, setUser }), [user]);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<AppContext.Provider value={value}>
|
|
80
|
+
{children}
|
|
81
|
+
</AppContext.Provider>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Or split contexts
|
|
86
|
+
<UserContext.Provider value={user}>
|
|
87
|
+
<UserActionsContext.Provider value={setUser}>
|
|
88
|
+
{children}
|
|
89
|
+
</UserActionsContext.Provider>
|
|
90
|
+
</UserContext.Provider>`
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "rerender-missing-memo",
|
|
95
|
+
category: "rerender",
|
|
96
|
+
impact: "LOW-MEDIUM",
|
|
97
|
+
// Detects: Component receiving many props without memo
|
|
98
|
+
pattern: /(?:export\s+)?(?:function|const)\s+([A-Z]\w+)\s*[=:]\s*(?:function\s*)?\(\s*\{[^}]{50,}\}/,
|
|
99
|
+
antiPattern: /memo\(|React\.memo/,
|
|
100
|
+
message: "Component with many props may benefit from React.memo",
|
|
101
|
+
suggestion: "Wrap with React.memo if parent re-renders frequently with same props",
|
|
102
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
103
|
+
examples: {
|
|
104
|
+
bad: `function UserCard({ id, name, email, avatar, role, lastActive, status }) {
|
|
105
|
+
return (
|
|
106
|
+
<div>
|
|
107
|
+
<img src={avatar} />
|
|
108
|
+
<h3>{name}</h3>
|
|
109
|
+
<p>{email}</p>
|
|
110
|
+
{/* More rendering... */}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}`,
|
|
114
|
+
good: `const UserCard = memo(function UserCard({
|
|
115
|
+
id, name, email, avatar, role, lastActive, status
|
|
116
|
+
}) {
|
|
117
|
+
return (
|
|
118
|
+
<div>
|
|
119
|
+
<img src={avatar} />
|
|
120
|
+
<h3>{name}</h3>
|
|
121
|
+
<p>{email}</p>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
});`
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "rerender-usestate-object",
|
|
129
|
+
category: "rerender",
|
|
130
|
+
impact: "LOW-MEDIUM",
|
|
131
|
+
// Detects: useState with complex object that could be split
|
|
132
|
+
pattern: /useState\s*<?\s*\{[^}]*\w+\s*:\s*[^}]*\w+\s*:\s*[^}]*\}>?\s*\(\s*\{/,
|
|
133
|
+
message: "useState with object may cause unnecessary re-renders",
|
|
134
|
+
suggestion: "Consider splitting into multiple useState or using useReducer",
|
|
135
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
136
|
+
examples: {
|
|
137
|
+
bad: `function Form() {
|
|
138
|
+
const [state, setState] = useState({
|
|
139
|
+
name: '',
|
|
140
|
+
email: '',
|
|
141
|
+
phone: '',
|
|
142
|
+
address: ''
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Updating one field re-renders everything
|
|
146
|
+
setState({ ...state, name: newName });
|
|
147
|
+
}`,
|
|
148
|
+
good: `function Form() {
|
|
149
|
+
const [name, setName] = useState('');
|
|
150
|
+
const [email, setEmail] = useState('');
|
|
151
|
+
const [phone, setPhone] = useState('');
|
|
152
|
+
const [address, setAddress] = useState('');
|
|
153
|
+
|
|
154
|
+
// Or use useReducer for complex state
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Or with useReducer
|
|
158
|
+
const [state, dispatch] = useReducer(formReducer, initialState);`
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
];
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/rules/server.ts
|
|
2
|
+
// Server Action Anti-patterns (Auth, Validation, Security)
|
|
3
|
+
/**
|
|
4
|
+
* Server rules detect common mistakes in Next.js Server Actions
|
|
5
|
+
* and Server Components that can cause security or performance issues.
|
|
6
|
+
*
|
|
7
|
+
* Based on Vercel agent-skills react-best-practices
|
|
8
|
+
*/
|
|
9
|
+
export const SERVER_RULES = [
|
|
10
|
+
{
|
|
11
|
+
id: "server-action-no-auth",
|
|
12
|
+
category: "server",
|
|
13
|
+
impact: "CRITICAL",
|
|
14
|
+
// Detects: 'use server' without auth check nearby
|
|
15
|
+
pattern: /['"]use server['"]\s*;?\s*\n\s*(?:export\s+)?(?:async\s+)?function\s+\w+/,
|
|
16
|
+
antiPattern: /(?:auth|session|getUser|currentUser|requireAuth|withAuth)/i,
|
|
17
|
+
message: "Server Action may lack authentication check",
|
|
18
|
+
suggestion: "Always verify user authentication at the start of Server Actions",
|
|
19
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
20
|
+
examples: {
|
|
21
|
+
bad: `'use server'
|
|
22
|
+
export async function deletePost(id: string) {
|
|
23
|
+
await db.posts.delete(id);
|
|
24
|
+
}`,
|
|
25
|
+
good: `'use server'
|
|
26
|
+
export async function deletePost(id: string) {
|
|
27
|
+
const user = await auth();
|
|
28
|
+
if (!user) throw new Error('Unauthorized');
|
|
29
|
+
await db.posts.delete(id);
|
|
30
|
+
}`
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "server-action-no-validation",
|
|
35
|
+
category: "server",
|
|
36
|
+
impact: "HIGH",
|
|
37
|
+
// Detects: Server Action with direct db/API call without validation
|
|
38
|
+
pattern: /['"]use server['"][\s\S]{0,200}(?:db\.|prisma\.|fetch\()/,
|
|
39
|
+
antiPattern: /(?:z\.|zod|yup|validate|parse|schema)/i,
|
|
40
|
+
message: "Server Action may lack input validation",
|
|
41
|
+
suggestion: "Validate all inputs with Zod or similar before processing",
|
|
42
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
43
|
+
examples: {
|
|
44
|
+
bad: `'use server'
|
|
45
|
+
export async function updateUser(data: FormData) {
|
|
46
|
+
await db.users.update(data);
|
|
47
|
+
}`,
|
|
48
|
+
good: `'use server'
|
|
49
|
+
export async function updateUser(data: FormData) {
|
|
50
|
+
const validated = updateUserSchema.parse(Object.fromEntries(data));
|
|
51
|
+
await db.users.update(validated);
|
|
52
|
+
}`
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "server-sensitive-in-client",
|
|
57
|
+
category: "server",
|
|
58
|
+
impact: "CRITICAL",
|
|
59
|
+
// Detects: 'use client' with sensitive env vars or secrets
|
|
60
|
+
pattern: /['"]use client['"][\s\S]{0,500}(?:process\.env\.(?!NEXT_PUBLIC)|API_KEY|SECRET|PASSWORD)/,
|
|
61
|
+
message: "Sensitive data may be exposed to client bundle",
|
|
62
|
+
suggestion: "Move sensitive logic to Server Component or Server Action",
|
|
63
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
64
|
+
examples: {
|
|
65
|
+
bad: `'use client'
|
|
66
|
+
const apiKey = process.env.API_KEY;`,
|
|
67
|
+
good: `// In Server Component or Server Action
|
|
68
|
+
const apiKey = process.env.API_KEY;`
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "server-component-hooks",
|
|
73
|
+
category: "server",
|
|
74
|
+
impact: "HIGH",
|
|
75
|
+
// Detects: React hooks without 'use client'
|
|
76
|
+
pattern: /(?<!['"]use client['"][\s\S]{0,2000})(?:useState|useEffect|useContext|useReducer|useCallback|useMemo|useRef)\s*\(/,
|
|
77
|
+
antiPattern: /['"]use client['"]/,
|
|
78
|
+
message: "React hooks used in potential Server Component",
|
|
79
|
+
suggestion: "Add 'use client' directive if using React hooks, or move hooks to Client Component",
|
|
80
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
81
|
+
examples: {
|
|
82
|
+
bad: `// No 'use client' directive
|
|
83
|
+
export default function Page() {
|
|
84
|
+
const [state, setState] = useState(0);
|
|
85
|
+
}`,
|
|
86
|
+
good: `'use client'
|
|
87
|
+
export default function Page() {
|
|
88
|
+
const [state, setState] = useState(0);
|
|
89
|
+
}`
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "server-action-try-catch",
|
|
94
|
+
category: "server",
|
|
95
|
+
impact: "MEDIUM-HIGH",
|
|
96
|
+
// Detects: Server Action without try-catch
|
|
97
|
+
pattern: /['"]use server['"]\s*;?\s*\n\s*(?:export\s+)?(?:async\s+)?function\s+\w+[^}]+\{(?![^}]*try\s*\{)/,
|
|
98
|
+
message: "Server Action may lack error handling",
|
|
99
|
+
suggestion: "Wrap Server Action logic in try-catch to handle errors gracefully",
|
|
100
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
101
|
+
examples: {
|
|
102
|
+
bad: `'use server'
|
|
103
|
+
export async function submitForm(data: FormData) {
|
|
104
|
+
await db.save(data);
|
|
105
|
+
}`,
|
|
106
|
+
good: `'use server'
|
|
107
|
+
export async function submitForm(data: FormData) {
|
|
108
|
+
try {
|
|
109
|
+
await db.save(data);
|
|
110
|
+
return { success: true };
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return { success: false, error: 'Failed to save' };
|
|
113
|
+
}
|
|
114
|
+
}`
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "server-redirect-after-action",
|
|
119
|
+
category: "server",
|
|
120
|
+
impact: "MEDIUM",
|
|
121
|
+
// Detects: Server Action without redirect for mutations
|
|
122
|
+
pattern: /['"]use server['"][\s\S]{0,300}(?:create|update|delete|insert)[\s\S]{0,200}(?:return|$)/,
|
|
123
|
+
antiPattern: /redirect\s*\(/,
|
|
124
|
+
message: "Server Action mutation may need redirect for proper revalidation",
|
|
125
|
+
suggestion: "Consider using redirect() after mutations to trigger revalidation",
|
|
126
|
+
fileFilter: /\.(tsx?|jsx?)$/,
|
|
127
|
+
examples: {
|
|
128
|
+
bad: `'use server'
|
|
129
|
+
export async function createPost(data: FormData) {
|
|
130
|
+
await db.posts.create(data);
|
|
131
|
+
return { success: true };
|
|
132
|
+
}`,
|
|
133
|
+
good: `'use server'
|
|
134
|
+
export async function createPost(data: FormData) {
|
|
135
|
+
await db.posts.create(data);
|
|
136
|
+
revalidatePath('/posts');
|
|
137
|
+
redirect('/posts');
|
|
138
|
+
}`
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/react_perf/types.ts
|
|
2
|
+
// React Performance Analysis Tool Types
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
// Output schemas are generated from JSON Schema SSOT.
|
|
5
|
+
import { ReactPerfCheckPatternsInputSchema } from "../../generated/react_perf_check_patterns_input.js";
|
|
6
|
+
import { ReactPerfCheckPatternsOutputSchema } from "../../generated/react_perf_check_patterns_output.js";
|
|
7
|
+
import { ReactPerfGenerateReportInputSchema } from "../../generated/react_perf_generate_report_input.js";
|
|
8
|
+
import { ReactPerfGenerateReportOutputSchema } from "../../generated/react_perf_generate_report_output.js";
|
|
9
|
+
// ============================================================
|
|
10
|
+
// Impact Levels (from Vercel agent-skills)
|
|
11
|
+
// ============================================================
|
|
12
|
+
export const impactLevelSchema = z.enum([
|
|
13
|
+
"CRITICAL", // Must fix: causes major performance issues
|
|
14
|
+
"HIGH", // Should fix: significant performance impact
|
|
15
|
+
"MEDIUM-HIGH", // Recommended: noticeable impact
|
|
16
|
+
"MEDIUM", // Consider: moderate impact
|
|
17
|
+
"LOW-MEDIUM", // Nice to have: minor impact
|
|
18
|
+
"LOW" // Optional: minimal impact
|
|
19
|
+
]);
|
|
20
|
+
// Impact level priority for sorting
|
|
21
|
+
export const IMPACT_PRIORITY = {
|
|
22
|
+
"CRITICAL": 6,
|
|
23
|
+
"HIGH": 5,
|
|
24
|
+
"MEDIUM-HIGH": 4,
|
|
25
|
+
"MEDIUM": 3,
|
|
26
|
+
"LOW-MEDIUM": 2,
|
|
27
|
+
"LOW": 1
|
|
28
|
+
};
|
|
29
|
+
// ============================================================
|
|
30
|
+
// Rule Categories
|
|
31
|
+
// ============================================================
|
|
32
|
+
export const ruleCategorySchema = z.enum([
|
|
33
|
+
"async", // Async/await patterns (waterfalls, parallel)
|
|
34
|
+
"bundle", // Bundle size (barrel imports, dynamic imports)
|
|
35
|
+
"server", // Server Actions (auth, validation)
|
|
36
|
+
"client", // Client components (use client, hydration)
|
|
37
|
+
"rerender", // Re-render patterns (memo, callbacks)
|
|
38
|
+
"rendering", // Rendering patterns (suspense, loading)
|
|
39
|
+
"js", // General JS patterns
|
|
40
|
+
"advanced" // Advanced patterns
|
|
41
|
+
]);
|
|
42
|
+
// ============================================================
|
|
43
|
+
// Input Schema
|
|
44
|
+
// ============================================================
|
|
45
|
+
// Input schema is generated from JSON Schema SSOT.
|
|
46
|
+
export const checkPatternsInputSchema = ReactPerfCheckPatternsInputSchema;
|
|
47
|
+
export const checkPatternsOutputSchema = ReactPerfCheckPatternsOutputSchema;
|
|
48
|
+
// ============================================================
|
|
49
|
+
// Helper Functions
|
|
50
|
+
// ============================================================
|
|
51
|
+
export function countViolationsByImpact(violations) {
|
|
52
|
+
const summary = {
|
|
53
|
+
critical: 0,
|
|
54
|
+
high: 0,
|
|
55
|
+
medium: 0,
|
|
56
|
+
low: 0
|
|
57
|
+
};
|
|
58
|
+
for (const v of violations) {
|
|
59
|
+
switch (v.impact) {
|
|
60
|
+
case "CRITICAL":
|
|
61
|
+
summary.critical++;
|
|
62
|
+
break;
|
|
63
|
+
case "HIGH":
|
|
64
|
+
summary.high++;
|
|
65
|
+
break;
|
|
66
|
+
case "MEDIUM-HIGH":
|
|
67
|
+
case "MEDIUM":
|
|
68
|
+
summary.medium++;
|
|
69
|
+
break;
|
|
70
|
+
case "LOW-MEDIUM":
|
|
71
|
+
case "LOW":
|
|
72
|
+
summary.low++;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return summary;
|
|
77
|
+
}
|
|
78
|
+
export function filterByMinImpact(violations, minImpact) {
|
|
79
|
+
const minPriority = IMPACT_PRIORITY[minImpact];
|
|
80
|
+
return violations.filter(v => IMPACT_PRIORITY[v.impact] >= minPriority);
|
|
81
|
+
}
|
|
82
|
+
export function sortByImpact(violations) {
|
|
83
|
+
return [...violations].sort((a, b) => IMPACT_PRIORITY[b.impact] - IMPACT_PRIORITY[a.impact]);
|
|
84
|
+
}
|
|
85
|
+
export function getTopSuggestions(violations, limit = 3) {
|
|
86
|
+
const sorted = sortByImpact(violations);
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const suggestions = [];
|
|
89
|
+
for (const v of sorted) {
|
|
90
|
+
if (!seen.has(v.rule_id) && suggestions.length < limit) {
|
|
91
|
+
seen.add(v.rule_id);
|
|
92
|
+
suggestions.push(`[${v.impact}] ${v.suggestion}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return suggestions;
|
|
96
|
+
}
|
|
97
|
+
// ============================================================
|
|
98
|
+
// Generate Report Types
|
|
99
|
+
// ============================================================
|
|
100
|
+
export const reportFormatSchema = z.enum(["markdown", "html", "json"]);
|
|
101
|
+
// Input schema is generated from JSON Schema SSOT.
|
|
102
|
+
export const generateReportInputSchema = ReactPerfGenerateReportInputSchema;
|
|
103
|
+
export const generateReportOutputSchema = ReactPerfGenerateReportOutputSchema;
|
|
104
|
+
/**
|
|
105
|
+
* Map impact levels to severity for scoring
|
|
106
|
+
*/
|
|
107
|
+
export function impactToSeverity(impact) {
|
|
108
|
+
switch (impact) {
|
|
109
|
+
case "CRITICAL":
|
|
110
|
+
case "HIGH":
|
|
111
|
+
return "error";
|
|
112
|
+
case "MEDIUM-HIGH":
|
|
113
|
+
case "MEDIUM":
|
|
114
|
+
return "warning";
|
|
115
|
+
case "LOW-MEDIUM":
|
|
116
|
+
case "LOW":
|
|
117
|
+
return "info";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Calculate performance score from violations
|
|
122
|
+
* score = 100 - (error × 10) - (warning × 3) - (info × 1)
|
|
123
|
+
*/
|
|
124
|
+
export function calculateScore(bySeverity) {
|
|
125
|
+
const raw = 100 - (bySeverity.error * 10) - (bySeverity.warning * 3) - (bySeverity.info * 1);
|
|
126
|
+
return Math.max(0, Math.min(100, raw));
|
|
127
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/vibe_pm/activate.ts
|
|
2
|
+
// vibe_pm.activate - Exchange a Gumroad license key for an offline-verifiable JWT token
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { getAuthGate, getTokenCache } from "../../auth/index.js";
|
|
7
|
+
import { ActivateOutputSchema } from "../../generated/activate_output.js";
|
|
8
|
+
function isOfflineMode() {
|
|
9
|
+
const v = (process.env.VIBECODE_OFFLINE ?? "").trim().toLowerCase();
|
|
10
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
11
|
+
}
|
|
12
|
+
function readPackageVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const pkgPath = path.join(__dirname, "..", "..", "..", "..", "package.json");
|
|
16
|
+
const raw = fs.readFileSync(pkgPath, "utf-8");
|
|
17
|
+
const parsed = JSON.parse(raw);
|
|
18
|
+
const v = (parsed.version ?? "").trim();
|
|
19
|
+
return v || "0.0.0";
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return "0.0.0";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* vibe_pm.activate
|
|
27
|
+
*
|
|
28
|
+
* Internal mapping:
|
|
29
|
+
* → Auth Proxy: POST /v1/auth/exchange (license_key)
|
|
30
|
+
* → Cache token locally for offline verification
|
|
31
|
+
*/
|
|
32
|
+
export async function activate(input) {
|
|
33
|
+
const tokenCache = getTokenCache();
|
|
34
|
+
const tokenFile = tokenCache.getTokenFilePath();
|
|
35
|
+
if (isOfflineMode()) {
|
|
36
|
+
return ActivateOutputSchema.parse({
|
|
37
|
+
status: "OFFLINE",
|
|
38
|
+
summary: "오프라인 모드에서는 라이선스 활성화를 진행할 수 없습니다.",
|
|
39
|
+
token_file: tokenFile,
|
|
40
|
+
offline_mode: true,
|
|
41
|
+
entitlements: null,
|
|
42
|
+
policy: null,
|
|
43
|
+
subject_id: null,
|
|
44
|
+
expires_at: null,
|
|
45
|
+
issues: ["offline_mode: activation_blocked"],
|
|
46
|
+
next_action: { tool: "vibe_pm.doctor", reason: "네트워크/설정 상태를 점검하겠습니다." }
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const licenseKey = (input.license_key ?? "").trim();
|
|
50
|
+
const appVersion = (input.app_version ?? "").trim() || readPackageVersion();
|
|
51
|
+
try {
|
|
52
|
+
if (input.force) {
|
|
53
|
+
await tokenCache.clear();
|
|
54
|
+
}
|
|
55
|
+
const gate = getAuthGate();
|
|
56
|
+
const exchanged = await gate.exchangeToken(licenseKey, appVersion);
|
|
57
|
+
if (!exchanged.success) {
|
|
58
|
+
return ActivateOutputSchema.parse({
|
|
59
|
+
status: "ERROR",
|
|
60
|
+
summary: "라이선스 활성화에 실패했습니다.",
|
|
61
|
+
token_file: tokenFile,
|
|
62
|
+
offline_mode: false,
|
|
63
|
+
entitlements: null,
|
|
64
|
+
policy: null,
|
|
65
|
+
subject_id: null,
|
|
66
|
+
expires_at: null,
|
|
67
|
+
issues: [exchanged.error ?? "unknown_error"],
|
|
68
|
+
next_action: { tool: "vibe_pm.activate", reason: "키를 확인한 뒤 다시 시도하세요." }
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const cached = await tokenCache.get();
|
|
72
|
+
const expiresAt = cached?.expiresAt ?? null;
|
|
73
|
+
const subjectId = cached?.subjectId ?? null;
|
|
74
|
+
return ActivateOutputSchema.parse({
|
|
75
|
+
status: "OK",
|
|
76
|
+
summary: "라이선스 활성화가 완료되었습니다.",
|
|
77
|
+
token_file: tokenFile,
|
|
78
|
+
offline_mode: false,
|
|
79
|
+
entitlements: exchanged.entitlements ?? null,
|
|
80
|
+
policy: exchanged.policy ?? null,
|
|
81
|
+
subject_id: subjectId,
|
|
82
|
+
expires_at: expiresAt,
|
|
83
|
+
issues: [],
|
|
84
|
+
next_action: { tool: "vibe_pm.status", reason: "현재 상태를 확인하겠습니다." }
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
89
|
+
return ActivateOutputSchema.parse({
|
|
90
|
+
status: "ERROR",
|
|
91
|
+
summary: "라이선스 활성화 중 오류가 발생했습니다.",
|
|
92
|
+
token_file: tokenFile,
|
|
93
|
+
offline_mode: false,
|
|
94
|
+
entitlements: null,
|
|
95
|
+
policy: null,
|
|
96
|
+
subject_id: null,
|
|
97
|
+
expires_at: null,
|
|
98
|
+
issues: [msg],
|
|
99
|
+
next_action: { tool: "vibe_pm.doctor", reason: "설치/네트워크 상태를 점검하겠습니다." }
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/vibe_pm/advisory_review.ts
|
|
2
|
+
// vibe_pm.advisory_review - Advisory review (triage/quick/thorough) with routing + cache
|
|
3
|
+
import { runEngine } from "../../engine.js";
|
|
4
|
+
import { safeJsonParse } from "../../cli.js";
|
|
5
|
+
import { validateToolInput } from "../../security/input-validator.js";
|
|
6
|
+
import { resolveProjectId } from "./context.js";
|
|
7
|
+
import { AdvisoryReviewOutputSchema } from "../../generated/advisory_review_output.js";
|
|
8
|
+
function clipArg(value, maxChars) {
|
|
9
|
+
const v = value.trim();
|
|
10
|
+
if (v.length <= maxChars)
|
|
11
|
+
return v;
|
|
12
|
+
return v.slice(0, maxChars).trimEnd() + "…";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* vibe_pm.advisory_review - Produce high-level advisory review output
|
|
16
|
+
*
|
|
17
|
+
* Internal mapping:
|
|
18
|
+
* → vibecoding-helper advisory-review --mode triage|quick|thorough ...
|
|
19
|
+
*/
|
|
20
|
+
export async function advisoryReview(input) {
|
|
21
|
+
const basePath = process.cwd();
|
|
22
|
+
validateToolInput({
|
|
23
|
+
project_id: input.project_id,
|
|
24
|
+
custom_input: input.diff_context
|
|
25
|
+
});
|
|
26
|
+
const project_id = input.project_id ?? resolveProjectId(undefined, basePath);
|
|
27
|
+
const mode = input.mode ?? "triage";
|
|
28
|
+
const trigger = input.trigger ?? "on_demand";
|
|
29
|
+
const budget = input.budget ?? "low";
|
|
30
|
+
const changed = Array.isArray(input.changed_paths) ? input.changed_paths : [];
|
|
31
|
+
const targets = Array.isArray(input.target_paths) ? input.target_paths : [];
|
|
32
|
+
const changedCsv = changed.map((p) => p.trim()).filter(Boolean).join(",");
|
|
33
|
+
const targetCsv = targets.map((p) => p.trim()).filter(Boolean).join(",");
|
|
34
|
+
const args = [
|
|
35
|
+
"advisory-review",
|
|
36
|
+
"--project-id",
|
|
37
|
+
project_id,
|
|
38
|
+
"--mode",
|
|
39
|
+
mode,
|
|
40
|
+
"--trigger",
|
|
41
|
+
trigger,
|
|
42
|
+
"--budget",
|
|
43
|
+
budget
|
|
44
|
+
];
|
|
45
|
+
if (input.run_id) {
|
|
46
|
+
args.push("--run-id", input.run_id);
|
|
47
|
+
}
|
|
48
|
+
if (changedCsv) {
|
|
49
|
+
args.push("--changed-paths", changedCsv);
|
|
50
|
+
}
|
|
51
|
+
else if (targetCsv) {
|
|
52
|
+
args.push("--target-paths", targetCsv);
|
|
53
|
+
}
|
|
54
|
+
if (input.diff_context && input.diff_context.trim()) {
|
|
55
|
+
args.push("--diff-context", clipArg(input.diff_context, 2000));
|
|
56
|
+
}
|
|
57
|
+
if (input.base_sha)
|
|
58
|
+
args.push("--base-sha", input.base_sha);
|
|
59
|
+
if (input.head_sha)
|
|
60
|
+
args.push("--head-sha", input.head_sha);
|
|
61
|
+
if (Array.isArray(input.focus) && input.focus.length > 0) {
|
|
62
|
+
args.push("--focus", input.focus.join(","));
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(input.product_intent_refs) && input.product_intent_refs.length > 0) {
|
|
65
|
+
args.push("--product-intent-refs", input.product_intent_refs.join(","));
|
|
66
|
+
}
|
|
67
|
+
const timeoutMs = mode === "triage" ? 30_000 : 180_000;
|
|
68
|
+
const { code, stdout, stderr } = await runEngine("vibecoding-helper", args, { timeoutMs });
|
|
69
|
+
if (code !== 0) {
|
|
70
|
+
throw new Error(stderr?.trim() ? `advisory_review_engine_error: ${stderr.trim()}` : `advisory_review_exit_code: ${code}`);
|
|
71
|
+
}
|
|
72
|
+
const parsed = safeJsonParse(stdout);
|
|
73
|
+
if (!parsed.ok) {
|
|
74
|
+
throw new Error(`advisory_review_invalid_json: ${parsed.error}`);
|
|
75
|
+
}
|
|
76
|
+
return AdvisoryReviewOutputSchema.parse(parsed.value);
|
|
77
|
+
}
|