@byh3071/vhk 1.3.0 β†’ 1.3.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,57 +1,90 @@
1
1
  ---
2
2
  id: vhk-readme
3
- date: 2026-05-24
4
- tags: [vhk, cli, readme, v1.0.0, ga]
3
+ date: 2026-05-28
4
+ tags: [vhk, cli, readme, v1.3.0, ga]
5
5
  ---
6
6
 
7
7
  # πŸ”§ VHK β€” Vibe Harness Kit
8
8
 
9
- > πŸŽ‰ **v1.0.0 GA** β€” λ°”μ΄λΈŒμ½”λ”μ˜ μ˜¬μΈμ› CLI. 곡개 API μ•ˆμ •μ„± 보μž₯.
9
+ > πŸŽ‰ **v1.3.0** β€” λ°”μ΄λΈŒμ½”λ”μ˜ μ˜¬μΈμ› CLI. μ»¨ν…μŠ€νŠΈ + 자율 ν•˜λ„€μŠ€.
10
10
  >
11
- > AI μ½”λ”© μ—μ΄μ „νŠΈλ₯Ό λΆ€λ¦¬λŠ” μ‚¬λžŒμ„ μœ„ν•œ **ν•œκ΅­μ–΄ 풀사이클 CLI** (v1.0.0)
11
+ > AI μ½”λ”© μ—μ΄μ „νŠΈλ₯Ό λΆ€λ¦¬λŠ” μ‚¬λžŒμ„ μœ„ν•œ **ν•œκ΅­μ–΄ 풀사이클 CLI**.
12
12
  >
13
13
  > 🍽️ **VHKλŠ” VHK둜 λΆ€νŠΈμŠ€νŠΈλž©λ¨** β€” 이 레포의 `docs/`, `CLAUDE.md`, `.cursorrules`도 `vhk init`이 λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€.
14
14
 
15
15
  λͺ…λ Ήμ–΄λ₯Ό μ™Έμš°μ§€ μ•Šμ•„λ„ λ©λ‹ˆλ‹€. `vhk`만 치면 메뉴가 λ‚˜μ˜€κ³ , ν•œκ΅­μ–΄λ‘œ 말해도 μ•Œμ•„λ“£μŠ΅λ‹ˆλ‹€.
16
16
 
17
- ## μ„€μΉ˜
17
+ ## 3λΆ„ μ•ˆμ— μ‹œμž‘ν•˜κΈ° (Getting Started)
18
+
19
+ ### 1. μ„€μΉ˜
18
20
 
19
21
  ```bash
20
22
  npm install -g @byh3071/vhk
23
+ vhk --version
21
24
  ```
22
25
 
26
+ > Node.js β‰₯ 20 ν•„μš”. `npx @byh3071/vhk` 둜 1νšŒμ„± 싀행도 κ°€λŠ₯.
27
+
28
+ ### 2. 첫 ν”„λ‘œμ νŠΈ β€” `vhk start` (λ§ˆλ²•μ‚¬)
29
+
23
30
  ```bash
24
- # ν•œ 번만 μ“Έ λ•Œ
25
- npx @byh3071/vhk
31
+ mkdir my-app && cd my-app
32
+ vhk start
26
33
  ```
27
34
 
28
- 둜컬 개발 쀑:
35
+ `vhk start` ν•œ 번이면 **git init β†’ λ¬Έμ„œ 생성 β†’ MCP 등둝 β†’ μ»¨ν…μŠ€νŠΈ 파일** κΉŒμ§€ μžλ™μœΌλ‘œ λλ‚©λ‹ˆλ‹€.
29
36
 
