@byh3071/vhk 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +165 -28
  2. package/dist/index.js +1032 -316
  3. package/package.json +56 -55
package/dist/index.js CHANGED
@@ -323,7 +323,7 @@ var require_ignore = __commonJS({
323
323
  // path matching.
324
324
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
325
325
  // @returns {TestResult} true if a file is ignored
326
- test(path14, checkUnignored, mode) {
326
+ test(path15, checkUnignored, mode) {
327
327
  let ignored = false;
328
328
  let unignored = false;
329
329
  let matchedRule;
@@ -332,7 +332,7 @@ var require_ignore = __commonJS({
332
332
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
333
333
  return;
334
334
  }
335
- const matched = rule[mode].test(path14);
335
+ const matched = rule[mode].test(path15);
336
336
  if (!matched) {
337
337
  return;
338
338
  }
@@ -353,17 +353,17 @@ var require_ignore = __commonJS({
353
353
  var throwError = (message, Ctor) => {
354
354
  throw new Ctor(message);
355
355
  };
356
- var checkPath = (path14, originalPath, doThrow) => {
357
- if (!isString(path14)) {
356
+ var checkPath = (path15, originalPath, doThrow) => {
357
+ if (!isString(path15)) {
358
358
  return doThrow(
359
359
  `path must be a string, but got \`${originalPath}\``,
360
360
  TypeError
361
361
  );
362
362
  }
363
- if (!path14) {
363
+ if (!path15) {
364
364
  return doThrow(`path must not be empty`, TypeError);
365
365
  }
366
- if (checkPath.isNotRelative(path14)) {
366
+ if (checkPath.isNotRelative(path15)) {
367
367
  const r = "`path.relative()`d";
368
368
  return doThrow(
369
369
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -372,7 +372,7 @@ var require_ignore = __commonJS({
372
372
  }
373
373
  return true;
374
374
  };
375
- var isNotRelative = (path14) => REGEX_TEST_INVALID_PATH.test(path14);
375
+ var isNotRelative = (path15) => REGEX_TEST_INVALID_PATH.test(path15);
376
376
  checkPath.isNotRelative = isNotRelative;
377
377
  checkPath.convert = (p) => p;
378
378
  var Ignore = class {
@@ -402,19 +402,19 @@ var require_ignore = __commonJS({
402
402
  }
403
403
  // @returns {TestResult}
404
404
  _test(originalPath, cache, checkUnignored, slices) {
405
- const path14 = originalPath && checkPath.convert(originalPath);
405
+ const path15 = originalPath && checkPath.convert(originalPath);
406
406
  checkPath(
407
- path14,
407
+ path15,
408
408
  originalPath,
409
409
  this._strictPathCheck ? throwError : RETURN_FALSE
410
410
  );
411
- return this._t(path14, cache, checkUnignored, slices);
411
+ return this._t(path15, cache, checkUnignored, slices);
412
412
  }
413
- checkIgnore(path14) {
414
- if (!REGEX_TEST_TRAILING_SLASH.test(path14)) {
415
- return this.test(path14);
413
+ checkIgnore(path15) {
414
+ if (!REGEX_TEST_TRAILING_SLASH.test(path15)) {
415
+ return this.test(path15);
416
416
  }
417
- const slices = path14.split(SLASH).filter(Boolean);
417
+ const slices = path15.split(SLASH).filter(Boolean);
418
418
  slices.pop();
419
419
  if (slices.length) {
420
420
  const parent = this._t(
@@ -427,18 +427,18 @@ var require_ignore = __commonJS({
427
427
  return parent;
428
428
  }
429
429
  }
430
- return this._rules.test(path14, false, MODE_CHECK_IGNORE);
430
+ return this._rules.test(path15, false, MODE_CHECK_IGNORE);
431
431
  }
432
- _t(path14, cache, checkUnignored, slices) {
433
- if (path14 in cache) {
434
- return cache[path14];
432
+ _t(path15, cache, checkUnignored, slices) {
433
+ if (path15 in cache) {
434
+ return cache[path15];
435
435
  }
436
436
  if (!slices) {
437
- slices = path14.split(SLASH).filter(Boolean);
437
+ slices = path15.split(SLASH).filter(Boolean);
438
438
  }
439
439
  slices.pop();
440
440
  if (!slices.length) {
441
- return cache[path14] = this._rules.test(path14, checkUnignored, MODE_IGNORE);
441
+ return cache[path15] = this._rules.test(path15, checkUnignored, MODE_IGNORE);
442
442
  }
443
443
  const parent = this._t(
444
444
  slices.join(SLASH) + SLASH,
@@ -446,29 +446,29 @@ var require_ignore = __commonJS({
446
446
  checkUnignored,
447
447
  slices
448
448
  );
449
- return cache[path14] = parent.ignored ? parent : this._rules.test(path14, checkUnignored, MODE_IGNORE);
449
+ return cache[path15] = parent.ignored ? parent : this._rules.test(path15, checkUnignored, MODE_IGNORE);
450
450
  }
451
- ignores(path14) {
452
- return this._test(path14, this._ignoreCache, false).ignored;
451
+ ignores(path15) {
452
+ return this._test(path15, this._ignoreCache, false).ignored;
453
453
  }
454
454
  createFilter() {
455
- return (path14) => !this.ignores(path14);
455
+ return (path15) => !this.ignores(path15);
456
456
  }
457
457
  filter(paths) {
458
458
  return makeArray(paths).filter(this.createFilter());
459
459
  }
460
460
  // @returns {TestResult}
461
- test(path14) {
462
- return this._test(path14, this._testCache, true);
461
+ test(path15) {
462
+ return this._test(path15, this._testCache, true);
463
463
  }
464
464
  };
465
465
  var factory = (options) => new Ignore(options);
466
- var isPathValid = (path14) => checkPath(path14 && checkPath.convert(path14), path14, RETURN_FALSE);
466
+ var isPathValid = (path15) => checkPath(path15 && checkPath.convert(path15), path15, RETURN_FALSE);
467
467
  var setupWindows = () => {
468
468
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
469
469
  checkPath.convert = makePosix;
470
470
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
471
- checkPath.isNotRelative = (path14) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path14) || isNotRelative(path14);
471
+ checkPath.isNotRelative = (path15) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path15) || isNotRelative(path15);
472
472
  };
473
473
  if (
474
474
  // Detect `process` so that it can run in browsers.
@@ -485,75 +485,110 @@ var require_ignore = __commonJS({
485
485
 
486
486
  // src/index.ts
487
487
  import { Command, Help } from "commander";
488
- import chalk11 from "chalk";
489
- import inquirer5 from "inquirer";
488
+ import chalk16 from "chalk";
489
+ import inquirer7 from "inquirer";
490
490
 
491
491
  // src/lib/nlp-router.ts
492
492
  function normalize(input) {
493
493
  return input.trim().toLowerCase().replace(/\s+/g, " ");
494
494
  }
495
+ var NLP_KEYWORDS = {
496
+ save: ["\uC800\uC7A5", "\uC138\uC774\uBE0C", "\uCEE4\uBC0B", "\uC62C\uB824", "\uC62C\uB9AC\uAE30", "\uD478\uC2DC", "push", "commit"],
497
+ undo: ["\uB418\uB3CC\uB824", "\uB418\uB3CC\uB9AC\uAE30", "\uCDE8\uC18C", "\uC6D0\uB798\uB300\uB85C", "\uB864\uBC31", "\uB9AC\uC14B", "reset", "rollback"],
498
+ status: ["\uC0C1\uD0DC", "\uD604\uD669", "\uC5B4\uB5BB\uAC8C", "\uC5B4\uB54C", "\uC9C0\uAE08"],
499
+ diff: ["\uBCC0\uACBD", "\uBC14\uB010", "\uBB50\uBC14\uB01C", "\uCC28\uC774", "\uB2EC\uB77C\uC9C4", "\uC218\uC815\uB41C"]
500
+ };
501
+ function matchesKeywords(text, command) {
502
+ const keywords = NLP_KEYWORDS[command];
503
+ if (!keywords) return false;
504
+ return keywords.some((kw) => text.includes(kw.toLowerCase()));
505
+ }
495
506
  var RULES = [
496
507
  {
497
508
  command: "init",
498
509
  explanation: "\uAC80\uC99D \uC2A4\uD0B5\uD558\uACE0 \uBC14\uB85C \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --skip-gate)",
499
510
  confidence: "high",
500
511
  args: ["--skip-gate"],
501
- test: (t) => /기획.*(끝|완료)|노션.*(기획|완료)|검증.*(스킵|건너)|gate.*(스킵|건너)|바로.*시작/.test(t)
512
+ test: (t2) => /기획.*(끝|완료)|노션.*(기획|완료)|검증.*(스킵|건너)|gate.*(스킵|건너)|바로.*시작/.test(t2)
502
513
  },
503
514
  {
504
515
  command: "init",
505
516
  explanation: "Notion\uC5D0\uC11C \uAC00\uC838\uC640 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --from-notion)",
506
517
  confidence: "low",
507
518
  args: ["--from-notion"],
508
- test: (t) => /노션|notion/.test(t) && /(시작|만들|import|가져)/.test(t)
519
+ test: (t2) => /노션|notion/.test(t2) && /(시작|만들|import|가져)/.test(t2)
509
520
  },
510
521
  {
511
522
  command: "init",
512
523
  explanation: "\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791)",
513
524
  confidence: "high",
514
- test: (t) => /프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|하네스|초기화/.test(t) || /^시작$/.test(t)
525
+ test: (t2) => /프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|하네스|초기화/.test(t2) || /^시작$/.test(t2)
515
526
  },
516
527
  {
517
- command: "recap",
518
- explanation: "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC (vhk \uC815\uB9AC)",
528
+ command: "secure",
529
+ explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548)",
530
+ confidence: "high",
531
+ test: (t2) => /보안|시크릿|비밀|키\s*유출|secure|scan/.test(t2)
532
+ },
533
+ {
534
+ command: "check",
535
+ explanation: "\uADDC\uCE59 \uC810\uAC80 (vhk \uC810\uAC80)",
519
536
  confidence: "high",
520
- test: (t) => /오늘.*(정리|기록)|한\s*일|세션|회고|recap|정리해/.test(t)
537
+ test: (t2) => /규칙.*(점검|위반)|린트|check|위반/.test(t2)
521
538
  },
522
539
  {
523
540
  command: "doctor",
524
541
  explanation: "\uD658\uACBD \uC810\uAC80 (vhk doctor)",
525
542
  confidence: "high",
526
- test: (t) => /뭔가\s*안|안\s*돼|안돼|환경\s*(점검|진단)|진단|doctor|설치.*확인|왜\s*안/.test(t)
543
+ test: (t2) => /뭔가\s*안|안\s*돼|안돼|환경\s*(점검|진단|확인)|진단|doctor|설치.*확인|왜\s*안/.test(t2)
527
544
  },
528
545
  {
529
- command: "gate",
530
- explanation: "\uC544\uC774\uB514\uC5B4 \uAC80\uC99D (vhk \uAC80\uC99D)",
546
+ command: "diff",
547
+ explanation: "\uBCC0\uACBD\uC0AC\uD56D \uC694\uC57D (vhk diff)",
531
548
  confidence: "high",
532
- test: (t) => /아이디어|검증|gate|go\/refine|pain\s*point/.test(t)
549
+ test: (t2) => (matchesKeywords(t2, "diff") || /^diff$/.test(t2) || /변경사항|수정\s*내역|차이\s*보/.test(t2)) && !/저장|커밋|push|푸시|상태|현황|세이브|commit/.test(t2)
533
550
  },
534
551
  {
535
- command: "secure",
536
- explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548 scan)",
552
+ command: "undo",
553
+ explanation: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (vhk \uB418\uB3CC\uB9AC\uAE30)",
537
554
  confidence: "high",
538
- test: (t) => /보안|시크릿|비밀|키\s*유출|secure|scan/.test(t)
555
+ test: (t2) => matchesKeywords(t2, "undo") || /undo|커밋\s*취/.test(t2)
539
556
  },
540
557
  {
541
- command: "check",
542
- explanation: "\uADDC\uCE59 \uC810\uAC80 (vhk \uC810\uAC80)",
558
+ command: "status",
559
+ explanation: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uD655\uC778 (vhk \uC0C1\uD0DC)",
543
560
  confidence: "high",
544
- test: (t) => /규칙.*(점검|위반)|린트|check|위반/.test(t)
561
+ test: (t2) => (matchesKeywords(t2, "status") || /^status$/.test(t2) || /브랜치.*(뭐|어디)|git\s*상태|동기화\s*상태|프로젝트\s*상태/.test(t2)) && !/보안|시크릿|규칙|점검|린트|환경|진단|doctor|secure|check|스캔|설치/.test(t2)
562
+ },
563
+ {
564
+ command: "save",
565
+ explanation: "Git\uC5D0 \uC800\uC7A5 (vhk \uC800\uC7A5)",
566
+ confidence: "high",
567
+ test: (t2) => (matchesKeywords(t2, "save") || /깃허브|github/.test(t2)) && !/정리|recap|되돌|취소|rollback|reset|리셋|롤백|원래대로/.test(t2)
568
+ },
569
+ {
570
+ command: "recap",
571
+ explanation: "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC (vhk \uC815\uB9AC)",
572
+ confidence: "high",
573
+ test: (t2) => /오늘.*(정리|기록)|한\s*일|세션|회고|recap|정리해/.test(t2)
574
+ },
575
+ {
576
+ command: "gate",
577
+ explanation: "\uC544\uC774\uB514\uC5B4 \uAC80\uC99D (vhk \uAC80\uC99D)",
578
+ confidence: "high",
579
+ test: (t2) => /아이디어|검증|gate|go\/refine|pain\s*point/.test(t2)
545
580
  },
546
581
  {
547
582
  command: "sync",
548
583
  explanation: "\uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654 (vhk \uADDC\uCE59)",
549
584
  confidence: "high",
550
- test: (t) => /규칙.*(맞|동기)|sync|cursorrules|claude\.md.*맞/.test(t)
585
+ test: (t2) => /규칙.*(맞|동기)|sync|cursorrules|claude\.md.*맞/.test(t2)
551
586
  },
552
587
  {
553
588
  command: "ship",
554
589
  explanation: "\uBC30\uD3EC \uCCB4\uD06C + \uD68C\uACE0 (vhk ship)",
555
590
  confidence: "high",
556
- test: (t) => /배포|출시|릴리스|ship|빌드\s*전/.test(t)
591
+ test: (t2) => /배포|출시|릴리스|ship|빌드\s*전/.test(t2)
557
592
  }
558
593
  ];
559
594
  function routeNaturalLanguage(input) {
@@ -578,6 +613,72 @@ function extractNotionUrl(input) {
578
613
 
579
614
  // src/i18n/ko.ts
580
615
  var ko = {
616
+ status: {
617
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC",
618
+ notGitRepo: "Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2C8\uC5D0\uC694. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
619
+ branch: "\uBE0C\uB79C\uCE58:",
620
+ changes: "\uBCC0\uACBD:",
621
+ recentCommits: "\uCD5C\uADFC \uCEE4\uBC0B (3):",
622
+ noCommits: "\uCEE4\uBC0B \uC5C6\uC74C",
623
+ remote: "\uC6D0\uACA9:",
624
+ noUpstream: "upstream \uC5C6\uC74C",
625
+ inSync: "\uB3D9\uAE30\uD654\uB428",
626
+ ahead: (n) => `\u2191${n} ahead`,
627
+ behind: (n) => `\u2193${n} behind`,
628
+ package: "package.json:",
629
+ noPackage: "package.json \uC5C6\uC74C",
630
+ detached: "(detached HEAD)",
631
+ unknownBranch: "(\uC54C \uC218 \uC5C6\uC74C)"
632
+ },
633
+ save: {
634
+ title: "\uC800\uC7A5\uD558\uAE30",
635
+ notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
636
+ noChanges: "\uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
637
+ filesHeader: (n) => `\uBCC0\uACBD\uB41C \uD30C\uC77C (${n}\uAC1C):`,
638
+ commitMessage: "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (Enter\uB85C \uAE30\uBCF8\uAC12 \uC0AC\uC6A9):",
639
+ saving: "\uC800\uC7A5 \uC911...",
640
+ pushing: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uC5D0 \uC62C\uB9AC\uB294 \uC911...",
641
+ successWithPush: "\uC800\uC7A5 + \uC6D0\uACA9 \uC5C5\uB85C\uB4DC \uC644\uB8CC!",
642
+ successLocal: "\uB85C\uCEEC \uC800\uC7A5 \uC644\uB8CC!",
643
+ noRemote: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 push\uB97C \uAC74\uB108\uB6F0\uC5C8\uC2B5\uB2C8\uB2E4.",
644
+ failed: "\uC800\uC7A5 \uC2E4\uD328",
645
+ stagedAfterFail: "\uCEE4\uBC0B\uC740 \uC2E4\uD328\uD588\uC9C0\uB9CC \uD30C\uC77C\uC740 \uC2A4\uD14C\uC774\uC9D5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uD655\uC778: git status / \uCDE8\uC18C: git reset HEAD",
646
+ securityWarnHeader: "\uC800\uC7A5 \uC804 \uBCF4\uC548 \uD655\uC778:",
647
+ secretsFound: (n) => `\uCF54\uB4DC\uC5D0\uC11C CRITICAL/HIGH \uC2DC\uD06C\uB9BF \uD328\uD134 ${n}\uAC74 \uAC10\uC9C0`,
648
+ secretsConfirm: "\uADF8\uB798\uB3C4 \uCEE4\uBC0B\xB7push\uB97C \uC9C4\uD589\uD560\uAE4C\uC694?",
649
+ cancelled: "\uC800\uC7A5\uC744 \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.",
650
+ pushFailed: "push \uC2E4\uD328 (\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uC644\uB8CC\uB428)",
651
+ commitOkPushFailed: "\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uB410\uC9C0\uB9CC \uC6D0\uACA9 push\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. git push\uB97C \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.",
652
+ done: (n) => `${n}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC!`,
653
+ doneLocalOnly: (n) => `${n}\uAC1C \uD30C\uC77C \uB85C\uCEEC \uC800\uC7A5\uB428 (push\uB294 \uC2E4\uD328)`
654
+ },
655
+ undo: {
656
+ title: "\uB418\uB3CC\uB9AC\uAE30",
657
+ notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
658
+ noCommits: "\uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
659
+ recentHeader: "\u{1F4CB} \uCD5C\uADFC \uCEE4\uBC0B:",
660
+ howMany: "\uBA87 \uAC1C\uC758 \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9B4\uAE4C\uC694?",
661
+ alreadyPushed: "\uC774 \uCEE4\uBC0B\uC740 \uC774\uBBF8 \uC6D0\uACA9\uC5D0 \uC62C\uB77C\uAC14\uC2B5\uB2C8\uB2E4. \uB418\uB3CC\uB9AC\uBA74 \uCDA9\uB3CC\uC774 \uC0DD\uAE38 \uC218 \uC788\uC5B4\uC694.",
662
+ noUpstreamWarning: "upstream \uBE0C\uB79C\uCE58\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 push\uD55C \uCEE4\uBC0B\uC77C \uC218 \uC788\uC5B4\uC694. \uB418\uB3CC\uB9B0 \uB4A4 force push\uAC00 \uD544\uC694\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
663
+ confirmMessage: "\uCD5C\uADFC \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9AC\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
664
+ confirmRisky: (n) => `\u26A0\uFE0F \uC704\uD5D8: \uCD5C\uADFC ${n}\uAC1C \uCEE4\uBC0B\uC744 soft reset\uD569\uB2C8\uB2E4. \uC6D0\uACA9\uACFC \uC5B4\uAE0B\uB0A0 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uACC4\uC18D\uD560\uAE4C\uC694?`,
665
+ cancelled: "\uCDE8\uC18C\uB428",
666
+ success: "\uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC! \uBCC0\uACBD\uC0AC\uD56D\uC740 \uADF8\uB300\uB85C \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.",
667
+ stagedHint: "\uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544 \uC788\uC5B4\uC694.",
668
+ rootCommit: "\uCCAB \uCEE4\uBC0B\uB9CC \uC788\uC5B4\uC11C \uB354 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
669
+ forcePushHint: "\uC6D0\uACA9\uACFC \uB9DE\uCD94\uB824\uBA74: git push --force-with-lease (\uD63C\uC790 \uC791\uC5C5\uD55C \uBE0C\uB79C\uCE58\uC5D0\uC11C\uB9CC, \uD300\uACFC \uD569\uC758 \uD6C4)",
670
+ failed: "\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328"
671
+ },
672
+ diff: {
673
+ title: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778",
674
+ notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
675
+ noChanges: "\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
676
+ stagedHeader: "\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):",
677
+ unstagedHeader: "\u270F\uFE0F \uC218\uC815\uB428 (unstaged):",
678
+ untrackedHeader: (n) => `\u2795 \uC0C8 \uD30C\uC77C (${n}\uAC1C):`,
679
+ summaryHeader: "\u{1F4CA} \uCD1D \uBCC0\uACBD \uC694\uC57D (\uC791\uC5C5 \uD2B8\uB9AC vs HEAD)",
680
+ filesLine: (n) => `\uD30C\uC77C: ${n}\uAC1C`
681
+ },
581
682
  start: {
582
683
  title: "\u{1F527} VHK \u2014 \uBB34\uC5C7\uC744 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
583
684
  subtitle: "\uBC88\uD638\uB9CC \uACE0\uB974\uBA74 \uB429\uB2C8\uB2E4. \uBA85\uB839\uC5B4\uB97C \uC678\uC6B8 \uD544\uC694 \uC5C6\uC5B4\uC694.",
@@ -749,6 +850,23 @@ var ko = {
749
850
  hintCommit: "git status \uD655\uC778"
750
851
  }
751
852
  };
853
+ function lookup(path15) {
854
+ const parts = path15.split(".");
855
+ let cur = ko;
856
+ for (const part of parts) {
857
+ if (cur === null || typeof cur !== "object") return void 0;
858
+ cur = cur[part];
859
+ }
860
+ return cur;
861
+ }
862
+ function t(key, ...args) {
863
+ const value = lookup(key);
864
+ if (typeof value === "function") {
865
+ return value(...args);
866
+ }
867
+ if (typeof value === "string") return value;
868
+ return key;
869
+ }
752
870
 
753
871
  // src/commands/gate.ts
754
872
  import inquirer from "inquirer";
@@ -861,7 +979,7 @@ ${ko.gate.checklistStart}
861
979
  name: "answer",
862
980
  message: `[${i + 1}/${total}] ${q.stage}: ${q.question}`
863
981
  }]);
864
- const { status } = await inquirer.prompt([{
982
+ const { status: status2 } = await inquirer.prompt([{
865
983
  type: "list",
866
984
  name: "status",
867
985
  message: ko.gate.verdictPrompt(q.failIf),
@@ -871,10 +989,10 @@ ${ko.gate.checklistStart}
871
989
  { name: ko.gate.statusFailChoice, value: "fail" }
872
990
  ]
873
991
  }]);
874
- if (status === "fail") failCount++;
875
- if (status === "hold") holdCount++;
876
- results.push({ id: q.id, stage: q.stage, status, answer });
877
- const icon = status === "pass" ? chalk2.green(ko.gate.statusPassLine) : status === "hold" ? chalk2.yellow(ko.gate.statusHoldLine) : chalk2.red(ko.gate.statusFailLine);
992
+ if (status2 === "fail") failCount++;
993
+ if (status2 === "hold") holdCount++;
994
+ results.push({ id: q.id, stage: q.stage, status: status2, answer });
995
+ const icon = status2 === "pass" ? chalk2.green(ko.gate.statusPassLine) : status2 === "hold" ? chalk2.yellow(ko.gate.statusHoldLine) : chalk2.red(ko.gate.statusFailLine);
878
996
  console.log(icon);
879
997
  }
880
998
  console.log(chalk2.bold(`
@@ -910,9 +1028,9 @@ ${ko.gate.verdictTitle}
910
1028
 
911
1029
  // src/commands/init.ts
912
1030
  import inquirer2 from "inquirer";
913
- import chalk4 from "chalk";
914
- import fs2 from "fs";
915
- import path2 from "path";
1031
+ import chalk5 from "chalk";
1032
+ import fs3 from "fs";
1033
+ import path3 from "path";
916
1034
 
917
1035
  // src/templates/claude-md.ts
918
1036
  function CLAUDE_MD_TEMPLATE(name, _stack) {
@@ -1122,27 +1240,113 @@ function COMMANDS_MD_TEMPLATE() {
1122
1240
  ].join("\n");
1123
1241
  }
1124
1242
 
1125
- // src/utils/logger.ts
1243
+ // src/lib/check-secure.ts
1244
+ var import_ignore = __toESM(require_ignore(), 1);
1245
+ import fs from "fs";
1246
+ import path from "path";
1126
1247
  import chalk3 from "chalk";
1248
+ function loadGitignore(rootDir) {
1249
+ const ig = (0, import_ignore.default)();
1250
+ const gitignorePath = path.join(rootDir, ".gitignore");
1251
+ if (fs.existsSync(gitignorePath)) {
1252
+ const content = fs.readFileSync(gitignorePath, "utf-8");
1253
+ ig.add(content);
1254
+ }
1255
+ return ig;
1256
+ }
1257
+ function isPathIgnored(ig, relativePath) {
1258
+ const normalized = relativePath.replace(/\\/g, "/");
1259
+ return ig.ignores(normalized);
1260
+ }
1261
+ function findExposedSensitiveFiles(rootDir, ig = loadGitignore(rootDir), maxDepth = 8) {
1262
+ const exposed = [];
1263
+ function walk(dir, depth) {
1264
+ if (depth > maxDepth) return;
1265
+ let entries;
1266
+ try {
1267
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1268
+ } catch {
1269
+ return;
1270
+ }
1271
+ for (const entry of entries) {
1272
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
1273
+ const fullPath = path.join(dir, entry.name);
1274
+ const rel = path.relative(rootDir, fullPath).replace(/\\/g, "/");
1275
+ if (entry.isDirectory()) {
1276
+ if (!isPathIgnored(ig, rel + "/")) walk(fullPath, depth + 1);
1277
+ continue;
1278
+ }
1279
+ if (isSensitiveName(entry.name) && !isPathIgnored(ig, rel)) {
1280
+ exposed.push(rel);
1281
+ }
1282
+ }
1283
+ }
1284
+ walk(rootDir, 0);
1285
+ return exposed;
1286
+ }
1287
+ function isSensitiveName(name) {
1288
+ const lower = name.toLowerCase();
1289
+ if (lower === ".env" || lower.startsWith(".env.")) return true;
1290
+ if (lower.endsWith(".pem") || lower.endsWith(".key")) return true;
1291
+ if (lower === "credentials.json" || lower === "secrets.json") return true;
1292
+ if (lower.startsWith("id_rsa")) return true;
1293
+ return false;
1294
+ }
1295
+ function checkProjectSecurity(rootDir = process.cwd()) {
1296
+ const gitignorePath = path.join(rootDir, ".gitignore");
1297
+ const missingGitignore = !fs.existsSync(gitignorePath);
1298
+ const ig = loadGitignore(rootDir);
1299
+ const exposedPaths = findExposedSensitiveFiles(rootDir, ig);
1300
+ const warnings = [];
1301
+ if (missingGitignore) {
1302
+ 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.");
1303
+ }
1304
+ if (exposedPaths.length > 0) {
1305
+ warnings.push(
1306
+ `ignore\uB418\uC9C0 \uC54A\uC740 \uBBFC\uAC10 \uD30C\uC77C ${exposedPaths.length}\uAC1C: ${exposedPaths.join(", ")}`
1307
+ );
1308
+ }
1309
+ return {
1310
+ ok: !missingGitignore && exposedPaths.length === 0,
1311
+ missingGitignore,
1312
+ exposedPaths,
1313
+ warnings
1314
+ };
1315
+ }
1316
+ function printSecurityWarnings(rootDir = process.cwd()) {
1317
+ const result = checkProjectSecurity(rootDir);
1318
+ if (result.ok) return true;
1319
+ for (const w of result.warnings) {
1320
+ console.log(chalk3.yellow(` \u26A0\uFE0F ${w}`));
1321
+ }
1322
+ return false;
1323
+ }
1324
+ function filterTrackedPaths(paths, rootDir = process.cwd()) {
1325
+ const ig = loadGitignore(rootDir);
1326
+ return paths.filter((p) => !isPathIgnored(ig, p.replace(/\\/g, "/")));
1327
+ }
1328
+
1329
+ // src/utils/logger.ts
1330
+ import chalk4 from "chalk";
1127
1331
  var log = {
1128
- success: (msg) => console.log(chalk3.green(`\u2705 ${msg}`)),
1129
- error: (msg) => console.log(chalk3.red(`\u274C ${msg}`)),
1130
- warn: (msg) => console.log(chalk3.yellow(`\u26A0\uFE0F ${msg}`)),
1131
- info: (msg) => console.log(chalk3.blue(`\u2139\uFE0F ${msg}`)),
1132
- step: (msg) => console.log(chalk3.bold(`
1332
+ success: (msg) => console.log(chalk4.green(`\u2705 ${msg}`)),
1333
+ error: (msg) => console.log(chalk4.red(`\u274C ${msg}`)),
1334
+ warn: (msg) => console.log(chalk4.yellow(`\u26A0\uFE0F ${msg}`)),
1335
+ info: (msg) => console.log(chalk4.blue(`\u2139\uFE0F ${msg}`)),
1336
+ step: (msg) => console.log(chalk4.bold(`
1133
1337
  \u25B8 ${msg}`))
1134
1338
  };
1135
1339
 
1136
1340
  // src/utils/file.ts
1137
- import fs from "fs";
1138
- import path from "path";
1341
+ import fs2 from "fs";
1342
+ import path2 from "path";
1139
1343
  function writeFile(filePath, content) {
1140
- const dir = path.dirname(filePath);
1141
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1142
- fs.writeFileSync(filePath, content, "utf-8");
1344
+ const dir = path2.dirname(filePath);
1345
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
1346
+ fs2.writeFileSync(filePath, content, "utf-8");
1143
1347
  }
1144
1348
  function fileExists(filePath) {
1145
- return fs.existsSync(filePath);
1349
+ return fs2.existsSync(filePath);
1146
1350
  }
1147
1351
 
1148
1352
  // src/lib/notion-import.ts
@@ -1176,7 +1380,7 @@ function extractPageId(url) {
1176
1380
  function getPageTitle(page) {
1177
1381
  for (const prop of Object.values(page.properties)) {
1178
1382
  if (prop.type === "title") {
1179
- return prop.title.map((t) => t.plain_text).join("");
1383
+ return prop.title.map((t2) => t2.plain_text).join("");
1180
1384
  }
1181
1385
  }
1182
1386
  return "Untitled";
@@ -1206,7 +1410,7 @@ function extractText(block) {
1206
1410
  const type = block.type;
1207
1411
  const data = block[type];
1208
1412
  if (!data?.rich_text) return "";
1209
- return data.rich_text.map((t) => t.plain_text).join("");
1413
+ return data.rich_text.map((t2) => t2.plain_text).join("");
1210
1414
  }
1211
1415
  function parseBlocks(blocks) {
1212
1416
  const sections = {};
@@ -1298,7 +1502,7 @@ var PROJECT_TYPES = [
1298
1502
  { name: "\u{1F916} \uB178\uC158 \uD1B5\uD569/MCP \uC11C\uBC84", value: "notion" },
1299
1503
  { name: "\u{1F4F1} \uBAA8\uBC14\uC77C \uC571 (Flutter)", value: "mobile" }
1300
1504
  ];
1301
- var VALID_TYPES = PROJECT_TYPES.map((t) => t.value);
1505
+ var VALID_TYPES = PROJECT_TYPES.map((t2) => t2.value);
1302
1506
  var STACK_PRESETS = {
1303
1507
  webapp: ["Next.js", "TypeScript", "Tailwind CSS", "shadcn/ui", "Supabase", "Vercel"],
1304
1508
  extension: ["Vite", "TypeScript", "@crxjs/vite-plugin", "Chrome Extension Manifest V3"],
@@ -1334,13 +1538,14 @@ async function collectAnswers(options, defaults = {}) {
1334
1538
  async function init(options = {}) {
1335
1539
  const skipGate = Boolean(options.skipGate || options.fromNotion);
1336
1540
  if (skipGate) {
1337
- console.log(chalk4.dim(`
1541
+ console.log(chalk5.dim(`
1338
1542
  ${ko.init.skipGate}
1339
1543
  `));
1340
1544
  }
1341
- console.log(chalk4.bold(`
1545
+ console.log(chalk5.bold(`
1342
1546
  ${ko.init.title}
1343
1547
  `));
1548
+ printSecurityWarnings();
1344
1549
  let prdContent = {};
1345
1550
  const defaults = {};
1346
1551
  if (options.fromNotion) {
@@ -1362,7 +1567,7 @@ ${ko.init.title}
1362
1567
  process.exit(1);
1363
1568
  }
1364
1569
  const stack = STACK_PRESETS[answers.type];
1365
- console.log(chalk4.dim(`
1570
+ console.log(chalk5.dim(`
1366
1571
  ${ko.init.recommendedStack} ${stack.join(" + ")}
1367
1572
  `));
1368
1573
  if (!options.yes) {
@@ -1381,7 +1586,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1381
1586
  const files = generateFiles(answers.name, answers.description, stack, prdContent);
1382
1587
  log.step(ko.init.filesGenerating);
1383
1588
  for (const [filePath, content] of Object.entries(files)) {
1384
- const fullPath = path2.join(cwd, filePath);
1589
+ const fullPath = path3.join(cwd, filePath);
1385
1590
  if (fileExists(fullPath)) {
1386
1591
  const { overwrite } = await inquirer2.prompt([{
1387
1592
  type: "confirm",
@@ -1398,21 +1603,21 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1398
1603
  log.success(filePath);
1399
1604
  }
1400
1605
  await writeInitExtras(cwd);
1401
- console.log(chalk4.bold.green(`
1606
+ console.log(chalk5.bold.green(`
1402
1607
  ${ko.init.done}`));
1403
- console.log(chalk4.dim(`
1608
+ console.log(chalk5.dim(`
1404
1609
  ${ko.init.nextSteps}`));
1405
1610
  if (options.fromNotion) {
1406
1611
  console.log(` 1. ${ko.init.notionReviewHint}`);
1407
1612
  console.log(` 2. ${ko.init.gitHintLabel}`);
1408
- console.log(` ${chalk4.cyan(ko.init.gitHintCommand)}`);
1613
+ console.log(` ${chalk5.cyan(ko.init.gitHintCommand)}`);
1409
1614
  console.log(` 3. ${ko.init.startDev}
1410
1615
  `);
1411
1616
  } else {
1412
1617
  console.log(` 1. ${ko.init.fillHint}`);
1413
1618
  console.log(` 2. ${ko.init.prdHint}`);
1414
1619
  console.log(` 3. ${ko.init.gitHintLabel}`);
1415
- console.log(` ${chalk4.cyan(ko.init.gitHintCommand)}`);
1620
+ console.log(` ${chalk5.cyan(ko.init.gitHintCommand)}`);
1416
1621
  console.log(` 4. ${ko.init.startDev}
1417
1622
  `);
1418
1623
  }
@@ -1452,7 +1657,7 @@ function generateFiles(name, description, stack, prdContent = {}) {
1452
1657
  };
1453
1658
  }
1454
1659
  var VHK_PACKAGE_SCRIPTS = {
1455
- save: "git add . && git commit -m",
1660
+ save: "vhk save",
1456
1661
  check: "vhk check",
1457
1662
  scan: "vhk secure scan",
1458
1663
  recap: "vhk recap",
@@ -1460,15 +1665,15 @@ var VHK_PACKAGE_SCRIPTS = {
1460
1665
  doctor: "vhk doctor"
1461
1666
  };
1462
1667
  function enhancePackageScripts(projectDir) {
1463
- const pkgPath = path2.join(projectDir, "package.json");
1464
- if (!fs2.existsSync(pkgPath)) return false;
1465
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
1668
+ const pkgPath = path3.join(projectDir, "package.json");
1669
+ if (!fs3.existsSync(pkgPath)) return false;
1670
+ const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
1466
1671
  pkg.scripts = { ...pkg.scripts, ...VHK_PACKAGE_SCRIPTS };
1467
- fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1672
+ fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1468
1673
  return true;
1469
1674
  }
1470
1675
  async function writeInitExtras(projectDir) {
1471
- const commandsPath = path2.join(projectDir, "COMMANDS.md");
1676
+ const commandsPath = path3.join(projectDir, "COMMANDS.md");
1472
1677
  if (fileExists(commandsPath)) {
1473
1678
  const { overwrite } = await inquirer2.prompt([{
1474
1679
  type: "confirm",
@@ -1493,37 +1698,13 @@ async function writeInitExtras(projectDir) {
1493
1698
 
1494
1699
  // src/commands/recap.ts
1495
1700
  import inquirer3 from "inquirer";
1496
- import chalk5 from "chalk";
1701
+ import chalk6 from "chalk";
1497
1702
  import fs5 from "fs";
1498
1703
  import path6 from "path";
1499
1704
 
1500
1705
  // src/lib/git.ts
1501
1706
  import path4 from "path";
1502
1707
  import simpleGit from "simple-git";
1503
-
1504
- // src/lib/check-secure.ts
1505
- var import_ignore = __toESM(require_ignore(), 1);
1506
- import fs3 from "fs";
1507
- import path3 from "path";
1508
- function loadGitignore(rootDir) {
1509
- const ig = (0, import_ignore.default)();
1510
- const gitignorePath = path3.join(rootDir, ".gitignore");
1511
- if (fs3.existsSync(gitignorePath)) {
1512
- const content = fs3.readFileSync(gitignorePath, "utf-8");
1513
- ig.add(content);
1514
- }
1515
- return ig;
1516
- }
1517
- function isPathIgnored(ig, relativePath) {
1518
- const normalized = relativePath.replace(/\\/g, "/");
1519
- return ig.ignores(normalized);
1520
- }
1521
- function filterTrackedPaths(paths, rootDir = process.cwd()) {
1522
- const ig = loadGitignore(rootDir);
1523
- return paths.filter((p) => !isPathIgnored(ig, p.replace(/\\/g, "/")));
1524
- }
1525
-
1526
- // src/lib/git.ts
1527
1708
  var git = simpleGit();
1528
1709
  function isNoiseRecapPath(filePath) {
1529
1710
  const base = path4.basename(filePath);
@@ -1540,29 +1721,19 @@ function filterRecapFiles(files) {
1540
1721
  const tracked = new Set(filterTrackedPaths(paths));
1541
1722
  return files.filter((f) => tracked.has(f.file) && !isNoiseRecapPath(f.file));
1542
1723
  }
1543
- function fileStatus(workingDir) {
1544
- if (workingDir === "?") return "new";
1545
- if (workingDir === "D") return "deleted";
1546
- if (workingDir === "R") return "renamed";
1724
+ function inferFileStatusFromDiff(insertions, deletions) {
1725
+ if (deletions > 0 && insertions === 0) return "deleted";
1726
+ if (insertions > 0 && deletions === 0) return "new";
1547
1727
  return "modified";
1548
1728
  }
1549
- async function getSessionDiff(since) {
1550
- const sinceDate = since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1551
- const diffSummary = await git.diffSummary([`--since=${sinceDate}`]);
1552
- const statusResult = await git.status();
1553
- const statByFile = new Map(
1554
- diffSummary.files.map((f) => [f.file, f])
1555
- );
1729
+ function buildSessionDiffFromSummary(diffSummary) {
1556
1730
  const files = filterRecapFiles(
1557
- statusResult.files.map((f) => {
1558
- const stat = statByFile.get(f.path);
1559
- return {
1560
- file: f.path,
1561
- insertions: stat?.insertions ?? 0,
1562
- deletions: stat?.deletions ?? 0,
1563
- status: fileStatus(f.working_dir)
1564
- };
1565
- })
1731
+ diffSummary.files.map((f) => ({
1732
+ file: f.file,
1733
+ insertions: f.insertions,
1734
+ deletions: f.deletions,
1735
+ status: inferFileStatusFromDiff(f.insertions, f.deletions)
1736
+ }))
1566
1737
  );
1567
1738
  return {
1568
1739
  filesChanged: files.length,
@@ -1571,6 +1742,11 @@ async function getSessionDiff(since) {
1571
1742
  files
1572
1743
  };
1573
1744
  }
1745
+ async function getSessionDiff(since) {
1746
+ const sinceDate = since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1747
+ const diffSummary = await git.diffSummary([`--since=${sinceDate}`]);
1748
+ return buildSessionDiffFromSummary(diffSummary);
1749
+ }
1574
1750
  async function getRecentCommits(count = 10, since) {
1575
1751
  const options = { maxCount: count };
1576
1752
  if (since) options["--since"] = since;
@@ -1621,10 +1797,10 @@ var ADR_RULES = [
1621
1797
  test: (f) => /\.env\.example$|auth\/|middleware\.(ts|js)$/.test(f)
1622
1798
  }
1623
1799
  ];
1624
- function detectAdrCandidates(diff) {
1800
+ function detectAdrCandidates(diff2) {
1625
1801
  const candidates = [];
1626
1802
  for (const rule of ADR_RULES) {
1627
- const matched = diff.files.map((f) => f.file).filter(rule.test);
1803
+ const matched = diff2.files.map((f) => f.file).filter(rule.test);
1628
1804
  if (matched.length > 0) {
1629
1805
  candidates.push({
1630
1806
  title: rule.title,
@@ -1681,39 +1857,40 @@ function createAdrFile(cwd, title, context, decision, consequences) {
1681
1857
 
1682
1858
  // src/commands/recap.ts
1683
1859
  async function recap(options = {}) {
1684
- console.log(chalk5.bold(`
1860
+ console.log(chalk6.bold(`
1685
1861
  ${ko.recap.title}
1686
1862
  `));
1687
1863
  if (!await isGitRepo()) {
1688
- console.log(chalk5.red(ko.recap.noRepo));
1864
+ console.log(chalk6.red(ko.recap.noRepo));
1689
1865
  return;
1690
1866
  }
1691
- console.log(chalk5.dim(`${ko.recap.analyzing}
1867
+ printSecurityWarnings();
1868
+ console.log(chalk6.dim(`${ko.recap.analyzing}
1692
1869
  `));
1693
1870
  const since = options.since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1694
- const diff = await getSessionDiff(since);
1871
+ const diff2 = await getSessionDiff(since);
1695
1872
  const commits = await getRecentCommits(10, since);
1696
- if (diff.filesChanged === 0 && commits.length === 0) {
1697
- console.log(chalk5.yellow(ko.recap.noChanges));
1873
+ if (diff2.filesChanged === 0 && commits.length === 0) {
1874
+ console.log(chalk6.yellow(ko.recap.noChanges));
1698
1875
  return;
1699
1876
  }
1700
- console.log(chalk5.bold("\u{1F4CA} \uBCC0\uACBD \uC694\uC57D:"));
1701
- console.log(` \uD30C\uC77C: ${chalk5.cyan(String(diff.filesChanged))}\uAC1C \uBCC0\uACBD`);
1702
- console.log(` \uCD94\uAC00: ${chalk5.green("+" + diff.insertions)} / \uC0AD\uC81C: ${chalk5.red("-" + diff.deletions)}`);
1703
- if (diff.files.length > 0) {
1704
- console.log(chalk5.dim("\n \uBCC0\uACBD \uD30C\uC77C:"));
1705
- diff.files.slice(0, 15).forEach((f) => {
1706
- const icon = f.status === "new" ? chalk5.green("\u{1F195}") : f.status === "deleted" ? chalk5.red("\u{1F5D1}\uFE0F") : chalk5.yellow("\u270F\uFE0F");
1877
+ console.log(chalk6.bold("\u{1F4CA} \uBCC0\uACBD \uC694\uC57D:"));
1878
+ console.log(` \uD30C\uC77C: ${chalk6.cyan(String(diff2.filesChanged))}\uAC1C \uBCC0\uACBD`);
1879
+ console.log(` \uCD94\uAC00: ${chalk6.green("+" + diff2.insertions)} / \uC0AD\uC81C: ${chalk6.red("-" + diff2.deletions)}`);
1880
+ if (diff2.files.length > 0) {
1881
+ console.log(chalk6.dim("\n \uBCC0\uACBD \uD30C\uC77C:"));
1882
+ diff2.files.slice(0, 15).forEach((f) => {
1883
+ const icon = f.status === "new" ? chalk6.green("\u{1F195}") : f.status === "deleted" ? chalk6.red("\u{1F5D1}\uFE0F") : chalk6.yellow("\u270F\uFE0F");
1707
1884
  console.log(` ${icon} ${f.file}`);
1708
1885
  });
1709
- if (diff.files.length > 15) {
1710
- console.log(chalk5.dim(` ... \uC678 ${diff.files.length - 15}\uAC1C`));
1886
+ if (diff2.files.length > 15) {
1887
+ console.log(chalk6.dim(` ... \uC678 ${diff2.files.length - 15}\uAC1C`));
1711
1888
  }
1712
1889
  }
1713
1890
  if (commits.length > 0) {
1714
- console.log(chalk5.dim("\n \uCD5C\uADFC \uCEE4\uBC0B:"));
1891
+ console.log(chalk6.dim("\n \uCD5C\uADFC \uCEE4\uBC0B:"));
1715
1892
  commits.slice(0, 5).forEach((c) => {
1716
- console.log(chalk5.dim(` \u2022 ${c.message}`));
1893
+ console.log(chalk6.dim(` \u2022 ${c.message}`));
1717
1894
  });
1718
1895
  }
1719
1896
  console.log("");
@@ -1748,7 +1925,7 @@ ${ko.recap.title}
1748
1925
  const sessionNum = existing.length + 1;
1749
1926
  const fileName = `${today}-session-${sessionNum}.md`;
1750
1927
  const filePath = path6.join(logDir, fileName);
1751
- const fileList = diff.files.map((f) => `| ${f.file} | ${f.status} |`).join("\n");
1928
+ const fileList = diff2.files.map((f) => `| ${f.file} | ${f.status} |`).join("\n");
1752
1929
  const commitList = commits.slice(0, 10).map((c) => `- \`${c.hash.slice(0, 7)}\` ${c.message}`).join("\n");
1753
1930
  const content = [
1754
1931
  `# \uC138\uC158 \uB85C\uADF8 \u2014 ${today} #${sessionNum}`,
@@ -1766,7 +1943,7 @@ ${ko.recap.title}
1766
1943
  answers.blockers,
1767
1944
  "",
1768
1945
  "## \uBCC0\uACBD \uD30C\uC77C",
1769
- `\uCD1D ${diff.filesChanged}\uAC1C \uD30C\uC77C (+${diff.insertions} -${diff.deletions})`,
1946
+ `\uCD1D ${diff2.filesChanged}\uAC1C \uD30C\uC77C (+${diff2.insertions} -${diff2.deletions})`,
1770
1947
  "",
1771
1948
  "| \uD30C\uC77C | \uC0C1\uD0DC |",
1772
1949
  "|------|------|",
@@ -1779,13 +1956,13 @@ ${ko.recap.title}
1779
1956
  `*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
1780
1957
  ].join("\n");
1781
1958
  fs5.writeFileSync(filePath, content, "utf-8");
1782
- const adrCandidates = detectAdrCandidates(diff);
1959
+ const adrCandidates = detectAdrCandidates(diff2);
1783
1960
  if (adrCandidates.length > 0) {
1784
- console.log(chalk5.cyan.bold(`
1961
+ console.log(chalk6.cyan.bold(`
1785
1962
  ${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
1786
1963
  for (const candidate of adrCandidates) {
1787
- console.log(chalk5.cyan(` \u2022 ${candidate.title}: ${candidate.context}`));
1788
- candidate.files.forEach((f) => console.log(chalk5.dim(` ${f}`)));
1964
+ console.log(chalk6.cyan(` \u2022 ${candidate.title}: ${candidate.context}`));
1965
+ candidate.files.forEach((f) => console.log(chalk6.dim(` ${f}`)));
1789
1966
  }
1790
1967
  const { createAdr } = await inquirer3.prompt([{
1791
1968
  type: "confirm",
@@ -1815,17 +1992,17 @@ ${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
1815
1992
  adrAnswers.decision,
1816
1993
  adrAnswers.consequences
1817
1994
  );
1818
- console.log(chalk5.green(` \u2705 ADR \uC0DD\uC131: ${path6.relative(process.cwd(), adrPath)}`));
1995
+ console.log(chalk6.green(` \u2705 ADR \uC0DD\uC131: ${path6.relative(process.cwd(), adrPath)}`));
1819
1996
  }
1820
1997
  }
1821
1998
  }
1822
1999
  const troubleshootingKeywords = /fix|bug|error|crash|hotfix|patch|revert|트러블|에러|버그|수정|핫픽스/i;
1823
2000
  const troubleCommits = commits.filter((c) => troubleshootingKeywords.test(c.message));
1824
2001
  if (troubleCommits.length > 0) {
1825
- console.log(chalk5.yellow.bold(`
2002
+ console.log(chalk6.yellow.bold(`
1826
2003
  ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
1827
2004
  troubleCommits.forEach((c) => {
1828
- console.log(chalk5.dim(` \u2022 ${c.message}`));
2005
+ console.log(chalk6.dim(` \u2022 ${c.message}`));
1829
2006
  });
1830
2007
  const { createTroubleshoot } = await inquirer3.prompt([{
1831
2008
  type: "confirm",
@@ -1876,12 +2053,12 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
1876
2053
  `*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
1877
2054
  ].join("\n");
1878
2055
  fs5.writeFileSync(tsFilePath, tsContent, "utf-8");
1879
- console.log(chalk5.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${path6.relative(process.cwd(), tsFilePath)}`));
2056
+ console.log(chalk6.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${path6.relative(process.cwd(), tsFilePath)}`));
1880
2057
  }
1881
2058
  }
1882
- console.log(chalk5.green.bold(`
2059
+ console.log(chalk6.green.bold(`
1883
2060
  ${ko.recap.done}`));
1884
- console.log(chalk5.dim(` \u{1F4C4} ${path6.relative(process.cwd(), filePath)}`));
2061
+ console.log(chalk6.dim(` \u{1F4C4} ${path6.relative(process.cwd(), filePath)}`));
1885
2062
  const claudeMdPath = path6.join(process.cwd(), "CLAUDE.md");
1886
2063
  if (fs5.existsSync(claudeMdPath)) {
1887
2064
  const { updateClaude } = await inquirer3.prompt([{
@@ -1901,7 +2078,7 @@ ${ko.recap.done}`));
1901
2078
  `- **\uB2E4\uC74C \uC561\uC158:** ${answers.nextTodo}`
1902
2079
  );
1903
2080
  fs5.writeFileSync(claudeMdPath, claudeContent, "utf-8");
1904
- console.log(chalk5.green(" \u2705 CLAUDE.md \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC"));
2081
+ console.log(chalk6.green(" \u2705 CLAUDE.md \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC"));
1905
2082
  }
1906
2083
  }
1907
2084
  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"';
@@ -1913,7 +2090,7 @@ ${ko.recap.done}`));
1913
2090
  }
1914
2091
 
1915
2092
  // src/commands/sync.ts
1916
- import chalk6 from "chalk";
2093
+ import chalk7 from "chalk";
1917
2094
  import fs6 from "fs";
1918
2095
  import path7 from "path";
1919
2096
  var CURSORRULES_KEYS = ["\uCF54\uB529 \uADDC\uCE59", "\uAE30\uC220 \uC2A4\uD0DD", "\uC544\uD0A4\uD14D\uCC98", "\uB514\uC790\uC778", "Anti-patterns", "\uCEE4\uBC0B"];
@@ -1983,32 +2160,32 @@ function toClaudeMd(sections, existing) {
1983
2160
  return lines.join("\n");
1984
2161
  }
1985
2162
  async function sync() {
1986
- console.log(chalk6.bold(`
2163
+ console.log(chalk7.bold(`
1987
2164
  ${ko.sync.title}
1988
2165
  `));
1989
2166
  const cwd = process.cwd();
1990
2167
  const rulesPath = path7.join(cwd, "RULES.md");
1991
2168
  if (!fs6.existsSync(rulesPath)) {
1992
- console.log(chalk6.yellow(ko.sync.noRules));
1993
- console.log(chalk6.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
1994
- console.log(chalk6.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
2169
+ console.log(chalk7.yellow(ko.sync.noRules));
2170
+ console.log(chalk7.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
2171
+ console.log(chalk7.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
1995
2172
  console.log("");
1996
- console.log(chalk6.dim(" RULES.md \uAE30\uBCF8 \uAD6C\uC870:"));
1997
- console.log(chalk6.dim(" ## \uD504\uB85C\uC81D\uD2B8 \uC815\uCCB4\uC131"));
1998
- console.log(chalk6.dim(" ## \uAE30\uC220 \uC2A4\uD0DD"));
1999
- console.log(chalk6.dim(" ## \uCF54\uB529 \uADDC\uCE59"));
2000
- console.log(chalk6.dim(" ## \uAE30\uB85D \uADDC\uCE59"));
2001
- console.log(chalk6.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
2173
+ console.log(chalk7.dim(" RULES.md \uAE30\uBCF8 \uAD6C\uC870:"));
2174
+ console.log(chalk7.dim(" ## \uD504\uB85C\uC81D\uD2B8 \uC815\uCCB4\uC131"));
2175
+ console.log(chalk7.dim(" ## \uAE30\uC220 \uC2A4\uD0DD"));
2176
+ console.log(chalk7.dim(" ## \uCF54\uB529 \uADDC\uCE59"));
2177
+ console.log(chalk7.dim(" ## \uAE30\uB85D \uADDC\uCE59"));
2178
+ console.log(chalk7.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
2002
2179
  return;
2003
2180
  }
2004
2181
  const rulesContent = fs6.readFileSync(rulesPath, "utf-8");
2005
2182
  const sections = parseRulesMd(rulesContent);
2006
- console.log(chalk6.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
2183
+ console.log(chalk7.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
2007
2184
  const firstLine = rulesContent.split("\n")[0];
2008
2185
  const projectName = firstLine.replace(/^#\s*/, "").replace(/\s*—.*/, "").trim() || "Project";
2009
2186
  const cursorrulesPath = path7.join(cwd, ".cursorrules");
2010
2187
  fs6.writeFileSync(cursorrulesPath, toCursorrules(sections, projectName), "utf-8");
2011
- console.log(chalk6.green(` ${ko.sync.cursorrulesDone}`));
2188
+ console.log(chalk7.green(` ${ko.sync.cursorrulesDone}`));
2012
2189
  const claudePath = path7.join(cwd, "CLAUDE.md");
2013
2190
  const existingClaude = fs6.existsSync(claudePath) ? fs6.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
2014
2191
 
@@ -2018,11 +2195,11 @@ ${ko.sync.title}
2018
2195
  - **\uB2E4\uC74C \uC561\uC158:** __FILL__
2019
2196
  - **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
2020
2197
  fs6.writeFileSync(claudePath, toClaudeMd(sections, existingClaude), "utf-8");
2021
- console.log(chalk6.green(` ${ko.sync.claudeDone}`));
2022
- console.log(chalk6.bold.green(`
2198
+ console.log(chalk7.green(` ${ko.sync.claudeDone}`));
2199
+ console.log(chalk7.bold.green(`
2023
2200
  ${ko.sync.done}`));
2024
- console.log(chalk6.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md (\uC790\uB3D9 \uC0DD\uC131)"));
2025
- console.log(chalk6.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
2201
+ console.log(chalk7.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md (\uC790\uB3D9 \uC0DD\uC131)"));
2202
+ console.log(chalk7.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
2026
2203
  printNextStep({
2027
2204
  message: "\uADDC\uCE59 \uB3D9\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C Cursor\uAC00 \uC0C8 \uADDC\uCE59\uC744 \uB530\uB985\uB2C8\uB2E4.",
2028
2205
  command: "vhk \uC810\uAC80",
@@ -2031,7 +2208,7 @@ ${ko.sync.done}`));
2031
2208
  }
2032
2209
 
2033
2210
  // src/commands/check.ts
2034
- import chalk7 from "chalk";
2211
+ import chalk8 from "chalk";
2035
2212
  import path9 from "path";
2036
2213
  import fs8 from "fs";
2037
2214
 
@@ -2090,15 +2267,6 @@ function parseRules(rulesPath) {
2090
2267
  ));
2091
2268
  }
2092
2269
  }
2093
- if (/반드시|필수|항상|must|always|required/i.test(ruleText)) {
2094
- rules.push({
2095
- id: `required-${ruleIndex}`,
2096
- section: currentSection,
2097
- type: "custom",
2098
- description: ruleText,
2099
- check: () => []
2100
- });
2101
- }
2102
2270
  }
2103
2271
  return rules;
2104
2272
  }
@@ -2199,22 +2367,22 @@ function escapeRegex(str) {
2199
2367
 
2200
2368
  // src/commands/check.ts
2201
2369
  async function check() {
2202
- console.log(chalk7.bold(`
2370
+ console.log(chalk8.bold(`
2203
2371
  ${ko.check.title}
2204
2372
  `));
2205
2373
  const cwd = process.cwd();
2206
2374
  const rulesPath = path9.join(cwd, "RULES.md");
2207
2375
  if (!fs8.existsSync(rulesPath)) {
2208
- console.log(chalk7.yellow(ko.check.noRules));
2209
- console.log(chalk7.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
2376
+ console.log(chalk8.yellow(ko.check.noRules));
2377
+ console.log(chalk8.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
2210
2378
  return;
2211
2379
  }
2212
2380
  const rules = parseRules(rulesPath);
2213
- console.log(chalk7.dim(` \u{1F4CF} ${rules.length}\uAC1C \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 \uAC10\uC9C0
2381
+ console.log(chalk8.dim(` \u{1F4CF} ${rules.length}\uAC1C \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 \uAC10\uC9C0
2214
2382
  `));
2215
2383
  if (rules.length === 0) {
2216
- console.log(chalk7.yellow(ko.check.noAutoRules));
2217
- 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."));
2384
+ console.log(chalk8.yellow(ko.check.noAutoRules));
2385
+ console.log(chalk8.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."));
2218
2386
  return;
2219
2387
  }
2220
2388
  const allViolations = [];
@@ -2222,13 +2390,13 @@ ${ko.check.title}
2222
2390
  for (const rule of rules) {
2223
2391
  const violations = rule.check(cwd);
2224
2392
  if (violations.length === 0) {
2225
- console.log(chalk7.green(` \u2705 ${rule.id}`) + chalk7.dim(` \u2014 ${rule.description.slice(0, 60)}`));
2393
+ console.log(chalk8.green(` \u2705 ${rule.id}`) + chalk8.dim(` \u2014 ${rule.description.slice(0, 60)}`));
2226
2394
  passCount++;
2227
2395
  } else {
2228
- console.log(chalk7.red(` \u274C ${rule.id}`) + chalk7.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
2396
+ console.log(chalk8.red(` \u274C ${rule.id}`) + chalk8.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
2229
2397
  violations.forEach((v) => {
2230
- const loc = v.file ? chalk7.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
2231
- const icon = v.severity === "error" ? chalk7.red("\u2716") : v.severity === "warning" ? chalk7.yellow("\u26A0") : chalk7.blue("\u2139");
2398
+ const loc = v.file ? chalk8.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
2399
+ const icon = v.severity === "error" ? chalk8.red("\u2716") : v.severity === "warning" ? chalk8.yellow("\u26A0") : chalk8.blue("\u2139");
2232
2400
  console.log(` ${icon} ${v.message}${loc}`);
2233
2401
  });
2234
2402
  allViolations.push(...violations);
@@ -2238,17 +2406,17 @@ ${ko.check.title}
2238
2406
  const errors = allViolations.filter((v) => v.severity === "error").length;
2239
2407
  const warnings = allViolations.filter((v) => v.severity === "warning").length;
2240
2408
  if (allViolations.length === 0) {
2241
- console.log(chalk7.green.bold(`${ko.check.allPassed} (${passCount}/${rules.length})`));
2409
+ console.log(chalk8.green.bold(`${ko.check.allPassed} (${passCount}/${rules.length})`));
2242
2410
  printNextStep({
2243
2411
  message: "\uBAA8\uB4E0 \uADDC\uCE59 \uD1B5\uACFC! \uBCF4\uC548 \uC2A4\uCE94\uB3C4 \uD574\uBCFC\uAE4C\uC694?",
2244
2412
  command: "vhk \uBCF4\uC548 scan",
2245
2413
  cursorHint: "\uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB824\uC918"
2246
2414
  });
2247
2415
  } else {
2248
- console.log(chalk7.bold(ko.check.summary));
2249
- console.log(` \uADDC\uCE59: ${chalk7.cyan(String(rules.length))}\uAC1C | \uD1B5\uACFC: ${chalk7.green(String(passCount))}\uAC1C | \uC704\uBC18: ${chalk7.red(String(allViolations.length))}\uAC74`);
2250
- if (errors > 0) console.log(` ${chalk7.red(`\u2716 ${errors}\uAC1C \uC5D0\uB7EC`)}`);
2251
- if (warnings > 0) console.log(` ${chalk7.yellow(`\u26A0 ${warnings}\uAC1C \uACBD\uACE0`)}`);
2416
+ console.log(chalk8.bold(ko.check.summary));
2417
+ console.log(` \uADDC\uCE59: ${chalk8.cyan(String(rules.length))}\uAC1C | \uD1B5\uACFC: ${chalk8.green(String(passCount))}\uAC1C | \uC704\uBC18: ${chalk8.red(String(allViolations.length))}\uAC74`);
2418
+ if (errors > 0) console.log(` ${chalk8.red(`\u2716 ${errors}\uAC1C \uC5D0\uB7EC`)}`);
2419
+ if (warnings > 0) console.log(` ${chalk8.yellow(`\u26A0 ${warnings}\uAC1C \uACBD\uACE0`)}`);
2252
2420
  printNextStep({
2253
2421
  message: "\uC704\uBC18 \uD56D\uBAA9\uC744 \uC218\uC815\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
2254
2422
  command: "vhk \uC810\uAC80",
@@ -2261,10 +2429,13 @@ ${ko.check.title}
2261
2429
  }
2262
2430
 
2263
2431
  // src/commands/secure.ts
2264
- import chalk8 from "chalk";
2265
- import fs10 from "fs";
2432
+ import chalk9 from "chalk";
2433
+ import fs11 from "fs";
2266
2434
  import path11 from "path";
2267
2435
 
2436
+ // src/lib/scan-secrets.ts
2437
+ import fs10 from "fs";
2438
+
2268
2439
  // src/lib/secret-patterns.ts
2269
2440
  var SECRET_PATTERNS = [
2270
2441
  {
@@ -2289,7 +2460,7 @@ var SECRET_PATTERNS = [
2289
2460
  id: "notion-token",
2290
2461
  name: "Notion Integration Token",
2291
2462
  severity: "critical",
2292
- pattern: /secret_[A-Za-z0-9]{24,}/
2463
+ pattern: /secret_[A-Za-z0-9]{40,50}/
2293
2464
  },
2294
2465
  {
2295
2466
  id: "github-token",
@@ -2301,7 +2472,7 @@ var SECRET_PATTERNS = [
2301
2472
  id: "openai-key",
2302
2473
  name: "OpenAI API Key",
2303
2474
  severity: "critical",
2304
- pattern: /sk-[A-Za-z0-9]{20,}/
2475
+ pattern: /\bsk-(?:proj-|ant-api03-|live-)[A-Za-z0-9_-]{16,}\b/
2305
2476
  },
2306
2477
  {
2307
2478
  id: "generic-api-key",
@@ -2403,69 +2574,88 @@ function walkProjectFiles(rootDir, onFile, ig = loadGitignore(rootDir)) {
2403
2574
  walk(rootDir);
2404
2575
  }
2405
2576
 
2406
- // src/commands/secure.ts
2407
- var MAX_FINDINGS = 200;
2577
+ // src/lib/scan-secrets.ts
2578
+ var MAX_SECRET_FINDINGS = 200;
2408
2579
  var MAX_LINE_CHARS = 4e3;
2580
+ function globalPattern(pattern) {
2581
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
2582
+ return new RegExp(pattern.source, flags);
2583
+ }
2584
+ function findSecretsInLine(line, relPath, lineNum) {
2585
+ const found = [];
2586
+ const trimmed = line.trim();
2587
+ if (trimmed.startsWith("//") && trimmed.includes("example")) return found;
2588
+ if (trimmed.startsWith("#") && trimmed.includes("example")) return found;
2589
+ if (line.length > MAX_LINE_CHARS) return found;
2590
+ for (const pattern of SECRET_PATTERNS) {
2591
+ const regex = globalPattern(pattern.pattern);
2592
+ for (const match of line.matchAll(regex)) {
2593
+ found.push({
2594
+ patternId: pattern.id,
2595
+ patternName: pattern.name,
2596
+ severity: pattern.severity,
2597
+ file: relPath,
2598
+ line: lineNum,
2599
+ match: maskSecret(match[0])
2600
+ });
2601
+ }
2602
+ }
2603
+ return found;
2604
+ }
2605
+ function scanProjectForSecrets(cwd) {
2606
+ const findings = [];
2607
+ let scannedFiles = 0;
2608
+ let truncated = false;
2609
+ walkProjectFiles(cwd, (filePath, relPath) => {
2610
+ scannedFiles++;
2611
+ const content = fs10.readFileSync(filePath, "utf-8");
2612
+ const lines = content.split("\n");
2613
+ lines.forEach((line, idx) => {
2614
+ if (truncated) return;
2615
+ const lineFindings = findSecretsInLine(line, relPath, idx + 1);
2616
+ for (const f of lineFindings) {
2617
+ findings.push(f);
2618
+ if (findings.length >= MAX_SECRET_FINDINGS) {
2619
+ truncated = true;
2620
+ return;
2621
+ }
2622
+ }
2623
+ });
2624
+ });
2625
+ return { findings, scannedFiles, truncated };
2626
+ }
2627
+ function filterSevereFindings(findings) {
2628
+ return findings.filter((f) => f.severity === "critical" || f.severity === "high");
2629
+ }
2630
+
2631
+ // src/commands/secure.ts
2409
2632
  async function secure() {
2410
- console.log(chalk8.bold(`
2633
+ console.log(chalk9.bold(`
2411
2634
  ${ko.secure.title}
2412
2635
  `));
2413
2636
  const cwd = process.cwd();
2414
- const findings = [];
2415
- let scannedFiles = 0;
2416
- let truncated = false;
2417
2637
  const gitignorePath = path11.join(cwd, ".gitignore");
2418
- const hasGitignore = fs10.existsSync(gitignorePath);
2638
+ const hasGitignore = fs11.existsSync(gitignorePath);
2419
2639
  if (!hasGitignore) {
2420
- console.log(chalk8.yellow(` ${ko.secure.noGitignore}`));
2421
- console.log(chalk8.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
2640
+ console.log(chalk9.yellow(` ${ko.secure.noGitignore}`));
2641
+ console.log(chalk9.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
2422
2642
  } else {
2423
- const gitignoreContent = fs10.readFileSync(gitignorePath, "utf-8");
2643
+ const gitignoreContent = fs11.readFileSync(gitignorePath, "utf-8");
2424
2644
  if (!gitignoreContent.includes(".env")) {
2425
- console.log(chalk8.yellow(` ${ko.secure.noEnvInGitignore}`));
2426
- console.log(chalk8.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
2645
+ console.log(chalk9.yellow(` ${ko.secure.noEnvInGitignore}`));
2646
+ console.log(chalk9.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
2427
2647
  }
2428
2648
  }
2429
- console.log(chalk8.dim(` ${ko.secure.scanning}
2649
+ console.log(chalk9.dim(` ${ko.secure.scanning}
2430
2650
  `));
2431
- walkProjectFiles(cwd, (filePath, relPath) => {
2432
- scannedFiles++;
2433
- const content = fs10.readFileSync(filePath, "utf-8");
2434
- const lines = content.split("\n");
2435
- for (const pattern of SECRET_PATTERNS) {
2436
- if (truncated) break;
2437
- lines.forEach((line, idx) => {
2438
- if (truncated) return;
2439
- if (line.length > MAX_LINE_CHARS) return;
2440
- const trimmed = line.trim();
2441
- if (trimmed.startsWith("//") && trimmed.includes("example")) return;
2442
- if (trimmed.startsWith("#") && trimmed.includes("example")) return;
2443
- const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
2444
- let match;
2445
- while ((match = regex.exec(line)) !== null) {
2446
- findings.push({
2447
- patternId: pattern.id,
2448
- patternName: pattern.name,
2449
- severity: pattern.severity,
2450
- file: relPath,
2451
- line: idx + 1,
2452
- match: maskSecret(match[0])
2453
- });
2454
- if (findings.length >= MAX_FINDINGS) {
2455
- truncated = true;
2456
- return;
2457
- }
2458
- }
2459
- });
2460
- }
2461
- });
2462
- console.log(chalk8.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC (lock\xB7node_modules\xB7>${MAX_SCAN_FILE_BYTES / 1024}KB \uC81C\uC678)`));
2651
+ const { findings, scannedFiles, truncated } = scanProjectForSecrets(cwd);
2652
+ console.log(chalk9.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC (lock\xB7node_modules\xB7>${MAX_SCAN_FILE_BYTES / 1024}KB \uC81C\uC678)`));
2463
2653
  if (truncated) {
2464
- console.log(chalk8.yellow(` \u26A0\uFE0F \uACB0\uACFC ${MAX_FINDINGS}\uAC74\uC5D0\uC11C \uCD9C\uB825\uC744 \uC81C\uD55C\uD588\uC2B5\uB2C8\uB2E4. lock \uD30C\uC77C \uB4F1\uC740 \uC790\uB3D9 \uC81C\uC678\uB429\uB2C8\uB2E4.`));
2654
+ console.log(chalk9.yellow(` \u26A0\uFE0F \uACB0\uACFC ${MAX_SECRET_FINDINGS}\uAC74\uC5D0\uC11C \uCD9C\uB825\uC744 \uC81C\uD55C\uD588\uC2B5\uB2C8\uB2E4. lock \uD30C\uC77C \uB4F1\uC740 \uC790\uB3D9 \uC81C\uC678\uB429\uB2C8\uB2E4.`));
2465
2655
  }
2466
2656
  console.log("");
2467
2657
  if (findings.length === 0) {
2468
- console.log(chalk8.green.bold(` ${ko.secure.clean}`));
2658
+ console.log(chalk9.green.bold(` ${ko.secure.clean}`));
2469
2659
  printNextStep({
2470
2660
  message: "\uBCF4\uC548 \uC774\uC0C1 \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
2471
2661
  command: "vhk \uC815\uB9AC",
@@ -2477,45 +2667,45 @@ ${ko.secure.title}
2477
2667
  const high = findings.filter((f) => f.severity === "high");
2478
2668
  const medium = findings.filter((f) => f.severity === "medium");
2479
2669
  if (critical.length > 0) {
2480
- console.log(chalk8.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
2670
+ console.log(chalk9.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
2481
2671
  critical.forEach((f) => {
2482
- console.log(chalk8.red(` \u2716 ${f.patternName}`));
2483
- console.log(chalk8.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2672
+ console.log(chalk9.red(` \u2716 ${f.patternName}`));
2673
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2484
2674
  });
2485
2675
  console.log("");
2486
2676
  }
2487
2677
  if (high.length > 0) {
2488
- console.log(chalk8.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
2678
+ console.log(chalk9.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
2489
2679
  high.forEach((f) => {
2490
- console.log(chalk8.yellow(` \u26A0 ${f.patternName}`));
2491
- console.log(chalk8.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2680
+ console.log(chalk9.yellow(` \u26A0 ${f.patternName}`));
2681
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2492
2682
  });
2493
2683
  console.log("");
2494
2684
  }
2495
2685
  if (medium.length > 0) {
2496
- console.log(chalk8.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
2686
+ console.log(chalk9.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
2497
2687
  medium.forEach((f) => {
2498
- console.log(chalk8.blue(` \u2139 ${f.patternName}`));
2499
- console.log(chalk8.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2688
+ console.log(chalk9.blue(` \u2139 ${f.patternName}`));
2689
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2500
2690
  });
2501
2691
  console.log("");
2502
2692
  }
2503
- console.log(chalk8.bold(` ${ko.secure.summary}`));
2504
- console.log(` \uCD1D ${chalk8.red(String(findings.length))}\uAC74 \uAC10\uC9C0 | CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length}`);
2693
+ console.log(chalk9.bold(` ${ko.secure.summary}`));
2694
+ console.log(` \uCD1D ${chalk9.red(String(findings.length))}\uAC74 \uAC10\uC9C0 | CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length}`);
2505
2695
  console.log("");
2506
- console.log(chalk8.dim(" \u{1F4A1} \uC870\uCE58 \uBC29\uBC95:"));
2507
- console.log(chalk8.dim(" 1. \uD574\uB2F9 \uD30C\uC77C\uC5D0\uC11C \uC2DC\uD06C\uB9BF\uC744 \uC81C\uAC70\uD558\uACE0 \uD658\uACBD\uBCC0\uC218\uB85C \uC774\uB3D9"));
2508
- console.log(chalk8.dim(" 2. git history\uC5D0\uC11C\uB3C4 \uC81C\uAC70: git filter-branch \uB610\uB294 BFG Repo-Cleaner"));
2509
- console.log(chalk8.dim(" 3. \uC720\uCD9C\uB41C \uD0A4\uB294 \uC989\uC2DC \uD3D0\uAE30\uD558\uACE0 \uC7AC\uBC1C\uAE09\n"));
2510
- if (critical.length > 0) {
2696
+ console.log(chalk9.dim(" \u{1F4A1} \uC870\uCE58 \uBC29\uBC95:"));
2697
+ console.log(chalk9.dim(" 1. \uD574\uB2F9 \uD30C\uC77C\uC5D0\uC11C \uC2DC\uD06C\uB9BF\uC744 \uC81C\uAC70\uD558\uACE0 \uD658\uACBD\uBCC0\uC218\uB85C \uC774\uB3D9"));
2698
+ console.log(chalk9.dim(" 2. git history\uC5D0\uC11C\uB3C4 \uC81C\uAC70: git filter-branch \uB610\uB294 BFG Repo-Cleaner"));
2699
+ console.log(chalk9.dim(" 3. \uC720\uCD9C\uB41C \uD0A4\uB294 \uC989\uC2DC \uD3D0\uAE30\uD558\uACE0 \uC7AC\uBC1C\uAE09\n"));
2700
+ if (critical.length > 0 || high.length > 0) {
2511
2701
  process.exitCode = 1;
2512
2702
  }
2513
2703
  }
2514
2704
 
2515
2705
  // src/commands/doctor.ts
2516
- import chalk9 from "chalk";
2706
+ import chalk10 from "chalk";
2517
2707
  import { execSync } from "child_process";
2518
- import fs11 from "fs";
2708
+ import fs12 from "fs";
2519
2709
  import path12 from "path";
2520
2710
  import { fileURLToPath } from "url";
2521
2711
  function checkCommand(name, command, hint) {
@@ -2534,8 +2724,8 @@ function getVhkVersion() {
2534
2724
  ];
2535
2725
  for (const pkgPath of candidates) {
2536
2726
  try {
2537
- if (fs11.existsSync(pkgPath)) {
2538
- const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
2727
+ if (fs12.existsSync(pkgPath)) {
2728
+ const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
2539
2729
  return pkg.version;
2540
2730
  }
2541
2731
  } catch {
@@ -2545,7 +2735,7 @@ function getVhkVersion() {
2545
2735
  return void 0;
2546
2736
  }
2547
2737
  async function doctor() {
2548
- console.log(chalk9.bold(`
2738
+ console.log(chalk10.bold(`
2549
2739
  ${ko.doctor.title}
2550
2740
  `));
2551
2741
  const checks = [
@@ -2557,22 +2747,22 @@ ${ko.doctor.title}
2557
2747
  let allOk = true;
2558
2748
  for (const check2 of checks) {
2559
2749
  if (check2.ok) {
2560
- console.log(chalk9.green(` \u2705 ${check2.name}`) + chalk9.dim(` \u2014 ${check2.version}`));
2750
+ console.log(chalk10.green(` \u2705 ${check2.name}`) + chalk10.dim(` \u2014 ${check2.version}`));
2561
2751
  } else {
2562
- console.log(chalk9.red(` \u274C ${check2.name} \uC5C6\uC74C`));
2563
- console.log(chalk9.dim(` \u2192 ${check2.hint}`));
2752
+ console.log(chalk10.red(` \u274C ${check2.name} \uC5C6\uC74C`));
2753
+ console.log(chalk10.dim(` \u2192 ${check2.hint}`));
2564
2754
  allOk = false;
2565
2755
  }
2566
2756
  }
2567
2757
  console.log("");
2568
2758
  const vhkVersion = getVhkVersion();
2569
2759
  if (vhkVersion) {
2570
- console.log(chalk9.green(" \u2705 VHK") + chalk9.dim(` \u2014 v${vhkVersion}`));
2760
+ console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(` \u2014 v${vhkVersion}`));
2571
2761
  } else {
2572
- console.log(chalk9.green(" \u2705 VHK") + chalk9.dim(" \u2014 \uC124\uCE58\uB428"));
2762
+ console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(" \u2014 \uC124\uCE58\uB428"));
2573
2763
  }
2574
2764
  console.log("");
2575
- console.log(chalk9.bold(` ${ko.doctor.projectFiles}`));
2765
+ console.log(chalk10.bold(` ${ko.doctor.projectFiles}`));
2576
2766
  const cwd = process.cwd();
2577
2767
  const projectFiles = [
2578
2768
  { name: "RULES.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
@@ -2582,32 +2772,32 @@ ${ko.doctor.title}
2582
2772
  { name: ".env", hint: ".gitignore\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778" }
2583
2773
  ];
2584
2774
  for (const file of projectFiles) {
2585
- const exists = fs11.existsSync(path12.join(cwd, file.name));
2775
+ const exists = fs12.existsSync(path12.join(cwd, file.name));
2586
2776
  if (exists) {
2587
- console.log(chalk9.green(` \u2705 ${file.name}`));
2777
+ console.log(chalk10.green(` \u2705 ${file.name}`));
2588
2778
  if (file.name === ".env") {
2589
2779
  const gitignorePath = path12.join(cwd, ".gitignore");
2590
- if (fs11.existsSync(gitignorePath)) {
2591
- const gitignore = fs11.readFileSync(gitignorePath, "utf-8");
2780
+ if (fs12.existsSync(gitignorePath)) {
2781
+ const gitignore = fs12.readFileSync(gitignorePath, "utf-8");
2592
2782
  if (!gitignore.includes(".env")) {
2593
- console.log(chalk9.yellow(` ${ko.doctor.envNotIgnored}`));
2783
+ console.log(chalk10.yellow(` ${ko.doctor.envNotIgnored}`));
2594
2784
  }
2595
2785
  }
2596
2786
  }
2597
2787
  } else {
2598
- console.log(chalk9.dim(` \u2B1A ${file.name}`) + chalk9.dim(` \u2014 ${file.hint}`));
2788
+ console.log(chalk10.dim(` \u2B1A ${file.name}`) + chalk10.dim(` \u2014 ${file.hint}`));
2599
2789
  }
2600
2790
  }
2601
2791
  console.log("");
2602
2792
  if (allOk) {
2603
- console.log(chalk9.green.bold(` ${ko.doctor.allOk}`));
2793
+ console.log(chalk10.green.bold(` ${ko.doctor.allOk}`));
2604
2794
  printNextStep({
2605
2795
  message: ko.doctor.nextOkMessage,
2606
2796
  command: "vhk \uC2DC\uC791",
2607
2797
  cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
2608
2798
  });
2609
2799
  } else {
2610
- console.log(chalk9.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
2800
+ console.log(chalk10.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
2611
2801
  printNextStep({
2612
2802
  message: ko.doctor.nextRetryMessage,
2613
2803
  command: "vhk doctor",
@@ -2618,9 +2808,9 @@ ${ko.doctor.title}
2618
2808
  }
2619
2809
 
2620
2810
  // src/commands/ship.ts
2621
- import chalk10 from "chalk";
2811
+ import chalk11 from "chalk";
2622
2812
  import inquirer4 from "inquirer";
2623
- import fs12 from "fs";
2813
+ import fs13 from "fs";
2624
2814
  import path13 from "path";
2625
2815
  var CHECKLIST = [
2626
2816
  { id: "build", questionKey: "checkBuild", hintKey: "hintBuild" },
@@ -2634,29 +2824,29 @@ function sanitizeVersion(version) {
2634
2824
  return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
2635
2825
  }
2636
2826
  async function ship() {
2637
- console.log(chalk10.bold(`
2827
+ console.log(chalk11.bold(`
2638
2828
  ${ko.ship.title}
2639
2829
  `));
2640
2830
  const cwd = process.cwd();
2641
- console.log(chalk10.cyan.bold(` ${ko.ship.checklist}
2831
+ console.log(chalk11.cyan.bold(` ${ko.ship.checklist}
2642
2832
  `));
2643
2833
  const { passed } = await inquirer4.prompt([{
2644
2834
  type: "checkbox",
2645
2835
  name: "passed",
2646
2836
  message: ko.ship.checkboxPrompt,
2647
2837
  choices: CHECKLIST.map((c) => ({
2648
- name: `${ko.ship[c.questionKey]} ${chalk10.dim(`(${ko.ship[c.hintKey]})`)}`,
2838
+ name: `${ko.ship[c.questionKey]} ${chalk11.dim(`(${ko.ship[c.hintKey]})`)}`,
2649
2839
  value: c.id
2650
2840
  }))
2651
2841
  }]);
2652
2842
  const allPassed = passed.length === CHECKLIST.length;
2653
2843
  const skipped = CHECKLIST.filter((c) => !passed.includes(c.id));
2654
2844
  if (!allPassed) {
2655
- console.log(chalk10.yellow(`
2845
+ console.log(chalk11.yellow(`
2656
2846
  ${ko.ship.incompleteHeader}`));
2657
2847
  skipped.forEach((s) => {
2658
- console.log(chalk10.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
2659
- console.log(chalk10.dim(` \u2192 ${ko.ship[s.hintKey]}`));
2848
+ console.log(chalk11.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
2849
+ console.log(chalk11.dim(` \u2192 ${ko.ship[s.hintKey]}`));
2660
2850
  });
2661
2851
  const { proceed } = await inquirer4.prompt([{
2662
2852
  type: "confirm",
@@ -2673,13 +2863,13 @@ ${ko.ship.title}
2673
2863
  return;
2674
2864
  }
2675
2865
  } else {
2676
- console.log(chalk10.green(`
2866
+ console.log(chalk11.green(`
2677
2867
  ${ko.ship.allPassed}
2678
2868
  `));
2679
2869
  }
2680
- console.log(chalk10.cyan.bold(` ${ko.ship.retro}
2870
+ console.log(chalk11.cyan.bold(` ${ko.ship.retro}
2681
2871
  `));
2682
- console.log(chalk10.dim(` ${ko.ship.versionHint}`));
2872
+ console.log(chalk11.dim(` ${ko.ship.versionHint}`));
2683
2873
  const retro = await inquirer4.prompt([
2684
2874
  { type: "input", name: "version", message: ko.ship.versionPrompt },
2685
2875
  { type: "input", name: "whatWentWell", message: ko.ship.questionWell },
@@ -2688,7 +2878,7 @@ ${ko.ship.title}
2688
2878
  { type: "input", name: "nextVersion", message: ko.ship.questionNext }
2689
2879
  ]);
2690
2880
  const buildLogDir = path13.join(cwd, "docs", "build-log");
2691
- if (!fs12.existsSync(buildLogDir)) fs12.mkdirSync(buildLogDir, { recursive: true });
2881
+ if (!fs13.existsSync(buildLogDir)) fs13.mkdirSync(buildLogDir, { recursive: true });
2692
2882
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2693
2883
  const versionSlug = sanitizeVersion(retro.version);
2694
2884
  const fileName = `${today}-v${versionSlug}.md`;
@@ -2721,8 +2911,8 @@ ${ko.ship.title}
2721
2911
  "---",
2722
2912
  `*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
2723
2913
  ].join("\n");
2724
- fs12.writeFileSync(filePath, content, "utf-8");
2725
- console.log(chalk10.green(`
2914
+ fs13.writeFileSync(filePath, content, "utf-8");
2915
+ console.log(chalk11.green(`
2726
2916
  ${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
2727
2917
  printNextStep({
2728
2918
  message: ko.ship.deployMessage,
@@ -2732,6 +2922,498 @@ ${ko.ship.title}
2732
2922
  });
2733
2923
  }
2734
2924
 
2925
+ // src/commands/save.ts
2926
+ import { execFileSync as execFileSync2 } from "child_process";
2927
+ import chalk12 from "chalk";
2928
+ import ora from "ora";
2929
+ import inquirer5 from "inquirer";
2930
+
2931
+ // src/lib/git-porcelain.ts
2932
+ function normalizePorcelain(raw) {
2933
+ return raw.replace(/\r\n/g, "\n").trimEnd();
2934
+ }
2935
+ function parsePorcelainLines(raw) {
2936
+ return normalizePorcelain(raw).split("\n").filter(Boolean);
2937
+ }
2938
+
2939
+ // src/lib/git-repo.ts
2940
+ import { execFileSync } from "child_process";
2941
+ function getGitRoot(cwd = process.cwd()) {
2942
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
2943
+ encoding: "utf-8",
2944
+ cwd,
2945
+ stdio: ["pipe", "pipe", "pipe"]
2946
+ }).trim();
2947
+ }
2948
+ function gitOut(args, cwd) {
2949
+ return execFileSync("git", args, {
2950
+ encoding: "utf-8",
2951
+ cwd,
2952
+ stdio: ["pipe", "pipe", "pipe"]
2953
+ });
2954
+ }
2955
+ function gitRun(args, cwd) {
2956
+ execFileSync("git", args, { stdio: "pipe", cwd });
2957
+ }
2958
+ function getExecErrorMessage(err) {
2959
+ if (err && typeof err === "object" && "stderr" in err) {
2960
+ const stderr = err.stderr;
2961
+ if (Buffer.isBuffer(stderr)) return stderr.toString("utf-8").trim();
2962
+ if (typeof stderr === "string") return stderr.trim();
2963
+ }
2964
+ return err instanceof Error ? err.message : String(err);
2965
+ }
2966
+ function hasGitRemote(cwd) {
2967
+ try {
2968
+ return gitOut(["remote"], cwd).trim().length > 0;
2969
+ } catch {
2970
+ return false;
2971
+ }
2972
+ }
2973
+ function countLocalCommits(cwd) {
2974
+ try {
2975
+ const out = gitOut(["rev-list", "--count", "HEAD"], cwd).trim();
2976
+ return parseInt(out, 10) || 0;
2977
+ } catch {
2978
+ return 0;
2979
+ }
2980
+ }
2981
+
2982
+ // src/commands/save.ts
2983
+ function formatDefaultCommitMessage(date = /* @__PURE__ */ new Date()) {
2984
+ const y = date.getFullYear();
2985
+ const m = String(date.getMonth() + 1).padStart(2, "0");
2986
+ const d = String(date.getDate()).padStart(2, "0");
2987
+ const h = String(date.getHours()).padStart(2, "0");
2988
+ const min = String(date.getMinutes()).padStart(2, "0");
2989
+ return `\u2728 vhk save: ${y}-${m}-${d} ${h}:${min}`;
2990
+ }
2991
+ function statusIcon(code) {
2992
+ if (code.includes("M")) return "\u270F\uFE0F";
2993
+ if (code.includes("A") || code.includes("?")) return "\u2795";
2994
+ if (code.includes("D")) return "\u{1F5D1}\uFE0F";
2995
+ return "\u{1F4C4}";
2996
+ }
2997
+ async function save() {
2998
+ console.log(chalk12.bold(`
2999
+ \u{1F4BE} ${t("save.title")}`));
3000
+ console.log(chalk12.gray("\u2500".repeat(40)));
3001
+ let gitRoot;
3002
+ try {
3003
+ execFileSync2("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3004
+ gitRoot = getGitRoot();
3005
+ } catch {
3006
+ console.log(chalk12.red(`\u274C ${t("save.notGitRepo")}`));
3007
+ return;
3008
+ }
3009
+ console.log(chalk12.cyan(`
3010
+ \u{1F512} ${t("save.securityWarnHeader")}`));
3011
+ printSecurityWarnings(gitRoot);
3012
+ const severe = filterSevereFindings(scanProjectForSecrets(gitRoot).findings);
3013
+ if (severe.length > 0) {
3014
+ console.log(chalk12.red(`
3015
+ \u26A0\uFE0F ${t("save.secretsFound", severe.length)}`));
3016
+ severe.slice(0, 5).forEach((f) => {
3017
+ console.log(chalk12.dim(` ${f.file}:${f.line} \u2014 ${f.patternName}`));
3018
+ });
3019
+ if (severe.length > 5) {
3020
+ console.log(chalk12.dim(` ... \uC678 ${severe.length - 5}\uAC74 (vhk \uBCF4\uC548 scan)`));
3021
+ }
3022
+ const { proceed } = await inquirer5.prompt([{
3023
+ type: "confirm",
3024
+ name: "proceed",
3025
+ message: t("save.secretsConfirm"),
3026
+ default: false
3027
+ }]);
3028
+ if (!proceed) {
3029
+ console.log(chalk12.gray(t("save.cancelled")));
3030
+ return;
3031
+ }
3032
+ }
3033
+ const lines = parsePorcelainLines(gitOut(["status", "--porcelain"], gitRoot));
3034
+ if (lines.length === 0) {
3035
+ console.log(chalk12.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
3036
+ return;
3037
+ }
3038
+ console.log(chalk12.cyan(`
3039
+ \u{1F4CB} ${t("save.filesHeader", lines.length)}`));
3040
+ lines.forEach((line) => {
3041
+ const code = line.substring(0, 2);
3042
+ const name = line.substring(3);
3043
+ console.log(` ${statusIcon(code)} ${name}`);
3044
+ });
3045
+ const { message } = await inquirer5.prompt([{
3046
+ type: "input",
3047
+ name: "message",
3048
+ message: t("save.commitMessage"),
3049
+ default: formatDefaultCommitMessage()
3050
+ }]);
3051
+ const spinner = ora(t("save.saving")).start();
3052
+ let didAdd = false;
3053
+ try {
3054
+ gitRun(["add", "."], gitRoot);
3055
+ didAdd = true;
3056
+ gitRun(["commit", "-m", message], gitRoot);
3057
+ spinner.text = t("save.pushing");
3058
+ if (!hasGitRemote(gitRoot)) {
3059
+ spinner.succeed(t("save.successLocal"));
3060
+ console.log(chalk12.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
3061
+ } else {
3062
+ try {
3063
+ gitRun(["push"], gitRoot);
3064
+ spinner.succeed(t("save.successWithPush"));
3065
+ } catch (pushErr) {
3066
+ spinner.fail(t("save.pushFailed"));
3067
+ console.log(chalk12.red(getExecErrorMessage(pushErr)));
3068
+ console.log(chalk12.yellow(`
3069
+ \u{1F4A1} ${t("save.commitOkPushFailed")}`));
3070
+ process.exitCode = 1;
3071
+ }
3072
+ }
3073
+ if (process.exitCode !== 1) {
3074
+ console.log(chalk12.green(`
3075
+ \u2705 ${t("save.done", lines.length)}`));
3076
+ } else {
3077
+ console.log(chalk12.green(`
3078
+ \u2705 ${t("save.doneLocalOnly", lines.length)}`));
3079
+ }
3080
+ } catch (err) {
3081
+ spinner.fail(t("save.failed"));
3082
+ console.log(chalk12.red(getExecErrorMessage(err)));
3083
+ if (didAdd) {
3084
+ try {
3085
+ const staged = gitOut(["diff", "--cached", "--stat"], gitRoot).trim();
3086
+ if (staged) {
3087
+ console.log(chalk12.yellow(`
3088
+ \u{1F4A1} ${t("save.stagedAfterFail")}`));
3089
+ }
3090
+ } catch {
3091
+ }
3092
+ }
3093
+ process.exitCode = 1;
3094
+ }
3095
+ }
3096
+
3097
+ // src/commands/undo.ts
3098
+ import { execFileSync as execFileSync3 } from "child_process";
3099
+ import chalk13 from "chalk";
3100
+ import inquirer6 from "inquirer";
3101
+ function parseRecentCommits(logOutput) {
3102
+ return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
3103
+ }
3104
+ function countUnpushedCommits(gitRoot) {
3105
+ const cwd = gitRoot ?? process.cwd();
3106
+ try {
3107
+ const out = gitOut(["rev-list", "--count", "@{u}..HEAD"], cwd).trim();
3108
+ return parseInt(out, 10) || 0;
3109
+ } catch {
3110
+ return -1;
3111
+ }
3112
+ }
3113
+ function willUndoPushedCommits(undoCount, unpushedCount) {
3114
+ if (unpushedCount < 0) return false;
3115
+ return undoCount > unpushedCount;
3116
+ }
3117
+ function isUndoRisky(undoCount, unpushedCount, hasRemote) {
3118
+ if (willUndoPushedCommits(undoCount, unpushedCount)) return true;
3119
+ if (unpushedCount < 0 && hasRemote) return true;
3120
+ return false;
3121
+ }
3122
+ async function undo() {
3123
+ console.log(chalk13.bold(`
3124
+ \u23EA ${t("undo.title")}`));
3125
+ console.log(chalk13.gray("\u2500".repeat(40)));
3126
+ let gitRoot;
3127
+ try {
3128
+ execFileSync3("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3129
+ gitRoot = getGitRoot();
3130
+ } catch {
3131
+ console.log(chalk13.red(`\u274C ${t("undo.notGitRepo")}`));
3132
+ return;
3133
+ }
3134
+ let logOutput;
3135
+ try {
3136
+ logOutput = gitOut(["log", "--oneline", "-5"], gitRoot).trim();
3137
+ } catch {
3138
+ console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3139
+ return;
3140
+ }
3141
+ const commits = parseRecentCommits(logOutput);
3142
+ if (commits.length === 0) {
3143
+ console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3144
+ return;
3145
+ }
3146
+ console.log(chalk13.cyan(`
3147
+ ${t("undo.recentHeader")}`));
3148
+ commits.forEach((c, i) => {
3149
+ console.log(` ${i === 0 ? "\u{1F449}" : " "} ${c}`);
3150
+ });
3151
+ const maxUndo = commits.length;
3152
+ const { count } = await inquirer6.prompt([{
3153
+ type: "number",
3154
+ name: "count",
3155
+ message: t("undo.howMany"),
3156
+ default: 1,
3157
+ min: 1,
3158
+ max: maxUndo
3159
+ }]);
3160
+ const undoCount = Math.min(Math.max(1, count || 1), maxUndo);
3161
+ const headCount = countLocalCommits(gitRoot);
3162
+ if (undoCount >= headCount) {
3163
+ console.log(chalk13.yellow(`
3164
+ \u{1F4ED} ${t("undo.rootCommit")}`));
3165
+ return;
3166
+ }
3167
+ const unpushed = countUnpushedCommits(gitRoot);
3168
+ const remote = hasGitRemote(gitRoot);
3169
+ const risky = isUndoRisky(undoCount, unpushed, remote);
3170
+ if (risky) {
3171
+ if (unpushed < 0) {
3172
+ console.log(chalk13.red(`
3173
+ \u26A0\uFE0F ${t("undo.noUpstreamWarning")}`));
3174
+ } else {
3175
+ console.log(chalk13.red(`
3176
+ \u26A0\uFE0F ${t("undo.alreadyPushed")}`));
3177
+ }
3178
+ }
3179
+ const { confirm } = await inquirer6.prompt([{
3180
+ type: "confirm",
3181
+ name: "confirm",
3182
+ message: risky ? t("undo.confirmRisky", undoCount) : t("undo.confirmMessage"),
3183
+ default: false
3184
+ }]);
3185
+ if (!confirm) {
3186
+ console.log(chalk13.gray(t("undo.cancelled")));
3187
+ return;
3188
+ }
3189
+ try {
3190
+ gitRun(["reset", "--soft", `HEAD~${undoCount}`], gitRoot);
3191
+ console.log(chalk13.green(`
3192
+ \u2705 ${t("undo.success")}`));
3193
+ console.log(chalk13.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
3194
+ if (risky) {
3195
+ console.log(chalk13.yellow(`
3196
+ \u{1F4A1} ${t("undo.forcePushHint")}`));
3197
+ }
3198
+ } catch (err) {
3199
+ console.log(chalk13.red(`\u274C ${t("undo.failed")}`));
3200
+ const msg = err instanceof Error ? err.message : String(err);
3201
+ console.log(chalk13.red(msg));
3202
+ process.exitCode = 1;
3203
+ }
3204
+ }
3205
+
3206
+ // src/commands/diff.ts
3207
+ import { execFileSync as execFileSync4, execSync as execSync2 } from "child_process";
3208
+ import chalk14 from "chalk";
3209
+ function gitOut2(args) {
3210
+ try {
3211
+ return execFileSync4("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3212
+ } catch {
3213
+ return "";
3214
+ }
3215
+ }
3216
+ function parseDiffStat(stat) {
3217
+ const files = [];
3218
+ const lines = stat.split("\n");
3219
+ for (const line of lines) {
3220
+ const match = line.match(/^\s*(.+?)\s*\|\s*(\d+)/);
3221
+ if (!match) continue;
3222
+ const name = match[1].trim();
3223
+ if (name.includes("changed") || name.includes("file")) continue;
3224
+ const plusMatch = line.match(/(\++)/);
3225
+ const minusMatch = line.match(/(\-+)/);
3226
+ files.push({
3227
+ name,
3228
+ additions: plusMatch ? plusMatch[1].length : 0,
3229
+ deletions: minusMatch ? minusMatch[1].length : 0
3230
+ });
3231
+ }
3232
+ return files;
3233
+ }
3234
+ function summarizeNumstat(numstat) {
3235
+ let totalAdd = 0;
3236
+ let totalDel = 0;
3237
+ let fileCount = 0;
3238
+ for (const line of numstat.split("\n").filter(Boolean)) {
3239
+ const [add, del] = line.split(" ");
3240
+ if (add === void 0 || del === void 0) continue;
3241
+ totalAdd += parseInt(add, 10) || 0;
3242
+ totalDel += parseInt(del, 10) || 0;
3243
+ fileCount++;
3244
+ }
3245
+ return { fileCount, totalAdd, totalDel };
3246
+ }
3247
+ function printFile(f) {
3248
+ const adds = f.additions > 0 ? chalk14.green(`+${f.additions}`) : "";
3249
+ const dels = f.deletions > 0 ? chalk14.red(`-${f.deletions}`) : "";
3250
+ const change = [adds, dels].filter(Boolean).join(" ");
3251
+ console.log(` ${f.name} ${change}`);
3252
+ }
3253
+ async function diff() {
3254
+ console.log(chalk14.bold(`
3255
+ \u{1F50D} ${t("diff.title")}`));
3256
+ console.log(chalk14.gray("\u2500".repeat(40)));
3257
+ try {
3258
+ execSync2("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
3259
+ } catch {
3260
+ console.log(chalk14.red(`\u274C ${t("diff.notGitRepo")}`));
3261
+ return;
3262
+ }
3263
+ const unstaged = gitOut2(["diff", "--stat"]);
3264
+ const staged = gitOut2(["diff", "--cached", "--stat"]);
3265
+ const untracked = gitOut2(["ls-files", "--others", "--exclude-standard"]);
3266
+ if (!unstaged && !staged && !untracked) {
3267
+ console.log(chalk14.green(`
3268
+ \u2705 ${t("diff.noChanges")}`));
3269
+ return;
3270
+ }
3271
+ if (staged) {
3272
+ console.log(chalk14.cyan(`
3273
+ ${t("diff.stagedHeader")}`));
3274
+ parseDiffStat(staged).forEach((f) => printFile(f));
3275
+ }
3276
+ if (unstaged) {
3277
+ console.log(chalk14.cyan(`
3278
+ ${t("diff.unstagedHeader")}`));
3279
+ parseDiffStat(unstaged).forEach((f) => printFile(f));
3280
+ }
3281
+ if (untracked) {
3282
+ const files = untracked.split("\n").filter(Boolean);
3283
+ console.log(chalk14.cyan(`
3284
+ ${t("diff.untrackedHeader", files.length)}`));
3285
+ files.forEach((f) => console.log(` ${chalk14.green("+")} ${f}`));
3286
+ }
3287
+ const numstat = gitOut2(["diff", "--numstat", "HEAD"]);
3288
+ if (numstat) {
3289
+ const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
3290
+ console.log(chalk14.cyan(`
3291
+ ${t("diff.summaryHeader")}`));
3292
+ console.log(` ${t("diff.filesLine", fileCount)}`);
3293
+ console.log(` \uCD94\uAC00: ${chalk14.green(`+${totalAdd}`)}\uC904`);
3294
+ console.log(` \uC0AD\uC81C: ${chalk14.red(`-${totalDel}`)}\uC904`);
3295
+ }
3296
+ console.log("");
3297
+ }
3298
+
3299
+ // src/commands/status.ts
3300
+ import { execFileSync as execFileSync5 } from "child_process";
3301
+ import fs14 from "fs";
3302
+ import path14 from "path";
3303
+ import chalk15 from "chalk";
3304
+ function countFileChanges(porcelain) {
3305
+ const lines = porcelain.split("\n").filter(Boolean);
3306
+ let staged = 0;
3307
+ let unstaged = 0;
3308
+ let untracked = 0;
3309
+ for (const line of lines) {
3310
+ const x = line[0];
3311
+ const y = line[1];
3312
+ if (x === "?" && y === "?") {
3313
+ untracked++;
3314
+ continue;
3315
+ }
3316
+ if (x !== " ") staged++;
3317
+ if (y !== " ") unstaged++;
3318
+ }
3319
+ return { staged, unstaged, untracked };
3320
+ }
3321
+ function parseSyncCounts(revListOutput) {
3322
+ const parts = revListOutput.trim().split(/\s+/);
3323
+ return {
3324
+ ahead: parseInt(parts[0] ?? "0", 10) || 0,
3325
+ behind: parseInt(parts[1] ?? "0", 10) || 0,
3326
+ hasUpstream: true
3327
+ };
3328
+ }
3329
+ function formatSyncLabel(sync2) {
3330
+ if (!sync2.hasUpstream) return t("status.noUpstream");
3331
+ if (sync2.ahead === 0 && sync2.behind === 0) return t("status.inSync");
3332
+ const parts = [];
3333
+ if (sync2.ahead > 0) parts.push(t("status.ahead", sync2.ahead));
3334
+ if (sync2.behind > 0) parts.push(t("status.behind", sync2.behind));
3335
+ return parts.join(" \xB7 ");
3336
+ }
3337
+ function parseRecentCommitLines(logOutput) {
3338
+ return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
3339
+ }
3340
+ function readProjectPackage(cwd = process.cwd()) {
3341
+ const pkgPath = path14.join(cwd, "package.json");
3342
+ if (!fs14.existsSync(pkgPath)) return null;
3343
+ try {
3344
+ const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
3345
+ if (!pkg.name && !pkg.version) return null;
3346
+ return {
3347
+ name: pkg.name ?? "(no name)",
3348
+ version: pkg.version ?? "(no version)"
3349
+ };
3350
+ } catch {
3351
+ return null;
3352
+ }
3353
+ }
3354
+ function getSyncCounts(gitRoot) {
3355
+ try {
3356
+ const out = gitOut(["rev-list", "--left-right", "--count", "HEAD...@{u}"], gitRoot);
3357
+ return parseSyncCounts(out);
3358
+ } catch {
3359
+ return { ahead: 0, behind: 0, hasUpstream: false };
3360
+ }
3361
+ }
3362
+ async function status() {
3363
+ console.log(chalk15.bold(`
3364
+ \u{1F4CA} ${t("status.title")}`));
3365
+ console.log(chalk15.gray("\u2500".repeat(40)));
3366
+ let gitRoot;
3367
+ try {
3368
+ execFileSync5("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3369
+ gitRoot = getGitRoot();
3370
+ } catch {
3371
+ console.log(chalk15.red(`\u274C ${t("status.notGitRepo")}`));
3372
+ return;
3373
+ }
3374
+ let branch;
3375
+ try {
3376
+ branch = gitOut(["branch", "--show-current"], gitRoot).trim() || t("status.detached");
3377
+ } catch {
3378
+ branch = t("status.unknownBranch");
3379
+ }
3380
+ const porcelain = normalizePorcelain(gitOut(["status", "--porcelain"], gitRoot));
3381
+ const counts = countFileChanges(porcelain);
3382
+ const sync2 = getSyncCounts(gitRoot);
3383
+ let commits = [];
3384
+ try {
3385
+ commits = parseRecentCommitLines(gitOut(["log", "--oneline", "-3"], gitRoot).trim());
3386
+ } catch {
3387
+ commits = [];
3388
+ }
3389
+ const pkg = readProjectPackage();
3390
+ console.log(chalk15.cyan(`
3391
+ \u{1F33F} ${t("status.branch")}`) + chalk15.white(` ${branch}`));
3392
+ console.log(
3393
+ chalk15.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk15.white(
3394
+ ` staged ${counts.staged} \xB7 unstaged ${counts.unstaged} \xB7 untracked ${counts.untracked}`
3395
+ )
3396
+ );
3397
+ console.log(chalk15.cyan(`
3398
+ \u{1F4CB} ${t("status.recentCommits")}`));
3399
+ if (commits.length === 0) {
3400
+ console.log(chalk15.dim(` ${t("status.noCommits")}`));
3401
+ } else {
3402
+ commits.forEach((c) => console.log(` ${chalk15.dim("\u2022")} ${c}`));
3403
+ }
3404
+ console.log(
3405
+ chalk15.cyan(`
3406
+ \u{1F504} ${t("status.remote")}`) + chalk15.white(` ${formatSyncLabel(sync2)}`)
3407
+ );
3408
+ console.log(chalk15.gray("\n" + "\u2500".repeat(40)));
3409
+ if (pkg) {
3410
+ console.log(chalk15.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk15.white(` ${pkg.name} v${pkg.version}`));
3411
+ } else {
3412
+ console.log(chalk15.dim(`\u{1F4E6} ${t("status.noPackage")}`));
3413
+ }
3414
+ console.log("");
3415
+ }
3416
+
2735
3417
  // src/index.ts
2736
3418
  var program = new Command();
2737
3419
  var defaultHelp = new Help();
@@ -2743,9 +3425,13 @@ var KO_ALIASES = {
2743
3425
  check: "\uC810\uAC80",
2744
3426
  secure: "\uBCF4\uC548",
2745
3427
  ship: "\uBC30\uD3EC",
2746
- doctor: "\uD658\uACBD"
3428
+ doctor: "\uD658\uACBD",
3429
+ save: "\uC800\uC7A5",
3430
+ undo: "\uB418\uB3CC\uB9AC\uAE30",
3431
+ status: "\uC0C1\uD0DC",
3432
+ diff: "\uBCC0\uACBD"
2747
3433
  };
2748
- 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("0.4.0");
3434
+ 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("0.5.1");
2749
3435
  program.configureHelp({
2750
3436
  formatHelp(cmd, helper) {
2751
3437
  if (cmd.parent) {
@@ -2753,7 +3439,7 @@ program.configureHelp({
2753
3439
  }
2754
3440
  const subs = helper.visibleCommands(cmd).filter((c) => c.name() !== "help");
2755
3441
  const terms = subs.map((c) => `${c.name()} (${KO_ALIASES[c.name()]})`);
2756
- const termWidth = Math.max(...terms.map((t) => t.length), 0);
3442
+ const termWidth = Math.max(...terms.map((t2) => t2.length), 0);
2757
3443
  const lines = [
2758
3444
  helper.commandDescription(cmd),
2759
3445
  "",
@@ -2771,26 +3457,36 @@ program.command("init").alias("\uC2DC\uC791").alias("\uB9CC\uB4E4\uAE30").descri
2771
3457
  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);
2772
3458
  program.command("sync").alias("\uB9DE\uCD94\uAE30").alias("\uADDC\uCE59").description("RULES.md \u2192 .cursorrules + CLAUDE.md \uB3D9\uAE30\uD654").action(sync);
2773
3459
  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);
2774
- var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C");
3460
+ 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);
2775
3461
  secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
2776
3462
  program.command("ship").alias("\uBC30\uD3EC").alias("\uB9B4\uB9AC\uC988").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
2777
3463
  program.command("doctor").alias("\uD658\uACBD").alias("\uC9C4\uB2E8").description("\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 \u2014 Node/Git/npm \uC0C1\uD0DC \uD655\uC778").action(doctor);
3464
+ program.command("save").alias("\uC800\uC7A5").description("\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)").action(async () => {
3465
+ await save();
3466
+ });
3467
+ program.command("undo").alias("\uB418\uB3CC\uB9AC\uAE30").description("\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30").action(async () => {
3468
+ await undo();
3469
+ });
3470
+ program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uB300\uC2DC\uBCF4\uB4DC").action(async () => {
3471
+ await status();
3472
+ });
3473
+ program.command("diff").alias("\uBCC0\uACBD").alias("\uCC28\uC774").description("Git \uBCC0\uACBD\uC0AC\uD56D \uD55C\uAD6D\uC5B4 \uC694\uC57D (staged / unstaged / \uC0C8 \uD30C\uC77C)").action(diff);
2778
3474
  program.on("command:*", async (operands) => {
2779
3475
  const input = operands.join(" ");
2780
3476
  const route = routeNaturalLanguage(input);
2781
3477
  if (route) {
2782
3478
  console.log("");
2783
- console.log(chalk11.cyan(` \u{1F4AC} "${input}"`));
2784
- console.log(chalk11.cyan(` \u2192 ${route.explanation}`));
3479
+ console.log(chalk16.cyan(` \u{1F4AC} "${input}"`));
3480
+ console.log(chalk16.cyan(` \u2192 ${route.explanation}`));
2785
3481
  if (route.confidence === "low") {
2786
- const { confirm } = await inquirer5.prompt([{
3482
+ const { confirm } = await inquirer7.prompt([{
2787
3483
  type: "confirm",
2788
3484
  name: "confirm",
2789
3485
  message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
2790
3486
  default: true
2791
3487
  }]);
2792
3488
  if (!confirm) {
2793
- console.log(chalk11.dim(` ${ko.nlp.menuHint}`));
3489
+ console.log(chalk16.dim(` ${ko.nlp.menuHint}`));
2794
3490
  return;
2795
3491
  }
2796
3492
  }
@@ -2815,15 +3511,23 @@ program.on("command:*", async (operands) => {
2815
3511
  return ship();
2816
3512
  case "doctor":
2817
3513
  return doctor();
3514
+ case "save":
3515
+ return save();
3516
+ case "undo":
3517
+ return undo();
3518
+ case "status":
3519
+ return status();
3520
+ case "diff":
3521
+ return diff();
2818
3522
  }
2819
3523
  }
2820
- console.log(chalk11.yellow(`
3524
+ console.log(chalk16.yellow(`
2821
3525
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
2822
3526
  `));
2823
3527
  });
2824
3528
  program.action(async () => {
2825
3529
  console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
2826
- const { choice } = await inquirer5.prompt([{
3530
+ const { choice } = await inquirer7.prompt([{
2827
3531
  type: "list",
2828
3532
  name: "choice",
2829
3533
  message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
@@ -2835,7 +3539,11 @@ program.action(async () => {
2835
3539
  { name: "\u{1F512} \uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB9AC\uAE30", value: "secure" },
2836
3540
  { name: "\u{1F504} \uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654", value: "sync" },
2837
3541
  { name: "\u{1F680} \uBC30\uD3EC\uD558\uAE30", value: "ship" },
2838
- { name: "\u{1FA7A} \uD658\uACBD \uC810\uAC80\uD558\uAE30", value: "doctor" }
3542
+ { name: "\u{1FA7A} \uD658\uACBD \uC810\uAC80\uD558\uAE30", value: "doctor" },
3543
+ { name: "\u{1F4BE} Git\uC5D0 \uC800\uC7A5\uD558\uAE30", value: "save" },
3544
+ { name: "\u23EA \uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30", value: "undo" },
3545
+ { name: "\u{1F50D} \uBCC0\uACBD\uC0AC\uD56D \uBCF4\uAE30", value: "diff" },
3546
+ { name: "\u{1F4CA} \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uBCF4\uAE30", value: "status" }
2839
3547
  ]
2840
3548
  }]);
2841
3549
  switch (choice) {
@@ -2855,6 +3563,14 @@ program.action(async () => {
2855
3563
  return doctor();
2856
3564
  case "ship":
2857
3565
  return ship();
3566
+ case "save":
3567
+ return save();
3568
+ case "undo":
3569
+ return undo();
3570
+ case "status":
3571
+ return status();
3572
+ case "diff":
3573
+ return diff();
2858
3574
  }
2859
3575
  });
2860
- program.parse();
3576
+ await program.parseAsync(process.argv);