@vibecodetown/mcp-server 2.1.4 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +10 -10
  2. package/build/auth/credential_store.js +146 -0
  3. package/build/auth/public_key.js +6 -4
  4. package/build/bootstrap/doctor.js +113 -5
  5. package/build/bootstrap/installer.js +85 -15
  6. package/build/bootstrap/registry.js +11 -6
  7. package/build/bootstrap/skills-installer.js +365 -0
  8. package/build/control_plane/gate.js +52 -70
  9. package/build/dx/activity.js +26 -3
  10. package/build/engine.js +151 -0
  11. package/build/errors.js +107 -0
  12. package/build/generated/bridge_build_seed_input.js +2 -0
  13. package/build/generated/bridge_build_seed_output.js +2 -0
  14. package/build/generated/bridge_confirm_reference_input.js +2 -0
  15. package/build/generated/bridge_confirm_reference_output.js +2 -0
  16. package/build/generated/bridge_confirmed_reference_file.js +2 -0
  17. package/build/generated/bridge_generate_references_input.js +2 -0
  18. package/build/generated/bridge_generate_references_output.js +2 -0
  19. package/build/generated/bridge_references_file.js +2 -0
  20. package/build/generated/bridge_work_order_seed_file.js +2 -0
  21. package/build/generated/contracts_bundle_info.js +3 -3
  22. package/build/generated/index.js +14 -0
  23. package/build/generated/ingress_input.js +2 -0
  24. package/build/generated/ingress_output.js +2 -0
  25. package/build/generated/ingress_resolution_file.js +2 -0
  26. package/build/generated/ingress_summary_file.js +2 -0
  27. package/build/generated/message_template_id_mapping_file.js +2 -0
  28. package/build/generated/run_app_input.js +1 -1
  29. package/build/index.js +4 -1
  30. package/build/local-mode/git.js +36 -22
  31. package/build/local-mode/paths.js +1 -0
  32. package/build/local-mode/project-state.js +176 -0
  33. package/build/local-mode/setup.js +21 -1
  34. package/build/local-mode/templates.js +3 -3
  35. package/build/path-utils.js +68 -0
  36. package/build/runtime/cli_invoker.js +416 -0
  37. package/build/tools/vibe_pm/advisory_review.js +5 -3
  38. package/build/tools/vibe_pm/bridge_build_seed.js +164 -0
  39. package/build/tools/vibe_pm/bridge_confirm_reference.js +91 -0
  40. package/build/tools/vibe_pm/bridge_generate_references.js +258 -0
  41. package/build/tools/vibe_pm/briefing.js +26 -1
  42. package/build/tools/vibe_pm/context.js +79 -0
  43. package/build/tools/vibe_pm/create_work_order.js +200 -3
  44. package/build/tools/vibe_pm/doctor.js +95 -0
  45. package/build/tools/vibe_pm/entity_gate/preflight.js +8 -3
  46. package/build/tools/vibe_pm/export_output.js +14 -13
  47. package/build/tools/vibe_pm/finalize_work.js +74 -0
  48. package/build/tools/vibe_pm/force_override.js +104 -0
  49. package/build/tools/vibe_pm/get_decision.js +2 -2
  50. package/build/tools/vibe_pm/index.js +160 -3
  51. package/build/tools/vibe_pm/ingress.js +645 -0
  52. package/build/tools/vibe_pm/ingress_gate.js +116 -0
  53. package/build/tools/vibe_pm/inspect_code.js +90 -20
  54. package/build/tools/vibe_pm/kce/doc_usage.js +4 -9
  55. package/build/tools/vibe_pm/kce/on_finalize.js +2 -2
  56. package/build/tools/vibe_pm/kce/preflight.js +11 -7
  57. package/build/tools/vibe_pm/list_rules.js +135 -0
  58. package/build/tools/vibe_pm/memory_status.js +11 -8
  59. package/build/tools/vibe_pm/memory_sync.js +11 -8
  60. package/build/tools/vibe_pm/pm_language.js +17 -16
  61. package/build/tools/vibe_pm/pre_commit_analysis.js +292 -0
  62. package/build/tools/vibe_pm/publish_mcp.js +271 -0
  63. package/build/tools/vibe_pm/python_error.js +115 -0
  64. package/build/tools/vibe_pm/run_app.js +215 -86
  65. package/build/tools/vibe_pm/run_app_podman.js +64 -2
  66. package/build/tools/vibe_pm/save_rule.js +120 -0
  67. package/build/tools/vibe_pm/search_oss.js +5 -3
  68. package/build/tools/vibe_pm/spec_rag.js +185 -0
  69. package/build/tools/vibe_pm/status.js +50 -3
  70. package/build/tools/vibe_pm/submit_decision.js +2 -2
  71. package/build/tools/vibe_pm/types.js +28 -0
  72. package/build/tools/vibe_pm/undo_last_task.js +23 -20
  73. package/build/tools/vibe_pm/waiter_mapping.js +155 -0
  74. package/build/tools/vibe_pm/zoekt_evidence.js +5 -3
  75. package/build/tools.js +13 -5
  76. package/build/version-check.js +5 -5
  77. package/build/vibe-cli.js +742 -39
  78. package/package.json +5 -4
  79. package/skills/VRIP_INSTALL_MANIFEST_DOCTOR.skill.md +288 -0
  80. package/skills/index.json +14 -0
