@byh3071/vhk 0.8.1 β†’ 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) {
@@ -457,8 +470,10 @@ function safeExecFile(cmd, args) {
457
470
  }).toString();
458
471
  return { ok: true, out: out.trim() };
459
472
  } catch (err) {
460
- const msg = err instanceof Error ? err.message : String(err);
461
- 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() };
462
477
  }
463
478
  }
464
479
  function safeExecFileStream(cmd, args) {
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  safeExecFileStream,
11
11
  startMcpServer,
12
12
  t
13
- } from "./chunk-X3CIIDO2.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({
@@ -472,10 +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
476
  import fs16 from "fs";
477
477
  import path15 from "path";
478
- import { fileURLToPath as fileURLToPath3 } from "url";
478
+ import { fileURLToPath as fileURLToPath4 } from "url";
479
479
 
480
480
  // src/lib/nlp-router.ts
481
481
  function normalize(input) {
@@ -543,6 +543,30 @@ var RULES = [
543
543
  confidence: "high",
544
544
  test: (t2) => /^레퍼런슀$|^ref$|레퍼런슀.*(보|λͺ©λ‘|확인|있|뭐)|μ°Έκ³ \s*(μ‚¬μ΄νŠΈ|λͺ©λ‘|링크)|reference.*list/.test(t2) && !/(add|μΆ”κ°€|open|μ—΄|https?:\/\/)/.test(t2)
545
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
+ },
546
570
  {
547
571
  command: "secure",
548
572
  explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548)",
@@ -709,6 +733,14 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
709
733
  "\uD14C\uB9C8",
710
734
  "ref",
711
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",
712
744
  "help"
713
745
  ]);
714
746
  function isOptionToken(token) {
@@ -732,8 +764,8 @@ function detectNaturalLanguageInput(argv) {
732
764
  }
733
765
 
734
766
  // src/lib/nlp-run.ts
735
- import chalk21 from "chalk";
736
- import inquirer11 from "inquirer";
767
+ import chalk25 from "chalk";
768
+ import inquirer13 from "inquirer";
737
769
 
738
770
  // src/commands/gate.ts
739
771
  import inquirer from "inquirer";
@@ -3933,6 +3965,367 @@ async function refOpen(indexStr) {
3933
3965
  }
3934
3966
  }
3935
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
+
3936
4329
  // src/lib/nlp-run.ts
3937
4330
  async function dispatchNlpRoute(route, input) {
3938
4331
  switch (route.command) {
@@ -3981,28 +4374,36 @@ async function dispatchNlpRoute(route, input) {
3981
4374
  return theme();
3982
4375
  case "ref":
3983
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();
3984
4385
  }
3985
4386
  }
3986
4387
  async function runNaturalLanguageRoute(input) {
3987
4388
  const route = routeNaturalLanguage(input);
3988
4389
  if (!route) {
3989
- console.log(chalk21.yellow(`
4390
+ console.log(chalk25.yellow(`
3990
4391
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
3991
4392
  `));
3992
4393
  return;
3993
4394
  }
3994
4395
  console.log("");
3995
- console.log(chalk21.cyan(` \u{1F4AC} "${input}"`));
3996
- console.log(chalk21.cyan(` \u2192 ${route.explanation}`));
4396
+ console.log(chalk25.cyan(` \u{1F4AC} "${input}"`));
4397
+ console.log(chalk25.cyan(` \u2192 ${route.explanation}`));
3997
4398
  if (route.confidence === "low") {
3998
- const { confirm } = await inquirer11.prompt([{
4399
+ const { confirm } = await inquirer13.prompt([{
3999
4400
  type: "confirm",
4000
4401
  name: "confirm",
4001
4402
  message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
4002
4403
  default: true
4003
4404
  }]);
4004
4405
  if (!confirm) {
4005
- console.log(chalk21.dim(` ${ko.nlp.menuHint}`));
4406
+ console.log(chalk25.dim(` ${ko.nlp.menuHint}`));
4006
4407
  return;
4007
4408
  }
4008
4409
  }
@@ -4012,7 +4413,7 @@ async function runNaturalLanguageRoute(input) {
4012
4413
 
4013
4414
  // src/index.ts
4014
4415
  function getVersion() {
4015
- const dir = path15.dirname(fileURLToPath3(import.meta.url));
4416
+ const dir = path15.dirname(fileURLToPath4(import.meta.url));
4016
4417
  for (const pkgPath of [path15.join(dir, "../package.json"), path15.join(dir, "../../package.json")]) {
4017
4418
  try {
4018
4419
  if (fs16.existsSync(pkgPath)) {
@@ -4047,7 +4448,11 @@ var KO_ALIASES = {
4047
4448
  design: "\uB514\uC790\uC778",
4048
4449
  "design-palette": "\uD314\uB808\uD2B8",
4049
4450
  theme: "\uD14C\uB9C8",
4050
- 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"
4051
4456
  };
4052
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());
4053
4458
  program.configureHelp({
@@ -4131,6 +4536,18 @@ refCmd.command("list").alias("\uBAA9\uB85D").description("\uC800\uC7A5\uB41C \uB
4131
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) => {
4132
4537
  await refOpen(index);
4133
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
+ });
4134
4551
  program.on("command:*", async (operands) => {
4135
4552
  const unknown = operands[0] ?? "";
4136
4553
  const rest = operands.slice(1);
@@ -4139,7 +4556,7 @@ program.on("command:*", async (operands) => {
4139
4556
  });
4140
4557
  program.action(async () => {
4141
4558
  console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
4142
- const { choice } = await inquirer12.prompt([{
4559
+ const { choice } = await inquirer14.prompt([{
4143
4560
  type: "list",
4144
4561
  name: "choice",
4145
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-X3CIIDO2.js";
4
+ } from "../chunk-UPXCLOBF.js";
5
5
 
6
6
  // src/mcp/index.ts
7
7
  startMcpServer().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byh3071/vhk",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "description": "Vibe Harness Kit β€” λ°”μ΄λΈŒμ½”λ”© 풀사이클 CLI",
5
5
  "bin": {
6
6
  "vhk": "dist/index.js",