@byh3071/vhk 0.8.0 β†’ 0.9.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.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  id: vhk-readme
3
3
  date: 2026-05-24
4
- tags: [vhk, cli, readme, v0.8.0]
4
+ tags: [vhk, cli, readme, v0.9.0]
5
5
  ---
6
6
 
7
7
  # πŸ”§ VHK β€” Vibe Harness Kit
8
8
 
9
- > AI μ½”λ”© μ—μ΄μ „νŠΈλ₯Ό λΆ€λ¦¬λŠ” μ‚¬λžŒμ„ μœ„ν•œ **ν•œκ΅­μ–΄ 풀사이클 CLI** (v0.8.0)
9
+ > AI μ½”λ”© μ—μ΄μ „νŠΈλ₯Ό λΆ€λ¦¬λŠ” μ‚¬λžŒμ„ μœ„ν•œ **ν•œκ΅­μ–΄ 풀사이클 CLI** (v0.9.0)
10
10
  >
11
11
  > 🍽️ **VHKλŠ” VHK둜 λΆ€νŠΈμŠ€νŠΈλž©λ¨** β€” 이 레포의 `docs/`, `CLAUDE.md`, `.cursorrules`도 `vhk init`이 λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€.
12
12
 
@@ -99,6 +99,10 @@ vhk 기획 끝났고 λ°”λ‘œ μ‹œμž‘
99
99
  | `vhk design-palette` | `νŒ”λ ˆνŠΈ` | 컬러 νŒ”λ ˆνŠΈ 프리셋 선택 + 적용 |
100
100
  | `vhk theme` | `ν…Œλ§ˆ` | 닀크/라이트 λͺ¨λ“œ CSS + ν† κΈ€ μœ ν‹Έλ¦¬ν‹° 생성 |
101
101
  | `vhk ref` | `레퍼런슀` | 레퍼런슀 URL 관리 (`add` / `list` / `open`) |
102
+ | `vhk harness` | `ν•˜λ„€μŠ€` | 톡합 ν’ˆμ§ˆ 점검 (lint + type-check + test + build 순차 μ‹€ν–‰ + 리포트) |
103
+ | `vhk audit` | `감사` | npm λ³΄μ•ˆ 취약점 감사 (`--fix`둜 μžλ™ μˆ˜μ •) |
104
+ | `vhk migrate [target]` | `μ „ν™˜` | νŒ¨ν‚€μ§€ λ§€λ‹ˆμ € μ „ν™˜ (`npm` / `yarn` / `pnpm`, lockfile + node_modules μž¬κ΅¬μ„±) |
105
+ | `vhk update` | `μ—…λ°μ΄νŠΈ` | VHK CLI μ΅œμ‹  λ²„μ „μœΌλ‘œ μ…€ν”„ μ—…λ°μ΄νŠΈ |
102
106
 
103
107
  ### init μ˜΅μ…˜
104
108
 
@@ -133,6 +137,24 @@ MCP μ„œλ²„λ₯Ό μˆ˜λ™μœΌλ‘œ λ„μš°λ €λ©΄:
133
137
  vhk mcp # stdio μ„œλ²„ μ‹œμž‘ (Cursorκ°€ μžλ™μœΌλ‘œ 호좜)