30
- ```powershell
31
- cd vhk-cli
32
- pnpm install
33
- pnpm build
34
- pnpm link --global
35
- vhk --version
37
+ 기획이 막 λ– μ˜¬λžλ‹€λ©΄ 검증뢀터:
38
+
39
+ ```bash
40
+ vhk gate # 퀡 5λ¬Έν•­ β€” GO / 닀듬기 / λ‹€λ₯Έ 아이디어
36
41
  ```
37
42
 
38
- ## λΉ λ₯Έ μ‹œμž‘
43
+ ### 3. v1.3 핡심 κΈ°λŠ₯ ν•œλˆˆμ—
39
44
 
40
- ```bash
41
- vhk
45
+ | κΈ°λŠ₯ | ν•œ 쀄 μš”μ•½ | μ§„μž… λͺ…λ Ή |
46
+ |------|-----------|-----------|
47
+ | 🎯 **Goals 체계** | 단계별 λ―Έμ…˜ + 게이트 슀크립트둜 AIκ°€ λͺ©ν‘œλ₯Ό 슀슀둜 좔적 | `vhk goal init` |
48
+ | ▢️ **자율 루프** | `goal next β†’ μž‘μ—… β†’ goal check β†’ goal done`. FAIL 3회면 μžλ™ λΈ”λ‘œμ»€ | `vhk goal next` |
49
+ | 🚧 **HARD_STOP μ•ˆμ „μž₯치** | λΈ”λ‘œμ»€ 3건 λˆ„μ  β†’ `.vhk/HARD_STOP` νŠΈλ¦½μ™€μ΄μ–΄. `vhk resume --confirm` 만 ν•΄μ œ | `vhk blocker "<증상>"` |
50
+ | πŸ”Œ **MCP 24 tool** | CursorΒ·Claude Desktop λ“±μ—μ„œ vhkλ₯Ό μ±„νŒ…μœΌλ‘œ 호좜 | `vhk mcp-init` |
51
+ | πŸ“‹ **μ»¨ν…μŠ€νŠΈ μ˜μ†ν™”** | `.vhk/context.md` + `memory.json` + `brief.md` 둜 μ„Έμ…˜ κ°„ λ§₯락 μœ μ§€ | `vhk context` |
52
+
53
+ ### 4. ꢌμž₯ 일일 사이클
54
+
55
+ ```text
56
+ μ„Έμ…˜ μ‹œμž‘ : vhk context # AI에 쀄 ν”„λ‘œμ νŠΈ λ§₯락 κ°±μ‹ 
57
+ vhk goal next # 였늘 μž‘μ—…ν•  λ―Έμ…˜ μžλ™ 선택
58
+
59
+ 개발 ...
60
+
61
+ μ„Έμ…˜ μ’…λ£Œ : vhk goal check # 게이트 슀크립트둜 톡과 검증
62
+ vhk goal done # 톡과 μ‹œ status: DONE 으둜 전이
63
+ vhk save # add β†’ commit β†’ push ν•œ λ²ˆμ—
64
+ vhk recap # docs/log/ 에 였늘 기둝
42
65
  ```
43
66
 
44
- 인자 없이 μ‹€ν–‰ν•˜λ©΄ **γ€Œλ­˜ λ„μ™€λ“œλ¦΄κΉŒμš”?」** 메뉴가 μ—΄λ¦½λ‹ˆλ‹€.
67
+ ### 5. μžμ—°μ–΄λ‘œλ„ λ©λ‹ˆλ‹€
45
68
 
46
69
  ```bash
47
- # μžμ—°μ–΄λ‘œλ„ κ°€λŠ₯
48
70
  vhk ν”„λ‘œμ νŠΈ λ§Œλ“€κ³  μ‹Άμ–΄
49
71
  vhk 기획 끝났고 λ°”λ‘œ μ‹œμž‘
50
72
  vhk 였늘 ν•œ 일 정리
51
73
  vhk μ €μž₯ν•΄μ€˜
52
- vhk 변경사항 λ³΄μ—¬μ€˜
74
+ vhk λ‹€μŒ λͺ©ν‘œ
75
+ vhk λΈ”λ‘œμ»€ "API 호좜 μ‹€νŒ¨"
76
+ ```
77
+
78
+ ---
79
+
80
+ ## λΉ λ₯Έ μ‹œμž‘ (μΈν„°λž™ν‹°λΈŒ 메뉴)
81
+
82
+ ```bash
83
+ vhk
53
84
  ```
54
85
 
86
+ 인자 없이 μ‹€ν–‰ν•˜λ©΄ **γ€Œλ­˜ λ„μ™€λ“œλ¦΄κΉŒμš”?」** 메뉴가 μ—΄λ¦½λ‹ˆλ‹€.
87
+
55
88
  ## μ›Œν¬ν”Œλ‘œμš° (ꢌμž₯ μˆœμ„œ)
56
89
 
