@byh3071/vhk 2.3.1 → 2.3.2

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.
@@ -762,7 +762,12 @@ var ko = {
762
762
  skipped: (p) => `\u23ED\uFE0F \uAC74\uB108\uB700: ${p} (\uB36E\uC5B4\uC4F0\uAE30 \uAC70\uBD80 \u2014 \uBC31\uC5C5\uB9CC \uBCF4\uAD00)`,
763
763
  dryRunHeader: "\u{1F50E} \uBBF8\uB9AC\uBCF4\uAE30 (--dry-run) \u2014 \uC2E4\uC81C \uD30C\uC77C \uBCC0\uACBD \uC5C6\uC74C",
764
764
  dryRunWouldWrite: (p, drift) => ` ${drift ? "\u270F\uFE0F \uBCC0\uACBD\uB428" : "\xB7 \uB3D9\uC77C"} : ${p}`,
765
- nonTtyAuto: (n, id) => `\u{1F916} \uBE44\uB300\uD654\uD615(CI/\uC5D0\uC774\uC804\uD2B8) \u2014 ${n}\uAC1C \uBC31\uC5C5 \uD6C4 \uC9C4\uD589. \uBCF5\uC6D0: vhk restore ${id}`
765
+ nonTtyAuto: (n, id) => `\u{1F916} \uBE44\uB300\uD654\uD615(CI/\uC5D0\uC774\uC804\uD2B8) \u2014 ${n}\uAC1C \uBC31\uC5C5 \uD6C4 \uC9C4\uD589. \uBCF5\uC6D0: vhk restore ${id}`,
766
+ // 배치1 — CLAUDE.md 를 vhk 마커(<!-- vhk:rules:start/end -->) 형식으로 1회 정리할 때의 안내.
767
+ // 사용자 섹션은 보존, RULES.md 기준 옛 자동생성 섹션만 재생성 교체(조용한 드롭 방지).
768
+ claudeMigrated: (preserved, removed) => `\u2139\uFE0F CLAUDE.md \uB97C vhk \uB9C8\uCEE4 \uD615\uC2DD\uC73C\uB85C \uC815\uB9AC\uD588\uC5B4\uC694 \u2014 \uB9C8\uCEE4 \uBC16 \uC0AC\uC6A9\uC790 \uC139\uC158\uC740 \uBCF4\uC874\uB429\uB2C8\uB2E4.` + (preserved.length ? `
769
+ \uBCF4\uC874\uB41C \uC0AC\uC6A9\uC790 \uC139\uC158 ${preserved.length}\uAC1C: ${preserved.join(", ")}` : "") + (removed.length ? `
770
+ RULES.md \uAE30\uC900\uC73C\uB85C \uC7AC\uC0DD\uC131\xB7\uAD50\uCCB4\uB41C \uC61B \uC790\uB3D9\uC0DD\uC131 \uC139\uC158 ${removed.length}\uAC1C: ${removed.join(", ")} (\uD544\uC694 \uC2DC .vhk/backups \uC5D0\uC11C \uBCF5\uAD6C)` : "")
766
771
  },