134
138
  ```
135
139
 
140
+ ## v0.9.0 ν•˜μ΄λΌμ΄νŠΈ
141
+
142
+ | κΈ°λŠ₯ | μ„€λͺ… |
143
+ |------|------|
144
+ | **harness** | `package.json` scripts μžλ™ 감지 β†’ `lint` / `type-check` / `test` / `build` 순차 μ‹€ν–‰ + 톡합 리포트. 일뢀 μ‹€νŒ¨ν•΄λ„ λκΉŒμ§€ μ§„ν–‰ |
145
+ | **audit** | `npm audit --json` λž˜ν•‘ + 심각도별 μš”μ•½. `Critical`/`High` 발견 μ‹œ μžλ™ fix μ˜΅μ…˜. Windows PowerShell ν˜Έν™˜ (`2>/dev/null` λ―Έμ‚¬μš©) |
146
+ | **migrate** | npm/yarn/pnpm μ „ν™˜ β€” λŒ€μƒ CLI 쑴재 확인 β†’ 확인 ν”„λ‘¬ν”„νŠΈ β†’ κΈ°μ‘΄ lockfile + node_modules 정리 β†’ `<pm> install` |
147
+ | **update** | npm registryμ—μ„œ `@byh3071/vhk` μ΅œμ‹  버전 쑰회 β†’ semver 비ꡐ β†’ `npm update -g` μ‹€ν–‰. ν˜„μž¬ 버전이 κ°™κ±°λ‚˜ 더 λ†’μœΌλ©΄ μŠ€ν‚΅ |
148
+ | **μžμ—°μ–΄ ν™•μž₯** | `"ν’ˆμ§ˆ μ κ²€ν•΄μ€˜"` β†’ harness Β· `"λ³΄μ•ˆ 감사 ν•΄μ€˜"` / `"취약점 확인"` β†’ audit Β· `"νŒ¨ν‚€μ§€ λ§€λ‹ˆμ € μ „ν™˜"` β†’ migrate Β· `"vhk μ—…λ°μ΄νŠΈ ν•΄μ€˜"` β†’ update. ν‚€μ›Œλ“œ 좩돌 κ°€λ“œ: `점검` 단독은 κΈ°μ‘΄ `check`에 양보, `λ³΄μ•ˆ` 단독은 κΈ°μ‘΄ `secure`에 양보 |
149
+
150
+ ```powershell
151
+ vhk harness # lint + type-check + test + build 순차 μ‹€ν–‰
152
+ vhk audit # npm λ³΄μ•ˆ 감사 (Critical/High 발견 μ‹œ μžλ™ fix μ˜΅μ…˜)
153
+ vhk audit --fix # 항상 npm audit fix μ‹€ν–‰
154
+ vhk migrate pnpm # npm/yarn β†’ pnpm μ „ν™˜ (λŒ€ν™”ν˜• 확인)
155
+ vhk update # @byh3071/vhk μ΅œμ‹  버전 체크 + κΈ€λ‘œλ²Œ μ—…λ°μ΄νŠΈ
156
+ ```
157
+
136
158
  ## v0.8.0 ν•˜μ΄λΌμ΄νŠΈ
137
159
 
138
160
  | κΈ°λŠ₯ | μ„€λͺ… |
@@ -224,6 +246,10 @@ vhk ref open 1 # 1번 레퍼런슀λ₯Ό λΈŒλΌμš°μ €λ‘œ μ—΄κΈ°
224
246
  | νŒ”λ ˆνŠΈ 골라쀘 | `vhk design-palette` |
225
247
  | 닀크 λͺ¨λ“œ 적용 | `vhk theme` |
226
248
  | 레퍼런슀 λ³΄μ—¬μ€˜ | `vhk ref` (list) |
249
+ | ν’ˆμ§ˆ μ κ²€ν•΄μ€˜ | `vhk harness` |
250
+ | λ³΄μ•ˆ 감사 ν•΄μ€˜ / 취약점 확인 | `vhk audit` |
251
+ | νŒ¨ν‚€μ§€ λ§€λ‹ˆμ € μ „ν™˜ | `vhk migrate` |
252
+ | vhk μ—…λ°μ΄νŠΈ ν•΄μ€˜ | `vhk update` |
227
253
 
228
254
  ## νŠΉμ§•
229
255
 
@@ -310,6 +310,19 @@ var ko = {
310
310
  publishing: "npm \uBC30\uD3EC \uC911...",
311
311
  publishSuccess: "npm \uBC30\uD3EC \uC131\uACF5!",
312
312
  publishFailed: "npm \uBC30\uD3EC \uC2E4\uD328"
313
+ },
314
+ harness: {
315
+ title: "\uD1B5\uD569 \uD488\uC9C8 \uC810\uAC80"
316
+ },
317
+ audit: {
318
+ title: "\uBCF4\uC548 \uAC10\uC0AC"
319
+ },
320
+ migrate: {
321
+ title: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658",
322
+ selectTarget: "\uC5B4\uB5A4 \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800\uB85C \uC804\uD658\uD560\uAE4C\uC694?"
323
+ },
324
+ update: {
325
+ title: "VHK CLI \uC5C5\uB370\uC774\uD2B8"
313
326
  }
314
327
  };
315
328
  function lookup(path) {
@@ -442,21 +455,31 @@ function platformCmd(cmd) {
442
455
  }
443
456
  return cmd;
444
457
  }
458
+ function resolveCmd(cmd, args) {
459
+ if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
460
+ return { bin: "cmd.exe", argv: ["/d", "/s", "/c", `${cmd}.cmd`, ...args] };
461
+ }
462
+ return { bin: platformCmd(cmd), argv: args };
463
+ }
445
464
  function safeExecFile(cmd, args) {
465
+ const { bin, argv } = resolveCmd(cmd, args);
446
466
  try {
447
- const out = execFileSync(platformCmd(cmd), args, {
467
+ const out = execFileSync(bin, argv, {
448
468
  encoding: "utf-8",
449
469
  stdio: ["pipe", "pipe", "pipe"]
450
470
  }).toString();
451
471
  return { ok: true, out: out.trim() };
452
472
  } catch (err) {
453
- const msg = err instanceof Error ? err.message : String(err);
454
- return { ok: false, err: msg };
473
+ const e = err;
474
+ const stdout = e.stdout ? e.stdout.toString() : "";
475
+ const msg = e.message ?? String(err);
476
+ return { ok: false, err: msg, out: stdout.trim() };
455
477
  }
456
478
  }
457
479
  function safeExecFileStream(cmd, args) {
480
+ const { bin, argv } = resolveCmd(cmd, args);
458
481
  try {
459
- execFileSync(platformCmd(cmd), args, {
482
+ execFileSync(bin, argv, {
460
483
  encoding: "utf-8",
461
484
  stdio: "inherit"
462
485
  });
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  safeExecFileStream,
11
11
  startMcpServer,
12
12
  t
13
- } from "./chunk-NQ4V3VN4.js";
13
+ } from "./chunk-UPXCLOBF.js";
14
14
 
15
15
  // node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
16
16
  var require_ignore = __commonJS({
@@ -310,7 +310,7 @@ var require_ignore = __commonJS({
310
310
  // path matching.
311
311
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
312
312
  // @returns {TestResult} true if a file is ignored
313
- test(path15, checkUnignored, mode) {
313
+ test(path16, checkUnignored, mode) {
314
314
  let ignored = false;
315
315
  let unignored = false;
316
316
  let matchedRule;
@@ -319,7 +319,7 @@ var require_ignore = __commonJS({
319
319
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
320
320
  return;
321
321
  }
322
- const matched = rule[mode].test(path15);
322
+ const matched = rule[mode].test(path16);
323
323
  if (!matched) {
324
324
  return;
325
325
  }
@@ -340,17 +340,17 @@ var require_ignore = __commonJS({
340
340
  var throwError = (message, Ctor) => {
341
341
  throw new Ctor(message);
342
342
  };
343
- var checkPath = (path15, originalPath, doThrow) => {
344
- if (!isString(path15)) {
343
+ var checkPath = (path16, originalPath, doThrow) => {
344
+ if (!isString(path16)) {
345
345
  return doThrow(
346
346
  `path must be a string, but got \`${originalPath}\``,
347
347
  TypeError
348
348
  );
349
349
  }