57
90
  ```text
@@ -265,7 +298,7 @@ vhk ref open 1 # 1번 레퍼런슀λ₯Ό λΈŒλΌμš°μ €λ‘œ μ—΄κΈ°
265
298
 
266
299
  | κΈ°λŠ₯ | μ„€λͺ… |
267
300
  |------|------|
268
- | **MCP μ„œλ²„** | `vhk mcp` β€” 8개 도ꡬ(save/undo/status/diff/ship/doctor/check/recap)λ₯Ό stdio둜 λ…ΈμΆœ. Cursor λ“± MCP ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ μžμ—°μ–΄λ‘œ 호좜 |
301
+ | **MCP μ„œλ²„** | `vhk mcp` β€” stdio MCP μ„œλ²„ 첫 λ„μž… (v0.6.0 λ‹Ήμ‹œ 8개 도ꡬ β€” save/undo/status/diff/ship/doctor/check/recap). ν˜„μž¬ v1.3 κΈ°μ€€ **24개** 둜 ν™•μž₯ β€” μœ„ "Cursor와 MCP둜 μ—°λ™ν•˜κΈ°" μ„Ήμ…˜ μ°Έμ‘° |
269
302
  | **mcp-init** | `vhk mcp-init` β€” Cursor `.cursor/mcp.json` μžλ™ 생성. μž¬μ‹œμž‘ ν•œ 번으둜 연동 μ™„λ£Œ |
270
303
  | **μžμ—°μ–΄ λΌμš°νŒ… ν™•μž₯** | `vhk mcpμ„€μ •` β†’ `vhk mcp-init` 별칭 |
271
304
  | **λ³΄μ•ˆ** | MCP save λ„κ΅¬μ˜ shell injection 차단 β€” λͺ¨λ“  git ν˜ΈμΆœμ— `execFileSync` μ‚¬μš© |
@@ -551,7 +551,11 @@ var ko = {
551
551
  package: "package.json:",
552
552
  noPackage: "package.json \uC5C6\uC74C",
553
553
  detached: "(detached HEAD)",
554
- unknownBranch: "(\uC54C \uC218 \uC5C6\uC74C)"
554
+ unknownBranch: "(\uC54C \uC218 \uC5C6\uC74C)",
555
+ nextWithChangesMessage: "\uBCC0\uACBD\uC0AC\uD56D\uC774 \uC788\uC5B4\uC694. \uCEE4\uBC0B\xB7\uD478\uC2DC\uB97C \uC9C4\uD589\uD558\uC138\uC694.",
556
+ nextWithChangesCursor: "\uC800\uC7A5\uD574\uC918",
557
+ nextCleanMessage: "\uD074\uB9B0 \uC0C1\uD0DC! \uB2E4\uC74C \uBBF8\uC158\uC73C\uB85C \uB118\uC5B4\uAC00\uC138\uC694.",
558
+ nextCleanCursor: "\uB2E4\uC74C \uBAA9\uD45C \uC54C\uB824\uC918"
555
559
  },
556
560
  save: {
557
561
  title: "\uC800\uC7A5\uD558\uAE30",
@@ -573,7 +577,11 @@ var ko = {
573
577
  pushFailed: "push \uC2E4\uD328 (\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uC644\uB8CC\uB428)",
574
578
  commitOkPushFailed: "\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uB410\uC9C0\uB9CC \uC6D0\uACA9 push\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. git push\uB97C \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.",
575
579
  done: (n) => `${n}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC!`,
576
- doneLocalOnly: (n) => `${n}\uAC1C \uD30C\uC77C \uB85C\uCEEC \uC800\uC7A5\uB428 (push\uB294 \uC2E4\uD328)`
580
+ doneLocalOnly: (n) => `${n}\uAC1C \uD30C\uC77C \uB85C\uCEEC \uC800\uC7A5\uB428 (push\uB294 \uC2E4\uD328)`,
581
+ nextOkMessage: "\uC800\uC7A5 \uC644\uB8CC! \uC624\uB298 \uC791\uC5C5\uC744 \uC815\uB9AC\uD574\uB450\uBA74 \uC88B\uC544\uC694.",
582
+ nextOkCursor: "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD574\uC918",
583
+ nextPushFailMessage: "\uCEE4\uBC0B\uC740 \uB410\uC9C0\uB9CC push \uC2E4\uD328. \uC6D0\uACA9\uC744 \uD655\uC778\uD558\uC138\uC694.",
584
+ nextPushFailCursor: "\uC65C push \uC2E4\uD328\uC778\uC9C0 \uC54C\uB824\uC918"
577
585
  },
578
586
  undo: {
579
587
  title: "\uB418\uB3CC\uB9AC\uAE30",
@@ -590,7 +598,9 @@ var ko = {
590
598
  stagedHint: "\uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544 \uC788\uC5B4\uC694.",
591
599
  rootCommit: "\uCCAB \uCEE4\uBC0B\uB9CC \uC788\uC5B4\uC11C \uB354 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
592
600
  forcePushHint: "\uC6D0\uACA9\uACFC \uB9DE\uCD94\uB824\uBA74: git push --force-with-lease (\uD63C\uC790 \uC791\uC5C5\uD55C \uBE0C\uB79C\uCE58\uC5D0\uC11C\uB9CC, \uD300\uACFC \uD569\uC758 \uD6C4)",
593
- failed: "\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328"
601
+ failed: "\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328",
602
+ nextMessage: "\uBCC0\uACBD\uC0AC\uD56D\uC774 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC558\uC5B4\uC694. \uBA54\uC2DC\uC9C0 \uACE0\uCCD0\uC11C \uB2E4\uC2DC \uC800\uC7A5\uD558\uC138\uC694.",
603
+ nextCursor: "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uBC14\uAFD4\uC11C \uC800\uC7A5\uD574\uC918"
594
604
  },
595
605
  diff: {
596
606
  title: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778",
@@ -799,7 +809,9 @@ var ko = {
799
809
  },
800
810
  mcp: {
801
811
  initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
802
- serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428"
812
+ serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428",
813
+ nextMessage: "Cursor / Claude Desktop \uC744 \uC7AC\uC2DC\uC791\uD55C \uB4A4 \uCC44\uD305\uC5D0\uC11C vhk \uB3C4\uAD6C\uB97C \uD638\uCD9C\uD558\uC138\uC694.",
814
+ nextCursor: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC54C\uB824\uC918"
803
815
  },
804
816
  deploy: {
805
817
  title: "\uBC30\uD3EC\uD558\uAE30",
@@ -836,7 +848,11 @@ var ko = {
836
848
  selectTarget: "\uC5B4\uB5A4 \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800\uB85C \uC804\uD658\uD560\uAE4C\uC694?"
837
849
  },
838
850
  update: {
839
- title: "VHK CLI \uC5C5\uB370\uC774\uD2B8"
851
+ title: "VHK CLI \uC5C5\uB370\uC774\uD2B8",
852
+ nextOkMessage: "\uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC! \uC0C8 \uBC84\uC804 \uD655\uC778\uD558\uC138\uC694.",
853
+ nextOkCursor: "vhk \uBC84\uC804 \uC54C\uB824\uC918",
854
+ nextFailMessage: "\uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328. \uD658\uACBD \uC810\uAC80\uBD80\uD130 \uD574\uBCF4\uC138\uC694.",
855
+ nextFailCursor: "vhk doctor \uC2E4\uD589\uD574\uC918"
840
856
  },
841
857
  context: {
842
858
  title: "\uD504\uB85C\uC81D\uD2B8 \uCEE8\uD14D\uC2A4\uD2B8 \uC0DD\uC131",
@@ -1300,11 +1316,33 @@ async function audit(autoFix = false) {
1300
1316
  });
1301
1317
  }
1302
1318
 
1319
+ // src/lib/version.ts
1320
+ import { existsSync as existsSync5 } from "fs";
1321
+ import { dirname, join } from "path";
1322
+ import { fileURLToPath } from "url";
1323
+ function getVhkVersion() {
1324
+ const dir = dirname(fileURLToPath(import.meta.url));
1325
+ for (const pkgPath of [
1326
+ join(dir, "../../package.json"),
1327
+ join(dir, "../package.json")
1328
+ ]) {
1329
+ try {
1330
+ if (existsSync5(pkgPath)) {
1331
+ const pkg = readJsonFile(pkgPath);
1332
+ if (pkg.version) return pkg.version;
1333
+ }
1334
+ } catch {
1335
+ continue;
1336
+ }
1337
+ }
1338
+ return "0.0.0";
1339
+ }
1340
+
1303
1341
  // src/mcp/server.ts
1304
1342
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1305
1343
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1306
1344
  import { z } from "zod";
1307
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
1345
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
1308
1346
 
1309
1347
  // src/lib/scan-secrets.ts
1310
1348
  import fs3 from "fs";
@@ -1590,7 +1628,7 @@ function filterSevereFindings(findings) {
1590
1628
  }
1591
1629
 
1592
1630
  // src/mcp/server.ts
1593
- var SERVER_VERSION = "1.3.0";
1631
+ var SERVER_VERSION = getVhkVersion();
1594
1632
  function isGitRepo() {
1595
1633
  return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
1596
1634
  }
@@ -1697,7 +1735,7 @@ ${preview}${more}
1697
1735
  });
1698
1736
  server.registerTool("status", { description: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uB300\uC2DC\uBCF4\uB4DC (\uBE0C\uB79C\uCE58/\uBCC0\uACBD\uC0AC\uD56D/\uCD5C\uADFC \uCEE4\uBC0B)" }, async () => {
1699
1737
  const lines = [];
1700
- if (existsSync5("package.json")) {
1738
+ if (existsSync6("package.json")) {
1701
1739
  try {
1702
1740
  const pkg = readJsonFile("package.json");
1703
1741
  lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
@@ -1782,7 +1820,7 @@ ${preview}${more}
1782
1820
  checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
1783
1821
  const test = safeExecFile("pnpm", ["test", "--run"]);
1784
1822
  checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
1785
- if (existsSync5("package.json")) {
1823
+ if (existsSync6("package.json")) {
1786
1824
  try {
1787
1825
  const pkg = readJsonFile("package.json");
1788
1826
  checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
@@ -1820,11 +1858,11 @@ ${preview}${more}
1820
1858
  const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
1821
1859
  const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
1822
1860
  required.forEach((f) => {
1823
- lines.push(` ${existsSync5(f) ? "\u2705" : "\u274C"} ${f}`);
1861
+ lines.push(` ${existsSync6(f) ? "\u2705" : "\u274C"} ${f}`);
1824
1862
  });
1825
1863
  lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
1826
1864
  recommended.forEach((f) => {
1827
- lines.push(` ${existsSync5(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
1865
+ lines.push(` ${existsSync6(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
1828
1866
  });
1829
1867
  return { content: [{ type: "text", text: lines.join("\n") }] };
1830
1868
  });
@@ -1858,7 +1896,7 @@ ${log.out}` }] };
1858
1896
  description: ".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore\uC5D0 .env \uC790\uB3D9 \uCD94\uAC00"
