@byh3071/vhk 1.0.2 → 1.3.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 +38 -2
- package/dist/chunk-3HGOQLRT.js +2116 -0
- package/dist/index.js +1190 -1507
- package/dist/mcp/index.js +1 -1
- package/package.json +14 -15
- package/dist/chunk-SD7O6HO7.js +0 -826
|
@@ -0,0 +1,2116 @@
|
|
|
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
|
+
// node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
29
|
+
var require_ignore = __commonJS({
|
|
30
|
+
"node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js"(exports, module) {
|
|
31
|
+
"use strict";
|
|
32
|
+
function makeArray(subject) {
|
|
33
|
+
return Array.isArray(subject) ? subject : [subject];
|
|
34
|
+
}
|
|
35
|
+
var UNDEFINED = void 0;
|
|
36
|
+
var EMPTY = "";
|
|
37
|
+
var SPACE = " ";
|
|
38
|
+
var ESCAPE = "\\";
|
|
39
|
+
var REGEX_TEST_BLANK_LINE = /^\s+$/;
|
|
40
|
+
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
41
|
+
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
42
|
+
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
43
|
+
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
44
|
+
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
45
|
+
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
46
|
+
var SLASH = "/";
|
|
47
|
+
var TMP_KEY_IGNORE = "node-ignore";
|
|
48
|
+
if (typeof Symbol !== "undefined") {
|
|
49
|
+
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
50
|
+
}
|
|
51
|
+
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
52
|
+
var define = (object, key, value) => {
|
|
53
|
+
Object.defineProperty(object, key, { value });
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
|
|
57
|
+
var RETURN_FALSE = () => false;
|
|
58
|
+
var sanitizeRange = (range) => range.replace(
|
|
59
|
+
REGEX_REGEXP_RANGE,
|
|
60
|
+
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
|
|
61
|
+
);
|
|
62
|
+
var cleanRangeBackSlash = (slashes) => {
|
|
63
|
+
const { length } = slashes;
|
|
64
|
+
return slashes.slice(0, length - length % 2);
|
|
65
|
+
};
|
|
66
|
+
var REPLACERS = [
|
|
67
|
+
[
|
|
68
|
+
// Remove BOM
|
|
69
|
+
// TODO:
|
|
70
|
+
// Other similar zero-width characters?
|
|
71
|
+
/^\uFEFF/,
|
|
72
|
+
() => EMPTY
|
|
73
|
+
],
|
|
74
|
+
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
|
|
75
|
+
[
|
|
76
|
+
// (a\ ) -> (a )
|
|
77
|
+
// (a ) -> (a)
|
|
78
|
+
// (a ) -> (a)
|
|
79
|
+
// (a \ ) -> (a )
|
|
80
|
+
/((?:\\\\)*?)(\\?\s+)$/,
|
|
81
|
+
(_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
|
|
82
|
+
],
|
|
83
|
+
// Replace (\ ) with ' '
|
|
84
|
+
// (\ ) -> ' '
|
|
85
|
+
// (\\ ) -> '\\ '
|
|
86
|
+
// (\\\ ) -> '\\ '
|
|
87
|
+
[
|
|
88
|
+
/(\\+?)\s/g,
|
|
89
|
+
(_, m1) => {
|
|
90
|
+
const { length } = m1;
|
|
91
|
+
return m1.slice(0, length - length % 2) + SPACE;
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
// Escape metacharacters
|
|
95
|
+
// which is written down by users but means special for regular expressions.
|
|
96
|
+
// > There are 12 characters with special meanings:
|
|
97
|
+
// > - the backslash \,
|
|
98
|
+
// > - the caret ^,
|
|
99
|
+
// > - the dollar sign $,
|
|
100
|
+
// > - the period or dot .,
|
|
101
|
+
// > - the vertical bar or pipe symbol |,
|
|
102
|
+
// > - the question mark ?,
|
|
103
|
+
// > - the asterisk or star *,
|
|
104
|
+
// > - the plus sign +,
|
|
105
|
+
// > - the opening parenthesis (,
|
|
106
|
+
// > - the closing parenthesis ),
|
|
107
|
+
// > - and the opening square bracket [,
|
|
108
|
+
// > - the opening curly brace {,
|
|
109
|
+
// > These special characters are often called "metacharacters".
|
|
110
|
+
[
|
|
111
|
+
/[\\$.|*+(){^]/g,
|
|
112
|
+
(match) => `\\${match}`
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
// > a question mark (?) matches a single character
|
|
116
|
+
/(?!\\)\?/g,
|
|
117
|
+
() => "[^/]"
|
|
118
|
+
],
|
|
119
|
+
// leading slash
|
|
120
|
+
[
|
|
121
|
+
// > A leading slash matches the beginning of the pathname.
|
|
122
|
+
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
123
|
+
// A leading slash matches the beginning of the pathname
|
|
124
|
+
/^\//,
|
|
125
|
+
() => "^"
|
|
126
|
+
],
|
|
127
|
+
// replace special metacharacter slash after the leading slash
|
|
128
|
+
[
|
|
129
|
+
/\//g,
|
|
130
|
+
() => "\\/"
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
// > A leading "**" followed by a slash means match in all directories.
|
|
134
|
+
// > For example, "**/foo" matches file or directory "foo" anywhere,
|
|
135
|
+
// > the same as pattern "foo".
|
|
136
|
+
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
137
|
+
// > under directory "foo".
|
|
138
|
+
// Notice that the '*'s have been replaced as '\\*'
|
|
139
|
+
/^\^*\\\*\\\*\\\//,
|
|
140
|
+
// '**/foo' <-> 'foo'
|
|
141
|
+
() => "^(?:.*\\/)?"
|
|
142
|
+
],
|
|
143
|
+
// starting
|
|
144
|
+
[
|
|
145
|
+
// there will be no leading '/'
|
|
146
|
+
// (which has been replaced by section "leading slash")
|
|
147
|
+
// If starts with '**', adding a '^' to the regular expression also works
|
|
148
|
+
/^(?=[^^])/,
|
|
149
|
+
function startingReplacer() {
|
|
150
|
+
return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
// two globstars
|
|
154
|
+
[
|
|
155
|
+
// Use lookahead assertions so that we could match more than one `'/**'`
|
|
156
|
+
/\\\/\\\*\\\*(?=\\\/|$)/g,
|
|
157
|
+
// Zero, one or several directories
|
|
158
|
+
// should not use '*', or it will be replaced by the next replacer
|
|
159
|
+
// Check if it is not the last `'/**'`
|
|
160
|
+
(_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
|
|
161
|
+
],
|
|
162
|
+
// normal intermediate wildcards
|
|
163
|
+
[
|
|
164
|
+
// Never replace escaped '*'
|
|
165
|
+
// ignore rule '\*' will match the path '*'
|
|
166
|
+
// 'abc.*/' -> go
|
|
167
|
+
// 'abc.*' -> skip this rule,
|
|
168
|
+
// coz trailing single wildcard will be handed by [trailing wildcard]
|
|
169
|
+
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
170
|
+
// '*.js' matches '.js'
|
|
171
|
+
// '*.js' doesn't match 'abc'
|
|
172
|
+
(_, p1, p2) => {
|
|
173
|
+
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
174
|
+
return p1 + unescaped;
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
// unescape, revert step 3 except for back slash
|
|
179
|
+
// For example, if a user escape a '\\*',
|
|
180
|
+
// after step 3, the result will be '\\\\\\*'
|
|
181
|
+
/\\\\\\(?=[$.|*+(){^])/g,
|
|
182
|
+
() => ESCAPE
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
// '\\\\' -> '\\'
|
|
186
|
+
/\\\\/g,
|
|
187
|
+
() => ESCAPE
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
// > The range notation, e.g. [a-zA-Z],
|
|
191
|
+
// > can be used to match one of the characters in a range.
|
|
192
|
+
// `\` is escaped by step 3
|
|
193
|
+
/(\\)?\[([^\]/]*?)(\\*)($|\])/g,
|
|
194
|
+
(match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
|
|
195
|
+
],
|
|
196
|
+
// ending
|
|
197
|
+
[
|
|
198
|
+
// 'js' will not match 'js.'
|
|
199
|
+
// 'ab' will not match 'abc'
|
|
200
|
+
/(?:[^*])$/,
|
|
201
|
+
// WTF!
|
|
202
|
+
// https://git-scm.com/docs/gitignore
|
|
203
|
+
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
|
|
204
|
+
// which re-fixes #24, #38
|
|
205
|
+
// > If there is a separator at the end of the pattern then the pattern
|
|
206
|
+
// > will only match directories, otherwise the pattern can match both
|
|
207
|
+
// > files and directories.
|
|
208
|
+
// 'js*' will not match 'a.js'
|
|
209
|
+
// 'js/' will not match 'a.js'
|
|
210
|
+
// 'js' will match 'a.js' and 'a.js/'
|
|
211
|
+
(match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
|
|
212
|
+
]
|
|
213
|
+
];
|
|
214
|
+
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
|
|
215
|
+
var MODE_IGNORE = "regex";
|
|
216
|
+
var MODE_CHECK_IGNORE = "checkRegex";
|
|
217
|
+
var UNDERSCORE = "_";
|
|
218
|
+
var TRAILING_WILD_CARD_REPLACERS = {
|
|
219
|
+
[MODE_IGNORE](_, p1) {
|
|
220
|
+
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
221
|
+
return `${prefix}(?=$|\\/$)`;
|
|
222
|
+
},
|
|
223
|
+
[MODE_CHECK_IGNORE](_, p1) {
|
|
224
|
+
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
225
|
+
return `${prefix}(?=$|\\/$)`;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var makeRegexPrefix = (pattern) => REPLACERS.reduce(
|
|
229
|
+
(prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
|
|
230
|
+
pattern
|
|
231
|
+
);
|
|
232
|
+
var isString = (subject) => typeof subject === "string";
|
|
233
|
+
var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
|
|
234
|
+
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
235
|
+
var IgnoreRule = class {
|
|
236
|
+
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
237
|
+
this.pattern = pattern;
|
|
238
|
+
this.mark = mark;
|
|
239
|
+
this.negative = negative;
|
|
240
|
+
define(this, "body", body);
|
|
241
|
+
define(this, "ignoreCase", ignoreCase);
|
|
242
|
+
define(this, "regexPrefix", prefix);
|
|
243
|
+
}
|
|
244
|
+
get regex() {
|
|
245
|
+
const key = UNDERSCORE + MODE_IGNORE;
|
|
246
|
+
if (this[key]) {
|
|
247
|
+
return this[key];
|
|
248
|
+
}
|
|
249
|
+
return this._make(MODE_IGNORE, key);
|
|
250
|
+
}
|
|
251
|
+
get checkRegex() {
|
|
252
|
+
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
253
|
+
if (this[key]) {
|
|
254
|
+
return this[key];
|
|
255
|
+
}
|
|
256
|
+
return this._make(MODE_CHECK_IGNORE, key);
|
|
257
|
+
}
|
|
258
|
+
_make(mode, key) {
|
|
259
|
+
const str = this.regexPrefix.replace(
|
|
260
|
+
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
261
|
+
// It does not need to bind pattern
|
|
262
|
+
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
263
|
+
);
|
|
264
|
+
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
265
|
+
return define(this, key, regex);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
var createRule = ({
|
|
269
|
+
pattern,
|
|
270
|
+
mark
|
|
271
|
+
}, ignoreCase) => {
|
|
272
|
+
let negative = false;
|
|
273
|
+
let body = pattern;
|
|
274
|
+
if (body.indexOf("!") === 0) {
|
|
275
|
+
negative = true;
|
|
276
|
+
body = body.substr(1);
|
|
277
|
+
}
|
|
278
|
+
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
279
|
+
const regexPrefix = makeRegexPrefix(body);
|
|
280
|
+
return new IgnoreRule(
|
|
281
|
+
pattern,
|
|
282
|
+
mark,
|
|
283
|
+
body,
|
|
284
|
+
ignoreCase,
|
|
285
|
+
negative,
|
|
286
|
+
regexPrefix
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
var RuleManager = class {
|
|
290
|
+
constructor(ignoreCase) {
|
|
291
|
+
this._ignoreCase = ignoreCase;
|
|
292
|
+
this._rules = [];
|
|
293
|
+
}
|
|
294
|
+
_add(pattern) {
|
|
295
|
+
if (pattern && pattern[KEY_IGNORE]) {
|
|
296
|
+
this._rules = this._rules.concat(pattern._rules._rules);
|
|
297
|
+
this._added = true;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (isString(pattern)) {
|
|
301
|
+
pattern = {
|
|
302
|
+
pattern
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (checkPattern(pattern.pattern)) {
|
|
306
|
+
const rule = createRule(pattern, this._ignoreCase);
|
|
307
|
+
this._added = true;
|
|
308
|
+
this._rules.push(rule);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// @param {Array<string> | string | Ignore} pattern
|
|
312
|
+
add(pattern) {
|
|
313
|
+
this._added = false;
|
|
314
|
+
makeArray(
|
|
315
|
+
isString(pattern) ? splitPattern(pattern) : pattern
|
|
316
|
+
).forEach(this._add, this);
|
|
317
|
+
return this._added;
|
|
318
|
+
}
|
|
319
|
+
// Test one single path without recursively checking parent directories
|
|
320
|
+
//
|
|
321
|
+
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
322
|
+
// setting `checkUnignored` to `false` could reduce additional
|
|
323
|
+
// path matching.
|
|
324
|
+
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
325
|
+
// @returns {TestResult} true if a file is ignored
|
|
326
|
+
test(path3, checkUnignored, mode) {
|
|
327
|
+
let ignored = false;
|
|
328
|
+
let unignored = false;
|
|
329
|
+
let matchedRule;
|
|
330
|
+
this._rules.forEach((rule) => {
|
|
331
|
+
const { negative } = rule;
|
|
332
|
+
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const matched = rule[mode].test(path3);
|
|
336
|
+
if (!matched) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
ignored = !negative;
|
|
340
|
+
unignored = negative;
|
|
341
|
+
matchedRule = negative ? UNDEFINED : rule;
|
|
342
|
+
});
|
|
343
|
+
const ret = {
|
|
344
|
+
ignored,
|
|
345
|
+
unignored
|
|
346
|
+
};
|
|
347
|
+
if (matchedRule) {
|
|
348
|
+
ret.rule = matchedRule;
|
|
349
|
+
}
|
|
350
|
+
return ret;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
var throwError = (message, Ctor) => {
|
|
354
|
+
throw new Ctor(message);
|
|
355
|
+
};
|
|
356
|
+
var checkPath = (path3, originalPath, doThrow) => {
|
|
357
|
+
if (!isString(path3)) {
|
|
358
|
+
return doThrow(
|
|
359
|
+
`path must be a string, but got \`${originalPath}\``,
|
|
360
|
+
TypeError
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (!path3) {
|
|
364
|
+
return doThrow(`path must not be empty`, TypeError);
|
|
365
|
+
}
|
|
366
|
+
if (checkPath.isNotRelative(path3)) {
|
|
367
|
+
const r = "`path.relative()`d";
|
|
368
|
+
return doThrow(
|
|
369
|
+
`path should be a ${r} string, but got "${originalPath}"`,
|
|
370
|
+
RangeError
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
};
|
|
375
|
+
var isNotRelative = (path3) => REGEX_TEST_INVALID_PATH.test(path3);
|
|
376
|
+
checkPath.isNotRelative = isNotRelative;
|
|
377
|
+
checkPath.convert = (p) => p;
|
|
378
|
+
var Ignore = class {
|
|
379
|
+
constructor({
|
|
380
|
+
ignorecase = true,
|
|
381
|
+
ignoreCase = ignorecase,
|
|
382
|
+
allowRelativePaths = false
|
|
383
|
+
} = {}) {
|
|
384
|
+
define(this, KEY_IGNORE, true);
|
|
385
|
+
this._rules = new RuleManager(ignoreCase);
|
|
386
|
+
this._strictPathCheck = !allowRelativePaths;
|
|
387
|
+
this._initCache();
|
|
388
|
+
}
|
|
389
|
+
_initCache() {
|
|
390
|
+
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
391
|
+
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
392
|
+
}
|
|
393
|
+
add(pattern) {
|
|
394
|
+
if (this._rules.add(pattern)) {
|
|
395
|
+
this._initCache();
|
|
396
|
+
}
|
|
397
|
+
return this;
|
|
398
|
+
}
|
|
399
|
+
// legacy
|
|
400
|
+
addPattern(pattern) {
|
|
401
|
+
return this.add(pattern);
|
|
402
|
+
}
|
|
403
|
+
// @returns {TestResult}
|
|
404
|
+
_test(originalPath, cache, checkUnignored, slices) {
|
|
405
|
+
const path3 = originalPath && checkPath.convert(originalPath);
|
|
406
|
+
checkPath(
|
|
407
|
+
path3,
|
|
408
|
+
originalPath,
|
|
409
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
410
|
+
);
|
|
411
|
+
return this._t(path3, cache, checkUnignored, slices);
|
|
412
|
+
}
|
|
413
|
+
checkIgnore(path3) {
|
|
414
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path3)) {
|
|
415
|
+
return this.test(path3);
|
|
416
|
+
}
|
|
417
|
+
const slices = path3.split(SLASH).filter(Boolean);
|
|
418
|
+
slices.pop();
|
|
419
|
+
if (slices.length) {
|
|
420
|
+
const parent = this._t(
|
|
421
|
+
slices.join(SLASH) + SLASH,
|
|
422
|
+
this._testCache,
|
|
423
|
+
true,
|
|
424
|
+
slices
|
|
425
|
+
);
|
|
426
|
+
if (parent.ignored) {
|
|
427
|
+
return parent;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return this._rules.test(path3, false, MODE_CHECK_IGNORE);
|
|
431
|
+
}
|
|
432
|
+
_t(path3, cache, checkUnignored, slices) {
|
|
433
|
+
if (path3 in cache) {
|
|
434
|
+
return cache[path3];
|
|
435
|
+
}
|
|
436
|
+
if (!slices) {
|
|
437
|
+
slices = path3.split(SLASH).filter(Boolean);
|
|
438
|
+
}
|
|
439
|
+
slices.pop();
|
|
440
|
+
if (!slices.length) {
|
|
441
|
+
return cache[path3] = this._rules.test(path3, checkUnignored, MODE_IGNORE);
|
|
442
|
+
}
|
|
443
|
+
const parent = this._t(
|
|
444
|
+
slices.join(SLASH) + SLASH,
|
|
445
|
+
cache,
|
|
446
|
+
checkUnignored,
|
|
447
|
+
slices
|
|
448
|
+
);
|
|
449
|
+
return cache[path3] = parent.ignored ? parent : this._rules.test(path3, checkUnignored, MODE_IGNORE);
|
|
450
|
+
}
|
|
451
|
+
ignores(path3) {
|
|
452
|
+
return this._test(path3, this._ignoreCache, false).ignored;
|
|
453
|
+
}
|
|
454
|
+
createFilter() {
|
|
455
|
+
return (path3) => !this.ignores(path3);
|
|
456
|
+
}
|
|
457
|
+
filter(paths) {
|
|
458
|
+
return makeArray(paths).filter(this.createFilter());
|
|
459
|
+
}
|
|
460
|
+
// @returns {TestResult}
|
|
461
|
+
test(path3) {
|
|
462
|
+
return this._test(path3, this._testCache, true);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
var factory = (options) => new Ignore(options);
|
|
466
|
+
var isPathValid = (path3) => checkPath(path3 && checkPath.convert(path3), path3, RETURN_FALSE);
|
|
467
|
+
var setupWindows = () => {
|
|
468
|
+
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
469
|
+
checkPath.convert = makePosix;
|
|
470
|
+
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
471
|
+
checkPath.isNotRelative = (path3) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path3) || isNotRelative(path3);
|
|
472
|
+
};
|
|
473
|
+
if (
|
|
474
|
+
// Detect `process` so that it can run in browsers.
|
|
475
|
+
typeof process !== "undefined" && process.platform === "win32"
|
|
476
|
+
) {
|
|
477
|
+
setupWindows();
|
|
478
|
+
}
|
|
479
|
+
module.exports = factory;
|
|
480
|
+
factory.default = factory;
|
|
481
|
+
module.exports.isPathValid = isPathValid;
|
|
482
|
+
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// src/commands/deploy.ts
|
|
487
|
+
import { existsSync } from "fs";
|
|
488
|
+
import chalk2 from "chalk";
|
|
489
|
+
import inquirer from "inquirer";
|
|
490
|
+
|
|
491
|
+
// src/lib/exec.ts
|
|
492
|
+
import { execFileSync } from "child_process";
|
|
493
|
+
var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
494
|
+
function platformCmd(cmd) {
|
|
495
|
+
if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
|
|
496
|
+
return `${cmd}.cmd`;
|
|
497
|
+
}
|
|
498
|
+
return cmd;
|
|
499
|
+
}
|
|
500
|
+
function resolveCmd(cmd, args) {
|
|
501
|
+
if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
|
|
502
|
+
return { bin: "cmd.exe", argv: ["/d", "/s", "/c", `${cmd}.cmd`, ...args] };
|
|
503
|
+
}
|
|
504
|
+
return { bin: platformCmd(cmd), argv: args };
|
|
505
|
+
}
|
|
506
|
+
function safeExecFile(cmd, args, opts = {}) {
|
|
507
|
+
const { bin, argv } = resolveCmd(cmd, args);
|
|
508
|
+
const env2 = opts.env ? { ...process.env, ...opts.env } : void 0;
|
|
509
|
+
try {
|
|
510
|
+
const out = execFileSync(bin, argv, {
|
|
511
|
+
encoding: "utf-8",
|
|
512
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
513
|
+
env: env2
|
|
514
|
+
}).toString();
|
|
515
|
+
return { ok: true, out: out.trim() };
|
|
516
|
+
} catch (err) {
|
|
517
|
+
const e = err;
|
|
518
|
+
const stdout = e.stdout ? e.stdout.toString() : "";
|
|
519
|
+
const msg = e.message ?? String(err);
|
|
520
|
+
return { ok: false, err: msg, out: stdout.trim() };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function safeExecFileStream(cmd, args) {
|
|
524
|
+
const { bin, argv } = resolveCmd(cmd, args);
|
|
525
|
+
try {
|
|
526
|
+
execFileSync(bin, argv, {
|
|
527
|
+
encoding: "utf-8",
|
|
528
|
+
stdio: "inherit"
|
|
529
|
+
});
|
|
530
|
+
return { ok: true };
|
|
531
|
+
} catch (err) {
|
|
532
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
533
|
+
return { ok: false, err: msg };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/i18n/ko.ts
|
|
538
|
+
var ko = {
|
|
539
|
+
status: {
|
|
540
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC",
|
|
541
|
+
notGitRepo: "Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2C8\uC5D0\uC694. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
542
|
+
branch: "\uBE0C\uB79C\uCE58:",
|
|
543
|
+
changes: "\uBCC0\uACBD:",
|
|
544
|
+
recentCommits: "\uCD5C\uADFC \uCEE4\uBC0B (3):",
|
|
545
|
+
noCommits: "\uCEE4\uBC0B \uC5C6\uC74C",
|
|
546
|
+
remote: "\uC6D0\uACA9:",
|
|
547
|
+
noUpstream: "upstream \uC5C6\uC74C",
|
|
548
|
+
inSync: "\uB3D9\uAE30\uD654\uB428",
|
|
549
|
+
ahead: (n) => `\u2191${n} ahead`,
|
|
550
|
+
behind: (n) => `\u2193${n} behind`,
|
|
551
|
+
package: "package.json:",
|
|
552
|
+
noPackage: "package.json \uC5C6\uC74C",
|
|
553
|
+
detached: "(detached HEAD)",
|
|
554
|
+
unknownBranch: "(\uC54C \uC218 \uC5C6\uC74C)"
|
|
555
|
+
},
|
|
556
|
+
save: {
|
|
557
|
+
title: "\uC800\uC7A5\uD558\uAE30",
|
|
558
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
559
|
+
noChanges: "\uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
560
|
+
filesHeader: (n) => `\uBCC0\uACBD\uB41C \uD30C\uC77C (${n}\uAC1C):`,
|
|
561
|
+
commitMessage: "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (Enter\uB85C \uAE30\uBCF8\uAC12 \uC0AC\uC6A9):",
|
|
562
|
+
saving: "\uC800\uC7A5 \uC911...",
|
|
563
|
+
pushing: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uC5D0 \uC62C\uB9AC\uB294 \uC911...",
|
|
564
|
+
successWithPush: "\uC800\uC7A5 + \uC6D0\uACA9 \uC5C5\uB85C\uB4DC \uC644\uB8CC!",
|
|
565
|
+
successLocal: "\uB85C\uCEEC \uC800\uC7A5 \uC644\uB8CC!",
|
|
566
|
+
noRemote: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 push\uB97C \uAC74\uB108\uB6F0\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
567
|
+
failed: "\uC800\uC7A5 \uC2E4\uD328",
|
|
568
|
+
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",
|
|
569
|
+
securityWarnHeader: "\uC800\uC7A5 \uC804 \uBCF4\uC548 \uD655\uC778:",
|
|
570
|
+
secretsFound: (n) => `\uCF54\uB4DC\uC5D0\uC11C CRITICAL/HIGH \uC2DC\uD06C\uB9BF \uD328\uD134 ${n}\uAC74 \uAC10\uC9C0`,
|
|
571
|
+
secretsConfirm: "\uADF8\uB798\uB3C4 \uCEE4\uBC0B\xB7push\uB97C \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
572
|
+
cancelled: "\uC800\uC7A5\uC744 \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.",
|
|
573
|
+
pushFailed: "push \uC2E4\uD328 (\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uC644\uB8CC\uB428)",
|
|
574
|
+
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.",
|
|
575
|
+
done: (n) => `${n}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC!`,
|
|
576
|
+
doneLocalOnly: (n) => `${n}\uAC1C \uD30C\uC77C \uB85C\uCEEC \uC800\uC7A5\uB428 (push\uB294 \uC2E4\uD328)`
|
|
577
|
+
},
|
|
578
|
+
undo: {
|
|
579
|
+
title: "\uB418\uB3CC\uB9AC\uAE30",
|
|
580
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
581
|
+
noCommits: "\uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
582
|
+
recentHeader: "\u{1F4CB} \uCD5C\uADFC \uCEE4\uBC0B:",
|
|
583
|
+
howMany: "\uBA87 \uAC1C\uC758 \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9B4\uAE4C\uC694?",
|
|
584
|
+
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.",
|
|
585
|
+
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.",
|
|
586
|
+
confirmMessage: "\uCD5C\uADFC \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9AC\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
587
|
+
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?`,
|
|
588
|
+
cancelled: "\uCDE8\uC18C\uB428",
|
|
589
|
+
success: "\uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC! \uBCC0\uACBD\uC0AC\uD56D\uC740 \uADF8\uB300\uB85C \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.",
|
|
590
|
+
stagedHint: "\uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544 \uC788\uC5B4\uC694.",
|
|
591
|
+
rootCommit: "\uCCAB \uCEE4\uBC0B\uB9CC \uC788\uC5B4\uC11C \uB354 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
592
|
+
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)",
|
|
593
|
+
failed: "\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328"
|
|
594
|
+
},
|
|
595
|
+
diff: {
|
|
596
|
+
title: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778",
|
|
597
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
598
|
+
noChanges: "\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
|
|
599
|
+
stagedHeader: "\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):",
|
|
600
|
+
unstagedHeader: "\u270F\uFE0F \uC218\uC815\uB428 (unstaged):",
|
|
601
|
+
untrackedHeader: (n) => `\u2795 \uC0C8 \uD30C\uC77C (${n}\uAC1C):`,
|
|
602
|
+
summaryHeader: "\u{1F4CA} \uCD1D \uBCC0\uACBD \uC694\uC57D (\uC791\uC5C5 \uD2B8\uB9AC vs HEAD)",
|
|
603
|
+
filesLine: (n) => `\uD30C\uC77C: ${n}\uAC1C`
|
|
604
|
+
},
|
|
605
|
+
start: {
|
|
606
|
+
title: "\u{1F680} VHK \uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 \uB9C8\uBC95\uC0AC",
|
|
607
|
+
intro: "4\uB2E8\uACC4\uB85C \uC790\uB3D9 \uC9C4\uD589\uB429\uB2C8\uB2E4:",
|
|
608
|
+
step1: "1) git \uC800\uC7A5\uC18C \uCD08\uAE30\uD654",
|
|
609
|
+
step2: "2) \uD504\uB85C\uC81D\uD2B8 \uBB38\uC11C \uC0DD\uC131 (vhk init)",
|
|
610
|
+
step3: "3) Cursor MCP \uB4F1\uB85D (vhk mcp-init)",
|
|
611
|
+
step4: "4) AI \uCEE8\uD14D\uC2A4\uD2B8 \uC0DD\uC131 (vhk context)",
|
|
612
|
+
confirmStart: "\uACC4\uC18D\uD560\uAE4C\uC694?",
|
|
613
|
+
cancelled: "\uCDE8\uC18C\uD588\uC5B4\uC694. \uB2E4\uC74C\uC5D0 \uB2E4\uC2DC vhk start\uB97C \uC2E4\uD589\uD558\uC138\uC694.",
|
|
614
|
+
step1Header: "[1/4] git \uC800\uC7A5\uC18C \uCD08\uAE30\uD654",
|
|
615
|
+
step2Header: "[2/4] \uD504\uB85C\uC81D\uD2B8 \uBB38\uC11C \uC0DD\uC131",
|
|
616
|
+
step3Header: "[3/4] Cursor MCP \uB4F1\uB85D",
|
|
617
|
+
step4Header: "[4/4] AI \uCEE8\uD14D\uC2A4\uD2B8 \uC0DD\uC131",
|
|
618
|
+
gitAlreadyInit: "\uC774\uBBF8 git \uC800\uC7A5\uC18C\uC785\uB2C8\uB2E4. \uAC74\uB108\uB701\uB2C8\uB2E4.",
|
|
619
|
+
gitInitDone: "git \uC800\uC7A5\uC18C \uCD08\uAE30\uD654 \uC644\uB8CC",
|
|
620
|
+
allDone: "\u{1F389} \uBAA8\uB4E0 \uB2E8\uACC4 \uC644\uB8CC!",
|
|
621
|
+
nextHintMessage: "\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 \uC900\uBE44 \uB05D! \uC774\uC81C \uAC1C\uBC1C\uC744 \uC2DC\uC791\uD558\uC138\uC694.",
|
|
622
|
+
nextHintCursor: "docs/PRD.md \uBCF4\uACE0 \uAC1C\uBC1C \uC2DC\uC791\uD574\uC918"
|
|
623
|
+
},
|
|
624
|
+
gate: {
|
|
625
|
+
title: "\u{1F4A1} \uC544\uC774\uB514\uC5B4 \uAC80\uC99D",
|
|
626
|
+
welcome: "\uC0C8 \uC544\uC774\uB514\uC5B4\uB97C \uAC80\uC99D\uD569\uB2C8\uB2E4. \uC9C8\uBB38\uC5D0 \uB2F5\uD574\uC8FC\uC138\uC694.",
|
|
627
|
+
modePrompt: "\uC5B4\uB5BB\uAC8C \uAC80\uC99D\uD560\uAE4C\uC694?",
|
|
628
|
+
modeQuickLabel: "\u26A1 \uC9E7\uAC8C (\uD575\uC2EC 5\uBB38\uD56D) \u2014 \uB9C9 \uB5A0\uC62C\uB790\uC744 \uB54C",
|
|
629
|
+
modeFullLabel: "\u{1F50D} \uC790\uC138\uD788 (13\uBB38\uD56D) \u2014 \uAE30\uD68D\uC774 \uC5B4\uB290 \uC815\uB3C4 \uC7A1\uD614\uC744 \uB54C",
|
|
630
|
+
modeSkipLabel: "\u23ED\uFE0F \uAC74\uB108\uB6F0\uAE30 \u2014 \uB178\uC158\xB7\uBB38\uC11C\uC5D0 \uC774\uBBF8 \uAE30\uD68D\uD574 \uB460",
|
|
631
|
+
skipSourcePrompt: "\u{1F4C4} \uAE30\uD68D \uBB38\uC11C \uC704\uCE58 (\uB178\uC158 \uC8FC\uC18C, \uD30C\uC77C \uACBD\uB85C \uB4F1):",
|
|
632
|
+
skipGo: "\u2705 \uC2DC\uC791\uD574\uB3C4 \uB3FC\uC694! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694 (vhk init)",
|
|
633
|
+
skipSourceLabel: (source) => `\uAE30\uD68D \uBB38\uC11C: ${source}`,
|
|
634
|
+
quickHeader: "\u26A1 \uC9E7\uC740 \uAC80\uC99D",
|
|
635
|
+
fullHeader: "\u{1F50D} \uC790\uC138\uD55C \uAC80\uC99D",
|
|
636
|
+
modeCountSuffix: (total) => `\u2014 ${total}\uBB38\uD56D`,
|
|
637
|
+
idea: "\u{1F4A1} \uC5B4\uB5A4 \uAC78 \uB9CC\uB4E4 \uAC74\uAC00\uC694? (\uD55C \uC904)",
|
|
638
|
+
ideaHint: '\uC608: "\uD300 \uD560 \uC77C\uC744 3\uCD08\uC5D0 \uCD94\uAC00\uD558\uB294 \uC571"',
|
|
639
|
+
painPoint: "\u{1F624} \uC774 \uBB38\uC81C, \uB204\uAC00 \uC5BC\uB9C8\uB098 \uC544\uD30C\uD574\uC694?",
|
|
640
|
+
painPointHint: '\uC608: "\uB9E4\uC77C \uC5D1\uC140\uC5D0 \uBCF5\uBD99\uD558\uB290\uB77C 30\uBD84\uC529 \uB0A0\uB9BC"',
|
|
641
|
+
edge: "\u{1F4AA} \uB098\uB9CC\uC758 \uAC15\uC810\uC740? (\uBE44\uC2B7\uD55C \uAC8C \uC788\uB294\uB370 \uC65C \uC774\uAC78?)",
|
|
642
|
+
edgeHint: '\uC608: "\uD55C\uAD6D\uC5B4\uB85C \uB41C \uAC00\uC774\uB4DC + \uBC14\uB85C \uC4F0\uB294 \uD15C\uD50C\uB9BF"',
|
|
643
|
+
checklistStart: "\u2500\u2500\u2500 \uC774\uC5B4\uC11C \uC9C8\uBB38\uD569\uB2C8\uB2E4 \u2500\u2500\u2500",
|
|
644
|
+
hintPrefix: " \u{1F4A1}",
|
|
645
|
+
verdictPrompt: (_failIf) => " \u2192 \uC9C0\uAE08 \uC0C1\uD0DC\uB294?",
|
|
646
|
+
statusPassChoice: "\u2705 \uAD1C\uCC2E\uC544\uC694",
|
|
647
|
+
statusHoldChoice: "\u{1F7E1} \uC544\uC9C1 \uBAA8\uB974\uACA0\uC5B4\uC694 (\uB098\uC911\uC5D0 \uCC44\uC6CC\uB3C4 \uB429\uB2C8\uB2E4)",
|
|
648
|
+
statusFailChoice: "\u{1F504} \uBC94\uC704\uB97C \uC904\uC5EC\uBCFC\uAC8C\uC694",
|
|
649
|
+
statusPassLine: " \u2705 \uAD1C\uCC2E\uC544\uC694",
|
|
650
|
+
statusHoldLine: " \u{1F7E1} \uBCF4\uB958 \u2014 \uAC1C\uBC1C\uD558\uBA74\uC11C \uCC44\uC6CC\uB3C4 \uB429\uB2C8\uB2E4",
|
|
651
|
+
statusFailLine: " \u{1F504} \uBC94\uC704 \uC870\uC815\uC774 \uD544\uC694\uD574 \uBCF4\uC5EC\uC694",
|
|
652
|
+
verdictTitle: "\u2550\u2550\u2550 \uACB0\uACFC \u2550\u2550\u2550",
|
|
653
|
+
ideaLabel: "\uB9CC\uB4E4 \uAC83:",
|
|
654
|
+
painPointLabel: "\uC544\uD508 \uC810:",
|
|
655
|
+
edgeLabel: "\uB098\uB9CC\uC758 \uAC15\uC810:",
|
|
656
|
+
countLine: (failCount, holdCount, total) => `\uBC94\uC704 \uC870\uC815 ${failCount}\uAC1C \xB7 \uBCF4\uB958 ${holdCount}\uAC1C / ${total}\uBB38\uD56D`,
|
|
657
|
+
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.",
|
|
658
|
+
refine: "\u{1F504} \uC870\uAE08 \uB354 \uB2E4\uB4EC\uC73C\uBA74 \uC88B\uACA0\uC5B4\uC694. \uC704 \uD56D\uBAA9\uC744 \uBCF4\uC644\uD574 \uBCF4\uC138\uC694.",
|
|
659
|
+
drop: "\u{1F4A1} \uB2E4\uB978 \uC544\uC774\uB514\uC5B4\uB97C \uAC80\uD1A0\uD574 \uBCF4\uB294 \uAC74 \uC5B4\uB5A8\uAE4C\uC694?",
|
|
660
|
+
nextCommand: "\uB2E4\uC74C: vhk init (\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30)",
|
|
661
|
+
holdRemainHint: "\u{1F4A1} \uBCF4\uB958\uD55C \uD56D\uBAA9\uC740 \uAC1C\uBC1C\uD558\uBA74\uC11C \uCC44\uC6CC\uB3C4 \uAD1C\uCC2E\uC544\uC694.",
|
|
662
|
+
failMessage: "\uC544\uC9C1 \uBAA8\uB974\uACA0\uC5B4\uC694 \u2192 \uAD1C\uCC2E\uC544\uC694, \uAC1C\uBC1C\uD558\uBA74\uC11C \uCC44\uC6CC\uB3C4 \uB429\uB2C8\uB2E4."
|
|
663
|
+
},
|
|
664
|
+
init: {
|
|
665
|
+
title: "\u{1F6E0}\uFE0F \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30",
|
|
666
|
+
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",
|
|
667
|
+
projectName: "\u{1F4E6} \uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC740?",
|
|
668
|
+
projectNameHint: '\uC608: "\uD300 \uD560 \uC77C \uC571"',
|
|
669
|
+
description: "\u{1F4DD} \uD55C \uC904\uB85C \uC124\uBA85\uD558\uBA74?",
|
|
670
|
+
descriptionHint: '\uC608: "3\uCD08 \uB9CC\uC5D0 \uD560 \uC77C \uCD94\uAC00"',
|
|
671
|
+
projectType: "\u{1F3D7}\uFE0F \uC5B4\uB5A4 \uC885\uB958\uC778\uAC00\uC694?",
|
|
672
|
+
confirmStack: "\uC774 \uAE30\uC220 \uBB36\uC74C\uC73C\uB85C \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
673
|
+
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.",
|
|
674
|
+
recommendedStack: "\uCD94\uCC9C \uAE30\uC220 \uBB36\uC74C:",
|
|
675
|
+
filesGenerating: "\u{1F4C2} \uD544\uC694\uD55C \uD30C\uC77C \uB9CC\uB4DC\uB294 \uC911...",
|
|
676
|
+
overwrite: (filePath) => ` \u26A0\uFE0F ${filePath} \uD30C\uC77C\uC774 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?`,
|
|
677
|
+
skipped: (filePath) => `${filePath} \u2014 \uAC74\uB108\uB700`,
|
|
678
|
+
done: "\u{1F389} \uD504\uB85C\uC81D\uD2B8 \uBF08\uB300\uAC00 \uC900\uBE44\uB410\uC5B4\uC694!",
|
|
679
|
+
nextSteps: "\uB2E4\uC74C\uC5D0 \uD560 \uC77C:",
|
|
680
|
+
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",
|
|
681
|
+
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",
|
|
682
|
+
notionFetching: "\u{1F4E1} \uB178\uC158 \uAE30\uD68D \uD398\uC774\uC9C0 \uBD88\uB7EC\uC624\uB294 \uC911...",
|
|
683
|
+
notionDone: (name) => `\uB178\uC158\uC5D0\uC11C \uAC00\uC838\uC624\uAE30 \uC644\uB8CC: ${name}`,
|
|
684
|
+
notionReviewHint: "docs/PRD.md\uB97C \uC77D\uACE0 \u{1F449} \uC5EC\uAE30\uB97C \uCC44\uC6CC\uC8FC\uC138\uC694 \uD56D\uBAA9\uC744 \uCC44\uC6B0\uC138\uC694",
|
|
685
|
+
gitHintLabel: "\uD130\uBBF8\uB110\uC5D0 \uBCF5\uC0AC\uD560 \uBA85\uB839 (\uC544\uB798 \uBC15\uC2A4 \uBCF5\uBD99):",
|
|
686
|
+
gitHintCommand: 'git init && git add . && git commit -m "feat: \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791"',
|
|
687
|
+
startDev: "\uC774\uC81C \uAC1C\uBC1C\uD574 \uBCF4\uC138\uC694! \u{1F680}",
|
|
688
|
+
commandsMdDone: "\u{1F4CB} COMMANDS.md \uC0DD\uC131",
|
|
689
|
+
scriptsDone: "\u{1F4E6} package.json scripts \uCD94\uAC00"
|
|
690
|
+
},
|
|
691
|
+
recap: {
|
|
692
|
+
title: "\u{1F4DD} \uC624\uB298 \uD55C \uC77C \uC815\uB9AC",
|
|
693
|
+
analyzing: "\u{1F4CA} \uC624\uB298 \uBC14\uB010 \uD30C\uC77C\xB7\uCEE4\uBC0B\uC744 \uC0B4\uD3B4\uBCF4\uB294 \uC911...",
|
|
694
|
+
noRepo: "\u274C Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2C8\uC5D0\uC694. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
695
|
+
noChanges: "\u26A0\uFE0F \uC624\uB298 \uBC14\uB010 \uB0B4\uC6A9\uC774 \uC5C6\uC5B4\uC694.",
|
|
696
|
+
summary: "\u{1F4DD} \uC774\uBC88\uC5D0 \uBB58 \uD588\uB098\uC694? (1~3\uC904)",
|
|
697
|
+
summaryHint: '\uC608: "\uB85C\uADF8\uC778 \uD654\uBA74 \uB9CC\uB4E4\uACE0 \uBC84\uD2BC \uC0C9 \uACE0\uCE68"',
|
|
698
|
+
decisions: "\u{1F9ED} \uC815\uD55C \uACB0\uC815\uC774 \uC788\uB098\uC694? (\uC5C6\uC73C\uBA74 Enter)",
|
|
699
|
+
nextTodo: "\u23ED\uFE0F \uB2E4\uC74C\uC5D0 \uD560 \uC77C\uC740?",
|
|
700
|
+
blockers: "\u{1F6A7} \uB9C9\uD78C \uAC8C \uC788\uB098\uC694? (\uC5C6\uC73C\uBA74 Enter)",
|
|
701
|
+
done: "\u2705 \uC624\uB298 \uAE30\uB85D\uC744 \uC800\uC7A5\uD588\uC5B4\uC694!",
|
|
702
|
+
updateClaude: 'CLAUDE.md "\uC9C0\uAE08 \uC0C1\uD0DC"\uB3C4 \uAC19\uC774 \uACE0\uCE60\uAE4C\uC694?',
|
|
703
|
+
adrDetected: "\u{1F4D0} \uC4F0\uB294 \uAE30\uC220\xB7\uC124\uC815\uC774 \uBC14\uB010 \uAC83 \uAC19\uC544\uC694!",
|
|
704
|
+
createAdr: "\uC65C \uADF8\uB807\uAC8C \uD588\uB294\uC9C0 \uAE30\uB85D \uBB38\uC11C\uB97C \uB9CC\uB4E4\uAE4C\uC694?",
|
|
705
|
+
troubleDetected: "\u{1F527} \uBC84\uADF8\xB7\uC624\uB958\uB97C \uACE0\uCE5C \uCEE4\uBC0B\uC774 \uBCF4\uC5EC\uC694!",
|
|
706
|
+
createTroubleshoot: "\uC5B4\uB5BB\uAC8C \uACE0\uCCE4\uB294\uC9C0 \uBA54\uBAA8\uB97C \uB0A8\uAE38\uAE4C\uC694?"
|
|
707
|
+
},
|
|
708
|
+
check: {
|
|
709
|
+
title: "\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59 \uC810\uAC80",
|
|
710
|
+
noRules: "\u26A0\uFE0F RULES.md \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694.",
|
|
711
|
+
noAutoRules: "\u26A0\uFE0F \uC790\uB3D9\uC73C\uB85C \uAC80\uC0AC\uD560 \uADDC\uCE59\uC774 \uC5C6\uC5B4\uC694.",
|
|
712
|
+
allPassed: "\u{1F389} \uADDC\uCE59\uC744 \uBAA8\uB450 \uC9C0\uCF30\uC5B4\uC694!",
|
|
713
|
+
summary: "\u{1F4CA} \uC810\uAC80 \uACB0\uACFC:"
|
|
714
|
+
},
|
|
715
|
+
doctor: {
|
|
716
|
+
title: "\u{1FA7A} \uAC1C\uBC1C \uD658\uACBD \uC810\uAC80",
|
|
717
|
+
allOk: "\u{1F389} \uAC1C\uBC1C \uD658\uACBD \uC900\uBE44 \uC644\uB8CC!",
|
|
718
|
+
missing: "\u26A0\uFE0F \uC77C\uBD80 \uB3C4\uAD6C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
719
|
+
missingHint: "\uC704 \uC548\uB0B4\uB97C \uB530\uB77C \uC124\uCE58\uD558\uC138\uC694.",
|
|
720
|
+
projectFiles: "\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uD655\uC778:",
|
|
721
|
+
envNotIgnored: "\u26A0\uFE0F .env\uAC00 .gitignore\uC5D0 \uC5C6\uC74C! \uCD94\uAC00\uD558\uC138\uC694",
|
|
722
|
+
nextOkMessage: "\uD658\uACBD \uC810\uAC80 \uD1B5\uACFC! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uC2DC\uC791\uD558\uC138\uC694.",
|
|
723
|
+
nextRetryMessage: "\uC704 \uB3C4\uAD6C\uB97C \uC124\uCE58\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
|
|
724
|
+
updateAvailable: (latest) => `\u{1F195} v${latest} \uC0AC\uC6A9 \uAC00\uB2A5 \u2014 npm i -g @byh3071/vhk`,
|
|
725
|
+
updateCurrent: "\uCD5C\uC2E0 \uBC84\uC804\uC744 \uC4F0\uACE0 \uC788\uC5B4\uC694"
|
|
726
|
+
},
|
|
727
|
+
nlp: {
|
|
728
|
+
matched: "\uC774\uAC8C \uB9DE\uB098\uC694?",
|
|
729
|
+
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.",
|
|
730
|
+
menuHint: "vhk\uB97C \uC785\uB825\uD558\uBA74 \uBA54\uB274\uC5D0\uC11C \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
|
731
|
+
},
|
|
732
|
+
secure: {
|
|
733
|
+
title: "\u{1F512} \uBE44\uBC00\uBC88\uD638\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC",
|
|
734
|
+
noGitignore: "\u26A0\uFE0F .gitignore \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694!",
|
|
735
|
+
noEnvInGitignore: "\u26A0\uFE0F .gitignore\uC5D0 .env\uAC00 \uC5C6\uC5B4\uC694!",
|
|
736
|
+
scanning: "\u{1F50D} \uD30C\uC77C\uC744 \uC0B4\uD3B4\uBCF4\uB294 \uC911...",
|
|
737
|
+
clean: "\u{1F389} \uBE44\uBC00\uBC88\uD638\xB7\uD0A4\uAC00 \uCF54\uB4DC\uC5D0 \uBCF4\uC774\uC9C0 \uC54A\uC544\uC694!",
|
|
738
|
+
summary: "\u{1F4CA} \uAC80\uC0AC \uC694\uC57D:"
|
|
739
|
+
},
|
|
740
|
+
sync: {
|
|
741
|
+
title: "\u{1F504} \uADDC\uCE59 \uD30C\uC77C \uB9DE\uCD94\uAE30",
|
|
742
|
+
noRules: "\u26A0\uFE0F RULES.md \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694.",
|
|
743
|
+
cursorrulesDone: "\u2705 .cursorrules \uB9DE\uCDA4 \uC644\uB8CC",
|
|
744
|
+
claudeDone: "\u2705 CLAUDE.md \uB9DE\uCDA4 \uC644\uB8CC",
|
|
745
|
+
done: "\u{1F504} \uB9DE\uCD94\uAE30 \uC644\uB8CC!"
|
|
746
|
+
},
|
|
747
|
+
ship: {
|
|
748
|
+
title: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
|
|
749
|
+
checklist: "\u{1F4CB} \uBC30\uD3EC \uC804 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
|
|
750
|
+
retro: "\u{1F50D} \uBC30\uD3EC \uD68C\uACE0",
|
|
751
|
+
buildLogCreated: "\u2705 \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131 \uC644\uB8CC",
|
|
752
|
+
buildLogDone: (rel) => `\u2705 \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131 \uC644\uB8CC: ${rel}`,
|
|
753
|
+
questionWell: "\uC798\uB41C \uC810\uC740?",
|
|
754
|
+
questionWrong: "\uC5B4\uB824\uC6E0\uB358 \uC810\uC740?",
|
|
755
|
+
questionLearned: "\uBC30\uC6B4 \uC810\uC740?",
|
|
756
|
+
questionNext: "\uB2E4\uC74C \uBC84\uC804\uC5D0\uC11C \uD560 \uAC83\uC740?",
|
|
757
|
+
checkboxPrompt: "\uC644\uB8CC\uD55C \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
758
|
+
incompleteHeader: "\u26A0\uFE0F \uC544\uC9C1 \uC644\uB8CC\uD558\uC9C0 \uC54A\uC740 \uD56D\uBAA9:",
|
|
759
|
+
proceedConfirm: "\uADF8\uB798\uB3C4 \uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
760
|
+
allPassed: "\u2705 \uBAA8\uB4E0 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uD1B5\uACFC!",
|
|
761
|
+
retryMessage: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uB97C \uB9C8\uCE5C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uBCF4\uC138\uC694.",
|
|
762
|
+
retryCursorHint: "\uBE4C\uB4DC\uD558\uACE0 \uD14C\uC2A4\uD2B8 \uB3CC\uB824\uC918",
|
|
763
|
+
versionPrompt: "\uBC30\uD3EC \uBC84\uC804\uC740?",
|
|
764
|
+
versionHint: "\uC608: 0.4.0",
|
|
765
|
+
emptySection: "(\uBBF8\uC791\uC131)",
|
|
766
|
+
emptyNext: "(\uBBF8\uC815)",
|
|
767
|
+
deployMessage: "\uBE4C\uB4DC \uB85C\uADF8\uB97C \uC800\uC7A5\uD588\uC5B4\uC694! \uC774\uC81C \uC2E4\uC81C \uBC30\uD3EC\uB97C \uC9C4\uD589\uD558\uC138\uC694.",
|
|
768
|
+
deployCursorHint: "\uBC30\uD3EC\uD574\uC918",
|
|
769
|
+
checkBuild: "\uBE4C\uB4DC\uAC00 \uC131\uACF5\uD588\uB098\uC694?",
|
|
770
|
+
hintBuild: "pnpm build",
|
|
771
|
+
checkTest: "\uBAA8\uB4E0 \uD14C\uC2A4\uD2B8\uAC00 \uD1B5\uACFC\uD588\uB098\uC694?",
|
|
772
|
+
hintTest: "pnpm test --run",
|
|
773
|
+
checkVersion: "package.json \uBC84\uC804\uC744 \uC62C\uB838\uB098\uC694?",
|
|
774
|
+
hintVersion: "version \uD544\uB4DC \uD655\uC778",
|
|
775
|
+
checkChangelog: "\uBCC0\uACBD \uB0B4\uC6A9\uC744 \uAE30\uB85D\uD588\uB098\uC694?",
|
|
776
|
+
hintChangelog: "README \uB610\uB294 CHANGELOG",
|
|
777
|
+
checkSecurity: "\uBCF4\uC548 \uC2A4\uCE94\uC744 \uB3CC\uB838\uB098\uC694?",
|
|
778
|
+
hintSecurity: "vhk \uBCF4\uC548 scan",
|
|
779
|
+
checkCommit: "\uBAA8\uB4E0 \uBCC0\uACBD\uC774 \uCEE4\uBC0B\uB418\uC5C8\uB098\uC694?",
|
|
780
|
+
hintCommit: "git status \uD655\uC778",
|
|
781
|
+
changelogUpdated: (version) => `CHANGELOG.md \uAC31\uC2E0\uB428 \u2014 [Unreleased] \u2192 [${version}] \uC139\uC158\uC73C\uB85C \uC774\uB3D9`,
|
|
782
|
+
changelogNoUnreleased: "CHANGELOG.md\uC5D0 [Unreleased] \uC139\uC158\uC774 \uC5C6\uC5B4 \uC790\uB3D9 \uAC31\uC2E0\uC744 \uC2A4\uD0B5\uD588\uC5B4\uC694",
|
|
783
|
+
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."
|
|
784
|
+
},
|
|
785
|
+
design: {
|
|
786
|
+
title: "\uB514\uC790\uC778 \uD1A0\uD070 \uC0DD\uC131",
|
|
787
|
+
selectPalette: "\uCEEC\uB7EC \uD314\uB808\uD2B8\uB97C \uC120\uD0DD\uD558\uC138\uC694:"
|
|
788
|
+
},
|
|
789
|
+
theme: {
|
|
790
|
+
title: "\uD14C\uB9C8 \uC124\uC815 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"
|
|
791
|
+
},
|
|
792
|
+
ref: {
|
|
793
|
+
addTitle: "\uB808\uD37C\uB7F0\uC2A4 \uCD94\uAC00",
|
|
794
|
+
listTitle: "\uB808\uD37C\uB7F0\uC2A4 \uBAA9\uB85D"
|
|
795
|
+
},
|
|
796
|
+
memory: {
|
|
797
|
+
addTitle: "\uAE30\uC5B5 \uCD94\uAC00",
|
|
798
|
+
listTitle: "\uAE30\uC5B5 \uBAA9\uB85D"
|
|
799
|
+
},
|
|
800
|
+
mcp: {
|
|
801
|
+
initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
|
|
802
|
+
serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428"
|
|
803
|
+
},
|
|
804
|
+
deploy: {
|
|
805
|
+
title: "\uBC30\uD3EC\uD558\uAE30",
|
|
806
|
+
selectPlatform: "\uC5B4\uB5A4 \uD50C\uB7AB\uD3FC\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?",
|
|
807
|
+
deploying: "\uBC30\uD3EC \uC911...",
|
|
808
|
+
success: "\uBC30\uD3EC \uC131\uACF5!",
|
|
809
|
+
failed: "\uBC30\uD3EC \uC2E4\uD328"
|
|
810
|
+
},
|
|
811
|
+
env: {
|
|
812
|
+
title: "\uD658\uACBD\uBCC0\uC218 \uAD00\uB9AC",
|
|
813
|
+
checkTitle: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80"
|
|
814
|
+
},
|
|
815
|
+
publish: {
|
|
816
|
+
title: "npm \uBC30\uD3EC",
|
|
817
|
+
selectBump: "\uBC84\uC804\uC744 \uC5B4\uB5BB\uAC8C \uC62C\uB9B4\uAE4C\uC694?",
|
|
818
|
+
building: "\uBE4C\uB4DC \uC911...",
|
|
819
|
+
buildSuccess: "\uBE4C\uB4DC \uC131\uACF5",
|
|
820
|
+
buildFailed: "\uBE4C\uB4DC \uC2E4\uD328",
|
|
821
|
+
testing: "\uD14C\uC2A4\uD2B8 \uC911...",
|
|
822
|
+
testSuccess: "\uD14C\uC2A4\uD2B8 \uD1B5\uACFC",
|
|
823
|
+
testFailed: "\uD14C\uC2A4\uD2B8 \uC2E4\uD328",
|
|
824
|
+
publishing: "npm \uBC30\uD3EC \uC911...",
|
|
825
|
+
publishSuccess: "npm \uBC30\uD3EC \uC131\uACF5!",
|
|
826
|
+
publishFailed: "npm \uBC30\uD3EC \uC2E4\uD328"
|
|
827
|
+
},
|
|
828
|
+
harness: {
|
|
829
|
+
title: "\uD1B5\uD569 \uD488\uC9C8 \uC810\uAC80"
|
|
830
|
+
},
|
|
831
|
+
audit: {
|
|
832
|
+
title: "\uBCF4\uC548 \uAC10\uC0AC"
|
|
833
|
+
},
|
|
834
|
+
migrate: {
|
|
835
|
+
title: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658",
|
|
836
|
+
selectTarget: "\uC5B4\uB5A4 \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800\uB85C \uC804\uD658\uD560\uAE4C\uC694?"
|
|
837
|
+
},
|
|
838
|
+
update: {
|
|
839
|
+
title: "VHK CLI \uC5C5\uB370\uC774\uD2B8"
|
|
840
|
+
},
|
|
841
|
+
context: {
|
|
842
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uCEE8\uD14D\uC2A4\uD2B8 \uC0DD\uC131",
|
|
843
|
+
showTitle: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C"
|
|
844
|
+
},
|
|
845
|
+
brief: {
|
|
846
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551"
|
|
847
|
+
},
|
|
848
|
+
goal: {
|
|
849
|
+
listTitle: "\u{1F3AF} Goal \uBAA9\uB85D",
|
|
850
|
+
nextTitle: "\u27A1\uFE0F \uB2E4\uC74C Goal",
|
|
851
|
+
initTitle: "\u{1F3D7}\uFE0F goals/ \uAD6C\uC870 \uC2A4\uCE90\uD3F4\uB529",
|
|
852
|
+
checkTitle: "\u2705 Goal \uAC8C\uC774\uD2B8 \uAC80\uC99D",
|
|
853
|
+
doneTitle: "\u{1F3C1} Goal \uC644\uB8CC \uCC98\uB9AC"
|
|
854
|
+
},
|
|
855
|
+
agent: {
|
|
856
|
+
blockerTitle: "\u{1F6D1} Blocker \uAE30\uB85D",
|
|
857
|
+
learnTitle: "\u{1F9E0} Learning \uAE30\uB85D",
|
|
858
|
+
resumeTitle: "\u25B6\uFE0F HARD_STOP \uD574\uC81C"
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
function lookup(path3) {
|
|
862
|
+
const parts = path3.split(".");
|
|
863
|
+
let cur = ko;
|
|
864
|
+
for (const part of parts) {
|
|
865
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
866
|
+
cur = cur[part];
|
|
867
|
+
}
|
|
868
|
+
return cur;
|
|
869
|
+
}
|
|
870
|
+
function t(key, ...args) {
|
|
871
|
+
const value = lookup(key);
|
|
872
|
+
if (typeof value === "function") {
|
|
873
|
+
return value(...args);
|
|
874
|
+
}
|
|
875
|
+
if (typeof value === "string") return value;
|
|
876
|
+
return key;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/lib/next-step.ts
|
|
880
|
+
import chalk from "chalk";
|
|
881
|
+
function printNextStep(step) {
|
|
882
|
+
console.log("");
|
|
883
|
+
console.log(chalk.cyan.bold("\u2501\u2501\u2501 \uB2E4\uC74C\uC5D0 \uC774\uAC83\uB9CC \uD558\uC138\uC694 \u2501\u2501\u2501"));
|
|
884
|
+
console.log("");
|
|
885
|
+
console.log(` ${step.message}`);
|
|
886
|
+
if (step.command) {
|
|
887
|
+
console.log("");
|
|
888
|
+
console.log(chalk.white.bgGray(" \uD130\uBBF8\uB110\uC5D0 \uBCF5\uBD99 "));
|
|
889
|
+
console.log(chalk.green(` ${step.command}`));
|
|
890
|
+
}
|
|
891
|
+
if (step.cursorHint) {
|
|
892
|
+
console.log("");
|
|
893
|
+
console.log(chalk.white.bgBlue(" Cursor\uC5D0\uAC8C \uB9D0\uD558\uAE30 "));
|
|
894
|
+
console.log(chalk.blue(` "${step.cursorHint}"`));
|
|
895
|
+
}
|
|
896
|
+
if (step.alternative) {
|
|
897
|
+
console.log("");
|
|
898
|
+
console.log(chalk.dim(` \uB610\uB294: ${step.alternative}`));
|
|
899
|
+
}
|
|
900
|
+
console.log("");
|
|
901
|
+
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"));
|
|
902
|
+
console.log("");
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// src/commands/deploy.ts
|
|
906
|
+
var PLATFORMS = {
|
|
907
|
+
vercel: {
|
|
908
|
+
name: "Vercel",
|
|
909
|
+
detectFiles: ["vercel.json", ".vercel"],
|
|
910
|
+
command: "vercel",
|
|
911
|
+
commandArgs: ["--prod"],
|
|
912
|
+
checkArgs: ["--version"],
|
|
913
|
+
installHint: "npm i -g vercel"
|
|
914
|
+
},
|
|
915
|
+
netlify: {
|
|
916
|
+
name: "Netlify",
|
|
917
|
+
detectFiles: ["netlify.toml", ".netlify"],
|
|
918
|
+
command: "netlify",
|
|
919
|
+
commandArgs: ["deploy", "--prod"],
|
|
920
|
+
checkArgs: ["--version"],
|
|
921
|
+
installHint: "npm i -g netlify-cli"
|
|
922
|
+
},
|
|
923
|
+
cloudflare: {
|
|
924
|
+
name: "Cloudflare Workers",
|
|
925
|
+
detectFiles: ["wrangler.toml"],
|
|
926
|
+
command: "wrangler",
|
|
927
|
+
commandArgs: ["deploy"],
|
|
928
|
+
checkArgs: ["--version"],
|
|
929
|
+
installHint: "npm i -g wrangler"
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
function detectPlatform() {
|
|
933
|
+
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
934
|
+
for (const file of config.detectFiles) {
|
|
935
|
+
if (existsSync(file)) return key;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
function isCLIAvailable(cmd, checkArgs) {
|
|
941
|
+
return safeExecFile(cmd, checkArgs).ok;
|
|
942
|
+
}
|
|
943
|
+
async function deploy() {
|
|
944
|
+
console.log(chalk2.bold("\n\u{1F680} " + t("deploy.title")));
|
|
945
|
+
console.log(chalk2.gray("\u2500".repeat(40)));
|
|
946
|
+
let platform = detectPlatform();
|
|
947
|
+
if (platform) {
|
|
948
|
+
console.log(chalk2.cyan(`
|
|
949
|
+
\u{1F50D} \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${PLATFORMS[platform].name}`));
|
|
950
|
+
} else {
|
|
951
|
+
const { selected } = await inquirer.prompt([
|
|
952
|
+
{
|
|
953
|
+
type: "list",
|
|
954
|
+
name: "selected",
|
|
955
|
+
message: t("deploy.selectPlatform"),
|
|
956
|
+
choices: [
|
|
957
|
+
{ name: "\u25B2 Vercel", value: "vercel" },
|
|
958
|
+
{ name: "\u25C6 Netlify", value: "netlify" },
|
|
959
|
+
{ name: "\u2601 Cloudflare Workers", value: "cloudflare" }
|
|
960
|
+
]
|
|
961
|
+
}
|
|
962
|
+
]);
|
|
963
|
+
platform = selected;
|
|
964
|
+
}
|
|
965
|
+
const config = PLATFORMS[platform];
|
|
966
|
+
if (!isCLIAvailable(config.command, config.checkArgs)) {
|
|
967
|
+
console.log(chalk2.red(`
|
|
968
|
+
\u274C ${config.name} CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
969
|
+
console.log(chalk2.yellow(` \u2192 ${config.installHint}`));
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const { confirm } = await inquirer.prompt([
|
|
973
|
+
{
|
|
974
|
+
type: "confirm",
|
|
975
|
+
name: "confirm",
|
|
976
|
+
message: `${config.name}\uC5D0 \uD504\uB85C\uB355\uC158 \uBC30\uD3EC\uD560\uAE4C\uC694?`,
|
|
977
|
+
default: true
|
|
978
|
+
}
|
|
979
|
+
]);
|
|
980
|
+
if (!confirm) {
|
|
981
|
+
console.log(chalk2.gray("\uCDE8\uC18C\uB428"));
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
console.log(chalk2.cyan(`
|
|
985
|
+
${t("deploy.deploying")}
|
|
986
|
+
`));
|
|
987
|
+
const result = safeExecFileStream(config.command, config.commandArgs);
|
|
988
|
+
if (result.ok) {
|
|
989
|
+
console.log(chalk2.green(`
|
|
990
|
+
\u2705 ${t("deploy.success")}`));
|
|
991
|
+
printNextStep({
|
|
992
|
+
message: "\uBC30\uD3EC \uC644\uB8CC! \uC0AC\uC774\uD2B8\uB97C \uD655\uC778\uD558\uC138\uC694.",
|
|
993
|
+
command: "vhk status",
|
|
994
|
+
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
995
|
+
});
|
|
996
|
+
} else {
|
|
997
|
+
console.log(chalk2.red(`
|
|
998
|
+
\u274C ${t("deploy.failed")}`));
|
|
999
|
+
console.log(chalk2.red(result.err));
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/commands/env.ts
|
|
1004
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, appendFileSync } from "fs";
|
|
1005
|
+
import chalk3 from "chalk";
|
|
1006
|
+
function parseEnvKeys(content) {
|
|
1007
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
|
|
1008
|
+
}
|
|
1009
|
+
function ensureGitignore() {
|
|
1010
|
+
const gitignorePath = ".gitignore";
|
|
1011
|
+
if (existsSync2(gitignorePath)) {
|
|
1012
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
1013
|
+
if (!content.split("\n").some((l) => l.trim() === ".env")) {
|
|
1014
|
+
appendFileSync(gitignorePath, "\n.env\n");
|
|
1015
|
+
console.log(chalk3.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
|
|
1016
|
+
}
|
|
1017
|
+
} else {
|
|
1018
|
+
writeFileSync(gitignorePath, ".env\nnode_modules/\ndist/\n");
|
|
1019
|
+
console.log(chalk3.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async function env() {
|
|
1023
|
+
console.log(chalk3.bold("\n\u{1F510} " + t("env.title")));
|
|
1024
|
+
console.log(chalk3.gray("\u2500".repeat(40)));
|
|
1025
|
+
if (!existsSync2(".env")) {
|
|
1026
|
+
console.log(chalk3.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
1027
|
+
console.log(chalk3.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const envContent = readFileSync(".env", "utf-8");
|
|
1031
|
+
const keys = parseEnvKeys(envContent);
|
|
1032
|
+
if (keys.length === 0) {
|
|
1033
|
+
console.log(chalk3.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
1037
|
+
writeFileSync(".env.example", exampleContent, "utf-8");
|
|
1038
|
+
console.log(chalk3.green(`
|
|
1039
|
+
\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`));
|
|
1040
|
+
keys.forEach((k) => console.log(chalk3.gray(` ${k}`)));
|
|
1041
|
+
ensureGitignore();
|
|
1042
|
+
printNextStep({
|
|
1043
|
+
message: ".env.example \uC0DD\uC131 \uC644\uB8CC!",
|
|
1044
|
+
command: "vhk env-check",
|
|
1045
|
+
cursorHint: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80\uD574\uC918"
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
async function envCheck() {
|
|
1049
|
+
console.log(chalk3.bold("\n\u{1F50D} " + t("env.checkTitle")));
|
|
1050
|
+
console.log(chalk3.gray("\u2500".repeat(40)));
|
|
1051
|
+
if (!existsSync2(".env.example")) {
|
|
1052
|
+
console.log(chalk3.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const requiredKeys = parseEnvKeys(readFileSync(".env.example", "utf-8"));
|
|
1056
|
+
const currentKeys = existsSync2(".env") ? parseEnvKeys(readFileSync(".env", "utf-8")) : [];
|
|
1057
|
+
const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
|
|
1058
|
+
const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
|
|
1059
|
+
console.log(chalk3.cyan(`
|
|
1060
|
+
\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`));
|
|
1061
|
+
if (missing.length === 0) {
|
|
1062
|
+
console.log(chalk3.green("\n\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!"));
|
|
1063
|
+
} else {
|
|
1064
|
+
console.log(chalk3.red(`
|
|
1065
|
+
\u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`));
|
|
1066
|
+
missing.forEach((k) => console.log(chalk3.red(` \u2022 ${k}`)));
|
|
1067
|
+
}
|
|
1068
|
+
if (extra.length > 0) {
|
|
1069
|
+
console.log(chalk3.yellow(`
|
|
1070
|
+
\u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`));
|
|
1071
|
+
extra.forEach((k) => console.log(chalk3.yellow(` \u2022 ${k}`)));
|
|
1072
|
+
}
|
|
1073
|
+
ensureGitignore();
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// src/commands/publish.ts
|
|
1077
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1078
|
+
import chalk4 from "chalk";
|
|
1079
|
+
import inquirer2 from "inquirer";
|
|
1080
|
+
import ora from "ora";
|
|
1081
|
+
|
|
1082
|
+
// src/lib/read-json.ts
|
|
1083
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1084
|
+
function stripBom(text) {
|
|
1085
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1086
|
+
}
|
|
1087
|
+
function readJsonFile(filePath) {
|
|
1088
|
+
const raw = stripBom(readFileSync2(filePath, "utf-8"));
|
|
1089
|
+
return JSON.parse(raw);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// src/commands/publish.ts
|
|
1093
|
+
function bumpVersion(current, type) {
|
|
1094
|
+
const [major, minor, patch] = current.split(".").map((n) => parseInt(n, 10) || 0);
|
|
1095
|
+
switch (type) {
|
|
1096
|
+
case "major":
|
|
1097
|
+
return `${major + 1}.0.0`;
|
|
1098
|
+
case "minor":
|
|
1099
|
+
return `${major}.${minor + 1}.0`;
|
|
1100
|
+
case "patch":
|
|
1101
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
async function publish() {
|
|
1105
|
+
console.log(chalk4.bold("\n\u{1F4E6} " + t("publish.title")));
|
|
1106
|
+
console.log(chalk4.gray("\u2500".repeat(40)));
|
|
1107
|
+
if (!existsSync3("package.json")) {
|
|
1108
|
+
console.log(chalk4.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
let pkg;
|
|
1112
|
+
try {
|
|
1113
|
+
pkg = readJsonFile("package.json");
|
|
1114
|
+
} catch {
|
|
1115
|
+
console.log(chalk4.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const currentVersion = pkg.version || "0.0.0";
|
|
1119
|
+
console.log(chalk4.cyan(`
|
|
1120
|
+
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${currentVersion}`));
|
|
1121
|
+
const { bumpType } = await inquirer2.prompt([
|
|
1122
|
+
{
|
|
1123
|
+
type: "list",
|
|
1124
|
+
name: "bumpType",
|
|
1125
|
+
message: t("publish.selectBump"),
|
|
1126
|
+
choices: [
|
|
1127
|
+
{ name: `\u{1F527} patch (${bumpVersion(currentVersion, "patch")}) \u2014 \uBC84\uADF8 \uC218\uC815`, value: "patch" },
|
|
1128
|
+
{ name: `\u2728 minor (${bumpVersion(currentVersion, "minor")}) \u2014 \uC0C8 \uAE30\uB2A5`, value: "minor" },
|
|
1129
|
+
{ name: `\u{1F4A5} major (${bumpVersion(currentVersion, "major")}) \u2014 \uD638\uD658\uC131 \uBCC0\uACBD`, value: "major" }
|
|
1130
|
+
]
|
|
1131
|
+
}
|
|
1132
|
+
]);
|
|
1133
|
+
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
1134
|
+
console.log(chalk4.cyan(`
|
|
1135
|
+
\u{1F195} \uC0C8 \uBC84\uC804: v${newVersion}`));
|
|
1136
|
+
pkg.version = newVersion;
|
|
1137
|
+
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1138
|
+
console.log(chalk4.green("\u2705 package.json \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8"));
|
|
1139
|
+
const buildSpinner = ora(t("publish.building")).start();
|
|
1140
|
+
const buildResult = safeExecFile("pnpm", ["build"]);
|
|
1141
|
+
if (!buildResult.ok) {
|
|
1142
|
+
buildSpinner.fail(t("publish.buildFailed"));
|
|
1143
|
+
console.log(chalk4.red(buildResult.err.slice(0, 500)));
|
|
1144
|
+
pkg.version = currentVersion;
|
|
1145
|
+
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
buildSpinner.succeed(t("publish.buildSuccess"));
|
|
1149
|
+
const testSpinner = ora(t("publish.testing")).start();
|
|
1150
|
+
const testResult = safeExecFile("pnpm", ["test", "--run"]);
|
|
1151
|
+
if (!testResult.ok) {
|
|
1152
|
+
testSpinner.fail(t("publish.testFailed"));
|
|
1153
|
+
console.log(chalk4.red(testResult.err.slice(0, 500)));
|
|
1154
|
+
pkg.version = currentVersion;
|
|
1155
|
+
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
testSpinner.succeed(t("publish.testSuccess"));
|
|
1159
|
+
const { confirm } = await inquirer2.prompt([
|
|
1160
|
+
{
|
|
1161
|
+
type: "confirm",
|
|
1162
|
+
name: "confirm",
|
|
1163
|
+
message: `v${newVersion}\uC744 npm\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?`,
|
|
1164
|
+
default: true
|
|
1165
|
+
}
|
|
1166
|
+
]);
|
|
1167
|
+
if (!confirm) {
|
|
1168
|
+
pkg.version = currentVersion;
|
|
1169
|
+
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1170
|
+
console.log(chalk4.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
console.log(chalk4.cyan(`
|
|
1174
|
+
\u{1F4E4} ${t("publish.publishing")}`));
|
|
1175
|
+
console.log(chalk4.gray(" 2FA \uD65C\uC131\uD654 \uC2DC: OTP 6\uC790\uB9AC \uC785\uB825 \uB610\uB294 \uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D URL \uD074\uB9AD (Windows Hello / PIN \uC9C0\uC6D0)"));
|
|
1176
|
+
const pubResult = safeExecFileStream("npm", ["publish", "--access", "public"]);
|
|
1177
|
+
if (!pubResult.ok) {
|
|
1178
|
+
console.log(chalk4.red(`
|
|
1179
|
+
\u2716 ${t("publish.publishFailed")}`));
|
|
1180
|
+
console.log(chalk4.red(pubResult.err.slice(0, 500)));
|
|
1181
|
+
pkg.version = currentVersion;
|
|
1182
|
+
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1183
|
+
console.log(chalk4.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
console.log(chalk4.green(`
|
|
1187
|
+
\u2714 ${t("publish.publishSuccess")}`));
|
|
1188
|
+
const addResult = safeExecFile("git", ["add", "package.json"]);
|
|
1189
|
+
if (addResult.ok) {
|
|
1190
|
+
safeExecFile("git", ["commit", "-m", `chore: release v${newVersion}`]);
|
|
1191
|
+
const tagResult = safeExecFile("git", ["tag", `v${newVersion}`]);
|
|
1192
|
+
if (tagResult.ok) {
|
|
1193
|
+
const pushResult = safeExecFile("git", ["push"]);
|
|
1194
|
+
const pushTagsResult = safeExecFile("git", ["push", "--tags"]);
|
|
1195
|
+
if (pushResult.ok && pushTagsResult.ok) {
|
|
1196
|
+
console.log(chalk4.green(`
|
|
1197
|
+
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131 + push \uC644\uB8CC`));
|
|
1198
|
+
} else {
|
|
1199
|
+
console.log(chalk4.yellow(`
|
|
1200
|
+
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131\uB428 (push\uB294 \uC218\uB3D9\uC73C\uB85C)`));
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
console.log(chalk4.green.bold(`
|
|
1205
|
+
\u{1F389} v${newVersion} \uBC30\uD3EC \uC644\uB8CC!`));
|
|
1206
|
+
printNextStep({
|
|
1207
|
+
message: "npm \uBC30\uD3EC \uC644\uB8CC!",
|
|
1208
|
+
command: "vhk status",
|
|
1209
|
+
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/commands/audit.ts
|
|
1214
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1215
|
+
import chalk5 from "chalk";
|
|
1216
|
+
import inquirer3 from "inquirer";
|
|
1217
|
+
import ora2 from "ora";
|
|
1218
|
+
function detectCurrentPM() {
|
|
1219
|
+
if (existsSync4("pnpm-lock.yaml")) return "pnpm";
|
|
1220
|
+
if (existsSync4("yarn.lock")) return "yarn";
|
|
1221
|
+
return "npm";
|
|
1222
|
+
}
|
|
1223
|
+
function parseAuditOutput(output, pm) {
|
|
1224
|
+
const empty = { critical: 0, high: 0, moderate: 0, low: 0, total: 0 };
|
|
1225
|
+
if (!output) return empty;
|
|
1226
|
+
try {
|
|
1227
|
+
const json = JSON.parse(output);
|
|
1228
|
+
const meta = json.metadata?.vulnerabilities;
|
|
1229
|
+
if (meta) {
|
|
1230
|
+
const summary = {
|
|
1231
|
+
critical: meta.critical ?? 0,
|
|
1232
|
+
high: meta.high ?? 0,
|
|
1233
|
+
moderate: meta.moderate ?? 0,
|
|
1234
|
+
low: meta.low ?? 0,
|
|
1235
|
+
total: meta.total ?? 0
|
|
1236
|
+
};
|
|
1237
|
+
if (!summary.total) {
|
|
1238
|
+
summary.total = summary.critical + summary.high + summary.moderate + summary.low;
|
|
1239
|
+
}
|
|
1240
|
+
return summary;
|
|
1241
|
+
}
|
|
1242
|
+
void pm;
|
|
1243
|
+
return empty;
|
|
1244
|
+
} catch {
|
|
1245
|
+
return empty;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function runAuditJson(pm) {
|
|
1249
|
+
const result = safeExecFile(pm, ["audit", "--json"]);
|
|
1250
|
+
return result.out;
|
|
1251
|
+
}
|
|
1252
|
+
function runAuditFix(pm) {
|
|
1253
|
+
if (pm !== "npm") {
|
|
1254
|
+
return { ok: false, err: `${pm}\uC740 \uC790\uB3D9 fix\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. npm \uD658\uACBD\uC5D0\uC11C\uB9CC \uB3D9\uC791\uD569\uB2C8\uB2E4.` };
|
|
1255
|
+
}
|
|
1256
|
+
const result = safeExecFile("npm", ["audit", "fix"]);
|
|
1257
|
+
return result.ok ? { ok: true } : { ok: false, err: result.err };
|
|
1258
|
+
}
|
|
1259
|
+
async function audit(autoFix = false) {
|
|
1260
|
+
console.log(chalk5.bold("\n\u{1F6E1}\uFE0F " + t("audit.title")));
|
|
1261
|
+
console.log(chalk5.gray("\u2500".repeat(40)));
|
|
1262
|
+
const pm = detectCurrentPM();
|
|
1263
|
+
console.log(chalk5.cyan(`\u{1F4E6} \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${pm}`));
|
|
1264
|
+
const spinner = ora2("\uBCF4\uC548 \uAC10\uC0AC \uC2E4\uD589 \uC911...").start();
|
|
1265
|
+
const output = runAuditJson(pm);
|
|
1266
|
+
spinner.stop();
|
|
1267
|
+
const summary = parseAuditOutput(output, pm);
|
|
1268
|
+
if (summary.total === 0) {
|
|
1269
|
+
console.log(chalk5.green.bold("\n\u{1F389} \uCDE8\uC57D\uC810\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4!"));
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
console.log(chalk5.bold("\n\u{1F4CA} \uCDE8\uC57D\uC810 \uC694\uC57D:"));
|
|
1273
|
+
if (summary.critical > 0) console.log(chalk5.red(` \u{1F534} Critical: ${summary.critical}`));
|
|
1274
|
+
if (summary.high > 0) console.log(chalk5.red(` \u{1F7E0} High: ${summary.high}`));
|
|
1275
|
+
if (summary.moderate > 0) console.log(chalk5.yellow(` \u{1F7E1} Moderate: ${summary.moderate}`));
|
|
1276
|
+
if (summary.low > 0) console.log(chalk5.gray(` \u26AA Low: ${summary.low}`));
|
|
1277
|
+
console.log(chalk5.bold(`
|
|
1278
|
+
\uCD1D ${summary.total}\uAC1C\uC758 \uCDE8\uC57D\uC810`));
|
|
1279
|
+
const shouldRunFix = autoFix ? true : summary.critical > 0 || summary.high > 0 ? (await inquirer3.prompt([
|
|
1280
|
+
{
|
|
1281
|
+
type: "confirm",
|
|
1282
|
+
name: "shouldFix",
|
|
1283
|
+
message: "\uC790\uB3D9 \uC218\uC815\uC744 \uC2DC\uB3C4\uD560\uAE4C\uC694? (npm audit fix)",
|
|
1284
|
+
default: true
|
|
1285
|
+
}
|
|
1286
|
+
])).shouldFix : false;
|
|
1287
|
+
if (shouldRunFix) {
|
|
1288
|
+
const fixSpinner = ora2("\uC790\uB3D9 \uC218\uC815 \uC911...").start();
|
|
1289
|
+
const result = runAuditFix(pm);
|
|
1290
|
+
if (result.ok) {
|
|
1291
|
+
fixSpinner.succeed("\uC790\uB3D9 \uC218\uC815 \uC644\uB8CC!");
|
|
1292
|
+
} else {
|
|
1293
|
+
fixSpinner.warn(result.err ?? "\uC77C\uBD80 \uCDE8\uC57D\uC810\uC740 \uC218\uB3D9 \uC218\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
printNextStep({
|
|
1297
|
+
message: "\uBCF4\uC548 \uAC10\uC0AC \uC644\uB8CC.",
|
|
1298
|
+
command: "vhk harness",
|
|
1299
|
+
cursorHint: "\uD488\uC9C8 \uC810\uAC80\uD574\uC918"
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// src/mcp/server.ts
|
|
1304
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1305
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1306
|
+
import { z } from "zod";
|
|
1307
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
|
|
1308
|
+
|
|
1309
|
+
// src/lib/scan-secrets.ts
|
|
1310
|
+
import fs3 from "fs";
|
|
1311
|
+
|
|
1312
|
+
// src/lib/secret-patterns.ts
|
|
1313
|
+
var SECRET_PATTERNS = [
|
|
1314
|
+
{
|
|
1315
|
+
id: "aws-access-key",
|
|
1316
|
+
name: "AWS Access Key",
|
|
1317
|
+
severity: "critical",
|
|
1318
|
+
pattern: /AKIA[0-9A-Z]{16}/
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
id: "aws-secret-key",
|
|
1322
|
+
name: "AWS Secret Key",
|
|
1323
|
+
severity: "critical",
|
|
1324
|
+
pattern: /aws_secret_access_key\s*=\s*['"]?[A-Za-z0-9/+=]{40}['"]?/i
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
id: "private-key",
|
|
1328
|
+
name: "Private Key",
|
|
1329
|
+
severity: "critical",
|
|
1330
|
+
pattern: /-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
id: "notion-token",
|
|
1334
|
+
name: "Notion Integration Token",
|
|
1335
|
+
severity: "critical",
|
|
1336
|
+
pattern: /secret_[A-Za-z0-9]{40,50}/
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
id: "github-token",
|
|
1340
|
+
name: "GitHub Token",
|
|
1341
|
+
severity: "critical",
|
|
1342
|
+
pattern: /ghp_[A-Za-z0-9]{36,}/
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
id: "openai-key",
|
|
1346
|
+
name: "OpenAI API Key",
|
|
1347
|
+
severity: "critical",
|
|
1348
|
+
pattern: /\bsk-(?:proj-|ant-api03-|live-)[A-Za-z0-9_-]{16,}\b/
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
id: "generic-api-key",
|
|
1352
|
+
name: "Generic API Key",
|
|
1353
|
+
severity: "high",
|
|
1354
|
+
pattern: /(?:api[_-]?key|apikey|access[_-]?token)\s*[:=]\s*['"]?[A-Za-z0-9_\-]{16,}['"]?/i
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
id: "password-inline",
|
|
1358
|
+
name: "Inline Password",
|
|
1359
|
+
severity: "high",
|
|
1360
|
+
pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]/i
|
|
1361
|
+
},
|
|
1362
|
+
{
|
|
1363
|
+
id: "jwt",
|
|
1364
|
+
name: "JWT Token",
|
|
1365
|
+
severity: "medium",
|
|
1366
|
+
pattern: /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/
|
|
1367
|
+
}
|
|
1368
|
+
];
|
|
1369
|
+
function maskSecret(value) {
|
|
1370
|
+
if (value.length <= 8) return "****";
|
|
1371
|
+
const visible = Math.min(8, value.length - 4);
|
|
1372
|
+
return value.slice(0, visible) + "****";
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// src/lib/scan-files.ts
|
|
1376
|
+
import fs2 from "fs";
|
|
1377
|
+
import path2 from "path";
|
|
1378
|
+
|
|
1379
|
+
// src/lib/check-secure.ts
|
|
1380
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
1381
|
+
import fs from "fs";
|
|
1382
|
+
import path from "path";
|
|
1383
|
+
import chalk6 from "chalk";
|
|
1384
|
+
function loadGitignore(rootDir) {
|
|
1385
|
+
const ig = (0, import_ignore.default)();
|
|
1386
|
+
const gitignorePath = path.join(rootDir, ".gitignore");
|
|
1387
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1388
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
1389
|
+
ig.add(content);
|
|
1390
|
+
}
|
|
1391
|
+
return ig;
|
|
1392
|
+
}
|
|
1393
|
+
function isPathIgnored(ig, relativePath) {
|
|
1394
|
+
const normalized = relativePath.replace(/\\/g, "/");
|
|
1395
|
+
return ig.ignores(normalized);
|
|
1396
|
+
}
|
|
1397
|
+
function findExposedSensitiveFiles(rootDir, ig = loadGitignore(rootDir), maxDepth = 8) {
|
|
1398
|
+
const exposed = [];
|
|
1399
|
+
function walk(dir, depth) {
|
|
1400
|
+
if (depth > maxDepth) return;
|
|
1401
|
+
let entries;
|
|
1402
|
+
try {
|
|
1403
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1404
|
+
} catch {
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
for (const entry of entries) {
|
|
1408
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
1409
|
+
const fullPath = path.join(dir, entry.name);
|
|
1410
|
+
const rel = path.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
1411
|
+
if (entry.isDirectory()) {
|
|
1412
|
+
if (!isPathIgnored(ig, rel + "/")) walk(fullPath, depth + 1);
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
if (isSensitiveName(entry.name) && !isPathIgnored(ig, rel)) {
|
|
1416
|
+
exposed.push(rel);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
walk(rootDir, 0);
|
|
1421
|
+
return exposed;
|
|
1422
|
+
}
|
|
1423
|
+
function isSensitiveName(name) {
|
|
1424
|
+
const lower = name.toLowerCase();
|
|
1425
|
+
if (lower === ".env" || lower.startsWith(".env.")) return true;
|
|
1426
|
+
if (lower.endsWith(".pem") || lower.endsWith(".key")) return true;
|
|
1427
|
+
if (lower === "credentials.json" || lower === "secrets.json") return true;
|
|
1428
|
+
if (lower.startsWith("id_rsa")) return true;
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1431
|
+
function checkProjectSecurity(rootDir = process.cwd()) {
|
|
1432
|
+
const gitignorePath = path.join(rootDir, ".gitignore");
|
|
1433
|
+
const missingGitignore = !fs.existsSync(gitignorePath);
|
|
1434
|
+
const ig = loadGitignore(rootDir);
|
|
1435
|
+
const exposedPaths = findExposedSensitiveFiles(rootDir, ig);
|
|
1436
|
+
const warnings = [];
|
|
1437
|
+
if (missingGitignore) {
|
|
1438
|
+
warnings.push(".gitignore \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBBFC\uAC10\uD55C \uD30C\uC77C\uC774 \uC2E4\uC218\uB85C \uC62C\uB77C\uAC08 \uC218 \uC788\uC5B4\uC694.");
|
|
1439
|
+
}
|
|
1440
|
+
if (exposedPaths.length > 0) {
|
|
1441
|
+
warnings.push(
|
|
1442
|
+
`ignore\uB418\uC9C0 \uC54A\uC740 \uBBFC\uAC10 \uD30C\uC77C ${exposedPaths.length}\uAC1C: ${exposedPaths.join(", ")}`
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
return {
|
|
1446
|
+
ok: !missingGitignore && exposedPaths.length === 0,
|
|
1447
|
+
missingGitignore,
|
|
1448
|
+
exposedPaths,
|
|
1449
|
+
warnings
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
function printSecurityWarnings(rootDir = process.cwd()) {
|
|
1453
|
+
const result = checkProjectSecurity(rootDir);
|
|
1454
|
+
if (result.ok) return true;
|
|
1455
|
+
for (const w of result.warnings) {
|
|
1456
|
+
console.log(chalk6.yellow(` \u26A0\uFE0F ${w}`));
|
|
1457
|
+
}
|
|
1458
|
+
return false;
|
|
1459
|
+
}
|
|
1460
|
+
function filterTrackedPaths(paths, rootDir = process.cwd()) {
|
|
1461
|
+
const ig = loadGitignore(rootDir);
|
|
1462
|
+
return paths.filter((p) => !isPathIgnored(ig, p.replace(/\\/g, "/")));
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// src/lib/scan-files.ts
|
|
1466
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
1467
|
+
"node_modules",
|
|
1468
|
+
".git",
|
|
1469
|
+
"dist",
|
|
1470
|
+
".next",
|
|
1471
|
+
".nuxt",
|
|
1472
|
+
"build",
|
|
1473
|
+
"coverage",
|
|
1474
|
+
"out",
|
|
1475
|
+
".turbo",
|
|
1476
|
+
".vercel",
|
|
1477
|
+
".cache",
|
|
1478
|
+
".pnpm",
|
|
1479
|
+
".idea",
|
|
1480
|
+
".claude",
|
|
1481
|
+
".cursor"
|
|
1482
|
+
]);
|
|
1483
|
+
var SKIP_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
1484
|
+
"pnpm-lock.yaml",
|
|
1485
|
+
"package-lock.json",
|
|
1486
|
+
"yarn.lock"
|
|
1487
|
+
]);
|
|
1488
|
+
var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1489
|
+
".ts",
|
|
1490
|
+
".tsx",
|
|
1491
|
+
".js",
|
|
1492
|
+
".jsx",
|
|
1493
|
+
".mjs",
|
|
1494
|
+
".cjs",
|
|
1495
|
+
".json",
|
|
1496
|
+
".yaml",
|
|
1497
|
+
".yml",
|
|
1498
|
+
".toml"
|
|
1499
|
+
]);
|
|
1500
|
+
var MAX_SCAN_FILE_BYTES = 512 * 1024;
|
|
1501
|
+
function isScannableFileName(fileName) {
|
|
1502
|
+
if (SKIP_FILE_NAMES.has(fileName)) return false;
|
|
1503
|
+
if (fileName.startsWith(".env")) return true;
|
|
1504
|
+
return SCAN_EXTENSIONS.has(path2.extname(fileName).toLowerCase());
|
|
1505
|
+
}
|
|
1506
|
+
function walkProjectFiles(rootDir, onFile, ig = loadGitignore(rootDir)) {
|
|
1507
|
+
function walk(dir) {
|
|
1508
|
+
let entries;
|
|
1509
|
+
try {
|
|
1510
|
+
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
1511
|
+
} catch {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
for (const entry of entries) {
|
|
1515
|
+
const fullPath = path2.join(dir, entry.name);
|
|
1516
|
+
const rel = path2.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
1517
|
+
if (entry.isDirectory()) {
|
|
1518
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
1519
|
+
if (isPathIgnored(ig, `${rel}/`)) continue;
|
|
1520
|
+
walk(fullPath);
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
if (!isScannableFileName(entry.name)) continue;
|
|
1524
|
+
if (isPathIgnored(ig, rel)) continue;
|
|
1525
|
+
let size = 0;
|
|
1526
|
+
try {
|
|
1527
|
+
size = fs2.statSync(fullPath).size;
|
|
1528
|
+
} catch {
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
if (size > MAX_SCAN_FILE_BYTES) continue;
|
|
1532
|
+
onFile(fullPath, rel);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
walk(rootDir);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// src/lib/scan-secrets.ts
|
|
1539
|
+
var MAX_SECRET_FINDINGS = 200;
|
|
1540
|
+
var MAX_LINE_CHARS = 4e3;
|
|
1541
|
+
function globalPattern(pattern) {
|
|
1542
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
1543
|
+
return new RegExp(pattern.source, flags);
|
|
1544
|
+
}
|
|
1545
|
+
function findSecretsInLine(line, relPath, lineNum) {
|
|
1546
|
+
const found = [];
|
|
1547
|
+
const trimmed = line.trim();
|
|
1548
|
+
if (trimmed.startsWith("//") && trimmed.includes("example")) return found;
|
|
1549
|
+
if (trimmed.startsWith("#") && trimmed.includes("example")) return found;
|
|
1550
|
+
if (line.length > MAX_LINE_CHARS) return found;
|
|
1551
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
1552
|
+
const regex = globalPattern(pattern.pattern);
|
|
1553
|
+
for (const match of line.matchAll(regex)) {
|
|
1554
|
+
found.push({
|
|
1555
|
+
patternId: pattern.id,
|
|
1556
|
+
patternName: pattern.name,
|
|
1557
|
+
severity: pattern.severity,
|
|
1558
|
+
file: relPath,
|
|
1559
|
+
line: lineNum,
|
|
1560
|
+
match: maskSecret(match[0])
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return found;
|
|
1565
|
+
}
|
|
1566
|
+
function scanProjectForSecrets(cwd) {
|
|
1567
|
+
const findings = [];
|
|
1568
|
+
let scannedFiles = 0;
|
|
1569
|
+
let truncated = false;
|
|
1570
|
+
walkProjectFiles(cwd, (filePath, relPath) => {
|
|
1571
|
+
scannedFiles++;
|
|
1572
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
1573
|
+
const lines = content.split("\n");
|
|
1574
|
+
lines.forEach((line, idx) => {
|
|
1575
|
+
if (truncated) return;
|
|
1576
|
+
const lineFindings = findSecretsInLine(line, relPath, idx + 1);
|
|
1577
|
+
for (const f of lineFindings) {
|
|
1578
|
+
findings.push(f);
|
|
1579
|
+
if (findings.length >= MAX_SECRET_FINDINGS) {
|
|
1580
|
+
truncated = true;
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
return { findings, scannedFiles, truncated };
|
|
1587
|
+
}
|
|
1588
|
+
function filterSevereFindings(findings) {
|
|
1589
|
+
return findings.filter((f) => f.severity === "critical" || f.severity === "high");
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// src/mcp/server.ts
|
|
1593
|
+
var SERVER_VERSION = "1.3.0";
|
|
1594
|
+
function isGitRepo() {
|
|
1595
|
+
return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
|
|
1596
|
+
}
|
|
1597
|
+
var ANSI_RE = /\x1B\[[0-9;?]*[ -/]*[@-~]/g;
|
|
1598
|
+
function stripAnsi(s) {
|
|
1599
|
+
return s.replace(ANSI_RE, "");
|
|
1600
|
+
}
|
|
1601
|
+
function runVhkCli(args, headline) {
|
|
1602
|
+
const result = safeExecFile("vhk", args, { env: { FORCE_COLOR: "0", NO_COLOR: "1" } });
|
|
1603
|
+
const body = stripAnsi(result.out || (result.ok ? "" : `(stdout \uC5C6\uC74C)
|
|
1604
|
+
${result.err}`));
|
|
1605
|
+
const prefix = result.ok ? `\u2705 ${headline}` : `\u274C ${headline} \uC2E4\uD328`;
|
|
1606
|
+
return { content: [{ type: "text", text: `${prefix}
|
|
1607
|
+
${body}`.trim() }] };
|
|
1608
|
+
}
|
|
1609
|
+
function createVhkMcpServer() {
|
|
1610
|
+
const server = new McpServer({
|
|
1611
|
+
name: "vhk",
|
|
1612
|
+
version: SERVER_VERSION
|
|
1613
|
+
});
|
|
1614
|
+
server.registerTool(
|
|
1615
|
+
"save",
|
|
1616
|
+
{
|
|
1617
|
+
description: "\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)",
|
|
1618
|
+
inputSchema: {
|
|
1619
|
+
message: z.string().optional().describe("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (\uBE44\uC6B0\uBA74 \uC790\uB3D9 \uC0DD\uC131)")
|
|
1620
|
+
}
|
|
1621
|
+
},
|
|
1622
|
+
async ({ message }) => {
|
|
1623
|
+
if (!isGitRepo()) {
|
|
1624
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
1625
|
+
}
|
|
1626
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
1627
|
+
if (!status.ok) {
|
|
1628
|
+
return { content: [{ type: "text", text: `\u274C git status \uC2E4\uD328: ${status.err}` }] };
|
|
1629
|
+
}
|
|
1630
|
+
if (!status.out) {
|
|
1631
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
1632
|
+
}
|
|
1633
|
+
const files = status.out.split("\n");
|
|
1634
|
+
const severe = filterSevereFindings(scanProjectForSecrets(process.cwd()).findings);
|
|
1635
|
+
if (severe.length > 0) {
|
|
1636
|
+
const preview = severe.slice(0, 5).map((f) => ` ${f.file}:${f.line} \u2014 ${f.patternName}`).join("\n");
|
|
1637
|
+
const more = severe.length > 5 ? `
|
|
1638
|
+
... \uC678 ${severe.length - 5}\uAC74` : "";
|
|
1639
|
+
return {
|
|
1640
|
+
content: [
|
|
1641
|
+
{
|
|
1642
|
+
type: "text",
|
|
1643
|
+
text: `\u{1F6D1} \uC2DC\uD06C\uB9BF \uC758\uC2EC ${severe.length}\uAC74 \uBC1C\uACAC \u2014 MCP \uBAA8\uB4DC\uC5D0\uC11C\uB294 commit \uAC70\uBD80.
|
|
1644
|
+
${preview}${more}
|
|
1645
|
+
|
|
1646
|
+
\uD574\uACB0: \uC2DC\uD06C\uB9BF\uC744 \uC81C\uAC70\uD558\uAC70\uB098 .gitignore \uCC98\uB9AC \uD6C4, \uC758\uC2DD\uC801\uC73C\uB85C \uC9C4\uD589\uD558\uB824\uBA74 \uD130\uBBF8\uB110\uC5D0\uC11C \`vhk save\` (CLI \uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uD1B5\uACFC \uD6C4 \uC9C4\uD589 \uAC00\uB2A5).`
|
|
1647
|
+
}
|
|
1648
|
+
]
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
const now = /* @__PURE__ */ new Date();
|
|
1652
|
+
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")}`;
|
|
1653
|
+
const commitMsg = message?.trim() || `\u2728 vhk save: ${ts}`;
|
|
1654
|
+
const add = safeExecFile("git", ["add", "."]);
|
|
1655
|
+
if (!add.ok) {
|
|
1656
|
+
return { content: [{ type: "text", text: `\u274C git add \uC2E4\uD328: ${add.err}` }] };
|
|
1657
|
+
}
|
|
1658
|
+
const commit = safeExecFile("git", ["commit", "-m", commitMsg]);
|
|
1659
|
+
if (!commit.ok) {
|
|
1660
|
+
return { content: [{ type: "text", text: `\u274C commit \uC2E4\uD328: ${commit.err}` }] };
|
|
1661
|
+
}
|
|
1662
|
+
const push = safeExecFile("git", ["push"]);
|
|
1663
|
+
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)";
|
|
1664
|
+
return {
|
|
1665
|
+
content: [
|
|
1666
|
+
{
|
|
1667
|
+
type: "text",
|
|
1668
|
+
text: `\u2705 ${files.length}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC! ${pushResult}
|
|
1669
|
+
\uCEE4\uBC0B: ${commitMsg}`
|
|
1670
|
+
}
|
|
1671
|
+
]
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
);
|
|
1675
|
+
server.registerTool("undo", { description: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (soft reset, \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC720\uC9C0)" }, async () => {
|
|
1676
|
+
if (!isGitRepo()) {
|
|
1677
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
1678
|
+
}
|
|
1679
|
+
const last = safeExecFile("git", ["log", "--oneline", "-1"]);
|
|
1680
|
+
if (!last.ok || !last.out) {
|
|
1681
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
1682
|
+
}
|
|
1683
|
+
const reset = safeExecFile("git", ["reset", "--soft", "HEAD~1"]);
|
|
1684
|
+
if (!reset.ok) {
|
|
1685
|
+
return { content: [{ type: "text", text: `\u274C reset \uC2E4\uD328: ${reset.err}` }] };
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
content: [
|
|
1689
|
+
{
|
|
1690
|
+
type: "text",
|
|
1691
|
+
text: `\u2705 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC!
|
|
1692
|
+
\uCDE8\uC18C\uB41C \uCEE4\uBC0B: ${last.out}
|
|
1693
|
+
\u{1F4A1} \uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.`
|
|
1694
|
+
}
|
|
1695
|
+
]
|
|
1696
|
+
};
|
|
1697
|
+
});
|
|
1698
|
+
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 () => {
|
|
1699
|
+
const lines = [];
|
|
1700
|
+
if (existsSync5("package.json")) {
|
|
1701
|
+
try {
|
|
1702
|
+
const pkg = readJsonFile("package.json");
|
|
1703
|
+
lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
|
|
1704
|
+
} catch {
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
if (!isGitRepo()) {
|
|
1708
|
+
lines.push("\u26A0\uFE0F git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
|
|
1709
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1710
|
+
}
|
|
1711
|
+
const branch = safeExecFile("git", ["branch", "--show-current"]);
|
|
1712
|
+
if (branch.ok) lines.push(`\u{1F33F} \uBE0C\uB79C\uCE58: ${branch.out || "(detached)"}`);
|
|
1713
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
1714
|
+
if (status.ok) {
|
|
1715
|
+
if (!status.out) {
|
|
1716
|
+
lines.push("\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: \u2705 \uAE68\uB057\uD568");
|
|
1717
|
+
} else {
|
|
1718
|
+
const fileLines = status.out.split("\n");
|
|
1719
|
+
const staged = fileLines.filter((l) => l[0] !== " " && l[0] !== "?").length;
|
|
1720
|
+
const unstaged = fileLines.filter((l) => l[1] === "M" || l[1] === "D").length;
|
|
1721
|
+
const untracked = fileLines.filter((l) => l.startsWith("??")).length;
|
|
1722
|
+
const parts = [];
|
|
1723
|
+
if (staged) parts.push(`\uC2A4\uD14C\uC774\uC9D5 ${staged}\uAC1C`);
|
|
1724
|
+
if (unstaged) parts.push(`\uC218\uC815 ${unstaged}\uAC1C`);
|
|
1725
|
+
if (untracked) parts.push(`\uC0C8\uD30C\uC77C ${untracked}\uAC1C`);
|
|
1726
|
+
lines.push(`\u{1F4DD} \uBCC0\uACBD\uC0AC\uD56D: ${parts.join(", ")}`);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
const log = safeExecFile("git", ["log", "--oneline", "-3"]);
|
|
1730
|
+
if (log.ok && log.out) {
|
|
1731
|
+
lines.push("\u{1F4DC} \uCD5C\uADFC \uCEE4\uBC0B:");
|
|
1732
|
+
log.out.split("\n").forEach((l) => lines.push(` ${l}`));
|
|
1733
|
+
}
|
|
1734
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1735
|
+
});
|
|
1736
|
+
server.registerTool("diff", { description: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778 (staged/unstaged/\uC0C8\uD30C\uC77C + \uCD1D \uBCC0\uACBD \uC694\uC57D)" }, async () => {
|
|
1737
|
+
if (!isGitRepo()) {
|
|
1738
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
1739
|
+
}
|
|
1740
|
+
const unstaged = safeExecFile("git", ["diff", "--stat"]);
|
|
1741
|
+
const staged = safeExecFile("git", ["diff", "--cached", "--stat"]);
|
|
1742
|
+
const untracked = safeExecFile("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
1743
|
+
const unstagedOut = unstaged.ok ? unstaged.out : "";
|
|
1744
|
+
const stagedOut = staged.ok ? staged.out : "";
|
|
1745
|
+
const untrackedOut = untracked.ok ? untracked.out : "";
|
|
1746
|
+
if (!unstagedOut && !stagedOut && !untrackedOut) {
|
|
1747
|
+
return { content: [{ type: "text", text: "\u2705 \uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4." }] };
|
|
1748
|
+
}
|
|
1749
|
+
const lines = [];
|
|
1750
|
+
if (stagedOut) {
|
|
1751
|
+
lines.push("\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):");
|
|
1752
|
+
lines.push(stagedOut);
|
|
1753
|
+
}
|
|
1754
|
+
if (unstagedOut) {
|
|
1755
|
+
lines.push("\u270F\uFE0F \uC218\uC815\uB428 (unstaged):");
|
|
1756
|
+
lines.push(unstagedOut);
|
|
1757
|
+
}
|
|
1758
|
+
if (untrackedOut) {
|
|
1759
|
+
const files = untrackedOut.split("\n");
|
|
1760
|
+
lines.push(`\u2795 \uC0C8 \uD30C\uC77C (${files.length}\uAC1C):`);
|
|
1761
|
+
files.forEach((f) => lines.push(` + ${f}`));
|
|
1762
|
+
}
|
|
1763
|
+
const numstat = safeExecFile("git", ["diff", "--numstat", "HEAD"]);
|
|
1764
|
+
if (numstat.ok && numstat.out) {
|
|
1765
|
+
let totalAdd = 0;
|
|
1766
|
+
let totalDel = 0;
|
|
1767
|
+
let fileCount = 0;
|
|
1768
|
+
numstat.out.split("\n").forEach((line) => {
|
|
1769
|
+
const [add, del] = line.split(" ");
|
|
1770
|
+
totalAdd += parseInt(add, 10) || 0;
|
|
1771
|
+
totalDel += parseInt(del, 10) || 0;
|
|
1772
|
+
fileCount += 1;
|
|
1773
|
+
});
|
|
1774
|
+
lines.push(`
|
|
1775
|
+
\u{1F4CA} \uCD1D \uBCC0\uACBD: ${fileCount}\uAC1C \uD30C\uC77C, +${totalAdd}\uC904 -${totalDel}\uC904`);
|
|
1776
|
+
}
|
|
1777
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1778
|
+
});
|
|
1779
|
+
server.registerTool("ship", { description: "\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC2E4\uD589 (\uBE4C\uB4DC + \uD14C\uC2A4\uD2B8 + \uBC84\uC804 + git \uC0C1\uD0DC)" }, async () => {
|
|
1780
|
+
const checks = [];
|
|
1781
|
+
const build = safeExecFile("pnpm", ["build"]);
|
|
1782
|
+
checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
|
|
1783
|
+
const test = safeExecFile("pnpm", ["test", "--run"]);
|
|
1784
|
+
checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
|
|
1785
|
+
if (existsSync5("package.json")) {
|
|
1786
|
+
try {
|
|
1787
|
+
const pkg = readJsonFile("package.json");
|
|
1788
|
+
checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
|
|
1789
|
+
} catch {
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
if (isGitRepo()) {
|
|
1793
|
+
const status = safeExecFile("git", ["status", "--porcelain"]);
|
|
1794
|
+
if (status.ok) {
|
|
1795
|
+
if (status.out) {
|
|
1796
|
+
checks.push(`\u26A0\uFE0F \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD\uC0AC\uD56D ${status.out.split("\n").length}\uAC1C`);
|
|
1797
|
+
} else {
|
|
1798
|
+
checks.push("\u2705 \uC6CC\uD0B9 \uB514\uB809\uD1A0\uB9AC \uAE68\uB057\uD568");
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
return { content: [{ type: "text", text: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\n" + checks.join("\n") }] };
|
|
1803
|
+
});
|
|
1804
|
+
server.registerTool("doctor", { description: "\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 (Node/Git/npm/pnpm/TypeScript)" }, async () => {
|
|
1805
|
+
const checks = [];
|
|
1806
|
+
const node = safeExecFile("node", ["--version"]);
|
|
1807
|
+
checks.push(node.ok ? `\u2705 Node.js: ${node.out}` : "\u274C Node.js: \uC124\uCE58 \uC548 \uB428");
|
|
1808
|
+
const git = safeExecFile("git", ["--version"]);
|
|
1809
|
+
checks.push(git.ok ? `\u2705 Git: ${git.out}` : "\u274C Git: \uC124\uCE58 \uC548 \uB428");
|
|
1810
|
+
const pnpm = safeExecFile("pnpm", ["--version"]);
|
|
1811
|
+
checks.push(pnpm.ok ? `\u2705 pnpm: v${pnpm.out}` : "\u26A0\uFE0F pnpm: \uC124\uCE58 \uC548 \uB428");
|
|
1812
|
+
const npm = safeExecFile("npm", ["--version"]);
|
|
1813
|
+
checks.push(npm.ok ? `\u2705 npm: v${npm.out}` : "\u274C npm: \uC124\uCE58 \uC548 \uB428");
|
|
1814
|
+
const tsc = safeExecFile("npx", ["tsc", "--version"]);
|
|
1815
|
+
checks.push(tsc.ok ? `\u2705 TypeScript: ${tsc.out}` : "\u26A0\uFE0F TypeScript: \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5C6\uC74C");
|
|
1816
|
+
return { content: [{ type: "text", text: "\u{1FA7A} \uD658\uACBD \uC810\uAC80 \uACB0\uACFC\n" + checks.join("\n") }] };
|
|
1817
|
+
});
|
|
1818
|
+
server.registerTool("check", { description: "\uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870 \uC810\uAC80 (\uD544\uC218 \uD30C\uC77C + VHK \uD558\uB124\uC2A4 \uD30C\uC77C)" }, async () => {
|
|
1819
|
+
const required = ["package.json", "tsconfig.json", "README.md", ".gitignore"];
|
|
1820
|
+
const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
|
|
1821
|
+
const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
|
|
1822
|
+
required.forEach((f) => {
|
|
1823
|
+
lines.push(` ${existsSync5(f) ? "\u2705" : "\u274C"} ${f}`);
|
|
1824
|
+
});
|
|
1825
|
+
lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
|
|
1826
|
+
recommended.forEach((f) => {
|
|
1827
|
+
lines.push(` ${existsSync5(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
|
|
1828
|
+
});
|
|
1829
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1830
|
+
});
|
|
1831
|
+
server.registerTool(
|
|
1832
|
+
"recap",
|
|
1833
|
+
{
|
|
1834
|
+
description: "\uCD5C\uADFC \uC791\uC5C5 \uC694\uC57D (\uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC \uAE30\uBC18, \uB0A0\uC9DC \uD3EC\uD568)",
|
|
1835
|
+
inputSchema: {
|
|
1836
|
+
count: z.number().optional().describe("\uD45C\uC2DC\uD560 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 10)")
|
|
1837
|
+
}
|
|
1838
|
+
},
|
|
1839
|
+
async ({ count }) => {
|
|
1840
|
+
if (!isGitRepo()) {
|
|
1841
|
+
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
1842
|
+
}
|
|
1843
|
+
const n = count && count > 0 ? Math.floor(count) : 10;
|
|
1844
|
+
const log = safeExecFile("git", ["log", "--format=%h %ad %s", "--date=short", `-${n}`]);
|
|
1845
|
+
if (!log.ok) {
|
|
1846
|
+
return { content: [{ type: "text", text: `\u274C git log \uC2E4\uD328: ${log.err}` }] };
|
|
1847
|
+
}
|
|
1848
|
+
if (!log.out) {
|
|
1849
|
+
return { content: [{ type: "text", text: "\u{1F4ED} \uCEE4\uBC0B \uD788\uC2A4\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
1850
|
+
}
|
|
1851
|
+
return { content: [{ type: "text", text: `\u{1F4CB} \uCD5C\uADFC \uC791\uC5C5 (${n}\uAC1C):
|
|
1852
|
+
${log.out}` }] };
|
|
1853
|
+
}
|
|
1854
|
+
);
|
|
1855
|
+
server.registerTool(
|
|
1856
|
+
"env",
|
|
1857
|
+
{
|
|
1858
|
+
description: ".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore\uC5D0 .env \uC790\uB3D9 \uCD94\uAC00"
|
|
1859
|
+
},
|
|
1860
|
+
async () => {
|
|
1861
|
+
if (!existsSync5(".env")) {
|
|
1862
|
+
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." }] };
|
|
1863
|
+
}
|
|
1864
|
+
const keys = parseEnvKeys(readFileSync4(".env", "utf-8"));
|
|
1865
|
+
if (keys.length === 0) {
|
|
1866
|
+
return { content: [{ type: "text", text: "\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
1867
|
+
}
|
|
1868
|
+
const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
1869
|
+
writeFileSync3(".env.example", exampleContent, "utf-8");
|
|
1870
|
+
const gitignoreLines = [];
|
|
1871
|
+
if (existsSync5(".gitignore")) {
|
|
1872
|
+
const content = readFileSync4(".gitignore", "utf-8");
|
|
1873
|
+
if (!content.split("\n").some((l) => l.trim() === ".env")) {
|
|
1874
|
+
appendFileSync2(".gitignore", "\n.env\n");
|
|
1875
|
+
gitignoreLines.push("\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428");
|
|
1876
|
+
}
|
|
1877
|
+
} else {
|
|
1878
|
+
writeFileSync3(".gitignore", ".env\nnode_modules/\ndist/\n");
|
|
1879
|
+
gitignoreLines.push("\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)");
|
|
1880
|
+
}
|
|
1881
|
+
const lines = [`\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`, ...keys.map((k) => ` ${k}`)];
|
|
1882
|
+
if (gitignoreLines.length) lines.push("", ...gitignoreLines);
|
|
1883
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1884
|
+
}
|
|
1885
|
+
);
|
|
1886
|
+
server.registerTool(
|
|
1887
|
+
"env-check",
|
|
1888
|
+
{
|
|
1889
|
+
description: "\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (.env.example \uAE30\uC900)"
|
|
1890
|
+
},
|
|
1891
|
+
async () => {
|
|
1892
|
+
if (!existsSync5(".env.example")) {
|
|
1893
|
+
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." }] };
|
|
1894
|
+
}
|
|
1895
|
+
const requiredKeys = parseEnvKeys(readFileSync4(".env.example", "utf-8"));
|
|
1896
|
+
const currentKeys = existsSync5(".env") ? parseEnvKeys(readFileSync4(".env", "utf-8")) : [];
|
|
1897
|
+
const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
|
|
1898
|
+
const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
|
|
1899
|
+
const lines = [`\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`];
|
|
1900
|
+
if (missing.length === 0) {
|
|
1901
|
+
lines.push("\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!");
|
|
1902
|
+
} else {
|
|
1903
|
+
lines.push(`\u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`);
|
|
1904
|
+
missing.forEach((k) => lines.push(` \u2022 ${k}`));
|
|
1905
|
+
}
|
|
1906
|
+
if (extra.length > 0) {
|
|
1907
|
+
lines.push(`\u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`);
|
|
1908
|
+
extra.forEach((k) => lines.push(` \u2022 ${k}`));
|
|
1909
|
+
}
|
|
1910
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1911
|
+
}
|
|
1912
|
+
);
|
|
1913
|
+
server.registerTool(
|
|
1914
|
+
"sync",
|
|
1915
|
+
{ description: "RULES.md \u2192 .cursorrules + CLAUDE.md \uC790\uB3D9 \uB3D9\uAE30\uD654" },
|
|
1916
|
+
async () => runVhkCli(["sync"], "sync")
|
|
1917
|
+
);
|
|
1918
|
+
server.registerTool(
|
|
1919
|
+
"secure",
|
|
1920
|
+
{ description: "\uC2DC\uD06C\uB9BF/\uD658\uACBD\uBCC0\uC218 \uBCF4\uC548 \uC2A4\uCE94 (.env \uB178\uCD9C + \uD0A4 \uD328\uD134 \uD0D0\uC9C0)" },
|
|
1921
|
+
async () => runVhkCli(["secure"], "secure")
|
|
1922
|
+
);
|
|
1923
|
+
server.registerTool(
|
|
1924
|
+
"audit",
|
|
1925
|
+
{
|
|
1926
|
+
description: "npm/pnpm/yarn \uBCF4\uC548 \uCDE8\uC57D\uC810 \uAC10\uC0AC \u2014 \uC694\uC57D\uB9CC (MCP \uBAA8\uB4DC: \uC2E4\uC81C fix \uBBF8\uC218\uD589, `vhk audit` \uC548\uB0B4)"
|
|
1927
|
+
},
|
|
1928
|
+
async () => {
|
|
1929
|
+
const pm = detectCurrentPM();
|
|
1930
|
+
const output = runAuditJson(pm);
|
|
1931
|
+
const summary = parseAuditOutput(output, pm);
|
|
1932
|
+
if (summary.total === 0) {
|
|
1933
|
+
return {
|
|
1934
|
+
content: [{ type: "text", text: `\u{1F389} ${pm}: \uCDE8\uC57D\uC810 0\uAC74.` }]
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
const breakdown = [
|
|
1938
|
+
summary.critical > 0 ? `\u{1F534} critical ${summary.critical}` : null,
|
|
1939
|
+
summary.high > 0 ? `\u{1F7E0} high ${summary.high}` : null,
|
|
1940
|
+
summary.moderate > 0 ? `\u{1F7E1} moderate ${summary.moderate}` : null,
|
|
1941
|
+
summary.low > 0 ? `\u26AA low ${summary.low}` : null
|
|
1942
|
+
].filter(Boolean).join(" ");
|
|
1943
|
+
return {
|
|
1944
|
+
content: [
|
|
1945
|
+
{
|
|
1946
|
+
type: "text",
|
|
1947
|
+
text: `\u{1F4E6} PM: ${pm}
|
|
1948
|
+
\u{1F4CA} \uCD1D ${summary.total}\uAC74
|
|
1949
|
+
${breakdown}
|
|
1950
|
+
|
|
1951
|
+
\uC2E4\uC81C fix \uB294 \uD130\uBBF8\uB110\uC5D0\uC11C: vhk audit (\uB610\uB294 ${pm} audit fix)`
|
|
1952
|
+
}
|
|
1953
|
+
]
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
);
|
|
1957
|
+
server.registerTool(
|
|
1958
|
+
"harness",
|
|
1959
|
+
{ description: "lint + typecheck + test + build \uD1B5\uD569 \uD488\uC9C8 \uC810\uAC80" },
|
|
1960
|
+
async () => runVhkCli(["harness"], "harness")
|
|
1961
|
+
);
|
|
1962
|
+
server.registerTool(
|
|
1963
|
+
"context",
|
|
1964
|
+
{ description: "\uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D \uD30C\uC77C(.vhk/context.md) \uC0DD\uC131 \u2014 \uAE30\uC220 \uC2A4\uD0DD + \uB514\uB809\uD1A0\uB9AC + \uBA85\uB839\uC5B4 + \uACB0\uC815\uC0AC\uD56D" },
|
|
1965
|
+
async () => runVhkCli(["context"], "context")
|
|
1966
|
+
);
|
|
1967
|
+
server.registerTool(
|
|
1968
|
+
"brief",
|
|
1969
|
+
{ description: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551(.vhk/brief.md) \uC0DD\uC131 \u2014 git \uC0C1\uD0DC + \uACB0\uC815\uC0AC\uD56D + \uB2E4\uC74C \uB2E8\uACC4 \uC81C\uC548" },
|
|
1970
|
+
async () => runVhkCli(["brief"], "brief")
|
|
1971
|
+
);
|
|
1972
|
+
server.registerTool(
|
|
1973
|
+
"deploy",
|
|
1974
|
+
{
|
|
1975
|
+
description: "\uBC30\uD3EC \uD50C\uB7AB\uD3FC \uC790\uB3D9 \uAC10\uC9C0 + CLI \uC124\uCE58 \uD655\uC778 (MCP \uBAA8\uB4DC: \uC2E4\uC81C \uBC30\uD3EC \uBBF8\uC218\uD589 \u2014 `vhk deploy` \uC548\uB0B4)"
|
|
1976
|
+
},
|
|
1977
|
+
async () => {
|
|
1978
|
+
const platform = detectPlatform();
|
|
1979
|
+
if (!platform) {
|
|
1980
|
+
return {
|
|
1981
|
+
content: [
|
|
1982
|
+
{
|
|
1983
|
+
type: "text",
|
|
1984
|
+
text: "\u274C \uBC30\uD3EC \uD50C\uB7AB\uD3FC \uBBF8\uAC10\uC9C0 (vercel.json / netlify.toml / wrangler.toml \uC5C6\uC74C).\n\uD50C\uB7AB\uD3FC \uC124\uC815 \uD30C\uC77C \uCD94\uAC00 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4."
|
|
1985
|
+
}
|
|
1986
|
+
]
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
const cliCheck = safeExecFile(platform, ["--version"]);
|
|
1990
|
+
const cliStatus = cliCheck.ok ? `\u2713 ${platform} CLI \uC124\uCE58\uB428 (${cliCheck.out})` : `\u2717 ${platform} CLI \uBBF8\uC124\uCE58 \u2014 npm i -g ${platform}`;
|
|
1991
|
+
return {
|
|
1992
|
+
content: [
|
|
1993
|
+
{
|
|
1994
|
+
type: "text",
|
|
1995
|
+
text: `\u{1F680} \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${platform}
|
|
1996
|
+
${cliStatus}
|
|
1997
|
+
|
|
1998
|
+
\uC2E4\uC81C \uBC30\uD3EC\uB294 \uD130\uBBF8\uB110\uC5D0\uC11C: vhk deploy`
|
|
1999
|
+
}
|
|
2000
|
+
]
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
);
|
|
2004
|
+
server.registerTool(
|
|
2005
|
+
"publish",
|
|
2006
|
+
{
|
|
2007
|
+
description: "\uD604\uC7AC \uBC84\uC804 + bump \uD6C4\uBCF4 \uD45C\uC2DC (MCP \uBAA8\uB4DC: \uC2E4\uC81C npm publish \uBBF8\uC218\uD589 \u2014 `vhk publish` \uC548\uB0B4)"
|
|
2008
|
+
},
|
|
2009
|
+
async () => {
|
|
2010
|
+
if (!existsSync5("package.json")) {
|
|
2011
|
+
return { content: [{ type: "text", text: "\u274C package.json \uC5C6\uC74C." }] };
|
|
2012
|
+
}
|
|
2013
|
+
try {
|
|
2014
|
+
const pkg = readJsonFile("package.json");
|
|
2015
|
+
const v = pkg.version ?? "0.0.0";
|
|
2016
|
+
const lines = [
|
|
2017
|
+
`\u{1F4E6} ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} \uD604\uC7AC v${v}`,
|
|
2018
|
+
` patch \u2192 v${bumpVersion(v, "patch")}`,
|
|
2019
|
+
` minor \u2192 v${bumpVersion(v, "minor")}`,
|
|
2020
|
+
` major \u2192 v${bumpVersion(v, "major")}`,
|
|
2021
|
+
"",
|
|
2022
|
+
"\uC2E4\uC81C \uBC30\uD3EC\uB294 \uD130\uBBF8\uB110\uC5D0\uC11C: vhk publish"
|
|
2023
|
+
];
|
|
2024
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2025
|
+
} catch (e) {
|
|
2026
|
+
return {
|
|
2027
|
+
content: [{ type: "text", text: `\u274C package.json \uD30C\uC2F1 \uC2E4\uD328: ${String(e)}` }]
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
);
|
|
2032
|
+
server.registerTool(
|
|
2033
|
+
"migrate",
|
|
2034
|
+
{
|
|
2035
|
+
description: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uAC10\uC9C0 + \uC804\uD658 \uD6C4\uBCF4 \uAC00\uC6A9\uC131 (MCP \uBAA8\uB4DC: \uC2E4\uC81C \uC804\uD658 \uBBF8\uC218\uD589 \u2014 `vhk migrate <target>` \uC548\uB0B4)"
|
|
2036
|
+
},
|
|
2037
|
+
async () => {
|
|
2038
|
+
const current = existsSync5("pnpm-lock.yaml") ? "pnpm" : existsSync5("yarn.lock") ? "yarn" : existsSync5("package-lock.json") ? "npm" : null;
|
|
2039
|
+
const candidates = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current);
|
|
2040
|
+
const lines = [`\uD604\uC7AC PM: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00 (lock \uD30C\uC77C \uC5C6\uC74C)"}`];
|
|
2041
|
+
for (const pm of candidates) {
|
|
2042
|
+
const r = safeExecFile(pm, ["--version"]);
|
|
2043
|
+
lines.push(` ${pm}: ${r.ok ? `\u2713 v${r.out}` : "\u2717 \uBBF8\uC124\uCE58"}`);
|
|
2044
|
+
}
|
|
2045
|
+
lines.push("", "\uC2E4\uC81C \uC804\uD658\uC740 \uD130\uBBF8\uB110\uC5D0\uC11C: vhk migrate <pnpm|npm|yarn>");
|
|
2046
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2047
|
+
}
|
|
2048
|
+
);
|
|
2049
|
+
server.registerTool(
|
|
2050
|
+
"update",
|
|
2051
|
+
{
|
|
2052
|
+
description: "VHK \uD604\uC7AC/\uCD5C\uC2E0 \uBC84\uC804 \uBE44\uAD50 (MCP \uBAA8\uB4DC: --check \uB9CC \u2014 \uC2E4\uC81C \uC5C5\uB370\uC774\uD2B8 \uBBF8\uC218\uD589)"
|
|
2053
|
+
},
|
|
2054
|
+
async () => {
|
|
2055
|
+
const cur = safeExecFile("vhk", ["--version"]);
|
|
2056
|
+
const latest = safeExecFile("npm", ["view", "@byh3071/vhk", "version"]);
|
|
2057
|
+
const lines = [
|
|
2058
|
+
`\uD604\uC7AC: ${cur.ok ? `v${cur.out.replace(/^v/, "")}` : "\uD655\uC778 \uC2E4\uD328"}`,
|
|
2059
|
+
`\uCD5C\uC2E0: ${latest.ok ? `v${latest.out.replace(/^v/, "")}` : "\uD655\uC778 \uC2E4\uD328 (\uB124\uD2B8\uC6CC\uD06C \uB610\uB294 npm registry)"}`
|
|
2060
|
+
];
|
|
2061
|
+
if (cur.ok && latest.ok) {
|
|
2062
|
+
const same = cur.out.replace(/^v/, "") === latest.out.replace(/^v/, "");
|
|
2063
|
+
lines.push(
|
|
2064
|
+
same ? "\u2713 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4." : "\u2B06\uFE0F \uC5C5\uB370\uC774\uD2B8 \uAC00\uB2A5. \uD130\uBBF8\uB110\uC5D0\uC11C: vhk update (\uB610\uB294 npm update -g @byh3071/vhk)"
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2068
|
+
}
|
|
2069
|
+
);
|
|
2070
|
+
server.registerTool(
|
|
2071
|
+
"ref-list",
|
|
2072
|
+
{ description: "\uC800\uC7A5\uB41C \uB808\uD37C\uB7F0\uC2A4 URL \uBAA9\uB85D \uBCF4\uAE30 (add/open \uC740 \uC778\uC790/\uB300\uD654\uD615 \u2192 CLI \uC804\uC6A9)" },
|
|
2073
|
+
async () => runVhkCli(["ref", "list"], "ref list")
|
|
2074
|
+
);
|
|
2075
|
+
server.registerTool(
|
|
2076
|
+
"memory-list",
|
|
2077
|
+
{ description: "\uC800\uC7A5\uB41C \uACB0\uC815\uC0AC\uD56D(memory) \uBAA9\uB85D \uBCF4\uAE30 (add/remove \uB294 \uC778\uC790 \uD544\uC694 \u2192 CLI \uC804\uC6A9)" },
|
|
2078
|
+
async () => runVhkCli(["memory", "list"], "memory list")
|
|
2079
|
+
);
|
|
2080
|
+
server.registerTool(
|
|
2081
|
+
"context-show",
|
|
2082
|
+
{ description: ".vhk/context.md \uD30C\uC77C \uB0B4\uC6A9 \uBCF4\uAE30 (\uC5C6\uC73C\uBA74 `vhk context` \uC548\uB0B4)" },
|
|
2083
|
+
async () => runVhkCli(["context-show"], "context-show")
|
|
2084
|
+
);
|
|
2085
|
+
server.registerTool(
|
|
2086
|
+
"mcp-init",
|
|
2087
|
+
{ description: ".cursor/mcp.json \uC0DD\uC131/\uAC31\uC2E0 \u2014 vhk MCP \uC11C\uBC84 \uB4F1\uB85D (Cursor \uC7AC\uC2DC\uC791 \uD544\uC694)" },
|
|
2088
|
+
async () => runVhkCli(["mcp-init"], "mcp-init")
|
|
2089
|
+
);
|
|
2090
|
+
return server;
|
|
2091
|
+
}
|
|
2092
|
+
async function startMcpServer() {
|
|
2093
|
+
const server = createVhkMcpServer();
|
|
2094
|
+
const transport = new StdioServerTransport();
|
|
2095
|
+
await server.connect(transport);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
export {
|
|
2099
|
+
ko,
|
|
2100
|
+
t,
|
|
2101
|
+
printNextStep,
|
|
2102
|
+
printSecurityWarnings,
|
|
2103
|
+
filterTrackedPaths,
|
|
2104
|
+
readJsonFile,
|
|
2105
|
+
safeExecFile,
|
|
2106
|
+
MAX_SCAN_FILE_BYTES,
|
|
2107
|
+
MAX_SECRET_FINDINGS,
|
|
2108
|
+
scanProjectForSecrets,
|
|
2109
|
+
filterSevereFindings,
|
|
2110
|
+
deploy,
|
|
2111
|
+
env,
|
|
2112
|
+
envCheck,
|
|
2113
|
+
publish,
|
|
2114
|
+
audit,
|
|
2115
|
+
startMcpServer
|
|
2116
|
+
};
|