767
772
  restore: {
768
773
  title: "\u{1F6DF} \uBC31\uC5C5 \uBCF5\uC6D0",
@@ -1378,29 +1383,80 @@ function toAntigravityRules(sections, projectName) {
1378
1383
  return truncateForAntigravity(buildCodingDoc("Antigravity Rules", sections, projectName));
1379
1384
  }
1380
1385
  var CLAUDE_AUTOGEN_BANNER = "> \u26A1 \uC544\uB798 \uADDC\uCE59 \uC139\uC158\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.";
1381
- function toClaudeMd(sections, existing) {
1382
- const recordSections = sections.filter(
1383
- (s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k))
1384
- );
1385
- const cleaned = existing.split("\n").filter((line) => line.trim() !== CLAUDE_AUTOGEN_BANNER).join("\n");
1386
- const statusMatch = cleaned.match(/## 현재 상태[\s\S]*?(?=\n## |$)/);
1387
- const statusSection = statusMatch ? statusMatch[0].trimEnd() : "";
1388
- const header = cleaned.split("## ")[0].trim();
1389
- const lines = [
1390
- header,
1391
- "",
1392
- statusSection,
1393
- "",
1394
- CLAUDE_AUTOGEN_BANNER,
1395
- ""
1396
- ];
1386
+ var VHK_BLOCK_START = "<!-- vhk:rules:start -->";
1387
+ var VHK_BLOCK_END = "<!-- vhk:rules:end -->";
1388
+ function buildVhkBlock(recordSections) {
1389
+ const lines = [VHK_BLOCK_START, CLAUDE_AUTOGEN_BANNER, ""];
1397
1390
  for (const section of recordSections) {
1398
1391
  lines.push(`## ${section.title}`);
1399
1392
  lines.push(section.content);
1400
1393
  lines.push("");
1401
1394
  }
1395
+ lines.push(VHK_BLOCK_END);
1402
1396
  return lines.join("\n");
1403
1397
  }
1398
+ function splitVhkBlock(existing) {
1399
+ const start = existing.indexOf(VHK_BLOCK_START);
1400
+ const end = existing.indexOf(VHK_BLOCK_END);
1401
+ if (start === -1 || end === -1 || end < start) return null;
1402
+ return {
1403
+ before: existing.slice(0, start),
1404
+ after: existing.slice(end + VHK_BLOCK_END.length)
1405
+ };
1406
+ }
1407
+ function stripLegacyAutogen(existing) {
1408
+ const lines = existing.split("\n").filter((line) => {
1409
+ const t2 = line.trim();
1410
+ return t2 !== CLAUDE_AUTOGEN_BANNER && t2 !== VHK_BLOCK_START && t2 !== VHK_BLOCK_END;
1411
+ });
1412
+ const headerLines = [];
1413
+ const blocks = [];
1414
+ let cur = null;
1415
+ for (const line of lines) {
1416
+ if (line.startsWith("## ")) {
1417
+ if (cur) blocks.push(cur);
1418
+ cur = { title: line.slice(3).trim(), body: [line] };
1419
+ } else if (cur) {
1420
+ cur.body.push(line);
1421
+ } else {
1422
+ headerLines.push(line);
1423
+ }
1424
+ }
1425
+ if (cur) blocks.push(cur);
1426
+ const removed = [];
1427
+ const preserved = [];
1428
+ const keptBodies = [];
1429
+ for (const b of blocks) {
1430
+ if (CLAUDE_MD_KEYS.some((k) => b.title.includes(k))) {
1431
+ removed.push(b.title);
1432
+ } else {
1433
+ preserved.push(b.title);
1434
+ keptBodies.push(b.body.join("\n").trimEnd());
1435
+ }
1436
+ }
1437
+ const header = headerLines.join("\n").trim();
1438
+ const cleaned = [header, ...keptBodies].filter(Boolean).join("\n\n");
1439
+ return { cleaned, removed, preserved };
1440
+ }
1441
+ function claudeMdMigration(existing) {
1442
+ if (splitVhkBlock(existing)) return { migrated: false, removed: [], preserved: [] };
1443
+ const { removed, preserved } = stripLegacyAutogen(existing);
1444
+ return { migrated: true, removed, preserved };
1445
+ }
1446
+ function toClaudeMd(sections, existing) {
1447
+ const recordSections = sections.filter(
1448
+ (s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k))
1449
+ );
1450
+ const vhkBlock = buildVhkBlock(recordSections);
1451
+ const split = splitVhkBlock(existing);
1452
+ if (split) {
1453
+ const before = split.before.replace(/\s+$/, "");
1454
+ const after = split.after.replace(/^\s+/, "").replace(/\s+$/, "");
1455
+ return [before, vhkBlock, after].filter((s) => s.length > 0).join("\n\n") + "\n";
1456
+ }
1457
+ const { cleaned } = stripLegacyAutogen(existing);
1458
+ return [cleaned, vhkBlock].filter((s) => s.length > 0).join("\n\n") + "\n";
1459
+ }
1404
1460
  function toAgentsMd(sections, projectName) {
1405
1461
  const codingSections = sections.filter((s) => CURSORRULES_KEYS.some((k) => s.title.includes(k)));
1406
1462
  const recordSections = sections.filter((s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k)));
@@ -1472,7 +1528,9 @@ function buildSyncPlan(rootDir, sections, projectName) {
1472
1528
  newContent: claudeNew,
1473
1529
  doneMessage: ko.sync.claudeDone,
1474
1530
  exists: claudeExists,
1475
- drift: claudeDrift
1531
+ drift: claudeDrift,
1532
+ // 기존 CLAUDE.md 가 있을 때만 마이그레이션 집계(첫 생성은 마이그레이션 아님). 추가 I/O 0 — 이미 읽은 existingClaude 재사용.
1533
+ migration: claudeExists ? claudeMdMigration(existingClaude) : void 0
1476
1534
  });