1859
1897
  },
1860
1898
  async () => {
1861
- if (!existsSync5(".env")) {
1899
+ if (!existsSync6(".env")) {
1862
1900
  return { content: [{ type: "text", text: "\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 .env\uB97C \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694." }] };
1863
1901
  }
1864
1902
  const keys = parseEnvKeys(readFileSync4(".env", "utf-8"));
@@ -1868,7 +1906,7 @@ ${log.out}` }] };
1868
1906
  const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
1869
1907
  writeFileSync3(".env.example", exampleContent, "utf-8");
1870
1908
  const gitignoreLines = [];
1871
- if (existsSync5(".gitignore")) {
1909
+ if (existsSync6(".gitignore")) {
1872
1910
  const content = readFileSync4(".gitignore", "utf-8");
1873
1911
  if (!content.split("\n").some((l) => l.trim() === ".env")) {
1874
1912
  appendFileSync2(".gitignore", "\n.env\n");
@@ -1889,11 +1927,11 @@ ${log.out}` }] };
1889
1927
  description: "\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (.env.example \uAE30\uC900)"
1890
1928
  },
1891
1929
  async () => {
1892
- if (!existsSync5(".env.example")) {
1930
+ if (!existsSync6(".env.example")) {
1893
1931
  return { content: [{ type: "text", text: "\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 env \uB3C4\uAD6C\uB97C \uC2E4\uD589\uD558\uC138\uC694." }] };
1894
1932
  }
1895
1933
  const requiredKeys = parseEnvKeys(readFileSync4(".env.example", "utf-8"));
1896
- const currentKeys = existsSync5(".env") ? parseEnvKeys(readFileSync4(".env", "utf-8")) : [];
1934
+ const currentKeys = existsSync6(".env") ? parseEnvKeys(readFileSync4(".env", "utf-8")) : [];
1897
1935
  const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
1898
1936
  const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
1899
1937
  const lines = [`\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`];
@@ -2007,7 +2045,7 @@ ${cliStatus}
2007
2045
  description: "\uD604\uC7AC \uBC84\uC804 + bump \uD6C4\uBCF4 \uD45C\uC2DC (MCP \uBAA8\uB4DC: \uC2E4\uC81C npm publish \uBBF8\uC218\uD589 \u2014 `vhk publish` \uC548\uB0B4)"
2008
2046
  },
2009
2047
  async () => {
2010
- if (!existsSync5("package.json")) {
2048
+ if (!existsSync6("package.json")) {
2011
2049
  return { content: [{ type: "text", text: "\u274C package.json \uC5C6\uC74C." }] };
2012
2050
  }
2013
2051
  try {
@@ -2035,7 +2073,7 @@ ${cliStatus}
2035
2073
  description: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uAC10\uC9C0 + \uC804\uD658 \uD6C4\uBCF4 \uAC00\uC6A9\uC131 (MCP \uBAA8\uB4DC: \uC2E4\uC81C \uC804\uD658 \uBBF8\uC218\uD589 \u2014 `vhk migrate <target>` \uC548\uB0B4)"
2036
2074
  },
2037
2075
  async () => {
2038
- const current = existsSync5("pnpm-lock.yaml") ? "pnpm" : existsSync5("yarn.lock") ? "yarn" : existsSync5("package-lock.json") ? "npm" : null;
2076
+ const current = existsSync6("pnpm-lock.yaml") ? "pnpm" : existsSync6("yarn.lock") ? "yarn" : existsSync6("package-lock.json") ? "npm" : null;
2039
2077
  const candidates = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current);
2040
2078
  const lines = [`\uD604\uC7AC PM: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00 (lock \uD30C\uC77C \uC5C6\uC74C)"}`];
2041
2079
  for (const pm of candidates) {
@@ -2112,5 +2150,6 @@ export {
2112
2150
  envCheck,
2113
2151
  publish,
2114
2152
  audit,
2153
+ getVhkVersion,
2115
2154
  startMcpServer
2116
2155
  };
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  envCheck,
9
9
  filterSevereFindings,
10
10
  filterTrackedPaths,
11
+ getVhkVersion,
11
12
  ko,
12
13
  printNextStep,
13
14
  printSecurityWarnings,
@@ -17,7 +18,7 @@ import {
17
18
  scanProjectForSecrets,
18
19
  startMcpServer,
19
20
  t
20
- } from "./chunk-3HGOQLRT.js";
21
+ } from "./chunk-6S3JYYZ3.js";
21
22
 
22
23
  // src/index.ts
23
24
  import { Command, Help } from "commander";
@@ -2052,9 +2053,18 @@ ${ko.goal.initTitle}
2052
2053
  });
2053
2054
  }
