@byh3071/vhk 0.5.3 โ†’ 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,12 @@
1
1
  ---
2
2
  id: vhk-readme
3
- date: 2026-05-23
4
- tags: [vhk, cli, readme, v0.5.3]
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.3)
9
+ > AI ์ฝ”๋”ฉ ์—์ด์ „ํŠธ๋ฅผ ๋ถ€๋ฆฌ๋Š” ์‚ฌ๋žŒ์„ ์œ„ํ•œ **ํ•œ๊ตญ์–ด ํ’€์‚ฌ์ดํด CLI** (v0.6.0)
10
10
  >
11
11
  > ๐Ÿฝ๏ธ **VHK๋Š” VHK๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๋จ** โ€” ์ด ๋ ˆํฌ์˜ `docs/`, `CLAUDE.md`, `.cursorrules`๋„ `vhk init`์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
12
12
 
@@ -89,6 +89,8 @@ vhk ๊ธฐํš ๋๋‚ฌ๊ณ  ๋ฐ”๋กœ ์‹œ์ž‘
89
89
  | `vhk undo` | `๋˜๋Œ๋ฆฌ๊ธฐ`, `์ทจ์†Œ` | ์ตœ๊ทผ ์ปค๋ฐ‹ soft reset (๋ณ€๊ฒฝ์€ staged ์œ ์ง€) |
90
90
  | `vhk diff` | `๋ณ€๊ฒฝ`, `์ฐจ์ด` | staged / unstaged / ์ƒˆ ํŒŒ์ผ ์š”์•ฝ (์ค„ ์ˆ˜ ํ•ฉ๊ณ„๋Š” trackedยทHEAD ๊ธฐ์ค€) |
91
91
  | `vhk status` | `์ƒํƒœ`, `ํ˜„ํ™ฉ` | ๋ธŒ๋žœ์น˜ยท๋ณ€๊ฒฝยท์ปค๋ฐ‹ยท์›๊ฒฉยท๋ฒ„์ „ ๋Œ€์‹œ๋ณด๋“œ |
92
+ | `vhk mcp` | โ€” | MCP ์„œ๋ฒ„ ์‹œ์ž‘ (Cursor ๋“ฑ MCP ํด๋ผ์ด์–ธํŠธ์šฉ, stdio) |
93
+ | `vhk mcp-init` | `mcp์„ค์ •` | Cursor `.cursor/mcp.json` ์ž๋™ ์ƒ์„ฑ |
92
94
 
93
95
  ### init ์˜ต์…˜
94
96
 
@@ -105,6 +107,33 @@ vhk ๊ธฐํš ๋๋‚ฌ๊ณ  ๋ฐ”๋กœ ์‹œ์ž‘
105
107
  |------|------|
106
108
  | `--since YYYY-MM-DD` | ๋ถ„์„ ์‹œ์ž‘์ผ (๊ธฐ๋ณธ: ์˜ค๋Š˜) |
107
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
+
108
137
  ## v0.5.3 ํ•˜์ด๋ผ์ดํŠธ
109
138
 
110
139
  | ๊ธฐ๋Šฅ | ์„ค๋ช… |
@@ -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) {
@@ -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
@@ -915,6 +904,10 @@ var ko = {
915
904
  changelogUpdated: (version) => `CHANGELOG.md \uAC31\uC2E0\uB428 \u2014 [Unreleased] \u2192 [${version}] \uC139\uC158\uC73C\uB85C \uC774\uB3D9`,
916
905
  changelogNoUnreleased: "CHANGELOG.md\uC5D0 [Unreleased] \uC139\uC158\uC774 \uC5C6\uC5B4 \uC790\uB3D9 \uAC31\uC2E0\uC744 \uC2A4\uD0B5\uD588\uC5B4\uC694",
917
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"
918
911
  }
919
912
  };
920
913
  function lookup(path15) {
@@ -3553,6 +3546,55 @@ ${t("diff.summaryHeader")}`));
3553
3546
  console.log("");
3554
3547
  }
3555
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
+
3556
3598
  // src/lib/nlp-run.ts
3557
3599
  async function dispatchNlpRoute(route, input) {
3558
3600
  switch (route.command) {
@@ -3583,19 +3625,21 @@ async function dispatchNlpRoute(route, input) {
3583
3625
  return status();
3584
3626
  case "diff":
3585
3627
  return diff();
3628
+ case "mcp-init":
3629
+ return mcpInit();
3586
3630
  }
3587
3631
  }
3588
3632
  async function runNaturalLanguageRoute(input) {
3589
3633
  const route = routeNaturalLanguage(input);
3590
3634
  if (!route) {
3591
- console.log(chalk16.yellow(`
3635
+ console.log(chalk17.yellow(`
3592
3636
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
3593
3637
  `));
3594
3638
  return;
3595
3639
  }
3596
3640
  console.log("");
