@byh3071/vhk 0.8.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  safeExecFileStream,
11
11
  startMcpServer,
12
12
  t
13
- } from "./chunk-X3CIIDO2.js";
13
+ } from "./chunk-7NGBIIA3.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";
476
- import fs16 from "fs";
475
+ import inquirer14 from "inquirer";
476
+ import fs15 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) {
@@ -511,7 +511,7 @@ var RULES = [
511
511
  command: "init",
512
512
  explanation: "\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791)",
513
513
  confidence: "high",
514
- test: (t2) => (/프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|하네스|초기화/.test(t2) || /^시작$/.test(t2)) && !/디자인|design|팔레트|palette|테마|theme|레퍼런스|reference|다크\s*모드|라이트\s*모드|색상\s*모드/.test(t2)
514
+ test: (t2) => (/프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|하네스|초기화/.test(t2) || /^시작$/.test(t2)) && !/디자인|design|팔레트|palette|테마|theme|레퍼런스|reference|다크\s*모드|라이트\s*모드|색상\s*모드|브리핑|brief|컨텍스트|context|맥락|기억|memory/.test(t2)
515
515
  },
516
516
  {
517
517
  command: "mcp-init",
@@ -543,6 +543,54 @@ 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
+ },
570
+ {
571
+ command: "context-show",
572
+ explanation: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uBCF4\uAE30 (vhk context-show)",
573
+ confidence: "high",
574
+ test: (t2) => /맥락\s*(보|확인|보여)|컨텍스트\s*(보|확인|보여)|context\s*show/.test(t2)
575
+ },
576
+ {
577
+ command: "context",
578
+ explanation: "\uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D \uC0DD\uC131 (vhk context)",
579
+ confidence: "high",
580
+ test: (t2) => /(^맥락$|^컨텍스트$|^context$|맥락\s*(만들|생성|갱신|업데이트)|컨텍스트\s*(만들|생성|갱신|업데이트)|프로젝트\s*맥락|프로젝트\s*정보\s*생성)/.test(t2) && !/보|확인|보여|show/.test(t2)
581
+ },
582
+ {
583
+ command: "memory",
584
+ explanation: "\uAE30\uC5B5 \uBAA9\uB85D \uC870\uD68C (vhk memory list)",
585
+ confidence: "high",
586
+ test: (t2) => /^기억$|기억\s*(목록|보|확인|뭐)|memory.*list|결정사항\s*(목록|확인|보여)/.test(t2) && !/(추가|add|삭제|remove|저장|기록해)/.test(t2)
587
+ },
588
+ {
589
+ command: "brief",
590
+ explanation: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC694\uC57D (vhk brief)",
591
+ confidence: "high",
592
+ test: (t2) => /브리핑|brief|상태\s*요약|프로젝트\s*요약|요약\s*(보고|리포트|보여|만들)|보고서\s*(만들|생성|보여)/.test(t2)
593
+ },
546
594
  {
547
595
  command: "secure",
548
596
  explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548)",
@@ -709,6 +757,22 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
709
757
  "\uD14C\uB9C8",
710
758
  "ref",
711
759
  "\uB808\uD37C\uB7F0\uC2A4",
760
+ "harness",
761
+ "\uD558\uB124\uC2A4",
762
+ "audit",
763
+ "\uAC10\uC0AC",
764
+ "migrate",
765
+ "\uC804\uD658",
766
+ "update",
767
+ "\uC5C5\uB370\uC774\uD2B8",
768
+ "context",
769
+ "\uB9E5\uB77D",
770
+ "context-show",
771
+ "\uB9E5\uB77D\uBCF4\uAE30",
772
+ "memory",
773
+ "\uAE30\uC5B5",
774
+ "brief",
775
+ "\uBE0C\uB9AC\uD551",
712
776
  "help"
713
777
  ]);
714
778
  function isOptionToken(token) {
@@ -732,8 +796,8 @@ function detectNaturalLanguageInput(argv) {
732
796
  }
733
797
 
734
798
  // src/lib/nlp-run.ts
735
- import chalk21 from "chalk";
736
- import inquirer11 from "inquirer";
799
+ import chalk28 from "chalk";
800
+ import inquirer13 from "inquirer";
737
801
 
738
802
  // src/commands/gate.ts
739
803
  import inquirer from "inquirer";
@@ -1658,7 +1722,7 @@ function nextAdrNumber(adrDir) {
1658
1722
  function slugify(title) {
1659
1723
  return title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9가-힣-]/g, "").slice(0, 40) || "decision";
1660
1724
  }
1661
- function createAdrFile(cwd, title, context, decision, consequences) {
1725
+ function createAdrFile(cwd, title, context2, decision, consequences) {
1662
1726
  const adrDir = path5.join(cwd, "docs", "adr");
1663
1727
  if (!fs4.existsSync(adrDir)) fs4.mkdirSync(adrDir, { recursive: true });
1664
1728
  const num = nextAdrNumber(adrDir);
@@ -1676,7 +1740,7 @@ function createAdrFile(cwd, title, context, decision, consequences) {
1676
1740
  `# ADR-${String(num).padStart(3, "0")}: ${title}`,
1677
1741
  "",
1678
1742
  "## \uB9E5\uB77D (Context)",
1679
- context,
1743
+ context2,
1680
1744
  "",
1681
1745
  "## \uACB0\uC815 (Decision)",
1682
1746
  decision,
@@ -3104,17 +3168,17 @@ ${t("undo.recentHeader")}`));
3104
3168
 
3105
3169
  // src/commands/status.ts
3106
3170
  import { execFileSync as execFileSync4 } from "child_process";
3107
- import fs15 from "fs";
3171
+ import fs14 from "fs";
3108
3172
  import path14 from "path";
3109
3173
  import chalk13 from "chalk";
3110
3174
 
3111
3175
  // src/lib/read-json.ts
3112
- import fs14 from "fs";
3176
+ import { readFileSync } from "fs";
3113
3177
  function stripBom(text) {
3114
3178
  return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
3115
3179
  }
3116
3180
  function readJsonFile(filePath) {
3117
- const raw = stripBom(fs14.readFileSync(filePath, "utf-8"));
3181
+ const raw = stripBom(readFileSync(filePath, "utf-8"));
3118
3182
  return JSON.parse(raw);
3119
3183
  }
3120
3184
 
@@ -3157,7 +3221,7 @@ function parseRecentCommitLines(logOutput) {
3157
3221
  }
3158
3222
  function readProjectPackage(cwd = process.cwd()) {
3159
3223
  const pkgPath = path14.join(cwd, "package.json");
3160
- if (!fs15.existsSync(pkgPath)) return null;
3224
+ if (!fs14.existsSync(pkgPath)) return null;
3161
3225
  try {
3162
3226
  const pkg = readJsonFile(pkgPath);
3163
3227
  if (!pkg.name && !pkg.version) return null;
@@ -3326,7 +3390,7 @@ ${t("diff.summaryHeader")}`));
3326
3390
  }
3327
3391
 
3328
3392
  // src/commands/mcp-init.ts