2054
2055
  }
2056
+ function findGateScript(id) {
2057
+ const mjs = join2(SCRIPTS_DIR, `check-goal-${id}.mjs`);
2058
+ if (existsSync2(mjs)) return mjs;
2059
+ const sh = join2(SCRIPTS_DIR, `check-goal-${id}.sh`);
2060
+ if (existsSync2(sh)) return sh;
2061
+ return null;
2062
+ }
2055
2063
  function runGate(scriptPath) {
2056
- const r = safeExecFile("bash", [scriptPath]);
2057
- return { ok: r.ok, out: r.out, err: r.ok ? "" : r.err };
2064
+ const isMjs = scriptPath.endsWith(".mjs");
2065
+ const runner = isMjs ? "node" : "bash";
2066
+ const r = safeExecFile(runner, [scriptPath]);
2067
+ return { ok: r.ok, out: r.out, err: r.ok ? "" : r.err, runner };
2058
2068
  }
2059
2069
  async function goalCheck(opts) {
2060
2070
  console.log(chalk6.bold(`
@@ -2069,15 +2079,17 @@ ${ko.goal.checkTitle}
2069
2079
  process.exitCode = 1;
2070
2080
  return;
2071
2081
  }
2072
- const scriptPath = join2(SCRIPTS_DIR, `check-goal-${id}.sh`);
2073
- if (!existsSync2(scriptPath)) {
2074
- console.log(chalk6.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: ${scriptPath}`));
2082
+ const scriptPath = findGateScript(id);
2083
+ if (!scriptPath) {
2084
+ console.log(
2085
+ chalk6.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: scripts/check-goal-${id}.{mjs,sh}`)
2086
+ );
2075
2087
  process.exitCode = 1;
2076
2088
  return;
2077
2089
  }
2078
- console.log(chalk6.dim(` \u25B6 bash ${scriptPath}
2079
- `));
2080
2090
  const gate2 = runGate(scriptPath);
2091
+ console.log(chalk6.dim(` \u25B6 ${gate2.runner} ${scriptPath}
2092
+ `));
2081
2093
  if (gate2.out) console.log(gate2.out);
2082
2094
  if (gate2.ok) {
2083
2095
  console.log(chalk6.green(`
@@ -2108,15 +2120,19 @@ ${ko.goal.doneTitle}
2108
2120
  process.exitCode = 1;
2109
2121
  return;
2110
2122
  }
2111
- const scriptPath = join2(SCRIPTS_DIR, `check-goal-${id}.sh`);
2112
- if (!existsSync2(scriptPath)) {
2113
- console.log(chalk6.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C \u2014 done \uCC98\uB9AC \uAC70\uBD80: ${scriptPath}`));
2123
+ const scriptPath = findGateScript(id);
2124
+ if (!scriptPath) {
2125
+ console.log(
2126
+ chalk6.red(
2127
+ ` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C \u2014 done \uCC98\uB9AC \uAC70\uBD80: scripts/check-goal-${id}.{mjs,sh}`
2128
+ )
2129
+ );
2114
2130
  process.exitCode = 1;
2115
2131
  return;
2116
2132
  }
2117
- console.log(chalk6.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: bash ${scriptPath}
2118
- `));
2119
2133
  const gate2 = runGate(scriptPath);
2134
+ console.log(chalk6.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: ${gate2.runner} ${scriptPath}
2135
+ `));
2120
2136
  if (gate2.out) console.log(gate2.out);
2121
2137
  if (!gate2.ok) {
2122
2138
  console.log(
@@ -2298,7 +2314,7 @@ function checkCommand(name, command, hint) {
2298
2314
  const version = result.out.split("\n")[0];
2299
2315
  return { name, command, version, ok: true, hint };
2300
2316
  }
2301
- function getVhkVersion() {
2317
+ function getVhkVersion2() {
2302
2318
  const dir = path10.dirname(fileURLToPath(import.meta.url));
2303
2319
  const candidates = [
2304
2320
  path10.join(dir, "../package.json"),
@@ -2352,7 +2368,7 @@ ${ko.doctor.title}
2352
2368
  }
2353
2369
  }
2354
2370
  console.log("");
2355
- const vhkVersion = getVhkVersion();
2371
+ const vhkVersion = getVhkVersion2();
2356
2372
  if (vhkVersion) {
2357
2373
  console.log(chalk9.green(" \u2705 VHK") + chalk9.dim(` \u2014 v${vhkVersion}`));
2358
2374
  } else {
@@ -2709,9 +2725,19 @@ async function save() {
2709
2725
  if (process.exitCode !== 1) {
2710
2726
  console.log(chalk11.green(`
2711
2727
  \u2705 ${t("save.done", lines.length)}`));
2728
+ printNextStep({
2729
+ message: t("save.nextOkMessage"),
2730
+ command: "vhk recap",
2731
+ cursorHint: t("save.nextOkCursor")
2732
+ });
2712
2733
  } else {
2713
2734
  console.log(chalk11.green(`
2714
2735
  \u2705 ${t("save.doneLocalOnly", lines.length)}`));
2736
+ printNextStep({
2737
+ message: t("save.nextPushFailMessage"),
2738
+ command: "vhk doctor",
2739
+ cursorHint: t("save.nextPushFailCursor")
2740
+ });
2715
2741
  }
2716
2742
  } catch (err) {
2717
2743
  spinner.fail(t("save.failed"));
@@ -2831,6 +2857,11 @@ ${t("undo.recentHeader")}`));
2831
2857
  console.log(chalk12.yellow(`
2832
2858
  \u{1F4A1} ${t("undo.forcePushHint")}`));
2833
2859
  }
