@byh3071/vhk 0.7.0 → 0.8.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.
@@ -0,0 +1,781 @@
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
+ design: {
276
+ title: "\uB514\uC790\uC778 \uD1A0\uD070 \uC0DD\uC131",
277
+ selectPalette: "\uCEEC\uB7EC \uD314\uB808\uD2B8\uB97C \uC120\uD0DD\uD558\uC138\uC694:"
278
+ },
279
+ theme: {
280
+ title: "\uD14C\uB9C8 \uC124\uC815 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"
281
+ },
282
+ ref: {
283
+ addTitle: "\uB808\uD37C\uB7F0\uC2A4 \uCD94\uAC00",
284
+ listTitle: "\uB808\uD37C\uB7F0\uC2A4 \uBAA9\uB85D"
285
+ },
286
+ mcp: {
287
+ initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
288
+ serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428"
289
+ },
290
+ deploy: {
291
+ title: "\uBC30\uD3EC\uD558\uAE30",
292
+ selectPlatform: "\uC5B4\uB5A4 \uD50C\uB7AB\uD3FC\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?",
293
+ deploying: "\uBC30\uD3EC \uC911...",
294
+ success: "\uBC30\uD3EC \uC131\uACF5!",
295
+ failed: "\uBC30\uD3EC \uC2E4\uD328"
296
+ },
297
+ env: {
298
+ title: "\uD658\uACBD\uBCC0\uC218 \uAD00\uB9AC",
299
+ checkTitle: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80"
300
+ },
301
+ publish: {
302
+ title: "npm \uBC30\uD3EC",
303
+ selectBump: "\uBC84\uC804\uC744 \uC5B4\uB5BB\uAC8C \uC62C\uB9B4\uAE4C\uC694?",
304
+ building: "\uBE4C\uB4DC \uC911...",
305
+ buildSuccess: "\uBE4C\uB4DC \uC131\uACF5",
306
+ buildFailed: "\uBE4C\uB4DC \uC2E4\uD328",
307
+ testing: "\uD14C\uC2A4\uD2B8 \uC911...",
308
+ testSuccess: "\uD14C\uC2A4\uD2B8 \uD1B5\uACFC",
309
+ testFailed: "\uD14C\uC2A4\uD2B8 \uC2E4\uD328",
310
+ publishing: "npm \uBC30\uD3EC \uC911...",
311
+ publishSuccess: "npm \uBC30\uD3EC \uC131\uACF5!",
312
+ publishFailed: "npm \uBC30\uD3EC \uC2E4\uD328"
313
+ }
314
+ };
315
+ function lookup(path) {
316
+ const parts = path.split(".");
317
+ let cur = ko;
318
+ for (const part of parts) {
319
+ if (cur === null || typeof cur !== "object") return void 0;
320
+ cur = cur[part];
321
+ }
322
+ return cur;
323
+ }
324
+ function t(key, ...args) {
325
+ const value = lookup(key);
326
+ if (typeof value === "function") {
327
+ return value(...args);
328
+ }
329
+ if (typeof value === "string") return value;
330
+ return key;
331
+ }
332
+
333
+ // src/lib/next-step.ts
334
+ import chalk from "chalk";
335
+ function printNextStep(step) {
336
+ console.log("");
337
+ console.log(chalk.cyan.bold("\u2501\u2501\u2501 \uB2E4\uC74C\uC5D0 \uC774\uAC83\uB9CC \uD558\uC138\uC694 \u2501\u2501\u2501"));
338
+ console.log("");
339
+ console.log(` ${step.message}`);
340
+ if (step.command) {
341
+ console.log("");
342
+ console.log(chalk.white.bgGray(" \uD130\uBBF8\uB110\uC5D0 \uBCF5\uBD99 "));
343
+ console.log(chalk.green(` ${step.command}`));
344
+ }
345
+ if (step.cursorHint) {
346
+ console.log("");
347
+ console.log(chalk.white.bgBlue(" Cursor\uC5D0\uAC8C \uB9D0\uD558\uAE30 "));
348
+ console.log(chalk.blue(` "${step.cursorHint}"`));
349
+ }
350
+ if (step.alternative) {
351
+ console.log("");
352
+ console.log(chalk.dim(` \uB610\uB294: ${step.alternative}`));
353
+ }
354
+ console.log("");
355
+ 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"));
356
+ console.log("");
357
+ }
358
+
359
+ // src/commands/env.ts
360
+ function parseEnvKeys(content) {
361
+ return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
362
+ }
363
+ function ensureGitignore() {
364
+ const gitignorePath = ".gitignore";
365
+ if (existsSync(gitignorePath)) {
366
+ const content = readFileSync(gitignorePath, "utf-8");
367
+ if (!content.split("\n").some((l) => l.trim() === ".env")) {
368
+ appendFileSync(gitignorePath, "\n.env\n");
369
+ console.log(chalk2.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
370
+ }
371
+ } else {
372
+ writeFileSync(gitignorePath, ".env\nnode_modules/\ndist/\n");
373
+ console.log(chalk2.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
374
+ }
375
+ }
376
+ async function env() {
377
+ console.log(chalk2.bold("\n\u{1F510} " + t("env.title")));
378
+ console.log(chalk2.gray("\u2500".repeat(40)));
379
+ if (!existsSync(".env")) {
380
+ console.log(chalk2.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
381
+ console.log(chalk2.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
382
+ return;
383
+ }
384
+ const envContent = readFileSync(".env", "utf-8");
385
+ const keys = parseEnvKeys(envContent);
386
+ if (keys.length === 0) {
387
+ console.log(chalk2.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
388
+ return;
389
+ }
390
+ const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
391
+ writeFileSync(".env.example", exampleContent, "utf-8");
392
+ console.log(chalk2.green(`
393
+ \u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`));
394
+ keys.forEach((k) => console.log(chalk2.gray(` ${k}`)));
395
+ ensureGitignore();
396
+ printNextStep({
397
+ message: ".env.example \uC0DD\uC131 \uC644\uB8CC!",
398
+ command: "vhk env-check",
399
+ cursorHint: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80\uD574\uC918"
400
+ });
401
+ }
402
+ async function envCheck() {
403
+ console.log(chalk2.bold("\n\u{1F50D} " + t("env.checkTitle")));
404
+ console.log(chalk2.gray("\u2500".repeat(40)));
405
+ if (!existsSync(".env.example")) {
406
+ console.log(chalk2.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
407
+ return;
408
+ }
409
+ const requiredKeys = parseEnvKeys(readFileSync(".env.example", "utf-8"));
410
+ const currentKeys = existsSync(".env") ? parseEnvKeys(readFileSync(".env", "utf-8")) : [];
411
+ const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
412
+ const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
413
+ console.log(chalk2.cyan(`
414
+ \u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`));
415
+ if (missing.length === 0) {
416
+ console.log(chalk2.green("\n\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!"));
417
+ } else {
418
+ console.log(chalk2.red(`
419
+ \u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`));
420
+ missing.forEach((k) => console.log(chalk2.red(` \u2022 ${k}`)));
421
+ }
422
+ if (extra.length > 0) {
423
+ console.log(chalk2.yellow(`
424
+ \u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`));
425
+ extra.forEach((k) => console.log(chalk2.yellow(` \u2022 ${k}`)));
426
+ }
427
+ ensureGitignore();
428
+ }
429
+
430
+ // src/mcp/server.ts
431
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
432
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
433
+ import { z } from "zod";
434
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2 } from "fs";
435
+
436
+ // src/lib/exec.ts
437
+ import { execFileSync } from "child_process";
438
+ var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
439
+ function platformCmd(cmd) {
440
+ if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
441
+ return `${cmd}.cmd`;
442
+ }
443
+ return cmd;
444
+ }
445
+ function safeExecFile(cmd, args) {
446
+ try {
447
+ const out = execFileSync(platformCmd(cmd), args, {
448
+ encoding: "utf-8",
449
+ stdio: ["pipe", "pipe", "pipe"]
450
+ }).toString();
451
+ return { ok: true, out: out.trim() };
452
+ } catch (err) {
453
+ const msg = err instanceof Error ? err.message : String(err);
454
+ return { ok: false, err: msg };
455
+ }
456
+ }
457
+ function safeExecFileStream(cmd, args) {
458
+ try {
459
+ execFileSync(platformCmd(cmd), args, {
460
+ encoding: "utf-8",
461
+ stdio: "inherit"
462
+ });
463
+ return { ok: true };
464
+ } catch (err) {
465
+ const msg = err instanceof Error ? err.message : String(err);
466
+ return { ok: false, err: msg };
467
+ }
468
+ }
469
+
470
+ // src/mcp/server.ts
471
+ var SERVER_VERSION = "0.7.1";
472
+ function isGitRepo() {
473
+ return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
474
+ }
475
+ function createVhkMcpServer() {
476
+ const server = new McpServer({
477
+ name: "vhk",
478
+ version: SERVER_VERSION
479
+ });
480
+ server.registerTool(
481
+ "save",
482
+ {
483
+ description: "\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)",
484
+ inputSchema: {
485
+ message: z.string().optional().describe("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (\uBE44\uC6B0\uBA74 \uC790\uB3D9 \uC0DD\uC131)")
486
+ }
487
+ },
488
+ async ({ message }) => {
489
+ if (!isGitRepo()) {
490
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
491
+ }
492
+ const status = safeExecFile("git", ["status", "--porcelain"]);
493
+ if (!status.ok) {
494
+ return { content: [{ type: "text", text: `\u274C git status \uC2E4\uD328: ${status.err}` }] };
495
+ }
496
+ if (!status.out) {
497
+ return { content: [{ type: "text", text: "\u{1F4ED} \uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
498
+ }
499
+ const files = status.out.split("\n");
500
+ const now = /* @__PURE__ */ new Date();
501
+ 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")}`;
502
+ const commitMsg = message?.trim() || `\u2728 vhk save: ${ts}`;
503
+ const add = safeExecFile("git", ["add", "."]);
504
+ if (!add.ok) {
505
+ return { content: [{ type: "text", text: `\u274C git add \uC2E4\uD328: ${add.err}` }] };
506
+ }
507
+ const commit = safeExecFile("git", ["commit", "-m", commitMsg]);
508
+ if (!commit.ok) {
509
+ return { content: [{ type: "text", text: `\u274C commit \uC2E4\uD328: ${commit.err}` }] };
510
+ }
511
+ const push = safeExecFile("git", ["push"]);
512
+ 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)";
513
+ return {
514
+ content: [
515
+ {
516
+ type: "text",
517
+ text: `\u2705 ${files.length}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC! ${pushResult}
518
+ \uCEE4\uBC0B: ${commitMsg}`
519
+ }
520
+ ]
521
+ };
522
+ }
523
+ );
524
+ server.registerTool("undo", { description: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (soft reset, \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC720\uC9C0)" }, async () => {
525
+ if (!isGitRepo()) {
526
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
527
+ }
528
+ const last = safeExecFile("git", ["log", "--oneline", "-1"]);
529
+ if (!last.ok || !last.out) {
530
+ return { content: [{ type: "text", text: "\u{1F4ED} \uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
531
+ }
532
+ const reset = safeExecFile("git", ["reset", "--soft", "HEAD~1"]);
533
+ if (!reset.ok) {
534
+ return { content: [{ type: "text", text: `\u274C reset \uC2E4\uD328: ${reset.err}` }] };
535
+ }
536
+ return {
537
+ content: [
538
+ {
539
+ type: "text",
540
+ text: `\u2705 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC!
541
+ \uCDE8\uC18C\uB41C \uCEE4\uBC0B: ${last.out}
542
+ \u{1F4A1} \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.`
543
+ }
544
+ ]
545
+ };
546
+ });
547
+ 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 () => {
548
+ const lines = [];
549
+ if (existsSync2("package.json")) {
550
+ try {
551
+ const pkg = JSON.parse(readFileSync2("package.json", "utf-8"));
552
+ lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
553
+ } catch {
554
+ }
555
+ }
556
+ if (!isGitRepo()) {
557
+ lines.push("\u26A0\uFE0F git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
558
+ return { content: [{ type: "text", text: lines.join("\n") }] };
559
+ }
560
+ const branch = safeExecFile("git", ["branch", "--show-current"]);
561
+ if (branch.ok) lines.push(`\u{1F33F} \uBE0C\uB79C\uCE58: ${branch.out || "(detached)"}`);
562
+ const status = safeExecFile("git", ["status", "--porcelain"]);
563
+ if (status.ok) {
564
+ if (!status.out) {
565
+ lines.push("\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: \u2705 \uAE68\uB057\uD568");
566
+ } else {
567
+ const fileLines = status.out.split("\n");
568
+ const staged = fileLines.filter((l) => l[0] !== " " && l[0] !== "?").length;
569
+ const unstaged = fileLines.filter((l) => l[1] === "M" || l[1] === "D").length;
570
+ const untracked = fileLines.filter((l) => l.startsWith("??")).length;
571
+ const parts = [];
572
+ if (staged) parts.push(`\uC2A4\uD14C\uC774\uC9D5 ${staged}\uAC1C`);
573
+ if (unstaged) parts.push(`\uC218\uC815 ${unstaged}\uAC1C`);
574
+ if (untracked) parts.push(`\uC0C8\uD30C\uC77C ${untracked}\uAC1C`);
575
+ lines.push(`\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: ${parts.join(", ")}`);
576
+ }
577
+ }
578
+ const log = safeExecFile("git", ["log", "--oneline", "-3"]);
579
+ if (log.ok && log.out) {
580
+ lines.push("\u{1F4DC} \uCD5C\uADFC \uCEE4\uBC0B:");
581
+ log.out.split("\n").forEach((l) => lines.push(` ${l}`));
582
+ }
583
+ return { content: [{ type: "text", text: lines.join("\n") }] };
584
+ });
585
+ server.registerTool("diff", { description: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778 (staged/unstaged/\uC0C8\uD30C\uC77C + \uCD1D \uBCC0\uACBD \uC694\uC57D)" }, async () => {
586
+ if (!isGitRepo()) {
587
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
588
+ }
589
+ const unstaged = safeExecFile("git", ["diff", "--stat"]);
590
+ const staged = safeExecFile("git", ["diff", "--cached", "--stat"]);
591
+ const untracked = safeExecFile("git", ["ls-files", "--others", "--exclude-standard"]);
592
+ const unstagedOut = unstaged.ok ? unstaged.out : "";
593
+ const stagedOut = staged.ok ? staged.out : "";
594
+ const untrackedOut = untracked.ok ? untracked.out : "";
595
+ if (!unstagedOut && !stagedOut && !untrackedOut) {
596
+ return { content: [{ type: "text", text: "\u2705 \uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4." }] };
597
+ }
598
+ const lines = [];
599
+ if (stagedOut) {
600
+ lines.push("\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):");
601
+ lines.push(stagedOut);
602
+ }
603
+ if (unstagedOut) {
604
+ lines.push("\u270F\uFE0F \uC218\uC815\uB428 (unstaged):");
605
+ lines.push(unstagedOut);
606
+ }
607
+ if (untrackedOut) {
608
+ const files = untrackedOut.split("\n");
609
+ lines.push(`\u2795 \uC0C8 \uD30C\uC77C (${files.length}\uAC1C):`);
610
+ files.forEach((f) => lines.push(` + ${f}`));
611
+ }
612
+ const numstat = safeExecFile("git", ["diff", "--numstat", "HEAD"]);
613
+ if (numstat.ok && numstat.out) {
614
+ let totalAdd = 0;
615
+ let totalDel = 0;
616
+ let fileCount = 0;
617
+ numstat.out.split("\n").forEach((line) => {
618
+ const [add, del] = line.split(" ");
619
+ totalAdd += parseInt(add, 10) || 0;
620
+ totalDel += parseInt(del, 10) || 0;
621
+ fileCount += 1;
622
+ });
623
+ lines.push(`
624
+ \u{1F4CA} \uCD1D \uBCC0\uACBD: ${fileCount}\uAC1C \uD30C\uC77C, +${totalAdd}\uC904 -${totalDel}\uC904`);
625
+ }
626
+ return { content: [{ type: "text", text: lines.join("\n") }] };
627
+ });
628
+ server.registerTool("ship", { description: "\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC2E4\uD589 (\uBE4C\uB4DC + \uD14C\uC2A4\uD2B8 + \uBC84\uC804 + git \uC0C1\uD0DC)" }, async () => {
629
+ const checks = [];
630
+ const build = safeExecFile("pnpm", ["build"]);
631
+ checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
632
+ const test = safeExecFile("pnpm", ["test", "--run"]);
633
+ checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
634
+ if (existsSync2("package.json")) {
635
+ try {
636
+ const pkg = JSON.parse(readFileSync2("package.json", "utf-8"));
637
+ checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
638
+ } catch {
639
+ }
640
+ }
641
+ if (isGitRepo()) {
642
+ const status = safeExecFile("git", ["status", "--porcelain"]);
643
+ if (status.ok) {
644
+ if (status.out) {
645
+ checks.push(`\u26A0\uFE0F \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD\uC0AC\uD56D ${status.out.split("\n").length}\uAC1C`);
646
+ } else {
647
+ checks.push("\u2705 \uC6CC\uD0B9 \uB514\uB809\uD1A0\uB9AC \uAE68\uB057\uD568");
648
+ }
649
+ }
650
+ }
651
+ return { content: [{ type: "text", text: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\n" + checks.join("\n") }] };
652
+ });
653
+ server.registerTool("doctor", { description: "\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 (Node/Git/npm/pnpm/TypeScript)" }, async () => {
654
+ const checks = [];
655
+ const node = safeExecFile("node", ["--version"]);
656
+ checks.push(node.ok ? `\u2705 Node.js: ${node.out}` : "\u274C Node.js: \uC124\uCE58 \uC548 \uB428");
657
+ const git = safeExecFile("git", ["--version"]);
658
+ checks.push(git.ok ? `\u2705 Git: ${git.out}` : "\u274C Git: \uC124\uCE58 \uC548 \uB428");
659
+ const pnpm = safeExecFile("pnpm", ["--version"]);
660
+ checks.push(pnpm.ok ? `\u2705 pnpm: v${pnpm.out}` : "\u26A0\uFE0F pnpm: \uC124\uCE58 \uC548 \uB428");
661
+ const npm = safeExecFile("npm", ["--version"]);
662
+ checks.push(npm.ok ? `\u2705 npm: v${npm.out}` : "\u274C npm: \uC124\uCE58 \uC548 \uB428");
663
+ const tsc = safeExecFile("npx", ["tsc", "--version"]);
664
+ checks.push(tsc.ok ? `\u2705 TypeScript: ${tsc.out}` : "\u26A0\uFE0F TypeScript: \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5C6\uC74C");
665
+ return { content: [{ type: "text", text: "\u{1FA7A} \uD658\uACBD \uC810\uAC80 \uACB0\uACFC\n" + checks.join("\n") }] };
666
+ });
667
+ server.registerTool("check", { description: "\uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870 \uC810\uAC80 (\uD544\uC218 \uD30C\uC77C + VHK \uD558\uB124\uC2A4 \uD30C\uC77C)" }, async () => {
668
+ const required = ["package.json", "tsconfig.json", "README.md", ".gitignore"];
669
+ const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
670
+ const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
671
+ required.forEach((f) => {
672
+ lines.push(` ${existsSync2(f) ? "\u2705" : "\u274C"} ${f}`);
673
+ });
674
+ lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
675
+ recommended.forEach((f) => {
676
+ lines.push(` ${existsSync2(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
677
+ });
678
+ return { content: [{ type: "text", text: lines.join("\n") }] };
679
+ });
680
+ server.registerTool(
681
+ "recap",
682
+ {
683
+ description: "\uCD5C\uADFC \uC791\uC5C5 \uC694\uC57D (\uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC \uAE30\uBC18, \uB0A0\uC9DC \uD3EC\uD568)",
684
+ inputSchema: {
685
+ count: z.number().optional().describe("\uD45C\uC2DC\uD560 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 10)")
686
+ }
687
+ },
688
+ async ({ count }) => {
689
+ if (!isGitRepo()) {
690
+ return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
691
+ }
692
+ const n = count && count > 0 ? Math.floor(count) : 10;
693
+ const log = safeExecFile("git", ["log", "--format=%h %ad %s", "--date=short", `-${n}`]);
694
+ if (!log.ok) {
695
+ return { content: [{ type: "text", text: `\u274C git log \uC2E4\uD328: ${log.err}` }] };
696
+ }
697
+ if (!log.out) {
698
+ return { content: [{ type: "text", text: "\u{1F4ED} \uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
699
+ }
700
+ return { content: [{ type: "text", text: `\u{1F4CB} \uCD5C\uADFC \uC791\uC5C5 (${n}\uAC1C):
701
+ ${log.out}` }] };
702
+ }
703
+ );
704
+ server.registerTool(
705
+ "env",
706
+ {
707
+ description: ".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore\uC5D0 .env \uC790\uB3D9 \uCD94\uAC00"
708
+ },
709
+ async () => {
710
+ if (!existsSync2(".env")) {
711
+ 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." }] };
712
+ }
713
+ const keys = parseEnvKeys(readFileSync2(".env", "utf-8"));
714
+ if (keys.length === 0) {
715
+ return { content: [{ type: "text", text: "\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
716
+ }
717
+ const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
718
+ writeFileSync2(".env.example", exampleContent, "utf-8");
719
+ const gitignoreLines = [];
720
+ if (existsSync2(".gitignore")) {
721
+ const content = readFileSync2(".gitignore", "utf-8");
722
+ if (!content.split("\n").some((l) => l.trim() === ".env")) {
723
+ appendFileSync2(".gitignore", "\n.env\n");
724
+ gitignoreLines.push("\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428");
725
+ }
726
+ } else {
727
+ writeFileSync2(".gitignore", ".env\nnode_modules/\ndist/\n");
728
+ gitignoreLines.push("\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)");
729
+ }
730
+ const lines = [`\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`, ...keys.map((k) => ` ${k}`)];
731
+ if (gitignoreLines.length) lines.push("", ...gitignoreLines);
732
+ return { content: [{ type: "text", text: lines.join("\n") }] };
733
+ }
734
+ );
735
+ server.registerTool(
736
+ "env-check",
737
+ {
738
+ description: "\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (.env.example \uAE30\uC900)"
739
+ },
740
+ async () => {
741
+ if (!existsSync2(".env.example")) {
742
+ 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." }] };
743
+ }
744
+ const requiredKeys = parseEnvKeys(readFileSync2(".env.example", "utf-8"));
745
+ const currentKeys = existsSync2(".env") ? parseEnvKeys(readFileSync2(".env", "utf-8")) : [];
746
+ const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
747
+ const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
748
+ const lines = [`\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`];
749
+ if (missing.length === 0) {
750
+ lines.push("\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!");
751
+ } else {
752
+ lines.push(`\u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`);
753
+ missing.forEach((k) => lines.push(` \u2022 ${k}`));
754
+ }
755
+ if (extra.length > 0) {
756
+ lines.push(`\u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`);
757
+ extra.forEach((k) => lines.push(` \u2022 ${k}`));
758
+ }
759
+ return { content: [{ type: "text", text: lines.join("\n") }] };
760
+ }
761
+ );
762
+ return server;
763
+ }
764
+ async function startMcpServer() {
765
+ const server = createVhkMcpServer();
766
+ const transport = new StdioServerTransport();
767
+ await server.connect(transport);
768
+ }
769
+
770
+ export {
771
+ __commonJS,
772
+ __toESM,
773
+ ko,
774
+ t,
775
+ printNextStep,
776
+ safeExecFile,
777
+ safeExecFileStream,
778
+ env,
779
+ envCheck,
780
+ startMcpServer
781
+ };