@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.
package/dist/index.js CHANGED
@@ -1,33 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CONTEXT_GIT_MARKER,
3
4
  MAX_SCAN_FILE_BYTES,
4
5
  MAX_SECRET_FINDINGS,
5
6
  NETWORK_EXEC_TIMEOUT_MS,
6
7
  __toESM,
7
8
  audit,
9
+ buildAdoptedRules,
10
+ checkContextDrift,
11
+ checkRuleDrift,
12
+ countLocalCommits,
8
13
  deploy,
14
+ detectExistingRuleFiles,
9
15
  env,
10
16
  envCheck,
11
17
  filterSevereFindings,
12
18
  filterTrackedPaths,
19
+ getExecErrorMessage,
20
+ getGitRoot,
13
21
  getVhkVersion,
22
+ gitOut,
23
+ gitRun,
24
+ hasGitRemote,
14
25
  ko,
26
+ listBackups,
27
+ localDate,
28
+ printContextResumeHint,
15
29
  printNextStep,
16
30
  printSecurityWarnings,
17
31
  publish,
18
32
  readJsonFile,
19
33
  require_ignore,
34
+ restoreBackup,
20
35
  safeExecFile,
21
36
  scanProjectForSecrets,
22
37
  startMcpServer,
38
+ sync,
23
39
  t
24
- } from "./chunk-ACJN723Q.js";
40
+ } from "./chunk-53RJHPP6.js";
25
41
 
26
42
  // src/index.ts
27
43
  import { Command, Help } from "commander";
28
44
  import { pathToFileURL } from "url";
29
- import chalk35 from "chalk";
30
- import inquirer14 from "inquirer";
45
+ import chalk34 from "chalk";
46
+ import inquirer13 from "inquirer";
31
47
 
32
48
  // src/lib/nlp-router.ts
