@byh3071/vhk 1.6.2 → 1.6.4

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,50 @@
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
+ stripBom,
39
+ sync,
23
40
  t
24
- } from "./chunk-ACJN723Q.js";
41
+ } from "./chunk-EJTVXWUZ.js";
25
42
 
26
43
  // src/index.ts
27
44
  import { Command, Help } from "commander";
28
45
  import { pathToFileURL } from "url";
29
- import chalk35 from "chalk";
30
- import inquirer14 from "inquirer";
46
+ import chalk34 from "chalk";
47
+ import inquirer13 from "inquirer";
31
48
 
32
49
  // src/lib/nlp-router.ts
33
50
  function normalize(input) {
@@ -289,7 +306,8 @@ var RULES = [
289
306
  explanation: "\uBAA9\uD45C \uAC8C\uC774\uD2B8 \uAC80\uC99D (vhk goal check)",
290
307
  confidence: "high",
291
308
  args: ["check"],
292
- test: (t2) => /목표\s*(점검|검증|체크)/.test(t2)
309
+ // '스크립트' 포함 시는 sync 의도(게이트 스크립트 생성) check 가 가로채지 않게 제외.
310
+ test: (t2) => /목표\s*(점검|검증|체크)/.test(t2) && !/스크립트/.test(t2)
293
311
  },
294
312
  {
295
313
  command: "goal",
@@ -304,6 +322,13 @@ var RULES = [
304
322
  confidence: "high",
305
323
  args: ["list"],
306
324
  test: (t2) => /목표\s*(목록|리스트)/.test(t2)
325
+ },
326
+ {
327
+ command: "goal",
328
+ explanation: "\uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uB3D9\uAE30\uD654 (vhk goal sync)",
329
+ confidence: "high",
330
+ args: ["sync"],
331
+ test: (t2) => /(게이트|목표).*(스크립트|동기화)|체크\s*스크립트\s*(생성|만들)/.test(t2)
307
332
  }
308
333
  ];
309
334
  function routeNaturalLanguage(input) {
@@ -328,7 +353,7 @@ function extractNotionUrl(input) {
328
353
 
329
354
  // src/lib/command-registry.ts
330
355
  var CONTAINER_SUBCOMMANDS = {
331
- goal: ["list", "next", "check", "init", "done"],
356
+ goal: ["list", "next", "check", "init", "done", "sync"],
332
357
  ref: ["add", "list", "open"],
333
358
  memory: ["add", "list", "remove"],
334
359
  cloud: ["push", "pull"],
@@ -475,12 +500,42 @@ function detectNaturalLanguageInput(argv) {
475
500
  }
476
501
 
477
502
  // src/lib/nlp-run.ts
478
- import chalk33 from "chalk";
479
- import inquirer13 from "inquirer";
503
+ import chalk32 from "chalk";
504
+ import inquirer12 from "inquirer";
480
505
 
481
506
  // src/commands/gate.ts
482
507
  import inquirer from "inquirer";
508
+ import chalk2 from "chalk";
509
+
510
+ // src/lib/interactive.ts
483
511
  import chalk from "chalk";
512
+ function isInteractive(opts) {
513
+ if (opts?.yes) return false;
514
+ if (process.env.VHK_FORCE_INTERACTIVE === "1") return true;
515
+ return !!process.stdin.isTTY;
516
+ }
517
+ async function promptOrDefault(ask, fallback, opts) {
518
+ if (!isInteractive(opts)) return fallback;
519
+ try {
520
+ return await ask();
521
+ } catch (err) {
522
+ if (isPromptAbortError(err)) return fallback;
523
+ throw err;
524
+ }
525
+ }
526
+ function ensureInteractive(hint = "") {
527
+ if (isInteractive()) return true;
528
+ console.error(chalk.yellow(" \u26A0\uFE0F \uC774 \uBA85\uB839\uC740 \uB300\uD654\uD615 \uC785\uB825\uC774 \uD544\uC694\uD569\uB2C8\uB2E4 \u2014 \uBE44-TTY/\uD30C\uC774\uD504 \uD658\uACBD\uC5D0\uC11C\uB294 \uC2E4\uD589\uD560 \uC218 \uC5C6\uC5B4\uC694."));
529
+ if (hint) console.error(chalk.dim(` ${hint}`));
530
+ process.exitCode = 1;
531
+ return false;
532
+ }
533
+ function isPromptAbortError(err) {
534
+ const msg = err instanceof Error ? err.message : String(err);
535
+ return /ERR_USE_AFTER_CLOSE|force closed|ExitPromptError|readline was closed|User force closed/i.test(msg);
536
+ }
537
+
538
+ // src/commands/gate.ts
484
539
  var GATE_QUESTIONS = [
485
540
  { id: 1, stage: "\uBB38\uC81C \uC815\uC758", question: "\uC774 \uC544\uC774\uB514\uC5B4\uAC00 \uD574\uACB0\uD558\uB294 \uBB38\uC81C\uB97C \uD55C \uBB38\uC7A5\uC73C\uB85C \uB9D0\uD574\uBCF4\uC138\uC694.", failIf: "\uD55C \uBB38\uC7A5 \uBD88\uAC00 \u2192 \uBBF8\uC131\uC219", quick: true },
486
541
  { id: 2, stage: "\uD575\uC2EC \uAE30\uB2A5", question: "\uB531 1\uAC1C \uAE30\uB2A5\uB9CC \uACE0\uB974\uBA74?", failIf: "2\uAC1C \uC774\uC0C1 \u2192 \uBC94\uC704 \uCD08\uACFC", quick: true },
@@ -502,7 +557,8 @@ function judgeGate(failCount, holdCount) {
502
557
  return "DROP";
503
558
  }
504
559
  async function gate() {
505
- console.log(chalk.bold(`
560
+ if (!ensureInteractive("\uC544\uC774\uB514\uC5B4 \uAC80\uC99D\uC740 \uB300\uD654\uD615 \uC9C8\uBB38\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uD130\uBBF8\uB110(PowerShell \uB4F1)\uC5D0\uC11C \uC9C1\uC811 \uC2E4\uD589\uD558\uC138\uC694. Git Bash \uBA74 VHK_FORCE_INTERACTIVE=1.")) return;
561
+ console.log(chalk2.bold(`
506
562
  ${ko.gate.title}
507
563
  `));
508
564
  const { mode: mode2 } = await inquirer.prompt([{
@@ -521,33 +577,33 @@ ${ko.gate.title}
521
577
  name: "source",
522
578
  message: ko.gate.skipSourcePrompt
523
579
  }]);
524
- console.log(chalk.green.bold(`
580
+ console.log(chalk2.green.bold(`
525
581
  ${ko.gate.skipGo}`));
526
- console.log(chalk.dim(ko.gate.skipSourceLabel(source)));
582
+ console.log(chalk2.dim(ko.gate.skipSourceLabel(source)));
527
583
  return;
528
584
  }
529
585
  const questions = mode2 === "quick" ? GATE_QUESTIONS.filter((q) => q.quick) : GATE_QUESTIONS;
530
586
  const total = questions.length;
531
587
  const header = mode2 === "quick" ? ko.gate.quickHeader : ko.gate.fullHeader;
532
- console.log(chalk.dim(`
588
+ console.log(chalk2.dim(`
533
589
  ${header} ${ko.gate.modeCountSuffix(total)}
534
590
  `));
535
- console.log(chalk.dim(`
591
+ console.log(chalk2.dim(`
536
592
  ${ko.gate.welcome}
537
593
  `));
538
- console.log(chalk.dim(` ${ko.gate.ideaHint}`));
594
+ console.log(chalk2.dim(` ${ko.gate.ideaHint}`));
539
595
  const { idea } = await inquirer.prompt([
540
596
  { type: "input", name: "idea", message: ko.gate.idea }
541
597
  ]);
542
- console.log(chalk.dim(` ${ko.gate.painPointHint}`));
598
+ console.log(chalk2.dim(` ${ko.gate.painPointHint}`));
543
599
  const { painPoint } = await inquirer.prompt([
544
600
  { type: "input", name: "painPoint", message: ko.gate.painPoint }
545
601
  ]);
546
- console.log(chalk.dim(` ${ko.gate.edgeHint}`));
602
+ console.log(chalk2.dim(` ${ko.gate.edgeHint}`));
547
603
  const { edge } = await inquirer.prompt([
548
604
  { type: "input", name: "edge", message: ko.gate.edge }
549
605
  ]);
550
- console.log(chalk.dim(`
606
+ console.log(chalk2.dim(`
551
607
  ${ko.gate.checklistStart}
552
608
  `));
553
609
  let failCount = 0;
@@ -555,7 +611,7 @@ ${ko.gate.checklistStart}
555
611
  const results = [];
556
612
  for (let i = 0; i < questions.length; i++) {
557
613
  const q = questions[i];
558
- if (q.hint) console.log(chalk.dim(`${ko.gate.hintPrefix} ${q.hint}`));
614
+ if (q.hint) console.log(chalk2.dim(`${ko.gate.hintPrefix} ${q.hint}`));
559
615
  const { answer } = await inquirer.prompt([{
560
616
  type: "input",
561
617
  name: "answer",
@@ -574,22 +630,22 @@ ${ko.gate.checklistStart}
574
630
  if (status2 === "fail") failCount++;
575
631
  if (status2 === "hold") holdCount++;
576
632
  results.push({ id: q.id, stage: q.stage, status: status2, answer });
577
- const icon = status2 === "pass" ? chalk.green(ko.gate.statusPassLine) : status2 === "hold" ? chalk.yellow(ko.gate.statusHoldLine) : chalk.red(ko.gate.statusFailLine);
633
+ const icon = status2 === "pass" ? chalk2.green(ko.gate.statusPassLine) : status2 === "hold" ? chalk2.yellow(ko.gate.statusHoldLine) : chalk2.red(ko.gate.statusFailLine);
578
634
  console.log(icon);
579
635
  }
580
- console.log(chalk.bold(`
636
+ console.log(chalk2.bold(`
581
637
  ${ko.gate.verdictTitle}
582
638
  `));
583
- console.log(`${ko.gate.ideaLabel} ${chalk.cyan(idea)}`);
639
+ console.log(`${ko.gate.ideaLabel} ${chalk2.cyan(idea)}`);
584
640
  console.log(`${ko.gate.painPointLabel} ${painPoint}`);
585
641
  console.log(`${ko.gate.edgeLabel} ${edge}`);
586
642
  console.log(`${ko.gate.countLine(failCount, holdCount, total)}
587
643
  `);
588
644
  const verdict = judgeGate(failCount, holdCount);
589
645
  if (verdict === "GO") {
590
- console.log(chalk.green.bold(ko.gate.go));
646
+ console.log(chalk2.green.bold(ko.gate.go));
591
647
  if (holdCount > 0) {
592
- console.log(chalk.yellow(ko.gate.holdRemainHint));
648
+ console.log(chalk2.yellow(ko.gate.holdRemainHint));
593
649
  }
594
650
  printNextStep({
595
651
  message: "\uC544\uC774\uB514\uC5B4 \uD1B5\uACFC! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uB9CC\uB4E4\uC5B4\uBCF4\uC138\uC694.",
@@ -597,28 +653,23 @@ ${ko.gate.verdictTitle}
597
653
  cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
598
654
  });
599
655
  } else if (verdict === "REFINE") {
600
- console.log(chalk.yellow.bold(ko.gate.refine));
656
+ console.log(chalk2.yellow.bold(ko.gate.refine));
601
657
  printNextStep({
602
658
  message: "\uC870\uAE08 \uB354 \uB2E4\uB4EC\uC740 \uD6C4 \uB2E4\uC2DC \uAC80\uC99D\uD574\uBCF4\uC138\uC694.",
603
659
  command: "vhk \uAC80\uC99D",
604
660
  cursorHint: "\uC544\uC774\uB514\uC5B4 \uB2E4\uC2DC \uAC80\uC99D\uD574\uC918"
605
661
  });
606
662
  } else {
607
- console.log(chalk.red.bold(ko.gate.drop));
663
+ console.log(chalk2.red.bold(ko.gate.drop));
608
664
  }
609
665
  }
610
666
 
611
667
  // src/commands/init.ts
612
668
  import inquirer2 from "inquirer";
613
- import chalk3 from "chalk";
669
+ import chalk4 from "chalk";
614
670
  import fs2 from "fs";
615
671
  import path2 from "path";
616
672
 
617
- // src/lib/date.ts
618
- function localDate(d = /* @__PURE__ */ new Date()) {
619
- return d.toLocaleDateString("sv-SE");
620
- }
621
-
622
673
  // src/templates/claude-md.ts
623
674
  function CLAUDE_MD_TEMPLATE(name, _stack) {
624
675
  const d = localDate();
@@ -898,6 +949,8 @@ function VHK_GITIGNORE_TEMPLATE() {
898
949
  "memory.json",
899
950
  "refs.json",
900
951
  "HARD_STOP",
952
+ "# secret gist \uD3EC\uC778\uD130 (gistId). \uACF5\uAC1C repo \uC5D0 \uCEE4\uBC0B\uB418\uBA74 \uBC31\uC5C5 gist \uAC00 \uB178\uCD9C\uB428 (VHK-022).",
953
+ "cloud.json",
901
954
  "# 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
955
  "backups/",
903
956
  ""
@@ -935,13 +988,13 @@ function VHK_CONTEXT_SEED(name, type, stack) {
935
988
  }
936
989
 
937
990
  // src/utils/logger.ts
938
- import chalk2 from "chalk";
991
+ import chalk3 from "chalk";
939
992
  var log = {
940
- success: (msg) => console.log(chalk2.green(`\u2705 ${msg}`)),
941
- error: (msg) => console.log(chalk2.red(`\u274C ${msg}`)),
942
- warn: (msg) => console.log(chalk2.yellow(`\u26A0\uFE0F ${msg}`)),
943
- info: (msg) => console.log(chalk2.blue(`\u2139\uFE0F ${msg}`)),
944
- step: (msg) => console.log(chalk2.bold(`
993
+ success: (msg) => console.log(chalk3.green(`\u2705 ${msg}`)),
994
+ error: (msg) => console.log(chalk3.red(`\u274C ${msg}`)),
995
+ warn: (msg) => console.log(chalk3.yellow(`\u26A0\uFE0F ${msg}`)),
996
+ info: (msg) => console.log(chalk3.blue(`\u2139\uFE0F ${msg}`)),
997
+ step: (msg) => console.log(chalk3.bold(`
945
998
  \u25B8 ${msg}`))
946
999
  };
947
1000
 
@@ -1102,90 +1155,8 @@ async function fetchPrdFromNotion(urlOrId) {
1102
1155
  };
1103
1156
  }
1104
1157
 
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
1158
  // src/lib/stack-detect.ts
1188
- import { join as join2 } from "path";
1159
+ import { join } from "path";
1189
1160
  function detectStackFromDeps(deps) {
1190
1161
  const stack = [];
1191
1162
  if (deps.next) stack.push("Next.js");
@@ -1208,7 +1179,7 @@ function detectStackFromDeps(deps) {
1208
1179
  function detectProjectStack(cwd = ".") {
1209
1180
  let pkg;
1210
1181
  try {
1211
- pkg = readJsonFile(join2(cwd, "package.json"));
1182
+ pkg = readJsonFile(join(cwd, "package.json"));
1212
1183
  } catch {
1213
1184
  return null;
1214
1185
  }
@@ -1241,32 +1212,36 @@ function resolveType(type) {
1241
1212
  }
1242
1213
  return type;
1243
1214
  }
1215
+ var DEFAULT_TYPE = PROJECT_TYPES[0].value;
1244
1216
  async function collectAnswers(options, defaults = {}) {
1217
+ const noninteractive = !isInteractive(options);
1245
1218
  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 });
1219
+ if (!noninteractive) {
1220
+ if (!options.name && !defaults.name) {
1221
+ prompts.push({ type: "input", name: "name", message: ko.init.projectName });
1222
+ }
1223
+ if (!options.description && !defaults.description) {
1224
+ prompts.push({ type: "input", name: "description", message: ko.init.description });
1225
+ }
1226
+ if (!options.type && !defaults.type) {
1227
+ prompts.push({ type: "list", name: "type", message: ko.init.projectType, choices: PROJECT_TYPES });
1228
+ }
1254
1229
  }
1255
1230
  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
- };
1231
+ const fallbackName = path2.basename(process.cwd()) || "my-project";
1232
+ const name = options.name || defaults.name || prompted.name || fallbackName;
1233
+ const description = options.description || defaults.description || prompted.description || `${name} \u2014 vhk \uD504\uB85C\uC81D\uD2B8`;
1234
+ const type = resolveType(options.type || defaults.type || prompted.type) ?? prompted.type ?? DEFAULT_TYPE;
1235
+ return { name, description, type };
1261
1236
  }
1262
1237
  async function init(options = {}) {
1263
1238
  const skipGate = Boolean(options.skipGate || options.fromNotion);
1264
1239
  if (skipGate) {
1265
- console.log(chalk3.dim(`
1240
+ console.log(chalk4.dim(`
1266
1241
  ${ko.init.skipGate}
1267
1242
  `));
1268
1243
  }
1269
- console.log(chalk3.bold(`
1244
+ console.log(chalk4.bold(`
1270
1245
  ${ko.init.title}
1271
1246
  `));
1272
1247
  printSecurityWarnings();
@@ -1292,11 +1267,11 @@ ${ko.init.title}
1292
1267
  }
1293
1268
  const detected = detectProjectStack(process.cwd());
1294
1269
  const stack = detected ?? STACK_PRESETS[answers.type];
1295
- if (detected) console.log(chalk3.dim(" \u{1F50E} package.json \uC758\uC874\uC131\uC5D0\uC11C \uC2E4\uC81C \uC2A4\uD0DD \uAC10\uC9C0"));
1296
- console.log(chalk3.dim(`
1270
+ if (detected) console.log(chalk4.dim(" \u{1F50E} package.json \uC758\uC874\uC131\uC5D0\uC11C \uC2E4\uC81C \uC2A4\uD0DD \uAC10\uC9C0"));
1271
+ console.log(chalk4.dim(`
1297
1272
  ${ko.init.recommendedStack} ${stack.join(" + ")}
1298
1273
  `));
1299
- if (!options.yes) {
1274
+ if (isInteractive(options)) {
1300
1275
  const { confirmStack } = await inquirer2.prompt([{
1301
1276
  type: "confirm",
1302
1277
  name: "confirmStack",
@@ -1310,7 +1285,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1310
1285
  }
1311
1286
  const cwd = process.cwd();
1312
1287
  let adoptedRules = null;
1313
- if (!options.yes && !options.fromNotion) {
1288
+ if (isInteractive(options) && !options.fromNotion) {
1314
1289
  const existingRules = detectExistingRuleFiles(cwd);
1315
1290
  if (existingRules.length > 0) {
1316
1291
  const { adopt } = await inquirer2.prompt([{
@@ -1324,7 +1299,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1324
1299
  }]);
1325
1300
  if (adopt) {
1326
1301
  adoptedRules = buildAdoptedRules(existingRules, answers.name);
1327
- console.log(chalk3.dim(` ${ko.init.adoptPreview(existingRules.length)}`));
1302
+ console.log(chalk4.dim(` ${ko.init.adoptPreview(existingRules.length)}`));
1328
1303
  }
1329
1304
  }
1330
1305
  }
@@ -1334,12 +1309,12 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1334
1309
  for (const [filePath, content] of Object.entries(files)) {
1335
1310
  const fullPath = path2.join(cwd, filePath);
1336
1311
  if (fileExists(fullPath)) {
1337
- const { overwrite } = await inquirer2.prompt([{
1312
+ const overwrite = !isInteractive(options) ? false : (await inquirer2.prompt([{
1338
1313
  type: "confirm",
1339
1314
  name: "overwrite",
1340
1315
  message: ko.init.overwrite(filePath),
1341
1316
  default: false
1342
- }]);
1317
+ }])).overwrite;
1343
1318
  if (!overwrite) {
1344
1319
  log.warn(ko.init.skipped(filePath));
1345
1320
  continue;
@@ -1348,22 +1323,22 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
1348
1323
  writeFile(fullPath, content);
1349
1324
  log.success(filePath);
1350
1325
  }
1351
- await writeInitExtras(cwd);
1352
- console.log(chalk3.bold.green(`
1326
+ await writeInitExtras(cwd, !isInteractive(options));
1327
+ console.log(chalk4.bold.green(`
1353
1328
  ${ko.init.done}`));
1354
- console.log(chalk3.dim(`
1329
+ console.log(chalk4.dim(`
1355
1330
  ${ko.init.nextSteps}`));
1356
1331
  if (options.fromNotion) {
1357
1332
  console.log(` 1. ${ko.init.notionReviewHint}`);
1358
1333
  console.log(` 2. ${ko.init.gitHintLabel}`);
1359
- console.log(` ${chalk3.cyan(ko.init.gitHintCommand)}`);
1334
+ console.log(` ${chalk4.cyan(ko.init.gitHintCommand)}`);
1360
1335
  console.log(` 3. ${ko.init.startDev}
1361
1336
  `);
1362
1337
  } else {
1363
1338
  console.log(` 1. ${ko.init.fillHint}`);
1364
1339
  console.log(` 2. ${ko.init.prdHint}`);
1365
1340
  console.log(` 3. ${ko.init.gitHintLabel}`);
1366
- console.log(` ${chalk3.cyan(ko.init.gitHintCommand)}`);
1341
+ console.log(` ${chalk4.cyan(ko.init.gitHintCommand)}`);
1367
1342
  console.log(` 4. ${ko.init.startDev}
1368
1343
  `);
1369
1344
  }
@@ -1462,16 +1437,16 @@ function projectHasTestScript(projectDir) {
1462
1437
  return false;
1463
1438
  }
1464
1439
  }
1465
- async function writeInitExtras(projectDir) {
1440
+ async function writeInitExtras(projectDir, noninteractive = false) {
1466
1441
  const commandsPath = path2.join(projectDir, "COMMANDS.md");
1467
1442
  const hasTest = projectHasTestScript(projectDir);
1468
1443
  if (fileExists(commandsPath)) {
1469
- const { overwrite } = await inquirer2.prompt([{
1444
+ const overwrite = noninteractive ? false : (await inquirer2.prompt([{
1470
1445
  type: "confirm",
1471
1446
  name: "overwrite",
1472
1447
  message: ko.init.overwrite("COMMANDS.md"),
1473
1448
  default: false
1474
- }]);
1449
+ }])).overwrite;
1475
1450
  if (!overwrite) {
1476
1451
  log.warn(ko.init.skipped("COMMANDS.md"));
1477
1452
  } else {
@@ -1680,31 +1655,17 @@ function createAdrFile(cwd, title, context2, decision, consequences) {
1680
1655
  return filePath;
1681
1656
  }
1682
1657
 
1683
- // src/lib/interactive.ts
1684
- import chalk4 from "chalk";
1685
- function ensureInteractive(hint = "") {
1686
- if (process.stdin.isTTY) return true;
1687
- console.error(chalk4.yellow(" \u26A0\uFE0F \uC774 \uBA85\uB839\uC740 \uB300\uD654\uD615 \uC785\uB825\uC774 \uD544\uC694\uD569\uB2C8\uB2E4 \u2014 \uBE44-TTY/\uD30C\uC774\uD504 \uD658\uACBD\uC5D0\uC11C\uB294 \uC2E4\uD589\uD560 \uC218 \uC5C6\uC5B4\uC694."));
1688
- if (hint) console.error(chalk4.dim(` ${hint}`));
1689
- process.exitCode = 1;
1690
- return false;
1691
- }
1692
- function isPromptAbortError(err) {
1693
- const msg = err instanceof Error ? err.message : String(err);
1694
- return /ERR_USE_AFTER_CLOSE|force closed|ExitPromptError|readline was closed|User force closed/i.test(msg);
1695
- }
1696
-
1697
1658
  // src/lib/hard-stop-guard.ts
1698
1659
  import chalk5 from "chalk";
1699
1660
 
1700
1661
  // 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";
1662
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, rmSync } from "fs";
1663
+ import { join as join2 } from "path";
1703
1664
  var STATE_DIR = "docs/state";
1704
- var BLOCKERS_PATH = join3(STATE_DIR, "blockers.md");
1705
- var LEARNINGS_PATH = join3(STATE_DIR, "learnings.md");
1665
+ var BLOCKERS_PATH = join2(STATE_DIR, "blockers.md");
1666
+ var LEARNINGS_PATH = join2(STATE_DIR, "learnings.md");
1706
1667
  var VHK_DIR = ".vhk";
1707
- var HARD_STOP_PATH = join3(VHK_DIR, "HARD_STOP");
1668
+ var HARD_STOP_PATH = join2(VHK_DIR, "HARD_STOP");
1708
1669
  var HARD_STOP_BLOCKER_THRESHOLD = 3;
1709
1670
  function ensureStateDir() {
1710
1671
  mkdirSync(STATE_DIR, { recursive: true });
@@ -1727,7 +1688,7 @@ function appendBlocker(description, goalId) {
1727
1688
  ensureStateDir();
1728
1689
  const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
1729
1690
  const line = `- [${isoDate()} ${tag}] ${description.trim()}`;
1730
- if (!existsSync2(BLOCKERS_PATH)) {
1691
+ if (!existsSync(BLOCKERS_PATH)) {
1731
1692
  writeFileSync(
1732
1693
  BLOCKERS_PATH,
1733
1694
  `# Blockers
@@ -1742,10 +1703,10 @@ ${line}
1742
1703
  appendFileSync(BLOCKERS_PATH, `${line}
1743
1704
  `, "utf-8");
1744
1705
  }
1745
- const current = readFileSync2(BLOCKERS_PATH, "utf-8");
1706
+ const current = readFileSync(BLOCKERS_PATH, "utf-8");
1746
1707
  const count = countActiveBlockers(current);
1747
1708
  let hardStopTripped = false;
1748
- if (count >= HARD_STOP_BLOCKER_THRESHOLD && !existsSync2(HARD_STOP_PATH)) {
1709
+ if (count >= HARD_STOP_BLOCKER_THRESHOLD && !existsSync(HARD_STOP_PATH)) {
1749
1710
  writeHardStop(`auto: ${count} active blockers (threshold ${HARD_STOP_BLOCKER_THRESHOLD})`);
1750
1711
  hardStopTripped = true;
1751
1712
  }
@@ -1755,7 +1716,7 @@ function appendLearning(lesson, goalId) {
1755
1716
  ensureStateDir();
1756
1717
  const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
1757
1718
  const line = `- [${isoDate()} ${tag}] ${lesson.trim()}`;
1758
- if (!existsSync2(LEARNINGS_PATH)) {
1719
+ if (!existsSync(LEARNINGS_PATH)) {
1759
1720
  writeFileSync(
1760
1721
  LEARNINGS_PATH,
1761
1722
  `# Learnings
@@ -1772,14 +1733,14 @@ ${line}
1772
1733
  }
1773
1734
  }
1774
1735
  function getRecentLearnings(limit = 3) {
1775
- if (!existsSync2(LEARNINGS_PATH)) return [];
1776
- const lines = readFileSync2(LEARNINGS_PATH, "utf-8").split(/\r?\n/);
1736
+ if (!existsSync(LEARNINGS_PATH)) return [];
1737
+ const lines = readFileSync(LEARNINGS_PATH, "utf-8").split(/\r?\n/);
1777
1738
  const entries = lines.filter((l) => l.startsWith("- ["));
1778
1739
  return entries.slice(-limit);
1779
1740
  }
1780
1741
  function getActiveBlockers(limit = 3) {
1781
- if (!existsSync2(BLOCKERS_PATH)) return [];
1782
- const lines = readFileSync2(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
1742
+ if (!existsSync(BLOCKERS_PATH)) return [];
1743
+ const lines = readFileSync(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
1783
1744
  const entries = lines.filter((l) => ACTIVE_BLOCKER_RE.test(l));
1784
1745
  return entries.slice(-limit);
1785
1746
  }
@@ -1791,18 +1752,18 @@ ${reason}
1791
1752
  `, "utf-8");
1792
1753
  }
1793
1754
  function isHardStopActive() {
1794
- return existsSync2(HARD_STOP_PATH);
1755
+ return existsSync(HARD_STOP_PATH);
1795
1756
  }
1796
1757
  function readHardStopReason() {
1797
- if (!existsSync2(HARD_STOP_PATH)) return null;
1758
+ if (!existsSync(HARD_STOP_PATH)) return null;
1798
1759
  try {
1799
- return readFileSync2(HARD_STOP_PATH, "utf-8").trim();
1760
+ return readFileSync(HARD_STOP_PATH, "utf-8").trim();
1800
1761
  } catch {
1801
1762
  return null;
1802
1763
  }
1803
1764
  }
1804
1765
  function clearHardStop() {
1805
- if (!existsSync2(HARD_STOP_PATH)) return false;
1766
+ if (!existsSync(HARD_STOP_PATH)) return false;
1806
1767
  rmSync(HARD_STOP_PATH, { force: true });
1807
1768
  return true;
1808
1769
  }
@@ -2060,543 +2021,17 @@ ${ko.recap.done}`));
2060
2021
  });
2061
2022
  }
2062
2023
 
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
2024
  // src/commands/check.ts
2590
- import chalk9 from "chalk";
2591
- import path10 from "path";
2592
- import fs9 from "fs";
2025
+ import chalk8 from "chalk";
2026
+ import path7 from "path";
2027
+ import fs6 from "fs";
2593
2028
 
2594
2029
  // src/lib/rules-parser.ts
2595
- import fs8 from "fs";
2596
- import path9 from "path";
2030
+ import fs5 from "fs";
2031
+ import path6 from "path";
2597
2032
  function parseRules(rulesPath) {
2598
- if (!fs8.existsSync(rulesPath)) return [];
2599
- const content = fs8.readFileSync(rulesPath, "utf-8");
2033
+ if (!fs5.existsSync(rulesPath)) return [];
2034
+ const content = fs5.readFileSync(rulesPath, "utf-8");
2600
2035
  const lines = content.split("\n");
2601
2036
  const rules = [];
2602
2037
  let currentSection = "";
@@ -2661,17 +2096,17 @@ function createNamingRule(id, section, desc, convention) {
2661
2096
  description: desc,
2662
2097
  check: (cwd) => {
2663
2098
  const violations = [];
2664
- const srcDir = path9.join(cwd, "src");
2665
- if (!fs8.existsSync(srcDir)) return violations;
2099
+ const srcDir = path6.join(cwd, "src");
2100
+ if (!fs5.existsSync(srcDir)) return violations;
2666
2101
  walkFiles(srcDir, (filePath) => {
2667
- const name = path9.basename(filePath, path9.extname(filePath));
2102
+ const name = path6.basename(filePath, path6.extname(filePath));
2668
2103
  if (convention === "kebab-case" && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
2669
2104
  if (!["index", "vite.config", "tsconfig"].includes(name)) {
2670
2105
  violations.push({
2671
2106
  ruleId: id,
2672
2107
  severity: "warning",
2673
2108
  message: `\uD30C\uC77C\uBA85\uC774 kebab-case\uAC00 \uC544\uB2D8: ${name}`,
2674
- file: path9.relative(cwd, filePath)
2109
+ file: path6.relative(cwd, filePath)
2675
2110
  });
2676
2111
  }
2677
2112
  }
@@ -2687,8 +2122,8 @@ function createStructureRule(id, section, desc, expectedPath) {
2687
2122
  type: "structure",
2688
2123
  description: desc,
2689
2124
  check: (cwd) => {
2690
- const fullPath = path9.join(cwd, expectedPath);
2691
- if (!fs8.existsSync(fullPath)) {
2125
+ const fullPath = path6.join(cwd, expectedPath);
2126
+ if (!fs5.existsSync(fullPath)) {
2692
2127
  return [{
2693
2128
  ruleId: id,
2694
2129
  severity: "error",
@@ -2708,11 +2143,11 @@ function createContentRule(id, section, desc, pattern, type) {
2708
2143
  pattern: new RegExp(escapeRegex(pattern), "i"),
2709
2144
  check: (cwd) => {
2710
2145
  const violations = [];
2711
- const srcDir = path9.join(cwd, "src");
2712
- if (!fs8.existsSync(srcDir)) return violations;
2146
+ const srcDir = path6.join(cwd, "src");
2147
+ if (!fs5.existsSync(srcDir)) return violations;
2713
2148
  const regex = new RegExp(escapeRegex(pattern), "i");
2714
2149
  walkFiles(srcDir, (filePath) => {
2715
- const fileContent = fs8.readFileSync(filePath, "utf-8");
2150
+ const fileContent = fs5.readFileSync(filePath, "utf-8");
2716
2151
  const fileLines = fileContent.split("\n");
2717
2152
  fileLines.forEach((line, idx) => {
2718
2153
  if (regex.test(line)) {
@@ -2720,7 +2155,7 @@ function createContentRule(id, section, desc, pattern, type) {
2720
2155
  ruleId: id,
2721
2156
  severity: type === "banned" ? "error" : "warning",
2722
2157
  message: type === "banned" ? `\uAE08\uC9C0 \uD328\uD134 \uBC1C\uACAC: \`${pattern}\`` : `\uD544\uC218 \uD328\uD134 \uB204\uB77D: \`${pattern}\``,
2723
- file: path9.relative(cwd, filePath),
2158
+ file: path6.relative(cwd, filePath),
2724
2159
  line: idx + 1
2725
2160
  });
2726
2161
  }
@@ -2732,9 +2167,9 @@ function createContentRule(id, section, desc, pattern, type) {
2732
2167
  };
2733
2168
  }
2734
2169
  function walkFiles(dir, callback) {
2735
- const entries = fs8.readdirSync(dir, { withFileTypes: true });
2170
+ const entries = fs5.readdirSync(dir, { withFileTypes: true });
2736
2171
  for (const entry of entries) {
2737
- const fullPath = path9.join(dir, entry.name);
2172
+ const fullPath = path6.join(dir, entry.name);
2738
2173
  if (entry.isDirectory()) {
2739
2174
  if (!["node_modules", ".git", "dist", ".next"].includes(entry.name)) {
2740
2175
  walkFiles(fullPath, callback);
@@ -2749,17 +2184,18 @@ function escapeRegex(str) {
2749
2184
  }
2750
2185
 
2751
2186
  // 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";
2187
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
2188
+ import { join as join4 } from "path";
2189
+ import chalk7 from "chalk";
2755
2190
 
2756
2191
  // src/lib/goal-frontmatter.ts
2757
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
2758
- import { join as join4 } from "path";
2192
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
2193
+ import { join as join3 } from "path";
2759
2194
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
2760
2195
  function parseFrontmatter(content) {
2761
- const m = content.match(FRONTMATTER_RE);
2762
- if (!m) return { frontmatter: {}, body: content };
2196
+ const normalized = stripBom(content);
2197
+ const m = normalized.match(FRONTMATTER_RE);
2198
+ if (!m) return { frontmatter: {}, body: normalized };
2763
2199
  const fm = parseSimpleYaml(m[1]);
2764
2200
  const body = (m[2] ?? "").replace(/^\r?\n+/, "");
2765
2201
  return { frontmatter: fm, body };
@@ -2787,9 +2223,9 @@ function parseSimpleYaml(yaml) {
2787
2223
  return out;
2788
2224
  }
2789
2225
  function parseGoalFile(filePath) {
2790
- if (!existsSync3(filePath)) return null;
2226
+ if (!existsSync2(filePath)) return null;
2791
2227
  try {
2792
- const content = readFileSync3(filePath, "utf-8");
2228
+ const content = readFileSync2(filePath, "utf-8");
2793
2229
  const { frontmatter, body } = parseFrontmatter(content);
2794
2230
  return { filePath, frontmatter, body };
2795
2231
  } catch {
@@ -2797,7 +2233,7 @@ function parseGoalFile(filePath) {
2797
2233
  }
2798
2234
  }
2799
2235
  function listGoals(goalsDir) {
2800
- if (!existsSync3(goalsDir)) return [];
2236
+ if (!existsSync2(goalsDir)) return [];
2801
2237
  let entries;
2802
2238
  try {
2803
2239
  entries = readdirSync(goalsDir);
@@ -2808,7 +2244,7 @@ function listGoals(goalsDir) {
2808
2244
  for (const name of entries) {
2809
2245
  if (!name.endsWith(".md")) continue;
2810
2246
  if (name === "_meta.md") continue;
2811
- const fp = join4(goalsDir, name);
2247
+ const fp = join3(goalsDir, name);
2812
2248
  try {
2813
2249
  if (!statSync(fp).isFile()) continue;
2814
2250
  } catch {
@@ -2823,6 +2259,36 @@ function listGoals(goalsDir) {
2823
2259
  parsed.sort((a, b) => a.frontmatter.id - b.frontmatter.id);
2824
2260
  return parsed;
2825
2261
  }
2262
+ function findSkippedGoalFiles(goalsDir) {
2263
+ if (!existsSync2(goalsDir)) return [];
2264
+ let entries;
2265
+ try {
2266
+ entries = readdirSync(goalsDir);
2267
+ } catch {
2268
+ return [];
2269
+ }
2270
+ const out = [];
2271
+ for (const name of entries) {
2272
+ if (!name.endsWith(".md")) continue;
2273
+ if (name === "_meta.md") continue;
2274
+ const fp = join3(goalsDir, name);
2275
+ try {
2276
+ if (!statSync(fp).isFile()) continue;
2277
+ } catch {
2278
+ continue;
2279
+ }
2280
+ const g = parseGoalFile(fp);
2281
+ if (!g) continue;
2282
+ const fm = g.frontmatter;
2283
+ if (fm.type === "goal" && typeof fm.id === "number") continue;
2284
+ if (fm.type === "meta") continue;
2285
+ const looksLikeGoal = fm.type === "goal" || "id" in fm || "status" in fm || "priority" in fm || "title" in fm;
2286
+ if (!looksLikeGoal) continue;
2287
+ 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)";
2288
+ out.push({ file: name, reason });
2289
+ }
2290
+ return out;
2291
+ }
2826
2292
  function findDuplicateIds(goals) {
2827
2293
  const counts = /* @__PURE__ */ new Map();
2828
2294
  for (const g of goals) {
@@ -2900,13 +2366,15 @@ function resolveGoalId(optId, goals) {
2900
2366
  return selectActiveId(goals);
2901
2367
  }
2902
2368
  async function goalList() {
2903
- console.log(chalk8.bold(`
2369
+ console.log(chalk7.bold(`
2904
2370
  ${ko.goal.listTitle}
2905
2371
  `));
2906
2372
  const goals = listGoals(GOALS_DIR);
2373
+ const skipped = findSkippedGoalFiles(GOALS_DIR);
2907
2374
  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."));
2375
+ console.log(chalk7.yellow(" \u{1F4ED} goals/ \uB514\uB809\uD1A0\uB9AC\uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2376
+ console.log(chalk7.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
2377
+ printSkippedGoalWarnings(skipped);
2910
2378
  return;
2911
2379
  }
2912
2380
  for (const g of goals) {
@@ -2923,22 +2391,33 @@ ${ko.goal.listTitle}
2923
2391
  const dups = findDuplicateIds(goals);
2924
2392
  if (dups.length > 0) {
2925
2393
  console.log("");
2926
- console.log(chalk8.yellow(` ${ko.goal.duplicateId(dups.join(", "))}`));
2394
+ console.log(chalk7.yellow(` ${ko.goal.duplicateId(dups.join(", "))}`));
2395
+ }
2396
+ printSkippedGoalWarnings(skipped);
2397
+ }
2398
+ function printSkippedGoalWarnings(skipped) {
2399
+ if (skipped.length > 0) {
2400
+ console.log("");
2401
+ console.log(chalk7.yellow(` ${ko.goal.skippedFiles(skipped.length)}`));
2402
+ for (const s of skipped) {
2403
+ console.log(chalk7.yellow(` - goals/${s.file}: ${s.reason}`));
2404
+ }
2405
+ console.log(chalk7.dim(" \uD544\uC218: type: goal + \uC22B\uC790 id. \uC2A4\uD0A4\uB9C8 \uC804\uCCB4: goals/_meta.md"));
2927
2406
  }
2928
2407
  }
2929
2408
  async function goalNext() {
2930
- console.log(chalk8.bold(`
2409
+ console.log(chalk7.bold(`
2931
2410
  ${ko.goal.nextTitle}
2932
2411
  `));
2933
2412
  const goals = listGoals(GOALS_DIR);
2934
2413
  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."));
2414
+ console.log(chalk7.yellow(" \u{1F4ED} \uC815\uC758\uB41C goal \uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2415
+ console.log(chalk7.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
2937
2416
  return;
2938
2417
  }
2939
2418
  const activeId = selectActiveId(goals);
2940
2419
  if (activeId === null) {
2941
- console.log(chalk8.green(" \u{1F389} \uBAA8\uB4E0 goal \uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"));
2420
+ console.log(chalk7.green(" \u{1F389} \uBAA8\uB4E0 goal \uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"));
2942
2421
  return;
2943
2422
  }
2944
2423
  const active = goals.find((g) => g.frontmatter.id === activeId);
@@ -2958,9 +2437,9 @@ ${ko.goal.nextTitle}
2958
2437
  ""
2959
2438
  ].join("\n");
2960
2439
  mkdirSync2(STATE_DIR2, { recursive: true });
2961
- writeFileSync2(join5(STATE_DIR2, "next-task.md"), text, "utf-8");
2440
+ writeFileSync2(join4(STATE_DIR2, "next-task.md"), text, "utf-8");
2962
2441
  console.log(
2963
- chalk8.green(
2442
+ chalk7.green(
2964
2443
  ` \u2705 next-task.md \uAC31\uC2E0 \u2014 Goal ${activeId}: ${active.frontmatter.title ?? ""}`
2965
2444
  )
2966
2445
  );
@@ -2979,35 +2458,70 @@ version: v0.1
2979
2458
  ## Forbidden Actions (\uC804\uC5ED)
2980
2459
 
2981
2460
  - (\uD574\uB2F9 \uC0AC\uD56D)
2461
+
2462
+ ## Goal \uD30C\uC77C \uC2A4\uD0A4\uB9C8 (\uD544\uB3C5 \u2014 VHK-021)
2463
+
2464
+ \`vhk goal list/next/check/done\` \uB294 \`goals/*.md\`(\uC774 \`_meta.md\` \uC81C\uC678) \uC911 \uC544\uB798
2465
+ 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
2466
+ \`vhk goal list\` \uAC00 \uACBD\uACE0\uB85C \uC54C\uB824\uC900\uB2E4.
2467
+
2468
+ | \uD544\uB4DC | \uD544\uC218 | \uAC12 |
2469
+ | --- | --- | --- |
2470
+ | \`type\` | \u2705 | \`goal\` (\uBB38\uC790\uC5F4 \uADF8\uB300\uB85C) |
2471
+ | \`id\` | \u2705 | **\uC22B\uC790\uB9CC** (\`1\`, \`2\` \u2026 \u2014 \`G1\` \uAC19\uC740 \uBB38\uC790\uC5F4 \u274C) |
2472
+ | \`status\` | \u2705 | \`NOT_STARTED\` | \`IN_PROGRESS\` | \`DONE\` | \`BLOCKED\` |
2473
+ | \`priority\` | \uAD8C\uC7A5 | \`P0\` | \`P1\` | \`P2\` |
2474
+ | \`title\` | \uAD8C\uC7A5 | \uD55C \uC904 \uC81C\uBAA9 |
2475
+
2476
+ \uD30C\uC77C\uBA85 \uADDC\uCE59: \`goals/<id>-<name>.md\` (\uC608: \`goals/1-login.md\`).
2477
+
2478
+ ### \uC0C8 goal \uD15C\uD50C\uB9BF (\uBCF5\uBD99)
2479
+
2480
+ \`\`\`markdown
2481
+ ---
2482
+ vhk_format: 1
2483
+ type: goal
2484
+ id: 1
2485
+ title: \uB85C\uADF8\uC778 \uAE30\uB2A5
2486
+ status: NOT_STARTED
2487
+ priority: P0
2488
+ ---
2489
+
2490
+ # Goal 1: \uB85C\uADF8\uC778 \uAE30\uB2A5
2491
+
2492
+ ## \uBC30\uACBD / \uB3D9\uC791 / Completion Check ...
2493
+ \`\`\`
2494
+
2495
+ \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8\uB294 \`vhk goal sync\` \uB85C \`scripts/check-goal-<id>.mjs\` \uB97C \uBC31\uD544\uD55C\uB2E4.
2982
2496
  `;
2983
2497
  var STATE_NEXT_TASK_TEMPLATE = "# Next Task\n\n```\nTASK: (vhk goal next \uB85C \uC790\uB3D9 \uAC31\uC2E0)\n```\n";
2984
2498
  var STATE_BLOCKERS_TEMPLATE = "# Blockers\n\n_Append-only. \uD574\uACB0 \uD56D\uBAA9\uC740 ~~\uCDE8\uC18C\uC120~~\uC73C\uB85C \uD45C\uAE30._\n";
2985
2499
  var STATE_LEARNINGS_TEMPLATE = "# Learnings\n\n_Append-only. \uD55C \uC904 = \uD55C \uAD50\uD6C8._\n";
2986
2500
  async function goalInit() {
2987
- console.log(chalk8.bold(`
2501
+ console.log(chalk7.bold(`
2988
2502
  ${ko.goal.initTitle}
2989
2503
  `));
2990
2504
  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 }
2505
+ { path: join4(GOALS_DIR, "_meta.md"), content: META_TEMPLATE },
2506
+ { path: join4(STATE_DIR2, "next-task.md"), content: STATE_NEXT_TASK_TEMPLATE },
2507
+ { path: join4(STATE_DIR2, "blockers.md"), content: STATE_BLOCKERS_TEMPLATE },
2508
+ { path: join4(STATE_DIR2, "learnings.md"), content: STATE_LEARNINGS_TEMPLATE }
2995
2509
  ];
2996
2510
  mkdirSync2(GOALS_DIR, { recursive: true });
2997
2511
  mkdirSync2(STATE_DIR2, { recursive: true });
2998
2512
  let created = 0;
2999
2513
  let skipped = 0;
3000
2514
  for (const t2 of targets) {
3001
- if (existsSync4(t2.path)) {
3002
- console.log(chalk8.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${t2.path}`));
2515
+ if (existsSync3(t2.path)) {
2516
+ console.log(chalk7.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${t2.path}`));
3003
2517
  skipped++;
3004
2518
  } else {
3005
2519
  writeFileSync2(t2.path, t2.content, "utf-8");
3006
- console.log(chalk8.green(` \u2713 created: ${t2.path}`));
2520
+ console.log(chalk7.green(` \u2713 created: ${t2.path}`));
3007
2521
  created++;
3008
2522
  }
3009
2523
  }
3010
- console.log(chalk8.bold(`
2524
+ console.log(chalk7.bold(`
3011
2525
  \u{1F4CA} created=${created} skipped=${skipped}`));
3012
2526
  if (created > 0) {
3013
2527
  printNextStep({
@@ -3018,10 +2532,10 @@ ${ko.goal.initTitle}
3018
2532
  }
3019
2533
  }
3020
2534
  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;
2535
+ const mjs = join4(SCRIPTS_DIR, `check-goal-${id}.mjs`);
2536
+ if (existsSync3(mjs)) return mjs;
2537
+ const sh = join4(SCRIPTS_DIR, `check-goal-${id}.sh`);
2538
+ if (existsSync3(sh)) return sh;
3025
2539
  return null;
3026
2540
  }
3027
2541
  function runGate(scriptPath) {
@@ -3030,82 +2544,93 @@ function runGate(scriptPath) {
3030
2544
  const r = safeExecFile(runner, [scriptPath]);
3031
2545
  return { ok: r.ok, out: r.out, err: r.ok ? "" : r.err, runner };
3032
2546
  }
2547
+ function warnIfBashOnWindows(scriptPath) {
2548
+ if (process.platform === "win32" && scriptPath.endsWith(".sh")) {
2549
+ console.log(
2550
+ chalk7.yellow(
2551
+ " \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"
2552
+ )
2553
+ );
2554
+ }
2555
+ }
3033
2556
  async function goalCheck(opts) {
3034
- console.log(chalk8.bold(`
2557
+ console.log(chalk7.bold(`
3035
2558
  ${ko.goal.checkTitle}
3036
2559
  `));
3037
2560
  const goals = listGoals(GOALS_DIR);
3038
2561
  const id = resolveGoalId(opts.id, goals);
3039
2562
  if (id === null) {
3040
2563
  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).")
2564
+ 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
2565
  );
3043
2566
  process.exitCode = 1;
3044
2567
  return;
3045
2568
  }
3046
2569
  if (!goals.some((g) => g.frontmatter.id === id)) {
3047
- console.log(chalk8.red(` \u274C ${ko.goal.notFound(id)}`));
2570
+ console.log(chalk7.red(` \u274C ${ko.goal.notFound(id)}`));
3048
2571
  process.exitCode = 1;
3049
2572
  return;
3050
2573
  }
3051
2574
  const scriptPath = findGateScript(id);
3052
2575
  if (!scriptPath) {
3053
2576
  console.log(
3054
- chalk8.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: scripts/check-goal-${id}.{mjs,sh}`)
2577
+ chalk7.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: scripts/check-goal-${id}.{mjs,sh}`)
3055
2578
  );
3056
2579
  process.exitCode = 1;
3057
2580
  return;
3058
2581
  }
2582
+ warnIfBashOnWindows(scriptPath);
3059
2583
  const gate2 = runGate(scriptPath);
3060
- console.log(chalk8.dim(` \u25B6 ${gate2.runner} ${scriptPath}
2584
+ console.log(chalk7.dim(` \u25B6 ${gate2.runner} ${scriptPath}
3061
2585
  `));
3062
2586
  if (gate2.out) console.log(gate2.out);
3063
2587
  if (gate2.ok) {
3064
- console.log(chalk8.green(`
2588
+ console.log(chalk7.green(`
3065
2589
  \u2705 Goal ${id} \uAC8C\uC774\uD2B8 \uD1B5\uACFC`));
3066
2590
  } else {
3067
- console.log(chalk8.red(`
2591
+ console.log(chalk7.red(`
3068
2592
  \u274C Goal ${id} \uAC8C\uC774\uD2B8 \uC2E4\uD328`));
3069
- if (gate2.err && !gate2.out) console.log(chalk8.dim(gate2.err.slice(0, 500)));
2593
+ if (gate2.err && !gate2.out) console.log(chalk7.dim(gate2.err.slice(0, 500)));
3070
2594
  process.exitCode = 1;
3071
2595
  }
3072
2596
  }
3073
2597
  async function goalDone(opts) {
3074
- console.log(chalk8.bold(`
2598
+ console.log(chalk7.bold(`
3075
2599
  ${ko.goal.doneTitle}
3076
2600
  `));
3077
2601
  const goals = listGoals(GOALS_DIR);
3078
2602
  const id = resolveGoalId(opts.id, goals);
3079
2603
  if (id === null) {
3080
2604
  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).")
2605
+ 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
2606
  );
3083
2607
  process.exitCode = 1;
3084
2608
  return;
3085
2609
  }
3086
2610
  const target = goals.find((g) => g.frontmatter.id === id);
3087
2611
  if (!target) {
3088
- console.log(chalk8.red(` \u274C ${ko.goal.notFound(id)}`));
2612
+ console.log(chalk7.red(` \u274C ${ko.goal.notFound(id)}`));
3089
2613
  process.exitCode = 1;
3090
2614
  return;
3091
2615
  }
3092
2616
  const scriptPath = findGateScript(id);
3093
2617
  if (!scriptPath) {
3094
2618
  console.log(
3095
- chalk8.red(
2619
+ chalk7.red(
3096
2620
  ` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C \u2014 done \uCC98\uB9AC \uAC70\uBD80: scripts/check-goal-${id}.{mjs,sh}`
3097
2621
  )
3098
2622
  );
3099
2623
  process.exitCode = 1;
3100
2624
  return;
3101
2625
  }
2626
+ warnIfBashOnWindows(scriptPath);
3102
2627
  const gate2 = runGate(scriptPath);
3103
- console.log(chalk8.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: ${gate2.runner} ${scriptPath}
2628
+ console.log(chalk7.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: ${gate2.runner} ${scriptPath}
3104
2629
  `));
3105
2630
  if (gate2.out) console.log(gate2.out);
3106
2631
  if (!gate2.ok) {
3107
2632
  console.log(
3108
- chalk8.red(
2633
+ chalk7.red(
3109
2634
  `
3110
2635
  \u274C \uAC8C\uC774\uD2B8 \uC2E4\uD328 \u2014 frontmatter \uBCC0\uACBD \uC5C6\uC774 \uC885\uB8CC. (Forbidden: \uC2E4\uD328 = \uBCF4\uC874)`
3111
2636
  )
@@ -3113,11 +2638,11 @@ ${ko.goal.doneTitle}
3113
2638
  process.exitCode = 1;
3114
2639
  return;
3115
2640
  }
3116
- const content = readFileSync4(target.filePath, "utf-8");
2641
+ const content = readFileSync3(target.filePath, "utf-8");
3117
2642
  const today = localDate();
3118
2643
  const updated = updateFrontmatterStatus(content, "DONE", { completed: today });
3119
2644
  writeFileSync2(target.filePath, updated, "utf-8");
3120
- console.log(chalk8.green(`
2645
+ console.log(chalk7.green(`
3121
2646
  \u2705 Goal ${id} \u2192 DONE (completed: ${today})`));
3122
2647
  printNextStep({
3123
2648
  message: `Goal ${id} \uC644\uB8CC! \uB2E4\uC74C goal \uB85C:`,
@@ -3125,6 +2650,112 @@ ${ko.goal.doneTitle}
3125
2650
  cursorHint: "\uB2E4\uC74C goal \uC54C\uB824\uC918"
3126
2651
  });
3127
2652
  }
2653
+ function generateGateScript(id) {
2654
+ const ID = String(id);
2655
+ return [
2656
+ "#!/usr/bin/env node",
2657
+ `// scripts/check-goal-${ID}.mjs \u2014 \uC790\uB3D9 \uC0DD\uC131 (vhk goal sync).`,
2658
+ "// \uAE30\uBCF8 \uAC8C\uC774\uD2B8 = typecheck + (lint) + test + build. goal \uACE0\uC720 \uAC80\uC99D\uC740 \uC544\uB798 \uAD6C\uC5ED\uC5D0 \uCD94\uAC00.",
2659
+ "// sync \uC7AC\uC2E4\uD589\uD574\uB3C4 \uAE30\uC874 \uD30C\uC77C\uC740 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (idempotent).",
2660
+ "//",
2661
+ "// Env: VHK_GATES_SKIP_DEEP=1 \u2192 test + build \uC2A4\uD0B5 (\uBE60\uB978 typecheck-only \uD328\uC2A4)",
2662
+ "",
2663
+ "import { execFileSync } from 'node:child_process'",
2664
+ "import { existsSync, readFileSync } from 'node:fs'",
2665
+ "",
2666
+ "const SHIM = new Set(['pnpm', 'npm', 'npx', 'yarn'])",
2667
+ "function run(cmd, args) {",
2668
+ " let bin = cmd, argv = args",
2669
+ " if (process.platform === 'win32' && SHIM.has(cmd)) {",
2670
+ " // Windows: .cmd shim \uC9C1\uC811 spawn \uC740 Node CVE-2024-27980 \uC73C\uB85C EINVAL \u2192 cmd.exe \uB798\uD551.",
2671
+ " bin = 'cmd.exe'; argv = ['/d', '/s', '/c', cmd + '.cmd', ...args]",
2672
+ " }",
2673
+ " try {",
2674
+ " // 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.",
2675
+ " execFileSync(bin, argv, { stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 })",
2676
+ " return true",
2677
+ " } catch (e) {",
2678
+ " const out = (e?.stdout?.toString() ?? '') + (e?.stderr?.toString() ?? '')",
2679
+ " if (out.trim()) console.log(out.split('\\n').slice(-25).join('\\n'))",
2680
+ " return false",
2681
+ " }",
2682
+ "}",
2683
+ "",
2684
+ "if (existsSync('.vhk/HARD_STOP')) {",
2685
+ ` console.log('\u{1F6D1} .vhk/HARD_STOP detected \u2014 refusing to run goal ${ID} gate.')`,
2686
+ " process.exit(1)",
2687
+ "}",
2688
+ "",
2689
+ "const pkg = existsSync('package.json') ? JSON.parse(readFileSync('package.json', 'utf-8')) : {}",
2690
+ "const scripts = pkg.scripts ?? {}",
2691
+ "const pm = existsSync('pnpm-lock.yaml') ? 'pnpm' : existsSync('yarn.lock') ? 'yarn' : 'npm'",
2692
+ "const skipDeep = process.env.VHK_GATES_SKIP_DEEP === '1'",
2693
+ "let pass = true",
2694
+ `const gate = (label, ok) => { console.log('[goal ${ID}] ' + label + ': ' + (ok ? '\u2713' : '\u2717')); if (!ok) pass = false }`,
2695
+ "const must = (cond, label) => { console.log((cond ? ' \u2713 ' : ' \u2717 ') + label); if (!cond) pass = false }",
2696
+ "",
2697
+ "// typecheck (\uC2A4\uD06C\uB9BD\uD2B8 \uC6B0\uC120, \uC5C6\uC73C\uBA74 tsc --noEmit)",
2698
+ "if (scripts.typecheck) gate('typecheck', run(pm, ['run', 'typecheck']))",
2699
+ "else if (existsSync('tsconfig.json')) gate('tsc --noEmit', run(pm, pm === 'npm' ? ['exec', '--', 'tsc', '--noEmit'] : ['exec', 'tsc', '--noEmit']))",
2700
+ "if (scripts.lint) gate('lint', run(pm, ['run', 'lint']))",
2701
+ "if (!skipDeep) {",
2702
+ " if (scripts['test:run']) gate('test', run(pm, ['run', 'test:run']))",
2703
+ " else if (scripts.test && /vitest/.test(scripts.test)) gate('test', run(pm, ['run', 'test', '--', '--run']))",
2704
+ " else if (scripts.test) gate('test', run(pm, ['run', 'test']))",
2705
+ " if (scripts.build) gate('build', run(pm, ['run', 'build']))",
2706
+ "}",
2707
+ "",
2708
+ `// \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`,
2709
+ "// const read = (p) => existsSync(p) ? readFileSync(p, 'utf-8') : null",
2710
+ "// must(read('src/foo.ts')?.includes('bar'), 'foo.ts \uC5D0 bar \uC874\uC7AC')",
2711
+ "",
2712
+ `if (pass) { console.log('\u2705 goal ${ID} gate passes'); process.exit(0) }`,
2713
+ `console.log('\u274C goal ${ID} gate failed'); process.exit(1)`,
2714
+ ""
2715
+ ].join("\n");
2716
+ }
2717
+ async function goalSync() {
2718
+ console.log(chalk7.bold(`
2719
+ ${ko.goal.syncTitle}
2720
+ `));
2721
+ const goals = listGoals(GOALS_DIR);
2722
+ const result = { created: [], skipped: [] };
2723
+ if (goals.length === 0) {
2724
+ console.log(
2725
+ chalk7.yellow(" \u{1F4ED} goals/ \uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694.")
2726
+ );
2727
+ return result;
2728
+ }
2729
+ mkdirSync2(SCRIPTS_DIR, { recursive: true });
2730
+ for (const g of goals) {
2731
+ const id = g.frontmatter.id;
2732
+ if (typeof id !== "number") continue;
2733
+ const target = join4(SCRIPTS_DIR, `check-goal-${id}.mjs`);
2734
+ if (existsSync3(target)) {
2735
+ console.log(chalk7.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${target}`));
2736
+ result.skipped.push(id);
2737
+ continue;
2738
+ }
2739
+ const shOnly = existsSync3(join4(SCRIPTS_DIR, `check-goal-${id}.sh`));
2740
+ writeFileSync2(target, generateGateScript(id), "utf-8");
2741
+ console.log(
2742
+ chalk7.green(` \u2713 created: ${target}${shOnly ? " (.sh \u2192 .mjs \uBC31\uD544, Windows 1\uAE09)" : ""}`)
2743
+ );
2744
+ result.created.push(id);
2745
+ }
2746
+ console.log(
2747
+ chalk7.bold(`
2748
+ \u{1F4CA} created=${result.created.length} skipped=${result.skipped.length}`)
2749
+ );
2750
+ if (result.created.length > 0) {
2751
+ printNextStep({
2752
+ message: `\uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 ${result.created.length}\uAC1C \uC0DD\uC131 (goal ${result.created.join(", ")}). \uAC80\uC99D\uD558\uB824\uBA74:`,
2753
+ command: `vhk goal check --id ${result.created[0]}`,
2754
+ cursorHint: `goal ${result.created[0]} \uAC8C\uC774\uD2B8 \uAC80\uC99D\uD574\uC918`
2755
+ });
2756
+ }
2757
+ return result;
2758
+ }
3128
2759
 
3129
2760
  // src/commands/check.ts
3130
2761
  async function check(opts = {}) {
@@ -3134,22 +2765,22 @@ async function check(opts = {}) {
3134
2765
  return checkRules();
3135
2766
  }
3136
2767
  async function checkRules() {
3137
- console.log(chalk9.bold(`
2768
+ console.log(chalk8.bold(`
3138
2769
  ${ko.check.title}
3139
2770
  `));
3140
2771
  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."));
2772
+ const rulesPath = path7.join(cwd, "RULES.md");
2773
+ if (!fs6.existsSync(rulesPath)) {
2774
+ console.log(chalk8.yellow(ko.check.noRules));
2775
+ console.log(chalk8.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
3145
2776
  return;
3146
2777
  }
3147
2778
  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)
2779
+ 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
2780
  `));
3150
2781
  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."));
2782
+ console.log(chalk8.yellow(ko.check.noAutoRules));
2783
+ 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
2784
  return;
3154
2785
  }
3155
2786
  const allViolations = [];
@@ -3157,14 +2788,14 @@ ${ko.check.title}
3157
2788
  for (const rule of rules) {
3158
2789
  const violations = rule.check(cwd);
3159
2790
  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);
2791
+ const patternHint = rule.type === "content" && rule.pattern ? chalk8.dim(` [\uAC80\uC0AC: ${rule.pattern.source}]`) : "";
2792
+ console.log(chalk8.green(` \u2705 ${rule.id}`) + chalk8.dim(` \u2014 ${rule.description.slice(0, 60)}`) + patternHint);
3162
2793
  passCount++;
3163
2794
  } else {
3164
- console.log(chalk9.red(` \u274C ${rule.id}`) + chalk9.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
2795
+ console.log(chalk8.red(` \u274C ${rule.id}`) + chalk8.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
3165
2796
  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");
2797
+ const loc = v.file ? chalk8.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
2798
+ const icon = v.severity === "error" ? chalk8.red("\u2716") : v.severity === "warning" ? chalk8.yellow("\u26A0") : chalk8.blue("\u2139");
3168
2799
  console.log(` ${icon} ${v.message}${loc}`);
3169
2800
  });
3170
2801
  allViolations.push(...violations);
@@ -3174,18 +2805,18 @@ ${ko.check.title}
3174
2805
  const errors = allViolations.filter((v) => v.severity === "error").length;
3175
2806
  const warnings = allViolations.filter((v) => v.severity === "warning").length;
3176
2807
  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.)"));
2808
+ console.log(chalk8.green.bold(`\u2705 \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${passCount}\uAC1C \uD1B5\uACFC`));
2809
+ 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
2810
  printNextStep({
3180
2811
  message: "\uBAA8\uB4E0 \uADDC\uCE59 \uD1B5\uACFC! \uBCF4\uC548 \uC2A4\uCE94\uB3C4 \uD574\uBCFC\uAE4C\uC694?",
3181
2812
  command: "vhk \uBCF4\uC548 scan",
3182
2813
  cursorHint: "\uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB824\uC918"
3183
2814
  });
3184
2815
  } 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`)}`);
2816
+ console.log(chalk8.bold(ko.check.summary));
2817
+ 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`);
2818
+ if (errors > 0) console.log(` ${chalk8.red(`\u2716 ${errors}\uAC1C \uC5D0\uB7EC`)}`);
2819
+ if (warnings > 0) console.log(` ${chalk8.yellow(`\u26A0 ${warnings}\uAC1C \uACBD\uACE0`)}`);
3189
2820
  printNextStep({
3190
2821
  message: "\uC704\uBC18 \uD56D\uBAA9\uC744 \uC218\uC815\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
3191
2822
  command: "vhk \uC810\uAC80",
@@ -3198,36 +2829,36 @@ ${ko.check.title}
3198
2829
  }
3199
2830
 
3200
2831
  // src/commands/secure.ts
3201
- import chalk10 from "chalk";
3202
- import fs10 from "fs";
3203
- import path11 from "path";
2832
+ import chalk9 from "chalk";
2833
+ import fs7 from "fs";
2834
+ import path8 from "path";
3204
2835
  async function secure() {
3205
- console.log(chalk10.bold(`
2836
+ console.log(chalk9.bold(`
3206
2837
  ${ko.secure.title}
3207
2838
  `));
3208
2839
  const cwd = process.cwd();
3209
- const gitignorePath = path11.join(cwd, ".gitignore");
3210
- const hasGitignore = fs10.existsSync(gitignorePath);
2840
+ const gitignorePath = path8.join(cwd, ".gitignore");
2841
+ const hasGitignore = fs7.existsSync(gitignorePath);
3211
2842
  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"));
2843
+ console.log(chalk9.yellow(` ${ko.secure.noGitignore}`));
2844
+ console.log(chalk9.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
3214
2845
  } else {
3215
- const gitignoreContent = fs10.readFileSync(gitignorePath, "utf-8");
2846
+ const gitignoreContent = fs7.readFileSync(gitignorePath, "utf-8");
3216
2847
  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"));
2848
+ console.log(chalk9.yellow(` ${ko.secure.noEnvInGitignore}`));
2849
+ console.log(chalk9.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
3219
2850
  }
3220
2851
  }
3221
- console.log(chalk10.dim(` ${ko.secure.scanning}
2852
+ console.log(chalk9.dim(` ${ko.secure.scanning}
3222
2853
  `));
3223
2854
  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)`));
2855
+ 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
2856
  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.`));
2857
+ 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
2858
  }
3228
2859
  console.log("");
3229
2860
  if (findings.length === 0) {
3230
- console.log(chalk10.green.bold(` ${ko.secure.clean}`));
2861
+ console.log(chalk9.green.bold(` ${ko.secure.clean}`));
3231
2862
  printNextStep({
3232
2863
  message: "\uBCF4\uC548 \uC774\uC0C1 \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
3233
2864
  command: "vhk \uC815\uB9AC",
@@ -3239,45 +2870,45 @@ ${ko.secure.title}
3239
2870
  const high = findings.filter((f) => f.severity === "high");
3240
2871
  const medium = findings.filter((f) => f.severity === "medium");
3241
2872
  if (critical.length > 0) {
3242
- console.log(chalk10.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
2873
+ console.log(chalk9.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
3243
2874
  critical.forEach((f) => {
3244
- console.log(chalk10.red(` \u2716 ${f.patternName}`));
3245
- console.log(chalk10.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2875
+ console.log(chalk9.red(` \u2716 ${f.patternName}`));
2876
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
3246
2877
  });
3247
2878
  console.log("");
3248
2879
  }
3249
2880
  if (high.length > 0) {
3250
- console.log(chalk10.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
2881
+ console.log(chalk9.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
3251
2882
  high.forEach((f) => {
3252
- console.log(chalk10.yellow(` \u26A0 ${f.patternName}`));
3253
- console.log(chalk10.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2883
+ console.log(chalk9.yellow(` \u26A0 ${f.patternName}`));
2884
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
3254
2885
  });
3255
2886
  console.log("");
3256
2887
  }
3257
2888
  if (medium.length > 0) {
3258
- console.log(chalk10.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
2889
+ console.log(chalk9.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
3259
2890
  medium.forEach((f) => {
3260
- console.log(chalk10.blue(` \u2139 ${f.patternName}`));
3261
- console.log(chalk10.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
2891
+ console.log(chalk9.blue(` \u2139 ${f.patternName}`));
2892
+ console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
3262
2893
  });
3263
2894
  console.log("");
3264
2895
  }
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}`);
2896
+ console.log(chalk9.bold(` ${ko.secure.summary}`));
2897
+ console.log(` \uCD1D ${chalk9.red(String(findings.length))}\uAC74 \uAC10\uC9C0 | CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length}`);
3267
2898
  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"));
2899
+ console.log(chalk9.dim(" \u{1F4A1} \uC870\uCE58 \uBC29\uBC95:"));
2900
+ 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"));
2901
+ console.log(chalk9.dim(" 2. git history\uC5D0\uC11C\uB3C4 \uC81C\uAC70: git filter-branch \uB610\uB294 BFG Repo-Cleaner"));
2902
+ console.log(chalk9.dim(" 3. \uC720\uCD9C\uB41C \uD0A4\uB294 \uC989\uC2DC \uD3D0\uAE30\uD558\uACE0 \uC7AC\uBC1C\uAE09\n"));
3272
2903
  if (critical.length > 0 || high.length > 0) {
3273
2904
  process.exitCode = 1;
3274
2905
  }
3275
2906
  }
3276
2907
 
3277
2908
  // src/commands/doctor.ts
3278
- import chalk11 from "chalk";
3279
- import fs11 from "fs";
3280
- import path12 from "path";
2909
+ import chalk10 from "chalk";
2910
+ import fs8 from "fs";
2911
+ import path9 from "path";
3281
2912
  import { fileURLToPath } from "url";
3282
2913
  function checkCommand(name, command, hint) {
3283
2914
  const result = safeExecFile(command, ["--version"]);
@@ -3286,14 +2917,14 @@ function checkCommand(name, command, hint) {
3286
2917
  return { name, command, version, ok: true, hint };
3287
2918
  }
3288
2919
  function getVhkVersion2() {
3289
- const dir = path12.dirname(fileURLToPath(import.meta.url));
2920
+ const dir = path9.dirname(fileURLToPath(import.meta.url));
3290
2921
  const candidates = [
3291
- path12.join(dir, "../package.json"),
3292
- path12.join(dir, "../../package.json")
2922
+ path9.join(dir, "../package.json"),
2923
+ path9.join(dir, "../../package.json")
3293
2924
  ];
3294
2925
  for (const pkgPath of candidates) {
3295
2926
  try {
3296
- if (fs11.existsSync(pkgPath)) {
2927
+ if (fs8.existsSync(pkgPath)) {
3297
2928
  const pkg = readJsonFile(pkgPath);
3298
2929
  return pkg.version;
3299
2930
  }
@@ -3321,7 +2952,7 @@ function compareSemver(a, b) {
3321
2952
  return a3 - b3;
3322
2953
  }
3323
2954
  async function doctor() {
3324
- console.log(chalk11.bold(`
2955
+ console.log(chalk10.bold(`
3325
2956
  ${ko.doctor.title}
3326
2957
  `));
3327
2958
  const checks = [
@@ -3333,30 +2964,30 @@ ${ko.doctor.title}
3333
2964
  let allOk = true;
3334
2965
  for (const check2 of checks) {
3335
2966
  if (check2.ok) {
3336
- console.log(chalk11.green(` \u2705 ${check2.name}`) + chalk11.dim(` \u2014 ${check2.version}`));
2967
+ console.log(chalk10.green(` \u2705 ${check2.name}`) + chalk10.dim(` \u2014 ${check2.version}`));
3337
2968
  } else {
3338
- console.log(chalk11.red(` \u274C ${check2.name} \uC5C6\uC74C`));
3339
- console.log(chalk11.dim(` \u2192 ${check2.hint}`));
2969
+ console.log(chalk10.red(` \u274C ${check2.name} \uC5C6\uC74C`));
2970
+ console.log(chalk10.dim(` \u2192 ${check2.hint}`));
3340
2971
  allOk = false;
3341
2972
  }
3342
2973
  }
3343
2974
  console.log("");
3344
2975
  const vhkVersion = getVhkVersion2();
3345
2976
  if (vhkVersion) {
3346
- console.log(chalk11.green(" \u2705 VHK") + chalk11.dim(` \u2014 v${vhkVersion}`));
2977
+ console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(` \u2014 v${vhkVersion}`));
3347
2978
  } else {
3348
- console.log(chalk11.green(" \u2705 VHK") + chalk11.dim(" \u2014 \uC124\uCE58\uB428"));
2979
+ console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(" \u2014 \uC124\uCE58\uB428"));
3349
2980
  }
3350
2981
  if (vhkVersion) {
3351
2982
  const latest = fetchLatestNpmVersion("@byh3071/vhk");
3352
2983
  if (latest && compareSemver(latest, vhkVersion) > 0) {
3353
- console.log(chalk11.yellow(` ${ko.doctor.updateAvailable(latest)}`));
2984
+ console.log(chalk10.yellow(` ${ko.doctor.updateAvailable(latest)}`));
3354
2985
  } else if (latest) {
3355
- console.log(chalk11.dim(` ${ko.doctor.updateCurrent}`));
2986
+ console.log(chalk10.dim(` ${ko.doctor.updateCurrent}`));
3356
2987
  }
3357
2988
  }
3358
2989
  console.log("");
3359
- console.log(chalk11.bold(` ${ko.doctor.projectFiles}`));
2990
+ console.log(chalk10.bold(` ${ko.doctor.projectFiles}`));
3360
2991
  const cwd = process.cwd();
3361
2992
  const projectFiles = [
3362
2993
  { name: "RULES.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
@@ -3366,51 +2997,51 @@ ${ko.doctor.title}
3366
2997
  { name: ".env", hint: ".gitignore\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778" }
3367
2998
  ];
3368
2999
  for (const file of projectFiles) {
3369
- const exists = fs11.existsSync(path12.join(cwd, file.name));
3000
+ const exists = fs8.existsSync(path9.join(cwd, file.name));
3370
3001
  if (exists) {
3371
- console.log(chalk11.green(` \u2705 ${file.name}`));
3002
+ console.log(chalk10.green(` \u2705 ${file.name}`));
3372
3003
  if (file.name === ".env") {
3373
- const gitignorePath = path12.join(cwd, ".gitignore");
3374
- if (fs11.existsSync(gitignorePath)) {
3375
- const gitignore = fs11.readFileSync(gitignorePath, "utf-8");
3004
+ const gitignorePath = path9.join(cwd, ".gitignore");
3005
+ if (fs8.existsSync(gitignorePath)) {
3006
+ const gitignore = fs8.readFileSync(gitignorePath, "utf-8");
3376
3007
  if (!gitignore.includes(".env")) {
3377
- console.log(chalk11.yellow(` ${ko.doctor.envNotIgnored}`));
3008
+ console.log(chalk10.yellow(` ${ko.doctor.envNotIgnored}`));
3378
3009
  }
3379
3010
  }
3380
3011
  }
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)"));
3012
+ } else if (file.name === ".env" && fs8.existsSync(path9.join(cwd, ".env.local"))) {
3013
+ console.log(chalk10.green(" \u2705 .env.local") + chalk10.dim(" \u2014 \uB85C\uCEEC env \uC0AC\uC6A9 \uC911 (.env \uC5C6\uC5B4\uB3C4 \uC815\uC0C1)"));
3383
3014
  } else {
3384
- console.log(chalk11.dim(` \u2B1A ${file.name}`) + chalk11.dim(` \u2014 ${file.hint}`));
3015
+ console.log(chalk10.dim(` \u2B1A ${file.name}`) + chalk10.dim(` \u2014 ${file.hint}`));
3385
3016
  }
3386
3017
  }
3387
3018
  console.log("");
3388
- console.log(chalk11.bold(` ${ko.doctor.driftTitle}`));
3019
+ console.log(chalk10.bold(` ${ko.doctor.driftTitle}`));
3389
3020
  const ruleDrift = checkRuleDrift(cwd);
3390
3021
  if (!ruleDrift.checked) {
3391
- console.log(chalk11.dim(` ${ko.doctor.driftNoRules}`));
3022
+ console.log(chalk10.dim(` ${ko.doctor.driftNoRules}`));
3392
3023
  } else {
3393
3024
  const drifted = ruleDrift.results.filter((r) => r.status === "drifted");
3394
3025
  if (drifted.length === 0) {
3395
- console.log(chalk11.green(` ${ko.doctor.driftRuleClean}`));
3026
+ console.log(chalk10.green(` ${ko.doctor.driftRuleClean}`));
3396
3027
  } else {
3397
- console.log(chalk11.yellow(` ${ko.doctor.driftRuleWarn(drifted.map((d) => d.path).join(", "))}`));
3028
+ console.log(chalk10.yellow(` ${ko.doctor.driftRuleWarn(drifted.map((d) => d.path).join(", "))}`));
3398
3029
  }
3399
3030
  }
3400
3031
  const ctxDrift = checkContextDrift(cwd);
3401
3032
  if (ctxDrift.checked && ctxDrift.stale) {
3402
- console.log(chalk11.yellow(` ${ko.doctor.driftContextWarn}`));
3033
+ console.log(chalk10.yellow(` ${ko.doctor.driftContextWarn}`));
3403
3034
  }
3404
3035
  console.log("");
3405
3036
  if (allOk) {
3406
- console.log(chalk11.green.bold(` ${ko.doctor.allOk}`));
3037
+ console.log(chalk10.green.bold(` ${ko.doctor.allOk}`));
3407
3038
  printNextStep({
3408
3039
  message: ko.doctor.nextOkMessage,
3409
3040
  command: "vhk \uC2DC\uC791",
3410
3041
  cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
3411
3042
  });
3412
3043
  } else {
3413
- console.log(chalk11.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
3044
+ console.log(chalk10.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
3414
3045
  printNextStep({
3415
3046
  message: ko.doctor.nextRetryMessage,
3416
3047
  command: "vhk doctor",
@@ -3421,10 +3052,10 @@ ${ko.doctor.title}
3421
3052
  }
3422
3053
 
3423
3054
  // src/commands/ship.ts
3424
- import chalk12 from "chalk";
3425
- import inquirer5 from "inquirer";
3426
- import fs12 from "fs";
3427
- import path13 from "path";
3055
+ import chalk11 from "chalk";
3056
+ import inquirer4 from "inquirer";
3057
+ import fs9 from "fs";
3058
+ import path10 from "path";
3428
3059
  var CHECKLIST = [
3429
3060
  { id: "build", questionKey: "checkBuild", hintKey: "hintBuild" },
3430
3061
  { id: "test", questionKey: "checkTest", hintKey: "hintTest" },
@@ -3437,9 +3068,9 @@ function sanitizeVersion(version) {
3437
3068
  return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
3438
3069
  }
3439
3070
  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");
3071
+ const changelogPath = path10.join(cwd, "CHANGELOG.md");
3072
+ if (!fs9.existsSync(changelogPath)) return { status: "missing" };
3073
+ const content = fs9.readFileSync(changelogPath, "utf-8");
3443
3074
  const unreleasedHeading = /^## \[Unreleased\][^\n]*$/m;
3444
3075
  if (!unreleasedHeading.test(content)) return { status: "no-unreleased" };
3445
3076
  const blankUnreleased = [
@@ -3456,36 +3087,36 @@ function updateChangelogUnreleased(cwd, version, date) {
3456
3087
  `## [${version}] \u2014 ${date}`
3457
3088
  ].join("\n");
3458
3089
  const updated = content.replace(unreleasedHeading, blankUnreleased);
3459
- fs12.writeFileSync(changelogPath, updated, "utf-8");
3090
+ fs9.writeFileSync(changelogPath, updated, "utf-8");
3460
3091
  return { status: "updated", version };
3461
3092
  }
3462
3093
  async function ship() {
3463
3094
  if (!ensureNotHardStopped("ship")) return;
3464
- console.log(chalk12.bold(`
3095
+ console.log(chalk11.bold(`
3465
3096
  ${ko.ship.title}
3466
3097
  `));
3467
3098
  const cwd = process.cwd();
3468
- console.log(chalk12.cyan.bold(` ${ko.ship.checklist}
3099
+ console.log(chalk11.cyan.bold(` ${ko.ship.checklist}
3469
3100
  `));
3470
- const { passed } = await inquirer5.prompt([{
3101
+ const { passed } = await inquirer4.prompt([{
3471
3102
  type: "checkbox",
3472
3103
  name: "passed",
3473
3104
  message: ko.ship.checkboxPrompt,
3474
3105
  choices: CHECKLIST.map((c) => ({
3475
- name: `${ko.ship[c.questionKey]} ${chalk12.dim(`(${ko.ship[c.hintKey]})`)}`,
3106
+ name: `${ko.ship[c.questionKey]} ${chalk11.dim(`(${ko.ship[c.hintKey]})`)}`,
3476
3107
  value: c.id
3477
3108
  }))
3478
3109
  }]);
3479
3110
  const allPassed = passed.length === CHECKLIST.length;
3480
3111
  const skipped = CHECKLIST.filter((c) => !passed.includes(c.id));
3481
3112
  if (!allPassed) {
3482
- console.log(chalk12.yellow(`
3113
+ console.log(chalk11.yellow(`
3483
3114
  ${ko.ship.incompleteHeader}`));
3484
3115
  skipped.forEach((s) => {
3485
- console.log(chalk12.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
3486
- console.log(chalk12.dim(` \u2192 ${ko.ship[s.hintKey]}`));
3116
+ console.log(chalk11.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
3117
+ console.log(chalk11.dim(` \u2192 ${ko.ship[s.hintKey]}`));
3487
3118
  });
3488
- const { proceed } = await inquirer5.prompt([{
3119
+ const { proceed } = await inquirer4.prompt([{
3489
3120
  type: "confirm",
3490
3121
  name: "proceed",
3491
3122
  message: ko.ship.proceedConfirm,
@@ -3500,26 +3131,26 @@ ${ko.ship.title}
3500
3131
  return;
3501
3132
  }
3502
3133
  } else {
3503
- console.log(chalk12.green(`
3134
+ console.log(chalk11.green(`
3504
3135
  ${ko.ship.allPassed}
3505
3136
  `));
3506
3137
  }
3507
- console.log(chalk12.cyan.bold(` ${ko.ship.retro}
3138
+ console.log(chalk11.cyan.bold(` ${ko.ship.retro}
3508
3139
  `));
3509
- console.log(chalk12.dim(` ${ko.ship.versionHint}`));
3510
- const retro = await inquirer5.prompt([
3140
+ console.log(chalk11.dim(` ${ko.ship.versionHint}`));
3141
+ const retro = await inquirer4.prompt([
3511
3142
  { type: "input", name: "version", message: ko.ship.versionPrompt },
3512
3143
  { type: "input", name: "whatWentWell", message: ko.ship.questionWell },
3513
3144
  { type: "input", name: "whatWentWrong", message: ko.ship.questionWrong },
3514
3145
  { type: "input", name: "learned", message: ko.ship.questionLearned },
3515
3146
  { type: "input", name: "nextVersion", message: ko.ship.questionNext }
3516
3147
  ]);
3517
- const buildLogDir = path13.join(cwd, "docs", "build-log");
3518
- if (!fs12.existsSync(buildLogDir)) fs12.mkdirSync(buildLogDir, { recursive: true });
3148
+ const buildLogDir = path10.join(cwd, "docs", "build-log");
3149
+ if (!fs9.existsSync(buildLogDir)) fs9.mkdirSync(buildLogDir, { recursive: true });
3519
3150
  const today = localDate();
3520
3151
  const versionSlug = sanitizeVersion(retro.version);
3521
3152
  const fileName = `${today}-v${versionSlug}.md`;
3522
- const filePath = path13.join(buildLogDir, fileName);
3153
+ const filePath = path10.join(buildLogDir, fileName);
3523
3154
  const content = [
3524
3155
  `# \uBE4C\uB4DC \uB85C\uADF8: v${versionSlug}`,
3525
3156
  "",
@@ -3548,9 +3179,9 @@ ${ko.ship.title}
3548
3179
  "---",
3549
3180
  `*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
3550
3181
  ].join("\n");
3551
- fs12.writeFileSync(filePath, content, "utf-8");
3552
- console.log(chalk12.green(`
3553
- ${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
3182
+ fs9.writeFileSync(filePath, content, "utf-8");
3183
+ console.log(chalk11.green(`
3184
+ ${ko.ship.buildLogDone(path10.relative(cwd, filePath))}`));
3554
3185
  const changelogResult = updateChangelogUnreleased(cwd, versionSlug, today);
3555
3186
  if (changelogResult.status === "updated") {
3556
3187
  log.success(ko.ship.changelogUpdated(changelogResult.version));
@@ -3568,10 +3199,10 @@ ${ko.ship.title}
3568
3199
  }
3569
3200
 
3570
3201
  // src/commands/save.ts
3571
- import { execFileSync as execFileSync2 } from "child_process";
3572
- import chalk13 from "chalk";
3202
+ import { execFileSync } from "child_process";
3203
+ import chalk12 from "chalk";
3573
3204
  import ora from "ora";
3574
- import inquirer6 from "inquirer";
3205
+ import inquirer5 from "inquirer";
3575
3206
 
3576
3207
  // src/lib/git-porcelain.ts
3577
3208
  function normalizePorcelain(raw) {
@@ -3597,59 +3228,66 @@ function statusIcon(code) {
3597
3228
  return "\u{1F4C4}";
3598
3229
  }
3599
3230
  async function save() {
3600
- console.log(chalk13.bold(`
3231
+ console.log(chalk12.bold(`
3601
3232
  \u{1F4BE} ${t("save.title")}`));
3602
- console.log(chalk13.gray("\u2500".repeat(40)));
3233
+ console.log(chalk12.gray("\u2500".repeat(40)));
3603
3234
  let gitRoot;
3604
3235
  try {
3605
- execFileSync2("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3236
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3606
3237
  gitRoot = getGitRoot();
3607
3238
  } catch {
3608
- console.log(chalk13.red(`\u274C ${t("save.notGitRepo")}`));
3239
+ console.log(chalk12.red(`\u274C ${t("save.notGitRepo")}`));
3609
3240
  return;
3610
3241
  }
3611
- console.log(chalk13.cyan(`
3242
+ console.log(chalk12.cyan(`
3612
3243
  \u{1F512} ${t("save.securityWarnHeader")}`));
3613
3244
  printSecurityWarnings(gitRoot);
3614
3245
  const severe = filterSevereFindings(scanProjectForSecrets(gitRoot).findings);
3615
3246
  if (severe.length > 0) {
3616
- console.log(chalk13.red(`
3247
+ console.log(chalk12.red(`
3617
3248
  \u26A0\uFE0F ${t("save.secretsFound", severe.length)}`));
3618
3249
  severe.slice(0, 5).forEach((f) => {
3619
- console.log(chalk13.dim(` ${f.file}:${f.line} \u2014 ${f.patternName}`));
3250
+ console.log(chalk12.dim(` ${f.file}:${f.line} \u2014 ${f.patternName}`));
3620
3251
  });
3621
3252
  if (severe.length > 5) {
3622
- console.log(chalk13.dim(` ... \uC678 ${severe.length - 5}\uAC74 (vhk \uBCF4\uC548 scan)`));
3253
+ console.log(chalk12.dim(` ... \uC678 ${severe.length - 5}\uAC74 (vhk \uBCF4\uC548 scan)`));
3623
3254
  }
3624
- const { proceed } = await inquirer6.prompt([{
3625
- type: "confirm",
3626
- name: "proceed",
3627
- message: t("save.secretsConfirm"),
3628
- default: false
3629
- }]);
3255
+ const proceed = await promptOrDefault(
3256
+ async () => (await inquirer5.prompt([{
3257
+ type: "confirm",
3258
+ name: "proceed",
3259
+ message: t("save.secretsConfirm"),
3260
+ default: false
3261
+ }])).proceed,
3262
+ false
3263
+ // 비대화형 = 시크릿 커밋 안 함 (안전)
3264
+ );
3630
3265
  if (!proceed) {
3631
- console.log(chalk13.gray(t("save.cancelled")));
3266
+ console.log(chalk12.gray(t("save.cancelled")));
3632
3267
  return;
3633
3268
  }
3634
3269
  }
3635
3270
  const lines = parsePorcelainLines(gitOut(["status", "--porcelain"], gitRoot));
3636
3271
  if (lines.length === 0) {
3637
- console.log(chalk13.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
3272
+ console.log(chalk12.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
3638
3273
  return;
3639
3274
  }
3640
- console.log(chalk13.cyan(`
3275
+ console.log(chalk12.cyan(`
3641
3276
  \u{1F4CB} ${t("save.filesHeader", lines.length)}`));
3642
3277
  lines.forEach((line) => {
3643
3278
  const code = line.substring(0, 2);
3644
3279
  const name = line.substring(3);
3645
3280
  console.log(` ${statusIcon(code)} ${name}`);
3646
3281
  });
3647
- const { message } = await inquirer6.prompt([{
3648
- type: "input",
3649
- name: "message",
3650
- message: t("save.commitMessage"),
3651
- default: formatDefaultCommitMessage()
3652
- }]);
3282
+ const message = await promptOrDefault(
3283
+ async () => (await inquirer5.prompt([{
3284
+ type: "input",
3285
+ name: "message",
3286
+ message: t("save.commitMessage"),
3287
+ default: formatDefaultCommitMessage()
3288
+ }])).message,
3289
+ "chore: vhk save"
3290
+ );
3653
3291
  const spinner = ora(t("save.saving")).start();
3654
3292
  let didAdd = false;
3655
3293
  try {
@@ -3659,21 +3297,21 @@ async function save() {
3659
3297
  spinner.text = t("save.pushing");
3660
3298
  if (!hasGitRemote(gitRoot)) {
3661
3299
  spinner.succeed(t("save.successLocal"));
3662
- console.log(chalk13.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
3300
+ console.log(chalk12.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
3663
3301
  } else {
3664
3302
  try {
3665
3303
  gitRun(["push"], gitRoot);
3666
3304
  spinner.succeed(t("save.successWithPush"));
3667
3305
  } catch (pushErr) {
3668
3306
  spinner.fail(t("save.pushFailed"));
3669
- console.log(chalk13.red(getExecErrorMessage(pushErr)));
3670
- console.log(chalk13.yellow(`
3307
+ console.log(chalk12.red(getExecErrorMessage(pushErr)));
3308
+ console.log(chalk12.yellow(`
3671
3309
  \u{1F4A1} ${t("save.commitOkPushFailed")}`));
3672
3310
  process.exitCode = 1;
3673
3311
  }
3674
3312
  }
3675
3313
  if (process.exitCode !== 1) {
3676
- console.log(chalk13.green(`
3314
+ console.log(chalk12.green(`
3677
3315
  \u2705 ${t("save.done", lines.length)}`));
3678
3316
  printNextStep({
3679
3317
  message: t("save.nextOkMessage"),
@@ -3681,7 +3319,7 @@ async function save() {
3681
3319
  cursorHint: t("save.nextOkCursor")
3682
3320
  });
3683
3321
  } else {
3684
- console.log(chalk13.green(`
3322
+ console.log(chalk12.green(`
3685
3323
  \u2705 ${t("save.doneLocalOnly", lines.length)}`));
3686
3324
  printNextStep({
3687
3325
  message: t("save.nextPushFailMessage"),
@@ -3691,12 +3329,12 @@ async function save() {
3691
3329
  }
3692
3330
  } catch (err) {
3693
3331
  spinner.fail(t("save.failed"));
3694
- console.log(chalk13.red(getExecErrorMessage(err)));
3332
+ console.log(chalk12.red(getExecErrorMessage(err)));
3695
3333
  if (didAdd) {
3696
3334
  try {
3697
3335
  const staged = gitOut(["diff", "--cached", "--stat"], gitRoot).trim();
3698
3336
  if (staged) {
3699
- console.log(chalk13.yellow(`
3337
+ console.log(chalk12.yellow(`
3700
3338
  \u{1F4A1} ${t("save.stagedAfterFail")}`));
3701
3339
  }
3702
3340
  } catch {
@@ -3707,9 +3345,9 @@ async function save() {
3707
3345
  }
3708
3346
 
3709
3347
  // src/commands/undo.ts
3710
- import { execFileSync as execFileSync3 } from "child_process";
3711
- import chalk14 from "chalk";
3712
- import inquirer7 from "inquirer";
3348
+ import { execFileSync as execFileSync2 } from "child_process";
3349
+ import chalk13 from "chalk";
3350
+ import inquirer6 from "inquirer";
3713
3351
  function parseRecentCommits(logOutput) {
3714
3352
  return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
3715
3353
  }
@@ -3732,36 +3370,36 @@ function isUndoRisky(undoCount, unpushedCount, hasRemote) {
3732
3370
  return false;
3733
3371
  }
3734
3372
  async function undo() {
3735
- console.log(chalk14.bold(`
3373
+ console.log(chalk13.bold(`
3736
3374
  \u23EA ${t("undo.title")}`));
3737
- console.log(chalk14.gray("\u2500".repeat(40)));
3375
+ console.log(chalk13.gray("\u2500".repeat(40)));
3738
3376
  let gitRoot;
3739
3377
  try {
3740
- execFileSync3("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3378
+ execFileSync2("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3741
3379
  gitRoot = getGitRoot();
3742
3380
  } catch {
3743
- console.log(chalk14.red(`\u274C ${t("undo.notGitRepo")}`));
3381
+ console.log(chalk13.red(`\u274C ${t("undo.notGitRepo")}`));
3744
3382
  return;
3745
3383
  }
3746
3384
  let logOutput;
3747
3385
  try {
3748
3386
  logOutput = gitOut(["log", "--oneline", "-5"], gitRoot).trim();
3749
3387
  } catch {
3750
- console.log(chalk14.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3388
+ console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3751
3389
  return;
3752
3390
  }
3753
3391
  const commits = parseRecentCommits(logOutput);
3754
3392
  if (commits.length === 0) {
3755
- console.log(chalk14.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3393
+ console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
3756
3394
  return;
3757
3395
  }
3758
- console.log(chalk14.cyan(`
3396
+ console.log(chalk13.cyan(`
3759
3397
  ${t("undo.recentHeader")}`));
3760
3398
  commits.forEach((c, i) => {
3761
3399
  console.log(` ${i === 0 ? "\u{1F449}" : " "} ${c}`);
3762
3400
  });
3763
3401
  const maxUndo = commits.length;
3764
- const { count } = await inquirer7.prompt([{
3402
+ const { count } = await inquirer6.prompt([{
3765
3403
  type: "number",
3766
3404
  name: "count",
3767
3405
  message: t("undo.howMany"),
@@ -3772,7 +3410,7 @@ ${t("undo.recentHeader")}`));
3772
3410
  const undoCount = Math.min(Math.max(1, count || 1), maxUndo);
3773
3411
  const headCount = countLocalCommits(gitRoot);
3774
3412
  if (undoCount >= headCount) {
3775
- console.log(chalk14.yellow(`
3413
+ console.log(chalk13.yellow(`
3776
3414
  \u{1F4ED} ${t("undo.rootCommit")}`));
3777
3415
  return;
3778
3416
  }
@@ -3781,30 +3419,30 @@ ${t("undo.recentHeader")}`));
3781
3419
  const risky = isUndoRisky(undoCount, unpushed, remote);
3782
3420
  if (risky) {
3783
3421
  if (unpushed < 0) {
3784
- console.log(chalk14.red(`
3422
+ console.log(chalk13.red(`
3785
3423
  \u26A0\uFE0F ${t("undo.noUpstreamWarning")}`));
3786
3424
  } else {
3787
- console.log(chalk14.red(`
3425
+ console.log(chalk13.red(`
3788
3426
  \u26A0\uFE0F ${t("undo.alreadyPushed")}`));
3789
3427
  }
3790
3428
  }
3791
- const { confirm } = await inquirer7.prompt([{
3429
+ const { confirm } = await inquirer6.prompt([{
3792
3430
  type: "confirm",
3793
3431
  name: "confirm",
3794
3432
  message: risky ? t("undo.confirmRisky", undoCount) : t("undo.confirmMessage"),
3795
3433
  default: false
3796
3434
  }]);
3797
3435
  if (!confirm) {
3798
- console.log(chalk14.gray(t("undo.cancelled")));
3436
+ console.log(chalk13.gray(t("undo.cancelled")));
3799
3437
  return;
3800
3438
  }
3801
3439
  try {
3802
3440
  gitRun(["reset", "--soft", `HEAD~${undoCount}`], gitRoot);
3803
- console.log(chalk14.green(`
3441
+ console.log(chalk13.green(`
3804
3442
  \u2705 ${t("undo.success")}`));
3805
- console.log(chalk14.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
3443
+ console.log(chalk13.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
3806
3444
  if (risky) {
3807
- console.log(chalk14.yellow(`
3445
+ console.log(chalk13.yellow(`
3808
3446
  \u{1F4A1} ${t("undo.forcePushHint")}`));
3809
3447
  }
3810
3448
  printNextStep({
@@ -3813,39 +3451,39 @@ ${t("undo.recentHeader")}`));
3813
3451
  cursorHint: t("undo.nextCursor")
3814
3452
  });
3815
3453
  } catch (err) {
3816
- console.log(chalk14.red(`\u274C ${t("undo.failed")}`));
3454
+ console.log(chalk13.red(`\u274C ${t("undo.failed")}`));
3817
3455
  const msg = err instanceof Error ? err.message : String(err);
3818
- console.log(chalk14.red(msg));
3456
+ console.log(chalk13.red(msg));
3819
3457
  process.exitCode = 1;
3820
3458
  }
3821
3459
  }
3822
3460
 
3823
3461
  // src/commands/restore.ts
3824
- import chalk15 from "chalk";
3825
- import inquirer8 from "inquirer";
3462
+ import chalk14 from "chalk";
3463
+ import inquirer7 from "inquirer";
3826
3464
  async function restore(id) {
3827
- console.log(chalk15.bold(`
3465
+ console.log(chalk14.bold(`
3828
3466
  ${ko.restore.title}`));
3829
- console.log(chalk15.gray("\u2500".repeat(40)));
3830
- console.log(chalk15.dim(` ${ko.restore.notGitNote}`));
3467
+ console.log(chalk14.gray("\u2500".repeat(40)));
3468
+ console.log(chalk14.dim(` ${ko.restore.notGitNote}`));
3831
3469
  const cwd = process.cwd();
3832
3470
  const backups = listBackups(cwd);
3833
3471
  if (backups.length === 0) {
3834
- console.log(chalk15.yellow(`
3472
+ console.log(chalk14.yellow(`
3835
3473
  ${ko.restore.noBackups}`));
3836
3474
  return;
3837
3475
  }
3838
3476
  let targetId = id;
3839
3477
  if (!targetId) {
3840
3478
  if (!process.stdout.isTTY) {
3841
- console.log(chalk15.cyan(`
3479
+ console.log(chalk14.cyan(`
3842
3480
  ${ko.restore.listHeader}`));
3843
3481
  for (const b of backups) console.log(` ${b.id} (${b.files.length}\uAC1C \uD30C\uC77C)`);
3844
- console.log(chalk15.yellow(`
3482
+ console.log(chalk14.yellow(`
3845
3483
  ${ko.restore.nonTtyHint}`));
3846
3484
  return;
3847
3485
  }
3848
- const { picked } = await inquirer8.prompt([
3486
+ const { picked } = await inquirer7.prompt([
3849
3487
  {
3850
3488
  type: "list",
3851
3489
  name: "picked",
@@ -3860,26 +3498,26 @@ ${ko.restore.nonTtyHint}`));
3860
3498
  }
3861
3499
  try {
3862
3500
  const restored = restoreBackup(targetId, cwd);
3863
- console.log(chalk15.green(`
3501
+ console.log(chalk14.green(`
3864
3502
  ${ko.restore.restored(restored.length, targetId)}`));
3865
- for (const r of restored) console.log(chalk15.gray(` ${r}`));
3503
+ for (const r of restored) console.log(chalk14.gray(` ${r}`));
3866
3504
  printNextStep({
3867
3505
  message: "\uBC31\uC5C5 \uBCF5\uC6D0 \uC644\uB8CC! \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694.",
3868
3506
  command: "vhk diff",
3869
3507
  cursorHint: "\uBCC0\uACBD\uC0AC\uD56D \uBCF4\uC5EC\uC918"
3870
3508
  });
3871
3509
  } catch {
3872
- console.log(chalk15.red(`
3510
+ console.log(chalk14.red(`
3873
3511
  ${ko.restore.notFound(targetId)}`));
3874
3512
  process.exitCode = 1;
3875
3513
  }
3876
3514
  }
3877
3515
 
3878
3516
  // 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";
3517
+ import { execFileSync as execFileSync3 } from "child_process";
3518
+ import fs10 from "fs";
3519
+ import path11 from "path";
3520
+ import chalk15 from "chalk";
3883
3521
  function countFileChanges(porcelain) {
3884
3522
  const lines = porcelain.split("\n").filter(Boolean);
3885
3523
  let staged = 0;
@@ -3917,8 +3555,8 @@ function parseRecentCommitLines(logOutput) {
3917
3555
  return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
3918
3556
  }
3919
3557
  function readProjectPackage(cwd = process.cwd()) {
3920
- const pkgPath = path14.join(cwd, "package.json");
3921
- if (!fs13.existsSync(pkgPath)) return null;
3558
+ const pkgPath = path11.join(cwd, "package.json");
3559
+ if (!fs10.existsSync(pkgPath)) return null;
3922
3560
  try {
3923
3561
  const pkg = readJsonFile(pkgPath);
3924
3562
  if (!pkg.name && !pkg.version) return null;
@@ -3954,15 +3592,15 @@ function getSyncCounts(gitRoot) {
3954
3592
  }
3955
3593
  }
3956
3594
  async function status() {
3957
- console.log(chalk16.bold(`
3595
+ console.log(chalk15.bold(`
3958
3596
  \u{1F4CA} ${t("status.title")}`));
3959
- console.log(chalk16.gray("\u2500".repeat(40)));
3597
+ console.log(chalk15.gray("\u2500".repeat(40)));
3960
3598
  let gitRoot;
3961
3599
  try {
3962
- execFileSync4("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3600
+ execFileSync3("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
3963
3601
  gitRoot = getGitRoot();
3964
3602
  } catch {
3965
- console.log(chalk16.red(`\u274C ${t("status.notGitRepo")}`));
3603
+ console.log(chalk15.red(`\u274C ${t("status.notGitRepo")}`));
3966
3604
  return;
3967
3605
  }
3968
3606
  let branch;
@@ -3981,36 +3619,37 @@ async function status() {
3981
3619
  commits = [];
3982
3620
  }
3983
3621
  const pkg = readProjectPackage();
3984
- console.log(chalk16.cyan(`
3985
- \u{1F33F} ${t("status.branch")}`) + chalk16.white(` ${branch}`));
3622
+ console.log(chalk15.cyan(`
3623
+ \u{1F33F} ${t("status.branch")}`) + chalk15.white(` ${branch}`));
3986
3624
  console.log(
3987
- chalk16.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk16.white(
3625
+ chalk15.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk15.white(
3988
3626
  ` staged ${counts.staged} \xB7 unstaged ${counts.unstaged} \xB7 untracked ${counts.untracked}`
3989
3627
  )
3990
3628
  );
3991
- console.log(chalk16.cyan(`
3629
+ console.log(chalk15.cyan(`
3992
3630
  \u{1F4CB} ${t("status.recentCommits", commits.length)}`));
3993
3631
  if (commits.length === 0) {
3994
- console.log(chalk16.dim(` ${t("status.noCommits")}`));
3632
+ console.log(chalk15.dim(` ${t("status.noCommits")}`));
3995
3633
  } else {
3996
- commits.forEach((c) => console.log(` ${chalk16.dim("\u2022")} ${c}`));
3634
+ commits.forEach((c) => console.log(` ${chalk15.dim("\u2022")} ${c}`));
3997
3635
  }
3998
3636
  console.log(
3999
- chalk16.cyan(`
4000
- \u{1F504} ${t("status.remote")}`) + chalk16.white(` ${formatSyncLabel(sync2)}`)
3637
+ chalk15.cyan(`
3638
+ \u{1F504} ${t("status.remote")}`) + chalk15.white(` ${formatSyncLabel(sync2)}`)
4001
3639
  );
4002
- console.log(chalk16.gray("\n" + "\u2500".repeat(40)));
3640
+ console.log(chalk15.gray("\n" + "\u2500".repeat(40)));
4003
3641
  if (pkg) {
4004
- console.log(chalk16.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk16.white(` ${pkg.name} v${pkg.version}`));
3642
+ console.log(chalk15.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk15.white(` ${pkg.name} v${pkg.version}`));
4005
3643
  } else {
4006
- console.log(chalk16.dim(`\u{1F4E6} ${t("status.noPackage")}`));
3644
+ console.log(chalk15.dim(`\u{1F4E6} ${t("status.noPackage")}`));
4007
3645
  }
4008
3646
  const hasChanges = counts.staged + counts.unstaged + counts.untracked > 0;
4009
3647
  printNextStep(selectStatusNextStep(hasChanges));
3648
+ printContextResumeHint();
4010
3649
  }
4011
3650
 
4012
3651
  // src/commands/diff.ts
4013
- import chalk17 from "chalk";
3652
+ import chalk16 from "chalk";
4014
3653
  function gitOut2(args) {
4015
3654
  const r = safeExecFile("git", args);
4016
3655
  return r.ok ? r.out : "";
@@ -4047,67 +3686,67 @@ function summarizeNumstat(numstat) {
4047
3686
  return { fileCount, totalAdd, totalDel };
4048
3687
  }
4049
3688
  function printFile(f) {
4050
- const adds = f.additions > 0 ? chalk17.green(`+${f.additions}`) : "";
4051
- const dels = f.deletions > 0 ? chalk17.red(`-${f.deletions}`) : "";
3689
+ const adds = f.additions > 0 ? chalk16.green(`+${f.additions}`) : "";
3690
+ const dels = f.deletions > 0 ? chalk16.red(`-${f.deletions}`) : "";
4052
3691
  const change = [adds, dels].filter(Boolean).join(" ");
4053
3692
  console.log(` ${f.name} ${change}`);
4054
3693
  }
4055
3694
  async function diff() {
4056
- console.log(chalk17.bold(`
3695
+ console.log(chalk16.bold(`
4057
3696
  \u{1F50D} ${t("diff.title")}`));
4058
- console.log(chalk17.gray("\u2500".repeat(40)));
3697
+ console.log(chalk16.gray("\u2500".repeat(40)));
4059
3698
  if (!safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok) {
4060
- console.log(chalk17.red(`\u274C ${t("diff.notGitRepo")}`));
3699
+ console.log(chalk16.red(`\u274C ${t("diff.notGitRepo")}`));
4061
3700
  return;
4062
3701
  }
4063
3702
  const unstaged = gitOut2(["diff", "--stat"]);
4064
3703
  const staged = gitOut2(["diff", "--cached", "--stat"]);
4065
3704
  const untracked = gitOut2(["ls-files", "--others", "--exclude-standard"]);
4066
3705
  if (!unstaged && !staged && !untracked) {
4067
- console.log(chalk17.green(`
3706
+ console.log(chalk16.green(`
4068
3707
  \u2705 ${t("diff.noChanges")}`));
4069
3708
  return;
4070
3709
  }
4071
3710
  if (staged) {
4072
- console.log(chalk17.cyan(`
3711
+ console.log(chalk16.cyan(`
4073
3712
  ${t("diff.stagedHeader")}`));
4074
3713
  parseDiffStat(staged).forEach((f) => printFile(f));
4075
3714
  }
4076
3715
  if (unstaged) {
4077
- console.log(chalk17.cyan(`
3716
+ console.log(chalk16.cyan(`
4078
3717
  ${t("diff.unstagedHeader")}`));
4079
3718
  parseDiffStat(unstaged).forEach((f) => printFile(f));
4080
3719
  }
4081
3720
  if (untracked) {
4082
3721
  const files = untracked.split("\n").filter(Boolean);
4083
- console.log(chalk17.cyan(`
3722
+ console.log(chalk16.cyan(`
4084
3723
  ${t("diff.untrackedHeader", files.length)}`));
4085
- files.forEach((f) => console.log(` ${chalk17.green("+")} ${f}`));
3724
+ files.forEach((f) => console.log(` ${chalk16.green("+")} ${f}`));
4086
3725
  }
4087
3726
  const numstat = gitOut2(["diff", "--numstat", "HEAD"]);
4088
3727
  if (numstat) {
4089
3728
  const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
4090
- console.log(chalk17.cyan(`
3729
+ console.log(chalk16.cyan(`
4091
3730
  ${t("diff.summaryHeader")}`));
4092
3731
  console.log(` ${t("diff.filesLine", fileCount)}`);
4093
- console.log(` \uCD94\uAC00: ${chalk17.green(`+${totalAdd}`)}\uC904`);
4094
- console.log(` \uC0AD\uC81C: ${chalk17.red(`-${totalDel}`)}\uC904`);
3732
+ console.log(` \uCD94\uAC00: ${chalk16.green(`+${totalAdd}`)}\uC904`);
3733
+ console.log(` \uC0AD\uC81C: ${chalk16.red(`-${totalDel}`)}\uC904`);
4095
3734
  }
4096
3735
  console.log("");
4097
3736
  }
4098
3737
 
4099
3738
  // 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";
3739
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
3740
+ import { join as join5, dirname } from "path";
4102
3741
  import { fileURLToPath as fileURLToPath2 } from "url";
4103
- import chalk18 from "chalk";
3742
+ import chalk17 from "chalk";
4104
3743
  function resolveMcpEntryPoint() {
4105
3744
  try {
4106
3745
  const here = fileURLToPath2(import.meta.url);
4107
3746
  const dir = dirname(here);
4108
3747
  for (const rel of [["mcp", "index.js"], ["..", "mcp", "index.js"]]) {
4109
- const candidate = join6(dir, ...rel);
4110
- if (existsSync5(candidate)) return candidate;
3748
+ const candidate = join5(dir, ...rel);
3749
+ if (existsSync4(candidate)) return candidate;
4111
3750
  }
4112
3751
  } catch {
4113
3752
  }
@@ -4115,17 +3754,17 @@ function resolveMcpEntryPoint() {
4115
3754
  const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
4116
3755
  if (typeof url === "string") {
4117
3756
  const p = fileURLToPath2(url);
4118
- if (existsSync5(p)) return p;
3757
+ if (existsSync4(p)) return p;
4119
3758
  }
4120
3759
  } catch {
4121
3760
  }
4122
3761
  try {
4123
- const pkgPath = join6(process.cwd(), "package.json");
4124
- if (existsSync5(pkgPath)) {
3762
+ const pkgPath = join5(process.cwd(), "package.json");
3763
+ if (existsSync4(pkgPath)) {
4125
3764
  const pkg = readJsonFile(pkgPath);
4126
3765
  if (pkg.name === "@byh3071/vhk") {
4127
- const local = join6(process.cwd(), "dist", "mcp", "index.js");
4128
- if (existsSync5(local)) return local;
3766
+ const local = join5(process.cwd(), "dist", "mcp", "index.js");
3767
+ if (existsSync4(local)) return local;
4129
3768
  }
4130
3769
  }
4131
3770
  } catch {
@@ -4140,31 +3779,31 @@ function resolveVhkMcpEntry() {
4140
3779
  return { command: "vhk-mcp", args: [] };
4141
3780
  }
4142
3781
  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)) {
3782
+ console.log(chalk17.bold("\n\u{1F50C} " + t("mcp.initTitle")));
3783
+ console.log(chalk17.gray("\u2500".repeat(40)));
3784
+ const cursorDir = join5(process.cwd(), ".cursor");
3785
+ if (!existsSync4(cursorDir)) {
4147
3786
  mkdirSync3(cursorDir, { recursive: true });
4148
3787
  }
4149
- const configPath = join6(cursorDir, "mcp.json");
3788
+ const configPath = join5(cursorDir, "mcp.json");
4150
3789
  const vhkEntry = resolveVhkMcpEntry();
4151
3790
  let config;
4152
- if (existsSync5(configPath)) {
3791
+ if (existsSync4(configPath)) {
4153
3792
  try {
4154
3793
  const parsed = readJsonFile(configPath);
4155
3794
  config = {
4156
3795
  mcpServers: { ...parsed.mcpServers ?? {}, vhk: vhkEntry }
4157
3796
  };
4158
3797
  } 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."));
3798
+ 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
3799
  config = { mcpServers: { vhk: vhkEntry } };
4161
3800
  }
4162
3801
  } else {
4163
3802
  config = { mcpServers: { vhk: vhkEntry } };
4164
3803
  }
4165
3804
  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:"));
3805
+ console.log(chalk17.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
3806
+ console.log(chalk17.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
4168
3807
  console.log(` ${configPath}`);
4169
3808
  printNextStep({
4170
3809
  message: t("mcp.nextMessage"),
@@ -4174,9 +3813,9 @@ async function mcpInit() {
4174
3813
  }
4175
3814
 
4176
3815
  // 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";
3816
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
3817
+ import chalk18 from "chalk";
3818
+ import inquirer8 from "inquirer";
4180
3819
  var PALETTES = [
4181
3820
  {
4182
3821
  name: "Minimal",
@@ -4228,7 +3867,7 @@ var PALETTES = [
4228
3867
  }
4229
3868
  ];
4230
3869
  function hasTailwind() {
4231
- return existsSync6("tailwind.config.js") || existsSync6("tailwind.config.ts") || existsSync6("tailwind.config.mjs") || existsSync6("tailwind.config.cjs");
3870
+ return existsSync5("tailwind.config.js") || existsSync5("tailwind.config.ts") || existsSync5("tailwind.config.mjs") || existsSync5("tailwind.config.cjs");
4232
3871
  }
4233
3872
  function isTailwindV4Deps(deps) {
4234
3873
  if (deps["@tailwindcss/vite"] || deps["@tailwindcss/postcss"]) return true;
@@ -4278,10 +3917,10 @@ export default vhkColors
4278
3917
  `;
4279
3918
  }
4280
3919
  async function design() {
4281
- console.log(chalk19.bold("\n\u{1F3A8} " + t("design.title")));
4282
- console.log(chalk19.gray("\u2500".repeat(40)));
3920
+ console.log(chalk18.bold("\n\u{1F3A8} " + t("design.title")));
3921
+ console.log(chalk18.gray("\u2500".repeat(40)));
4283
3922
  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([
3923
+ const { paletteIndex } = await inquirer8.prompt([
4285
3924
  {
4286
3925
  type: "list",
4287
3926
  name: "paletteIndex",
@@ -4293,37 +3932,37 @@ async function design() {
4293
3932
  }
4294
3933
  ]);
4295
3934
  const palette = PALETTES[paletteIndex];
4296
- console.log(chalk19.cyan(`
3935
+ console.log(chalk18.cyan(`
4297
3936
  \u{1F3A8} \uC120\uD0DD\uB41C \uD314\uB808\uD2B8: ${palette.name}`));
4298
3937
  const v4 = hasTailwindV4();
4299
3938
  const targetPath = v4 ? "src/styles/theme.css" : hasTailwind() ? "src/styles/vhk-colors.ts" : "src/styles/tokens.css";
4300
3939
  const content = v4 ? generateTailwindV4Theme(palette) : hasTailwind() ? generateTailwindExtend(palette) : generateCSSTokens(palette);
4301
- if (existsSync6(targetPath)) {
4302
- const { overwrite } = await inquirer9.prompt([{
3940
+ if (existsSync5(targetPath)) {
3941
+ const { overwrite } = await inquirer8.prompt([{
4303
3942
  type: "confirm",
4304
3943
  name: "overwrite",
4305
3944
  message: `${targetPath} \uC774\uBBF8 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?`,
4306
3945
  default: false
4307
3946
  }]);
4308
3947
  if (!overwrite) {
4309
- console.log(chalk19.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
3948
+ console.log(chalk18.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
4310
3949
  return;
4311
3950
  }
4312
3951
  }
4313
3952
  mkdirSync4("src/styles", { recursive: true });
4314
3953
  writeFileSync4(targetPath, content, "utf-8");
4315
3954
  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."));
3955
+ console.log(chalk18.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (Tailwind v4 @theme)"));
3956
+ 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.'));
3957
+ console.log(chalk18.gray(" \uB2E4\uD06C \uD1A0\uAE00: \uB8E8\uD2B8 <html>/<body> \uC5D0 `.dark` \uD074\uB798\uC2A4 on/off."));
4319
3958
  } 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."));
3959
+ console.log(chalk18.green("\n\u2705 src/styles/vhk-colors.ts \uC0DD\uC131"));
3960
+ console.log(chalk18.gray(" tailwind.config\uC758 extend.colors\uC5D0 import \uD574\uC11C \uC0AC\uC6A9\uD558\uC138\uC694."));
4322
3961
  } 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."));
3962
+ console.log(chalk18.green("\n\u2705 src/styles/tokens.css \uC0DD\uC131"));
3963
+ console.log(chalk18.gray(" HTML\uC5D0 <link>\uB85C \uCD94\uAC00\uD558\uAC70\uB098 CSS\uC5D0\uC11C @import \uD558\uC138\uC694."));
4325
3964
  }
4326
- console.log(chalk19.bold("\n\u{1F308} \uCEEC\uB7EC \uBBF8\uB9AC\uBCF4\uAE30:"));
3965
+ console.log(chalk18.bold("\n\u{1F308} \uCEEC\uB7EC \uBBF8\uB9AC\uBCF4\uAE30:"));
4327
3966
  for (const [key, value] of Object.entries(palette.colors)) {
4328
3967
  console.log(` ${key.padEnd(12)} ${value}`);
4329
3968
  }
@@ -4338,9 +3977,9 @@ async function designPalette() {
4338
3977
  }
4339
3978
 
4340
3979
  // 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";
3980
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
3981
+ import chalk19 from "chalk";
3982
+ import inquirer9 from "inquirer";
4344
3983
  function generateDarkCSS() {
4345
3984
  return `/* vhk theme \u2014 \uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC CSS \uBCC0\uC218 */
4346
3985
 
@@ -4396,13 +4035,13 @@ export function initTheme(): void {
4396
4035
  `;
4397
4036
  }
4398
4037
  async function theme() {
4399
- console.log(chalk20.bold("\n\u{1F319} " + t("theme.title")));
4400
- console.log(chalk20.gray("\u2500".repeat(40)));
4038
+ console.log(chalk19.bold("\n\u{1F319} " + t("theme.title")));
4039
+ console.log(chalk19.gray("\u2500".repeat(40)));
4401
4040
  const cssPath = "src/styles/theme.css";
4402
4041
  const togglePath = "src/lib/theme-toggle.ts";
4403
- const conflicts = [cssPath, togglePath].filter((p) => existsSync7(p));
4042
+ const conflicts = [cssPath, togglePath].filter((p) => existsSync6(p));
4404
4043
  if (conflicts.length > 0) {
4405
- const { overwrite } = await inquirer10.prompt([{
4044
+ const { overwrite } = await inquirer9.prompt([{
4406
4045
  type: "confirm",
4407
4046
  name: "overwrite",
4408
4047
  message: `\uB2E4\uC74C \uD30C\uC77C\uC774 \uC774\uBBF8 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?
@@ -4410,21 +4049,21 @@ async function theme() {
4410
4049
  default: false
4411
4050
  }]);
4412
4051
  if (!overwrite) {
4413
- console.log(chalk20.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
4052
+ console.log(chalk19.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
4414
4053
  return;
4415
4054
  }
4416
4055
  }
4417
4056
  mkdirSync5("src/styles", { recursive: true });
4418
4057
  mkdirSync5("src/lib", { recursive: true });
4419
4058
  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)"));
4059
+ console.log(chalk19.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"));
4421
4060
  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"));
4061
+ console.log(chalk19.green("\u2705 src/lib/theme-toggle.ts \uC0DD\uC131 (\uD1A0\uAE00 \uC720\uD2F8\uB9AC\uD2F0)"));
4062
+ console.log(chalk19.bold("\n\u{1F4D6} \uC0AC\uC6A9\uBC95:"));
4063
+ console.log(chalk19.gray(" 1. theme.css\uB97C \uAE00\uB85C\uBC8C \uC2A4\uD0C0\uC77C\uC5D0 \uCD94\uAC00"));
4064
+ console.log(chalk19.gray(' 2. import { initTheme, toggleTheme } from "./lib/theme-toggle"'));
4065
+ console.log(chalk19.gray(" 3. \uC571 \uC9C4\uC785\uC810\uC5D0\uC11C initTheme() \uD638\uCD9C"));
4066
+ console.log(chalk19.gray(" 4. \uD1A0\uAE00 \uBC84\uD2BC\uC5D0\uC11C toggleTheme() \uD638\uCD9C"));
4428
4067
  printNextStep({
4429
4068
  message: "\uD14C\uB9C8 \uC124\uC815 \uC644\uB8CC!",
4430
4069
  command: "vhk ref list",
@@ -4433,11 +4072,11 @@ async function theme() {
4433
4072
  }
4434
4073
 
4435
4074
  // src/commands/ref.ts
4436
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
4437
- import chalk21 from "chalk";
4075
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
4076
+ import chalk20 from "chalk";
4438
4077
  var REFS_PATH = ".vhk/refs.json";
4439
4078
  function loadRefs() {
4440
- if (!existsSync8(REFS_PATH)) return [];
4079
+ if (!existsSync7(REFS_PATH)) return [];
4441
4080
  try {
4442
4081
  const parsed = readJsonFile(REFS_PATH);
4443
4082
  return Array.isArray(parsed) ? parsed : [];
@@ -4450,24 +4089,24 @@ function saveRefs(refs) {
4450
4089
  writeFileSync6(REFS_PATH, JSON.stringify(refs, null, 2) + "\n", "utf-8");
4451
4090
  }
4452
4091
  async function refAdd(url, memo = "") {
4453
- console.log(chalk21.bold("\n\u{1F517} " + t("ref.addTitle")));
4454
- console.log(chalk21.gray("\u2500".repeat(40)));
4092
+ console.log(chalk20.bold("\n\u{1F517} " + t("ref.addTitle")));
4093
+ console.log(chalk20.gray("\u2500".repeat(40)));
4455
4094
  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"'));
4095
+ console.log(chalk20.red("\u274C URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4096
+ console.log(chalk20.gray(' \uC608: vhk ref add https://example.com --memo "\uCC38\uACE0 \uC0AC\uC774\uD2B8"'));
4458
4097
  return;
4459
4098
  }
4460
4099
  const refs = loadRefs();
4461
4100
  if (refs.some((r) => r.url === url)) {
4462
- console.log(chalk21.yellow("\u26A0\uFE0F \uC774\uBBF8 \uC800\uC7A5\uB41C URL\uC785\uB2C8\uB2E4."));
4101
+ console.log(chalk20.yellow("\u26A0\uFE0F \uC774\uBBF8 \uC800\uC7A5\uB41C URL\uC785\uB2C8\uB2E4."));
4463
4102
  return;
4464
4103
  }
4465
4104
  refs.push({ url, memo, addedAt: (/* @__PURE__ */ new Date()).toISOString() });
4466
4105
  saveRefs(refs);
4467
- console.log(chalk21.green(`
4106
+ console.log(chalk20.green(`
4468
4107
  \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}`));
4108
+ console.log(chalk20.cyan(` ${url}`));
4109
+ if (memo) console.log(chalk20.gray(` \u{1F4DD} ${memo}`));
4471
4110
  printNextStep({
4472
4111
  message: "\uB808\uD37C\uB7F0\uC2A4 \uC800\uC7A5 \uC644\uB8CC!",
4473
4112
  command: "vhk ref list",
@@ -4475,22 +4114,22 @@ async function refAdd(url, memo = "") {
4475
4114
  });
4476
4115
  }
4477
4116
  async function refList() {
4478
- console.log(chalk21.bold("\n\u{1F4DA} " + t("ref.listTitle")));
4479
- console.log(chalk21.gray("\u2500".repeat(40)));
4117
+ console.log(chalk20.bold("\n\u{1F4DA} " + t("ref.listTitle")));
4118
+ console.log(chalk20.gray("\u2500".repeat(40)));
4480
4119
  const refs = loadRefs();
4481
4120
  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.'));
4121
+ console.log(chalk20.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uB808\uD37C\uB7F0\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4122
+ console.log(chalk20.gray(' vhk ref add <url> --memo "\uBA54\uBAA8"\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
4484
4123
  return;
4485
4124
  }
4486
- console.log(chalk21.cyan(`
4125
+ console.log(chalk20.cyan(`
4487
4126
  \uCD1D ${refs.length}\uAC1C\uC758 \uB808\uD37C\uB7F0\uC2A4:
4488
4127
  `));
4489
4128
  refs.forEach((ref, index) => {
4490
4129
  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}`));
4130
+ console.log(chalk20.white(` [${index + 1}] ${ref.url}`));
4131
+ if (ref.memo) console.log(chalk20.gray(` \u{1F4DD} ${ref.memo}`));
4132
+ console.log(chalk20.gray(` \u{1F4C5} ${date}`));
4494
4133
  console.log("");
4495
4134
  });
4496
4135
  }
@@ -4498,7 +4137,7 @@ async function refOpen(indexStr) {
4498
4137
  const refs = loadRefs();
4499
4138
  const idx = parseInt(indexStr, 10) - 1;
4500
4139
  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})`));
4140
+ console.log(chalk20.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${refs.length || 0})`));
4502
4141
  return;
4503
4142
  }
4504
4143
  const ref = refs[idx];
@@ -4506,14 +4145,14 @@ async function refOpen(indexStr) {
4506
4145
  try {
4507
4146
  parsed = new URL(ref.url);
4508
4147
  } catch {
4509
- console.log(chalk21.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 URL: ${ref.url}`));
4148
+ console.log(chalk20.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 URL: ${ref.url}`));
4510
4149
  return;
4511
4150
  }
4512
4151
  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})`));
4152
+ console.log(chalk20.red(`\u274C http(s) URL\uB9CC \uC5F4 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (${parsed.protocol})`));
4514
4153
  return;
4515
4154
  }
4516
- console.log(chalk21.cyan(`
4155
+ console.log(chalk20.cyan(`
4517
4156
  \u{1F310} \uC5F4\uAE30: ${ref.url}`));
4518
4157
  let result;
4519
4158
  if (process.platform === "darwin") {
@@ -4524,19 +4163,19 @@ async function refOpen(indexStr) {
4524
4163
  result = safeExecFile("xdg-open", [ref.url]);
4525
4164
  }
4526
4165
  if (result.ok) {
4527
- console.log(chalk21.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
4166
+ console.log(chalk20.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
4528
4167
  } 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."));
4168
+ 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
4169
  }
4531
4170
  }
4532
4171
 
4533
4172
  // src/commands/harness.ts
4534
- import { existsSync as existsSync9 } from "fs";
4535
- import chalk22 from "chalk";
4173
+ import { existsSync as existsSync8 } from "fs";
4174
+ import chalk21 from "chalk";
4536
4175
  import ora2 from "ora";
4537
4176
  function detectPM() {
4538
- if (existsSync9("pnpm-lock.yaml")) return "pnpm";
4539
- if (existsSync9("yarn.lock")) return "yarn";
4177
+ if (existsSync8("pnpm-lock.yaml")) return "pnpm";
4178
+ if (existsSync8("yarn.lock")) return "yarn";
4540
4179
  return "npm";
4541
4180
  }
4542
4181
  function pmRun(pm, script) {
@@ -4554,14 +4193,14 @@ function detectChecks() {
4554
4193
  const pm = detectPM();
4555
4194
  if (s.lint) {
4556
4195
  checks.push({ name: "lint", bin: pm, args: pmRun(pm, "lint") });
4557
- } else if (existsSync9(".eslintrc.js") || existsSync9(".eslintrc.json") || existsSync9("eslint.config.js")) {
4196
+ } else if (existsSync8(".eslintrc.js") || existsSync8(".eslintrc.json") || existsSync8("eslint.config.js")) {
4558
4197
  checks.push({ name: "lint", bin: "npx", args: ["eslint", ".", "--ext", ".ts,.tsx"] });
4559
4198
  }
4560
4199
  if (s["type-check"]) {
4561
4200
  checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "type-check") });
4562
4201
  } else if (s.typecheck) {
4563
4202
  checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "typecheck") });
4564
- } else if (existsSync9("tsconfig.json")) {
4203
+ } else if (existsSync8("tsconfig.json")) {
4565
4204
  checks.push({ name: "type-check", bin: "npx", args: ["tsc", "--noEmit"] });
4566
4205
  }
4567
4206
  if (s.test) {
@@ -4574,15 +4213,15 @@ function detectChecks() {
4574
4213
  }
4575
4214
  async function harness() {
4576
4215
  if (!ensureNotHardStopped("harness")) return;
4577
- console.log(chalk22.bold("\n\u{1F527} " + t("harness.title")));
4578
- console.log(chalk22.gray("\u2500".repeat(40)));
4216
+ console.log(chalk21.bold("\n\u{1F527} " + t("harness.title")));
4217
+ console.log(chalk21.gray("\u2500".repeat(40)));
4579
4218
  const checks = detectChecks();
4580
4219
  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."));
4220
+ console.log(chalk21.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
4221
+ console.log(chalk21.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
4583
4222
  return;
4584
4223
  }
4585
- console.log(chalk22.cyan(`
4224
+ console.log(chalk21.cyan(`
4586
4225
  \u{1F3C3} ${checks.length}\uAC1C \uC810\uAC80 \uC2DC\uC791:
4587
4226
  `));
4588
4227
  const results = [];
@@ -4594,10 +4233,10 @@ async function harness() {
4594
4233
  const duration = Date.now() - start2;
4595
4234
  const sec = (duration / 1e3).toFixed(1);
4596
4235
  if (result.ok) {
4597
- spinner.succeed(`${check2.name} ${chalk22.gray(`(${sec}s)`)}`);
4236
+ spinner.succeed(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
4598
4237
  results.push({ name: check2.name, command: display, passed: true, duration });
4599
4238
  } else {
4600
- spinner.fail(`${check2.name} ${chalk22.gray(`(${sec}s)`)}`);
4239
+ spinner.fail(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
4601
4240
  results.push({
4602
4241
  name: check2.name,
4603
4242
  command: display,
@@ -4607,22 +4246,22 @@ async function harness() {
4607
4246
  });
4608
4247
  }
4609
4248
  }
4610
- console.log(chalk22.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
4611
- console.log(chalk22.gray("\u2500".repeat(40)));
4249
+ console.log(chalk21.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
4250
+ console.log(chalk21.gray("\u2500".repeat(40)));
4612
4251
  for (const r of results) {
4613
- const icon = r.passed ? chalk22.green("\u2705") : chalk22.red("\u274C");
4252
+ const icon = r.passed ? chalk21.green("\u2705") : chalk21.red("\u274C");
4614
4253
  const sec = (r.duration / 1e3).toFixed(1);
4615
- console.log(` ${icon} ${r.name.padEnd(15)} ${chalk22.gray(`${sec}s`)}`);
4254
+ console.log(` ${icon} ${r.name.padEnd(15)} ${chalk21.gray(`${sec}s`)}`);
4616
4255
  }
4617
4256
  const passed = results.filter((r) => r.passed).length;
4618
4257
  const all = passed === results.length;
4619
- console.log(chalk22.gray("\u2500".repeat(40)));
4258
+ console.log(chalk21.gray("\u2500".repeat(40)));
4620
4259
  if (all) {
4621
- console.log(chalk22.green.bold(`
4260
+ console.log(chalk21.green.bold(`
4622
4261
  \u{1F389} \uC804\uCCB4 \uD1B5\uACFC! (${passed}/${results.length})`));
4623
4262
  } else {
4624
4263
  console.log(
4625
- chalk22.red.bold(`
4264
+ chalk21.red.bold(`
4626
4265
  \u26A0\uFE0F ${results.length - passed}\uAC1C \uC2E4\uD328 (${passed}/${results.length} \uD1B5\uACFC)`)
4627
4266
  );
4628
4267
  process.exitCode = 1;
@@ -4635,9 +4274,9 @@ async function harness() {
4635
4274
  }
4636
4275
 
4637
4276
  // 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";
4277
+ import { existsSync as existsSync9, unlinkSync, rmSync as rmSync2 } from "fs";
4278
+ import chalk22 from "chalk";
4279
+ import inquirer10 from "inquirer";
4641
4280
  import ora3 from "ora";
4642
4281
  var LOCK_FILES = {
4643
4282
  npm: "package-lock.json",
@@ -4645,26 +4284,26 @@ var LOCK_FILES = {
4645
4284
  pnpm: "pnpm-lock.yaml"
4646
4285
  };
4647
4286
  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";
4287
+ if (existsSync9("pnpm-lock.yaml")) return "pnpm";
4288
+ if (existsSync9("yarn.lock")) return "yarn";
4289
+ if (existsSync9("package-lock.json")) return "npm";
4651
4290
  return null;
4652
4291
  }
4653
4292
  function isCLIAvailable(pm) {
4654
4293
  return safeExecFile(pm, ["--version"]).ok;
4655
4294
  }
4656
4295
  async function migrate(target) {
4657
- console.log(chalk23.bold("\n\u{1F504} " + t("migrate.title")));
4658
- console.log(chalk23.gray("\u2500".repeat(40)));
4296
+ console.log(chalk22.bold("\n\u{1F504} " + t("migrate.title")));
4297
+ console.log(chalk22.gray("\u2500".repeat(40)));
4659
4298
  const current = detectCurrentPM();
4660
- console.log(chalk23.cyan(`
4299
+ console.log(chalk22.cyan(`
4661
4300
  \uD604\uC7AC \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00"}`));
4662
4301
  let targetPM;
4663
4302
  if (target && ["npm", "yarn", "pnpm"].includes(target)) {
4664
4303
  targetPM = target;
4665
4304
  } else {
4666
4305
  const choices = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current).map((pm) => ({ name: pm, value: pm }));
4667
- const { selected } = await inquirer11.prompt([
4306
+ const { selected } = await inquirer10.prompt([
4668
4307
  {
4669
4308
  type: "list",
4670
4309
  name: "selected",
@@ -4675,17 +4314,17 @@ async function migrate(target) {
4675
4314
  targetPM = selected;
4676
4315
  }
4677
4316
  if (targetPM === current) {
4678
- console.log(chalk23.yellow(`
4317
+ console.log(chalk22.yellow(`
4679
4318
  \u26A0\uFE0F \uC774\uBBF8 ${targetPM}\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4.`));
4680
4319
  return;
4681
4320
  }
4682
4321
  if (!isCLIAvailable(targetPM)) {
4683
- console.log(chalk23.red(`
4322
+ console.log(chalk22.red(`
4684
4323
  \u274C ${targetPM}\uC774 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
4685
- console.log(chalk23.yellow(` npm i -g ${targetPM}`));
4324
+ console.log(chalk22.yellow(` npm i -g ${targetPM}`));
4686
4325
  return;
4687
4326
  }
4688
- const { confirm } = await inquirer11.prompt([
4327
+ const { confirm } = await inquirer10.prompt([
4689
4328
  {
4690
4329
  type: "confirm",
4691
4330
  name: "confirm",
@@ -4694,16 +4333,16 @@ async function migrate(target) {
4694
4333
  }
4695
4334
  ]);
4696
4335
  if (!confirm) {
4697
- console.log(chalk23.gray("\uCDE8\uC18C\uB428"));
4336
+ console.log(chalk22.gray("\uCDE8\uC18C\uB428"));
4698
4337
  return;
4699
4338
  }
4700
4339
  const cleanup = ora3("\uAE30\uC874 lock \uD30C\uC77C \uC815\uB9AC \uC911...").start();
4701
4340
  for (const lockFile of Object.values(LOCK_FILES)) {
4702
- if (existsSync10(lockFile)) {
4341
+ if (existsSync9(lockFile)) {
4703
4342
  unlinkSync(lockFile);
4704
4343
  }
4705
4344
  }
4706
- if (existsSync10("node_modules")) {
4345
+ if (existsSync9("node_modules")) {
4707
4346
  cleanup.text = "node_modules \uC0AD\uC81C \uC911...";
4708
4347
  rmSync2("node_modules", { recursive: true, force: true });
4709
4348
  }
@@ -4714,10 +4353,10 @@ async function migrate(target) {
4714
4353
  install.succeed(`${targetPM} install \uC644\uB8CC!`);
4715
4354
  } else {
4716
4355
  install.fail(`${targetPM} install \uC2E4\uD328`);
4717
- console.log(chalk23.red(installResult.err.slice(0, 300)));
4356
+ console.log(chalk22.red(installResult.err.slice(0, 300)));
4718
4357
  return;
4719
4358
  }
4720
- console.log(chalk23.green.bold(`
4359
+ console.log(chalk22.green.bold(`
4721
4360
  \u{1F389} ${current ?? "\uC774\uC804"} \u2192 ${targetPM} \uC804\uD658 \uC644\uB8CC!`));
4722
4361
  printNextStep({
4723
4362
  message: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 \uC644\uB8CC!",
@@ -4727,17 +4366,17 @@ async function migrate(target) {
4727
4366
  }
4728
4367
 
4729
4368
  // src/commands/update.ts
4730
- import { existsSync as existsSync11 } from "fs";
4731
- import { dirname as dirname2, join as join7 } from "path";
4369
+ import { existsSync as existsSync10 } from "fs";
4370
+ import { dirname as dirname2, join as join6 } from "path";
4732
4371
  import { fileURLToPath as fileURLToPath3 } from "url";
4733
- import chalk24 from "chalk";
4372
+ import chalk23 from "chalk";
4734
4373
  import ora4 from "ora";
4735
4374
  var PACKAGE = "@byh3071/vhk";
4736
4375
  function getCurrentVersion() {
4737
4376
  const dir = dirname2(fileURLToPath3(import.meta.url));
4738
- for (const pkgPath of [join7(dir, "../package.json"), join7(dir, "../../package.json")]) {
4377
+ for (const pkgPath of [join6(dir, "../package.json"), join6(dir, "../../package.json")]) {
4739
4378
  try {
4740
- if (existsSync11(pkgPath)) {
4379
+ if (existsSync10(pkgPath)) {
4741
4380
  const pkg = readJsonFile(pkgPath);
4742
4381
  if (pkg.version) return pkg.version;
4743
4382
  }
@@ -4760,32 +4399,32 @@ function isUpToDate(current, latest) {
4760
4399
  return cc >= lc;
4761
4400
  }
4762
4401
  async function update() {
4763
- console.log(chalk24.bold("\n\u2B06\uFE0F " + t("update.title")));
4764
- console.log(chalk24.gray("\u2500".repeat(40)));
4402
+ console.log(chalk23.bold("\n\u2B06\uFE0F " + t("update.title")));
4403
+ console.log(chalk23.gray("\u2500".repeat(40)));
4765
4404
  const current = getCurrentVersion();
4766
- console.log(chalk24.cyan(`
4405
+ console.log(chalk23.cyan(`
4767
4406
  \u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${current}`));
4768
4407
  const spinner = ora4("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...").start();
4769
4408
  const latest = getLatestVersion();
4770
4409
  if (!latest) {
4771
4410
  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}`));
4411
+ 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:"));
4412
+ console.log(chalk23.gray(` npm update -g ${PACKAGE}`));
4774
4413
  return;
4775
4414
  }
4776
4415
  spinner.stop();
4777
- console.log(chalk24.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
4416
+ console.log(chalk23.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
4778
4417
  if (isUpToDate(current, latest)) {
4779
- console.log(chalk24.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
4418
+ console.log(chalk23.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
4780
4419
  return;
4781
4420
  }
4782
4421
  const updateSpinner = ora4(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC911...`).start();
4783
4422
  const upd = safeExecFile("npm", ["update", "-g", PACKAGE]);
4784
4423
  if (upd.ok) {
4785
4424
  updateSpinner.succeed(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`);
4786
- console.log(chalk24.green.bold(`
4425
+ console.log(chalk23.green.bold(`
4787
4426
  \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."));
4427
+ console.log(chalk23.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
4789
4428
  printNextStep({
4790
4429
  message: t("update.nextOkMessage"),
4791
4430
  command: "vhk --version",
@@ -4793,9 +4432,9 @@ async function update() {
4793
4432
  });
4794
4433
  } else {
4795
4434
  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}`));
4435
+ console.log(chalk23.red(upd.err.slice(0, 300)));
4436
+ console.log(chalk23.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
4437
+ console.log(chalk23.gray(` npm update -g ${PACKAGE}`));
4799
4438
  printNextStep({
4800
4439
  message: t("update.nextFailMessage"),
4801
4440
  command: "vhk doctor",
@@ -4806,16 +4445,16 @@ async function update() {
4806
4445
 
4807
4446
  // src/commands/context.ts
4808
4447
  import {
4809
- existsSync as existsSync12,
4448
+ existsSync as existsSync11,
4810
4449
  mkdirSync as mkdirSync7,
4811
- readFileSync as readFileSync5,
4450
+ readFileSync as readFileSync4,
4812
4451
  readdirSync as readdirSync2,
4813
4452
  statSync as statSync2,
4814
4453
  writeFileSync as writeFileSync7
4815
4454
  } from "fs";
4816
- import { join as join8 } from "path";
4817
- import chalk25 from "chalk";
4818
- var CONTEXT_PATH2 = ".vhk/context.md";
4455
+ import { join as join7 } from "path";
4456
+ import chalk24 from "chalk";
4457
+ var CONTEXT_PATH = ".vhk/context.md";
4819
4458
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
4820
4459
  "node_modules",
4821
4460
  ".git",
@@ -4839,7 +4478,7 @@ function buildTree(dir, prefix = "", maxDepth = 3, depth = 0) {
4839
4478
  filtered.forEach((entry, index) => {
4840
4479
  const isLast = index === filtered.length - 1;
4841
4480
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4842
- const fullPath = join8(dir, entry);
4481
+ const fullPath = join7(dir, entry);
4843
4482
  const stat = statSync2(fullPath);
4844
4483
  const isDir = stat.isDirectory();
4845
4484
  lines.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
@@ -4871,8 +4510,8 @@ function extractTechStack() {
4871
4510
  else if (all.jest) stack["\uD14C\uC2A4\uD2B8"] = "jest";
4872
4511
  if (all.commander) stack["CLI"] = "commander";
4873
4512
  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";
4513
+ if (existsSync11("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
4514
+ else if (existsSync11("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
4876
4515
  else stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "npm";
4877
4516
  if (pkg.name) stack["\uD328\uD0A4\uC9C0 \uC774\uB984"] = pkg.name;
4878
4517
  if (pkg.version) stack["\uBC84\uC804"] = pkg.version;
@@ -4915,8 +4554,8 @@ function getVhkCommands() {
4915
4554
  }
4916
4555
  async function context(opts = {}) {
4917
4556
  const compact = opts.compact === true;
4918
- console.log(chalk25.bold("\n\u{1F9E0} " + t("context.title")));
4919
- console.log(chalk25.gray("\u2500".repeat(40)));
4557
+ console.log(chalk24.bold("\n\u{1F9E0} " + t("context.title")));
4558
+ console.log(chalk24.gray("\u2500".repeat(40)));
4920
4559
  const stack = extractTechStack();
4921
4560
  const tree = buildTree(".", "", compact ? 2 : 3).join("\n");
4922
4561
  const commands = getVhkCommands();
@@ -4951,7 +4590,7 @@ async function context(opts = {}) {
4951
4590
  }
4952
4591
  lines.push("");
4953
4592
  }
4954
- if (existsSync12(".vhk/memory.json")) {
4593
+ if (existsSync11(".vhk/memory.json")) {
4955
4594
  try {
4956
4595
  const memories = readJsonFile(
4957
4596
  ".vhk/memory.json"
@@ -5030,11 +4669,11 @@ async function context(opts = {}) {
5030
4669
  }
5031
4670
  lines.push("");
5032
4671
  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."));
4672
+ writeFileSync7(CONTEXT_PATH, lines.join("\n"), "utf-8");
4673
+ console.log(chalk24.green(`
4674
+ \u2705 ${CONTEXT_PATH} \uC0DD\uC131 \uC644\uB8CC!`));
4675
+ console.log(chalk24.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
4676
+ console.log(chalk24.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
5038
4677
  printNextStep({
5039
4678
  message: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC!",
5040
4679
  command: "vhk context-show",
@@ -5042,23 +4681,23 @@ async function context(opts = {}) {
5042
4681
  });
5043
4682
  }
5044
4683
  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."));
4684
+ console.log(chalk24.bold("\n\u{1F4C4} " + t("context.showTitle")));
4685
+ console.log(chalk24.gray("\u2500".repeat(40)));
4686
+ if (!existsSync11(CONTEXT_PATH)) {
4687
+ console.log(chalk24.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4688
+ console.log(chalk24.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
5050
4689
  return;
5051
4690
  }
5052
- const content = readFileSync5(CONTEXT_PATH2, "utf-8");
4691
+ const content = readFileSync4(CONTEXT_PATH, "utf-8");
5053
4692
  console.log("\n" + content);
5054
4693
  }
5055
4694
 
5056
4695
  // src/commands/memory.ts
5057
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
5058
- import chalk26 from "chalk";
4696
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
4697
+ import chalk25 from "chalk";
5059
4698
  var MEMORY_PATH = ".vhk/memory.json";
5060
4699
  function loadMemories() {
5061
- if (!existsSync13(MEMORY_PATH)) return [];
4700
+ if (!existsSync12(MEMORY_PATH)) return [];
5062
4701
  try {
5063
4702
  const parsed = readJsonFile(MEMORY_PATH);
5064
4703
  return Array.isArray(parsed) ? parsed : [];
@@ -5071,11 +4710,11 @@ function saveMemories(memories) {
5071
4710
  writeFileSync8(MEMORY_PATH, JSON.stringify(memories, null, 2) + "\n", "utf-8");
5072
4711
  }
5073
4712
  async function memoryAdd(content, tags) {
5074
- console.log(chalk26.bold("\n\u{1F9E0} " + t("memory.addTitle")));
5075
- console.log(chalk26.gray("\u2500".repeat(40)));
4713
+ console.log(chalk25.bold("\n\u{1F9E0} " + t("memory.addTitle")));
4714
+ console.log(chalk25.gray("\u2500".repeat(40)));
5076
4715
  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"'));
4716
+ console.log(chalk25.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4717
+ console.log(chalk25.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9\uD558\uAE30\uB85C \uACB0\uC815"'));
5079
4718
  process.exitCode = 1;
5080
4719
  return;
5081
4720
  }
@@ -5086,9 +4725,9 @@ async function memoryAdd(content, tags) {
5086
4725
  tags: tags && tags.length > 0 ? tags : []
5087
4726
  });
5088
4727
  saveMemories(memories);
5089
- console.log(chalk26.green(`
4728
+ console.log(chalk25.green(`
5090
4729
  \u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (#${memories.length})`));
5091
- console.log(chalk26.cyan(` \u{1F4DD} ${content}`));
4730
+ console.log(chalk25.cyan(` \u{1F4DD} ${content}`));
5092
4731
  printNextStep({
5093
4732
  message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!",
5094
4733
  command: "vhk memory list",
@@ -5096,24 +4735,24 @@ async function memoryAdd(content, tags) {
5096
4735
  });
5097
4736
  }
5098
4737
  async function memoryList() {
5099
- console.log(chalk26.bold("\n\u{1F9E0} " + t("memory.listTitle")));
5100
- console.log(chalk26.gray("\u2500".repeat(40)));
4738
+ console.log(chalk25.bold("\n\u{1F9E0} " + t("memory.listTitle")));
4739
+ console.log(chalk25.gray("\u2500".repeat(40)));
5101
4740
  const memories = loadMemories();
5102
4741
  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.'));
4742
+ console.log(chalk25.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4743
+ console.log(chalk25.gray(' vhk memory add "\uB0B4\uC6A9"\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
5105
4744
  return;
5106
4745
  }
5107
- console.log(chalk26.cyan(`
4746
+ console.log(chalk25.cyan(`
5108
4747
  \uCD1D ${memories.length}\uAC1C\uC758 \uAE30\uC5B5:
5109
4748
  `));
5110
4749
  memories.forEach((m, index) => {
5111
4750
  const date = new Date(m.addedAt).toLocaleDateString("ko-KR");
5112
- console.log(chalk26.white(` [${index + 1}] ${m.content}`));
4751
+ console.log(chalk25.white(` [${index + 1}] ${m.content}`));
5113
4752
  if (m.tags && m.tags.length > 0) {
5114
- console.log(chalk26.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
4753
+ console.log(chalk25.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
5115
4754
  }
5116
- console.log(chalk26.gray(` \u{1F4C5} ${date}`));
4755
+ console.log(chalk25.gray(` \u{1F4C5} ${date}`));
5117
4756
  console.log("");
5118
4757
  });
5119
4758
  }
@@ -5121,31 +4760,31 @@ async function memoryRemove(indexStr) {
5121
4760
  const memories = loadMemories();
5122
4761
  const idx = parseInt(indexStr, 10) - 1;
5123
4762
  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})`));
4763
+ console.log(chalk25.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${memories.length || 0})`));
5125
4764
  return;
5126
4765
  }
5127
4766
  const removed = memories.splice(idx, 1)[0];
5128
4767
  saveMemories(memories);
5129
- console.log(chalk26.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
5130
- console.log(chalk26.gray(` ${removed.content}`));
4768
+ console.log(chalk25.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
4769
+ console.log(chalk25.gray(` ${removed.content}`));
5131
4770
  }
5132
4771
 
5133
4772
  // 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";
4773
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync5 } from "fs";
4774
+ import chalk26 from "chalk";
5136
4775
  var BRIEF_PATH = ".vhk/brief.md";
5137
4776
  function readProjectIdentity() {
5138
4777
  const out = {};
5139
4778
  try {
5140
- if (existsSync14("RULES.md")) {
5141
- const r = readFileSync6("RULES.md", "utf-8");
4779
+ if (existsSync13("RULES.md")) {
4780
+ const r = readFileSync5("RULES.md", "utf-8");
5142
4781
  const m = r.split("\n")[0].match(/^#\s*(.+?)(?:\s*—.*)?$/);
5143
4782
  if (m) out.name = m[1].trim();
5144
4783
  const d = r.match(/한 줄 설명:\s*(.+)/);
5145
4784
  if (d) out.description = d[1].trim();
5146
4785
  }
5147
- if (!out.name && existsSync14("CLAUDE.md")) {
5148
- const m = readFileSync6("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
4786
+ if (!out.name && existsSync13("CLAUDE.md")) {
4787
+ const m = readFileSync5("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
5149
4788
  if (m) out.name = m[1].trim();
5150
4789
  }
5151
4790
  } catch {
@@ -5157,8 +4796,8 @@ function git2(args) {
5157
4796
  return result.ok ? result.out : "";
5158
4797
  }
5159
4798
  async function brief() {
5160
- console.log(chalk27.bold("\n\u{1F4CB} " + t("brief.title")));
5161
- console.log(chalk27.gray("\u2500".repeat(40)));
4799
+ console.log(chalk26.bold("\n\u{1F4CB} " + t("brief.title")));
4800
+ console.log(chalk26.gray("\u2500".repeat(40)));
5162
4801
  const lines = [];
5163
4802
  lines.push("# \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551");
5164
4803
  lines.push("");
@@ -5195,7 +4834,7 @@ async function brief() {
5195
4834
  `- **\uBBF8\uCEE4\uBC0B \uBCC0\uACBD**: ${uncommitted ? `${uncommitted.split("\n").length}\uAC1C \uD30C\uC77C` : "\uC5C6\uC74C \u2705"}`
5196
4835
  );
5197
4836
  lines.push("");
5198
- if (existsSync14(".vhk/memory.json")) {
4837
+ if (existsSync13(".vhk/memory.json")) {
5199
4838
  try {
5200
4839
  const memories = readJsonFile(".vhk/memory.json");
5201
4840
  if (Array.isArray(memories) && memories.length > 0) {
@@ -5212,7 +4851,7 @@ async function brief() {
5212
4851
  } catch {
5213
4852
  }
5214
4853
  }
5215
- if (existsSync14(".vhk/refs.json")) {
4854
+ if (existsSync13(".vhk/refs.json")) {
5216
4855
  try {
5217
4856
  const refs = readJsonFile(".vhk/refs.json");
5218
4857
  if (Array.isArray(refs) && refs.length > 0) {
@@ -5243,7 +4882,7 @@ async function brief() {
5243
4882
  mkdirSync9(".vhk", { recursive: true });
5244
4883
  writeFileSync9(BRIEF_PATH, lines.join("\n"), "utf-8");
5245
4884
  console.log("\n" + lines.join("\n"));
5246
- console.log(chalk27.green(`
4885
+ console.log(chalk26.green(`
5247
4886
  \u2705 ${BRIEF_PATH} \uC800\uC7A5 \uC644\uB8CC`));
5248
4887
  printNextStep({
5249
4888
  message: "\uBE0C\uB9AC\uD551 \uC0DD\uC131 \uC644\uB8CC!",
@@ -5253,11 +4892,11 @@ async function brief() {
5253
4892
  }
5254
4893
 
5255
4894
  // src/commands/start.ts
5256
- import chalk28 from "chalk";
5257
- import inquirer12 from "inquirer";
4895
+ import chalk27 from "chalk";
4896
+ import inquirer11 from "inquirer";
5258
4897
  import { simpleGit as simpleGit2 } from "simple-git";
5259
- import { existsSync as existsSync15 } from "fs";
5260
- import { join as join9 } from "path";
4898
+ import { existsSync as existsSync14 } from "fs";
4899
+ import { join as join8 } from "path";
5261
4900
  var VHK_FOOTPRINT_FILES = [
5262
4901
  "CLAUDE.md",
5263
4902
  ".cursorrules",
@@ -5266,7 +4905,7 @@ var VHK_FOOTPRINT_FILES = [
5266
4905
  "docs/PRD.md"
5267
4906
  ];
5268
4907
  function detectExistingFootprint(cwd) {
5269
- return VHK_FOOTPRINT_FILES.filter((rel) => existsSync15(join9(cwd, rel)));
4908
+ return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join8(cwd, rel)));
5270
4909
  }
5271
4910
  async function runGitInit(cwd) {
5272
4911
  try {
@@ -5295,22 +4934,22 @@ async function runStep(label, fn) {
5295
4934
  }
5296
4935
  }
5297
4936
  async function start(options = {}) {
5298
- console.log(chalk28.bold(`
4937
+ console.log(chalk27.bold(`
5299
4938
  ${ko.start.title}
5300
4939
  `));
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}`));
4940
+ console.log(chalk27.dim(ko.start.intro));
4941
+ console.log(chalk27.dim(` ${ko.start.step1}`));
4942
+ console.log(chalk27.dim(` ${ko.start.step2}`));
4943
+ console.log(chalk27.dim(` ${ko.start.step3}`));
4944
+ console.log(chalk27.dim(` ${ko.start.step4}`));
5306
4945
  console.log();
5307
4946
  const cwd = process.cwd();
5308
4947
  const footprint = detectExistingFootprint(cwd);
5309
4948
  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([{
4949
+ console.log(chalk27.yellow("\u26A0\uFE0F \uC774\uBBF8 VHK \uC124\uCE58 \uD754\uC801\uC774 \uAC10\uC9C0\uB410\uC5B4\uC694:"));
4950
+ for (const f of footprint) console.log(chalk27.dim(` - ${f}`));
4951
+ 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."));
4952
+ const { proceedExisting } = await inquirer11.prompt([{
5314
4953
  type: "confirm",
5315
4954
  name: "proceedExisting",
5316
4955
  message: "\uADF8\uB798\uB3C4 \uB2E4\uC2DC \uB9C8\uBC95\uC0AC\uB97C \uC9C4\uD589\uD560\uAE4C\uC694?",
@@ -5321,7 +4960,7 @@ ${ko.start.title}
5321
4960
  return;
5322
4961
  }
5323
4962
  } else if (!options.yes) {
5324
- const { proceed } = await inquirer12.prompt([{
4963
+ const { proceed } = await inquirer11.prompt([{
5325
4964
  type: "confirm",
5326
4965
  name: "proceed",
5327
4966
  message: ko.start.confirmStart,
@@ -5347,7 +4986,7 @@ ${ko.start.title}
5347
4986
  await runStep("[3/4] vhk mcp-init", () => mcpInit());
5348
4987
  log.step(ko.start.step4Header);
5349
4988
  await runStep("[4/4] vhk context", () => context());
5350
- console.log(chalk28.bold.green(`
4989
+ console.log(chalk27.bold.green(`
5351
4990
  ${ko.start.allDone}
5352
4991
  `));
5353
4992
  printNextStep({
@@ -5357,15 +4996,15 @@ ${ko.start.allDone}
5357
4996
  }
5358
4997
 
5359
4998
  // src/commands/cloud.ts
5360
- import fs15 from "fs";
4999
+ import fs12 from "fs";
5361
5000
  import os from "os";
5362
- import path16 from "path";
5363
- import chalk29 from "chalk";
5001
+ import path13 from "path";
5002
+ import chalk28 from "chalk";
5364
5003
 
5365
5004
  // src/lib/vhk-cloud.ts
5366
5005
  var import_ignore = __toESM(require_ignore(), 1);
5367
- import fs14 from "fs";
5368
- import path15 from "path";
5006
+ import fs11 from "fs";
5007
+ import path12 from "path";
5369
5008
  var DEFAULT_CLOUD_EXCLUDES = [
5370
5009
  "memory.json",
5371
5010
  // 개인 의사결정 메모
@@ -5383,17 +5022,17 @@ var CLOUD_CONFIG_FILE = "cloud.json";
5383
5022
  function loadVhkignore(rootDir) {
5384
5023
  const ig = (0, import_ignore.default)();
5385
5024
  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"));
5025
+ const ignorePath = path12.join(rootDir, ".vhkignore");
5026
+ if (fs11.existsSync(ignorePath)) {
5027
+ ig.add(fs11.readFileSync(ignorePath, "utf-8"));
5389
5028
  }
5390
5029
  return ig;
5391
5030
  }
5392
5031
  function collectVhkFiles(rootDir, ig = loadVhkignore(rootDir)) {
5393
- const vhkDir = path15.join(rootDir, VHK_DIR2);
5032
+ const vhkDir = path12.join(rootDir, VHK_DIR2);
5394
5033
  let entries;
5395
5034
  try {
5396
- entries = fs14.readdirSync(vhkDir, { withFileTypes: true });
5035
+ entries = fs11.readdirSync(vhkDir, { withFileTypes: true });
5397
5036
  } catch {
5398
5037
  return [];
5399
5038
  }
@@ -5409,10 +5048,10 @@ function partitionGistFiles(gistFiles, ig) {
5409
5048
  return { keep, excluded };
5410
5049
  }
5411
5050
  function readCloudConfig(rootDir) {
5412
- const p = path15.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
5413
- if (!fs14.existsSync(p)) return null;
5051
+ const p = path12.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
5052
+ if (!fs11.existsSync(p)) return null;
5414
5053
  try {
5415
- const parsed = JSON.parse(fs14.readFileSync(p, "utf-8"));
5054
+ const parsed = JSON.parse(fs11.readFileSync(p, "utf-8"));
5416
5055
  if (parsed && typeof parsed.gistId === "string" && parsed.gistId) {
5417
5056
  return { gistId: parsed.gistId };
5418
5057
  }
@@ -5422,24 +5061,44 @@ function readCloudConfig(rootDir) {
5422
5061
  }
5423
5062
  }
5424
5063
  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");
5064
+ const vhkDir = path12.join(rootDir, VHK_DIR2);
5065
+ fs11.mkdirSync(vhkDir, { recursive: true });
5066
+ const p = path12.join(vhkDir, CLOUD_CONFIG_FILE);
5067
+ fs11.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", "utf-8");
5068
+ ensureCloudConfigIgnored(vhkDir);
5069
+ }
5070
+ function ensureCloudConfigIgnored(vhkDir) {
5071
+ const giPath = path12.join(vhkDir, ".gitignore");
5072
+ let content = "";
5073
+ try {
5074
+ if (fs11.existsSync(giPath)) content = fs11.readFileSync(giPath, "utf-8");
5075
+ } catch {
5076
+ return;
5077
+ }
5078
+ const already = content.split(/\r?\n/).some((l) => l.trim() === CLOUD_CONFIG_FILE);
5079
+ if (already) return;
5080
+ const block = `# secret gist \uD3EC\uC778\uD130 \u2014 \uCD94\uC801 \uAE08\uC9C0 (VHK-022)
5081
+ ${CLOUD_CONFIG_FILE}
5082
+ `;
5083
+ const base = content.length === 0 ? "" : content.endsWith("\n") ? content : content + "\n";
5084
+ try {
5085
+ fs11.writeFileSync(giPath, base + block, "utf-8");
5086
+ } catch {
5087
+ }
5429
5088
  }
5430
5089
 
5431
5090
  // src/commands/cloud.ts
5432
5091
  function ensureGhReady() {
5433
5092
  const ver = safeExecFile("gh", ["--version"]);
5434
5093
  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`)"));
5094
+ console.log(chalk28.red(` ${ko.cloud.noGh}`));
5095
+ console.log(chalk28.dim(" \uC124\uCE58: https://cli.github.com/ (\uC124\uCE58 \uD6C4 `gh auth login`)"));
5437
5096
  return false;
5438
5097
  }
5439
5098
  const auth = safeExecFile("gh", ["auth", "status"]);
5440
5099
  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)"));
5100
+ console.log(chalk28.red(` ${ko.cloud.noAuth}`));
5101
+ console.log(chalk28.dim(" \uC2E4\uD589: gh auth login (gist \uAD8C\uD55C \uD544\uC694)"));
5443
5102
  return false;
5444
5103
  }
5445
5104
  return true;
@@ -5452,29 +5111,29 @@ function parseGistId(output) {
5452
5111
  return null;
5453
5112
  }
5454
5113
  async function cloudPush() {
5455
- console.log(chalk29.bold(`
5114
+ console.log(chalk28.bold(`
5456
5115
  ${ko.cloud.pushTitle}
5457
5116
  `));
5458
5117
  const cwd = process.cwd();
5459
- if (!fs15.existsSync(path16.join(cwd, VHK_DIR2))) {
5460
- console.log(chalk29.yellow(` ${ko.cloud.noVhkDir}`));
5118
+ if (!fs12.existsSync(path13.join(cwd, VHK_DIR2))) {
5119
+ console.log(chalk28.yellow(` ${ko.cloud.noVhkDir}`));
5461
5120
  return;
5462
5121
  }
5463
5122
  const ig = loadVhkignore(cwd);
5464
5123
  const files = collectVhkFiles(cwd, ig);
5465
5124
  if (files.length === 0) {
5466
- console.log(chalk29.yellow(` ${ko.cloud.nothingToSync}`));
5125
+ console.log(chalk28.yellow(` ${ko.cloud.nothingToSync}`));
5467
5126
  return;
5468
5127
  }
5469
5128
  if (!ensureGhReady()) {
5470
5129
  process.exitCode = 1;
5471
5130
  return;
5472
5131
  }
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(", ")}
5132
+ const filePaths = files.map((f) => path13.join(cwd, VHK_DIR2, f));
5133
+ console.log(chalk28.dim(` \u{1F4E6} \uBC31\uC5C5 \uB300\uC0C1 ${files.length}\uAC1C: ${files.join(", ")}
5475
5134
  `));
5476
5135
  const existing = readCloudConfig(cwd);
5477
- const desc = `vhk .vhk backup \u2014 ${path16.basename(cwd)}`;
5136
+ const desc = `vhk .vhk backup \u2014 ${path13.basename(cwd)}`;
5478
5137
  if (existing) {
5479
5138
  const gistFiles = listGistFiles(existing.gistId);
5480
5139
  for (let i = 0; i < files.length; i++) {
@@ -5483,8 +5142,8 @@ ${ko.cloud.pushTitle}
5483
5142
  const args = gistFiles.includes(name) ? ["gist", "edit", existing.gistId, "-f", name, src] : ["gist", "edit", existing.gistId, "-a", src];
5484
5143
  const res2 = safeExecFile("gh", args);
5485
5144
  if (!res2.ok) {
5486
- console.log(chalk29.red(` ${ko.cloud.pushFail}: ${name}`));
5487
- console.log(chalk29.dim(` ${res2.err}`));
5145
+ console.log(chalk28.red(` ${ko.cloud.pushFail}: ${name}`));
5146
+ console.log(chalk28.dim(` ${res2.err}`));
5488
5147
  process.exitCode = 1;
5489
5148
  return;
5490
5149
  }
@@ -5499,15 +5158,15 @@ ${ko.cloud.pushTitle}
5499
5158
  if (!purgeFailed.includes(name)) purgeFailed.push(name);
5500
5159
  }
5501
5160
  }
5502
- console.log(chalk29.green.bold(` ${ko.cloud.pushDone}`));
5503
- console.log(chalk29.dim(` gist: ${existing.gistId} (\uAC31\uC2E0)`));
5161
+ console.log(chalk28.green.bold(` ${ko.cloud.pushDone}`));
5162
+ console.log(chalk28.dim(` gist: ${existing.gistId} (\uAC31\uC2E0)`));
5504
5163
  if (excluded.length > 0) {
5505
5164
  const purged = excluded.filter((n) => !purgeFailed.includes(n));
5506
5165
  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(", ")}`));
5166
+ console.log(chalk28.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${purged.length}\uAC1C gist \uC5D0\uC11C \uC81C\uAC70: ${purged.join(", ")}`));
5508
5167
  }
5509
5168
  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)`));
5169
+ 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
5170
  }
5512
5171
  }
5513
5172
  printPushNext();
@@ -5515,32 +5174,32 @@ ${ko.cloud.pushTitle}
5515
5174
  }
5516
5175
  const res = safeExecFile("gh", ["gist", "create", "--desc", desc, ...filePaths]);
5517
5176
  if (!res.ok) {
5518
- console.log(chalk29.red(` ${ko.cloud.pushFail}`));
5519
- console.log(chalk29.dim(` ${res.err || res.out}`));
5177
+ console.log(chalk28.red(` ${ko.cloud.pushFail}`));
5178
+ console.log(chalk28.dim(` ${res.err || res.out}`));
5520
5179
  process.exitCode = 1;
5521
5180
  return;
5522
5181
  }
5523
5182
  const gistId = parseGistId(res.out);
5524
5183
  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}`));
5184
+ console.log(chalk28.red(` ${ko.cloud.pushFail} \u2014 gist id \uD30C\uC2F1 \uC2E4\uD328`));
5185
+ console.log(chalk28.dim(` \uCD9C\uB825: ${res.out}`));
5527
5186
  process.exitCode = 1;
5528
5187
  return;
5529
5188
  }
5530
5189
  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`));
5190
+ console.log(chalk28.green.bold(` ${ko.cloud.pushDone}`));
5191
+ console.log(chalk28.dim(` gist: ${gistId} (\uC2E0\uADDC, secret) \u2192 .vhk/cloud.json \uC800\uC7A5`));
5533
5192
  printPushNext();
5534
5193
  }
5535
5194
  async function cloudPull(gistIdArg) {
5536
- console.log(chalk29.bold(`
5195
+ console.log(chalk28.bold(`
5537
5196
  ${ko.cloud.pullTitle}
5538
5197
  `));
5539
5198
  const cwd = process.cwd();
5540
5199
  const gistId = gistIdArg || readCloudConfig(cwd)?.gistId;
5541
5200
  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)"));
5201
+ console.log(chalk28.yellow(` ${ko.cloud.noGistId}`));
5202
+ console.log(chalk28.dim(" \uC0AC\uC6A9\uBC95: vhk cloud pull <gistId> (\uB610\uB294 cloud.json \uC774 \uC788\uB294 \uACF3\uC5D0\uC11C \uC2E4\uD589)"));
5544
5203
  return;
5545
5204
  }
5546
5205
  if (!ensureGhReady()) {
@@ -5549,34 +5208,34 @@ ${ko.cloud.pullTitle}
5549
5208
  }
5550
5209
  const allNames = listGistFiles(gistId);
5551
5210
  if (allNames.length === 0) {
5552
- console.log(chalk29.red(` ${ko.cloud.pullFail} \u2014 gist \uBE44\uC5C8\uAC70\uB098 \uC811\uADFC \uBD88\uAC00: ${gistId}`));
5211
+ console.log(chalk28.red(` ${ko.cloud.pullFail} \u2014 gist \uBE44\uC5C8\uAC70\uB098 \uC811\uADFC \uBD88\uAC00: ${gistId}`));
5553
5212
  process.exitCode = 1;
5554
5213
  return;
5555
5214
  }
5556
5215
  const { keep: names, excluded: skipped } = partitionGistFiles(allNames, loadVhkignore(cwd));
5557
5216
  if (skipped.length > 0) {
5558
- console.log(chalk29.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${skipped.length}\uAC1C \uBCF5\uC6D0 \uC2A4\uD0B5: ${skipped.join(", ")}`));
5217
+ console.log(chalk28.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${skipped.length}\uAC1C \uBCF5\uC6D0 \uC2A4\uD0B5: ${skipped.join(", ")}`));
5559
5218
  }
5560
5219
  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).`));
5220
+ 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
5221
  return;
5563
5222
  }
5564
- const vhkDir = path16.join(cwd, VHK_DIR2);
5565
- fs15.mkdirSync(vhkDir, { recursive: true });
5223
+ const vhkDir = path13.join(cwd, VHK_DIR2);
5224
+ fs12.mkdirSync(vhkDir, { recursive: true });
5566
5225
  let restored = 0;
5567
5226
  for (const name of names) {
5568
5227
  const res = safeExecFile("gh", ["gist", "view", gistId, "-f", name, "--raw"]);
5569
5228
  if (!res.ok) {
5570
- console.log(chalk29.red(` ${ko.cloud.pullFail}: ${name}`));
5571
- console.log(chalk29.dim(` ${res.err}`));
5229
+ console.log(chalk28.red(` ${ko.cloud.pullFail}: ${name}`));
5230
+ console.log(chalk28.dim(` ${res.err}`));
5572
5231
  continue;
5573
5232
  }
5574
- fs15.writeFileSync(path16.join(vhkDir, name), ensureTrailingNewline(res.out), "utf-8");
5233
+ fs12.writeFileSync(path13.join(vhkDir, name), ensureTrailingNewline(res.out), "utf-8");
5575
5234
  restored++;
5576
5235
  }
5577
5236
  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})`));
5237
+ console.log(chalk28.green.bold(` ${ko.cloud.pullDone}`));
5238
+ console.log(chalk28.dim(` ${restored}\uAC1C \uD30C\uC77C \uBCF5\uC6D0 (gist: ${gistId})`));
5580
5239
  printNextStep({
5581
5240
  message: "\uD074\uB77C\uC6B0\uB4DC\uC5D0\uC11C .vhk/ \uBCF5\uC6D0 \uC644\uB8CC!",
5582
5241
  command: "vhk \uB9E5\uB77D",
@@ -5588,9 +5247,9 @@ function purgeExcludedFromGist(gistId, names) {
5588
5247
  const body = JSON.stringify({
5589
5248
  files: Object.fromEntries(names.map((n) => [n, null]))
5590
5249
  });
5591
- const tmp = path16.join(os.tmpdir(), `vhk-gist-purge-${process.pid}.json`);
5250
+ const tmp = path13.join(os.tmpdir(), `vhk-gist-purge-${process.pid}.json`);
5592
5251
  try {
5593
- fs15.writeFileSync(tmp, body, "utf-8");
5252
+ fs12.writeFileSync(tmp, body, "utf-8");
5594
5253
  for (let attempt = 0; attempt < 2; attempt++) {
5595
5254
  const res = safeExecFile(
5596
5255
  "gh",
@@ -5602,7 +5261,7 @@ function purgeExcludedFromGist(gistId, names) {
5602
5261
  return false;
5603
5262
  } finally {
5604
5263
  try {
5605
- fs15.unlinkSync(tmp);
5264
+ fs12.unlinkSync(tmp);
5606
5265
  } catch {
5607
5266
  }
5608
5267
  }
@@ -5624,7 +5283,7 @@ function printPushNext() {
5624
5283
  }
5625
5284
 
5626
5285
  // src/commands/help.ts
5627
- import chalk30 from "chalk";
5286
+ import chalk29 from "chalk";
5628
5287
  var QUICK_ACTIONS = [
5629
5288
  { say: "\uC0C1\uD0DC \uC54C\uB824\uC918", does: "vhk status" },
5630
5289
  { say: "\uBB50 \uBC14\uB00C\uC5C8\uC5B4?", does: "vhk diff" },
@@ -5638,21 +5297,21 @@ var QUICK_ACTIONS = [
5638
5297
  { say: "\uC804\uCCB4 \uBA85\uB839\uC5B4 \uBCF4\uAE30", does: "vhk --help" }
5639
5298
  ];
5640
5299
  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)));
5300
+ console.log(chalk29.bold("\n\u{1F9ED} VHK \u2014 \uC774\uB807\uAC8C \uB9D0\uD558\uBA74 \uB429\uB2C8\uB2E4 (quick actions)"));
5301
+ console.log(chalk29.gray("\u2500".repeat(40)));
5643
5302
  for (const a of QUICK_ACTIONS) {
5644
- console.log(` "${chalk30.cyan(a.say)}" \u2192 ${chalk30.dim(a.does)}`);
5303
+ console.log(` "${chalk29.cyan(a.say)}" \u2192 ${chalk29.dim(a.does)}`);
5645
5304
  }
5646
- console.log(chalk30.gray("\n \uC804\uCCB4 \uBA85\uB839\uC740 `vhk --help` \uB610\uB294 COMMANDS.md \uB97C \uBCF4\uC138\uC694."));
5305
+ console.log(chalk29.gray("\n \uC804\uCCB4 \uBA85\uB839\uC740 `vhk --help` \uB610\uB294 COMMANDS.md \uB97C \uBCF4\uC138\uC694."));
5647
5306
  console.log("");
5648
5307
  }
5649
5308
 
5650
5309
  // src/commands/mode.ts
5651
- import chalk31 from "chalk";
5310
+ import chalk30 from "chalk";
5652
5311
 
5653
5312
  // src/lib/config.ts
5654
- import { existsSync as existsSync16, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
5655
- import { join as join10 } from "path";
5313
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
5314
+ import { join as join9 } from "path";
5656
5315
 
5657
5316
  // src/lib/safety-mode.ts
5658
5317
  var SAFETY_MODES = ["lite", "standard", "strict"];
@@ -5668,11 +5327,11 @@ function isSafetyMode(value) {
5668
5327
 
5669
5328
  // src/lib/config.ts
5670
5329
  var CONFIG_DIR = ".vhk";
5671
- var CONFIG_PATH = join10(CONFIG_DIR, "config.json");
5330
+ var CONFIG_PATH = join9(CONFIG_DIR, "config.json");
5672
5331
  var DEFAULT_CONFIG = { safetyMode: DEFAULT_SAFETY_MODE };
5673
5332
  function readConfig(rootDir = process.cwd()) {
5674
- const full = join10(rootDir, CONFIG_PATH);
5675
- if (!existsSync16(full)) return { ...DEFAULT_CONFIG };
5333
+ const full = join9(rootDir, CONFIG_PATH);
5334
+ if (!existsSync15(full)) return { ...DEFAULT_CONFIG };
5676
5335
  try {
5677
5336
  const raw = readJsonFile(full);
5678
5337
  return {
@@ -5683,23 +5342,23 @@ function readConfig(rootDir = process.cwd()) {
5683
5342
  }
5684
5343
  }
5685
5344
  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");
5345
+ mkdirSync10(join9(rootDir, CONFIG_DIR), { recursive: true });
5346
+ writeFileSync10(join9(rootDir, CONFIG_PATH), JSON.stringify(config, null, 2) + "\n", "utf-8");
5688
5347
  }
5689
5348
 
5690
5349
  // src/commands/mode.ts
5691
5350
  async function mode(target) {
5692
- console.log(chalk31.bold("\n\u{1F6E1}\uFE0F Safety Mode"));
5693
- console.log(chalk31.gray("\u2500".repeat(40)));
5351
+ console.log(chalk30.bold("\n\u{1F6E1}\uFE0F Safety Mode"));
5352
+ console.log(chalk30.gray("\u2500".repeat(40)));
5694
5353
  const current = readConfig().safetyMode;
5695
5354
  if (!target) {
5696
- console.log(chalk31.cyan(`
5697
- \uD604\uC7AC \uBAA8\uB4DC: ${chalk31.bold(current)}`));
5698
- console.log(chalk31.dim(` ${SAFETY_MODE_DESC[current]}`));
5355
+ console.log(chalk30.cyan(`
5356
+ \uD604\uC7AC \uBAA8\uB4DC: ${chalk30.bold(current)}`));
5357
+ console.log(chalk30.dim(` ${SAFETY_MODE_DESC[current]}`));
5699
5358
  console.log("");
5700
5359
  for (const m of SAFETY_MODES) {
5701
5360
  const mark = m === current ? "\u25CF" : "\u25CB";
5702
- console.log(` ${mark} ${m.padEnd(9)} ${chalk31.dim(SAFETY_MODE_DESC[m])}`);
5361
+ console.log(` ${mark} ${m.padEnd(9)} ${chalk30.dim(SAFETY_MODE_DESC[m])}`);
5703
5362
  }
5704
5363
  printNextStep({
5705
5364
  message: "\uBAA8\uB4DC\uB97C \uBC14\uAFB8\uB824\uBA74:",
@@ -5709,20 +5368,20 @@ async function mode(target) {
5709
5368
  return;
5710
5369
  }
5711
5370
  if (!isSafetyMode(target)) {
5712
- console.log(chalk31.red(`
5371
+ console.log(chalk30.red(`
5713
5372
  \u274C \uC54C \uC218 \uC5C6\uB294 \uBAA8\uB4DC: ${target}`));
5714
- console.log(chalk31.dim(` \uAC00\uB2A5: ${SAFETY_MODES.join(" | ")}`));
5373
+ console.log(chalk30.dim(` \uAC00\uB2A5: ${SAFETY_MODES.join(" | ")}`));
5715
5374
  process.exitCode = 1;
5716
5375
  return;
5717
5376
  }
5718
5377
  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]}`));
5378
+ console.log(chalk30.green(`
5379
+ \u2705 Safety Mode \u2192 ${chalk30.bold(target)}`));
5380
+ console.log(chalk30.dim(` ${SAFETY_MODE_DESC[target]}`));
5722
5381
  }
5723
5382
 
5724
5383
  // src/commands/verify.ts
5725
- import chalk32 from "chalk";
5384
+ import chalk31 from "chalk";
5726
5385
  function verificationChecklist() {
5727
5386
  return [
5728
5387
  "\uD0C0\uC785 \uCCB4\uD06C \u2014 pnpm exec tsc --noEmit",
@@ -5732,15 +5391,15 @@ function verificationChecklist() {
5732
5391
  ];
5733
5392
  }
5734
5393
  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)));
5394
+ console.log(chalk31.bold("\n\u{1F50E} \uAC80\uC99D \uBB36\uC74C (verify \u2014 lite)"));
5395
+ console.log(chalk31.gray("\u2500".repeat(40)));
5737
5396
  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:"));
5397
+ console.log(chalk31.dim(` \uD604\uC7AC Safety Mode: ${mode2} \u2014 ${SAFETY_MODE_DESC[mode2]}`));
5398
+ console.log(chalk31.cyan("\n \uC704\uD5D8 \uC791\uC5C5/\uC800\uC7A5 \uC804 \uAD8C\uC7A5 \uAC80\uC99D:"));
5740
5399
  for (const item of verificationChecklist()) {
5741
5400
  console.log(` \u2610 ${item}`);
5742
5401
  }
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)."));
5402
+ 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
5403
  printNextStep({
5745
5404
  message: "\uAC80\uC99D \uD1B5\uACFC \uD6C4 \uC800\uC7A5\uD558\uC138\uC694:",
5746
5405
  command: "vhk save",
@@ -5757,7 +5416,8 @@ var HIGH_RISK_ACTIONS = [
5757
5416
  "cloud-pull",
5758
5417
  "resume",
5759
5418
  "env-write",
5760
- "delete"
5419
+ "delete",
5420
+ "restore"
5761
5421
  ];
5762
5422
  var STRICT_EXTRA_ACTIONS = /* @__PURE__ */ new Set(["save", "sync"]);
5763
5423
  var NL_GUARDED_ACTIONS = {
@@ -5768,7 +5428,8 @@ var NL_GUARDED_ACTIONS = {
5768
5428
  "cloud-pull": "cloud-pull",
5769
5429
  env: "env-write",
5770
5430
  save: "save",
5771
- sync: "sync"
5431
+ sync: "sync",
5432
+ restore: "restore"
5772
5433
  };
5773
5434
  function isHighRisk(action) {
5774
5435
  return HIGH_RISK_ACTIONS.includes(action);
@@ -5790,6 +5451,11 @@ async function runGuarded(action, deps, run) {
5790
5451
  return { outcome: { ran: true, guard, reason: "low-risk" }, result: await run() };
5791
5452
  }
5792
5453
  if (guard === "warn") {
5454
+ const canConfirm = deps.isTTY ?? !!process.stdin.isTTY;
5455
+ if (!deps.approved && !canConfirm) {
5456
+ log2(`\u26A0\uFE0F \uC704\uD5D8 \uC791\uC5C5(${action}) \u2014 lite \uC9C0\uB9CC \uBE44\uB300\uD654\uD615+\uBBF8\uC2B9\uC778 \u2192 \uC911\uB2E8. (--yes \uB85C \uC2B9\uC778)`);
5457
+ return { outcome: { ran: false, guard, reason: "lite-noninteractive-block" } };
5458
+ }
5793
5459
  log2(`\u26A0\uFE0F \uC704\uD5D8 \uC791\uC5C5(${action}) \u2014 lite \uBAA8\uB4DC: \uACBD\uACE0\uB9CC \uD558\uACE0 \uC9C4\uD589\uD569\uB2C8\uB2E4.`);
5794
5460
  return { outcome: { ran: true, guard, reason: "lite-warn" }, result: await run() };
5795
5461
  }
@@ -5797,7 +5463,7 @@ async function runGuarded(action, deps, run) {
5797
5463
  if (deps.approved === true) {
5798
5464
  return { outcome: { ran: true, guard, reason: "approved" }, result: await run() };
5799
5465
  }
5800
- const tty = deps.isTTY ?? !!process.stdout.isTTY;
5466
+ const tty = deps.isTTY ?? !!process.stdin.isTTY;
5801
5467
  if (tty && deps.confirm) {
5802
5468
  const ok = await deps.confirm();
5803
5469
  if (ok) return { outcome: { ran: true, guard, reason: "confirmed" }, result: await run() };
@@ -5894,6 +5560,10 @@ async function dispatchNlpRoute(route, input) {
5894
5560
  if (sub === "next") return goalNext();
5895
5561
  if (sub === "check") return goalCheck({});
5896
5562
  if (sub === "done") return goalDone({});
5563
+ if (sub === "sync") {
5564
+ await goalSync();
5565
+ return;
5566
+ }
5897
5567
  return goalList();
5898
5568
  }
5899
5569
  case "help":
@@ -5909,28 +5579,29 @@ var STATE_CHANGING_COMMANDS = /* @__PURE__ */ new Set([
5909
5579
  "init"
5910
5580
  ]);
5911
5581
  function requiresConfirmation(route) {
5912
- return route.confidence === "low" || STATE_CHANGING_COMMANDS.has(route.command);
5582
+ const goalSync2 = route.command === "goal" && route.args?.[0] === "sync";
5583
+ return route.confidence === "low" || STATE_CHANGING_COMMANDS.has(route.command) || goalSync2;
5913
5584
  }
5914
5585
  async function runNaturalLanguageRoute(input) {
5915
5586
  const route = routeNaturalLanguage(input);
5916
5587
  if (!route) {
5917
- console.log(chalk33.yellow(`
5588
+ console.log(chalk32.yellow(`
5918
5589
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
5919
5590
  `));
5920
5591
  return;
5921
5592
  }
5922
5593
  console.log("");
5923
- console.log(chalk33.cyan(` \u{1F4AC} "${input}"`));
5924
- console.log(chalk33.cyan(` \u2192 ${route.explanation}`));
5594
+ console.log(chalk32.cyan(` \u{1F4AC} "${input}"`));
5595
+ console.log(chalk32.cyan(` \u2192 ${route.explanation}`));
5925
5596
  if (requiresConfirmation(route)) {
5926
- const { confirm } = await inquirer13.prompt([{
5597
+ const { confirm } = await inquirer12.prompt([{
5927
5598
  type: "confirm",
5928
5599
  name: "confirm",
5929
5600
  message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
5930
5601
  default: true
5931
5602
  }]);
5932
5603
  if (!confirm) {
5933
- console.log(chalk33.dim(` ${ko.nlp.menuHint}`));
5604
+ console.log(chalk32.dim(` ${ko.nlp.menuHint}`));
5934
5605
  return;
5935
5606
  }
5936
5607
  }
@@ -5939,7 +5610,7 @@ async function runNaturalLanguageRoute(input) {
5939
5610
  if (riskAction) {
5940
5611
  await runGuarded(
5941
5612
  riskAction,
5942
- { channel: "nl", approved: false, log: (m) => console.log(chalk33.yellow(` ${m}`)) },
5613
+ { channel: "nl", approved: false, log: (m) => console.log(chalk32.yellow(` ${m}`)) },
5943
5614
  () => dispatchNlpRoute(route, input)
5944
5615
  );
5945
5616
  return;
@@ -5948,77 +5619,77 @@ async function runNaturalLanguageRoute(input) {
5948
5619
  }
5949
5620
 
5950
5621
  // src/commands/agent.ts
5951
- import chalk34 from "chalk";
5622
+ import chalk33 from "chalk";
5952
5623
  function activeGoalId() {
5953
5624
  const goals = listGoals("goals");
5954
5625
  const id = selectActiveId(goals);
5955
5626
  return id ?? void 0;
5956
5627
  }
5957
5628
  async function blocker(description) {
5958
- console.log(chalk34.bold(`
5629
+ console.log(chalk33.bold(`
5959
5630
  ${ko.agent.blockerTitle}
5960
5631
  `));
5961
5632
  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"'));
5633
+ console.log(chalk33.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
5634
+ console.log(chalk33.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
5964
5635
  process.exitCode = 1;
5965
5636
  return;
5966
5637
  }
5967
5638
  const goalId = activeGoalId();
5968
5639
  const r = appendBlocker(description, goalId);
5969
- console.log(chalk34.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
5640
+ console.log(chalk33.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
5970
5641
  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."));
5642
+ console.log(chalk33.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
5643
+ console.log(chalk33.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
5973
5644
  process.exitCode = 2;
5974
5645
  }
5975
5646
  }
5976
5647
  async function learn(lesson) {
5977
- console.log(chalk34.bold(`
5648
+ console.log(chalk33.bold(`
5978
5649
  ${ko.agent.learnTitle}
5979
5650
  `));
5980
5651
  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)"'));
5652
+ console.log(chalk33.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
5653
+ console.log(chalk33.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
5983
5654
  process.exitCode = 1;
5984
5655
  return;
5985
5656
  }
5986
5657
  const goalId = activeGoalId();
5987
5658
  appendLearning(lesson, goalId);
5988
- console.log(chalk34.green(" \u2705 learnings.md append."));
5659
+ console.log(chalk33.green(" \u2705 learnings.md append."));
5989
5660
  console.log(
5990
- chalk34.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
5661
+ chalk33.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
5991
5662
  );
5992
5663
  }
5993
5664
  async function resume(opts = {}) {
5994
- console.log(chalk34.bold(`
5665
+ console.log(chalk33.bold(`
5995
5666
  ${ko.agent.resumeTitle}
5996
5667
  `));
5997
5668
  if (!isHardStopActive()) {
5998
- console.log(chalk34.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
5669
+ console.log(chalk33.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
5999
5670
  return;
6000
5671
  }
6001
5672
  const reason = readHardStopReason();
6002
5673
  if (reason) {
6003
- console.log(chalk34.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
6004
- console.log(chalk34.dim(` ${reason.split("\n").join("\n ")}`));
5674
+ console.log(chalk33.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
5675
+ console.log(chalk33.dim(` ${reason.split("\n").join("\n ")}`));
6005
5676
  console.log("");
6006
5677
  }
6007
5678
  if (!opts.confirm) {
6008
5679
  console.log(
6009
- chalk34.red(
5680
+ chalk33.red(
6010
5681
  " \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
6011
5682
  )
6012
5683
  );
6013
- console.log(chalk34.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
5684
+ console.log(chalk33.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
6014
5685
  process.exitCode = 1;
6015
5686
  return;
6016
5687
  }
6017
5688
  const removed = clearHardStop();
6018
5689
  if (removed) {
6019
- console.log(chalk34.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
5690
+ console.log(chalk33.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
6020
5691
  } else {
6021
- console.log(chalk34.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
5692
+ console.log(chalk33.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
6022
5693
  }
6023
5694
  }
6024
5695
 
@@ -6031,7 +5702,7 @@ async function guardCli(action, approved, run) {
6031
5702
  channel: "cli",
6032
5703
  approved,
6033
5704
  confirm: async () => {
6034
- const { ok } = await inquirer14.prompt([{
5705
+ const { ok } = await inquirer13.prompt([{
6035
5706
  type: "confirm",
6036
5707
  name: "ok",
6037
5708
  message: `\u26A0\uFE0F \uC704\uD5D8 \uC791\uC5C5(${action})\uC744 \uC2E4\uD589\uD560\uAE4C\uC694?`,
@@ -6039,7 +5710,7 @@ async function guardCli(action, approved, run) {
6039
5710
  }]);
6040
5711
  return ok;
6041
5712
  },
6042
- log: (m) => console.log(chalk35.yellow(` ${m}`))
5713
+ log: (m) => console.log(chalk34.yellow(` ${m}`))
6043
5714
  },
6044
5715
  run
6045
5716
  );
@@ -6052,7 +5723,7 @@ async function guardCliDefer(action, approved, run) {
6052
5723
  approved,
6053
5724
  // TTY 면 통과(명령이 자체 확인), 비대화형은 confirm 불가 → 가드가 차단.
6054
5725
  confirm: async () => !!process.stdout.isTTY,
6055
- log: (m) => console.log(chalk35.yellow(` ${m}`))
5726
+ log: (m) => console.log(chalk34.yellow(` ${m}`))
6056
5727
  },
6057
5728
  run
6058
5729
  );
@@ -6148,8 +5819,8 @@ program.command("save").alias("\uC800\uC7A5").option("--yes", "\uD655\uC778 \uC5
6148
5819
  program.command("undo").alias("\uB418\uB3CC\uB9AC\uAE30").option("--yes", "\uD655\uC778 \uC5C6\uC774 \uC2E4\uD589 (\uC704\uD5D8 \uC791\uC5C5 \uBA85\uC2DC \uC2B9\uC778)").description("\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30").action(async (opts) => {
6149
5820
  await guardCliDefer("undo", opts?.yes === true, () => undo());
6150
5821
  });
6151
- program.command("restore").alias("\uBCF5\uC6D0").argument("[id]", "\uBCF5\uC6D0\uD560 \uBC31\uC5C5 id (\uC0DD\uB7B5 \uC2DC \uBAA9\uB85D\uC5D0\uC11C \uC120\uD0DD)").description("sync \uBC31\uC5C5 \uBCF5\uC6D0 (.vhk/backups/ \u2014 \uC5B8\uCEE4\uBC0B \uB36E\uC5B4\uC4F0\uAE30 \uBCF5\uAD6C)").action(async (id) => {
6152
- await restore(id);
5822
+ program.command("restore").alias("\uBCF5\uC6D0").argument("[id]", "\uBCF5\uC6D0\uD560 \uBC31\uC5C5 id (\uC0DD\uB7B5 \uC2DC \uBAA9\uB85D\uC5D0\uC11C \uC120\uD0DD)").option("--yes", "\uD655\uC778 \uC5C6\uC774 \uC2E4\uD589 (\uC704\uD5D8 \uC791\uC5C5 \uBA85\uC2DC \uC2B9\uC778)").description("sync \uBC31\uC5C5 \uBCF5\uC6D0 (.vhk/backups/ \u2014 \uC5B8\uCEE4\uBC0B \uB36E\uC5B4\uC4F0\uAE30 \uBCF5\uAD6C)").action(async (id, opts) => {
5823
+ await guardCli("restore", opts?.yes === true, () => restore(id));
6153
5824
  });
6154
5825
  program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uB300\uC2DC\uBCF4\uB4DC").action(async () => {
6155
5826
  await status();
@@ -6234,7 +5905,7 @@ memoryCmd.command("remove <index>").alias("\uC0AD\uC81C").description("\uAE30\uC
6234
5905
  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
5906
  await brief();
6236
5907
  });
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 () => {
5908
+ 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
5909
  await goalList();
6239
5910
  });
6240
5911
  goalCmd.command("list").alias("\uBAA9\uB85D").description("goals/*.md \uBAA9\uB85D (id, status, priority, title)").action(async () => {
@@ -6246,12 +5917,15 @@ goalCmd.command("next").alias("\uB2E4\uC74C").description("active goal \uC790\uB
6246
5917
  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
5918
  await goalInit();
6248
5919
  });
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) => {
5920
+ 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
5921
  await goalCheck(opts);
6251
5922
  });
6252
5923
  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
5924
  await goalDone(opts);
6254
5925
  });
5926
+ 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 () => {
5927
+ await goalSync();
5928
+ });
6255
5929
  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
5930
  await blocker(description);
6257
5931
  });
@@ -6269,7 +5943,7 @@ program.on("command:*", async (operands) => {
6269
5943
  });
6270
5944
  program.action(async () => {
6271
5945
  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([{
5946
+ const { choice } = await inquirer13.prompt([{
6273
5947
  type: "list",
6274
5948
  name: "choice",
6275
5949
  message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
@@ -6326,9 +6000,9 @@ if (isMainModule) {
6326
6000
  }
6327
6001
  } catch (err) {
6328
6002
  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)"));
6003
+ 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
6004
  } else {
6331
- console.error(chalk35.red(`
6005
+ console.error(chalk34.red(`
6332
6006
  \u274C ${err instanceof Error ? err.message : String(err)}`));
6333
6007
  }
6334
6008
  process.exitCode = 1;