@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
package/dist/index.js
CHANGED
|
@@ -1,479 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
MAX_SCAN_FILE_BYTES,
|
|
4
|
+
MAX_SECRET_FINDINGS,
|
|
5
|
+
audit,
|
|
6
|
+
deploy,
|
|
5
7
|
env,
|
|
6
8
|
envCheck,
|
|
9
|
+
filterSevereFindings,
|
|
10
|
+
filterTrackedPaths,
|
|
7
11
|
ko,
|
|
8
12
|
printNextStep,
|
|
13
|
+
printSecurityWarnings,
|
|
14
|
+
publish,
|
|
9
15
|
readJsonFile,
|
|
10
16
|
safeExecFile,
|
|
11
|
-
|
|
17
|
+
scanProjectForSecrets,
|
|
12
18
|
startMcpServer,
|
|
13
19
|
t
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
|
|
16
|
-
// node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
17
|
-
var require_ignore = __commonJS({
|
|
18
|
-
"node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js"(exports, module) {
|
|
19
|
-
"use strict";
|
|
20
|
-
function makeArray(subject) {
|
|
21
|
-
return Array.isArray(subject) ? subject : [subject];
|
|
22
|
-
}
|
|
23
|
-
var UNDEFINED = void 0;
|
|
24
|
-
var EMPTY = "";
|
|
25
|
-
var SPACE = " ";
|
|
26
|
-
var ESCAPE = "\\";
|
|
27
|
-
var REGEX_TEST_BLANK_LINE = /^\s+$/;
|
|
28
|
-
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
29
|
-
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
30
|
-
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
31
|
-
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
32
|
-
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
33
|
-
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
34
|
-
var SLASH = "/";
|
|
35
|
-
var TMP_KEY_IGNORE = "node-ignore";
|
|
36
|
-
if (typeof Symbol !== "undefined") {
|
|
37
|
-
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
38
|
-
}
|
|
39
|
-
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
40
|
-
var define = (object, key, value) => {
|
|
41
|
-
Object.defineProperty(object, key, { value });
|
|
42
|
-
return value;
|
|
43
|
-
};
|
|
44
|
-
var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
|
|
45
|
-
var RETURN_FALSE = () => false;
|
|
46
|
-
var sanitizeRange = (range) => range.replace(
|
|
47
|
-
REGEX_REGEXP_RANGE,
|
|
48
|
-
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
|
|
49
|
-
);
|
|
50
|
-
var cleanRangeBackSlash = (slashes) => {
|
|
51
|
-
const { length } = slashes;
|
|
52
|
-
return slashes.slice(0, length - length % 2);
|
|
53
|
-
};
|
|
54
|
-
var REPLACERS = [
|
|
55
|
-
[
|
|
56
|
-
// Remove BOM
|
|
57
|
-
// TODO:
|
|
58
|
-
// Other similar zero-width characters?
|
|
59
|
-
/^\uFEFF/,
|
|
60
|
-
() => EMPTY
|
|
61
|
-
],
|
|
62
|
-
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
|
|
63
|
-
[
|
|
64
|
-
// (a\ ) -> (a )
|
|
65
|
-
// (a ) -> (a)
|
|
66
|
-
// (a ) -> (a)
|
|
67
|
-
// (a \ ) -> (a )
|
|
68
|
-
/((?:\\\\)*?)(\\?\s+)$/,
|
|
69
|
-
(_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
|
|
70
|
-
],
|
|
71
|
-
// Replace (\ ) with ' '
|
|
72
|
-
// (\ ) -> ' '
|
|
73
|
-
// (\\ ) -> '\\ '
|
|
74
|
-
// (\\\ ) -> '\\ '
|
|
75
|
-
[
|
|
76
|
-
/(\\+?)\s/g,
|
|
77
|
-
(_, m1) => {
|
|
78
|
-
const { length } = m1;
|
|
79
|
-
return m1.slice(0, length - length % 2) + SPACE;
|
|
80
|
-
}
|
|
81
|
-
],
|
|
82
|
-
// Escape metacharacters
|
|
83
|
-
// which is written down by users but means special for regular expressions.
|
|
84
|
-
// > There are 12 characters with special meanings:
|
|
85
|
-
// > - the backslash \,
|
|
86
|
-
// > - the caret ^,
|
|
87
|
-
// > - the dollar sign $,
|
|
88
|
-
// > - the period or dot .,
|
|
89
|
-
// > - the vertical bar or pipe symbol |,
|
|
90
|
-
// > - the question mark ?,
|
|
91
|
-
// > - the asterisk or star *,
|
|
92
|
-
// > - the plus sign +,
|
|
93
|
-
// > - the opening parenthesis (,
|
|
94
|
-
// > - the closing parenthesis ),
|
|
95
|
-
// > - and the opening square bracket [,
|
|
96
|
-
// > - the opening curly brace {,
|
|
97
|
-
// > These special characters are often called "metacharacters".
|
|
98
|
-
[
|
|
99
|
-
/[\\$.|*+(){^]/g,
|
|
100
|
-
(match) => `\\${match}`
|
|
101
|
-
],
|
|
102
|
-
[
|
|
103
|
-
// > a question mark (?) matches a single character
|
|
104
|
-
/(?!\\)\?/g,
|
|
105
|
-
() => "[^/]"
|
|
106
|
-
],
|
|
107
|
-
// leading slash
|
|
108
|
-
[
|
|
109
|
-
// > A leading slash matches the beginning of the pathname.
|
|
110
|
-
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
111
|
-
// A leading slash matches the beginning of the pathname
|
|
112
|
-
/^\//,
|
|
113
|
-
() => "^"
|
|
114
|
-
],
|
|
115
|
-
// replace special metacharacter slash after the leading slash
|
|
116
|
-
[
|
|
117
|
-
/\//g,
|
|
118
|
-
() => "\\/"
|
|
119
|
-
],
|
|
120
|
-
[
|
|
121
|
-
// > A leading "**" followed by a slash means match in all directories.
|
|
122
|
-
// > For example, "**/foo" matches file or directory "foo" anywhere,
|
|
123
|
-
// > the same as pattern "foo".
|
|
124
|
-
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
125
|
-
// > under directory "foo".
|
|
126
|
-
// Notice that the '*'s have been replaced as '\\*'
|
|
127
|
-
/^\^*\\\*\\\*\\\//,
|
|
128
|
-
// '**/foo' <-> 'foo'
|
|
129
|
-
() => "^(?:.*\\/)?"
|
|
130
|
-
],
|
|
131
|
-
// starting
|
|
132
|
-
[
|
|
133
|
-
// there will be no leading '/'
|
|
134
|
-
// (which has been replaced by section "leading slash")
|
|
135
|
-
// If starts with '**', adding a '^' to the regular expression also works
|
|
136
|
-
/^(?=[^^])/,
|
|
137
|
-
function startingReplacer() {
|
|
138
|
-
return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
|
|
139
|
-
}
|
|
140
|
-
],
|
|
141
|
-
// two globstars
|
|
142
|
-
[
|
|
143
|
-
// Use lookahead assertions so that we could match more than one `'/**'`
|
|
144
|
-
/\\\/\\\*\\\*(?=\\\/|$)/g,
|
|
145
|
-
// Zero, one or several directories
|
|
146
|
-
// should not use '*', or it will be replaced by the next replacer
|
|
147
|
-
// Check if it is not the last `'/**'`
|
|
148
|
-
(_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
|
|
149
|
-
],
|
|
150
|
-
// normal intermediate wildcards
|
|
151
|
-
[
|
|
152
|
-
// Never replace escaped '*'
|
|
153
|
-
// ignore rule '\*' will match the path '*'
|
|
154
|
-
// 'abc.*/' -> go
|
|
155
|
-
// 'abc.*' -> skip this rule,
|
|
156
|
-
// coz trailing single wildcard will be handed by [trailing wildcard]
|
|
157
|
-
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
158
|
-
// '*.js' matches '.js'
|
|
159
|
-
// '*.js' doesn't match 'abc'
|
|
160
|
-
(_, p1, p2) => {
|
|
161
|
-
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
162
|
-
return p1 + unescaped;
|
|
163
|
-
}
|
|
164
|
-
],
|
|
165
|
-
[
|
|
166
|
-
// unescape, revert step 3 except for back slash
|
|
167
|
-
// For example, if a user escape a '\\*',
|
|
168
|
-
// after step 3, the result will be '\\\\\\*'
|
|
169
|
-
/\\\\\\(?=[$.|*+(){^])/g,
|
|
170
|
-
() => ESCAPE
|
|
171
|
-
],
|
|
172
|
-
[
|
|
173
|
-
// '\\\\' -> '\\'
|
|
174
|
-
/\\\\/g,
|
|
175
|
-
() => ESCAPE
|
|
176
|
-
],
|
|
177
|
-
[
|
|
178
|
-
// > The range notation, e.g. [a-zA-Z],
|
|
179
|
-
// > can be used to match one of the characters in a range.
|
|
180
|
-
// `\` is escaped by step 3
|
|
181
|
-
/(\\)?\[([^\]/]*?)(\\*)($|\])/g,
|
|
182
|
-
(match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
|
|
183
|
-
],
|
|
184
|
-
// ending
|
|
185
|
-
[
|
|
186
|
-
// 'js' will not match 'js.'
|
|
187
|
-
// 'ab' will not match 'abc'
|
|
188
|
-
/(?:[^*])$/,
|
|
189
|
-
// WTF!
|
|
190
|
-
// https://git-scm.com/docs/gitignore
|
|
191
|
-
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
|
|
192
|
-
// which re-fixes #24, #38
|
|
193
|
-
// > If there is a separator at the end of the pattern then the pattern
|
|
194
|
-
// > will only match directories, otherwise the pattern can match both
|
|
195
|
-
// > files and directories.
|
|
196
|
-
// 'js*' will not match 'a.js'
|
|
197
|
-
// 'js/' will not match 'a.js'
|
|
198
|
-
// 'js' will match 'a.js' and 'a.js/'
|
|
199
|
-
(match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
|
|
200
|
-
]
|
|
201
|
-
];
|
|
202
|
-
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
|
|
203
|
-
var MODE_IGNORE = "regex";
|
|
204
|
-
var MODE_CHECK_IGNORE = "checkRegex";
|
|
205
|
-
var UNDERSCORE = "_";
|
|
206
|
-
var TRAILING_WILD_CARD_REPLACERS = {
|
|
207
|
-
[MODE_IGNORE](_, p1) {
|
|
208
|
-
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
209
|
-
return `${prefix}(?=$|\\/$)`;
|
|
210
|
-
},
|
|
211
|
-
[MODE_CHECK_IGNORE](_, p1) {
|
|
212
|
-
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
213
|
-
return `${prefix}(?=$|\\/$)`;
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
var makeRegexPrefix = (pattern) => REPLACERS.reduce(
|
|
217
|
-
(prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
|
|
218
|
-
pattern
|
|
219
|
-
);
|
|
220
|
-
var isString = (subject) => typeof subject === "string";
|
|
221
|
-
var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
|
|
222
|
-
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
223
|
-
var IgnoreRule = class {
|
|
224
|
-
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
225
|
-
this.pattern = pattern;
|
|
226
|
-
this.mark = mark;
|
|
227
|
-
this.negative = negative;
|
|
228
|
-
define(this, "body", body);
|
|
229
|
-
define(this, "ignoreCase", ignoreCase);
|
|
230
|
-
define(this, "regexPrefix", prefix);
|
|
231
|
-
}
|
|
232
|
-
get regex() {
|
|
233
|
-
const key = UNDERSCORE + MODE_IGNORE;
|
|
234
|
-
if (this[key]) {
|
|
235
|
-
return this[key];
|
|
236
|
-
}
|
|
237
|
-
return this._make(MODE_IGNORE, key);
|
|
238
|
-
}
|
|
239
|
-
get checkRegex() {
|
|
240
|
-
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
241
|
-
if (this[key]) {
|
|
242
|
-
return this[key];
|
|
243
|
-
}
|
|
244
|
-
return this._make(MODE_CHECK_IGNORE, key);
|
|
245
|
-
}
|
|
246
|
-
_make(mode, key) {
|
|
247
|
-
const str = this.regexPrefix.replace(
|
|
248
|
-
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
249
|
-
// It does not need to bind pattern
|
|
250
|
-
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
251
|
-
);
|
|
252
|
-
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
253
|
-
return define(this, key, regex);
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
var createRule = ({
|
|
257
|
-
pattern,
|
|
258
|
-
mark
|
|
259
|
-
}, ignoreCase) => {
|
|
260
|
-
let negative = false;
|
|
261
|
-
let body = pattern;
|
|
262
|
-
if (body.indexOf("!") === 0) {
|
|
263
|
-
negative = true;
|
|
264
|
-
body = body.substr(1);
|
|
265
|
-
}
|
|
266
|
-
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
267
|
-
const regexPrefix = makeRegexPrefix(body);
|
|
268
|
-
return new IgnoreRule(
|
|
269
|
-
pattern,
|
|
270
|
-
mark,
|
|
271
|
-
body,
|
|
272
|
-
ignoreCase,
|
|
273
|
-
negative,
|
|
274
|
-
regexPrefix
|
|
275
|
-
);
|
|
276
|
-
};
|
|
277
|
-
var RuleManager = class {
|
|
278
|
-
constructor(ignoreCase) {
|
|
279
|
-
this._ignoreCase = ignoreCase;
|
|
280
|
-
this._rules = [];
|
|
281
|
-
}
|
|
282
|
-
_add(pattern) {
|
|
283
|
-
if (pattern && pattern[KEY_IGNORE]) {
|
|
284
|
-
this._rules = this._rules.concat(pattern._rules._rules);
|
|
285
|
-
this._added = true;
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
if (isString(pattern)) {
|
|
289
|
-
pattern = {
|
|
290
|
-
pattern
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
if (checkPattern(pattern.pattern)) {
|
|
294
|
-
const rule = createRule(pattern, this._ignoreCase);
|
|
295
|
-
this._added = true;
|
|
296
|
-
this._rules.push(rule);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// @param {Array<string> | string | Ignore} pattern
|
|
300
|
-
add(pattern) {
|
|
301
|
-
this._added = false;
|
|
302
|
-
makeArray(
|
|
303
|
-
isString(pattern) ? splitPattern(pattern) : pattern
|
|
304
|
-
).forEach(this._add, this);
|
|
305
|
-
return this._added;
|
|
306
|
-
}
|
|
307
|
-
// Test one single path without recursively checking parent directories
|
|
308
|
-
//
|
|
309
|
-
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
310
|
-
// setting `checkUnignored` to `false` could reduce additional
|
|
311
|
-
// path matching.
|
|
312
|
-
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
313
|
-
// @returns {TestResult} true if a file is ignored
|
|
314
|
-
test(path15, checkUnignored, mode) {
|
|
315
|
-
let ignored = false;
|
|
316
|
-
let unignored = false;
|
|
317
|
-
let matchedRule;
|
|
318
|
-
this._rules.forEach((rule) => {
|
|
319
|
-
const { negative } = rule;
|
|
320
|
-
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const matched = rule[mode].test(path15);
|
|
324
|
-
if (!matched) {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
ignored = !negative;
|
|
328
|
-
unignored = negative;
|
|
329
|
-
matchedRule = negative ? UNDEFINED : rule;
|
|
330
|
-
});
|
|
331
|
-
const ret = {
|
|
332
|
-
ignored,
|
|
333
|
-
unignored
|
|
334
|
-
};
|
|
335
|
-
if (matchedRule) {
|
|
336
|
-
ret.rule = matchedRule;
|
|
337
|
-
}
|
|
338
|
-
return ret;
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
var throwError = (message, Ctor) => {
|
|
342
|
-
throw new Ctor(message);
|
|
343
|
-
};
|
|
344
|
-
var checkPath = (path15, originalPath, doThrow) => {
|
|
345
|
-
if (!isString(path15)) {
|
|
346
|
-
return doThrow(
|
|
347
|
-
`path must be a string, but got \`${originalPath}\``,
|
|
348
|
-
TypeError
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
if (!path15) {
|
|
352
|
-
return doThrow(`path must not be empty`, TypeError);
|
|
353
|
-
}
|
|
354
|
-
if (checkPath.isNotRelative(path15)) {
|
|
355
|
-
const r = "`path.relative()`d";
|
|
356
|
-
return doThrow(
|
|
357
|
-
`path should be a ${r} string, but got "${originalPath}"`,
|
|
358
|
-
RangeError
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
return true;
|
|
362
|
-
};
|
|
363
|
-
var isNotRelative = (path15) => REGEX_TEST_INVALID_PATH.test(path15);
|
|
364
|
-
checkPath.isNotRelative = isNotRelative;
|
|
365
|
-
checkPath.convert = (p) => p;
|
|
366
|
-
var Ignore = class {
|
|
367
|
-
constructor({
|
|
368
|
-
ignorecase = true,
|
|
369
|
-
ignoreCase = ignorecase,
|
|
370
|
-
allowRelativePaths = false
|
|
371
|
-
} = {}) {
|
|
372
|
-
define(this, KEY_IGNORE, true);
|
|
373
|
-
this._rules = new RuleManager(ignoreCase);
|
|
374
|
-
this._strictPathCheck = !allowRelativePaths;
|
|
375
|
-
this._initCache();
|
|
376
|
-
}
|
|
377
|
-
_initCache() {
|
|
378
|
-
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
379
|
-
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
380
|
-
}
|
|
381
|
-
add(pattern) {
|
|
382
|
-
if (this._rules.add(pattern)) {
|
|
383
|
-
this._initCache();
|
|
384
|
-
}
|
|
385
|
-
return this;
|
|
386
|
-
}
|
|
387
|
-
// legacy
|
|
388
|
-
addPattern(pattern) {
|
|
389
|
-
return this.add(pattern);
|
|
390
|
-
}
|
|
391
|
-
// @returns {TestResult}
|
|
392
|
-
_test(originalPath, cache, checkUnignored, slices) {
|
|
393
|
-
const path15 = originalPath && checkPath.convert(originalPath);
|
|
394
|
-
checkPath(
|
|
395
|
-
path15,
|
|
396
|
-
originalPath,
|
|
397
|
-
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
398
|
-
);
|
|
399
|
-
return this._t(path15, cache, checkUnignored, slices);
|
|
400
|
-
}
|
|
401
|
-
checkIgnore(path15) {
|
|
402
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(path15)) {
|
|
403
|
-
return this.test(path15);
|
|
404
|
-
}
|
|
405
|
-
const slices = path15.split(SLASH).filter(Boolean);
|
|
406
|
-
slices.pop();
|
|
407
|
-
if (slices.length) {
|
|
408
|
-
const parent = this._t(
|
|
409
|
-
slices.join(SLASH) + SLASH,
|
|
410
|
-
this._testCache,
|
|
411
|
-
true,
|
|
412
|
-
slices
|
|
413
|
-
);
|
|
414
|
-
if (parent.ignored) {
|
|
415
|
-
return parent;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
return this._rules.test(path15, false, MODE_CHECK_IGNORE);
|
|
419
|
-
}
|
|
420
|
-
_t(path15, cache, checkUnignored, slices) {
|
|
421
|
-
if (path15 in cache) {
|
|
422
|
-
return cache[path15];
|
|
423
|
-
}
|
|
424
|
-
if (!slices) {
|
|
425
|
-
slices = path15.split(SLASH).filter(Boolean);
|
|
426
|
-
}
|
|
427
|
-
slices.pop();
|
|
428
|
-
if (!slices.length) {
|
|
429
|
-
return cache[path15] = this._rules.test(path15, checkUnignored, MODE_IGNORE);
|
|
430
|
-
}
|
|
431
|
-
const parent = this._t(
|
|
432
|
-
slices.join(SLASH) + SLASH,
|
|
433
|
-
cache,
|
|
434
|
-
checkUnignored,
|
|
435
|
-
slices
|
|
436
|
-
);
|
|
437
|
-
return cache[path15] = parent.ignored ? parent : this._rules.test(path15, checkUnignored, MODE_IGNORE);
|
|
438
|
-
}
|
|
439
|
-
ignores(path15) {
|
|
440
|
-
return this._test(path15, this._ignoreCache, false).ignored;
|
|
441
|
-
}
|
|
442
|
-
createFilter() {
|
|
443
|
-
return (path15) => !this.ignores(path15);
|
|
444
|
-
}
|
|
445
|
-
filter(paths) {
|
|
446
|
-
return makeArray(paths).filter(this.createFilter());
|
|
447
|
-
}
|
|
448
|
-
// @returns {TestResult}
|
|
449
|
-
test(path15) {
|
|
450
|
-
return this._test(path15, this._testCache, true);
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
var factory = (options) => new Ignore(options);
|
|
454
|
-
var isPathValid = (path15) => checkPath(path15 && checkPath.convert(path15), path15, RETURN_FALSE);
|
|
455
|
-
var setupWindows = () => {
|
|
456
|
-
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
457
|
-
checkPath.convert = makePosix;
|
|
458
|
-
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
459
|
-
checkPath.isNotRelative = (path15) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path15) || isNotRelative(path15);
|
|
460
|
-
};
|
|
461
|
-
if (
|
|
462
|
-
// Detect `process` so that it can run in browsers.
|
|
463
|
-
typeof process !== "undefined" && process.platform === "win32"
|
|
464
|
-
) {
|
|
465
|
-
setupWindows();
|
|
466
|
-
}
|
|
467
|
-
module.exports = factory;
|
|
468
|
-
factory.default = factory;
|
|
469
|
-
module.exports.isPathValid = isPathValid;
|
|
470
|
-
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
471
|
-
}
|
|
472
|
-
});
|
|
20
|
+
} from "./chunk-3HGOQLRT.js";
|
|
473
21
|
|
|
474
22
|
// src/index.ts
|
|
475
23
|
import { Command, Help } from "commander";
|
|
476
|
-
import
|
|
24
|
+
import inquirer12 from "inquirer";
|
|
477
25
|
|
|
478
26
|
// src/lib/nlp-router.ts
|
|
479
27
|
function normalize(input) {
|
|
@@ -492,24 +40,23 @@ function matchesKeywords(text, command) {
|
|
|
492
40
|
}
|
|
493
41
|
var RULES = [
|
|
494
42
|
{
|
|
495
|
-
command: "
|
|
496
|
-
explanation: "\
|
|
497
|
-
confidence: "high",
|
|
498
|
-
args: ["--skip-gate"],
|
|
499
|
-
test: (t2) => /기획.*(끝|완료)|노션.*(기획|완료)|검증.*(스킵|건너)|gate.*(스킵|건너)|바로.*시작/.test(t2)
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
command: "init",
|
|
503
|
-
explanation: "Notion\uC5D0\uC11C \uAC00\uC838\uC640 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --from-notion)",
|
|
43
|
+
command: "start",
|
|
44
|
+
explanation: "\uB178\uC158\uC5D0\uC11C \uAC00\uC838\uC640 \uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 \uB9C8\uBC95\uC0AC (vhk start --from-notion)",
|
|
504
45
|
confidence: "low",
|
|
505
46
|
args: ["--from-notion"],
|
|
506
47
|
test: (t2) => /노션|notion/.test(t2) && /(시작|만들|import|가져)/.test(t2)
|
|
507
48
|
},
|
|
49
|
+
{
|
|
50
|
+
command: "start",
|
|
51
|
+
explanation: "\uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 \uB9C8\uBC95\uC0AC \u2014 git+\uBB38\uC11C+MCP+\uCEE8\uD14D\uC2A4\uD2B8 (vhk start)",
|
|
52
|
+
confidence: "high",
|
|
53
|
+
test: (t2) => /프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|새\s*프로젝트|^시작$|마법사|기획.*(끝|완료)|검증.*(스킵|건너)|gate.*(스킵|건너)|바로.*시작/.test(t2) && !/디자인|design|팔레트|palette|테마|theme|레퍼런스|reference|다크\s*모드|라이트\s*모드|색상\s*모드|브리핑|brief|컨텍스트|context|맥락|기억|memory|^초기화$|하네스.*만/.test(t2)
|
|
54
|
+
},
|
|
508
55
|
{
|
|
509
56
|
command: "init",
|
|
510
|
-
explanation: "\
|
|
57
|
+
explanation: "\uBB38\uC11C/\uD558\uB124\uC2A4 \uD30C\uC77C\uB9CC \uC0DD\uC131 (vhk init) \u2014 git/MCP/context\uB294 \uC81C\uC678",
|
|
511
58
|
confidence: "high",
|
|
512
|
-
test: (t2) =>
|
|
59
|
+
test: (t2) => /^init$|^초기화$|하네스\s*만|문서\s*만\s*만들|init\s*만/.test(t2)
|
|
513
60
|
},
|
|
514
61
|
{
|
|
515
62
|
command: "mcp-init",
|
|
@@ -678,6 +225,36 @@ var RULES = [
|
|
|
678
225
|
explanation: "npm \uBC30\uD3EC (vhk publish)",
|
|
679
226
|
confidence: "high",
|
|
680
227
|
test: (t2) => /^출시$|출시\s*해|^publish$|퍼블리시|npm\s*(배포|출시)|버전\s*올|^릴리즈$|^release$/.test(t2) && !/체크|준비|회고/.test(t2)
|
|
228
|
+
},
|
|
229
|
+
// NLP 규칙은 한국어 표현만 매칭. 영문 `goal <sub>` 은 commander 가 직접 처리하도록
|
|
230
|
+
// 가로채기 금지 — vhk goal list / next / check / done 그대로 동작.
|
|
231
|
+
{
|
|
232
|
+
command: "goal",
|
|
233
|
+
explanation: "\uB2E4\uC74C goal \uC790\uB3D9 \uC120\uD0DD (vhk goal next)",
|
|
234
|
+
confidence: "high",
|
|
235
|
+
args: ["next"],
|
|
236
|
+
test: (t2) => /다음\s*목표|목표\s*다음/.test(t2)
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
command: "goal",
|
|
240
|
+
explanation: "\uBAA9\uD45C \uAC8C\uC774\uD2B8 \uAC80\uC99D (vhk goal check)",
|
|
241
|
+
confidence: "high",
|
|
242
|
+
args: ["check"],
|
|
243
|
+
test: (t2) => /목표\s*(점검|검증|체크)/.test(t2)
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
command: "goal",
|
|
247
|
+
explanation: "\uBAA9\uD45C \uC644\uB8CC \uCC98\uB9AC (vhk goal done)",
|
|
248
|
+
confidence: "high",
|
|
249
|
+
args: ["done"],
|
|
250
|
+
test: (t2) => /목표\s*(완료|마감)/.test(t2)
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
command: "goal",
|
|
254
|
+
explanation: "\uBAA9\uD45C \uBAA9\uB85D (vhk goal list)",
|
|
255
|
+
confidence: "high",
|
|
256
|
+
args: ["list"],
|
|
257
|
+
test: (t2) => /목표\s*(목록|리스트)/.test(t2)
|
|
681
258
|
}
|
|
682
259
|
];
|
|
683
260
|
function routeNaturalLanguage(input) {
|
|
@@ -705,8 +282,11 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
705
282
|
"gate",
|
|
706
283
|
"\uAC80\uC99D",
|
|
707
284
|
"\uC544\uC774\uB514\uC5B4",
|
|
708
|
-
"
|
|
285
|
+
"start",
|
|
709
286
|
"\uC2DC\uC791",
|
|
287
|
+
"\uC0C8\uD504\uB85C\uC81D\uD2B8",
|
|
288
|
+
"init",
|
|
289
|
+
"\uCD08\uAE30\uD654",
|
|
710
290
|
"\uB9CC\uB4E4\uAE30",
|
|
711
291
|
"recap",
|
|
712
292
|
"\uC815\uB9AC",
|
|
@@ -771,6 +351,14 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
771
351
|
"\uAE30\uC5B5",
|
|
772
352
|
"brief",
|
|
773
353
|
"\uBE0C\uB9AC\uD551",
|
|
354
|
+
"goal",
|
|
355
|
+
"\uBAA9\uD45C",
|
|
356
|
+
"blocker",
|
|
357
|
+
"\uBE14\uB85C\uCEE4",
|
|
358
|
+
"learn",
|
|
359
|
+
"\uAD50\uD6C8",
|
|
360
|
+
"resume",
|
|
361
|
+
"\uC7AC\uAC1C",
|
|
774
362
|
"help"
|
|
775
363
|
]);
|
|
776
364
|
function isOptionToken(token) {
|
|
@@ -794,8 +382,8 @@ function detectNaturalLanguageInput(argv) {
|
|
|
794
382
|
}
|
|
795
383
|
|
|
796
384
|
// src/lib/nlp-run.ts
|
|
797
|
-
import
|
|
798
|
-
import
|
|
385
|
+
import chalk26 from "chalk";
|
|
386
|
+
import inquirer11 from "inquirer";
|
|
799
387
|
|
|
800
388
|
// src/commands/gate.ts
|
|
801
389
|
import inquirer from "inquirer";
|
|
@@ -929,9 +517,9 @@ ${ko.gate.verdictTitle}
|
|
|
929
517
|
|
|
930
518
|
// src/commands/init.ts
|
|
931
519
|
import inquirer2 from "inquirer";
|
|
932
|
-
import
|
|
933
|
-
import
|
|
934
|
-
import
|
|
520
|
+
import chalk3 from "chalk";
|
|
521
|
+
import fs2 from "fs";
|
|
522
|
+
import path2 from "path";
|
|
935
523
|
|
|
936
524
|
// src/templates/claude-md.ts
|
|
937
525
|
function CLAUDE_MD_TEMPLATE(name, _stack) {
|
|
@@ -1119,6 +707,14 @@ function COMMANDS_MD_TEMPLATE() {
|
|
|
1119
707
|
"\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC790\uC8FC \uC4F0\uB294 \uBA85\uB839\uC5B4\uC785\uB2C8\uB2E4.",
|
|
1120
708
|
"Cursor\uC5D0\uAC8C \uD55C\uAD6D\uC5B4\uB85C \uB9D0\uD574\uB3C4 \uB429\uB2C8\uB2E4.",
|
|
1121
709
|
"",
|
|
710
|
+
"## \uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791",
|
|
711
|
+
"",
|
|
712
|
+
"| \uD558\uACE0 \uC2F6\uC740 \uAC83 | \uD130\uBBF8\uB110 \uBA85\uB839 | Cursor\uC5D0\uAC8C \uB9D0\uD558\uAE30 |",
|
|
713
|
+
"|-------------|-----------|------------------|",
|
|
714
|
+
'| \uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (\uC62C\uC778\uC6D0) | `vhk start` | "\uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD574\uC918" |',
|
|
715
|
+
"",
|
|
716
|
+
"> `vhk start` \uD558\uB098\uB85C `git init` + \uBB38\uC11C \uC0DD\uC131 + Cursor MCP \uB4F1\uB85D + AI \uCEE8\uD14D\uC2A4\uD2B8 \uC0DD\uC131\uC744 \uD55C \uBC88\uC5D0 \uCC98\uB9AC\uD569\uB2C8\uB2E4. \uBA85\uB839\uC5B4 \uC678\uC6B8 \uD544\uC694 \uC5C6\uC5B4\uC694.",
|
|
717
|
+
"",
|
|
1122
718
|
"## \uB9E4\uC77C \uC4F0\uB294 \uBA85\uB839\uC5B4",
|
|
1123
719
|
"",
|
|
1124
720
|
"| \uD558\uACE0 \uC2F6\uC740 \uAC83 | \uD130\uBBF8\uB110 \uBA85\uB839 | Cursor\uC5D0\uAC8C \uB9D0\uD558\uAE30 |",
|
|
@@ -1141,113 +737,27 @@ function COMMANDS_MD_TEMPLATE() {
|
|
|
1141
737
|
].join("\n");
|
|
1142
738
|
}
|
|
1143
739
|
|
|
1144
|
-
// src/lib/check-secure.ts
|
|
1145
|
-
var import_ignore = __toESM(require_ignore(), 1);
|
|
1146
|
-
import fs from "fs";
|
|
1147
|
-
import path from "path";
|
|
1148
|
-
import chalk2 from "chalk";
|
|
1149
|
-
function loadGitignore(rootDir) {
|
|
1150
|
-
const ig = (0, import_ignore.default)();
|
|
1151
|
-
const gitignorePath = path.join(rootDir, ".gitignore");
|
|
1152
|
-
if (fs.existsSync(gitignorePath)) {
|
|
1153
|
-
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
1154
|
-
ig.add(content);
|
|
1155
|
-
}
|
|
1156
|
-
return ig;
|
|
1157
|
-
}
|
|
1158
|
-
function isPathIgnored(ig, relativePath) {
|
|
1159
|
-
const normalized = relativePath.replace(/\\/g, "/");
|
|
1160
|
-
return ig.ignores(normalized);
|
|
1161
|
-
}
|
|
1162
|
-
function findExposedSensitiveFiles(rootDir, ig = loadGitignore(rootDir), maxDepth = 8) {
|
|
1163
|
-
const exposed = [];
|
|
1164
|
-
function walk(dir, depth) {
|
|
1165
|
-
if (depth > maxDepth) return;
|
|
1166
|
-
let entries;
|
|
1167
|
-
try {
|
|
1168
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1169
|
-
} catch {
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
for (const entry of entries) {
|
|
1173
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
1174
|
-
const fullPath = path.join(dir, entry.name);
|
|
1175
|
-
const rel = path.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
1176
|
-
if (entry.isDirectory()) {
|
|
1177
|
-
if (!isPathIgnored(ig, rel + "/")) walk(fullPath, depth + 1);
|
|
1178
|
-
continue;
|
|
1179
|
-
}
|
|
1180
|
-
if (isSensitiveName(entry.name) && !isPathIgnored(ig, rel)) {
|
|
1181
|
-
exposed.push(rel);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
walk(rootDir, 0);
|
|
1186
|
-
return exposed;
|
|
1187
|
-
}
|
|
1188
|
-
function isSensitiveName(name) {
|
|
1189
|
-
const lower = name.toLowerCase();
|
|
1190
|
-
if (lower === ".env" || lower.startsWith(".env.")) return true;
|
|
1191
|
-
if (lower.endsWith(".pem") || lower.endsWith(".key")) return true;
|
|
1192
|
-
if (lower === "credentials.json" || lower === "secrets.json") return true;
|
|
1193
|
-
if (lower.startsWith("id_rsa")) return true;
|
|
1194
|
-
return false;
|
|
1195
|
-
}
|
|
1196
|
-
function checkProjectSecurity(rootDir = process.cwd()) {
|
|
1197
|
-
const gitignorePath = path.join(rootDir, ".gitignore");
|
|
1198
|
-
const missingGitignore = !fs.existsSync(gitignorePath);
|
|
1199
|
-
const ig = loadGitignore(rootDir);
|
|
1200
|
-
const exposedPaths = findExposedSensitiveFiles(rootDir, ig);
|
|
1201
|
-
const warnings = [];
|
|
1202
|
-
if (missingGitignore) {
|
|
1203
|
-
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.");
|
|
1204
|
-
}
|
|
1205
|
-
if (exposedPaths.length > 0) {
|
|
1206
|
-
warnings.push(
|
|
1207
|
-
`ignore\uB418\uC9C0 \uC54A\uC740 \uBBFC\uAC10 \uD30C\uC77C ${exposedPaths.length}\uAC1C: ${exposedPaths.join(", ")}`
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
return {
|
|
1211
|
-
ok: !missingGitignore && exposedPaths.length === 0,
|
|
1212
|
-
missingGitignore,
|
|
1213
|
-
exposedPaths,
|
|
1214
|
-
warnings
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
function printSecurityWarnings(rootDir = process.cwd()) {
|
|
1218
|
-
const result = checkProjectSecurity(rootDir);
|
|
1219
|
-
if (result.ok) return true;
|
|
1220
|
-
for (const w of result.warnings) {
|
|
1221
|
-
console.log(chalk2.yellow(` \u26A0\uFE0F ${w}`));
|
|
1222
|
-
}
|
|
1223
|
-
return false;
|
|
1224
|
-
}
|
|
1225
|
-
function filterTrackedPaths(paths, rootDir = process.cwd()) {
|
|
1226
|
-
const ig = loadGitignore(rootDir);
|
|
1227
|
-
return paths.filter((p) => !isPathIgnored(ig, p.replace(/\\/g, "/")));
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
740
|
// src/utils/logger.ts
|
|
1231
|
-
import
|
|
741
|
+
import chalk2 from "chalk";
|
|
1232
742
|
var log = {
|
|
1233
|
-
success: (msg) => console.log(
|
|
1234
|
-
error: (msg) => console.log(
|
|
1235
|
-
warn: (msg) => console.log(
|
|
1236
|
-
info: (msg) => console.log(
|
|
1237
|
-
step: (msg) => console.log(
|
|
743
|
+
success: (msg) => console.log(chalk2.green(`\u2705 ${msg}`)),
|
|
744
|
+
error: (msg) => console.log(chalk2.red(`\u274C ${msg}`)),
|
|
745
|
+
warn: (msg) => console.log(chalk2.yellow(`\u26A0\uFE0F ${msg}`)),
|
|
746
|
+
info: (msg) => console.log(chalk2.blue(`\u2139\uFE0F ${msg}`)),
|
|
747
|
+
step: (msg) => console.log(chalk2.bold(`
|
|
1238
748
|
\u25B8 ${msg}`))
|
|
1239
749
|
};
|
|
1240
750
|
|
|
1241
751
|
// src/utils/file.ts
|
|
1242
|
-
import
|
|
1243
|
-
import
|
|
752
|
+
import fs from "fs";
|
|
753
|
+
import path from "path";
|
|
1244
754
|
function writeFile(filePath, content) {
|
|
1245
|
-
const dir =
|
|
1246
|
-
if (!
|
|
1247
|
-
|
|
755
|
+
const dir = path.dirname(filePath);
|
|
756
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
757
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
1248
758
|
}
|
|
1249
759
|
function fileExists(filePath) {
|
|
1250
|
-
return
|
|
760
|
+
return fs.existsSync(filePath);
|
|
1251
761
|
}
|
|
1252
762
|
|
|
1253
763
|
// src/lib/notion-import.ts
|
|
@@ -1439,11 +949,11 @@ async function collectAnswers(options, defaults = {}) {
|
|
|
1439
949
|
async function init(options = {}) {
|
|
1440
950
|
const skipGate = Boolean(options.skipGate || options.fromNotion);
|
|
1441
951
|
if (skipGate) {
|
|
1442
|
-
console.log(
|
|
952
|
+
console.log(chalk3.dim(`
|
|
1443
953
|
${ko.init.skipGate}
|
|
1444
954
|
`));
|
|
1445
955
|
}
|
|
1446
|
-
console.log(
|
|
956
|
+
console.log(chalk3.bold(`
|
|
1447
957
|
${ko.init.title}
|
|
1448
958
|
`));
|
|
1449
959
|
printSecurityWarnings();
|
|
@@ -1468,7 +978,7 @@ ${ko.init.title}
|
|
|
1468
978
|
process.exit(1);
|
|
1469
979
|
}
|
|
1470
980
|
const stack = STACK_PRESETS[answers.type];
|
|
1471
|
-
console.log(
|
|
981
|
+
console.log(chalk3.dim(`
|
|
1472
982
|
${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
1473
983
|
`));
|
|
1474
984
|
if (!options.yes) {
|
|
@@ -1487,7 +997,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
1487
997
|
const files = generateFiles(answers.name, answers.description, stack, prdContent);
|
|
1488
998
|
log.step(ko.init.filesGenerating);
|
|
1489
999
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1490
|
-
const fullPath =
|
|
1000
|
+
const fullPath = path2.join(cwd, filePath);
|
|
1491
1001
|
if (fileExists(fullPath)) {
|
|
1492
1002
|
const { overwrite } = await inquirer2.prompt([{
|
|
1493
1003
|
type: "confirm",
|
|
@@ -1504,21 +1014,21 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
1504
1014
|
log.success(filePath);
|
|
1505
1015
|
}
|
|
1506
1016
|
await writeInitExtras(cwd);
|
|
1507
|
-
console.log(
|
|
1017
|
+
console.log(chalk3.bold.green(`
|
|
1508
1018
|
${ko.init.done}`));
|
|
1509
|
-
console.log(
|
|
1019
|
+
console.log(chalk3.dim(`
|
|
1510
1020
|
${ko.init.nextSteps}`));
|
|
1511
1021
|
if (options.fromNotion) {
|
|
1512
1022
|
console.log(` 1. ${ko.init.notionReviewHint}`);
|
|
1513
1023
|
console.log(` 2. ${ko.init.gitHintLabel}`);
|
|
1514
|
-
console.log(` ${
|
|
1024
|
+
console.log(` ${chalk3.cyan(ko.init.gitHintCommand)}`);
|
|
1515
1025
|
console.log(` 3. ${ko.init.startDev}
|
|
1516
1026
|
`);
|
|
1517
1027
|
} else {
|
|
1518
1028
|
console.log(` 1. ${ko.init.fillHint}`);
|
|
1519
1029
|
console.log(` 2. ${ko.init.prdHint}`);
|
|
1520
1030
|
console.log(` 3. ${ko.init.gitHintLabel}`);
|
|
1521
|
-
console.log(` ${
|
|
1031
|
+
console.log(` ${chalk3.cyan(ko.init.gitHintCommand)}`);
|
|
1522
1032
|
console.log(` 4. ${ko.init.startDev}
|
|
1523
1033
|
`);
|
|
1524
1034
|
}
|
|
@@ -1566,15 +1076,15 @@ var VHK_PACKAGE_SCRIPTS = {
|
|
|
1566
1076
|
doctor: "vhk doctor"
|
|
1567
1077
|
};
|
|
1568
1078
|
function enhancePackageScripts(projectDir) {
|
|
1569
|
-
const pkgPath =
|
|
1570
|
-
if (!
|
|
1079
|
+
const pkgPath = path2.join(projectDir, "package.json");
|
|
1080
|
+
if (!fs2.existsSync(pkgPath)) return false;
|
|
1571
1081
|
const pkg = readJsonFile(pkgPath);
|
|
1572
1082
|
pkg.scripts = { ...VHK_PACKAGE_SCRIPTS, ...pkg.scripts };
|
|
1573
|
-
|
|
1083
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1574
1084
|
return true;
|
|
1575
1085
|
}
|
|
1576
1086
|
async function writeInitExtras(projectDir) {
|
|
1577
|
-
const commandsPath =
|
|
1087
|
+
const commandsPath = path2.join(projectDir, "COMMANDS.md");
|
|
1578
1088
|
if (fileExists(commandsPath)) {
|
|
1579
1089
|
const { overwrite } = await inquirer2.prompt([{
|
|
1580
1090
|
type: "confirm",
|
|
@@ -1599,16 +1109,16 @@ async function writeInitExtras(projectDir) {
|
|
|
1599
1109
|
|
|
1600
1110
|
// src/commands/recap.ts
|
|
1601
1111
|
import inquirer3 from "inquirer";
|
|
1602
|
-
import
|
|
1603
|
-
import
|
|
1604
|
-
import
|
|
1112
|
+
import chalk4 from "chalk";
|
|
1113
|
+
import fs4 from "fs";
|
|
1114
|
+
import path5 from "path";
|
|
1605
1115
|
|
|
1606
1116
|
// src/lib/git.ts
|
|
1607
|
-
import
|
|
1608
|
-
import simpleGit from "simple-git";
|
|
1117
|
+
import path3 from "path";
|
|
1118
|
+
import { simpleGit } from "simple-git";
|
|
1609
1119
|
var git = simpleGit();
|
|
1610
1120
|
function isNoiseRecapPath(filePath) {
|
|
1611
|
-
const base =
|
|
1121
|
+
const base = path3.basename(filePath);
|
|
1612
1122
|
if (base.includes("${") || base.includes("`")) return true;
|
|
1613
1123
|
if (/^\d+(\.\d+)?$/.test(base)) return true;
|
|
1614
1124
|
if (/^(pnpm|vhk|npm|node|yarn)$/i.test(base)) return true;
|
|
@@ -1647,7 +1157,16 @@ async function getSessionDiff(since) {
|
|
|
1647
1157
|
const sinceDate = since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1648
1158
|
try {
|
|
1649
1159
|
const diffSummary = await git.diffSummary([`--since=${sinceDate}`]);
|
|
1650
|
-
|
|
1160
|
+
const normalized = diffSummary.files.map((f) => ({
|
|
1161
|
+
file: f.file,
|
|
1162
|
+
insertions: "insertions" in f ? f.insertions : 0,
|
|
1163
|
+
deletions: "deletions" in f ? f.deletions : 0
|
|
1164
|
+
}));
|
|
1165
|
+
return buildSessionDiffFromSummary({
|
|
1166
|
+
insertions: diffSummary.insertions,
|
|
1167
|
+
deletions: diffSummary.deletions,
|
|
1168
|
+
files: normalized
|
|
1169
|
+
});
|
|
1651
1170
|
} catch {
|
|
1652
1171
|
return { filesChanged: 0, insertions: 0, deletions: 0, files: [] };
|
|
1653
1172
|
}
|
|
@@ -1685,8 +1204,8 @@ async function hasAnyCommits() {
|
|
|
1685
1204
|
}
|
|
1686
1205
|
|
|
1687
1206
|
// src/lib/adr.ts
|
|
1688
|
-
import
|
|
1689
|
-
import
|
|
1207
|
+
import fs3 from "fs";
|
|
1208
|
+
import path4 from "path";
|
|
1690
1209
|
var ADR_RULES = [
|
|
1691
1210
|
{
|
|
1692
1211
|
title: "\uC758\uC874\uC131 \uBCC0\uACBD",
|
|
@@ -1729,20 +1248,20 @@ function detectAdrCandidates(diff2) {
|
|
|
1729
1248
|
return candidates;
|
|
1730
1249
|
}
|
|
1731
1250
|
function nextAdrNumber(adrDir) {
|
|
1732
|
-
if (!
|
|
1733
|
-
const nums =
|
|
1251
|
+
if (!fs3.existsSync(adrDir)) return 1;
|
|
1252
|
+
const nums = fs3.readdirSync(adrDir).map((name) => name.match(/^ADR-(\d+)/i)?.[1]).filter((n) => Boolean(n)).map((n) => parseInt(n, 10));
|
|
1734
1253
|
return nums.length ? Math.max(...nums) + 1 : 1;
|
|
1735
1254
|
}
|
|
1736
1255
|
function slugify(title) {
|
|
1737
1256
|
return title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9가-힣-]/g, "").slice(0, 40) || "decision";
|
|
1738
1257
|
}
|
|
1739
1258
|
function createAdrFile(cwd, title, context2, decision, consequences) {
|
|
1740
|
-
const adrDir =
|
|
1741
|
-
if (!
|
|
1259
|
+
const adrDir = path4.join(cwd, "docs", "adr");
|
|
1260
|
+
if (!fs3.existsSync(adrDir)) fs3.mkdirSync(adrDir, { recursive: true });
|
|
1742
1261
|
const num = nextAdrNumber(adrDir);
|
|
1743
1262
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1744
1263
|
const fileName = `ADR-${String(num).padStart(3, "0")}-${slugify(title)}.md`;
|
|
1745
|
-
const filePath =
|
|
1264
|
+
const filePath = path4.join(adrDir, fileName);
|
|
1746
1265
|
const content = [
|
|
1747
1266
|
"---",
|
|
1748
1267
|
`id: ADR-${String(num).padStart(3, "0")}`,
|
|
@@ -1768,51 +1287,51 @@ function createAdrFile(cwd, title, context2, decision, consequences) {
|
|
|
1768
1287
|
"---",
|
|
1769
1288
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1770
1289
|
].join("\n");
|
|
1771
|
-
|
|
1290
|
+
fs3.writeFileSync(filePath, content, "utf-8");
|
|
1772
1291
|
return filePath;
|
|
1773
1292
|
}
|
|
1774
1293
|
|
|
1775
1294
|
// src/commands/recap.ts
|
|
1776
1295
|
async function recap(options = {}) {
|
|
1777
|
-
console.log(
|
|
1296
|
+
console.log(chalk4.bold(`
|
|
1778
1297
|
${ko.recap.title}
|
|
1779
1298
|
`));
|
|
1780
1299
|
if (!await isGitRepo()) {
|
|
1781
|
-
console.log(
|
|
1300
|
+
console.log(chalk4.red(ko.recap.noRepo));
|
|
1782
1301
|
return;
|
|
1783
1302
|
}
|
|
1784
1303
|
if (!await hasAnyCommits()) {
|
|
1785
|
-
console.log(
|
|
1786
|
-
console.log(
|
|
1304
|
+
console.log(chalk4.yellow("\u26A0\uFE0F \uC544\uC9C1 \uCEE4\uBC0B\uC774 \uC5C6\uC5B4\uC694."));
|
|
1305
|
+
console.log(chalk4.gray(" \uD30C\uC77C\uC744 \uCD94\uAC00\uD558\uACE0 `vhk save` \uB610\uB294 `git commit`\uC73C\uB85C \uCCAB \uCEE4\uBC0B\uC744 \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
1787
1306
|
return;
|
|
1788
1307
|
}
|
|
1789
1308
|
printSecurityWarnings();
|
|
1790
|
-
console.log(
|
|
1309
|
+
console.log(chalk4.dim(`${ko.recap.analyzing}
|
|
1791
1310
|
`));
|
|
1792
1311
|
const since = options.since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1793
1312
|
const diff2 = await getSessionDiff(since);
|
|
1794
1313
|
const commits = await getRecentCommits(10, since);
|
|
1795
1314
|
if (diff2.filesChanged === 0 && commits.length === 0) {
|
|
1796
|
-
console.log(
|
|
1315
|
+
console.log(chalk4.yellow(ko.recap.noChanges));
|
|
1797
1316
|
return;
|
|
1798
1317
|
}
|
|
1799
|
-
console.log(
|
|
1800
|
-
console.log(` \uD30C\uC77C: ${
|
|
1801
|
-
console.log(` \uCD94\uAC00: ${
|
|
1318
|
+
console.log(chalk4.bold("\u{1F4CA} \uBCC0\uACBD \uC694\uC57D:"));
|
|
1319
|
+
console.log(` \uD30C\uC77C: ${chalk4.cyan(String(diff2.filesChanged))}\uAC1C \uBCC0\uACBD`);
|
|
1320
|
+
console.log(` \uCD94\uAC00: ${chalk4.green("+" + diff2.insertions)} / \uC0AD\uC81C: ${chalk4.red("-" + diff2.deletions)}`);
|
|
1802
1321
|
if (diff2.files.length > 0) {
|
|
1803
|
-
console.log(
|
|
1322
|
+
console.log(chalk4.dim("\n \uBCC0\uACBD \uD30C\uC77C:"));
|
|
1804
1323
|
diff2.files.slice(0, 15).forEach((f) => {
|
|
1805
|
-
const icon = f.status === "new" ?
|
|
1324
|
+
const icon = f.status === "new" ? chalk4.green("\u{1F195}") : f.status === "deleted" ? chalk4.red("\u{1F5D1}\uFE0F") : chalk4.yellow("\u270F\uFE0F");
|
|
1806
1325
|
console.log(` ${icon} ${f.file}`);
|
|
1807
1326
|
});
|
|
1808
1327
|
if (diff2.files.length > 15) {
|
|
1809
|
-
console.log(
|
|
1328
|
+
console.log(chalk4.dim(` ... \uC678 ${diff2.files.length - 15}\uAC1C`));
|
|
1810
1329
|
}
|
|
1811
1330
|
}
|
|
1812
1331
|
if (commits.length > 0) {
|
|
1813
|
-
console.log(
|
|
1332
|
+
console.log(chalk4.dim("\n \uCD5C\uADFC \uCEE4\uBC0B:"));
|
|
1814
1333
|
commits.slice(0, 5).forEach((c) => {
|
|
1815
|
-
console.log(
|
|
1334
|
+
console.log(chalk4.dim(` \u2022 ${c.message}`));
|
|
1816
1335
|
});
|
|
1817
1336
|
}
|
|
1818
1337
|
console.log("");
|
|
@@ -1841,12 +1360,12 @@ ${ko.recap.title}
|
|
|
1841
1360
|
}
|
|
1842
1361
|
]);
|
|
1843
1362
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1844
|
-
const logDir =
|
|
1845
|
-
if (!
|
|
1846
|
-
const existing =
|
|
1363
|
+
const logDir = path5.join(process.cwd(), "docs", "log");
|
|
1364
|
+
if (!fs4.existsSync(logDir)) fs4.mkdirSync(logDir, { recursive: true });
|
|
1365
|
+
const existing = fs4.readdirSync(logDir).filter((f) => f.startsWith(today));
|
|
1847
1366
|
const sessionNum = existing.length + 1;
|
|
1848
1367
|
const fileName = `${today}-session-${sessionNum}.md`;
|
|
1849
|
-
const filePath =
|
|
1368
|
+
const filePath = path5.join(logDir, fileName);
|
|
1850
1369
|
const fileList = diff2.files.map((f) => `| ${f.file} | ${f.status} |`).join("\n");
|
|
1851
1370
|
const commitList = commits.slice(0, 10).map((c) => `- \`${c.hash.slice(0, 7)}\` ${c.message}`).join("\n");
|
|
1852
1371
|
const content = [
|
|
@@ -1877,14 +1396,14 @@ ${ko.recap.title}
|
|
|
1877
1396
|
"---",
|
|
1878
1397
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1879
1398
|
].join("\n");
|
|
1880
|
-
|
|
1399
|
+
fs4.writeFileSync(filePath, content, "utf-8");
|
|
1881
1400
|
const adrCandidates = detectAdrCandidates(diff2);
|
|
1882
1401
|
if (adrCandidates.length > 0) {
|
|
1883
|
-
console.log(
|
|
1402
|
+
console.log(chalk4.cyan.bold(`
|
|
1884
1403
|
${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
1885
1404
|
for (const candidate of adrCandidates) {
|
|
1886
|
-
console.log(
|
|
1887
|
-
candidate.files.forEach((f) => console.log(
|
|
1405
|
+
console.log(chalk4.cyan(` \u2022 ${candidate.title}: ${candidate.context}`));
|
|
1406
|
+
candidate.files.forEach((f) => console.log(chalk4.dim(` ${f}`)));
|
|
1888
1407
|
}
|
|
1889
1408
|
const { createAdr } = await inquirer3.prompt([{
|
|
1890
1409
|
type: "confirm",
|
|
@@ -1914,17 +1433,17 @@ ${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
|
1914
1433
|
adrAnswers.decision,
|
|
1915
1434
|
adrAnswers.consequences
|
|
1916
1435
|
);
|
|
1917
|
-
console.log(
|
|
1436
|
+
console.log(chalk4.green(` \u2705 ADR \uC0DD\uC131: ${path5.relative(process.cwd(), adrPath)}`));
|
|
1918
1437
|
}
|
|
1919
1438
|
}
|
|
1920
1439
|
}
|
|
1921
1440
|
const troubleshootingKeywords = /fix|bug|error|crash|hotfix|patch|revert|트러블|에러|버그|수정|핫픽스/i;
|
|
1922
1441
|
const troubleCommits = commits.filter((c) => troubleshootingKeywords.test(c.message));
|
|
1923
1442
|
if (troubleCommits.length > 0) {
|
|
1924
|
-
console.log(
|
|
1443
|
+
console.log(chalk4.yellow.bold(`
|
|
1925
1444
|
${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
1926
1445
|
troubleCommits.forEach((c) => {
|
|
1927
|
-
console.log(
|
|
1446
|
+
console.log(chalk4.dim(` \u2022 ${c.message}`));
|
|
1928
1447
|
});
|
|
1929
1448
|
const { createTroubleshoot } = await inquirer3.prompt([{
|
|
1930
1449
|
type: "confirm",
|
|
@@ -1933,8 +1452,8 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1933
1452
|
default: true
|
|
1934
1453
|
}]);
|
|
1935
1454
|
if (createTroubleshoot) {
|
|
1936
|
-
const tsDir =
|
|
1937
|
-
if (!
|
|
1455
|
+
const tsDir = path5.join(process.cwd(), "docs", "troubleshooting");
|
|
1456
|
+
if (!fs4.existsSync(tsDir)) fs4.mkdirSync(tsDir, { recursive: true });
|
|
1938
1457
|
const tsAnswers = await inquirer3.prompt([
|
|
1939
1458
|
{
|
|
1940
1459
|
type: "input",
|
|
@@ -1953,7 +1472,7 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1953
1472
|
}
|
|
1954
1473
|
]);
|
|
1955
1474
|
const tsFileName = `${today}-${tsAnswers.problem.slice(0, 30).replace(/[^a-zA-Z0-9가-힣]/g, "-")}.md`;
|
|
1956
|
-
const tsFilePath =
|
|
1475
|
+
const tsFilePath = path5.join(tsDir, tsFileName);
|
|
1957
1476
|
const tsContent = [
|
|
1958
1477
|
`# \uD2B8\uB7EC\uBE14\uC288\uD305: ${tsAnswers.problem}`,
|
|
1959
1478
|
"",
|
|
@@ -1974,15 +1493,15 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1974
1493
|
"---",
|
|
1975
1494
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1976
1495
|
].join("\n");
|
|
1977
|
-
|
|
1978
|
-
console.log(
|
|
1496
|
+
fs4.writeFileSync(tsFilePath, tsContent, "utf-8");
|
|
1497
|
+
console.log(chalk4.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${path5.relative(process.cwd(), tsFilePath)}`));
|
|
1979
1498
|
}
|
|
1980
1499
|
}
|
|
1981
|
-
console.log(
|
|
1500
|
+
console.log(chalk4.green.bold(`
|
|
1982
1501
|
${ko.recap.done}`));
|
|
1983
|
-
console.log(
|
|
1984
|
-
const claudeMdPath =
|
|
1985
|
-
if (
|
|
1502
|
+
console.log(chalk4.dim(` \u{1F4C4} ${path5.relative(process.cwd(), filePath)}`));
|
|
1503
|
+
const claudeMdPath = path5.join(process.cwd(), "CLAUDE.md");
|
|
1504
|
+
if (fs4.existsSync(claudeMdPath)) {
|
|
1986
1505
|
const { updateClaude } = await inquirer3.prompt([{
|
|
1987
1506
|
type: "confirm",
|
|
1988
1507
|
name: "updateClaude",
|
|
@@ -1990,7 +1509,7 @@ ${ko.recap.done}`));
|
|
|
1990
1509
|
default: true
|
|
1991
1510
|
}]);
|
|
1992
1511
|
if (updateClaude) {
|
|
1993
|
-
let claudeContent =
|
|
1512
|
+
let claudeContent = fs4.readFileSync(claudeMdPath, "utf-8");
|
|
1994
1513
|
claudeContent = claudeContent.replace(
|
|
1995
1514
|
/- \*\*마지막 업데이트:\*\*.*/,
|
|
1996
1515
|
`- **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${today}`
|
|
@@ -1999,8 +1518,8 @@ ${ko.recap.done}`));
|
|
|
1999
1518
|
/- \*\*다음 액션:\*\*.*/,
|
|
2000
1519
|
`- **\uB2E4\uC74C \uC561\uC158:** ${answers.nextTodo}`
|
|
2001
1520
|
);
|
|
2002
|
-
|
|
2003
|
-
console.log(
|
|
1521
|
+
fs4.writeFileSync(claudeMdPath, claudeContent, "utf-8");
|
|
1522
|
+
console.log(chalk4.green(" \u2705 CLAUDE.md \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC"));
|
|
2004
1523
|
}
|
|
2005
1524
|
}
|
|
2006
1525
|
const gitSaveCmd = process.platform === "win32" ? 'git add .; git commit -m "recap: \uC138\uC158 \uAE30\uB85D"' : 'git add . && git commit -m "recap: \uC138\uC158 \uAE30\uB85D"';
|
|
@@ -2012,9 +1531,9 @@ ${ko.recap.done}`));
|
|
|
2012
1531
|
}
|
|
2013
1532
|
|
|
2014
1533
|
// src/commands/sync.ts
|
|
2015
|
-
import
|
|
2016
|
-
import
|
|
2017
|
-
import
|
|
1534
|
+
import chalk5 from "chalk";
|
|
1535
|
+
import fs5 from "fs";
|
|
1536
|
+
import path6 from "path";
|
|
2018
1537
|
var CURSORRULES_KEYS = ["\uCF54\uB529 \uADDC\uCE59", "\uAE30\uC220 \uC2A4\uD0DD", "\uC544\uD0A4\uD14D\uCC98", "\uB514\uC790\uC778", "Anti-patterns", "\uCEE4\uBC0B"];
|
|
2019
1538
|
var CLAUDE_MD_KEYS = ["\uAE30\uB85D", "\uB85C\uADF8", "ADR", "\uD2B8\uB7EC\uBE14\uC288\uD305", "TIL", "/done", "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8"];
|
|
2020
1539
|
function parseRulesMd(content) {
|
|
@@ -2082,46 +1601,46 @@ function toClaudeMd(sections, existing) {
|
|
|
2082
1601
|
return lines.join("\n");
|
|
2083
1602
|
}
|
|
2084
1603
|
async function sync() {
|
|
2085
|
-
console.log(
|
|
1604
|
+
console.log(chalk5.bold(`
|
|
2086
1605
|
${ko.sync.title}
|
|
2087
1606
|
`));
|
|
2088
1607
|
const cwd = process.cwd();
|
|
2089
|
-
const rulesPath =
|
|
2090
|
-
if (!
|
|
2091
|
-
console.log(
|
|
2092
|
-
console.log(
|
|
2093
|
-
console.log(
|
|
1608
|
+
const rulesPath = path6.join(cwd, "RULES.md");
|
|
1609
|
+
if (!fs5.existsSync(rulesPath)) {
|
|
1610
|
+
console.log(chalk5.yellow(ko.sync.noRules));
|
|
1611
|
+
console.log(chalk5.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
|
|
1612
|
+
console.log(chalk5.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
|
|
2094
1613
|
console.log("");
|
|
2095
|
-
console.log(
|
|
2096
|
-
console.log(
|
|
2097
|
-
console.log(
|
|
2098
|
-
console.log(
|
|
2099
|
-
console.log(
|
|
2100
|
-
console.log(
|
|
1614
|
+
console.log(chalk5.dim(" RULES.md \uAE30\uBCF8 \uAD6C\uC870:"));
|
|
1615
|
+
console.log(chalk5.dim(" ## \uD504\uB85C\uC81D\uD2B8 \uC815\uCCB4\uC131"));
|
|
1616
|
+
console.log(chalk5.dim(" ## \uAE30\uC220 \uC2A4\uD0DD"));
|
|
1617
|
+
console.log(chalk5.dim(" ## \uCF54\uB529 \uADDC\uCE59"));
|
|
1618
|
+
console.log(chalk5.dim(" ## \uAE30\uB85D \uADDC\uCE59"));
|
|
1619
|
+
console.log(chalk5.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
|
|
2101
1620
|
return;
|
|
2102
1621
|
}
|
|
2103
|
-
const rulesContent =
|
|
1622
|
+
const rulesContent = fs5.readFileSync(rulesPath, "utf-8");
|
|
2104
1623
|
const sections = parseRulesMd(rulesContent);
|
|
2105
|
-
console.log(
|
|
1624
|
+
console.log(chalk5.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
|
|
2106
1625
|
const firstLine = rulesContent.split("\n")[0];
|
|
2107
1626
|
const projectName = firstLine.replace(/^#\s*/, "").replace(/\s*—.*/, "").trim() || "Project";
|
|
2108
|
-
const cursorrulesPath =
|
|
2109
|
-
|
|
2110
|
-
console.log(
|
|
2111
|
-
const claudePath =
|
|
2112
|
-
const existingClaude =
|
|
1627
|
+
const cursorrulesPath = path6.join(cwd, ".cursorrules");
|
|
1628
|
+
fs5.writeFileSync(cursorrulesPath, toCursorrules(sections, projectName), "utf-8");
|
|
1629
|
+
console.log(chalk5.green(` ${ko.sync.cursorrulesDone}`));
|
|
1630
|
+
const claudePath = path6.join(cwd, "CLAUDE.md");
|
|
1631
|
+
const existingClaude = fs5.existsSync(claudePath) ? fs5.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
|
|
2113
1632
|
|
|
2114
1633
|
## \uD604\uC7AC \uC0C1\uD0DC
|
|
2115
1634
|
- **Phase:** __FILL__
|
|
2116
1635
|
- **\uBE14\uB85C\uCEE4:** \uC5C6\uC74C
|
|
2117
1636
|
- **\uB2E4\uC74C \uC561\uC158:** __FILL__
|
|
2118
1637
|
- **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
|
|
2119
|
-
|
|
2120
|
-
console.log(
|
|
2121
|
-
console.log(
|
|
1638
|
+
fs5.writeFileSync(claudePath, toClaudeMd(sections, existingClaude), "utf-8");
|
|
1639
|
+
console.log(chalk5.green(` ${ko.sync.claudeDone}`));
|
|
1640
|
+
console.log(chalk5.bold.green(`
|
|
2122
1641
|
${ko.sync.done}`));
|
|
2123
|
-
console.log(
|
|
2124
|
-
console.log(
|
|
1642
|
+
console.log(chalk5.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md (\uC790\uB3D9 \uC0DD\uC131)"));
|
|
1643
|
+
console.log(chalk5.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
|
|
2125
1644
|
printNextStep({
|
|
2126
1645
|
message: "\uADDC\uCE59 \uB3D9\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C Cursor\uAC00 \uC0C8 \uADDC\uCE59\uC744 \uB530\uB985\uB2C8\uB2E4.",
|
|
2127
1646
|
command: "vhk \uC810\uAC80",
|
|
@@ -2131,15 +1650,15 @@ ${ko.sync.done}`));
|
|
|
2131
1650
|
|
|
2132
1651
|
// src/commands/check.ts
|
|
2133
1652
|
import chalk7 from "chalk";
|
|
2134
|
-
import
|
|
2135
|
-
import
|
|
1653
|
+
import path8 from "path";
|
|
1654
|
+
import fs7 from "fs";
|
|
2136
1655
|
|
|
2137
1656
|
// src/lib/rules-parser.ts
|
|
2138
|
-
import
|
|
2139
|
-
import
|
|
1657
|
+
import fs6 from "fs";
|
|
1658
|
+
import path7 from "path";
|
|
2140
1659
|
function parseRules(rulesPath) {
|
|
2141
|
-
if (!
|
|
2142
|
-
const content =
|
|
1660
|
+
if (!fs6.existsSync(rulesPath)) return [];
|
|
1661
|
+
const content = fs6.readFileSync(rulesPath, "utf-8");
|
|
2143
1662
|
const lines = content.split("\n");
|
|
2144
1663
|
const rules = [];
|
|
2145
1664
|
let currentSection = "";
|
|
@@ -2200,17 +1719,17 @@ function createNamingRule(id, section, desc, convention) {
|
|
|
2200
1719
|
description: desc,
|
|
2201
1720
|
check: (cwd) => {
|
|
2202
1721
|
const violations = [];
|
|
2203
|
-
const srcDir =
|
|
2204
|
-
if (!
|
|
1722
|
+
const srcDir = path7.join(cwd, "src");
|
|
1723
|
+
if (!fs6.existsSync(srcDir)) return violations;
|
|
2205
1724
|
walkFiles(srcDir, (filePath) => {
|
|
2206
|
-
const name =
|
|
1725
|
+
const name = path7.basename(filePath, path7.extname(filePath));
|
|
2207
1726
|
if (convention === "kebab-case" && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
2208
1727
|
if (!["index", "vite.config", "tsconfig"].includes(name)) {
|
|
2209
1728
|
violations.push({
|
|
2210
1729
|
ruleId: id,
|
|
2211
1730
|
severity: "warning",
|
|
2212
1731
|
message: `\uD30C\uC77C\uBA85\uC774 kebab-case\uAC00 \uC544\uB2D8: ${name}`,
|
|
2213
|
-
file:
|
|
1732
|
+
file: path7.relative(cwd, filePath)
|
|
2214
1733
|
});
|
|
2215
1734
|
}
|
|
2216
1735
|
}
|
|
@@ -2226,8 +1745,8 @@ function createStructureRule(id, section, desc, expectedPath) {
|
|
|
2226
1745
|
type: "structure",
|
|
2227
1746
|
description: desc,
|
|
2228
1747
|
check: (cwd) => {
|
|
2229
|
-
const fullPath =
|
|
2230
|
-
if (!
|
|
1748
|
+
const fullPath = path7.join(cwd, expectedPath);
|
|
1749
|
+
if (!fs6.existsSync(fullPath)) {
|
|
2231
1750
|
return [{
|
|
2232
1751
|
ruleId: id,
|
|
2233
1752
|
severity: "error",
|
|
@@ -2247,11 +1766,11 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
2247
1766
|
pattern: new RegExp(escapeRegex(pattern), "i"),
|
|
2248
1767
|
check: (cwd) => {
|
|
2249
1768
|
const violations = [];
|
|
2250
|
-
const srcDir =
|
|
2251
|
-
if (!
|
|
1769
|
+
const srcDir = path7.join(cwd, "src");
|
|
1770
|
+
if (!fs6.existsSync(srcDir)) return violations;
|
|
2252
1771
|
const regex = new RegExp(escapeRegex(pattern), "i");
|
|
2253
1772
|
walkFiles(srcDir, (filePath) => {
|
|
2254
|
-
const fileContent =
|
|
1773
|
+
const fileContent = fs6.readFileSync(filePath, "utf-8");
|
|
2255
1774
|
const fileLines = fileContent.split("\n");
|
|
2256
1775
|
fileLines.forEach((line, idx) => {
|
|
2257
1776
|
if (regex.test(line)) {
|
|
@@ -2259,7 +1778,7 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
2259
1778
|
ruleId: id,
|
|
2260
1779
|
severity: type === "banned" ? "error" : "warning",
|
|
2261
1780
|
message: type === "banned" ? `\uAE08\uC9C0 \uD328\uD134 \uBC1C\uACAC: \`${pattern}\`` : `\uD544\uC218 \uD328\uD134 \uB204\uB77D: \`${pattern}\``,
|
|
2262
|
-
file:
|
|
1781
|
+
file: path7.relative(cwd, filePath),
|
|
2263
1782
|
line: idx + 1
|
|
2264
1783
|
});
|
|
2265
1784
|
}
|
|
@@ -2271,9 +1790,9 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
2271
1790
|
};
|
|
2272
1791
|
}
|
|
2273
1792
|
function walkFiles(dir, callback) {
|
|
2274
|
-
const entries =
|
|
1793
|
+
const entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2275
1794
|
for (const entry of entries) {
|
|
2276
|
-
const fullPath =
|
|
1795
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2277
1796
|
if (entry.isDirectory()) {
|
|
2278
1797
|
if (!["node_modules", ".git", "dist", ".next"].includes(entry.name)) {
|
|
2279
1798
|
walkFiles(fullPath, callback);
|
|
@@ -2287,45 +1806,386 @@ function escapeRegex(str) {
|
|
|
2287
1806
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2288
1807
|
}
|
|
2289
1808
|
|
|
2290
|
-
// src/commands/
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
1809
|
+
// src/commands/goal.ts
|
|
1810
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
|
|
1811
|
+
import { join as join2 } from "path";
|
|
1812
|
+
import chalk6 from "chalk";
|
|
1813
|
+
|
|
1814
|
+
// src/lib/goal-frontmatter.ts
|
|
1815
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
1816
|
+
import { join } from "path";
|
|
1817
|
+
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
1818
|
+
function parseFrontmatter(content) {
|
|
1819
|
+
const m = content.match(FRONTMATTER_RE);
|
|
1820
|
+
if (!m) return { frontmatter: {}, body: content };
|
|
1821
|
+
const fm = parseSimpleYaml(m[1]);
|
|
1822
|
+
const body = (m[2] ?? "").replace(/^\r?\n+/, "");
|
|
1823
|
+
return { frontmatter: fm, body };
|
|
1824
|
+
}
|
|
1825
|
+
function parseSimpleYaml(yaml) {
|
|
1826
|
+
const out = {};
|
|
1827
|
+
const lines = yaml.split(/\r?\n/);
|
|
1828
|
+
for (const raw of lines) {
|
|
1829
|
+
const line = raw.trim();
|
|
1830
|
+
if (!line || line.startsWith("#")) continue;
|
|
1831
|
+
const idx = line.indexOf(":");
|
|
1832
|
+
if (idx <= 0) continue;
|
|
1833
|
+
const key = line.slice(0, idx).trim();
|
|
1834
|
+
let value = line.slice(idx + 1).trim();
|
|
1835
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1836
|
+
value = value.slice(1, -1);
|
|
1837
|
+
}
|
|
1838
|
+
if (key === "id" || key === "vhk_format") {
|
|
1839
|
+
const n = Number(value);
|
|
1840
|
+
out[key] = Number.isFinite(n) ? n : void 0;
|
|
1841
|
+
} else {
|
|
1842
|
+
out[key] = value;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return out;
|
|
1846
|
+
}
|
|
1847
|
+
function parseGoalFile(filePath) {
|
|
1848
|
+
if (!existsSync(filePath)) return null;
|
|
1849
|
+
try {
|
|
1850
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1851
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
1852
|
+
return { filePath, frontmatter, body };
|
|
1853
|
+
} catch {
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
function listGoals(goalsDir) {
|
|
1858
|
+
if (!existsSync(goalsDir)) return [];
|
|
1859
|
+
let entries;
|
|
1860
|
+
try {
|
|
1861
|
+
entries = readdirSync(goalsDir);
|
|
1862
|
+
} catch {
|
|
1863
|
+
return [];
|
|
1864
|
+
}
|
|
1865
|
+
const parsed = [];
|
|
1866
|
+
for (const name of entries) {
|
|
1867
|
+
if (!name.endsWith(".md")) continue;
|
|
1868
|
+
if (name === "_meta.md") continue;
|
|
1869
|
+
const fp = join(goalsDir, name);
|
|
1870
|
+
try {
|
|
1871
|
+
if (!statSync(fp).isFile()) continue;
|
|
1872
|
+
} catch {
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
const g = parseGoalFile(fp);
|
|
1876
|
+
if (!g) continue;
|
|
1877
|
+
if (g.frontmatter.type !== "goal") continue;
|
|
1878
|
+
if (typeof g.frontmatter.id !== "number") continue;
|
|
1879
|
+
parsed.push(g);
|
|
1880
|
+
}
|
|
1881
|
+
parsed.sort((a, b) => a.frontmatter.id - b.frontmatter.id);
|
|
1882
|
+
return parsed;
|
|
1883
|
+
}
|
|
1884
|
+
function updateFrontmatterStatus(content, newStatus, extraFields) {
|
|
1885
|
+
const m = content.match(FRONTMATTER_RE);
|
|
1886
|
+
if (!m) return content;
|
|
1887
|
+
const fmRaw = m[1];
|
|
1888
|
+
const body = m[2] ?? "";
|
|
1889
|
+
const lines = fmRaw.split(/\r?\n/);
|
|
1890
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1891
|
+
let hadStatus = false;
|
|
1892
|
+
const updated = lines.map((raw) => {
|
|
1893
|
+
const trimmed = raw.trim();
|
|
1894
|
+
if (!trimmed || trimmed.startsWith("#")) return raw;
|
|
1895
|
+
const idx = trimmed.indexOf(":");
|
|
1896
|
+
if (idx <= 0) return raw;
|
|
1897
|
+
const key = trimmed.slice(0, idx).trim();
|
|
1898
|
+
seenKeys.add(key);
|
|
1899
|
+
if (key === "status") {
|
|
1900
|
+
hadStatus = true;
|
|
1901
|
+
return `status: ${newStatus}`;
|
|
1902
|
+
}
|
|
1903
|
+
if (extraFields && key in extraFields) {
|
|
1904
|
+
return `${key}: ${extraFields[key]}`;
|
|
1905
|
+
}
|
|
1906
|
+
return raw;
|
|
1907
|
+
});
|
|
1908
|
+
if (!hadStatus) updated.push(`status: ${newStatus}`);
|
|
1909
|
+
if (extraFields) {
|
|
1910
|
+
for (const [k, v] of Object.entries(extraFields)) {
|
|
1911
|
+
if (!seenKeys.has(k)) updated.push(`${k}: ${v}`);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
return `---
|
|
1915
|
+
${updated.join("\n")}
|
|
1916
|
+
---
|
|
1917
|
+
${body}`;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// src/commands/goal.ts
|
|
1921
|
+
var GOALS_DIR = "goals";
|
|
1922
|
+
var STATE_DIR = "docs/state";
|
|
1923
|
+
var SCRIPTS_DIR = "scripts";
|
|
1924
|
+
var STATUS_ICON = {
|
|
1925
|
+
NOT_STARTED: "\u26AA",
|
|
1926
|
+
IN_PROGRESS: "\u{1F7E1}",
|
|
1927
|
+
DONE: "\u2705",
|
|
1928
|
+
BLOCKED: "\u{1F6D1}"
|
|
1929
|
+
};
|
|
1930
|
+
function selectActiveId(goals) {
|
|
1931
|
+
const ip = goals.find((g) => g.frontmatter.status === "IN_PROGRESS");
|
|
1932
|
+
if (ip && typeof ip.frontmatter.id === "number") return ip.frontmatter.id;
|
|
1933
|
+
const ns = goals.find(
|
|
1934
|
+
(g) => g.frontmatter.status === "NOT_STARTED" || g.frontmatter.status === void 0
|
|
1935
|
+
);
|
|
1936
|
+
if (ns && typeof ns.frontmatter.id === "number") return ns.frontmatter.id;
|
|
1937
|
+
return null;
|
|
1938
|
+
}
|
|
1939
|
+
function resolveGoalId(optId, goals) {
|
|
1940
|
+
if (optId !== void 0) {
|
|
1941
|
+
const n = Number(optId);
|
|
1942
|
+
if (!Number.isFinite(n)) return null;
|
|
1943
|
+
return n;
|
|
1944
|
+
}
|
|
1945
|
+
return selectActiveId(goals);
|
|
1946
|
+
}
|
|
1947
|
+
async function goalList() {
|
|
1948
|
+
console.log(chalk6.bold(`
|
|
1949
|
+
${ko.goal.listTitle}
|
|
2294
1950
|
`));
|
|
2295
|
-
const
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
console.log(
|
|
2299
|
-
console.log(chalk7.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
1951
|
+
const goals = listGoals(GOALS_DIR);
|
|
1952
|
+
if (goals.length === 0) {
|
|
1953
|
+
console.log(chalk6.yellow(" \u{1F4ED} goals/ \uB514\uB809\uD1A0\uB9AC\uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
1954
|
+
console.log(chalk6.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
|
|
2300
1955
|
return;
|
|
2301
1956
|
}
|
|
2302
|
-
const
|
|
2303
|
-
|
|
1957
|
+
for (const g of goals) {
|
|
1958
|
+
const fm = g.frontmatter;
|
|
1959
|
+
const status2 = fm.status ?? "NOT_STARTED";
|
|
1960
|
+
const icon = STATUS_ICON[status2] ?? "?";
|
|
1961
|
+
const id = String(fm.id).padStart(2);
|
|
1962
|
+
const pri = String(fm.priority ?? "--").padEnd(3);
|
|
1963
|
+
const ver = String(fm.version ?? "----").padEnd(6);
|
|
1964
|
+
console.log(
|
|
1965
|
+
` [${id}] ${icon} ${status2.padEnd(11)} ${pri} ${ver} ${fm.title ?? "(untitled)"}`
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
async function goalNext() {
|
|
1970
|
+
console.log(chalk6.bold(`
|
|
1971
|
+
${ko.goal.nextTitle}
|
|
2304
1972
|
`));
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
1973
|
+
const goals = listGoals(GOALS_DIR);
|
|
1974
|
+
const activeId = selectActiveId(goals);
|
|
1975
|
+
if (activeId === null) {
|
|
1976
|
+
console.log(chalk6.green(" \u{1F389} \uBAA8\uB4E0 goal \uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"));
|
|
2308
1977
|
return;
|
|
2309
1978
|
}
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
1979
|
+
const active = goals.find((g) => g.frontmatter.id === activeId);
|
|
1980
|
+
if (!active) return;
|
|
1981
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1982
|
+
const text = [
|
|
1983
|
+
"# Next Task",
|
|
1984
|
+
"",
|
|
1985
|
+
`_Auto-updated ${ts} via \`vhk goal next\`._`,
|
|
1986
|
+
"",
|
|
1987
|
+
"```",
|
|
1988
|
+
`TASK: Goal ${activeId} \u2014 ${active.frontmatter.title ?? ""}`,
|
|
1989
|
+
` status: ${active.frontmatter.status ?? "NOT_STARTED"}`,
|
|
1990
|
+
` priority: ${active.frontmatter.priority ?? "--"}`,
|
|
1991
|
+
` file: ${active.filePath}`,
|
|
1992
|
+
"```",
|
|
1993
|
+
""
|
|
1994
|
+
].join("\n");
|
|
1995
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
1996
|
+
writeFileSync(join2(STATE_DIR, "next-task.md"), text, "utf-8");
|
|
1997
|
+
console.log(
|
|
1998
|
+
chalk6.green(
|
|
1999
|
+
` \u2705 next-task.md \uAC31\uC2E0 \u2014 Goal ${activeId}: ${active.frontmatter.title ?? ""}`
|
|
2000
|
+
)
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
var META_TEMPLATE = `---
|
|
2004
|
+
vhk_format: 1
|
|
2005
|
+
type: meta
|
|
2006
|
+
project: __FILL__
|
|
2007
|
+
version: v0.1
|
|
2008
|
+
---
|
|
2009
|
+
|
|
2010
|
+
# Common Gates
|
|
2011
|
+
|
|
2012
|
+
1. (\uD504\uB85C\uC81D\uD2B8\uBCC4 \uAC8C\uC774\uD2B8 \u2014 \uC608: pnpm test:run)
|
|
2013
|
+
|
|
2014
|
+
## Forbidden Actions (\uC804\uC5ED)
|
|
2015
|
+
|
|
2016
|
+
- (\uD574\uB2F9 \uC0AC\uD56D)
|
|
2017
|
+
`;
|
|
2018
|
+
var STATE_NEXT_TASK_TEMPLATE = "# Next Task\n\n```\nTASK: (vhk goal next \uB85C \uC790\uB3D9 \uAC31\uC2E0)\n```\n";
|
|
2019
|
+
var STATE_BLOCKERS_TEMPLATE = "# Blockers\n\n_Append-only. \uD574\uACB0 \uD56D\uBAA9\uC740 ~~\uCDE8\uC18C\uC120~~\uC73C\uB85C \uD45C\uAE30._\n";
|
|
2020
|
+
var STATE_LEARNINGS_TEMPLATE = "# Learnings\n\n_Append-only. \uD55C \uC904 = \uD55C \uAD50\uD6C8._\n";
|
|
2021
|
+
async function goalInit() {
|
|
2022
|
+
console.log(chalk6.bold(`
|
|
2023
|
+
${ko.goal.initTitle}
|
|
2024
|
+
`));
|
|
2025
|
+
const targets = [
|
|
2026
|
+
{ path: join2(GOALS_DIR, "_meta.md"), content: META_TEMPLATE },
|
|
2027
|
+
{ path: join2(STATE_DIR, "next-task.md"), content: STATE_NEXT_TASK_TEMPLATE },
|
|
2028
|
+
{ path: join2(STATE_DIR, "blockers.md"), content: STATE_BLOCKERS_TEMPLATE },
|
|
2029
|
+
{ path: join2(STATE_DIR, "learnings.md"), content: STATE_LEARNINGS_TEMPLATE }
|
|
2030
|
+
];
|
|
2031
|
+
mkdirSync(GOALS_DIR, { recursive: true });
|
|
2032
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
2033
|
+
let created = 0;
|
|
2034
|
+
let skipped = 0;
|
|
2035
|
+
for (const t2 of targets) {
|
|
2036
|
+
if (existsSync2(t2.path)) {
|
|
2037
|
+
console.log(chalk6.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${t2.path}`));
|
|
2038
|
+
skipped++;
|
|
2317
2039
|
} else {
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
const icon = v.severity === "error" ? chalk7.red("\u2716") : v.severity === "warning" ? chalk7.yellow("\u26A0") : chalk7.blue("\u2139");
|
|
2322
|
-
console.log(` ${icon} ${v.message}${loc}`);
|
|
2323
|
-
});
|
|
2324
|
-
allViolations.push(...violations);
|
|
2040
|
+
writeFileSync(t2.path, t2.content, "utf-8");
|
|
2041
|
+
console.log(chalk6.green(` \u2713 created: ${t2.path}`));
|
|
2042
|
+
created++;
|
|
2325
2043
|
}
|
|
2326
2044
|
}
|
|
2327
|
-
console.log(
|
|
2328
|
-
|
|
2045
|
+
console.log(chalk6.bold(`
|
|
2046
|
+
\u{1F4CA} created=${created} skipped=${skipped}`));
|
|
2047
|
+
if (created > 0) {
|
|
2048
|
+
printNextStep({
|
|
2049
|
+
message: "goals/ \uAD6C\uC870 \uC2A4\uCE90\uD3F4\uB529 \uC644\uB8CC!",
|
|
2050
|
+
command: "vhk goal list",
|
|
2051
|
+
cursorHint: "goal \uBAA9\uB85D \uBCF4\uC5EC\uC918"
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
function runGate(scriptPath) {
|
|
2056
|
+
const r = safeExecFile("bash", [scriptPath]);
|
|
2057
|
+
return { ok: r.ok, out: r.out, err: r.ok ? "" : r.err };
|
|
2058
|
+
}
|
|
2059
|
+
async function goalCheck(opts) {
|
|
2060
|
+
console.log(chalk6.bold(`
|
|
2061
|
+
${ko.goal.checkTitle}
|
|
2062
|
+
`));
|
|
2063
|
+
const goals = listGoals(GOALS_DIR);
|
|
2064
|
+
const id = resolveGoalId(opts.id, goals);
|
|
2065
|
+
if (id === null) {
|
|
2066
|
+
console.log(
|
|
2067
|
+
chalk6.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
|
|
2068
|
+
);
|
|
2069
|
+
process.exitCode = 1;
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
const scriptPath = join2(SCRIPTS_DIR, `check-goal-${id}.sh`);
|
|
2073
|
+
if (!existsSync2(scriptPath)) {
|
|
2074
|
+
console.log(chalk6.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: ${scriptPath}`));
|
|
2075
|
+
process.exitCode = 1;
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
console.log(chalk6.dim(` \u25B6 bash ${scriptPath}
|
|
2079
|
+
`));
|
|
2080
|
+
const gate2 = runGate(scriptPath);
|
|
2081
|
+
if (gate2.out) console.log(gate2.out);
|
|
2082
|
+
if (gate2.ok) {
|
|
2083
|
+
console.log(chalk6.green(`
|
|
2084
|
+
\u2705 Goal ${id} \uAC8C\uC774\uD2B8 \uD1B5\uACFC`));
|
|
2085
|
+
} else {
|
|
2086
|
+
console.log(chalk6.red(`
|
|
2087
|
+
\u274C Goal ${id} \uAC8C\uC774\uD2B8 \uC2E4\uD328`));
|
|
2088
|
+
if (gate2.err && !gate2.out) console.log(chalk6.dim(gate2.err.slice(0, 500)));
|
|
2089
|
+
process.exitCode = 1;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
async function goalDone(opts) {
|
|
2093
|
+
console.log(chalk6.bold(`
|
|
2094
|
+
${ko.goal.doneTitle}
|
|
2095
|
+
`));
|
|
2096
|
+
const goals = listGoals(GOALS_DIR);
|
|
2097
|
+
const id = resolveGoalId(opts.id, goals);
|
|
2098
|
+
if (id === null) {
|
|
2099
|
+
console.log(
|
|
2100
|
+
chalk6.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
|
|
2101
|
+
);
|
|
2102
|
+
process.exitCode = 1;
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const target = goals.find((g) => g.frontmatter.id === id);
|
|
2106
|
+
if (!target) {
|
|
2107
|
+
console.log(chalk6.red(` \u274C goal id ${id} \uD30C\uC77C \uC5C6\uC74C.`));
|
|
2108
|
+
process.exitCode = 1;
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
const scriptPath = join2(SCRIPTS_DIR, `check-goal-${id}.sh`);
|
|
2112
|
+
if (!existsSync2(scriptPath)) {
|
|
2113
|
+
console.log(chalk6.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C \u2014 done \uCC98\uB9AC \uAC70\uBD80: ${scriptPath}`));
|
|
2114
|
+
process.exitCode = 1;
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
console.log(chalk6.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: bash ${scriptPath}
|
|
2118
|
+
`));
|
|
2119
|
+
const gate2 = runGate(scriptPath);
|
|
2120
|
+
if (gate2.out) console.log(gate2.out);
|
|
2121
|
+
if (!gate2.ok) {
|
|
2122
|
+
console.log(
|
|
2123
|
+
chalk6.red(
|
|
2124
|
+
`
|
|
2125
|
+
\u274C \uAC8C\uC774\uD2B8 \uC2E4\uD328 \u2014 frontmatter \uBCC0\uACBD \uC5C6\uC774 \uC885\uB8CC. (Forbidden: \uC2E4\uD328 = \uBCF4\uC874)`
|
|
2126
|
+
)
|
|
2127
|
+
);
|
|
2128
|
+
process.exitCode = 1;
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
const content = readFileSync2(target.filePath, "utf-8");
|
|
2132
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2133
|
+
const updated = updateFrontmatterStatus(content, "DONE", { completed: today });
|
|
2134
|
+
writeFileSync(target.filePath, updated, "utf-8");
|
|
2135
|
+
console.log(chalk6.green(`
|
|
2136
|
+
\u2705 Goal ${id} \u2192 DONE (completed: ${today})`));
|
|
2137
|
+
printNextStep({
|
|
2138
|
+
message: `Goal ${id} \uC644\uB8CC! \uB2E4\uC74C goal \uB85C:`,
|
|
2139
|
+
command: "vhk goal next",
|
|
2140
|
+
cursorHint: "\uB2E4\uC74C goal \uC54C\uB824\uC918"
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/commands/check.ts
|
|
2145
|
+
async function check(opts = {}) {
|
|
2146
|
+
if (opts.goal !== void 0) {
|
|
2147
|
+
return goalCheck({ id: opts.goal });
|
|
2148
|
+
}
|
|
2149
|
+
return checkRules();
|
|
2150
|
+
}
|
|
2151
|
+
async function checkRules() {
|
|
2152
|
+
console.log(chalk7.bold(`
|
|
2153
|
+
${ko.check.title}
|
|
2154
|
+
`));
|
|
2155
|
+
const cwd = process.cwd();
|
|
2156
|
+
const rulesPath = path8.join(cwd, "RULES.md");
|
|
2157
|
+
if (!fs7.existsSync(rulesPath)) {
|
|
2158
|
+
console.log(chalk7.yellow(ko.check.noRules));
|
|
2159
|
+
console.log(chalk7.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const rules = parseRules(rulesPath);
|
|
2163
|
+
console.log(chalk7.dim(` \u{1F4CF} ${rules.length}\uAC1C \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 \uAC10\uC9C0
|
|
2164
|
+
`));
|
|
2165
|
+
if (rules.length === 0) {
|
|
2166
|
+
console.log(chalk7.yellow(ko.check.noAutoRules));
|
|
2167
|
+
console.log(chalk7.dim(" RULES.md\uC5D0 \uD30C\uC77C \uC774\uB984\xB7\uD3F4\uB354 \uADDC\uCE59\uC744 \uC801\uC73C\uBA74 \uC790\uB3D9\uC73C\uB85C \uC810\uAC80\uD574\uC694."));
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
const allViolations = [];
|
|
2171
|
+
let passCount = 0;
|
|
2172
|
+
for (const rule of rules) {
|
|
2173
|
+
const violations = rule.check(cwd);
|
|
2174
|
+
if (violations.length === 0) {
|
|
2175
|
+
console.log(chalk7.green(` \u2705 ${rule.id}`) + chalk7.dim(` \u2014 ${rule.description.slice(0, 60)}`));
|
|
2176
|
+
passCount++;
|
|
2177
|
+
} else {
|
|
2178
|
+
console.log(chalk7.red(` \u274C ${rule.id}`) + chalk7.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
|
|
2179
|
+
violations.forEach((v) => {
|
|
2180
|
+
const loc = v.file ? chalk7.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
|
|
2181
|
+
const icon = v.severity === "error" ? chalk7.red("\u2716") : v.severity === "warning" ? chalk7.yellow("\u26A0") : chalk7.blue("\u2139");
|
|
2182
|
+
console.log(` ${icon} ${v.message}${loc}`);
|
|
2183
|
+
});
|
|
2184
|
+
allViolations.push(...violations);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
console.log("");
|
|
2188
|
+
const errors = allViolations.filter((v) => v.severity === "error").length;
|
|
2329
2189
|
const warnings = allViolations.filter((v) => v.severity === "warning").length;
|
|
2330
2190
|
if (allViolations.length === 0) {
|
|
2331
2191
|
console.log(chalk7.green.bold(`${ko.check.allPassed} (${passCount}/${rules.length})`));
|
|
@@ -2352,217 +2212,20 @@ ${ko.check.title}
|
|
|
2352
2212
|
|
|
2353
2213
|
// src/commands/secure.ts
|
|
2354
2214
|
import chalk8 from "chalk";
|
|
2355
|
-
import
|
|
2356
|
-
import
|
|
2357
|
-
|
|
2358
|
-
// src/lib/scan-secrets.ts
|
|
2359
|
-
import fs10 from "fs";
|
|
2360
|
-
|
|
2361
|
-
// src/lib/secret-patterns.ts
|
|
2362
|
-
var SECRET_PATTERNS = [
|
|
2363
|
-
{
|
|
2364
|
-
id: "aws-access-key",
|
|
2365
|
-
name: "AWS Access Key",
|
|
2366
|
-
severity: "critical",
|
|
2367
|
-
pattern: /AKIA[0-9A-Z]{16}/
|
|
2368
|
-
},
|
|
2369
|
-
{
|
|
2370
|
-
id: "aws-secret-key",
|
|
2371
|
-
name: "AWS Secret Key",
|
|
2372
|
-
severity: "critical",
|
|
2373
|
-
pattern: /aws_secret_access_key\s*=\s*['"]?[A-Za-z0-9/+=]{40}['"]?/i
|
|
2374
|
-
},
|
|
2375
|
-
{
|
|
2376
|
-
id: "private-key",
|
|
2377
|
-
name: "Private Key",
|
|
2378
|
-
severity: "critical",
|
|
2379
|
-
pattern: /-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/
|
|
2380
|
-
},
|
|
2381
|
-
{
|
|
2382
|
-
id: "notion-token",
|
|
2383
|
-
name: "Notion Integration Token",
|
|
2384
|
-
severity: "critical",
|
|
2385
|
-
pattern: /secret_[A-Za-z0-9]{40,50}/
|
|
2386
|
-
},
|
|
2387
|
-
{
|
|
2388
|
-
id: "github-token",
|
|
2389
|
-
name: "GitHub Token",
|
|
2390
|
-
severity: "critical",
|
|
2391
|
-
pattern: /ghp_[A-Za-z0-9]{36,}/
|
|
2392
|
-
},
|
|
2393
|
-
{
|
|
2394
|
-
id: "openai-key",
|
|
2395
|
-
name: "OpenAI API Key",
|
|
2396
|
-
severity: "critical",
|
|
2397
|
-
pattern: /\bsk-(?:proj-|ant-api03-|live-)[A-Za-z0-9_-]{16,}\b/
|
|
2398
|
-
},
|
|
2399
|
-
{
|
|
2400
|
-
id: "generic-api-key",
|
|
2401
|
-
name: "Generic API Key",
|
|
2402
|
-
severity: "high",
|
|
2403
|
-
pattern: /(?:api[_-]?key|apikey|access[_-]?token)\s*[:=]\s*['"]?[A-Za-z0-9_\-]{16,}['"]?/i
|
|
2404
|
-
},
|
|
2405
|
-
{
|
|
2406
|
-
id: "password-inline",
|
|
2407
|
-
name: "Inline Password",
|
|
2408
|
-
severity: "high",
|
|
2409
|
-
pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]/i
|
|
2410
|
-
},
|
|
2411
|
-
{
|
|
2412
|
-
id: "jwt",
|
|
2413
|
-
name: "JWT Token",
|
|
2414
|
-
severity: "medium",
|
|
2415
|
-
pattern: /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/
|
|
2416
|
-
}
|
|
2417
|
-
];
|
|
2418
|
-
function maskSecret(value) {
|
|
2419
|
-
if (value.length <= 8) return "****";
|
|
2420
|
-
const visible = Math.min(8, value.length - 4);
|
|
2421
|
-
return value.slice(0, visible) + "****";
|
|
2422
|
-
}
|
|
2423
|
-
|
|
2424
|
-
// src/lib/scan-files.ts
|
|
2425
|
-
import fs9 from "fs";
|
|
2426
|
-
import path10 from "path";
|
|
2427
|
-
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
2428
|
-
"node_modules",
|
|
2429
|
-
".git",
|
|
2430
|
-
"dist",
|
|
2431
|
-
".next",
|
|
2432
|
-
".nuxt",
|
|
2433
|
-
"build",
|
|
2434
|
-
"coverage",
|
|
2435
|
-
"out",
|
|
2436
|
-
".turbo",
|
|
2437
|
-
".vercel",
|
|
2438
|
-
".cache",
|
|
2439
|
-
".pnpm",
|
|
2440
|
-
".idea",
|
|
2441
|
-
".claude",
|
|
2442
|
-
".cursor"
|
|
2443
|
-
]);
|
|
2444
|
-
var SKIP_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
2445
|
-
"pnpm-lock.yaml",
|
|
2446
|
-
"package-lock.json",
|
|
2447
|
-
"yarn.lock"
|
|
2448
|
-
]);
|
|
2449
|
-
var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2450
|
-
".ts",
|
|
2451
|
-
".tsx",
|
|
2452
|
-
".js",
|
|
2453
|
-
".jsx",
|
|
2454
|
-
".mjs",
|
|
2455
|
-
".cjs",
|
|
2456
|
-
".json",
|
|
2457
|
-
".yaml",
|
|
2458
|
-
".yml",
|
|
2459
|
-
".toml"
|
|
2460
|
-
]);
|
|
2461
|
-
var MAX_SCAN_FILE_BYTES = 512 * 1024;
|
|
2462
|
-
function isScannableFileName(fileName) {
|
|
2463
|
-
if (SKIP_FILE_NAMES.has(fileName)) return false;
|
|
2464
|
-
if (fileName.startsWith(".env")) return true;
|
|
2465
|
-
return SCAN_EXTENSIONS.has(path10.extname(fileName).toLowerCase());
|
|
2466
|
-
}
|
|
2467
|
-
function walkProjectFiles(rootDir, onFile, ig = loadGitignore(rootDir)) {
|
|
2468
|
-
function walk(dir) {
|
|
2469
|
-
let entries;
|
|
2470
|
-
try {
|
|
2471
|
-
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
2472
|
-
} catch {
|
|
2473
|
-
return;
|
|
2474
|
-
}
|
|
2475
|
-
for (const entry of entries) {
|
|
2476
|
-
const fullPath = path10.join(dir, entry.name);
|
|
2477
|
-
const rel = path10.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
2478
|
-
if (entry.isDirectory()) {
|
|
2479
|
-
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
2480
|
-
if (isPathIgnored(ig, `${rel}/`)) continue;
|
|
2481
|
-
walk(fullPath);
|
|
2482
|
-
continue;
|
|
2483
|
-
}
|
|
2484
|
-
if (!isScannableFileName(entry.name)) continue;
|
|
2485
|
-
if (isPathIgnored(ig, rel)) continue;
|
|
2486
|
-
let size = 0;
|
|
2487
|
-
try {
|
|
2488
|
-
size = fs9.statSync(fullPath).size;
|
|
2489
|
-
} catch {
|
|
2490
|
-
continue;
|
|
2491
|
-
}
|
|
2492
|
-
if (size > MAX_SCAN_FILE_BYTES) continue;
|
|
2493
|
-
onFile(fullPath, rel);
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
walk(rootDir);
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
// src/lib/scan-secrets.ts
|
|
2500
|
-
var MAX_SECRET_FINDINGS = 200;
|
|
2501
|
-
var MAX_LINE_CHARS = 4e3;
|
|
2502
|
-
function globalPattern(pattern) {
|
|
2503
|
-
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
2504
|
-
return new RegExp(pattern.source, flags);
|
|
2505
|
-
}
|
|
2506
|
-
function findSecretsInLine(line, relPath, lineNum) {
|
|
2507
|
-
const found = [];
|
|
2508
|
-
const trimmed = line.trim();
|
|
2509
|
-
if (trimmed.startsWith("//") && trimmed.includes("example")) return found;
|
|
2510
|
-
if (trimmed.startsWith("#") && trimmed.includes("example")) return found;
|
|
2511
|
-
if (line.length > MAX_LINE_CHARS) return found;
|
|
2512
|
-
for (const pattern of SECRET_PATTERNS) {
|
|
2513
|
-
const regex = globalPattern(pattern.pattern);
|
|
2514
|
-
for (const match of line.matchAll(regex)) {
|
|
2515
|
-
found.push({
|
|
2516
|
-
patternId: pattern.id,
|
|
2517
|
-
patternName: pattern.name,
|
|
2518
|
-
severity: pattern.severity,
|
|
2519
|
-
file: relPath,
|
|
2520
|
-
line: lineNum,
|
|
2521
|
-
match: maskSecret(match[0])
|
|
2522
|
-
});
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
return found;
|
|
2526
|
-
}
|
|
2527
|
-
function scanProjectForSecrets(cwd) {
|
|
2528
|
-
const findings = [];
|
|
2529
|
-
let scannedFiles = 0;
|
|
2530
|
-
let truncated = false;
|
|
2531
|
-
walkProjectFiles(cwd, (filePath, relPath) => {
|
|
2532
|
-
scannedFiles++;
|
|
2533
|
-
const content = fs10.readFileSync(filePath, "utf-8");
|
|
2534
|
-
const lines = content.split("\n");
|
|
2535
|
-
lines.forEach((line, idx) => {
|
|
2536
|
-
if (truncated) return;
|
|
2537
|
-
const lineFindings = findSecretsInLine(line, relPath, idx + 1);
|
|
2538
|
-
for (const f of lineFindings) {
|
|
2539
|
-
findings.push(f);
|
|
2540
|
-
if (findings.length >= MAX_SECRET_FINDINGS) {
|
|
2541
|
-
truncated = true;
|
|
2542
|
-
return;
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
});
|
|
2546
|
-
});
|
|
2547
|
-
return { findings, scannedFiles, truncated };
|
|
2548
|
-
}
|
|
2549
|
-
function filterSevereFindings(findings) {
|
|
2550
|
-
return findings.filter((f) => f.severity === "critical" || f.severity === "high");
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
// src/commands/secure.ts
|
|
2215
|
+
import fs8 from "fs";
|
|
2216
|
+
import path9 from "path";
|
|
2554
2217
|
async function secure() {
|
|
2555
2218
|
console.log(chalk8.bold(`
|
|
2556
2219
|
${ko.secure.title}
|
|
2557
2220
|
`));
|
|
2558
2221
|
const cwd = process.cwd();
|
|
2559
|
-
const gitignorePath =
|
|
2560
|
-
const hasGitignore =
|
|
2222
|
+
const gitignorePath = path9.join(cwd, ".gitignore");
|
|
2223
|
+
const hasGitignore = fs8.existsSync(gitignorePath);
|
|
2561
2224
|
if (!hasGitignore) {
|
|
2562
2225
|
console.log(chalk8.yellow(` ${ko.secure.noGitignore}`));
|
|
2563
2226
|
console.log(chalk8.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
|
|
2564
2227
|
} else {
|
|
2565
|
-
const gitignoreContent =
|
|
2228
|
+
const gitignoreContent = fs8.readFileSync(gitignorePath, "utf-8");
|
|
2566
2229
|
if (!gitignoreContent.includes(".env")) {
|
|
2567
2230
|
console.log(chalk8.yellow(` ${ko.secure.noEnvInGitignore}`));
|
|
2568
2231
|
console.log(chalk8.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
|
|
@@ -2626,27 +2289,24 @@ ${ko.secure.title}
|
|
|
2626
2289
|
|
|
2627
2290
|
// src/commands/doctor.ts
|
|
2628
2291
|
import chalk9 from "chalk";
|
|
2629
|
-
import
|
|
2630
|
-
import
|
|
2631
|
-
import path12 from "path";
|
|
2292
|
+
import fs9 from "fs";
|
|
2293
|
+
import path10 from "path";
|
|
2632
2294
|
import { fileURLToPath } from "url";
|
|
2633
2295
|
function checkCommand(name, command, hint) {
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
return { name, command, ok: false, hint };
|
|
2639
|
-
}
|
|
2296
|
+
const result = safeExecFile(command, ["--version"]);
|
|
2297
|
+
if (!result.ok) return { name, command, ok: false, hint };
|
|
2298
|
+
const version = result.out.split("\n")[0];
|
|
2299
|
+
return { name, command, version, ok: true, hint };
|
|
2640
2300
|
}
|
|
2641
2301
|
function getVhkVersion() {
|
|
2642
|
-
const dir =
|
|
2302
|
+
const dir = path10.dirname(fileURLToPath(import.meta.url));
|
|
2643
2303
|
const candidates = [
|
|
2644
|
-
|
|
2645
|
-
|
|
2304
|
+
path10.join(dir, "../package.json"),
|
|
2305
|
+
path10.join(dir, "../../package.json")
|
|
2646
2306
|
];
|
|
2647
2307
|
for (const pkgPath of candidates) {
|
|
2648
2308
|
try {
|
|
2649
|
-
if (
|
|
2309
|
+
if (fs9.existsSync(pkgPath)) {
|
|
2650
2310
|
const pkg = readJsonFile(pkgPath);
|
|
2651
2311
|
return pkg.version;
|
|
2652
2312
|
}
|
|
@@ -2657,17 +2317,11 @@ function getVhkVersion() {
|
|
|
2657
2317
|
return void 0;
|
|
2658
2318
|
}
|
|
2659
2319
|
function fetchLatestNpmVersion(packageName) {
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
}).trim();
|
|
2666
|
-
if (/^\d+\.\d+\.\d+/.test(result)) return result;
|
|
2667
|
-
return void 0;
|
|
2668
|
-
} catch {
|
|
2669
|
-
return void 0;
|
|
2670
|
-
}
|
|
2320
|
+
const result = safeExecFile("npm", ["view", packageName, "version"]);
|
|
2321
|
+
if (!result.ok) return void 0;
|
|
2322
|
+
const out = result.out;
|
|
2323
|
+
if (/^\d+\.\d+\.\d+/.test(out)) return out;
|
|
2324
|
+
return void 0;
|
|
2671
2325
|
}
|
|
2672
2326
|
function compareSemver(a, b) {
|
|
2673
2327
|
const parse = (v) => v.replace(/^v/i, "").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -2723,13 +2377,13 @@ ${ko.doctor.title}
|
|
|
2723
2377
|
{ name: ".env", hint: ".gitignore\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778" }
|
|
2724
2378
|
];
|
|
2725
2379
|
for (const file of projectFiles) {
|
|
2726
|
-
const exists =
|
|
2380
|
+
const exists = fs9.existsSync(path10.join(cwd, file.name));
|
|
2727
2381
|
if (exists) {
|
|
2728
2382
|
console.log(chalk9.green(` \u2705 ${file.name}`));
|
|
2729
2383
|
if (file.name === ".env") {
|
|
2730
|
-
const gitignorePath =
|
|
2731
|
-
if (
|
|
2732
|
-
const gitignore =
|
|
2384
|
+
const gitignorePath = path10.join(cwd, ".gitignore");
|
|
2385
|
+
if (fs9.existsSync(gitignorePath)) {
|
|
2386
|
+
const gitignore = fs9.readFileSync(gitignorePath, "utf-8");
|
|
2733
2387
|
if (!gitignore.includes(".env")) {
|
|
2734
2388
|
console.log(chalk9.yellow(` ${ko.doctor.envNotIgnored}`));
|
|
2735
2389
|
}
|
|
@@ -2761,8 +2415,8 @@ ${ko.doctor.title}
|
|
|
2761
2415
|
// src/commands/ship.ts
|
|
2762
2416
|
import chalk10 from "chalk";
|
|
2763
2417
|
import inquirer4 from "inquirer";
|
|
2764
|
-
import
|
|
2765
|
-
import
|
|
2418
|
+
import fs10 from "fs";
|
|
2419
|
+
import path11 from "path";
|
|
2766
2420
|
var CHECKLIST = [
|
|
2767
2421
|
{ id: "build", questionKey: "checkBuild", hintKey: "hintBuild" },
|
|
2768
2422
|
{ id: "test", questionKey: "checkTest", hintKey: "hintTest" },
|
|
@@ -2775,9 +2429,9 @@ function sanitizeVersion(version) {
|
|
|
2775
2429
|
return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
|
|
2776
2430
|
}
|
|
2777
2431
|
function updateChangelogUnreleased(cwd, version, date) {
|
|
2778
|
-
const changelogPath =
|
|
2779
|
-
if (!
|
|
2780
|
-
const content =
|
|
2432
|
+
const changelogPath = path11.join(cwd, "CHANGELOG.md");
|
|
2433
|
+
if (!fs10.existsSync(changelogPath)) return { status: "missing" };
|
|
2434
|
+
const content = fs10.readFileSync(changelogPath, "utf-8");
|
|
2781
2435
|
const unreleasedHeading = /^## \[Unreleased\][^\n]*$/m;
|
|
2782
2436
|
if (!unreleasedHeading.test(content)) return { status: "no-unreleased" };
|
|
2783
2437
|
const blankUnreleased = [
|
|
@@ -2794,7 +2448,7 @@ function updateChangelogUnreleased(cwd, version, date) {
|
|
|
2794
2448
|
`## [${version}] \u2014 ${date}`
|
|
2795
2449
|
].join("\n");
|
|
2796
2450
|
const updated = content.replace(unreleasedHeading, blankUnreleased);
|
|
2797
|
-
|
|
2451
|
+
fs10.writeFileSync(changelogPath, updated, "utf-8");
|
|
2798
2452
|
return { status: "updated", version };
|
|
2799
2453
|
}
|
|
2800
2454
|
async function ship() {
|
|
@@ -2851,12 +2505,12 @@ ${ko.ship.title}
|
|
|
2851
2505
|
{ type: "input", name: "learned", message: ko.ship.questionLearned },
|
|
2852
2506
|
{ type: "input", name: "nextVersion", message: ko.ship.questionNext }
|
|
2853
2507
|
]);
|
|
2854
|
-
const buildLogDir =
|
|
2855
|
-
if (!
|
|
2508
|
+
const buildLogDir = path11.join(cwd, "docs", "build-log");
|
|
2509
|
+
if (!fs10.existsSync(buildLogDir)) fs10.mkdirSync(buildLogDir, { recursive: true });
|
|
2856
2510
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2857
2511
|
const versionSlug = sanitizeVersion(retro.version);
|
|
2858
2512
|
const fileName = `${today}-v${versionSlug}.md`;
|
|
2859
|
-
const filePath =
|
|
2513
|
+
const filePath = path11.join(buildLogDir, fileName);
|
|
2860
2514
|
const content = [
|
|
2861
2515
|
`# \uBE4C\uB4DC \uB85C\uADF8: v${versionSlug}`,
|
|
2862
2516
|
"",
|
|
@@ -2885,9 +2539,9 @@ ${ko.ship.title}
|
|
|
2885
2539
|
"---",
|
|
2886
2540
|
`*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
2887
2541
|
].join("\n");
|
|
2888
|
-
|
|
2542
|
+
fs10.writeFileSync(filePath, content, "utf-8");
|
|
2889
2543
|
console.log(chalk10.green(`
|
|
2890
|
-
${ko.ship.buildLogDone(
|
|
2544
|
+
${ko.ship.buildLogDone(path11.relative(cwd, filePath))}`));
|
|
2891
2545
|
const changelogResult = updateChangelogUnreleased(cwd, versionSlug, today);
|
|
2892
2546
|
if (changelogResult.status === "updated") {
|
|
2893
2547
|
log.success(ko.ship.changelogUpdated(changelogResult.version));
|
|
@@ -3187,8 +2841,8 @@ ${t("undo.recentHeader")}`));
|
|
|
3187
2841
|
|
|
3188
2842
|
// src/commands/status.ts
|
|
3189
2843
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
3190
|
-
import
|
|
3191
|
-
import
|
|
2844
|
+
import fs11 from "fs";
|
|
2845
|
+
import path12 from "path";
|
|
3192
2846
|
import chalk13 from "chalk";
|
|
3193
2847
|
function countFileChanges(porcelain) {
|
|
3194
2848
|
const lines = porcelain.split("\n").filter(Boolean);
|
|
@@ -3227,8 +2881,8 @@ function parseRecentCommitLines(logOutput) {
|
|
|
3227
2881
|
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3228
2882
|
}
|
|
3229
2883
|
function readProjectPackage(cwd = process.cwd()) {
|
|
3230
|
-
const pkgPath =
|
|
3231
|
-
if (!
|
|
2884
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
2885
|
+
if (!fs11.existsSync(pkgPath)) return null;
|
|
3232
2886
|
try {
|
|
3233
2887
|
const pkg = readJsonFile(pkgPath);
|
|
3234
2888
|
if (!pkg.name && !pkg.version) return null;
|
|
@@ -3304,14 +2958,10 @@ async function status() {
|
|
|
3304
2958
|
}
|
|
3305
2959
|
|
|
3306
2960
|
// src/commands/diff.ts
|
|
3307
|
-
import { execFileSync as execFileSync5, execSync as execSync2 } from "child_process";
|
|
3308
2961
|
import chalk14 from "chalk";
|
|
3309
2962
|
function gitOut2(args) {
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
} catch {
|
|
3313
|
-
return "";
|
|
3314
|
-
}
|
|
2963
|
+
const r = safeExecFile("git", args);
|
|
2964
|
+
return r.ok ? r.out : "";
|
|
3315
2965
|
}
|
|
3316
2966
|
function parseDiffStat(stat) {
|
|
3317
2967
|
const files = [];
|
|
@@ -3354,9 +3004,7 @@ async function diff() {
|
|
|
3354
3004
|
console.log(chalk14.bold(`
|
|
3355
3005
|
\u{1F50D} ${t("diff.title")}`));
|
|
3356
3006
|
console.log(chalk14.gray("\u2500".repeat(40)));
|
|
3357
|
-
|
|
3358
|
-
execSync2("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
3359
|
-
} catch {
|
|
3007
|
+
if (!safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok) {
|
|
3360
3008
|
console.log(chalk14.red(`\u274C ${t("diff.notGitRepo")}`));
|
|
3361
3009
|
return;
|
|
3362
3010
|
}
|
|
@@ -3397,8 +3045,8 @@ ${t("diff.summaryHeader")}`));
|
|
|
3397
3045
|
}
|
|
3398
3046
|
|
|
3399
3047
|
// src/commands/mcp-init.ts
|
|
3400
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
3401
|
-
import { join, dirname } from "path";
|
|
3048
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3049
|
+
import { join as join3, dirname } from "path";
|
|
3402
3050
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3403
3051
|
import chalk15 from "chalk";
|
|
3404
3052
|
function resolveMcpEntryPoint() {
|
|
@@ -3406,8 +3054,8 @@ function resolveMcpEntryPoint() {
|
|
|
3406
3054
|
const here = fileURLToPath2(import.meta.url);
|
|
3407
3055
|
const dir = dirname(here);
|
|
3408
3056
|
for (const rel of [["mcp", "index.js"], ["..", "mcp", "index.js"]]) {
|
|
3409
|
-
const candidate =
|
|
3410
|
-
if (
|
|
3057
|
+
const candidate = join3(dir, ...rel);
|
|
3058
|
+
if (existsSync3(candidate)) return candidate;
|
|
3411
3059
|
}
|
|
3412
3060
|
} catch {
|
|
3413
3061
|
}
|
|
@@ -3415,17 +3063,17 @@ function resolveMcpEntryPoint() {
|
|
|
3415
3063
|
const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
|
|
3416
3064
|
if (typeof url === "string") {
|
|
3417
3065
|
const p = fileURLToPath2(url);
|
|
3418
|
-
if (
|
|
3066
|
+
if (existsSync3(p)) return p;
|
|
3419
3067
|
}
|
|
3420
3068
|
} catch {
|
|
3421
3069
|
}
|
|
3422
3070
|
try {
|
|
3423
|
-
const pkgPath =
|
|
3424
|
-
if (
|
|
3071
|
+
const pkgPath = join3(process.cwd(), "package.json");
|
|
3072
|
+
if (existsSync3(pkgPath)) {
|
|
3425
3073
|
const pkg = readJsonFile(pkgPath);
|
|
3426
3074
|
if (pkg.name === "@byh3071/vhk") {
|
|
3427
|
-
const local =
|
|
3428
|
-
if (
|
|
3075
|
+
const local = join3(process.cwd(), "dist", "mcp", "index.js");
|
|
3076
|
+
if (existsSync3(local)) return local;
|
|
3429
3077
|
}
|
|
3430
3078
|
}
|
|
3431
3079
|
} catch {
|
|
@@ -3442,14 +3090,14 @@ function resolveVhkMcpEntry() {
|
|
|
3442
3090
|
async function mcpInit() {
|
|
3443
3091
|
console.log(chalk15.bold("\n\u{1F50C} " + t("mcp.initTitle")));
|
|
3444
3092
|
console.log(chalk15.gray("\u2500".repeat(40)));
|
|
3445
|
-
const cursorDir =
|
|
3446
|
-
if (!
|
|
3447
|
-
|
|
3093
|
+
const cursorDir = join3(process.cwd(), ".cursor");
|
|
3094
|
+
if (!existsSync3(cursorDir)) {
|
|
3095
|
+
mkdirSync2(cursorDir, { recursive: true });
|
|
3448
3096
|
}
|
|
3449
|
-
const configPath =
|
|
3097
|
+
const configPath = join3(cursorDir, "mcp.json");
|
|
3450
3098
|
const vhkEntry = resolveVhkMcpEntry();
|
|
3451
3099
|
let config;
|
|
3452
|
-
if (
|
|
3100
|
+
if (existsSync3(configPath)) {
|
|
3453
3101
|
try {
|
|
3454
3102
|
const parsed = readJsonFile(configPath);
|
|
3455
3103
|
config = {
|
|
@@ -3462,7 +3110,7 @@ async function mcpInit() {
|
|
|
3462
3110
|
} else {
|
|
3463
3111
|
config = { mcpServers: { vhk: vhkEntry } };
|
|
3464
3112
|
}
|
|
3465
|
-
|
|
3113
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3466
3114
|
console.log(chalk15.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
|
|
3467
3115
|
console.log(chalk15.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
|
|
3468
3116
|
console.log(` ${configPath}`);
|
|
@@ -3472,236 +3120,10 @@ async function mcpInit() {
|
|
|
3472
3120
|
console.log(chalk15.gray('\n\u{1F4A1} \uC608: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC54C\uB824\uC918" \u2192 Cursor\uAC00 vhk status \uD638\uCD9C'));
|
|
3473
3121
|
}
|
|
3474
3122
|
|
|
3475
|
-
// src/commands/
|
|
3476
|
-
import { existsSync as
|
|
3123
|
+
// src/commands/design.ts
|
|
3124
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3477
3125
|
import chalk16 from "chalk";
|
|
3478
3126
|
import inquirer7 from "inquirer";
|
|
3479
|
-
var PLATFORMS = {
|
|
3480
|
-
vercel: {
|
|
3481
|
-
name: "Vercel",
|
|
3482
|
-
detectFiles: ["vercel.json", ".vercel"],
|
|
3483
|
-
command: "vercel",
|
|
3484
|
-
commandArgs: ["--prod"],
|
|
3485
|
-
checkArgs: ["--version"],
|
|
3486
|
-
installHint: "npm i -g vercel"
|
|
3487
|
-
},
|
|
3488
|
-
netlify: {
|
|
3489
|
-
name: "Netlify",
|
|
3490
|
-
detectFiles: ["netlify.toml", ".netlify"],
|
|
3491
|
-
command: "netlify",
|
|
3492
|
-
commandArgs: ["deploy", "--prod"],
|
|
3493
|
-
checkArgs: ["--version"],
|
|
3494
|
-
installHint: "npm i -g netlify-cli"
|
|
3495
|
-
},
|
|
3496
|
-
cloudflare: {
|
|
3497
|
-
name: "Cloudflare Workers",
|
|
3498
|
-
detectFiles: ["wrangler.toml"],
|
|
3499
|
-
command: "wrangler",
|
|
3500
|
-
commandArgs: ["deploy"],
|
|
3501
|
-
checkArgs: ["--version"],
|
|
3502
|
-
installHint: "npm i -g wrangler"
|
|
3503
|
-
}
|
|
3504
|
-
};
|
|
3505
|
-
function detectPlatform() {
|
|
3506
|
-
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
3507
|
-
for (const file of config.detectFiles) {
|
|
3508
|
-
if (existsSync2(file)) return key;
|
|
3509
|
-
}
|
|
3510
|
-
}
|
|
3511
|
-
return null;
|
|
3512
|
-
}
|
|
3513
|
-
function isCLIAvailable(cmd, checkArgs) {
|
|
3514
|
-
return safeExecFile(cmd, checkArgs).ok;
|
|
3515
|
-
}
|
|
3516
|
-
async function deploy() {
|
|
3517
|
-
console.log(chalk16.bold("\n\u{1F680} " + t("deploy.title")));
|
|
3518
|
-
console.log(chalk16.gray("\u2500".repeat(40)));
|
|
3519
|
-
let platform = detectPlatform();
|
|
3520
|
-
if (platform) {
|
|
3521
|
-
console.log(chalk16.cyan(`
|
|
3522
|
-
\u{1F50D} \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${PLATFORMS[platform].name}`));
|
|
3523
|
-
} else {
|
|
3524
|
-
const { selected } = await inquirer7.prompt([
|
|
3525
|
-
{
|
|
3526
|
-
type: "list",
|
|
3527
|
-
name: "selected",
|
|
3528
|
-
message: t("deploy.selectPlatform"),
|
|
3529
|
-
choices: [
|
|
3530
|
-
{ name: "\u25B2 Vercel", value: "vercel" },
|
|
3531
|
-
{ name: "\u25C6 Netlify", value: "netlify" },
|
|
3532
|
-
{ name: "\u2601 Cloudflare Workers", value: "cloudflare" }
|
|
3533
|
-
]
|
|
3534
|
-
}
|
|
3535
|
-
]);
|
|
3536
|
-
platform = selected;
|
|
3537
|
-
}
|
|
3538
|
-
const config = PLATFORMS[platform];
|
|
3539
|
-
if (!isCLIAvailable(config.command, config.checkArgs)) {
|
|
3540
|
-
console.log(chalk16.red(`
|
|
3541
|
-
\u274C ${config.name} CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
3542
|
-
console.log(chalk16.yellow(` \u2192 ${config.installHint}`));
|
|
3543
|
-
return;
|
|
3544
|
-
}
|
|
3545
|
-
const { confirm } = await inquirer7.prompt([
|
|
3546
|
-
{
|
|
3547
|
-
type: "confirm",
|
|
3548
|
-
name: "confirm",
|
|
3549
|
-
message: `${config.name}\uC5D0 \uD504\uB85C\uB355\uC158 \uBC30\uD3EC\uD560\uAE4C\uC694?`,
|
|
3550
|
-
default: true
|
|
3551
|
-
}
|
|
3552
|
-
]);
|
|
3553
|
-
if (!confirm) {
|
|
3554
|
-
console.log(chalk16.gray("\uCDE8\uC18C\uB428"));
|
|
3555
|
-
return;
|
|
3556
|
-
}
|
|
3557
|
-
console.log(chalk16.cyan(`
|
|
3558
|
-
${t("deploy.deploying")}
|
|
3559
|
-
`));
|
|
3560
|
-
const result = safeExecFileStream(config.command, config.commandArgs);
|
|
3561
|
-
if (result.ok) {
|
|
3562
|
-
console.log(chalk16.green(`
|
|
3563
|
-
\u2705 ${t("deploy.success")}`));
|
|
3564
|
-
printNextStep({
|
|
3565
|
-
message: "\uBC30\uD3EC \uC644\uB8CC! \uC0AC\uC774\uD2B8\uB97C \uD655\uC778\uD558\uC138\uC694.",
|
|
3566
|
-
command: "vhk status",
|
|
3567
|
-
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
3568
|
-
});
|
|
3569
|
-
} else {
|
|
3570
|
-
console.log(chalk16.red(`
|
|
3571
|
-
\u274C ${t("deploy.failed")}`));
|
|
3572
|
-
console.log(chalk16.red(result.err));
|
|
3573
|
-
}
|
|
3574
|
-
}
|
|
3575
|
-
|
|
3576
|
-
// src/commands/publish.ts
|
|
3577
|
-
import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3578
|
-
import chalk17 from "chalk";
|
|
3579
|
-
import inquirer8 from "inquirer";
|
|
3580
|
-
import ora2 from "ora";
|
|
3581
|
-
function bumpVersion(current, type) {
|
|
3582
|
-
const [major, minor, patch] = current.split(".").map((n) => parseInt(n, 10) || 0);
|
|
3583
|
-
switch (type) {
|
|
3584
|
-
case "major":
|
|
3585
|
-
return `${major + 1}.0.0`;
|
|
3586
|
-
case "minor":
|
|
3587
|
-
return `${major}.${minor + 1}.0`;
|
|
3588
|
-
case "patch":
|
|
3589
|
-
return `${major}.${minor}.${patch + 1}`;
|
|
3590
|
-
}
|
|
3591
|
-
}
|
|
3592
|
-
async function publish() {
|
|
3593
|
-
console.log(chalk17.bold("\n\u{1F4E6} " + t("publish.title")));
|
|
3594
|
-
console.log(chalk17.gray("\u2500".repeat(40)));
|
|
3595
|
-
if (!existsSync3("package.json")) {
|
|
3596
|
-
console.log(chalk17.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3597
|
-
return;
|
|
3598
|
-
}
|
|
3599
|
-
let pkg;
|
|
3600
|
-
try {
|
|
3601
|
-
pkg = readJsonFile("package.json");
|
|
3602
|
-
} catch {
|
|
3603
|
-
console.log(chalk17.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
|
|
3604
|
-
return;
|
|
3605
|
-
}
|
|
3606
|
-
const currentVersion = pkg.version || "0.0.0";
|
|
3607
|
-
console.log(chalk17.cyan(`
|
|
3608
|
-
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${currentVersion}`));
|
|
3609
|
-
const { bumpType } = await inquirer8.prompt([
|
|
3610
|
-
{
|
|
3611
|
-
type: "list",
|
|
3612
|
-
name: "bumpType",
|
|
3613
|
-
message: t("publish.selectBump"),
|
|
3614
|
-
choices: [
|
|
3615
|
-
{ name: `\u{1F527} patch (${bumpVersion(currentVersion, "patch")}) \u2014 \uBC84\uADF8 \uC218\uC815`, value: "patch" },
|
|
3616
|
-
{ name: `\u2728 minor (${bumpVersion(currentVersion, "minor")}) \u2014 \uC0C8 \uAE30\uB2A5`, value: "minor" },
|
|
3617
|
-
{ name: `\u{1F4A5} major (${bumpVersion(currentVersion, "major")}) \u2014 \uD638\uD658\uC131 \uBCC0\uACBD`, value: "major" }
|
|
3618
|
-
]
|
|
3619
|
-
}
|
|
3620
|
-
]);
|
|
3621
|
-
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
3622
|
-
console.log(chalk17.cyan(`
|
|
3623
|
-
\u{1F195} \uC0C8 \uBC84\uC804: v${newVersion}`));
|
|
3624
|
-
pkg.version = newVersion;
|
|
3625
|
-
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3626
|
-
console.log(chalk17.green("\u2705 package.json \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8"));
|
|
3627
|
-
const buildSpinner = ora2(t("publish.building")).start();
|
|
3628
|
-
const buildResult = safeExecFile("pnpm", ["build"]);
|
|
3629
|
-
if (!buildResult.ok) {
|
|
3630
|
-
buildSpinner.fail(t("publish.buildFailed"));
|
|
3631
|
-
console.log(chalk17.red(buildResult.err.slice(0, 500)));
|
|
3632
|
-
pkg.version = currentVersion;
|
|
3633
|
-
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3634
|
-
return;
|
|
3635
|
-
}
|
|
3636
|
-
buildSpinner.succeed(t("publish.buildSuccess"));
|
|
3637
|
-
const testSpinner = ora2(t("publish.testing")).start();
|
|
3638
|
-
const testResult = safeExecFile("pnpm", ["test", "--run"]);
|
|
3639
|
-
if (!testResult.ok) {
|
|
3640
|
-
testSpinner.fail(t("publish.testFailed"));
|
|
3641
|
-
console.log(chalk17.red(testResult.err.slice(0, 500)));
|
|
3642
|
-
pkg.version = currentVersion;
|
|
3643
|
-
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3644
|
-
return;
|
|
3645
|
-
}
|
|
3646
|
-
testSpinner.succeed(t("publish.testSuccess"));
|
|
3647
|
-
const { confirm } = await inquirer8.prompt([
|
|
3648
|
-
{
|
|
3649
|
-
type: "confirm",
|
|
3650
|
-
name: "confirm",
|
|
3651
|
-
message: `v${newVersion}\uC744 npm\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?`,
|
|
3652
|
-
default: true
|
|
3653
|
-
}
|
|
3654
|
-
]);
|
|
3655
|
-
if (!confirm) {
|
|
3656
|
-
pkg.version = currentVersion;
|
|
3657
|
-
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3658
|
-
console.log(chalk17.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
|
|
3659
|
-
return;
|
|
3660
|
-
}
|
|
3661
|
-
console.log(chalk17.cyan(`
|
|
3662
|
-
\u{1F4E4} ${t("publish.publishing")}`));
|
|
3663
|
-
console.log(chalk17.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)"));
|
|
3664
|
-
const pubResult = safeExecFileStream("npm", ["publish", "--access", "public"]);
|
|
3665
|
-
if (!pubResult.ok) {
|
|
3666
|
-
console.log(chalk17.red(`
|
|
3667
|
-
\u2716 ${t("publish.publishFailed")}`));
|
|
3668
|
-
console.log(chalk17.red(pubResult.err.slice(0, 500)));
|
|
3669
|
-
pkg.version = currentVersion;
|
|
3670
|
-
writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3671
|
-
console.log(chalk17.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
|
|
3672
|
-
return;
|
|
3673
|
-
}
|
|
3674
|
-
console.log(chalk17.green(`
|
|
3675
|
-
\u2714 ${t("publish.publishSuccess")}`));
|
|
3676
|
-
const addResult = safeExecFile("git", ["add", "package.json"]);
|
|
3677
|
-
if (addResult.ok) {
|
|
3678
|
-
safeExecFile("git", ["commit", "-m", `chore: release v${newVersion}`]);
|
|
3679
|
-
const tagResult = safeExecFile("git", ["tag", `v${newVersion}`]);
|
|
3680
|
-
if (tagResult.ok) {
|
|
3681
|
-
const pushResult = safeExecFile("git", ["push"]);
|
|
3682
|
-
const pushTagsResult = safeExecFile("git", ["push", "--tags"]);
|
|
3683
|
-
if (pushResult.ok && pushTagsResult.ok) {
|
|
3684
|
-
console.log(chalk17.green(`
|
|
3685
|
-
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131 + push \uC644\uB8CC`));
|
|
3686
|
-
} else {
|
|
3687
|
-
console.log(chalk17.yellow(`
|
|
3688
|
-
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131\uB428 (push\uB294 \uC218\uB3D9\uC73C\uB85C)`));
|
|
3689
|
-
}
|
|
3690
|
-
}
|
|
3691
|
-
}
|
|
3692
|
-
console.log(chalk17.green.bold(`
|
|
3693
|
-
\u{1F389} v${newVersion} \uBC30\uD3EC \uC644\uB8CC!`));
|
|
3694
|
-
printNextStep({
|
|
3695
|
-
message: "npm \uBC30\uD3EC \uC644\uB8CC!",
|
|
3696
|
-
command: "vhk status",
|
|
3697
|
-
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
3698
|
-
});
|
|
3699
|
-
}
|
|
3700
|
-
|
|
3701
|
-
// src/commands/design.ts
|
|
3702
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3703
|
-
import chalk18 from "chalk";
|
|
3704
|
-
import inquirer9 from "inquirer";
|
|
3705
3127
|
var PALETTES = [
|
|
3706
3128
|
{
|
|
3707
3129
|
name: "Minimal",
|
|
@@ -3774,9 +3196,9 @@ export default vhkColors
|
|
|
3774
3196
|
`;
|
|
3775
3197
|
}
|
|
3776
3198
|
async function design() {
|
|
3777
|
-
console.log(
|
|
3778
|
-
console.log(
|
|
3779
|
-
const { paletteIndex } = await
|
|
3199
|
+
console.log(chalk16.bold("\n\u{1F3A8} " + t("design.title")));
|
|
3200
|
+
console.log(chalk16.gray("\u2500".repeat(40)));
|
|
3201
|
+
const { paletteIndex } = await inquirer7.prompt([
|
|
3780
3202
|
{
|
|
3781
3203
|
type: "list",
|
|
3782
3204
|
name: "paletteIndex",
|
|
@@ -3788,32 +3210,32 @@ async function design() {
|
|
|
3788
3210
|
}
|
|
3789
3211
|
]);
|
|
3790
3212
|
const palette = PALETTES[paletteIndex];
|
|
3791
|
-
console.log(
|
|
3213
|
+
console.log(chalk16.cyan(`
|
|
3792
3214
|
\u{1F3A8} \uC120\uD0DD\uB41C \uD314\uB808\uD2B8: ${palette.name}`));
|
|
3793
3215
|
const targetPath = hasTailwind() ? "src/styles/vhk-colors.ts" : "src/styles/tokens.css";
|
|
3794
3216
|
const content = hasTailwind() ? generateTailwindExtend(palette) : generateCSSTokens(palette);
|
|
3795
3217
|
if (existsSync4(targetPath)) {
|
|
3796
|
-
const { overwrite } = await
|
|
3218
|
+
const { overwrite } = await inquirer7.prompt([{
|
|
3797
3219
|
type: "confirm",
|
|
3798
3220
|
name: "overwrite",
|
|
3799
3221
|
message: `${targetPath} \uC774\uBBF8 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?`,
|
|
3800
3222
|
default: false
|
|
3801
3223
|
}]);
|
|
3802
3224
|
if (!overwrite) {
|
|
3803
|
-
console.log(
|
|
3225
|
+
console.log(chalk16.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
|
|
3804
3226
|
return;
|
|
3805
3227
|
}
|
|
3806
3228
|
}
|
|
3807
|
-
|
|
3229
|
+
mkdirSync3("src/styles", { recursive: true });
|
|
3808
3230
|
writeFileSync3(targetPath, content, "utf-8");
|
|
3809
3231
|
if (hasTailwind()) {
|
|
3810
|
-
console.log(
|
|
3811
|
-
console.log(
|
|
3232
|
+
console.log(chalk16.green("\n\u2705 src/styles/vhk-colors.ts \uC0DD\uC131"));
|
|
3233
|
+
console.log(chalk16.gray(" tailwind.config\uC758 extend.colors\uC5D0 import \uD574\uC11C \uC0AC\uC6A9\uD558\uC138\uC694."));
|
|
3812
3234
|
} else {
|
|
3813
|
-
console.log(
|
|
3814
|
-
console.log(
|
|
3235
|
+
console.log(chalk16.green("\n\u2705 src/styles/tokens.css \uC0DD\uC131"));
|
|
3236
|
+
console.log(chalk16.gray(" HTML\uC5D0 <link>\uB85C \uCD94\uAC00\uD558\uAC70\uB098 CSS\uC5D0\uC11C @import \uD558\uC138\uC694."));
|
|
3815
3237
|
}
|
|
3816
|
-
console.log(
|
|
3238
|
+
console.log(chalk16.bold("\n\u{1F308} \uCEEC\uB7EC \uBBF8\uB9AC\uBCF4\uAE30:"));
|
|
3817
3239
|
for (const [key, value] of Object.entries(palette.colors)) {
|
|
3818
3240
|
console.log(` ${key.padEnd(12)} ${value}`);
|
|
3819
3241
|
}
|
|
@@ -3828,9 +3250,9 @@ async function designPalette() {
|
|
|
3828
3250
|
}
|
|
3829
3251
|
|
|
3830
3252
|
// src/commands/theme.ts
|
|
3831
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
3832
|
-
import
|
|
3833
|
-
import
|
|
3253
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
3254
|
+
import chalk17 from "chalk";
|
|
3255
|
+
import inquirer8 from "inquirer";
|
|
3834
3256
|
function generateDarkCSS() {
|
|
3835
3257
|
return `/* vhk theme \u2014 \uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC CSS \uBCC0\uC218 */
|
|
3836
3258
|
|
|
@@ -3886,13 +3308,13 @@ export function initTheme(): void {
|
|
|
3886
3308
|
`;
|
|
3887
3309
|
}
|
|
3888
3310
|
async function theme() {
|
|
3889
|
-
console.log(
|
|
3890
|
-
console.log(
|
|
3311
|
+
console.log(chalk17.bold("\n\u{1F319} " + t("theme.title")));
|
|
3312
|
+
console.log(chalk17.gray("\u2500".repeat(40)));
|
|
3891
3313
|
const cssPath = "src/styles/theme.css";
|
|
3892
3314
|
const togglePath = "src/lib/theme-toggle.ts";
|
|
3893
3315
|
const conflicts = [cssPath, togglePath].filter((p) => existsSync5(p));
|
|
3894
3316
|
if (conflicts.length > 0) {
|
|
3895
|
-
const { overwrite } = await
|
|
3317
|
+
const { overwrite } = await inquirer8.prompt([{
|
|
3896
3318
|
type: "confirm",
|
|
3897
3319
|
name: "overwrite",
|
|
3898
3320
|
message: `\uB2E4\uC74C \uD30C\uC77C\uC774 \uC774\uBBF8 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?
|
|
@@ -3900,21 +3322,21 @@ async function theme() {
|
|
|
3900
3322
|
default: false
|
|
3901
3323
|
}]);
|
|
3902
3324
|
if (!overwrite) {
|
|
3903
|
-
console.log(
|
|
3325
|
+
console.log(chalk17.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
|
|
3904
3326
|
return;
|
|
3905
3327
|
}
|
|
3906
3328
|
}
|
|
3907
|
-
|
|
3908
|
-
|
|
3329
|
+
mkdirSync4("src/styles", { recursive: true });
|
|
3330
|
+
mkdirSync4("src/lib", { recursive: true });
|
|
3909
3331
|
writeFileSync4(cssPath, generateDarkCSS(), "utf-8");
|
|
3910
|
-
console.log(
|
|
3332
|
+
console.log(chalk17.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"));
|
|
3911
3333
|
writeFileSync4(togglePath, generateToggleUtil(), "utf-8");
|
|
3912
|
-
console.log(
|
|
3913
|
-
console.log(
|
|
3914
|
-
console.log(
|
|
3915
|
-
console.log(
|
|
3916
|
-
console.log(
|
|
3917
|
-
console.log(
|
|
3334
|
+
console.log(chalk17.green("\u2705 src/lib/theme-toggle.ts \uC0DD\uC131 (\uD1A0\uAE00 \uC720\uD2F8\uB9AC\uD2F0)"));
|
|
3335
|
+
console.log(chalk17.bold("\n\u{1F4D6} \uC0AC\uC6A9\uBC95:"));
|
|
3336
|
+
console.log(chalk17.gray(" 1. theme.css\uB97C \uAE00\uB85C\uBC8C \uC2A4\uD0C0\uC77C\uC5D0 \uCD94\uAC00"));
|
|
3337
|
+
console.log(chalk17.gray(' 2. import { initTheme, toggleTheme } from "./lib/theme-toggle"'));
|
|
3338
|
+
console.log(chalk17.gray(" 3. \uC571 \uC9C4\uC785\uC810\uC5D0\uC11C initTheme() \uD638\uCD9C"));
|
|
3339
|
+
console.log(chalk17.gray(" 4. \uD1A0\uAE00 \uBC84\uD2BC\uC5D0\uC11C toggleTheme() \uD638\uCD9C"));
|
|
3918
3340
|
printNextStep({
|
|
3919
3341
|
message: "\uD14C\uB9C8 \uC124\uC815 \uC644\uB8CC!",
|
|
3920
3342
|
command: "vhk ref list",
|
|
@@ -3923,8 +3345,8 @@ async function theme() {
|
|
|
3923
3345
|
}
|
|
3924
3346
|
|
|
3925
3347
|
// src/commands/ref.ts
|
|
3926
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
3927
|
-
import
|
|
3348
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
3349
|
+
import chalk18 from "chalk";
|
|
3928
3350
|
var REFS_PATH = ".vhk/refs.json";
|
|
3929
3351
|
function loadRefs() {
|
|
3930
3352
|
if (!existsSync6(REFS_PATH)) return [];
|
|
@@ -3936,28 +3358,28 @@ function loadRefs() {
|
|
|
3936
3358
|
}
|
|
3937
3359
|
}
|
|
3938
3360
|
function saveRefs(refs) {
|
|
3939
|
-
|
|
3361
|
+
mkdirSync5(".vhk", { recursive: true });
|
|
3940
3362
|
writeFileSync5(REFS_PATH, JSON.stringify(refs, null, 2) + "\n", "utf-8");
|
|
3941
3363
|
}
|
|
3942
3364
|
async function refAdd(url, memo = "") {
|
|
3943
|
-
console.log(
|
|
3944
|
-
console.log(
|
|
3365
|
+
console.log(chalk18.bold("\n\u{1F517} " + t("ref.addTitle")));
|
|
3366
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
3945
3367
|
if (!url) {
|
|
3946
|
-
console.log(
|
|
3947
|
-
console.log(
|
|
3368
|
+
console.log(chalk18.red("\u274C URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
|
|
3369
|
+
console.log(chalk18.gray(' \uC608: vhk ref add https://example.com --memo "\uCC38\uACE0 \uC0AC\uC774\uD2B8"'));
|
|
3948
3370
|
return;
|
|
3949
3371
|
}
|
|
3950
3372
|
const refs = loadRefs();
|
|
3951
3373
|
if (refs.some((r) => r.url === url)) {
|
|
3952
|
-
console.log(
|
|
3374
|
+
console.log(chalk18.yellow("\u26A0\uFE0F \uC774\uBBF8 \uC800\uC7A5\uB41C URL\uC785\uB2C8\uB2E4."));
|
|
3953
3375
|
return;
|
|
3954
3376
|
}
|
|
3955
3377
|
refs.push({ url, memo, addedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3956
3378
|
saveRefs(refs);
|
|
3957
|
-
console.log(
|
|
3379
|
+
console.log(chalk18.green(`
|
|
3958
3380
|
\u2705 \uB808\uD37C\uB7F0\uC2A4 \uCD94\uAC00\uB428 (#${refs.length})`));
|
|
3959
|
-
console.log(
|
|
3960
|
-
if (memo) console.log(
|
|
3381
|
+
console.log(chalk18.cyan(` ${url}`));
|
|
3382
|
+
if (memo) console.log(chalk18.gray(` \u{1F4DD} ${memo}`));
|
|
3961
3383
|
printNextStep({
|
|
3962
3384
|
message: "\uB808\uD37C\uB7F0\uC2A4 \uC800\uC7A5 \uC644\uB8CC!",
|
|
3963
3385
|
command: "vhk ref list",
|
|
@@ -3965,22 +3387,22 @@ async function refAdd(url, memo = "") {
|
|
|
3965
3387
|
});
|
|
3966
3388
|
}
|
|
3967
3389
|
async function refList() {
|
|
3968
|
-
console.log(
|
|
3969
|
-
console.log(
|
|
3390
|
+
console.log(chalk18.bold("\n\u{1F4DA} " + t("ref.listTitle")));
|
|
3391
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
3970
3392
|
const refs = loadRefs();
|
|
3971
3393
|
if (refs.length === 0) {
|
|
3972
|
-
console.log(
|
|
3973
|
-
console.log(
|
|
3394
|
+
console.log(chalk18.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uB808\uD37C\uB7F0\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3395
|
+
console.log(chalk18.gray(' vhk ref add <url> --memo "\uBA54\uBAA8"\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
|
|
3974
3396
|
return;
|
|
3975
3397
|
}
|
|
3976
|
-
console.log(
|
|
3398
|
+
console.log(chalk18.cyan(`
|
|
3977
3399
|
\uCD1D ${refs.length}\uAC1C\uC758 \uB808\uD37C\uB7F0\uC2A4:
|
|
3978
3400
|
`));
|
|
3979
3401
|
refs.forEach((ref, index) => {
|
|
3980
3402
|
const date = new Date(ref.addedAt).toLocaleDateString("ko-KR");
|
|
3981
|
-
console.log(
|
|
3982
|
-
if (ref.memo) console.log(
|
|
3983
|
-
console.log(
|
|
3403
|
+
console.log(chalk18.white(` [${index + 1}] ${ref.url}`));
|
|
3404
|
+
if (ref.memo) console.log(chalk18.gray(` \u{1F4DD} ${ref.memo}`));
|
|
3405
|
+
console.log(chalk18.gray(` \u{1F4C5} ${date}`));
|
|
3984
3406
|
console.log("");
|
|
3985
3407
|
});
|
|
3986
3408
|
}
|
|
@@ -3988,7 +3410,7 @@ async function refOpen(indexStr) {
|
|
|
3988
3410
|
const refs = loadRefs();
|
|
3989
3411
|
const idx = parseInt(indexStr, 10) - 1;
|
|
3990
3412
|
if (Number.isNaN(idx) || idx < 0 || idx >= refs.length) {
|
|
3991
|
-
console.log(
|
|
3413
|
+
console.log(chalk18.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${refs.length || 0})`));
|
|
3992
3414
|
return;
|
|
3993
3415
|
}
|
|
3994
3416
|
const ref = refs[idx];
|
|
@@ -3996,34 +3418,34 @@ async function refOpen(indexStr) {
|
|
|
3996
3418
|
try {
|
|
3997
3419
|
parsed = new URL(ref.url);
|
|
3998
3420
|
} catch {
|
|
3999
|
-
console.log(
|
|
3421
|
+
console.log(chalk18.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 URL: ${ref.url}`));
|
|
4000
3422
|
return;
|
|
4001
3423
|
}
|
|
4002
3424
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
4003
|
-
console.log(
|
|
3425
|
+
console.log(chalk18.red(`\u274C http(s) URL\uB9CC \uC5F4 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (${parsed.protocol})`));
|
|
4004
3426
|
return;
|
|
4005
3427
|
}
|
|
4006
|
-
console.log(
|
|
3428
|
+
console.log(chalk18.cyan(`
|
|
4007
3429
|
\u{1F310} \uC5F4\uAE30: ${ref.url}`));
|
|
4008
3430
|
let result;
|
|
4009
3431
|
if (process.platform === "darwin") {
|
|
4010
3432
|
result = safeExecFile("open", [ref.url]);
|
|
4011
3433
|
} else if (process.platform === "win32") {
|
|
4012
|
-
result = safeExecFile("
|
|
3434
|
+
result = safeExecFile("rundll32.exe", ["url.dll,FileProtocolHandler", ref.url]);
|
|
4013
3435
|
} else {
|
|
4014
3436
|
result = safeExecFile("xdg-open", [ref.url]);
|
|
4015
3437
|
}
|
|
4016
3438
|
if (result.ok) {
|
|
4017
|
-
console.log(
|
|
3439
|
+
console.log(chalk18.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
4018
3440
|
} else {
|
|
4019
|
-
console.log(
|
|
3441
|
+
console.log(chalk18.yellow("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. URL\uC744 \uC9C1\uC811 \uBC29\uBB38\uD574\uC8FC\uC138\uC694."));
|
|
4020
3442
|
}
|
|
4021
3443
|
}
|
|
4022
3444
|
|
|
4023
3445
|
// src/commands/harness.ts
|
|
4024
3446
|
import { existsSync as existsSync7 } from "fs";
|
|
4025
|
-
import
|
|
4026
|
-
import
|
|
3447
|
+
import chalk19 from "chalk";
|
|
3448
|
+
import ora2 from "ora";
|
|
4027
3449
|
function detectPM() {
|
|
4028
3450
|
if (existsSync7("pnpm-lock.yaml")) return "pnpm";
|
|
4029
3451
|
if (existsSync7("yarn.lock")) return "yarn";
|
|
@@ -4063,30 +3485,30 @@ function detectChecks() {
|
|
|
4063
3485
|
return checks;
|
|
4064
3486
|
}
|
|
4065
3487
|
async function harness() {
|
|
4066
|
-
console.log(
|
|
4067
|
-
console.log(
|
|
3488
|
+
console.log(chalk19.bold("\n\u{1F527} " + t("harness.title")));
|
|
3489
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
4068
3490
|
const checks = detectChecks();
|
|
4069
3491
|
if (checks.length === 0) {
|
|
4070
|
-
console.log(
|
|
4071
|
-
console.log(
|
|
3492
|
+
console.log(chalk19.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3493
|
+
console.log(chalk19.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
|
|
4072
3494
|
return;
|
|
4073
3495
|
}
|
|
4074
|
-
console.log(
|
|
3496
|
+
console.log(chalk19.cyan(`
|
|
4075
3497
|
\u{1F3C3} ${checks.length}\uAC1C \uC810\uAC80 \uC2DC\uC791:
|
|
4076
3498
|
`));
|
|
4077
3499
|
const results = [];
|
|
4078
3500
|
for (const check2 of checks) {
|
|
4079
3501
|
const display = `${check2.bin} ${check2.args.join(" ")}`;
|
|
4080
|
-
const spinner =
|
|
4081
|
-
const
|
|
3502
|
+
const spinner = ora2(`${check2.name} \uC2E4\uD589 \uC911...`).start();
|
|
3503
|
+
const start2 = Date.now();
|
|
4082
3504
|
const result = safeExecFile(check2.bin, check2.args);
|
|
4083
|
-
const duration = Date.now() -
|
|
3505
|
+
const duration = Date.now() - start2;
|
|
4084
3506
|
const sec = (duration / 1e3).toFixed(1);
|
|
4085
3507
|
if (result.ok) {
|
|
4086
|
-
spinner.succeed(`${check2.name} ${
|
|
3508
|
+
spinner.succeed(`${check2.name} ${chalk19.gray(`(${sec}s)`)}`);
|
|
4087
3509
|
results.push({ name: check2.name, command: display, passed: true, duration });
|
|
4088
3510
|
} else {
|
|
4089
|
-
spinner.fail(`${check2.name} ${
|
|
3511
|
+
spinner.fail(`${check2.name} ${chalk19.gray(`(${sec}s)`)}`);
|
|
4090
3512
|
results.push({
|
|
4091
3513
|
name: check2.name,
|
|
4092
3514
|
command: display,
|
|
@@ -4096,22 +3518,22 @@ async function harness() {
|
|
|
4096
3518
|
});
|
|
4097
3519
|
}
|
|
4098
3520
|
}
|
|
4099
|
-
console.log(
|
|
4100
|
-
console.log(
|
|
3521
|
+
console.log(chalk19.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
|
|
3522
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
4101
3523
|
for (const r of results) {
|
|
4102
|
-
const icon = r.passed ?
|
|
3524
|
+
const icon = r.passed ? chalk19.green("\u2705") : chalk19.red("\u274C");
|
|
4103
3525
|
const sec = (r.duration / 1e3).toFixed(1);
|
|
4104
|
-
console.log(` ${icon} ${r.name.padEnd(15)} ${
|
|
3526
|
+
console.log(` ${icon} ${r.name.padEnd(15)} ${chalk19.gray(`${sec}s`)}`);
|
|
4105
3527
|
}
|
|
4106
3528
|
const passed = results.filter((r) => r.passed).length;
|
|
4107
3529
|
const all = passed === results.length;
|
|
4108
|
-
console.log(
|
|
3530
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
4109
3531
|
if (all) {
|
|
4110
|
-
console.log(
|
|
3532
|
+
console.log(chalk19.green.bold(`
|
|
4111
3533
|
\u{1F389} \uC804\uCCB4 \uD1B5\uACFC! (${passed}/${results.length})`));
|
|
4112
3534
|
} else {
|
|
4113
3535
|
console.log(
|
|
4114
|
-
|
|
3536
|
+
chalk19.red.bold(`
|
|
4115
3537
|
\u26A0\uFE0F ${results.length - passed}\uAC1C \uC2E4\uD328 (${passed}/${results.length} \uD1B5\uACFC)`)
|
|
4116
3538
|
);
|
|
4117
3539
|
}
|
|
@@ -4122,127 +3544,37 @@ async function harness() {
|
|
|
4122
3544
|
});
|
|
4123
3545
|
}
|
|
4124
3546
|
|
|
4125
|
-
// src/commands/audit.ts
|
|
4126
|
-
import { existsSync as existsSync8 } from "fs";
|
|
4127
|
-
import chalk22 from "chalk";
|
|
4128
|
-
import inquirer11 from "inquirer";
|
|
4129
|
-
import ora4 from "ora";
|
|
4130
|
-
function detectCurrentPM() {
|
|
4131
|
-
if (existsSync8("pnpm-lock.yaml")) return "pnpm";
|
|
4132
|
-
if (existsSync8("yarn.lock")) return "yarn";
|
|
4133
|
-
return "npm";
|
|
4134
|
-
}
|
|
4135
|
-
function parseAuditOutput(output, pm) {
|
|
4136
|
-
const empty = { critical: 0, high: 0, moderate: 0, low: 0, total: 0 };
|
|
4137
|
-
if (!output) return empty;
|
|
4138
|
-
try {
|
|
4139
|
-
const json = JSON.parse(output);
|
|
4140
|
-
const meta = json.metadata?.vulnerabilities;
|
|
4141
|
-
if (meta) {
|
|
4142
|
-
const summary = {
|
|
4143
|
-
critical: meta.critical ?? 0,
|
|
4144
|
-
high: meta.high ?? 0,
|
|
4145
|
-
moderate: meta.moderate ?? 0,
|
|
4146
|
-
low: meta.low ?? 0,
|
|
4147
|
-
total: meta.total ?? 0
|
|
4148
|
-
};
|
|
4149
|
-
if (!summary.total) {
|
|
4150
|
-
summary.total = summary.critical + summary.high + summary.moderate + summary.low;
|
|
4151
|
-
}
|
|
4152
|
-
return summary;
|
|
4153
|
-
}
|
|
4154
|
-
void pm;
|
|
4155
|
-
return empty;
|
|
4156
|
-
} catch {
|
|
4157
|
-
return empty;
|
|
4158
|
-
}
|
|
4159
|
-
}
|
|
4160
|
-
function runAuditJson(pm) {
|
|
4161
|
-
const result = safeExecFile(pm, ["audit", "--json"]);
|
|
4162
|
-
return result.out;
|
|
4163
|
-
}
|
|
4164
|
-
function runAuditFix(pm) {
|
|
4165
|
-
if (pm !== "npm") {
|
|
4166
|
-
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.` };
|
|
4167
|
-
}
|
|
4168
|
-
const result = safeExecFile("npm", ["audit", "fix"]);
|
|
4169
|
-
return result.ok ? { ok: true } : { ok: false, err: result.err };
|
|
4170
|
-
}
|
|
4171
|
-
async function audit(autoFix = false) {
|
|
4172
|
-
console.log(chalk22.bold("\n\u{1F6E1}\uFE0F " + t("audit.title")));
|
|
4173
|
-
console.log(chalk22.gray("\u2500".repeat(40)));
|
|
4174
|
-
const pm = detectCurrentPM();
|
|
4175
|
-
console.log(chalk22.cyan(`\u{1F4E6} \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${pm}`));
|
|
4176
|
-
const spinner = ora4("\uBCF4\uC548 \uAC10\uC0AC \uC2E4\uD589 \uC911...").start();
|
|
4177
|
-
const output = runAuditJson(pm);
|
|
4178
|
-
spinner.stop();
|
|
4179
|
-
const summary = parseAuditOutput(output, pm);
|
|
4180
|
-
if (summary.total === 0) {
|
|
4181
|
-
console.log(chalk22.green.bold("\n\u{1F389} \uCDE8\uC57D\uC810\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4!"));
|
|
4182
|
-
return;
|
|
4183
|
-
}
|
|
4184
|
-
console.log(chalk22.bold("\n\u{1F4CA} \uCDE8\uC57D\uC810 \uC694\uC57D:"));
|
|
4185
|
-
if (summary.critical > 0) console.log(chalk22.red(` \u{1F534} Critical: ${summary.critical}`));
|
|
4186
|
-
if (summary.high > 0) console.log(chalk22.red(` \u{1F7E0} High: ${summary.high}`));
|
|
4187
|
-
if (summary.moderate > 0) console.log(chalk22.yellow(` \u{1F7E1} Moderate: ${summary.moderate}`));
|
|
4188
|
-
if (summary.low > 0) console.log(chalk22.gray(` \u26AA Low: ${summary.low}`));
|
|
4189
|
-
console.log(chalk22.bold(`
|
|
4190
|
-
\uCD1D ${summary.total}\uAC1C\uC758 \uCDE8\uC57D\uC810`));
|
|
4191
|
-
const shouldRunFix = autoFix ? true : summary.critical > 0 || summary.high > 0 ? (await inquirer11.prompt([
|
|
4192
|
-
{
|
|
4193
|
-
type: "confirm",
|
|
4194
|
-
name: "shouldFix",
|
|
4195
|
-
message: "\uC790\uB3D9 \uC218\uC815\uC744 \uC2DC\uB3C4\uD560\uAE4C\uC694? (npm audit fix)",
|
|
4196
|
-
default: true
|
|
4197
|
-
}
|
|
4198
|
-
])).shouldFix : false;
|
|
4199
|
-
if (shouldRunFix) {
|
|
4200
|
-
const fixSpinner = ora4("\uC790\uB3D9 \uC218\uC815 \uC911...").start();
|
|
4201
|
-
const result = runAuditFix(pm);
|
|
4202
|
-
if (result.ok) {
|
|
4203
|
-
fixSpinner.succeed("\uC790\uB3D9 \uC218\uC815 \uC644\uB8CC!");
|
|
4204
|
-
} else {
|
|
4205
|
-
fixSpinner.warn(result.err ?? "\uC77C\uBD80 \uCDE8\uC57D\uC810\uC740 \uC218\uB3D9 \uC218\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
4206
|
-
}
|
|
4207
|
-
}
|
|
4208
|
-
printNextStep({
|
|
4209
|
-
message: "\uBCF4\uC548 \uAC10\uC0AC \uC644\uB8CC.",
|
|
4210
|
-
command: "vhk harness",
|
|
4211
|
-
cursorHint: "\uD488\uC9C8 \uC810\uAC80\uD574\uC918"
|
|
4212
|
-
});
|
|
4213
|
-
}
|
|
4214
|
-
|
|
4215
3547
|
// src/commands/migrate.ts
|
|
4216
|
-
import { existsSync as
|
|
4217
|
-
import
|
|
4218
|
-
import
|
|
4219
|
-
import
|
|
3548
|
+
import { existsSync as existsSync8, unlinkSync, rmSync } from "fs";
|
|
3549
|
+
import chalk20 from "chalk";
|
|
3550
|
+
import inquirer9 from "inquirer";
|
|
3551
|
+
import ora3 from "ora";
|
|
4220
3552
|
var LOCK_FILES = {
|
|
4221
3553
|
npm: "package-lock.json",
|
|
4222
3554
|
yarn: "yarn.lock",
|
|
4223
3555
|
pnpm: "pnpm-lock.yaml"
|
|
4224
3556
|
};
|
|
4225
|
-
function
|
|
4226
|
-
if (
|
|
4227
|
-
if (
|
|
4228
|
-
if (
|
|
3557
|
+
function detectCurrentPM() {
|
|
3558
|
+
if (existsSync8("pnpm-lock.yaml")) return "pnpm";
|
|
3559
|
+
if (existsSync8("yarn.lock")) return "yarn";
|
|
3560
|
+
if (existsSync8("package-lock.json")) return "npm";
|
|
4229
3561
|
return null;
|
|
4230
3562
|
}
|
|
4231
|
-
function
|
|
3563
|
+
function isCLIAvailable(pm) {
|
|
4232
3564
|
return safeExecFile(pm, ["--version"]).ok;
|
|
4233
3565
|
}
|
|
4234
3566
|
async function migrate(target) {
|
|
4235
|
-
console.log(
|
|
4236
|
-
console.log(
|
|
4237
|
-
const current =
|
|
4238
|
-
console.log(
|
|
3567
|
+
console.log(chalk20.bold("\n\u{1F504} " + t("migrate.title")));
|
|
3568
|
+
console.log(chalk20.gray("\u2500".repeat(40)));
|
|
3569
|
+
const current = detectCurrentPM();
|
|
3570
|
+
console.log(chalk20.cyan(`
|
|
4239
3571
|
\uD604\uC7AC \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00"}`));
|
|
4240
3572
|
let targetPM;
|
|
4241
3573
|
if (target && ["npm", "yarn", "pnpm"].includes(target)) {
|
|
4242
3574
|
targetPM = target;
|
|
4243
3575
|
} else {
|
|
4244
3576
|
const choices = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current).map((pm) => ({ name: pm, value: pm }));
|
|
4245
|
-
const { selected } = await
|
|
3577
|
+
const { selected } = await inquirer9.prompt([
|
|
4246
3578
|
{
|
|
4247
3579
|
type: "list",
|
|
4248
3580
|
name: "selected",
|
|
@@ -4253,17 +3585,17 @@ async function migrate(target) {
|
|
|
4253
3585
|
targetPM = selected;
|
|
4254
3586
|
}
|
|
4255
3587
|
if (targetPM === current) {
|
|
4256
|
-
console.log(
|
|
3588
|
+
console.log(chalk20.yellow(`
|
|
4257
3589
|
\u26A0\uFE0F \uC774\uBBF8 ${targetPM}\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4.`));
|
|
4258
3590
|
return;
|
|
4259
3591
|
}
|
|
4260
|
-
if (!
|
|
4261
|
-
console.log(
|
|
3592
|
+
if (!isCLIAvailable(targetPM)) {
|
|
3593
|
+
console.log(chalk20.red(`
|
|
4262
3594
|
\u274C ${targetPM}\uC774 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
4263
|
-
console.log(
|
|
3595
|
+
console.log(chalk20.yellow(` npm i -g ${targetPM}`));
|
|
4264
3596
|
return;
|
|
4265
3597
|
}
|
|
4266
|
-
const { confirm } = await
|
|
3598
|
+
const { confirm } = await inquirer9.prompt([
|
|
4267
3599
|
{
|
|
4268
3600
|
type: "confirm",
|
|
4269
3601
|
name: "confirm",
|
|
@@ -4272,30 +3604,30 @@ async function migrate(target) {
|
|
|
4272
3604
|
}
|
|
4273
3605
|
]);
|
|
4274
3606
|
if (!confirm) {
|
|
4275
|
-
console.log(
|
|
3607
|
+
console.log(chalk20.gray("\uCDE8\uC18C\uB428"));
|
|
4276
3608
|
return;
|
|
4277
3609
|
}
|
|
4278
|
-
const cleanup =
|
|
3610
|
+
const cleanup = ora3("\uAE30\uC874 lock \uD30C\uC77C \uC815\uB9AC \uC911...").start();
|
|
4279
3611
|
for (const lockFile of Object.values(LOCK_FILES)) {
|
|
4280
|
-
if (
|
|
3612
|
+
if (existsSync8(lockFile)) {
|
|
4281
3613
|
unlinkSync(lockFile);
|
|
4282
3614
|
}
|
|
4283
3615
|
}
|
|
4284
|
-
if (
|
|
3616
|
+
if (existsSync8("node_modules")) {
|
|
4285
3617
|
cleanup.text = "node_modules \uC0AD\uC81C \uC911...";
|
|
4286
3618
|
rmSync("node_modules", { recursive: true, force: true });
|
|
4287
3619
|
}
|
|
4288
3620
|
cleanup.succeed("\uAE30\uC874 \uD30C\uC77C \uC815\uB9AC \uC644\uB8CC");
|
|
4289
|
-
const install =
|
|
3621
|
+
const install = ora3(`${targetPM} install \uC2E4\uD589 \uC911...`).start();
|
|
4290
3622
|
const installResult = safeExecFile(targetPM, ["install"]);
|
|
4291
3623
|
if (installResult.ok) {
|
|
4292
3624
|
install.succeed(`${targetPM} install \uC644\uB8CC!`);
|
|
4293
3625
|
} else {
|
|
4294
3626
|
install.fail(`${targetPM} install \uC2E4\uD328`);
|
|
4295
|
-
console.log(
|
|
3627
|
+
console.log(chalk20.red(installResult.err.slice(0, 300)));
|
|
4296
3628
|
return;
|
|
4297
3629
|
}
|
|
4298
|
-
console.log(
|
|
3630
|
+
console.log(chalk20.green.bold(`
|
|
4299
3631
|
\u{1F389} ${current ?? "\uC774\uC804"} \u2192 ${targetPM} \uC804\uD658 \uC644\uB8CC!`));
|
|
4300
3632
|
printNextStep({
|
|
4301
3633
|
message: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 \uC644\uB8CC!",
|
|
@@ -4305,18 +3637,17 @@ async function migrate(target) {
|
|
|
4305
3637
|
}
|
|
4306
3638
|
|
|
4307
3639
|
// src/commands/update.ts
|
|
4308
|
-
import {
|
|
4309
|
-
import {
|
|
4310
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
3640
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3641
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
4311
3642
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4312
|
-
import
|
|
4313
|
-
import
|
|
3643
|
+
import chalk21 from "chalk";
|
|
3644
|
+
import ora4 from "ora";
|
|
4314
3645
|
var PACKAGE = "@byh3071/vhk";
|
|
4315
3646
|
function getCurrentVersion() {
|
|
4316
3647
|
const dir = dirname2(fileURLToPath3(import.meta.url));
|
|
4317
|
-
for (const pkgPath of [
|
|
3648
|
+
for (const pkgPath of [join4(dir, "../package.json"), join4(dir, "../../package.json")]) {
|
|
4318
3649
|
try {
|
|
4319
|
-
if (
|
|
3650
|
+
if (existsSync9(pkgPath)) {
|
|
4320
3651
|
const pkg = readJsonFile(pkgPath);
|
|
4321
3652
|
if (pkg.version) return pkg.version;
|
|
4322
3653
|
}
|
|
@@ -4327,15 +3658,8 @@ function getCurrentVersion() {
|
|
|
4327
3658
|
return "0.0.0";
|
|
4328
3659
|
}
|
|
4329
3660
|
function getLatestVersion() {
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
encoding: "utf-8",
|
|
4333
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4334
|
-
}).toString();
|
|
4335
|
-
return out.trim();
|
|
4336
|
-
} catch {
|
|
4337
|
-
return null;
|
|
4338
|
-
}
|
|
3661
|
+
const r = safeExecFile("npm", ["view", PACKAGE, "version"]);
|
|
3662
|
+
return r.ok ? r.out : null;
|
|
4339
3663
|
}
|
|
4340
3664
|
function isUpToDate(current, latest) {
|
|
4341
3665
|
const parse = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -4346,54 +3670,159 @@ function isUpToDate(current, latest) {
|
|
|
4346
3670
|
return cc >= lc;
|
|
4347
3671
|
}
|
|
4348
3672
|
async function update() {
|
|
4349
|
-
console.log(
|
|
4350
|
-
console.log(
|
|
3673
|
+
console.log(chalk21.bold("\n\u2B06\uFE0F " + t("update.title")));
|
|
3674
|
+
console.log(chalk21.gray("\u2500".repeat(40)));
|
|
4351
3675
|
const current = getCurrentVersion();
|
|
4352
|
-
console.log(
|
|
3676
|
+
console.log(chalk21.cyan(`
|
|
4353
3677
|
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${current}`));
|
|
4354
|
-
const spinner =
|
|
3678
|
+
const spinner = ora4("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...").start();
|
|
4355
3679
|
const latest = getLatestVersion();
|
|
4356
3680
|
if (!latest) {
|
|
4357
3681
|
spinner.fail("\uCD5C\uC2E0 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
4358
|
-
console.log(
|
|
4359
|
-
console.log(
|
|
3682
|
+
console.log(chalk21.yellow(" \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uAC70\uB098 \uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
|
|
3683
|
+
console.log(chalk21.gray(` npm update -g ${PACKAGE}`));
|
|
4360
3684
|
return;
|
|
4361
3685
|
}
|
|
4362
3686
|
spinner.stop();
|
|
4363
|
-
console.log(
|
|
3687
|
+
console.log(chalk21.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
|
|
4364
3688
|
if (isUpToDate(current, latest)) {
|
|
4365
|
-
console.log(
|
|
3689
|
+
console.log(chalk21.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
|
|
4366
3690
|
return;
|
|
4367
3691
|
}
|
|
4368
|
-
const updateSpinner =
|
|
4369
|
-
|
|
4370
|
-
|
|
3692
|
+
const updateSpinner = ora4(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC911...`).start();
|
|
3693
|
+
const upd = safeExecFile("npm", ["update", "-g", PACKAGE]);
|
|
3694
|
+
if (upd.ok) {
|
|
4371
3695
|
updateSpinner.succeed(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`);
|
|
4372
|
-
console.log(
|
|
3696
|
+
console.log(chalk21.green.bold(`
|
|
4373
3697
|
\u{1F389} VHK CLI v${latest} \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
|
|
4374
|
-
console.log(
|
|
4375
|
-
}
|
|
3698
|
+
console.log(chalk21.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
|
|
3699
|
+
} else {
|
|
4376
3700
|
updateSpinner.fail("\uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
|
|
4377
|
-
|
|
4378
|
-
console.log(
|
|
4379
|
-
console.log(
|
|
4380
|
-
console.log(chalk24.gray(` npm update -g ${PACKAGE}`));
|
|
3701
|
+
console.log(chalk21.red(upd.err.slice(0, 300)));
|
|
3702
|
+
console.log(chalk21.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
|
|
3703
|
+
console.log(chalk21.gray(` npm update -g ${PACKAGE}`));
|
|
4381
3704
|
}
|
|
4382
3705
|
}
|
|
4383
3706
|
|
|
4384
3707
|
// src/commands/context.ts
|
|
4385
3708
|
import {
|
|
4386
3709
|
existsSync as existsSync11,
|
|
4387
|
-
mkdirSync as
|
|
4388
|
-
readFileSync as
|
|
4389
|
-
readdirSync,
|
|
4390
|
-
statSync,
|
|
4391
|
-
writeFileSync as
|
|
3710
|
+
mkdirSync as mkdirSync7,
|
|
3711
|
+
readFileSync as readFileSync4,
|
|
3712
|
+
readdirSync as readdirSync2,
|
|
3713
|
+
statSync as statSync2,
|
|
3714
|
+
writeFileSync as writeFileSync7
|
|
4392
3715
|
} from "fs";
|
|
4393
|
-
import { join as
|
|
4394
|
-
import
|
|
3716
|
+
import { join as join6 } from "path";
|
|
3717
|
+
import chalk22 from "chalk";
|
|
3718
|
+
|
|
3719
|
+
// src/lib/state-files.ts
|
|
3720
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6, appendFileSync, rmSync as rmSync2 } from "fs";
|
|
3721
|
+
import { join as join5 } from "path";
|
|
3722
|
+
var STATE_DIR2 = "docs/state";
|
|
3723
|
+
var BLOCKERS_PATH = join5(STATE_DIR2, "blockers.md");
|
|
3724
|
+
var LEARNINGS_PATH = join5(STATE_DIR2, "learnings.md");
|
|
3725
|
+
var VHK_DIR = ".vhk";
|
|
3726
|
+
var HARD_STOP_PATH = join5(VHK_DIR, "HARD_STOP");
|
|
3727
|
+
var HARD_STOP_BLOCKER_THRESHOLD = 3;
|
|
3728
|
+
function ensureStateDir() {
|
|
3729
|
+
mkdirSync6(STATE_DIR2, { recursive: true });
|
|
3730
|
+
}
|
|
3731
|
+
function ensureVhkDir() {
|
|
3732
|
+
mkdirSync6(VHK_DIR, { recursive: true });
|
|
3733
|
+
}
|
|
3734
|
+
function isoDate() {
|
|
3735
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3736
|
+
}
|
|
3737
|
+
var ACTIVE_BLOCKER_RE = /^- (?!~~)\[/;
|
|
3738
|
+
function countActiveBlockers(content) {
|
|
3739
|
+
let count = 0;
|
|
3740
|
+
for (const line of content.split(/\r?\n/)) {
|
|
3741
|
+
if (ACTIVE_BLOCKER_RE.test(line)) count++;
|
|
3742
|
+
}
|
|
3743
|
+
return count;
|
|
3744
|
+
}
|
|
3745
|
+
function appendBlocker(description, goalId) {
|
|
3746
|
+
ensureStateDir();
|
|
3747
|
+
const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
|
|
3748
|
+
const line = `- [${isoDate()} ${tag}] ${description.trim()}`;
|
|
3749
|
+
if (!existsSync10(BLOCKERS_PATH)) {
|
|
3750
|
+
writeFileSync6(
|
|
3751
|
+
BLOCKERS_PATH,
|
|
3752
|
+
`# Blockers
|
|
3753
|
+
|
|
3754
|
+
_Append-only. \uD574\uACB0 \uD56D\uBAA9\uC740 ~~\uCDE8\uC18C\uC120~~\uC73C\uB85C \uD45C\uAE30._
|
|
3755
|
+
|
|
3756
|
+
${line}
|
|
3757
|
+
`,
|
|
3758
|
+
"utf-8"
|
|
3759
|
+
);
|
|
3760
|
+
} else {
|
|
3761
|
+
appendFileSync(BLOCKERS_PATH, `${line}
|
|
3762
|
+
`, "utf-8");
|
|
3763
|
+
}
|
|
3764
|
+
const current = readFileSync3(BLOCKERS_PATH, "utf-8");
|
|
3765
|
+
const count = countActiveBlockers(current);
|
|
3766
|
+
let hardStopTripped = false;
|
|
3767
|
+
if (count >= HARD_STOP_BLOCKER_THRESHOLD && !existsSync10(HARD_STOP_PATH)) {
|
|
3768
|
+
writeHardStop(`auto: ${count} active blockers (threshold ${HARD_STOP_BLOCKER_THRESHOLD})`);
|
|
3769
|
+
hardStopTripped = true;
|
|
3770
|
+
}
|
|
3771
|
+
return { count, hardStopTripped };
|
|
3772
|
+
}
|
|
3773
|
+
function appendLearning(lesson, goalId) {
|
|
3774
|
+
ensureStateDir();
|
|
3775
|
+
const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
|
|
3776
|
+
const line = `- [${isoDate()} ${tag}] ${lesson.trim()}`;
|
|
3777
|
+
if (!existsSync10(LEARNINGS_PATH)) {
|
|
3778
|
+
writeFileSync6(
|
|
3779
|
+
LEARNINGS_PATH,
|
|
3780
|
+
`# Learnings
|
|
3781
|
+
|
|
3782
|
+
_Append-only. \uD55C \uC904 = \uD55C \uAD50\uD6C8._
|
|
3783
|
+
|
|
3784
|
+
${line}
|
|
3785
|
+
`,
|
|
3786
|
+
"utf-8"
|
|
3787
|
+
);
|
|
3788
|
+
} else {
|
|
3789
|
+
appendFileSync(LEARNINGS_PATH, `${line}
|
|
3790
|
+
`, "utf-8");
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
function getRecentLearnings(limit = 3) {
|
|
3794
|
+
if (!existsSync10(LEARNINGS_PATH)) return [];
|
|
3795
|
+
const lines = readFileSync3(LEARNINGS_PATH, "utf-8").split(/\r?\n/);
|
|
3796
|
+
const entries = lines.filter((l) => l.startsWith("- ["));
|
|
3797
|
+
return entries.slice(-limit);
|
|
3798
|
+
}
|
|
3799
|
+
function writeHardStop(reason) {
|
|
3800
|
+
ensureVhkDir();
|
|
3801
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3802
|
+
writeFileSync6(HARD_STOP_PATH, `${ts}
|
|
3803
|
+
${reason}
|
|
3804
|
+
`, "utf-8");
|
|
3805
|
+
}
|
|
3806
|
+
function isHardStopActive() {
|
|
3807
|
+
return existsSync10(HARD_STOP_PATH);
|
|
3808
|
+
}
|
|
3809
|
+
function readHardStopReason() {
|
|
3810
|
+
if (!existsSync10(HARD_STOP_PATH)) return null;
|
|
3811
|
+
try {
|
|
3812
|
+
return readFileSync3(HARD_STOP_PATH, "utf-8").trim();
|
|
3813
|
+
} catch {
|
|
3814
|
+
return null;
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
function clearHardStop() {
|
|
3818
|
+
if (!existsSync10(HARD_STOP_PATH)) return false;
|
|
3819
|
+
rmSync2(HARD_STOP_PATH, { force: true });
|
|
3820
|
+
return true;
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
// src/commands/context.ts
|
|
4395
3824
|
var CONTEXT_PATH = ".vhk/context.md";
|
|
4396
|
-
var
|
|
3825
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
4397
3826
|
"node_modules",
|
|
4398
3827
|
".git",
|
|
4399
3828
|
"dist",
|
|
@@ -4409,15 +3838,15 @@ function buildTree(dir, prefix = "", maxDepth = 3, depth = 0) {
|
|
|
4409
3838
|
if (depth >= maxDepth) return [];
|
|
4410
3839
|
const lines = [];
|
|
4411
3840
|
try {
|
|
4412
|
-
const entries =
|
|
3841
|
+
const entries = readdirSync2(dir);
|
|
4413
3842
|
const filtered = entries.filter(
|
|
4414
|
-
(e) => (!e.startsWith(".") || e === ".env.example") && !
|
|
3843
|
+
(e) => (!e.startsWith(".") || e === ".env.example") && !IGNORE_DIRS.has(e)
|
|
4415
3844
|
);
|
|
4416
3845
|
filtered.forEach((entry, index) => {
|
|
4417
3846
|
const isLast = index === filtered.length - 1;
|
|
4418
3847
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
4419
|
-
const fullPath =
|
|
4420
|
-
const stat =
|
|
3848
|
+
const fullPath = join6(dir, entry);
|
|
3849
|
+
const stat = statSync2(fullPath);
|
|
4421
3850
|
const isDir = stat.isDirectory();
|
|
4422
3851
|
lines.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
|
|
4423
3852
|
if (isDir) {
|
|
@@ -4491,8 +3920,8 @@ function getVhkCommands() {
|
|
|
4491
3920
|
];
|
|
4492
3921
|
}
|
|
4493
3922
|
async function context() {
|
|
4494
|
-
console.log(
|
|
4495
|
-
console.log(
|
|
3923
|
+
console.log(chalk22.bold("\n\u{1F9E0} " + t("context.title")));
|
|
3924
|
+
console.log(chalk22.gray("\u2500".repeat(40)));
|
|
4496
3925
|
const stack = extractTechStack();
|
|
4497
3926
|
const tree = buildTree(".").join("\n");
|
|
4498
3927
|
const commands = getVhkCommands();
|
|
@@ -4537,16 +3966,45 @@ async function context() {
|
|
|
4537
3966
|
} catch {
|
|
4538
3967
|
}
|
|
4539
3968
|
}
|
|
3969
|
+
const goals = listGoals("goals");
|
|
3970
|
+
const activeId = selectActiveId(goals);
|
|
3971
|
+
if (activeId !== null) {
|
|
3972
|
+
const active = goals.find((g) => g.frontmatter.id === activeId);
|
|
3973
|
+
if (active) {
|
|
3974
|
+
lines.push("## Active Goal");
|
|
3975
|
+
lines.push("");
|
|
3976
|
+
lines.push(`- **id**: ${activeId}`);
|
|
3977
|
+
lines.push(`- **title**: ${active.frontmatter.title ?? "(untitled)"}`);
|
|
3978
|
+
lines.push(`- **status**: ${active.frontmatter.status ?? "NOT_STARTED"}`);
|
|
3979
|
+
lines.push(`- **priority**: ${active.frontmatter.priority ?? "--"}`);
|
|
3980
|
+
lines.push(`- **file**: ${active.filePath}`);
|
|
3981
|
+
lines.push("");
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
const recent = getRecentLearnings(3);
|
|
3985
|
+
if (recent.length > 0) {
|
|
3986
|
+
lines.push("## Recent Learnings");
|
|
3987
|
+
lines.push("");
|
|
3988
|
+
for (const r of recent) lines.push(r);
|
|
3989
|
+
lines.push("");
|
|
3990
|
+
}
|
|
3991
|
+
if (isHardStopActive()) {
|
|
3992
|
+
lines.push("## \u26A0\uFE0F HARD_STOP \uD65C\uC131");
|
|
3993
|
+
lines.push("");
|
|
3994
|
+
lines.push("`.vhk/HARD_STOP` \uD30C\uC77C \uC874\uC7AC \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8 \uC0C1\uD0DC.");
|
|
3995
|
+
lines.push("\uD574\uC81C: `vhk resume --confirm` (\uC0AC\uB78C \uD655\uC778 \uD6C4\uB9CC)");
|
|
3996
|
+
lines.push("");
|
|
3997
|
+
}
|
|
4540
3998
|
lines.push("---");
|
|
4541
3999
|
lines.push("");
|
|
4542
4000
|
lines.push(`_\uC0DD\uC131: ${(/* @__PURE__ */ new Date()).toLocaleString("ko-KR")}_`);
|
|
4543
4001
|
lines.push("");
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
console.log(
|
|
4002
|
+
mkdirSync7(".vhk", { recursive: true });
|
|
4003
|
+
writeFileSync7(CONTEXT_PATH, lines.join("\n"), "utf-8");
|
|
4004
|
+
console.log(chalk22.green(`
|
|
4547
4005
|
\u2705 ${CONTEXT_PATH} \uC0DD\uC131 \uC644\uB8CC!`));
|
|
4548
|
-
console.log(
|
|
4549
|
-
console.log(
|
|
4006
|
+
console.log(chalk22.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
|
|
4007
|
+
console.log(chalk22.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
|
|
4550
4008
|
printNextStep({
|
|
4551
4009
|
message: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC!",
|
|
4552
4010
|
command: "vhk context-show",
|
|
@@ -4554,20 +4012,20 @@ async function context() {
|
|
|
4554
4012
|
});
|
|
4555
4013
|
}
|
|
4556
4014
|
async function contextShow() {
|
|
4557
|
-
console.log(
|
|
4558
|
-
console.log(
|
|
4015
|
+
console.log(chalk22.bold("\n\u{1F4C4} " + t("context.showTitle")));
|
|
4016
|
+
console.log(chalk22.gray("\u2500".repeat(40)));
|
|
4559
4017
|
if (!existsSync11(CONTEXT_PATH)) {
|
|
4560
|
-
console.log(
|
|
4561
|
-
console.log(
|
|
4018
|
+
console.log(chalk22.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4019
|
+
console.log(chalk22.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
|
|
4562
4020
|
return;
|
|
4563
4021
|
}
|
|
4564
|
-
const content =
|
|
4022
|
+
const content = readFileSync4(CONTEXT_PATH, "utf-8");
|
|
4565
4023
|
console.log("\n" + content);
|
|
4566
4024
|
}
|
|
4567
4025
|
|
|
4568
4026
|
// src/commands/memory.ts
|
|
4569
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
4570
|
-
import
|
|
4027
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
4028
|
+
import chalk23 from "chalk";
|
|
4571
4029
|
var MEMORY_PATH = ".vhk/memory.json";
|
|
4572
4030
|
function loadMemories() {
|
|
4573
4031
|
if (!existsSync12(MEMORY_PATH)) return [];
|
|
@@ -4579,15 +4037,15 @@ function loadMemories() {
|
|
|
4579
4037
|
}
|
|
4580
4038
|
}
|
|
4581
4039
|
function saveMemories(memories) {
|
|
4582
|
-
|
|
4583
|
-
|
|
4040
|
+
mkdirSync8(".vhk", { recursive: true });
|
|
4041
|
+
writeFileSync8(MEMORY_PATH, JSON.stringify(memories, null, 2) + "\n", "utf-8");
|
|
4584
4042
|
}
|
|
4585
4043
|
async function memoryAdd(content, tags) {
|
|
4586
|
-
console.log(
|
|
4587
|
-
console.log(
|
|
4044
|
+
console.log(chalk23.bold("\n\u{1F9E0} " + t("memory.addTitle")));
|
|
4045
|
+
console.log(chalk23.gray("\u2500".repeat(40)));
|
|
4588
4046
|
if (!content) {
|
|
4589
|
-
console.log(
|
|
4590
|
-
console.log(
|
|
4047
|
+
console.log(chalk23.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
|
|
4048
|
+
console.log(chalk23.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9\uD558\uAE30\uB85C \uACB0\uC815"'));
|
|
4591
4049
|
return;
|
|
4592
4050
|
}
|
|
4593
4051
|
const memories = loadMemories();
|
|
@@ -4597,9 +4055,9 @@ async function memoryAdd(content, tags) {
|
|
|
4597
4055
|
tags: tags && tags.length > 0 ? tags : []
|
|
4598
4056
|
});
|
|
4599
4057
|
saveMemories(memories);
|
|
4600
|
-
console.log(
|
|
4058
|
+
console.log(chalk23.green(`
|
|
4601
4059
|
\u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (#${memories.length})`));
|
|
4602
|
-
console.log(
|
|
4060
|
+
console.log(chalk23.cyan(` \u{1F4DD} ${content}`));
|
|
4603
4061
|
printNextStep({
|
|
4604
4062
|
message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!",
|
|
4605
4063
|
command: "vhk memory list",
|
|
@@ -4607,24 +4065,24 @@ async function memoryAdd(content, tags) {
|
|
|
4607
4065
|
});
|
|
4608
4066
|
}
|
|
4609
4067
|
async function memoryList() {
|
|
4610
|
-
console.log(
|
|
4611
|
-
console.log(
|
|
4068
|
+
console.log(chalk23.bold("\n\u{1F9E0} " + t("memory.listTitle")));
|
|
4069
|
+
console.log(chalk23.gray("\u2500".repeat(40)));
|
|
4612
4070
|
const memories = loadMemories();
|
|
4613
4071
|
if (memories.length === 0) {
|
|
4614
|
-
console.log(
|
|
4615
|
-
console.log(
|
|
4072
|
+
console.log(chalk23.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4073
|
+
console.log(chalk23.gray(' vhk memory add "\uB0B4\uC6A9"\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
|
|
4616
4074
|
return;
|
|
4617
4075
|
}
|
|
4618
|
-
console.log(
|
|
4076
|
+
console.log(chalk23.cyan(`
|
|
4619
4077
|
\uCD1D ${memories.length}\uAC1C\uC758 \uAE30\uC5B5:
|
|
4620
4078
|
`));
|
|
4621
4079
|
memories.forEach((m, index) => {
|
|
4622
4080
|
const date = new Date(m.addedAt).toLocaleDateString("ko-KR");
|
|
4623
|
-
console.log(
|
|
4081
|
+
console.log(chalk23.white(` [${index + 1}] ${m.content}`));
|
|
4624
4082
|
if (m.tags && m.tags.length > 0) {
|
|
4625
|
-
console.log(
|
|
4083
|
+
console.log(chalk23.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
|
|
4626
4084
|
}
|
|
4627
|
-
console.log(
|
|
4085
|
+
console.log(chalk23.gray(` \u{1F4C5} ${date}`));
|
|
4628
4086
|
console.log("");
|
|
4629
4087
|
});
|
|
4630
4088
|
}
|
|
@@ -4632,26 +4090,26 @@ async function memoryRemove(indexStr) {
|
|
|
4632
4090
|
const memories = loadMemories();
|
|
4633
4091
|
const idx = parseInt(indexStr, 10) - 1;
|
|
4634
4092
|
if (Number.isNaN(idx) || idx < 0 || idx >= memories.length) {
|
|
4635
|
-
console.log(
|
|
4093
|
+
console.log(chalk23.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${memories.length || 0})`));
|
|
4636
4094
|
return;
|
|
4637
4095
|
}
|
|
4638
4096
|
const removed = memories.splice(idx, 1)[0];
|
|
4639
4097
|
saveMemories(memories);
|
|
4640
|
-
console.log(
|
|
4641
|
-
console.log(
|
|
4098
|
+
console.log(chalk23.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
|
|
4099
|
+
console.log(chalk23.gray(` ${removed.content}`));
|
|
4642
4100
|
}
|
|
4643
4101
|
|
|
4644
4102
|
// src/commands/brief.ts
|
|
4645
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
4646
|
-
import
|
|
4103
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
4104
|
+
import chalk24 from "chalk";
|
|
4647
4105
|
var BRIEF_PATH = ".vhk/brief.md";
|
|
4648
4106
|
function git2(args) {
|
|
4649
4107
|
const result = safeExecFile("git", args);
|
|
4650
4108
|
return result.ok ? result.out : "";
|
|
4651
4109
|
}
|
|
4652
4110
|
async function brief() {
|
|
4653
|
-
console.log(
|
|
4654
|
-
console.log(
|
|
4111
|
+
console.log(chalk24.bold("\n\u{1F4CB} " + t("brief.title")));
|
|
4112
|
+
console.log(chalk24.gray("\u2500".repeat(40)));
|
|
4655
4113
|
const lines = [];
|
|
4656
4114
|
lines.push("# \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551");
|
|
4657
4115
|
lines.push("");
|
|
@@ -4732,10 +4190,10 @@ async function brief() {
|
|
|
4732
4190
|
lines.push("");
|
|
4733
4191
|
lines.push("_VHK CLI \uBE0C\uB9AC\uD551_");
|
|
4734
4192
|
lines.push("");
|
|
4735
|
-
|
|
4736
|
-
|
|
4193
|
+
mkdirSync9(".vhk", { recursive: true });
|
|
4194
|
+
writeFileSync9(BRIEF_PATH, lines.join("\n"), "utf-8");
|
|
4737
4195
|
console.log("\n" + lines.join("\n"));
|
|
4738
|
-
console.log(
|
|
4196
|
+
console.log(chalk24.green(`
|
|
4739
4197
|
\u2705 ${BRIEF_PATH} \uC800\uC7A5 \uC644\uB8CC`));
|
|
4740
4198
|
printNextStep({
|
|
4741
4199
|
message: "\uBE0C\uB9AC\uD551 \uC0DD\uC131 \uC644\uB8CC!",
|
|
@@ -4744,11 +4202,119 @@ async function brief() {
|
|
|
4744
4202
|
});
|
|
4745
4203
|
}
|
|
4746
4204
|
|
|
4205
|
+
// src/commands/start.ts
|
|
4206
|
+
import chalk25 from "chalk";
|
|
4207
|
+
import inquirer10 from "inquirer";
|
|
4208
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
4209
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4210
|
+
import { join as join7 } from "path";
|
|
4211
|
+
var VHK_FOOTPRINT_FILES = [
|
|
4212
|
+
"CLAUDE.md",
|
|
4213
|
+
".cursorrules",
|
|
4214
|
+
".cursor/mcp.json",
|
|
4215
|
+
".vhk/context.md",
|
|
4216
|
+
"docs/PRD.md"
|
|
4217
|
+
];
|
|
4218
|
+
function detectExistingFootprint(cwd) {
|
|
4219
|
+
return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join7(cwd, rel)));
|
|
4220
|
+
}
|
|
4221
|
+
async function runGitInit(cwd) {
|
|
4222
|
+
try {
|
|
4223
|
+
const git3 = simpleGit2(cwd);
|
|
4224
|
+
const isRepo = await git3.checkIsRepo().catch(() => false);
|
|
4225
|
+
if (isRepo) {
|
|
4226
|
+
log.info(ko.start.gitAlreadyInit);
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
await git3.init();
|
|
4230
|
+
log.success(ko.start.gitInitDone);
|
|
4231
|
+
} catch (err) {
|
|
4232
|
+
log.error(`git init \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`);
|
|
4233
|
+
throw new Error("step1-git-init");
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
async function runStep(label, fn) {
|
|
4237
|
+
try {
|
|
4238
|
+
return await fn();
|
|
4239
|
+
} catch (err) {
|
|
4240
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4241
|
+
log.error(`${label} \uC2E4\uD328: ${msg}`);
|
|
4242
|
+
log.warn("\uB9C8\uBC95\uC0AC\uB97C \uC911\uB2E8\uD569\uB2C8\uB2E4. \uC774\uBBF8 \uC0DD\uC131\uB41C \uD30C\uC77C\uC740 \uADF8\uB300\uB85C \uB0A8\uC544\uC788\uC5B4\uC694.");
|
|
4243
|
+
log.warn("\uBB38\uC81C\uB97C \uACE0\uCE5C \uB4A4 `vhk start`\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uAC70\uB098, \uAC1C\uBCC4 \uBA85\uB839(`vhk init`, `vhk mcp-init`, `vhk context`)\uC73C\uB85C \uC774\uC5B4 \uC9C4\uD589\uD558\uC138\uC694.");
|
|
4244
|
+
throw err;
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
async function start(options = {}) {
|
|
4248
|
+
console.log(chalk25.bold(`
|
|
4249
|
+
${ko.start.title}
|
|
4250
|
+
`));
|
|
4251
|
+
console.log(chalk25.dim(ko.start.intro));
|
|
4252
|
+
console.log(chalk25.dim(` ${ko.start.step1}`));
|
|
4253
|
+
console.log(chalk25.dim(` ${ko.start.step2}`));
|
|
4254
|
+
console.log(chalk25.dim(` ${ko.start.step3}`));
|
|
4255
|
+
console.log(chalk25.dim(` ${ko.start.step4}`));
|
|
4256
|
+
console.log();
|
|
4257
|
+
const cwd = process.cwd();
|
|
4258
|
+
const footprint = detectExistingFootprint(cwd);
|
|
4259
|
+
if (footprint.length > 0 && !options.yes) {
|
|
4260
|
+
console.log(chalk25.yellow("\u26A0\uFE0F \uC774\uBBF8 VHK \uC124\uCE58 \uD754\uC801\uC774 \uAC10\uC9C0\uB410\uC5B4\uC694:"));
|
|
4261
|
+
for (const f of footprint) console.log(chalk25.dim(` - ${f}`));
|
|
4262
|
+
console.log(chalk25.dim(" \uACC4\uC18D \uC9C4\uD589\uD558\uBA74 \uC77C\uBD80 \uD30C\uC77C(`.cursor/mcp.json`, `.vhk/context.md`)\uC740 \uAC31\uC2E0\xB7\uB36E\uC5B4\uC4F0\uAE30\uB429\uB2C8\uB2E4."));
|
|
4263
|
+
const { proceedExisting } = await inquirer10.prompt([{
|
|
4264
|
+
type: "confirm",
|
|
4265
|
+
name: "proceedExisting",
|
|
4266
|
+
message: "\uADF8\uB798\uB3C4 \uB2E4\uC2DC \uB9C8\uBC95\uC0AC\uB97C \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
4267
|
+
default: false
|
|
4268
|
+
}]);
|
|
4269
|
+
if (!proceedExisting) {
|
|
4270
|
+
log.warn(ko.start.cancelled);
|
|
4271
|
+
return;
|
|
4272
|
+
}
|
|
4273
|
+
} else if (!options.yes) {
|
|
4274
|
+
const { proceed } = await inquirer10.prompt([{
|
|
4275
|
+
type: "confirm",
|
|
4276
|
+
name: "proceed",
|
|
4277
|
+
message: ko.start.confirmStart,
|
|
4278
|
+
default: true
|
|
4279
|
+
}]);
|
|
4280
|
+
if (!proceed) {
|
|
4281
|
+
log.warn(ko.start.cancelled);
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
log.step(ko.start.step1Header);
|
|
4286
|
+
await runStep("[1/4] git init", () => runGitInit(cwd));
|
|
4287
|
+
log.step(ko.start.step2Header);
|
|
4288
|
+
await runStep("[2/4] vhk init", () => init({
|
|
4289
|
+
skipGate: true,
|
|
4290
|
+
fromNotion: options.fromNotion,
|
|
4291
|
+
name: options.name,
|
|
4292
|
+
description: options.description,
|
|
4293
|
+
type: options.type,
|
|
4294
|
+
yes: options.yes
|
|
4295
|
+
}));
|
|
4296
|
+
log.step(ko.start.step3Header);
|
|
4297
|
+
await runStep("[3/4] vhk mcp-init", () => mcpInit());
|
|
4298
|
+
log.step(ko.start.step4Header);
|
|
4299
|
+
await runStep("[4/4] vhk context", () => context());
|
|
4300
|
+
console.log(chalk25.bold.green(`
|
|
4301
|
+
${ko.start.allDone}
|
|
4302
|
+
`));
|
|
4303
|
+
printNextStep({
|
|
4304
|
+
message: ko.start.nextHintMessage,
|
|
4305
|
+
cursorHint: ko.start.nextHintCursor
|
|
4306
|
+
});
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4747
4309
|
// src/lib/nlp-run.ts
|
|
4748
4310
|
async function dispatchNlpRoute(route, input) {
|
|
4749
4311
|
switch (route.command) {
|
|
4750
4312
|
case "gate":
|
|
4751
4313
|
return gate();
|
|
4314
|
+
case "start":
|
|
4315
|
+
return start({
|
|
4316
|
+
fromNotion: route.args?.includes("--from-notion") ? extractNotionUrl(input) : void 0
|
|
4317
|
+
});
|
|
4752
4318
|
case "init":
|
|
4753
4319
|
return init({
|
|
4754
4320
|
skipGate: route.args?.includes("--skip-gate"),
|
|
@@ -4808,28 +4374,35 @@ async function dispatchNlpRoute(route, input) {
|
|
|
4808
4374
|
return memoryList();
|
|
4809
4375
|
case "brief":
|
|
4810
4376
|
return brief();
|
|
4377
|
+
case "goal": {
|
|
4378
|
+
const sub = route.args?.[0];
|
|
4379
|
+
if (sub === "next") return goalNext();
|
|
4380
|
+
if (sub === "check") return goalCheck({});
|
|
4381
|
+
if (sub === "done") return goalDone({});
|
|
4382
|
+
return goalList();
|
|
4383
|
+
}
|
|
4811
4384
|
}
|
|
4812
4385
|
}
|
|
4813
4386
|
async function runNaturalLanguageRoute(input) {
|
|
4814
4387
|
const route = routeNaturalLanguage(input);
|
|
4815
4388
|
if (!route) {
|
|
4816
|
-
console.log(
|
|
4389
|
+
console.log(chalk26.yellow(`
|
|
4817
4390
|
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
4818
4391
|
`));
|
|
4819
4392
|
return;
|
|
4820
4393
|
}
|
|
4821
4394
|
console.log("");
|
|
4822
|
-
console.log(
|
|
4823
|
-
console.log(
|
|
4395
|
+
console.log(chalk26.cyan(` \u{1F4AC} "${input}"`));
|
|
4396
|
+
console.log(chalk26.cyan(` \u2192 ${route.explanation}`));
|
|
4824
4397
|
if (route.confidence === "low") {
|
|
4825
|
-
const { confirm } = await
|
|
4398
|
+
const { confirm } = await inquirer11.prompt([{
|
|
4826
4399
|
type: "confirm",
|
|
4827
4400
|
name: "confirm",
|
|
4828
4401
|
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
4829
4402
|
default: true
|
|
4830
4403
|
}]);
|
|
4831
4404
|
if (!confirm) {
|
|
4832
|
-
console.log(
|
|
4405
|
+
console.log(chalk26.dim(` ${ko.nlp.menuHint}`));
|
|
4833
4406
|
return;
|
|
4834
4407
|
}
|
|
4835
4408
|
}
|
|
@@ -4838,17 +4411,17 @@ async function runNaturalLanguageRoute(input) {
|
|
|
4838
4411
|
}
|
|
4839
4412
|
|
|
4840
4413
|
// src/lib/version.ts
|
|
4841
|
-
import { existsSync as
|
|
4842
|
-
import { dirname as dirname3, join as
|
|
4414
|
+
import { existsSync as existsSync15 } from "fs";
|
|
4415
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
4843
4416
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4844
4417
|
function getVhkVersion2() {
|
|
4845
4418
|
const dir = dirname3(fileURLToPath4(import.meta.url));
|
|
4846
4419
|
for (const pkgPath of [
|
|
4847
|
-
|
|
4848
|
-
|
|
4420
|
+
join8(dir, "../../package.json"),
|
|
4421
|
+
join8(dir, "../package.json")
|
|
4849
4422
|
]) {
|
|
4850
4423
|
try {
|
|
4851
|
-
if (
|
|
4424
|
+
if (existsSync15(pkgPath)) {
|
|
4852
4425
|
const pkg = readJsonFile(pkgPath);
|
|
4853
4426
|
if (pkg.version) return pkg.version;
|
|
4854
4427
|
}
|
|
@@ -4859,12 +4432,88 @@ function getVhkVersion2() {
|
|
|
4859
4432
|
return "0.0.0";
|
|
4860
4433
|
}
|
|
4861
4434
|
|
|
4435
|
+
// src/commands/agent.ts
|
|
4436
|
+
import chalk27 from "chalk";
|
|
4437
|
+
function activeGoalId() {
|
|
4438
|
+
const goals = listGoals("goals");
|
|
4439
|
+
const id = selectActiveId(goals);
|
|
4440
|
+
return id ?? void 0;
|
|
4441
|
+
}
|
|
4442
|
+
async function blocker(description) {
|
|
4443
|
+
console.log(chalk27.bold(`
|
|
4444
|
+
${ko.agent.blockerTitle}
|
|
4445
|
+
`));
|
|
4446
|
+
if (!description || !description.trim()) {
|
|
4447
|
+
console.log(chalk27.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
|
|
4448
|
+
console.log(chalk27.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
|
|
4449
|
+
process.exitCode = 1;
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4452
|
+
const goalId = activeGoalId();
|
|
4453
|
+
const r = appendBlocker(description, goalId);
|
|
4454
|
+
console.log(chalk27.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
|
|
4455
|
+
if (r.hardStopTripped) {
|
|
4456
|
+
console.log(chalk27.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
|
|
4457
|
+
console.log(chalk27.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
|
|
4458
|
+
process.exitCode = 2;
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
async function learn(lesson) {
|
|
4462
|
+
console.log(chalk27.bold(`
|
|
4463
|
+
${ko.agent.learnTitle}
|
|
4464
|
+
`));
|
|
4465
|
+
if (!lesson || !lesson.trim()) {
|
|
4466
|
+
console.log(chalk27.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
|
|
4467
|
+
console.log(chalk27.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
|
|
4468
|
+
process.exitCode = 1;
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
const goalId = activeGoalId();
|
|
4472
|
+
appendLearning(lesson, goalId);
|
|
4473
|
+
console.log(chalk27.green(" \u2705 learnings.md append."));
|
|
4474
|
+
console.log(
|
|
4475
|
+
chalk27.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
|
|
4476
|
+
);
|
|
4477
|
+
}
|
|
4478
|
+
async function resume(opts = {}) {
|
|
4479
|
+
console.log(chalk27.bold(`
|
|
4480
|
+
${ko.agent.resumeTitle}
|
|
4481
|
+
`));
|
|
4482
|
+
if (!isHardStopActive()) {
|
|
4483
|
+
console.log(chalk27.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
|
|
4484
|
+
return;
|
|
4485
|
+
}
|
|
4486
|
+
const reason = readHardStopReason();
|
|
4487
|
+
if (reason) {
|
|
4488
|
+
console.log(chalk27.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
|
|
4489
|
+
console.log(chalk27.dim(` ${reason.split("\n").join("\n ")}`));
|
|
4490
|
+
console.log("");
|
|
4491
|
+
}
|
|
4492
|
+
if (!opts.confirm) {
|
|
4493
|
+
console.log(
|
|
4494
|
+
chalk27.red(
|
|
4495
|
+
" \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
|
|
4496
|
+
)
|
|
4497
|
+
);
|
|
4498
|
+
console.log(chalk27.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
|
|
4499
|
+
process.exitCode = 1;
|
|
4500
|
+
return;
|
|
4501
|
+
}
|
|
4502
|
+
const removed = clearHardStop();
|
|
4503
|
+
if (removed) {
|
|
4504
|
+
console.log(chalk27.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
|
|
4505
|
+
} else {
|
|
4506
|
+
console.log(chalk27.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4862
4510
|
// src/index.ts
|
|
4863
4511
|
var program = new Command();
|
|
4864
4512
|
var defaultHelp = new Help();
|
|
4865
4513
|
var KO_ALIASES = {
|
|
4866
4514
|
gate: "\uAC80\uC99D",
|
|
4867
|
-
|
|
4515
|
+
start: "\uC2DC\uC791",
|
|
4516
|
+
init: "\uCD08\uAE30\uD654",
|
|
4868
4517
|
recap: "\uC815\uB9AC",
|
|
4869
4518
|
sync: "\uADDC\uCE59",
|
|
4870
4519
|
check: "\uC810\uAC80",
|
|
@@ -4890,7 +4539,11 @@ var KO_ALIASES = {
|
|
|
4890
4539
|
context: "\uB9E5\uB77D",
|
|
4891
4540
|
"context-show": "\uB9E5\uB77D\uBCF4\uAE30",
|
|
4892
4541
|
memory: "\uAE30\uC5B5",
|
|
4893
|
-
brief: "\uBE0C\uB9AC\uD551"
|
|
4542
|
+
brief: "\uBE0C\uB9AC\uD551",
|
|
4543
|
+
goal: "\uBAA9\uD45C",
|
|
4544
|
+
blocker: "\uBE14\uB85C\uCEE4",
|
|
4545
|
+
learn: "\uAD50\uD6C8",
|
|
4546
|
+
resume: "\uC7AC\uAC1C"
|
|
4894
4547
|
};
|
|
4895
4548
|
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version(getVhkVersion2());
|
|
4896
4549
|
program.configureHelp({
|
|
@@ -4917,10 +4570,13 @@ program.configureHelp({
|
|
|
4917
4570
|
}
|
|
4918
4571
|
});
|
|
4919
4572
|
program.command("gate").alias("\uAC80\uC99D").alias("\uC544\uC774\uB514\uC5B4").description("\uC544\uC774\uB514\uC5B4 \uAC80\uC99D \u2192 \uC2DC\uC791\uD574\uB3C4 \uB3FC\uC694 / \uB2E4\uB4EC\uAE30 / \uB2E4\uB978 \uC544\uC774\uB514\uC5B4").action(gate);
|
|
4920
|
-
program.command("
|
|
4573
|
+
program.command("start").alias("\uC2DC\uC791").alias("\uC0C8\uD504\uB85C\uC81D\uD2B8").description("\uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 \uB9C8\uBC95\uC0AC \u2014 git init + \uBB38\uC11C + MCP + \uCEE8\uD14D\uC2A4\uD2B8 \uD55C \uBC88\uC5D0").option("--from-notion <url>", "Notion PRD \uD398\uC774\uC9C0\uC5D0\uC11C import").option("--name <name>", "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984").option("--description <desc>", "\uD55C \uC904 \uC124\uBA85").option("--type <type>", "\uD504\uB85C\uC81D\uD2B8 \uC720\uD615 (webapp|extension|cli|notion|mobile)").option("-y, --yes", "\uBAA8\uB4E0 \uD655\uC778 \uC2A4\uD0B5 (\uC790\uB3D9 yes)").action(start);
|
|
4574
|
+
program.command("init").alias("\uCD08\uAE30\uD654").alias("\uB9CC\uB4E4\uAE30").description("\uD558\uB124\uC2A4 \uD30C\uC77C\uB9CC \uC0DD\uC131 (git/MCP/context\uB294 \uC81C\uC678) \u2014 \uBCF4\uD1B5 vhk start \uAD8C\uC7A5").option("--skip-gate", "gate \uAC80\uC99D \uC2A4\uD0B5").option("--from-notion <url>", "Notion PRD \uD398\uC774\uC9C0\uC5D0\uC11C import").option("--name <name>", "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984").option("--description <desc>", "\uD55C \uC904 \uC124\uBA85").option("--type <type>", "\uD504\uB85C\uC81D\uD2B8 \uC720\uD615 (webapp|extension|cli|notion|mobile)").option("-y, --yes", "\uC2A4\uD0DD \uD655\uC778 \uC2A4\uD0B5").action(init);
|
|
4921
4575
|
program.command("recap").alias("\uC815\uB9AC").alias("\uC624\uB298").description("\uC624\uB298 \uD55C \uC77C \uC815\uB9AC + ADR/\uD2B8\uB7EC\uBE14\uC288\uD305 \uC790\uB3D9 \uBD84\uB9AC").option("--since <date>", "\uBD84\uC11D \uC2DC\uC791\uC77C (YYYY-MM-DD)").action(recap);
|
|
4922
4576
|
program.command("sync").alias("\uB9DE\uCD94\uAE30").alias("\uADDC\uCE59").description("RULES.md \u2192 .cursorrules + CLAUDE.md \uB3D9\uAE30\uD654").action(sync);
|
|
4923
|
-
program.command("check").alias("\uC810\uAC80").alias("\uB9B0\uD2B8").description("RULES.md \uADDC\uCE59 \uC810\uAC80 \u2014 \uCF54\uB4DC \uC704\uBC18 \uAC80\uC0AC").action(
|
|
4577
|
+
program.command("check").alias("\uC810\uAC80").alias("\uB9B0\uD2B8").option("--goal <id>", "goal id \uC9C0\uC815 \uC2DC scripts/check-goal-<id>.sh \uAC8C\uC774\uD2B8 \uC2E4\uD589").description("RULES.md \uADDC\uCE59 \uC810\uAC80 \u2014 \uCF54\uB4DC \uC704\uBC18 \uAC80\uC0AC (\uB610\uB294 --goal <id> \uB85C goal \uAC8C\uC774\uD2B8)").action(async (opts) => {
|
|
4578
|
+
await check(opts);
|
|
4579
|
+
});
|
|
4924
4580
|
var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C \u2014 scan: \uC2DC\uD06C\uB9BF\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC").action(secure);
|
|
4925
4581
|
secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
|
|
4926
4582
|
program.command("ship").alias("\uCD9C\uD558").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
|
|
@@ -5008,6 +4664,33 @@ memoryCmd.command("remove <index>").alias("\uC0AD\uC81C").description("\uAE30\uC
|
|
|
5008
4664
|
program.command("brief").alias("\uBE0C\uB9AC\uD551").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC694\uC57D \uBCF4\uACE0\uC11C \uC0DD\uC131 (.vhk/brief.md)").action(async () => {
|
|
5009
4665
|
await brief();
|
|
5010
4666
|
});
|
|
4667
|
+
var goalCmd = program.command("goal").alias("\uBAA9\uD45C").description("Goal \uB2E8\uACC4\uBCC4 \uBBF8\uC158 \uAD00\uB9AC (init / list / next / check / done)").action(async () => {
|
|
4668
|
+
await goalList();
|
|
4669
|
+
});
|
|
4670
|
+
goalCmd.command("list").alias("\uBAA9\uB85D").description("goals/*.md \uBAA9\uB85D (id, status, priority, title)").action(async () => {
|
|
4671
|
+
await goalList();
|
|
4672
|
+
});
|
|
4673
|
+
goalCmd.command("next").alias("\uB2E4\uC74C").description("active goal \uC790\uB3D9 \uC120\uD0DD \u2192 docs/state/next-task.md \uAC31\uC2E0").action(async () => {
|
|
4674
|
+
await goalNext();
|
|
4675
|
+
});
|
|
4676
|
+
goalCmd.command("init").alias("\uCD08\uAE30\uD654").description("\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uC5D0 goals/ + docs/state/ \uC2A4\uCE90\uD3F4\uB529 (\uAE30\uC874 \uD30C\uC77C \uBCF4\uC874)").action(async () => {
|
|
4677
|
+
await goalInit();
|
|
4678
|
+
});
|
|
4679
|
+
goalCmd.command("check").alias("\uAC80\uC99D").option("--id <id>", "goal id \uC9C0\uC815 (\uC0DD\uB7B5 \uC2DC active goal)").description("scripts/check-goal-<id>.sh \uC2E4\uD589 + exit code \uC804\uB2EC").action(async (opts) => {
|
|
4680
|
+
await goalCheck(opts);
|
|
4681
|
+
});
|
|
4682
|
+
goalCmd.command("done").alias("\uC644\uB8CC").option("--id <id>", "goal id \uC9C0\uC815 (\uC0DD\uB7B5 \uC2DC active goal)").description("\uAC8C\uC774\uD2B8 \uC7AC\uAC80\uC99D \u2192 \uD1B5\uACFC \uC2DC frontmatter status=DONE \uC73C\uB85C \uC804\uC774").action(async (opts) => {
|
|
4683
|
+
await goalDone(opts);
|
|
4684
|
+
});
|
|
4685
|
+
program.command("blocker <description>").alias("\uBE14\uB85C\uCEE4").description("\uBE14\uB85C\uCEE4 \uAE30\uB85D \u2192 docs/state/blockers.md append (3\uAC74 \uB204\uC801 \uC2DC HARD_STOP \uC790\uB3D9 \uC0DD\uC131)").action(async (description) => {
|
|
4686
|
+
await blocker(description);
|
|
4687
|
+
});
|
|
4688
|
+
program.command("learn <lesson>").alias("\uAD50\uD6C8").description("\uAD50\uD6C8 \uAE30\uB85D \u2192 docs/state/learnings.md append (memory.json \uACFC \uBCC4\uB3C4 SoT)").action(async (lesson) => {
|
|
4689
|
+
await learn(lesson);
|
|
4690
|
+
});
|
|
4691
|
+
program.command("resume").alias("\uC7AC\uAC1C").option("--confirm", "\uC0AC\uB78C \uD655\uC778 \u2014 \uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0 (Forbidden \uC704\uBC18)").description(".vhk/HARD_STOP \uD574\uC81C (\uC0AC\uC6A9\uC790\uAC00 \uC0AC\uC720 \uD655\uC778 \uD6C4 --confirm \uD544\uC694)").action(async (opts) => {
|
|
4692
|
+
await resume(opts);
|
|
4693
|
+
});
|
|
5011
4694
|
program.on("command:*", async (operands) => {
|
|
5012
4695
|
const unknown = operands[0] ?? "";
|
|
5013
4696
|
const rest = operands.slice(1);
|
|
@@ -5016,13 +4699,13 @@ program.on("command:*", async (operands) => {
|
|
|
5016
4699
|
});
|
|
5017
4700
|
program.action(async () => {
|
|
5018
4701
|
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
|
|
5019
|
-
const { choice } = await
|
|
4702
|
+
const { choice } = await inquirer12.prompt([{
|
|
5020
4703
|
type: "list",
|
|
5021
4704
|
name: "choice",
|
|
5022
4705
|
message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
|
5023
4706
|
choices: [
|
|
5024
4707
|
{ name: "\u{1F4A1} \uC0C8 \uC544\uC774\uB514\uC5B4 \uAC80\uC99D\uD558\uAE30", value: "gate" },
|
|
5025
|
-
{ name: "\u{
|
|
4708
|
+
{ name: "\u{1F680} \uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 \uB9C8\uBC95\uC0AC (start)", value: "start" },
|
|
5026
4709
|
{ name: "\u{1F4DD} \uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD558\uAE30", value: "recap" },
|
|
5027
4710
|
{ name: "\u{1F50D} \uADDC\uCE59 \uD30C\uC77C \uC810\uAC80\uD558\uAE30", value: "check" },
|
|
5028
4711
|
{ name: "\u{1F512} \uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB9AC\uAE30", value: "secure" },
|
|
@@ -5038,8 +4721,8 @@ program.action(async () => {
|
|
|
5038
4721
|
switch (choice) {
|
|
5039
4722
|
case "gate":
|
|
5040
4723
|
return gate();
|
|
5041
|
-
case "
|
|
5042
|
-
return
|
|
4724
|
+
case "start":
|
|
4725
|
+
return start();
|
|
5043
4726
|
case "recap":
|
|
5044
4727
|
return recap({});
|
|
5045
4728
|
case "check":
|