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