@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/dist/index.js CHANGED
@@ -1,479 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- __commonJS,
4
- __toESM,
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
- safeExecFileStream,
17
+ scanProjectForSecrets,
12
18
  startMcpServer,
13
19
  t
14
- } from "./chunk-SD7O6HO7.js";
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 inquirer14 from "inquirer";
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: "init",
496
- explanation: "\uAC80\uC99D \uC2A4\uD0B5\uD558\uACE0 \uBC14\uB85C \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --skip-gate)",
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: "\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791)",
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) => (/프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|하네스|초기화/.test(t2) || /^시작$/.test(t2)) && !/디자인|design|팔레트|palette|테마|theme|레퍼런스|reference|다크\s*모드|라이트\s*모드|색상\s*모드|브리핑|brief|컨텍스트|context|맥락|기억|memory/.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
- "init",
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 chalk28 from "chalk";
798
- import inquirer13 from "inquirer";
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 chalk4 from "chalk";
933
- import fs3 from "fs";
934
- import path3 from "path";
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 chalk3 from "chalk";
741
+ import chalk2 from "chalk";
1232
742
  var log = {
1233
- success: (msg) => console.log(chalk3.green(`\u2705 ${msg}`)),
1234
- error: (msg) => console.log(chalk3.red(`\u274C ${msg}`)),
1235
- warn: (msg) => console.log(chalk3.yellow(`\u26A0\uFE0F ${msg}`)),
1236
- info: (msg) => console.log(chalk3.blue(`\u2139\uFE0F ${msg}`)),
1237
- step: (msg) => console.log(chalk3.bold(`
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 fs2 from "fs";
1243
- import path2 from "path";
752
+ import fs from "fs";
753
+ import path from "path";
1244
754
  function writeFile(filePath, content) {
1245
- const dir = path2.dirname(filePath);
1246
- if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
1247
- fs2.writeFileSync(filePath, content, "utf-8");
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 fs2.existsSync(filePath);
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(chalk4.dim(`
952
+ console.log(chalk3.dim(`
1443
953
  ${ko.init.skipGate}
1444
954
  `));
1445
955
  }
1446
- console.log(chalk4.bold(`
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(chalk4.dim(`
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 = path3.join(cwd, filePath);
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(chalk4.bold.green(`
1017
+ console.log(chalk3.bold.green(`
1508
1018
  ${ko.init.done}`));
1509
- console.log(chalk4.dim(`
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(` ${chalk4.cyan(ko.init.gitHintCommand)}`);
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(` ${chalk4.cyan(ko.init.gitHintCommand)}`);
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 = path3.join(projectDir, "package.json");
1570
- if (!fs3.existsSync(pkgPath)) return false;
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
- fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
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 = path3.join(projectDir, "COMMANDS.md");
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 chalk5 from "chalk";
1603
- import fs5 from "fs";
1604
- import path6 from "path";
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 path4 from "path";
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 = path4.basename(filePath);
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
- return buildSessionDiffFromSummary(diffSummary);
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 fs4 from "fs";
1689
- import path5 from "path";
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 (!fs4.existsSync(adrDir)) return 1;
1733
- const nums = fs4.readdirSync(adrDir).map((name) => name.match(/^ADR-(\d+)/i)?.[1]).filter((n) => Boolean(n)).map((n) => parseInt(n, 10));
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 = path5.join(cwd, "docs", "adr");
1741
- if (!fs4.existsSync(adrDir)) fs4.mkdirSync(adrDir, { recursive: true });
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 = path5.join(adrDir, fileName);
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
- fs4.writeFileSync(filePath, content, "utf-8");
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(chalk5.bold(`
1296
+ console.log(chalk4.bold(`
1778
1297
  ${ko.recap.title}
1779
1298
  `));
1780
1299
  if (!await isGitRepo()) {
1781
- console.log(chalk5.red(ko.recap.noRepo));
1300
+ console.log(chalk4.red(ko.recap.noRepo));
1782
1301
  return;
1783
1302
  }
1784
1303
  if (!await hasAnyCommits()) {
1785
- console.log(chalk5.yellow("\u26A0\uFE0F \uC544\uC9C1 \uCEE4\uBC0B\uC774 \uC5C6\uC5B4\uC694."));
1786
- console.log(chalk5.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."));
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(chalk5.dim(`${ko.recap.analyzing}
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(chalk5.yellow(ko.recap.noChanges));
1315
+ console.log(chalk4.yellow(ko.recap.noChanges));
1797
1316
  return;
1798
1317
  }
1799
- console.log(chalk5.bold("\u{1F4CA} \uBCC0\uACBD \uC694\uC57D:"));
1800
- console.log(` \uD30C\uC77C: ${chalk5.cyan(String(diff2.filesChanged))}\uAC1C \uBCC0\uACBD`);
1801
- console.log(` \uCD94\uAC00: ${chalk5.green("+" + diff2.insertions)} / \uC0AD\uC81C: ${chalk5.red("-" + diff2.deletions)}`);
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(chalk5.dim("\n \uBCC0\uACBD \uD30C\uC77C:"));
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" ? chalk5.green("\u{1F195}") : f.status === "deleted" ? chalk5.red("\u{1F5D1}\uFE0F") : chalk5.yellow("\u270F\uFE0F");
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(chalk5.dim(` ... \uC678 ${diff2.files.length - 15}\uAC1C`));
1328
+ console.log(chalk4.dim(` ... \uC678 ${diff2.files.length - 15}\uAC1C`));
1810
1329
  }
1811
1330
  }
1812
1331
  if (commits.length > 0) {
1813
- console.log(chalk5.dim("\n \uCD5C\uADFC \uCEE4\uBC0B:"));
1332
+ console.log(chalk4.dim("\n \uCD5C\uADFC \uCEE4\uBC0B:"));
1814
1333
  commits.slice(0, 5).forEach((c) => {
1815
- console.log(chalk5.dim(` \u2022 ${c.message}`));
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 = path6.join(process.cwd(), "docs", "log");
1845
- if (!fs5.existsSync(logDir)) fs5.mkdirSync(logDir, { recursive: true });
1846
- const existing = fs5.readdirSync(logDir).filter((f) => f.startsWith(today));
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 = path6.join(logDir, fileName);
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
- fs5.writeFileSync(filePath, content, "utf-8");
1399
+ fs4.writeFileSync(filePath, content, "utf-8");
1881
1400
  const adrCandidates = detectAdrCandidates(diff2);
1882
1401
  if (adrCandidates.length > 0) {
1883
- console.log(chalk5.cyan.bold(`
1402
+ console.log(chalk4.cyan.bold(`
1884
1403
  ${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
1885
1404
  for (const candidate of adrCandidates) {
1886
- console.log(chalk5.cyan(` \u2022 ${candidate.title}: ${candidate.context}`));
1887
- candidate.files.forEach((f) => console.log(chalk5.dim(` ${f}`)));
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(chalk5.green(` \u2705 ADR \uC0DD\uC131: ${path6.relative(process.cwd(), adrPath)}`));
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(chalk5.yellow.bold(`
1443
+ console.log(chalk4.yellow.bold(`
1925
1444
  ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
1926
1445
  troubleCommits.forEach((c) => {
1927
- console.log(chalk5.dim(` \u2022 ${c.message}`));
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 = path6.join(process.cwd(), "docs", "troubleshooting");
1937
- if (!fs5.existsSync(tsDir)) fs5.mkdirSync(tsDir, { recursive: true });
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 = path6.join(tsDir, tsFileName);
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
- fs5.writeFileSync(tsFilePath, tsContent, "utf-8");
1978
- console.log(chalk5.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${path6.relative(process.cwd(), tsFilePath)}`));
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(chalk5.green.bold(`
1500
+ console.log(chalk4.green.bold(`
1982
1501
  ${ko.recap.done}`));
1983
- console.log(chalk5.dim(` \u{1F4C4} ${path6.relative(process.cwd(), filePath)}`));
1984
- const claudeMdPath = path6.join(process.cwd(), "CLAUDE.md");
1985
- if (fs5.existsSync(claudeMdPath)) {
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 = fs5.readFileSync(claudeMdPath, "utf-8");
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
- fs5.writeFileSync(claudeMdPath, claudeContent, "utf-8");
2003
- console.log(chalk5.green(" \u2705 CLAUDE.md \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC"));
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 chalk6 from "chalk";
2016
- import fs6 from "fs";
2017
- import path7 from "path";
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(chalk6.bold(`
1604
+ console.log(chalk5.bold(`
2086
1605
  ${ko.sync.title}
2087
1606
  `));
2088
1607
  const cwd = process.cwd();
2089
- const rulesPath = path7.join(cwd, "RULES.md");
2090
- if (!fs6.existsSync(rulesPath)) {
2091
- console.log(chalk6.yellow(ko.sync.noRules));
2092
- console.log(chalk6.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
2093
- console.log(chalk6.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
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(chalk6.dim(" RULES.md \uAE30\uBCF8 \uAD6C\uC870:"));
2096
- console.log(chalk6.dim(" ## \uD504\uB85C\uC81D\uD2B8 \uC815\uCCB4\uC131"));
2097
- console.log(chalk6.dim(" ## \uAE30\uC220 \uC2A4\uD0DD"));
2098
- console.log(chalk6.dim(" ## \uCF54\uB529 \uADDC\uCE59"));
2099
- console.log(chalk6.dim(" ## \uAE30\uB85D \uADDC\uCE59"));
2100
- console.log(chalk6.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
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 = fs6.readFileSync(rulesPath, "utf-8");
1622
+ const rulesContent = fs5.readFileSync(rulesPath, "utf-8");
2104
1623
  const sections = parseRulesMd(rulesContent);
2105
- console.log(chalk6.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
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 = path7.join(cwd, ".cursorrules");
2109
- fs6.writeFileSync(cursorrulesPath, toCursorrules(sections, projectName), "utf-8");
2110
- console.log(chalk6.green(` ${ko.sync.cursorrulesDone}`));
2111
- const claudePath = path7.join(cwd, "CLAUDE.md");
2112
- const existingClaude = fs6.existsSync(claudePath) ? fs6.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
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
- fs6.writeFileSync(claudePath, toClaudeMd(sections, existingClaude), "utf-8");
2120
- console.log(chalk6.green(` ${ko.sync.claudeDone}`));
2121
- console.log(chalk6.bold.green(`
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(chalk6.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md (\uC790\uB3D9 \uC0DD\uC131)"));
2124
- console.log(chalk6.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
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 path9 from "path";
2135
- import fs8 from "fs";
1653
+ import path8 from "path";
1654
+ import fs7 from "fs";
2136
1655
 
2137
1656
  // src/lib/rules-parser.ts
2138
- import fs7 from "fs";
2139
- import path8 from "path";
1657
+ import fs6 from "fs";
1658
+ import path7 from "path";
2140
1659
  function parseRules(rulesPath) {
2141
- if (!fs7.existsSync(rulesPath)) return [];
2142
- const content = fs7.readFileSync(rulesPath, "utf-8");
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 = path8.join(cwd, "src");
2204
- if (!fs7.existsSync(srcDir)) return violations;
1722
+ const srcDir = path7.join(cwd, "src");
1723
+ if (!fs6.existsSync(srcDir)) return violations;
2205
1724
  walkFiles(srcDir, (filePath) => {
2206
- const name = path8.basename(filePath, path8.extname(filePath));
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: path8.relative(cwd, filePath)
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 = path8.join(cwd, expectedPath);
2230
- if (!fs7.existsSync(fullPath)) {
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 = path8.join(cwd, "src");
2251
- if (!fs7.existsSync(srcDir)) return violations;
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 = fs7.readFileSync(filePath, "utf-8");
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: path8.relative(cwd, filePath),
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 = fs7.readdirSync(dir, { withFileTypes: true });
1793
+ const entries = fs6.readdirSync(dir, { withFileTypes: true });
2275
1794
  for (const entry of entries) {
2276
- const fullPath = path8.join(dir, entry.name);
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/check.ts
2291
- async function check() {
2292
- console.log(chalk7.bold(`
2293
- ${ko.check.title}
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 cwd = process.cwd();
2296
- const rulesPath = path9.join(cwd, "RULES.md");
2297
- if (!fs8.existsSync(rulesPath)) {
2298
- console.log(chalk7.yellow(ko.check.noRules));
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 rules = parseRules(rulesPath);
2303
- console.log(chalk7.dim(` \u{1F4CF} ${rules.length}\uAC1C \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 \uAC10\uC9C0
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
- if (rules.length === 0) {
2306
- console.log(chalk7.yellow(ko.check.noAutoRules));
2307
- 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."));
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 allViolations = [];
2311
- let passCount = 0;
2312
- for (const rule of rules) {
2313
- const violations = rule.check(cwd);
2314
- if (violations.length === 0) {
2315
- console.log(chalk7.green(` \u2705 ${rule.id}`) + chalk7.dim(` \u2014 ${rule.description.slice(0, 60)}`));
2316
- passCount++;
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
- console.log(chalk7.red(` \u274C ${rule.id}`) + chalk7.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
2319
- violations.forEach((v) => {
2320
- const loc = v.file ? chalk7.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
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
- const errors = allViolations.filter((v) => v.severity === "error").length;
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 fs11 from "fs";
2356
- import path11 from "path";
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 = path11.join(cwd, ".gitignore");
2560
- const hasGitignore = fs11.existsSync(gitignorePath);
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 = fs11.readFileSync(gitignorePath, "utf-8");
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 { execSync } from "child_process";
2630
- import fs12 from "fs";
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
- try {
2635
- const version = execSync(`${command} --version`, { encoding: "utf-8" }).trim().split("\n")[0];
2636
- return { name, command, version, ok: true, hint };
2637
- } catch {
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 = path12.dirname(fileURLToPath(import.meta.url));
2302
+ const dir = path10.dirname(fileURLToPath(import.meta.url));
2643
2303
  const candidates = [
2644
- path12.join(dir, "../package.json"),
2645
- path12.join(dir, "../../package.json")
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 (fs12.existsSync(pkgPath)) {
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
- try {
2661
- const result = execSync(`npm view ${packageName} version`, {
2662
- encoding: "utf-8",
2663
- timeout: 5e3,
2664
- stdio: ["ignore", "pipe", "ignore"]
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 = fs12.existsSync(path12.join(cwd, file.name));
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 = path12.join(cwd, ".gitignore");
2731
- if (fs12.existsSync(gitignorePath)) {
2732
- const gitignore = fs12.readFileSync(gitignorePath, "utf-8");
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 fs13 from "fs";
2765
- import path13 from "path";
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 = path13.join(cwd, "CHANGELOG.md");
2779
- if (!fs13.existsSync(changelogPath)) return { status: "missing" };
2780
- const content = fs13.readFileSync(changelogPath, "utf-8");
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
- fs13.writeFileSync(changelogPath, updated, "utf-8");
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 = path13.join(cwd, "docs", "build-log");
2855
- if (!fs13.existsSync(buildLogDir)) fs13.mkdirSync(buildLogDir, { recursive: true });
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 = path13.join(buildLogDir, fileName);
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
- fs13.writeFileSync(filePath, content, "utf-8");
2542
+ fs10.writeFileSync(filePath, content, "utf-8");
2889
2543
  console.log(chalk10.green(`
2890
- ${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
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 fs14 from "fs";
3191
- import path14 from "path";
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 = path14.join(cwd, "package.json");
3231
- if (!fs14.existsSync(pkgPath)) return null;
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
- try {
3311
- return execFileSync5("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
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
- try {
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 = join(dir, ...rel);
3410
- if (existsSync(candidate)) return candidate;
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 (existsSync(p)) return p;
3066
+ if (existsSync3(p)) return p;
3419
3067
  }
3420
3068
  } catch {
3421
3069
  }
3422
3070
  try {
3423
- const pkgPath = join(process.cwd(), "package.json");
3424
- if (existsSync(pkgPath)) {
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 = join(process.cwd(), "dist", "mcp", "index.js");
3428
- if (existsSync(local)) return local;
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 = join(process.cwd(), ".cursor");
3446
- if (!existsSync(cursorDir)) {
3447
- mkdirSync(cursorDir, { recursive: true });
3093
+ const cursorDir = join3(process.cwd(), ".cursor");
3094
+ if (!existsSync3(cursorDir)) {
3095
+ mkdirSync2(cursorDir, { recursive: true });
3448
3096
  }
3449
- const configPath = join(cursorDir, "mcp.json");
3097
+ const configPath = join3(cursorDir, "mcp.json");
3450
3098
  const vhkEntry = resolveVhkMcpEntry();
3451
3099
  let config;
3452
- if (existsSync(configPath)) {
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
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
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/deploy.ts
3476
- import { existsSync as existsSync2 } from "fs";
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(chalk18.bold("\n\u{1F3A8} " + t("design.title")));
3778
- console.log(chalk18.gray("\u2500".repeat(40)));
3779
- const { paletteIndex } = await inquirer9.prompt([
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(chalk18.cyan(`
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 inquirer9.prompt([{
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(chalk18.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
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
- mkdirSync2("src/styles", { recursive: true });
3229
+ mkdirSync3("src/styles", { recursive: true });
3808
3230
  writeFileSync3(targetPath, content, "utf-8");
3809
3231
  if (hasTailwind()) {
3810
- console.log(chalk18.green("\n\u2705 src/styles/vhk-colors.ts \uC0DD\uC131"));
3811
- console.log(chalk18.gray(" tailwind.config\uC758 extend.colors\uC5D0 import \uD574\uC11C \uC0AC\uC6A9\uD558\uC138\uC694."));
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(chalk18.green("\n\u2705 src/styles/tokens.css \uC0DD\uC131"));
3814
- console.log(chalk18.gray(" HTML\uC5D0 <link>\uB85C \uCD94\uAC00\uD558\uAC70\uB098 CSS\uC5D0\uC11C @import \uD558\uC138\uC694."));
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(chalk18.bold("\n\u{1F308} \uCEEC\uB7EC \uBBF8\uB9AC\uBCF4\uAE30:"));
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 mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
3832
- import chalk19 from "chalk";
3833
- import inquirer10 from "inquirer";
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(chalk19.bold("\n\u{1F319} " + t("theme.title")));
3890
- console.log(chalk19.gray("\u2500".repeat(40)));
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 inquirer10.prompt([{
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(chalk19.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
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
- mkdirSync3("src/styles", { recursive: true });
3908
- mkdirSync3("src/lib", { recursive: true });
3329
+ mkdirSync4("src/styles", { recursive: true });
3330
+ mkdirSync4("src/lib", { recursive: true });
3909
3331
  writeFileSync4(cssPath, generateDarkCSS(), "utf-8");
3910
- console.log(chalk19.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"));
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(chalk19.green("\u2705 src/lib/theme-toggle.ts \uC0DD\uC131 (\uD1A0\uAE00 \uC720\uD2F8\uB9AC\uD2F0)"));
3913
- console.log(chalk19.bold("\n\u{1F4D6} \uC0AC\uC6A9\uBC95:"));
3914
- console.log(chalk19.gray(" 1. theme.css\uB97C \uAE00\uB85C\uBC8C \uC2A4\uD0C0\uC77C\uC5D0 \uCD94\uAC00"));
3915
- console.log(chalk19.gray(' 2. import { initTheme, toggleTheme } from "./lib/theme-toggle"'));
3916
- console.log(chalk19.gray(" 3. \uC571 \uC9C4\uC785\uC810\uC5D0\uC11C initTheme() \uD638\uCD9C"));
3917
- console.log(chalk19.gray(" 4. \uD1A0\uAE00 \uBC84\uD2BC\uC5D0\uC11C toggleTheme() \uD638\uCD9C"));
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 mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
3927
- import chalk20 from "chalk";
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
- mkdirSync4(".vhk", { recursive: true });
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(chalk20.bold("\n\u{1F517} " + t("ref.addTitle")));
3944
- console.log(chalk20.gray("\u2500".repeat(40)));
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(chalk20.red("\u274C URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
3947
- console.log(chalk20.gray(' \uC608: vhk ref add https://example.com --memo "\uCC38\uACE0 \uC0AC\uC774\uD2B8"'));
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(chalk20.yellow("\u26A0\uFE0F \uC774\uBBF8 \uC800\uC7A5\uB41C URL\uC785\uB2C8\uB2E4."));
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(chalk20.green(`
3379
+ console.log(chalk18.green(`
3958
3380
  \u2705 \uB808\uD37C\uB7F0\uC2A4 \uCD94\uAC00\uB428 (#${refs.length})`));
3959
- console.log(chalk20.cyan(` ${url}`));
3960
- if (memo) console.log(chalk20.gray(` \u{1F4DD} ${memo}`));
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(chalk20.bold("\n\u{1F4DA} " + t("ref.listTitle")));
3969
- console.log(chalk20.gray("\u2500".repeat(40)));
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(chalk20.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uB808\uD37C\uB7F0\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
3973
- console.log(chalk20.gray(' vhk ref add <url> --memo "\uBA54\uBAA8"\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
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(chalk20.cyan(`
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(chalk20.white(` [${index + 1}] ${ref.url}`));
3982
- if (ref.memo) console.log(chalk20.gray(` \u{1F4DD} ${ref.memo}`));
3983
- console.log(chalk20.gray(` \u{1F4C5} ${date}`));
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(chalk20.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${refs.length || 0})`));
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(chalk20.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 URL: ${ref.url}`));
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(chalk20.red(`\u274C http(s) URL\uB9CC \uC5F4 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (${parsed.protocol})`));
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(chalk20.cyan(`
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("cmd.exe", ["/c", "start", "", ref.url]);
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(chalk20.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
3439
+ console.log(chalk18.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
4018
3440
  } else {
4019
- console.log(chalk20.yellow("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. URL\uC744 \uC9C1\uC811 \uBC29\uBB38\uD574\uC8FC\uC138\uC694."));
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 chalk21 from "chalk";
4026
- import ora3 from "ora";
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(chalk21.bold("\n\u{1F527} " + t("harness.title")));
4067
- console.log(chalk21.gray("\u2500".repeat(40)));
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(chalk21.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4071
- console.log(chalk21.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
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(chalk21.cyan(`
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 = ora3(`${check2.name} \uC2E4\uD589 \uC911...`).start();
4081
- const start = Date.now();
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() - start;
3505
+ const duration = Date.now() - start2;
4084
3506
  const sec = (duration / 1e3).toFixed(1);
4085
3507
  if (result.ok) {
4086
- spinner.succeed(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
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} ${chalk21.gray(`(${sec}s)`)}`);
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(chalk21.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
4100
- console.log(chalk21.gray("\u2500".repeat(40)));
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 ? chalk21.green("\u2705") : chalk21.red("\u274C");
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)} ${chalk21.gray(`${sec}s`)}`);
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(chalk21.gray("\u2500".repeat(40)));
3530
+ console.log(chalk19.gray("\u2500".repeat(40)));
4109
3531
  if (all) {
4110
- console.log(chalk21.green.bold(`
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
- chalk21.red.bold(`
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 existsSync9, unlinkSync, rmSync } from "fs";
4217
- import chalk23 from "chalk";
4218
- import inquirer12 from "inquirer";
4219
- import ora5 from "ora";
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 detectCurrentPM2() {
4226
- if (existsSync9("pnpm-lock.yaml")) return "pnpm";
4227
- if (existsSync9("yarn.lock")) return "yarn";
4228
- if (existsSync9("package-lock.json")) return "npm";
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 isCLIAvailable2(pm) {
3563
+ function isCLIAvailable(pm) {
4232
3564
  return safeExecFile(pm, ["--version"]).ok;
4233
3565
  }
4234
3566
  async function migrate(target) {
4235
- console.log(chalk23.bold("\n\u{1F504} " + t("migrate.title")));
4236
- console.log(chalk23.gray("\u2500".repeat(40)));
4237
- const current = detectCurrentPM2();
4238
- console.log(chalk23.cyan(`
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 inquirer12.prompt([
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(chalk23.yellow(`
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 (!isCLIAvailable2(targetPM)) {
4261
- console.log(chalk23.red(`
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(chalk23.yellow(` npm i -g ${targetPM}`));
3595
+ console.log(chalk20.yellow(` npm i -g ${targetPM}`));
4264
3596
  return;
4265
3597
  }
4266
- const { confirm } = await inquirer12.prompt([
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(chalk23.gray("\uCDE8\uC18C\uB428"));
3607
+ console.log(chalk20.gray("\uCDE8\uC18C\uB428"));
4276
3608
  return;
4277
3609
  }
4278
- const cleanup = ora5("\uAE30\uC874 lock \uD30C\uC77C \uC815\uB9AC \uC911...").start();
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 (existsSync9(lockFile)) {
3612
+ if (existsSync8(lockFile)) {
4281
3613
  unlinkSync(lockFile);
4282
3614
  }
4283
3615
  }
4284
- if (existsSync9("node_modules")) {
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 = ora5(`${targetPM} install \uC2E4\uD589 \uC911...`).start();
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(chalk23.red(installResult.err.slice(0, 300)));
3627
+ console.log(chalk20.red(installResult.err.slice(0, 300)));
4296
3628
  return;
4297
3629
  }
4298
- console.log(chalk23.green.bold(`
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 { execSync as execSync3 } from "child_process";
4309
- import { existsSync as existsSync10 } from "fs";
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 chalk24 from "chalk";
4313
- import ora6 from "ora";
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 [join2(dir, "../package.json"), join2(dir, "../../package.json")]) {
3648
+ for (const pkgPath of [join4(dir, "../package.json"), join4(dir, "../../package.json")]) {
4318
3649
  try {
4319
- if (existsSync10(pkgPath)) {
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
- try {
4331
- const out = execSync3(`npm view ${PACKAGE} version`, {
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(chalk24.bold("\n\u2B06\uFE0F " + t("update.title")));
4350
- console.log(chalk24.gray("\u2500".repeat(40)));
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(chalk24.cyan(`
3676
+ console.log(chalk21.cyan(`
4353
3677
  \u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${current}`));
4354
- const spinner = ora6("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...").start();
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(chalk24.yellow(" \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uAC70\uB098 \uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4359
- console.log(chalk24.gray(` npm update -g ${PACKAGE}`));
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(chalk24.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
3687
+ console.log(chalk21.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
4364
3688
  if (isUpToDate(current, latest)) {
4365
- console.log(chalk24.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
3689
+ console.log(chalk21.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
4366
3690
  return;
4367
3691
  }
4368
- const updateSpinner = ora6(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC911...`).start();
4369
- try {
4370
- execSync3(`npm update -g ${PACKAGE}`, { stdio: ["pipe", "pipe", "pipe"] });
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(chalk24.green.bold(`
3696
+ console.log(chalk21.green.bold(`
4373
3697
  \u{1F389} VHK CLI v${latest} \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
4374
- console.log(chalk24.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
4375
- } catch (err) {
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
- const msg = err instanceof Error ? err.message.slice(0, 300) : String(err);
4378
- console.log(chalk24.red(msg));
4379
- console.log(chalk24.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
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 mkdirSync5,
4388
- readFileSync as readFileSync2,
4389
- readdirSync,
4390
- statSync,
4391
- writeFileSync as writeFileSync6
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 join3 } from "path";
4394
- import chalk25 from "chalk";
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 IGNORE_DIRS2 = /* @__PURE__ */ new Set([
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 = readdirSync(dir);
3841
+ const entries = readdirSync2(dir);
4413
3842
  const filtered = entries.filter(
4414
- (e) => (!e.startsWith(".") || e === ".env.example") && !IGNORE_DIRS2.has(e)
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 = join3(dir, entry);
4420
- const stat = statSync(fullPath);
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(chalk25.bold("\n\u{1F9E0} " + t("context.title")));
4495
- console.log(chalk25.gray("\u2500".repeat(40)));
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
- mkdirSync5(".vhk", { recursive: true });
4545
- writeFileSync6(CONTEXT_PATH, lines.join("\n"), "utf-8");
4546
- console.log(chalk25.green(`
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(chalk25.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
4549
- console.log(chalk25.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
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(chalk25.bold("\n\u{1F4C4} " + t("context.showTitle")));
4558
- console.log(chalk25.gray("\u2500".repeat(40)));
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(chalk25.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4561
- console.log(chalk25.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
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 = readFileSync2(CONTEXT_PATH, "utf-8");
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 mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
4570
- import chalk26 from "chalk";
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
- mkdirSync6(".vhk", { recursive: true });
4583
- writeFileSync7(MEMORY_PATH, JSON.stringify(memories, null, 2) + "\n", "utf-8");
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(chalk26.bold("\n\u{1F9E0} " + t("memory.addTitle")));
4587
- console.log(chalk26.gray("\u2500".repeat(40)));
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(chalk26.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4590
- console.log(chalk26.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9\uD558\uAE30\uB85C \uACB0\uC815"'));
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(chalk26.green(`
4058
+ console.log(chalk23.green(`
4601
4059
  \u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (#${memories.length})`));
4602
- console.log(chalk26.cyan(` \u{1F4DD} ${content}`));
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(chalk26.bold("\n\u{1F9E0} " + t("memory.listTitle")));
4611
- console.log(chalk26.gray("\u2500".repeat(40)));
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(chalk26.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4615
- console.log(chalk26.gray(' vhk memory add "\uB0B4\uC6A9"\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
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(chalk26.cyan(`
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(chalk26.white(` [${index + 1}] ${m.content}`));
4081
+ console.log(chalk23.white(` [${index + 1}] ${m.content}`));
4624
4082
  if (m.tags && m.tags.length > 0) {
4625
- console.log(chalk26.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
4083
+ console.log(chalk23.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
4626
4084
  }
4627
- console.log(chalk26.gray(` \u{1F4C5} ${date}`));
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(chalk26.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${memories.length || 0})`));
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(chalk26.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
4641
- console.log(chalk26.gray(` ${removed.content}`));
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 mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
4646
- import chalk27 from "chalk";
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(chalk27.bold("\n\u{1F4CB} " + t("brief.title")));
4654
- console.log(chalk27.gray("\u2500".repeat(40)));
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
- mkdirSync7(".vhk", { recursive: true });
4736
- writeFileSync8(BRIEF_PATH, lines.join("\n"), "utf-8");
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(chalk27.green(`
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(chalk28.yellow(`
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(chalk28.cyan(` \u{1F4AC} "${input}"`));
4823
- console.log(chalk28.cyan(` \u2192 ${route.explanation}`));
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 inquirer13.prompt([{
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(chalk28.dim(` ${ko.nlp.menuHint}`));
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 existsSync14 } from "fs";
4842
- import { dirname as dirname3, join as join4 } from "path";
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
- join4(dir, "../../package.json"),
4848
- join4(dir, "../package.json")
4420
+ join8(dir, "../../package.json"),
4421
+ join8(dir, "../package.json")
4849
4422
  ]) {
4850
4423
  try {
4851
- if (existsSync14(pkgPath)) {
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
- init: "\uC2DC\uC791",
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("init").alias("\uC2DC\uC791").alias("\uB9CC\uB4E4\uAE30").description("\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30 \u2014 \uD3F4\uB354 + \uD558\uB124\uC2A4 \uD30C\uC77C \uC790\uB3D9 \uC0DD\uC131").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);
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(check);
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 inquirer14.prompt([{
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{1F4E6} \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30", value: "init" },
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 "init":
5042
- return init({ skipGate: false });
4724
+ case "start":
4725
+ return start();
5043
4726
  case "recap":
5044
4727
  return recap({});
5045
4728
  case "check":