@byh3071/vhk 1.3.1 โ†’ 1.5.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/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  id: vhk-readme
3
3
  date: 2026-05-28
4
- tags: [vhk, cli, readme, v1.3.0, ga]
4
+ tags: [vhk, cli, readme, v1.5.0, ga]
5
5
  ---
6
6
 
7
7
  # ๐Ÿ”ง VHK โ€” Vibe Harness Kit
8
8
 
9
- > ๐ŸŽ‰ **v1.3.0** โ€” ๋ฐ”์ด๋ธŒ์ฝ”๋”์˜ ์˜ฌ์ธ์› CLI. ์ปจํ…์ŠคํŠธ + ์ž์œจ ํ•˜๋„ค์Šค.
9
+ > ๐ŸŽ‰ **v1.5.0** โ€” **๊ทœ์น™์€ ํ•œ ๋ฒŒ๋กœ CursorยทClaudeยทWindsurfยทCopilotยทAntigravity์—, ๋งฅ๋ฝ์€ ํด๋ผ์šฐ๋“œ๋กœ.**
10
+ > ๋„๊ตฌยท๊ธฐ๊ธฐ๋ฅผ ์˜ฎ๊ฒจ๋„ `vhk` ๋ช…๋ น์œผ๋กœ ๊ทธ๋Œ€๋กœ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. (ํฌํ„ฐ๋นŒ๋ฆฌํ‹ฐ)
10
11
  >
11
12
  > AI ์ฝ”๋”ฉ ์—์ด์ „ํŠธ๋ฅผ ๋ถ€๋ฆฌ๋Š” ์‚ฌ๋žŒ์„ ์œ„ํ•œ **ํ•œ๊ตญ์–ด ํ’€์‚ฌ์ดํด CLI**.
12
13
  >
@@ -14,6 +15,20 @@ tags: [vhk, cli, readme, v1.3.0, ga]
14
15
 
15
16
  ๋ช…๋ น์–ด๋ฅผ ์™ธ์šฐ์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. `vhk`๋งŒ ์น˜๋ฉด ๋ฉ”๋‰ด๊ฐ€ ๋‚˜์˜ค๊ณ , ํ•œ๊ตญ์–ด๋กœ ๋งํ•ด๋„ ์•Œ์•„๋“ฃ์Šต๋‹ˆ๋‹ค.
16
17
 
18
+ ## ์™œ VHK? โ€” ํฌํ„ฐ๋นŒ๋ฆฌํ‹ฐ
19
+
20
+ AI ์ฝ”๋”ฉ ๋„๊ตฌ๋Š” ์ €๋งˆ๋‹ค ๊ทœ์น™ ํŒŒ์ผ์ด ๋‹ค๋ฅด๊ณ (`.cursorrules`ยท`CLAUDE.md`ยท`.windsurfrules`ยท`.github/copilot-instructions.md`ยท`.agents/rules/`โ€ฆ), ์ปดํ“จํ„ฐ๋ฅผ ๋ฐ”๊พธ๋ฉด ํ”„๋กœ์ ํŠธ ๋งฅ๋ฝ์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋ชจ์๋‹ˆ๋‹ค. VHK๋Š” ์ด ๋‘˜์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
21
+
22
+ | ๋ฌธ์ œ | VHK ํ•ด๊ฒฐ | ๋ช…๋ น |
23
+ |------|----------|------|
24
+ | ๋„๊ตฌ๋งˆ๋‹ค ๊ทœ์น™ ํŒŒ์ผ์ด ๋”ฐ๋กœ ๋…ผ๋‹ค | `RULES.md` ํ•œ ๋ฒŒ โ†’ CursorยทClaudeยทWindsurfยทCopilotยทAntigravity ๊ทœ์น™ ๋™์‹œ ์ƒ์„ฑ | `vhk sync` |
25
+ | ์ปดํ“จํ„ฐยทํ™˜๊ฒฝ ๋ฐ”๋€Œ๋ฉด ๋งฅ๋ฝ ์œ ์‹ค | `.vhk/` ๋งฅ๋ฝ์„ GitHub gist๋กœ ๋ฐฑ์—…ยท๋ณต์› | `vhk cloud push` / `pull` |
26
+ | ์ƒˆ ํ”„๋กœ์ ํŠธ ์„ธํŒ… ๋ฐ˜๋ณต | ์œ ํ˜•๋ณ„ ๋ฌธ์„œยท๊ทœ์น™ยท๋งฅ๋ฝ ๋ผˆ๋Œ€๋ฅผ ํ•œ ๋ฒˆ์— | `vhk init` |
27
+
28
+ > ๊ทœ์น™ยท๋งฅ๋ฝ์€ **์ž๋™์ด ์•„๋‹ˆ๋ผ ๋ช…๋ น์œผ๋กœ** ๋™๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค(ํ•œ ์ค„์ด๋ฉด ์ถฉ๋ถ„). ๊ฐœ์ธ ๋ฉ”๋ชจ(`memory.json`)ยท์ฐธ๊ณ ๋งํฌ(`refs.json`)๋Š” ํ”„๋ผ์ด๋ฒ„์‹œ ์œ„ํ•ด ๊ธฐ๋ณธ ์ œ์™ธ, ์ƒˆ PC์˜ ์ฝ”๋“œ ์ž์ฒด๋Š” `git clone` ์œผ๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค.
29
+ >
30
+ > โ„น๏ธ WindsurfยทCopilotยทAntigravity ์ถœ๋ ฅ ๊ฒฝ๋กœยทํฌ๋งท์€ ๊ฐ ๋„๊ตฌ์˜ **๊ณต์‹ ๋ฌธ์„œ ๊ธฐ์ค€**์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค(`.windsurfrules` ยท `.github/copilot-instructions.md` ยท `.agents/rules/`). Antigravity ๋Š” ํŒŒ์ผ๋‹น 12,000์ž ์ œํ•œ์ด ์žˆ์–ด ์ดˆ๊ณผ ์‹œ ์•ˆ์ „ํ•˜๊ฒŒ ์ ˆ์‚ญํ•˜๊ณ  ์ „์ฒด๋Š” `RULES.md` ์— ๋‚จ์Šต๋‹ˆ๋‹ค.
31
+
17
32
  ## 3๋ถ„ ์•ˆ์— ์‹œ์ž‘ํ•˜๊ธฐ (Getting Started)
18
33
 
19
34
  ### 1. ์„ค์น˜
@@ -40,12 +55,12 @@ vhk start
40
55
  vhk gate # ํ€ต 5๋ฌธํ•ญ โ€” GO / ๋‹ค๋“ฌ๊ธฐ / ๋‹ค๋ฅธ ์•„์ด๋””์–ด
