@byh3071/vhk 0.7.0 → 0.7.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.
@@ -0,0 +1,770 @@
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/commands/env.ts
29
+ import { existsSync, readFileSync, writeFileSync, appendFileSync } from "fs";
30
+ import chalk2 from "chalk";
31
+
32
+ // src/i18n/ko.ts
33
+ var ko = {
34
+ status: {
35
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC",
36
+ notGitRepo: "Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2C8\uC5D0\uC694. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
37
+ branch: "\uBE0C\uB79C\uCE58:",
38
+ changes: "\uBCC0\uACBD:",
39
+ recentCommits: "\uCD5C\uADFC \uCEE4\uBC0B (3):",
40
+ noCommits: "\uCEE4\uBC0B \uC5C6\uC74C",
41
+ remote: "\uC6D0\uACA9:",
42
+ noUpstream: "upstream \uC5C6\uC74C",
43
+ inSync: "\uB3D9\uAE30\uD654\uB428",
44
+ ahead: (n) => `\u2191${n} ahead`,
45
+ behind: (n) => `\u2193${n} behind`,
46
+ package: "package.json:",
47
+ noPackage: "package.json \uC5C6\uC74C",
48
+ detached: "(detached HEAD)",
49
+ unknownBranch: "(\uC54C \uC218 \uC5C6\uC74C)"
50
+ },
51
+ save: {
52
+ title: "\uC800\uC7A5\uD558\uAE30",
53
+ notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
54
+ noChanges: "\uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
55
+ filesHeader: (n) => `\uBCC0\uACBD\uB41C \uD30C\uC77C (${n}\uAC1C):`,
56
+ commitMessage: "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (Enter\uB85C \uAE30\uBCF8\uAC12 \uC0AC\uC6A9):",
57
+ saving: "\uC800\uC7A5 \uC911...",
58
+ pushing: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uC5D0 \uC62C\uB9AC\uB294 \uC911...",
59
+ successWithPush: "\uC800\uC7A5 + \uC6D0\uACA9 \uC5C5\uB85C\uB4DC \uC644\uB8CC!",
60
+ successLocal: "\uB85C\uCEEC \uC800\uC7A5 \uC644\uB8CC!",
61
+ noRemote: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 push\uB97C \uAC74\uB108\uB6F0\uC5C8\uC2B5\uB2C8\uB2E4.",
62
+ failed: "\uC800\uC7A5 \uC2E4\uD328",
63
+ stagedAfterFail: "\uCEE4\uBC0B\uC740 \uC2E4\uD328\uD588\uC9C0\uB9CC \uD30C\uC77C\uC740 \uC2A4\uD14C\uC774\uC9D5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uD655\uC778: git status / \uCDE8\uC18C: git reset HEAD",
64
+ securityWarnHeader: "\uC800\uC7A5 \uC804 \uBCF4\uC548 \uD655\uC778:",
65
+ secretsFound: (n) => `\uCF54\uB4DC\uC5D0\uC11C CRITICAL/HIGH \uC2DC\uD06C\uB9BF \uD328\uD134 ${n}\uAC74 \uAC10\uC9C0`,
66
+ secretsConfirm: "\uADF8\uB798\uB3C4 \uCEE4\uBC0B\xB7push\uB97C \uC9C4\uD589\uD560\uAE4C\uC694?",
67
+ cancelled: "\uC800\uC7A5\uC744 \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.",
68
+ pushFailed: "push \uC2E4\uD328 (\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uC644\uB8CC\uB428)",
69
+ commitOkPushFailed: "\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uB410\uC9C0\uB9CC \uC6D0\uACA9 push\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. git push\uB97C \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.",
70
+ done: (n) => `${n}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC!`,
71
+ doneLocalOnly: (n) => `${n}\uAC1C \uD30C\uC77C \uB85C\uCEEC \uC800\uC7A5\uB428 (push\uB294 \uC2E4\uD328)`
72
+ },
73
+ undo: {
74
+ title: "\uB418\uB3CC\uB9AC\uAE30",
75
+ notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
76
+ noCommits: "\uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
77
+ recentHeader: "\u{1F4CB} \uCD5C\uADFC \uCEE4\uBC0B:",
78
+ howMany: "\uBA87 \uAC1C\uC758 \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9B4\uAE4C\uC694?",
79
+ alreadyPushed: "\uC774 \uCEE4\uBC0B\uC740 \uC774\uBBF8 \uC6D0\uACA9\uC5D0 \uC62C\uB77C\uAC14\uC2B5\uB2C8\uB2E4. \uB418\uB3CC\uB9AC\uBA74 \uCDA9\uB3CC\uC774 \uC0DD\uAE38 \uC218 \uC788\uC5B4\uC694.",
80
+ noUpstreamWarning: "upstream \uBE0C\uB79C\uCE58\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 push\uD55C \uCEE4\uBC0B\uC77C \uC218 \uC788\uC5B4\uC694. \uB418\uB3CC\uB9B0 \uB4A4 force push\uAC00 \uD544\uC694\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
81
+ confirmMessage: "\uCD5C\uADFC \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9AC\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
82
+ confirmRisky: (n) => `\u26A0\uFE0F \uC704\uD5D8: \uCD5C\uADFC ${n}\uAC1C \uCEE4\uBC0B\uC744 soft reset\uD569\uB2C8\uB2E4. \uC6D0\uACA9\uACFC \uC5B4\uAE0B\uB0A0 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uACC4\uC18D\uD560\uAE4C\uC694?`,
83
+ cancelled: "\uCDE8\uC18C\uB428",
84
+ success: "\uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC! \uBCC0\uACBD\uC0AC\uD56D\uC740 \uADF8\uB300\uB85C \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.",
85
+ stagedHint: "\uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544 \uC788\uC5B4\uC694.",
86
+ rootCommit: "\uCCAB \uCEE4\uBC0B\uB9CC \uC788\uC5B4\uC11C \uB354 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
87
+ forcePushHint: "\uC6D0\uACA9\uACFC \uB9DE\uCD94\uB824\uBA74: git push --force-with-lease (\uD63C\uC790 \uC791\uC5C5\uD55C \uBE0C\uB79C\uCE58\uC5D0\uC11C\uB9CC, \uD300\uACFC \uD569\uC758 \uD6C4)",
88
+ failed: "\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328"
89
+ },
90
+ diff: {
91
+ title: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778",
92
+ notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
93
+ noChanges: "\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
94
+ stagedHeader: "\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):",
95
+ unstagedHeader: "\u270F\uFE0F \uC218\uC815\uB428 (unstaged):",
96
+ untrackedHeader: (n) => `\u2795 \uC0C8 \uD30C\uC77C (${n}\uAC1C):`,
97
+ summaryHeader: "\u{1F4CA} \uCD1D \uBCC0\uACBD \uC694\uC57D (\uC791\uC5C5 \uD2B8\uB9AC vs HEAD)",
98
+ filesLine: (n) => `\uD30C\uC77C: ${n}\uAC1C`
99
+ },
100
+ start: {
101
+ title: "\u{1F527} VHK \u2014 \uBB34\uC5C7\uC744 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
102
+ subtitle: "\uBC88\uD638\uB9CC \uACE0\uB974\uBA74 \uB429\uB2C8\uB2E4. \uBA85\uB839\uC5B4\uB97C \uC678\uC6B8 \uD544\uC694 \uC5C6\uC5B4\uC694.",
103
+ menuPrompt: "\uC5B4\uB5A4 \uC791\uC5C5\uC744 \uD560\uAE4C\uC694?",
104
+ choiceGate: "1) \uC0C8 \uC544\uC774\uB514\uC5B4 \uAC80\uC99D\uD558\uAE30",
105
+ choiceInit: "2) \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30 \u2014 \uD3F4\uB354\xB7\uD30C\uC77C \uB9CC\uB4E4\uAE30",
106
+ choiceInitSkipGate: "3) \uAE30\uD68D\uC740 \uB05D\uB0AC\uC5B4\uC694 \u2014 \uBC14\uB85C \uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uAE30",
107
+ choiceRecap: "4) \uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD558\uAE30",
108
+ choiceSync: "5) \uADDC\uCE59 \uD30C\uC77C \uB9DE\uCD94\uAE30",
109
+ choiceCheck: "6) \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59 \uC810\uAC80\uD558\uAE30",
110
+ choiceSecure: "7) \uBE44\uBC00\uBC88\uD638\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC\uD558\uAE30",
111
+ choiceExit: "\uB098\uAC00\uAE30",
112
+ goodbye: "\uB2E4\uC74C\uC5D0 \uB610 \uBD88\uB7EC\uC8FC\uC138\uC694."
113
+ },
114
+ gate: {
115
+ title: "\u{1F4A1} \uC544\uC774\uB514\uC5B4 \uAC80\uC99D",
116
+ welcome: "\uC0C8 \uC544\uC774\uB514\uC5B4\uB97C \uAC80\uC99D\uD569\uB2C8\uB2E4. \uC9C8\uBB38\uC5D0 \uB2F5\uD574\uC8FC\uC138\uC694.",
117
+ modePrompt: "\uC5B4\uB5BB\uAC8C \uAC80\uC99D\uD560\uAE4C\uC694?",
118
+ modeQuickLabel: "\u26A1 \uC9E7\uAC8C (\uD575\uC2EC 5\uBB38\uD56D) \u2014 \uB9C9 \uB5A0\uC62C\uB790\uC744 \uB54C",
119
+ modeFullLabel: "\u{1F50D} \uC790\uC138\uD788 (13\uBB38\uD56D) \u2014 \uAE30\uD68D\uC774 \uC5B4\uB290 \uC815\uB3C4 \uC7A1\uD614\uC744 \uB54C",
120
+ modeSkipLabel: "\u23ED\uFE0F \uAC74\uB108\uB6F0\uAE30 \u2014 \uB178\uC158\xB7\uBB38\uC11C\uC5D0 \uC774\uBBF8 \uAE30\uD68D\uD574 \uB460",
121
+ skipSourcePrompt: "\u{1F4C4} \uAE30\uD68D \uBB38\uC11C \uC704\uCE58 (\uB178\uC158 \uC8FC\uC18C, \uD30C\uC77C \uACBD\uB85C \uB4F1):",
122
+ skipGo: "\u2705 \uC2DC\uC791\uD574\uB3C4 \uB3FC\uC694! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694 (vhk init)",
123
+ skipSourceLabel: (source) => `\uAE30\uD68D \uBB38\uC11C: ${source}`,
124
+ quickHeader: "\u26A1 \uC9E7\uC740 \uAC80\uC99D",
125
+ fullHeader: "\u{1F50D} \uC790\uC138\uD55C \uAC80\uC99D",
126
+ modeCountSuffix: (total) => `\u2014 ${total}\uBB38\uD56D`,
127
+ idea: "\u{1F4A1} \uC5B4\uB5A4 \uAC78 \uB9CC\uB4E4 \uAC74\uAC00\uC694? (\uD55C \uC904)",
128
+ ideaHint: '\uC608: "\uD300 \uD560 \uC77C\uC744 3\uCD08\uC5D0 \uCD94\uAC00\uD558\uB294 \uC571"',
129
+ painPoint: "\u{1F624} \uC774 \uBB38\uC81C, \uB204\uAC00 \uC5BC\uB9C8\uB098 \uC544\uD30C\uD574\uC694?",
130
+ painPointHint: '\uC608: "\uB9E4\uC77C \uC5D1\uC140\uC5D0 \uBCF5\uBD99\uD558\uB290\uB77C 30\uBD84\uC529 \uB0A0\uB9BC"',
131
+ edge: "\u{1F4AA} \uB098\uB9CC\uC758 \uAC15\uC810\uC740? (\uBE44\uC2B7\uD55C \uAC8C \uC788\uB294\uB370 \uC65C \uC774\uAC78?)",
132
+ edgeHint: '\uC608: "\uD55C\uAD6D\uC5B4\uB85C \uB41C \uAC00\uC774\uB4DC + \uBC14\uB85C \uC4F0\uB294 \uD15C\uD50C\uB9BF"',
133
+ checklistStart: "\u2500\u2500\u2500 \uC774\uC5B4\uC11C \uC9C8\uBB38\uD569\uB2C8\uB2E4 \u2500\u2500\u2500",
134
+ hintPrefix: " \u{1F4A1}",
135
+ verdictPrompt: (_failIf) => " \u2192 \uC9C0\uAE08 \uC0C1\uD0DC\uB294?",
136
+ statusPassChoice: "\u2705 \uAD1C\uCC2E\uC544\uC694",
137
+ statusHoldChoice: "\u{1F7E1} \uC544\uC9C1 \uBAA8\uB974\uACA0\uC5B4\uC694 (\uB098\uC911\uC5D0 \uCC44\uC6CC\uB3C4 \uB429\uB2C8\uB2E4)",
138
+ statusFailChoice: "\u{1F504} \uBC94\uC704\uB97C \uC904\uC5EC\uBCFC\uAC8C\uC694",
139
+ statusPassLine: " \u2705 \uAD1C\uCC2E\uC544\uC694",
140
+ statusHoldLine: " \u{1F7E1} \uBCF4\uB958 \u2014 \uAC1C\uBC1C\uD558\uBA74\uC11C \uCC44\uC6CC\uB3C4 \uB429\uB2C8\uB2E4",
141
+ statusFailLine: " \u{1F504} \uBC94\uC704 \uC870\uC815\uC774 \uD544\uC694\uD574 \uBCF4\uC5EC\uC694",
142
+ verdictTitle: "\u2550\u2550\u2550 \uACB0\uACFC \u2550\u2550\u2550",
143
+ ideaLabel: "\uB9CC\uB4E4 \uAC83:",
144
+ painPointLabel: "\uC544\uD508 \uC810:",
145
+ edgeLabel: "\uB098\uB9CC\uC758 \uAC15\uC810:",
146
+ countLine: (failCount, holdCount, total) => `\uBC94\uC704 \uC870\uC815 ${failCount}\uAC1C \xB7 \uBCF4\uB958 ${holdCount}\uAC1C / ${total}\uBB38\uD56D`,
147
+ go: "\u2705 \uC2DC\uC791\uD574\uB3C4 \uB3FC\uC694! \uB2E4\uC74C \uB2E8\uACC4(\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uAE30)\uB85C \uB118\uC5B4\uAC00\uC138\uC694.",
148
+ refine: "\u{1F504} \uC870\uAE08 \uB354 \uB2E4\uB4EC\uC73C\uBA74 \uC88B\uACA0\uC5B4\uC694. \uC704 \uD56D\uBAA9\uC744 \uBCF4\uC644\uD574 \uBCF4\uC138\uC694.",
149
+ drop: "\u{1F4A1} \uB2E4\uB978 \uC544\uC774\uB514\uC5B4\uB97C \uAC80\uD1A0\uD574 \uBCF4\uB294 \uAC74 \uC5B4\uB5A8\uAE4C\uC694?",
150
+ nextCommand: "\uB2E4\uC74C: vhk init (\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30)",
151
+ holdRemainHint: "\u{1F4A1} \uBCF4\uB958\uD55C \uD56D\uBAA9\uC740 \uAC1C\uBC1C\uD558\uBA74\uC11C \uCC44\uC6CC\uB3C4 \uAD1C\uCC2E\uC544\uC694.",
152
+ failMessage: "\uC544\uC9C1 \uBAA8\uB974\uACA0\uC5B4\uC694 \u2192 \uAD1C\uCC2E\uC544\uC694, \uAC1C\uBC1C\uD558\uBA74\uC11C \uCC44\uC6CC\uB3C4 \uB429\uB2C8\uB2E4."
153
+ },
154
+ init: {
155
+ title: "\u{1F6E0}\uFE0F \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30",
156
+ skipGate: "\u23ED\uFE0F 1\uB2E8\uACC4(\uC544\uC774\uB514\uC5B4 \uAC80\uC99D) \uAC74\uB108\uB6F0\uAE30 \u2014 \uAE30\uD68D\xB7\uC124\uACC4\uAC00 \uC774\uBBF8 \uC788\uC5B4\uC694",
157
+ projectName: "\u{1F4E6} \uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC740?",
158
+ projectNameHint: '\uC608: "\uD300 \uD560 \uC77C \uC571"',
159
+ description: "\u{1F4DD} \uD55C \uC904\uB85C \uC124\uBA85\uD558\uBA74?",
160
+ descriptionHint: '\uC608: "3\uCD08 \uB9CC\uC5D0 \uD560 \uC77C \uCD94\uAC00"',
161
+ projectType: "\u{1F3D7}\uFE0F \uC5B4\uB5A4 \uC885\uB958\uC778\uAC00\uC694?",
162
+ confirmStack: "\uC774 \uAE30\uC220 \uBB36\uC74C\uC73C\uB85C \uC9C4\uD589\uD560\uAE4C\uC694?",
163
+ canceled: "\uCDE8\uC18C\uD588\uC5B4\uC694. \uAE30\uC220 \uBB36\uC74C\uC744 \uBC14\uAFB8\uB824\uBA74 \uB2E4\uC2DC vhk init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
164
+ recommendedStack: "\uCD94\uCC9C \uAE30\uC220 \uBB36\uC74C:",
165
+ filesGenerating: "\u{1F4C2} \uD544\uC694\uD55C \uD30C\uC77C \uB9CC\uB4DC\uB294 \uC911...",
166
+ overwrite: (filePath) => ` \u26A0\uFE0F ${filePath} \uD30C\uC77C\uC774 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?`,
167
+ skipped: (filePath) => `${filePath} \u2014 \uAC74\uB108\uB700`,
168
+ done: "\u{1F389} \uD504\uB85C\uC81D\uD2B8 \uBF08\uB300\uAC00 \uC900\uBE44\uB410\uC5B4\uC694!",
169
+ nextSteps: "\uB2E4\uC74C\uC5D0 \uD560 \uC77C:",
170
+ fillHint: "CLAUDE.md \xB7 .cursorrules\uC5D0\uC11C \u{1F449} \uC5EC\uAE30\uB97C \uCC44\uC6CC\uC8FC\uC138\uC694 \uD45C\uC2DC\uB97C \uCC3E\uC544 \uCC44\uC6B0\uC138\uC694",
171
+ prdHint: "docs/PRD.md\uC5D0 1\uCC28 \uBC84\uC804\uC5D0 \uB123\uC744 \uAE30\uB2A5\xB7\uBE7C\uB294 \uAE30\uB2A5\uC744 \uC801\uC5B4 \uBCF4\uC138\uC694",
172
+ notionFetching: "\u{1F4E1} \uB178\uC158 \uAE30\uD68D \uD398\uC774\uC9C0 \uBD88\uB7EC\uC624\uB294 \uC911...",
173
+ notionDone: (name) => `\uB178\uC158\uC5D0\uC11C \uAC00\uC838\uC624\uAE30 \uC644\uB8CC: ${name}`,
174
+ notionReviewHint: "docs/PRD.md\uB97C \uC77D\uACE0 \u{1F449} \uC5EC\uAE30\uB97C \uCC44\uC6CC\uC8FC\uC138\uC694 \uD56D\uBAA9\uC744 \uCC44\uC6B0\uC138\uC694",
175
+ gitHintLabel: "\uD130\uBBF8\uB110\uC5D0 \uBCF5\uC0AC\uD560 \uBA85\uB839 (\uC544\uB798 \uBC15\uC2A4 \uBCF5\uBD99):",
176
+ gitHintCommand: 'git init && git add . && git commit -m "feat: \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791"',
177
+ startDev: "\uC774\uC81C \uAC1C\uBC1C\uD574 \uBCF4\uC138\uC694! \u{1F680}",
178
+ commandsMdDone: "\u{1F4CB} COMMANDS.md \uC0DD\uC131",
179
+ scriptsDone: "\u{1F4E6} package.json scripts \uCD94\uAC00"
180
+ },
181
+ recap: {
182
+ title: "\u{1F4DD} \uC624\uB298 \uD55C \uC77C \uC815\uB9AC",
183
+ analyzing: "\u{1F4CA} \uC624\uB298 \uBC14\uB010 \uD30C\uC77C\xB7\uCEE4\uBC0B\uC744 \uC0B4\uD3B4\uBCF4\uB294 \uC911...",
184
+ noRepo: "\u274C Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2C8\uC5D0\uC694. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
185
+ noChanges: "\u26A0\uFE0F \uC624\uB298 \uBC14\uB010 \uB0B4\uC6A9\uC774 \uC5C6\uC5B4\uC694.",
186
+ summary: "\u{1F4DD} \uC774\uBC88\uC5D0 \uBB58 \uD588\uB098\uC694? (1~3\uC904)",
187
+ summaryHint: '\uC608: "\uB85C\uADF8\uC778 \uD654\uBA74 \uB9CC\uB4E4\uACE0 \uBC84\uD2BC \uC0C9 \uACE0\uCE68"',
188
+ decisions: "\u{1F9ED} \uC815\uD55C \uACB0\uC815\uC774 \uC788\uB098\uC694? (\uC5C6\uC73C\uBA74 Enter)",
189
+ nextTodo: "\u23ED\uFE0F \uB2E4\uC74C\uC5D0 \uD560 \uC77C\uC740?",
190
+ blockers: "\u{1F6A7} \uB9C9\uD78C \uAC8C \uC788\uB098\uC694? (\uC5C6\uC73C\uBA74 Enter)",
191
+ done: "\u2705 \uC624\uB298 \uAE30\uB85D\uC744 \uC800\uC7A5\uD588\uC5B4\uC694!",
192
+ updateClaude: 'CLAUDE.md "\uC9C0\uAE08 \uC0C1\uD0DC"\uB3C4 \uAC19\uC774 \uACE0\uCE60\uAE4C\uC694?',
193
+ adrDetected: "\u{1F4D0} \uC4F0\uB294 \uAE30\uC220\xB7\uC124\uC815\uC774 \uBC14\uB010 \uAC83 \uAC19\uC544\uC694!",
194
+ createAdr: "\uC65C \uADF8\uB807\uAC8C \uD588\uB294\uC9C0 \uAE30\uB85D \uBB38\uC11C\uB97C \uB9CC\uB4E4\uAE4C\uC694?",
195
+ troubleDetected: "\u{1F527} \uBC84\uADF8\xB7\uC624\uB958\uB97C \uACE0\uCE5C \uCEE4\uBC0B\uC774 \uBCF4\uC5EC\uC694!",
196
+ createTroubleshoot: "\uC5B4\uB5BB\uAC8C \uACE0\uCCE4\uB294\uC9C0 \uBA54\uBAA8\uB97C \uB0A8\uAE38\uAE4C\uC694?"
197
+ },
198
+ check: {
199
+ title: "\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59 \uC810\uAC80",
200
+ noRules: "\u26A0\uFE0F RULES.md \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694.",
201
+ noAutoRules: "\u26A0\uFE0F \uC790\uB3D9\uC73C\uB85C \uAC80\uC0AC\uD560 \uADDC\uCE59\uC774 \uC5C6\uC5B4\uC694.",
202
+ allPassed: "\u{1F389} \uADDC\uCE59\uC744 \uBAA8\uB450 \uC9C0\uCF30\uC5B4\uC694!",
203
+ summary: "\u{1F4CA} \uC810\uAC80 \uACB0\uACFC:"
204
+ },
205
+ doctor: {
206
+ title: "\u{1FA7A} \uAC1C\uBC1C \uD658\uACBD \uC810\uAC80",
207
+ allOk: "\u{1F389} \uAC1C\uBC1C \uD658\uACBD \uC900\uBE44 \uC644\uB8CC!",
208
+ missing: "\u26A0\uFE0F \uC77C\uBD80 \uB3C4\uAD6C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
209
+ missingHint: "\uC704 \uC548\uB0B4\uB97C \uB530\uB77C \uC124\uCE58\uD558\uC138\uC694.",
210
+ projectFiles: "\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uD655\uC778:",
211
+ envNotIgnored: "\u26A0\uFE0F .env\uAC00 .gitignore\uC5D0 \uC5C6\uC74C! \uCD94\uAC00\uD558\uC138\uC694",
212
+ nextOkMessage: "\uD658\uACBD \uC810\uAC80 \uD1B5\uACFC! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uC2DC\uC791\uD558\uC138\uC694.",
213
+ nextRetryMessage: "\uC704 \uB3C4\uAD6C\uB97C \uC124\uCE58\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
214
+ updateAvailable: (latest) => `\u{1F195} v${latest} \uC0AC\uC6A9 \uAC00\uB2A5 \u2014 npm i -g @byh3071/vhk`,
215
+ updateCurrent: "\uCD5C\uC2E0 \uBC84\uC804\uC744 \uC4F0\uACE0 \uC788\uC5B4\uC694"
216
+ },
217
+ nlp: {
218
+ matched: "\uC774\uAC8C \uB9DE\uB098\uC694?",
219
+ notMatched: "\uBB34\uC2A8 \uB73B\uC778\uC9C0 \uBAA8\uB974\uACA0\uC5B4\uC694. vhk\uB97C \uC785\uB825\uD558\uBA74 \uBA54\uB274\uC5D0\uC11C \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
220
+ menuHint: "vhk\uB97C \uC785\uB825\uD558\uBA74 \uBA54\uB274\uC5D0\uC11C \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
221
+ },
222
+ secure: {
223
+ title: "\u{1F512} \uBE44\uBC00\uBC88\uD638\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC",
224
+ noGitignore: "\u26A0\uFE0F .gitignore \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694!",
225
+ noEnvInGitignore: "\u26A0\uFE0F .gitignore\uC5D0 .env\uAC00 \uC5C6\uC5B4\uC694!",
226
+ scanning: "\u{1F50D} \uD30C\uC77C\uC744 \uC0B4\uD3B4\uBCF4\uB294 \uC911...",
227
+ clean: "\u{1F389} \uBE44\uBC00\uBC88\uD638\xB7\uD0A4\uAC00 \uCF54\uB4DC\uC5D0 \uBCF4\uC774\uC9C0 \uC54A\uC544\uC694!",
228
+ summary: "\u{1F4CA} \uAC80\uC0AC \uC694\uC57D:"
229
+ },
230
+ sync: {
231
+ title: "\u{1F504} \uADDC\uCE59 \uD30C\uC77C \uB9DE\uCD94\uAE30",
232
+ noRules: "\u26A0\uFE0F RULES.md \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694.",
233
+ cursorrulesDone: "\u2705 .cursorrules \uB9DE\uCDA4 \uC644\uB8CC",
234
+ claudeDone: "\u2705 CLAUDE.md \uB9DE\uCDA4 \uC644\uB8CC",
235
+ done: "\u{1F504} \uB9DE\uCD94\uAE30 \uC644\uB8CC!"
236
+ },
237
+ ship: {
238
+ title: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
239
+ checklist: "\u{1F4CB} \uBC30\uD3EC \uC804 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
240
+ retro: "\u{1F50D} \uBC30\uD3EC \uD68C\uACE0",
241
+ buildLogCreated: "\u2705 \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131 \uC644\uB8CC",
242
+ buildLogDone: (rel) => `\u2705 \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131 \uC644\uB8CC: ${rel}`,
243
+ questionWell: "\uC798\uB41C \uC810\uC740?",
244
+ questionWrong: "\uC5B4\uB824\uC6E0\uB358 \uC810\uC740?",
245
+ questionLearned: "\uBC30\uC6B4 \uC810\uC740?",
246
+ questionNext: "\uB2E4\uC74C \uBC84\uC804\uC5D0\uC11C \uD560 \uAC83\uC740?",
247
+ checkboxPrompt: "\uC644\uB8CC\uD55C \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
248
+ incompleteHeader: "\u26A0\uFE0F \uC544\uC9C1 \uC644\uB8CC\uD558\uC9C0 \uC54A\uC740 \uD56D\uBAA9:",
249
+ proceedConfirm: "\uADF8\uB798\uB3C4 \uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694?",
250
+ allPassed: "\u2705 \uBAA8\uB4E0 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uD1B5\uACFC!",
251
+ retryMessage: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uB97C \uB9C8\uCE5C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uBCF4\uC138\uC694.",
252
+ retryCursorHint: "\uBE4C\uB4DC\uD558\uACE0 \uD14C\uC2A4\uD2B8 \uB3CC\uB824\uC918",
253
+ versionPrompt: "\uBC30\uD3EC \uBC84\uC804\uC740?",
254
+ versionHint: "\uC608: 0.4.0",
255
+ emptySection: "(\uBBF8\uC791\uC131)",
256
+ emptyNext: "(\uBBF8\uC815)",
257
+ deployMessage: "\uBE4C\uB4DC \uB85C\uADF8\uB97C \uC800\uC7A5\uD588\uC5B4\uC694! \uC774\uC81C \uC2E4\uC81C \uBC30\uD3EC\uB97C \uC9C4\uD589\uD558\uC138\uC694.",
258
+ deployCursorHint: "\uBC30\uD3EC\uD574\uC918",
259
+ checkBuild: "\uBE4C\uB4DC\uAC00 \uC131\uACF5\uD588\uB098\uC694?",
260
+ hintBuild: "pnpm build",
261
+ checkTest: "\uBAA8\uB4E0 \uD14C\uC2A4\uD2B8\uAC00 \uD1B5\uACFC\uD588\uB098\uC694?",
262
+ hintTest: "pnpm test --run",
263
+ checkVersion: "package.json \uBC84\uC804\uC744 \uC62C\uB838\uB098\uC694?",
264
+ hintVersion: "version \uD544\uB4DC \uD655\uC778",
265
+ checkChangelog: "\uBCC0\uACBD \uB0B4\uC6A9\uC744 \uAE30\uB85D\uD588\uB098\uC694?",
266
+ hintChangelog: "README \uB610\uB294 CHANGELOG",
267
+ checkSecurity: "\uBCF4\uC548 \uC2A4\uCE94\uC744 \uB3CC\uB838\uB098\uC694?",
268
+ hintSecurity: "vhk \uBCF4\uC548 scan",
269
+ checkCommit: "\uBAA8\uB4E0 \uBCC0\uACBD\uC774 \uCEE4\uBC0B\uB418\uC5C8\uB098\uC694?",
270
+ hintCommit: "git status \uD655\uC778",
271
+ changelogUpdated: (version) => `CHANGELOG.md \uAC31\uC2E0\uB428 \u2014 [Unreleased] \u2192 [${version}] \uC139\uC158\uC73C\uB85C \uC774\uB3D9`,
272
+ changelogNoUnreleased: "CHANGELOG.md\uC5D0 [Unreleased] \uC139\uC158\uC774 \uC5C6\uC5B4 \uC790\uB3D9 \uAC31\uC2E0\uC744 \uC2A4\uD0B5\uD588\uC5B4\uC694",
273
+ 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."
274
+ },
275
+ mcp: {
276
+ initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
277
+ serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428"
278
+ },
279
+ deploy: {
280
+ title: "\uBC30\uD3EC\uD558\uAE30",
281
+ selectPlatform: "\uC5B4\uB5A4 \uD50C\uB7AB\uD3FC\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?",
282
+ deploying: "\uBC30\uD3EC \uC911...",
283
+ success: "\uBC30\uD3EC \uC131\uACF5!",
284
+ failed: "\uBC30\uD3EC \uC2E4\uD328"
285
+ },
286
+ env: {
287
+ title: "\uD658\uACBD\uBCC0\uC218 \uAD00\uB9AC",
288
+ checkTitle: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80"
289
+ },
290
+ publish: {
291
+ title: "npm \uBC30\uD3EC",
292
+ selectBump: "\uBC84\uC804\uC744 \uC5B4\uB5BB\uAC8C \uC62C\uB9B4\uAE4C\uC694?",
293
+ building: "\uBE4C\uB4DC \uC911...",
294
+ buildSuccess: "\uBE4C\uB4DC \uC131\uACF5",
295
+ buildFailed: "\uBE4C\uB4DC \uC2E4\uD328",
296
+ testing: "\uD14C\uC2A4\uD2B8 \uC911...",
297
+ testSuccess: "\uD14C\uC2A4\uD2B8 \uD1B5\uACFC",
298
+ testFailed: "\uD14C\uC2A4\uD2B8 \uC2E4\uD328",
299
+ publishing: "npm \uBC30\uD3EC \uC911...",
300
+ publishSuccess: "npm \uBC30\uD3EC \uC131\uACF5!",
301
+ publishFailed: "npm \uBC30\uD3EC \uC2E4\uD328"
302
+ }
303
+ };
304
+ function lookup(path) {
305
+ const parts = path.split(".");
306
+ let cur = ko;
307
+ for (const part of parts) {
308
+ if (cur === null || typeof cur !== "object") return void 0;
309
+ cur = cur[part];
310
+ }
311
+ return cur;
312
+ }
313
+ function t(key, ...args) {
314
+ const value = lookup(key);
315
+ if (typeof value === "function") {
316
+ return value(...args);
317
+ }
318
+ if (typeof value === "string") return value;
319
+ return key;
320
+ }
321
+
322
+ // src/lib/next-step.ts
323
+ import chalk from "chalk";
324
+ function printNextStep(step) {
325
+ console.log("");
326
+ console.log(chalk.cyan.bold("\u2501\u2501\u2501 \uB2E4\uC74C\uC5D0 \uC774\uAC83\uB9CC \uD558\uC138\uC694 \u2501\u2501\u2501"));
327
+ console.log("");
328
+ console.log(` ${step.message}`);
329
+ if (step.command) {
330
+ console.log("");
331
+ console.log(chalk.white.bgGray(" \uD130\uBBF8\uB110\uC5D0 \uBCF5\uBD99 "));
332
+ console.log(chalk.green(` ${step.command}`));
333
+ }
334
+ if (step.cursorHint) {
335
+ console.log("");
336
+ console.log(chalk.white.bgBlue(" Cursor\uC5D0\uAC8C \uB9D0\uD558\uAE30 "));
337
+ console.log(chalk.blue(` "${step.cursorHint}"`));
338
+ }
339
+ if (step.alternative) {
340
+ console.log("");
341
+ console.log(chalk.dim(` \uB610\uB294: ${step.alternative}`));
342
+ }
343
+ console.log("");
344
+ console.log(chalk.cyan.bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
345
+ console.log("");
346
+ }
347
+
348
+ // src/commands/env.ts
349
+ function parseEnvKeys(content) {
350
+ return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
351
+ }
352
+ function ensureGitignore() {
353
+ const gitignorePath = ".gitignore";
354
+ if (existsSync(gitignorePath)) {
355
+ const content = readFileSync(gitignorePath, "utf-8");
356
+ if (!content.split("\n").some((l) => l.trim() === ".env")) {
357
+ appendFileSync(gitignorePath, "\n.env\n");
358
+ console.log(chalk2.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
359
+ }
360
+ } else {
361
+ writeFileSync(gitignorePath, ".env\nnode_modules/\ndist/\n");
362
+ console.log(chalk2.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
363
+ }
364
+ }
365
+ async function env() {
366
+ console.log(chalk2.bold("\n\u{1F510} " + t("env.title")));
367
+ console.log(chalk2.gray("\u2500".repeat(40)));
368
+ if (!existsSync(".env")) {
369
+ console.log(chalk2.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
370
+ console.log(chalk2.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
371
+ return;
372
+ }
373
+ const envContent = readFileSync(".env", "utf-8");
374
+ const keys = parseEnvKeys(envContent);
375
+ if (keys.length === 0) {
376
+ console.log(chalk2.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
377
+ return;
378
+ }
379
+ const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
380
+ writeFileSync(".env.example", exampleContent, "utf-8");
381
+ console.log(chalk2.green(`
382
+ \u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`));
383
+ keys.forEach((k) => console.log(chalk2.gray(` ${k}`)));
384
+ ensureGitignore();
385
+ printNextStep({
386
+ message: ".env.example \uC0DD\uC131 \uC644\uB8CC!",
387
+ command: "vhk env-check",
388
+ cursorHint: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80\uD574\uC918"
389
+ });
390
+ }
391
+ async function envCheck() {
392
+ console.log(chalk2.bold("\n\u{1F50D} " + t("env.checkTitle")));
393
+ console.log(chalk2.gray("\u2500".repeat(40)));
394
+ if (!existsSync(".env.example")) {
395
+ console.log(chalk2.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
396
+ return;
397
+ }
398
+ const requiredKeys = parseEnvKeys(readFileSync(".env.example", "utf-8"));
399
+ const currentKeys = existsSync(".env") ? parseEnvKeys(readFileSync(".env", "utf-8")) : [];
400
+ const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
401
+ const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
402
+ console.log(chalk2.cyan(`
403
+ \u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`));
404
+ if (missing.length === 0) {
405
+ console.log(chalk2.green("\n\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!"));
406
+ } else {
407
+ console.log(chalk2.red(`
408
+ \u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`));
409
+ missing.forEach((k) => console.log(chalk2.red(` \u2022 ${k}`)));
410
+ }
411
+ if (extra.length > 0) {
412
+ console.log(chalk2.yellow(`
413
+ \u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`));
414
+ extra.forEach((k) => console.log(chalk2.yellow(` \u2022 ${k}`)));
415
+ }
416
+ ensureGitignore();
417
+ }
418
+
419
+ // src/mcp/server.ts
420
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
421
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
422
+ import { z } from "zod";
423
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2 } from "fs";
424
+
425
+ // src/lib/exec.ts
426
+ import { execFileSync } from "child_process";
427
+ var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
428
+ function platformCmd(cmd) {
429
+ if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
430
+ return `${cmd}.cmd`;
431
+ }
432
+ return cmd;
433
+ }
434
+ function safeExecFile(cmd, args) {
435
+ try {
436
+ const out = execFileSync(platformCmd(cmd), args, {
437
+ encoding: "utf-8",
438
+ stdio: ["pipe", "pipe", "pipe"]
439
+ }).toString();
440
+ return { ok: true, out: out.trim() };
441
+ } catch (err) {
442
+ const msg = err instanceof Error ? err.message : String(err);
443
+ return { ok: false, err: msg };
444
+ }
445
+ }
446
+ function safeExecFileStream(cmd, args) {
447
+ try {
448
+ execFileSync(platformCmd(cmd), args, {
449
+ encoding: "utf-8",
450
+ stdio: "inherit"
451
+ });
452
+ return { ok: true };
453
+ } catch (err) {
454
+ const msg = err instanceof Error ? err.message : String(err);
455
+ return { ok: false, err: msg };
456
+ }
457
+ }
458
+
459
+ // src/mcp/server.ts
460
+ var SERVER_VERSION = "0.7.1";
461
+ function isGitRepo() {
462
+ return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
463
+ }
464
+ function createVhkMcpServer() {
465
+ const server = new McpServer({
466
+ name: "vhk",
467
+ version: SERVER_VERSION
468
+ });
469
+ server.registerTool(
470
+ "save",
471
+ {
472
+ description: "\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)",
473
+ inputSchema: {
474
+ message: z.string().optional().describe("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (\uBE44\uC6B0\uBA74 \uC790\uB3D9 \uC0DD\uC131)")
475
+ }
476
+ },
477
+ async ({ message }) => {
478
+ if (!isGitRepo()) {
479
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
480
+ }
481
+ const status = safeExecFile("git", ["status", "--porcelain"]);
482
+ if (!status.ok) {
483
+ return { content: [{ type: "text", text: `\u274C git status \uC2E4\uD328: ${status.err}` }] };
484
+ }
485
+ if (!status.out) {
486
+ return { content: [{ type: "text", text: "\u{1F4ED} \uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
487
+ }
488
+ const files = status.out.split("\n");
489
+ const now = /* @__PURE__ */ new Date();
490
+ 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")}`;
491
+ const commitMsg = message?.trim() || `\u2728 vhk save: ${ts}`;
492
+ const add = safeExecFile("git", ["add", "."]);
493
+ if (!add.ok) {
494
+ return { content: [{ type: "text", text: `\u274C git add \uC2E4\uD328: ${add.err}` }] };
495
+ }
496
+ const commit = safeExecFile("git", ["commit", "-m", commitMsg]);
497
+ if (!commit.ok) {
498
+ return { content: [{ type: "text", text: `\u274C commit \uC2E4\uD328: ${commit.err}` }] };
499
+ }
500
+ const push = safeExecFile("git", ["push"]);
501
+ 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)";
502
+ return {
503
+ content: [
504
+ {
505
+ type: "text",
506
+ text: `\u2705 ${files.length}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC! ${pushResult}
507
+ \uCEE4\uBC0B: ${commitMsg}`
508
+ }
509
+ ]
510
+ };
511
+ }
512
+ );
513
+ server.registerTool("undo", { description: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (soft reset, \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC720\uC9C0)" }, async () => {
514
+ if (!isGitRepo()) {
515
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
516
+ }
517
+ const last = safeExecFile("git", ["log", "--oneline", "-1"]);
518
+ if (!last.ok || !last.out) {
519
+ return { content: [{ type: "text", text: "\u{1F4ED} \uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
520
+ }
521
+ const reset = safeExecFile("git", ["reset", "--soft", "HEAD~1"]);
522
+ if (!reset.ok) {
523
+ return { content: [{ type: "text", text: `\u274C reset \uC2E4\uD328: ${reset.err}` }] };
524
+ }
525
+ return {
526
+ content: [
527
+ {
528
+ type: "text",
529
+ text: `\u2705 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC!
530
+ \uCDE8\uC18C\uB41C \uCEE4\uBC0B: ${last.out}
531
+ \u{1F4A1} \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.`
532
+ }
533
+ ]
534
+ };
535
+ });
536
+ 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 () => {
537
+ const lines = [];
538
+ if (existsSync2("package.json")) {
539
+ try {
540
+ const pkg = JSON.parse(readFileSync2("package.json", "utf-8"));
541
+ lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
542
+ } catch {
543
+ }
544
+ }
545
+ if (!isGitRepo()) {
546
+ lines.push("\u26A0\uFE0F git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
547
+ return { content: [{ type: "text", text: lines.join("\n") }] };
548
+ }
549
+ const branch = safeExecFile("git", ["branch", "--show-current"]);
550
+ if (branch.ok) lines.push(`\u{1F33F} \uBE0C\uB79C\uCE58: ${branch.out || "(detached)"}`);
551
+ const status = safeExecFile("git", ["status", "--porcelain"]);
552
+ if (status.ok) {
553
+ if (!status.out) {
554
+ lines.push("\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: \u2705 \uAE68\uB057\uD568");
555
+ } else {
556
+ const fileLines = status.out.split("\n");
557
+ const staged = fileLines.filter((l) => l[0] !== " " && l[0] !== "?").length;
558
+ const unstaged = fileLines.filter((l) => l[1] === "M" || l[1] === "D").length;
559
+ const untracked = fileLines.filter((l) => l.startsWith("??")).length;
560
+ const parts = [];
561
+ if (staged) parts.push(`\uC2A4\uD14C\uC774\uC9D5 ${staged}\uAC1C`);
562
+ if (unstaged) parts.push(`\uC218\uC815 ${unstaged}\uAC1C`);
563
+ if (untracked) parts.push(`\uC0C8\uD30C\uC77C ${untracked}\uAC1C`);
564
+ lines.push(`\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: ${parts.join(", ")}`);
565
+ }
566
+ }
567
+ const log = safeExecFile("git", ["log", "--oneline", "-3"]);
568
+ if (log.ok && log.out) {
569
+ lines.push("\u{1F4DC} \uCD5C\uADFC \uCEE4\uBC0B:");
570
+ log.out.split("\n").forEach((l) => lines.push(` ${l}`));
571
+ }
572
+ return { content: [{ type: "text", text: lines.join("\n") }] };
573
+ });
574
+ server.registerTool("diff", { description: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778 (staged/unstaged/\uC0C8\uD30C\uC77C + \uCD1D \uBCC0\uACBD \uC694\uC57D)" }, async () => {
575
+ if (!isGitRepo()) {
576
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
577
+ }
578
+ const unstaged = safeExecFile("git", ["diff", "--stat"]);
579
+ const staged = safeExecFile("git", ["diff", "--cached", "--stat"]);
580
+ const untracked = safeExecFile("git", ["ls-files", "--others", "--exclude-standard"]);
581
+ const unstagedOut = unstaged.ok ? unstaged.out : "";
582
+ const stagedOut = staged.ok ? staged.out : "";
583
+ const untrackedOut = untracked.ok ? untracked.out : "";
584
+ if (!unstagedOut && !stagedOut && !untrackedOut) {
585
+ return { content: [{ type: "text", text: "\u2705 \uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4." }] };
586
+ }
587
+ const lines = [];
588
+ if (stagedOut) {
589
+ lines.push("\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):");
590
+ lines.push(stagedOut);
591
+ }
592
+ if (unstagedOut) {
593
+ lines.push("\u270F\uFE0F \uC218\uC815\uB428 (unstaged):");
594
+ lines.push(unstagedOut);
595
+ }
596
+ if (untrackedOut) {
597
+ const files = untrackedOut.split("\n");
598
+ lines.push(`\u2795 \uC0C8 \uD30C\uC77C (${files.length}\uAC1C):`);
599
+ files.forEach((f) => lines.push(` + ${f}`));
600
+ }
601
+ const numstat = safeExecFile("git", ["diff", "--numstat", "HEAD"]);
602
+ if (numstat.ok && numstat.out) {
603
+ let totalAdd = 0;
604
+ let totalDel = 0;
605
+ let fileCount = 0;
606
+ numstat.out.split("\n").forEach((line) => {
607
+ const [add, del] = line.split(" ");
608
+ totalAdd += parseInt(add, 10) || 0;
609
+ totalDel += parseInt(del, 10) || 0;
610
+ fileCount += 1;
611
+ });
612
+ lines.push(`
613
+ \u{1F4CA} \uCD1D \uBCC0\uACBD: ${fileCount}\uAC1C \uD30C\uC77C, +${totalAdd}\uC904 -${totalDel}\uC904`);
614
+ }
615
+ return { content: [{ type: "text", text: lines.join("\n") }] };
616
+ });
617
+ server.registerTool("ship", { description: "\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC2E4\uD589 (\uBE4C\uB4DC + \uD14C\uC2A4\uD2B8 + \uBC84\uC804 + git \uC0C1\uD0DC)" }, async () => {
618
+ const checks = [];
619
+ const build = safeExecFile("pnpm", ["build"]);
620
+ checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
621
+ const test = safeExecFile("pnpm", ["test", "--run"]);
622
+ checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
623
+ if (existsSync2("package.json")) {
624
+ try {
625
+ const pkg = JSON.parse(readFileSync2("package.json", "utf-8"));
626
+ checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
627
+ } catch {
628
+ }
629
+ }
630
+ if (isGitRepo()) {
631
+ const status = safeExecFile("git", ["status", "--porcelain"]);
632
+ if (status.ok) {
633
+ if (status.out) {
634
+ checks.push(`\u26A0\uFE0F \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD\uC0AC\uD56D ${status.out.split("\n").length}\uAC1C`);
635
+ } else {
636
+ checks.push("\u2705 \uC6CC\uD0B9 \uB514\uB809\uD1A0\uB9AC \uAE68\uB057\uD568");
637
+ }
638
+ }
639
+ }
640
+ return { content: [{ type: "text", text: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\n" + checks.join("\n") }] };
641
+ });
642
+ server.registerTool("doctor", { description: "\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 (Node/Git/npm/pnpm/TypeScript)" }, async () => {
643
+ const checks = [];
644
+ const node = safeExecFile("node", ["--version"]);
645
+ checks.push(node.ok ? `\u2705 Node.js: ${node.out}` : "\u274C Node.js: \uC124\uCE58 \uC548 \uB428");
646
+ const git = safeExecFile("git", ["--version"]);
647
+ checks.push(git.ok ? `\u2705 Git: ${git.out}` : "\u274C Git: \uC124\uCE58 \uC548 \uB428");
648
+ const pnpm = safeExecFile("pnpm", ["--version"]);
649
+ checks.push(pnpm.ok ? `\u2705 pnpm: v${pnpm.out}` : "\u26A0\uFE0F pnpm: \uC124\uCE58 \uC548 \uB428");
650
+ const npm = safeExecFile("npm", ["--version"]);
651
+ checks.push(npm.ok ? `\u2705 npm: v${npm.out}` : "\u274C npm: \uC124\uCE58 \uC548 \uB428");
652
+ const tsc = safeExecFile("npx", ["tsc", "--version"]);
653
+ checks.push(tsc.ok ? `\u2705 TypeScript: ${tsc.out}` : "\u26A0\uFE0F TypeScript: \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5C6\uC74C");
654
+ return { content: [{ type: "text", text: "\u{1FA7A} \uD658\uACBD \uC810\uAC80 \uACB0\uACFC\n" + checks.join("\n") }] };
655
+ });
656
+ server.registerTool("check", { description: "\uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870 \uC810\uAC80 (\uD544\uC218 \uD30C\uC77C + VHK \uD558\uB124\uC2A4 \uD30C\uC77C)" }, async () => {
657
+ const required = ["package.json", "tsconfig.json", "README.md", ".gitignore"];
658
+ const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
659
+ const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
660
+ required.forEach((f) => {
661
+ lines.push(` ${existsSync2(f) ? "\u2705" : "\u274C"} ${f}`);
662
+ });
663
+ lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
664
+ recommended.forEach((f) => {
665
+ lines.push(` ${existsSync2(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
666
+ });
667
+ return { content: [{ type: "text", text: lines.join("\n") }] };
668
+ });
669
+ server.registerTool(
670
+ "recap",
671
+ {
672
+ description: "\uCD5C\uADFC \uC791\uC5C5 \uC694\uC57D (\uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC \uAE30\uBC18, \uB0A0\uC9DC \uD3EC\uD568)",
673
+ inputSchema: {
674
+ count: z.number().optional().describe("\uD45C\uC2DC\uD560 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 10)")
675
+ }
676
+ },
677
+ async ({ count }) => {
678
+ if (!isGitRepo()) {
679
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
680
+ }
681
+ const n = count && count > 0 ? Math.floor(count) : 10;
682
+ const log = safeExecFile("git", ["log", "--format=%h %ad %s", "--date=short", `-${n}`]);
683
+ if (!log.ok) {
684
+ return { content: [{ type: "text", text: `\u274C git log \uC2E4\uD328: ${log.err}` }] };
685
+ }
686
+ if (!log.out) {
687
+ return { content: [{ type: "text", text: "\u{1F4ED} \uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
688
+ }
689
+ return { content: [{ type: "text", text: `\u{1F4CB} \uCD5C\uADFC \uC791\uC5C5 (${n}\uAC1C):
690
+ ${log.out}` }] };
691
+ }
692
+ );
693
+ server.registerTool(
694
+ "env",
695
+ {
696
+ description: ".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore\uC5D0 .env \uC790\uB3D9 \uCD94\uAC00"
697
+ },
698
+ async () => {
699
+ if (!existsSync2(".env")) {
700
+ return { content: [{ type: "text", text: "\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 .env\uB97C \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694." }] };
701
+ }
702
+ const keys = parseEnvKeys(readFileSync2(".env", "utf-8"));
703
+ if (keys.length === 0) {
704
+ return { content: [{ type: "text", text: "\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
705
+ }
706
+ const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
707
+ writeFileSync2(".env.example", exampleContent, "utf-8");
708
+ const gitignoreLines = [];
709
+ if (existsSync2(".gitignore")) {
710
+ const content = readFileSync2(".gitignore", "utf-8");
711
+ if (!content.split("\n").some((l) => l.trim() === ".env")) {
712
+ appendFileSync2(".gitignore", "\n.env\n");
713
+ gitignoreLines.push("\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428");
714
+ }
715
+ } else {
716
+ writeFileSync2(".gitignore", ".env\nnode_modules/\ndist/\n");
717
+ gitignoreLines.push("\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)");
718
+ }
719
+ const lines = [`\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`, ...keys.map((k) => ` ${k}`)];
720
+ if (gitignoreLines.length) lines.push("", ...gitignoreLines);
721
+ return { content: [{ type: "text", text: lines.join("\n") }] };
722
+ }
723
+ );
724
+ server.registerTool(
725
+ "env-check",
726
+ {
727
+ description: "\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (.env.example \uAE30\uC900)"
728
+ },
729
+ async () => {
730
+ if (!existsSync2(".env.example")) {
731
+ return { content: [{ type: "text", text: "\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 env \uB3C4\uAD6C\uB97C \uC2E4\uD589\uD558\uC138\uC694." }] };
732
+ }
733
+ const requiredKeys = parseEnvKeys(readFileSync2(".env.example", "utf-8"));
734
+ const currentKeys = existsSync2(".env") ? parseEnvKeys(readFileSync2(".env", "utf-8")) : [];
735
+ const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
736
+ const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
737
+ const lines = [`\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`];
738
+ if (missing.length === 0) {
739
+ lines.push("\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!");
740
+ } else {
741
+ lines.push(`\u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`);
742
+ missing.forEach((k) => lines.push(` \u2022 ${k}`));
743
+ }
744
+ if (extra.length > 0) {
745
+ lines.push(`\u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`);
746
+ extra.forEach((k) => lines.push(` \u2022 ${k}`));
747
+ }
748
+ return { content: [{ type: "text", text: lines.join("\n") }] };
749
+ }
750
+ );
751
+ return server;
752
+ }
753
+ async function startMcpServer() {
754
+ const server = createVhkMcpServer();
755
+ const transport = new StdioServerTransport();
756
+ await server.connect(transport);
757
+ }
758
+
759
+ export {
760
+ __commonJS,
761
+ __toESM,
762
+ ko,
763
+ t,
764
+ printNextStep,
765
+ safeExecFile,
766
+ safeExecFileStream,
767
+ env,
768
+ envCheck,
769
+ startMcpServer
770
+ };