2860
+ printNextStep({
2861
+ message: t("undo.nextMessage"),
2862
+ command: "vhk save",
2863
+ cursorHint: t("undo.nextCursor")
2864
+ });
2834
2865
  } catch (err) {
2835
2866
  console.log(chalk12.red(`\u274C ${t("undo.failed")}`));
2836
2867
  const msg = err instanceof Error ? err.message : String(err);
@@ -2954,7 +2985,20 @@ async function status() {
2954
2985
  } else {
2955
2986
  console.log(chalk13.dim(`\u{1F4E6} ${t("status.noPackage")}`));
2956
2987
  }
2957
- console.log("");
2988
+ const hasChanges = counts.staged + counts.unstaged + counts.untracked > 0;
2989
+ if (hasChanges) {
2990
+ printNextStep({
2991
+ message: t("status.nextWithChangesMessage"),
2992
+ command: "vhk save",
2993
+ cursorHint: t("status.nextWithChangesCursor")
2994
+ });
2995
+ } else {
2996
+ printNextStep({
2997
+ message: t("status.nextCleanMessage"),
2998
+ command: "vhk goal next",
2999
+ cursorHint: t("status.nextCleanCursor")
3000
+ });
3001
+ }
2958
3002
  }
2959
3003
 
2960
3004
  // src/commands/diff.ts