41
56
  ```
42
57
 
43
- ### 3. v1.3 ํ•ต์‹ฌ ๊ธฐ๋Šฅ ํ•œ๋ˆˆ์—
58
+ ### 3. ๊ทธ ์™ธ ๊ธฐ๋Šฅ ํ•œ๋ˆˆ์— (v1.5)
44
59
 
45
60
  | ๊ธฐ๋Šฅ | ํ•œ ์ค„ ์š”์•ฝ | ์ง„์ž… ๋ช…๋ น |
46
61
  |------|-----------|-----------|
47
62
  | ๐ŸŽฏ **Goals ์ฒด๊ณ„** | ๋‹จ๊ณ„๋ณ„ ๋ฏธ์…˜ + ๊ฒŒ์ดํŠธ ์Šคํฌ๋ฆฝํŠธ๋กœ AI๊ฐ€ ๋ชฉํ‘œ๋ฅผ ์Šค์Šค๋กœ ์ถ”์  | `vhk goal init` |
48
- | โ–ถ๏ธ **์ž์œจ ๋ฃจํ”„** | `goal next โ†’ ์ž‘์—… โ†’ goal check โ†’ goal done`. FAIL 3ํšŒ๋ฉด ์ž๋™ ๋ธ”๋กœ์ปค | `vhk goal next` |
63
+ | โ–ถ๏ธ **์ž์œจ ๋ฃจํ”„** | `goal next โ†’ ์ž‘์—… โ†’ goal check โ†’ goal done`. FAIL ์‹œ `vhk blocker` ์ˆ˜๋™ ๊ธฐ๋ก โ†’ ๋ธ”๋กœ์ปค 3๊ฑด ๋ˆ„์  ์‹œ HARD_STOP ์ž๋™ | `vhk goal next` |
49
64
  | ๐Ÿšง **HARD_STOP ์•ˆ์ „์žฅ์น˜** | ๋ธ”๋กœ์ปค 3๊ฑด ๋ˆ„์  โ†’ `.vhk/HARD_STOP` ํŠธ๋ฆฝ์™€์ด์–ด. `vhk resume --confirm` ๋งŒ ํ•ด์ œ | `vhk blocker "<์ฆ์ƒ>"` |
50
65
  | ๐Ÿ”Œ **MCP 24 tool** | CursorยทClaude Desktop ๋“ฑ์—์„œ vhk๋ฅผ ์ฑ„ํŒ…์œผ๋กœ ํ˜ธ์ถœ | `vhk mcp-init` |
51
66
  | ๐Ÿ“‹ **์ปจํ…์ŠคํŠธ ์˜์†ํ™”** | `.vhk/context.md` + `memory.json` + `brief.md` ๋กœ ์„ธ์…˜ ๊ฐ„ ๋งฅ๋ฝ ์œ ์ง€ | `vhk context` |
@@ -115,11 +130,13 @@ vhk ๊ธฐํš ๋๋‚ฌ๊ณ  ๋ฐ”๋กœ ์‹œ์ž‘
115
130
  | `vhk gate` | `๊ฒ€์ฆ`, `์•„์ด๋””์–ด` | ์•„์ด๋””์–ด ๊ฒ€์ฆ (ํ€ต 5๋ฌธํ•ญ / ํ’€ 13๋ฌธํ•ญ / ์Šคํ‚ต) |
116
131
  | `vhk init` | `์‹œ์ž‘`, `๋งŒ๋“ค๊ธฐ` | ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™” + ํ•˜๋„ค์Šค ์ƒ์„ฑ |
117
132
  | `vhk recap` | `์ •๋ฆฌ`, `์˜ค๋Š˜` | Git ๋ณ€๊ฒฝ โ†’ `docs/log/` ์„ธ์…˜ ๋กœ๊ทธ |
118
- | `vhk sync` | `๊ทœ์น™`, `๋งž์ถ”๊ธฐ` | RULES.md โ†’ `.cursorrules` + CLAUDE.md |
133
+ | `vhk sync` | `๊ทœ์น™`, `๋งž์ถ”๊ธฐ` | RULES.md โ†’ `.cursorrules` + CLAUDE.md + `.windsurfrules` + `.github/copilot-instructions.md` + `.agents/rules/vhk-rules.md` (5๊ฐœ) |
119
134
  | `vhk check` | `์ ๊ฒ€`, `๋ฆฐํŠธ` | RULES.md ๊ทœ์น™ ์œ„๋ฐ˜ ๊ฒ€์‚ฌ |
120
135
  | `vhk secure` | `๋ณด์•ˆ` | ์‹œํฌ๋ฆฟยทํ‚ค ์œ ์ถœ ์Šค์บ” (`scan` / `์Šค์บ”` ๋™์ผ). **CRITICAL/HIGH ๋ฐœ๊ฒฌ ์‹œ exit code 1** (CI์šฉ) |
121
136
  | `vhk ship` | `์ถœํ•˜` | ๋ฐฐํฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + ํšŒ๊ณ  + ๋นŒ๋“œ ๋กœ๊ทธ |
122
137
  | `vhk doctor` | `ํ™˜๊ฒฝ`, `์ง„๋‹จ` | Node / npm / pnpm / Git ํ™˜๊ฒฝ ์ ๊ฒ€ |
138
+ | `vhk cloud push` | `ํด๋ผ์šฐ๋“œ`, `์˜ฌ๋ฆฌ๊ธฐ` | `.vhk/` ๋ฅผ GitHub secret gist ๋กœ ๋ฐฑ์—… (gh CLI ์ธ์ฆ ์‚ฌ์šฉ) |
139
+ | `vhk cloud pull` | `๋‚ด๋ฆฌ๊ธฐ` | gist ์—์„œ `.vhk/` ๋ณต์› (`vhk cloud pull <gistId>` ๋˜๋Š” cloud.json) |
123
140
  | `vhk save` | `์ €์žฅ`, `์ปค๋ฐ‹` | git add ยท commit ยท push ํ•œ ๋ฒˆ์— |
124
141
  | `vhk undo` | `๋˜๋Œ๋ฆฌ๊ธฐ`, `์ทจ์†Œ` | ์ตœ๊ทผ ์ปค๋ฐ‹ soft reset (๋ณ€๊ฒฝ์€ staged ์œ ์ง€) |
125
142
  | `vhk diff` | `๋ณ€๊ฒฝ`, `์ฐจ์ด` | staged / unstaged / ์ƒˆ ํŒŒ์ผ ์š”์•ฝ (์ค„ ์ˆ˜ ํ•ฉ๊ณ„๋Š” trackedยทHEAD ๊ธฐ์ค€) |
@@ -298,10 +315,10 @@ vhk ref open 1 # 1๋ฒˆ ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ๋ธŒ๋ผ์šฐ์ €๋กœ ์—ด๊ธฐ
298
315
 
299
316
  | ๊ธฐ๋Šฅ | ์„ค๋ช… |
300
317
  |------|------|
301
- | **MCP ์„œ๋ฒ„** | `vhk mcp` โ€” stdio MCP ์„œ๋ฒ„ ์ฒซ ๋„์ž… (v0.6.0 ๋‹น์‹œ 8๊ฐœ ๋„๊ตฌ โ€” save/undo/status/diff/ship/doctor/check/recap). ํ˜„์žฌ v1.3 ๊ธฐ์ค€ **24๊ฐœ** ๋กœ ํ™•์žฅ โ€” ์œ„ "Cursor์™€ MCP๋กœ ์—ฐ๋™ํ•˜๊ธฐ" ์„น์…˜ ์ฐธ์กฐ |
318
+ | **MCP ์„œ๋ฒ„** | `vhk mcp` โ€” stdio MCP ์„œ๋ฒ„ ์ฒซ ๋„์ž… (v0.6.0 ๋‹น์‹œ 8๊ฐœ ๋„๊ตฌ โ€” save/undo/status/diff/ship/doctor/check/recap). ํ˜„์žฌ v1.5 ๊ธฐ์ค€ **24๊ฐœ** ๋กœ ํ™•์žฅ โ€” ์œ„ "Cursor์™€ MCP๋กœ ์—ฐ๋™ํ•˜๊ธฐ" ์„น์…˜ ์ฐธ์กฐ |
302
319
  | **mcp-init** | `vhk mcp-init` โ€” Cursor `.cursor/mcp.json` ์ž๋™ ์ƒ์„ฑ. ์žฌ์‹œ์ž‘ ํ•œ ๋ฒˆ์œผ๋กœ ์—ฐ๋™ ์™„๋ฃŒ |
303
320
  | **์ž์—ฐ์–ด ๋ผ์šฐํŒ… ํ™•์žฅ** | `vhk mcp์„ค์ •` โ†’ `vhk mcp-init` ๋ณ„์นญ |
304
- | **๋ณด์•ˆ** | MCP save ๋„๊ตฌ์˜ shell injection ์ฐจ๋‹จ โ€” ๋ชจ๋“  git ํ˜ธ์ถœ์— `execFileSync` ์‚ฌ์šฉ |
321
+ | **๋ณด์•ˆ** | MCP save ๋„๊ตฌ์˜ shell injection ์ฐจ๋‹จ โ€” ๋ชจ๋“  git ํ˜ธ์ถœ์— shell ๋ฏธ๊ฒฝ์œ  `safeExecFile` ์‚ฌ์šฉ |
305
322
 
306
323
  ## v0.5.3 ํ•˜์ด๋ผ์ดํŠธ
307
324
 
@@ -340,8 +357,26 @@ vhk ref open 1 # 1๋ฒˆ ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ๋ธŒ๋ผ์šฐ์ €๋กœ ์—ด๊ธฐ
340
357
  - `docs/PRD.md`, `docs/ARCHITECTURE.md`
341
358
  - `docs/adr/`, `docs/log/`, `docs/troubleshooting/`
342
359
  - `COMMANDS.md`, `BACKLOG.md` (ํ”„๋กœ์ ํŠธ ์œ ํ˜•์— ๋”ฐ๋ผ)
360
+ - `.vhk/README.md` + `.vhk/context.md` (์œ ํ˜•๋ณ„ ์”จ์•— โ€” ๊ทœ๊ฒฉ: [`docs/spec.md`](docs/spec.md))
361
+ - `.vhk/.gitignore` + `.vhkignore` (๋กœ์ปฌ ์ „์šฉยทํด๋ผ์šฐ๋“œ ์ œ์™ธ ๊ทœ์น™)
362
+ - ๋ฃจํŠธ `.gitignore` (`.env`ยท`node_modules`ยท`dist` ๋ณดํ˜ธ โ€” ๊ธฐ์กด ํŒŒ์ผ์€ ๋ณด์กดํ•˜๊ณ  ๋ˆ„๋ฝ๋ถ„๋งŒ ์ถ”๊ฐ€)
343
363
  - `package.json` scripts: `save`, `check`, `scan`, `recap`, `ship`, `doctor` โ†’ `vhk` ํ˜ธ์ถœ
344
364
 
365
+ ## ํด๋ผ์šฐ๋“œ ๋ฐฑ์—… (vhk cloud)
366
+
367
+ `.vhk/` ํ”„๋กœ์ ํŠธ ๋งฅ๋ฝ์„ GitHub **secret gist** ๋กœ ๋ฐฑ์—…ยท๋ณต์›ํ•ฉ๋‹ˆ๋‹ค. ์ปดํ“จํ„ฐ๋ฅผ ๋ฐ”๊ฟ”๋„
368
+ ๊ทœ์น™ยท๋งฅ๋ฝ์ด ๋”ฐ๋ผ์˜ต๋‹ˆ๋‹ค. ๊ทœ๊ฒฉ์€ [`docs/spec.md`](docs/spec.md) ์ฐธ์กฐ.
369
+
370
+ ```bash
371
+ vhk cloud push # .vhk/ โ†’ secret gist ๋ฐฑ์—… (gist id ๋Š” .vhk/cloud.json ์— ์ €์žฅ)
372
+ vhk cloud pull # cloud.json ์˜ gist ์—์„œ ๋ณต์›
373
+ vhk cloud pull <gistId> # ์ƒˆ ํ™˜๊ฒฝ์—์„œ gist id ๋กœ ์ง์ ‘ ๋ณต์›
374
+ ```
375
+
376
+ - **์ธ์ฆ:** `gh` CLI ์‚ฌ์šฉ (`gh auth login`, gist ๊ถŒํ•œ). ์ฝ”๋“œยท์„ค์ •์— ํ† ํฐ์„ ์ €์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
377
+ - **ํ”„๋ผ์ด๋ฒ„์‹œ:** gist ๋Š” secret(๋น„๊ณต๊ฐœ). ๊ฐœ์ธ ๋ฉ”๋ชจ(`memory.json`)ยท์ฐธ๊ณ ๋งํฌ(`refs.json`)ยท
378
+ `HARD_STOP` ์€ ๊ธฐ๋ณธ ์ œ์™ธ๋ฉ๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ์ œ์™ธ๋Š” ๋ฃจํŠธ `.vhkignore` ์— ํ•œ ์ค„์”ฉ ์ ์œผ์„ธ์š”.
379
+
345
380
  ## ์ž์—ฐ์–ด ์˜ˆ์‹œ
346
381
 
347
382
  | ๋งํ•˜๋ฉด | ์‹คํ–‰ |
@@ -696,7 +696,9 @@ var ko = {
696
696
  gitHintCommand: 'git init && git add . && git commit -m "feat: \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791"',
697
697
  startDev: "\uC774\uC81C \uAC1C\uBC1C\uD574 \uBCF4\uC138\uC694! \u{1F680}",
698
698
  commandsMdDone: "\u{1F4CB} COMMANDS.md \uC0DD\uC131",
699
- scriptsDone: "\u{1F4E6} package.json scripts \uCD94\uAC00"
699
+ scriptsDone: "\u{1F4E6} package.json scripts \uCD94\uAC00",
700
+ gitignoreCreated: "\u{1F512} .gitignore \uC0DD\uC131 (.env\xB7node_modules\xB7dist \uC81C\uC678)",
701
+ gitignoreUpdated: "\u{1F512} .gitignore \uBCF4\uAC15 (\uB204\uB77D \uD56D\uBAA9 \uCD94\uAC00)"
700
702
  },
701
703
  recap: {
702
704
  title: "\u{1F4DD} \uC624\uB298 \uD55C \uC77C \uC815\uB9AC",
@@ -752,8 +754,25 @@ var ko = {
752
754
  noRules: "\u26A0\uFE0F RULES.md \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694.",
753
755
  cursorrulesDone: "\u2705 .cursorrules \uB9DE\uCDA4 \uC644\uB8CC",
754
756
  claudeDone: "\u2705 CLAUDE.md \uB9DE\uCDA4 \uC644\uB8CC",
757
+ windsurfDone: "\u2705 .windsurfrules \uB9DE\uCDA4 \uC644\uB8CC",
758
+ copilotDone: "\u2705 .github/copilot-instructions.md \uB9DE\uCDA4 \uC644\uB8CC",
759
+ antigravityDone: "\u2705 .agents/rules/vhk-rules.md \uB9DE\uCDA4 \uC644\uB8CC",
760
+ antigravityTruncated: "Antigravity 12,000\uC790 \uC81C\uD55C\uC73C\uB85C \uC77C\uBD80 \uC808\uC0AD\uB428 \u2014 \uC804\uCCB4\uB294 RULES.md \uCC38\uC870",
755
761
  done: "\u{1F504} \uB9DE\uCD94\uAE30 \uC644\uB8CC!"
756
762
  },
763
+ cloud: {
764
+ pushTitle: "\u2601\uFE0F .vhk \uD074\uB77C\uC6B0\uB4DC \uBC31\uC5C5 (gist \uC62C\uB9AC\uAE30)",
765
+ pullTitle: "\u2601\uFE0F .vhk \uD074\uB77C\uC6B0\uB4DC \uBCF5\uC6D0 (gist \uB0B4\uB9AC\uAE30)",
766
+ noGh: "gh CLI \uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
767
+ noAuth: "gh \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4 (gist \uAD8C\uD55C).",
768
+ noVhkDir: ".vhk/ \uD3F4\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. vhk init \uB610\uB294 vhk context \uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.",
769
+ nothingToSync: "\uBC31\uC5C5\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (.vhkignore \uB85C \uBAA8\uB450 \uC81C\uC678\uB428).",
770
+ noGistId: "\uBCF5\uC6D0\uD560 gist id \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
771
+ pushDone: "\u2705 \uD074\uB77C\uC6B0\uB4DC \uBC31\uC5C5 \uC644\uB8CC",
772
+ pullDone: "\u2705 \uD074\uB77C\uC6B0\uB4DC \uBCF5\uC6D0 \uC644\uB8CC",
773
+ pushFail: "\u274C \uBC31\uC5C5 \uC2E4\uD328",
774
+ pullFail: "\u274C \uBCF5\uC6D0 \uC2E4\uD328"
775
+ },
757
776
  ship: {
758
777
  title: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
759
778
  checklist: "\u{1F4CB} \uBC30\uD3EC \uC804 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
@@ -866,7 +885,9 @@ var ko = {
866
885
  nextTitle: "\u27A1\uFE0F \uB2E4\uC74C Goal",
867
886
  initTitle: "\u{1F3D7}\uFE0F goals/ \uAD6C\uC870 \uC2A4\uCE90\uD3F4\uB529",
868
887
  checkTitle: "\u2705 Goal \uAC8C\uC774\uD2B8 \uAC80\uC99D",
869
- doneTitle: "\u{1F3C1} Goal \uC644\uB8CC \uCC98\uB9AC"
888
+ doneTitle: "\u{1F3C1} Goal \uC644\uB8CC \uCC98\uB9AC",
889
+ duplicateId: (ids) => `\u26A0 \uC911\uBCF5\uB41C goal id: ${ids} \u2014 \uAC19\uC740 id \uD30C\uC77C\uC774 \uC5EC\uB7EC \uAC1C\uBA74 \uCCAB \uB9E4\uCE58\uB9CC \uC0AC\uC6A9\uB429\uB2C8\uB2E4. id \uB97C \uC720\uC77C\uD558\uAC8C \uACE0\uCE58\uC138\uC694.`,
890
+ notFound: (id) => `goal id ${id} \uC5C6\uC74C \u2014 vhk goal list \uB85C \uD655\uC778\uD558\uC138\uC694.`
870
891
  },
871
892
  agent: {
872
893
  blockerTitle: "\u{1F6D1} Blocker \uAE30\uB85D",
@@ -2134,9 +2155,11 @@ async function startMcpServer() {
2134
2155
  }
2135
2156
 
2136
2157
  export {
2158
+ __toESM,
2137
2159
  ko,
2138
2160
  t,
2139
2161
  printNextStep,
2162
+ require_ignore,
2140
2163
  printSecurityWarnings,
2141
2164
  filterTrackedPaths,
2142
2165
  readJsonFile,
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  MAX_SCAN_FILE_BYTES,
4
4
  MAX_SECRET_FINDINGS,
5
+ __toESM,
5
6
  audit,
6
7
  deploy,
7
8
  env,
@@ -14,11 +15,12 @@ import {
14
15
  printSecurityWarnings,
15
16
  publish,
16
17
  readJsonFile,
18
+ require_ignore,
17
19
  safeExecFile,
18
20
  scanProjectForSecrets,
19
21
  startMcpServer,
20
22
  t
21
- } from "./chunk-6S3JYYZ3.js";
23
+ } from "./chunk-4KWZANQG.js";
22
24
 
23
25
  // src/index.ts
24
26
  import { Command, Help } from "commander";
@@ -40,6 +42,19 @@ function matchesKeywords(text, command) {
40
42
  return keywords.some((kw) => text.includes(kw.toLowerCase()));
41
43
  }
42
44
  var RULES = [
45
+ // ์˜๋ฌธ `vhk cloud push|pull [id]` ์€ commander ๊ฐ€ ์ง์ ‘ ์ฒ˜๋ฆฌ(๊ฐ€๋กœ์ฑ„๊ธฐ ๊ธˆ์ง€) โ€” ํ•œ๊ตญ์–ด ํ‘œํ˜„๋งŒ ๋งค์นญ.
46
+ {
47
+ command: "cloud-pull",
48
+ explanation: "\uD074\uB77C\uC6B0\uB4DC\uC5D0\uC11C .vhk \uBCF5\uC6D0 (vhk cloud pull)",
49
+ confidence: "high",
50
+ test: (t2) => (/(ํด๋ผ์šฐ๋“œ|gist)\s*(์—์„œ)?\s*(๋ณต์›|๋‚ด๋ ค๋ฐ›?|๋‚ด๋ฆฌ|๋ฐ›์•„)/.test(t2) || /(\.?vhk\s*)?(๋ณต์›ํ•ด|๋ณต๊ตฌํ•ด|๋ณต์›\s*ํ•˜|๋ณต๊ตฌ\s*ํ•˜)/.test(t2)) && !/๋ฐฑ์—…|์˜ฌ๋ ค|์˜ฌ๋ฆฌ/.test(t2)
51
+ },
52
+ {
53
+ command: "cloud-push",
54
+ explanation: ".vhk \uB97C \uD074\uB77C\uC6B0\uB4DC\uC5D0 \uBC31\uC5C5 (vhk cloud push)",
55
+ confidence: "high",
56
+ test: (t2) => (/(ํด๋ผ์šฐ๋“œ|gist)\s*(์—)?\s*(๋ฐฑ์—…|์˜ฌ๋ ค|์˜ฌ๋ฆฌ)/.test(t2) || /(\.?vhk\s*)?๋ฐฑ์—…\s*ํ•ด|(\.?vhk\s*)?๋ฐฑ์—…ํ•˜/.test(t2)) && !/๋ณต์›|๋‚ด๋ ค|๋‚ด๋ฆฌ|๋ณต๊ตฌ/.test(t2)
57
+ },
43
58
  {
44
59
  command: "start",
45
60
  explanation: "\uB178\uC158\uC5D0\uC11C \uAC00\uC838\uC640 \uC0C8 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 \uB9C8\uBC95\uC0AC (vhk start --from-notion)",
@@ -177,7 +192,7 @@ var RULES = [
177
192
  command: "save",
178
193
  explanation: "Git\uC5D0 \uC800\uC7A5 (vhk \uC800\uC7A5)",
179
194
  confidence: "high",
180
- test: (t2) => (matchesKeywords(t2, "save") || /๊นƒํ—ˆ๋ธŒ|github/.test(t2)) && !/์ •๋ฆฌ|recap|๋˜๋Œ|์ทจ์†Œ|rollback|reset|๋ฆฌ์…‹|๋กค๋ฐฑ|์›๋ž˜๋Œ€๋กœ/.test(t2)
195
+ test: (t2) => (matchesKeywords(t2, "save") || /๊นƒํ—ˆ๋ธŒ|github/.test(t2)) && !/์ •๋ฆฌ|recap|๋˜๋Œ|์ทจ์†Œ|rollback|reset|๋ฆฌ์…‹|๋กค๋ฐฑ|์›๋ž˜๋Œ€๋กœ|ํด๋ผ์šฐ๋“œ|cloud|gist/.test(t2)
181
196
  },
182
197
  {
183
198
  command: "recap",
@@ -352,6 +367,8 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
352
367
  "\uAE30\uC5B5",
353
368
  "brief",
354
369
  "\uBE0C\uB9AC\uD551",
370
+ "cloud",
371
+ "\uD074\uB77C\uC6B0\uB4DC",
355
372
  "goal",
356
373
  "\uBAA9\uD45C",
357
374
  "blocker",
@@ -383,7 +400,7 @@ function detectNaturalLanguageInput(argv) {
383
400
  }
384
401
 
385
402
  // src/lib/nlp-run.ts
386
- import chalk26 from "chalk";
403
+ import chalk27 from "chalk";
387
404
  import inquirer11 from "inquirer";
388
405
 
389
406
  // src/commands/gate.ts
@@ -738,6 +755,70 @@ function COMMANDS_MD_TEMPLATE() {
738
755
  ].join("\n");
739
756
  }
740
757
 
758
+ // src/templates/vhk-dir.ts
759
+ function VHK_README_TEMPLATE() {
760
+ return [
761
+ "# `.vhk/` \u2014 VHK runtime state",
762
+ "",
763
+ "\uC774 \uB514\uB809\uD1A0\uB9AC\uB294 VHK\uAC00 \uD504\uB85C\uC81D\uD2B8\uBCC4 \uC0C1\uD0DC\uB97C \uC800\uC7A5\uD558\uB294 \uACF3\uC785\uB2C8\uB2E4.",
764
+ "\uC804\uCCB4 \uADDC\uACA9\uC740 `docs/spec.md` (spec_version 1.0) \uCC38\uC870.",
765
+ "",
766
+ "## \uD2B8\uB798\uD0B9 \uC815\uCC45",
767
+ "",
768
+ "| \uD30C\uC77C | \uD2B8\uB798\uD0B9 | \uC6A9\uB3C4 |",
769
+ "| --- | --- | --- |",
770
+ "| `README.md` | \u2705 | \uBCF8 \uC548\uB0B4 |",
771
+ "| `context.md` | \u2705 | \uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D (`vhk context` \uB85C \uAC31\uC2E0) |",
772
+ "| `brief.md` | \u2705 | \uC0C1\uD0DC \uC694\uC57D \uBE0C\uB9AC\uD551 (`vhk brief`) |",
773
+ "| `memory.json` | \u274C \uB85C\uCEEC \uC804\uC6A9 | \uC758\uC0AC\uACB0\uC815 \uBA54\uBAA8 (`vhk memory add`) |",
774
+ "| `refs.json` | \u274C \uB85C\uCEEC \uC804\uC6A9 | \uCC38\uACE0 URL (`vhk ref add`) |",
775
+ "| `HARD_STOP` | \u274C \uB85C\uCEEC \uC804\uC6A9 | \uC874\uC7AC\uD558\uBA74 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC989\uC2DC \uC911\uB2E8 |",
776
+ "",
777
+ "> `memory.json`\xB7`refs.json` \uC740 \uAC1C\uC778 \uBA54\uBAA8 \uB178\uCD9C \uBC29\uC9C0\uB97C \uC704\uD574 `.gitignore` \uC5D0 \uB4F1\uB85D\uB429\uB2C8\uB2E4.",
778
+ "> `HARD_STOP` \uD574\uC81C\uB294 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4.",
779
+ ""
780
+ ].join("\n");
781
+ }
782
+ function VHK_GITIGNORE_TEMPLATE() {
783
+ return [
784
+ "# VHK \uB85C\uCEEC \uC804\uC6A9 \u2014 \uAC1C\uC778 \uBA54\uBAA8/\uCC38\uACE0\uB9C1\uD06C/\uC548\uC804\uC2E0\uD638 (docs/spec.md \uD2B8\uB798\uD0B9 \uC815\uCC45)",
785
+ "memory.json",
786
+ "refs.json",
787
+ "HARD_STOP",
788
+ ""
789
+ ].join("\n");
790
+ }
791
+ function VHK_IGNORE_TEMPLATE() {
792
+ return [
793
+ "# vhk cloud push \uBC31\uC5C5\uC5D0\uC11C \uC81C\uC678\uD560 .vhk/ \uD30C\uC77C (\uD55C \uC904\uC5D0 \uD558\uB098)",
794
+ "# \uAE30\uBCF8 \uC81C\uC678(\uC790\uB3D9): memory.json, refs.json, HARD_STOP, cloud.json, .gitignore",
795
+ "# \uC608) \uC544\uB798 \uC8FC\uC11D\uC744 \uD480\uBA74 brief.md \uB3C4 \uBC31\uC5C5\uC5D0\uC11C \uC81C\uC678\uB429\uB2C8\uB2E4.",
796
+ "# brief.md",
797
+ ""
798
+ ].join("\n");
799
+ }
800
+ function VHK_CONTEXT_SEED(name, type, stack) {
801
+ const stackList = stack.map((s) => "- " + s).join("\n");
802
+ return [
803
+ "# " + name + " \u2014 \uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D",
804
+ "",
805
+ "> \u26A1 \uC774 \uD30C\uC77C\uC740 vhk init \uC774 \uC0DD\uC131\uD55C \uC528\uC557\uC785\uB2C8\uB2E4. `vhk context` \uB85C \uAC31\uC2E0\uD558\uC138\uC694.",
806
+ "",
807
+ "## \uD504\uB85C\uC81D\uD2B8 \uC720\uD615",
808
+ "- " + type,
809
+ "",
810
+ "## \uAE30\uC220 \uC2A4\uD0DD",
811
+ stackList,
812
+ "",
813
+ "## \uC8FC\uC694 \uACB0\uC815\uC0AC\uD56D",
814
+ "- (\uC544\uC9C1 \uC5C6\uC74C \u2014 `vhk memory add` \uB85C \uAE30\uB85D)",
815
+ "",
816
+ "## \uB2E4\uC74C \uB2E8\uACC4",
817
+ "- docs/PRD.md \uC791\uC131",
818
+ ""
819
+ ].join("\n");
820
+ }
821
+
741
822
  // src/utils/logger.ts
742
823
  import chalk2 from "chalk";
743
824
  var log = {
@@ -995,7 +1076,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
995
1076
  }
996
1077
  }
997
1078
  const cwd = process.cwd();
998
- const files = generateFiles(answers.name, answers.description, stack, prdContent);
1079
+ const files = generateFiles(answers.name, answers.description, stack, prdContent, answers.type);
999
1080
  log.step(ko.init.filesGenerating);
1000
1081
  for (const [filePath, content] of Object.entries(files)) {
1001
1082
  const fullPath = path2.join(cwd, filePath);
@@ -1040,7 +1121,7 @@ ${ko.init.nextSteps}`));
1040
1121
  alternative: "VS Code/Cursor\uC5D0\uC11C \uD3F4\uB354\uB97C \uC5F4\uC5B4\uB3C4 \uB429\uB2C8\uB2E4"
