@byh3071/vhk 0.5.2 โ†’ 0.6.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 CHANGED
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  id: vhk-readme
3
- date: 2026-05-23
4
- tags: [vhk, cli, readme, v0.5.2]
3
+ date: 2026-05-24
4
+ tags: [vhk, cli, readme, v0.6.0]
5
5
  ---
6
6
 
7
7
  # ๐Ÿ”ง VHK โ€” Vibe Harness Kit
8
8
 
9
- > AI ์ฝ”๋”ฉ ์—์ด์ „ํŠธ๋ฅผ ๋ถ€๋ฆฌ๋Š” ์‚ฌ๋žŒ์„ ์œ„ํ•œ **ํ•œ๊ตญ์–ด ํ’€์‚ฌ์ดํด CLI** (v0.5.2)
9
+ > AI ์ฝ”๋”ฉ ์—์ด์ „ํŠธ๋ฅผ ๋ถ€๋ฆฌ๋Š” ์‚ฌ๋žŒ์„ ์œ„ํ•œ **ํ•œ๊ตญ์–ด ํ’€์‚ฌ์ดํด CLI** (v0.6.0)
10
+ >
11
+ > ๐Ÿฝ๏ธ **VHK๋Š” VHK๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๋จ** โ€” ์ด ๋ ˆํฌ์˜ `docs/`, `CLAUDE.md`, `.cursorrules`๋„ `vhk init`์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
10
12
 
11
13
  ๋ช…๋ น์–ด๋ฅผ ์™ธ์šฐ์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. `vhk`๋งŒ ์น˜๋ฉด ๋ฉ”๋‰ด๊ฐ€ ๋‚˜์˜ค๊ณ , ํ•œ๊ตญ์–ด๋กœ ๋งํ•ด๋„ ์•Œ์•„๋“ฃ์Šต๋‹ˆ๋‹ค.
12
14
 
@@ -87,6 +89,8 @@ vhk ๊ธฐํš ๋๋‚ฌ๊ณ  ๋ฐ”๋กœ ์‹œ์ž‘
87
89
  | `vhk undo` | `๋˜๋Œ๋ฆฌ๊ธฐ`, `์ทจ์†Œ` | ์ตœ๊ทผ ์ปค๋ฐ‹ soft reset (๋ณ€๊ฒฝ์€ staged ์œ ์ง€) |
88
90
  | `vhk diff` | `๋ณ€๊ฒฝ`, `์ฐจ์ด` | staged / unstaged / ์ƒˆ ํŒŒ์ผ ์š”์•ฝ (์ค„ ์ˆ˜ ํ•ฉ๊ณ„๋Š” trackedยทHEAD ๊ธฐ์ค€) |
89
91
  | `vhk status` | `์ƒํƒœ`, `ํ˜„ํ™ฉ` | ๋ธŒ๋žœ์น˜ยท๋ณ€๊ฒฝยท์ปค๋ฐ‹ยท์›๊ฒฉยท๋ฒ„์ „ ๋Œ€์‹œ๋ณด๋“œ |
92
+ | `vhk mcp` | โ€” | MCP ์„œ๋ฒ„ ์‹œ์ž‘ (Cursor ๋“ฑ MCP ํด๋ผ์ด์–ธํŠธ์šฉ, stdio) |
93
+ | `vhk mcp-init` | `mcp์„ค์ •` | Cursor `.cursor/mcp.json` ์ž๋™ ์ƒ์„ฑ |
90
94
 
91
95
  ### init ์˜ต์…˜
92
96
 
@@ -103,6 +107,42 @@ vhk ๊ธฐํš ๋๋‚ฌ๊ณ  ๋ฐ”๋กœ ์‹œ์ž‘
103
107
  |------|------|
104
108
  | `--since YYYY-MM-DD` | ๋ถ„์„ ์‹œ์ž‘์ผ (๊ธฐ๋ณธ: ์˜ค๋Š˜) |
105
109
 
