@byh3071/vhk 2.3.2 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -874,7 +874,10 @@ var ko = {
874
874
  testFailed: "\uD14C\uC2A4\uD2B8 \uC2E4\uD328",
875
875
  publishing: "npm \uBC30\uD3EC \uC911...",
876
876
  publishSuccess: "npm \uBC30\uD3EC \uC131\uACF5!",
877
- publishFailed: "npm \uBC30\uD3EC \uC2E4\uD328"
877
+ publishFailed: "npm \uBC30\uD3EC \uC2E4\uD328",
878
+ // 발행 전 안전 가드 — feature 브랜치/미커밋 발행로 픽스 누락본이 latest 로 나가는 사고 방지(v2.3.1 사례)
879
+ preflightWrongBranch: (branch, def) => `\uBC1C\uD589 \uC911\uB2E8 \u2014 \uD604\uC7AC '${branch}' \uBE0C\uB79C\uCE58\uC785\uB2C8\uB2E4. \uBC1C\uD589\uC740 '${def}' \uC5D0\uC11C\uB9CC \uD558\uC138\uC694 (feature \uBE0C\uB79C\uCE58 \uBC1C\uD589 \u2192 \uD53D\uC2A4 \uB204\uB77D\uBCF8\uC774 npm latest \uB85C \uB098\uAC00\uB294 \uC0AC\uACE0 \uBC29\uC9C0). git checkout ${def} && git pull \uD6C4 \uC7AC\uC2DC\uB3C4.`,
880
+ preflightDirty: "\uBC1C\uD589 \uC911\uB2E8 \u2014 \uCEE4\uBC0B \uC548 \uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uBC1C\uD589 \uC804 \uCEE4\uBC0B/\uC815\uB9AC\uD558\uC138\uC694 (untracked \uD30C\uC77C\uC740 \uBB34\uC2DC)."
878
881
  },
