@byh3071/vhk 1.6.2 → 1.6.3

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.
@@ -323,7 +323,7 @@ var require_ignore = __commonJS({
323
323
  // path matching.
324
324
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
325
325
  // @returns {TestResult} true if a file is ignored
326
- test(path3, checkUnignored, mode) {
326
+ test(path7, checkUnignored, mode) {
327
327
  let ignored = false;
328
328
  let unignored = false;
329
329
  let matchedRule;
@@ -332,7 +332,7 @@ var require_ignore = __commonJS({
332
332
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
333
333
  return;
334
334
  }
335
- const matched = rule[mode].test(path3);
335
+ const matched = rule[mode].test(path7);
336
336
  if (!matched) {
337
337
  return;
338
338
  }
@@ -353,17 +353,17 @@ var require_ignore = __commonJS({
353
353
  var throwError = (message, Ctor) => {
354
354
  throw new Ctor(message);
355
355
  };
356
- var checkPath = (path3, originalPath, doThrow) => {
357
- if (!isString(path3)) {
356
+ var checkPath = (path7, originalPath, doThrow) => {
357
+ if (!isString(path7)) {
358
358
  return doThrow(
359
359
  `path must be a string, but got \`${originalPath}\``,
360
360
  TypeError
361
361
  );
362
362
  }
363
- if (!path3) {
363
+ if (!path7) {
364
364
  return doThrow(`path must not be empty`, TypeError);
365
365
  }
366
- if (checkPath.isNotRelative(path3)) {
366
+ if (checkPath.isNotRelative(path7)) {
367
367
  const r = "`path.relative()`d";
368
368
  return doThrow(
369
369
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -372,7 +372,7 @@ var require_ignore = __commonJS({
372
372
  }
373
373
  return true;
374
374
  };
375
- var isNotRelative = (path3) => REGEX_TEST_INVALID_PATH.test(path3);
375
+ var isNotRelative = (path7) => REGEX_TEST_INVALID_PATH.test(path7);
376
376
  checkPath.isNotRelative = isNotRelative;
377
377
  checkPath.convert = (p) => p;
378
378
  var Ignore = class {
@@ -402,19 +402,19 @@ var require_ignore = __commonJS({
402
402
  }
403
403
  // @returns {TestResult}
404
404
  _test(originalPath, cache, checkUnignored, slices) {
405
- const path3 = originalPath && checkPath.convert(originalPath);
405
+ const path7 = originalPath && checkPath.convert(originalPath);
406
406
  checkPath(
407
- path3,
407
+ path7,
408
408
  originalPath,
409
409
  this._strictPathCheck ? throwError : RETURN_FALSE
410
410
  );
411
- return this._t(path3, cache, checkUnignored, slices);
411
+ return this._t(path7, cache, checkUnignored, slices);
412
412
  }
413
- checkIgnore(path3) {
414
- if (!REGEX_TEST_TRAILING_SLASH.test(path3)) {
415
- return this.test(path3);
413
+ checkIgnore(path7) {
414
+ if (!REGEX_TEST_TRAILING_SLASH.test(path7)) {
415
+ return this.test(path7);
416
416
  }
417
- const slices = path3.split(SLASH).filter(Boolean);
417
+ const slices = path7.split(SLASH).filter(Boolean);
418
418
  slices.pop();
419
419
  if (slices.length) {
420
420
  const parent = this._t(
@@ -427,18 +427,18 @@ var require_ignore = __commonJS({
427
427
  return parent;
428
428
  }
429
429
  }
430
- return this._rules.test(path3, false, MODE_CHECK_IGNORE);
430
+ return this._rules.test(path7, false, MODE_CHECK_IGNORE);
431
431
  }
432
- _t(path3, cache, checkUnignored, slices) {
433
- if (path3 in cache) {
434
- return cache[path3];
432
+ _t(path7, cache, checkUnignored, slices) {
433
+ if (path7 in cache) {
434
+ return cache[path7];
435
435
  }
436
436
  if (!slices) {
437
- slices = path3.split(SLASH).filter(Boolean);
437
+ slices = path7.split(SLASH).filter(Boolean);
438
438
  }
439
439
  slices.pop();
440
440
  if (!slices.length) {
441
- return cache[path3] = this._rules.test(path3, checkUnignored, MODE_IGNORE);
441
+ return cache[path7] = this._rules.test(path7, checkUnignored, MODE_IGNORE);
442
442
  }
443
443
  const parent = this._t(
444
444
  slices.join(SLASH) + SLASH,
@@ -446,29 +446,29 @@ var require_ignore = __commonJS({
446
446
  checkUnignored,
447
447
  slices
448
448
  );
449
- return cache[path3] = parent.ignored ? parent : this._rules.test(path3, checkUnignored, MODE_IGNORE);
449
+ return cache[path7] = parent.ignored ? parent : this._rules.test(path7, checkUnignored, MODE_IGNORE);
450
450
  }
451
- ignores(path3) {
452
- return this._test(path3, this._ignoreCache, false).ignored;
451
+ ignores(path7) {
452
+ return this._test(path7, this._ignoreCache, false).ignored;
453
453
  }
454
454
  createFilter() {
455
- return (path3) => !this.ignores(path3);
455
+ return (path7) => !this.ignores(path7);
456
456
  }
457
457
  filter(paths) {
458
458
  return makeArray(paths).filter(this.createFilter());
459
459
  }
460
460
  // @returns {TestResult}
461
- test(path3) {
462
- return this._test(path3, this._testCache, true);
461
+ test(path7) {
462
+ return this._test(path7, this._testCache, true);
463
463
  }
464
464
  };
465
465
  var factory = (options) => new Ignore(options);
466
- var isPathValid = (path3) => checkPath(path3 && checkPath.convert(path3), path3, RETURN_FALSE);
466
+ var isPathValid = (path7) => checkPath(path7 && checkPath.convert(path7), path7, RETURN_FALSE);
467
467
  var setupWindows = () => {
468
468
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
469
469
  checkPath.convert = makePosix;
470
470
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
471
- checkPath.isNotRelative = (path3) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path3) || isNotRelative(path3);
471
+ checkPath.isNotRelative = (path7) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path7) || isNotRelative(path7);
472
472
  };
473
473
  if (
474
474
  // Detect `process` so that it can run in browsers.
@@ -483,76 +483,15 @@ var require_ignore = __commonJS({
483
483
  }
484
484
  });
485
485
 
486
- // src/commands/deploy.ts
487
- import { existsSync } from "fs";
486
+ // src/commands/sync.ts
488
487
  import chalk2 from "chalk";
488
+ import fs4 from "fs";
489
+ import path4 from "path";
489
490
  import inquirer from "inquirer";
490
491
 
491
- // src/lib/exec.ts
492
- import { execFileSync } from "child_process";
493
- var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
494
- function platformCmd(cmd) {
495
- if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
496
- return `${cmd}.cmd`;
497
- }
498
- return cmd;
499
- }
500
- function resolveCmd(cmd, args) {
501
- if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
502
- return { bin: "cmd.exe", argv: ["/d", "/s", "/c", `${cmd}.cmd`, ...args] };
503
- }
504
- return { bin: platformCmd(cmd), argv: args };
505
- }
506
- var DEFAULT_EXEC_TIMEOUT_MS = 6e5;
507
- var NETWORK_EXEC_TIMEOUT_MS = 3e4;
508
- function resolveTimeout(timeoutMs, fallback) {
509
- const v = timeoutMs === void 0 ? fallback : timeoutMs;
510
- return v > 0 ? v : void 0;
511
- }
512
- function isTimeoutError(e, timeout) {
513
- if (!timeout) return false;
514
- return e.code === "ETIMEDOUT";
515
- }
516
- function safeExecFile(cmd, args, opts = {}) {
517
- const { bin, argv } = resolveCmd(cmd, args);
518
- const env2 = opts.env ? { ...process.env, ...opts.env } : void 0;
519
- const timeout = resolveTimeout(opts.timeoutMs, DEFAULT_EXEC_TIMEOUT_MS);
520
- try {
521
- const out = execFileSync(bin, argv, {
522
- encoding: "utf-8",
523
- stdio: ["pipe", "pipe", "pipe"],
524
- env: env2,
525
- ...timeout ? { timeout, killSignal: "SIGTERM" } : {}
526
- }).toString();
527
- return { ok: true, out: out.trim() };
528
- } catch (err) {
529
- const e = err;
530
- const stdout = e.stdout ? e.stdout.toString() : "";
531
- let msg = e.message ?? String(err);
532
- if (isTimeoutError(e, timeout)) {
533
- msg = `\uBA85\uB839 \uC2DC\uAC04 \uCD08\uACFC (timeout ${timeout}ms): ${cmd} ${args.join(" ")}`.trim();
534
- }
535
- return { ok: false, err: msg, out: stdout.trim() };
536
- }
537
- }
538
- function safeExecFileStream(cmd, args, opts = {}) {
539
- const { bin, argv } = resolveCmd(cmd, args);
540
- const timeout = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : void 0;
541
- try {
542
- execFileSync(bin, argv, {
543
- encoding: "utf-8",
544
- stdio: "inherit",
545
- ...timeout ? { timeout, killSignal: "SIGTERM" } : {}
546
- });
547
- return { ok: true };
548
- } catch (err) {
549
- const e = err;
550
- let msg = err instanceof Error ? err.message : String(err);
551
- if (isTimeoutError(e, timeout)) {
552
- msg = `\uBA85\uB839 \uC2DC\uAC04 \uCD08\uACFC (timeout ${timeout}ms): ${cmd} ${args.join(" ")}`.trim();
553
- }
554
- return { ok: false, err: msg };
555
- }
492
+ // src/lib/date.ts
493
+ function localDate(d = /* @__PURE__ */ new Date()) {
494
+ return d.toLocaleDateString("sv-SE");
556
495
  }
557
496
 
558
497
  // src/i18n/ko.ts
@@ -926,7 +865,10 @@ var ko = {
926
865
  },
927
866
  context: {
928
867
  title: "\uD504\uB85C\uC81D\uD2B8 \uCEE8\uD14D\uC2A4\uD2B8 \uC0DD\uC131",
929
- showTitle: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C"
868
+ showTitle: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C",
869
+ resumeMissing: "\u{1F9ED} AI \uC138\uC158 \uBCF5\uC6D0 \uCEE8\uD14D\uC2A4\uD2B8 \uC5C6\uC74C \u2192 \uC0DD\uC131: vhk context",
870
+ resumeExists: "\u{1F9ED} \uC0C8 \uC138\uC158\uC774\uBA74 AI \uCEE8\uD14D\uC2A4\uD2B8 \uBCF5\uC6D0: vhk context-show (\uAC31\uC2E0: vhk context)",
871
+ resumeStale: "\u{1F9ED} \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC624\uB798\uB428(\uCF54\uB4DC \uBCC0\uACBD \uC774\uD6C4) \u2192 \uAC31\uC2E0: vhk context"
930
872
  },
931
873
  brief: {
932
874
  title: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551"
@@ -937,7 +879,9 @@ var ko = {
937
879
  initTitle: "\u{1F3D7}\uFE0F goals/ \uAD6C\uC870 \uC2A4\uCE90\uD3F4\uB529",
938
880
  checkTitle: "\u2705 Goal \uAC8C\uC774\uD2B8 \uAC80\uC99D",
939
881
  doneTitle: "\u{1F3C1} Goal \uC644\uB8CC \uCC98\uB9AC",
882
+ syncTitle: "\u{1F504} Goal \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uB3D9\uAE30\uD654",
940
883
  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.`,
884
+ skippedFiles: (n) => `\u26A0 \uC2A4\uD0A4\uB9C8 \uBD88\uC77C\uCE58\uB85C \uBB34\uC2DC\uB41C \uD30C\uC77C ${n}\uAC1C (goal \uB85C \uC548 \uC7A1\uD798 \u2014 silent skip):`,
941
885
  notFound: (id) => `goal id ${id} \uC5C6\uC74C \u2014 vhk goal list \uB85C \uD655\uC778\uD558\uC138\uC694.`
942
886
  },
943
887
  agent: {
@@ -946,8 +890,8 @@ var ko = {
946
890
  resumeTitle: "\u25B6\uFE0F HARD_STOP \uD574\uC81C"
947
891
  }
948
892
  };
949
- function lookup(path3) {
950
- const parts = path3.split(".");
893
+ function lookup(path7) {
894
+ const parts = path7.split(".");
951
895
  let cur = ko;
952
896
  for (const part of parts) {
953
897
  if (cur === null || typeof cur !== "object") return void 0;
@@ -965,7 +909,124 @@ function t(key, ...args) {
965
909
  }
966
910
 
967
911
  // src/lib/next-step.ts
912
+ import fs2 from "fs";
913
+ import path2 from "path";
968
914
  import chalk from "chalk";
915
+
916
+ // src/lib/drift.ts
917
+ import fs from "fs";
918
+ import path from "path";
919
+
920
+ // src/lib/git-repo.ts
921
+ import { execFileSync } from "child_process";
922
+ function getGitRoot(cwd = process.cwd()) {
923
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
924
+ encoding: "utf-8",
925
+ cwd,
926
+ stdio: ["pipe", "pipe", "pipe"]
927
+ }).trim();
928
+ }
929
+ function gitOut(args, cwd) {
930
+ return execFileSync("git", args, {
931
+ encoding: "utf-8",
932
+ cwd,
933
+ stdio: ["pipe", "pipe", "pipe"]
934
+ });
935
+ }
936
+ function gitRun(args, cwd) {
937
+ execFileSync("git", args, { stdio: "pipe", cwd });
938
+ }
939
+ function getExecErrorMessage(err) {
940
+ if (err && typeof err === "object" && "stderr" in err) {
941
+ const stderr = err.stderr;
942
+ if (Buffer.isBuffer(stderr)) return stderr.toString("utf-8").trim();
943
+ if (typeof stderr === "string") return stderr.trim();
944
+ }
945
+ return err instanceof Error ? err.message : String(err);
946
+ }
947
+ function hasGitRemote(cwd) {
948
+ try {
949
+ return gitOut(["remote"], cwd).trim().length > 0;
950
+ } catch {
951
+ return false;
952
+ }
953
+ }
954
+ function countLocalCommits(cwd) {
955
+ try {
956
+ const out = gitOut(["rev-list", "--count", "HEAD"], cwd).trim();
957
+ return parseInt(out, 10) || 0;
958
+ } catch {
959
+ return 0;
960
+ }
961
+ }
962
+
963
+ // src/lib/drift.ts
964
+ function normalizeForCompare(s) {
965
+ return s.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n+$/, "\n");
966
+ }
967
+ function checkRuleDrift(rootDir) {
968
+ const rulesPath = path.join(rootDir, "RULES.md");
969
+ if (!fs.existsSync(rulesPath)) return { checked: false, results: [] };
970
+ const rulesContent = fs.readFileSync(rulesPath, "utf-8");
971
+ const sections = parseRulesMd(rulesContent);
972
+ const projectName = deriveProjectName(rulesContent);
973
+ const results = [];
974
+ for (const target of SYNC_TARGETS) {
975
+ const fullPath = path.join(rootDir, target.path);
976
+ if (!fs.existsSync(fullPath)) {
977
+ results.push({ path: target.path, status: "missing" });
978
+ continue;
979
+ }
980
+ const expected = normalizeForCompare(target.generate(sections, projectName));
981
+ const actual = normalizeForCompare(fs.readFileSync(fullPath, "utf-8"));
982
+ results.push({ path: target.path, status: expected === actual ? "ok" : "drifted" });
983
+ }
984
+ return { checked: true, results };
985
+ }
986
+ var CONTEXT_GIT_MARKER = "vhk-context-git";
987
+ var CONTEXT_PATH = ".vhk/context.md";
988
+ function extractContextSha(content) {
989
+ const m = content.match(new RegExp(`${CONTEXT_GIT_MARKER}:\\s*([0-9a-f]{7,40})`));
990
+ return m ? m[1] : null;
991
+ }
992
+ var CONTEXT_SOURCE_PATHS = ["package.json", "goals", "docs/state/learnings.md"];
993
+ function contextSourcesChanged(generatedSha, rootDir) {
994
+ const content = gitOut(
995
+ ["diff", "--name-only", generatedSha, "HEAD", "--", ...CONTEXT_SOURCE_PATHS],
996
+ rootDir
997
+ ).trim();
998
+ if (content) return true;
999
+ const structural = gitOut(
1000
+ ["diff", "--name-only", "--diff-filter=ADR", generatedSha, "HEAD"],
1001
+ rootDir
1002
+ ).trim();
1003
+ return structural.length > 0;
1004
+ }
1005
+ function checkContextDrift(rootDir) {
1006
+ const ctxPath = path.join(rootDir, CONTEXT_PATH);
1007
+ if (!fs.existsSync(ctxPath)) return { checked: false, stale: false };
1008
+ const generatedSha = extractContextSha(fs.readFileSync(ctxPath, "utf-8"));
1009
+ if (!generatedSha) return { checked: false, stale: false };
1010
+ let currentSha;
1011
+ try {
1012
+ currentSha = gitOut(["rev-parse", "HEAD"], rootDir).trim();
1013
+ } catch {
1014
+ return { checked: false, stale: false };
1015
+ }
1016
+ if (!currentSha) return { checked: false, stale: false };
1017
+ if (currentSha.startsWith(generatedSha) || generatedSha.startsWith(currentSha)) {
1018
+ return { checked: true, stale: false, generatedSha, currentSha };
1019
+ }
1020
+ let stale;
1021
+ try {
1022
+ stale = contextSourcesChanged(generatedSha, rootDir);
1023
+ } catch {
1024
+ return { checked: false, stale: false };
1025
+ }
1026
+ return { checked: true, stale, generatedSha, currentSha };
1027
+ }
1028
+
1029
+ // src/lib/next-step.ts
969
1030
  function printNextStep(step) {
970
1031
  console.log("");
971
1032
  console.log(chalk.cyan.bold("\u2501\u2501\u2501 \uB2E4\uC74C\uC5D0 \uC774\uAC83\uB9CC \uD558\uC138\uC694 \u2501\u2501\u2501"));
@@ -989,6 +1050,580 @@ function printNextStep(step) {
989
1050
  console.log(chalk.cyan.bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
990
1051
  console.log("");
991
1052
  }
1053
+ function printContextResumeHint(cwd = process.cwd()) {
1054
+ const ctxPath = path2.join(cwd, ".vhk", "context.md");
1055
+ if (!fs2.existsSync(ctxPath)) {
1056
+ console.log(chalk.dim(` ${t("context.resumeMissing")}`));
1057
+ return;
1058
+ }
1059
+ const drift = checkContextDrift(cwd);
1060
+ if (drift.checked && drift.stale) {
1061
+ console.log(chalk.dim(` ${t("context.resumeStale")}`));
1062
+ return;
1063
+ }
1064
+ console.log(chalk.dim(` ${t("context.resumeExists")}`));
1065
+ }
1066
+
1067
+ // src/lib/backup.ts
1068
+ import fs3 from "fs";
1069
+ import path3 from "path";
1070
+ var BACKUPS_REL = path3.join(".vhk", "backups");
1071
+ var VHK_GITIGNORE_REL = path3.join(".vhk", ".gitignore");
1072
+ function fsSafeStamp(d) {
1073
+ return d.toISOString().replace(/[:.]/g, "-");
1074
+ }
1075
+ function ensureVhkIgnored(rootDir, ...entries) {
1076
+ const giPath = path3.join(rootDir, VHK_GITIGNORE_REL);
1077
+ fs3.mkdirSync(path3.dirname(giPath), { recursive: true });
1078
+ let content = fs3.existsSync(giPath) ? fs3.readFileSync(giPath, "utf-8") : "";
1079
+ const present = new Set(content.split("\n").map((l) => l.trim().replace(/\/$/, "")));
1080
+ const missing = entries.filter((e) => !present.has(e.trim().replace(/\/$/, "")));
1081
+ if (missing.length === 0) return;
1082
+ if (content.length > 0 && !content.endsWith("\n")) content += "\n";
1083
+ content += missing.join("\n") + "\n";
1084
+ fs3.writeFileSync(giPath, content, "utf-8");
1085
+ }
1086
+ function walkRelFiles(baseDir, cur = baseDir) {
1087
+ const out = [];
1088
+ for (const entry of fs3.readdirSync(cur)) {
1089
+ const full = path3.join(cur, entry);
1090
+ if (fs3.statSync(full).isDirectory()) {
1091
+ out.push(...walkRelFiles(baseDir, full));
1092
+ } else {
1093
+ out.push(path3.relative(baseDir, full).split(path3.sep).join("/"));
1094
+ }
1095
+ }
1096
+ return out;
1097
+ }
1098
+ function saveBackup(files, rootDir, stamp) {
1099
+ const baseId = stamp ?? fsSafeStamp(/* @__PURE__ */ new Date());
1100
+ let id = baseId;
1101
+ let n = 1;
1102
+ while (fs3.existsSync(path3.join(rootDir, BACKUPS_REL, id))) {
1103
+ id = `${baseId}-${String(n++).padStart(3, "0")}`;
1104
+ }
1105
+ const backupDir = path3.join(rootDir, BACKUPS_REL, id);
1106
+ const saved = [];
1107
+ for (const rel of files) {
1108
+ const src = path3.join(rootDir, rel);
1109
+ if (!fs3.existsSync(src)) continue;
1110
+ const dest = path3.join(backupDir, rel);
1111
+ fs3.mkdirSync(path3.dirname(dest), { recursive: true });
1112
+ fs3.copyFileSync(src, dest);
1113
+ saved.push(rel);
1114
+ }
1115
+ ensureVhkIgnored(rootDir, "backups/");
1116
+ return { id, dir: backupDir, files: saved };
1117
+ }
1118
+ function backupOrderKey(id) {
1119
+ const m = /^(.*Z)(?:-(\d+))?$/.exec(id);
1120
+ return m ? [m[1], m[2] ? parseInt(m[2], 10) : 0] : [id, 0];
1121
+ }
1122
+ function listBackups(rootDir) {
1123
+ const root = path3.join(rootDir, BACKUPS_REL);
1124
+ if (!fs3.existsSync(root)) return [];
1125
+ return fs3.readdirSync(root).filter((e) => fs3.statSync(path3.join(root, e)).isDirectory()).sort((a, b) => {
1126
+ const [ba, na] = backupOrderKey(a);
1127
+ const [bb, nb] = backupOrderKey(b);
1128
+ if (ba !== bb) return ba < bb ? 1 : -1;
1129
+ return nb - na;
1130
+ }).map((id) => {
1131
+ const dir = path3.join(root, id);
1132
+ return { id, dir, files: walkRelFiles(dir) };
1133
+ });
1134
+ }
1135
+ function restoreBackup(id, rootDir) {
1136
+ const backupDir = path3.join(rootDir, BACKUPS_REL, id);
1137
+ if (!fs3.existsSync(backupDir)) {
1138
+ throw new Error(`\uBC31\uC5C5 \uC5C6\uC74C: ${id}`);
1139
+ }
1140
+ const rels = walkRelFiles(backupDir);
1141
+ for (const rel of rels) {
1142
+ const src = path3.join(backupDir, rel);
1143
+ const dest = path3.join(rootDir, rel);
1144
+ fs3.mkdirSync(path3.dirname(dest), { recursive: true });
1145
+ fs3.copyFileSync(src, dest);
1146
+ }
1147
+ return rels;
1148
+ }
1149
+ function pruneBackups(keepN, rootDir) {
1150
+ const all = listBackups(rootDir);
1151
+ const toDelete = all.slice(Math.max(0, keepN));
1152
+ for (const b of toDelete) {
1153
+ fs3.rmSync(b.dir, { recursive: true, force: true });
1154
+ }
1155
+ return toDelete.map((b) => b.id);
1156
+ }
1157
+
1158
+ // src/lib/rules-import.ts
1159
+ import { existsSync, readFileSync } from "fs";
1160
+ import { join } from "path";
1161
+ var ADOPT_SOURCES = [
1162
+ ".cursorrules",
1163
+ "CLAUDE.md",
1164
+ "AGENTS.md",
1165
+ ".windsurfrules",
1166
+ ".github/copilot-instructions.md"
1167
+ ];
1168
+ var PREAMBLE_TITLE = "\uC11C\uBB38";
1169
+ function detectExistingRuleFiles(cwd) {
1170
+ const found = [];
1171
+ for (const rel of ADOPT_SOURCES) {
1172
+ const full = join(cwd, rel);
1173
+ if (existsSync(full)) {
1174
+ try {
1175
+ found.push({ path: rel, content: readFileSync(full, "utf-8") });
1176
+ } catch {
1177
+ }
1178
+ }
1179
+ }
1180
+ return found;
1181
+ }
1182
+ function splitSections(content) {
1183
+ const sections = [];
1184
+ let title = "";
1185
+ let buf = [];
1186
+ const preamble = [];
1187
+ let sawHeading = false;
1188
+ for (const line of content.split("\n")) {
1189
+ if (line.startsWith("## ")) {
1190
+ sawHeading = true;
1191
+ if (title) sections.push({ title, content: buf.join("\n").trim() });
1192
+ title = line.replace("## ", "").trim();
1193
+ buf = [];
1194
+ } else if (title) {
1195
+ buf.push(line);
1196
+ } else if (!sawHeading) {
1197
+ preamble.push(line);
1198
+ }
1199
+ }
1200
+ if (title) sections.push({ title, content: buf.join("\n").trim() });
1201
+ const pre = preamble.join("\n").trim();
1202
+ if (pre) sections.unshift({ title: PREAMBLE_TITLE, content: pre });
1203
+ return sections;
1204
+ }
1205
+ function buildAdoptedRules(files, projectName) {
1206
+ const order = [];
1207
+ const byTitle = /* @__PURE__ */ new Map();
1208
+ for (const file of files) {
1209
+ for (const sec of splitSections(file.content)) {
1210
+ let merged = byTitle.get(sec.title);
1211
+ if (!merged) {
1212
+ merged = { title: sec.title, parts: [] };
1213
+ byTitle.set(sec.title, merged);
1214
+ order.push(sec.title);
1215
+ }
1216
+ merged.parts.push({ source: file.path, content: sec.content });
1217
+ }
1218
+ }
1219
+ const lines = [
1220
+ `# ${projectName} \u2014 Rules`,
1221
+ "",
1222
+ "> \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 \uB2E8\uC77C \uC18C\uC2A4(SoT). \uAE30\uC874 \uADDC\uCE59\uC744 `vhk init` adopt \uB85C \uAC00\uC838\uC654\uC2B5\uB2C8\uB2E4.",
1223
+ "> \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 \uC774 \uD30C\uC77C\uC5D0\uC11C\uB9CC \u2014 `vhk sync` \uB85C \uAC01 \uB3C4\uAD6C\uC5D0 \uC804\uD30C\uB429\uB2C8\uB2E4.",
1224
+ ""
1225
+ ];
1226
+ for (const title of order) {
1227
+ const merged = byTitle.get(title);
1228
+ const nonEmpty = merged.parts.filter((p) => p.content.trim());
1229
+ if (!nonEmpty.length) continue;
1230
+ lines.push(`## ${title}`);
1231
+ for (const part of nonEmpty) {
1232
+ lines.push(`<!-- \uCD9C\uCC98: ${part.source} -->`);
1233
+ lines.push(part.content);
1234
+ }
1235
+ lines.push("");
1236
+ }
1237
+ return lines.join("\n");
1238
+ }
1239
+
1240
+ // src/commands/sync.ts
1241
+ var CURSORRULES_KEYS = ["\uCF54\uB529 \uADDC\uCE59", "\uAE30\uC220 \uC2A4\uD0DD", "\uC544\uD0A4\uD14D\uCC98", "\uB514\uC790\uC778", "Anti-patterns", "\uCEE4\uBC0B"];
1242
+ var CLAUDE_MD_KEYS = ["\uAE30\uB85D", "\uB85C\uADF8", "ADR", "\uD2B8\uB7EC\uBE14\uC288\uD305", "TIL", "/done", "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8"];
1243
+ function findUnmappedSections(sections) {
1244
+ const allKeys = [...CURSORRULES_KEYS, ...CLAUDE_MD_KEYS];
1245
+ return sections.filter((s) => s.title !== PREAMBLE_TITLE && !allKeys.some((k) => s.title.includes(k))).map((s) => s.title);
1246
+ }
1247
+ function parseRulesMd(content) {
1248
+ const sections = [];
1249
+ const lines = content.split("\n");
1250
+ let currentTitle = "";
1251
+ let currentContent = [];
1252
+ for (const line of lines) {
1253
+ if (line.startsWith("## ")) {
1254
+ if (currentTitle) {
1255
+ sections.push({ title: currentTitle, content: currentContent.join("\n").trim() });
1256
+ }
1257
+ currentTitle = line.replace("## ", "").trim();
1258
+ currentContent = [];
1259
+ } else {
1260
+ currentContent.push(line);
1261
+ }
1262
+ }
1263
+ if (currentTitle) {
1264
+ sections.push({ title: currentTitle, content: currentContent.join("\n").trim() });
1265
+ }
1266
+ return sections;
1267
+ }
1268
+ function buildCodingDoc(headerTitle, sections, projectName) {
1269
+ const codingSections = sections.filter(
1270
+ (s) => CURSORRULES_KEYS.some((k) => s.title.includes(k))
1271
+ );
1272
+ const lines = [
1273
+ `# ${projectName} \u2014 ${headerTitle}`,
1274
+ "",
1275
+ "> \uCF54\uB529/\uB514\uC790\uC778 \uC804\uC6A9. \uAE30\uB85D/\uC6B4\uC601 \u2192 CLAUDE.md \uCC38\uC870.",
1276
+ "> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
1277
+ "",
1278
+ "## \uD544\uC218 \uCC38\uC870",
1279
+ "- docs/PRD.md \xB7 docs/ARCHITECTURE.md \xB7 CLAUDE.md \xB7 RULES.md",
1280
+ ""
1281
+ ];
1282
+ for (const section of codingSections) {
1283
+ lines.push(`## ${section.title}`);
1284
+ lines.push(section.content);
1285
+ lines.push("");
1286
+ }
1287
+ return lines.join("\n");
1288
+ }
1289
+ function toCursorrules(sections, projectName) {
1290
+ return buildCodingDoc("Cursor Rules", sections, projectName);
1291
+ }
1292
+ function toWindsurfrules(sections, projectName) {
1293
+ return buildCodingDoc("Windsurf Rules", sections, projectName);
1294
+ }
1295
+ function toCopilotInstructions(sections, projectName) {
1296
+ return buildCodingDoc("GitHub Copilot Instructions", sections, projectName);
1297
+ }
1298
+ var ANTIGRAVITY_CHAR_LIMIT = 12e3;
1299
+ 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";
1300
+ function truncateForAntigravity(content, limit = ANTIGRAVITY_CHAR_LIMIT) {
1301
+ if (Buffer.byteLength(content, "utf8") <= limit) return content;
1302
+ const SAFETY = 200;
1303
+ const budget = limit - Buffer.byteLength(ANTIGRAVITY_TRUNCATE_MARKER, "utf8") - SAFETY;
1304
+ let lo = 0;
1305
+ let hi = content.length;
1306
+ while (lo < hi) {
1307
+ const mid = lo + hi + 1 >> 1;
1308
+ if (Buffer.byteLength(content.slice(0, mid), "utf8") <= budget) lo = mid;
1309
+ else hi = mid - 1;
1310
+ }
1311
+ const charCut = lo;
1312
+ let cut = content.lastIndexOf("\n## ", charCut);
1313
+ if (cut < charCut * 0.5) {
1314
+ const nl = content.lastIndexOf("\n", charCut);
1315
+ cut = nl > 0 ? nl : charCut;
1316
+ }
1317
+ return content.slice(0, cut).trimEnd() + ANTIGRAVITY_TRUNCATE_MARKER;
1318
+ }
1319
+ function toAntigravityRules(sections, projectName) {
1320
+ return truncateForAntigravity(buildCodingDoc("Antigravity Rules", sections, projectName));
1321
+ }
1322
+ 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.";
1323
+ function toClaudeMd(sections, existing) {
1324
+ const recordSections = sections.filter(
1325
+ (s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k))
1326
+ );
1327
+ const cleaned = existing.split("\n").filter((line) => line.trim() !== CLAUDE_AUTOGEN_BANNER).join("\n");
1328
+ const statusMatch = cleaned.match(/## 현재 상태[\s\S]*?(?=\n## |$)/);
1329
+ const statusSection = statusMatch ? statusMatch[0].trimEnd() : "";
1330
+ const header = cleaned.split("## ")[0].trim();
1331
+ const lines = [
1332
+ header,
1333
+ "",
1334
+ statusSection,
1335
+ "",
1336
+ CLAUDE_AUTOGEN_BANNER,
1337
+ ""
1338
+ ];
1339
+ for (const section of recordSections) {
1340
+ lines.push(`## ${section.title}`);
1341
+ lines.push(section.content);
1342
+ lines.push("");
1343
+ }
1344
+ return lines.join("\n");
1345
+ }
1346
+ function toAgentsMd(sections, projectName) {
1347
+ const codingSections = sections.filter((s) => CURSORRULES_KEYS.some((k) => s.title.includes(k)));
1348
+ const recordSections = sections.filter((s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k)));
1349
+ const lines = [
1350
+ `# ${projectName} \u2014 AGENTS.md (\uC5D0\uC774\uC804\uD2B8 \uC791\uB3D9 \uADDC\uC57D)`,
1351
+ "",
1352
+ "> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
1353
+ "> \uBE60\uB978 \uC2DC\uC791(\uD1A0\uD070 \uC808\uAC10): `docs/context/agent-compact.md` \uB97C \uBA3C\uC800 \uC77D\uC73C\uC138\uC694.",
1354
+ "",
1355
+ "## Loop Protocol",
1356
+ "- \uB8E8\uD504: `context \u2192 goal next \u2192 \uC791\uC5C5 \u2192 goal check \u2192 goal done`",
1357
+ "- \uC791\uC5C5 \uC2DC\uC791 \uC2DC `.vhk/HARD_STOP` \uD655\uC778 \u2014 \uC788\uC73C\uBA74 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC989\uC2DC \uC911\uB2E8.",
1358
+ "- active goal \uB9CC \uC791\uC5C5. `docs/state`(next-task/blockers/learnings)\uB294 SoT, append-only.",
1359
+ "- \uAC8C\uC774\uD2B8(tsc / test:run / build) \uD1B5\uACFC\uD574\uC57C\uB9CC `vhk goal done`.",
1360
+ ""
1361
+ ];
1362
+ for (const section of codingSections) {
1363
+ lines.push(`## ${section.title}`);
1364
+ lines.push(section.content);
1365
+ lines.push("");
1366
+ }
1367
+ for (const section of recordSections) {
1368
+ lines.push(`## ${section.title}`);
1369
+ lines.push(section.content);
1370
+ lines.push("");
1371
+ }
1372
+ return lines.join("\n");
1373
+ }
1374
+ function deriveProjectName(rulesContent) {
1375
+ const firstLine = rulesContent.split("\n")[0];
1376
+ return firstLine.replace(/^#\s*/, "").replace(/\s*—.*/, "").trim() || "Project";
1377
+ }
1378
+ var SYNC_TARGETS = [
1379
+ { path: ".cursorrules", generate: toCursorrules, doneMessage: ko.sync.cursorrulesDone },
1380
+ { path: ".windsurfrules", generate: toWindsurfrules, doneMessage: ko.sync.windsurfDone },
1381
+ { path: ".github/copilot-instructions.md", generate: toCopilotInstructions, doneMessage: ko.sync.copilotDone },
1382
+ { path: ".agents/rules/vhk-rules.md", generate: toAntigravityRules, doneMessage: ko.sync.antigravityDone },
1383
+ // AGENTS.md — 6번째 타겟. 항목 1개 추가로 sync·드리프트·백업 가드가 자동 반영된다.
1384
+ { path: "AGENTS.md", generate: toAgentsMd, doneMessage: ko.sync.agentsDone }
1385
+ ];
1386
+ var BACKUP_KEEP = 10;
1387
+ var SYNCED_MARKER_REL = path4.join(".vhk", ".synced");
1388
+ function buildSyncPlan(rootDir, sections, projectName) {
1389
+ const plan = [];
1390
+ for (const target of SYNC_TARGETS) {
1391
+ const fullPath = path4.join(rootDir, target.path);
1392
+ const exists = fs4.existsSync(fullPath);
1393
+ const newContent = target.generate(sections, projectName);
1394
+ const drift = exists ? normalizeForCompare(fs4.readFileSync(fullPath, "utf-8")) !== normalizeForCompare(newContent) : false;
1395
+ plan.push({ path: target.path, newContent, doneMessage: target.doneMessage, exists, drift });
1396
+ }
1397
+ const claudePath = path4.join(rootDir, "CLAUDE.md");
1398
+ const claudeExists = fs4.existsSync(claudePath);
1399
+ const existingClaude = claudeExists ? fs4.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
1400
+
1401
+ ## \uD604\uC7AC \uC0C1\uD0DC
1402
+ - **Phase:** **FILL**
1403
+ - **\uBE14\uB85C\uCEE4:** \uC5C6\uC74C
1404
+ - **\uB2E4\uC74C \uC561\uC158:** **FILL**
1405
+ - **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${localDate()}`;
1406
+ const claudeNew = toClaudeMd(sections, existingClaude);
1407
+ const claudeDrift = claudeExists ? normalizeForCompare(existingClaude) !== normalizeForCompare(claudeNew) : false;
1408
+ plan.push({
1409
+ path: "CLAUDE.md",
1410
+ newContent: claudeNew,
1411
+ doneMessage: ko.sync.claudeDone,
1412
+ exists: claudeExists,
1413
+ drift: claudeDrift
1414
+ });
1415
+ return plan;
1416
+ }
1417
+ async function syncCore(rootDir, opts, confirmOverwrite) {
1418
+ const rulesContent = fs4.readFileSync(path4.join(rootDir, "RULES.md"), "utf-8");
1419
+ const sections = parseRulesMd(rulesContent);
1420
+ const projectName = deriveProjectName(rulesContent);
1421
+ const plan = buildSyncPlan(rootDir, sections, projectName);
1422
+ const firstSync = !fs4.existsSync(path4.join(rootDir, SYNCED_MARKER_REL));
1423
+ const unmapped = findUnmappedSections(sections);
1424
+ if (opts.dryRun) {
1425
+ return {
1426
+ dryRun: true,
1427
+ firstSync,
1428
+ backupId: null,
1429
+ backedUp: [],
1430
+ written: [],
1431
+ skipped: [],
1432
+ truncated: [],
1433
+ plan,
1434
+ unmapped
1435
+ };
1436
+ }
1437
+ const toBackup = plan.filter((p) => p.exists && (p.drift || firstSync)).map((p) => p.path);
1438
+ let backupId = null;
1439
+ let backedUp = [];
1440
+ if (toBackup.length) {
1441
+ const info = saveBackup(toBackup, rootDir);
1442
+ pruneBackups(BACKUP_KEEP, rootDir);
1443
+ backupId = info.id;
1444
+ backedUp = info.files;
1445
+ }
1446
+ const drifted = plan.filter((p) => p.drift);
1447
+ const overwriteDrift = drifted.length ? await confirmOverwrite(drifted) : true;
1448
+ const written = [];
1449
+ const skipped = [];
1450
+ const truncated = [];
1451
+ for (const item of plan) {
1452
+ if (item.drift && !overwriteDrift) {
1453
+ skipped.push(item.path);
1454
+ continue;
1455
+ }
1456
+ const fullPath = path4.join(rootDir, item.path);
1457
+ fs4.mkdirSync(path4.dirname(fullPath), { recursive: true });
1458
+ fs4.writeFileSync(fullPath, item.newContent, "utf-8");
1459
+ written.push(item.path);
1460
+ if (item.newContent.includes("Antigravity 12,000\uC790 \uC81C\uD55C\uC73C\uB85C \uC808\uC0AD\uB428")) {
1461
+ truncated.push(item.path);
1462
+ }
1463
+ }
1464
+ fs4.mkdirSync(path4.join(rootDir, ".vhk"), { recursive: true });
1465
+ fs4.writeFileSync(path4.join(rootDir, SYNCED_MARKER_REL), (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1466
+ ensureVhkIgnored(rootDir, ".synced");
1467
+ return { dryRun: false, firstSync, backupId, backedUp, written, skipped, truncated, plan, unmapped };
1468
+ }
1469
+ async function sync(opts = {}) {
1470
+ console.log(chalk2.bold(`
1471
+ ${ko.sync.title}
1472
+ `));
1473
+ const cwd = process.cwd();
1474
+ const rulesPath = path4.join(cwd, "RULES.md");
1475
+ if (!fs4.existsSync(rulesPath)) {
1476
+ console.log(chalk2.yellow(ko.sync.noRules));
1477
+ console.log(chalk2.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
1478
+ console.log(chalk2.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
1479
+ console.log("");
1480
+ console.log(chalk2.dim(" RULES.md \uAE30\uBCF8 \uAD6C\uC870:"));
1481
+ console.log(chalk2.dim(" ## \uD504\uB85C\uC81D\uD2B8 \uC815\uCCB4\uC131"));
1482
+ console.log(chalk2.dim(" ## \uAE30\uC220 \uC2A4\uD0DD"));
1483
+ console.log(chalk2.dim(" ## \uCF54\uB529 \uADDC\uCE59"));
1484
+ console.log(chalk2.dim(" ## \uAE30\uB85D \uADDC\uCE59"));
1485
+ console.log(chalk2.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
1486
+ return;
1487
+ }
1488
+ const sections = parseRulesMd(fs4.readFileSync(rulesPath, "utf-8"));
1489
+ console.log(chalk2.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
1490
+ const interactive = !!process.stdout.isTTY && !opts.yes;
1491
+ const confirmOverwrite = async (drifted) => {
1492
+ if (!interactive) return true;
1493
+ for (const d of drifted) console.log(chalk2.yellow(` ${ko.sync.driftWarn(d.path)}`));
1494
+ const { confirm } = await inquirer.prompt([
1495
+ {
1496
+ type: "confirm",
1497
+ name: "confirm",
1498
+ message: ko.sync.driftConfirm(drifted.length),
1499
+ default: false
1500
+ }
1501
+ ]);
1502
+ return confirm;
1503
+ };
1504
+ const result = await syncCore(cwd, opts, confirmOverwrite);
1505
+ if (result.unmapped.length) {
1506
+ console.error(
1507
+ chalk2.yellow(
1508
+ ` \u26A0\uFE0F ${result.unmapped.length}\uAC1C \uC139\uC158\uC774 \uC5B4\uB290 \uD0C0\uAE43\uC5D0\uB3C4 \uB9E4\uD551 \uC548 \uB3FC \uC0B0\uCD9C\uBB3C\uC5D0\uC11C \uC81C\uC678\uB428: ${result.unmapped.join(", ")}
1509
+ (\uCF54\uB529 \uADDC\uCE59/\uAE30\uC220 \uC2A4\uD0DD/\uCEE4\uBC0B/\uAE30\uB85D \uB4F1 \uD45C\uC900 \uC81C\uBAA9\uC744 \uC4F0\uAC70\uB098, \uC774 \uC139\uC158\uC740 RULES.md \uC5D0\uB9CC \uBCF4\uC874\uB429\uB2C8\uB2E4.)`
1510
+ )
1511
+ );
1512
+ }
1513
+ if (result.dryRun) {
1514
+ console.log(chalk2.cyan(`
1515
+ ${ko.sync.dryRunHeader}`));
1516
+ for (const item of result.plan) {
1517
+ console.log(ko.sync.dryRunWouldWrite(item.path, item.exists && item.drift));
1518
+ }
1519
+ const wouldBackup = result.plan.filter((p) => p.exists && (p.drift || result.firstSync)).map((p) => p.path);
1520
+ if (wouldBackup.length) {
1521
+ console.log(chalk2.dim(`
1522
+ \uBC31\uC5C5 \uC608\uC815(${wouldBackup.length}): ${wouldBackup.join(", ")}`));
1523
+ }
1524
+ return;
1525
+ }
1526
+ if (result.backupId) {
1527
+ if (result.firstSync) console.log(chalk2.cyan(` ${ko.sync.firstSync}`));
1528
+ if (!process.stdout.isTTY) {
1529
+ console.log(chalk2.yellow(` ${ko.sync.nonTtyAuto(result.backedUp.length, result.backupId)}`));
1530
+ } else {
1531
+ console.log(chalk2.cyan(` ${ko.sync.backupSaved(result.backedUp.length, result.backupId)}`));
1532
+ }
1533
+ }
1534
+ for (const p of result.written) {
1535
+ const item = result.plan.find((i) => i.path === p);
1536
+ if (item) console.log(chalk2.green(` ${item.doneMessage}`));
1537
+ }
1538
+ for (const _ of result.truncated) {
1539
+ console.log(chalk2.yellow(` \u26A0\uFE0F ${ko.sync.antigravityTruncated}`));
1540
+ }
1541
+ for (const p of result.skipped) {
1542
+ console.log(chalk2.gray(` ${ko.sync.skipped(p)}`));
1543
+ }
1544
+ console.log(chalk2.bold.green(`
1545
+ ${ko.sync.done}`));
1546
+ console.log(chalk2.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md + .windsurfrules"));
1547
+ console.log(chalk2.dim(" + .github/copilot-instructions.md + .agents/rules/vhk-rules.md (\uC790\uB3D9 \uC0DD\uC131)"));
1548
+ console.log(chalk2.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
1549
+ printNextStep({
1550
+ message: "\uADDC\uCE59 \uB3D9\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C Cursor\uAC00 \uC0C8 \uADDC\uCE59\uC744 \uB530\uB985\uB2C8\uB2E4.",
1551
+ command: "vhk \uC810\uAC80",
1552
+ cursorHint: "\uADDC\uCE59 \uC810\uAC80\uD574\uC918"
1553
+ });
1554
+ }
1555
+
1556
+ // src/commands/deploy.ts
1557
+ import { existsSync as existsSync2 } from "fs";
1558
+ import chalk3 from "chalk";
1559
+ import inquirer2 from "inquirer";
1560
+
1561
+ // src/lib/exec.ts
1562
+ import { execFileSync as execFileSync2 } from "child_process";
1563
+ var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
1564
+ function platformCmd(cmd) {
1565
+ if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
1566
+ return `${cmd}.cmd`;
1567
+ }
1568
+ return cmd;
1569
+ }
1570
+ function resolveCmd(cmd, args) {
1571
+ if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
1572
+ return { bin: "cmd.exe", argv: ["/d", "/s", "/c", `${cmd}.cmd`, ...args] };
1573
+ }
1574
+ return { bin: platformCmd(cmd), argv: args };
1575
+ }
1576
+ var DEFAULT_EXEC_TIMEOUT_MS = 6e5;
1577
+ var NETWORK_EXEC_TIMEOUT_MS = 3e4;
1578
+ function resolveTimeout(timeoutMs, fallback) {
1579
+ const v = timeoutMs === void 0 ? fallback : timeoutMs;
1580
+ return v > 0 ? v : void 0;
1581
+ }
1582
+ function isTimeoutError(e, timeout) {
1583
+ if (!timeout) return false;
1584
+ return e.code === "ETIMEDOUT";
1585
+ }
1586
+ function safeExecFile(cmd, args, opts = {}) {
1587
+ const { bin, argv } = resolveCmd(cmd, args);
1588
+ const env2 = opts.env ? { ...process.env, ...opts.env } : void 0;
1589
+ const timeout = resolveTimeout(opts.timeoutMs, DEFAULT_EXEC_TIMEOUT_MS);
1590
+ try {
1591
+ const out = execFileSync2(bin, argv, {
1592
+ encoding: "utf-8",
1593
+ stdio: ["pipe", "pipe", "pipe"],
1594
+ env: env2,
1595
+ ...timeout ? { timeout, killSignal: "SIGTERM" } : {}
1596
+ }).toString();
1597
+ return { ok: true, out: out.trim() };
1598
+ } catch (err) {
1599
+ const e = err;
1600
+ const stdout = e.stdout ? e.stdout.toString() : "";
1601
+ let msg = e.message ?? String(err);
1602
+ if (isTimeoutError(e, timeout)) {
1603
+ msg = `\uBA85\uB839 \uC2DC\uAC04 \uCD08\uACFC (timeout ${timeout}ms): ${cmd} ${args.join(" ")}`.trim();
1604
+ }
1605
+ return { ok: false, err: msg, out: stdout.trim() };
1606
+ }
1607
+ }
1608
+ function safeExecFileStream(cmd, args, opts = {}) {
1609
+ const { bin, argv } = resolveCmd(cmd, args);
1610
+ const timeout = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : void 0;
1611
+ try {
1612
+ execFileSync2(bin, argv, {
1613
+ encoding: "utf-8",
1614
+ stdio: "inherit",
1615
+ ...timeout ? { timeout, killSignal: "SIGTERM" } : {}
1616
+ });
1617
+ return { ok: true };
1618
+ } catch (err) {
1619
+ const e = err;
1620
+ let msg = err instanceof Error ? err.message : String(err);
1621
+ if (isTimeoutError(e, timeout)) {
1622
+ msg = `\uBA85\uB839 \uC2DC\uAC04 \uCD08\uACFC (timeout ${timeout}ms): ${cmd} ${args.join(" ")}`.trim();
1623
+ }
1624
+ return { ok: false, err: msg };
1625
+ }
1626
+ }
992
1627
 
993
1628
  // src/commands/deploy.ts
994
1629
  var PLATFORMS = {
@@ -1020,7 +1655,7 @@ var PLATFORMS = {
1020
1655
  function detectPlatform() {
1021
1656
  for (const [key, config] of Object.entries(PLATFORMS)) {
1022
1657
  for (const file of config.detectFiles) {
1023
- if (existsSync(file)) return key;
1658
+ if (existsSync2(file)) return key;
1024
1659
  }
1025
1660
  }
1026
1661
  return null;
@@ -1029,14 +1664,14 @@ function isCLIAvailable(cmd, checkArgs) {
1029
1664
  return safeExecFile(cmd, checkArgs).ok;
1030
1665
  }
1031
1666
  async function deploy() {
1032
- console.log(chalk2.bold("\n\u{1F680} " + t("deploy.title")));
1033
- console.log(chalk2.gray("\u2500".repeat(40)));
1667
+ console.log(chalk3.bold("\n\u{1F680} " + t("deploy.title")));
1668
+ console.log(chalk3.gray("\u2500".repeat(40)));
1034
1669
  let platform = detectPlatform();
1035
1670
  if (platform) {
1036
- console.log(chalk2.cyan(`
1671
+ console.log(chalk3.cyan(`
1037
1672
  \u{1F50D} \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${PLATFORMS[platform].name}`));
1038
1673
  } else {
1039
- const { selected } = await inquirer.prompt([
1674
+ const { selected } = await inquirer2.prompt([
1040
1675
  {
1041
1676
  type: "list",
1042
1677
  name: "selected",
@@ -1052,12 +1687,12 @@ async function deploy() {
1052
1687
  }
1053
1688
  const config = PLATFORMS[platform];
1054
1689
  if (!isCLIAvailable(config.command, config.checkArgs)) {
1055
- console.log(chalk2.red(`
1690
+ console.log(chalk3.red(`
1056
1691
  \u274C ${config.name} CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
1057
- console.log(chalk2.yellow(` \u2192 ${config.installHint}`));
1692
+ console.log(chalk3.yellow(` \u2192 ${config.installHint}`));
1058
1693
  return;
1059
1694
  }
1060
- const { confirm } = await inquirer.prompt([
1695
+ const { confirm } = await inquirer2.prompt([
1061
1696
  {
1062
1697
  type: "confirm",
1063
1698
  name: "confirm",
@@ -1066,15 +1701,15 @@ async function deploy() {
1066
1701
  }
1067
1702
  ]);
1068
1703
  if (!confirm) {
1069
- console.log(chalk2.gray("\uCDE8\uC18C\uB428"));
1704
+ console.log(chalk3.gray("\uCDE8\uC18C\uB428"));
1070
1705
  return;
1071
1706
  }
1072
- console.log(chalk2.cyan(`
1707
+ console.log(chalk3.cyan(`
1073
1708
  ${t("deploy.deploying")}
1074
1709
  `));
1075
1710
  const result = safeExecFileStream(config.command, config.commandArgs);
1076
1711
  if (result.ok) {
1077
- console.log(chalk2.green(`
1712
+ console.log(chalk3.green(`
1078
1713
  \u2705 ${t("deploy.success")}`));
1079
1714
  printNextStep({
1080
1715
  message: "\uBC30\uD3EC \uC644\uB8CC! \uC0AC\uC774\uD2B8\uB97C \uD655\uC778\uD558\uC138\uC694.",
@@ -1082,16 +1717,16 @@ ${t("deploy.deploying")}
1082
1717
  cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
1083
1718
  });
1084
1719
  } else {
1085
- console.log(chalk2.red(`
1720
+ console.log(chalk3.red(`
1086
1721
  \u274C ${t("deploy.failed")}`));
1087
- console.log(chalk2.red(result.err));
1722
+ console.log(chalk3.red(result.err));
1088
1723
  }
1089
1724
  }
1090
1725
 
1091
1726
  // src/commands/env.ts
1092
- import { existsSync as existsSync2, readFileSync, writeFileSync, appendFileSync, readdirSync } from "fs";
1093
- import { join } from "path";
1094
- import chalk3 from "chalk";
1727
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, appendFileSync, readdirSync } from "fs";
1728
+ import { join as join2 } from "path";
1729
+ import chalk4 from "chalk";
1095
1730
  function parseEnvKeys(content) {
1096
1731
  return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
1097
1732
  }
@@ -1107,7 +1742,7 @@ function loadDefinedEnvKeys(dir = ".") {
1107
1742
  if (!name.startsWith(".env")) continue;
1108
1743
  if (name.endsWith(".example") || name.endsWith(".sample")) continue;
1109
1744
  try {
1110
- for (const k of parseEnvKeys(readFileSync(join(dir, name), "utf-8"))) keys.add(k);
1745
+ for (const k of parseEnvKeys(readFileSync2(join2(dir, name), "utf-8"))) keys.add(k);
1111
1746
  } catch {
1112
1747
  }
1113
1748
  }
@@ -1115,36 +1750,36 @@ function loadDefinedEnvKeys(dir = ".") {
1115
1750
  }
1116
1751
  function ensureGitignore() {
1117
1752
  const gitignorePath = ".gitignore";
1118
- if (existsSync2(gitignorePath)) {
1119
- const content = readFileSync(gitignorePath, "utf-8");
1753
+ if (existsSync3(gitignorePath)) {
1754
+ const content = readFileSync2(gitignorePath, "utf-8");
1120
1755
  if (!content.split("\n").some((l) => l.trim() === ".env")) {
1121
1756
  appendFileSync(gitignorePath, "\n.env\n");
1122
- console.log(chalk3.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
1757
+ console.log(chalk4.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
1123
1758
  }
1124
1759
  } else {
1125
1760
  writeFileSync(gitignorePath, ".env\nnode_modules/\ndist/\n");
1126
- console.log(chalk3.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
1761
+ console.log(chalk4.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
1127
1762
  }
1128
1763
  }
1129
1764
  async function env() {
1130
- console.log(chalk3.bold("\n\u{1F510} " + t("env.title")));
1131
- console.log(chalk3.gray("\u2500".repeat(40)));
1132
- if (!existsSync2(".env")) {
1133
- console.log(chalk3.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
1134
- console.log(chalk3.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
1765
+ console.log(chalk4.bold("\n\u{1F510} " + t("env.title")));
1766
+ console.log(chalk4.gray("\u2500".repeat(40)));
1767
+ if (!existsSync3(".env")) {
1768
+ console.log(chalk4.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
1769
+ console.log(chalk4.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
1135
1770
  return;
1136
1771
  }
1137
- const envContent = readFileSync(".env", "utf-8");
1772
+ const envContent = readFileSync2(".env", "utf-8");
1138
1773
  const keys = parseEnvKeys(envContent);
1139
1774
  if (keys.length === 0) {
1140
- console.log(chalk3.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
1775
+ console.log(chalk4.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
1141
1776
  return;
1142
1777
  }
1143
1778
  const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
1144
1779
  writeFileSync(".env.example", exampleContent, "utf-8");
1145
- console.log(chalk3.green(`
1780
+ console.log(chalk4.green(`
1146
1781
  \u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`));
1147
- keys.forEach((k) => console.log(chalk3.gray(` ${k}`)));
1782
+ keys.forEach((k) => console.log(chalk4.gray(` ${k}`)));
1148
1783
  ensureGitignore();
1149
1784
  printNextStep({
1150
1785
  message: ".env.example \uC0DD\uC131 \uC644\uB8CC!",
@@ -1153,47 +1788,47 @@ async function env() {
1153
1788
  });
1154
1789
  }
1155
1790
  async function envCheck() {
1156
- console.log(chalk3.bold("\n\u{1F50D} " + t("env.checkTitle")));
1157
- console.log(chalk3.gray("\u2500".repeat(40)));
1158
- if (!existsSync2(".env.example")) {
1159
- console.log(chalk3.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
1791
+ console.log(chalk4.bold("\n\u{1F50D} " + t("env.checkTitle")));
1792
+ console.log(chalk4.gray("\u2500".repeat(40)));
1793
+ if (!existsSync3(".env.example")) {
1794
+ console.log(chalk4.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
1160
1795
  return;
1161
1796
  }
1162
- const requiredKeys = parseEnvKeys(readFileSync(".env.example", "utf-8"));
1797
+ const requiredKeys = parseEnvKeys(readFileSync2(".env.example", "utf-8"));
1163
1798
  const currentKeys = loadDefinedEnvKeys();
1164
1799
  const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
1165
1800
  const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
1166
- console.log(chalk3.cyan(`
1801
+ console.log(chalk4.cyan(`
1167
1802
  \u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`));
1168
1803
  if (missing.length === 0) {
1169
- console.log(chalk3.green("\n\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!"));
1804
+ console.log(chalk4.green("\n\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!"));
1170
1805
  } else {
1171
- console.log(chalk3.red(`
1806
+ console.log(chalk4.red(`
1172
1807
  \u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`));
1173
- missing.forEach((k) => console.log(chalk3.red(` \u2022 ${k}`)));
1808
+ missing.forEach((k) => console.log(chalk4.red(` \u2022 ${k}`)));
1174
1809
  process.exitCode = 1;
1175
1810
  }
1176
1811
  if (extra.length > 0) {
1177
- console.log(chalk3.yellow(`
1812
+ console.log(chalk4.yellow(`
1178
1813
  \u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`));
1179
- extra.forEach((k) => console.log(chalk3.yellow(` \u2022 ${k}`)));
1814
+ extra.forEach((k) => console.log(chalk4.yellow(` \u2022 ${k}`)));
1180
1815
  }
1181
1816
  ensureGitignore();
1182
1817
  }
1183
1818
 
1184
1819
  // src/commands/publish.ts
1185
- import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
1186
- import chalk4 from "chalk";
1187
- import inquirer2 from "inquirer";
1820
+ import { existsSync as existsSync4, writeFileSync as writeFileSync2 } from "fs";
1821
+ import chalk5 from "chalk";
1822
+ import inquirer3 from "inquirer";
1188
1823
  import ora from "ora";
1189
1824
 
1190
1825
  // src/lib/read-json.ts
1191
- import { readFileSync as readFileSync2 } from "fs";
1826
+ import { readFileSync as readFileSync3 } from "fs";
1192
1827
  function stripBom(text) {
1193
1828
  return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
1194
1829
  }
1195
1830
  function readJsonFile(filePath) {
1196
- const raw = stripBom(readFileSync2(filePath, "utf-8"));
1831
+ const raw = stripBom(readFileSync3(filePath, "utf-8"));
1197
1832
  return JSON.parse(raw);
1198
1833
  }
1199
1834
 
@@ -1252,23 +1887,23 @@ function gitPostRelease(newVersion) {
1252
1887
  };
1253
1888
  }
1254
1889
  async function publish() {
1255
- console.log(chalk4.bold("\n\u{1F4E6} " + t("publish.title")));
1256
- console.log(chalk4.gray("\u2500".repeat(40)));
1257
- if (!existsSync3("package.json")) {
1258
- console.log(chalk4.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
1890
+ console.log(chalk5.bold("\n\u{1F4E6} " + t("publish.title")));
1891
+ console.log(chalk5.gray("\u2500".repeat(40)));
1892
+ if (!existsSync4("package.json")) {
1893
+ console.log(chalk5.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
1259
1894
  return;
1260
1895
  }
1261
1896
  let pkg;
1262
1897
  try {
1263
1898
  pkg = readJsonFile("package.json");
1264
1899
  } catch {
1265
- console.log(chalk4.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
1900
+ console.log(chalk5.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
1266
1901
  return;
1267
1902
  }
1268
1903
  const currentVersion = pkg.version || "0.0.0";
1269
- console.log(chalk4.cyan(`
1904
+ console.log(chalk5.cyan(`
1270
1905
  \u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${currentVersion}`));
1271
- const { bumpType } = await inquirer2.prompt([
1906
+ const { bumpType } = await inquirer3.prompt([
1272
1907
  {
1273
1908
  type: "list",
1274
1909
  name: "bumpType",
@@ -1281,16 +1916,16 @@ async function publish() {
1281
1916
  }
1282
1917
  ]);
1283
1918
  const newVersion = bumpVersion(currentVersion, bumpType);
1284
- console.log(chalk4.cyan(`
1919
+ console.log(chalk5.cyan(`
1285
1920
  \u{1F195} \uC0C8 \uBC84\uC804: v${newVersion}`));
1286
1921
  pkg.version = newVersion;
1287
1922
  writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1288
- console.log(chalk4.green("\u2705 package.json \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8"));
1923
+ console.log(chalk5.green("\u2705 package.json \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8"));
1289
1924
  const buildSpinner = ora(t("publish.building")).start();
1290
1925
  const buildResult = safeExecFile("pnpm", ["build"]);
1291
1926
  if (!buildResult.ok) {
1292
1927
  buildSpinner.fail(t("publish.buildFailed"));
1293
- console.log(chalk4.red(buildResult.err.slice(0, 500)));
1928
+ console.log(chalk5.red(buildResult.err.slice(0, 500)));
1294
1929
  pkg.version = currentVersion;
1295
1930
  writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1296
1931
  return;
@@ -1300,13 +1935,13 @@ async function publish() {
1300
1935
  const testResult = safeExecFile("pnpm", ["test", "--run"]);
1301
1936
  if (!testResult.ok) {
1302
1937
  testSpinner.fail(t("publish.testFailed"));
1303
- console.log(chalk4.red(testResult.err.slice(0, 500)));
1938
+ console.log(chalk5.red(testResult.err.slice(0, 500)));
1304
1939
  pkg.version = currentVersion;
1305
1940
  writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1306
1941
  return;
1307
1942
  }
1308
1943
  testSpinner.succeed(t("publish.testSuccess"));
1309
- const { confirm } = await inquirer2.prompt([
1944
+ const { confirm } = await inquirer3.prompt([
1310
1945
  {
1311
1946
  type: "confirm",
1312
1947
  name: "confirm",
@@ -1317,37 +1952,37 @@ async function publish() {
1317
1952
  if (!confirm) {
1318
1953
  pkg.version = currentVersion;
1319
1954
  writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1320
- console.log(chalk4.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
1955
+ console.log(chalk5.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
1321
1956
  return;
1322
1957
  }
1323
- console.log(chalk4.cyan(`
1958
+ console.log(chalk5.cyan(`
1324
1959
  \u{1F4E4} ${t("publish.publishing")}`));
1325
- console.log(chalk4.gray(" 2FA \uD65C\uC131\uD654 \uC2DC: OTP 6\uC790\uB9AC \uC785\uB825 \uB610\uB294 \uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D URL \uD074\uB9AD (Windows Hello / PIN \uC9C0\uC6D0)"));
1960
+ console.log(chalk5.gray(" 2FA \uD65C\uC131\uD654 \uC2DC: OTP 6\uC790\uB9AC \uC785\uB825 \uB610\uB294 \uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D URL \uD074\uB9AD (Windows Hello / PIN \uC9C0\uC6D0)"));
1326
1961
  const pubResult = safeExecFileStream("npm", ["publish", "--access", "public"]);
1327
1962
  if (!pubResult.ok) {
1328
- console.log(chalk4.red(`
1963
+ console.log(chalk5.red(`
1329
1964
  \u2716 ${t("publish.publishFailed")}`));
1330
- console.log(chalk4.red(pubResult.err.slice(0, 500)));
1965
+ console.log(chalk5.red(pubResult.err.slice(0, 500)));
1331
1966
  pkg.version = currentVersion;
1332
1967
  writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1333
- console.log(chalk4.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
1968
+ console.log(chalk5.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
1334
1969
  return;
1335
1970
  }
1336
- console.log(chalk4.green(`
1971
+ console.log(chalk5.green(`
1337
1972
  \u2714 ${t("publish.publishSuccess")}`));
1338
1973
  const git = gitPostRelease(newVersion);
1339
1974
  if (git.warning) {
1340
- console.log(chalk4.yellow(`
1975
+ console.log(chalk5.yellow(`
1341
1976
  \u26A0\uFE0F ${git.warning}`));
1342
- console.log(chalk4.dim(` npm \uBC30\uD3EC\uB294 \uC774\uBBF8 \uC131\uACF5\uD588\uC2B5\uB2C8\uB2E4 (v${newVersion}).`));
1977
+ console.log(chalk5.dim(` npm \uBC30\uD3EC\uB294 \uC774\uBBF8 \uC131\uACF5\uD588\uC2B5\uB2C8\uB2E4 (v${newVersion}).`));
1343
1978
  } else if (git.tagged && git.pushed) {
1344
- console.log(chalk4.green(`
1979
+ console.log(chalk5.green(`
1345
1980
  \u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131 + push \uC644\uB8CC`));
1346
1981
  } else if (git.tagged) {
1347
- console.log(chalk4.yellow(`
1982
+ console.log(chalk5.yellow(`
1348
1983
  \u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131\uB428 (push\uB294 \uC218\uB3D9\uC73C\uB85C)`));
1349
1984
  }
1350
- console.log(chalk4.green.bold(`
1985
+ console.log(chalk5.green.bold(`
1351
1986
  \u{1F389} v${newVersion} \uBC30\uD3EC \uC644\uB8CC!`));
1352
1987
  printNextStep({
1353
1988
  message: "npm \uBC30\uD3EC \uC644\uB8CC!",
@@ -1357,13 +1992,13 @@ async function publish() {
1357
1992
  }
1358
1993
 
1359
1994
  // src/commands/audit.ts
1360
- import { existsSync as existsSync4 } from "fs";
1361
- import chalk5 from "chalk";
1362
- import inquirer3 from "inquirer";
1995
+ import { existsSync as existsSync5 } from "fs";
1996
+ import chalk6 from "chalk";
1997
+ import inquirer4 from "inquirer";
1363
1998
  import ora2 from "ora";
1364
1999
  function detectCurrentPM() {
1365
- if (existsSync4("pnpm-lock.yaml")) return "pnpm";
1366
- if (existsSync4("yarn.lock")) return "yarn";
2000
+ if (existsSync5("pnpm-lock.yaml")) return "pnpm";
2001
+ if (existsSync5("yarn.lock")) return "yarn";
1367
2002
  return "npm";
1368
2003
  }
1369
2004
  function parseAuditOutput(output, pm) {
@@ -1403,26 +2038,26 @@ function runAuditFix(pm) {
1403
2038
  return result.ok ? { ok: true } : { ok: false, err: result.err };
1404
2039
  }
1405
2040
  async function audit(autoFix = false) {
1406
- console.log(chalk5.bold("\n\u{1F6E1}\uFE0F " + t("audit.title")));
1407
- console.log(chalk5.gray("\u2500".repeat(40)));
2041
+ console.log(chalk6.bold("\n\u{1F6E1}\uFE0F " + t("audit.title")));
2042
+ console.log(chalk6.gray("\u2500".repeat(40)));
1408
2043
  const pm = detectCurrentPM();
1409
- console.log(chalk5.cyan(`\u{1F4E6} \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${pm}`));
2044
+ console.log(chalk6.cyan(`\u{1F4E6} \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${pm}`));
1410
2045
  const spinner = ora2("\uBCF4\uC548 \uAC10\uC0AC \uC2E4\uD589 \uC911...").start();
1411
2046
  const output = runAuditJson(pm);
1412
2047
  spinner.stop();
1413
2048
  const summary = parseAuditOutput(output, pm);
1414
2049
  if (summary.total === 0) {
1415
- console.log(chalk5.green.bold("\n\u{1F389} \uCDE8\uC57D\uC810\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4!"));
2050
+ console.log(chalk6.green.bold("\n\u{1F389} \uCDE8\uC57D\uC810\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4!"));
1416
2051
  return;
1417
2052
  }
1418
- console.log(chalk5.bold("\n\u{1F4CA} \uCDE8\uC57D\uC810 \uC694\uC57D:"));
1419
- if (summary.critical > 0) console.log(chalk5.red(` \u{1F534} Critical: ${summary.critical}`));
1420
- if (summary.high > 0) console.log(chalk5.red(` \u{1F7E0} High: ${summary.high}`));
1421
- if (summary.moderate > 0) console.log(chalk5.yellow(` \u{1F7E1} Moderate: ${summary.moderate}`));
1422
- if (summary.low > 0) console.log(chalk5.gray(` \u26AA Low: ${summary.low}`));
1423
- console.log(chalk5.bold(`
2053
+ console.log(chalk6.bold("\n\u{1F4CA} \uCDE8\uC57D\uC810 \uC694\uC57D:"));
2054
+ if (summary.critical > 0) console.log(chalk6.red(` \u{1F534} Critical: ${summary.critical}`));
2055
+ if (summary.high > 0) console.log(chalk6.red(` \u{1F7E0} High: ${summary.high}`));
2056
+ if (summary.moderate > 0) console.log(chalk6.yellow(` \u{1F7E1} Moderate: ${summary.moderate}`));
2057
+ if (summary.low > 0) console.log(chalk6.gray(` \u26AA Low: ${summary.low}`));
2058
+ console.log(chalk6.bold(`
1424
2059
  \uCD1D ${summary.total}\uAC1C\uC758 \uCDE8\uC57D\uC810`));
1425
- const shouldRunFix = autoFix ? true : summary.critical > 0 || summary.high > 0 ? (await inquirer3.prompt([
2060
+ const shouldRunFix = autoFix ? true : summary.critical > 0 || summary.high > 0 ? (await inquirer4.prompt([
1426
2061
  {
1427
2062
  type: "confirm",
1428
2063
  name: "shouldFix",
@@ -1447,17 +2082,17 @@ async function audit(autoFix = false) {
1447
2082
  }
1448
2083
 
1449
2084
  // src/lib/version.ts
1450
- import { existsSync as existsSync5 } from "fs";
1451
- import { dirname, join as join2 } from "path";
2085
+ import { existsSync as existsSync6 } from "fs";
2086
+ import { dirname, join as join3 } from "path";
1452
2087
  import { fileURLToPath } from "url";
1453
2088
  function getVhkVersion() {
1454
2089
  const dir = dirname(fileURLToPath(import.meta.url));
1455
2090
  for (const pkgPath of [
1456
- join2(dir, "../../package.json"),
1457
- join2(dir, "../package.json")
2091
+ join3(dir, "../../package.json"),
2092
+ join3(dir, "../package.json")
1458
2093
  ]) {
1459
2094
  try {
1460
- if (existsSync5(pkgPath)) {
2095
+ if (existsSync6(pkgPath)) {
1461
2096
  const pkg = readJsonFile(pkgPath);
1462
2097
  if (pkg.version) return pkg.version;
1463
2098
  }
@@ -1469,7 +2104,7 @@ function getVhkVersion() {
1469
2104
  }
1470
2105
 
1471
2106
  // src/mcp/cli-path.ts
1472
- import { existsSync as existsSync6 } from "fs";
2107
+ import { existsSync as existsSync7 } from "fs";
1473
2108
  import { fileURLToPath as fileURLToPath2 } from "url";
1474
2109
  import { dirname as dirname2, resolve } from "path";
1475
2110
  function pickCliInvocation(globalAvailable, localCli, localExists) {
@@ -1487,17 +2122,17 @@ function localCliPath() {
1487
2122
  function resolveVhkCliInvocation() {
1488
2123
  const globalAvailable = safeExecFile("vhk", ["--version"]).ok;
1489
2124
  const local = localCliPath();
1490
- return pickCliInvocation(globalAvailable, local, existsSync6(local));
2125
+ return pickCliInvocation(globalAvailable, local, existsSync7(local));
1491
2126
  }
1492
2127
 
1493
2128
  // src/mcp/server.ts
1494
2129
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1495
2130
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1496
2131
  import { z } from "zod";
1497
- import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
2132
+ import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
1498
2133
 
1499
2134
  // src/lib/scan-secrets.ts
1500
- import fs3 from "fs";
2135
+ import fs7 from "fs";
1501
2136
 
1502
2137
  // src/lib/secret-patterns.ts
1503
2138
  var SECRET_PATTERNS = [
@@ -1563,19 +2198,19 @@ function maskSecret(value) {
1563
2198
  }
1564
2199
 
1565
2200
  // src/lib/scan-files.ts
1566
- import fs2 from "fs";
1567
- import path2 from "path";
2201
+ import fs6 from "fs";
2202
+ import path6 from "path";
1568
2203
 
1569
2204
  // src/lib/check-secure.ts
1570
2205
  var import_ignore = __toESM(require_ignore(), 1);
1571
- import fs from "fs";
1572
- import path from "path";
1573
- import chalk6 from "chalk";
2206
+ import fs5 from "fs";
2207
+ import path5 from "path";
2208
+ import chalk7 from "chalk";
1574
2209
  function loadGitignore(rootDir) {
1575
2210
  const ig = (0, import_ignore.default)();
1576
- const gitignorePath = path.join(rootDir, ".gitignore");
1577
- if (fs.existsSync(gitignorePath)) {
1578
- const content = fs.readFileSync(gitignorePath, "utf-8");
2211
+ const gitignorePath = path5.join(rootDir, ".gitignore");
2212
+ if (fs5.existsSync(gitignorePath)) {
2213
+ const content = fs5.readFileSync(gitignorePath, "utf-8");
1579
2214
  ig.add(content);
1580
2215
  }
1581
2216
  return ig;
@@ -1590,14 +2225,14 @@ function findExposedSensitiveFiles(rootDir, ig = loadGitignore(rootDir), maxDept
1590
2225
  if (depth > maxDepth) return;
1591
2226
  let entries;
1592
2227
  try {
1593
- entries = fs.readdirSync(dir, { withFileTypes: true });
2228
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
1594
2229
  } catch {
1595
2230
  return;
1596
2231
  }
1597
2232
  for (const entry of entries) {
1598
2233
  if (entry.name === "node_modules" || entry.name === ".git") continue;
1599
- const fullPath = path.join(dir, entry.name);
1600
- const rel = path.relative(rootDir, fullPath).replace(/\\/g, "/");
2234
+ const fullPath = path5.join(dir, entry.name);
2235
+ const rel = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
1601
2236
  if (entry.isDirectory()) {
1602
2237
  if (!isPathIgnored(ig, rel + "/")) walk(fullPath, depth + 1);
1603
2238
  continue;
@@ -1620,8 +2255,8 @@ function isSensitiveName(name) {
1620
2255
  return false;
1621
2256
  }
1622
2257
  function checkProjectSecurity(rootDir = process.cwd()) {
1623
- const gitignorePath = path.join(rootDir, ".gitignore");
1624
- const missingGitignore = !fs.existsSync(gitignorePath);
2258
+ const gitignorePath = path5.join(rootDir, ".gitignore");
2259
+ const missingGitignore = !fs5.existsSync(gitignorePath);
1625
2260
  const ig = loadGitignore(rootDir);
1626
2261
  const exposedPaths = findExposedSensitiveFiles(rootDir, ig);
1627
2262
  const warnings = [];
@@ -1644,7 +2279,7 @@ function printSecurityWarnings(rootDir = process.cwd()) {
1644
2279
  const result = checkProjectSecurity(rootDir);
1645
2280
  if (result.ok) return true;
1646
2281
  for (const w of result.warnings) {
1647
- console.log(chalk6.yellow(` \u26A0\uFE0F ${w}`));
2282
+ console.log(chalk7.yellow(` \u26A0\uFE0F ${w}`));
1648
2283
  }
1649
2284
  return false;
1650
2285
  }
@@ -1692,19 +2327,19 @@ var MAX_SCAN_FILE_BYTES = 512 * 1024;
1692
2327
  function isScannableFileName(fileName) {
1693
2328
  if (SKIP_FILE_NAMES.has(fileName)) return false;
1694
2329
  if (fileName.startsWith(".env")) return true;
1695
- return SCAN_EXTENSIONS.has(path2.extname(fileName).toLowerCase());
2330
+ return SCAN_EXTENSIONS.has(path6.extname(fileName).toLowerCase());
1696
2331
  }
1697
2332
  function walkProjectFiles(rootDir, onFile, ig = loadGitignore(rootDir)) {
1698
2333
  function walk(dir) {
1699
2334
  let entries;
1700
2335
  try {
1701
- entries = fs2.readdirSync(dir, { withFileTypes: true });
2336
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
1702
2337
  } catch {
1703
2338
  return;
1704
2339
  }
1705
2340
  for (const entry of entries) {
1706
- const fullPath = path2.join(dir, entry.name);
1707
- const rel = path2.relative(rootDir, fullPath).replace(/\\/g, "/");
2341
+ const fullPath = path6.join(dir, entry.name);
2342
+ const rel = path6.relative(rootDir, fullPath).replace(/\\/g, "/");
1708
2343
  if (entry.isDirectory()) {
1709
2344
  if (IGNORE_DIRS.has(entry.name)) continue;
1710
2345
  if (isPathIgnored(ig, `${rel}/`)) continue;
@@ -1715,7 +2350,7 @@ function walkProjectFiles(rootDir, onFile, ig = loadGitignore(rootDir)) {
1715
2350
  if (isPathIgnored(ig, rel)) continue;
1716
2351
  let size = 0;
1717
2352
  try {
1718
- size = fs2.statSync(fullPath).size;
2353
+ size = fs6.statSync(fullPath).size;
1719
2354
  } catch {
1720
2355
  continue;
1721
2356
  }
@@ -1760,7 +2395,7 @@ function scanProjectForSecrets(cwd) {
1760
2395
  let truncated = false;
1761
2396
  walkProjectFiles(cwd, (filePath, relPath) => {
1762
2397
  scannedFiles++;
1763
- const content = fs3.readFileSync(filePath, "utf-8");
2398
+ const content = fs7.readFileSync(filePath, "utf-8");
1764
2399
  const lines = content.split("\n");
1765
2400
  lines.forEach((line, idx) => {
1766
2401
  if (truncated) return;
@@ -1919,7 +2554,7 @@ ${last.out}
1919
2554
  );
1920
2555
  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 () => {
1921
2556
  const lines = [];
1922
- if (existsSync7("package.json")) {
2557
+ if (existsSync8("package.json")) {
1923
2558
  try {
1924
2559
  const pkg = readJsonFile("package.json");
1925
2560
  lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
@@ -2004,7 +2639,7 @@ ${last.out}
2004
2639
  checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
2005
2640
  const test = safeExecFile("pnpm", ["test", "--run"]);
2006
2641
  checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
2007
- if (existsSync7("package.json")) {
2642
+ if (existsSync8("package.json")) {
2008
2643
  try {
2009
2644
  const pkg = readJsonFile("package.json");
2010
2645
  checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
@@ -2042,11 +2677,11 @@ ${last.out}
2042
2677
  const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
2043
2678
  const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
2044
2679
  required.forEach((f) => {
2045
- lines.push(` ${existsSync7(f) ? "\u2705" : "\u274C"} ${f}`);
2680
+ lines.push(` ${existsSync8(f) ? "\u2705" : "\u274C"} ${f}`);
2046
2681
  });
2047
2682
  lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
2048
2683
  recommended.forEach((f) => {
2049
- lines.push(` ${existsSync7(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
2684
+ lines.push(` ${existsSync8(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
2050
2685
  });
2051
2686
  return { content: [{ type: "text", text: lines.join("\n") }] };
2052
2687
  });
@@ -2080,18 +2715,18 @@ ${log.out}` }] };
2080
2715
  description: ".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore\uC5D0 .env \uC790\uB3D9 \uCD94\uAC00"
2081
2716
  },
2082
2717
  async () => {
2083
- if (!existsSync7(".env")) {
2718
+ if (!existsSync8(".env")) {
2084
2719
  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." }] };
2085
2720
  }
2086
- const keys = parseEnvKeys(readFileSync4(".env", "utf-8"));
2721
+ const keys = parseEnvKeys(readFileSync5(".env", "utf-8"));
2087
2722
  if (keys.length === 0) {
2088
2723
  return { content: [{ type: "text", text: "\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
2089
2724
  }
2090
2725
  const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
2091
2726
  writeFileSync3(".env.example", exampleContent, "utf-8");
2092
2727
  const gitignoreLines = [];
2093
- if (existsSync7(".gitignore")) {
2094
- const content = readFileSync4(".gitignore", "utf-8");
2728
+ if (existsSync8(".gitignore")) {
2729
+ const content = readFileSync5(".gitignore", "utf-8");
2095
2730
  if (!content.split("\n").some((l) => l.trim() === ".env")) {
2096
2731
  appendFileSync2(".gitignore", "\n.env\n");
2097
2732
  gitignoreLines.push("\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428");
@@ -2111,11 +2746,11 @@ ${log.out}` }] };
2111
2746
  description: "\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (.env.example \uAE30\uC900)"
2112
2747
  },
2113
2748
  async () => {
2114
- if (!existsSync7(".env.example")) {
2749
+ if (!existsSync8(".env.example")) {
2115
2750
  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." }] };
2116
2751
  }
2117
- const requiredKeys = parseEnvKeys(readFileSync4(".env.example", "utf-8"));
2118
- const currentKeys = existsSync7(".env") ? parseEnvKeys(readFileSync4(".env", "utf-8")) : [];
2752
+ const requiredKeys = parseEnvKeys(readFileSync5(".env.example", "utf-8"));
2753
+ const currentKeys = existsSync8(".env") ? parseEnvKeys(readFileSync5(".env", "utf-8")) : [];
2119
2754
  const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
2120
2755
  const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
2121
2756
  const lines = [`\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`];
@@ -2231,7 +2866,7 @@ ${cliStatus}
2231
2866
  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)"
2232
2867
  },
2233
2868
  async () => {
2234
- if (!existsSync7("package.json")) {
2869
+ if (!existsSync8("package.json")) {
2235
2870
  return { content: [{ type: "text", text: "\u274C package.json \uC5C6\uC74C." }] };
2236
2871
  }
2237
2872
  try {
@@ -2259,7 +2894,7 @@ ${cliStatus}
2259
2894
  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)"
2260
2895
  },
2261
2896
  async () => {
2262
- const current = existsSync7("pnpm-lock.yaml") ? "pnpm" : existsSync7("yarn.lock") ? "yarn" : existsSync7("package-lock.json") ? "npm" : null;
2897
+ const current = existsSync8("pnpm-lock.yaml") ? "pnpm" : existsSync8("yarn.lock") ? "yarn" : existsSync8("package-lock.json") ? "npm" : null;
2263
2898
  const candidates = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current);
2264
2899
  const lines = [`\uD604\uC7AC PM: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00 (lock \uD30C\uC77C \uC5C6\uC74C)"}`];
2265
2900
  for (const pm of candidates) {
@@ -2325,7 +2960,23 @@ export {
2325
2960
  __toESM,
2326
2961
  ko,
2327
2962
  t,
2963
+ localDate,
2964
+ listBackups,
2965
+ restoreBackup,
2966
+ detectExistingRuleFiles,
2967
+ buildAdoptedRules,
2968
+ sync,
2969
+ getGitRoot,
2970
+ gitOut,
2971
+ gitRun,
2972
+ getExecErrorMessage,
2973
+ hasGitRemote,
2974
+ countLocalCommits,
2975
+ checkRuleDrift,
2976
+ CONTEXT_GIT_MARKER,
2977
+ checkContextDrift,
2328
2978
  printNextStep,
2979
+ printContextResumeHint,
2329
2980
  require_ignore,
2330
2981
  printSecurityWarnings,
2331
2982
  filterTrackedPaths,