@@ -1,6 +1,7 @@
1
1
  // adapters/mcp-ts/src/tools/vibe_pm/pm_language.ts
2
2
  // PM Language transformation utilities
3
3
  // Reference: docs/DEV_SPEC/MCP_TOOL_SCHEMA_SPEC.md Section: Status Mapping
4
+ import { applyTermDictionary, resolveLocaleFromText } from "./waiter_mapping.js";
4
5
  // ============================================================
5
6
  // Signal Level Mapping (Internal → User-facing)
6
7
  // ============================================================
@@ -202,21 +203,21 @@ ${verifyList}
202
203
  * This is a simplified version - full implementation would parse and restructure
203
204
  */
204
205
  export function transformAskQueueText(rawText) {
205
- let transformed = rawText;
206
- // Replace common technical terms with PM-friendly alternatives
207
- const replacements = [
208
- [/run_id/gi, "프로젝트"],
209
- [/bridge/gi, "작업 지시서"],
210
- [/schema/gi, "구조"],
211
- [/gate/gi, "검토"],
212
- [/signal/gi, "상태"],
213
- [/FSM/gi, "워크플로우"],
214
- [/spec_drift/gi, "범위 변경"],
215
- [/decision_card/gi, "결재 안건"],
216
- [/clinic_bridge/gi, "검수 기준"]
217
- ];
218
- for (const [pattern, replacement] of replacements) {
219
- transformed = transformed.replace(pattern, replacement);
206
+ try {
207
+ const locale = resolveLocaleFromText(rawText);
208
+ return applyTermDictionary(rawText, locale);
209
+ }
210
+ catch {
211
+ // Best-effort fallback: preserve previous behavior when mapping is unavailable.
212
+ return rawText
213
+ .replace(/run_id/gi, "프로젝트")
214
+ .replace(/bridge/gi, "작업 지시서")
215
+ .replace(/schema/gi, "구조")
216
+ .replace(/gate/gi, "검토")
217
+ .replace(/signal/gi, "상태")
218
+ .replace(/FSM/gi, "워크플로우")
219
+ .replace(/spec_drift/gi, "범위 변경")
220
+ .replace(/decision_card/gi, "결재 안건")
221
+ .replace(/clinic_bridge/gi, "검수 기준");
220
222
  }
221
- return transformed;
222
223
  }
@@ -0,0 +1,292 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/pre_commit_analysis.ts
2
+ // Pre-commit analysis: categorize changes and suggest staging strategy
3
+ import { simpleGit } from "simple-git";
4
+ /**
5
+ * Categorize a file based on its path
6
+ */
7
+ function categorizeFile(filePath) {
8
+ const normalized = filePath.replace(/\\/g, "/");
9
+ // MCP TypeScript
10
+ if (normalized.includes("adapters/mcp-ts/src/"))
11
+ return "mcp_source";
12
+ if (normalized.includes("adapters/mcp-ts/build/"))
13
+ return "mcp_build";
14
+ if (normalized.includes("adapters/mcp-ts/test"))
15
+ return "mcp_test";
16
+ if (normalized.includes("adapters/mcp-ts/"))
17
+ return "mcp_other";
18
+ // Engines (Rust)
19
+ if (normalized.includes("engines/") && normalized.endsWith(".rs"))
20
+ return "engine_rust";
21
+ if (normalized.includes("engines/"))
22
+ return "engine_other";
23
+ // Documentation
24
+ if (normalized.includes("docs/"))
25
+ return "docs";
26
+ if (normalized.endsWith(".md"))
27
+ return "docs";
28
+ // Python
29
+ if (normalized.includes("vibecoding_helper/"))
30
+ return "python_core";
31
+ if (normalized.endsWith(".py"))
32
+ return "python";
33
+ // Config/CI
34
+ if (normalized.includes(".github/"))
35
+ return "ci";
36
+ if (normalized.includes(".vibe/"))
37
+ return "vibe_config";
38
+ if (normalized.includes("schemas/"))
39
+ return "schemas";
40
+ if (normalized.endsWith(".json") ||
41
+ normalized.endsWith(".yaml") ||
42
+ normalized.endsWith(".yml") ||
43
+ normalized.endsWith(".toml"))
44
+ return "config";
45
+ // Generated
46
+ if (normalized.includes("/generated/"))
47
+ return "generated";
48
+ // Tests
49
+ if (normalized.includes("/test") || normalized.includes("/tests/"))
50
+ return "tests";
51
+ // Build artifacts
52
+ if (normalized.includes("/dist/") ||
53
+ normalized.includes("/build/") ||
54
+ normalized.includes("/target/"))
55
+ return "build_artifacts";
56
+ // Lock files
57
+ if (normalized.endsWith("-lock.json") ||
58
+ normalized.endsWith(".lock") ||
59
+ normalized.endsWith("-lock.yaml"))
60
+ return "lock_files";
61
+ return "other";
62
+ }
63
+ /**
64
+ * Get human-readable category info
65
+ */
66
+ function getCategoryInfo(category) {
67
+ const info = {
68
+ mcp_source: {
69
+ name: "MCP 소스 코드",
70
+ description: "TypeScript MCP 서버 소스 (빌드 후 npm 배포 필요할 수 있음)",
71
+ suggested: true,
72
+ },
73
+ mcp_build: {
74
+ name: "MCP 빌드 결과물",
75
+ description: "컴파일된 JavaScript (보통 gitignore됨)",
76
+ suggested: false,
77
+ },
78
+ mcp_test: {
79
+ name: "MCP 테스트",
80
+ description: "TypeScript 테스트 파일",
81
+ suggested: true,
82
+ },
83
+ mcp_other: {
84
+ name: "MCP 기타",
85
+ description: "MCP 패키지 설정, 스크립트 등",
86
+ suggested: true,
87
+ },
88
+ engine_rust: {
89
+ name: "Rust 엔진 코드",
90
+ description: "엔진 소스 (빌드 후 GitHub Release 필요할 수 있음)",
91
+ suggested: true,
92
+ },
93
+ engine_other: {
94
+ name: "엔진 기타",
95
+ description: "엔진 설정, 문서 등",
96
+ suggested: true,
97
+ },
98
+ docs: {
99
+ name: "문서",
100
+ description: "마크다운 문서, 스펙",
101
+ suggested: true,
102
+ },
103
+ python_core: {
104
+ name: "Python 코어",
105
+ description: "vibecoding_helper 모듈",
106
+ suggested: true,
107
+ },
108
+ python: {
109
+ name: "Python 기타",
110
+ description: "Python 스크립트, 테스트",
111
+ suggested: true,
112
+ },
113
+ ci: {
114
+ name: "CI/CD",
115
+ description: "GitHub Actions 워크플로우",
116
+ suggested: true,
117
+ },
118
+ vibe_config: {
119
+ name: "Vibe 설정",
120
+ description: ".vibe/ 로컬 설정 (보통 gitignore됨)",
121
+ suggested: false,
122
+ },
123
+ schemas: {
124
+ name: "스키마",
125
+ description: "JSON Schema 정의",
126
+ suggested: true,
127
+ },
128
+ config: {
129
+ name: "설정 파일",
130
+ description: "JSON, YAML, TOML 설정",
131
+ suggested: true,
132
+ },
133
+ generated: {
134
+ name: "생성된 코드",
135
+ description: "스키마에서 자동 생성된 코드 (재생성 가능)",
136
+ suggested: false,
137
+ },
138
+ tests: {
139
+ name: "테스트",
140
+ description: "테스트 파일",
141
+ suggested: true,
142
+ },
143
+ build_artifacts: {
144
+ name: "빌드 결과물",
145
+ description: "컴파일된 파일 (보통 gitignore됨)",
146
+ suggested: false,
147
+ },
148
+ lock_files: {
149
+ name: "Lock 파일",
150
+ description: "의존성 잠금 파일",
151
+ suggested: true,
152
+ },
153
+ other: {
154
+ name: "기타",
155
+ description: "분류되지 않은 파일",
156
+ suggested: false,
157
+ },
158
+ };
159
+ return info[category] || info.other;
160
+ }
161
+ /**
162
+ * Analyze git status and categorize changes
163
+ */
164
+ export async function analyzePreCommit(basePath = process.cwd()) {
165
+ const git = simpleGit({ baseDir: basePath });
166
+ const status = await git.status();
167
+ // Collect all changed files
168
+ const changes = [];
169
+ for (const file of status.not_added) {
170
+ changes.push({ path: file, status: "new", staged: false });
171
+ }
172
+ for (const file of status.created) {
173
+ changes.push({ path: file, status: "new", staged: true });
174
+ }
175
+ for (const file of status.modified) {
176
+ changes.push({ path: file, status: "modified", staged: false });
177
+ }
178
+ for (const file of status.staged) {
179
+ if (!changes.find((c) => c.path === file)) {
180
+ changes.push({ path: file, status: "modified", staged: true });
181
+ }
182
+ }
183
+ for (const file of status.deleted) {
184
+ changes.push({ path: file, status: "deleted", staged: false });
185
+ }
186
+ for (const file of status.renamed) {
187
+ changes.push({ path: file.to, status: "renamed", staged: true });
188
+ }
189
+ // Group by category
190
+ const categoryMap = new Map();
191
+ for (const change of changes) {
192
+ const cat = categorizeFile(change.path);
193
+ if (!categoryMap.has(cat)) {
194
+ categoryMap.set(cat, []);
195
+ }
196
+ categoryMap.get(cat).push(change);
197
+ }
198
+ // Build categories
199
+ const categories = [];
200
+ for (const [cat, files] of categoryMap) {
201
+ const info = getCategoryInfo(cat);
202
+ categories.push({
203
+ name: info.name,
204
+ description: info.description,
205
+ files,
206
+ suggested: info.suggested,
207
+ });
208
+ }
209
+ // Sort: suggested first, then by file count
210
+ categories.sort((a, b) => {
211
+ if (a.suggested !== b.suggested)
212
+ return a.suggested ? -1 : 1;
213
+ return b.files.length - a.files.length;
214
+ });
215
+ // Generate suggestions
216
+ const suggestions = [];
217
+ const warnings = [];
218
+ // Check for MCP source changes
219
+ const mcpSource = categoryMap.get("mcp_source");
220
+ if (mcpSource && mcpSource.length > 0) {
221
+ suggestions.push("MCP 소스 변경됨 → 커밋 후 `vibe_pm.publish_mcp` 실행 권장");
222
+ }
223
+ // Check for engine changes
224
+ const engineRust = categoryMap.get("engine_rust");
225
+ if (engineRust && engineRust.length > 0) {
226
+ suggestions.push("Rust 엔진 변경됨 → 커밋 후 엔진 빌드 + GitHub Release 필요할 수 있음");
227
+ }
228
+ // Check for vibe config
229
+ const vibeConfig = categoryMap.get("vibe_config");
230
+ if (vibeConfig && vibeConfig.length > 0) {
231
+ warnings.push(".vibe/ 파일은 보통 gitignore됩니다. 의도적 커밋인지 확인하세요.");
232
+ }
233
+ // Check for build artifacts
234
+ const buildArtifacts = categoryMap.get("build_artifacts");
235
+ if (buildArtifacts && buildArtifacts.length > 0) {
236
+ warnings.push("빌드 결과물은 보통 gitignore됩니다. 의도적 커밋인지 확인하세요.");
237
+ }
238
+ // Check for sensitive patterns
239
+ for (const change of changes) {
240
+ const lower = change.path.toLowerCase();
241
+ if (lower.includes("secret") ||
242
+ lower.includes("credential") ||
243
+ lower.includes(".env") ||
244
+ lower.includes("token")) {
245
+ warnings.push(`민감한 파일 감지: ${change.path} - 커밋 전 확인 필요`);
246
+ }
247
+ }
248
+ return {
249
+ categories,
250
+ total_files: changes.length,
251
+ suggestions,
252
+ warnings,
253
+ };
254
+ }
255
+ /**
256
+ * Generate a summary for user decision
257
+ */
258
+ export function formatAnalysisSummary(analysis) {
259
+ const lines = [];
260
+ lines.push(`## 변경 파일 분석 (총 ${analysis.total_files}개)\n`);
261
+ for (const cat of analysis.categories) {
262
+ const emoji = cat.suggested ? "✅" : "⚠️";
263
+ lines.push(`### ${emoji} ${cat.name} (${cat.files.length}개)`);
264
+ lines.push(`> ${cat.description}`);
265
+ lines.push("");
266
+ // Show up to 5 files
267
+ const shown = cat.files.slice(0, 5);
268
+ for (const f of shown) {
269
+ const statusIcon = f.staged ? "📦" : "📝";
270
+ lines.push(`- ${statusIcon} ${f.path}`);
271
+ }
272
+ if (cat.files.length > 5) {
273
+ lines.push(`- ... 외 ${cat.files.length - 5}개`);
274
+ }
275
+ lines.push("");
276
+ }
277
+ if (analysis.suggestions.length > 0) {
278
+ lines.push("### 💡 제안");
279
+ for (const s of analysis.suggestions) {
280
+ lines.push(`- ${s}`);
281
+ }
282
+ lines.push("");
283
+ }
284
+ if (analysis.warnings.length > 0) {
285
+ lines.push("### ⚠️ 경고");
286
+ for (const w of analysis.warnings) {
287
+ lines.push(`- ${w}`);
288
+ }
289
+ lines.push("");
290
+ }
291
+ return lines.join("\n");
292
+ }
@@ -0,0 +1,271 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/publish_mcp.ts
2
+ // MCP build and publish workflow
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { getCredentialStore } from "../../auth/credential_store.js";
7
+ /**
8
+ * Check if MCP source files have changed since last build
9
+ */
10
+ async function hasSourceChanges(mcpRoot) {
11
+ const srcDir = path.join(mcpRoot, "src");
12
+ const buildDir = path.join(mcpRoot, "build");
13
+ // If no build folder, definitely changed
14
+ if (!fs.existsSync(buildDir)) {
15
+ return { changed: true, files: ["build/ not found"] };
16
+ }
17
+ // Get build timestamp
18
+ const buildIndex = path.join(buildDir, "index.js");
19
+ if (!fs.existsSync(buildIndex)) {
20
+ return { changed: true, files: ["build/index.js not found"] };
21
+ }
22
+ const buildStat = fs.statSync(buildIndex);
23
+ const buildTime = buildStat.mtime.getTime();
24
+ // Find source files newer than build
25
+ const changedFiles = [];
26
+ function checkDir(dir) {
27
+ if (!fs.existsSync(dir))
28
+ return;
29
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
30
+ for (const entry of entries) {
31
+ const fullPath = path.join(dir, entry.name);
32
+ if (entry.isDirectory()) {
33
+ checkDir(fullPath);
34
+ }
35
+ else if (entry.name.endsWith(".ts")) {
36
+ const stat = fs.statSync(fullPath);
37
+ if (stat.mtime.getTime() > buildTime) {
38
+ changedFiles.push(path.relative(mcpRoot, fullPath));
39
+ }
40
+ }
41
+ }
42
+ }
43
+ checkDir(srcDir);
44
+ return { changed: changedFiles.length > 0, files: changedFiles };
45
+ }
46
+ /**
47
+ * Run TypeScript build
48
+ */
49
+ function runBuild(mcpRoot) {
50
+ try {
51
+ const result = spawnSync("npm", ["run", "build"], {
52
+ cwd: mcpRoot,
53
+ encoding: "utf-8",
54
+ shell: true,
55
+ timeout: 120_000, // 2 minutes
56
+ });
57
+ if (result.status === 0) {
58
+ return { success: true, output: result.stdout || "" };
59
+ }
60
+ return { success: false, output: result.stderr || result.stdout || "Build failed" };
61
+ }
62
+ catch (e) {
63
+ return { success: false, output: e instanceof Error ? e.message : String(e) };
64
+ }
65
+ }
66
+ /**
67
+ * Bump version in package.json
68
+ */
69
+ function bumpVersion(mcpRoot, type) {
70
+ try {
71
+ const result = spawnSync("npm", ["version", type, "--no-git-tag-version"], {
72
+ cwd: mcpRoot,
73
+ encoding: "utf-8",
74
+ shell: true,
75
+ });
76
+ if (result.status === 0) {
77
+ // Read new version from package.json
78
+ const pkgPath = path.join(mcpRoot, "package.json");
79
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
80
+ return pkg.version;
81
+ }
82
+ throw new Error(result.stderr || "Version bump failed");
83
+ }
84
+ catch (e) {
85
+ throw new Error(`Version bump failed: ${e instanceof Error ? e.message : String(e)}`);
86
+ }
87
+ }
88
+ /**
89
+ * Run npm publish
90
+ */
91
+ async function runPublish(mcpRoot, dryRun) {
92
+ const store = getCredentialStore();
93
+ const npmToken = await store.getNpmToken();
94
+ // Set NPM_TOKEN if we have one stored
95
+ const env = { ...process.env };
96
+ if (npmToken) {
97
+ env.NPM_TOKEN = npmToken;
98
+ }
99
+ try {
100
+ const args = ["publish", "--access", "public"];
101
+ if (dryRun) {
102
+ args.push("--dry-run");
103
+ }
104
+ const result = spawnSync("npm", args, {
105
+ cwd: mcpRoot,
106
+ encoding: "utf-8",
107
+ shell: true,
108
+ env,
109
+ timeout: 120_000, // 2 minutes
110
+ });
111
+ if (result.status === 0) {
112
+ return { success: true, output: result.stdout || "" };
113
+ }
114
+ return { success: false, output: result.stderr || result.stdout || "Publish failed" };
115
+ }
116
+ catch (e) {
117
+ return { success: false, output: e instanceof Error ? e.message : String(e) };
118
+ }
119
+ }
120
+ /**
121
+ * vibe_pm.publish_mcp - Build and publish MCP package
122
+ *
123
+ * Workflow:
124
+ * 1. Check for source changes (skip if no changes unless --force)
125
+ * 2. Run npm build (tsc)
126
+ * 3. Optionally bump version
127
+ * 4. Run npm publish
128
+ *
129
+ * PM-friendly description:
130
+ * MCP 패키지를 빌드하고 npm에 배포합니다.
131
+ */
132
+ export async function publishMcp(input, basePath = process.cwd()) {
133
+ const warnings = [];
134
+ // Find MCP root (adapters/mcp-ts)
135
+ const mcpRoot = path.join(basePath, "adapters", "mcp-ts");
136
+ if (!fs.existsSync(path.join(mcpRoot, "package.json"))) {
137
+ return {
138
+ success: false,
139
+ phase: "check",
140
+ version: "unknown",
141
+ built: false,
142
+ published: false,
143
+ message: "MCP 패키지를 찾을 수 없습니다: adapters/mcp-ts/package.json",
144
+ warnings,
145
+ };
146
+ }
147
+ // Read current version
148
+ const pkgPath = path.join(mcpRoot, "package.json");
149
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
150
+ let version = pkg.version;
151
+ // Phase 1: Check for changes
152
+ const { changed, files } = await hasSourceChanges(mcpRoot);
153
+ if (!changed && !input.force && !input.skip_build) {
154
+ return {
155
+ success: true,
156
+ phase: "check",
157
+ version,
158
+ built: false,
159
+ published: false,
160
+ message: "소스 변경 없음. 빌드 스킵.",
161
+ warnings: ["--force로 강제 빌드 가능"],
162
+ };
163
+ }
164
+ // Phase 2: Build
165
+ let built = false;
166
+ if (!input.skip_build) {
167
+ const buildResult = runBuild(mcpRoot);
168
+ if (!buildResult.success) {
169
+ return {
170
+ success: false,
171
+ phase: "build",
172
+ version,
173
+ built: false,
174
+ published: false,
175
+ message: `빌드 실패: ${buildResult.output}`,
176
+ warnings,
177
+ };
178
+ }
179
+ built = true;
180
+ if (files.length > 0) {
181
+ warnings.push(`변경된 파일: ${files.slice(0, 5).join(", ")}${files.length > 5 ? ` 외 ${files.length - 5}개` : ""}`);
182
+ }
183
+ }
184
+ // Build only mode
185
+ if (input.build_only) {
186
+ return {
187
+ success: true,
188
+ phase: "build",
189
+ version,
190
+ built,
191
+ published: false,
192
+ message: `빌드 완료 (v${version})`,
193
+ warnings,
194
+ next_action: {
195
+ tool: "vibe_pm.publish_mcp",
196
+ reason: "배포하려면 build_only: false로 다시 실행",
197
+ },
198
+ };
199
+ }
200
+ // Phase 3: Version bump (optional)
201
+ if (input.bump) {
202
+ try {
203
+ version = bumpVersion(mcpRoot, input.bump);
204
+ warnings.push(`버전 범프: ${pkg.version} → ${version}`);
205
+ }
206
+ catch (e) {
207
+ return {
208
+ success: false,
209
+ phase: "publish",
210
+ version,
211
+ built,
212
+ published: false,
213
+ message: e instanceof Error ? e.message : String(e),
214
+ warnings,
215
+ };
216
+ }
217
+ }
218
+ // Phase 4: Publish
219
+ const publishResult = await runPublish(mcpRoot, input.dry_run ?? false);
220
+ if (!publishResult.success) {
221
+ // Check if it's an auth error
222
+ if (publishResult.output.includes("ENEEDAUTH") || publishResult.output.includes("401")) {
223
+ return {
224
+ success: false,
225
+ phase: "publish",
226
+ version,
227
+ built,
228
+ published: false,
229
+ message: "npm 인증 실패. NPM_TOKEN을 설정하거나 npm login을 실행하세요.",
230
+ warnings: [
231
+ "vibe config set npm_token <TOKEN> 으로 토큰 저장 가능",
232
+ "또는 NPM_TOKEN 환경변수 설정",
233
+ ],
234
+ };
235
+ }
236
+ return {
237
+ success: false,
238
+ phase: "publish",
239
+ version,
240
+ built,
241
+ published: false,
242
+ message: `배포 실패: ${publishResult.output}`,
243
+ warnings,
244
+ };
245
+ }
246
+ const dryRunMsg = input.dry_run ? " (dry-run)" : "";
247
+ return {
248
+ success: true,
249
+ phase: "complete",
250
+ version,
251
+ built,
252
+ published: !input.dry_run,
253
+ message: `@vibecodetown/mcp-server@${version} 배포 완료${dryRunMsg}`,
254
+ warnings,
255
+ next_action: {
256
+ tool: "vibe_pm.status",
257
+ reason: "배포 상태 확인",
258
+ },
259
+ };
260
+ }
261
+ /**
262
+ * Quick check if MCP needs rebuild
263
+ */
264
+ export async function needsMcpBuild(basePath = process.cwd()) {
265
+ const mcpRoot = path.join(basePath, "adapters", "mcp-ts");
266
+ if (!fs.existsSync(path.join(mcpRoot, "package.json"))) {
267
+ return false;
268
+ }
269
+ const { changed } = await hasSourceChanges(mcpRoot);
270
+ return changed;
271
+ }