110
+ ## Cursor์™€ MCP๋กœ ์—ฐ๋™ํ•˜๊ธฐ
111
+
112
+ `v0.6.0`๋ถ€ํ„ฐ vhk๋Š” [Model Context Protocol](https://modelcontextprotocol.io) ์„œ๋ฒ„๋ฅผ ๋‚ด์žฅํ•ฉ๋‹ˆ๋‹ค. Cursor ์ฑ„ํŒ…์—์„œ ์ž์—ฐ์–ด๋กœ vhk ๋„๊ตฌ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
113
+
114
+ ```powershell
115
+ vhk mcp-init # .cursor/mcp.json ์ž๋™ ์ƒ์„ฑ
116
+ # โ†’ Cursor ์žฌ์‹œ์ž‘ ํ›„ ์ฑ„ํŒ…์—์„œ ์ž์—ฐ์–ด๋กœ vhk ๋„๊ตฌ ํ˜ธ์ถœ ๊ฐ€๋Šฅ
117
+ # ์˜ˆ: "์ƒํƒœ ์•Œ๋ ค์ค˜" โ†’ Cursor๊ฐ€ vhk status ๋„๊ตฌ ํ˜ธ์ถœ
118
+ ```
119
+
120
+ ๋…ธ์ถœ๋˜๋Š” MCP ๋„๊ตฌ 8๊ฐœ: `save`, `undo`, `status`, `diff`, `ship`, `doctor`, `check`, `recap`.
121
+
122
+ MCP ์„œ๋ฒ„๋ฅผ ์ˆ˜๋™์œผ๋กœ ๋„์šฐ๋ ค๋ฉด:
123
+
124
+ ```powershell
125
+ vhk mcp # stdio ์„œ๋ฒ„ ์‹œ์ž‘ (Cursor๊ฐ€ ์ž๋™์œผ๋กœ ํ˜ธ์ถœ)
126
+ ```
127
+
128
+ ## v0.6.0 ํ•˜์ด๋ผ์ดํŠธ
129
+
130
+ | ๊ธฐ๋Šฅ | ์„ค๋ช… |
131
+ |------|------|
132
+ | **MCP ์„œ๋ฒ„** | `vhk mcp` โ€” 8๊ฐœ ๋„๊ตฌ(save/undo/status/diff/ship/doctor/check/recap)๋ฅผ stdio๋กœ ๋…ธ์ถœ. Cursor ๋“ฑ MCP ํด๋ผ์ด์–ธํŠธ์—์„œ ์ž์—ฐ์–ด๋กœ ํ˜ธ์ถœ |
133
+ | **mcp-init** | `vhk mcp-init` โ€” Cursor `.cursor/mcp.json` ์ž๋™ ์ƒ์„ฑ. ์žฌ์‹œ์ž‘ ํ•œ ๋ฒˆ์œผ๋กœ ์—ฐ๋™ ์™„๋ฃŒ |
134
+ | **์ž์—ฐ์–ด ๋ผ์šฐํŒ… ํ™•์žฅ** | `vhk mcp์„ค์ •` โ†’ `vhk mcp-init` ๋ณ„์นญ |
135
+ | **๋ณด์•ˆ** | MCP save ๋„๊ตฌ์˜ shell injection ์ฐจ๋‹จ โ€” ๋ชจ๋“  git ํ˜ธ์ถœ์— `execFileSync` ์‚ฌ์šฉ |
136
+
137
+ ## v0.5.3 ํ•˜์ด๋ผ์ดํŠธ
138
+
139
+ | ๊ธฐ๋Šฅ | ์„ค๋ช… |
140
+ |------|------|
141
+ | **์…€ํ”„ํ˜ธ์ŠคํŒ…** | `vhk init`์ด vhk-cli ๋ ˆํฌ ์ž์ฒด๋ฅผ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ โ€” ์ž๊ธฐ ๋„๊ตฌ๋กœ ์ž๊ธฐ ๋ ˆํฌ ๋งŒ๋“ค๊ธฐ |
142
+ | **CHANGELOG.md** | ๋ณ€๊ฒฝ ์ด๋ ฅ ํ‘œ์ค€ ํŒŒ์ผ ์‹ ์„ค. `vhk ship`์ด `[Unreleased]` โ†’ `[๋ฒ„์ „]` ์ž๋™ ์ด๋™ |
143
+ | **doctor ์—…๋ฐ์ดํŠธ ์•Œ๋ฆผ** | `vhk doctor`๊ฐ€ npm ์ตœ์‹  ๋ฒ„์ „ ๋น„๊ต ํ›„ `๐Ÿ†• v0.X.X ์‚ฌ์šฉ ๊ฐ€๋Šฅ` ํ•œ ์ค„ ํ‘œ์‹œ |
144
+ | **init ์•ˆ์ „์„ฑ** | ์˜ต์…˜๊ฐ’ ํฌํ•จ ๋ช…๋ น ๋ผ์šฐํŒ… ๋ฒ„๊ทธ ํ”ฝ์Šค, ์‚ฌ์šฉ์ž ์ •์˜ `package.json` scripts ๋ณด์กด |
145
+
106
146
  ## v0.5.0 ํ•˜์ด๋ผ์ดํŠธ
107
147
 
108
148
  | ๊ธฐ๋Šฅ | ์„ค๋ช… |
@@ -0,0 +1,298 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+
28
+ // src/mcp/server.ts
29
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
30
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
31
+ import { z } from "zod";
32
+ import { execFileSync } from "child_process";
33
+ import { existsSync, readFileSync } from "fs";
34
+ var SERVER_VERSION = "0.6.0";
35
+ var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
36
+ function platformCmd(cmd) {
37
+ if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
38
+ return `${cmd}.cmd`;
39
+ }
40
+ return cmd;
41
+ }
42
+ function safeExecFile(cmd, args) {
43
+ try {
44
+ const out = execFileSync(platformCmd(cmd), args, {
45
+ encoding: "utf-8",
46
+ stdio: ["pipe", "pipe", "pipe"]
47
+ }).toString();
48
+ return { ok: true, out: out.trim() };
49
+ } catch (err) {
50
+ const msg = err instanceof Error ? err.message : String(err);
51
+ return { ok: false, err: msg };
52
+ }
53
+ }
54
+ function isGitRepo() {
55
+ return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
56
+ }
57
+ function createVhkMcpServer() {
58
+ const server = new McpServer({
59
+ name: "vhk",
60
+ version: SERVER_VERSION
61
+ });
62
+ server.registerTool(
63
+ "save",
64
+ {
65
+ description: "\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)",
66
+ inputSchema: {
67
+ message: z.string().optional().describe("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (\uBE44\uC6B0\uBA74 \uC790\uB3D9 \uC0DD\uC131)")
68
+ }
69
+ },
70
+ async ({ message }) => {
71
+ if (!isGitRepo()) {
72
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
73
+ }
74
+ const status = safeExecFile("git", ["status", "--porcelain"]);
75
+ if (!status.ok) {
76
+ return { content: [{ type: "text", text: `\u274C git status \uC2E4\uD328: ${status.err}` }] };
77
+ }
78
+ if (!status.out) {
79
+ return { content: [{ type: "text", text: "\u{1F4ED} \uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
80
+ }
81
+ const files = status.out.split("\n");
82
+ const now = /* @__PURE__ */ new Date();
83
+ const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
84
+ const commitMsg = message?.trim() || `\u2728 vhk save: ${ts}`;
85
+ const add = safeExecFile("git", ["add", "."]);
86
+ if (!add.ok) {
87
+ return { content: [{ type: "text", text: `\u274C git add \uC2E4\uD328: ${add.err}` }] };
88
+ }
89
+ const commit = safeExecFile("git", ["commit", "-m", commitMsg]);
90
+ if (!commit.ok) {
91
+ return { content: [{ type: "text", text: `\u274C commit \uC2E4\uD328: ${commit.err}` }] };
92
+ }
93
+ const push = safeExecFile("git", ["push"]);
94
+ const pushResult = push.ok ? "+ \uC6D0\uACA9 \uC5C5\uB85C\uB4DC \uC644\uB8CC" : "(\uC6D0\uACA9 \uC800\uC7A5\uC18C \uC5C6\uAC70\uB098 push \uC2E4\uD328 \u2192 \uC2A4\uD0B5)";
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: `\u2705 ${files.length}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC! ${pushResult}
100
+ \uCEE4\uBC0B: ${commitMsg}`
101
+ }
102
+ ]
103
+ };
104
+ }
105
+ );
106
+ server.registerTool("undo", { description: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (soft reset, \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC720\uC9C0)" }, async () => {
107
+ if (!isGitRepo()) {
108
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
109
+ }
110
+ const last = safeExecFile("git", ["log", "--oneline", "-1"]);
111
+ if (!last.ok || !last.out) {
112
+ return { content: [{ type: "text", text: "\u{1F4ED} \uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
113
+ }
114
+ const reset = safeExecFile("git", ["reset", "--soft", "HEAD~1"]);
115
+ if (!reset.ok) {
116
+ return { content: [{ type: "text", text: `\u274C reset \uC2E4\uD328: ${reset.err}` }] };
117
+ }
118
+ return {
119
+ content: [
120
+ {
121
+ type: "text",
122
+ text: `\u2705 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC!
123
+ \uCDE8\uC18C\uB41C \uCEE4\uBC0B: ${last.out}
124
+ \u{1F4A1} \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.`
125
+ }
126
+ ]
127
+ };
128
+ });
129
+ server.registerTool("status", { description: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uB300\uC2DC\uBCF4\uB4DC (\uBE0C\uB79C\uCE58/\uBCC0\uACBD\uC0AC\uD56D/\uCD5C\uADFC \uCEE4\uBC0B)" }, async () => {
130
+ const lines = [];
131
+ if (existsSync("package.json")) {
132
+ try {
133
+ const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
134
+ lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
135
+ } catch {
136
+ }
137
+ }
138
+ if (!isGitRepo()) {
139
+ lines.push("\u26A0\uFE0F git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
140
+ return { content: [{ type: "text", text: lines.join("\n") }] };
141
+ }
142
+ const branch = safeExecFile("git", ["branch", "--show-current"]);
143
+ if (branch.ok) lines.push(`\u{1F33F} \uBE0C\uB79C\uCE58: ${branch.out || "(detached)"}`);
144
+ const status = safeExecFile("git", ["status", "--porcelain"]);
145
+ if (status.ok) {
146
+ if (!status.out) {
147
+ lines.push("\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: \u2705 \uAE68\uB057\uD568");
148
+ } else {
149
+ const fileLines = status.out.split("\n");
150
+ const staged = fileLines.filter((l) => l[0] !== " " && l[0] !== "?").length;
151
+ const unstaged = fileLines.filter((l) => l[1] === "M" || l[1] === "D").length;
152
+ const untracked = fileLines.filter((l) => l.startsWith("??")).length;
153
+ const parts = [];
154
+ if (staged) parts.push(`\uC2A4\uD14C\uC774\uC9D5 ${staged}\uAC1C`);
155
+ if (unstaged) parts.push(`\uC218\uC815 ${unstaged}\uAC1C`);
156
+ if (untracked) parts.push(`\uC0C8\uD30C\uC77C ${untracked}\uAC1C`);
157
+ lines.push(`\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: ${parts.join(", ")}`);
158
+ }
159
+ }
160
+ const log = safeExecFile("git", ["log", "--oneline", "-3"]);
161
+ if (log.ok && log.out) {
162
+ lines.push("\u{1F4DC} \uCD5C\uADFC \uCEE4\uBC0B:");
163
+ log.out.split("\n").forEach((l) => lines.push(` ${l}`));
164
+ }
165
+ return { content: [{ type: "text", text: lines.join("\n") }] };
166
+ });
167
+ server.registerTool("diff", { description: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778 (staged/unstaged/\uC0C8\uD30C\uC77C + \uCD1D \uBCC0\uACBD \uC694\uC57D)" }, async () => {
168
+ if (!isGitRepo()) {
169
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
170
+ }
171
+ const unstaged = safeExecFile("git", ["diff", "--stat"]);
172
+ const staged = safeExecFile("git", ["diff", "--cached", "--stat"]);
173
+ const untracked = safeExecFile("git", ["ls-files", "--others", "--exclude-standard"]);
174
+ const unstagedOut = unstaged.ok ? unstaged.out : "";
175
+ const stagedOut = staged.ok ? staged.out : "";
176
+ const untrackedOut = untracked.ok ? untracked.out : "";
177
+ if (!unstagedOut && !stagedOut && !untrackedOut) {
178
+ return { content: [{ type: "text", text: "\u2705 \uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4." }] };
179
+ }
180
+ const lines = [];
181
+ if (stagedOut) {
182
+ lines.push("\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):");
183
+ lines.push(stagedOut);
184
+ }
185
+ if (unstagedOut) {
186
+ lines.push("\u270F\uFE0F \uC218\uC815\uB428 (unstaged):");
187
+ lines.push(unstagedOut);
188
+ }
189
+ if (untrackedOut) {
190
+ const files = untrackedOut.split("\n");
191
+ lines.push(`\u2795 \uC0C8 \uD30C\uC77C (${files.length}\uAC1C):`);
192
+ files.forEach((f) => lines.push(` + ${f}`));
193
+ }
194
+ const numstat = safeExecFile("git", ["diff", "--numstat", "HEAD"]);
195
+ if (numstat.ok && numstat.out) {
196
+ let totalAdd = 0;
197
+ let totalDel = 0;
198
+ let fileCount = 0;
199
+ numstat.out.split("\n").forEach((line) => {
200
+ const [add, del] = line.split(" ");
201
+ totalAdd += parseInt(add, 10) || 0;
202
+ totalDel += parseInt(del, 10) || 0;
203
+ fileCount += 1;
204
+ });
205
+ lines.push(`
206
+ \u{1F4CA} \uCD1D \uBCC0\uACBD: ${fileCount}\uAC1C \uD30C\uC77C, +${totalAdd}\uC904 -${totalDel}\uC904`);
207
+ }
208
+ return { content: [{ type: "text", text: lines.join("\n") }] };
209
+ });
210
+ server.registerTool("ship", { description: "\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC2E4\uD589 (\uBE4C\uB4DC + \uD14C\uC2A4\uD2B8 + \uBC84\uC804 + git \uC0C1\uD0DC)" }, async () => {
211
+ const checks = [];
212
+ const build = safeExecFile("pnpm", ["build"]);
213
+ checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
214
+ const test = safeExecFile("pnpm", ["test", "--run"]);
215
+ checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
216
+ if (existsSync("package.json")) {
217
+ try {
218
+ const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
219
+ checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
220
+ } catch {
221
+ }
222
+ }
223
+ if (isGitRepo()) {
224
+ const status = safeExecFile("git", ["status", "--porcelain"]);
225
+ if (status.ok) {
226
+ if (status.out) {
227
+ checks.push(`\u26A0\uFE0F \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD\uC0AC\uD56D ${status.out.split("\n").length}\uAC1C`);
228
+ } else {
229
+ checks.push("\u2705 \uC6CC\uD0B9 \uB514\uB809\uD1A0\uB9AC \uAE68\uB057\uD568");
230
+ }
231
+ }
232
+ }
233
+ return { content: [{ type: "text", text: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\n" + checks.join("\n") }] };
234
+ });
235
+ server.registerTool("doctor", { description: "\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 (Node/Git/npm/pnpm/TypeScript)" }, async () => {
236
+ const checks = [];
237
+ const node = safeExecFile("node", ["--version"]);
238
+ checks.push(node.ok ? `\u2705 Node.js: ${node.out}` : "\u274C Node.js: \uC124\uCE58 \uC548 \uB428");
239
+ const git = safeExecFile("git", ["--version"]);
240
+ checks.push(git.ok ? `\u2705 Git: ${git.out}` : "\u274C Git: \uC124\uCE58 \uC548 \uB428");
241
+ const pnpm = safeExecFile("pnpm", ["--version"]);
242
+ checks.push(pnpm.ok ? `\u2705 pnpm: v${pnpm.out}` : "\u26A0\uFE0F pnpm: \uC124\uCE58 \uC548 \uB428");
243
+ const npm = safeExecFile("npm", ["--version"]);
244
+ checks.push(npm.ok ? `\u2705 npm: v${npm.out}` : "\u274C npm: \uC124\uCE58 \uC548 \uB428");
245
+ const tsc = safeExecFile("npx", ["tsc", "--version"]);
246
+ checks.push(tsc.ok ? `\u2705 TypeScript: ${tsc.out}` : "\u26A0\uFE0F TypeScript: \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5C6\uC74C");
247
+ return { content: [{ type: "text", text: "\u{1FA7A} \uD658\uACBD \uC810\uAC80 \uACB0\uACFC\n" + checks.join("\n") }] };
248
+ });
249
+ server.registerTool("check", { description: "\uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870 \uC810\uAC80 (\uD544\uC218 \uD30C\uC77C + VHK \uD558\uB124\uC2A4 \uD30C\uC77C)" }, async () => {
250
+ const required = ["package.json", "tsconfig.json", "README.md", ".gitignore"];
251
+ const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
252
+ const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
253
+ required.forEach((f) => {
254
+ lines.push(` ${existsSync(f) ? "\u2705" : "\u274C"} ${f}`);
255
+ });
256
+ lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
257
+ recommended.forEach((f) => {
258
+ lines.push(` ${existsSync(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
259
+ });
260
+ return { content: [{ type: "text", text: lines.join("\n") }] };
261
+ });
262
+ server.registerTool(
263
+ "recap",
264
+ {
265
+ description: "\uCD5C\uADFC \uC791\uC5C5 \uC694\uC57D (\uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC \uAE30\uBC18, \uB0A0\uC9DC \uD3EC\uD568)",
266
+ inputSchema: {
267
+ count: z.number().optional().describe("\uD45C\uC2DC\uD560 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 10)")
268
+ }
269
+ },
270
+ async ({ count }) => {
271
+ if (!isGitRepo()) {
272
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
273
+ }
274
+ const n = count && count > 0 ? Math.floor(count) : 10;
275
+ const log = safeExecFile("git", ["log", "--format=%h %ad %s", "--date=short", `-${n}`]);
276
+ if (!log.ok) {
277
+ return { content: [{ type: "text", text: `\u274C git log \uC2E4\uD328: ${log.err}` }] };
278
+ }
279
+ if (!log.out) {
280
+ return { content: [{ type: "text", text: "\u{1F4ED} \uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
281
+ }
282
+ return { content: [{ type: "text", text: `\u{1F4CB} \uCD5C\uADFC \uC791\uC5C5 (${n}\uAC1C):
283
+ ${log.out}` }] };
284
+ }
285
+ );
286
+ return server;
287
+ }
288
+ async function startMcpServer() {
289
+ const server = createVhkMcpServer();
290
+ const transport = new StdioServerTransport();
291
+ await server.connect(transport);
292
+ }
293
+
294
+ export {
295
+ __commonJS,
296
+ __toESM,
297
+ startMcpServer
298
+ };
package/dist/index.js CHANGED
@@ -1,29 +1,9 @@
1
1
  #!/usr/bin/env node
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __commonJS = (cb, mod) => function __require() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
2
+ import {
3
+ __commonJS,
4
+ __toESM,
5
+ startMcpServer
6
+ } from "./chunk-IU37BEQA.js";
27
7
 
28
8
  // node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
29
9
  var require_ignore = __commonJS({
@@ -523,6 +503,12 @@ var RULES = [
523
503
  confidence: "high",
524
504
  test: (t2) => /ํ”„๋กœ์ ํŠธ.*(๋งŒ๋“ค|์‹œ์ž‘)|ํด๋”.*๋งŒ๋“ค|๋งŒ๋“ค๊ณ \s*์‹ถ|ํ•˜๋„ค์Šค|์ดˆ๊ธฐํ™”/.test(t2) || /^์‹œ์ž‘$/.test(t2)
525
505
  },
506
+ {
507
+ command: "mcp-init",
508
+ explanation: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815 (vhk mcp-init)",
509
+ confidence: "high",
510
+ test: (t2) => /mcp.*(์„ค์ •|์—ฐ๋™|์ดˆ๊ธฐ|init)|์ปค์„œ.*(์—ฐ๋™|์„ค์ •|mcp)|cursor.*mcp/.test(t2)
511
+ },
526
512
  {
527
513
  command: "secure",
528
514
  explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548)",
@@ -647,6 +633,9 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
647
633
  "diff",
648
634
  "\uBCC0\uACBD",
649
635
  "\uCC28\uC774",
636
+ "mcp",
637
+ "mcp-init",
638
+ "mcp\uC124\uC815",
650
639
  "help"
651
640
  ]);
652
641
  function isOptionToken(token) {
@@ -657,11 +646,11 @@ function detectNaturalLanguageInput(argv) {
657
646
  if (rest.length === 0) return null;
658
647
  const first = rest[0];
659
648
  if (isOptionToken(first)) return null;
649
+ if (rest.some(isOptionToken)) return null;
660
650
  const input = rest.join(" ").trim();
661
651
  if (!input) return null;
662
652
  const firstIsKnown = KNOWN_COMMAND_TOKENS.has(first);
663
653
  if (firstIsKnown && rest.length === 1) return null;
664
- if (firstIsKnown && rest.slice(1).every(isOptionToken)) return null;
665
654
  if (firstIsKnown && rest.length > 1) {
666
655
  if (routeNaturalLanguage(input)) return input;
667
656
  return null;
@@ -670,7 +659,7 @@ function detectNaturalLanguageInput(argv) {
670
659
  }
671
660
 
672
661
  // src/lib/nlp-run.ts
673
- import chalk16 from "chalk";
662
+ import chalk17 from "chalk";
674
663
  import inquirer7 from "inquirer";
675
664
 
676
665
  // src/i18n/ko.ts
@@ -854,7 +843,9 @@ var ko = {
854
843
  projectFiles: "\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uD655\uC778:",
855
844
  envNotIgnored: "\u26A0\uFE0F .env\uAC00 .gitignore\uC5D0 \uC5C6\uC74C! \uCD94\uAC00\uD558\uC138\uC694",
856
845
  nextOkMessage: "\uD658\uACBD \uC810\uAC80 \uD1B5\uACFC! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uC2DC\uC791\uD558\uC138\uC694.",
857
- nextRetryMessage: "\uC704 \uB3C4\uAD6C\uB97C \uC124\uCE58\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694."
846
+ nextRetryMessage: "\uC704 \uB3C4\uAD6C\uB97C \uC124\uCE58\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
847
+ updateAvailable: (latest) => `\u{1F195} v${latest} \uC0AC\uC6A9 \uAC00\uB2A5 \u2014 npm i -g @byh3071/vhk`,
848
+ updateCurrent: "\uCD5C\uC2E0 \uBC84\uC804\uC744 \uC4F0\uACE0 \uC788\uC5B4\uC694"
858
849
  },
859
850
  nlp: {
860
851
  matched: "\uC774\uAC8C \uB9DE\uB098\uC694?",
@@ -909,7 +900,14 @@ var ko = {
909
900
  checkSecurity: "\uBCF4\uC548 \uC2A4\uCE94\uC744 \uB3CC\uB838\uB098\uC694?",
910
901
  hintSecurity: "vhk \uBCF4\uC548 scan",
911
902
  checkCommit: "\uBAA8\uB4E0 \uBCC0\uACBD\uC774 \uCEE4\uBC0B\uB418\uC5C8\uB098\uC694?",
912
- hintCommit: "git status \uD655\uC778"
903
+ hintCommit: "git status \uD655\uC778",
904
+ changelogUpdated: (version) => `CHANGELOG.md \uAC31\uC2E0\uB428 \u2014 [Unreleased] \u2192 [${version}] \uC139\uC158\uC73C\uB85C \uC774\uB3D9`,
905
+ changelogNoUnreleased: "CHANGELOG.md\uC5D0 [Unreleased] \uC139\uC158\uC774 \uC5C6\uC5B4 \uC790\uB3D9 \uAC31\uC2E0\uC744 \uC2A4\uD0B5\uD588\uC5B4\uC694",
906
+ changelogMissing: "CHANGELOG.md\uAC00 \uC5C6\uC5B4\uC694. \uB9CC\uB4E4\uBA74 ship\uC774 \uC790\uB3D9\uC73C\uB85C [Unreleased] \u2192 \uBC84\uC804 \uC139\uC158\uC73C\uB85C \uC62E\uACA8\uC90D\uB2C8\uB2E4."
907
+ },
908
+ mcp: {
909
+ initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
910
+ serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428"
913
911
  }
914
912
  };
915
913
  function lookup(path15) {
@@ -1730,7 +1728,7 @@ function enhancePackageScripts(projectDir) {
1730
1728
  const pkgPath = path3.join(projectDir, "package.json");
1731
1729
  if (!fs3.existsSync(pkgPath)) return false;
1732
1730
  const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
1733
- pkg.scripts = { ...pkg.scripts, ...VHK_PACKAGE_SCRIPTS };
1731
+ pkg.scripts = { ...VHK_PACKAGE_SCRIPTS, ...pkg.scripts };
1734
1732
  fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1735
1733
  return true;
1736
1734
  }
@@ -2796,6 +2794,27 @@ function getVhkVersion() {
2796
2794
  }
2797
2795
  return void 0;
2798
2796
  }
2797
+ function fetchLatestNpmVersion(packageName) {
2798
+ try {
2799
+ const result = execSync(`npm view ${packageName} version`, {
2800
+ encoding: "utf-8",
2801
+ timeout: 5e3,
2802
+ stdio: ["ignore", "pipe", "ignore"]
2803
+ }).trim();
2804
+ if (/^\d+\.\d+\.\d+/.test(result)) return result;
2805
+ return void 0;
2806
+ } catch {
2807
+ return void 0;
2808
+ }
2809
+ }
2810
+ function compareSemver(a, b) {
2811
+ const parse = (v) => v.replace(/^v/i, "").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
2812
+ const [a1 = 0, a2 = 0, a3 = 0] = parse(a);
2813
+ const [b1 = 0, b2 = 0, b3 = 0] = parse(b);
2814
+ if (a1 !== b1) return a1 - b1;
2815
+ if (a2 !== b2) return a2 - b2;
2816
+ return a3 - b3;
2817
+ }
2799
2818
  async function doctor() {
2800
2819
  console.log(chalk10.bold(`
2801
2820
  ${ko.doctor.title}
@@ -2823,6 +2842,14 @@ ${ko.doctor.title}
2823
2842
  } else {
2824
2843
  console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(" \u2014 \uC124\uCE58\uB428"));
2825
2844
  }
2845
+ if (vhkVersion) {
2846
+ const latest = fetchLatestNpmVersion("@byh3071/vhk");
2847
+ if (latest && compareSemver(latest, vhkVersion) > 0) {
2848
+ console.log(chalk10.yellow(` ${ko.doctor.updateAvailable(latest)}`));
2849
+ } else if (latest) {
2850
+ console.log(chalk10.dim(` ${ko.doctor.updateCurrent}`));
2851
+ }
2852
+ }
2826
2853
  console.log("");
2827
2854
  console.log(chalk10.bold(` ${ko.doctor.projectFiles}`));
2828
2855
  const cwd = process.cwd();
@@ -2885,6 +2912,29 @@ var CHECKLIST = [
2885
2912
  function sanitizeVersion(version) {
2886
2913
  return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
2887
2914
  }
2915
+ function updateChangelogUnreleased(cwd, version, date) {
2916
+ const changelogPath = path13.join(cwd, "CHANGELOG.md");
2917
+ if (!fs13.existsSync(changelogPath)) return { status: "missing" };
2918
+ const content = fs13.readFileSync(changelogPath, "utf-8");
2919
+ const unreleasedHeading = /^## \[Unreleased\][^\n]*$/m;
2920
+ if (!unreleasedHeading.test(content)) return { status: "no-unreleased" };
2921
+ const blankUnreleased = [
2922
+ "## [Unreleased]",
2923
+ "",
2924
+ "### Added",
2925
+ "- ",
2926
+ "",
2927
+ "### Fixed",
2928
+ "- ",
2929
+ "",
2930
+ "---",
2931
+ "",
2932
+ `## [${version}] \u2014 ${date}`
2933
+ ].join("\n");
2934
+ const updated = content.replace(unreleasedHeading, blankUnreleased);
2935
+ fs13.writeFileSync(changelogPath, updated, "utf-8");
2936
+ return { status: "updated", version };
2937
+ }
2888
2938
  async function ship() {
2889
2939
  console.log(chalk11.bold(`
2890
2940
  ${ko.ship.title}
@@ -2976,6 +3026,14 @@ ${ko.ship.title}
2976
3026
  fs13.writeFileSync(filePath, content, "utf-8");
2977
3027
  console.log(chalk11.green(`
2978
3028
  ${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
3029
+ const changelogResult = updateChangelogUnreleased(cwd, versionSlug, today);
3030
+ if (changelogResult.status === "updated") {
3031
+ log.success(ko.ship.changelogUpdated(changelogResult.version));
3032
+ } else if (changelogResult.status === "no-unreleased") {
3033
+ log.warn(ko.ship.changelogNoUnreleased);
3034
+ } else {
3035
+ log.info(ko.ship.changelogMissing);
3036
+ }
2979
3037
  printNextStep({
2980
3038
  message: ko.ship.deployMessage,
2981
3039
  command: "npm publish --access=public",
@@ -3488,6 +3546,55 @@ ${t("diff.summaryHeader")}`));
3488
3546
  console.log("");
3489
3547
  }
3490
3548
 
3549
+ // src/commands/mcp-init.ts
3550
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3551
+ import { join } from "path";
3552
+ import { fileURLToPath as fileURLToPath2 } from "url";
3553
+ import chalk16 from "chalk";
3554
+ function resolveVhkMcpPath() {
3555
+ try {
3556
+ const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
3557
+ if (typeof url === "string") return fileURLToPath2(url);
3558
+ } catch {
3559
+ }
3560
+ return join(process.cwd(), "node_modules", "@byh3071", "vhk", "dist", "mcp", "index.js");
3561
+ }
3562
+ async function mcpInit() {
3563
+ console.log(chalk16.bold("\n\u{1F50C} " + t("mcp.initTitle")));
3564
+ console.log(chalk16.gray("\u2500".repeat(40)));
3565
+ const cursorDir = join(process.cwd(), ".cursor");
3566
+ if (!existsSync(cursorDir)) {
3567
+ mkdirSync(cursorDir, { recursive: true });
3568
+ }
3569
+ const configPath = join(cursorDir, "mcp.json");
3570
+ const vhkEntry = {
3571
+ command: "node",
3572
+ args: [resolveVhkMcpPath()]
3573
+ };
3574
+ let config;
3575
+ if (existsSync(configPath)) {
3576
+ try {
3577
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
3578
+ config = {
3579
+ mcpServers: { ...parsed.mcpServers ?? {}, vhk: vhkEntry }
3580
+ };
3581
+ } catch {
3582
+ console.log(chalk16.yellow("\u26A0\uFE0F \uAE30\uC874 .cursor/mcp.json \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uC0C8 \uD30C\uC77C\uB85C \uB36E\uC5B4\uC501\uB2C8\uB2E4."));
3583
+ config = { mcpServers: { vhk: vhkEntry } };
3584
+ }
3585
+ } else {
3586
+ config = { mcpServers: { vhk: vhkEntry } };
3587
+ }
3588
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3589
+ console.log(chalk16.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
3590
+ console.log(chalk16.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
3591
+ console.log(` ${configPath}`);
3592
+ console.log(chalk16.cyan("\n\u{1F504} \uB2E4\uC74C \uB2E8\uACC4:"));
3593
+ console.log(" 1. Cursor\uB97C \uC7AC\uC2DC\uC791\uD558\uC138\uC694");
3594
+ console.log(" 2. Cursor \uCC44\uD305\uC5D0\uC11C vhk \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
3595
+ console.log(chalk16.gray('\n\u{1F4A1} \uC608: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC54C\uB824\uC918" \u2192 Cursor\uAC00 vhk status \uD638\uCD9C'));
3596
+ }
3597
+
3491
3598
  // src/lib/nlp-run.ts
3492
3599
  async function dispatchNlpRoute(route, input) {
3493
3600
  switch (route.command) {
@@ -3518,19 +3625,21 @@ async function dispatchNlpRoute(route, input) {
3518
3625
  return status();
3519
3626
  case "diff":
3520
3627
  return diff();
3628
+ case "mcp-init":
3629
+ return mcpInit();
3521
3630
  }
3522
3631
  }
3523
3632
  async function runNaturalLanguageRoute(input) {
3524
3633
  const route = routeNaturalLanguage(input);
3525
3634
  if (!route) {
3526
- console.log(chalk16.yellow(`
3635
+ console.log(chalk17.yellow(`
3527
3636
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
3528
3637
  `));
3529
3638
  return;
3530
3639
  }
3531
3640
  console.log("");
3532
- console.log(chalk16.cyan(` \u{1F4AC} "${input}"`));
3533
- console.log(chalk16.cyan(` \u2192 ${route.explanation}`));
3641
+ console.log(chalk17.cyan(` \u{1F4AC} "${input}"`));
3642
+ console.log(chalk17.cyan(` \u2192 ${route.explanation}`));
3534
3643
  if (route.confidence === "low") {
3535
3644
  const { confirm } = await inquirer7.prompt([{
3536
3645
  type: "confirm",
@@ -3539,7 +3648,7 @@ async function runNaturalLanguageRoute(input) {
3539
3648
  default: true
3540
3649
  }]);
3541
3650
  if (!confirm) {
3542
- console.log(chalk16.dim(` ${ko.nlp.menuHint}`));
3651
+ console.log(chalk17.dim(` ${ko.nlp.menuHint}`));
3543
3652
  return;
3544
3653
  }
3545
3654
  }
@@ -3564,14 +3673,17 @@ var KO_ALIASES = {
3564
3673
  status: "\uC0C1\uD0DC",
3565
3674
  diff: "\uBCC0\uACBD"
3566
3675
  };
3567
- program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.5.2");
3676
+ program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.6.0");
3568
3677
  program.configureHelp({
3569
3678
  formatHelp(cmd, helper) {
3570
3679
  if (cmd.parent) {
3571
3680
  return defaultHelp.formatHelp(cmd, helper);
3572
3681
  }
3573
3682
  const subs = helper.visibleCommands(cmd).filter((c) => c.name() !== "help");
3574
- const terms = subs.map((c) => `${c.name()} (${KO_ALIASES[c.name()]})`);
3683
+ const terms = subs.map((c) => {
3684
+ const alias = KO_ALIASES[c.name()];
3685
+ return alias ? `${c.name()} (${alias})` : c.name();
3686
+ });
3575
3687
  const termWidth = Math.max(...terms.map((t2) => t2.length), 0);
3576
3688
  const lines = [
3577
3689
  helper.commandDescription(cmd),
@@ -3604,6 +3716,12 @@ program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\
3604
3716
  await status();
3605
3717
  });
3606
3718
  program.command("diff").alias("\uBCC0\uACBD").alias("\uCC28\uC774").description("Git \uBCC0\uACBD\uC0AC\uD56D \uD55C\uAD6D\uC5B4 \uC694\uC57D (staged / unstaged / \uC0C8 \uD30C\uC77C)").action(diff);
3719
+ program.command("mcp").description("MCP \uC11C\uBC84 \uC2DC\uC791 (Cursor \uB4F1 MCP \uD074\uB77C\uC774\uC5B8\uD2B8\uC6A9)").action(async () => {
3720
+ await startMcpServer();
3721
+ });
3722
+ program.command("mcp-init").alias("mcp\uC124\uC815").description("Cursor MCP \uC5F0\uB3D9 \uC124\uC815 \uC790\uB3D9 \uC0DD\uC131 (.cursor/mcp.json)").action(async () => {
3723
+ await mcpInit();
3724
+ });
3607
3725
  program.on("command:*", async (operands) => {
3608
3726
  const unknown = operands[0] ?? "";
3609
3727
  const rest = operands.slice(1);
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ startMcpServer
4
+ } from "../chunk-IU37BEQA.js";
5
+
6
+ // src/mcp/index.ts
7
+ startMcpServer().catch((err) => {
8
+ console.error("VHK MCP \uC11C\uBC84 \uC2DC\uC791 \uC2E4\uD328:", err);
9
+ process.exit(1);
10
+ });
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@byh3071/vhk",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Vibe Harness Kit โ€” ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ํ’€์‚ฌ์ดํด CLI",
5
5
  "bin": {
6
- "vhk": "./dist/index.js"
6
+ "vhk": "dist/index.js",
7
+ "vhk-mcp": "dist/mcp/index.js"
7
8
  },
8
9
  "type": "module",
9
10
  "scripts": {
@@ -11,7 +12,13 @@
11
12
  "build": "tsup",
12
13
  "test": "vitest",
13
14
  "test:run": "vitest --run",
14
- "prepublishOnly": "pnpm build && pnpm test:run"
15
+ "prepublishOnly": "pnpm build && pnpm test:run",
16
+ "save": "vhk save",
17
+ "check": "vhk check",
18
+ "scan": "vhk secure scan",
19
+ "recap": "vhk recap",
20
+ "ship": "vhk ship",
21
+ "doctor": "vhk doctor"
15
22
  },
16
23
  "files": [
17
24
  "dist",
@@ -30,19 +37,21 @@
30
37
  "license": "MIT",
31
38
  "repository": {
32
39
  "type": "git",
33
- "url": "https://github.com/byh3071-cpu/vhk.git"
40
+ "url": "git+https://github.com/byh3071-cpu/vhk.git"
34
41
  },
35
42
  "engines": {
36
43
  "node": ">=20"
37
44
  },
38
45
  "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.29.0",
39
47
  "@notionhq/client": "^5.22.0",
40
48
  "chalk": "^5.6.2",
41
49
  "commander": "^14.0.3",
42
50
  "handlebars": "^4.7.9",
43
51
  "inquirer": "^9.3.8",
44
52
  "ora": "^9.4.0",
45
- "simple-git": "^3.36.0"
53
+ "simple-git": "^3.36.0",
54
+ "zod": "^4.4.3"
46
55
  },
47
56
  "devDependencies": {
48
57
  "@types/inquirer": "^9.0.9",