3329
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3393
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
3330
3394
  import { join } from "path";
3331
3395
  import { fileURLToPath as fileURLToPath2 } from "url";
3332
3396
  import chalk15 from "chalk";
@@ -3334,7 +3398,7 @@ function resolveVhkMcpPath() {
3334
3398
  try {
3335
3399
  const pkgPath = join(process.cwd(), "package.json");
3336
3400
  if (existsSync(pkgPath)) {
3337
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
3401
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
3338
3402
  if (pkg.name === "@byh3071/vhk") {
3339
3403
  return join(process.cwd(), "dist", "mcp", "index.js");
3340
3404
  }
@@ -3363,7 +3427,7 @@ async function mcpInit() {
3363
3427
  let config;
3364
3428
  if (existsSync(configPath)) {
3365
3429
  try {
3366
- const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
3430
+ const parsed = JSON.parse(readFileSync2(configPath, "utf-8"));
3367
3431
  config = {
3368
3432
  mcpServers: { ...parsed.mcpServers ?? {}, vhk: vhkEntry }
3369
3433
  };
@@ -3486,7 +3550,7 @@ ${t("deploy.deploying")}
3486
3550
  }
3487
3551
 
3488
3552
  // src/commands/publish.ts
3489
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
3553
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3490
3554
  import chalk17 from "chalk";
3491
3555
  import inquirer8 from "inquirer";
3492
3556
  import ora2 from "ora";
@@ -3510,7 +3574,7 @@ async function publish() {
3510
3574
  }
3511
3575
  let pkg;
3512
3576
  try {
3513
- pkg = JSON.parse(readFileSync2("package.json", "utf-8"));
3577
+ pkg = JSON.parse(readFileSync3("package.json", "utf-8"));
3514
3578
  } catch {
3515
3579
  console.log(chalk17.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
3516
3580
  return;
@@ -3835,13 +3899,13 @@ async function theme() {
3835
3899
  }
3836
3900
 
3837
3901
  // src/commands/ref.ts
3838
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "fs";
3902
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
3839
3903
  import chalk20 from "chalk";
3840
3904
  var REFS_PATH = ".vhk/refs.json";
3841
3905
  function loadRefs() {
3842
3906
  if (!existsSync6(REFS_PATH)) return [];
3843
3907
  try {
3844
- const raw = readFileSync3(REFS_PATH, "utf-8");
3908
+ const raw = readFileSync4(REFS_PATH, "utf-8");
3845
3909
  const parsed = JSON.parse(raw);
3846
3910
  return Array.isArray(parsed) ? parsed : [];
3847
3911
  } catch {
@@ -3933,6 +3997,730 @@ async function refOpen(indexStr) {
3933
3997
  }
3934
3998
  }
3935
3999
 
4000
+ // src/commands/harness.ts
4001
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
4002
+ import chalk21 from "chalk";
4003
+ import ora3 from "ora";
4004
+ function detectPM() {
4005
+ if (existsSync7("pnpm-lock.yaml")) return "pnpm";
4006
+ if (existsSync7("yarn.lock")) return "yarn";
4007
+ return "npm";
4008
+ }
4009
+ function pmRun(pm, script) {
4010
+ return pm === "npm" ? ["run", script] : [script];
4011
+ }
4012
+ function detectChecks() {
4013
+ const checks = [];
4014
+ let pkg = {};
4015
+ try {
4016
+ pkg = JSON.parse(readFileSync5("package.json", "utf-8"));
4017
+ } catch {
4018
+ return checks;
4019
+ }
4020
+ const s = pkg.scripts ?? {};
4021
+ const pm = detectPM();
4022
+ if (s.lint) {
4023
+ checks.push({ name: "lint", bin: pm, args: pmRun(pm, "lint") });
4024
+ } else if (existsSync7(".eslintrc.js") || existsSync7(".eslintrc.json") || existsSync7("eslint.config.js")) {
4025
+ checks.push({ name: "lint", bin: "npx", args: ["eslint", ".", "--ext", ".ts,.tsx"] });
4026
+ }
4027
+ if (s["type-check"]) {
4028
+ checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "type-check") });
4029
+ } else if (s.typecheck) {
4030
+ checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "typecheck") });
4031
+ } else if (existsSync7("tsconfig.json")) {
4032
+ checks.push({ name: "type-check", bin: "npx", args: ["tsc", "--noEmit"] });
4033
+ }
4034
+ if (s.test) {
4035
+ checks.push({ name: "test", bin: pm, args: pmRun(pm, "test") });
4036
+ }
4037
+ if (s.build) {
4038
+ checks.push({ name: "build", bin: pm, args: pmRun(pm, "build") });
4039
+ }
4040
+ return checks;
4041
+ }
4042
+ async function harness() {
4043
+ console.log(chalk21.bold("\n\u{1F527} " + t("harness.title")));
4044
+ console.log(chalk21.gray("\u2500".repeat(40)));
4045
+ const checks = detectChecks();
4046
+ if (checks.length === 0) {
4047
+ console.log(chalk21.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4048
+ console.log(chalk21.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
4049
+ return;
4050
+ }
4051
+ console.log(chalk21.cyan(`
4052
+ \u{1F3C3} ${checks.length}\uAC1C \uC810\uAC80 \uC2DC\uC791:
4053
+ `));
4054
+ const results = [];
4055
+ for (const check2 of checks) {
4056
+ const display = `${check2.bin} ${check2.args.join(" ")}`;
4057
+ const spinner = ora3(`${check2.name} \uC2E4\uD589 \uC911...`).start();
4058
+ const start = Date.now();
4059
+ const result = safeExecFile(check2.bin, check2.args);
4060
+ const duration = Date.now() - start;
4061
+ const sec = (duration / 1e3).toFixed(1);
4062
+ if (result.ok) {
4063
+ spinner.succeed(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
4064
+ results.push({ name: check2.name, command: display, passed: true, duration });
4065
+ } else {
4066
+ spinner.fail(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
4067
+ results.push({
4068
+ name: check2.name,
4069
+ command: display,
4070
+ passed: false,
4071
+ duration,
4072
+ error: result.err.slice(0, 200)
4073
+ });
4074
+ }
4075
+ }
4076
+ console.log(chalk21.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
4077
+ console.log(chalk21.gray("\u2500".repeat(40)));
4078
+ for (const r of results) {
4079
+ const icon = r.passed ? chalk21.green("\u2705") : chalk21.red("\u274C");
4080
+ const sec = (r.duration / 1e3).toFixed(1);
4081
+ console.log(` ${icon} ${r.name.padEnd(15)} ${chalk21.gray(`${sec}s`)}`);
4082
+ }
4083
+ const passed = results.filter((r) => r.passed).length;
4084
+ const all = passed === results.length;
4085
+ console.log(chalk21.gray("\u2500".repeat(40)));
4086
+ if (all) {
4087
+ console.log(chalk21.green.bold(`
4088
+ \u{1F389} \uC804\uCCB4 \uD1B5\uACFC! (${passed}/${results.length})`));
4089
+ } else {
4090
+ console.log(
4091
+ chalk21.red.bold(`
4092
+ \u26A0\uFE0F ${results.length - passed}\uAC1C \uC2E4\uD328 (${passed}/${results.length} \uD1B5\uACFC)`)
4093
+ );
4094
+ }
4095
+ printNextStep({
4096
+ message: all ? "\uD488\uC9C8 \uC810\uAC80 \uD1B5\uACFC!" : "\uC2E4\uD328 \uD56D\uBAA9\uC744 \uC218\uC815\uD558\uC138\uC694.",
4097
+ command: all ? "vhk ship" : "vhk doctor",
4098
+ cursorHint: all ? "\uBC30\uD3EC\uD574\uC918" : "\uBB38\uC81C \uC9C4\uB2E8\uD574\uC918"
4099
+ });
4100
+ }
4101
+
4102
+ // src/commands/audit.ts
4103
+ import { existsSync as existsSync8 } from "fs";
4104
+ import chalk22 from "chalk";
4105
+ import inquirer11 from "inquirer";
4106
+ import ora4 from "ora";
4107
+ function detectCurrentPM() {
4108
+ if (existsSync8("pnpm-lock.yaml")) return "pnpm";
4109
+ if (existsSync8("yarn.lock")) return "yarn";
4110
+ return "npm";
4111
+ }
4112
+ function parseAuditOutput(output, pm) {
4113
+ const empty = { critical: 0, high: 0, moderate: 0, low: 0, total: 0 };
4114
+ if (!output) return empty;
4115
+ try {
4116
+ const json = JSON.parse(output);
4117
+ const meta = json.metadata?.vulnerabilities;
4118
+ if (meta) {
4119
+ const summary = {
4120
+ critical: meta.critical ?? 0,
4121
+ high: meta.high ?? 0,
4122
+ moderate: meta.moderate ?? 0,
4123
+ low: meta.low ?? 0,
4124
+ total: meta.total ?? 0
4125
+ };
4126
+ if (!summary.total) {
4127
+ summary.total = summary.critical + summary.high + summary.moderate + summary.low;
4128
+ }
4129
+ return summary;
4130
+ }
4131
+ void pm;
4132
+ return empty;
4133
+ } catch {
4134
+ return empty;
4135
+ }
4136
+ }
4137
+ function runAuditJson(pm) {
4138
+ const result = safeExecFile(pm, ["audit", "--json"]);
4139
+ return result.out;
4140
+ }
4141
+ function runAuditFix(pm) {
4142
+ if (pm !== "npm") {
4143
+ 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.` };
4144
+ }
4145
+ const result = safeExecFile("npm", ["audit", "fix"]);
4146
+ return result.ok ? { ok: true } : { ok: false, err: result.err };
4147
+ }
4148
+ async function audit(autoFix = false) {
4149
+ console.log(chalk22.bold("\n\u{1F6E1}\uFE0F " + t("audit.title")));
4150
+ console.log(chalk22.gray("\u2500".repeat(40)));
4151
+ const pm = detectCurrentPM();
4152
+ console.log(chalk22.cyan(`\u{1F4E6} \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${pm}`));
4153
+ const spinner = ora4("\uBCF4\uC548 \uAC10\uC0AC \uC2E4\uD589 \uC911...").start();
4154
+ const output = runAuditJson(pm);
4155
+ spinner.stop();
4156
+ const summary = parseAuditOutput(output, pm);
4157
+ if (summary.total === 0) {
4158
+ console.log(chalk22.green.bold("\n\u{1F389} \uCDE8\uC57D\uC810\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4!"));
4159
+ return;
4160
+ }
4161
+ console.log(chalk22.bold("\n\u{1F4CA} \uCDE8\uC57D\uC810 \uC694\uC57D:"));
4162
+ if (summary.critical > 0) console.log(chalk22.red(` \u{1F534} Critical: ${summary.critical}`));
4163
+ if (summary.high > 0) console.log(chalk22.red(` \u{1F7E0} High: ${summary.high}`));
4164
+ if (summary.moderate > 0) console.log(chalk22.yellow(` \u{1F7E1} Moderate: ${summary.moderate}`));
4165
+ if (summary.low > 0) console.log(chalk22.gray(` \u26AA Low: ${summary.low}`));
4166
+ console.log(chalk22.bold(`
4167
+ \uCD1D ${summary.total}\uAC1C\uC758 \uCDE8\uC57D\uC810`));
4168
+ const shouldRunFix = autoFix ? true : summary.critical > 0 || summary.high > 0 ? (await inquirer11.prompt([
4169
+ {
4170
+ type: "confirm",
4171
+ name: "shouldFix",
4172
+ message: "\uC790\uB3D9 \uC218\uC815\uC744 \uC2DC\uB3C4\uD560\uAE4C\uC694? (npm audit fix)",
4173
+ default: true
4174
+ }
4175
+ ])).shouldFix : false;
4176
+ if (shouldRunFix) {
4177
+ const fixSpinner = ora4("\uC790\uB3D9 \uC218\uC815 \uC911...").start();
4178
+ const result = runAuditFix(pm);
4179
+ if (result.ok) {
4180
+ fixSpinner.succeed("\uC790\uB3D9 \uC218\uC815 \uC644\uB8CC!");
4181
+ } else {
4182
+ fixSpinner.warn(result.err ?? "\uC77C\uBD80 \uCDE8\uC57D\uC810\uC740 \uC218\uB3D9 \uC218\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
4183
+ }
4184
+ }
4185
+ printNextStep({
4186
+ message: "\uBCF4\uC548 \uAC10\uC0AC \uC644\uB8CC.",
4187
+ command: "vhk harness",
4188
+ cursorHint: "\uD488\uC9C8 \uC810\uAC80\uD574\uC918"
4189
+ });
4190
+ }
4191
+
4192
+ // src/commands/migrate.ts
4193
+ import { existsSync as existsSync9, unlinkSync, rmSync } from "fs";
4194
+ import chalk23 from "chalk";
4195
+ import inquirer12 from "inquirer";
4196
+ import ora5 from "ora";
4197
+ var LOCK_FILES = {
4198
+ npm: "package-lock.json",
4199
+ yarn: "yarn.lock",
4200
+ pnpm: "pnpm-lock.yaml"
4201
+ };
4202
+ function detectCurrentPM2() {
4203
+ if (existsSync9("pnpm-lock.yaml")) return "pnpm";
4204
+ if (existsSync9("yarn.lock")) return "yarn";
4205
+ if (existsSync9("package-lock.json")) return "npm";
4206
+ return null;
4207
+ }
4208
+ function isCLIAvailable2(pm) {
4209
+ return safeExecFile(pm, ["--version"]).ok;
4210
+ }
4211
+ async function migrate(target) {
4212
+ console.log(chalk23.bold("\n\u{1F504} " + t("migrate.title")));
4213
+ console.log(chalk23.gray("\u2500".repeat(40)));
4214
+ const current = detectCurrentPM2();
4215
+ console.log(chalk23.cyan(`
4216
+ \uD604\uC7AC \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00"}`));
4217
+ let targetPM;
4218
+ if (target && ["npm", "yarn", "pnpm"].includes(target)) {
4219
+ targetPM = target;
4220
+ } else {
4221
+ const choices = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current).map((pm) => ({ name: pm, value: pm }));
4222
+ const { selected } = await inquirer12.prompt([
4223
+ {
4224
+ type: "list",
4225
+ name: "selected",
4226
+ message: t("migrate.selectTarget"),
4227
+ choices
4228
+ }
4229
+ ]);
4230
+ targetPM = selected;
4231
+ }
4232
+ if (targetPM === current) {
4233
+ console.log(chalk23.yellow(`
4234
+ \u26A0\uFE0F \uC774\uBBF8 ${targetPM}\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4.`));
4235
+ return;
4236
+ }
4237
+ if (!isCLIAvailable2(targetPM)) {
4238
+ console.log(chalk23.red(`
4239
+ \u274C ${targetPM}\uC774 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
4240
+ console.log(chalk23.yellow(` npm i -g ${targetPM}`));
4241
+ return;
4242
+ }
4243
+ const { confirm } = await inquirer12.prompt([
4244
+ {
4245
+ type: "confirm",
4246
+ name: "confirm",
4247
+ message: `${current ?? "\uD604\uC7AC"} \u2192 ${targetPM}\uC73C\uB85C \uC804\uD658\uD560\uAE4C\uC694? (node_modules \uC7AC\uC124\uCE58)`,
4248
+ default: true
4249
+ }
4250
+ ]);
4251
+ if (!confirm) {
4252
+ console.log(chalk23.gray("\uCDE8\uC18C\uB428"));
4253
+ return;
4254
+ }
4255
+ const cleanup = ora5("\uAE30\uC874 lock \uD30C\uC77C \uC815\uB9AC \uC911...").start();
4256
+ for (const lockFile of Object.values(LOCK_FILES)) {
4257
+ if (existsSync9(lockFile)) {
4258
+ unlinkSync(lockFile);
4259
+ }
4260
+ }
4261
+ if (existsSync9("node_modules")) {
4262
+ cleanup.text = "node_modules \uC0AD\uC81C \uC911...";
4263
+ rmSync("node_modules", { recursive: true, force: true });
4264
+ }
4265
+ cleanup.succeed("\uAE30\uC874 \uD30C\uC77C \uC815\uB9AC \uC644\uB8CC");
4266
+ const install = ora5(`${targetPM} install \uC2E4\uD589 \uC911...`).start();
4267
+ const installResult = safeExecFile(targetPM, ["install"]);
4268
+ if (installResult.ok) {
4269
+ install.succeed(`${targetPM} install \uC644\uB8CC!`);
4270
+ } else {
4271
+ install.fail(`${targetPM} install \uC2E4\uD328`);
4272
+ console.log(chalk23.red(installResult.err.slice(0, 300)));
4273
+ return;
4274
+ }
4275
+ console.log(chalk23.green.bold(`
4276
+ \u{1F389} ${current ?? "\uC774\uC804"} \u2192 ${targetPM} \uC804\uD658 \uC644\uB8CC!`));
4277
+ printNextStep({
4278
+ message: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 \uC644\uB8CC!",
4279
+ command: "vhk harness",
4280
+ cursorHint: "\uD488\uC9C8 \uC810\uAC80\uD574\uC918"
4281
+ });
4282
+ }
4283
+
4284
+ // src/commands/update.ts
4285
+ import { execSync as execSync3 } from "child_process";
4286
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
4287
+ import { dirname, join as join2 } from "path";
4288
+ import { fileURLToPath as fileURLToPath3 } from "url";
4289
+ import chalk24 from "chalk";
4290
+ import ora6 from "ora";
4291
+ var PACKAGE = "@byh3071/vhk";
4292
+ function getCurrentVersion() {
4293
+ const dir = dirname(fileURLToPath3(import.meta.url));
4294
+ for (const pkgPath of [join2(dir, "../package.json"), join2(dir, "../../package.json")]) {
4295
+ try {
4296
+ if (existsSync10(pkgPath)) {
4297
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
4298
+ if (pkg.version) return pkg.version;
4299
+ }
4300
+ } catch {
4301
+ continue;
4302
+ }
4303
+ }
4304
+ return "0.0.0";
4305
+ }
4306
+ function getLatestVersion() {
4307
+ try {
4308
+ const out = execSync3(`npm view ${PACKAGE} version`, {
4309
+ encoding: "utf-8",
4310
+ stdio: ["pipe", "pipe", "pipe"]
4311
+ }).toString();
4312
+ return out.trim();
4313
+ } catch {
4314
+ return null;
4315
+ }
4316
+ }
4317
+ function isUpToDate(current, latest) {
4318
+ const parse = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
4319
+ const [ca, cb, cc] = parse(current);
4320
+ const [la, lb, lc] = parse(latest);
4321
+ if (ca !== la) return ca > la;
4322
+ if (cb !== lb) return cb > lb;
4323
+ return cc >= lc;
4324
+ }
4325
+ async function update() {
4326
+ console.log(chalk24.bold("\n\u2B06\uFE0F " + t("update.title")));
4327
+ console.log(chalk24.gray("\u2500".repeat(40)));
4328
+ const current = getCurrentVersion();
4329
+ console.log(chalk24.cyan(`
4330
+ \u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${current}`));
4331
+ const spinner = ora6("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...").start();
4332
+ const latest = getLatestVersion();
4333
+ if (!latest) {
4334
+ spinner.fail("\uCD5C\uC2E0 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4335
+ 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:"));
4336
+ console.log(chalk24.gray(` npm update -g ${PACKAGE}`));
4337
+ return;
4338
+ }
4339
+ spinner.stop();
4340
+ console.log(chalk24.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
4341
+ if (isUpToDate(current, latest)) {
4342
+ console.log(chalk24.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
4343
+ return;
4344
+ }
4345
+ const updateSpinner = ora6(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC911...`).start();
4346
+ try {
4347
+ execSync3(`npm update -g ${PACKAGE}`, { stdio: ["pipe", "pipe", "pipe"] });
4348
+ updateSpinner.succeed(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`);
4349
+ console.log(chalk24.green.bold(`
4350
+ \u{1F389} VHK CLI v${latest} \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
4351
+ console.log(chalk24.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
4352
+ } catch (err) {
4353
+ updateSpinner.fail("\uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
4354
+ const msg = err instanceof Error ? err.message.slice(0, 300) : String(err);
4355
+ console.log(chalk24.red(msg));
4356
+ console.log(chalk24.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4357
+ console.log(chalk24.gray(` npm update -g ${PACKAGE}`));
4358
+ }
4359
+ }
4360
+
4361
+ // src/commands/context.ts
4362
+ import {
4363
+ existsSync as existsSync11,
4364
+ mkdirSync as mkdirSync5,
4365
+ readFileSync as readFileSync7,
4366
+ readdirSync,
4367
+ statSync,
4368
+ writeFileSync as writeFileSync6
4369
+ } from "fs";
4370
+ import { join as join3 } from "path";
4371
+ import chalk25 from "chalk";
4372
+ var CONTEXT_PATH = ".vhk/context.md";
4373
+ var IGNORE_DIRS2 = /* @__PURE__ */ new Set([
4374
+ "node_modules",
4375
+ ".git",
4376
+ "dist",
4377
+ ".next",
4378
+ ".nuxt",
4379
+ ".output",
4380
+ "coverage",
4381
+ ".cache",
4382
+ ".turbo",
4383
+ ".vhk"
4384
+ ]);
4385
+ function buildTree(dir, prefix = "", maxDepth = 3, depth = 0) {
4386
+ if (depth >= maxDepth) return [];
4387
+ const lines = [];
4388
+ try {
4389
+ const entries = readdirSync(dir);
4390
+ const filtered = entries.filter(
4391
+ (e) => (!e.startsWith(".") || e === ".env.example") && !IGNORE_DIRS2.has(e)
4392
+ );
4393
+ filtered.forEach((entry, index) => {
4394
+ const isLast = index === filtered.length - 1;
4395
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4396
+ const fullPath = join3(dir, entry);
4397
+ const stat = statSync(fullPath);
4398
+ const isDir = stat.isDirectory();
4399
+ lines.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
4400
+ if (isDir) {
4401
+ const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
4402
+ lines.push(...buildTree(fullPath, nextPrefix, maxDepth, depth + 1));
4403
+ }
4404
+ });
4405
+ } catch {
4406
+ }
4407
+ return lines;
4408
+ }
4409
+ function extractTechStack() {
4410
+ const stack = {};
4411
+ try {
4412
+ const pkg = readJsonFile("package.json");
4413
+ const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
4414
+ if (all.next) stack["\uD504\uB808\uC784\uC6CC\uD06C"] = `Next.js ${all.next}`;
4415
+ else if (all.nuxt) stack["\uD504\uB808\uC784\uC6CC\uD06C"] = `Nuxt ${all.nuxt}`;
4416
+ else if (all.react) stack["\uD504\uB808\uC784\uC6CC\uD06C"] = `React ${all.react}`;
4417
+ else if (all.vue) stack["\uD504\uB808\uC784\uC6CC\uD06C"] = `Vue ${all.vue}`;
4418
+ else if (all.svelte) stack["\uD504\uB808\uC784\uC6CC\uD06C"] = `Svelte ${all.svelte}`;
4419
+ if (all.typescript) stack["\uC5B8\uC5B4"] = `TypeScript ${all.typescript}`;
4420
+ if (all.tailwindcss) stack["\uC2A4\uD0C0\uC77C"] = `Tailwind CSS ${all.tailwindcss}`;
4421
+ if (all.tsup) stack["\uBE4C\uB4DC"] = "tsup";
4422
+ else if (all.vite) stack["\uBE4C\uB4DC"] = `Vite ${all.vite}`;
4423
+ else if (all.webpack) stack["\uBE4C\uB4DC"] = "webpack";
4424
+ if (all.vitest) stack["\uD14C\uC2A4\uD2B8"] = "vitest";
4425
+ else if (all.jest) stack["\uD14C\uC2A4\uD2B8"] = "jest";
4426
+ if (all.commander) stack["CLI"] = "commander";
4427
+ if (all.inquirer) stack["\uC778\uD130\uB799\uD2F0\uBE0C"] = "inquirer";
4428
+ if (existsSync11("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
4429
+ else if (existsSync11("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
4430
+ else stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "npm";
4431
+ if (pkg.name) stack["\uD328\uD0A4\uC9C0 \uC774\uB984"] = pkg.name;
4432
+ if (pkg.version) stack["\uBC84\uC804"] = pkg.version;
4433
+ } catch {
4434
+ }
4435
+ return stack;
4436
+ }
4437
+ function getVhkCommands() {
4438
+ return [
4439
+ "gate \u2014 \uC544\uC774\uB514\uC5B4 \uAC80\uC99D",
4440
+ "init \u2014 \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654",
4441
+ "recap \u2014 \uC138\uC158 \uC694\uC57D \uC800\uC7A5",
4442
+ "sync \u2014 \uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654",
4443
+ "check \u2014 \uADDC\uCE59 \uC810\uAC80",
4444
+ "secure \u2014 \uBCF4\uC548 \uC2A4\uCE94",
4445
+ "ship \u2014 \uBC30\uD3EC \uCCB4\uD06C + \uD68C\uACE0",
4446
+ "doctor \u2014 \uD658\uACBD \uC9C4\uB2E8",
4447
+ "save \u2014 git \uC800\uC7A5 (add+commit+push)",
4448
+ "undo \u2014 \uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30",
4449
+ "status \u2014 git \uC0C1\uD0DC \uD655\uC778",
4450
+ "diff \u2014 git \uBCC0\uACBD \uC0AC\uD56D \uC694\uC57D",
4451
+ "deploy \u2014 \uD504\uB85C\uB355\uC158 \uBC30\uD3EC",
4452
+ "env \u2014 \uD658\uACBD\uBCC0\uC218 \uAD00\uB9AC",
4453
+ "publish \u2014 npm \uBC30\uD3EC \uC790\uB3D9\uD654",
4454
+ "design \u2014 \uB514\uC790\uC778 \uD1A0\uD070 \uC0DD\uC131",
4455
+ "design-palette \u2014 \uCEEC\uB7EC \uD314\uB808\uD2B8 \uC120\uD0DD",
4456
+ "theme \u2014 \uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC",
4457
+ "ref add|list|open \u2014 \uB808\uD37C\uB7F0\uC2A4 URL \uAD00\uB9AC",
4458
+ "harness \u2014 \uD1B5\uD569 \uD488\uC9C8 \uC810\uAC80",
4459
+ "audit \u2014 \uBCF4\uC548 \uCDE8\uC57D\uC810 \uAC10\uC0AC",
4460
+ "migrate \u2014 \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658",
4461
+ "update \u2014 VHK CLI \uC140\uD504 \uC5C5\uB370\uC774\uD2B8",
4462
+ "context \u2014 \uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D \uC0DD\uC131",
4463
+ "context-show \u2014 \uB9E5\uB77D \uD30C\uC77C \uBCF4\uAE30",
4464
+ "memory add|list|remove \u2014 \uACB0\uC815\uC0AC\uD56D \uAE30\uC5B5",
4465
+ "brief \u2014 \uD504\uB85C\uC81D\uD2B8 \uC694\uC57D \uBCF4\uACE0\uC11C",
4466
+ "mcp \u2014 MCP \uC11C\uBC84 \uC2DC\uC791",
4467
+ "mcp-init \u2014 Cursor MCP \uC124\uC815"
4468
+ ];
4469
+ }
4470
+ async function context() {
4471
+ console.log(chalk25.bold("\n\u{1F9E0} " + t("context.title")));
4472
+ console.log(chalk25.gray("\u2500".repeat(40)));
4473
+ const stack = extractTechStack();
4474
+ const tree = buildTree(".").join("\n");
4475
+ const commands = getVhkCommands();
4476
+ const lines = [];
4477
+ lines.push("# \uD504\uB85C\uC81D\uD2B8 \uCEE8\uD14D\uC2A4\uD2B8");
4478
+ lines.push("");
4479
+ lines.push("> \uC774 \uD30C\uC77C\uC740 `vhk context`\uB85C \uC790\uB3D9 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
4480
+ lines.push("> AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D\uC744 \uC81C\uACF5\uD569\uB2C8\uB2E4.");
4481
+ lines.push("");
4482
+ lines.push("## \uAE30\uC220 \uC2A4\uD0DD");
4483
+ lines.push("");
4484
+ for (const [key, value] of Object.entries(stack)) {
4485
+ lines.push(`- **${key}**: ${value}`);
4486
+ }
4487
+ lines.push("");
4488
+ lines.push("## \uB514\uB809\uD1A0\uB9AC \uAD6C\uC870");
4489
+ lines.push("");
4490
+ lines.push("```");
4491
+ lines.push(tree);
4492
+ lines.push("```");
4493
+ lines.push("");
4494
+ lines.push("## VHK CLI \uBA85\uB839\uC5B4");
4495
+ lines.push("");
4496
+ for (const cmd of commands) {
4497
+ lines.push(`- \`vhk ${cmd}\``);
4498
+ }
4499
+ lines.push("");
4500
+ if (existsSync11(".vhk/memory.json")) {
4501
+ try {
4502
+ const memories = readJsonFile(
4503
+ ".vhk/memory.json"
4504
+ );
4505
+ if (Array.isArray(memories) && memories.length > 0) {
4506
+ lines.push("## \uC800\uC7A5\uB41C \uACB0\uC815\uC0AC\uD56D");
4507
+ lines.push("");
4508
+ for (const m of memories) {
4509
+ const date = new Date(m.addedAt).toLocaleDateString("ko-KR");
4510
+ lines.push(`- ${m.content} _(${date})_`);
4511
+ }
4512
+ lines.push("");
4513
+ }
4514
+ } catch {
4515
+ }
4516
+ }
4517
+ lines.push("---");
4518
+ lines.push("");
4519
+ lines.push(`_\uC0DD\uC131: ${(/* @__PURE__ */ new Date()).toLocaleString("ko-KR")}_`);
4520
+ lines.push("");
4521
+ mkdirSync5(".vhk", { recursive: true });
4522
+ writeFileSync6(CONTEXT_PATH, lines.join("\n"), "utf-8");
4523
+ console.log(chalk25.green(`
4524
+ \u2705 ${CONTEXT_PATH} \uC0DD\uC131 \uC644\uB8CC!`));
4525
+ console.log(chalk25.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
4526
+ console.log(chalk25.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
4527
+ printNextStep({
4528
+ message: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC!",
4529
+ command: "vhk context-show",
4530
+ cursorHint: "\uCEE8\uD14D\uC2A4\uD2B8 \uBCF4\uC5EC\uC918"
4531
+ });
4532
+ }
4533
+ async function contextShow() {
4534
+ console.log(chalk25.bold("\n\u{1F4C4} " + t("context.showTitle")));
4535
+ console.log(chalk25.gray("\u2500".repeat(40)));
4536
+ if (!existsSync11(CONTEXT_PATH)) {
4537
+ console.log(chalk25.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4538
+ console.log(chalk25.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
4539
+ return;
4540
+ }
4541
+ const content = readFileSync7(CONTEXT_PATH, "utf-8");
4542
+ console.log("\n" + content);
4543
+ }
4544
+
4545
+ // src/commands/memory.ts
4546
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
4547
+ import chalk26 from "chalk";
4548
+ var MEMORY_PATH = ".vhk/memory.json";
4549
+ function loadMemories() {
4550
+ if (!existsSync12(MEMORY_PATH)) return [];
4551
+ try {
4552
+ const parsed = readJsonFile(MEMORY_PATH);
4553
+ return Array.isArray(parsed) ? parsed : [];
4554
+ } catch {
4555
+ return [];
4556
+ }
4557
+ }
4558
+ function saveMemories(memories) {
4559
+ mkdirSync6(".vhk", { recursive: true });
4560
+ writeFileSync7(MEMORY_PATH, JSON.stringify(memories, null, 2) + "\n", "utf-8");
4561
+ }
4562
+ async function memoryAdd(content, tags) {
4563
+ console.log(chalk26.bold("\n\u{1F9E0} " + t("memory.addTitle")));
4564
+ console.log(chalk26.gray("\u2500".repeat(40)));
4565
+ if (!content) {
4566
+ console.log(chalk26.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4567
+ console.log(chalk26.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9\uD558\uAE30\uB85C \uACB0\uC815"'));
4568
+ return;
4569
+ }
4570
+ const memories = loadMemories();
4571
+ memories.push({
4572
+ content,
4573
+ addedAt: (/* @__PURE__ */ new Date()).toISOString(),
4574
+ tags: tags && tags.length > 0 ? tags : []
4575
+ });
4576
+ saveMemories(memories);
4577
+ console.log(chalk26.green(`
4578
+ \u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (#${memories.length})`));
4579
+ console.log(chalk26.cyan(` \u{1F4DD} ${content}`));
4580
+ printNextStep({
4581
+ message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!",
4582
+ command: "vhk memory list",
4583
+ cursorHint: "\uAE30\uC5B5 \uBAA9\uB85D \uBCF4\uC5EC\uC918"
4584
+ });
4585
+ }
4586
+ async function memoryList() {
4587
+ console.log(chalk26.bold("\n\u{1F9E0} " + t("memory.listTitle")));
4588
+ console.log(chalk26.gray("\u2500".repeat(40)));
4589
+ const memories = loadMemories();
4590
+ if (memories.length === 0) {
4591
+ console.log(chalk26.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4592
+ console.log(chalk26.gray(' vhk memory add "\uB0B4\uC6A9"\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
4593
+ return;
4594
+ }
4595
+ console.log(chalk26.cyan(`
4596
+ \uCD1D ${memories.length}\uAC1C\uC758 \uAE30\uC5B5:
4597
+ `));
4598
+ memories.forEach((m, index) => {
4599
+ const date = new Date(m.addedAt).toLocaleDateString("ko-KR");
4600
+ console.log(chalk26.white(` [${index + 1}] ${m.content}`));
4601
+ if (m.tags && m.tags.length > 0) {
4602
+ console.log(chalk26.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
4603
+ }
4604
+ console.log(chalk26.gray(` \u{1F4C5} ${date}`));
4605
+ console.log("");
4606
+ });
4607
+ }
4608
+ async function memoryRemove(indexStr) {
4609
+ const memories = loadMemories();
4610
+ const idx = parseInt(indexStr, 10) - 1;
4611
+ if (Number.isNaN(idx) || idx < 0 || idx >= memories.length) {
4612
+ console.log(chalk26.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${memories.length || 0})`));
4613
+ return;
4614
+ }
4615
+ const removed = memories.splice(idx, 1)[0];
4616
+ saveMemories(memories);
4617
+ console.log(chalk26.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
4618
+ console.log(chalk26.gray(` ${removed.content}`));
4619
+ }
4620
+
4621
+ // src/commands/brief.ts
4622
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
4623
+ import chalk27 from "chalk";
4624
+ var BRIEF_PATH = ".vhk/brief.md";
4625
+ function git2(args) {
4626
+ const result = safeExecFile("git", args);
4627
+ return result.ok ? result.out : "";
4628
+ }
4629
+ async function brief() {
4630
+ console.log(chalk27.bold("\n\u{1F4CB} " + t("brief.title")));
4631
+ console.log(chalk27.gray("\u2500".repeat(40)));
4632
+ const lines = [];
4633
+ lines.push("# \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551");
4634
+ lines.push("");
4635
+ lines.push(`> \uC0DD\uC131: ${(/* @__PURE__ */ new Date()).toLocaleString("ko-KR")}`);
4636
+ lines.push("");
4637
+ try {
4638
+ const pkg = readJsonFile("package.json");
4639
+ lines.push("## \uD504\uB85C\uC81D\uD2B8 \uC815\uBCF4");
4640
+ lines.push("");
4641
+ lines.push(`- **\uC774\uB984**: ${pkg.name ?? "\uBBF8\uC815"}`);
4642
+ lines.push(`- **\uBC84\uC804**: ${pkg.version ?? "\uBBF8\uC815"}`);
4643
+ lines.push(`- **\uC124\uBA85**: ${pkg.description ?? "\uC5C6\uC74C"}`);
4644
+ const deps = Object.keys(pkg.dependencies ?? {}).length;
4645
+ const devDeps = Object.keys(pkg.devDependencies ?? {}).length;
4646
+ lines.push(`- **\uC758\uC874\uC131**: ${deps}\uAC1C (dev: ${devDeps}\uAC1C)`);
4647
+ lines.push("");
4648
+ } catch {
4649
+ lines.push("## \uD504\uB85C\uC81D\uD2B8 \uC815\uBCF4");
4650
+ lines.push("");
4651
+ lines.push("\u26A0\uFE0F package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4652
+ lines.push("");
4653
+ }
4654
+ const branch = git2(["branch", "--show-current"]);
4655
+ const lastCommit = git2(["log", "-1", "--pretty=format:%h %s (%cr)"]);
4656
+ const uncommitted = git2(["status", "--porcelain"]);
4657
+ const totalCommits = git2(["rev-list", "--count", "HEAD"]);
4658
+ lines.push("## Git \uC0C1\uD0DC");
4659
+ lines.push("");
4660
+ lines.push(`- **\uD604\uC7AC \uBE0C\uB79C\uCE58**: ${branch || "\uC54C \uC218 \uC5C6\uC74C"}`);
4661
+ lines.push(`- **\uB9C8\uC9C0\uB9C9 \uCEE4\uBC0B**: ${lastCommit || "\uC5C6\uC74C"}`);
4662
+ lines.push(`- **\uCD1D \uCEE4\uBC0B \uC218**: ${totalCommits || "\uC54C \uC218 \uC5C6\uC74C"}`);
4663
+ lines.push(
4664
+ `- **\uBBF8\uCEE4\uBC0B \uBCC0\uACBD**: ${uncommitted ? `${uncommitted.split("\n").length}\uAC1C \uD30C\uC77C` : "\uC5C6\uC74C \u2705"}`
4665
+ );
4666
+ lines.push("");
4667
+ if (existsSync13(".vhk/memory.json")) {
4668
+ try {
4669
+ const memories = readJsonFile(".vhk/memory.json");
4670
+ if (Array.isArray(memories) && memories.length > 0) {
4671
+ lines.push(`## \uC800\uC7A5\uB41C \uACB0\uC815\uC0AC\uD56D (${memories.length}\uAC1C)`);
4672
+ lines.push("");
4673
+ for (const m of memories.slice(-5)) {
4674
+ lines.push(`- ${m.content}`);
4675
+ }
4676
+ if (memories.length > 5) {
4677
+ lines.push(`- ... \uC678 ${memories.length - 5}\uAC1C`);
4678
+ }
4679
+ lines.push("");
4680
+ }
4681
+ } catch {
4682
+ }
4683
+ }
4684
+ if (existsSync13(".vhk/refs.json")) {
4685
+ try {
4686
+ const refs = readJsonFile(".vhk/refs.json");
4687
+ if (Array.isArray(refs) && refs.length > 0) {
4688
+ lines.push(`## \uB808\uD37C\uB7F0\uC2A4 (${refs.length}\uAC1C)`);
4689
+ lines.push("");
4690
+ for (const r of refs.slice(-3)) {
4691
+ const label = r.memo && r.memo.length > 0 ? r.memo : r.url;
4692
+ lines.push(`- [${label}](${r.url})`);
4693
+ }
4694
+ lines.push("");
4695
+ }
4696
+ } catch {
4697
+ }
4698
+ }
4699
+ lines.push("## \uB2E4\uC74C \uB2E8\uACC4 \uC81C\uC548");
4700
+ lines.push("");
4701
+ const steps = [];
4702
+ if (uncommitted) steps.push("\uBBF8\uCEE4\uBC0B \uBCC0\uACBD \uC0AC\uD56D\uC744 \uCEE4\uBC0B\uD558\uC138\uC694: `vhk save`");
4703
+ steps.push("\uD488\uC9C8 \uC810\uAC80 \uC2E4\uD589: `vhk harness`");
4704
+ steps.push("\uBCF4\uC548 \uAC10\uC0AC: `vhk audit`");
4705
+ steps.push("\uCEE8\uD14D\uC2A4\uD2B8 \uAC31\uC2E0: `vhk context`");
4706
+ steps.forEach((s, i) => lines.push(`${i + 1}. ${s}`));
4707
+ lines.push("");
4708
+ lines.push("---");
4709
+ lines.push("");
4710
+ lines.push("_VHK CLI \uBE0C\uB9AC\uD551_");
4711
+ lines.push("");
4712
+ mkdirSync7(".vhk", { recursive: true });
4713
+ writeFileSync8(BRIEF_PATH, lines.join("\n"), "utf-8");
4714
+ console.log("\n" + lines.join("\n"));
4715
+ console.log(chalk27.green(`
4716
+ \u2705 ${BRIEF_PATH} \uC800\uC7A5 \uC644\uB8CC`));
4717
+ printNextStep({
4718
+ message: "\uBE0C\uB9AC\uD551 \uC0DD\uC131 \uC644\uB8CC!",
4719
+ command: "vhk context",
4720
+ cursorHint: "\uCEE8\uD14D\uC2A4\uD2B8 \uC5C5\uB370\uC774\uD2B8\uD574\uC918"
4721
+ });
4722
+ }
4723
+
3936
4724
  // src/lib/nlp-run.ts
3937
4725
  async function dispatchNlpRoute(route, input) {
3938
4726
  switch (route.command) {
@@ -3981,28 +4769,44 @@ async function dispatchNlpRoute(route, input) {
3981
4769
  return theme();
3982
4770
  case "ref":
3983
4771
  return refList();
4772
+ case "harness":
4773
+ return harness();
4774
+ case "audit":
4775
+ return audit();
4776
+ case "migrate":
4777
+ return migrate();
4778
+ case "update":
4779
+ return update();
4780
+ case "context":
4781
+ return context();
4782
+ case "context-show":
4783
+ return contextShow();
4784
+ case "memory":
4785
+ return memoryList();
4786
+ case "brief":
4787
+ return brief();
3984
4788
  }
3985
4789
  }
3986
4790
  async function runNaturalLanguageRoute(input) {
3987
4791
  const route = routeNaturalLanguage(input);
3988
4792
  if (!route) {
3989
- console.log(chalk21.yellow(`
4793
+ console.log(chalk28.yellow(`
3990
4794
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
3991
4795
  `));
3992
4796
  return;
3993
4797
  }
3994
4798
  console.log("");
3995
- console.log(chalk21.cyan(` \u{1F4AC} "${input}"`));
3996
- console.log(chalk21.cyan(` \u2192 ${route.explanation}`));
4799
+ console.log(chalk28.cyan(` \u{1F4AC} "${input}"`));
4800
+ console.log(chalk28.cyan(` \u2192 ${route.explanation}`));
3997
4801
  if (route.confidence === "low") {
3998
- const { confirm } = await inquirer11.prompt([{
4802
+ const { confirm } = await inquirer13.prompt([{
3999
4803
  type: "confirm",
4000
4804
  name: "confirm",
4001
4805
  message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
4002
4806
  default: true
4003
4807
  }]);
4004
4808
  if (!confirm) {
4005
- console.log(chalk21.dim(` ${ko.nlp.menuHint}`));
4809
+ console.log(chalk28.dim(` ${ko.nlp.menuHint}`));
4006
4810
  return;
4007
4811
  }
4008
4812
  }
@@ -4012,11 +4816,11 @@ async function runNaturalLanguageRoute(input) {
4012
4816
 
4013
4817
  // src/index.ts
4014
4818
  function getVersion() {
4015
- const dir = path15.dirname(fileURLToPath3(import.meta.url));
4819
+ const dir = path15.dirname(fileURLToPath4(import.meta.url));
4016
4820
  for (const pkgPath of [path15.join(dir, "../package.json"), path15.join(dir, "../../package.json")]) {
4017
4821
  try {
4018
- if (fs16.existsSync(pkgPath)) {
4019
- const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf-8"));
4822
+ if (fs15.existsSync(pkgPath)) {
4823
+ const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
4020
4824
  if (pkg.version) return pkg.version;
4021
4825
  }
4022
4826
  } catch {
@@ -4047,7 +4851,15 @@ var KO_ALIASES = {
4047
4851
  design: "\uB514\uC790\uC778",
4048
4852
  "design-palette": "\uD314\uB808\uD2B8",
4049
4853
  theme: "\uD14C\uB9C8",
4050
- ref: "\uB808\uD37C\uB7F0\uC2A4"
4854
+ ref: "\uB808\uD37C\uB7F0\uC2A4",
4855
+ harness: "\uD558\uB124\uC2A4",
4856
+ audit: "\uAC10\uC0AC",
4857
+ migrate: "\uC804\uD658",
4858
+ update: "\uC5C5\uB370\uC774\uD2B8",
4859
+ context: "\uB9E5\uB77D",
4860
+ "context-show": "\uB9E5\uB77D\uBCF4\uAE30",
4861
+ memory: "\uAE30\uC5B5",
4862
+ brief: "\uBE0C\uB9AC\uD551"
4051
4863
  };
4052
4864
  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
4865
  program.configureHelp({
@@ -4131,6 +4943,40 @@ refCmd.command("list").alias("\uBAA9\uB85D").description("\uC800\uC7A5\uB41C \uB
4131
4943
  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
4944
  await refOpen(index);
4133
4945
  });
4946
+ program.command("harness").alias("\uD558\uB124\uC2A4").description("\uD1B5\uD569 \uD488\uC9C8 \uC810\uAC80 (lint + type-check + test + build)").action(async () => {
4947
+ await harness();
4948
+ });
4949
+ 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) => {
4950
+ await audit(opts.fix);
4951
+ });
4952
+ program.command("migrate [target]").alias("\uC804\uD658").description("\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 (npm/yarn/pnpm)").action(async (target) => {
4953
+ await migrate(target);
4954
+ });
4955
+ program.command("update").alias("\uC5C5\uB370\uC774\uD2B8").description("VHK CLI \uCD5C\uC2E0 \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8").action(async () => {
4956
+ await update();
4957
+ });
4958
+ program.command("context").alias("\uB9E5\uB77D").description("\uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D \uD30C\uC77C \uC0DD\uC131 (.vhk/context.md)").action(async () => {
4959
+ await context();
4960
+ });
4961
+ program.command("context-show").alias("\uB9E5\uB77D\uBCF4\uAE30").description("\uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uB0B4\uC6A9 \uCD9C\uB825").action(async () => {
4962
+ await contextShow();
4963
+ });
4964
+ var memoryCmd = program.command("memory").alias("\uAE30\uC5B5").description("\uACB0\uC815\uC0AC\uD56D \uAE30\uC5B5 \uAD00\uB9AC (add / list / remove)").action(async () => {
4965
+ await memoryList();
4966
+ });
4967
+ memoryCmd.command("add <content>").option("--tags <tags>", "\uD0DC\uADF8 (\uC27C\uD45C \uAD6C\uBD84)").description("\uACB0\uC815\uC0AC\uD56D \uAE30\uC5B5 \uC800\uC7A5").action(async (content, opts) => {
4968
+ const tags = opts.tags ? opts.tags.split(",").map((s) => s.trim()) : void 0;
4969
+ await memoryAdd(content, tags);
4970
+ });
4971
+ memoryCmd.command("list").alias("\uBAA9\uB85D").description("\uC800\uC7A5\uB41C \uAE30\uC5B5 \uBAA9\uB85D").action(async () => {
4972
+ await memoryList();
4973
+ });
4974
+ memoryCmd.command("remove <index>").alias("\uC0AD\uC81C").description("\uAE30\uC5B5 \uC0AD\uC81C (1\uBD80\uD130 \uC2DC\uC791\uD558\uB294 \uBC88\uD638)").action(async (index) => {
4975
+ await memoryRemove(index);
4976
+ });
4977
+ program.command("brief").alias("\uBE0C\uB9AC\uD551").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC694\uC57D \uBCF4\uACE0\uC11C \uC0DD\uC131 (.vhk/brief.md)").action(async () => {
4978
+ await brief();
4979
+ });
4134
4980
  program.on("command:*", async (operands) => {
4135
4981
  const unknown = operands[0] ?? "";
4136
4982
  const rest = operands.slice(1);
@@ -4139,7 +4985,7 @@ program.on("command:*", async (operands) => {
4139
4985
  });
4140
4986
  program.action(async () => {
4141
4987
  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([{
4988
+ const { choice } = await inquirer14.prompt([{
4143
4989
  type: "list",
4144
4990
  name: "choice",
4145
4991
  message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",