3597
- console.log(chalk16.cyan(` \u{1F4AC} "${input}"`));
3598
- console.log(chalk16.cyan(` \u2192 ${route.explanation}`));
3641
+ console.log(chalk17.cyan(` \u{1F4AC} "${input}"`));
3642
+ console.log(chalk17.cyan(` \u2192 ${route.explanation}`));
3599
3643
  if (route.confidence === "low") {
3600
3644
  const { confirm } = await inquirer7.prompt([{
3601
3645
  type: "confirm",
@@ -3604,7 +3648,7 @@ async function runNaturalLanguageRoute(input) {
3604
3648
  default: true
3605
3649
  }]);
3606
3650
  if (!confirm) {
3607
- console.log(chalk16.dim(` ${ko.nlp.menuHint}`));
3651
+ console.log(chalk17.dim(` ${ko.nlp.menuHint}`));
3608
3652
  return;
3609
3653
  }
3610
3654
  }
@@ -3629,14 +3673,17 @@ var KO_ALIASES = {
3629
3673
  status: "\uC0C1\uD0DC",
3630
3674
  diff: "\uBCC0\uACBD"
3631
3675
  };
3632
- 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.3");
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");
3633
3677
  program.configureHelp({
3634
3678
  formatHelp(cmd, helper) {
3635
3679
  if (cmd.parent) {
3636
3680
  return defaultHelp.formatHelp(cmd, helper);
3637
3681
  }
3638
3682
  const subs = helper.visibleCommands(cmd).filter((c) => c.name() !== "help");
3639
- 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
+ });
3640
3687
  const termWidth = Math.max(...terms.map((t2) => t2.length), 0);
3641
3688
  const lines = [
3642
3689
  helper.commandDescription(cmd),
@@ -3669,6 +3716,12 @@ program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\
3669
3716
  await status();
3670
3717
  });
3671
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
+ });
3672
3725
  program.on("command:*", async (operands) => {
3673
3726
  const unknown = operands[0] ?? "";
3674
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,62 +1,65 @@
1
- {
2
- "name": "@byh3071/vhk",
3
- "version": "0.5.3",
4
- "description": "Vibe Harness Kit โ€” ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ํ’€์‚ฌ์ดํด CLI",
5
- "bin": {
6
- "vhk": "dist/index.js"
7
- },
8
- "type": "module",
9
- "scripts": {
10
- "dev": "tsx src/index.ts",
11
- "build": "tsup",
12
- "test": "vitest",
13
- "test:run": "vitest --run",
14
- "prepublishOnly": "pnpm build && pnpm test:run",
15
- "save": "vhk save",
16
- "check": "vhk check",
17
- "scan": "vhk secure scan",
18
- "recap": "vhk recap",
19
- "ship": "vhk ship",
20
- "doctor": "vhk doctor"
21
- },
22
- "files": [
23
- "dist",
24
- "README.md",
25
- "LICENSE"
26
- ],
27
- "keywords": [
28
- "vibe-coding",
29
- "harness",
30
- "cli",
31
- "scaffold",
32
- "session-log",
33
- "rules-sync"
34
- ],
35
- "author": "byh3071 <byh3071@gmail.com>",
36
- "license": "MIT",
37
- "repository": {
38
- "type": "git",
39
- "url": "git+https://github.com/byh3071-cpu/vhk.git"
40
- },
41
- "engines": {
42
- "node": ">=20"
43
- },
44
- "dependencies": {
45
- "@notionhq/client": "^5.22.0",
46
- "chalk": "^5.6.2",
47
- "commander": "^14.0.3",
48
- "handlebars": "^4.7.9",
49
- "inquirer": "^9.3.8",
50
- "ora": "^9.4.0",
51
- "simple-git": "^3.36.0"
52
- },
53
- "devDependencies": {
54
- "@types/inquirer": "^9.0.9",
55
- "@types/node": "^25.9.1",
56
- "ignore": "^7.0.5",
57
- "tsup": "^8.5.1",
58
- "tsx": "^4.22.3",
59
- "typescript": "^6.0.3",
60
- "vitest": "^4.1.7"
61
- }
62
- }
1
+ {
2
+ "name": "@byh3071/vhk",
3
+ "version": "0.6.0",
4
+ "description": "Vibe Harness Kit โ€” ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ํ’€์‚ฌ์ดํด CLI",
5
+ "bin": {
6
+ "vhk": "dist/index.js",
7
+ "vhk-mcp": "dist/mcp/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "dev": "tsx src/index.ts",
12
+ "build": "tsup",
13
+ "test": "vitest",
14
+ "test:run": "vitest --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"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "keywords": [
29
+ "vibe-coding",
30
+ "harness",
31
+ "cli",
32
+ "scaffold",
33
+ "session-log",
34
+ "rules-sync"
35
+ ],
36
+ "author": "byh3071 <byh3071@gmail.com>",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/byh3071-cpu/vhk.git"
41
+ },
42
+ "engines": {
43
+ "node": ">=20"
44
+ },
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.29.0",
47
+ "@notionhq/client": "^5.22.0",
48
+ "chalk": "^5.6.2",
49
+ "commander": "^14.0.3",
50
+ "handlebars": "^4.7.9",
51
+ "inquirer": "^9.3.8",
52
+ "ora": "^9.4.0",
53
+ "simple-git": "^3.36.0",
54
+ "zod": "^4.4.3"
55
+ },
56
+ "devDependencies": {
57
+ "@types/inquirer": "^9.0.9",
58
+ "@types/node": "^25.9.1",
59
+ "ignore": "^7.0.5",
60
+ "tsup": "^8.5.1",
61
+ "tsx": "^4.22.3",
62
+ "typescript": "^6.0.3",
63
+ "vitest": "^4.1.7"
64
+ }
65
+ }