879
882
  harness: {
880
883
  title: "\uD1B5\uD569 \uD488\uC9C8 \uC810\uAC80"
@@ -919,6 +922,10 @@ var ko = {
919
922
  learnTitle: "\u{1F9E0} Learning \uAE30\uB85D",
920
923
  resumeTitle: "\u25B6\uFE0F HARD_STOP \uD574\uC81C"
921
924
  },
925
+ work: {
926
+ workTitle: "\u{1F680} vhk work \u2014 \uC791\uC5C5 \uC2DC\uC791/\uC774\uC5B4\uD558\uAE30",
927
+ handoffTitle: "\u23F8\uFE0F vhk work handoff \u2014 \uC911\uB2E8 \uC815\uB9AC"
928
+ },
922
929
  pattern: {
923
930
  detectTitle: "\uD328\uD134 \uAC10\uC9C0",
924
931
  listTitle: "\uD328\uD134 \uBAA9\uB85D",
@@ -1685,8 +1692,42 @@ ${ko.sync.done}`));
1685
1692
  });
1686
1693
  }
1687
1694
 
1688
- // src/commands/deploy.ts
1695
+ // src/lib/version.ts
1689
1696
  import { existsSync as existsSync2 } from "fs";
1697
+ import { dirname, join as join2 } from "path";
1698
+ import { fileURLToPath } from "url";
1699
+
1700
+ // src/lib/read-json.ts
1701
+ import { readFileSync as readFileSync2 } from "fs";
1702
+ function stripBom(text) {
1703
+ return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
1704
+ }
1705
+ function readJsonFile(filePath) {
1706
+ const raw = stripBom(readFileSync2(filePath, "utf-8"));
1707
+ return JSON.parse(raw);
1708
+ }
1709
+
1710
+ // src/lib/version.ts
1711
+ function getVhkVersion() {
1712
+ const dir = dirname(fileURLToPath(import.meta.url));
1713
+ for (const pkgPath of [
1714
+ join2(dir, "../../package.json"),
1715
+ join2(dir, "../package.json")
1716
+ ]) {
1717
+ try {
1718
+ if (existsSync2(pkgPath)) {
1719
+ const pkg = readJsonFile(pkgPath);
1720
+ if (pkg.version) return pkg.version;
1721
+ }
1722
+ } catch {
1723
+ continue;
1724
+ }
1725
+ }
1726
+ return "0.0.0";
1727
+ }
1728
+
1729
+ // src/commands/deploy.ts
1730
+ import { existsSync as existsSync3 } from "fs";
1690
1731
  import chalk4 from "chalk";
1691
1732
  import inquirer2 from "inquirer";
1692
1733
 
@@ -1787,7 +1828,7 @@ var PLATFORMS = {
1787
1828
  function detectPlatform() {
1788
1829
  for (const [key, config] of Object.entries(PLATFORMS)) {
1789
1830
  for (const file of config.detectFiles) {
1790
- if (existsSync2(file)) return key;
1831
+ if (existsSync3(file)) return key;
1791
1832
  }
1792
1833
  }
1793
1834
  return null;
@@ -1856,8 +1897,8 @@ ${t("deploy.deploying")}
1856
1897
  }
1857
1898
 
1858
1899
  // src/commands/env.ts
1859
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, appendFileSync, readdirSync } from "fs";
1860
- import { join as join2 } from "path";
1900
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync, appendFileSync, readdirSync } from "fs";
1901
+ import { join as join3 } from "path";
1861
1902
  import chalk5 from "chalk";
1862
1903
  function parseEnvKeys(content) {
1863
1904
  return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
@@ -1874,7 +1915,7 @@ function loadDefinedEnvKeys(dir = ".") {
1874
1915
  if (!name.startsWith(".env")) continue;
1875
1916
  if (name.endsWith(".example") || name.endsWith(".sample")) continue;
1876
1917
  try {
1877
- for (const k of parseEnvKeys(readFileSync2(join2(dir, name), "utf-8"))) keys.add(k);
1918
+ for (const k of parseEnvKeys(readFileSync3(join3(dir, name), "utf-8"))) keys.add(k);
1878
1919
  } catch {
1879
1920
  }
1880
1921
  }
@@ -1882,8 +1923,8 @@ function loadDefinedEnvKeys(dir = ".") {
1882
1923
  }
1883
1924
  function ensureGitignore() {
1884
1925
  const gitignorePath = ".gitignore";
1885
- if (existsSync3(gitignorePath)) {
1886
- const content = readFileSync2(gitignorePath, "utf-8");
1926
+ if (existsSync4(gitignorePath)) {
1927
+ const content = readFileSync3(gitignorePath, "utf-8");
1887
1928
  if (!content.split("\n").some((l) => l.trim() === ".env")) {
1888
1929
  appendFileSync(gitignorePath, "\n.env\n");
1889
1930
  console.log(chalk5.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
@@ -1896,12 +1937,12 @@ function ensureGitignore() {
1896
1937
  async function env() {
1897
1938
  console.log(chalk5.bold("\n\u{1F510} " + t("env.title")));
1898
1939
  console.log(chalk5.gray("\u2500".repeat(40)));
1899
- if (!existsSync3(".env")) {
1940
+ if (!existsSync4(".env")) {
1900
1941
  console.log(chalk5.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
1901
1942
  console.log(chalk5.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
1902
1943
  return;
1903
1944
  }
1904
- const envContent = readFileSync2(".env", "utf-8");
1945
+ const envContent = readFileSync3(".env", "utf-8");
1905
1946
  const keys = parseEnvKeys(envContent);
1906
1947
  if (keys.length === 0) {
1907
1948
  console.log(chalk5.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
@@ -1922,11 +1963,11 @@ async function env() {
1922
1963
  async function envCheck() {
1923
1964
  console.log(chalk5.bold("\n\u{1F50D} " + t("env.checkTitle")));
1924
1965
  console.log(chalk5.gray("\u2500".repeat(40)));
1925
- if (!existsSync3(".env.example")) {
1966
+ if (!existsSync4(".env.example")) {
1926
1967
  console.log(chalk5.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
1927
1968
  return;
1928
1969
  }
1929
- const requiredKeys = parseEnvKeys(readFileSync2(".env.example", "utf-8"));
1970
+ const requiredKeys = parseEnvKeys(readFileSync3(".env.example", "utf-8"));
1930
1971
  const currentKeys = loadDefinedEnvKeys();
1931
1972
  const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
1932
1973
  const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
@@ -1949,22 +1990,10 @@ async function envCheck() {
1949
1990
  }
1950
1991
 
1951
1992
  // src/commands/publish.ts
1952
- import { existsSync as existsSync4, writeFileSync as writeFileSync2 } from "fs";
1993
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1953
1994
  import chalk6 from "chalk";
1954
1995
  import inquirer3 from "inquirer";
1955
1996
  import ora from "ora";
1956
-
1957
- // src/lib/read-json.ts
1958
- import { readFileSync as readFileSync3 } from "fs";
1959
- function stripBom(text) {
1960
- return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
1961
- }
1962
- function readJsonFile(filePath) {
1963
- const raw = stripBom(readFileSync3(filePath, "utf-8"));
1964
- return JSON.parse(raw);
1965
- }
1966
-
1967
- // src/commands/publish.ts
1968
1997
  function bumpVersion(current, type) {
1969
1998
  const [major, minor, patch] = current.split(".").map((n) => parseInt(n, 10) || 0);
1970
1999
  switch (type) {
@@ -1976,8 +2005,28 @@ function bumpVersion(current, type) {
1976
2005
  return `${major}.${minor}.${patch + 1}`;
1977
2006
  }
1978
2007
  }
2008
+ function insertChangelogStub(content, version, date) {
2009
+ const escaped = version.replace(/\./g, "\\.");
2010
+ if (new RegExp(`^## \\[${escaped}\\]`, "m").test(content)) return content;
2011
+ const stub = `## [${version}] - ${date}
2012
+
2013
+ _\uBCC0\uACBD \uB0B4\uC5ED \uC791\uC131 \uD544\uC694._
2014
+
2015
+ `;
2016
+ const firstEntry = content.match(/^## \[\d+\.\d+\.\d+\]/m);
2017
+ if (firstEntry && firstEntry.index !== void 0) {
2018
+ return content.slice(0, firstEntry.index) + stub + content.slice(firstEntry.index);
2019
+ }
2020
+ return content.trimEnd() + "\n\n" + stub;
2021
+ }
2022
+ function bumpClaudeMdVersion(content, newVersion) {
2023
+ return content.replace(/(\*\*버전:\*\*\s*v)\d+\.\d+\.\d+/, `$1${newVersion}`);
2024
+ }
1979
2025
  function gitPostRelease(newVersion) {
1980
- const add = safeExecFile("git", ["add", "package.json"]);
2026
+ const filesToAdd = ["package.json"];
2027
+ if (existsSync5("CHANGELOG.md")) filesToAdd.push("CHANGELOG.md");
2028
+ if (existsSync5("CLAUDE.md")) filesToAdd.push("CLAUDE.md");
2029
+ const add = safeExecFile("git", ["add", ...filesToAdd]);
1981
2030
  if (!add.ok) {
1982
2031
  return {
1983
2032
  added: false,
@@ -2018,10 +2067,31 @@ function gitPostRelease(newVersion) {
2018
2067
  pushed: push.ok && pushTags.ok
2019
2068
  };
2020
2069
  }
2070
+ function evaluatePublishPreflight(branch, trackedStatus, defaultBranch) {
2071
+ if (branch !== defaultBranch) return { ok: false, code: "wrong-branch" };
2072
+ if (trackedStatus.trim()) return { ok: false, code: "dirty" };
2073
+ return { ok: true };
2074
+ }
2075
+ function publishPreflight() {
2076
+ const br = safeExecFile("git", ["branch", "--show-current"]);
2077
+ const branch = br.ok ? br.out.trim() : "";
2078
+ const head = safeExecFile("git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
2079
+ const defaultBranch = head.ok ? head.out.trim().split("/").pop() || "main" : "main";
2080
+ const st = safeExecFile("git", ["status", "--porcelain", "--untracked-files=no"]);
2081
+ const trackedStatus = st.ok ? st.out : "";
2082
+ return { ...evaluatePublishPreflight(branch, trackedStatus, defaultBranch), branch, defaultBranch };
2083
+ }
2021
2084
  async function publish() {
2022
2085
  console.log(chalk6.bold("\n\u{1F4E6} " + t("publish.title")));
2023
2086
  console.log(chalk6.gray("\u2500".repeat(40)));
2024
- if (!existsSync4("package.json")) {
2087
+ const pre = publishPreflight();
2088
+ if (!pre.ok) {
2089
+ const msg = pre.code === "wrong-branch" ? t("publish.preflightWrongBranch", pre.branch || "(detached)", pre.defaultBranch) : t("publish.preflightDirty");
2090
+ console.log(chalk6.red(`
2091
+ \u274C ${msg}`));
2092
+ return;
2093
+ }
2094
+ if (!existsSync5("package.json")) {
2025
2095
  console.log(chalk6.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2026
2096
  return;
2027
2097
  }
@@ -2053,13 +2123,25 @@ async function publish() {
2053
2123
  pkg.version = newVersion;
2054
2124
  writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
2055
2125
  console.log(chalk6.green("\u2705 package.json \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8"));
2126
+ const claudeMdOriginal = existsSync5("CLAUDE.md") ? readFileSync4("CLAUDE.md", "utf-8") : null;
2127
+ if (claudeMdOriginal !== null) {
2128
+ const bumped = bumpClaudeMdVersion(claudeMdOriginal, newVersion);
2129
+ if (bumped !== claudeMdOriginal) {
2130
+ writeFileSync2("CLAUDE.md", bumped, "utf-8");
2131
+ console.log(chalk6.green("\u2705 CLAUDE.md \uBC84\uC804\uC904 \uB3D9\uAE30\uD654"));
2132
+ }
2133
+ }
2134
+ const rollbackVersion = () => {
2135
+ pkg.version = currentVersion;
2136
+ writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
2137
+ if (claudeMdOriginal !== null) writeFileSync2("CLAUDE.md", claudeMdOriginal, "utf-8");
2138
+ };
2056
2139
  const buildSpinner = ora(t("publish.building")).start();
2057
2140
  const buildResult = safeExecFile("pnpm", ["build"]);
2058
2141
  if (!buildResult.ok) {
2059
2142
  buildSpinner.fail(t("publish.buildFailed"));
2060
2143
  console.log(chalk6.red(buildResult.err.slice(0, 500)));
2061
- pkg.version = currentVersion;
2062
- writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
2144
+ rollbackVersion();
2063
2145
  return;
2064
2146
  }
2065
2147
  buildSpinner.succeed(t("publish.buildSuccess"));
@@ -2068,8 +2150,7 @@ async function publish() {
2068
2150
  if (!testResult.ok) {
2069
2151
  testSpinner.fail(t("publish.testFailed"));
2070
2152
  console.log(chalk6.red(testResult.err.slice(0, 500)));
2071
- pkg.version = currentVersion;
2072
- writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
2153
+ rollbackVersion();
2073
2154
  return;
2074
2155
  }
2075
2156
  testSpinner.succeed(t("publish.testSuccess"));
@@ -2082,8 +2163,7 @@ async function publish() {
2082
2163
  }
2083
2164
  ]);
2084
2165
  if (!confirm) {
2085
- pkg.version = currentVersion;
2086
- writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
2166
+ rollbackVersion();
2087
2167
  console.log(chalk6.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
2088
2168
  return;
2089
2169
  }
@@ -2095,13 +2175,21 @@ async function publish() {
2095
2175
  console.log(chalk6.red(`
2096
2176
  \u2716 ${t("publish.publishFailed")}`));
2097
2177
  console.log(chalk6.red(pubResult.err.slice(0, 500)));
2098
- pkg.version = currentVersion;
2099
- writeFileSync2("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
2178
+ rollbackVersion();
2100
2179
  console.log(chalk6.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
2101
2180
  return;
2102
2181
  }
2103
2182
  console.log(chalk6.green(`
2104
2183
  \u2714 ${t("publish.publishSuccess")}`));
2184
+ if (existsSync5("CHANGELOG.md")) {
2185
+ const cl = readFileSync4("CHANGELOG.md", "utf-8");
2186
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2187
+ const updated = insertChangelogStub(cl, newVersion, date);
2188
+ if (updated !== cl) {
2189
+ writeFileSync2("CHANGELOG.md", updated, "utf-8");
2190
+ console.log(chalk6.green(`\u2705 CHANGELOG.md \uC5D0 [${newVersion}] \uC2A4\uD141 \uCD94\uAC00 \u2014 \uBCF8\uBB38 \uBCF4\uAC15 \uD544\uC694`));
2191
+ }
2192
+ }
2105
2193
  const git = gitPostRelease(newVersion);
2106
2194
  if (git.warning) {
2107
2195
  console.log(chalk6.yellow(`
@@ -2124,13 +2212,13 @@ async function publish() {
2124
2212
  }
2125
2213
 
2126
2214
  // src/commands/audit.ts
2127
- import { existsSync as existsSync5 } from "fs";
2215
+ import { existsSync as existsSync6 } from "fs";
2128
2216
  import chalk7 from "chalk";
2129
2217
  import inquirer4 from "inquirer";
2130
2218
  import ora2 from "ora";
2131
2219
  function detectCurrentPM() {
2132
- if (existsSync5("pnpm-lock.yaml")) return "pnpm";
2133
- if (existsSync5("yarn.lock")) return "yarn";
2220
+ if (existsSync6("pnpm-lock.yaml")) return "pnpm";
2221
+ if (existsSync6("yarn.lock")) return "yarn";
2134
2222
  return "npm";
2135
2223
  }
2136
2224
  function parseAuditOutput(output, pm) {
@@ -2213,28 +2301,6 @@ async function audit(autoFix = false) {
2213
2301
  });
2214
2302
  }
2215
2303
 
2216
- // src/lib/version.ts
2217
- import { existsSync as existsSync6 } from "fs";
2218
- import { dirname, join as join3 } from "path";
2219
- import { fileURLToPath } from "url";
2220
- function getVhkVersion() {
2221
- const dir = dirname(fileURLToPath(import.meta.url));
2222
- for (const pkgPath of [
2223
- join3(dir, "../../package.json"),
2224
- join3(dir, "../package.json")
2225
- ]) {
2226
- try {
2227
- if (existsSync6(pkgPath)) {
2228
- const pkg = readJsonFile(pkgPath);
2229
- if (pkg.version) return pkg.version;
2230
- }
2231
- } catch {
2232
- continue;
2233
- }
2234
- }
2235
- return "0.0.0";
2236
- }
2237
-
2238
2304
  // src/mcp/cli-path.ts
2239
2305
  import { existsSync as existsSync7 } from "fs";
2240
2306
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -3187,12 +3253,12 @@ export {
3187
3253
  MAX_SECRET_FINDINGS,
3188
3254
  scanProjectForSecrets,
3189
3255
  filterSevereFindings,
3256
+ getVhkVersion,
3190
3257
  deploy,
3191
3258
  env,
3192
3259
  envCheck,
3193
3260
  publish,
3194
3261
  audit,
3195
- getVhkVersion,
3196
3262
  resolveVhkCliInvocation,
3197
3263
  startMcpServer
3198
3264
  };