@byh3071/vhk 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  id: vhk-readme
3
3
  date: 2026-05-28
4
- tags: [vhk, cli, readme, v1.4.0, ga]
4
+ tags: [vhk, cli, readme, v1.5.1, ga]
5
5
  ---
6
6
 
7
7
  # 🔧 VHK — Vibe Harness Kit
8
8
 
9
- > 🎉 **v1.4.0** — 바이브코더의 올인원 CLI. 컨텍스트 + 자율 하네스 + 포터빌리티.
9
+ > 🎉 **v1.5.1** — **규칙은 벌로 Cursor·Claude·Windsurf·Copilot·Antigravity에, 맥락은 클라우드로.**
10
+ > 도구·기기를 옮겨도 `vhk` 명령으로 그대로 불러옵니다. (포터빌리티)
10
11
  >
11
12
  > AI 코딩 에이전트를 부리는 사람을 위한 **한국어 풀사이클 CLI**.
12
13
  >
@@ -14,6 +15,20 @@ tags: [vhk, cli, readme, v1.4.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,7 +130,7 @@ 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 + `.windsurfrules` |
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` | `출하` | 배포 체크리스트 + 회고 + 빌드 로그 |
@@ -300,10 +315,10 @@ vhk ref open 1 # 1번 레퍼런스를 브라우저로 열기
300
315
 
301
316
  | 기능 | 설명 |
302
317
  |------|------|
303
- | **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로 연동하기" 섹션 참조 |
304
319
  | **mcp-init** | `vhk mcp-init` — Cursor `.cursor/mcp.json` 자동 생성. 재시작 한 번으로 연동 완료 |
305
320
  | **자연어 라우팅 확장** | `vhk mcp설정` → `vhk mcp-init` 별칭 |
306
- | **보안** | MCP save 도구의 shell injection 차단 — 모든 git 호출에 `execFileSync` 사용 |
321
+ | **보안** | MCP save 도구의 shell injection 차단 — 모든 git 호출에 shell 미경유 `safeExecFile` 사용 |
307
322
 
308
323
  ## v0.5.3 하이라이트
309
324
 
@@ -755,6 +755,9 @@ var ko = {
755
755
  cursorrulesDone: "\u2705 .cursorrules \uB9DE\uCDA4 \uC644\uB8CC",
756
756
  claudeDone: "\u2705 CLAUDE.md \uB9DE\uCDA4 \uC644\uB8CC",
757
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",
758
761
  done: "\u{1F504} \uB9DE\uCD94\uAE30 \uC644\uB8CC!"
759
762
  },
760
763
  cloud: {
@@ -882,7 +885,9 @@ var ko = {
882
885
  nextTitle: "\u27A1\uFE0F \uB2E4\uC74C Goal",
883
886
  initTitle: "\u{1F3D7}\uFE0F goals/ \uAD6C\uC870 \uC2A4\uCE90\uD3F4\uB529",
884
887
  checkTitle: "\u2705 Goal \uAC8C\uC774\uD2B8 \uAC80\uC99D",
885
- 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.`
886
891
  },
887
892
  agent: {
888
893
  blockerTitle: "\u{1F6D1} Blocker \uAE30\uB85D",
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  scanProjectForSecrets,
21
21
  startMcpServer,
22
22
  t
23
- } from "./chunk-3DV7AEN4.js";
23
+ } from "./chunk-4KWZANQG.js";
24
24
 
25
25
  // src/index.ts
26
26
  import { Command, Help } from "commander";
@@ -1676,12 +1676,12 @@ function parseRulesMd(content) {
1676
1676
  }
1677
1677
  return sections;
1678
1678
  }
1679
- function toCursorrules(sections, projectName) {
1679
+ function buildCodingDoc(headerTitle, sections, projectName) {
1680
1680
  const codingSections = sections.filter(
1681
1681
  (s) => CURSORRULES_KEYS.some((k) => s.title.includes(k))
1682
1682
  );
1683
1683
  const lines = [
1684
- `# ${projectName} \u2014 Cursor Rules`,
1684
+ `# ${projectName} \u2014 ${headerTitle}`,
1685
1685
  "",
1686
1686
  "> \uCF54\uB529/\uB514\uC790\uC778 \uC804\uC6A9. \uAE30\uB85D/\uC6B4\uC601 \u2192 CLAUDE.md \uCC38\uC870.",
1687
1687
  "> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
@@ -1697,26 +1697,38 @@ function toCursorrules(sections, projectName) {
1697
1697
  }
1698
1698
  return lines.join("\n");
1699
1699
  }
1700
+ function toCursorrules(sections, projectName) {
1701
+ return buildCodingDoc("Cursor Rules", sections, projectName);
1702
+ }
1700
1703
  function toWindsurfrules(sections, projectName) {
1701
- const codingSections = sections.filter(
1702
- (s) => CURSORRULES_KEYS.some((k) => s.title.includes(k))
1703
- );
1704
- const lines = [
1705
- `# ${projectName} \u2014 Windsurf Rules`,
1706
- "",
1707
- "> \uCF54\uB529/\uB514\uC790\uC778 \uC804\uC6A9. \uAE30\uB85D/\uC6B4\uC601 \u2192 CLAUDE.md \uCC38\uC870.",
1708
- "> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
1709
- "",
1710
- "## \uD544\uC218 \uCC38\uC870",
1711
- "- docs/PRD.md \xB7 docs/ARCHITECTURE.md \xB7 CLAUDE.md \xB7 RULES.md",
1712
- ""
1713
- ];
1714
- for (const section of codingSections) {
1715
- lines.push(`## ${section.title}`);
1716
- lines.push(section.content);
1717
- lines.push("");
1718
- }
1719
- return lines.join("\n");
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));
1720
1732
  }
