coding-buddy-mcp 1.0.3 → 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/README.md +218 -71
- package/dist/index.js +197 -447
- package/package.json +1 -1
- package/scripts/buddy-hook.sh +116 -0
- package/scripts/guard-claude-md.sh +23 -0
- package/scripts/model-recommend.sh +156 -0
- package/scripts/session-check.sh +33 -0
- package/TEST_CASES.md +0 -165
package/dist/index.js
CHANGED
|
@@ -3,60 +3,26 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
// =============================================================================
|
|
6
|
-
//
|
|
7
|
-
// 두 글의 37개 액션아이템을 전부 Claude 행동 규칙으로 인코딩.
|
|
6
|
+
// MCP Instructions — 극도로 짧게. 매 턴 system prompt 토큰으로 잡힌다.
|
|
8
7
|
// =============================================================================
|
|
9
8
|
const INSTRUCTIONS = `
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
STEP 1: CHECK SPECIFICITY
|
|
13
|
-
If the request has NO file path, NO function name, and NO error message → ask for specifics. Do NOT call any tools.
|
|
14
|
-
- "버그 찾아줘" → respond: "어떤 파일에서 어떤 증상이 나오나요? 파일 경로와 증상을 알려주세요."
|
|
15
|
-
- "리팩토링 해줘" → respond: "어떤 파일을 리팩토링할까요?"
|
|
16
|
-
- "테스트 작성해줘" → respond: "어떤 파일/함수에 대한 테스트인가요?"
|
|
17
|
-
If the request IS specific (has a file path or function name) → go to Step 2.
|
|
18
|
-
|
|
19
|
-
STEP 2: RECOMMEND MODEL
|
|
20
|
-
Before doing any work, tell the user which model fits this task:
|
|
21
|
-
- Simple (오타, rename, format, 간단 조회) → "이 작업은 Haiku면 충분합니다 ($1/$5). 현재 모델이 다르다면 새 세션에서 /model haiku 로 시작하세요."
|
|
22
|
-
- Medium (기능 구현, 버그 수정, 테스트, 리뷰) → "이 작업은 Sonnet이 적합합니다 ($15/$75)."
|
|
23
|
-
- Complex (아키텍처, 마이그레이션, 전체 리팩토링) → "이 작업은 Opus가 필요합니다 ($15/$75). Plan Mode를 먼저 사용하세요."
|
|
24
|
-
Say this in 1 line, then proceed to Step 3.
|
|
25
|
-
|
|
26
|
-
STEP 3: DO THE WORK
|
|
27
|
-
Now proceed with the task. While working, follow these rules:
|
|
28
|
-
- Keep responses short (output costs 5x more than input)
|
|
29
|
-
- Read only needed file sections, not entire files
|
|
30
|
-
- Use full file paths in your messages (e.g., src/auth/login.ts) — they survive compaction
|
|
31
|
-
- After finishing → suggest "/cost 로 비용 확인해보세요"
|
|
32
|
-
- If the topic changes → suggest new session ("새 작업이니 새 세션이 효율적입니다")
|
|
33
|
-
- If conversation is long → suggest /compact
|
|
34
|
-
- NEVER change model mid-session (cache break = 10x cost spike). Suggest new session instead.
|
|
35
|
-
- WARN before editing CLAUDE.md mid-session ("캐시가 깨집니다. 세션 시작 전에 수정하세요")
|
|
36
|
-
- Complex tasks → suggest Plan Mode first
|
|
37
|
-
- Multiple independent tasks → use sub-agents in parallel
|
|
38
|
-
- After code changes → suggest "/diff → /commit → /pr"
|
|
9
|
+
코딩 버디가 활성화되어 있습니다. UserPromptSubmit 훅이 매 프롬프트를 분석하여 IMPORTANT 지시를 주입합니다. 반드시 따르세요.
|
|
10
|
+
추가 규칙: 파일 경로를 항상 전체 경로로 언급하라 (압축 후 보존됨). 응답은 간결하게. 작업 완료 시 "/cost로 비용 확인" 안내.
|
|
39
11
|
`;
|
|
40
12
|
// =============================================================================
|
|
41
|
-
//
|
|
13
|
+
// 복잡도 분석 헬퍼
|
|
42
14
|
// =============================================================================
|
|
43
15
|
const SIMPLE_KEYWORDS = [
|
|
44
|
-
// English
|
|
45
16
|
"rename", "typo", "format", "formatting", "delete line", "remove line",
|
|
46
17
|
"add import", "simple", "quick", "small", "trivial", "boilerplate",
|
|
47
|
-
"template", "copy", "move file", "
|
|
48
|
-
"
|
|
49
|
-
// Korean
|
|
50
|
-
"오타", "이름 변경", "이름변경", "포맷", "간단", "삭제", "뭐야", "어디",
|
|
51
|
-
"보여줘", "설명해", "주석", "임포트", "복사",
|
|
18
|
+
"template", "copy", "move file", "change name", "fix typo", "add comment",
|
|
19
|
+
"오타", "이름 변경", "이름변경", "포맷", "간단", "삭제", "주석", "임포트", "복사",
|
|
52
20
|
];
|
|
53
21
|
const COMPLEX_KEYWORDS = [
|
|
54
|
-
// English
|
|
55
22
|
"migrate", "migration", "architecture", "redesign", "refactor entire",
|
|
56
23
|
"refactor all", "system design", "all files", "entire project",
|
|
57
24
|
"multi-file", "cross-cutting", "overhaul", "rewrite", "restructure",
|
|
58
25
|
"database schema", "auth system", "from scratch",
|
|
59
|
-
// Korean
|
|
60
26
|
"마이그레이션", "아키텍처", "전체 리팩토링", "시스템 설계", "전부",
|
|
61
27
|
"전체 구조", "다시 만들", "데이터베이스 스키마", "인증 시스템",
|
|
62
28
|
];
|
|
@@ -66,455 +32,239 @@ function analyzeComplexity(task) {
|
|
|
66
32
|
return "complex";
|
|
67
33
|
if (SIMPLE_KEYWORDS.some((k) => lower.includes(k)))
|
|
68
34
|
return "simple";
|
|
69
|
-
// Heuristic: multiple numbered items suggest multi-step = complex
|
|
70
35
|
const numberedItems = (task.match(/\d+[\.\)]/g) || []).length;
|
|
71
36
|
if (numberedItems >= 3)
|
|
72
37
|
return "complex";
|
|
73
|
-
// Long descriptions tend toward complexity
|
|
74
38
|
if (task.length > 300)
|
|
75
39
|
return "complex";
|
|
76
40
|
if (task.length < 80)
|
|
77
41
|
return "simple";
|
|
78
42
|
return "medium";
|
|
79
43
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
case "simple":
|
|
83
|
-
return {
|
|
84
|
-
model: "haiku",
|
|
85
|
-
reason_ko: "단순 작업입니다. Haiku는 Sonnet 대비 input 15배, output 15배 저렴합니다.",
|
|
86
|
-
reason_en: "Simple task. Haiku is 15x cheaper than Sonnet for both input and output.",
|
|
87
|
-
switch_command: "/model haiku",
|
|
88
|
-
estimated_cost_range: "$0.01 - $0.05",
|
|
89
|
-
input_price: "$1.00 / 1M tokens",
|
|
90
|
-
output_price: "$5.00 / 1M tokens",
|
|
91
|
-
};
|
|
92
|
-
case "complex":
|
|
93
|
-
return {
|
|
94
|
-
model: "opus",
|
|
95
|
-
reason_ko: "복잡한 추론이 필요합니다. Opus를 추천하며, Plan Mode를 먼저 사용하세요.",
|
|
96
|
-
reason_en: "Deep reasoning required. Opus recommended. Use Plan Mode first.",
|
|
97
|
-
switch_command: "/model opus",
|
|
98
|
-
estimated_cost_range: "$2.00 - $10.00",
|
|
99
|
-
input_price: "$15.00 / 1M tokens",
|
|
100
|
-
output_price: "$75.00 / 1M tokens",
|
|
101
|
-
};
|
|
102
|
-
default:
|
|
103
|
-
return {
|
|
104
|
-
model: "sonnet",
|
|
105
|
-
reason_ko: "표준 개발 작업입니다. Sonnet이 성능과 비용의 최적 균형입니다.",
|
|
106
|
-
reason_en: "Standard development task. Sonnet is the best balance of capability and cost.",
|
|
107
|
-
switch_command: "/model sonnet",
|
|
108
|
-
estimated_cost_range: "$0.20 - $1.00",
|
|
109
|
-
input_price: "$15.00 / 1M tokens",
|
|
110
|
-
output_price: "$75.00 / 1M tokens",
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function getApproach(complexity, task) {
|
|
115
|
-
const approaches = [];
|
|
116
|
-
if (complexity === "complex") {
|
|
117
|
-
approaches.push("Plan Mode first: 먼저 /plan 으로 영향받는 파일을 파악한 후 실행하세요");
|
|
118
|
-
}
|
|
119
|
-
// Multiple independent subtasks → parallel sub-agents
|
|
120
|
-
const multiTaskPatterns = [
|
|
121
|
-
/\d+\.\s/,
|
|
122
|
-
/and also/i,
|
|
123
|
-
/additionally/i,
|
|
124
|
-
/그리고/,
|
|
125
|
-
/또한/,
|
|
126
|
-
/동시에/,
|
|
127
|
-
/병렬/,
|
|
128
|
-
];
|
|
129
|
-
if (multiTaskPatterns.some((p) => p.test(task))) {
|
|
130
|
-
approaches.push("Sub-agents: 독립적인 하위 작업을 병렬로 실행하면 속도가 빨라집니다");
|
|
131
|
-
}
|
|
132
|
-
// No file path detected → need specifics
|
|
133
|
-
const hasFilePath = /[\w-]+\/[\w.-]+\.\w{1,5}/.test(task);
|
|
134
|
-
if (!hasFilePath && complexity !== "simple") {
|
|
135
|
-
approaches.push("Specificity needed: 구체적인 파일 경로를 지정하면 도구 호출이 줄어듭니다");
|
|
136
|
-
}
|
|
137
|
-
if (approaches.length === 0) {
|
|
138
|
-
approaches.push("Direct implementation: 작업 범위가 명확합니다. 바로 진행 가능합니다");
|
|
139
|
-
}
|
|
140
|
-
return approaches;
|
|
44
|
+
function hasFilePath(task) {
|
|
45
|
+
return /[\w-]+\/[\w.-]+\.\w{1,5}/.test(task);
|
|
141
46
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// MCP Server
|
|
49
|
+
// =============================================================================
|
|
50
|
+
const server = new McpServer({ name: "coding-buddy", version: "2.0.0" }, { instructions: INSTRUCTIONS });
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// Tool 1: analyze_task — 복잡도/모델/비용 추정 (온디맨드)
|
|
53
|
+
// =============================================================================
|
|
54
|
+
server.tool("analyze_task", "Analyze task complexity and recommend optimal model, approach, and estimated cost. Call this before starting any new task.", {
|
|
55
|
+
task: z.string().describe("Description of the task"),
|
|
56
|
+
}, async ({ task }) => {
|
|
57
|
+
const complexity = analyzeComplexity(task);
|
|
58
|
+
const specific = hasFilePath(task);
|
|
59
|
+
const models = {
|
|
60
|
+
simple: { model: "haiku", price: "$1/$5 per 1M", cost_range: "$0.01-$0.05" },
|
|
61
|
+
medium: { model: "sonnet", price: "$15/$75 per 1M", cost_range: "$0.20-$1.00" },
|
|
62
|
+
complex: { model: "opus", price: "$15/$75 per 1M", cost_range: "$2.00-$10.00" },
|
|
63
|
+
};
|
|
64
|
+
const rec = models[complexity];
|
|
65
|
+
const tips = [];
|
|
66
|
+
if (!specific) {
|
|
67
|
+
tips.push("파일 경로를 명시하면 도구 호출 횟수가 줄어듭니다 (비용 절감)");
|
|
160
68
|
}
|
|
161
|
-
// Complex task without plan
|
|
162
69
|
if (complexity === "complex") {
|
|
163
|
-
|
|
70
|
+
tips.push("Plan Mode에서 먼저 계획을 세우면 불필요한 탐색을 줄일 수 있습니다");
|
|
164
71
|
}
|
|
165
|
-
return warnings;
|
|
166
|
-
}
|
|
167
|
-
function getTips(complexity, task) {
|
|
168
|
-
const tips = [];
|
|
169
72
|
if (complexity === "simple") {
|
|
170
|
-
tips.push("
|
|
171
|
-
}
|
|
172
|
-
// Remind about compaction keywords
|
|
173
|
-
tips.push('todo/next/pending 키워드를 메시지에 포함하면 자동 압축 후에도 맥락이 보존됩니다.');
|
|
174
|
-
// File path reminder
|
|
175
|
-
if (!/[\w-]+\/[\w.-]+\.\w{1,5}/.test(task)) {
|
|
176
|
-
tips.push("파일 경로를 정확히 언급하면 (예: src/auth/login.ts) 압축 후에도 Claude가 기억합니다.");
|
|
73
|
+
tips.push("Extended thinking이 불필요합니다. thinking 토큰은 output 단가($75/1M)로 과금됩니다");
|
|
177
74
|
}
|
|
178
|
-
|
|
179
|
-
|
|
75
|
+
const result = {
|
|
76
|
+
complexity,
|
|
77
|
+
recommended_model: rec.model,
|
|
78
|
+
model_price: rec.price,
|
|
79
|
+
estimated_cost: rec.cost_range,
|
|
80
|
+
is_specific: specific,
|
|
81
|
+
approach: complexity === "complex" ? "plan_mode_first" : "direct",
|
|
82
|
+
tips,
|
|
83
|
+
switch_note: `현재 모델이 ${rec.model}이 아니라면 새 세션에서 /model ${rec.model} 로 시작하세요. 세션 중 모델 변경은 캐시 브레이크(비용 10배)를 유발합니다.`,
|
|
84
|
+
};
|
|
85
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
86
|
+
});
|
|
180
87
|
// =============================================================================
|
|
181
|
-
//
|
|
88
|
+
// Tool 2: cost_reference — 가격표/캐시/팁 조회 (온디맨드)
|
|
182
89
|
// =============================================================================
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
90
|
+
server.tool("cost_reference", "Get Claude Code pricing, cache settings, and cost optimization tips by topic.", {
|
|
91
|
+
topic: z.enum(["pricing", "cache", "compaction", "thinking", "all"]).optional().describe("Topic to query. Defaults to 'all'."),
|
|
92
|
+
}, async ({ topic }) => {
|
|
93
|
+
const t = topic || "all";
|
|
94
|
+
const sections = {
|
|
95
|
+
pricing: {
|
|
96
|
+
haiku: { input: "$1", output: "$5", cache_write: "$1.25", cache_read: "$0.10" },
|
|
97
|
+
sonnet: { input: "$15", output: "$75", cache_write: "$18.75", cache_read: "$1.50" },
|
|
98
|
+
opus: { input: "$15", output: "$75", cache_write: "$18.75", cache_read: "$1.50" },
|
|
99
|
+
unit: "USD per 1M tokens",
|
|
100
|
+
key_insight: "output은 input보다 5배 비쌈. cache_read는 input보다 10배 저렴.",
|
|
101
|
+
},
|
|
102
|
+
cache: {
|
|
103
|
+
prompt_ttl: "300초 (5분) — Anthropic 서버 캐시",
|
|
104
|
+
completion_ttl: "30초 — 동일 요청 로컬 캐시",
|
|
105
|
+
breakers: ["모델 변경", "CLAUDE.md 수정", "MCP 도구 변경", "시스템 프롬프트 변경"],
|
|
106
|
+
tip: "5분 이상 자리비움 → 캐시 만료 → 첫 요청 비용 증가",
|
|
107
|
+
},
|
|
108
|
+
compaction: {
|
|
109
|
+
threshold: "100,000 input tokens (CLAUDE_CODE_AUTO_COMPACT_INPUT_TOKENS로 조정)",
|
|
110
|
+
surviving_keywords: ["todo", "next", "pending", "follow up", "remaining"],
|
|
111
|
+
surviving_paths: "파일 경로 (/ 포함 + 확장자) 최대 8개",
|
|
112
|
+
tip: "한 세션 한 작업. 길어지면 /compact. 마무리되면 새 세션.",
|
|
113
|
+
},
|
|
114
|
+
thinking: {
|
|
115
|
+
billing: "output 토큰으로 과금 ($75/1M for Sonnet/Opus)",
|
|
116
|
+
risk: "단순 작업에 thinking 8,000토큰 → $0.60 낭비",
|
|
117
|
+
tip: "단순 작업은 Haiku (thinking 없음). 복잡한 추론에만 Opus.",
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const result = t === "all" ? sections : { [t]: sections[t] };
|
|
121
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
188
122
|
});
|
|
189
123
|
// =============================================================================
|
|
190
|
-
// Tool:
|
|
191
|
-
// 매 새 작업 시작 시 Claude가 자동으로 호출
|
|
124
|
+
// Tool 3: session_health — 세션 상태 진단 (온디맨드)
|
|
192
125
|
// =============================================================================
|
|
193
|
-
server.tool("
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
.
|
|
204
|
-
|
|
205
|
-
message_count
|
|
206
|
-
|
|
207
|
-
.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
warnings,
|
|
220
|
-
tips,
|
|
126
|
+
server.tool("session_health", "Diagnose current session health and recommend whether to continue, compact, or start a new session.", {
|
|
127
|
+
message_count: z.number().optional().describe("Approximate number of messages in session"),
|
|
128
|
+
minutes_since_start: z.number().optional().describe("Minutes since session started"),
|
|
129
|
+
minutes_since_last_interaction: z.number().optional().describe("Minutes since last user message"),
|
|
130
|
+
topic_changed: z.boolean().optional().describe("Whether the topic has changed from the original task"),
|
|
131
|
+
}, async ({ message_count, minutes_since_start, minutes_since_last_interaction, topic_changed }) => {
|
|
132
|
+
let recommendation = "continue";
|
|
133
|
+
const warnings = [];
|
|
134
|
+
if (topic_changed) {
|
|
135
|
+
recommendation = "new_session";
|
|
136
|
+
warnings.push("주제가 바뀌었습니다. 새 세션이 비용과 품질 모두 유리합니다.");
|
|
137
|
+
}
|
|
138
|
+
if (message_count && message_count > 20) {
|
|
139
|
+
recommendation = recommendation === "new_session" ? "new_session" : "compact";
|
|
140
|
+
warnings.push(`${message_count}개 메시지 누적. 매 턴 전체 대화가 전송되어 비용이 증가 중.`);
|
|
141
|
+
}
|
|
142
|
+
if (minutes_since_start && minutes_since_start > 30) {
|
|
143
|
+
warnings.push("30분+ 세션. 압축이 누적되어 맥락 손실 위험.");
|
|
144
|
+
}
|
|
145
|
+
if (minutes_since_last_interaction && minutes_since_last_interaction > 5) {
|
|
146
|
+
warnings.push("5분+ 미응답. Anthropic 서버 캐시가 만료됐을 가능성 높음. 첫 요청 비용 증가.");
|
|
147
|
+
}
|
|
148
|
+
const actions = {
|
|
149
|
+
continue: "현재 세션 계속 진행",
|
|
150
|
+
compact: "/compact 로 대화 압축 후 계속",
|
|
151
|
+
new_session: "새 세션 시작 추천",
|
|
221
152
|
};
|
|
222
153
|
return {
|
|
223
|
-
content: [
|
|
224
|
-
{
|
|
154
|
+
content: [{
|
|
225
155
|
type: "text",
|
|
226
|
-
text: JSON.stringify(
|
|
227
|
-
|
|
228
|
-
|
|
156
|
+
text: JSON.stringify({
|
|
157
|
+
recommendation,
|
|
158
|
+
action: actions[recommendation],
|
|
159
|
+
warnings,
|
|
160
|
+
tips: [
|
|
161
|
+
"claude --resume latest 로 5분 이내 이어하기 가능",
|
|
162
|
+
"중요 맥락은 CLAUDE.md에 저장 (압축과 무관하게 보존)",
|
|
163
|
+
],
|
|
164
|
+
}, null, 2),
|
|
165
|
+
}],
|
|
229
166
|
};
|
|
230
167
|
});
|
|
231
168
|
// =============================================================================
|
|
232
|
-
// Tool:
|
|
233
|
-
// CLAUDE.md, 권한, hooks, settings.json 종합 안내
|
|
169
|
+
// Tool 4: optimize_prompt — 모호한 프롬프트 최적화 제안 (온디맨드)
|
|
234
170
|
// =============================================================================
|
|
235
|
-
server.tool("
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
.
|
|
242
|
-
.describe("Project type: react, nextjs, rust, python, go, monorepo, etc."),
|
|
243
|
-
team_size: z
|
|
244
|
-
.number()
|
|
245
|
-
.optional()
|
|
246
|
-
.describe("Number of developers working on this project"),
|
|
247
|
-
current_permission_mode: z
|
|
248
|
-
.string()
|
|
249
|
-
.optional()
|
|
250
|
-
.describe("Current permission mode: prompt, read-only, workspace-write, danger-full-access"),
|
|
251
|
-
}, async ({ has_claude_md, project_type, team_size, current_permission_mode, }) => {
|
|
252
|
-
const recommendations = [];
|
|
253
|
-
// --- CLAUDE.md ---
|
|
254
|
-
if (!has_claude_md) {
|
|
255
|
-
recommendations.push({
|
|
256
|
-
priority: "HIGH",
|
|
257
|
-
category: "CLAUDE.md",
|
|
258
|
-
action: "Create CLAUDE.md with /init",
|
|
259
|
-
detail: `Run /init to create CLAUDE.md, then add:
|
|
260
|
-
|
|
261
|
-
## Include in CLAUDE.md:
|
|
262
|
-
- Project structure (directories and their purpose)
|
|
263
|
-
- Build/test/lint commands
|
|
264
|
-
- Coding conventions (error handling, naming, commit style)
|
|
265
|
-
- Common file paths (routes, components, types, configs)
|
|
266
|
-
|
|
267
|
-
## Hierarchical structure (saves tokens by loading only relevant context):
|
|
268
|
-
project/
|
|
269
|
-
CLAUDE.md # Project-wide rules
|
|
270
|
-
CLAUDE.local.md # Personal env (gitignored)
|
|
271
|
-
src/
|
|
272
|
-
CLAUDE.md # src-specific context
|
|
273
|
-
tests/
|
|
274
|
-
CLAUDE.md # test-specific context
|
|
275
|
-
|
|
276
|
-
## Example:
|
|
277
|
-
\`\`\`markdown
|
|
278
|
-
# Project Structure
|
|
279
|
-
- src/api/: REST API routes (Express)
|
|
280
|
-
- src/components/: React components (Atomic Design)
|
|
281
|
-
- src/types/: TypeScript type definitions
|
|
282
|
-
|
|
283
|
-
# Commands
|
|
284
|
-
- Test: pnpm test
|
|
285
|
-
- Lint: pnpm lint
|
|
286
|
-
- Build: pnpm build
|
|
287
|
-
|
|
288
|
-
# Conventions
|
|
289
|
-
- Error handling: use Result type, no try-catch
|
|
290
|
-
- Commits: conventional commits (feat:, fix:, chore:)
|
|
291
|
-
\`\`\``,
|
|
292
|
-
cost_impact: "Saves ~$0.10-0.50 per session by eliminating repeated project explanations",
|
|
293
|
-
});
|
|
294
|
-
recommendations.push({
|
|
295
|
-
priority: "MEDIUM",
|
|
296
|
-
category: "CLAUDE.local.md",
|
|
297
|
-
action: "Create CLAUDE.local.md for personal environment",
|
|
298
|
-
detail: `Create CLAUDE.local.md (add to .gitignore):
|
|
299
|
-
- Local database connection strings
|
|
300
|
-
- Your branch naming convention
|
|
301
|
-
- Personal preferences
|
|
302
|
-
- PR reviewer defaults`,
|
|
303
|
-
cost_impact: "Prevents personal config from polluting team CLAUDE.md",
|
|
304
|
-
});
|
|
171
|
+
server.tool("optimize_prompt", "Analyze a vague user prompt and suggest an optimized version that reduces tool calls and cost.", {
|
|
172
|
+
user_prompt: z.string().describe("The user's original prompt to optimize"),
|
|
173
|
+
}, async ({ user_prompt }) => {
|
|
174
|
+
const issues = [];
|
|
175
|
+
const specific = hasFilePath(user_prompt);
|
|
176
|
+
if (!specific) {
|
|
177
|
+
issues.push("파일 경로 없음 → 탐색 도구 5~10회 예상");
|
|
305
178
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
category: "CLAUDE.md",
|
|
310
|
-
action: "Review CLAUDE.md for completeness",
|
|
311
|
-
detail: "Ensure it includes: project structure, commands, conventions, common paths. Edit BEFORE starting a session (editing mid-session breaks cache).",
|
|
312
|
-
cost_impact: "Well-structured CLAUDE.md reduces repeated explanations",
|
|
313
|
-
});
|
|
179
|
+
const lower = user_prompt.toLowerCase();
|
|
180
|
+
if (lower.includes("전체") || lower.includes("entire") || lower.includes("all")) {
|
|
181
|
+
issues.push("범위가 '전체' → 대량 파일 읽기 발생");
|
|
314
182
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
recommendations.push({
|
|
318
|
-
priority: "HIGH",
|
|
319
|
-
category: "Permissions",
|
|
320
|
-
action: "Optimize permission mode and auto-allow list",
|
|
321
|
-
detail: `Every permission prompt interrupts flow and wastes time.
|
|
322
|
-
|
|
323
|
-
## Option 1: Permission mode flag
|
|
324
|
-
- Code review only: claude --permission-mode read-only
|
|
325
|
-
- Active development: claude --permission-mode workspace-write
|
|
326
|
-
|
|
327
|
-
## Option 2: Auto-allow common tools in settings.json
|
|
328
|
-
Add to .claude/settings.json:
|
|
329
|
-
{
|
|
330
|
-
"permissions": {
|
|
331
|
-
"allow": [
|
|
332
|
-
"Read",
|
|
333
|
-
"Edit",
|
|
334
|
-
"Write",
|
|
335
|
-
"Glob",
|
|
336
|
-
"Grep",
|
|
337
|
-
"Bash(git *)",
|
|
338
|
-
"Bash(npm *)",
|
|
339
|
-
"Bash(pnpm *)",
|
|
340
|
-
"Bash(cargo *)"
|
|
341
|
-
]
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
This auto-approves safe operations while still prompting for dangerous ones (rm, curl, etc).`,
|
|
346
|
-
cost_impact: "No direct token savings, but eliminates workflow interruptions",
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
// --- Hooks ---
|
|
350
|
-
recommendations.push({
|
|
351
|
-
priority: "MEDIUM",
|
|
352
|
-
category: "Hooks",
|
|
353
|
-
action: "Set up automation hooks",
|
|
354
|
-
detail: `Add to .claude/settings.json for automatic formatting and safety:
|
|
355
|
-
|
|
356
|
-
{
|
|
357
|
-
"hooks": {
|
|
358
|
-
"postToolUse": [
|
|
359
|
-
{
|
|
360
|
-
"matcher": "Edit",
|
|
361
|
-
"command": "${project_type === "rust" ? "cargo fmt -- $HOOK_TOOL_INPUT 2>/dev/null; exit 0" : "npx prettier --write $HOOK_TOOL_INPUT 2>/dev/null; exit 0"}"
|
|
362
|
-
}
|
|
363
|
-
],
|
|
364
|
-
"preToolUse": [
|
|
365
|
-
{
|
|
366
|
-
"matcher": "Bash",
|
|
367
|
-
"command": "echo $HOOK_TOOL_INPUT | grep -qE 'rm -rf|drop table|force push' && exit 2 || exit 0"
|
|
368
|
-
}
|
|
369
|
-
]
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
Hook exit codes: 0=allow, 2=block, other=fail`,
|
|
374
|
-
cost_impact: "Prevents costly mistakes (accidental deletions, force pushes)",
|
|
375
|
-
});
|
|
376
|
-
// --- MCP optimization ---
|
|
377
|
-
recommendations.push({
|
|
378
|
-
priority: "MEDIUM",
|
|
379
|
-
category: "MCP",
|
|
380
|
-
action: "Audit active MCP servers",
|
|
381
|
-
detail: `Each MCP server's tool definitions are sent with EVERY API request, consuming tokens.
|
|
382
|
-
|
|
383
|
-
Rules:
|
|
384
|
-
- Only enable MCPs you actively use
|
|
385
|
-
- Set up ALL needed MCPs BEFORE starting a session (adding/removing = cache break)
|
|
386
|
-
- MCP initialization timeout: 10s, tool list timeout: 30s — unstable servers hurt performance
|
|
387
|
-
|
|
388
|
-
Check current MCPs: /mcp`,
|
|
389
|
-
cost_impact: "Removing 1 unused MCP with 5 tools saves ~100-200 tokens per request",
|
|
390
|
-
});
|
|
391
|
-
// --- Session strategy ---
|
|
392
|
-
recommendations.push({
|
|
393
|
-
priority: "HIGH",
|
|
394
|
-
category: "Session Strategy",
|
|
395
|
-
action: "Adopt one-session-one-task discipline",
|
|
396
|
-
detail: `Session management rules:
|
|
397
|
-
1. One session = one focused task
|
|
398
|
-
2. Task done → /clear or new session
|
|
399
|
-
3. Conversation long → /compact (or set CLAUDE_CODE_AUTO_COMPACT_INPUT_TOKENS=50000 for earlier auto-compact)
|
|
400
|
-
4. Resume within 5 min: claude --resume latest (cache alive)
|
|
401
|
-
5. Want two approaches: use session fork
|
|
402
|
-
6. Monitor costs: /cost
|
|
403
|
-
|
|
404
|
-
Important keywords that survive compaction: todo, next, pending, follow up, remaining
|
|
405
|
-
File paths with extensions (e.g., src/auth/login.ts) also survive compaction.`,
|
|
406
|
-
cost_impact: "Short focused sessions maximize cache hits and minimize context bloat",
|
|
407
|
-
});
|
|
408
|
-
// --- Team collaboration ---
|
|
409
|
-
if (team_size && team_size > 1) {
|
|
410
|
-
recommendations.push({
|
|
411
|
-
priority: "MEDIUM",
|
|
412
|
-
category: "Team",
|
|
413
|
-
action: "Set up team-wide CLAUDE.md with CLAUDE.local.md split",
|
|
414
|
-
detail: `For teams:
|
|
415
|
-
- CLAUDE.md: shared conventions, commands, structure (committed to git)
|
|
416
|
-
- CLAUDE.local.md: personal env, preferences (gitignored)
|
|
417
|
-
|
|
418
|
-
Add to .gitignore:
|
|
419
|
-
CLAUDE.local.md`,
|
|
420
|
-
cost_impact: "Consistent behavior across team members, personal overrides without conflicts",
|
|
421
|
-
});
|
|
183
|
+
if (!lower.match(/함수|function|컴포넌트|component|클래스|class|메서드|method/)) {
|
|
184
|
+
issues.push("함수/컴포넌트 지정 없음 → 파일 전체 읽기 필요");
|
|
422
185
|
}
|
|
186
|
+
const vague_cost = "$1.00-$5.00 (4+턴, 10+ 도구 호출)";
|
|
187
|
+
const specific_cost = "$0.10-$0.30 (1-2턴, 1-2 도구 호출)";
|
|
423
188
|
return {
|
|
424
|
-
content: [
|
|
425
|
-
{
|
|
189
|
+
content: [{
|
|
426
190
|
type: "text",
|
|
427
|
-
text: JSON.stringify({
|
|
428
|
-
|
|
429
|
-
|
|
191
|
+
text: JSON.stringify({
|
|
192
|
+
original: user_prompt,
|
|
193
|
+
issues,
|
|
194
|
+
optimization_tips: [
|
|
195
|
+
"파일 경로를 추가하세요 (예: src/auth/login.ts)",
|
|
196
|
+
"함수명이나 컴포넌트명을 지정하세요",
|
|
197
|
+
"에러 메시지가 있다면 포함하세요",
|
|
198
|
+
"증상을 구체적으로 설명하세요",
|
|
199
|
+
],
|
|
200
|
+
estimated_cost: { vague: vague_cost, specific: specific_cost },
|
|
201
|
+
example: {
|
|
202
|
+
before: "버그 찾아줘",
|
|
203
|
+
after: "src/routes/auth.ts의 login 함수에서 세션 만료 처리가 안 됨. 로그아웃 후에도 세션이 유지됨.",
|
|
204
|
+
},
|
|
205
|
+
}, null, 2),
|
|
206
|
+
}],
|
|
430
207
|
};
|
|
431
208
|
});
|
|
432
209
|
// =============================================================================
|
|
433
|
-
// Tool:
|
|
434
|
-
// 모델별 단가, 토큰 비율, 캐시 설정, 캐시 브레이커 등
|
|
210
|
+
// Tool 5: setup_project — 프로젝트 설정 생성 (온디맨드)
|
|
435
211
|
// =============================================================================
|
|
436
|
-
server.tool("
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
cache_config: {
|
|
465
|
-
completion_cache_ttl: "30 seconds (exact duplicate request reuse, stored locally)",
|
|
466
|
-
prompt_cache_ttl: "300 seconds (5 minutes — Anthropic server-side cache)",
|
|
467
|
-
cache_break_threshold: "2,000+ token drop in cache_read signals a cache break",
|
|
468
|
-
},
|
|
469
|
-
cache_breakers: [
|
|
470
|
-
"Model change mid-session (model_hash changes)",
|
|
471
|
-
"CLAUDE.md edit during session (system_hash changes)",
|
|
472
|
-
"MCP server add/remove during session (tools_hash changes)",
|
|
473
|
-
"Any system prompt modification",
|
|
474
|
-
],
|
|
475
|
-
compaction: {
|
|
476
|
-
auto_threshold: "100,000 input tokens (configurable: CLAUDE_CODE_AUTO_COMPACT_INPUT_TOKENS)",
|
|
477
|
-
manual_command: "/compact",
|
|
478
|
-
surviving_keywords: [
|
|
479
|
-
"todo",
|
|
480
|
-
"next",
|
|
481
|
-
"pending",
|
|
482
|
-
"follow up",
|
|
483
|
-
"remaining",
|
|
212
|
+
server.tool("setup_project", "Generate optimized Claude Code project configuration: CLAUDE.md structure, permissions, and hooks.", {
|
|
213
|
+
project_type: z.string().optional().describe("Project type: react, nextjs, rust, python, go, monorepo"),
|
|
214
|
+
has_claude_md: z.boolean().optional().describe("Whether project has CLAUDE.md"),
|
|
215
|
+
team_size: z.number().optional().describe("Number of developers"),
|
|
216
|
+
}, async ({ project_type, has_claude_md, team_size }) => {
|
|
217
|
+
const type = project_type || "unknown";
|
|
218
|
+
const formatter = type === "rust" ? "cargo fmt" : "npx prettier --write";
|
|
219
|
+
const test_cmd = type === "rust" ? "cargo test" : type === "python" ? "pytest" : "pnpm test";
|
|
220
|
+
const lint_cmd = type === "rust" ? "cargo clippy" : type === "python" ? "ruff check" : "pnpm lint";
|
|
221
|
+
const result = {};
|
|
222
|
+
if (!has_claude_md) {
|
|
223
|
+
result.claude_md = {
|
|
224
|
+
structure: [
|
|
225
|
+
"project/CLAUDE.md — 프로젝트 전체 규칙",
|
|
226
|
+
"project/CLAUDE.local.md — 개인 환경 (gitignored)",
|
|
227
|
+
"project/src/CLAUDE.md — src 하위 작업 시 추가 컨텍스트",
|
|
228
|
+
"project/tests/CLAUDE.md — 테스트 작업 시 추가 컨텍스트",
|
|
229
|
+
],
|
|
230
|
+
template: `# Project\n\n## Structure\n- src/: source code\n\n## Commands\n- Test: ${test_cmd}\n- Lint: ${lint_cmd}\n\n## Conventions\n- Commit: conventional commits (feat:, fix:, chore:)`,
|
|
231
|
+
local_template: "# Personal\n- Branch naming: yourname/feature-name",
|
|
232
|
+
tip: ".gitignore에 CLAUDE.local.md 추가",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
result.settings = {
|
|
236
|
+
permissions: {
|
|
237
|
+
allow: [
|
|
238
|
+
"Read", "Edit", "Write", "Glob", "Grep",
|
|
239
|
+
"Bash(git *)", `Bash(${type === "rust" ? "cargo *" : "pnpm *"})`,
|
|
484
240
|
],
|
|
485
|
-
surviving_paths: "File paths with / separator and known extensions (.ts, .js, .rs, .py, .json, .md) — max 8 paths preserved",
|
|
486
|
-
multi_compaction: "Previous summaries are merged, not discarded ('Previously compacted context')",
|
|
487
|
-
},
|
|
488
|
-
retry_config: {
|
|
489
|
-
max_retries: "2 (total 3 attempts)",
|
|
490
|
-
initial_backoff: "200ms",
|
|
491
|
-
max_backoff: "2 seconds",
|
|
492
|
-
retried_status_codes: "429, 500, 502, 503, 529",
|
|
493
|
-
cost_note: "429/502/503 = no charge (rejected before processing). 500/timeout = possible partial charge.",
|
|
494
241
|
},
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
242
|
+
hooks: {
|
|
243
|
+
PostToolUse: [{
|
|
244
|
+
matcher: "Edit",
|
|
245
|
+
hooks: [{ type: "command", command: `${formatter} $HOOK_TOOL_INPUT 2>/dev/null; exit 0` }],
|
|
246
|
+
}],
|
|
247
|
+
PreToolUse: [{
|
|
248
|
+
matcher: "Bash",
|
|
249
|
+
hooks: [{ type: "command", command: "echo $HOOK_TOOL_INPUT | grep -qE 'rm -rf|drop table|force push' && exit 2 || exit 0" }],
|
|
250
|
+
}],
|
|
498
251
|
},
|
|
499
|
-
quick_tips: [
|
|
500
|
-
"Output 1M tokens saved = $75 saved. Input 1M tokens saved = $15 saved. Prioritize reducing output.",
|
|
501
|
-
"Keep sessions under 30 min for optimal cache utilization",
|
|
502
|
-
"One session = one task. Short focused sessions > long wandering ones.",
|
|
503
|
-
"/cost to monitor, /compact to compress, /clear to reset",
|
|
504
|
-
"Cafe WiFi? Avoid large operations. Timeout → retry → possible double billing.",
|
|
505
|
-
],
|
|
506
|
-
};
|
|
507
|
-
return {
|
|
508
|
-
content: [
|
|
509
|
-
{
|
|
510
|
-
type: "text",
|
|
511
|
-
text: JSON.stringify(reference, null, 2),
|
|
512
|
-
},
|
|
513
|
-
],
|
|
514
252
|
};
|
|
253
|
+
result.cost_tips = [
|
|
254
|
+
"CLAUDE.md는 세션 시작 전에 수정 (세션 중 수정 = 캐시 브레이크)",
|
|
255
|
+
"MCP 서버는 세션 시작 전에 설정 (도구 변경 = 캐시 브레이크)",
|
|
256
|
+
"필요한 MCP만 활성화 (각 도구 정의가 매 턴 토큰 차지)",
|
|
257
|
+
];
|
|
258
|
+
if (team_size && team_size > 1) {
|
|
259
|
+
result.team = {
|
|
260
|
+
shared: "CLAUDE.md (git tracked) — 팀 공통 규칙",
|
|
261
|
+
personal: "CLAUDE.local.md (gitignored) — 개인 설정",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
515
265
|
});
|
|
516
266
|
// =============================================================================
|
|
517
|
-
// Start
|
|
267
|
+
// Start
|
|
518
268
|
// =============================================================================
|
|
519
269
|
async function main() {
|
|
520
270
|
const transport = new StdioServerTransport();
|