1041
1122
  });
1042
1123
  }
1043
- function generateFiles(name, description, stack, prdContent = {}) {
1124
+ function generateFiles(name, description, stack, prdContent = {}, type = "") {
1044
1125
  const stackStr = stack.join(" + ");
1045
1126
  const prd = {
1046
1127
  tagline: description,
@@ -1065,7 +1146,12 @@ function generateFiles(name, description, stack, prdContent = {}) {
1065
1146
  ## v1.1 \uD6C4\uBCF4
1066
1147
 
1067
1148
  -
1068
- `
1149
+ `,
1150
+ // .vhk/ ์”จ์•— โ€” ๊ทœ๊ฒฉ: docs/spec.md (spec_version 1.0)
1151
+ ".vhk/README.md": VHK_README_TEMPLATE(),
1152
+ ".vhk/context.md": VHK_CONTEXT_SEED(name, type || "unknown", stack),
1153
+ ".vhk/.gitignore": VHK_GITIGNORE_TEMPLATE(),
1154
+ ".vhkignore": VHK_IGNORE_TEMPLATE()
1069
1155
  };
1070
1156
  }
1071
1157
  var VHK_PACKAGE_SCRIPTS = {
@@ -1076,6 +1162,32 @@ var VHK_PACKAGE_SCRIPTS = {
1076
1162
  ship: "vhk ship",
1077
1163
  doctor: "vhk doctor"
1078
1164
  };
1165
+ var ROOT_GITIGNORE_ENTRIES = [
1166
+ ".env",
1167
+ ".env.local",
1168
+ ".env.*.local",
1169
+ "node_modules/",
1170
+ "dist/",
1171
+ "*.tsbuildinfo",
1172
+ ".DS_Store"
1173
+ ];
1174
+ function ensureRootGitignore(projectDir) {
1175
+ const gitignorePath = path2.join(projectDir, ".gitignore");
1176
+ if (!fs2.existsSync(gitignorePath)) {
1177
+ fs2.writeFileSync(gitignorePath, ROOT_GITIGNORE_ENTRIES.join("\n") + "\n", "utf-8");
1178
+ return "created";
1179
+ }
1180
+ const content = fs2.readFileSync(gitignorePath, "utf-8");
1181
+ const existing = new Set(content.split("\n").map((l) => l.trim()));
1182
+ const missing = ROOT_GITIGNORE_ENTRIES.filter((e) => !existing.has(e));
1183
+ if (missing.length === 0) return "unchanged";
1184
+ const prefix = content.endsWith("\n") ? "" : "\n";
1185
+ fs2.appendFileSync(gitignorePath, `${prefix}
1186
+ # vhk init
1187
+ ${missing.join("\n")}
1188
+ `, "utf-8");
1189
+ return "updated";
1190
+ }
1079
1191
  function enhancePackageScripts(projectDir) {
1080
1192
  const pkgPath = path2.join(projectDir, "package.json");
1081
1193
  if (!fs2.existsSync(pkgPath)) return false;
@@ -1106,6 +1218,12 @@ async function writeInitExtras(projectDir) {
1106
1218
  if (enhancePackageScripts(projectDir)) {
1107
1219
  log.success(ko.init.scriptsDone);
1108
1220
  }
1221
+ const gitignoreResult = ensureRootGitignore(projectDir);
1222
+ if (gitignoreResult === "created") {
1223
+ log.success(ko.init.gitignoreCreated);
1224
+ } else if (gitignoreResult === "updated") {
1225
+ log.success(ko.init.gitignoreUpdated);
1226
+ }
1109
1227
  }
1110
1228
 
1111
1229
  // src/commands/recap.ts
@@ -1558,12 +1676,12 @@ function parseRulesMd(content) {
1558
1676
  }
1559
1677
  return sections;
1560
1678
  }
1561
- function toCursorrules(sections, projectName) {
1679
+ function buildCodingDoc(headerTitle, sections, projectName) {
1562
1680
  const codingSections = sections.filter(
1563
1681
  (s) => CURSORRULES_KEYS.some((k) => s.title.includes(k))
1564
1682
  );
1565
1683
  const lines = [
1566
- `# ${projectName} \u2014 Cursor Rules`,
1684
+ `# ${projectName} \u2014 ${headerTitle}`,
1567
1685
  "",
1568
1686
  "> \uCF54\uB529/\uB514\uC790\uC778 \uC804\uC6A9. \uAE30\uB85D/\uC6B4\uC601 \u2192 CLAUDE.md \uCC38\uC870.",
1569
1687
  "> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
@@ -1579,6 +1697,39 @@ function toCursorrules(sections, projectName) {
1579
1697
  }
1580
1698
  return lines.join("\n");
1581
1699
  }
1700
+ function toCursorrules(sections, projectName) {
1701
+ return buildCodingDoc("Cursor Rules", sections, projectName);
1702
+ }
1703
+ function toWindsurfrules(sections, projectName) {
1704
+ return buildCodingDoc("Windsurf Rules", sections, projectName);
1705
+ }
1706
+ function toCopilotInstructions(sections, projectName) {
1707
+ return buildCodingDoc("GitHub Copilot Instructions", sections, projectName);
1708
+ }
1709
+ var ANTIGRAVITY_CHAR_LIMIT = 12e3;
1710
+ var ANTIGRAVITY_TRUNCATE_MARKER = "\n\n<!-- \u26A0\uFE0F Antigravity 12,000\uC790 \uC81C\uD55C\uC73C\uB85C \uC808\uC0AD\uB428 \u2014 \uC804\uCCB4 \uADDC\uCE59\uC740 RULES.md \uCC38\uC870 -->\n";
1711
+ function truncateForAntigravity(content, limit = ANTIGRAVITY_CHAR_LIMIT) {
1712
+ if (Buffer.byteLength(content, "utf8") <= limit) return content;
1713
+ const SAFETY = 200;
1714
+ const budget = limit - Buffer.byteLength(ANTIGRAVITY_TRUNCATE_MARKER, "utf8") - SAFETY;
1715
+ let lo = 0;
1716
+ let hi = content.length;
1717
+ while (lo < hi) {
1718
+ const mid = lo + hi + 1 >> 1;
1719
+ if (Buffer.byteLength(content.slice(0, mid), "utf8") <= budget) lo = mid;
1720
+ else hi = mid - 1;
1721
+ }
1722
+ const charCut = lo;
1723
+ let cut = content.lastIndexOf("\n## ", charCut);
1724
+ if (cut < charCut * 0.5) {
1725
+ const nl = content.lastIndexOf("\n", charCut);
1726
+ cut = nl > 0 ? nl : charCut;
1727
+ }
1728
+ return content.slice(0, cut).trimEnd() + ANTIGRAVITY_TRUNCATE_MARKER;
1729
+ }
1730
+ function toAntigravityRules(sections, projectName) {
1731
+ return truncateForAntigravity(buildCodingDoc("Antigravity Rules", sections, projectName));
1732
+ }
1582
1733
  function toClaudeMd(sections, existing) {
1583
1734
  const recordSections = sections.filter(
1584
1735
  (s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k))
@@ -1638,9 +1789,25 @@ ${ko.sync.title}
1638
1789
  - **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
1639
1790
  fs5.writeFileSync(claudePath, toClaudeMd(sections, existingClaude), "utf-8");
1640
1791
  console.log(chalk5.green(` ${ko.sync.claudeDone}`));
1792
+ const windsurfPath = path6.join(cwd, ".windsurfrules");
1793
+ fs5.writeFileSync(windsurfPath, toWindsurfrules(sections, projectName), "utf-8");
1794
+ console.log(chalk5.green(` ${ko.sync.windsurfDone}`));
1795
+ const copilotPath = path6.join(cwd, ".github", "copilot-instructions.md");
1796
+ fs5.mkdirSync(path6.dirname(copilotPath), { recursive: true });
1797
+ fs5.writeFileSync(copilotPath, toCopilotInstructions(sections, projectName), "utf-8");
1798
+ console.log(chalk5.green(` ${ko.sync.copilotDone}`));
1799
+ const antigravityPath = path6.join(cwd, ".agents", "rules", "vhk-rules.md");
1800
+ fs5.mkdirSync(path6.dirname(antigravityPath), { recursive: true });
1801
+ const antigravityDoc = toAntigravityRules(sections, projectName);
1802
+ fs5.writeFileSync(antigravityPath, antigravityDoc, "utf-8");
1803
+ console.log(chalk5.green(` ${ko.sync.antigravityDone}`));
1804
+ if (antigravityDoc.includes("\uC808\uC0AD\uB428")) {
1805
+ console.log(chalk5.yellow(` \u26A0\uFE0F ${ko.sync.antigravityTruncated}`));
1806
+ }
1641
1807
  console.log(chalk5.bold.green(`
1642
1808
  ${ko.sync.done}`));
1643
- console.log(chalk5.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md (\uC790\uB3D9 \uC0DD\uC131)"));
1809
+ console.log(chalk5.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md + .windsurfrules"));
1810
+ console.log(chalk5.dim(" + .github/copilot-instructions.md + .agents/rules/vhk-rules.md (\uC790\uB3D9 \uC0DD\uC131)"));
1644
1811
  console.log(chalk5.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
1645
1812
  printNextStep({
1646
1813
  message: "\uADDC\uCE59 \uB3D9\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C Cursor\uAC00 \uC0C8 \uADDC\uCE59\uC744 \uB530\uB985\uB2C8\uB2E4.",
@@ -1882,6 +2049,19 @@ function listGoals(goalsDir) {
1882
2049
  parsed.sort((a, b) => a.frontmatter.id - b.frontmatter.id);
1883
2050
  return parsed;
1884
2051
  }
2052
+ function findDuplicateIds(goals) {
2053
+ const counts = /* @__PURE__ */ new Map();
2054
+ for (const g of goals) {
2055
+ const id = g.frontmatter.id;
2056
+ if (typeof id !== "number") continue;
2057
+ counts.set(id, (counts.get(id) ?? 0) + 1);
2058
+ }
2059
+ const dups = [];
2060
+ for (const [id, n] of counts) {
2061
+ if (n > 1) dups.push(id);
2062
+ }
2063
+ return dups.sort((a, b) => a - b);
2064
+ }
1885
2065
  function updateFrontmatterStatus(content, newStatus, extraFields) {
1886
2066
  const m = content.match(FRONTMATTER_RE);
1887
2067
  if (!m) return content;
@@ -1966,6 +2146,11 @@ ${ko.goal.listTitle}
1966
2146
  ` [${id}] ${icon} ${status2.padEnd(11)} ${pri} ${ver} ${fm.title ?? "(untitled)"}`
1967
2147
  );
1968
2148
  }
2149
+ const dups = findDuplicateIds(goals);
2150
+ if (dups.length > 0) {
2151
+ console.log("");
2152
+ console.log(chalk6.yellow(` ${ko.goal.duplicateId(dups.join(", "))}`));
2153
+ }
1969
2154
  }
1970
2155
  async function goalNext() {
1971
2156
  console.log(chalk6.bold(`
@@ -2079,6 +2264,11 @@ ${ko.goal.checkTitle}
2079
2264
  process.exitCode = 1;
2080
2265
  return;
2081
2266
  }
2267
+ if (!goals.some((g) => g.frontmatter.id === id)) {
2268
+ console.log(chalk6.red(` \u274C ${ko.goal.notFound(id)}`));
2269
+ process.exitCode = 1;
2270
+ return;
2271
+ }
2082
2272
  const scriptPath = findGateScript(id);
2083
2273
  if (!scriptPath) {
2084
2274
  console.log(
@@ -2116,7 +2306,7 @@ ${ko.goal.doneTitle}
2116
2306
  }
2117
2307
  const target = goals.find((g) => g.frontmatter.id === id);
2118
2308
  if (!target) {
2119
- console.log(chalk6.red(` \u274C goal id ${id} \uD30C\uC77C \uC5C6\uC74C.`));
2309
+ console.log(chalk6.red(` \u274C ${ko.goal.notFound(id)}`));
2120
2310
  process.exitCode = 1;
2121
2311
  return;
2122
2312
  }
@@ -4361,6 +4551,211 @@ ${ko.start.allDone}
4361
4551
  });
4362
4552
  }
4363
4553
 
4554
+ // src/commands/cloud.ts
4555
+ import fs13 from "fs";
4556
+ import path14 from "path";
4557
+ import chalk26 from "chalk";
4558
+
4559
+ // src/lib/vhk-cloud.ts
4560
+ var import_ignore = __toESM(require_ignore(), 1);
4561
+ import fs12 from "fs";
4562
+ import path13 from "path";
4563
+ var DEFAULT_CLOUD_EXCLUDES = [
4564
+ "memory.json",
4565
+ // ๊ฐœ์ธ ์˜์‚ฌ๊ฒฐ์ • ๋ฉ”๋ชจ
4566
+ "refs.json",
4567
+ // ๊ฐœ์ธ ์ฐธ๊ณ ๋งํฌ
4568
+ "HARD_STOP",
4569
+ // ๋กœ์ปฌ ์•ˆ์ „ ์‹ ํ˜ธ
4570
+ "cloud.json",
4571
+ // gist ํฌ์ธํ„ฐ (๋ฐฑ์—… ๋Œ€์ƒ ์•„๋‹˜)
4572
+ ".gitignore"
4573
+ // .vhk/ ๋‚ด๋ถ€ gitignore
4574
+ ];
4575
+ var VHK_DIR2 = ".vhk";
4576
+ var CLOUD_CONFIG_FILE = "cloud.json";
4577
+ function loadVhkignore(rootDir) {
4578
+ const ig = (0, import_ignore.default)();
4579
+ ig.add(DEFAULT_CLOUD_EXCLUDES);
4580
+ const ignorePath = path13.join(rootDir, ".vhkignore");
4581
+ if (fs12.existsSync(ignorePath)) {
4582
+ ig.add(fs12.readFileSync(ignorePath, "utf-8"));
4583
+ }
4584
+ return ig;
4585
+ }
4586
+ function collectVhkFiles(rootDir, ig = loadVhkignore(rootDir)) {
4587
+ const vhkDir = path13.join(rootDir, VHK_DIR2);
4588
+ let entries;
4589
+ try {
4590
+ entries = fs12.readdirSync(vhkDir, { withFileTypes: true });
4591
+ } catch {
4592
+ return [];
4593
+ }
4594
+ return entries.filter((e) => e.isFile()).map((e) => e.name).filter((name) => !ig.ignores(name)).sort();
4595
+ }
4596
+ function readCloudConfig(rootDir) {
4597
+ const p = path13.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
4598
+ if (!fs12.existsSync(p)) return null;
4599
+ try {
4600
+ const parsed = JSON.parse(fs12.readFileSync(p, "utf-8"));
4601
+ if (parsed && typeof parsed.gistId === "string" && parsed.gistId) {
4602
+ return { gistId: parsed.gistId };
4603
+ }
4604
+ return null;
4605
+ } catch {
4606
+ return null;
4607
+ }
4608
+ }
4609
+ function writeCloudConfig(rootDir, config) {
4610
+ const vhkDir = path13.join(rootDir, VHK_DIR2);
4611
+ fs12.mkdirSync(vhkDir, { recursive: true });
4612
+ const p = path13.join(vhkDir, CLOUD_CONFIG_FILE);
4613
+ fs12.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", "utf-8");
4614
+ }
4615
+
4616
+ // src/commands/cloud.ts
4617
+ function ensureGhReady() {
4618
+ const ver = safeExecFile("gh", ["--version"]);
4619
+ if (!ver.ok) {
4620
+ console.log(chalk26.red(` ${ko.cloud.noGh}`));
4621
+ console.log(chalk26.dim(" \uC124\uCE58: https://cli.github.com/ (\uC124\uCE58 \uD6C4 `gh auth login`)"));
4622
+ return false;
4623
+ }
4624
+ const auth = safeExecFile("gh", ["auth", "status"]);
4625
+ if (!auth.ok) {
4626
+ console.log(chalk26.red(` ${ko.cloud.noAuth}`));
4627
+ console.log(chalk26.dim(" \uC2E4\uD589: gh auth login (gist \uAD8C\uD55C \uD544\uC694)"));
4628
+ return false;
4629
+ }
4630
+ return true;
4631
+ }
4632
+ function parseGistId(output) {
4633
+ const match = output.match(/gist\.github\.com\/(?:[^/]+\/)?([0-9a-f]+)/i);
4634
+ if (match) return match[1];
4635
+ const trimmed = output.trim();
4636
+ if (/^[0-9a-f]{8,}$/i.test(trimmed)) return trimmed;
4637
+ return null;
4638
+ }
4639
+ async function cloudPush() {
4640
+ console.log(chalk26.bold(`
4641
+ ${ko.cloud.pushTitle}
4642
+ `));
4643
+ const cwd = process.cwd();
4644
+ if (!fs13.existsSync(path14.join(cwd, VHK_DIR2))) {
4645
+ console.log(chalk26.yellow(` ${ko.cloud.noVhkDir}`));
4646
+ return;
4647
+ }
4648
+ const files = collectVhkFiles(cwd);
4649
+ if (files.length === 0) {
4650
+ console.log(chalk26.yellow(` ${ko.cloud.nothingToSync}`));
4651
+ return;
4652
+ }
4653
+ if (!ensureGhReady()) {
4654
+ process.exitCode = 1;
4655
+ return;
4656
+ }
4657
+ const filePaths = files.map((f) => path14.join(cwd, VHK_DIR2, f));
4658
+ console.log(chalk26.dim(` \u{1F4E6} \uBC31\uC5C5 \uB300\uC0C1 ${files.length}\uAC1C: ${files.join(", ")}
4659
+ `));
4660
+ const existing = readCloudConfig(cwd);
4661
+ const desc = `vhk .vhk backup \u2014 ${path14.basename(cwd)}`;
4662
+ if (existing) {
4663
+ const gistFiles = listGistFiles(existing.gistId);
4664
+ for (let i = 0; i < files.length; i++) {
4665
+ const name = files[i];
4666
+ const src = filePaths[i];
4667
+ const args = gistFiles.includes(name) ? ["gist", "edit", existing.gistId, "-f", name, src] : ["gist", "edit", existing.gistId, "-a", src];
4668
+ const res2 = safeExecFile("gh", args);
4669
+ if (!res2.ok) {
4670
+ console.log(chalk26.red(` ${ko.cloud.pushFail}: ${name}`));
4671
+ console.log(chalk26.dim(` ${res2.err}`));
4672
+ process.exitCode = 1;
4673
+ return;
4674
+ }
4675
+ }
4676
+ console.log(chalk26.green.bold(` ${ko.cloud.pushDone}`));
4677
+ console.log(chalk26.dim(` gist: ${existing.gistId} (\uAC31\uC2E0)`));
4678
+ printPushNext();
4679
+ return;
4680
+ }
4681
+ const res = safeExecFile("gh", ["gist", "create", "--desc", desc, ...filePaths]);
4682
+ if (!res.ok) {
4683
+ console.log(chalk26.red(` ${ko.cloud.pushFail}`));
4684
+ console.log(chalk26.dim(` ${res.err || res.out}`));
4685
+ process.exitCode = 1;
4686
+ return;
4687
+ }
4688
+ const gistId = parseGistId(res.out);
4689
+ if (!gistId) {
4690
+ console.log(chalk26.red(` ${ko.cloud.pushFail} \u2014 gist id \uD30C\uC2F1 \uC2E4\uD328`));
4691
+ console.log(chalk26.dim(` \uCD9C\uB825: ${res.out}`));
4692
+ process.exitCode = 1;
4693
+ return;
4694
+ }
4695
+ writeCloudConfig(cwd, { gistId });
4696
+ console.log(chalk26.green.bold(` ${ko.cloud.pushDone}`));
4697
+ console.log(chalk26.dim(` gist: ${gistId} (\uC2E0\uADDC, secret) \u2192 .vhk/cloud.json \uC800\uC7A5`));
4698
+ printPushNext();
4699
+ }
4700
+ async function cloudPull(gistIdArg) {
4701
+ console.log(chalk26.bold(`
4702
+ ${ko.cloud.pullTitle}
4703
+ `));
4704
+ const cwd = process.cwd();
4705
+ const gistId = gistIdArg || readCloudConfig(cwd)?.gistId;
4706
+ if (!gistId) {
4707
+ console.log(chalk26.yellow(` ${ko.cloud.noGistId}`));
4708
+ console.log(chalk26.dim(" \uC0AC\uC6A9\uBC95: vhk cloud pull <gistId> (\uB610\uB294 cloud.json \uC774 \uC788\uB294 \uACF3\uC5D0\uC11C \uC2E4\uD589)"));
4709
+ return;
4710
+ }
4711
+ if (!ensureGhReady()) {
4712
+ process.exitCode = 1;
4713
+ return;
4714
+ }
4715
+ const names = listGistFiles(gistId);
4716
+ if (names.length === 0) {
4717
+ console.log(chalk26.red(` ${ko.cloud.pullFail} \u2014 gist \uBE44\uC5C8\uAC70\uB098 \uC811\uADFC \uBD88\uAC00: ${gistId}`));
4718
+ process.exitCode = 1;
4719
+ return;
4720
+ }
4721
+ const vhkDir = path14.join(cwd, VHK_DIR2);
4722
+ fs13.mkdirSync(vhkDir, { recursive: true });
4723
+ let restored = 0;
4724
+ for (const name of names) {
4725
+ const res = safeExecFile("gh", ["gist", "view", gistId, "-f", name, "--raw"]);
4726
+ if (!res.ok) {
4727
+ console.log(chalk26.red(` ${ko.cloud.pullFail}: ${name}`));
4728
+ console.log(chalk26.dim(` ${res.err}`));
4729
+ continue;
4730
+ }
4731
+ fs13.writeFileSync(path14.join(vhkDir, name), ensureTrailingNewline(res.out), "utf-8");
4732
+ restored++;
4733
+ }
4734
+ writeCloudConfig(cwd, { gistId });
4735
+ console.log(chalk26.green.bold(` ${ko.cloud.pullDone}`));
4736
+ console.log(chalk26.dim(` ${restored}\uAC1C \uD30C\uC77C \uBCF5\uC6D0 (gist: ${gistId})`));
4737
+ printNextStep({
4738
+ message: "\uD074\uB77C\uC6B0\uB4DC\uC5D0\uC11C .vhk/ \uBCF5\uC6D0 \uC644\uB8CC!",
4739
+ command: "vhk \uB9E5\uB77D",
4740
+ cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D \uBCF4\uC5EC\uC918"
4741
+ });
4742
+ }
4743
+ function listGistFiles(gistId) {
4744
+ const res = safeExecFile("gh", ["gist", "view", gistId, "--files"]);
4745
+ if (!res.ok) return [];
4746
+ return res.out.split("\n").map((l) => l.trim()).filter(Boolean);
4747
+ }
4748
+ function ensureTrailingNewline(s) {
4749
+ return s.endsWith("\n") ? s : s + "\n";
4750
+ }
4751
+ function printPushNext() {
4752
+ printNextStep({
4753
+ message: "\uD074\uB77C\uC6B0\uB4DC \uBC31\uC5C5 \uC644\uB8CC! \uB2E4\uB978 \uD658\uACBD\uC5D0\uC11C vhk cloud pull \uB85C \uBCF5\uC6D0\uD558\uC138\uC694.",
4754
+ command: "vhk cloud pull",
4755
+ cursorHint: "\uB2E4\uB978 \uCEF4\uD4E8\uD130\uC5D0\uC11C .vhk \uBCF5\uC6D0\uD574\uC918"
4756
+ });
4757
+ }
4758
+
4364
4759
  // src/lib/nlp-run.ts
4365
4760
  async function dispatchNlpRoute(route, input) {
4366
4761
  switch (route.command) {
@@ -4429,6 +4824,10 @@ async function dispatchNlpRoute(route, input) {
4429
4824
  return memoryList();
4430
4825
  case "brief":
4431
4826
  return brief();
4827
+ case "cloud-push":
4828
+ return cloudPush();
4829
+ case "cloud-pull":
4830
+ return cloudPull();
4432
4831
  case "goal": {
4433
4832
  const sub = route.args?.[0];
4434
4833
  if (sub === "next") return goalNext();
@@ -4441,14 +4840,14 @@ async function dispatchNlpRoute(route, input) {
4441
4840
  async function runNaturalLanguageRoute(input) {
4442
4841
  const route = routeNaturalLanguage(input);
4443
4842
  if (!route) {
4444
- console.log(chalk26.yellow(`
4843
+ console.log(chalk27.yellow(`
4445
4844
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
4446
4845
  `));
4447
4846
  return;
4448
4847
  }
4449
4848
  console.log("");
4450
- console.log(chalk26.cyan(` \u{1F4AC} "${input}"`));
4451
- console.log(chalk26.cyan(` \u2192 ${route.explanation}`));
4849
+ console.log(chalk27.cyan(` \u{1F4AC} "${input}"`));
4850
+ console.log(chalk27.cyan(` \u2192 ${route.explanation}`));
4452
4851
  if (route.confidence === "low") {
4453
4852
  const { confirm } = await inquirer11.prompt([{
4454
4853
  type: "confirm",
@@ -4457,7 +4856,7 @@ async function runNaturalLanguageRoute(input) {
4457
4856
  default: true
4458
4857
  }]);
4459
4858
  if (!confirm) {
4460
- console.log(chalk26.dim(` ${ko.nlp.menuHint}`));
4859
+ console.log(chalk27.dim(` ${ko.nlp.menuHint}`));
4461
4860
  return;
4462
4861
  }
4463
4862
  }
@@ -4466,77 +4865,77 @@ async function runNaturalLanguageRoute(input) {
4466
4865
  }
4467
4866
 
4468
4867
  // src/commands/agent.ts
4469
- import chalk27 from "chalk";
4868
+ import chalk28 from "chalk";
4470
4869
  function activeGoalId() {
4471
4870
  const goals = listGoals("goals");
4472
4871
  const id = selectActiveId(goals);
4473
4872
  return id ?? void 0;
4474
4873
  }
4475
4874
  async function blocker(description) {
4476
- console.log(chalk27.bold(`
4875
+ console.log(chalk28.bold(`
4477
4876
  ${ko.agent.blockerTitle}
4478
4877
  `));
4479
4878
  if (!description || !description.trim()) {
4480
- console.log(chalk27.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
4481
- console.log(chalk27.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
4879
+ console.log(chalk28.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
4880
+ console.log(chalk28.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
4482
4881
  process.exitCode = 1;
4483
4882
  return;
4484
4883
  }
4485
4884
  const goalId = activeGoalId();
4486
4885
  const r = appendBlocker(description, goalId);
4487
- console.log(chalk27.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
4886
+ console.log(chalk28.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
4488
4887
  if (r.hardStopTripped) {
4489
- console.log(chalk27.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
4490
- console.log(chalk27.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
4888
+ console.log(chalk28.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
4889
+ console.log(chalk28.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
4491
4890
  process.exitCode = 2;
4492
4891
  }
4493
4892
  }
4494
4893
  async function learn(lesson) {
4495
- console.log(chalk27.bold(`
4894
+ console.log(chalk28.bold(`
4496
4895
  ${ko.agent.learnTitle}
4497
4896
  `));
4498
4897
  if (!lesson || !lesson.trim()) {
4499
- console.log(chalk27.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
4500
- console.log(chalk27.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
4898
+ console.log(chalk28.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
4899
+ console.log(chalk28.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
4501
4900
  process.exitCode = 1;
4502
4901
  return;
4503
4902
  }
4504
4903
  const goalId = activeGoalId();
4505
4904
  appendLearning(lesson, goalId);
4506
- console.log(chalk27.green(" \u2705 learnings.md append."));
4905
+ console.log(chalk28.green(" \u2705 learnings.md append."));
4507
4906
  console.log(
4508
- chalk27.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
4907
+ chalk28.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
4509
4908
  );
4510
4909
  }
4511
4910
  async function resume(opts = {}) {
4512
- console.log(chalk27.bold(`
4911
+ console.log(chalk28.bold(`
4513
4912
  ${ko.agent.resumeTitle}
4514
4913
  `));
4515
4914
  if (!isHardStopActive()) {
4516
- console.log(chalk27.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
4915
+ console.log(chalk28.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
4517
4916
  return;
4518
4917
  }
4519
4918
  const reason = readHardStopReason();
4520
4919
  if (reason) {
4521
- console.log(chalk27.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
4522
- console.log(chalk27.dim(` ${reason.split("\n").join("\n ")}`));
4920
+ console.log(chalk28.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
4921
+ console.log(chalk28.dim(` ${reason.split("\n").join("\n ")}`));
4523
4922
  console.log("");
4524
4923
  }
4525
4924
  if (!opts.confirm) {
4526
4925
  console.log(
4527
- chalk27.red(
4926
+ chalk28.red(
4528
4927
  " \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
4529
4928
  )
4530
4929
  );
4531
- console.log(chalk27.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
4930
+ console.log(chalk28.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
4532
4931
  process.exitCode = 1;
4533
4932
  return;
4534
4933
  }
4535
4934
  const removed = clearHardStop();
4536
4935
  if (removed) {
4537
- console.log(chalk27.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
4936
+ console.log(chalk28.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
4538
4937
  } else {
4539
- console.log(chalk27.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
4938
+ console.log(chalk28.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
4540
4939
  }
4541
4940
  }
4542
4941
 
@@ -4612,6 +5011,15 @@ program.command("check").alias("\uC810\uAC80").alias("\uB9B0\uD2B8").option("--g
4612
5011
  });
4613
5012
  var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C \u2014 scan: \uC2DC\uD06C\uB9BF\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC").action(secure);
4614
5013
  secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
5014
+ var cloudCmd = program.command("cloud").alias("\uD074\uB77C\uC6B0\uB4DC").description(".vhk/ \uD074\uB77C\uC6B0\uB4DC \uBC31\uC5C5\xB7\uBCF5\uC6D0 (GitHub gist) \u2014 push: \uC62C\uB9AC\uAE30, pull: \uB0B4\uB9AC\uAE30").action(() => {
5015
+ cloudCmd.help();
5016
+ });
5017
+ cloudCmd.command("push").alias("\uC62C\uB9AC\uAE30").description(".vhk/ \uB97C secret gist \uB85C \uBC31\uC5C5").action(async () => {
5018
+ await cloudPush();
5019
+ });
5020
+ cloudCmd.command("pull").alias("\uB0B4\uB9AC\uAE30").argument("[gistId]", "\uBCF5\uC6D0\uD560 gist id (\uC0DD\uB7B5 \uC2DC .vhk/cloud.json \uC0AC\uC6A9)").description("gist \uC5D0\uC11C .vhk/ \uBCF5\uC6D0").action(async (gistId) => {
5021
+ await cloudPull(gistId);
5022
+ });
4615
5023
  program.command("ship").alias("\uCD9C\uD558").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
4616
5024
  program.command("doctor").alias("\uD658\uACBD").alias("\uC9C4\uB2E8").description("\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 \u2014 Node/Git/npm \uC0C1\uD0DC \uD655\uC778").action(doctor);
4617
5025
  program.command("save").alias("\uC800\uC7A5").description("\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)").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-6S3JYYZ3.js";
4
+ } from "../chunk-4KWZANQG.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.1",
3
+ "version": "1.5.0",
4
4
  "description": "Vibe Harness Kit โ€” ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ํ’€์‚ฌ์ดํด CLI",
5
5
  "bin": {
6
6
  "vhk": "dist/index.js",