350
- if (!path15) {
350
+ if (!path16) {
351
351
  return doThrow(`path must not be empty`, TypeError);
352
352
  }
353
- if (checkPath.isNotRelative(path15)) {
353
+ if (checkPath.isNotRelative(path16)) {
354
354
  const r = "`path.relative()`d";
355
355
  return doThrow(
356
356
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -359,7 +359,7 @@ var require_ignore = __commonJS({
359
359
  }
360
360
  return true;
361
361
  };
362
- var isNotRelative = (path15) => REGEX_TEST_INVALID_PATH.test(path15);
362
+ var isNotRelative = (path16) => REGEX_TEST_INVALID_PATH.test(path16);
363
363
  checkPath.isNotRelative = isNotRelative;
364
364
  checkPath.convert = (p) => p;
365
365
  var Ignore = class {
@@ -389,19 +389,19 @@ var require_ignore = __commonJS({
389
389
  }
390
390
  // @returns {TestResult}
391
391
  _test(originalPath, cache, checkUnignored, slices) {
392
- const path15 = originalPath && checkPath.convert(originalPath);
392
+ const path16 = originalPath && checkPath.convert(originalPath);
393
393
  checkPath(
394
- path15,
394
+ path16,
395
395
  originalPath,
396
396
  this._strictPathCheck ? throwError : RETURN_FALSE
397
397
  );
398
- return this._t(path15, cache, checkUnignored, slices);
398
+ return this._t(path16, cache, checkUnignored, slices);
399
399
  }
400
- checkIgnore(path15) {
401
- if (!REGEX_TEST_TRAILING_SLASH.test(path15)) {
402
- return this.test(path15);
400
+ checkIgnore(path16) {
401
+ if (!REGEX_TEST_TRAILING_SLASH.test(path16)) {
402
+ return this.test(path16);
403
403
  }
404
- const slices = path15.split(SLASH).filter(Boolean);
404
+ const slices = path16.split(SLASH).filter(Boolean);
405
405
  slices.pop();
406
406
  if (slices.length) {
407
407
  const parent = this._t(
@@ -414,18 +414,18 @@ var require_ignore = __commonJS({
414
414
  return parent;
415
415
  }
416
416
  }
417
- return this._rules.test(path15, false, MODE_CHECK_IGNORE);
417
+ return this._rules.test(path16, false, MODE_CHECK_IGNORE);
418
418
  }
419
- _t(path15, cache, checkUnignored, slices) {
420
- if (path15 in cache) {
421
- return cache[path15];
419
+ _t(path16, cache, checkUnignored, slices) {
420
+ if (path16 in cache) {
421
+ return cache[path16];
422
422
  }
423
423
  if (!slices) {
424
- slices = path15.split(SLASH).filter(Boolean);
424
+ slices = path16.split(SLASH).filter(Boolean);
425
425
  }
426
426
  slices.pop();
427
427
  if (!slices.length) {
428
- return cache[path15] = this._rules.test(path15, checkUnignored, MODE_IGNORE);
428
+ return cache[path16] = this._rules.test(path16, checkUnignored, MODE_IGNORE);
429
429
  }
430
430
  const parent = this._t(
431
431
  slices.join(SLASH) + SLASH,
@@ -433,29 +433,29 @@ var require_ignore = __commonJS({
433
433
  checkUnignored,
434
434
  slices
435
435
  );
436
- return cache[path15] = parent.ignored ? parent : this._rules.test(path15, checkUnignored, MODE_IGNORE);
436
+ return cache[path16] = parent.ignored ? parent : this._rules.test(path16, checkUnignored, MODE_IGNORE);
437
437
  }
438
- ignores(path15) {
439
- return this._test(path15, this._ignoreCache, false).ignored;
438
+ ignores(path16) {
439
+ return this._test(path16, this._ignoreCache, false).ignored;
440
440
  }
441
441
  createFilter() {
442
- return (path15) => !this.ignores(path15);
442
+ return (path16) => !this.ignores(path16);
443
443
  }
444
444
  filter(paths) {
445
445
  return makeArray(paths).filter(this.createFilter());
446
446
  }
447
447
  // @returns {TestResult}
448
- test(path15) {
449
- return this._test(path15, this._testCache, true);
448
+ test(path16) {
449
+ return this._test(path16, this._testCache, true);
450
450
  }
451
451
  };
452
452
  var factory = (options) => new Ignore(options);
453
- var isPathValid = (path15) => checkPath(path15 && checkPath.convert(path15), path15, RETURN_FALSE);
453
+ var isPathValid = (path16) => checkPath(path16 && checkPath.convert(path16), path16, RETURN_FALSE);
454
454
  var setupWindows = () => {
455
455
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
456
456
  checkPath.convert = makePosix;
457
457
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
458
- checkPath.isNotRelative = (path15) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path15) || isNotRelative(path15);
458
+ checkPath.isNotRelative = (path16) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path16) || isNotRelative(path16);
459
459
  };
460
460
  if (
461
461
  // Detect `process` so that it can run in browsers.
@@ -472,7 +472,10 @@ var require_ignore = __commonJS({
472
472
 
473
473
  // src/index.ts
474
474
  import { Command, Help } from "commander";
475
- import inquirer12 from "inquirer";
475
+ import inquirer14 from "inquirer";
476
+ import fs16 from "fs";
477
+ import path15 from "path";
478
+ import { fileURLToPath as fileURLToPath4 } from "url";
476
479
 
477
480
  // src/lib/nlp-router.ts
478
481
  function normalize(input) {
@@ -508,7 +511,7 @@ var RULES = [
508
511
  command: "init",
509
512
  explanation: "\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791)",
510
513
  confidence: "high",
511
- test: (t2) => /ν”„λ‘œμ νŠΈ.*(λ§Œλ“€|μ‹œμž‘)|폴더.*λ§Œλ“€|λ§Œλ“€κ³ \s*μ‹Ά|ν•˜λ„€μŠ€|μ΄ˆκΈ°ν™”/.test(t2) || /^μ‹œμž‘$/.test(t2)
514
+ test: (t2) => (/ν”„λ‘œμ νŠΈ.*(λ§Œλ“€|μ‹œμž‘)|폴더.*λ§Œλ“€|λ§Œλ“€κ³ \s*μ‹Ά|ν•˜λ„€μŠ€|μ΄ˆκΈ°ν™”/.test(t2) || /^μ‹œμž‘$/.test(t2)) && !/λ””μžμΈ|design|νŒ”λ ˆνŠΈ|palette|ν…Œλ§ˆ|theme|레퍼런슀|reference|닀크\s*λͺ¨λ“œ|라이트\s*λͺ¨λ“œ|색상\s*λͺ¨λ“œ/.test(t2)
512
515
  },
513
516
  {
514
517
  command: "mcp-init",
@@ -526,13 +529,13 @@ var RULES = [
526
529
  command: "design",
527
530
  explanation: "\uB514\uC790\uC778 \uD1A0\uD070 \uC0DD\uC131 (vhk design)",
528
531
  confidence: "high",
529
- test: (t2) => /λ””μžμΈ\s*(토큰|μ‹œμŠ€ν…œ|λ§Œλ“€|생성|μ…‹μ—…|μ„€μ •)|design\s*(token|system|setup)|토큰\s*λ§Œλ“€|css\s*λ³€μˆ˜.*λ§Œλ“€|tailwind\s*(컬러|μ„€μ •)/.test(t2)
532
+ test: (t2) => /λ””μžμΈ\s*(토큰|μ‹œμŠ€ν…œ|λ§Œλ“€|생성|μ…‹μ—…|μ„€μ •)|design\s*(token|system|setup)|토큰\s*λ§Œλ“€|css\s*λ³€μˆ˜.*λ§Œλ“€|tailwind\s*(컬러|μ„€μ •)/.test(t2) && !/배포|deploy|vercel|netlify|cloudflare|wrangler|μΆœμ‹œ|publish|npm/.test(t2)
530
533
  },
531
534
  {
532
535
  command: "theme",
533
536
  explanation: "\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uD14C\uB9C8 \uC801\uC6A9 (vhk theme)",
534
537
  confidence: "high",
535
- test: (t2) => /ν…Œλ§ˆ(?!\s*(파일|이름))|theme|닀크\s*λͺ¨λ“œ|라이트\s*λͺ¨λ“œ|dark\s*mode|light\s*mode|색상\s*λͺ¨λ“œ|λͺ¨λ“œ\s*μ „ν™˜/.test(t2)
538
+ test: (t2) => /ν…Œλ§ˆ(?!\s*(파일|이름))|theme|닀크\s*λͺ¨λ“œ|라이트\s*λͺ¨λ“œ|dark\s*mode|light\s*mode|색상\s*λͺ¨λ“œ|λͺ¨λ“œ\s*μ „ν™˜/.test(t2) && !/λ³΄μ•ˆ|μ‹œν¬λ¦Ώ|λΉ„λ°€|ν‚€\s*유좜|secure|scan|μŠ€μΊ”|배포|deploy/.test(t2)
536
539
  },
537
540
  {
538
541
  command: "ref",
@@ -540,6 +543,30 @@ var RULES = [
540
543
  confidence: "high",
541
544
  test: (t2) => /^레퍼런슀$|^ref$|레퍼런슀.*(보|λͺ©λ‘|확인|있|뭐)|μ°Έκ³ \s*(μ‚¬μ΄νŠΈ|λͺ©λ‘|링크)|reference.*list/.test(t2) && !/(add|μΆ”κ°€|open|μ—΄|https?:\/\/)/.test(t2)
542
545
  },
546
+ {
547
+ command: "harness",
548
+ explanation: "\uD1B5\uD569 \uD488\uC9C8 \uC810\uAC80 (vhk harness)",
549
+ confidence: "high",
550
+ test: (t2) => /ν•˜λ„€μŠ€|harness|톡합\s*점검|ν’ˆμ§ˆ\s*점검|λΉŒλ“œ\s*ν…ŒμŠ€νŠΈ|lint.*(test|build)|전체\s*점검|ν’ˆμ§ˆ\s*확인/.test(t2)
551
+ },
552
+ {
553
+ command: "audit",
554
+ explanation: "\uBCF4\uC548 \uCDE8\uC57D\uC810 \uAC10\uC0AC (vhk audit)",
555
+ confidence: "high",
556
+ test: (t2) => /감사|취약점|audit|vulnerability|λ³΄μ•ˆ\s*감사|λ³΄μ•ˆ\s*μ·¨μ•½|μ˜μ‘΄μ„±\s*μ·¨μ•½/.test(t2)
557
+ },
558
+ {
559
+ command: "migrate",
560
+ explanation: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 (vhk migrate)",
561
+ confidence: "high",
562
+ test: (t2) => /μ „ν™˜|마이그레이트|migrate|νŒ¨ν‚€μ§€\s*λ§€λ‹ˆμ €|npm.*pnpm|pnpm.*npm|yarn.*μ „ν™˜|npm.*μ „ν™˜|pnpm.*μ „ν™˜/.test(t2)
563
+ },
564
+ {
565
+ command: "update",
566
+ explanation: "VHK CLI \uCD5C\uC2E0 \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8 (vhk update)",
567
+ confidence: "high",
568
+ test: (t2) => /μ—…λ°μ΄νŠΈ|update|버전\s*μ—…|μ΅œμ‹ \s*버전|μ…€ν”„\s*μ—…λ°μ΄νŠΈ|vhk.*μ΅œμ‹ |vhk.*μ—…λ°μ΄νŠΈ/.test(t2)
569
+ },
543
570
  {
544
571
  command: "secure",
545
572
  explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548)",
@@ -706,6 +733,14 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
706
733
  "\uD14C\uB9C8",
707
734
  "ref",
708
735
  "\uB808\uD37C\uB7F0\uC2A4",
736
+ "harness",
737
+ "\uD558\uB124\uC2A4",
738
+ "audit",
739
+ "\uAC10\uC0AC",
740
+ "migrate",
741
+ "\uC804\uD658",
742
+ "update",
743
+ "\uC5C5\uB370\uC774\uD2B8",
709
744
  "help"
710
745
  ]);
711
746
  function isOptionToken(token) {
@@ -729,8 +764,8 @@ function detectNaturalLanguageInput(argv) {
729
764
  }
730
765
 
731
766
  // src/lib/nlp-run.ts
732
- import chalk21 from "chalk";
733
- import inquirer11 from "inquirer";
767
+ import chalk25 from "chalk";
768
+ import inquirer13 from "inquirer";
734
769
 
735
770
  // src/commands/gate.ts
736
771
  import inquirer from "inquirer";
@@ -3567,17 +3602,21 @@ async function publish() {
3567
3602
  console.log(chalk17.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
3568
3603
  return;
3569
3604
  }
3570
- const pubSpinner = ora2(t("publish.publishing")).start();
3571
- const pubResult = safeExecFile("npm", ["publish", "--access", "public"]);
3605
+ console.log(chalk17.cyan(`
3606
+ \u{1F4E4} ${t("publish.publishing")}`));
3607
+ console.log(chalk17.gray(" 2FA \uD65C\uC131\uD654 \uC2DC: OTP 6\uC790\uB9AC \uC785\uB825 \uB610\uB294 \uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D URL \uD074\uB9AD (Windows Hello / PIN \uC9C0\uC6D0)"));
3608
+ const pubResult = safeExecFileStream("npm", ["publish", "--access", "public"]);
3572
3609
  if (!pubResult.ok) {
3573
- pubSpinner.fail(t("publish.publishFailed"));
3610
+ console.log(chalk17.red(`
3611
+ \u2716 ${t("publish.publishFailed")}`));
3574
3612
  console.log(chalk17.red(pubResult.err.slice(0, 500)));
3575
3613
  pkg.version = currentVersion;
3576
3614
  writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
3577
3615
  console.log(chalk17.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
3578
3616
  return;
3579
3617
  }
3580
- pubSpinner.succeed(t("publish.publishSuccess"));
3618
+ console.log(chalk17.green(`
3619
+ \u2714 ${t("publish.publishSuccess")}`));
3581
3620
  const addResult = safeExecFile("git", ["add", "package.json"]);
3582
3621
  if (addResult.ok) {
3583
3622
  safeExecFile("git", ["commit", "-m", `chore: release v${newVersion}`]);
@@ -3926,6 +3965,367 @@ async function refOpen(indexStr) {
3926
3965
  }
3927
3966
  }
3928
3967
 
3968
+ // src/commands/harness.ts
3969
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
3970
+ import chalk21 from "chalk";
3971
+ import ora3 from "ora";
3972
+ function detectPM() {
3973
+ if (existsSync7("pnpm-lock.yaml")) return "pnpm";
3974
+ if (existsSync7("yarn.lock")) return "yarn";
3975
+ return "npm";
3976
+ }
3977
+ function pmRun(pm, script) {
3978
+ return pm === "npm" ? ["run", script] : [script];
3979
+ }
3980
+ function detectChecks() {
3981
+ const checks = [];
3982
+ let pkg = {};
3983
+ try {
3984
+ pkg = JSON.parse(readFileSync4("package.json", "utf-8"));
3985
+ } catch {
3986
+ return checks;
3987
+ }
3988
+ const s = pkg.scripts ?? {};
3989
+ const pm = detectPM();
3990
+ if (s.lint) {
3991
+ checks.push({ name: "lint", bin: pm, args: pmRun(pm, "lint") });
3992
+ } else if (existsSync7(".eslintrc.js") || existsSync7(".eslintrc.json") || existsSync7("eslint.config.js")) {
3993
+ checks.push({ name: "lint", bin: "npx", args: ["eslint", ".", "--ext", ".ts,.tsx"] });
3994
+ }
3995
+ if (s["type-check"]) {
3996
+ checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "type-check") });
3997
+ } else if (s.typecheck) {
3998
+ checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "typecheck") });
3999
+ } else if (existsSync7("tsconfig.json")) {
4000
+ checks.push({ name: "type-check", bin: "npx", args: ["tsc", "--noEmit"] });
4001
+ }
4002
+ if (s.test) {
4003
+ checks.push({ name: "test", bin: pm, args: pmRun(pm, "test") });
4004
+ }
4005
+ if (s.build) {
4006
+ checks.push({ name: "build", bin: pm, args: pmRun(pm, "build") });
4007
+ }
4008
+ return checks;
4009
+ }
4010
+ async function harness() {
4011
+ console.log(chalk21.bold("\n\u{1F527} " + t("harness.title")));
4012
+ console.log(chalk21.gray("\u2500".repeat(40)));
4013
+ const checks = detectChecks();
4014
+ if (checks.length === 0) {
4015
+ console.log(chalk21.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4016
+ console.log(chalk21.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
4017
+ return;
4018
+ }
4019
+ console.log(chalk21.cyan(`
4020
+ \u{1F3C3} ${checks.length}\uAC1C \uC810\uAC80 \uC2DC\uC791:
4021
+ `));
4022
+ const results = [];
4023
+ for (const check2 of checks) {
4024
+ const display = `${check2.bin} ${check2.args.join(" ")}`;
4025
+ const spinner = ora3(`${check2.name} \uC2E4\uD589 \uC911...`).start();
4026
+ const start = Date.now();
4027
+ const result = safeExecFile(check2.bin, check2.args);
4028
+ const duration = Date.now() - start;
4029
+ const sec = (duration / 1e3).toFixed(1);
4030
+ if (result.ok) {
4031
+ spinner.succeed(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
4032
+ results.push({ name: check2.name, command: display, passed: true, duration });
4033
+ } else {
4034
+ spinner.fail(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
4035
+ results.push({
4036
+ name: check2.name,
4037
+ command: display,
4038
+ passed: false,
4039
+ duration,
4040
+ error: result.err.slice(0, 200)
4041
+ });
4042
+ }
4043
+ }
4044
+ console.log(chalk21.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
4045
+ console.log(chalk21.gray("\u2500".repeat(40)));
4046
+ for (const r of results) {
4047
+ const icon = r.passed ? chalk21.green("\u2705") : chalk21.red("\u274C");
4048
+ const sec = (r.duration / 1e3).toFixed(1);
4049
+ console.log(` ${icon} ${r.name.padEnd(15)} ${chalk21.gray(`${sec}s`)}`);
4050
+ }
4051
+ const passed = results.filter((r) => r.passed).length;
4052
+ const all = passed === results.length;
4053
+ console.log(chalk21.gray("\u2500".repeat(40)));
4054
+ if (all) {
4055
+ console.log(chalk21.green.bold(`
4056
+ \u{1F389} \uC804\uCCB4 \uD1B5\uACFC! (${passed}/${results.length})`));
4057
+ } else {
4058
+ console.log(
4059
+ chalk21.red.bold(`
4060
+ \u26A0\uFE0F ${results.length - passed}\uAC1C \uC2E4\uD328 (${passed}/${results.length} \uD1B5\uACFC)`)
4061
+ );
4062
+ }
4063
+ printNextStep({
4064
+ message: all ? "\uD488\uC9C8 \uC810\uAC80 \uD1B5\uACFC!" : "\uC2E4\uD328 \uD56D\uBAA9\uC744 \uC218\uC815\uD558\uC138\uC694.",
4065
+ command: all ? "vhk ship" : "vhk doctor",
4066
+ cursorHint: all ? "\uBC30\uD3EC\uD574\uC918" : "\uBB38\uC81C \uC9C4\uB2E8\uD574\uC918"
4067
+ });
4068
+ }
4069
+
4070
+ // src/commands/audit.ts
4071
+ import { existsSync as existsSync8 } from "fs";
4072
+ import chalk22 from "chalk";
4073
+ import inquirer11 from "inquirer";
4074
+ import ora4 from "ora";
4075
+ function detectCurrentPM() {
4076
+ if (existsSync8("pnpm-lock.yaml")) return "pnpm";
4077
+ if (existsSync8("yarn.lock")) return "yarn";
4078
+ return "npm";
4079
+ }
4080
+ function parseAuditOutput(output, pm) {
4081
+ const empty = { critical: 0, high: 0, moderate: 0, low: 0, total: 0 };
4082
+ if (!output) return empty;
4083
+ try {
4084
+ const json = JSON.parse(output);
4085
+ const meta = json.metadata?.vulnerabilities;
4086
+ if (meta) {
4087
+ const summary = {
4088
+ critical: meta.critical ?? 0,
4089
+ high: meta.high ?? 0,
4090
+ moderate: meta.moderate ?? 0,
4091
+ low: meta.low ?? 0,
4092
+ total: meta.total ?? 0
4093
+ };
4094
+ if (!summary.total) {
4095
+ summary.total = summary.critical + summary.high + summary.moderate + summary.low;
4096
+ }
4097
+ return summary;
4098
+ }
4099
+ void pm;
4100
+ return empty;
4101
+ } catch {
4102
+ return empty;
4103
+ }
4104
+ }
4105
+ function runAuditJson(pm) {
4106
+ const result = safeExecFile(pm, ["audit", "--json"]);
4107
+ return result.out;
4108
+ }
4109
+ function runAuditFix(pm) {
4110
+ if (pm !== "npm") {
4111
+ return { ok: false, err: `${pm}\uC740 \uC790\uB3D9 fix\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. npm \uD658\uACBD\uC5D0\uC11C\uB9CC \uB3D9\uC791\uD569\uB2C8\uB2E4.` };
4112
+ }
4113
+ const result = safeExecFile("npm", ["audit", "fix"]);
4114
+ return result.ok ? { ok: true } : { ok: false, err: result.err };
4115
+ }
4116
+ async function audit(autoFix = false) {
4117
+ console.log(chalk22.bold("\n\u{1F6E1}\uFE0F " + t("audit.title")));
4118
+ console.log(chalk22.gray("\u2500".repeat(40)));
4119
+ const pm = detectCurrentPM();
4120
+ console.log(chalk22.cyan(`\u{1F4E6} \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${pm}`));
4121
+ const spinner = ora4("\uBCF4\uC548 \uAC10\uC0AC \uC2E4\uD589 \uC911...").start();
4122
+ const output = runAuditJson(pm);
4123
+ spinner.stop();
4124
+ const summary = parseAuditOutput(output, pm);
4125
+ if (summary.total === 0) {
4126
+ console.log(chalk22.green.bold("\n\u{1F389} \uCDE8\uC57D\uC810\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4!"));
4127
+ return;
4128
+ }
4129
+ console.log(chalk22.bold("\n\u{1F4CA} \uCDE8\uC57D\uC810 \uC694\uC57D:"));
4130
+ if (summary.critical > 0) console.log(chalk22.red(` \u{1F534} Critical: ${summary.critical}`));
4131
+ if (summary.high > 0) console.log(chalk22.red(` \u{1F7E0} High: ${summary.high}`));
4132
+ if (summary.moderate > 0) console.log(chalk22.yellow(` \u{1F7E1} Moderate: ${summary.moderate}`));
4133
+ if (summary.low > 0) console.log(chalk22.gray(` \u26AA Low: ${summary.low}`));
4134
+ console.log(chalk22.bold(`
4135
+ \uCD1D ${summary.total}\uAC1C\uC758 \uCDE8\uC57D\uC810`));
4136
+ const shouldRunFix = autoFix ? true : summary.critical > 0 || summary.high > 0 ? (await inquirer11.prompt([
4137
+ {
4138
+ type: "confirm",
4139
+ name: "shouldFix",
4140
+ message: "\uC790\uB3D9 \uC218\uC815\uC744 \uC2DC\uB3C4\uD560\uAE4C\uC694? (npm audit fix)",
4141
+ default: true
4142
+ }
4143
+ ])).shouldFix : false;
4144
+ if (shouldRunFix) {
4145
+ const fixSpinner = ora4("\uC790\uB3D9 \uC218\uC815 \uC911...").start();
4146
+ const result = runAuditFix(pm);
4147
+ if (result.ok) {
4148
+ fixSpinner.succeed("\uC790\uB3D9 \uC218\uC815 \uC644\uB8CC!");
4149
+ } else {
4150
+ fixSpinner.warn(result.err ?? "\uC77C\uBD80 \uCDE8\uC57D\uC810\uC740 \uC218\uB3D9 \uC218\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
4151
+ }
4152
+ }
4153
+ printNextStep({
4154
+ message: "\uBCF4\uC548 \uAC10\uC0AC \uC644\uB8CC.",
4155
+ command: "vhk harness",
4156
+ cursorHint: "\uD488\uC9C8 \uC810\uAC80\uD574\uC918"
4157
+ });
4158
+ }
4159
+
4160
+ // src/commands/migrate.ts
4161
+ import { existsSync as existsSync9, unlinkSync, rmSync } from "fs";
4162
+ import chalk23 from "chalk";
4163
+ import inquirer12 from "inquirer";
4164
+ import ora5 from "ora";
4165
+ var LOCK_FILES = {
4166
+ npm: "package-lock.json",
4167
+ yarn: "yarn.lock",
4168
+ pnpm: "pnpm-lock.yaml"
4169
+ };
4170
+ function detectCurrentPM2() {
4171
+ if (existsSync9("pnpm-lock.yaml")) return "pnpm";
4172
+ if (existsSync9("yarn.lock")) return "yarn";
4173
+ if (existsSync9("package-lock.json")) return "npm";
4174
+ return null;
4175
+ }
4176
+ function isCLIAvailable2(pm) {
4177
+ return safeExecFile(pm, ["--version"]).ok;
4178
+ }
4179
+ async function migrate(target) {
4180
+ console.log(chalk23.bold("\n\u{1F504} " + t("migrate.title")));
4181
+ console.log(chalk23.gray("\u2500".repeat(40)));
4182
+ const current = detectCurrentPM2();
4183
+ console.log(chalk23.cyan(`
4184
+ \uD604\uC7AC \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00"}`));
4185
+ let targetPM;
4186
+ if (target && ["npm", "yarn", "pnpm"].includes(target)) {
4187
+ targetPM = target;
4188
+ } else {
4189
+ const choices = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current).map((pm) => ({ name: pm, value: pm }));
4190
+ const { selected } = await inquirer12.prompt([
4191
+ {
4192
+ type: "list",
4193
+ name: "selected",
4194
+ message: t("migrate.selectTarget"),
4195
+ choices
4196
+ }
4197
+ ]);
4198
+ targetPM = selected;
4199
+ }
4200
+ if (targetPM === current) {
4201
+ console.log(chalk23.yellow(`
4202
+ \u26A0\uFE0F \uC774\uBBF8 ${targetPM}\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4.`));
4203
+ return;
4204
+ }
4205
+ if (!isCLIAvailable2(targetPM)) {
4206
+ console.log(chalk23.red(`
4207
+ \u274C ${targetPM}\uC774 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
4208
+ console.log(chalk23.yellow(` npm i -g ${targetPM}`));
4209
+ return;
4210
+ }
4211
+ const { confirm } = await inquirer12.prompt([
4212
+ {
4213
+ type: "confirm",
4214
+ name: "confirm",
4215
+ message: `${current ?? "\uD604\uC7AC"} \u2192 ${targetPM}\uC73C\uB85C \uC804\uD658\uD560\uAE4C\uC694? (node_modules \uC7AC\uC124\uCE58)`,
4216
+ default: true
4217
+ }
4218
+ ]);
4219
+ if (!confirm) {
4220
+ console.log(chalk23.gray("\uCDE8\uC18C\uB428"));
4221
+ return;
4222
+ }
4223
+ const cleanup = ora5("\uAE30\uC874 lock \uD30C\uC77C \uC815\uB9AC \uC911...").start();
4224
+ for (const lockFile of Object.values(LOCK_FILES)) {
4225
+ if (existsSync9(lockFile)) {
4226
+ unlinkSync(lockFile);
4227
+ }
4228
+ }
4229
+ if (existsSync9("node_modules")) {
4230
+ cleanup.text = "node_modules \uC0AD\uC81C \uC911...";
4231
+ rmSync("node_modules", { recursive: true, force: true });
4232
+ }
4233
+ cleanup.succeed("\uAE30\uC874 \uD30C\uC77C \uC815\uB9AC \uC644\uB8CC");
4234
+ const install = ora5(`${targetPM} install \uC2E4\uD589 \uC911...`).start();
4235
+ const installResult = safeExecFile(targetPM, ["install"]);
4236
+ if (installResult.ok) {
4237
+ install.succeed(`${targetPM} install \uC644\uB8CC!`);
4238
+ } else {
4239
+ install.fail(`${targetPM} install \uC2E4\uD328`);
4240
+ console.log(chalk23.red(installResult.err.slice(0, 300)));
4241
+ return;
4242
+ }
4243
+ console.log(chalk23.green.bold(`
4244
+ \u{1F389} ${current ?? "\uC774\uC804"} \u2192 ${targetPM} \uC804\uD658 \uC644\uB8CC!`));
4245
+ printNextStep({
4246
+ message: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 \uC644\uB8CC!",
4247
+ command: "vhk harness",
4248
+ cursorHint: "\uD488\uC9C8 \uC810\uAC80\uD574\uC918"
4249
+ });
4250
+ }
4251
+
4252
+ // src/commands/update.ts
4253
+ import { execSync as execSync3 } from "child_process";
4254
+ import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
4255
+ import { dirname, join as join2 } from "path";
4256
+ import { fileURLToPath as fileURLToPath3 } from "url";
4257
+ import chalk24 from "chalk";
4258
+ import ora6 from "ora";
4259
+ var PACKAGE = "@byh3071/vhk";
4260
+ function getCurrentVersion() {
4261
+ const dir = dirname(fileURLToPath3(import.meta.url));
4262
+ for (const pkgPath of [join2(dir, "../package.json"), join2(dir, "../../package.json")]) {
4263
+ try {
4264
+ if (existsSync10(pkgPath)) {
4265
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
4266
+ if (pkg.version) return pkg.version;
4267
+ }
4268
+ } catch {
4269
+ continue;
4270
+ }
4271
+ }
4272
+ return "0.0.0";
4273
+ }
4274
+ function getLatestVersion() {
4275
+ try {
4276
+ const out = execSync3(`npm view ${PACKAGE} version`, {
4277
+ encoding: "utf-8",
4278
+ stdio: ["pipe", "pipe", "pipe"]
4279
+ }).toString();
4280
+ return out.trim();
4281
+ } catch {
4282
+ return null;
4283
+ }
4284
+ }
4285
+ function isUpToDate(current, latest) {
4286
+ const parse = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
4287
+ const [ca, cb, cc] = parse(current);
4288
+ const [la, lb, lc] = parse(latest);
4289
+ if (ca !== la) return ca > la;
4290
+ if (cb !== lb) return cb > lb;
4291
+ return cc >= lc;
4292
+ }
4293
+ async function update() {
4294
+ console.log(chalk24.bold("\n\u2B06\uFE0F " + t("update.title")));
4295
+ console.log(chalk24.gray("\u2500".repeat(40)));
4296
+ const current = getCurrentVersion();
4297
+ console.log(chalk24.cyan(`
4298
+ \u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${current}`));
4299
+ const spinner = ora6("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...").start();
4300
+ const latest = getLatestVersion();
4301
+ if (!latest) {
4302
+ spinner.fail("\uCD5C\uC2E0 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4303
+ console.log(chalk24.yellow(" \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uAC70\uB098 \uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4304
+ console.log(chalk24.gray(` npm update -g ${PACKAGE}`));
4305
+ return;
4306
+ }
4307
+ spinner.stop();
4308
+ console.log(chalk24.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
4309
+ if (isUpToDate(current, latest)) {
4310
+ console.log(chalk24.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
4311
+ return;
4312
+ }
4313
+ const updateSpinner = ora6(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC911...`).start();
4314
+ try {
4315
+ execSync3(`npm update -g ${PACKAGE}`, { stdio: ["pipe", "pipe", "pipe"] });
4316
+ updateSpinner.succeed(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`);
4317
+ console.log(chalk24.green.bold(`
4318
+ \u{1F389} VHK CLI v${latest} \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
4319
+ console.log(chalk24.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
4320
+ } catch (err) {
4321
+ updateSpinner.fail("\uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
4322
+ const msg = err instanceof Error ? err.message.slice(0, 300) : String(err);
4323
+ console.log(chalk24.red(msg));
4324
+ console.log(chalk24.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4325
+ console.log(chalk24.gray(` npm update -g ${PACKAGE}`));
4326
+ }
4327
+ }
4328
+
3929
4329
  // src/lib/nlp-run.ts
3930
4330
  async function dispatchNlpRoute(route, input) {
3931
4331
  switch (route.command) {
@@ -3974,28 +4374,36 @@ async function dispatchNlpRoute(route, input) {
3974
4374
  return theme();
3975
4375
  case "ref":
3976
4376
  return refList();
4377
+ case "harness":
4378
+ return harness();
4379
+ case "audit":
4380
+ return audit();
4381
+ case "migrate":
4382
+ return migrate();
4383
+ case "update":
4384
+ return update();
3977
4385
  }
3978
4386
  }
3979
4387
  async function runNaturalLanguageRoute(input) {
3980
4388
  const route = routeNaturalLanguage(input);
3981
4389
  if (!route) {
3982
- console.log(chalk21.yellow(`
4390
+ console.log(chalk25.yellow(`
3983
4391
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
3984
4392
  `));
3985
4393
  return;
3986
4394
  }
3987
4395
  console.log("");
3988
- console.log(chalk21.cyan(` \u{1F4AC} "${input}"`));
3989
- console.log(chalk21.cyan(` \u2192 ${route.explanation}`));
4396
+ console.log(chalk25.cyan(` \u{1F4AC} "${input}"`));
4397
+ console.log(chalk25.cyan(` \u2192 ${route.explanation}`));
3990
4398
  if (route.confidence === "low") {
3991
- const { confirm } = await inquirer11.prompt([{
4399
+ const { confirm } = await inquirer13.prompt([{
3992
4400
  type: "confirm",
3993
4401
  name: "confirm",
3994
4402
  message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
3995
4403
  default: true
3996
4404
  }]);
3997
4405
  if (!confirm) {
3998
- console.log(chalk21.dim(` ${ko.nlp.menuHint}`));
4406
+ console.log(chalk25.dim(` ${ko.nlp.menuHint}`));
3999
4407
  return;
4000
4408
  }
4001
4409
  }
@@ -4004,6 +4412,20 @@ async function runNaturalLanguageRoute(input) {
4004
4412
  }
4005
4413
 
4006
4414
  // src/index.ts
4415
+ function getVersion() {
4416
+ const dir = path15.dirname(fileURLToPath4(import.meta.url));
4417
+ for (const pkgPath of [path15.join(dir, "../package.json"), path15.join(dir, "../../package.json")]) {
4418
+ try {
4419
+ if (fs16.existsSync(pkgPath)) {
4420
+ const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf-8"));
4421
+ if (pkg.version) return pkg.version;
4422
+ }
4423
+ } catch {
4424
+ continue;
4425
+ }
4426
+ }
4427
+ return "0.0.0";
4428
+ }
4007
4429
  var program = new Command();
4008
4430
  var defaultHelp = new Help();
4009
4431
  var KO_ALIASES = {
@@ -4026,9 +4448,13 @@ var KO_ALIASES = {
4026
4448
  design: "\uB514\uC790\uC778",
4027
4449
  "design-palette": "\uD314\uB808\uD2B8",
4028
4450
  theme: "\uD14C\uB9C8",
4029
- ref: "\uB808\uD37C\uB7F0\uC2A4"
4451
+ ref: "\uB808\uD37C\uB7F0\uC2A4",
4452
+ harness: "\uD558\uB124\uC2A4",
4453
+ audit: "\uAC10\uC0AC",
4454
+ migrate: "\uC804\uD658",
4455
+ update: "\uC5C5\uB370\uC774\uD2B8"
4030
4456
  };
4031
- 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.8.0");
4457
+ 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(getVersion());
4032
4458
  program.configureHelp({
4033
4459
  formatHelp(cmd, helper) {
4034
4460
  if (cmd.parent) {
@@ -4110,6 +4536,18 @@ refCmd.command("list").alias("\uBAA9\uB85D").description("\uC800\uC7A5\uB41C \uB
4110
4536
  refCmd.command("open <index>").alias("\uC5F4\uAE30").description("\uB808\uD37C\uB7F0\uC2A4\uB97C \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uAE30").action(async (index) => {
4111
4537
  await refOpen(index);
4112
4538
  });
4539
+ program.command("harness").alias("\uD558\uB124\uC2A4").description("\uD1B5\uD569 \uD488\uC9C8 \uC810\uAC80 (lint + type-check + test + build)").action(async () => {
4540
+ await harness();
4541
+ });
4542
+ program.command("audit").alias("\uAC10\uC0AC").option("--fix", "\uC790\uB3D9 \uC218\uC815 \uC2DC\uB3C4").description("\uBCF4\uC548 \uCDE8\uC57D\uC810 \uAC10\uC0AC (npm audit \uB798\uD551)").action(async (opts) => {
4543
+ await audit(opts.fix);
4544
+ });
4545
+ program.command("migrate [target]").alias("\uC804\uD658").description("\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 (npm/yarn/pnpm)").action(async (target) => {
4546
+ await migrate(target);
4547
+ });
4548
+ program.command("update").alias("\uC5C5\uB370\uC774\uD2B8").description("VHK CLI \uCD5C\uC2E0 \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8").action(async () => {
4549
+ await update();
4550
+ });
4113
4551
  program.on("command:*", async (operands) => {
4114
4552
  const unknown = operands[0] ?? "";
4115
4553
  const rest = operands.slice(1);
@@ -4118,7 +4556,7 @@ program.on("command:*", async (operands) => {
4118
4556
  });
4119
4557
  program.action(async () => {
4120
4558
  console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
4121
- const { choice } = await inquirer12.prompt([{
4559
+ const { choice } = await inquirer14.prompt([{
4122
4560
  type: "list",
4123
4561
  name: "choice",
4124
4562
  message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
package/dist/mcp/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcpServer
4
- } from "../chunk-NQ4V3VN4.js";
4
+ } from "../chunk-UPXCLOBF.js";
5
5
 
6
6
  // src/mcp/index.ts
7
7
  startMcpServer().catch((err) => {
package/package.json CHANGED
@@ -1,65 +1,65 @@
1
- {
2
- "name": "@byh3071/vhk",
3
- "version": "0.8.0",
4
- "description": "Vibe Harness Kit β€” λ°”μ΄λΈŒμ½”λ”© 풀사이클 CLI",
5
- "bin": {
6
- "vhk": "dist/index.js",
7
- "vhk-mcp": "dist/mcp/index.js"
8
- },
9
- "type": "module",
10
- "scripts": {
11
- "dev": "tsx src/index.ts",
12
- "build": "tsup",
13
- "test": "vitest",
14
- "test:run": "vitest --run",
15
- "prepublishOnly": "pnpm build && pnpm test:run",
16
- "save": "vhk save",
17
- "check": "vhk check",
18
- "scan": "vhk secure scan",
19
- "recap": "vhk recap",
20
- "ship": "vhk ship",
21
- "doctor": "vhk doctor"
22
- },
23
- "files": [
24
- "dist",
25
- "README.md",
26
- "LICENSE"
27
- ],
28
- "keywords": [
29
- "vibe-coding",
30
- "harness",
31
- "cli",
32
- "scaffold",
33
- "session-log",
34
- "rules-sync"
35
- ],
36
- "author": "byh3071 <byh3071@gmail.com>",
37
- "license": "MIT",
38
- "repository": {
39
- "type": "git",
40
- "url": "git+https://github.com/byh3071-cpu/vhk.git"
41
- },
42
- "engines": {
43
- "node": ">=20"
44
- },
45
- "dependencies": {
46
- "@modelcontextprotocol/sdk": "^1.29.0",
47
- "@notionhq/client": "^5.22.0",
48
- "chalk": "^5.6.2",
49
- "commander": "^14.0.3",
50
- "handlebars": "^4.7.9",
51
- "inquirer": "^9.3.8",
52
- "ora": "^9.4.0",
53
- "simple-git": "^3.36.0",
54
- "zod": "^4.4.3"
55
- },
56
- "devDependencies": {
57
- "@types/inquirer": "^9.0.9",
58
- "@types/node": "^25.9.1",
59
- "ignore": "^7.0.5",
60
- "tsup": "^8.5.1",
61
- "tsx": "^4.22.3",
62
- "typescript": "^6.0.3",
63
- "vitest": "^4.1.7"
64
- }
65
- }
1
+ {
2
+ "name": "@byh3071/vhk",
3
+ "version": "0.9.1",
4
+ "description": "Vibe Harness Kit β€” λ°”μ΄λΈŒμ½”λ”© 풀사이클 CLI",
5
+ "bin": {
6
+ "vhk": "dist/index.js",
7
+ "vhk-mcp": "dist/mcp/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "dev": "tsx src/index.ts",
12
+ "build": "tsup",
13
+ "test": "vitest",
14
+ "test:run": "vitest --run",
15
+ "prepublishOnly": "pnpm build && pnpm test:run",
16
+ "save": "vhk save",
17
+ "check": "vhk check",
18
+ "scan": "vhk secure scan",
19
+ "recap": "vhk recap",
20
+ "ship": "vhk ship",
21
+ "doctor": "vhk doctor"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "keywords": [
29
+ "vibe-coding",
30
+ "harness",
31
+ "cli",
32
+ "scaffold",
33
+ "session-log",
34
+ "rules-sync"
35
+ ],
36
+ "author": "byh3071 <byh3071@gmail.com>",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/byh3071-cpu/vhk.git"
41
+ },
42
+ "engines": {
43
+ "node": ">=20"
44
+ },
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.29.0",
47
+ "@notionhq/client": "^5.22.0",
48
+ "chalk": "^5.6.2",
49
+ "commander": "^14.0.3",
50
+ "handlebars": "^4.7.9",
51
+ "inquirer": "^9.3.8",
52
+ "ora": "^9.4.0",
53
+ "simple-git": "^3.36.0",
54
+ "zod": "^4.4.3"
55
+ },
56
+ "devDependencies": {
57
+ "@types/inquirer": "^9.0.9",
58
+ "@types/node": "^25.9.1",
59
+ "ignore": "^7.0.5",
60
+ "tsup": "^8.5.1",
61
+ "tsx": "^4.22.3",
62
+ "typescript": "^6.0.3",
63
+ "vitest": "^4.1.7"
64
+ }
65
+ }