1721
1733
  function toClaudeMd(sections, existing) {
1722
1734
  const recordSections = sections.filter(
@@ -1780,9 +1792,22 @@ ${ko.sync.title}
1780
1792
  const windsurfPath = path6.join(cwd, ".windsurfrules");
1781
1793
  fs5.writeFileSync(windsurfPath, toWindsurfrules(sections, projectName), "utf-8");
1782
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
+ }
1783
1807
  console.log(chalk5.bold.green(`
1784
1808
  ${ko.sync.done}`));
1785
- console.log(chalk5.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md + .windsurfrules (\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)"));
1786
1811
  console.log(chalk5.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
1787
1812
  printNextStep({
1788
1813
  message: "\uADDC\uCE59 \uB3D9\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C Cursor\uAC00 \uC0C8 \uADDC\uCE59\uC744 \uB530\uB985\uB2C8\uB2E4.",
@@ -2024,6 +2049,19 @@ function listGoals(goalsDir) {
2024
2049
  parsed.sort((a, b) => a.frontmatter.id - b.frontmatter.id);
2025
2050
  return parsed;
2026
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
+ }
2027
2065
  function updateFrontmatterStatus(content, newStatus, extraFields) {
2028
2066
  const m = content.match(FRONTMATTER_RE);
2029
2067
  if (!m) return content;
@@ -2108,6 +2146,11 @@ ${ko.goal.listTitle}
2108
2146
  ` [${id}] ${icon} ${status2.padEnd(11)} ${pri} ${ver} ${fm.title ?? "(untitled)"}`
2109
2147
  );
2110
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
+ }
2111
2154
  }
2112
2155
  async function goalNext() {
2113
2156
  console.log(chalk6.bold(`
@@ -2221,6 +2264,11 @@ ${ko.goal.checkTitle}
2221
2264
  process.exitCode = 1;
2222
2265
  return;
2223
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
+ }
2224
2272
  const scriptPath = findGateScript(id);
2225
2273
  if (!scriptPath) {
2226
2274
  console.log(
@@ -2258,7 +2306,7 @@ ${ko.goal.doneTitle}
2258
2306
  }
2259
2307
  const target = goals.find((g) => g.frontmatter.id === id);
2260
2308
  if (!target) {
2261
- console.log(chalk6.red(` \u274C goal id ${id} \uD30C\uC77C \uC5C6\uC74C.`));
2309
+ console.log(chalk6.red(` \u274C ${ko.goal.notFound(id)}`));
2262
2310
  process.exitCode = 1;
2263
2311
  return;
2264
2312
  }
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-3DV7AEN4.js";
4
+ } from "../chunk-4KWZANQG.js";
5
5
 
6
6
  // src/mcp/index.ts
7
7
  startMcpServer().catch((err) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@byh3071/vhk",
3
- "version": "1.4.0",
4
- "description": "Vibe Harness Kit — 바이브코딩 풀사이클 CLI",
3
+ "version": "1.5.1",
4
+ "description": "Vibe Harness Kit — AI 코딩 도구·기기를 바꿔도 규칙·맥락이 따라가는 포터빌리티 CLI (sync: Cursor·Claude·Windsurf·Copilot·Antigravity / cloud 백업)",
5
5
  "bin": {
6
6
  "vhk": "dist/index.js",
7
7
  "vhk-mcp": "dist/mcp/index.js"
@@ -18,7 +18,14 @@
18
18
  "cli",
19
19
  "scaffold",
20
20
  "session-log",
21
- "rules-sync"
21
+ "rules-sync",
22
+ "portability",
23
+ "ai-coding",
24
+ "cursor",
25
+ "claude",
26
+ "windsurf",
27
+ "copilot",
28
+ "context-sync"
22
29
  ],
23
30
  "author": "byh3071 <byh3071@gmail.com>",
24
31
  "license": "MIT",