33
49
  function normalize(input) {
@@ -289,7 +305,8 @@ var RULES = [
289
305
  explanation: "\uBAA9\uD45C \uAC8C\uC774\uD2B8 \uAC80\uC99D (vhk goal check)",
290
306
  confidence: "high",
291
307
  args: ["check"],
292
- test: (t2) => /목표\s*(점검|검증|체크)/.test(t2)
308
+ // '스크립트' 포함 시는 sync 의도(게이트 스크립트 생성) check 가 가로채지 않게 제외.
309
+ test: (t2) => /목표\s*(점검|검증|체크)/.test(t2) && !/스크립트/.test(t2)
293
310
  },
294
311
  {
295
312
  command: "goal",
@@ -304,6 +321,13 @@ var RULES = [
304
321
  confidence: "high",
305
322
  args: ["list"],
306
323
  test: (t2) => /목표\s*(목록|리스트)/.test(t2)
324
+ },
325
+ {
326
+ command: "goal",
327
+ explanation: "\uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uB3D9\uAE30\uD654 (vhk goal sync)",
328
+ confidence: "high",
329
+ args: ["sync"],
330
+ test: (t2) => /(게이트|목표).*(스크립트|동기화)|체크\s*스크립트\s*(생성|만들)/.test(t2)
307
331
  }
308
332
  ];
309
333
  function routeNaturalLanguage(input) {
@@ -328,7 +352,7 @@ function extractNotionUrl(input) {
328
352
 
329
353
  // src/lib/command-registry.ts
330
354
  var CONTAINER_SUBCOMMANDS = {
331
- goal: ["list", "next", "check", "init", "done"],
355
+ goal: ["list", "next", "check", "init", "done", "sync"],
332
356
  ref: ["add", "list", "open"],
333
357
  memory: ["add", "list", "remove"],
334
358
  cloud: ["push", "pull"],
@@ -475,8 +499,8 @@ function detectNaturalLanguageInput(argv) {
475
499
  }
476
500
 
477
501
  // src/lib/nlp-run.ts
478
- import chalk33 from "chalk";
479
- import inquirer13 from "inquirer";
502
+ import chalk32 from "chalk";
503
+ import inquirer12 from "inquirer";
480
504
 
481
505
  // src/commands/gate.ts
482
506
  import inquirer from "inquirer";
@@ -614,11 +638,6 @@ import chalk3 from "chalk";
614
638
  import fs2 from "fs";
615
639
  import path2 from "path";
616
640
 
617
- // src/lib/date.ts
618
- function localDate(d = /* @__PURE__ */ new Date()) {
619
- return d.toLocaleDateString("sv-SE");
620
- }
621
-
622
641
  // src/templates/claude-md.ts
623
642
  function CLAUDE_MD_TEMPLATE(name, _stack) {
624
643
  const d = localDate();
@@ -898,6 +917,8 @@ function VHK_GITIGNORE_TEMPLATE() {
898
917
  "memory.json",
899
918
  "refs.json",
900
919
  "HARD_STOP",
920
+ "# secret gist \uD3EC\uC778\uD130 (gistId). \uACF5\uAC1C repo \uC5D0 \uCEE4\uBC0B\uB418\uBA74 \uBC31\uC5C5 gist \uAC00 \uB178\uCD9C\uB428 (VHK-022).",
921
+ "cloud.json",
901
922
  "# sync \uB36E\uC5B4\uC4F0\uAE30 \uC804 \uC790\uB3D9 \uBC31\uC5C5 (\uB85C\uCEEC \uBCF5\uAD6C\uC6A9 \u2014 vhk restore). \uCD94\uC801/\uD074\uB77C\uC6B0\uB4DC \uC81C\uC678.",
902
923
  "backups/",
903
924
  ""
@@ -1102,90 +1123,8 @@ async function fetchPrdFromNotion(urlOrId) {
1102
1123
  };
1103
1124
  }
1104
1125
 
1105
- // src/lib/rules-import.ts
1106
- import { existsSync, readFileSync } from "fs";
1107
- import { join } from "path";
1108
- var ADOPT_SOURCES = [
1109
- ".cursorrules",
1110
- "CLAUDE.md",
1111
- "AGENTS.md",
1112
- ".windsurfrules",
1113
- ".github/copilot-instructions.md"
1114
- ];
1115
- var PREAMBLE_TITLE = "\uC11C\uBB38";
1116
- function detectExistingRuleFiles(cwd) {
1117
- const found = [];
1118
- for (const rel of ADOPT_SOURCES) {
1119
- const full = join(cwd, rel);
1120
- if (existsSync(full)) {
1121
- try {
1122
- found.push({ path: rel, content: readFileSync(full, "utf-8") });
1123
- } catch {
1124
- }
1125
- }
1126
- }
1127
- return found;
1128
- }
1129
- function splitSections(content) {
1130
- const sections = [];
1131
- let title = "";
1132
- let buf = [];
1133
- const preamble = [];
1134
- let sawHeading = false;
1135
- for (const line of content.split("\n")) {
1136
- if (line.startsWith("## ")) {
1137
- sawHeading = true;
1138
- if (title) sections.push({ title, content: buf.join("\n").trim() });
1139
- title = line.replace("## ", "").trim();
1140
- buf = [];
1141
- } else if (title) {
1142
- buf.push(line);
1143
- } else if (!sawHeading) {
1144
- preamble.push(line);
1145
- }
1146
- }
1147
- if (title) sections.push({ title, content: buf.join("\n").trim() });
1148
- const pre = preamble.join("\n").trim();
1149
- if (pre) sections.unshift({ title: PREAMBLE_TITLE, content: pre });
1150
- return sections;
1151
- }
1152
- function buildAdoptedRules(files, projectName) {
1153
- const order = [];
1154
- const byTitle = /* @__PURE__ */ new Map();
1155
- for (const file of files) {
1156
- for (const sec of splitSections(file.content)) {
1157
- let merged = byTitle.get(sec.title);
1158
- if (!merged) {
1159
- merged = { title: sec.title, parts: [] };
1160
- byTitle.set(sec.title, merged);
1161
- order.push(sec.title);
1162
- }
1163
- merged.parts.push({ source: file.path, content: sec.content });
1164
- }
1165
- }
1166
- const lines = [
1167
- `# ${projectName} \u2014 Rules`,
1168
- "",
1169
- "> \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.",
1170
- "> \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.",
1171
- ""
1172
- ];
1173
- for (const title of order) {
1174
- const merged = byTitle.get(title);
1175
- const nonEmpty = merged.parts.filter((p) => p.content.trim());
1176
- if (!nonEmpty.length) continue;
1177
- lines.push(`## ${title}`);
1178
- for (const part of nonEmpty) {
1179
- lines.push(`<!-- \uCD9C\uCC98: ${part.source} -->`);
1180
- lines.push(part.content);
1181
- }
1182
- lines.push("");
1183
- }
1184
- return lines.join("\n");
1185
- }
1186
-
1187
1126
  // src/lib/stack-detect.ts
1188
- import { join as join2 } from "path";
1127
+ import { join } from "path";
1189
1128
  function detectStackFromDeps(deps) {
1190
1129
  const stack = [];
1191
1130
  if (deps.next) stack.push("Next.js");
@@ -1208,7 +1147,7 @@ function detectStackFromDeps(deps) {
1208
1147
  function detectProjectStack(cwd = ".") {
1209
1148
  let pkg;
1210
1149
  try {
1211
- pkg = readJsonFile(join2(cwd, "package.json"));
1150
+ pkg = readJsonFile(join(cwd, "package.json"));
1212
1151
  } catch {
1213
1152
  return null;
1214
1153
  }
@@ -1241,23 +1180,30 @@ function resolveType(type) {
1241
1180
  }
1242
1181
  return type;
1243
1182
  }
1183
+ function isNonInteractive(options) {
1184
+ return Boolean(options.yes) || !process.stdin.isTTY || !process.stdout.isTTY;
1185
+ }
1186
+ var DEFAULT_TYPE = PROJECT_TYPES[0].value;
1244
1187
  async function collectAnswers(options, defaults = {}) {
1188
+ const noninteractive = isNonInteractive(options);
1245
1189
  const prompts = [];
1246
- if (!options.name && !defaults.name) {
1247
- prompts.push({ type: "input", name: "name", message: ko.init.projectName });
1248
- }
1249
- if (!options.description && !defaults.description) {
1250
- prompts.push({ type: "input", name: "description", message: ko.init.description });
1251
- }
1252
- if (!options.type && !defaults.type) {
1253
- prompts.push({ type: "list", name: "type", message: ko.init.projectType, choices: PROJECT_TYPES });
1190
+ if (!noninteractive) {
1191
+ if (!options.name && !defaults.name) {
1192
+ prompts.push({ type: "input", name: "name", message: ko.init.projectName });
1193
+ }
1194
+ if (!options.description && !defaults.description) {
1195
+ prompts.push({ type: "input", name: "description", message: ko.init.description });
1196
+ }
1197
+ if (!options.type && !defaults.type) {
1198
+ prompts.push({ type: "list", name: "type", message: ko.init.projectType, choices: PROJECT_TYPES });
1199
+ }
1254
1200
  }
1255
1201
  const prompted = prompts.length ? await inquirer2.prompt(prompts) : {};
1256
- return {
1257
- name: options.name ?? defaults.name ?? prompted.name,
1258
- description: options.description ?? defaults.description ?? prompted.description,
1259
- type: resolveType(options.type ?? defaults.type ?? prompted.type) ?? prompted.type
1260
- };
1202
+ const fallbackName = path2.basename(process.cwd()) || "my-project";
1203
+ const name = options.name || defaults.name || prompted.name || fallbackName;
1204
+ const description = options.description || defaults.description || prompted.description || `${name} \u2014 vhk \uD504\uB85C\uC81D\uD2B8`;
1205
+ const type = resolveType(options.type || defaults.type || prompted.type) ?? prompted.type ?? DEFAULT_TYPE;
1206
+ return { name, description, type };
1261
1207
  }
1262
1208
  async function init(options = {}) {
1263
1209
  const skipGate = Boolean(options.skipGate || options.fromNotion);
@@ -1296,7 +1242,7 @@ ${ko.init.title}
1296
1242
  console.log(chalk3.dim(`
1297
1243
  ${ko.init.recommendedStack} ${stack.join(" + ")}
1298
1244
  `));
1299
- if (!options.yes) {
1245
+ if (!isNonInteractive(options)) {
1300
1246
  const { confirmStack } = await inquirer2.prompt([{
1301
1247
  type: "confirm",
1302
1248
  name: "confirmStack",
@@ -1310,7 +1256,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1310
1256
  }
1311
1257
  const cwd = process.cwd();
1312
1258
  let adoptedRules = null;
1313
- if (!options.yes && !options.fromNotion) {
1259
+ if (!isNonInteractive(options) && !options.fromNotion) {
1314
1260
  const existingRules = detectExistingRuleFiles(cwd);
1315
1261
  if (existingRules.length > 0) {
1316
1262
  const { adopt } = await inquirer2.prompt([{
@@ -1334,12 +1280,12 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1334
1280
  for (const [filePath, content] of Object.entries(files)) {
1335
1281
  const fullPath = path2.join(cwd, filePath);
1336
1282
  if (fileExists(fullPath)) {
1337
- const { overwrite } = await inquirer2.prompt([{
1283
+ const overwrite = isNonInteractive(options) ? false : (await inquirer2.prompt([{
1338
1284
  type: "confirm",
1339
1285
  name: "overwrite",
1340
1286
  message: ko.init.overwrite(filePath),
1341
1287
  default: false
1342
- }]);
1288
+ }])).overwrite;
1343
1289
  if (!overwrite) {
1344
1290
  log.warn(ko.init.skipped(filePath));
1345
1291
  continue;
@@ -1348,7 +1294,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1348
1294
  writeFile(fullPath, content);
1349
1295
  log.success(filePath);
1350
1296
  }
1351
- await writeInitExtras(cwd);
1297
+ await writeInitExtras(cwd, isNonInteractive(options));
1352
1298
  console.log(chalk3.bold.green(`
1353
1299
  ${ko.init.done}`));
1354
1300
  console.log(chalk3.dim(`
@@ -1462,16 +1408,16 @@ function projectHasTestScript(projectDir) {
1462
1408
  return false;
1463
1409
  }
1464
1410
  }
1465
- async function writeInitExtras(projectDir) {
1411
+ async function writeInitExtras(projectDir, noninteractive = false) {
1466
1412
  const commandsPath = path2.join(projectDir, "COMMANDS.md");
1467
1413
  const hasTest = projectHasTestScript(projectDir);
1468
1414
  if (fileExists(commandsPath)) {
1469
- const { overwrite } = await inquirer2.prompt([{
1415
+ const overwrite = noninteractive ? false : (await inquirer2.prompt([{
1470
1416
  type: "confirm",
1471
1417
  name: "overwrite",
1472
1418
  message: ko.init.overwrite("COMMANDS.md"),
1473
1419
  default: false
1474
- }]);
1420
+ }])).overwrite;
1475
1421
  if (!overwrite) {
1476
1422
  log.warn(ko.init.skipped("COMMANDS.md"));
1477
1423
  } else {
@@ -1698,13 +1644,13 @@ function isPromptAbortError(err) {
1698
1644
  import chalk5 from "chalk";
1699
1645
 
1700
1646
  // src/lib/state-files.ts
1701
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, appendFileSync, rmSync } from "fs";
1702
- import { join as join3 } from "path";
1647
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, rmSync } from "fs";
1648
+ import { join as join2 } from "path";
1703
1649
  var STATE_DIR = "docs/state";
1704
- var BLOCKERS_PATH = join3(STATE_DIR, "blockers.md");
1705
- var LEARNINGS_PATH = join3(STATE_DIR, "learnings.md");
1650
+ var BLOCKERS_PATH = join2(STATE_DIR, "blockers.md");
1651
+ var LEARNINGS_PATH = join2(STATE_DIR, "learnings.md");
1706
1652
  var VHK_DIR = ".vhk";
1707
- var HARD_STOP_PATH = join3(VHK_DIR, "HARD_STOP");
1653
+ var HARD_STOP_PATH = join2(VHK_DIR, "HARD_STOP");
1708
1654
  var HARD_STOP_BLOCKER_THRESHOLD = 3;
1709
1655
  function ensureStateDir() {
1710
1656
  mkdirSync(STATE_DIR, { recursive: true });
@@ -1727,7 +1673,7 @@ function appendBlocker(description, goalId) {
1727
1673
  ensureStateDir();
1728
1674
  const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
1729
1675
  const line = `- [${isoDate()} ${tag}] ${description.trim()}`;
1730
- if (!existsSync2(BLOCKERS_PATH)) {
1676
+ if (!existsSync(BLOCKERS_PATH)) {
1731
1677
  writeFileSync(
1732
1678
  BLOCKERS_PATH,
1733
1679
  `# Blockers
@@ -1742,10 +1688,10 @@ ${line}
1742
1688
  appendFileSync(BLOCKERS_PATH, `${line}
1743
1689
  `, "utf-8");
1744
1690
  }
1745
- const current = readFileSync2(BLOCKERS_PATH, "utf-8");
1691
+ const current = readFileSync(BLOCKERS_PATH, "utf-8");
1746
1692
  const count = countActiveBlockers(current);
1747
1693
  let hardStopTripped = false;
1748
- if (count >= HARD_STOP_BLOCKER_THRESHOLD && !existsSync2(HARD_STOP_PATH)) {
1694
+ if (count >= HARD_STOP_BLOCKER_THRESHOLD && !existsSync(HARD_STOP_PATH)) {
1749
1695
  writeHardStop(`auto: ${count} active blockers (threshold ${HARD_STOP_BLOCKER_THRESHOLD})`);
1750
1696
  hardStopTripped = true;
1751
1697
  }
@@ -1755,7 +1701,7 @@ function appendLearning(lesson, goalId) {
1755
1701
  ensureStateDir();
1756
1702
  const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
1757
1703
  const line = `- [${isoDate()} ${tag}] ${lesson.trim()}`;
1758
- if (!existsSync2(LEARNINGS_PATH)) {
1704
+ if (!existsSync(LEARNINGS_PATH)) {
1759
1705
  writeFileSync(
1760
1706
  LEARNINGS_PATH,
1761
1707
  `# Learnings
@@ -1772,14 +1718,14 @@ ${line}
1772
1718
  }
1773
1719
  }
1774
1720
  function getRecentLearnings(limit = 3) {
1775
- if (!existsSync2(LEARNINGS_PATH)) return [];
1776
- const lines = readFileSync2(LEARNINGS_PATH, "utf-8").split(/\r?\n/);
1721
+ if (!existsSync(LEARNINGS_PATH)) return [];
1722
+ const lines = readFileSync(LEARNINGS_PATH, "utf-8").split(/\r?\n/);
1777
1723
  const entries = lines.filter((l) => l.startsWith("- ["));
1778
1724
  return entries.slice(-limit);
1779
1725
  }
1780
1726
  function getActiveBlockers(limit = 3) {
1781
- if (!existsSync2(BLOCKERS_PATH)) return [];
1782
- const lines = readFileSync2(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
1727
+ if (!existsSync(BLOCKERS_PATH)) return [];
1728
+ const lines = readFileSync(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
1783
1729
  const entries = lines.filter((l) => ACTIVE_BLOCKER_RE.test(l));
1784
1730
  return entries.slice(-limit);
1785
1731
  }
@@ -1791,18 +1737,18 @@ ${reason}
1791
1737
  `, "utf-8");
1792
1738
  }
1793
1739
  function isHardStopActive() {
1794
- return existsSync2(HARD_STOP_PATH);
1740
+ return existsSync(HARD_STOP_PATH);
1795
1741
  }
1796
1742
  function readHardStopReason() {
1797
- if (!existsSync2(HARD_STOP_PATH)) return null;
1743
+ if (!existsSync(HARD_STOP_PATH)) return null;
1798
1744
  try {
1799
- return readFileSync2(HARD_STOP_PATH, "utf-8").trim();
1745
+ return readFileSync(HARD_STOP_PATH, "utf-8").trim();
1800
1746
  } catch {
1801
1747
  return null;
1802
1748
  }
1803
1749
  }
1804
1750
  function clearHardStop() {
1805
- if (!existsSync2(HARD_STOP_PATH)) return false;
1751
+ if (!existsSync(HARD_STOP_PATH)) return false;
1806
1752
  rmSync(HARD_STOP_PATH, { force: true });
1807
1753
  return true;
1808
1754
  }
@@ -2060,543 +2006,17 @@ ${ko.recap.done}`));
2060
2006
  });
2061
2007
  }
2062
2008
 
2063
- // src/commands/sync.ts
2064
- import chalk7 from "chalk";
2065
- import fs7 from "fs";
2066
- import path8 from "path";
2067
- import inquirer4 from "inquirer";
2068
-
2069
- // src/lib/drift.ts
2070
- import fs5 from "fs";
2071
- import path6 from "path";
2072
-
2073
- // src/lib/git-repo.ts
2074
- import { execFileSync } from "child_process";
2075
- function getGitRoot(cwd = process.cwd()) {
2076
- return execFileSync("git", ["rev-parse", "--show-toplevel"], {
2077
- encoding: "utf-8",
2078
- cwd,
2079
- stdio: ["pipe", "pipe", "pipe"]
2080
- }).trim();
2081
- }
2082
- function gitOut(args, cwd) {
2083
- return execFileSync("git", args, {
2084
- encoding: "utf-8",
2085
- cwd,
2086
- stdio: ["pipe", "pipe", "pipe"]
2087
- });
2088
- }
2089
- function gitRun(args, cwd) {
2090
- execFileSync("git", args, { stdio: "pipe", cwd });
2091
- }
2092
- function getExecErrorMessage(err) {
2093
- if (err && typeof err === "object" && "stderr" in err) {
2094
- const stderr = err.stderr;
2095
- if (Buffer.isBuffer(stderr)) return stderr.toString("utf-8").trim();
2096
- if (typeof stderr === "string") return stderr.trim();
2097
- }
2098
- return err instanceof Error ? err.message : String(err);
2099
- }
2100
- function hasGitRemote(cwd) {
2101
- try {
2102
- return gitOut(["remote"], cwd).trim().length > 0;
2103
- } catch {
2104
- return false;
2105
- }
2106
- }
2107
- function countLocalCommits(cwd) {
2108
- try {
2109
- const out = gitOut(["rev-list", "--count", "HEAD"], cwd).trim();
2110
- return parseInt(out, 10) || 0;
2111
- } catch {
2112
- return 0;
2113
- }
2114
- }
2115
-
2116
- // src/lib/drift.ts
2117
- function normalizeForCompare(s) {
2118
- return s.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n+$/, "\n");
2119
- }
2120
- function checkRuleDrift(rootDir) {
2121
- const rulesPath = path6.join(rootDir, "RULES.md");
2122
- if (!fs5.existsSync(rulesPath)) return { checked: false, results: [] };
2123
- const rulesContent = fs5.readFileSync(rulesPath, "utf-8");
2124
- const sections = parseRulesMd(rulesContent);
2125
- const projectName = deriveProjectName(rulesContent);
2126
- const results = [];
2127
- for (const target of SYNC_TARGETS) {
2128
- const fullPath = path6.join(rootDir, target.path);
2129
- if (!fs5.existsSync(fullPath)) {
2130
- results.push({ path: target.path, status: "missing" });
2131
- continue;
2132
- }
2133
- const expected = normalizeForCompare(target.generate(sections, projectName));
2134
- const actual = normalizeForCompare(fs5.readFileSync(fullPath, "utf-8"));
2135
- results.push({ path: target.path, status: expected === actual ? "ok" : "drifted" });
2136
- }
2137
- return { checked: true, results };
2138
- }
2139
- var CONTEXT_GIT_MARKER = "vhk-context-git";
2140
- var CONTEXT_PATH = ".vhk/context.md";
2141
- function extractContextSha(content) {
2142
- const m = content.match(new RegExp(`${CONTEXT_GIT_MARKER}:\\s*([0-9a-f]{7,40})`));
2143
- return m ? m[1] : null;
2144
- }
2145
- var CONTEXT_SOURCE_PATHS = ["package.json", "goals", "docs/state/learnings.md"];
2146
- function contextSourcesChanged(generatedSha, rootDir) {
2147
- const content = gitOut(
2148
- ["diff", "--name-only", generatedSha, "HEAD", "--", ...CONTEXT_SOURCE_PATHS],
2149
- rootDir
2150
- ).trim();
2151
- if (content) return true;
2152
- const structural = gitOut(
2153
- ["diff", "--name-only", "--diff-filter=ADR", generatedSha, "HEAD"],
2154
- rootDir
2155
- ).trim();
2156
- return structural.length > 0;
2157
- }
2158
- function checkContextDrift(rootDir) {
2159
- const ctxPath = path6.join(rootDir, CONTEXT_PATH);
2160
- if (!fs5.existsSync(ctxPath)) return { checked: false, stale: false };
2161
- const generatedSha = extractContextSha(fs5.readFileSync(ctxPath, "utf-8"));
2162
- if (!generatedSha) return { checked: false, stale: false };
2163
- let currentSha;
2164
- try {
2165
- currentSha = gitOut(["rev-parse", "HEAD"], rootDir).trim();
2166
- } catch {
2167
- return { checked: false, stale: false };
2168
- }
2169
- if (!currentSha) return { checked: false, stale: false };
2170
- if (currentSha.startsWith(generatedSha) || generatedSha.startsWith(currentSha)) {
2171
- return { checked: true, stale: false, generatedSha, currentSha };
2172
- }
2173
- let stale;
2174
- try {
2175
- stale = contextSourcesChanged(generatedSha, rootDir);
2176
- } catch {
2177
- return { checked: false, stale: false };
2178
- }
2179
- return { checked: true, stale, generatedSha, currentSha };
2180
- }
2181
-
2182
- // src/lib/backup.ts
2183
- import fs6 from "fs";
2184
- import path7 from "path";
2185
- var BACKUPS_REL = path7.join(".vhk", "backups");
2186
- var VHK_GITIGNORE_REL = path7.join(".vhk", ".gitignore");
2187
- function fsSafeStamp(d) {
2188
- return d.toISOString().replace(/[:.]/g, "-");
2189
- }
2190
- function ensureVhkIgnored(rootDir, ...entries) {
2191
- const giPath = path7.join(rootDir, VHK_GITIGNORE_REL);
2192
- fs6.mkdirSync(path7.dirname(giPath), { recursive: true });
2193
- let content = fs6.existsSync(giPath) ? fs6.readFileSync(giPath, "utf-8") : "";
2194
- const present = new Set(content.split("\n").map((l) => l.trim().replace(/\/$/, "")));
2195
- const missing = entries.filter((e) => !present.has(e.trim().replace(/\/$/, "")));
2196
- if (missing.length === 0) return;
2197
- if (content.length > 0 && !content.endsWith("\n")) content += "\n";
2198
- content += missing.join("\n") + "\n";
2199
- fs6.writeFileSync(giPath, content, "utf-8");
2200
- }
2201
- function walkRelFiles(baseDir, cur = baseDir) {
2202
- const out = [];
2203
- for (const entry of fs6.readdirSync(cur)) {
2204
- const full = path7.join(cur, entry);
2205
- if (fs6.statSync(full).isDirectory()) {
2206
- out.push(...walkRelFiles(baseDir, full));
2207
- } else {
2208
- out.push(path7.relative(baseDir, full).split(path7.sep).join("/"));
2209
- }
2210
- }
2211
- return out;
2212
- }
2213
- function saveBackup(files, rootDir, stamp) {
2214
- const baseId = stamp ?? fsSafeStamp(/* @__PURE__ */ new Date());
2215
- let id = baseId;
2216
- let n = 1;
2217
- while (fs6.existsSync(path7.join(rootDir, BACKUPS_REL, id))) {
2218
- id = `${baseId}-${String(n++).padStart(3, "0")}`;
2219
- }
2220
- const backupDir = path7.join(rootDir, BACKUPS_REL, id);
2221
- const saved = [];
2222
- for (const rel of files) {
2223
- const src = path7.join(rootDir, rel);
2224
- if (!fs6.existsSync(src)) continue;
2225
- const dest = path7.join(backupDir, rel);
2226
- fs6.mkdirSync(path7.dirname(dest), { recursive: true });
2227
- fs6.copyFileSync(src, dest);
2228
- saved.push(rel);
2229
- }
2230
- ensureVhkIgnored(rootDir, "backups/");
2231
- return { id, dir: backupDir, files: saved };
2232
- }
2233
- function backupOrderKey(id) {
2234
- const m = /^(.*Z)(?:-(\d+))?$/.exec(id);
2235
- return m ? [m[1], m[2] ? parseInt(m[2], 10) : 0] : [id, 0];
2236
- }
2237
- function listBackups(rootDir) {
2238
- const root = path7.join(rootDir, BACKUPS_REL);
2239
- if (!fs6.existsSync(root)) return [];
2240
- return fs6.readdirSync(root).filter((e) => fs6.statSync(path7.join(root, e)).isDirectory()).sort((a, b) => {
2241
- const [ba, na] = backupOrderKey(a);
2242
- const [bb, nb] = backupOrderKey(b);
2243
- if (ba !== bb) return ba < bb ? 1 : -1;
2244
- return nb - na;
2245
- }).map((id) => {
2246
- const dir = path7.join(root, id);
2247
- return { id, dir, files: walkRelFiles(dir) };
2248
- });
2249
- }
2250
- function restoreBackup(id, rootDir) {
2251
- const backupDir = path7.join(rootDir, BACKUPS_REL, id);
2252
- if (!fs6.existsSync(backupDir)) {
2253
- throw new Error(`\uBC31\uC5C5 \uC5C6\uC74C: ${id}`);
2254
- }
2255
- const rels = walkRelFiles(backupDir);
2256
- for (const rel of rels) {
2257
- const src = path7.join(backupDir, rel);
2258
- const dest = path7.join(rootDir, rel);
2259
- fs6.mkdirSync(path7.dirname(dest), { recursive: true });
2260
- fs6.copyFileSync(src, dest);
2261
- }
2262
- return rels;
2263
- }
2264
- function pruneBackups(keepN, rootDir) {
2265
- const all = listBackups(rootDir);
2266
- const toDelete = all.slice(Math.max(0, keepN));
2267
- for (const b of toDelete) {
2268
- fs6.rmSync(b.dir, { recursive: true, force: true });
2269
- }
2270
- return toDelete.map((b) => b.id);
2271
- }
2272
-
2273
- // src/commands/sync.ts
2274
- var CURSORRULES_KEYS = ["\uCF54\uB529 \uADDC\uCE59", "\uAE30\uC220 \uC2A4\uD0DD", "\uC544\uD0A4\uD14D\uCC98", "\uB514\uC790\uC778", "Anti-patterns", "\uCEE4\uBC0B"];
2275
- var CLAUDE_MD_KEYS = ["\uAE30\uB85D", "\uB85C\uADF8", "ADR", "\uD2B8\uB7EC\uBE14\uC288\uD305", "TIL", "/done", "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8"];
2276
- function findUnmappedSections(sections) {
2277
- const allKeys = [...CURSORRULES_KEYS, ...CLAUDE_MD_KEYS];
2278
- return sections.filter((s) => s.title !== PREAMBLE_TITLE && !allKeys.some((k) => s.title.includes(k))).map((s) => s.title);
2279
- }
2280
- function parseRulesMd(content) {
2281
- const sections = [];
2282
- const lines = content.split("\n");
2283
- let currentTitle = "";
2284
- let currentContent = [];
2285
- for (const line of lines) {
2286
- if (line.startsWith("## ")) {
2287
- if (currentTitle) {
2288
- sections.push({ title: currentTitle, content: currentContent.join("\n").trim() });
2289
- }
2290
- currentTitle = line.replace("## ", "").trim();
2291
- currentContent = [];
2292
- } else {
2293
- currentContent.push(line);
2294
- }
2295
- }
2296
- if (currentTitle) {
2297
- sections.push({ title: currentTitle, content: currentContent.join("\n").trim() });
2298
- }
2299
- return sections;
2300
- }
2301
- function buildCodingDoc(headerTitle, sections, projectName) {
2302
- const codingSections = sections.filter(
2303
- (s) => CURSORRULES_KEYS.some((k) => s.title.includes(k))
2304
- );
2305
- const lines = [
2306
- `# ${projectName} \u2014 ${headerTitle}`,
2307
- "",
2308
- "> \uCF54\uB529/\uB514\uC790\uC778 \uC804\uC6A9. \uAE30\uB85D/\uC6B4\uC601 \u2192 CLAUDE.md \uCC38\uC870.",
2309
- "> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
2310
- "",
2311
- "## \uD544\uC218 \uCC38\uC870",
2312
- "- docs/PRD.md \xB7 docs/ARCHITECTURE.md \xB7 CLAUDE.md \xB7 RULES.md",
2313
- ""
2314
- ];
2315
- for (const section of codingSections) {
2316
- lines.push(`## ${section.title}`);
2317
- lines.push(section.content);
2318
- lines.push("");
2319
- }
2320
- return lines.join("\n");
2321
- }
2322
- function toCursorrules(sections, projectName) {
2323
- return buildCodingDoc("Cursor Rules", sections, projectName);
2324
- }
2325
- function toWindsurfrules(sections, projectName) {
2326
- return buildCodingDoc("Windsurf Rules", sections, projectName);
2327
- }
2328
- function toCopilotInstructions(sections, projectName) {
2329
- return buildCodingDoc("GitHub Copilot Instructions", sections, projectName);
2330
- }
2331
- var ANTIGRAVITY_CHAR_LIMIT = 12e3;
2332
- 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";
2333
- function truncateForAntigravity(content, limit = ANTIGRAVITY_CHAR_LIMIT) {
2334
- if (Buffer.byteLength(content, "utf8") <= limit) return content;
2335
- const SAFETY = 200;
2336
- const budget = limit - Buffer.byteLength(ANTIGRAVITY_TRUNCATE_MARKER, "utf8") - SAFETY;
2337
- let lo = 0;
2338
- let hi = content.length;
2339
- while (lo < hi) {
2340
- const mid = lo + hi + 1 >> 1;
2341
- if (Buffer.byteLength(content.slice(0, mid), "utf8") <= budget) lo = mid;
2342
- else hi = mid - 1;
2343
- }
2344
- const charCut = lo;
2345
- let cut = content.lastIndexOf("\n## ", charCut);
2346
- if (cut < charCut * 0.5) {
2347
- const nl = content.lastIndexOf("\n", charCut);
2348
- cut = nl > 0 ? nl : charCut;
2349
- }
2350
- return content.slice(0, cut).trimEnd() + ANTIGRAVITY_TRUNCATE_MARKER;
2351
- }
2352
- function toAntigravityRules(sections, projectName) {
2353
- return truncateForAntigravity(buildCodingDoc("Antigravity Rules", sections, projectName));
2354
- }
2355
- 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.";
2356
- function toClaudeMd(sections, existing) {
2357
- const recordSections = sections.filter(
2358
- (s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k))
2359
- );
2360
- const cleaned = existing.split("\n").filter((line) => line.trim() !== CLAUDE_AUTOGEN_BANNER).join("\n");
2361
- const statusMatch = cleaned.match(/## 현재 상태[\s\S]*?(?=\n## |$)/);
2362
- const statusSection = statusMatch ? statusMatch[0].trimEnd() : "";
2363
- const header = cleaned.split("## ")[0].trim();
2364
- const lines = [
2365
- header,
2366
- "",
2367
- statusSection,
2368
- "",
2369
- CLAUDE_AUTOGEN_BANNER,
2370
- ""
2371
- ];
2372
- for (const section of recordSections) {
2373
- lines.push(`## ${section.title}`);
2374
- lines.push(section.content);
2375
- lines.push("");
2376
- }
2377
- return lines.join("\n");
2378
- }
2379
- function toAgentsMd(sections, projectName) {
2380
- const codingSections = sections.filter((s) => CURSORRULES_KEYS.some((k) => s.title.includes(k)));
2381
- const recordSections = sections.filter((s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k)));
2382
- const lines = [
2383
- `# ${projectName} \u2014 AGENTS.md (\uC5D0\uC774\uC804\uD2B8 \uC791\uB3D9 \uADDC\uC57D)`,
2384
- "",
2385
- "> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
2386
- "> \uBE60\uB978 \uC2DC\uC791(\uD1A0\uD070 \uC808\uAC10): `docs/context/agent-compact.md` \uB97C \uBA3C\uC800 \uC77D\uC73C\uC138\uC694.",
2387
- "",
2388
- "## Loop Protocol",
2389
- "- \uB8E8\uD504: `context \u2192 goal next \u2192 \uC791\uC5C5 \u2192 goal check \u2192 goal done`",
2390
- "- \uC791\uC5C5 \uC2DC\uC791 \uC2DC `.vhk/HARD_STOP` \uD655\uC778 \u2014 \uC788\uC73C\uBA74 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC989\uC2DC \uC911\uB2E8.",
2391
- "- active goal \uB9CC \uC791\uC5C5. `docs/state`(next-task/blockers/learnings)\uB294 SoT, append-only.",
2392
- "- \uAC8C\uC774\uD2B8(tsc / test:run / build) \uD1B5\uACFC\uD574\uC57C\uB9CC `vhk goal done`.",
2393
- ""
2394
- ];
2395
- for (const section of codingSections) {
2396
- lines.push(`## ${section.title}`);
2397
- lines.push(section.content);
2398
- lines.push("");
2399
- }
2400
- for (const section of recordSections) {
2401
- lines.push(`## ${section.title}`);
2402
- lines.push(section.content);
2403
- lines.push("");
2404
- }
2405
- return lines.join("\n");
2406
- }
2407
- function deriveProjectName(rulesContent) {
2408
- const firstLine = rulesContent.split("\n")[0];
2409
- return firstLine.replace(/^#\s*/, "").replace(/\s*—.*/, "").trim() || "Project";
2410
- }
2411
- var SYNC_TARGETS = [
2412
- { path: ".cursorrules", generate: toCursorrules, doneMessage: ko.sync.cursorrulesDone },
2413
- { path: ".windsurfrules", generate: toWindsurfrules, doneMessage: ko.sync.windsurfDone },
2414
- { path: ".github/copilot-instructions.md", generate: toCopilotInstructions, doneMessage: ko.sync.copilotDone },
2415
- { path: ".agents/rules/vhk-rules.md", generate: toAntigravityRules, doneMessage: ko.sync.antigravityDone },
2416
- // AGENTS.md — 6번째 타겟. 항목 1개 추가로 sync·드리프트·백업 가드가 자동 반영된다.
2417
- { path: "AGENTS.md", generate: toAgentsMd, doneMessage: ko.sync.agentsDone }
2418
- ];
2419
- var BACKUP_KEEP = 10;
2420
- var SYNCED_MARKER_REL = path8.join(".vhk", ".synced");
2421
- function buildSyncPlan(rootDir, sections, projectName) {
2422
- const plan = [];
2423
- for (const target of SYNC_TARGETS) {
2424
- const fullPath = path8.join(rootDir, target.path);
2425
- const exists = fs7.existsSync(fullPath);
2426
- const newContent = target.generate(sections, projectName);
2427
- const drift = exists ? normalizeForCompare(fs7.readFileSync(fullPath, "utf-8")) !== normalizeForCompare(newContent) : false;
2428
- plan.push({ path: target.path, newContent, doneMessage: target.doneMessage, exists, drift });
2429
- }
2430
- const claudePath = path8.join(rootDir, "CLAUDE.md");
2431
- const claudeExists = fs7.existsSync(claudePath);
2432
- const existingClaude = claudeExists ? fs7.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
2433
-
2434
- ## \uD604\uC7AC \uC0C1\uD0DC
2435
- - **Phase:** **FILL**
2436
- - **\uBE14\uB85C\uCEE4:** \uC5C6\uC74C
2437
- - **\uB2E4\uC74C \uC561\uC158:** **FILL**
2438
- - **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${localDate()}`;
2439
- const claudeNew = toClaudeMd(sections, existingClaude);
2440
- const claudeDrift = claudeExists ? normalizeForCompare(existingClaude) !== normalizeForCompare(claudeNew) : false;
2441
- plan.push({
2442
- path: "CLAUDE.md",
2443
- newContent: claudeNew,
2444
- doneMessage: ko.sync.claudeDone,
2445
- exists: claudeExists,
2446
- drift: claudeDrift
2447
- });
2448
- return plan;
2449
- }
2450
- async function syncCore(rootDir, opts, confirmOverwrite) {
2451
- const rulesContent = fs7.readFileSync(path8.join(rootDir, "RULES.md"), "utf-8");
2452
- const sections = parseRulesMd(rulesContent);
2453
- const projectName = deriveProjectName(rulesContent);
2454
- const plan = buildSyncPlan(rootDir, sections, projectName);
2455
- const firstSync = !fs7.existsSync(path8.join(rootDir, SYNCED_MARKER_REL));
2456
- const unmapped = findUnmappedSections(sections);
2457
- if (opts.dryRun) {
2458
- return {
2459
- dryRun: true,
2460
- firstSync,
2461
- backupId: null,
2462
- backedUp: [],
2463
- written: [],
2464
- skipped: [],
2465
- truncated: [],
2466
- plan,
2467
- unmapped
2468
- };
2469
- }
2470
- const toBackup = plan.filter((p) => p.exists && (p.drift || firstSync)).map((p) => p.path);
2471
- let backupId = null;
2472
- let backedUp = [];
2473
- if (toBackup.length) {
2474
- const info = saveBackup(toBackup, rootDir);
2475
- pruneBackups(BACKUP_KEEP, rootDir);
2476
- backupId = info.id;
2477
- backedUp = info.files;
2478
- }
2479
- const drifted = plan.filter((p) => p.drift);
2480
- const overwriteDrift = drifted.length ? await confirmOverwrite(drifted) : true;
2481
- const written = [];
2482
- const skipped = [];
2483
- const truncated = [];
2484
- for (const item of plan) {
2485
- if (item.drift && !overwriteDrift) {
2486
- skipped.push(item.path);
2487
- continue;
2488
- }
2489
- const fullPath = path8.join(rootDir, item.path);
2490
- fs7.mkdirSync(path8.dirname(fullPath), { recursive: true });
2491
- fs7.writeFileSync(fullPath, item.newContent, "utf-8");
2492
- written.push(item.path);
2493
- if (item.newContent.includes("Antigravity 12,000\uC790 \uC81C\uD55C\uC73C\uB85C \uC808\uC0AD\uB428")) {
2494
- truncated.push(item.path);
2495
- }
2496
- }
2497
- fs7.mkdirSync(path8.join(rootDir, ".vhk"), { recursive: true });
2498
- fs7.writeFileSync(path8.join(rootDir, SYNCED_MARKER_REL), (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2499
- ensureVhkIgnored(rootDir, ".synced");
2500
- return { dryRun: false, firstSync, backupId, backedUp, written, skipped, truncated, plan, unmapped };
2501
- }
2502
- async function sync(opts = {}) {
2503
- console.log(chalk7.bold(`
2504
- ${ko.sync.title}
2505
- `));
2506
- const cwd = process.cwd();
2507
- const rulesPath = path8.join(cwd, "RULES.md");
2508
- if (!fs7.existsSync(rulesPath)) {
2509
- console.log(chalk7.yellow(ko.sync.noRules));
2510
- console.log(chalk7.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
2511
- console.log(chalk7.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
2512
- console.log("");
2513
- console.log(chalk7.dim(" RULES.md \uAE30\uBCF8 \uAD6C\uC870:"));
2514
- console.log(chalk7.dim(" ## \uD504\uB85C\uC81D\uD2B8 \uC815\uCCB4\uC131"));
2515
- console.log(chalk7.dim(" ## \uAE30\uC220 \uC2A4\uD0DD"));
2516
- console.log(chalk7.dim(" ## \uCF54\uB529 \uADDC\uCE59"));
2517
- console.log(chalk7.dim(" ## \uAE30\uB85D \uADDC\uCE59"));
2518
- console.log(chalk7.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
2519
- return;
2520
- }
2521
- const sections = parseRulesMd(fs7.readFileSync(rulesPath, "utf-8"));
2522
- console.log(chalk7.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
2523
- const interactive = !!process.stdout.isTTY && !opts.yes;
2524
- const confirmOverwrite = async (drifted) => {
2525
- if (!interactive) return true;
2526
- for (const d of drifted) console.log(chalk7.yellow(` ${ko.sync.driftWarn(d.path)}`));
2527
- const { confirm } = await inquirer4.prompt([
2528
- {
2529
- type: "confirm",
2530
- name: "confirm",
2531
- message: ko.sync.driftConfirm(drifted.length),
2532
- default: false
2533
- }
2534
- ]);
2535
- return confirm;
2536
- };
2537
- const result = await syncCore(cwd, opts, confirmOverwrite);
2538
- if (result.unmapped.length) {
2539
- console.error(
2540
- chalk7.yellow(
2541
- ` \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(", ")}
2542
- (\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.)`
2543
- )
2544
- );
2545
- }
2546
- if (result.dryRun) {
2547
- console.log(chalk7.cyan(`
2548
- ${ko.sync.dryRunHeader}`));
2549
- for (const item of result.plan) {
2550
- console.log(ko.sync.dryRunWouldWrite(item.path, item.exists && item.drift));
2551
- }
2552
- const wouldBackup = result.plan.filter((p) => p.exists && (p.drift || result.firstSync)).map((p) => p.path);
2553
- if (wouldBackup.length) {
2554
- console.log(chalk7.dim(`
2555
- \uBC31\uC5C5 \uC608\uC815(${wouldBackup.length}): ${wouldBackup.join(", ")}`));
2556
- }
2557
- return;
2558
- }
2559
- if (result.backupId) {
2560
- if (result.firstSync) console.log(chalk7.cyan(` ${ko.sync.firstSync}`));
2561
- if (!process.stdout.isTTY) {
2562
- console.log(chalk7.yellow(` ${ko.sync.nonTtyAuto(result.backedUp.length, result.backupId)}`));
2563
- } else {
2564
- console.log(chalk7.cyan(` ${ko.sync.backupSaved(result.backedUp.length, result.backupId)}`));
2565
- }
2566
- }
2567
- for (const p of result.written) {
2568
- const item = result.plan.find((i) => i.path === p);
2569
- if (item) console.log(chalk7.green(` ${item.doneMessage}`));
2570
- }
2571
- for (const _ of result.truncated) {
2572
- console.log(chalk7.yellow(` \u26A0\uFE0F ${ko.sync.antigravityTruncated}`));
2573
- }
2574
- for (const p of result.skipped) {
2575
- console.log(chalk7.gray(` ${ko.sync.skipped(p)}`));
2576
- }
2577
- console.log(chalk7.bold.green(`
2578
- ${ko.sync.done}`));
2579
- console.log(chalk7.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md + .windsurfrules"));
2580
- console.log(chalk7.dim(" + .github/copilot-instructions.md + .agents/rules/vhk-rules.md (\uC790\uB3D9 \uC0DD\uC131)"));
2581
- console.log(chalk7.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
2582
- printNextStep({
2583
- message: "\uADDC\uCE59 \uB3D9\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C Cursor\uAC00 \uC0C8 \uADDC\uCE59\uC744 \uB530\uB985\uB2C8\uB2E4.",
2584
- command: "vhk \uC810\uAC80",
2585
- cursorHint: "\uADDC\uCE59 \uC810\uAC80\uD574\uC918"
2586
- });
2587
- }
2588
-
2589
2009
  // src/commands/check.ts
2590
- import chalk9 from "chalk";
2591
- import path10 from "path";
2592
- import fs9 from "fs";
2010
+ import chalk8 from "chalk";
2011
+ import path7 from "path";
2012
+ import fs6 from "fs";
2593
2013
 
2594
2014
  // src/lib/rules-parser.ts
2595
- import fs8 from "fs";
2596
- import path9 from "path";
2015
+ import fs5 from "fs";
2016
+ import path6 from "path";
2597
2017
  function parseRules(rulesPath) {
2598
- if (!fs8.existsSync(rulesPath)) return [];
2599
- const content = fs8.readFileSync(rulesPath, "utf-8");
2018
+ if (!fs5.existsSync(rulesPath)) return [];
2019
+ const content = fs5.readFileSync(rulesPath, "utf-8");
2600
2020
  const lines = content.split("\n");
2601
2021
  const rules = [];
2602
2022
  let currentSection = "";
@@ -2661,17 +2081,17 @@ function createNamingRule(id, section, desc, convention) {
2661
2081
  description: desc,
2662
2082
  check: (cwd) => {
2663
2083
  const violations = [];
2664
- const srcDir = path9.join(cwd, "src");
2665
- if (!fs8.existsSync(srcDir)) return violations;
2084
+ const srcDir = path6.join(cwd, "src");
2085
+ if (!fs5.existsSync(srcDir)) return violations;
2666
2086
  walkFiles(srcDir, (filePath) => {
2667
- const name = path9.basename(filePath, path9.extname(filePath));
2087
+ const name = path6.basename(filePath, path6.extname(filePath));
2668
2088
  if (convention === "kebab-case" && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
2669
2089
  if (!["index", "vite.config", "tsconfig"].includes(name)) {
2670
2090
  violations.push({
2671
2091
  ruleId: id,
2672
2092
  severity: "warning",
2673
2093
  message: `\uD30C\uC77C\uBA85\uC774 kebab-case\uAC00 \uC544\uB2D8: ${name}`,
2674
- file: path9.relative(cwd, filePath)
2094
+ file: path6.relative(cwd, filePath)
2675
2095
  });
2676
2096
  }
2677
2097
  }
@@ -2687,8 +2107,8 @@ function createStructureRule(id, section, desc, expectedPath) {
2687
2107
  type: "structure",
2688
2108
  description: desc,
2689
2109
  check: (cwd) => {
2690
- const fullPath = path9.join(cwd, expectedPath);
2691
- if (!fs8.existsSync(fullPath)) {
2110
+ const fullPath = path6.join(cwd, expectedPath);
2111
+ if (!fs5.existsSync(fullPath)) {
2692
2112
  return [{
2693
2113
  ruleId: id,
2694
2114
  severity: "error",
@@ -2708,11 +2128,11 @@ function createContentRule(id, section, desc, pattern, type) {
2708
2128
  pattern: new RegExp(escapeRegex(pattern), "i"),
2709
2129
  check: (cwd) => {
2710
2130
  const violations = [];
2711
- const srcDir = path9.join(cwd, "src");
2712
- if (!fs8.existsSync(srcDir)) return violations;
2131
+ const srcDir = path6.join(cwd, "src");
2132
+ if (!fs5.existsSync(srcDir)) return violations;
2713
2133
  const regex = new RegExp(escapeRegex(pattern), "i");
2714
2134
  walkFiles(srcDir, (filePath) => {
2715
- const fileContent = fs8.readFileSync(filePath, "utf-8");
2135
+ const fileContent = fs5.readFileSync(filePath, "utf-8");
2716
2136
  const fileLines = fileContent.split("\n");
2717
2137
  fileLines.forEach((line, idx) => {
2718
2138
  if (regex.test(line)) {
@@ -2720,7 +2140,7 @@ function createContentRule(id, section, desc, pattern, type) {
2720
2140
  ruleId: id,
2721
2141
  severity: type === "banned" ? "error" : "warning",
2722
2142
  message: type === "banned" ? `\uAE08\uC9C0 \uD328\uD134 \uBC1C\uACAC: \`${pattern}\`` : `\uD544\uC218 \uD328\uD134 \uB204\uB77D: \`${pattern}\``,
2723
- file: path9.relative(cwd, filePath),
2143
+ file: path6.relative(cwd, filePath),
2724
2144
  line: idx + 1
2725
2145
  });
2726
2146
  }
@@ -2732,9 +2152,9 @@ function createContentRule(id, section, desc, pattern, type) {
2732
2152
  };
2733
2153
  }
2734
2154
  function walkFiles(dir, callback) {
2735
- const entries = fs8.readdirSync(dir, { withFileTypes: true });
2155
+ const entries = fs5.readdirSync(dir, { withFileTypes: true });
2736
2156
  for (const entry of entries) {
2737
- const fullPath = path9.join(dir, entry.name);
2157
+ const fullPath = path6.join(dir, entry.name);
2738
2158
  if (entry.isDirectory()) {
2739
2159
  if (!["node_modules", ".git", "dist", ".next"].includes(entry.name)) {
2740
2160
  walkFiles(fullPath, callback);
@@ -2749,13 +2169,13 @@ function escapeRegex(str) {
2749
2169
  }
2750
2170
 
2751
2171
  // src/commands/goal.ts
2752
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync4 } from "fs";
2753
- import { join as join5 } from "path";
2754
- import chalk8 from "chalk";
2172
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
2173
+ import { join as join4 } from "path";
2174
+ import chalk7 from "chalk";
2755
2175
 
2756
2176
  // src/lib/goal-frontmatter.ts
2757
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
2758
- import { join as join4 } from "path";
2177
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
2178
+ import { join as join3 } from "path";
2759
2179
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
2760
2180
  function parseFrontmatter(content) {
2761
2181
  const m = content.match(FRONTMATTER_RE);
@@ -2787,9 +2207,9 @@ function parseSimpleYaml(yaml) {
2787
2207
  return out;
2788
2208
  }
2789
2209
  function parseGoalFile(filePath) {
2790
- if (!existsSync3(filePath)) return null;
2210
+ if (!existsSync2(filePath)) return null;
2791
2211
  try {
2792
- const content = readFileSync3(filePath, "utf-8");
2212
+ const content = readFileSync2(filePath, "utf-8");
2793
2213
  const { frontmatter, body } = parseFrontmatter(content);
2794
2214
  return { filePath, frontmatter, body };
2795
2215
  } catch {
@@ -2797,7 +2217,7 @@ function parseGoalFile(filePath) {
2797
2217
  }
2798
2218
  }
2799
2219
  function listGoals(goalsDir) {
2800
- if (!existsSync3(goalsDir)) return [];
2220
+ if (!existsSync2(goalsDir)) return [];
2801
2221
  let entries;
2802
2222
  try {
2803
2223
  entries = readdirSync(goalsDir);
@@ -2808,7 +2228,7 @@ function listGoals(goalsDir) {
2808
2228
  for (const name of entries) {
2809
2229
  if (!name.endsWith(".md")) continue;
2810
2230
  if (name === "_meta.md") continue;
2811
- const fp = join4(goalsDir, name);
2231
+ const fp = join3(goalsDir, name);
2812
2232
  try {
2813
2233
  if (!statSync(fp).isFile()) continue;
2814
2234
  } catch {
@@ -2823,6 +2243,36 @@ function listGoals(goalsDir) {
2823
2243
  parsed.sort((a, b) => a.frontmatter.id - b.frontmatter.id);
2824
2244
  return parsed;
2825
2245
  }
2246
+ function findSkippedGoalFiles(goalsDir) {
2247
+ if (!existsSync2(goalsDir)) return [];
2248
+ let entries;
2249
+ try {
2250
+ entries = readdirSync(goalsDir);
2251
+ } catch {
2252
+ return [];
2253
+ }
2254
+ const out = [];
2255
+ for (const name of entries) {
2256
+ if (!name.endsWith(".md")) continue;
2257
+ if (name === "_meta.md") continue;
2258
+ const fp = join3(goalsDir, name);
2259
+ try {
2260
+ if (!statSync(fp).isFile()) continue;
2261
+ } catch {
2262
+ continue;
2263
+ }
2264
+ const g = parseGoalFile(fp);
2265
+ if (!g) continue;
2266
+ const fm = g.frontmatter;
2267
+ if (fm.type === "goal" && typeof fm.id === "number") continue;
2268
+ if (fm.type === "meta") continue;
2269
+ const looksLikeGoal = fm.type === "goal" || "id" in fm || "status" in fm || "priority" in fm || "title" in fm;
2270
+ if (!looksLikeGoal) continue;
2271
+ const reason = fm.type !== "goal" ? "type: goal \uB204\uB77D (frontmatter \uC5D0 'type: goal' \uD544\uC694)" : "id \uAC00 \uC22B\uC790\uAC00 \uC544\uB2D8 ('id: 1' \uCC98\uB7FC \uC22B\uC790\uB9CC)";
2272
+ out.push({ file: name, reason });
2273
+ }
2274
+ return out;
2275
+ }
2826
2276
  function findDuplicateIds(goals) {
2827
2277
  const counts = /* @__PURE__ */ new Map();
2828
2278
  for (const g of goals) {
@@ -2900,13 +2350,15 @@ function resolveGoalId(optId, goals) {
2900
2350
  return selectActiveId(goals);
2901
2351
  }
2902
2352
  async function goalList() {
2903
- console.log(chalk8.bold(`
2353
+ console.log(chalk7.bold(`
2904
2354
  ${ko.goal.listTitle}
2905
2355
  `));
2906
2356
  const goals = listGoals(GOALS_DIR);
2357
+ const skipped = findSkippedGoalFiles(GOALS_DIR);
2907
2358
  if (goals.length === 0) {
2908
- console.log(chalk8.yellow(" \u{1F4ED} goals/ \uB514\uB809\uD1A0\uB9AC\uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2909
- console.log(chalk8.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
2359
+ console.log(chalk7.yellow(" \u{1F4ED} goals/ \uB514\uB809\uD1A0\uB9AC\uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2360
+ console.log(chalk7.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
2361
+ printSkippedGoalWarnings(skipped);
2910
2362
  return;
2911
2363
  }
2912
2364
  for (const g of goals) {
@@ -2923,22 +2375,33 @@ ${ko.goal.listTitle}
2923
2375
  const dups = findDuplicateIds(goals);
2924
2376
  if (dups.length > 0) {
2925
2377
  console.log("");
2926
- console.log(chalk8.yellow(` ${ko.goal.duplicateId(dups.join(", "))}`));
2378
+ console.log(chalk7.yellow(` ${ko.goal.duplicateId(dups.join(", "))}`));
2379
+ }
2380
+ printSkippedGoalWarnings(skipped);
2381
+ }
2382
+ function printSkippedGoalWarnings(skipped) {
2383
+ if (skipped.length > 0) {
2384
+ console.log("");
2385
+ console.log(chalk7.yellow(` ${ko.goal.skippedFiles(skipped.length)}`));
2386
+ for (const s of skipped) {
2387
+ console.log(chalk7.yellow(` - goals/${s.file}: ${s.reason}`));
2388
+ }
2389
+ console.log(chalk7.dim(" \uD544\uC218: type: goal + \uC22B\uC790 id. \uC2A4\uD0A4\uB9C8 \uC804\uCCB4: goals/_meta.md"));
2927
2390
  }
2928
2391
  }
2929
2392
  async function goalNext() {
2930
- console.log(chalk8.bold(`
2393
+ console.log(chalk7.bold(`
2931
2394
  ${ko.goal.nextTitle}
2932
2395
  `));
2933
2396
  const goals = listGoals(GOALS_DIR);
2934
2397
  if (goals.length === 0) {
2935
- console.log(chalk8.yellow(" \u{1F4ED} \uC815\uC758\uB41C goal \uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2936
- console.log(chalk8.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
2398
+ console.log(chalk7.yellow(" \u{1F4ED} \uC815\uC758\uB41C goal \uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2399
+ console.log(chalk7.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
2937
2400
  return;
2938
2401
  }
2939
2402
  const activeId = selectActiveId(goals);
2940
2403
  if (activeId === null) {
2941
- console.log(chalk8.green(" \u{1F389} \uBAA8\uB4E0 goal \uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"));
2404
+ console.log(chalk7.green(" \u{1F389} \uBAA8\uB4E0 goal \uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"));
2942
2405
  return;
2943
2406
  }
2944
2407
  const active = goals.find((g) => g.frontmatter.id === activeId);
@@ -2958,9 +2421,9 @@ ${ko.goal.nextTitle}
2958
2421
  ""
2959
2422
  ].join("\n");
2960
2423
  mkdirSync2(STATE_DIR2, { recursive: true });
2961
- writeFileSync2(join5(STATE_DIR2, "next-task.md"), text, "utf-8");
2424
+ writeFileSync2(join4(STATE_DIR2, "next-task.md"), text, "utf-8");
2962
2425
  console.log(
2963
- chalk8.green(
2426
+ chalk7.green(
2964
2427
  ` \u2705 next-task.md \uAC31\uC2E0 \u2014 Goal ${activeId}: ${active.frontmatter.title ?? ""}`
2965
2428
  )
2966
2429
  );
@@ -2979,35 +2442,70 @@ version: v0.1
2979
2442
  ## Forbidden Actions (\uC804\uC5ED)
2980
2443
 
2981
2444
  - (\uD574\uB2F9 \uC0AC\uD56D)
2445
+
2446
+ ## Goal \uD30C\uC77C \uC2A4\uD0A4\uB9C8 (\uD544\uB3C5 \u2014 VHK-021)
2447
+
2448
+ \`vhk goal list/next/check/done\` \uB294 \`goals/*.md\`(\uC774 \`_meta.md\` \uC81C\uC678) \uC911 \uC544\uB798
2449
+ frontmatter \uB97C \uB9CC\uC871\uD558\uB294 \uD30C\uC77C\uB9CC goal \uB85C \uC778\uC2DD\uD55C\uB2E4. **\uD558\uB098\uB77C\uB3C4 \uC5B4\uAE0B\uB098\uBA74 \uC870\uC6A9\uD788 \uBB34\uC2DC**\uB418\uBA70
2450
+ \`vhk goal list\` \uAC00 \uACBD\uACE0\uB85C \uC54C\uB824\uC900\uB2E4.
2451
+
2452
+ | \uD544\uB4DC | \uD544\uC218 | \uAC12 |
2453
+ | --- | --- | --- |
2454
+ | \`type\` | \u2705 | \`goal\` (\uBB38\uC790\uC5F4 \uADF8\uB300\uB85C) |
2455
+ | \`id\` | \u2705 | **\uC22B\uC790\uB9CC** (\`1\`, \`2\` \u2026 \u2014 \`G1\` \uAC19\uC740 \uBB38\uC790\uC5F4 \u274C) |
2456
+ | \`status\` | \u2705 | \`NOT_STARTED\` | \`IN_PROGRESS\` | \`DONE\` | \`BLOCKED\` |
2457
+ | \`priority\` | \uAD8C\uC7A5 | \`P0\` | \`P1\` | \`P2\` |
2458
+ | \`title\` | \uAD8C\uC7A5 | \uD55C \uC904 \uC81C\uBAA9 |
2459
+
2460
+ \uD30C\uC77C\uBA85 \uADDC\uCE59: \`goals/<id>-<name>.md\` (\uC608: \`goals/1-login.md\`).
2461
+
2462
+ ### \uC0C8 goal \uD15C\uD50C\uB9BF (\uBCF5\uBD99)
2463
+
2464
+ \`\`\`markdown
2465
+ ---
2466
+ vhk_format: 1
2467
+ type: goal
2468
+ id: 1
2469
+ title: \uB85C\uADF8\uC778 \uAE30\uB2A5
2470
+ status: NOT_STARTED
2471
+ priority: P0
2472
+ ---
2473
+
2474
+ # Goal 1: \uB85C\uADF8\uC778 \uAE30\uB2A5
2475
+
2476
+ ## \uBC30\uACBD / \uB3D9\uC791 / Completion Check ...
2477
+ \`\`\`
2478
+
2479
+ \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8\uB294 \`vhk goal sync\` \uB85C \`scripts/check-goal-<id>.mjs\` \uB97C \uBC31\uD544\uD55C\uB2E4.
2982
2480
  `;
2983
2481
  var STATE_NEXT_TASK_TEMPLATE = "# Next Task\n\n```\nTASK: (vhk goal next \uB85C \uC790\uB3D9 \uAC31\uC2E0)\n```\n";
2984
2482
  var STATE_BLOCKERS_TEMPLATE = "# Blockers\n\n_Append-only. \uD574\uACB0 \uD56D\uBAA9\uC740 ~~\uCDE8\uC18C\uC120~~\uC73C\uB85C \uD45C\uAE30._\n";
2985
2483
  var STATE_LEARNINGS_TEMPLATE = "# Learnings\n\n_Append-only. \uD55C \uC904 = \uD55C \uAD50\uD6C8._\n";
2986
2484
  async function goalInit() {
2987
- console.log(chalk8.bold(`
2485
+ console.log(chalk7.bold(`
2988
2486
  ${ko.goal.initTitle}
2989
2487
  `));
2990
2488
  const targets = [
2991
- { path: join5(GOALS_DIR, "_meta.md"), content: META_TEMPLATE },
2992
- { path: join5(STATE_DIR2, "next-task.md"), content: STATE_NEXT_TASK_TEMPLATE },
2993
- { path: join5(STATE_DIR2, "blockers.md"), content: STATE_BLOCKERS_TEMPLATE },
2994
- { path: join5(STATE_DIR2, "learnings.md"), content: STATE_LEARNINGS_TEMPLATE }
2489
+ { path: join4(GOALS_DIR, "_meta.md"), content: META_TEMPLATE },
2490
+ { path: join4(STATE_DIR2, "next-task.md"), content: STATE_NEXT_TASK_TEMPLATE },
2491
+ { path: join4(STATE_DIR2, "blockers.md"), content: STATE_BLOCKERS_TEMPLATE },
2492
+ { path: join4(STATE_DIR2, "learnings.md"), content: STATE_LEARNINGS_TEMPLATE }
2995
2493
  ];
2996
2494
  mkdirSync2(GOALS_DIR, { recursive: true });
2997
2495
  mkdirSync2(STATE_DIR2, { recursive: true });
2998
2496
  let created = 0;
2999
2497
  let skipped = 0;
3000
2498
  for (const t2 of targets) {
3001
- if (existsSync4(t2.path)) {
3002
- console.log(chalk8.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${t2.path}`));
2499
+ if (existsSync3(t2.path)) {
2500
+ console.log(chalk7.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${t2.path}`));
3003
2501
  skipped++;
3004
2502
  } else {
3005
2503
  writeFileSync2(t2.path, t2.content, "utf-8");
3006
- console.log(chalk8.green(` \u2713 created: ${t2.path}`));
2504
+ console.log(chalk7.green(` \u2713 created: ${t2.path}`));
3007
2505
  created++;
3008
2506
  }
3009
2507
  }
3010
- console.log(chalk8.bold(`
2508
+ console.log(chalk7.bold(`
3011
2509
  \u{1F4CA} created=${created} skipped=${skipped}`));
3012
2510
  if (created > 0) {
3013
2511
  printNextStep({
@@ -3018,10 +2516,10 @@ ${ko.goal.initTitle}
3018
2516
  }
3019
2517
  }
3020
2518
  function findGateScript(id) {
3021
- const mjs = join5(SCRIPTS_DIR, `check-goal-${id}.mjs`);
3022
- if (existsSync4(mjs)) return mjs;
3023
- const sh = join5(SCRIPTS_DIR, `check-goal-${id}.sh`);
3024
- if (existsSync4(sh)) return sh;
2519
+ const mjs = join4(SCRIPTS_DIR, `check-goal-${id}.mjs`);
2520
+ if (existsSync3(mjs)) return mjs;
2521
+ const sh = join4(SCRIPTS_DIR, `check-goal-${id}.sh`);
2522
+ if (existsSync3(sh)) return sh;
3025
2523
  return null;
3026
2524
  }
3027
2525
  function runGate(scriptPath) {
@@ -3030,82 +2528,93 @@ function runGate(scriptPath) {
3030
2528
  const r = safeExecFile(runner, [scriptPath]);
3031
2529
  return { ok: r.ok, out: r.out, err: r.ok ? "" : r.err, runner };
3032
2530
  }
2531
+ function warnIfBashOnWindows(scriptPath) {
2532
+ if (process.platform === "win32" && scriptPath.endsWith(".sh")) {
2533
+ console.log(
2534
+ chalk7.yellow(
2535
+ " \u26A0 Windows: .sh \uAC8C\uC774\uD2B8\uB294 bash \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. cross-platform .mjs \uB85C \uBC31\uD544\uD558\uC138\uC694 \u2192 vhk goal sync"
2536
+ )
2537
+ );
2538
+ }
2539
+ }
3033
2540
  async function goalCheck(opts) {
3034
- console.log(chalk8.bold(`
2541
+ console.log(chalk7.bold(`
3035
2542
  ${ko.goal.checkTitle}
3036
2543
  `));
3037
2544
  const goals = listGoals(GOALS_DIR);
3038
2545
  const id = resolveGoalId(opts.id, goals);
3039
2546
  if (id === null) {
3040
2547
  console.log(
3041
- chalk8.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
2548
+ chalk7.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
3042
2549
  );
3043
2550
  process.exitCode = 1;
3044
2551
  return;
3045
2552
  }
3046
2553
  if (!goals.some((g) => g.frontmatter.id === id)) {
3047
- console.log(chalk8.red(` \u274C ${ko.goal.notFound(id)}`));
2554
+ console.log(chalk7.red(` \u274C ${ko.goal.notFound(id)}`));
3048
2555
  process.exitCode = 1;
3049
2556
  return;
3050
2557
  }
3051
2558
  const scriptPath = findGateScript(id);
3052
2559
  if (!scriptPath) {
3053
2560
  console.log(
3054
- chalk8.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: scripts/check-goal-${id}.{mjs,sh}`)
2561
+ chalk7.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: scripts/check-goal-${id}.{mjs,sh}`)
3055
2562
  );
3056
2563
  process.exitCode = 1;
3057
2564
  return;
3058
2565
  }
2566
+ warnIfBashOnWindows(scriptPath);
3059
2567
  const gate2 = runGate(scriptPath);
3060
- console.log(chalk8.dim(` \u25B6 ${gate2.runner} ${scriptPath}
2568
+ console.log(chalk7.dim(` \u25B6 ${gate2.runner} ${scriptPath}
3061
2569
  `));
3062
2570
  if (gate2.out) console.log(gate2.out);
3063
2571
  if (gate2.ok) {
3064
- console.log(chalk8.green(`
2572
+ console.log(chalk7.green(`
3065
2573
  \u2705 Goal ${id} \uAC8C\uC774\uD2B8 \uD1B5\uACFC`));
3066
2574
  } else {
3067
- console.log(chalk8.red(`
2575
+ console.log(chalk7.red(`
3068
2576
  \u274C Goal ${id} \uAC8C\uC774\uD2B8 \uC2E4\uD328`));
3069
- if (gate2.err && !gate2.out) console.log(chalk8.dim(gate2.err.slice(0, 500)));
2577
+ if (gate2.err && !gate2.out) console.log(chalk7.dim(gate2.err.slice(0, 500)));
3070
2578
  process.exitCode = 1;
3071
2579
  }
3072
2580
  }
3073
2581
  async function goalDone(opts) {
3074
- console.log(chalk8.bold(`
2582
+ console.log(chalk7.bold(`
3075
2583
  ${ko.goal.doneTitle}
3076
2584
  `));
3077
2585
  const goals = listGoals(GOALS_DIR);
3078
2586
  const id = resolveGoalId(opts.id, goals);
3079
2587
  if (id === null) {
3080
2588
  console.log(
3081
- chalk8.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
2589
+ chalk7.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
3082
2590
  );
3083
2591
  process.exitCode = 1;
3084
2592
  return;
3085
2593
  }
3086
2594
  const target = goals.find((g) => g.frontmatter.id === id);
3087
2595
  if (!target) {
3088
- console.log(chalk8.red(` \u274C ${ko.goal.notFound(id)}`));
2596
+ console.log(chalk7.red(` \u274C ${ko.goal.notFound(id)}`));
3089
2597
  process.exitCode = 1;
3090
2598
  return;
3091
2599
  }
3092
2600
  const scriptPath = findGateScript(id);
3093
2601
  if (!scriptPath) {
3094
2602
  console.log(
3095
- chalk8.red(
2603
+ chalk7.red(
3096
2604
  ` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C \u2014 done \uCC98\uB9AC \uAC70\uBD80: scripts/check-goal-${id}.{mjs,sh}`
3097
2605
  )
3098
2606
  );
3099
2607
  process.exitCode = 1;
3100
2608
  return;
3101
2609
  }
2610
+ warnIfBashOnWindows(scriptPath);
3102
2611
  const gate2 = runGate(scriptPath);
3103
- console.log(chalk8.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: ${gate2.runner} ${scriptPath}
2612
+ console.log(chalk7.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: ${gate2.runner} ${scriptPath}
3104
2613
  `));
3105
2614
  if (gate2.out) console.log(gate2.out);
3106
2615
  if (!gate2.ok) {
3107
2616
  console.log(
3108
- chalk8.red(
2617
+ chalk7.red(
3109
2618
  `
3110
2619
  \u274C \uAC8C\uC774\uD2B8 \uC2E4\uD328 \u2014 frontmatter \uBCC0\uACBD \uC5C6\uC774 \uC885\uB8CC. (Forbidden: \uC2E4\uD328 = \uBCF4\uC874)`
3111
2620
  )
@@ -3113,11 +2622,11 @@ ${ko.goal.doneTitle}
3113
2622
  process.exitCode = 1;
3114
2623
  return;
3115
2624
  }
3116
- const content = readFileSync4(target.filePath, "utf-8");
2625
+ const content = readFileSync3(target.filePath, "utf-8");
3117
2626
  const today = localDate();
3118
2627
  const updated = updateFrontmatterStatus(content, "DONE", { completed: today });
3119
2628
  writeFileSync2(target.filePath, updated, "utf-8");
3120
- console.log(chalk8.green(`
2629
+ console.log(chalk7.green(`
3121
2630
  \u2705 Goal ${id} \u2192 DONE (completed: ${today})`));
3122
2631
  printNextStep({
3123
2632
  message: `Goal ${id} \uC644\uB8CC! \uB2E4\uC74C goal \uB85C:`,
@@ -3125,6 +2634,112 @@ ${ko.goal.doneTitle}
3125
2634
  cursorHint: "\uB2E4\uC74C goal \uC54C\uB824\uC918"
3126
2635
  });
3127
2636
  }
2637
+ function generateGateScript(id) {
2638
+ const ID = String(id);
2639
+ return [
2640
+ "#!/usr/bin/env node",
2641
+ `// scripts/check-goal-${ID}.mjs \u2014 \uC790\uB3D9 \uC0DD\uC131 (vhk goal sync).`,
2642
+ "// \uAE30\uBCF8 \uAC8C\uC774\uD2B8 = typecheck + (lint) + test + build. goal \uACE0\uC720 \uAC80\uC99D\uC740 \uC544\uB798 \uAD6C\uC5ED\uC5D0 \uCD94\uAC00.",
2643
+ "// sync \uC7AC\uC2E4\uD589\uD574\uB3C4 \uAE30\uC874 \uD30C\uC77C\uC740 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (idempotent).",
2644
+ "//",
2645
+ "// Env: VHK_GATES_SKIP_DEEP=1 \u2192 test + build \uC2A4\uD0B5 (\uBE60\uB978 typecheck-only \uD328\uC2A4)",
2646
+ "",
2647
+ "import { execFileSync } from 'node:child_process'",
2648
+ "import { existsSync, readFileSync } from 'node:fs'",
2649
+ "",
2650
+ "const SHIM = new Set(['pnpm', 'npm', 'npx', 'yarn'])",
2651
+ "function run(cmd, args) {",
2652
+ " let bin = cmd, argv = args",
2653
+ " if (process.platform === 'win32' && SHIM.has(cmd)) {",
2654
+ " // Windows: .cmd shim \uC9C1\uC811 spawn \uC740 Node CVE-2024-27980 \uC73C\uB85C EINVAL \u2192 cmd.exe \uB798\uD551.",
2655
+ " bin = 'cmd.exe'; argv = ['/d', '/s', '/c', cmd + '.cmd', ...args]",
2656
+ " }",
2657
+ " try {",
2658
+ " // maxBuffer \uC0C1\uD5A5: \uD070 \uBE4C\uB4DC/\uD14C\uC2A4\uD2B8 \uB85C\uADF8(>1MB)\uC5D0\uC11C \uC131\uACF5\uD574\uB3C4 ENOBUFS \uAC70\uC9D3\uC2E4\uD328 \uBC29\uC9C0.",
2659
+ " execFileSync(bin, argv, { stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 })",
2660
+ " return true",
2661
+ " } catch (e) {",
2662
+ " const out = (e?.stdout?.toString() ?? '') + (e?.stderr?.toString() ?? '')",
2663
+ " if (out.trim()) console.log(out.split('\\n').slice(-25).join('\\n'))",
2664
+ " return false",
2665
+ " }",
2666
+ "}",
2667
+ "",
2668
+ "if (existsSync('.vhk/HARD_STOP')) {",
2669
+ ` console.log('\u{1F6D1} .vhk/HARD_STOP detected \u2014 refusing to run goal ${ID} gate.')`,
2670
+ " process.exit(1)",
2671
+ "}",
2672
+ "",
2673
+ "const pkg = existsSync('package.json') ? JSON.parse(readFileSync('package.json', 'utf-8')) : {}",
2674
+ "const scripts = pkg.scripts ?? {}",
2675
+ "const pm = existsSync('pnpm-lock.yaml') ? 'pnpm' : existsSync('yarn.lock') ? 'yarn' : 'npm'",
2676
+ "const skipDeep = process.env.VHK_GATES_SKIP_DEEP === '1'",
2677
+ "let pass = true",
2678
+ `const gate = (label, ok) => { console.log('[goal ${ID}] ' + label + ': ' + (ok ? '\u2713' : '\u2717')); if (!ok) pass = false }`,
2679
+ "const must = (cond, label) => { console.log((cond ? ' \u2713 ' : ' \u2717 ') + label); if (!cond) pass = false }",
2680
+ "",
2681
+ "// typecheck (\uC2A4\uD06C\uB9BD\uD2B8 \uC6B0\uC120, \uC5C6\uC73C\uBA74 tsc --noEmit)",
2682
+ "if (scripts.typecheck) gate('typecheck', run(pm, ['run', 'typecheck']))",
2683
+ "else if (existsSync('tsconfig.json')) gate('tsc --noEmit', run(pm, pm === 'npm' ? ['exec', '--', 'tsc', '--noEmit'] : ['exec', 'tsc', '--noEmit']))",
2684
+ "if (scripts.lint) gate('lint', run(pm, ['run', 'lint']))",
2685
+ "if (!skipDeep) {",
2686
+ " if (scripts['test:run']) gate('test', run(pm, ['run', 'test:run']))",
2687
+ " else if (scripts.test && /vitest/.test(scripts.test)) gate('test', run(pm, ['run', 'test', '--', '--run']))",
2688
+ " else if (scripts.test) gate('test', run(pm, ['run', 'test']))",
2689
+ " if (scripts.build) gate('build', run(pm, ['run', 'build']))",
2690
+ "}",
2691
+ "",
2692
+ `// \u2500\u2500\u2500 goal ${ID} \uACE0\uC720 \uAC80\uC99D (\uC9C1\uC811 \uCD94\uAC00) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
2693
+ "// const read = (p) => existsSync(p) ? readFileSync(p, 'utf-8') : null",
2694
+ "// must(read('src/foo.ts')?.includes('bar'), 'foo.ts \uC5D0 bar \uC874\uC7AC')",
2695
+ "",
2696
+ `if (pass) { console.log('\u2705 goal ${ID} gate passes'); process.exit(0) }`,
2697
+ `console.log('\u274C goal ${ID} gate failed'); process.exit(1)`,
2698
+ ""
2699
+ ].join("\n");
2700
+ }
2701
+ async function goalSync() {
2702
+ console.log(chalk7.bold(`
2703
+ ${ko.goal.syncTitle}
2704
+ `));
2705
+ const goals = listGoals(GOALS_DIR);
2706
+ const result = { created: [], skipped: [] };
2707
+ if (goals.length === 0) {
2708
+ console.log(
2709
+ chalk7.yellow(" \u{1F4ED} goals/ \uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694.")
2710
+ );
2711
+ return result;
2712
+ }
2713
+ mkdirSync2(SCRIPTS_DIR, { recursive: true });
2714
+ for (const g of goals) {
2715
+ const id = g.frontmatter.id;
2716
+ if (typeof id !== "number") continue;
2717
+ const target = join4(SCRIPTS_DIR, `check-goal-${id}.mjs`);
2718
+ if (existsSync3(target)) {
2719
+ console.log(chalk7.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${target}`));
2720
+ result.skipped.push(id);
2721
+ continue;
2722
+ }
2723
+ const shOnly = existsSync3(join4(SCRIPTS_DIR, `check-goal-${id}.sh`));
2724
+ writeFileSync2(target, generateGateScript(id), "utf-8");
2725
+ console.log(
2726
+ chalk7.green(` \u2713 created: ${target}${shOnly ? " (.sh \u2192 .mjs \uBC31\uD544, Windows 1\uAE09)" : ""}`)
2727
+ );
2728
+ result.created.push(id);
2729
+ }
2730
+ console.log(
2731
+ chalk7.bold(`
2732
+ \u{1F4CA} created=${result.created.length} skipped=${result.skipped.length}`)
2733
+ );
2734
+ if (result.created.length > 0) {
2735
+ printNextStep({
2736
+ message: `\uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 ${result.created.length}\uAC1C \uC0DD\uC131 (goal ${result.created.join(", ")}). \uAC80\uC99D\uD558\uB824\uBA74:`,
2737
+ command: `vhk goal check --id ${result.created[0]}`,
2738
+ cursorHint: `goal ${result.created[0]} \uAC8C\uC774\uD2B8 \uAC80\uC99D\uD574\uC918`
2739
+ });
2740
+ }
2741
+ return result;
2742
+ }
3128
2743
 
3129
2744
  // src/commands/check.ts
3130
2745
  async function check(opts = {}) {
@@ -3134,22 +2749,22 @@ async function check(opts = {}) {
3134
2749
  return checkRules();
3135
2750
  }
3136
2751
  async function checkRules() {
3137
- console.log(chalk9.bold(`
2752
+ console.log(chalk8.bold(`
3138
2753
  ${ko.check.title}
3139
2754
  `));
3140
2755
  const cwd = process.cwd();
3141
- const rulesPath = path10.join(cwd, "RULES.md");
3142
- if (!fs9.existsSync(rulesPath)) {
3143
- console.log(chalk9.yellow(ko.check.noRules));
3144
- console.log(chalk9.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
2756
+ const rulesPath = path7.join(cwd, "RULES.md");
2757
+ if (!fs6.existsSync(rulesPath)) {
2758
+ console.log(chalk8.yellow(ko.check.noRules));
2759
+ console.log(chalk8.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
3145
2760
  return;
3146
2761
  }
3147
2762
  const rules = parseRules(rulesPath);
3148
- console.log(chalk9.dim(` \u{1F4CF} \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${rules.length}\uAC1C \uAC10\uC9C0 (\uB098\uBA38\uC9C0 \uADDC\uCE59\uC740 \uC218\uB3D9/\uB3C4\uAD6C \uD655\uC778)
2763
+ console.log(chalk8.dim(` \u{1F4CF} \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${rules.length}\uAC1C \uAC10\uC9C0 (\uB098\uBA38\uC9C0 \uADDC\uCE59\uC740 \uC218\uB3D9/\uB3C4\uAD6C \uD655\uC778)
3149
2764
  `));
3150
2765
  if (rules.length === 0) {
3151
- console.log(chalk9.yellow(ko.check.noAutoRules));
3152
- console.log(chalk9.dim(" RULES.md\uC5D0 \uD30C\uC77C \uC774\uB984\xB7\uD3F4\uB354 \uADDC\uCE59\uC744 \uC801\uC73C\uBA74 \uC790\uB3D9\uC73C\uB85C \uC810\uAC80\uD574\uC694."));
2766
+ console.log(chalk8.yellow(ko.check.noAutoRules));
2767
+ console.log(chalk8.dim(" RULES.md\uC5D0 \uD30C\uC77C \uC774\uB984\xB7\uD3F4\uB354 \uADDC\uCE59\uC744 \uC801\uC73C\uBA74 \uC790\uB3D9\uC73C\uB85C \uC810\uAC80\uD574\uC694."));
3153
2768
  return;
3154
2769
  }
3155
2770
  const allViolations = [];
@@ -3157,14 +2772,14 @@ ${ko.check.title}
3157
2772
  for (const rule of rules) {
3158
2773
  const violations = rule.check(cwd);
3159
2774
  if (violations.length === 0) {
3160
- const patternHint = rule.type === "content" && rule.pattern ? chalk9.dim(` [\uAC80\uC0AC: ${rule.pattern.source}]`) : "";
3161
- console.log(chalk9.green(` \u2705 ${rule.id}`) + chalk9.dim(` \u2014 ${rule.description.slice(0, 60)}`) + patternHint);
2775
+ const patternHint = rule.type === "content" && rule.pattern ? chalk8.dim(` [\uAC80\uC0AC: ${rule.pattern.source}]`) : "";
2776
+ console.log(chalk8.green(` \u2705 ${rule.id}`) + chalk8.dim(` \u2014 ${rule.description.slice(0, 60)}`) + patternHint);
3162
2777
  passCount++;
3163
2778
  } else {
3164
- console.log(chalk9.red(` \u274C ${rule.id}`) + chalk9.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
2779
+ console.log(chalk8.red(` \u274C ${rule.id}`) + chalk8.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
3165
2780
  violations.forEach((v) => {
3166
- const loc = v.file ? chalk9.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
3167
- const icon = v.severity === "error" ? chalk9.red("\u2716") : v.severity === "warning" ? chalk9.yellow("\u26A0") : chalk9.blue("\u2139");
2781
+ const loc = v.file ? chalk8.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
2782
+ const icon = v.severity === "error" ? chalk8.red("\u2716") : v.severity === "warning" ? chalk8.yellow("\u26A0") : chalk8.blue("\u2139");
3168
2783
  console.log(` ${icon} ${v.message}${loc}`);
3169
2784
  });
3170
2785
  allViolations.push(...violations);
@@ -3174,18 +2789,18 @@ ${ko.check.title}
3174
2789
  const errors = allViolations.filter((v) => v.severity === "error").length;
3175
2790
  const warnings = allViolations.filter((v) => v.severity === "warning").length;
3176
2791
  if (allViolations.length === 0) {
3177
- console.log(chalk9.green.bold(`\u2705 \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${passCount}\uAC1C \uD1B5\uACFC`));
3178
- console.log(chalk9.dim(" (RULES.md \uC758 \uB098\uBA38\uC9C0 \uADDC\uCE59\uC740 \uCF54\uB4DC \uC790\uB3D9 \uAC80\uC0AC \uBD88\uAC00 \u2014 \uC9C1\uC811/\uB3C4\uAD6C\uB85C \uD655\uC778\uD558\uC138\uC694.)"));
2792
+ console.log(chalk8.green.bold(`\u2705 \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${passCount}\uAC1C \uD1B5\uACFC`));
2793
+ console.log(chalk8.dim(" (RULES.md \uC758 \uB098\uBA38\uC9C0 \uADDC\uCE59\uC740 \uCF54\uB4DC \uC790\uB3D9 \uAC80\uC0AC \uBD88\uAC00 \u2014 \uC9C1\uC811/\uB3C4\uAD6C\uB85C \uD655\uC778\uD558\uC138\uC694.)"));
3179
2794
  printNextStep({
3180
2795
  message: "\uBAA8\uB4E0 \uADDC\uCE59 \uD1B5\uACFC! \uBCF4\uC548 \uC2A4\uCE94\uB3C4 \uD574\uBCFC\uAE4C\uC694?",
3181
2796
  command: "vhk \uBCF4\uC548 scan",
3182
2797
  cursorHint: "\uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB824\uC918"
3183
2798
  });
3184
2799
  } else {
3185
- console.log(chalk9.bold(ko.check.summary));
3186
- console.log(` \uADDC\uCE59: ${chalk9.cyan(String(rules.length))}\uAC1C | \uD1B5\uACFC: ${chalk9.green(String(passCount))}\uAC1C | \uC704\uBC18: ${chalk9.red(String(allViolations.length))}\uAC74`);
3187
- if (errors > 0) console.log(` ${chalk9.red(`\u2716 ${errors}\uAC1C \uC5D0\uB7EC`)}`);
3188
- if (warnings > 0) console.log(` ${chalk9.yellow(`\u26A0 ${warnings}\uAC1C \uACBD\uACE0`)}`);
2800
+ console.log(chalk8.bold(ko.check.summary));
2801
+ console.log(` \uADDC\uCE59: ${chalk8.cyan(String(rules.length))}\uAC1C | \uD1B5\uACFC: ${chalk8.green(String(passCount))}\uAC1C | \uC704\uBC18: ${chalk8.red(String(allViolations.length))}\uAC74`);
2802
+ if (errors > 0) console.log(` ${chalk8.red(`\u2716 ${errors}\uAC1C \uC5D0\uB7EC`)}`);
2803
+ if (warnings > 0) console.log(` ${chalk8.yellow(`\u26A0 ${warnings}\uAC1C \uACBD\uACE0`)}`);
3189
2804
  printNextStep({
3190
2805
  message: "\uC704\uBC18 \uD56D\uBAA9\uC744 \uC218\uC815\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
3191
2806
  command: "vhk \uC810\uAC80",
@@ -3198,36 +2813,36 @@ ${ko.check.title}
3198
2813
  }
3199
2814
 
3200
2815
  // src/commands/secure.ts
3201
- import chalk10 from "chalk";
3202
- import fs10 from "fs";
3203
- import path11 from "path";
2816
+ import chalk9 from "chalk";
2817
+ import fs7 from "fs";
2818
+ import path8 from "path";
3204
2819
  async function secure() {
3205
- console.log(chalk10.bold(`
2820
+ console.log(chalk9.bold(`
3206
2821
  ${ko.secure.title}
3207
2822
  `));
3208
2823
  const cwd = process.cwd();
3209
- const gitignorePath = path11.join(cwd, ".gitignore");
3210
- const hasGitignore = fs10.existsSync(gitignorePath);
2824
+ const gitignorePath = path8.join(cwd, ".gitignore");
2825
+ const hasGitignore = fs7.existsSync(gitignorePath);
3211
2826
  if (!hasGitignore) {
3212
- console.log(chalk10.yellow(` ${ko.secure.noGitignore}`));
3213
- console.log(chalk10.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
2827
+ console.log(chalk9.yellow(` ${ko.secure.noGitignore}`));
2828
+ console.log(chalk9.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
3214
2829
  } else {
3215
- const gitignoreContent = fs10.readFileSync(gitignorePath, "utf-8");
2830
+ const gitignoreContent = fs7.readFileSync(gitignorePath, "utf-8");
3216
2831
  if (!gitignoreContent.includes(".env")) {
3217
- console.log(chalk10.yellow(` ${ko.secure.noEnvInGitignore}`));
3218
- console.log(chalk10.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
2832
+ console.log(chalk9.yellow(` ${ko.secure.noEnvInGitignore}`));
2833
+ console.log(chalk9.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
3219
2834
  }
3220
2835
  }
3221
- console.log(chalk10.dim(` ${ko.secure.scanning}
2836
+ console.log(chalk9.dim(` ${ko.secure.scanning}
3222
2837
  `));
3223
2838
  const { findings, scannedFiles, truncated } = scanProjectForSecrets(cwd);
3224
- console.log(chalk10.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC (lock\xB7node_modules\xB7>${MAX_SCAN_FILE_BYTES / 1024}KB \uC81C\uC678)`));
2839
+ console.log(chalk9.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC (lock\xB7node_modules\xB7>${MAX_SCAN_FILE_BYTES / 1024}KB \uC81C\uC678)`));
3225
2840
  if (truncated) {
3226
- console.log(chalk10.yellow(` \u26A0\uFE0F \uACB0\uACFC ${MAX_SECRET_FINDINGS}\uAC74\uC5D0\uC11C \uCD9C\uB825\uC744 \uC81C\uD55C\uD588\uC2B5\uB2C8\uB2E4. lock \uD30C\uC77C \uB4F1\uC740 \uC790\uB3D9 \uC81C\uC678\uB429\uB2C8\uB2E4.`));
2841
+ console.log(chalk9.yellow(` \u26A0\uFE0F \uACB0\uACFC ${MAX_SECRET_FINDINGS}\uAC74\uC5D0\uC11C \uCD9C\uB825\uC744 \uC81C\uD55C\uD588\uC2B5\uB2C8\uB2E4. lock \uD30C\uC77C \uB4F1\uC740 \uC790\uB3D9 \uC81C\uC678\uB429\uB2C8\uB2E4.`));
3227
2842
  }
3228
2843
  console.log("");
3229
2844
  if (findings.length === 0) {
3230
- console.log(chalk10.green.bold(` ${ko.secure.clean}`));
2845
+ console.log(chalk9.green.bold(` ${ko.secure.clean}`));
3231
2846
  printNextStep({
3232
2847
  message: "\uBCF4\uC548 \uC774\uC0C1 \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
3233
2848
  command: "vhk \uC815\uB9AC",
@@ -3239,45 +2854,45 @@ ${ko.secure.title}
3239
2854
  const high = findings.filter((f) => f.severity === "high");
3240
2855
  const medium = findings.filter((f) => f.severity === "medium");
3241
2856
  if (critical.length > 0) {
3242
- console.log(chalk10.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
2857
+ console.log(chalk9.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
3243
2858
  critical.forEach((f) => {
3244
- console.log(chalk10.red(` \u2716 ${f.patternName}`));
3245
- console.log(chalk10.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2859
+ console.log(chalk9.red(` \u2716 ${f.patternName}`));
2860
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
3246
2861
  });
3247
2862
  console.log("");
3248
2863
  }
3249
2864
  if (high.length > 0) {
3250
- console.log(chalk10.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
2865
+ console.log(chalk9.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
3251
2866
  high.forEach((f) => {
3252
- console.log(chalk10.yellow(` \u26A0 ${f.patternName}`));
3253
- console.log(chalk10.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2867
+ console.log(chalk9.yellow(` \u26A0 ${f.patternName}`));
2868
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
3254
2869
  });
3255
2870
  console.log("");
3256
2871
  }
3257
2872
  if (medium.length > 0) {
3258
- console.log(chalk10.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
2873
+ console.log(chalk9.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
3259
2874
  medium.forEach((f) => {
3260
- console.log(chalk10.blue(` \u2139 ${f.patternName}`));
3261
- console.log(chalk10.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2875
+ console.log(chalk9.blue(` \u2139 ${f.patternName}`));
2876
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
3262
2877
  });
3263
2878
  console.log("");
3264
2879
  }
3265
- console.log(chalk10.bold(` ${ko.secure.summary}`));
3266
- console.log(` \uCD1D ${chalk10.red(String(findings.length))}\uAC74 \uAC10\uC9C0 | CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length}`);
2880
+ console.log(chalk9.bold(` ${ko.secure.summary}`));
2881
+ console.log(` \uCD1D ${chalk9.red(String(findings.length))}\uAC74 \uAC10\uC9C0 | CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length}`);
3267
2882
  console.log("");
3268
- console.log(chalk10.dim(" \u{1F4A1} \uC870\uCE58 \uBC29\uBC95:"));
3269
- console.log(chalk10.dim(" 1. \uD574\uB2F9 \uD30C\uC77C\uC5D0\uC11C \uC2DC\uD06C\uB9BF\uC744 \uC81C\uAC70\uD558\uACE0 \uD658\uACBD\uBCC0\uC218\uB85C \uC774\uB3D9"));
3270
- console.log(chalk10.dim(" 2. git history\uC5D0\uC11C\uB3C4 \uC81C\uAC70: git filter-branch \uB610\uB294 BFG Repo-Cleaner"));
3271
- console.log(chalk10.dim(" 3. \uC720\uCD9C\uB41C \uD0A4\uB294 \uC989\uC2DC \uD3D0\uAE30\uD558\uACE0 \uC7AC\uBC1C\uAE09\n"));
2883
+ console.log(chalk9.dim(" \u{1F4A1} \uC870\uCE58 \uBC29\uBC95:"));
2884
+ console.log(chalk9.dim(" 1. \uD574\uB2F9 \uD30C\uC77C\uC5D0\uC11C \uC2DC\uD06C\uB9BF\uC744 \uC81C\uAC70\uD558\uACE0 \uD658\uACBD\uBCC0\uC218\uB85C \uC774\uB3D9"));
2885
+ console.log(chalk9.dim(" 2. git history\uC5D0\uC11C\uB3C4 \uC81C\uAC70: git filter-branch \uB610\uB294 BFG Repo-Cleaner"));
2886
+ console.log(chalk9.dim(" 3. \uC720\uCD9C\uB41C \uD0A4\uB294 \uC989\uC2DC \uD3D0\uAE30\uD558\uACE0 \uC7AC\uBC1C\uAE09\n"));
3272
2887
  if (critical.length > 0 || high.length > 0) {
3273
2888
  process.exitCode = 1;
3274
2889
  }
3275
2890
  }
3276
2891
 
3277
2892
  // src/commands/doctor.ts
3278
- import chalk11 from "chalk";
3279
- import fs11 from "fs";
3280
- import path12 from "path";
2893
+ import chalk10 from "chalk";
2894
+ import fs8 from "fs";
2895
+ import path9 from "path";
3281
2896
  import { fileURLToPath } from "url";
3282
2897
  function checkCommand(name, command, hint) {
3283
2898
  const result = safeExecFile(command, ["--version"]);
@@ -3286,14 +2901,14 @@ function checkCommand(name, command, hint) {
3286
2901
  return { name, command, version, ok: true, hint };
3287
2902
  }
3288
2903
  function getVhkVersion2() {
3289
- const dir = path12.dirname(fileURLToPath(import.meta.url));
2904
+ const dir = path9.dirname(fileURLToPath(import.meta.url));
3290
2905
  const candidates = [
3291
- path12.join(dir, "../package.json"),
3292
- path12.join(dir, "../../package.json")
2906
+ path9.join(dir, "../package.json"),
2907
+ path9.join(dir, "../../package.json")
3293
2908
  ];
3294
2909
  for (const pkgPath of candidates) {
3295
2910
  try {
3296
- if (fs11.existsSync(pkgPath)) {
2911
+ if (fs8.existsSync(pkgPath)) {
3297
2912
  const pkg = readJsonFile(pkgPath);
3298
2913
  return pkg.version;
3299
2914
  }
@@ -3321,7 +2936,7 @@ function compareSemver(a, b) {
3321
2936
  return a3 - b3;
3322
2937
  }
3323
2938
  async function doctor() {
3324
- console.log(chalk11.bold(`
2939
+ console.log(chalk10.bold(`
3325
2940
  ${ko.doctor.title}
3326
2941
  `));
3327
2942
  const checks = [
@@ -3333,30 +2948,30 @@ ${ko.doctor.title}
3333
2948
  let allOk = true;
3334
2949
  for (const check2 of checks) {
3335
2950
  if (check2.ok) {
3336
- console.log(chalk11.green(` \u2705 ${check2.name}`) + chalk11.dim(` \u2014 ${check2.version}`));
2951
+ console.log(chalk10.green(` \u2705 ${check2.name}`) + chalk10.dim(` \u2014 ${check2.version}`));
3337
2952
  } else {
3338
- console.log(chalk11.red(` \u274C ${check2.name} \uC5C6\uC74C`));
3339
- console.log(chalk11.dim(` \u2192 ${check2.hint}`));
2953
+ console.log(chalk10.red(` \u274C ${check2.name} \uC5C6\uC74C`));
2954
+ console.log(chalk10.dim(` \u2192 ${check2.hint}`));
3340
2955
  allOk = false;
3341
2956
  }
3342
2957
  }
3343
2958
  console.log("");
3344
2959
  const vhkVersion = getVhkVersion2();
3345
2960
  if (vhkVersion) {
3346
- console.log(chalk11.green(" \u2705 VHK") + chalk11.dim(` \u2014 v${vhkVersion}`));
2961
+ console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(` \u2014 v${vhkVersion}`));
3347
2962
  } else {
3348
- console.log(chalk11.green(" \u2705 VHK") + chalk11.dim(" \u2014 \uC124\uCE58\uB428"));
2963
+ console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(" \u2014 \uC124\uCE58\uB428"));
3349
2964
  }
3350
2965
  if (vhkVersion) {
3351
2966
  const latest = fetchLatestNpmVersion("@byh3071/vhk");
3352
2967
  if (latest && compareSemver(latest, vhkVersion) > 0) {
3353
- console.log(chalk11.yellow(` ${ko.doctor.updateAvailable(latest)}`));
2968
+ console.log(chalk10.yellow(` ${ko.doctor.updateAvailable(latest)}`));
3354
2969
  } else if (latest) {
3355
- console.log(chalk11.dim(` ${ko.doctor.updateCurrent}`));
2970
+ console.log(chalk10.dim(` ${ko.doctor.updateCurrent}`));
3356
2971
  }
3357
2972
  }
3358
2973
  console.log("");
3359
- console.log(chalk11.bold(` ${ko.doctor.projectFiles}`));
2974
+ console.log(chalk10.bold(` ${ko.doctor.projectFiles}`));
3360
2975
  const cwd = process.cwd();
3361
2976
  const projectFiles = [
3362
2977
  { name: "RULES.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
@@ -3366,51 +2981,51 @@ ${ko.doctor.title}
3366
2981
  { name: ".env", hint: ".gitignore\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778" }
3367
2982
  ];
3368
2983
  for (const file of projectFiles) {
3369
- const exists = fs11.existsSync(path12.join(cwd, file.name));
2984
+ const exists = fs8.existsSync(path9.join(cwd, file.name));
3370
2985
  if (exists) {
3371
- console.log(chalk11.green(` \u2705 ${file.name}`));
2986
+ console.log(chalk10.green(` \u2705 ${file.name}`));
3372
2987
  if (file.name === ".env") {
3373
- const gitignorePath = path12.join(cwd, ".gitignore");
3374
- if (fs11.existsSync(gitignorePath)) {
3375
- const gitignore = fs11.readFileSync(gitignorePath, "utf-8");
2988
+ const gitignorePath = path9.join(cwd, ".gitignore");
2989
+ if (fs8.existsSync(gitignorePath)) {
2990
+ const gitignore = fs8.readFileSync(gitignorePath, "utf-8");
3376
2991
  if (!gitignore.includes(".env")) {
3377
- console.log(chalk11.yellow(` ${ko.doctor.envNotIgnored}`));
2992
+ console.log(chalk10.yellow(` ${ko.doctor.envNotIgnored}`));
3378
2993
  }
3379
2994
  }
3380
2995
  }
3381
- } else if (file.name === ".env" && fs11.existsSync(path12.join(cwd, ".env.local"))) {
3382
- console.log(chalk11.green(" \u2705 .env.local") + chalk11.dim(" \u2014 \uB85C\uCEEC env \uC0AC\uC6A9 \uC911 (.env \uC5C6\uC5B4\uB3C4 \uC815\uC0C1)"));
2996
+ } else if (file.name === ".env" && fs8.existsSync(path9.join(cwd, ".env.local"))) {
2997
+ console.log(chalk10.green(" \u2705 .env.local") + chalk10.dim(" \u2014 \uB85C\uCEEC env \uC0AC\uC6A9 \uC911 (.env \uC5C6\uC5B4\uB3C4 \uC815\uC0C1)"));
3383
2998
  } else {
3384
- console.log(chalk11.dim(` \u2B1A ${file.name}`) + chalk11.dim(` \u2014 ${file.hint}`));
2999
+ console.log(chalk10.dim(` \u2B1A ${file.name}`) + chalk10.dim(` \u2014 ${file.hint}`));
3385
3000
  }
3386
3001
  }
3387
3002
  console.log("");
3388
- console.log(chalk11.bold(` ${ko.doctor.driftTitle}`));
3003
+ console.log(chalk10.bold(` ${ko.doctor.driftTitle}`));
3389
3004
  const ruleDrift = checkRuleDrift(cwd);
3390
3005
  if (!ruleDrift.checked) {
3391
- console.log(chalk11.dim(` ${ko.doctor.driftNoRules}`));
3006
+ console.log(chalk10.dim(` ${ko.doctor.driftNoRules}`));
3392
3007
  } else {
3393
3008
  const drifted = ruleDrift.results.filter((r) => r.status === "drifted");
3394
3009
  if (drifted.length === 0) {
3395
- console.log(chalk11.green(` ${ko.doctor.driftRuleClean}`));
3010
+ console.log(chalk10.green(` ${ko.doctor.driftRuleClean}`));
3396
3011
  } else {
3397
- console.log(chalk11.yellow(` ${ko.doctor.driftRuleWarn(drifted.map((d) => d.path).join(", "))}`));
3012
+ console.log(chalk10.yellow(` ${ko.doctor.driftRuleWarn(drifted.map((d) => d.path).join(", "))}`));
3398
3013
  }
3399
3014
  }
3400
3015
  const ctxDrift = checkContextDrift(cwd);
3401
3016
  if (ctxDrift.checked && ctxDrift.stale) {
3402
- console.log(chalk11.yellow(` ${ko.doctor.driftContextWarn}`));
3017
+ console.log(chalk10.yellow(` ${ko.doctor.driftContextWarn}`));
3403
3018
  }
3404
3019
  console.log("");
3405
3020
  if (allOk) {
3406
- console.log(chalk11.green.bold(` ${ko.doctor.allOk}`));
3021
+ console.log(chalk10.green.bold(` ${ko.doctor.allOk}`));
3407
3022
  printNextStep({
3408
3023
  message: ko.doctor.nextOkMessage,
3409
3024
  command: "vhk \uC2DC\uC791",
3410
3025
  cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
3411
3026
  });
3412
3027
  } else {
3413
- console.log(chalk11.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
3028
+ console.log(chalk10.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
3414
3029
  printNextStep({
3415
3030
  message: ko.doctor.nextRetryMessage,
3416
3031
  command: "vhk doctor",
@@ -3421,10 +3036,10 @@ ${ko.doctor.title}
3421
3036
  }
3422
3037
 
3423
3038
  // src/commands/ship.ts
3424
- import chalk12 from "chalk";
3425
- import inquirer5 from "inquirer";
3426
- import fs12 from "fs";
3427
- import path13 from "path";
3039
+ import chalk11 from "chalk";
3040
+ import inquirer4 from "inquirer";
3041
+ import fs9 from "fs";
3042
+ import path10 from "path";
3428
3043
  var CHECKLIST = [
3429
3044
  { id: "build", questionKey: "checkBuild", hintKey: "hintBuild" },
3430
3045
  { id: "test", questionKey: "checkTest", hintKey: "hintTest" },
@@ -3437,9 +3052,9 @@ function sanitizeVersion(version) {
3437
3052
  return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
3438
3053
  }
3439
3054
  function updateChangelogUnreleased(cwd, version, date) {
3440
- const changelogPath = path13.join(cwd, "CHANGELOG.md");
3441
- if (!fs12.existsSync(changelogPath)) return { status: "missing" };
3442
- const content = fs12.readFileSync(changelogPath, "utf-8");
3055
+ const changelogPath = path10.join(cwd, "CHANGELOG.md");
3056
+ if (!fs9.existsSync(changelogPath)) return { status: "missing" };
3057
+ const content = fs9.readFileSync(changelogPath, "utf-8");
3443
3058
  const unreleasedHeading = /^## \[Unreleased\][^\n]*$/m;
3444
3059
  if (!unreleasedHeading.test(content)) return { status: "no-unreleased" };
3445
3060
  const blankUnreleased = [
@@ -3456,36 +3071,36 @@ function updateChangelogUnreleased(cwd, version, date) {
3456
3071
  `## [${version}] \u2014 ${date}`
3457
3072
  ].join("\n");
3458
3073
  const updated = content.replace(unreleasedHeading, blankUnreleased);
3459
- fs12.writeFileSync(changelogPath, updated, "utf-8");
3074
+ fs9.writeFileSync(changelogPath, updated, "utf-8");
3460
3075
  return { status: "updated", version };
3461
3076
  }
3462
3077
  async function ship() {
3463
3078
  if (!ensureNotHardStopped("ship")) return;
3464
- console.log(chalk12.bold(`
3079
+ console.log(chalk11.bold(`
3465
3080
  ${ko.ship.title}
3466
3081
  `));
3467
3082
  const cwd = process.cwd();
3468
- console.log(chalk12.cyan.bold(` ${ko.ship.checklist}
3083
+ console.log(chalk11.cyan.bold(` ${ko.ship.checklist}
3469
3084
  `));
3470
- const { passed } = await inquirer5.prompt([{
3085
+ const { passed } = await inquirer4.prompt([{
3471
3086
  type: "checkbox",
3472
3087
  name: "passed",
3473
3088
  message: ko.ship.checkboxPrompt,
3474
3089
  choices: CHECKLIST.map((c) => ({
3475
- name: `${ko.ship[c.questionKey]} ${chalk12.dim(`(${ko.ship[c.hintKey]})`)}`,
3090
+ name: `${ko.ship[c.questionKey]} ${chalk11.dim(`(${ko.ship[c.hintKey]})`)}`,
3476
3091
  value: c.id
3477
3092
  }))
3478
3093
  }]);
3479
3094
  const allPassed = passed.length === CHECKLIST.length;
3480
3095
  const skipped = CHECKLIST.filter((c) => !passed.includes(c.id));
3481
3096
  if (!allPassed) {
3482
- console.log(chalk12.yellow(`
3097
+ console.log(chalk11.yellow(`
3483
3098
  ${ko.ship.incompleteHeader}`));
3484
3099
  skipped.forEach((s) => {
3485
- console.log(chalk12.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
3486
- console.log(chalk12.dim(` \u2192 ${ko.ship[s.hintKey]}`));
3100
+ console.log(chalk11.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
3101
+ console.log(chalk11.dim(` \u2192 ${ko.ship[s.hintKey]}`));
3487
3102
  });
3488
- const { proceed } = await inquirer5.prompt([{
3103
+ const { proceed } = await inquirer4.prompt([{
3489
3104
  type: "confirm",
3490
3105
  name: "proceed",
3491
3106
  message: ko.ship.proceedConfirm,
@@ -3500,26 +3115,26 @@ ${ko.ship.title}
3500
3115
  return;
3501
3116
  }
3502
3117
  } else {
3503
- console.log(chalk12.green(`
3118
+ console.log(chalk11.green(`
3504
3119
  ${ko.ship.allPassed}
3505
3120
  `));
3506
3121
  }
3507
- console.log(chalk12.cyan.bold(` ${ko.ship.retro}
3122
+ console.log(chalk11.cyan.bold(` ${ko.ship.retro}
3508
3123
  `));
3509
- console.log(chalk12.dim(` ${ko.ship.versionHint}`));
3510
- const retro = await inquirer5.prompt([
3124
+ console.log(chalk11.dim(` ${ko.ship.versionHint}`));
3125
+ const retro = await inquirer4.prompt([
3511
3126
  { type: "input", name: "version", message: ko.ship.versionPrompt },
3512
3127
  { type: "input", name: "whatWentWell", message: ko.ship.questionWell },
3513
3128
  { type: "input", name: "whatWentWrong", message: ko.ship.questionWrong },
3514
3129
  { type: "input", name: "learned", message: ko.ship.questionLearned },
3515
3130
  { type: "input", name: "nextVersion", message: ko.ship.questionNext }
3516
3131
  ]);
3517
- const buildLogDir = path13.join(cwd, "docs", "build-log");
3518
- if (!fs12.existsSync(buildLogDir)) fs12.mkdirSync(buildLogDir, { recursive: true });
3132
+ const buildLogDir = path10.join(cwd, "docs", "build-log");
3133
+ if (!fs9.existsSync(buildLogDir)) fs9.mkdirSync(buildLogDir, { recursive: true });
3519
3134
  const today = localDate();
3520
3135
  const versionSlug = sanitizeVersion(retro.version);
3521
3136
  const fileName = `${today}-v${versionSlug}.md`;
3522
- const filePath = path13.join(buildLogDir, fileName);
3137
+ const filePath = path10.join(buildLogDir, fileName);
3523
3138
  const content = [
3524
3139
  `# \uBE4C\uB4DC \uB85C\uADF8: v${versionSlug}`,
3525
3140
  "",
@@ -3548,9 +3163,9 @@ ${ko.ship.title}
3548
3163
  "---",
3549
3164
  `*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
3550
3165
  ].join("\n");
3551
- fs12.writeFileSync(filePath, content, "utf-8");
3552
- console.log(chalk12.green(`
3553
- ${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
3166
+ fs9.writeFileSync(filePath, content, "utf-8");
3167
+ console.log(chalk11.green(`
3168
+ ${ko.ship.buildLogDone(path10.relative(cwd, filePath))}`));
3554
3169
  const changelogResult = updateChangelogUnreleased(cwd, versionSlug, today);
3555
3170
  if (changelogResult.status === "updated") {
3556
3171
  log.success(ko.ship.changelogUpdated(changelogResult.version));
@@ -3568,10 +3183,10 @@ ${ko.ship.title}
3568
3183
  }
3569
3184
 
3570
3185
  // src/commands/save.ts
3571
- import { execFileSync as execFileSync2 } from "child_process";
3572
- import chalk13 from "chalk";
3186
+ import { execFileSync } from "child_process";
3187
+ import chalk12 from "chalk";
3573
3188
  import ora from "ora";
3574
- import inquirer6 from "inquirer";
3189
+ import inquirer5 from "inquirer";
3575
3190
 
3576
3191
  // src/lib/git-porcelain.ts
3577
3192
  function normalizePorcelain(raw) {
@@ -3597,54 +3212,54 @@ function statusIcon(code) {
3597
3212
  return "\u{1F4C4}";
3598
3213
  }
3599
3214
  async function save() {
3600
- console.log(chalk13.bold(`
3215
+ console.log(chalk12.bold(`
3601
3216
  \u{1F4BE} ${t("save.title")}`));
3602
- console.log(chalk13.gray("\u2500".repeat(40)));
3217
+ console.log(chalk12.gray("\u2500".repeat(40)));
3603
3218
  let gitRoot;
3604
3219
  try {
3605
- execFileSync2("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3220
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3606
3221
  gitRoot = getGitRoot();
3607
3222
  } catch {
3608
- console.log(chalk13.red(`\u274C ${t("save.notGitRepo")}`));
3223
+ console.log(chalk12.red(`\u274C ${t("save.notGitRepo")}`));
3609
3224
  return;
3610
3225
  }
3611
- console.log(chalk13.cyan(`
3226
+ console.log(chalk12.cyan(`
3612
3227
  \u{1F512} ${t("save.securityWarnHeader")}`));
3613
3228
  printSecurityWarnings(gitRoot);
3614
3229
  const severe = filterSevereFindings(scanProjectForSecrets(gitRoot).findings);
3615
3230
  if (severe.length > 0) {
3616
- console.log(chalk13.red(`
3231
+ console.log(chalk12.red(`
3617
3232
  \u26A0\uFE0F ${t("save.secretsFound", severe.length)}`));
3618
3233
  severe.slice(0, 5).forEach((f) => {
3619
- console.log(chalk13.dim(` ${f.file}:${f.line} \u2014 ${f.patternName}`));
3234
+ console.log(chalk12.dim(` ${f.file}:${f.line} \u2014 ${f.patternName}`));
3620
3235
  });
3621
3236
  if (severe.length > 5) {
3622
- console.log(chalk13.dim(` ... \uC678 ${severe.length - 5}\uAC74 (vhk \uBCF4\uC548 scan)`));
3237
+ console.log(chalk12.dim(` ... \uC678 ${severe.length - 5}\uAC74 (vhk \uBCF4\uC548 scan)`));
3623
3238
  }
3624
- const { proceed } = await inquirer6.prompt([{
3239
+ const { proceed } = await inquirer5.prompt([{
3625
3240
  type: "confirm",
3626
3241
  name: "proceed",
3627
3242
  message: t("save.secretsConfirm"),
3628
3243
  default: false
3629
3244
  }]);
3630
3245
  if (!proceed) {
3631
- console.log(chalk13.gray(t("save.cancelled")));
3246
+ console.log(chalk12.gray(t("save.cancelled")));
3632
3247
  return;
3633
3248
  }
3634
3249
  }
3635
3250
  const lines = parsePorcelainLines(gitOut(["status", "--porcelain"], gitRoot));
3636
3251
  if (lines.length === 0) {
3637
- console.log(chalk13.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
3252
+ console.log(chalk12.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
3638
3253
  return;
3639
3254
  }
3640
- console.log(chalk13.cyan(`
3255
+ console.log(chalk12.cyan(`
3641
3256
  \u{1F4CB} ${t("save.filesHeader", lines.length)}`));
3642
3257
  lines.forEach((line) => {
3643
3258
  const code = line.substring(0, 2);
3644
3259
  const name = line.substring(3);
3645
3260
  console.log(` ${statusIcon(code)} ${name}`);
3646
3261
  });
3647
- const { message } = await inquirer6.prompt([{
3262
+ const { message } = await inquirer5.prompt([{
3648
3263
  type: "input",
3649
3264
  name: "message",
3650
3265
  message: t("save.commitMessage"),
@@ -3659,21 +3274,21 @@ async function save() {
3659
3274
  spinner.text = t("save.pushing");
3660
3275
  if (!hasGitRemote(gitRoot)) {
3661
3276
  spinner.succeed(t("save.successLocal"));
3662
- console.log(chalk13.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
3277
+ console.log(chalk12.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
3663
3278
  } else {
3664
3279
  try {
3665
3280
  gitRun(["push"], gitRoot);
3666
3281
  spinner.succeed(t("save.successWithPush"));
3667
3282
  } catch (pushErr) {
3668
3283
  spinner.fail(t("save.pushFailed"));
3669
- console.log(chalk13.red(getExecErrorMessage(pushErr)));
3670
- console.log(chalk13.yellow(`
3284
+ console.log(chalk12.red(getExecErrorMessage(pushErr)));
3285
+ console.log(chalk12.yellow(`
3671
3286
  \u{1F4A1} ${t("save.commitOkPushFailed")}`));
3672
3287
  process.exitCode = 1;
3673
3288
  }
3674
3289
  }
3675
3290
  if (process.exitCode !== 1) {
3676
- console.log(chalk13.green(`
3291
+ console.log(chalk12.green(`
3677
3292
  \u2705 ${t("save.done", lines.length)}`));
3678
3293
  printNextStep({
3679
3294
  message: t("save.nextOkMessage"),
@@ -3681,7 +3296,7 @@ async function save() {
3681
3296
  cursorHint: t("save.nextOkCursor")
3682
3297
  });
3683
3298
  } else {
3684
- console.log(chalk13.green(`
3299
+ console.log(chalk12.green(`
3685
3300
  \u2705 ${t("save.doneLocalOnly", lines.length)}`));
3686
3301
  printNextStep({
3687
3302
  message: t("save.nextPushFailMessage"),
@@ -3691,12 +3306,12 @@ async function save() {
3691
3306
  }
3692
3307
  } catch (err) {
3693
3308
  spinner.fail(t("save.failed"));
3694
- console.log(chalk13.red(getExecErrorMessage(err)));
3309
+ console.log(chalk12.red(getExecErrorMessage(err)));
3695
3310
  if (didAdd) {
3696
3311
  try {
3697
3312
  const staged = gitOut(["diff", "--cached", "--stat"], gitRoot).trim();
3698
3313
  if (staged) {
3699
- console.log(chalk13.yellow(`
3314
+ console.log(chalk12.yellow(`
3700
3315
  \u{1F4A1} ${t("save.stagedAfterFail")}`));
3701
3316
  }
3702
3317
  } catch {
@@ -3707,9 +3322,9 @@ async function save() {
3707
3322
  }
3708
3323
 
3709
3324
  // src/commands/undo.ts
3710
- import { execFileSync as execFileSync3 } from "child_process";
3711
- import chalk14 from "chalk";
3712
- import inquirer7 from "inquirer";
3325
+ import { execFileSync as execFileSync2 } from "child_process";
3326
+ import chalk13 from "chalk";
3327
+ import inquirer6 from "inquirer";
3713
3328
  function parseRecentCommits(logOutput) {
3714
3329
  return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
3715
3330
  }
@@ -3732,36 +3347,36 @@ function isUndoRisky(undoCount, unpushedCount, hasRemote) {
3732
3347
  return false;
3733
3348
  }
3734
3349
  async function undo() {
3735
- console.log(chalk14.bold(`
3350
+ console.log(chalk13.bold(`
3736
3351
  \u23EA ${t("undo.title")}`));
3737
- console.log(chalk14.gray("\u2500".repeat(40)));
3352
+ console.log(chalk13.gray("\u2500".repeat(40)));
3738
3353
  let gitRoot;
3739
3354
  try {
3740
- execFileSync3("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3355
+ execFileSync2("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3741
3356
  gitRoot = getGitRoot();
3742
3357
  } catch {
3743
- console.log(chalk14.red(`\u274C ${t("undo.notGitRepo")}`));
3358
+ console.log(chalk13.red(`\u274C ${t("undo.notGitRepo")}`));
3744
3359
  return;
3745
3360
  }
3746
3361
  let logOutput;
3747
3362
  try {
3748
3363
  logOutput = gitOut(["log", "--oneline", "-5"], gitRoot).trim();
3749
3364
  } catch {
3750
- console.log(chalk14.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3365
+ console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3751
3366
  return;
3752
3367
  }
3753
3368
  const commits = parseRecentCommits(logOutput);
3754
3369
  if (commits.length === 0) {
3755
- console.log(chalk14.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3370
+ console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3756
3371
  return;
3757
3372
  }
3758
- console.log(chalk14.cyan(`
3373
+ console.log(chalk13.cyan(`
3759
3374
  ${t("undo.recentHeader")}`));
3760
3375
  commits.forEach((c, i) => {
3761
3376
  console.log(` ${i === 0 ? "\u{1F449}" : " "} ${c}`);
3762
3377
  });
3763
3378
  const maxUndo = commits.length;
3764
- const { count } = await inquirer7.prompt([{
3379
+ const { count } = await inquirer6.prompt([{
3765
3380
  type: "number",
3766
3381
  name: "count",
3767
3382
  message: t("undo.howMany"),
@@ -3772,7 +3387,7 @@ ${t("undo.recentHeader")}`));
3772
3387
  const undoCount = Math.min(Math.max(1, count || 1), maxUndo);
3773
3388
  const headCount = countLocalCommits(gitRoot);
3774
3389
  if (undoCount >= headCount) {
3775
- console.log(chalk14.yellow(`
3390
+ console.log(chalk13.yellow(`
3776
3391
  \u{1F4ED} ${t("undo.rootCommit")}`));
3777
3392
  return;
3778
3393
  }
@@ -3781,30 +3396,30 @@ ${t("undo.recentHeader")}`));
3781
3396
  const risky = isUndoRisky(undoCount, unpushed, remote);
3782
3397
  if (risky) {
3783
3398
  if (unpushed < 0) {
3784
- console.log(chalk14.red(`
3399
+ console.log(chalk13.red(`
3785
3400
  \u26A0\uFE0F ${t("undo.noUpstreamWarning")}`));
3786
3401
  } else {
3787
- console.log(chalk14.red(`
3402
+ console.log(chalk13.red(`
3788
3403
  \u26A0\uFE0F ${t("undo.alreadyPushed")}`));
3789
3404
  }
3790
3405
  }
3791
- const { confirm } = await inquirer7.prompt([{
3406
+ const { confirm } = await inquirer6.prompt([{
3792
3407
  type: "confirm",
3793
3408
  name: "confirm",
3794
3409
  message: risky ? t("undo.confirmRisky", undoCount) : t("undo.confirmMessage"),
3795
3410
  default: false
3796
3411
  }]);
3797
3412
  if (!confirm) {
3798
- console.log(chalk14.gray(t("undo.cancelled")));
3413
+ console.log(chalk13.gray(t("undo.cancelled")));
3799
3414
  return;
3800
3415
  }
3801
3416
  try {
3802
3417
  gitRun(["reset", "--soft", `HEAD~${undoCount}`], gitRoot);
3803
- console.log(chalk14.green(`
3418
+ console.log(chalk13.green(`
3804
3419
  \u2705 ${t("undo.success")}`));
3805
- console.log(chalk14.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
3420
+ console.log(chalk13.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
3806
3421
  if (risky) {
3807
- console.log(chalk14.yellow(`
3422
+ console.log(chalk13.yellow(`
3808
3423
  \u{1F4A1} ${t("undo.forcePushHint")}`));
3809
3424
  }
3810
3425
  printNextStep({
@@ -3813,39 +3428,39 @@ ${t("undo.recentHeader")}`));
3813
3428
  cursorHint: t("undo.nextCursor")
3814
3429
  });
3815
3430
  } catch (err) {
3816
- console.log(chalk14.red(`\u274C ${t("undo.failed")}`));
3431
+ console.log(chalk13.red(`\u274C ${t("undo.failed")}`));
3817
3432
  const msg = err instanceof Error ? err.message : String(err);
3818
- console.log(chalk14.red(msg));
3433
+ console.log(chalk13.red(msg));
3819
3434
  process.exitCode = 1;
3820
3435
  }
3821
3436
  }
3822
3437
 
3823
3438
  // src/commands/restore.ts
3824
- import chalk15 from "chalk";
3825
- import inquirer8 from "inquirer";
3439
+ import chalk14 from "chalk";
3440
+ import inquirer7 from "inquirer";
3826
3441
  async function restore(id) {
3827
- console.log(chalk15.bold(`
3442
+ console.log(chalk14.bold(`
3828
3443
  ${ko.restore.title}`));
3829
- console.log(chalk15.gray("\u2500".repeat(40)));
3830
- console.log(chalk15.dim(` ${ko.restore.notGitNote}`));
3444
+ console.log(chalk14.gray("\u2500".repeat(40)));
3445
+ console.log(chalk14.dim(` ${ko.restore.notGitNote}`));
3831
3446
  const cwd = process.cwd();
3832
3447
  const backups = listBackups(cwd);
3833
3448
  if (backups.length === 0) {
3834
- console.log(chalk15.yellow(`
3449
+ console.log(chalk14.yellow(`
3835
3450
  ${ko.restore.noBackups}`));
3836
3451
  return;
3837
3452
  }
3838
3453
  let targetId = id;
3839
3454
  if (!targetId) {
3840
3455
  if (!process.stdout.isTTY) {
3841
- console.log(chalk15.cyan(`
3456
+ console.log(chalk14.cyan(`
3842
3457
  ${ko.restore.listHeader}`));
3843
3458
  for (const b of backups) console.log(` ${b.id} (${b.files.length}\uAC1C \uD30C\uC77C)`);
3844
- console.log(chalk15.yellow(`
3459
+ console.log(chalk14.yellow(`
3845
3460
  ${ko.restore.nonTtyHint}`));
3846
3461
  return;
3847
3462
  }
3848
- const { picked } = await inquirer8.prompt([
3463
+ const { picked } = await inquirer7.prompt([
3849
3464
  {
3850
3465
  type: "list",
3851
3466
  name: "picked",
@@ -3860,26 +3475,26 @@ ${ko.restore.nonTtyHint}`));
3860
3475
  }
3861
3476
  try {
3862
3477
  const restored = restoreBackup(targetId, cwd);
3863
- console.log(chalk15.green(`
3478
+ console.log(chalk14.green(`
3864
3479
  ${ko.restore.restored(restored.length, targetId)}`));
3865
- for (const r of restored) console.log(chalk15.gray(` ${r}`));
3480
+ for (const r of restored) console.log(chalk14.gray(` ${r}`));
3866
3481
  printNextStep({
3867
3482
  message: "\uBC31\uC5C5 \uBCF5\uC6D0 \uC644\uB8CC! \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694.",
3868
3483
  command: "vhk diff",
3869
3484
  cursorHint: "\uBCC0\uACBD\uC0AC\uD56D \uBCF4\uC5EC\uC918"
3870
3485
  });
3871
3486
  } catch {
3872
- console.log(chalk15.red(`
3487
+ console.log(chalk14.red(`
3873
3488
  ${ko.restore.notFound(targetId)}`));
3874
3489
  process.exitCode = 1;
3875
3490
  }
3876
3491
  }
3877
3492
 
3878
3493
  // src/commands/status.ts
3879
- import { execFileSync as execFileSync4 } from "child_process";
3880
- import fs13 from "fs";
3881
- import path14 from "path";
3882
- import chalk16 from "chalk";
3494
+ import { execFileSync as execFileSync3 } from "child_process";
3495
+ import fs10 from "fs";
3496
+ import path11 from "path";
3497
+ import chalk15 from "chalk";
3883
3498
  function countFileChanges(porcelain) {
3884
3499
  const lines = porcelain.split("\n").filter(Boolean);
3885
3500
  let staged = 0;
@@ -3917,8 +3532,8 @@ function parseRecentCommitLines(logOutput) {
3917
3532
  return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
3918
3533
  }
3919
3534
  function readProjectPackage(cwd = process.cwd()) {
3920
- const pkgPath = path14.join(cwd, "package.json");
3921
- if (!fs13.existsSync(pkgPath)) return null;
3535
+ const pkgPath = path11.join(cwd, "package.json");
3536
+ if (!fs10.existsSync(pkgPath)) return null;
3922
3537
  try {
3923
3538
  const pkg = readJsonFile(pkgPath);
3924
3539
  if (!pkg.name && !pkg.version) return null;
@@ -3954,15 +3569,15 @@ function getSyncCounts(gitRoot) {
3954
3569
  }
3955
3570
  }
3956
3571
  async function status() {
3957
- console.log(chalk16.bold(`
3572
+ console.log(chalk15.bold(`
3958
3573
  \u{1F4CA} ${t("status.title")}`));
3959
- console.log(chalk16.gray("\u2500".repeat(40)));
3574
+ console.log(chalk15.gray("\u2500".repeat(40)));
3960
3575
  let gitRoot;
3961
3576
  try {
3962
- execFileSync4("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3577
+ execFileSync3("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3963
3578
  gitRoot = getGitRoot();
3964
3579
  } catch {
3965
- console.log(chalk16.red(`\u274C ${t("status.notGitRepo")}`));
3580
+ console.log(chalk15.red(`\u274C ${t("status.notGitRepo")}`));
3966
3581
  return;
3967
3582
  }
3968
3583
  let branch;
@@ -3981,36 +3596,37 @@ async function status() {
3981
3596
  commits = [];
3982
3597
  }
3983
3598
  const pkg = readProjectPackage();
3984
- console.log(chalk16.cyan(`
3985
- \u{1F33F} ${t("status.branch")}`) + chalk16.white(` ${branch}`));
3599
+ console.log(chalk15.cyan(`
3600
+ \u{1F33F} ${t("status.branch")}`) + chalk15.white(` ${branch}`));
3986
3601
  console.log(
3987
- chalk16.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk16.white(
3602
+ chalk15.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk15.white(
3988
3603
  ` staged ${counts.staged} \xB7 unstaged ${counts.unstaged} \xB7 untracked ${counts.untracked}`
3989
3604
  )
3990
3605
  );
3991
- console.log(chalk16.cyan(`
3606
+ console.log(chalk15.cyan(`
3992
3607
  \u{1F4CB} ${t("status.recentCommits", commits.length)}`));
3993
3608
  if (commits.length === 0) {
3994
- console.log(chalk16.dim(` ${t("status.noCommits")}`));
3609
+ console.log(chalk15.dim(` ${t("status.noCommits")}`));
3995
3610
  } else {
3996
- commits.forEach((c) => console.log(` ${chalk16.dim("\u2022")} ${c}`));
3611
+ commits.forEach((c) => console.log(` ${chalk15.dim("\u2022")} ${c}`));
3997
3612
  }
3998
3613
  console.log(
3999
- chalk16.cyan(`
4000
- \u{1F504} ${t("status.remote")}`) + chalk16.white(` ${formatSyncLabel(sync2)}`)
3614
+ chalk15.cyan(`
3615
+ \u{1F504} ${t("status.remote")}`) + chalk15.white(` ${formatSyncLabel(sync2)}`)
4001
3616
  );
4002
- console.log(chalk16.gray("\n" + "\u2500".repeat(40)));
3617
+ console.log(chalk15.gray("\n" + "\u2500".repeat(40)));
4003
3618
  if (pkg) {
4004
- console.log(chalk16.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk16.white(` ${pkg.name} v${pkg.version}`));
3619
+ console.log(chalk15.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk15.white(` ${pkg.name} v${pkg.version}`));
4005
3620
  } else {
4006
- console.log(chalk16.dim(`\u{1F4E6} ${t("status.noPackage")}`));
3621
+ console.log(chalk15.dim(`\u{1F4E6} ${t("status.noPackage")}`));
4007
3622
  }
4008
3623
  const hasChanges = counts.staged + counts.unstaged + counts.untracked > 0;
4009
3624
  printNextStep(selectStatusNextStep(hasChanges));
3625
+ printContextResumeHint();
4010
3626
  }
4011
3627
 
4012
3628
  // src/commands/diff.ts
4013
- import chalk17 from "chalk";
3629
+ import chalk16 from "chalk";
4014
3630
  function gitOut2(args) {
4015
3631
  const r = safeExecFile("git", args);
4016
3632
  return r.ok ? r.out : "";
@@ -4047,67 +3663,67 @@ function summarizeNumstat(numstat) {
4047
3663
  return { fileCount, totalAdd, totalDel };
4048
3664
  }
4049
3665
  function printFile(f) {
4050
- const adds = f.additions > 0 ? chalk17.green(`+${f.additions}`) : "";
4051
- const dels = f.deletions > 0 ? chalk17.red(`-${f.deletions}`) : "";
3666
+ const adds = f.additions > 0 ? chalk16.green(`+${f.additions}`) : "";
3667
+ const dels = f.deletions > 0 ? chalk16.red(`-${f.deletions}`) : "";
4052
3668
  const change = [adds, dels].filter(Boolean).join(" ");
4053
3669
  console.log(` ${f.name} ${change}`);
4054
3670
  }
4055
3671
  async function diff() {
4056
- console.log(chalk17.bold(`
3672
+ console.log(chalk16.bold(`
4057
3673
  \u{1F50D} ${t("diff.title")}`));
4058
- console.log(chalk17.gray("\u2500".repeat(40)));
3674
+ console.log(chalk16.gray("\u2500".repeat(40)));
4059
3675
  if (!safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok) {
4060
- console.log(chalk17.red(`\u274C ${t("diff.notGitRepo")}`));
3676
+ console.log(chalk16.red(`\u274C ${t("diff.notGitRepo")}`));
4061
3677
  return;
4062
3678
  }
4063
3679
  const unstaged = gitOut2(["diff", "--stat"]);
4064
3680
  const staged = gitOut2(["diff", "--cached", "--stat"]);
4065
3681
  const untracked = gitOut2(["ls-files", "--others", "--exclude-standard"]);
4066
3682
  if (!unstaged && !staged && !untracked) {
4067
- console.log(chalk17.green(`
3683
+ console.log(chalk16.green(`
4068
3684
  \u2705 ${t("diff.noChanges")}`));
4069
3685
  return;
4070
3686
  }
4071
3687
  if (staged) {
4072
- console.log(chalk17.cyan(`
3688
+ console.log(chalk16.cyan(`
4073
3689
  ${t("diff.stagedHeader")}`));
4074
3690
  parseDiffStat(staged).forEach((f) => printFile(f));
4075
3691
  }
4076
3692
  if (unstaged) {
4077
- console.log(chalk17.cyan(`
3693
+ console.log(chalk16.cyan(`
4078
3694
  ${t("diff.unstagedHeader")}`));
4079
3695
  parseDiffStat(unstaged).forEach((f) => printFile(f));
4080
3696
  }
4081
3697
  if (untracked) {
4082
3698
  const files = untracked.split("\n").filter(Boolean);
4083
- console.log(chalk17.cyan(`
3699
+ console.log(chalk16.cyan(`
4084
3700
  ${t("diff.untrackedHeader", files.length)}`));
4085
- files.forEach((f) => console.log(` ${chalk17.green("+")} ${f}`));
3701
+ files.forEach((f) => console.log(` ${chalk16.green("+")} ${f}`));
4086
3702
  }
4087
3703
  const numstat = gitOut2(["diff", "--numstat", "HEAD"]);
4088
3704
  if (numstat) {
4089
3705
  const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
4090
- console.log(chalk17.cyan(`
3706
+ console.log(chalk16.cyan(`
4091
3707
  ${t("diff.summaryHeader")}`));
4092
3708
  console.log(` ${t("diff.filesLine", fileCount)}`);
4093
- console.log(` \uCD94\uAC00: ${chalk17.green(`+${totalAdd}`)}\uC904`);
4094
- console.log(` \uC0AD\uC81C: ${chalk17.red(`-${totalDel}`)}\uC904`);
3709
+ console.log(` \uCD94\uAC00: ${chalk16.green(`+${totalAdd}`)}\uC904`);
3710
+ console.log(` \uC0AD\uC81C: ${chalk16.red(`-${totalDel}`)}\uC904`);
4095
3711
  }
4096
3712
  console.log("");
4097
3713
  }
4098
3714
 
4099
3715
  // src/commands/mcp-init.ts
4100
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
4101
- import { join as join6, dirname } from "path";
3716
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
3717
+ import { join as join5, dirname } from "path";
4102
3718
  import { fileURLToPath as fileURLToPath2 } from "url";
4103
- import chalk18 from "chalk";
3719
+ import chalk17 from "chalk";
4104
3720
  function resolveMcpEntryPoint() {
4105
3721
  try {
4106
3722
  const here = fileURLToPath2(import.meta.url);
4107
3723
  const dir = dirname(here);
4108
3724
  for (const rel of [["mcp", "index.js"], ["..", "mcp", "index.js"]]) {
4109
- const candidate = join6(dir, ...rel);
4110
- if (existsSync5(candidate)) return candidate;
3725
+ const candidate = join5(dir, ...rel);
3726
+ if (existsSync4(candidate)) return candidate;
4111
3727
  }
4112
3728
  } catch {
4113
3729
  }
@@ -4115,17 +3731,17 @@ function resolveMcpEntryPoint() {
4115
3731
  const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
4116
3732
  if (typeof url === "string") {
4117
3733
  const p = fileURLToPath2(url);
4118
- if (existsSync5(p)) return p;
3734
+ if (existsSync4(p)) return p;
4119
3735
  }
4120
3736
  } catch {
4121
3737
  }
4122
3738
  try {
4123
- const pkgPath = join6(process.cwd(), "package.json");
4124
- if (existsSync5(pkgPath)) {
3739
+ const pkgPath = join5(process.cwd(), "package.json");
3740
+ if (existsSync4(pkgPath)) {
4125
3741
  const pkg = readJsonFile(pkgPath);
4126
3742
  if (pkg.name === "@byh3071/vhk") {
4127
- const local = join6(process.cwd(), "dist", "mcp", "index.js");
4128
- if (existsSync5(local)) return local;
3743
+ const local = join5(process.cwd(), "dist", "mcp", "index.js");
3744
+ if (existsSync4(local)) return local;
4129
3745
  }
4130
3746
  }
4131
3747
  } catch {
@@ -4140,31 +3756,31 @@ function resolveVhkMcpEntry() {
4140
3756
  return { command: "vhk-mcp", args: [] };
4141
3757
  }
4142
3758
  async function mcpInit() {
4143
- console.log(chalk18.bold("\n\u{1F50C} " + t("mcp.initTitle")));
4144
- console.log(chalk18.gray("\u2500".repeat(40)));
4145
- const cursorDir = join6(process.cwd(), ".cursor");
4146
- if (!existsSync5(cursorDir)) {
3759
+ console.log(chalk17.bold("\n\u{1F50C} " + t("mcp.initTitle")));
3760
+ console.log(chalk17.gray("\u2500".repeat(40)));
3761
+ const cursorDir = join5(process.cwd(), ".cursor");
3762
+ if (!existsSync4(cursorDir)) {
4147
3763
  mkdirSync3(cursorDir, { recursive: true });
4148
3764
  }
4149
- const configPath = join6(cursorDir, "mcp.json");
3765
+ const configPath = join5(cursorDir, "mcp.json");
4150
3766
  const vhkEntry = resolveVhkMcpEntry();
4151
3767
  let config;
4152
- if (existsSync5(configPath)) {
3768
+ if (existsSync4(configPath)) {
4153
3769
  try {
4154
3770
  const parsed = readJsonFile(configPath);
4155
3771
  config = {
4156
3772
  mcpServers: { ...parsed.mcpServers ?? {}, vhk: vhkEntry }
4157
3773
  };
4158
3774
  } catch {
4159
- console.log(chalk18.yellow("\u26A0\uFE0F \uAE30\uC874 .cursor/mcp.json \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uC0C8 \uD30C\uC77C\uB85C \uB36E\uC5B4\uC501\uB2C8\uB2E4."));
3775
+ console.log(chalk17.yellow("\u26A0\uFE0F \uAE30\uC874 .cursor/mcp.json \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uC0C8 \uD30C\uC77C\uB85C \uB36E\uC5B4\uC501\uB2C8\uB2E4."));
4160
3776
  config = { mcpServers: { vhk: vhkEntry } };
4161
3777
  }
4162
3778
  } else {
4163
3779
  config = { mcpServers: { vhk: vhkEntry } };
4164
3780
  }
4165
3781
  writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
4166
- console.log(chalk18.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
4167
- console.log(chalk18.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
3782
+ console.log(chalk17.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
3783
+ console.log(chalk17.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
4168
3784
  console.log(` ${configPath}`);
4169
3785
  printNextStep({
4170
3786
  message: t("mcp.nextMessage"),
@@ -4174,9 +3790,9 @@ async function mcpInit() {
4174
3790
  }
4175
3791
 
4176
3792
  // src/commands/design.ts
4177
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
4178
- import chalk19 from "chalk";
4179
- import inquirer9 from "inquirer";
3793
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
3794
+ import chalk18 from "chalk";
3795
+ import inquirer8 from "inquirer";
4180
3796
  var PALETTES = [
4181
3797
  {
4182
3798
  name: "Minimal",
@@ -4228,7 +3844,7 @@ var PALETTES = [
4228
3844
  }
4229
3845
  ];
4230
3846
  function hasTailwind() {
4231
- return existsSync6("tailwind.config.js") || existsSync6("tailwind.config.ts") || existsSync6("tailwind.config.mjs") || existsSync6("tailwind.config.cjs");
3847
+ return existsSync5("tailwind.config.js") || existsSync5("tailwind.config.ts") || existsSync5("tailwind.config.mjs") || existsSync5("tailwind.config.cjs");
4232
3848
  }
4233
3849
  function isTailwindV4Deps(deps) {
4234
3850
  if (deps["@tailwindcss/vite"] || deps["@tailwindcss/postcss"]) return true;
@@ -4278,10 +3894,10 @@ export default vhkColors
4278
3894
  `;
4279
3895
  }
4280
3896
  async function design() {
4281
- console.log(chalk19.bold("\n\u{1F3A8} " + t("design.title")));
4282
- console.log(chalk19.gray("\u2500".repeat(40)));
3897
+ console.log(chalk18.bold("\n\u{1F3A8} " + t("design.title")));
3898
+ console.log(chalk18.gray("\u2500".repeat(40)));
4283
3899
  if (!ensureInteractive("\uCEEC\uB7EC \uD314\uB808\uD2B8 \uC120\uD0DD\uC740 \uB300\uD654\uD615\uC73C\uB85C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4.")) return;
4284
- const { paletteIndex } = await inquirer9.prompt([
3900
+ const { paletteIndex } = await inquirer8.prompt([
4285
3901
  {
4286
3902
  type: "list",
4287
3903
  name: "paletteIndex",
@@ -4293,37 +3909,37 @@ async function design() {
4293
3909
  }
4294
3910
  ]);
4295
3911
  const palette = PALETTES[paletteIndex];
4296
- console.log(chalk19.cyan(`
3912
+ console.log(chalk18.cyan(`
4297
3913
  \u{1F3A8} \uC120\uD0DD\uB41C \uD314\uB808\uD2B8: ${palette.name}`));
4298
3914
  const v4 = hasTailwindV4();
4299
3915
  const targetPath = v4 ? "src/styles/theme.css" : hasTailwind() ? "src/styles/vhk-colors.ts" : "src/styles/tokens.css";
4300
3916
  const content = v4 ? generateTailwindV4Theme(palette) : hasTailwind() ? generateTailwindExtend(palette) : generateCSSTokens(palette);
4301
- if (existsSync6(targetPath)) {
4302
- const { overwrite } = await inquirer9.prompt([{
3917
+ if (existsSync5(targetPath)) {
3918
+ const { overwrite } = await inquirer8.prompt([{
4303
3919
  type: "confirm",
4304
3920
  name: "overwrite",
4305
3921
  message: `${targetPath} \uC774\uBBF8 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?`,
4306
3922
  default: false
4307
3923
  }]);
4308
3924
  if (!overwrite) {
4309
- console.log(chalk19.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
3925
+ console.log(chalk18.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
4310
3926
  return;
4311
3927
  }
4312
3928
  }
4313
3929
  mkdirSync4("src/styles", { recursive: true });
4314
3930
  writeFileSync4(targetPath, content, "utf-8");
4315
3931
  if (v4) {
4316
- console.log(chalk19.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (Tailwind v4 @theme)"));
4317
- console.log(chalk19.gray(' \uC9C4\uC785 CSS(\uC608: src/index.css)\uC5D0 `@import "./styles/theme.css";` \uCD94\uAC00 \u2192 bg-primary \uB4F1 \uC720\uD2F8 \uC0AC\uC6A9.'));
4318
- console.log(chalk19.gray(" \uB2E4\uD06C \uD1A0\uAE00: \uB8E8\uD2B8 <html>/<body> \uC5D0 `.dark` \uD074\uB798\uC2A4 on/off."));
3932
+ console.log(chalk18.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (Tailwind v4 @theme)"));
3933
+ console.log(chalk18.gray(' \uC9C4\uC785 CSS(\uC608: src/index.css)\uC5D0 `@import "./styles/theme.css";` \uCD94\uAC00 \u2192 bg-primary \uB4F1 \uC720\uD2F8 \uC0AC\uC6A9.'));
3934
+ console.log(chalk18.gray(" \uB2E4\uD06C \uD1A0\uAE00: \uB8E8\uD2B8 <html>/<body> \uC5D0 `.dark` \uD074\uB798\uC2A4 on/off."));
4319
3935
  } else if (hasTailwind()) {
4320
- console.log(chalk19.green("\n\u2705 src/styles/vhk-colors.ts \uC0DD\uC131"));
4321
- console.log(chalk19.gray(" tailwind.config\uC758 extend.colors\uC5D0 import \uD574\uC11C \uC0AC\uC6A9\uD558\uC138\uC694."));
3936
+ console.log(chalk18.green("\n\u2705 src/styles/vhk-colors.ts \uC0DD\uC131"));
3937
+ console.log(chalk18.gray(" tailwind.config\uC758 extend.colors\uC5D0 import \uD574\uC11C \uC0AC\uC6A9\uD558\uC138\uC694."));
4322
3938
  } else {
4323
- console.log(chalk19.green("\n\u2705 src/styles/tokens.css \uC0DD\uC131"));
4324
- console.log(chalk19.gray(" HTML\uC5D0 <link>\uB85C \uCD94\uAC00\uD558\uAC70\uB098 CSS\uC5D0\uC11C @import \uD558\uC138\uC694."));
3939
+ console.log(chalk18.green("\n\u2705 src/styles/tokens.css \uC0DD\uC131"));
3940
+ console.log(chalk18.gray(" HTML\uC5D0 <link>\uB85C \uCD94\uAC00\uD558\uAC70\uB098 CSS\uC5D0\uC11C @import \uD558\uC138\uC694."));
4325
3941
  }
4326
- console.log(chalk19.bold("\n\u{1F308} \uCEEC\uB7EC \uBBF8\uB9AC\uBCF4\uAE30:"));
3942
+ console.log(chalk18.bold("\n\u{1F308} \uCEEC\uB7EC \uBBF8\uB9AC\uBCF4\uAE30:"));
4327
3943
  for (const [key, value] of Object.entries(palette.colors)) {
4328
3944
  console.log(` ${key.padEnd(12)} ${value}`);
4329
3945
  }
@@ -4338,9 +3954,9 @@ async function designPalette() {
4338
3954
  }
4339
3955
 
4340
3956
  // src/commands/theme.ts
4341
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
4342
- import chalk20 from "chalk";
4343
- import inquirer10 from "inquirer";
3957
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
3958
+ import chalk19 from "chalk";
3959
+ import inquirer9 from "inquirer";
4344
3960
  function generateDarkCSS() {
4345
3961
  return `/* vhk theme \u2014 \uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC CSS \uBCC0\uC218 */
4346
3962
 
@@ -4396,13 +4012,13 @@ export function initTheme(): void {
4396
4012
  `;
4397
4013
  }
4398
4014
  async function theme() {
4399
- console.log(chalk20.bold("\n\u{1F319} " + t("theme.title")));
4400
- console.log(chalk20.gray("\u2500".repeat(40)));
4015
+ console.log(chalk19.bold("\n\u{1F319} " + t("theme.title")));
4016
+ console.log(chalk19.gray("\u2500".repeat(40)));
4401
4017
  const cssPath = "src/styles/theme.css";
4402
4018
  const togglePath = "src/lib/theme-toggle.ts";
4403
- const conflicts = [cssPath, togglePath].filter((p) => existsSync7(p));
4019
+ const conflicts = [cssPath, togglePath].filter((p) => existsSync6(p));
4404
4020
  if (conflicts.length > 0) {
4405
- const { overwrite } = await inquirer10.prompt([{
4021
+ const { overwrite } = await inquirer9.prompt([{
4406
4022
  type: "confirm",
4407
4023
  name: "overwrite",
4408
4024
  message: `\uB2E4\uC74C \uD30C\uC77C\uC774 \uC774\uBBF8 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?
@@ -4410,21 +4026,21 @@ async function theme() {
4410
4026
  default: false
4411
4027
  }]);
4412
4028
  if (!overwrite) {
4413
- console.log(chalk20.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
4029
+ console.log(chalk19.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
4414
4030
  return;
4415
4031
  }
4416
4032
  }
4417
4033
  mkdirSync5("src/styles", { recursive: true });
4418
4034
  mkdirSync5("src/lib", { recursive: true });
4419
4035
  writeFileSync5(cssPath, generateDarkCSS(), "utf-8");
4420
- console.log(chalk20.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"));
4036
+ console.log(chalk19.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"));
4421
4037
  writeFileSync5(togglePath, generateToggleUtil(), "utf-8");
4422
- console.log(chalk20.green("\u2705 src/lib/theme-toggle.ts \uC0DD\uC131 (\uD1A0\uAE00 \uC720\uD2F8\uB9AC\uD2F0)"));
4423
- console.log(chalk20.bold("\n\u{1F4D6} \uC0AC\uC6A9\uBC95:"));
4424
- console.log(chalk20.gray(" 1. theme.css\uB97C \uAE00\uB85C\uBC8C \uC2A4\uD0C0\uC77C\uC5D0 \uCD94\uAC00"));
4425
- console.log(chalk20.gray(' 2. import { initTheme, toggleTheme } from "./lib/theme-toggle"'));
4426
- console.log(chalk20.gray(" 3. \uC571 \uC9C4\uC785\uC810\uC5D0\uC11C initTheme() \uD638\uCD9C"));
4427
- console.log(chalk20.gray(" 4. \uD1A0\uAE00 \uBC84\uD2BC\uC5D0\uC11C toggleTheme() \uD638\uCD9C"));
4038
+ console.log(chalk19.green("\u2705 src/lib/theme-toggle.ts \uC0DD\uC131 (\uD1A0\uAE00 \uC720\uD2F8\uB9AC\uD2F0)"));
4039
+ console.log(chalk19.bold("\n\u{1F4D6} \uC0AC\uC6A9\uBC95:"));
4040
+ console.log(chalk19.gray(" 1. theme.css\uB97C \uAE00\uB85C\uBC8C \uC2A4\uD0C0\uC77C\uC5D0 \uCD94\uAC00"));
4041
+ console.log(chalk19.gray(' 2. import { initTheme, toggleTheme } from "./lib/theme-toggle"'));
4042
+ console.log(chalk19.gray(" 3. \uC571 \uC9C4\uC785\uC810\uC5D0\uC11C initTheme() \uD638\uCD9C"));
4043
+ console.log(chalk19.gray(" 4. \uD1A0\uAE00 \uBC84\uD2BC\uC5D0\uC11C toggleTheme() \uD638\uCD9C"));
4428
4044
  printNextStep({
4429
4045
  message: "\uD14C\uB9C8 \uC124\uC815 \uC644\uB8CC!",
4430
4046
  command: "vhk ref list",
@@ -4433,11 +4049,11 @@ async function theme() {
4433
4049
  }
4434
4050
 
4435
4051
  // src/commands/ref.ts
4436
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
4437
- import chalk21 from "chalk";
4052
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
4053
+ import chalk20 from "chalk";
4438
4054
  var REFS_PATH = ".vhk/refs.json";
4439
4055
  function loadRefs() {
4440
- if (!existsSync8(REFS_PATH)) return [];
4056
+ if (!existsSync7(REFS_PATH)) return [];
4441
4057
  try {
4442
4058
  const parsed = readJsonFile(REFS_PATH);
4443
4059
  return Array.isArray(parsed) ? parsed : [];
@@ -4450,24 +4066,24 @@ function saveRefs(refs) {
4450
4066
  writeFileSync6(REFS_PATH, JSON.stringify(refs, null, 2) + "\n", "utf-8");
4451
4067
  }
4452
4068
  async function refAdd(url, memo = "") {
4453
- console.log(chalk21.bold("\n\u{1F517} " + t("ref.addTitle")));
4454
- console.log(chalk21.gray("\u2500".repeat(40)));
4069
+ console.log(chalk20.bold("\n\u{1F517} " + t("ref.addTitle")));
4070
+ console.log(chalk20.gray("\u2500".repeat(40)));
4455
4071
  if (!url) {
4456
- console.log(chalk21.red("\u274C URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4457
- console.log(chalk21.gray(' \uC608: vhk ref add https://example.com --memo "\uCC38\uACE0 \uC0AC\uC774\uD2B8"'));
4072
+ console.log(chalk20.red("\u274C URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4073
+ console.log(chalk20.gray(' \uC608: vhk ref add https://example.com --memo "\uCC38\uACE0 \uC0AC\uC774\uD2B8"'));
4458
4074
  return;
4459
4075
  }
4460
4076
  const refs = loadRefs();
4461
4077
  if (refs.some((r) => r.url === url)) {
4462
- console.log(chalk21.yellow("\u26A0\uFE0F \uC774\uBBF8 \uC800\uC7A5\uB41C URL\uC785\uB2C8\uB2E4."));
4078
+ console.log(chalk20.yellow("\u26A0\uFE0F \uC774\uBBF8 \uC800\uC7A5\uB41C URL\uC785\uB2C8\uB2E4."));
4463
4079
  return;
4464
4080
  }
4465
4081
  refs.push({ url, memo, addedAt: (/* @__PURE__ */ new Date()).toISOString() });
4466
4082
  saveRefs(refs);
4467
- console.log(chalk21.green(`
4083
+ console.log(chalk20.green(`
4468
4084
  \u2705 \uB808\uD37C\uB7F0\uC2A4 \uCD94\uAC00\uB428 (#${refs.length})`));
4469
- console.log(chalk21.cyan(` ${url}`));
4470
- if (memo) console.log(chalk21.gray(` \u{1F4DD} ${memo}`));
4085
+ console.log(chalk20.cyan(` ${url}`));
4086
+ if (memo) console.log(chalk20.gray(` \u{1F4DD} ${memo}`));
4471
4087
  printNextStep({
4472
4088
  message: "\uB808\uD37C\uB7F0\uC2A4 \uC800\uC7A5 \uC644\uB8CC!",
4473
4089
  command: "vhk ref list",
@@ -4475,22 +4091,22 @@ async function refAdd(url, memo = "") {
4475
4091
  });
4476
4092
  }
4477
4093
  async function refList() {
4478
- console.log(chalk21.bold("\n\u{1F4DA} " + t("ref.listTitle")));
4479
- console.log(chalk21.gray("\u2500".repeat(40)));
4094
+ console.log(chalk20.bold("\n\u{1F4DA} " + t("ref.listTitle")));
4095
+ console.log(chalk20.gray("\u2500".repeat(40)));
4480
4096
  const refs = loadRefs();
4481
4097
  if (refs.length === 0) {
4482
- console.log(chalk21.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uB808\uD37C\uB7F0\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4483
- console.log(chalk21.gray(' vhk ref add <url> --memo "\uBA54\uBAA8"\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
4098
+ console.log(chalk20.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uB808\uD37C\uB7F0\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4099
+ console.log(chalk20.gray(' vhk ref add <url> --memo "\uBA54\uBAA8"\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
4484
4100
  return;
4485
4101
  }
4486
- console.log(chalk21.cyan(`
4102
+ console.log(chalk20.cyan(`
4487
4103
  \uCD1D ${refs.length}\uAC1C\uC758 \uB808\uD37C\uB7F0\uC2A4:
4488
4104
  `));
4489
4105
  refs.forEach((ref, index) => {
4490
4106
  const date = new Date(ref.addedAt).toLocaleDateString("ko-KR");
4491
- console.log(chalk21.white(` [${index + 1}] ${ref.url}`));
4492
- if (ref.memo) console.log(chalk21.gray(` \u{1F4DD} ${ref.memo}`));
4493
- console.log(chalk21.gray(` \u{1F4C5} ${date}`));
4107
+ console.log(chalk20.white(` [${index + 1}] ${ref.url}`));
4108
+ if (ref.memo) console.log(chalk20.gray(` \u{1F4DD} ${ref.memo}`));
4109
+ console.log(chalk20.gray(` \u{1F4C5} ${date}`));
4494
4110
  console.log("");
4495
4111
  });
4496
4112
  }
@@ -4498,7 +4114,7 @@ async function refOpen(indexStr) {
4498
4114
  const refs = loadRefs();
4499
4115
  const idx = parseInt(indexStr, 10) - 1;
4500
4116
  if (Number.isNaN(idx) || idx < 0 || idx >= refs.length) {
4501
- console.log(chalk21.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${refs.length || 0})`));
4117
+ console.log(chalk20.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${refs.length || 0})`));
4502
4118
  return;
4503
4119
  }
4504
4120
  const ref = refs[idx];
@@ -4506,14 +4122,14 @@ async function refOpen(indexStr) {
4506
4122
  try {
4507
4123
  parsed = new URL(ref.url);
4508
4124
  } catch {
4509
- console.log(chalk21.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 URL: ${ref.url}`));
4125
+ console.log(chalk20.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 URL: ${ref.url}`));
4510
4126
  return;
4511
4127
  }
4512
4128
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
4513
- console.log(chalk21.red(`\u274C http(s) URL\uB9CC \uC5F4 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (${parsed.protocol})`));
4129
+ console.log(chalk20.red(`\u274C http(s) URL\uB9CC \uC5F4 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (${parsed.protocol})`));
4514
4130
  return;
4515
4131
  }
4516
- console.log(chalk21.cyan(`
4132
+ console.log(chalk20.cyan(`
4517
4133
  \u{1F310} \uC5F4\uAE30: ${ref.url}`));
4518
4134
  let result;
4519
4135
  if (process.platform === "darwin") {
@@ -4524,19 +4140,19 @@ async function refOpen(indexStr) {
4524
4140
  result = safeExecFile("xdg-open", [ref.url]);
4525
4141
  }
4526
4142
  if (result.ok) {
4527
- console.log(chalk21.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
4143
+ console.log(chalk20.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
4528
4144
  } else {
4529
- console.log(chalk21.yellow("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. URL\uC744 \uC9C1\uC811 \uBC29\uBB38\uD574\uC8FC\uC138\uC694."));
4145
+ console.log(chalk20.yellow("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. URL\uC744 \uC9C1\uC811 \uBC29\uBB38\uD574\uC8FC\uC138\uC694."));
4530
4146
  }
4531
4147
  }
4532
4148
 
4533
4149
  // src/commands/harness.ts
4534
- import { existsSync as existsSync9 } from "fs";
4535
- import chalk22 from "chalk";
4150
+ import { existsSync as existsSync8 } from "fs";
4151
+ import chalk21 from "chalk";
4536
4152
  import ora2 from "ora";
4537
4153
  function detectPM() {
4538
- if (existsSync9("pnpm-lock.yaml")) return "pnpm";
4539
- if (existsSync9("yarn.lock")) return "yarn";
4154
+ if (existsSync8("pnpm-lock.yaml")) return "pnpm";
4155
+ if (existsSync8("yarn.lock")) return "yarn";
4540
4156
  return "npm";
4541
4157
  }
4542
4158
  function pmRun(pm, script) {
@@ -4554,14 +4170,14 @@ function detectChecks() {
4554
4170
  const pm = detectPM();
4555
4171
  if (s.lint) {
4556
4172
  checks.push({ name: "lint", bin: pm, args: pmRun(pm, "lint") });
4557
- } else if (existsSync9(".eslintrc.js") || existsSync9(".eslintrc.json") || existsSync9("eslint.config.js")) {
4173
+ } else if (existsSync8(".eslintrc.js") || existsSync8(".eslintrc.json") || existsSync8("eslint.config.js")) {
4558
4174
  checks.push({ name: "lint", bin: "npx", args: ["eslint", ".", "--ext", ".ts,.tsx"] });
4559
4175
  }
4560
4176
  if (s["type-check"]) {
4561
4177
  checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "type-check") });
4562
4178
  } else if (s.typecheck) {
4563
4179
  checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "typecheck") });
4564
- } else if (existsSync9("tsconfig.json")) {
4180
+ } else if (existsSync8("tsconfig.json")) {
4565
4181
  checks.push({ name: "type-check", bin: "npx", args: ["tsc", "--noEmit"] });
4566
4182
  }
4567
4183
  if (s.test) {
@@ -4574,15 +4190,15 @@ function detectChecks() {
4574
4190
  }
4575
4191
  async function harness() {
4576
4192
  if (!ensureNotHardStopped("harness")) return;
4577
- console.log(chalk22.bold("\n\u{1F527} " + t("harness.title")));
4578
- console.log(chalk22.gray("\u2500".repeat(40)));
4193
+ console.log(chalk21.bold("\n\u{1F527} " + t("harness.title")));
4194
+ console.log(chalk21.gray("\u2500".repeat(40)));
4579
4195
  const checks = detectChecks();
4580
4196
  if (checks.length === 0) {
4581
- console.log(chalk22.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4582
- console.log(chalk22.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
4197
+ console.log(chalk21.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4198
+ console.log(chalk21.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
4583
4199
  return;
4584
4200
  }
4585
- console.log(chalk22.cyan(`
4201
+ console.log(chalk21.cyan(`
4586
4202
  \u{1F3C3} ${checks.length}\uAC1C \uC810\uAC80 \uC2DC\uC791:
4587
4203
  `));
4588
4204
  const results = [];
@@ -4594,10 +4210,10 @@ async function harness() {
4594
4210
  const duration = Date.now() - start2;
4595
4211
  const sec = (duration / 1e3).toFixed(1);
4596
4212
  if (result.ok) {
4597
- spinner.succeed(`${check2.name} ${chalk22.gray(`(${sec}s)`)}`);
4213
+ spinner.succeed(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
4598
4214
  results.push({ name: check2.name, command: display, passed: true, duration });
4599
4215
  } else {
4600
- spinner.fail(`${check2.name} ${chalk22.gray(`(${sec}s)`)}`);
4216
+ spinner.fail(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
4601
4217
  results.push({
4602
4218
  name: check2.name,
4603
4219
  command: display,
@@ -4607,22 +4223,22 @@ async function harness() {
4607
4223
  });
4608
4224
  }
4609
4225
  }
4610
- console.log(chalk22.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
4611
- console.log(chalk22.gray("\u2500".repeat(40)));
4226
+ console.log(chalk21.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
4227
+ console.log(chalk21.gray("\u2500".repeat(40)));
4612
4228
  for (const r of results) {
4613
- const icon = r.passed ? chalk22.green("\u2705") : chalk22.red("\u274C");
4229
+ const icon = r.passed ? chalk21.green("\u2705") : chalk21.red("\u274C");
4614
4230
  const sec = (r.duration / 1e3).toFixed(1);
4615
- console.log(` ${icon} ${r.name.padEnd(15)} ${chalk22.gray(`${sec}s`)}`);
4231
+ console.log(` ${icon} ${r.name.padEnd(15)} ${chalk21.gray(`${sec}s`)}`);
4616
4232
  }
4617
4233
  const passed = results.filter((r) => r.passed).length;
4618
4234
  const all = passed === results.length;
4619
- console.log(chalk22.gray("\u2500".repeat(40)));
4235
+ console.log(chalk21.gray("\u2500".repeat(40)));
4620
4236
  if (all) {
4621
- console.log(chalk22.green.bold(`
4237
+ console.log(chalk21.green.bold(`
4622
4238
  \u{1F389} \uC804\uCCB4 \uD1B5\uACFC! (${passed}/${results.length})`));
4623
4239
  } else {
4624
4240
  console.log(
4625
- chalk22.red.bold(`
4241
+ chalk21.red.bold(`
4626
4242
  \u26A0\uFE0F ${results.length - passed}\uAC1C \uC2E4\uD328 (${passed}/${results.length} \uD1B5\uACFC)`)
4627
4243
  );
4628
4244
  process.exitCode = 1;
@@ -4635,9 +4251,9 @@ async function harness() {
4635
4251
  }
4636
4252
 
4637
4253
  // src/commands/migrate.ts
4638
- import { existsSync as existsSync10, unlinkSync, rmSync as rmSync2 } from "fs";
4639
- import chalk23 from "chalk";
4640
- import inquirer11 from "inquirer";
4254
+ import { existsSync as existsSync9, unlinkSync, rmSync as rmSync2 } from "fs";
4255
+ import chalk22 from "chalk";
4256
+ import inquirer10 from "inquirer";
4641
4257
  import ora3 from "ora";
4642
4258
  var LOCK_FILES = {
4643
4259
  npm: "package-lock.json",
@@ -4645,26 +4261,26 @@ var LOCK_FILES = {
4645
4261
  pnpm: "pnpm-lock.yaml"
4646
4262
  };
4647
4263
  function detectCurrentPM() {
4648
- if (existsSync10("pnpm-lock.yaml")) return "pnpm";
4649
- if (existsSync10("yarn.lock")) return "yarn";
4650
- if (existsSync10("package-lock.json")) return "npm";
4264
+ if (existsSync9("pnpm-lock.yaml")) return "pnpm";
4265
+ if (existsSync9("yarn.lock")) return "yarn";
4266
+ if (existsSync9("package-lock.json")) return "npm";
4651
4267
  return null;
4652
4268
  }
4653
4269
  function isCLIAvailable(pm) {
4654
4270
  return safeExecFile(pm, ["--version"]).ok;
4655
4271
  }
4656
4272
  async function migrate(target) {
4657
- console.log(chalk23.bold("\n\u{1F504} " + t("migrate.title")));
4658
- console.log(chalk23.gray("\u2500".repeat(40)));
4273
+ console.log(chalk22.bold("\n\u{1F504} " + t("migrate.title")));
4274
+ console.log(chalk22.gray("\u2500".repeat(40)));
4659
4275
  const current = detectCurrentPM();
4660
- console.log(chalk23.cyan(`
4276
+ console.log(chalk22.cyan(`
4661
4277
  \uD604\uC7AC \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00"}`));
4662
4278
  let targetPM;
4663
4279
  if (target && ["npm", "yarn", "pnpm"].includes(target)) {
4664
4280
  targetPM = target;
4665
4281
  } else {
4666
4282
  const choices = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current).map((pm) => ({ name: pm, value: pm }));
4667
- const { selected } = await inquirer11.prompt([
4283
+ const { selected } = await inquirer10.prompt([
4668
4284
  {
4669
4285
  type: "list",
4670
4286
  name: "selected",
@@ -4675,17 +4291,17 @@ async function migrate(target) {
4675
4291
  targetPM = selected;
4676
4292
  }
4677
4293
  if (targetPM === current) {
4678
- console.log(chalk23.yellow(`
4294
+ console.log(chalk22.yellow(`
4679
4295
  \u26A0\uFE0F \uC774\uBBF8 ${targetPM}\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4.`));
4680
4296
  return;
4681
4297
  }
4682
4298
  if (!isCLIAvailable(targetPM)) {
4683
- console.log(chalk23.red(`
4299
+ console.log(chalk22.red(`
4684
4300
  \u274C ${targetPM}\uC774 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
4685
- console.log(chalk23.yellow(` npm i -g ${targetPM}`));
4301
+ console.log(chalk22.yellow(` npm i -g ${targetPM}`));
4686
4302
  return;
4687
4303
  }
4688
- const { confirm } = await inquirer11.prompt([
4304
+ const { confirm } = await inquirer10.prompt([
4689
4305
  {
4690
4306
  type: "confirm",
4691
4307
  name: "confirm",
@@ -4694,16 +4310,16 @@ async function migrate(target) {
4694
4310
  }
4695
4311
  ]);
4696
4312
  if (!confirm) {
4697
- console.log(chalk23.gray("\uCDE8\uC18C\uB428"));
4313
+ console.log(chalk22.gray("\uCDE8\uC18C\uB428"));
4698
4314
  return;
4699
4315
  }
4700
4316
  const cleanup = ora3("\uAE30\uC874 lock \uD30C\uC77C \uC815\uB9AC \uC911...").start();
4701
4317
  for (const lockFile of Object.values(LOCK_FILES)) {
4702
- if (existsSync10(lockFile)) {
4318
+ if (existsSync9(lockFile)) {
4703
4319
  unlinkSync(lockFile);
4704
4320
  }
4705
4321
  }
4706
- if (existsSync10("node_modules")) {
4322
+ if (existsSync9("node_modules")) {
4707
4323
  cleanup.text = "node_modules \uC0AD\uC81C \uC911...";
4708
4324
  rmSync2("node_modules", { recursive: true, force: true });
4709
4325
  }
@@ -4714,10 +4330,10 @@ async function migrate(target) {
4714
4330
  install.succeed(`${targetPM} install \uC644\uB8CC!`);
4715
4331
  } else {
4716
4332
  install.fail(`${targetPM} install \uC2E4\uD328`);
4717
- console.log(chalk23.red(installResult.err.slice(0, 300)));
4333
+ console.log(chalk22.red(installResult.err.slice(0, 300)));
4718
4334
  return;
4719
4335
  }
4720
- console.log(chalk23.green.bold(`
4336
+ console.log(chalk22.green.bold(`
4721
4337
  \u{1F389} ${current ?? "\uC774\uC804"} \u2192 ${targetPM} \uC804\uD658 \uC644\uB8CC!`));
4722
4338
  printNextStep({
4723
4339
  message: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 \uC644\uB8CC!",
@@ -4727,17 +4343,17 @@ async function migrate(target) {
4727
4343
  }
4728
4344
 
4729
4345
  // src/commands/update.ts
4730
- import { existsSync as existsSync11 } from "fs";
4731
- import { dirname as dirname2, join as join7 } from "path";
4346
+ import { existsSync as existsSync10 } from "fs";
4347
+ import { dirname as dirname2, join as join6 } from "path";
4732
4348
  import { fileURLToPath as fileURLToPath3 } from "url";
4733
- import chalk24 from "chalk";
4349
+ import chalk23 from "chalk";
4734
4350
  import ora4 from "ora";
4735
4351
  var PACKAGE = "@byh3071/vhk";
4736
4352
  function getCurrentVersion() {
4737
4353
  const dir = dirname2(fileURLToPath3(import.meta.url));
4738
- for (const pkgPath of [join7(dir, "../package.json"), join7(dir, "../../package.json")]) {
4354
+ for (const pkgPath of [join6(dir, "../package.json"), join6(dir, "../../package.json")]) {
4739
4355
  try {
4740
- if (existsSync11(pkgPath)) {
4356
+ if (existsSync10(pkgPath)) {
4741
4357
  const pkg = readJsonFile(pkgPath);
4742
4358
  if (pkg.version) return pkg.version;
4743
4359
  }
@@ -4760,32 +4376,32 @@ function isUpToDate(current, latest) {
4760
4376
  return cc >= lc;
4761
4377
  }
4762
4378
  async function update() {
4763
- console.log(chalk24.bold("\n\u2B06\uFE0F " + t("update.title")));
4764
- console.log(chalk24.gray("\u2500".repeat(40)));
4379
+ console.log(chalk23.bold("\n\u2B06\uFE0F " + t("update.title")));
4380
+ console.log(chalk23.gray("\u2500".repeat(40)));
4765
4381
  const current = getCurrentVersion();
4766
- console.log(chalk24.cyan(`
4382
+ console.log(chalk23.cyan(`
4767
4383
  \u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${current}`));
4768
4384
  const spinner = ora4("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...").start();
4769
4385
  const latest = getLatestVersion();
4770
4386
  if (!latest) {
4771
4387
  spinner.fail("\uCD5C\uC2E0 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4772
- console.log(chalk24.yellow(" \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uAC70\uB098 \uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4773
- console.log(chalk24.gray(` npm update -g ${PACKAGE}`));
4388
+ console.log(chalk23.yellow(" \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uAC70\uB098 \uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4389
+ console.log(chalk23.gray(` npm update -g ${PACKAGE}`));
4774
4390
  return;
4775
4391
  }
4776
4392
  spinner.stop();
4777
- console.log(chalk24.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
4393
+ console.log(chalk23.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
4778
4394
  if (isUpToDate(current, latest)) {
4779
- console.log(chalk24.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
4395
+ console.log(chalk23.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
4780
4396
  return;
4781
4397
  }
4782
4398
  const updateSpinner = ora4(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC911...`).start();
4783
4399
  const upd = safeExecFile("npm", ["update", "-g", PACKAGE]);
4784
4400
  if (upd.ok) {
4785
4401
  updateSpinner.succeed(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`);
4786
- console.log(chalk24.green.bold(`
4402
+ console.log(chalk23.green.bold(`
4787
4403
  \u{1F389} VHK CLI v${latest} \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
4788
- console.log(chalk24.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
4404
+ console.log(chalk23.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
4789
4405
  printNextStep({
4790
4406
  message: t("update.nextOkMessage"),
4791
4407
  command: "vhk --version",
@@ -4793,9 +4409,9 @@ async function update() {
4793
4409
  });
4794
4410
  } else {
4795
4411
  updateSpinner.fail("\uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
4796
- console.log(chalk24.red(upd.err.slice(0, 300)));
4797
- console.log(chalk24.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4798
- console.log(chalk24.gray(` npm update -g ${PACKAGE}`));
4412
+ console.log(chalk23.red(upd.err.slice(0, 300)));
4413
+ console.log(chalk23.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4414
+ console.log(chalk23.gray(` npm update -g ${PACKAGE}`));
4799
4415
  printNextStep({
4800
4416
  message: t("update.nextFailMessage"),
4801
4417
  command: "vhk doctor",
@@ -4806,16 +4422,16 @@ async function update() {
4806
4422
 
4807
4423
  // src/commands/context.ts
4808
4424
  import {
4809
- existsSync as existsSync12,
4425
+ existsSync as existsSync11,
4810
4426
  mkdirSync as mkdirSync7,
4811
- readFileSync as readFileSync5,
4427
+ readFileSync as readFileSync4,
4812
4428
  readdirSync as readdirSync2,
4813
4429
  statSync as statSync2,
4814
4430
  writeFileSync as writeFileSync7
4815
4431
  } from "fs";
4816
- import { join as join8 } from "path";
4817
- import chalk25 from "chalk";
4818
- var CONTEXT_PATH2 = ".vhk/context.md";
4432
+ import { join as join7 } from "path";
4433
+ import chalk24 from "chalk";
4434
+ var CONTEXT_PATH = ".vhk/context.md";
4819
4435
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
4820
4436
  "node_modules",
4821
4437
  ".git",
@@ -4839,7 +4455,7 @@ function buildTree(dir, prefix = "", maxDepth = 3, depth = 0) {
4839
4455
  filtered.forEach((entry, index) => {
4840
4456
  const isLast = index === filtered.length - 1;
4841
4457
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4842
- const fullPath = join8(dir, entry);
4458
+ const fullPath = join7(dir, entry);
4843
4459
  const stat = statSync2(fullPath);
4844
4460
  const isDir = stat.isDirectory();
4845
4461
  lines.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
@@ -4871,8 +4487,8 @@ function extractTechStack() {
4871
4487
  else if (all.jest) stack["\uD14C\uC2A4\uD2B8"] = "jest";
4872
4488
  if (all.commander) stack["CLI"] = "commander";
4873
4489
  if (all.inquirer) stack["\uC778\uD130\uB799\uD2F0\uBE0C"] = "inquirer";
4874
- if (existsSync12("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
4875
- else if (existsSync12("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
4490
+ if (existsSync11("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
4491
+ else if (existsSync11("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
4876
4492
  else stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "npm";
4877
4493
  if (pkg.name) stack["\uD328\uD0A4\uC9C0 \uC774\uB984"] = pkg.name;
4878
4494
  if (pkg.version) stack["\uBC84\uC804"] = pkg.version;
@@ -4915,8 +4531,8 @@ function getVhkCommands() {
4915
4531
  }
4916
4532
  async function context(opts = {}) {
4917
4533
  const compact = opts.compact === true;
4918
- console.log(chalk25.bold("\n\u{1F9E0} " + t("context.title")));
4919
- console.log(chalk25.gray("\u2500".repeat(40)));
4534
+ console.log(chalk24.bold("\n\u{1F9E0} " + t("context.title")));
4535
+ console.log(chalk24.gray("\u2500".repeat(40)));
4920
4536
  const stack = extractTechStack();
4921
4537
  const tree = buildTree(".", "", compact ? 2 : 3).join("\n");
4922
4538
  const commands = getVhkCommands();
@@ -4951,7 +4567,7 @@ async function context(opts = {}) {
4951
4567
  }
4952
4568
  lines.push("");
4953
4569
  }
4954
- if (existsSync12(".vhk/memory.json")) {
4570
+ if (existsSync11(".vhk/memory.json")) {
4955
4571
  try {
4956
4572
  const memories = readJsonFile(
4957
4573
  ".vhk/memory.json"
@@ -5030,11 +4646,11 @@ async function context(opts = {}) {
5030
4646
  }
5031
4647
  lines.push("");
5032
4648
  mkdirSync7(".vhk", { recursive: true });
5033
- writeFileSync7(CONTEXT_PATH2, lines.join("\n"), "utf-8");
5034
- console.log(chalk25.green(`
5035
- \u2705 ${CONTEXT_PATH2} \uC0DD\uC131 \uC644\uB8CC!`));
5036
- console.log(chalk25.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
5037
- console.log(chalk25.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
4649
+ writeFileSync7(CONTEXT_PATH, lines.join("\n"), "utf-8");
4650
+ console.log(chalk24.green(`
4651
+ \u2705 ${CONTEXT_PATH} \uC0DD\uC131 \uC644\uB8CC!`));
4652
+ console.log(chalk24.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
4653
+ console.log(chalk24.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
5038
4654
  printNextStep({
5039
4655
  message: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC!",
5040
4656
  command: "vhk context-show",
@@ -5042,23 +4658,23 @@ async function context(opts = {}) {
5042
4658
  });
5043
4659
  }
5044
4660
  async function contextShow() {
5045
- console.log(chalk25.bold("\n\u{1F4C4} " + t("context.showTitle")));
5046
- console.log(chalk25.gray("\u2500".repeat(40)));
5047
- if (!existsSync12(CONTEXT_PATH2)) {
5048
- console.log(chalk25.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
5049
- console.log(chalk25.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
4661
+ console.log(chalk24.bold("\n\u{1F4C4} " + t("context.showTitle")));
4662
+ console.log(chalk24.gray("\u2500".repeat(40)));
4663
+ if (!existsSync11(CONTEXT_PATH)) {
4664
+ console.log(chalk24.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4665
+ console.log(chalk24.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
5050
4666
  return;
5051
4667
  }
5052
- const content = readFileSync5(CONTEXT_PATH2, "utf-8");
4668
+ const content = readFileSync4(CONTEXT_PATH, "utf-8");
5053
4669
  console.log("\n" + content);
5054
4670
  }
5055
4671
 
5056
4672
  // src/commands/memory.ts
5057
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
5058
- import chalk26 from "chalk";
4673
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
4674
+ import chalk25 from "chalk";
5059
4675
  var MEMORY_PATH = ".vhk/memory.json";
5060
4676
  function loadMemories() {
5061
- if (!existsSync13(MEMORY_PATH)) return [];
4677
+ if (!existsSync12(MEMORY_PATH)) return [];
5062
4678
  try {
5063
4679
  const parsed = readJsonFile(MEMORY_PATH);
5064
4680
  return Array.isArray(parsed) ? parsed : [];
@@ -5071,11 +4687,11 @@ function saveMemories(memories) {
5071
4687
  writeFileSync8(MEMORY_PATH, JSON.stringify(memories, null, 2) + "\n", "utf-8");
5072
4688
  }
5073
4689
  async function memoryAdd(content, tags) {
5074
- console.log(chalk26.bold("\n\u{1F9E0} " + t("memory.addTitle")));
5075
- console.log(chalk26.gray("\u2500".repeat(40)));
4690
+ console.log(chalk25.bold("\n\u{1F9E0} " + t("memory.addTitle")));
4691
+ console.log(chalk25.gray("\u2500".repeat(40)));
5076
4692
  if (!content) {
5077
- console.log(chalk26.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
5078
- console.log(chalk26.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9\uD558\uAE30\uB85C \uACB0\uC815"'));
4693
+ console.log(chalk25.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4694
+ console.log(chalk25.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9\uD558\uAE30\uB85C \uACB0\uC815"'));
5079
4695
  process.exitCode = 1;
5080
4696
  return;
5081
4697
  }
@@ -5086,9 +4702,9 @@ async function memoryAdd(content, tags) {
5086
4702
  tags: tags && tags.length > 0 ? tags : []
5087
4703
  });
5088
4704
  saveMemories(memories);
5089
- console.log(chalk26.green(`
4705
+ console.log(chalk25.green(`
5090
4706
  \u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (#${memories.length})`));
5091
- console.log(chalk26.cyan(` \u{1F4DD} ${content}`));
4707
+ console.log(chalk25.cyan(` \u{1F4DD} ${content}`));
5092
4708
  printNextStep({
5093
4709
  message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!",
5094
4710
  command: "vhk memory list",
@@ -5096,24 +4712,24 @@ async function memoryAdd(content, tags) {
5096
4712
  });
5097
4713
  }
5098
4714
  async function memoryList() {
5099
- console.log(chalk26.bold("\n\u{1F9E0} " + t("memory.listTitle")));
5100
- console.log(chalk26.gray("\u2500".repeat(40)));
4715
+ console.log(chalk25.bold("\n\u{1F9E0} " + t("memory.listTitle")));
4716
+ console.log(chalk25.gray("\u2500".repeat(40)));
5101
4717
  const memories = loadMemories();
5102
4718
  if (memories.length === 0) {
5103
- console.log(chalk26.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
5104
- console.log(chalk26.gray(' vhk memory add "\uB0B4\uC6A9"\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
4719
+ console.log(chalk25.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4720
+ console.log(chalk25.gray(' vhk memory add "\uB0B4\uC6A9"\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
5105
4721
  return;
5106
4722
  }
5107
- console.log(chalk26.cyan(`
4723
+ console.log(chalk25.cyan(`
5108
4724
  \uCD1D ${memories.length}\uAC1C\uC758 \uAE30\uC5B5:
5109
4725
  `));
5110
4726
  memories.forEach((m, index) => {
5111
4727
  const date = new Date(m.addedAt).toLocaleDateString("ko-KR");
5112
- console.log(chalk26.white(` [${index + 1}] ${m.content}`));
4728
+ console.log(chalk25.white(` [${index + 1}] ${m.content}`));
5113
4729
  if (m.tags && m.tags.length > 0) {
5114
- console.log(chalk26.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
4730
+ console.log(chalk25.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
5115
4731
  }
5116
- console.log(chalk26.gray(` \u{1F4C5} ${date}`));
4732
+ console.log(chalk25.gray(` \u{1F4C5} ${date}`));
5117
4733
  console.log("");
5118
4734
  });
5119
4735
  }
@@ -5121,31 +4737,31 @@ async function memoryRemove(indexStr) {
5121
4737
  const memories = loadMemories();
5122
4738
  const idx = parseInt(indexStr, 10) - 1;
5123
4739
  if (Number.isNaN(idx) || idx < 0 || idx >= memories.length) {
5124
- console.log(chalk26.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${memories.length || 0})`));
4740
+ console.log(chalk25.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${memories.length || 0})`));
5125
4741
  return;
5126
4742
  }
5127
4743
  const removed = memories.splice(idx, 1)[0];
5128
4744
  saveMemories(memories);
5129
- console.log(chalk26.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
5130
- console.log(chalk26.gray(` ${removed.content}`));
4745
+ console.log(chalk25.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
4746
+ console.log(chalk25.gray(` ${removed.content}`));
5131
4747
  }
5132
4748
 
5133
4749
  // src/commands/brief.ts
5134
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync6 } from "fs";
5135
- import chalk27 from "chalk";
4750
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync5 } from "fs";
4751
+ import chalk26 from "chalk";
5136
4752
  var BRIEF_PATH = ".vhk/brief.md";
5137
4753
  function readProjectIdentity() {
5138
4754
  const out = {};
5139
4755
  try {
5140
- if (existsSync14("RULES.md")) {
5141
- const r = readFileSync6("RULES.md", "utf-8");
4756
+ if (existsSync13("RULES.md")) {
4757
+ const r = readFileSync5("RULES.md", "utf-8");
5142
4758
  const m = r.split("\n")[0].match(/^#\s*(.+?)(?:\s*—.*)?$/);
5143
4759
  if (m) out.name = m[1].trim();
5144
4760
  const d = r.match(/한 줄 설명:\s*(.+)/);
5145
4761
  if (d) out.description = d[1].trim();
5146
4762
  }
5147
- if (!out.name && existsSync14("CLAUDE.md")) {
5148
- const m = readFileSync6("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
4763
+ if (!out.name && existsSync13("CLAUDE.md")) {
4764
+ const m = readFileSync5("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
5149
4765
  if (m) out.name = m[1].trim();
5150
4766
  }
5151
4767
  } catch {
@@ -5157,8 +4773,8 @@ function git2(args) {
5157
4773
  return result.ok ? result.out : "";
5158
4774
  }
5159
4775
  async function brief() {
5160
- console.log(chalk27.bold("\n\u{1F4CB} " + t("brief.title")));
5161
- console.log(chalk27.gray("\u2500".repeat(40)));
4776
+ console.log(chalk26.bold("\n\u{1F4CB} " + t("brief.title")));
4777
+ console.log(chalk26.gray("\u2500".repeat(40)));
5162
4778
  const lines = [];
5163
4779
  lines.push("# \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551");
5164
4780
  lines.push("");
@@ -5195,7 +4811,7 @@ async function brief() {
5195
4811
  `- **\uBBF8\uCEE4\uBC0B \uBCC0\uACBD**: ${uncommitted ? `${uncommitted.split("\n").length}\uAC1C \uD30C\uC77C` : "\uC5C6\uC74C \u2705"}`
5196
4812
  );
5197
4813
  lines.push("");
5198
- if (existsSync14(".vhk/memory.json")) {
4814
+ if (existsSync13(".vhk/memory.json")) {
5199
4815
  try {
5200
4816
  const memories = readJsonFile(".vhk/memory.json");
5201
4817
  if (Array.isArray(memories) && memories.length > 0) {
@@ -5212,7 +4828,7 @@ async function brief() {
5212
4828
  } catch {
5213
4829
  }
5214
4830
  }
5215
- if (existsSync14(".vhk/refs.json")) {
4831
+ if (existsSync13(".vhk/refs.json")) {
5216
4832
  try {
5217
4833
  const refs = readJsonFile(".vhk/refs.json");
5218
4834
  if (Array.isArray(refs) && refs.length > 0) {
@@ -5243,7 +4859,7 @@ async function brief() {
5243
4859
  mkdirSync9(".vhk", { recursive: true });
5244
4860
  writeFileSync9(BRIEF_PATH, lines.join("\n"), "utf-8");
5245
4861
  console.log("\n" + lines.join("\n"));
5246
- console.log(chalk27.green(`
4862
+ console.log(chalk26.green(`
5247
4863
  \u2705 ${BRIEF_PATH} \uC800\uC7A5 \uC644\uB8CC`));
5248
4864
  printNextStep({
5249
4865
  message: "\uBE0C\uB9AC\uD551 \uC0DD\uC131 \uC644\uB8CC!",
@@ -5253,11 +4869,11 @@ async function brief() {
5253
4869
  }
5254
4870
 
5255
4871
  // src/commands/start.ts
5256
- import chalk28 from "chalk";
5257
- import inquirer12 from "inquirer";
4872
+ import chalk27 from "chalk";
4873
+ import inquirer11 from "inquirer";
5258
4874
  import { simpleGit as simpleGit2 } from "simple-git";
5259
- import { existsSync as existsSync15 } from "fs";
5260
- import { join as join9 } from "path";
4875
+ import { existsSync as existsSync14 } from "fs";
4876
+ import { join as join8 } from "path";
5261
4877
  var VHK_FOOTPRINT_FILES = [
5262
4878
  "CLAUDE.md",
5263
4879
  ".cursorrules",
@@ -5266,7 +4882,7 @@ var VHK_FOOTPRINT_FILES = [
5266
4882
  "docs/PRD.md"
5267
4883
  ];
5268
4884
  function detectExistingFootprint(cwd) {
5269
- return VHK_FOOTPRINT_FILES.filter((rel) => existsSync15(join9(cwd, rel)));
4885
+ return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join8(cwd, rel)));
5270
4886
  }
5271
4887
  async function runGitInit(cwd) {
5272
4888
  try {
@@ -5295,22 +4911,22 @@ async function runStep(label, fn) {
5295
4911
  }
5296
4912
  }
5297
4913
  async function start(options = {}) {
5298
- console.log(chalk28.bold(`
4914
+ console.log(chalk27.bold(`
5299
4915
  ${ko.start.title}
5300
4916
  `));
5301
- console.log(chalk28.dim(ko.start.intro));
5302
- console.log(chalk28.dim(` ${ko.start.step1}`));
5303
- console.log(chalk28.dim(` ${ko.start.step2}`));
5304
- console.log(chalk28.dim(` ${ko.start.step3}`));
5305
- console.log(chalk28.dim(` ${ko.start.step4}`));
4917
+ console.log(chalk27.dim(ko.start.intro));
4918
+ console.log(chalk27.dim(` ${ko.start.step1}`));
4919
+ console.log(chalk27.dim(` ${ko.start.step2}`));
4920
+ console.log(chalk27.dim(` ${ko.start.step3}`));
4921
+ console.log(chalk27.dim(` ${ko.start.step4}`));
5306
4922
  console.log();
5307
4923
  const cwd = process.cwd();
5308
4924
  const footprint = detectExistingFootprint(cwd);
5309
4925
  if (footprint.length > 0 && !options.yes) {
5310
- console.log(chalk28.yellow("\u26A0\uFE0F \uC774\uBBF8 VHK \uC124\uCE58 \uD754\uC801\uC774 \uAC10\uC9C0\uB410\uC5B4\uC694:"));
5311
- for (const f of footprint) console.log(chalk28.dim(` - ${f}`));
5312
- console.log(chalk28.dim(" \uACC4\uC18D \uC9C4\uD589\uD558\uBA74 \uC77C\uBD80 \uD30C\uC77C(`.cursor/mcp.json`, `.vhk/context.md`)\uC740 \uAC31\uC2E0\xB7\uB36E\uC5B4\uC4F0\uAE30\uB429\uB2C8\uB2E4."));
5313
- const { proceedExisting } = await inquirer12.prompt([{
4926
+ console.log(chalk27.yellow("\u26A0\uFE0F \uC774\uBBF8 VHK \uC124\uCE58 \uD754\uC801\uC774 \uAC10\uC9C0\uB410\uC5B4\uC694:"));
4927
+ for (const f of footprint) console.log(chalk27.dim(` - ${f}`));
4928
+ console.log(chalk27.dim(" \uACC4\uC18D \uC9C4\uD589\uD558\uBA74 \uC77C\uBD80 \uD30C\uC77C(`.cursor/mcp.json`, `.vhk/context.md`)\uC740 \uAC31\uC2E0\xB7\uB36E\uC5B4\uC4F0\uAE30\uB429\uB2C8\uB2E4."));
4929
+ const { proceedExisting } = await inquirer11.prompt([{
5314
4930
  type: "confirm",
5315
4931
  name: "proceedExisting",
5316
4932
  message: "\uADF8\uB798\uB3C4 \uB2E4\uC2DC \uB9C8\uBC95\uC0AC\uB97C \uC9C4\uD589\uD560\uAE4C\uC694?",
@@ -5321,7 +4937,7 @@ ${ko.start.title}
5321
4937
  return;
5322
4938
  }
5323
4939
  } else if (!options.yes) {
5324
- const { proceed } = await inquirer12.prompt([{
4940
+ const { proceed } = await inquirer11.prompt([{
5325
4941
  type: "confirm",
5326
4942
  name: "proceed",
5327
4943
  message: ko.start.confirmStart,
@@ -5347,7 +4963,7 @@ ${ko.start.title}
5347
4963
  await runStep("[3/4] vhk mcp-init", () => mcpInit());
5348
4964
  log.step(ko.start.step4Header);
5349
4965
  await runStep("[4/4] vhk context", () => context());
5350
- console.log(chalk28.bold.green(`
4966
+ console.log(chalk27.bold.green(`
5351
4967
  ${ko.start.allDone}
5352
4968
  `));
5353
4969
  printNextStep({
@@ -5357,15 +4973,15 @@ ${ko.start.allDone}
5357
4973
  }
5358
4974
 
5359
4975
  // src/commands/cloud.ts
5360
- import fs15 from "fs";
4976
+ import fs12 from "fs";
5361
4977
  import os from "os";
5362
- import path16 from "path";
5363
- import chalk29 from "chalk";
4978
+ import path13 from "path";
4979
+ import chalk28 from "chalk";
5364
4980
 
5365
4981
  // src/lib/vhk-cloud.ts
5366
4982
  var import_ignore = __toESM(require_ignore(), 1);
5367
- import fs14 from "fs";
5368
- import path15 from "path";
4983
+ import fs11 from "fs";
4984
+ import path12 from "path";
5369
4985
  var DEFAULT_CLOUD_EXCLUDES = [
5370
4986
  "memory.json",
5371
4987
  // 개인 의사결정 메모
@@ -5383,17 +4999,17 @@ var CLOUD_CONFIG_FILE = "cloud.json";
5383
4999
  function loadVhkignore(rootDir) {
5384
5000
  const ig = (0, import_ignore.default)();
5385
5001
  ig.add(DEFAULT_CLOUD_EXCLUDES);
5386
- const ignorePath = path15.join(rootDir, ".vhkignore");
5387
- if (fs14.existsSync(ignorePath)) {
5388
- ig.add(fs14.readFileSync(ignorePath, "utf-8"));
5002
+ const ignorePath = path12.join(rootDir, ".vhkignore");
5003
+ if (fs11.existsSync(ignorePath)) {
5004
+ ig.add(fs11.readFileSync(ignorePath, "utf-8"));
5389
5005
  }
5390
5006
  return ig;
5391
5007
  }
5392
5008
  function collectVhkFiles(rootDir, ig = loadVhkignore(rootDir)) {
5393
- const vhkDir = path15.join(rootDir, VHK_DIR2);
5009
+ const vhkDir = path12.join(rootDir, VHK_DIR2);
5394
5010
  let entries;
5395
5011
  try {
5396
- entries = fs14.readdirSync(vhkDir, { withFileTypes: true });
5012
+ entries = fs11.readdirSync(vhkDir, { withFileTypes: true });
5397
5013
  } catch {
5398
5014
  return [];
5399
5015
  }
@@ -5409,10 +5025,10 @@ function partitionGistFiles(gistFiles, ig) {
5409
5025
  return { keep, excluded };
5410
5026
  }
5411
5027
  function readCloudConfig(rootDir) {
5412
- const p = path15.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
5413
- if (!fs14.existsSync(p)) return null;
5028
+ const p = path12.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
5029
+ if (!fs11.existsSync(p)) return null;
5414
5030
  try {
5415
- const parsed = JSON.parse(fs14.readFileSync(p, "utf-8"));
5031
+ const parsed = JSON.parse(fs11.readFileSync(p, "utf-8"));
5416
5032
  if (parsed && typeof parsed.gistId === "string" && parsed.gistId) {
5417
5033
  return { gistId: parsed.gistId };
5418
5034
  }
@@ -5422,24 +5038,44 @@ function readCloudConfig(rootDir) {
5422
5038
  }
5423
5039
  }
5424
5040
  function writeCloudConfig(rootDir, config) {
5425
- const vhkDir = path15.join(rootDir, VHK_DIR2);
5426
- fs14.mkdirSync(vhkDir, { recursive: true });
5427
- const p = path15.join(vhkDir, CLOUD_CONFIG_FILE);
5428
- fs14.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", "utf-8");
5041
+ const vhkDir = path12.join(rootDir, VHK_DIR2);
5042
+ fs11.mkdirSync(vhkDir, { recursive: true });
5043
+ const p = path12.join(vhkDir, CLOUD_CONFIG_FILE);
5044
+ fs11.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", "utf-8");
5045
+ ensureCloudConfigIgnored(vhkDir);
5046
+ }
5047
+ function ensureCloudConfigIgnored(vhkDir) {
5048
+ const giPath = path12.join(vhkDir, ".gitignore");
5049
+ let content = "";
5050
+ try {
5051
+ if (fs11.existsSync(giPath)) content = fs11.readFileSync(giPath, "utf-8");
5052
+ } catch {
5053
+ return;
5054
+ }
5055
+ const already = content.split(/\r?\n/).some((l) => l.trim() === CLOUD_CONFIG_FILE);
5056
+ if (already) return;
5057
+ const block = `# secret gist \uD3EC\uC778\uD130 \u2014 \uCD94\uC801 \uAE08\uC9C0 (VHK-022)
5058
+ ${CLOUD_CONFIG_FILE}
5059
+ `;
5060
+ const base = content.length === 0 ? "" : content.endsWith("\n") ? content : content + "\n";
5061
+ try {
5062
+ fs11.writeFileSync(giPath, base + block, "utf-8");
5063
+ } catch {
5064
+ }
5429
5065
  }
5430
5066
 
5431
5067
  // src/commands/cloud.ts
5432
5068
  function ensureGhReady() {
5433
5069
  const ver = safeExecFile("gh", ["--version"]);
5434
5070
  if (!ver.ok) {
5435
- console.log(chalk29.red(` ${ko.cloud.noGh}`));
5436
- console.log(chalk29.dim(" \uC124\uCE58: https://cli.github.com/ (\uC124\uCE58 \uD6C4 `gh auth login`)"));
5071
+ console.log(chalk28.red(` ${ko.cloud.noGh}`));
5072
+ console.log(chalk28.dim(" \uC124\uCE58: https://cli.github.com/ (\uC124\uCE58 \uD6C4 `gh auth login`)"));
5437
5073
  return false;
5438
5074
  }
5439
5075
  const auth = safeExecFile("gh", ["auth", "status"]);
5440
5076
  if (!auth.ok) {
5441
- console.log(chalk29.red(` ${ko.cloud.noAuth}`));
5442
- console.log(chalk29.dim(" \uC2E4\uD589: gh auth login (gist \uAD8C\uD55C \uD544\uC694)"));
5077
+ console.log(chalk28.red(` ${ko.cloud.noAuth}`));
5078
+ console.log(chalk28.dim(" \uC2E4\uD589: gh auth login (gist \uAD8C\uD55C \uD544\uC694)"));
5443
5079
  return false;
5444
5080
  }
5445
5081
  return true;
@@ -5452,29 +5088,29 @@ function parseGistId(output) {
5452
5088
  return null;
5453
5089
  }
5454
5090
  async function cloudPush() {
5455
- console.log(chalk29.bold(`
5091
+ console.log(chalk28.bold(`
5456
5092
  ${ko.cloud.pushTitle}
5457
5093
  `));
5458
5094
  const cwd = process.cwd();
5459
- if (!fs15.existsSync(path16.join(cwd, VHK_DIR2))) {
5460
- console.log(chalk29.yellow(` ${ko.cloud.noVhkDir}`));
5095
+ if (!fs12.existsSync(path13.join(cwd, VHK_DIR2))) {
5096
+ console.log(chalk28.yellow(` ${ko.cloud.noVhkDir}`));
5461
5097
  return;
5462
5098
  }
5463
5099
  const ig = loadVhkignore(cwd);
5464
5100
  const files = collectVhkFiles(cwd, ig);
5465
5101
  if (files.length === 0) {
5466
- console.log(chalk29.yellow(` ${ko.cloud.nothingToSync}`));
5102
+ console.log(chalk28.yellow(` ${ko.cloud.nothingToSync}`));
5467
5103
  return;
5468
5104
  }
5469
5105
  if (!ensureGhReady()) {
5470
5106
  process.exitCode = 1;
5471
5107
  return;
5472
5108
  }
5473
- const filePaths = files.map((f) => path16.join(cwd, VHK_DIR2, f));
5474
- console.log(chalk29.dim(` \u{1F4E6} \uBC31\uC5C5 \uB300\uC0C1 ${files.length}\uAC1C: ${files.join(", ")}
5109
+ const filePaths = files.map((f) => path13.join(cwd, VHK_DIR2, f));
5110
+ console.log(chalk28.dim(` \u{1F4E6} \uBC31\uC5C5 \uB300\uC0C1 ${files.length}\uAC1C: ${files.join(", ")}
5475
5111
  `));
5476
5112
  const existing = readCloudConfig(cwd);
5477
- const desc = `vhk .vhk backup \u2014 ${path16.basename(cwd)}`;
5113
+ const desc = `vhk .vhk backup \u2014 ${path13.basename(cwd)}`;
5478
5114
  if (existing) {
5479
5115
  const gistFiles = listGistFiles(existing.gistId);
5480
5116
  for (let i = 0; i < files.length; i++) {
@@ -5483,8 +5119,8 @@ ${ko.cloud.pushTitle}
5483
5119
  const args = gistFiles.includes(name) ? ["gist", "edit", existing.gistId, "-f", name, src] : ["gist", "edit", existing.gistId, "-a", src];
5484
5120
  const res2 = safeExecFile("gh", args);
5485
5121
  if (!res2.ok) {
5486
- console.log(chalk29.red(` ${ko.cloud.pushFail}: ${name}`));
5487
- console.log(chalk29.dim(` ${res2.err}`));
5122
+ console.log(chalk28.red(` ${ko.cloud.pushFail}: ${name}`));
5123
+ console.log(chalk28.dim(` ${res2.err}`));
5488
5124
  process.exitCode = 1;
5489
5125
  return;
5490
5126
  }
@@ -5499,15 +5135,15 @@ ${ko.cloud.pushTitle}
5499
5135
  if (!purgeFailed.includes(name)) purgeFailed.push(name);
5500
5136
  }
5501
5137
  }
5502
- console.log(chalk29.green.bold(` ${ko.cloud.pushDone}`));
5503
- console.log(chalk29.dim(` gist: ${existing.gistId} (\uAC31\uC2E0)`));
5138
+ console.log(chalk28.green.bold(` ${ko.cloud.pushDone}`));
5139
+ console.log(chalk28.dim(` gist: ${existing.gistId} (\uAC31\uC2E0)`));
5504
5140
  if (excluded.length > 0) {
5505
5141
  const purged = excluded.filter((n) => !purgeFailed.includes(n));
5506
5142
  if (purged.length > 0) {
5507
- console.log(chalk29.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${purged.length}\uAC1C gist \uC5D0\uC11C \uC81C\uAC70: ${purged.join(", ")}`));
5143
+ console.log(chalk28.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${purged.length}\uAC1C gist \uC5D0\uC11C \uC81C\uAC70: ${purged.join(", ")}`));
5508
5144
  }
5509
5145
  if (purgeFailed.length > 0) {
5510
- console.log(chalk29.yellow(` \u26A0\uFE0F \uC81C\uC678 \uB300\uC0C1 \uC81C\uAC70 \uC2E4\uD328: ${purgeFailed.join(", ")} (\uC218\uB3D9 \uC81C\uAC70 \uAD8C\uC7A5 \u2014 pull \uC2DC\uC5D4 \uBCF5\uC6D0 \uC548 \uB428)`));
5146
+ console.log(chalk28.yellow(` \u26A0\uFE0F \uC81C\uC678 \uB300\uC0C1 \uC81C\uAC70 \uC2E4\uD328: ${purgeFailed.join(", ")} (\uC218\uB3D9 \uC81C\uAC70 \uAD8C\uC7A5 \u2014 pull \uC2DC\uC5D4 \uBCF5\uC6D0 \uC548 \uB428)`));
5511
5147
  }
5512
5148
  }
5513
5149
  printPushNext();
@@ -5515,32 +5151,32 @@ ${ko.cloud.pushTitle}
5515
5151
  }
5516
5152
  const res = safeExecFile("gh", ["gist", "create", "--desc", desc, ...filePaths]);
5517
5153
  if (!res.ok) {
5518
- console.log(chalk29.red(` ${ko.cloud.pushFail}`));
5519
- console.log(chalk29.dim(` ${res.err || res.out}`));
5154
+ console.log(chalk28.red(` ${ko.cloud.pushFail}`));
5155
+ console.log(chalk28.dim(` ${res.err || res.out}`));
5520
5156
  process.exitCode = 1;
5521
5157
  return;
5522
5158
  }
5523
5159
  const gistId = parseGistId(res.out);
5524
5160
  if (!gistId) {
5525
- console.log(chalk29.red(` ${ko.cloud.pushFail} \u2014 gist id \uD30C\uC2F1 \uC2E4\uD328`));
5526
- console.log(chalk29.dim(` \uCD9C\uB825: ${res.out}`));
5161
+ console.log(chalk28.red(` ${ko.cloud.pushFail} \u2014 gist id \uD30C\uC2F1 \uC2E4\uD328`));
5162
+ console.log(chalk28.dim(` \uCD9C\uB825: ${res.out}`));
5527
5163
  process.exitCode = 1;
5528
5164
  return;
5529
5165
  }
5530
5166
  writeCloudConfig(cwd, { gistId });
5531
- console.log(chalk29.green.bold(` ${ko.cloud.pushDone}`));
5532
- console.log(chalk29.dim(` gist: ${gistId} (\uC2E0\uADDC, secret) \u2192 .vhk/cloud.json \uC800\uC7A5`));
5167
+ console.log(chalk28.green.bold(` ${ko.cloud.pushDone}`));
5168
+ console.log(chalk28.dim(` gist: ${gistId} (\uC2E0\uADDC, secret) \u2192 .vhk/cloud.json \uC800\uC7A5`));
5533
5169
  printPushNext();
5534
5170
  }
5535
5171
  async function cloudPull(gistIdArg) {
5536
- console.log(chalk29.bold(`
5172
+ console.log(chalk28.bold(`
5537
5173
  ${ko.cloud.pullTitle}
5538
5174
  `));
5539
5175
  const cwd = process.cwd();
5540
5176
  const gistId = gistIdArg || readCloudConfig(cwd)?.gistId;
5541
5177
  if (!gistId) {
5542
- console.log(chalk29.yellow(` ${ko.cloud.noGistId}`));
5543
- console.log(chalk29.dim(" \uC0AC\uC6A9\uBC95: vhk cloud pull <gistId> (\uB610\uB294 cloud.json \uC774 \uC788\uB294 \uACF3\uC5D0\uC11C \uC2E4\uD589)"));
5178
+ console.log(chalk28.yellow(` ${ko.cloud.noGistId}`));
5179
+ console.log(chalk28.dim(" \uC0AC\uC6A9\uBC95: vhk cloud pull <gistId> (\uB610\uB294 cloud.json \uC774 \uC788\uB294 \uACF3\uC5D0\uC11C \uC2E4\uD589)"));
5544
5180
  return;
5545
5181
  }
5546
5182
  if (!ensureGhReady()) {
@@ -5549,34 +5185,34 @@ ${ko.cloud.pullTitle}
5549
5185
  }
5550
5186
  const allNames = listGistFiles(gistId);
5551
5187
  if (allNames.length === 0) {
5552
- console.log(chalk29.red(` ${ko.cloud.pullFail} \u2014 gist \uBE44\uC5C8\uAC70\uB098 \uC811\uADFC \uBD88\uAC00: ${gistId}`));
5188
+ console.log(chalk28.red(` ${ko.cloud.pullFail} \u2014 gist \uBE44\uC5C8\uAC70\uB098 \uC811\uADFC \uBD88\uAC00: ${gistId}`));
5553
5189
  process.exitCode = 1;
5554
5190
  return;
5555
5191
  }
5556
5192
  const { keep: names, excluded: skipped } = partitionGistFiles(allNames, loadVhkignore(cwd));
5557
5193
  if (skipped.length > 0) {
5558
- console.log(chalk29.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${skipped.length}\uAC1C \uBCF5\uC6D0 \uC2A4\uD0B5: ${skipped.join(", ")}`));
5194
+ console.log(chalk28.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${skipped.length}\uAC1C \uBCF5\uC6D0 \uC2A4\uD0B5: ${skipped.join(", ")}`));
5559
5195
  }
5560
5196
  if (names.length === 0) {
5561
- console.log(chalk29.yellow(` \uBCF5\uC6D0 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (gist \uD30C\uC77C\uC774 \uBAA8\uB450 \uC81C\uC678 \uADDC\uCE59\uC5D0 \uD574\uB2F9).`));
5197
+ console.log(chalk28.yellow(` \uBCF5\uC6D0 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (gist \uD30C\uC77C\uC774 \uBAA8\uB450 \uC81C\uC678 \uADDC\uCE59\uC5D0 \uD574\uB2F9).`));
5562
5198
  return;
5563
5199
  }
5564
- const vhkDir = path16.join(cwd, VHK_DIR2);
5565
- fs15.mkdirSync(vhkDir, { recursive: true });
5200
+ const vhkDir = path13.join(cwd, VHK_DIR2);
5201
+ fs12.mkdirSync(vhkDir, { recursive: true });
5566
5202
  let restored = 0;
5567
5203
  for (const name of names) {
5568
5204
  const res = safeExecFile("gh", ["gist", "view", gistId, "-f", name, "--raw"]);
5569
5205
  if (!res.ok) {
5570
- console.log(chalk29.red(` ${ko.cloud.pullFail}: ${name}`));
5571
- console.log(chalk29.dim(` ${res.err}`));
5206
+ console.log(chalk28.red(` ${ko.cloud.pullFail}: ${name}`));
5207
+ console.log(chalk28.dim(` ${res.err}`));
5572
5208
  continue;
5573
5209
  }
5574
- fs15.writeFileSync(path16.join(vhkDir, name), ensureTrailingNewline(res.out), "utf-8");
5210
+ fs12.writeFileSync(path13.join(vhkDir, name), ensureTrailingNewline(res.out), "utf-8");
5575
5211
  restored++;
5576
5212
  }
5577
5213
  writeCloudConfig(cwd, { gistId });
5578
- console.log(chalk29.green.bold(` ${ko.cloud.pullDone}`));
5579
- console.log(chalk29.dim(` ${restored}\uAC1C \uD30C\uC77C \uBCF5\uC6D0 (gist: ${gistId})`));
5214
+ console.log(chalk28.green.bold(` ${ko.cloud.pullDone}`));
5215
+ console.log(chalk28.dim(` ${restored}\uAC1C \uD30C\uC77C \uBCF5\uC6D0 (gist: ${gistId})`));
5580
5216
  printNextStep({
5581
5217
  message: "\uD074\uB77C\uC6B0\uB4DC\uC5D0\uC11C .vhk/ \uBCF5\uC6D0 \uC644\uB8CC!",
5582
5218
  command: "vhk \uB9E5\uB77D",
@@ -5588,9 +5224,9 @@ function purgeExcludedFromGist(gistId, names) {
5588
5224
  const body = JSON.stringify({
5589
5225
  files: Object.fromEntries(names.map((n) => [n, null]))
5590
5226
  });
5591
- const tmp = path16.join(os.tmpdir(), `vhk-gist-purge-${process.pid}.json`);
5227
+ const tmp = path13.join(os.tmpdir(), `vhk-gist-purge-${process.pid}.json`);
5592
5228
  try {
5593
- fs15.writeFileSync(tmp, body, "utf-8");
5229
+ fs12.writeFileSync(tmp, body, "utf-8");
5594
5230
  for (let attempt = 0; attempt < 2; attempt++) {
5595
5231
  const res = safeExecFile(
5596
5232
  "gh",
@@ -5602,7 +5238,7 @@ function purgeExcludedFromGist(gistId, names) {
5602
5238
  return false;
5603
5239
  } finally {
5604
5240
  try {
5605
- fs15.unlinkSync(tmp);
5241
+ fs12.unlinkSync(tmp);
5606
5242
  } catch {
5607
5243
  }
5608
5244
  }
@@ -5624,7 +5260,7 @@ function printPushNext() {
5624
5260
  }
5625
5261
 
5626
5262
  // src/commands/help.ts
5627
- import chalk30 from "chalk";
5263
+ import chalk29 from "chalk";
5628
5264
  var QUICK_ACTIONS = [
5629
5265
  { say: "\uC0C1\uD0DC \uC54C\uB824\uC918", does: "vhk status" },
5630
5266
  { say: "\uBB50 \uBC14\uB00C\uC5C8\uC5B4?", does: "vhk diff" },
@@ -5638,21 +5274,21 @@ var QUICK_ACTIONS = [
5638
5274
  { say: "\uC804\uCCB4 \uBA85\uB839\uC5B4 \uBCF4\uAE30", does: "vhk --help" }
5639
5275
  ];
5640
5276
  function quickActions() {
5641
- console.log(chalk30.bold("\n\u{1F9ED} VHK \u2014 \uC774\uB807\uAC8C \uB9D0\uD558\uBA74 \uB429\uB2C8\uB2E4 (quick actions)"));
5642
- console.log(chalk30.gray("\u2500".repeat(40)));
5277
+ console.log(chalk29.bold("\n\u{1F9ED} VHK \u2014 \uC774\uB807\uAC8C \uB9D0\uD558\uBA74 \uB429\uB2C8\uB2E4 (quick actions)"));
5278
+ console.log(chalk29.gray("\u2500".repeat(40)));
5643
5279
  for (const a of QUICK_ACTIONS) {
5644
- console.log(` "${chalk30.cyan(a.say)}" \u2192 ${chalk30.dim(a.does)}`);
5280
+ console.log(` "${chalk29.cyan(a.say)}" \u2192 ${chalk29.dim(a.does)}`);
5645
5281
  }
5646
- console.log(chalk30.gray("\n \uC804\uCCB4 \uBA85\uB839\uC740 `vhk --help` \uB610\uB294 COMMANDS.md \uB97C \uBCF4\uC138\uC694."));
5282
+ console.log(chalk29.gray("\n \uC804\uCCB4 \uBA85\uB839\uC740 `vhk --help` \uB610\uB294 COMMANDS.md \uB97C \uBCF4\uC138\uC694."));
5647
5283
  console.log("");
5648
5284
  }
5649
5285
 
5650
5286
  // src/commands/mode.ts
5651
- import chalk31 from "chalk";
5287
+ import chalk30 from "chalk";
5652
5288
 
5653
5289
  // src/lib/config.ts
5654
- import { existsSync as existsSync16, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
5655
- import { join as join10 } from "path";
5290
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
5291
+ import { join as join9 } from "path";
5656
5292
 
5657
5293
  // src/lib/safety-mode.ts
5658
5294
  var SAFETY_MODES = ["lite", "standard", "strict"];
@@ -5668,11 +5304,11 @@ function isSafetyMode(value) {
5668
5304
 
5669
5305
  // src/lib/config.ts
5670
5306
  var CONFIG_DIR = ".vhk";
5671
- var CONFIG_PATH = join10(CONFIG_DIR, "config.json");
5307
+ var CONFIG_PATH = join9(CONFIG_DIR, "config.json");
5672
5308
  var DEFAULT_CONFIG = { safetyMode: DEFAULT_SAFETY_MODE };
5673
5309
  function readConfig(rootDir = process.cwd()) {
5674
- const full = join10(rootDir, CONFIG_PATH);
5675
- if (!existsSync16(full)) return { ...DEFAULT_CONFIG };
5310
+ const full = join9(rootDir, CONFIG_PATH);
5311
+ if (!existsSync15(full)) return { ...DEFAULT_CONFIG };
5676
5312
  try {
5677
5313
  const raw = readJsonFile(full);
5678
5314
  return {
@@ -5683,23 +5319,23 @@ function readConfig(rootDir = process.cwd()) {
5683
5319
  }
5684
5320
  }
5685
5321
  function writeConfig(config, rootDir = process.cwd()) {
5686
- mkdirSync10(join10(rootDir, CONFIG_DIR), { recursive: true });
5687
- writeFileSync10(join10(rootDir, CONFIG_PATH), JSON.stringify(config, null, 2) + "\n", "utf-8");
5322
+ mkdirSync10(join9(rootDir, CONFIG_DIR), { recursive: true });
5323
+ writeFileSync10(join9(rootDir, CONFIG_PATH), JSON.stringify(config, null, 2) + "\n", "utf-8");
5688
5324
  }
5689
5325
 
5690
5326
  // src/commands/mode.ts
5691
5327
  async function mode(target) {
5692
- console.log(chalk31.bold("\n\u{1F6E1}\uFE0F Safety Mode"));
5693
- console.log(chalk31.gray("\u2500".repeat(40)));
5328
+ console.log(chalk30.bold("\n\u{1F6E1}\uFE0F Safety Mode"));
5329
+ console.log(chalk30.gray("\u2500".repeat(40)));
5694
5330
  const current = readConfig().safetyMode;
5695
5331
  if (!target) {
5696
- console.log(chalk31.cyan(`
5697
- \uD604\uC7AC \uBAA8\uB4DC: ${chalk31.bold(current)}`));
5698
- console.log(chalk31.dim(` ${SAFETY_MODE_DESC[current]}`));
5332
+ console.log(chalk30.cyan(`
5333
+ \uD604\uC7AC \uBAA8\uB4DC: ${chalk30.bold(current)}`));
5334
+ console.log(chalk30.dim(` ${SAFETY_MODE_DESC[current]}`));
5699
5335
  console.log("");
5700
5336
  for (const m of SAFETY_MODES) {
5701
5337
  const mark = m === current ? "\u25CF" : "\u25CB";
5702
- console.log(` ${mark} ${m.padEnd(9)} ${chalk31.dim(SAFETY_MODE_DESC[m])}`);
5338
+ console.log(` ${mark} ${m.padEnd(9)} ${chalk30.dim(SAFETY_MODE_DESC[m])}`);
5703
5339
  }
5704
5340
  printNextStep({
5705
5341
  message: "\uBAA8\uB4DC\uB97C \uBC14\uAFB8\uB824\uBA74:",
@@ -5709,20 +5345,20 @@ async function mode(target) {
5709
5345
  return;
5710
5346
  }
5711
5347
  if (!isSafetyMode(target)) {
5712
- console.log(chalk31.red(`
5348
+ console.log(chalk30.red(`
5713
5349
  \u274C \uC54C \uC218 \uC5C6\uB294 \uBAA8\uB4DC: ${target}`));
5714
- console.log(chalk31.dim(` \uAC00\uB2A5: ${SAFETY_MODES.join(" | ")}`));
5350
+ console.log(chalk30.dim(` \uAC00\uB2A5: ${SAFETY_MODES.join(" | ")}`));
5715
5351
  process.exitCode = 1;
5716
5352
  return;
5717
5353
  }
5718
5354
  writeConfig({ ...readConfig(), safetyMode: target });
5719
- console.log(chalk31.green(`
5720
- \u2705 Safety Mode \u2192 ${chalk31.bold(target)}`));
5721
- console.log(chalk31.dim(` ${SAFETY_MODE_DESC[target]}`));
5355
+ console.log(chalk30.green(`
5356
+ \u2705 Safety Mode \u2192 ${chalk30.bold(target)}`));
5357
+ console.log(chalk30.dim(` ${SAFETY_MODE_DESC[target]}`));
5722
5358
  }
5723
5359
 
5724
5360
  // src/commands/verify.ts
5725
- import chalk32 from "chalk";
5361
+ import chalk31 from "chalk";
5726
5362
  function verificationChecklist() {
5727
5363
  return [
5728
5364
  "\uD0C0\uC785 \uCCB4\uD06C \u2014 pnpm exec tsc --noEmit",
@@ -5732,15 +5368,15 @@ function verificationChecklist() {
5732
5368
  ];
5733
5369
  }
5734
5370
  async function verify() {
5735
- console.log(chalk32.bold("\n\u{1F50E} \uAC80\uC99D \uBB36\uC74C (verify \u2014 lite)"));
5736
- console.log(chalk32.gray("\u2500".repeat(40)));
5371
+ console.log(chalk31.bold("\n\u{1F50E} \uAC80\uC99D \uBB36\uC74C (verify \u2014 lite)"));
5372
+ console.log(chalk31.gray("\u2500".repeat(40)));
5737
5373
  const mode2 = readConfig().safetyMode;
5738
- console.log(chalk32.dim(` \uD604\uC7AC Safety Mode: ${mode2} \u2014 ${SAFETY_MODE_DESC[mode2]}`));
5739
- console.log(chalk32.cyan("\n \uC704\uD5D8 \uC791\uC5C5/\uC800\uC7A5 \uC804 \uAD8C\uC7A5 \uAC80\uC99D:"));
5374
+ console.log(chalk31.dim(` \uD604\uC7AC Safety Mode: ${mode2} \u2014 ${SAFETY_MODE_DESC[mode2]}`));
5375
+ console.log(chalk31.cyan("\n \uC704\uD5D8 \uC791\uC5C5/\uC800\uC7A5 \uC804 \uAD8C\uC7A5 \uAC80\uC99D:"));
5740
5376
  for (const item of verificationChecklist()) {
5741
5377
  console.log(` \u2610 ${item}`);
5742
5378
  }
5743
- console.log(chalk32.dim("\n \u203B \uBA54\uD0C0\uB7EC\uB108(\uC790\uB3D9 \uC2E4\uD589) \uC790\uB9AC \u2014 \uD604\uC7AC\uB294 \uBB36\uC74C \uC548\uB0B4\uB9CC(lite)."));
5379
+ console.log(chalk31.dim("\n \u203B \uBA54\uD0C0\uB7EC\uB108(\uC790\uB3D9 \uC2E4\uD589) \uC790\uB9AC \u2014 \uD604\uC7AC\uB294 \uBB36\uC74C \uC548\uB0B4\uB9CC(lite)."));
5744
5380
  printNextStep({
5745
5381
  message: "\uAC80\uC99D \uD1B5\uACFC \uD6C4 \uC800\uC7A5\uD558\uC138\uC694:",
5746
5382
  command: "vhk save",
@@ -5894,6 +5530,10 @@ async function dispatchNlpRoute(route, input) {
5894
5530
  if (sub === "next") return goalNext();
5895
5531
  if (sub === "check") return goalCheck({});
5896
5532
  if (sub === "done") return goalDone({});
5533
+ if (sub === "sync") {
5534
+ await goalSync();
5535
+ return;
5536
+ }
5897
5537
  return goalList();
5898
5538
  }
5899
5539
  case "help":
@@ -5909,28 +5549,29 @@ var STATE_CHANGING_COMMANDS = /* @__PURE__ */ new Set([
5909
5549
  "init"
5910
5550
  ]);
5911
5551
  function requiresConfirmation(route) {
5912
- return route.confidence === "low" || STATE_CHANGING_COMMANDS.has(route.command);
5552
+ const goalSync2 = route.command === "goal" && route.args?.[0] === "sync";
5553
+ return route.confidence === "low" || STATE_CHANGING_COMMANDS.has(route.command) || goalSync2;
5913
5554
  }
5914
5555
  async function runNaturalLanguageRoute(input) {
5915
5556
  const route = routeNaturalLanguage(input);
5916
5557
  if (!route) {
5917
- console.log(chalk33.yellow(`
5558
+ console.log(chalk32.yellow(`
5918
5559
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
5919
5560
  `));
5920
5561
  return;
5921
5562
  }
5922
5563
  console.log("");
5923
- console.log(chalk33.cyan(` \u{1F4AC} "${input}"`));
5924
- console.log(chalk33.cyan(` \u2192 ${route.explanation}`));
5564
+ console.log(chalk32.cyan(` \u{1F4AC} "${input}"`));
5565
+ console.log(chalk32.cyan(` \u2192 ${route.explanation}`));
5925
5566
  if (requiresConfirmation(route)) {
5926
- const { confirm } = await inquirer13.prompt([{
5567
+ const { confirm } = await inquirer12.prompt([{
5927
5568
  type: "confirm",
5928
5569
  name: "confirm",
5929
5570
  message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
5930
5571
  default: true
5931
5572
  }]);
5932
5573
  if (!confirm) {
5933
- console.log(chalk33.dim(` ${ko.nlp.menuHint}`));
5574
+ console.log(chalk32.dim(` ${ko.nlp.menuHint}`));
5934
5575
  return;
5935
5576
  }
5936
5577
  }
@@ -5939,7 +5580,7 @@ async function runNaturalLanguageRoute(input) {
5939
5580
  if (riskAction) {
5940
5581
  await runGuarded(
5941
5582
  riskAction,
5942
- { channel: "nl", approved: false, log: (m) => console.log(chalk33.yellow(` ${m}`)) },
5583
+ { channel: "nl", approved: false, log: (m) => console.log(chalk32.yellow(` ${m}`)) },
5943
5584
  () => dispatchNlpRoute(route, input)
5944
5585
  );
5945
5586
  return;
@@ -5948,77 +5589,77 @@ async function runNaturalLanguageRoute(input) {
5948
5589
  }
5949
5590
 
5950
5591
  // src/commands/agent.ts
5951
- import chalk34 from "chalk";
5592
+ import chalk33 from "chalk";
5952
5593
  function activeGoalId() {
5953
5594
  const goals = listGoals("goals");
5954
5595
  const id = selectActiveId(goals);
5955
5596
  return id ?? void 0;
5956
5597
  }
5957
5598
  async function blocker(description) {
5958
- console.log(chalk34.bold(`
5599
+ console.log(chalk33.bold(`
5959
5600
  ${ko.agent.blockerTitle}
5960
5601
  `));
5961
5602
  if (!description || !description.trim()) {
5962
- console.log(chalk34.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
5963
- console.log(chalk34.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
5603
+ console.log(chalk33.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
5604
+ console.log(chalk33.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
5964
5605
  process.exitCode = 1;
5965
5606
  return;
5966
5607
  }
5967
5608
  const goalId = activeGoalId();
5968
5609
  const r = appendBlocker(description, goalId);
5969
- console.log(chalk34.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
5610
+ console.log(chalk33.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
5970
5611
  if (r.hardStopTripped) {
5971
- console.log(chalk34.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
5972
- console.log(chalk34.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
5612
+ console.log(chalk33.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
5613
+ console.log(chalk33.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
5973
5614
  process.exitCode = 2;
5974
5615
  }
5975
5616
  }
5976
5617
  async function learn(lesson) {
5977
- console.log(chalk34.bold(`
5618
+ console.log(chalk33.bold(`
5978
5619
  ${ko.agent.learnTitle}
5979
5620
  `));
5980
5621
  if (!lesson || !lesson.trim()) {
5981
- console.log(chalk34.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
5982
- console.log(chalk34.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
5622
+ console.log(chalk33.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
5623
+ console.log(chalk33.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
5983
5624
  process.exitCode = 1;
5984
5625
  return;
5985
5626
  }
5986
5627
  const goalId = activeGoalId();
5987
5628
  appendLearning(lesson, goalId);
5988
- console.log(chalk34.green(" \u2705 learnings.md append."));
5629
+ console.log(chalk33.green(" \u2705 learnings.md append."));
5989
5630
  console.log(
5990
- chalk34.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
5631
+ chalk33.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
5991
5632
  );
5992
5633
  }
5993
5634
  async function resume(opts = {}) {
5994
- console.log(chalk34.bold(`
5635
+ console.log(chalk33.bold(`
5995
5636
  ${ko.agent.resumeTitle}
5996
5637
  `));
5997
5638
  if (!isHardStopActive()) {
5998
- console.log(chalk34.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
5639
+ console.log(chalk33.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
5999
5640
  return;
6000
5641
  }
6001
5642
  const reason = readHardStopReason();
6002
5643
  if (reason) {
6003
- console.log(chalk34.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
6004
- console.log(chalk34.dim(` ${reason.split("\n").join("\n ")}`));
5644
+ console.log(chalk33.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
5645
+ console.log(chalk33.dim(` ${reason.split("\n").join("\n ")}`));
6005
5646
  console.log("");
6006
5647
  }
6007
5648
  if (!opts.confirm) {
6008
5649
  console.log(
6009
- chalk34.red(
5650
+ chalk33.red(
6010
5651
  " \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
6011
5652
  )
6012
5653
  );
6013
- console.log(chalk34.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
5654
+ console.log(chalk33.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
6014
5655
  process.exitCode = 1;
6015
5656
  return;
6016
5657
  }
6017
5658
  const removed = clearHardStop();
6018
5659
  if (removed) {
6019
- console.log(chalk34.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
5660
+ console.log(chalk33.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
6020
5661
  } else {
6021
- console.log(chalk34.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
5662
+ console.log(chalk33.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
6022
5663
  }
6023
5664
  }
6024
5665
 
@@ -6031,7 +5672,7 @@ async function guardCli(action, approved, run) {
6031
5672
  channel: "cli",
6032
5673
  approved,
6033
5674
  confirm: async () => {
6034
- const { ok } = await inquirer14.prompt([{
5675
+ const { ok } = await inquirer13.prompt([{
6035
5676
  type: "confirm",
6036
5677
  name: "ok",
6037
5678
  message: `\u26A0\uFE0F \uC704\uD5D8 \uC791\uC5C5(${action})\uC744 \uC2E4\uD589\uD560\uAE4C\uC694?`,
@@ -6039,7 +5680,7 @@ async function guardCli(action, approved, run) {
6039
5680
  }]);
6040
5681
  return ok;
6041
5682
  },
6042
- log: (m) => console.log(chalk35.yellow(` ${m}`))
5683
+ log: (m) => console.log(chalk34.yellow(` ${m}`))
6043
5684
  },
6044
5685
  run
6045
5686
  );
@@ -6052,7 +5693,7 @@ async function guardCliDefer(action, approved, run) {
6052
5693
  approved,
6053
5694
  // TTY 면 통과(명령이 자체 확인), 비대화형은 confirm 불가 → 가드가 차단.
6054
5695
  confirm: async () => !!process.stdout.isTTY,
6055
- log: (m) => console.log(chalk35.yellow(` ${m}`))
5696
+ log: (m) => console.log(chalk34.yellow(` ${m}`))
6056
5697
  },
6057
5698
  run
6058
5699
  );
@@ -6234,7 +5875,7 @@ memoryCmd.command("remove <index>").alias("\uC0AD\uC81C").description("\uAE30\uC
6234
5875
  program.command("brief").alias("\uBE0C\uB9AC\uD551").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC694\uC57D \uBCF4\uACE0\uC11C \uC0DD\uC131 (.vhk/brief.md)").action(async () => {
6235
5876
  await brief();
6236
5877
  });
6237
- var goalCmd = program.command("goal").alias("\uBAA9\uD45C").description("Goal \uB2E8\uACC4\uBCC4 \uBBF8\uC158 \uAD00\uB9AC (init / list / next / check / done)").action(async () => {
5878
+ var goalCmd = program.command("goal").alias("\uBAA9\uD45C").description("Goal \uB2E8\uACC4\uBCC4 \uBBF8\uC158 \uAD00\uB9AC (init / list / next / check / done / sync)").action(async () => {
6238
5879
  await goalList();
6239
5880
  });
6240
5881
  goalCmd.command("list").alias("\uBAA9\uB85D").description("goals/*.md \uBAA9\uB85D (id, status, priority, title)").action(async () => {
@@ -6246,12 +5887,15 @@ goalCmd.command("next").alias("\uB2E4\uC74C").description("active goal \uC790\uB
6246
5887
  goalCmd.command("init").alias("\uCD08\uAE30\uD654").description("\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uC5D0 goals/ + docs/state/ \uC2A4\uCE90\uD3F4\uB529 (\uAE30\uC874 \uD30C\uC77C \uBCF4\uC874)").action(async () => {
6247
5888
  await goalInit();
6248
5889
  });
6249
- goalCmd.command("check").alias("\uAC80\uC99D").option("--id <id>", "goal id \uC9C0\uC815 (\uC0DD\uB7B5 \uC2DC active goal)").description("scripts/check-goal-<id>.sh \uC2E4\uD589 + exit code \uC804\uB2EC").action(async (opts) => {
5890
+ goalCmd.command("check").alias("\uAC80\uC99D").option("--id <id>", "goal id \uC9C0\uC815 (\uC0DD\uB7B5 \uC2DC active goal)").description("scripts/check-goal-<id>.{mjs,sh} \uC2E4\uD589 + exit code \uC804\uB2EC (.mjs \uC6B0\uC120)").action(async (opts) => {
6250
5891
  await goalCheck(opts);
6251
5892
  });
6252
5893
  goalCmd.command("done").alias("\uC644\uB8CC").option("--id <id>", "goal id \uC9C0\uC815 (\uC0DD\uB7B5 \uC2DC active goal)").description("\uAC8C\uC774\uD2B8 \uC7AC\uAC80\uC99D \u2192 \uD1B5\uACFC \uC2DC frontmatter status=DONE \uC73C\uB85C \uC804\uC774").action(async (opts) => {
6253
5894
  await goalDone(opts);
6254
5895
  });
5896
+ goalCmd.command("sync").alias("\uB3D9\uAE30\uD654").description("goals/*.md \uC2A4\uCE94 \u2192 \uB204\uB77D\uB41C check-goal-<id>.mjs \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uBC31\uD544 (idempotent)").action(async () => {
5897
+ await goalSync();
5898
+ });
6255
5899
  program.command("blocker <description>").alias("\uBE14\uB85C\uCEE4").description("\uBE14\uB85C\uCEE4 \uAE30\uB85D \u2192 docs/state/blockers.md append (3\uAC74 \uB204\uC801 \uC2DC HARD_STOP \uC790\uB3D9 \uC0DD\uC131)").action(async (description) => {
6256
5900
  await blocker(description);
6257
5901
  });
@@ -6269,7 +5913,7 @@ program.on("command:*", async (operands) => {
6269
5913
  });
6270
5914
  program.action(async () => {
6271
5915
  console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
6272
- const { choice } = await inquirer14.prompt([{
5916
+ const { choice } = await inquirer13.prompt([{
6273
5917
  type: "list",
6274
5918
  name: "choice",
6275
5919
  message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
@@ -6326,9 +5970,9 @@ if (isMainModule) {
6326
5970
  }
6327
5971
  } catch (err) {
6328
5972
  if (isPromptAbortError(err)) {
6329
- console.error(chalk35.yellow("\n \u26A0\uFE0F \uB300\uD654\uD615 \uC785\uB825\uC774 \uCDE8\uC18C/\uC885\uB8CC\uB410\uC2B5\uB2C8\uB2E4. (\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C\uB294 \uD574\uB2F9 \uBA85\uB839\uC744 \uC4F8 \uC218 \uC5C6\uC5B4\uC694)"));
5973
+ console.error(chalk34.yellow("\n \u26A0\uFE0F \uB300\uD654\uD615 \uC785\uB825\uC774 \uCDE8\uC18C/\uC885\uB8CC\uB410\uC2B5\uB2C8\uB2E4. (\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C\uB294 \uD574\uB2F9 \uBA85\uB839\uC744 \uC4F8 \uC218 \uC5C6\uC5B4\uC694)"));
6330
5974
  } else {
6331
- console.error(chalk35.red(`
5975
+ console.error(chalk34.red(`
6332
5976
  \u274C ${err instanceof Error ? err.message : String(err)}`));
6333
5977
  }
6334
5978
  process.exitCode = 1;