1477
1535
  return plan;
1478
1536
  }
@@ -1483,6 +1541,7 @@ async function syncCore(rootDir, opts, confirmOverwrite) {
1483
1541
  const plan = buildSyncPlan(rootDir, sections, projectName);
1484
1542
  const firstSync = !fs4.existsSync(path4.join(rootDir, SYNCED_MARKER_REL));
1485
1543
  const unmapped = findUnmappedSections(sections);
1544
+ const claudeMigration = plan.find((p) => p.path === "CLAUDE.md")?.migration;
1486
1545
  if (opts.dryRun) {
1487
1546
  return {
1488
1547
  dryRun: true,
@@ -1493,7 +1552,8 @@ async function syncCore(rootDir, opts, confirmOverwrite) {
1493
1552
  skipped: [],
1494
1553
  truncated: [],
1495
1554
  plan,
1496
- unmapped
1555
+ unmapped,
1556
+ claudeMigration
1497
1557
  };
1498
1558
  }
1499
1559
  const toBackup = plan.filter((p) => p.exists && (p.drift || firstSync)).map((p) => p.path);
@@ -1526,7 +1586,7 @@ async function syncCore(rootDir, opts, confirmOverwrite) {
1526
1586
  fs4.mkdirSync(path4.join(rootDir, ".vhk"), { recursive: true });
1527
1587
  fs4.writeFileSync(path4.join(rootDir, SYNCED_MARKER_REL), (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1528
1588
  ensureVhkIgnored(rootDir, ".synced");
1529
- return { dryRun: false, firstSync, backupId, backedUp, written, skipped, truncated, plan, unmapped };
1589
+ return { dryRun: false, firstSync, backupId, backedUp, written, skipped, truncated, plan, unmapped, claudeMigration };
1530
1590
  }
1531
1591
  async function sync(opts = {}) {
1532
1592
  console.log(chalk3.bold(`
@@ -1575,6 +1635,13 @@ ${ko.sync.title}
1575
1635
  )
1576
1636
  );
1577
1637
  }
1638
+ if (result.claudeMigration?.migrated) {
1639
+ console.log(
1640
+ chalk3.cyan(
1641
+ ` ${ko.sync.claudeMigrated(result.claudeMigration.preserved, result.claudeMigration.removed)}`
1642
+ )
1643
+ );
1644
+ }
1578
1645
  if (result.dryRun) {
1579
1646
  console.log(chalk3.cyan(`
1580
1647
  ${ko.sync.dryRunHeader}`));
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  stripBom,
44
44
  sync,
45
45
  t
46
- } from "./chunk-WQWPM364.js";
46
+ } from "./chunk-OHGVVAKT.js";
47
47
 
48
48
  // src/index.ts
49
49
  import { Command, Help } from "commander";
@@ -516,6 +516,10 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
516
516
  "\uAC80\uD1A0",
517
517
  "mission",
518
518
  "\uBBF8\uC158",
519
+ "pattern",
520
+ "\uD328\uD134",
521
+ "evolve",
522
+ "\uC9C4\uD654",
519
523
  "help"
520
524
  ]);
521
525
  function isOptionToken(token) {
@@ -6592,41 +6596,19 @@ function detectCandidates(mem, minFreq) {
6592
6596
  processBucket(mem.successes, "reinforce", (e) => e.content ?? "");
6593
6597
  return candidates.sort((a, b) => b.count - a.count || a.signal.localeCompare(b.signal));
6594
6598
  }
6595
- function nextPatternId(mem) {
6596
- const re = /^p(\d+)$/;
6597
- let max = 0;
6598
- for (const p of mem.patterns) {
6599
- const m = p.id.match(re);
6600
- if (m) max = Math.max(max, Number(m[1]));
6601
- }
6602
- return `p${max + 1}`;
6603
- }
6604
- async function patternDetect(opts = {}) {
6605
- const minFreq = opts.min !== void 0 ? parseInt(opts.min, 10) : MIN_TAG_FREQ;
6606
- if (!Number.isFinite(minFreq) || minFreq < 1) {
6607
- console.log(chalk33.red("\u274C --min \uC740 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4."));
6608
- process.exitCode = 1;
6609
- return;
6610
- }
6611
- const cwd = process.cwd();
6612
- const loaded = loadForMutation(cwd);
6613
- if (!loaded.ok) {
6614
- console.log(chalk33.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uAC10\uC9C0 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874). \uBC31\uC5C5 \uD655\uC778 \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694."));
6615
- process.exitCode = 1;
6616
- return;
6599
+ function reconcilePatterns(patterns, candidates, now) {
6600
+ let added = 0;
6601
+ let updated = 0;
6602
+ let maxId = 0;
6603
+ for (const p of patterns) {
6604
+ const m = p.id.match(/^p(\d+)$/);
6605
+ if (m) maxId = Math.max(maxId, Number(m[1]));
6617
6606
  }
6618
- const mem = loaded.mem;
6619
- const candidates = detectCandidates(mem, minFreq);
6620
- const now = (/* @__PURE__ */ new Date()).toISOString();
6621
- let added = 0, updated = 0;
6622
6607
  for (const c of candidates) {
6623
6608
  const sig = sigOf(c.kind, c.axis, c.signal);
6624
- const existing = mem.patterns.find((p) => {
6625
- const pp = p;
6626
- const sigMatch = pp._sig === sig;
6627
- const fieldMatch = pp.kind === c.kind && pp.axis === c.axis && pp.signal === c.signal;
6628
- return (sigMatch || fieldMatch) && pp.status !== "archived";
6629
- });
6609
+ const matches = (pp) => pp._sig === sig || pp.kind === c.kind && pp.axis === c.axis && pp.signal === c.signal;
6610
+ if (patterns.some((p) => matches(p) && p.status === "archived")) continue;
6611
+ const existing = patterns.find((p) => matches(p) && p.status !== "archived");
6630
6612
  if (existing) {
6631
6613
  existing._sig = sig;
6632
6614
  existing.count = c.count;
@@ -6635,8 +6617,8 @@ async function patternDetect(opts = {}) {
6635
6617
  existing.tags = c.sourceTags;
6636
6618
  updated++;
6637
6619
  } else {
6638
- const entry = {
6639
- id: nextPatternId(mem),
6620
+ patterns.push({
6621
+ id: `p${++maxId}`,
6640
6622
  kind: c.kind,
6641
6623
  axis: c.axis,
6642
6624
  signal: c.signal,
@@ -6647,11 +6629,30 @@ async function patternDetect(opts = {}) {
6647
6629
  status: "active",
6648
6630
  tags: c.sourceTags,
6649
6631
  _sig: sig
6650
- };
6651
- mem.patterns.push(entry);
6632
+ });
6652
6633
  added++;
6653
6634
  }
6654
6635
  }
6636
+ return { added, updated };
6637
+ }
6638
+ async function patternDetect(opts = {}) {
6639
+ const minFreq = opts.min !== void 0 ? parseInt(opts.min, 10) : MIN_TAG_FREQ;
6640
+ if (!Number.isFinite(minFreq) || minFreq < 1) {
6641
+ console.log(chalk33.red("\u274C --min \uC740 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4."));
6642
+ process.exitCode = 1;
6643
+ return;
6644
+ }
6645
+ const cwd = process.cwd();
6646
+ const loaded = loadForMutation(cwd);
6647
+ if (!loaded.ok) {
6648
+ console.log(chalk33.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uAC10\uC9C0 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874). \uBC31\uC5C5 \uD655\uC778 \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694."));
6649
+ process.exitCode = 1;
6650
+ return;
6651
+ }
6652
+ const mem = loaded.mem;
6653
+ const candidates = detectCandidates(mem, minFreq);
6654
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6655
+ const { added, updated } = reconcilePatterns(mem.patterns, candidates, now);
6655
6656
  if (added > 0 || updated > 0) {
6656
6657
  writeMemory(cwd, mem);
6657
6658
  }
package/dist/mcp/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  resolveVhkCliInvocation,
4
4
  startMcpServer
5
- } from "../chunk-WQWPM364.js";
5
+ } from "../chunk-OHGVVAKT.js";
6
6
 
7
7
  // src/mcp/index.ts
8
8
  var cli = resolveVhkCliInvocation();
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "@byh3071/vhk",
3
- "version": "2.3.1",
4
- "description": "Vibe Harness Kit — AI 코딩 도구·기기를 바꿔도 규칙·맥락이 따라가는 포터빌리티 CLI (sync: Cursor·Claude·Windsurf·Copilot·Antigravity / cloud 백업)",
5
- "bin": {
6
- "vhk": "dist/index.js",
7
- "vhk-mcp": "dist/mcp/index.js"
8
- },
9
- "type": "module",
10
- "scripts": {
11
- "dev": "tsx src/index.ts",
12
- "build": "tsup",
13
- "test": "vitest",
14
- "test:run": "vitest --run",
15
- "prepublishOnly": "pnpm build && pnpm test:run",
16
- "save": "vhk save",
17
- "check": "vhk check",
18
- "scan": "vhk secure scan",
19
- "recap": "vhk recap",
20
- "ship": "vhk ship",
21
- "doctor": "vhk doctor"
22
- },
23
- "files": [
24
- "dist",
25
- "README.md",
26
- "LICENSE"
27
- ],
28
- "keywords": [
29
- "vibe-coding",
30
- "harness",
31
- "cli",
32
- "scaffold",
33
- "session-log",
34
- "rules-sync",
35
- "portability",
36
- "ai-coding",
37
- "cursor",
38
- "claude",
39
- "windsurf",
40
- "copilot",
41
- "context-sync"
42
- ],
43
- "author": "byh3071 <byh3071@gmail.com>",
44
- "license": "MIT",
45
- "repository": {
46
- "type": "git",
47
- "url": "git+https://github.com/byh3071-cpu/vhk.git"
48
- },
49
- "engines": {
50
- "node": ">=20"
51
- },
52
- "dependencies": {
53
- "@modelcontextprotocol/sdk": "^1.29.0",
54
- "@notionhq/client": "^5.22.0",
55
- "chalk": "^5.6.2",
56
- "commander": "^14.0.3",
57
- "handlebars": "^4.7.9",
58
- "inquirer": "^9.3.8",
59
- "ora": "^9.4.0",
60
- "simple-git": "^3.36.0",
61
- "zod": "^4.4.3"
62
- },
63
- "devDependencies": {
64
- "@types/inquirer": "^9.0.9",
65
- "@types/node": "^25.9.1",
66
- "ignore": "^7.0.5",
67
- "tsup": "^8.5.1",
68
- "tsx": "^4.22.3",
69
- "typescript": "^6.0.3",
70
- "vitest": "^4.1.7"
71
- }
72
- }
1
+ {
2
+ "name": "@byh3071/vhk",
3
+ "version": "2.3.2",
4
+ "description": "Vibe Harness Kit — AI 코딩 도구·기기를 바꿔도 규칙·맥락이 따라가는 포터빌리티 CLI (sync: Cursor·Claude·Windsurf·Copilot·Antigravity / cloud 백업)",
5
+ "bin": {
6
+ "vhk": "dist/index.js",
7
+ "vhk-mcp": "dist/mcp/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "dev": "tsx src/index.ts",
12
+ "build": "tsup",
13
+ "test": "vitest",
14
+ "test:run": "vitest --run",
15
+ "prepublishOnly": "pnpm build && pnpm test:run",
16
+ "save": "vhk save",
17
+ "check": "vhk check",
18
+ "scan": "vhk secure scan",
19
+ "recap": "vhk recap",
20
+ "ship": "vhk ship",
21
+ "doctor": "vhk doctor"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "keywords": [
29
+ "vibe-coding",
30
+ "harness",
31
+ "cli",
32
+ "scaffold",
33
+ "session-log",
34
+ "rules-sync",
35
+ "portability",
36
+ "ai-coding",
37
+ "cursor",
38
+ "claude",
39
+ "windsurf",
40
+ "copilot",
41
+ "context-sync"
42
+ ],
43
+ "author": "byh3071 <byh3071@gmail.com>",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/byh3071-cpu/vhk.git"
48
+ },
49
+ "engines": {
50
+ "node": ">=20"
51
+ },
52
+ "dependencies": {
53
+ "@modelcontextprotocol/sdk": "^1.29.0",
54
+ "@notionhq/client": "^5.22.0",
55
+ "chalk": "^5.6.2",
56
+ "commander": "^14.0.3",
57
+ "handlebars": "^4.7.9",
58
+ "inquirer": "^9.3.8",
59
+ "ora": "^9.4.0",
60
+ "simple-git": "^3.36.0",
61
+ "zod": "^4.4.3"
62
+ },
63
+ "devDependencies": {
64
+ "@types/inquirer": "^9.0.9",
65
+ "@types/node": "^25.9.1",
66
+ "ignore": "^7.0.5",
67
+ "tsup": "^8.5.1",
68
+ "tsx": "^4.22.3",
69
+ "typescript": "^6.0.3",
70
+ "vitest": "^4.1.7"
71
+ }
72
+ }