@@ -3114,10 +3158,11 @@ async function mcpInit() {
3114
3158
  console.log(chalk15.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
3115
3159
  console.log(chalk15.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
3116
3160
  console.log(` ${configPath}`);
3117
- console.log(chalk15.cyan("\n\u{1F504} \uB2E4\uC74C \uB2E8\uACC4:"));
3118
- console.log(" 1. Cursor\uB97C \uC7AC\uC2DC\uC791\uD558\uC138\uC694");
3119
- console.log(" 2. Cursor \uCC44\uD305\uC5D0\uC11C vhk \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
3120
- console.log(chalk15.gray('\n\u{1F4A1} \uC608: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC54C\uB824\uC918" \u2192 Cursor\uAC00 vhk status \uD638\uCD9C'));
3161
+ printNextStep({
3162
+ message: t("mcp.nextMessage"),
3163
+ command: "vhk mcp",
3164
+ cursorHint: t("mcp.nextCursor")
3165
+ });
3121
3166
  }
3122
3167
 
3123
3168
  // src/commands/design.ts
@@ -3696,11 +3741,21 @@ async function update() {
3696
3741
  console.log(chalk21.green.bold(`
3697
3742
  \u{1F389} VHK CLI v${latest} \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
3698
3743
  console.log(chalk21.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
3744
+ printNextStep({
3745
+ message: t("update.nextOkMessage"),
3746
+ command: "vhk --version",
3747
+ cursorHint: t("update.nextOkCursor")
3748
+ });
3699
3749
  } else {
3700
3750
  updateSpinner.fail("\uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
3701
3751
  console.log(chalk21.red(upd.err.slice(0, 300)));
3702
3752
  console.log(chalk21.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
3703
3753
  console.log(chalk21.gray(` npm update -g ${PACKAGE}`));
3754
+ printNextStep({
3755
+ message: t("update.nextFailMessage"),
3756
+ command: "vhk doctor",
3757
+ cursorHint: t("update.nextFailCursor")
3758
+ });
3704
3759
  }
3705
3760
  }
3706
3761
 
@@ -4410,28 +4465,6 @@ async function runNaturalLanguageRoute(input) {
4410
4465
  await dispatchNlpRoute(route, input);
4411
4466
  }
4412
4467
 
4413
- // src/lib/version.ts
4414
- import { existsSync as existsSync15 } from "fs";
4415
- import { dirname as dirname3, join as join8 } from "path";
4416
- import { fileURLToPath as fileURLToPath4 } from "url";
4417
- function getVhkVersion2() {
4418
- const dir = dirname3(fileURLToPath4(import.meta.url));
4419
- for (const pkgPath of [
4420
- join8(dir, "../../package.json"),
4421
- join8(dir, "../package.json")
4422
- ]) {
4423
- try {
4424
- if (existsSync15(pkgPath)) {
4425
- const pkg = readJsonFile(pkgPath);
4426
- if (pkg.version) return pkg.version;
4427
- }
4428
- } catch {
4429
- continue;
4430
- }
4431
- }
4432
- return "0.0.0";
4433
- }
4434
-
4435
4468
  // src/commands/agent.ts
4436
4469
  import chalk27 from "chalk";
4437
4470
  function activeGoalId() {
@@ -4545,7 +4578,7 @@ var KO_ALIASES = {
4545
4578
  learn: "\uAD50\uD6C8",
4546
4579
  resume: "\uC7AC\uAC1C"
4547
4580
  };
4548
- program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version(getVhkVersion2());
4581
+ 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(getVhkVersion());
4549
4582
  program.configureHelp({
4550
4583
  formatHelp(cmd, helper) {
4551
4584
  if (cmd.parent) {
@@ -4591,10 +4624,10 @@ program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\
4591
4624
  await status();
4592
4625
  });
4593
4626
  program.command("diff").alias("\uBCC0\uACBD").alias("\uCC28\uC774").description("Git \uBCC0\uACBD\uC0AC\uD56D \uD55C\uAD6D\uC5B4 \uC694\uC57D (staged / unstaged / \uC0C8 \uD30C\uC77C)").action(diff);
4594
- program.command("mcp").description("MCP \uC11C\uBC84 \uC2DC\uC791 (Cursor \uB4F1 MCP \uD074\uB77C\uC774\uC5B8\uD2B8\uC6A9)").action(async () => {
4627
+ program.command("mcp").description("MCP \uC11C\uBC84 \uC2DC\uC791 (24 tool stdio \u2014 Cursor\xB7Claude Desktop \uB4F1)").action(async () => {
4595
4628
  await startMcpServer();
4596
4629
  });
4597
- program.command("mcp-init").alias("mcp\uC124\uC815").description("Cursor MCP \uC5F0\uB3D9 \uC124\uC815 \uC790\uB3D9 \uC0DD\uC131 (.cursor/mcp.json)").action(async () => {
4630
+ program.command("mcp-init").alias("mcp\uC124\uC815").description("Cursor\xB7Claude Desktop MCP \uC5F0\uB3D9 \uC124\uC815 \uC790\uB3D9 \uC0DD\uC131 (.cursor/mcp.json)").action(async () => {
4598
4631
  await mcpInit();
4599
4632
  });
4600
4633
  program.command("deploy").alias("\uBC30\uD3EC").description("\uD504\uB85C\uB355\uC158 \uBC30\uD3EC (Vercel/Netlify/Cloudflare \uC790\uB3D9 \uAC10\uC9C0)").action(async () => {
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-3HGOQLRT.js";
4
+ } from "../chunk-6S3JYYZ3.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": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Vibe Harness Kit β€” λ°”μ΄λΈŒμ½”λ”© 풀사이클 CLI",
5
5
  "bin": {
6
6
  "vhk": "dist/index.js",