@byh3071/vhk 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/{chunk-MN6LSPN6.js → chunk-WZH5CA4H.js} +312 -156
- package/dist/index.js +1537 -1039
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
|
@@ -716,6 +716,7 @@ var ko = {
|
|
|
716
716
|
allOk: "\u{1F389} \uAC1C\uBC1C \uD658\uACBD \uC900\uBE44 \uC644\uB8CC!",
|
|
717
717
|
missing: "\u26A0\uFE0F \uC77C\uBD80 \uB3C4\uAD6C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
718
718
|
missingHint: "\uC704 \uC548\uB0B4\uB97C \uB530\uB77C \uC124\uCE58\uD558\uC138\uC694.",
|
|
719
|
+
warnSummary: (n) => `\u26A0\uFE0F \uACBD\uACE0 ${n}\uAC1C \u2014 \uC704 \uAD8C\uC7A5 \uC870\uCE58\uB97C \uD655\uC778\uD558\uC138\uC694 (\uD544\uC218\uB294 \uC544\uB2D8)`,
|
|
719
720
|
projectFiles: "\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uD655\uC778:",
|
|
720
721
|
envNotIgnored: "\u26A0\uFE0F .env\uAC00 .gitignore\uC5D0 \uC5C6\uC74C! \uCD94\uAC00\uD558\uC138\uC694",
|
|
721
722
|
nextOkMessage: "\uD658\uACBD \uC810\uAC80 \uD1B5\uACFC! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uC2DC\uC791\uD558\uC138\uC694.",
|
|
@@ -728,6 +729,21 @@ var ko = {
|
|
|
728
729
|
driftRuleWarn: (files) => `\u26A0\uFE0F RULES.md\uC640 \uC5B4\uAE0B\uB09C \uADDC\uCE59 \uD30C\uC77C: ${files} \u2014 vhk sync \uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694`,
|
|
729
730
|
driftContextWarn: "\u26A0\uFE0F .vhk/context.md \uAC00 \uD604\uC7AC \uCF54\uB4DC\uBCF4\uB2E4 \uB0A1\uC558\uC5B4\uC694 \u2014 vhk context \uB85C \uAC31\uC2E0\uD558\uC138\uC694"
|
|
730
731
|
},
|
|
732
|
+
preflight: {
|
|
733
|
+
title: "\u{1F6EB} Preflight \u2014 \uCD9C\uACE0 \uC804 \uC548\uC804\uC810\uAC80",
|
|
734
|
+
resultBlocked: (n) => `\uACB0\uACFC: \uCC28\uB2E8 \u2014 \uCE58\uBA85 \uC2E4\uD328 ${n}\uAC1C. \uACE0\uCE5C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`,
|
|
735
|
+
resultPass: (warn) => warn > 0 ? `\uACB0\uACFC: \uD1B5\uACFC (\uACBD\uACE0 ${warn})` : "\uACB0\uACFC: \uD1B5\uACFC",
|
|
736
|
+
nextBlocked: "\uCE58\uBA85(\u{1F534}) \uD56D\uBAA9\uC744 \uC218\uC815\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
|
|
737
|
+
nextPass: "publish/PR \uC9C4\uD589 \uAC00\uB2A5"
|
|
738
|
+
},
|
|
739
|
+
standup: {
|
|
740
|
+
title: (d) => `\u{1F305} Standup \u2014 ${d}`,
|
|
741
|
+
yesterday: "\u{1F4CC} \uC5B4\uC81C \uD55C \uC77C",
|
|
742
|
+
noHistory: "\uC774\uC804 \uAE30\uB85D \uC5C6\uC74C \u2014 \uCCAB \uC138\uC158\uC774\uAC70\uB098 \uC2E0\uADDC repo",
|
|
743
|
+
todayRecommend: "\u{1F3AF} \uC624\uB298 \uCD94\uCC9C",
|
|
744
|
+
unresolved: "\u26A0\uFE0F \uBBF8\uD574\uACB0",
|
|
745
|
+
commitsLine: (n) => `\uCEE4\uBC0B ${n}\uAC1C`
|
|
746
|
+
},
|
|
731
747
|
nlp: {
|
|
732
748
|
matched: "\uC774\uAC8C \uB9DE\uB098\uC694?",
|
|
733
749
|
notMatched: "\uBB34\uC2A8 \uB73B\uC778\uC9C0 \uBAA8\uB974\uACA0\uC5B4\uC694. vhk\uB97C \uC785\uB825\uD558\uBA74 \uBA54\uB274\uC5D0\uC11C \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
@@ -1219,9 +1235,27 @@ function pruneBackups(keepN, rootDir) {
|
|
|
1219
1235
|
return toDelete.map((b) => b.id);
|
|
1220
1236
|
}
|
|
1221
1237
|
|
|
1238
|
+
// src/lib/atomic-write.ts
|
|
1239
|
+
import { writeFileSync, renameSync, rmSync } from "fs";
|
|
1240
|
+
import { join, dirname, basename } from "path";
|
|
1241
|
+
var writeCounter = 0;
|
|
1242
|
+
function atomicWriteFile(filePath, data) {
|
|
1243
|
+
const tmp = join(dirname(filePath), `.${basename(filePath)}.tmp-${process.pid}-${writeCounter++}`);
|
|
1244
|
+
try {
|
|
1245
|
+
writeFileSync(tmp, data, "utf-8");
|
|
1246
|
+
renameSync(tmp, filePath);
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
try {
|
|
1249
|
+
rmSync(tmp, { force: true });
|
|
1250
|
+
} catch {
|
|
1251
|
+
}
|
|
1252
|
+
throw err;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1222
1256
|
// src/lib/rules-import.ts
|
|
1223
1257
|
import { existsSync, readFileSync } from "fs";
|
|
1224
|
-
import { join } from "path";
|
|
1258
|
+
import { join as join2 } from "path";
|
|
1225
1259
|
var ADOPT_SOURCES = [
|
|
1226
1260
|
".cursorrules",
|
|
1227
1261
|
"CLAUDE.md",
|
|
@@ -1233,7 +1267,7 @@ var PREAMBLE_TITLE = "\uC11C\uBB38";
|
|
|
1233
1267
|
function detectExistingRuleFiles(cwd) {
|
|
1234
1268
|
const found = [];
|
|
1235
1269
|
for (const rel of ADOPT_SOURCES) {
|
|
1236
|
-
const full =
|
|
1270
|
+
const full = join2(cwd, rel);
|
|
1237
1271
|
if (existsSync(full)) {
|
|
1238
1272
|
try {
|
|
1239
1273
|
found.push({ path: rel, content: readFileSync(full, "utf-8") });
|
|
@@ -1591,7 +1625,7 @@ async function syncCore(rootDir, opts, confirmOverwrite) {
|
|
|
1591
1625
|
}
|
|
1592
1626
|
}
|
|
1593
1627
|
fs4.mkdirSync(path4.join(rootDir, ".vhk"), { recursive: true });
|
|
1594
|
-
|
|
1628
|
+
atomicWriteFile(path4.join(rootDir, SYNCED_MARKER_REL), (/* @__PURE__ */ new Date()).toISOString() + "\n");
|
|
1595
1629
|
ensureVhkIgnored(rootDir, ".synced");
|
|
1596
1630
|
return { dryRun: false, firstSync, backupId, backedUp, written, skipped, truncated, plan, unmapped, claudeMigration };
|
|
1597
1631
|
}
|
|
@@ -1692,9 +1726,141 @@ ${ko.sync.done}`));
|
|
|
1692
1726
|
});
|
|
1693
1727
|
}
|
|
1694
1728
|
|
|
1695
|
-
// src/
|
|
1696
|
-
import { existsSync as existsSync2 } from "fs";
|
|
1729
|
+
// src/lib/hard-stop-guard.ts
|
|
1697
1730
|
import chalk4 from "chalk";
|
|
1731
|
+
|
|
1732
|
+
// src/lib/state-files.ts
|
|
1733
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, appendFileSync, rmSync as rmSync2 } from "fs";
|
|
1734
|
+
import { join as join3 } from "path";
|
|
1735
|
+
var STATE_DIR = "docs/state";
|
|
1736
|
+
var BLOCKERS_PATH = join3(STATE_DIR, "blockers.md");
|
|
1737
|
+
var LEARNINGS_PATH = join3(STATE_DIR, "learnings.md");
|
|
1738
|
+
var VHK_DIR = ".vhk";
|
|
1739
|
+
var HARD_STOP_PATH = join3(VHK_DIR, "HARD_STOP");
|
|
1740
|
+
var HARD_STOP_BLOCKER_THRESHOLD = 3;
|
|
1741
|
+
function ensureStateDir() {
|
|
1742
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
1743
|
+
}
|
|
1744
|
+
function ensureVhkDir() {
|
|
1745
|
+
mkdirSync(VHK_DIR, { recursive: true });
|
|
1746
|
+
}
|
|
1747
|
+
function isoDate() {
|
|
1748
|
+
return localDate();
|
|
1749
|
+
}
|
|
1750
|
+
var ACTIVE_BLOCKER_RE = /^- (?!~~)\[/;
|
|
1751
|
+
function countActiveBlockers(content) {
|
|
1752
|
+
let count = 0;
|
|
1753
|
+
for (const line of content.split(/\r?\n/)) {
|
|
1754
|
+
if (ACTIVE_BLOCKER_RE.test(line)) count++;
|
|
1755
|
+
}
|
|
1756
|
+
return count;
|
|
1757
|
+
}
|
|
1758
|
+
function appendBlocker(description, goalId) {
|
|
1759
|
+
ensureStateDir();
|
|
1760
|
+
const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
|
|
1761
|
+
const line = `- [${isoDate()} ${tag}] ${description.trim()}`;
|
|
1762
|
+
if (!existsSync2(BLOCKERS_PATH)) {
|
|
1763
|
+
atomicWriteFile(
|
|
1764
|
+
BLOCKERS_PATH,
|
|
1765
|
+
`# Blockers
|
|
1766
|
+
|
|
1767
|
+
_Append-only. \uD574\uACB0 \uD56D\uBAA9\uC740 ~~\uCDE8\uC18C\uC120~~\uC73C\uB85C \uD45C\uAE30._
|
|
1768
|
+
|
|
1769
|
+
${line}
|
|
1770
|
+
`
|
|
1771
|
+
);
|
|
1772
|
+
} else {
|
|
1773
|
+
appendFileSync(BLOCKERS_PATH, `${line}
|
|
1774
|
+
`, "utf-8");
|
|
1775
|
+
}
|
|
1776
|
+
const current = readFileSync2(BLOCKERS_PATH, "utf-8");
|
|
1777
|
+
const count = countActiveBlockers(current);
|
|
1778
|
+
let hardStopTripped = false;
|
|
1779
|
+
if (count >= HARD_STOP_BLOCKER_THRESHOLD && !existsSync2(HARD_STOP_PATH)) {
|
|
1780
|
+
writeHardStop(`auto: ${count} active blockers (threshold ${HARD_STOP_BLOCKER_THRESHOLD})`);
|
|
1781
|
+
hardStopTripped = true;
|
|
1782
|
+
}
|
|
1783
|
+
return { count, hardStopTripped };
|
|
1784
|
+
}
|
|
1785
|
+
function getActiveBlockers(limit = 3) {
|
|
1786
|
+
if (!existsSync2(BLOCKERS_PATH)) return [];
|
|
1787
|
+
const lines = readFileSync2(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
|
|
1788
|
+
const entries = lines.filter((l) => ACTIVE_BLOCKER_RE.test(l));
|
|
1789
|
+
return entries.slice(-limit);
|
|
1790
|
+
}
|
|
1791
|
+
function writeHardStop(reason) {
|
|
1792
|
+
ensureVhkDir();
|
|
1793
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1794
|
+
atomicWriteFile(HARD_STOP_PATH, `${ts}
|
|
1795
|
+
${reason}
|
|
1796
|
+
`);
|
|
1797
|
+
}
|
|
1798
|
+
function isHardStopActive() {
|
|
1799
|
+
return existsSync2(HARD_STOP_PATH);
|
|
1800
|
+
}
|
|
1801
|
+
function readHardStopReason() {
|
|
1802
|
+
if (!existsSync2(HARD_STOP_PATH)) return null;
|
|
1803
|
+
try {
|
|
1804
|
+
return readFileSync2(HARD_STOP_PATH, "utf-8").trim();
|
|
1805
|
+
} catch {
|
|
1806
|
+
return null;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
function clearHardStop() {
|
|
1810
|
+
if (!existsSync2(HARD_STOP_PATH)) return false;
|
|
1811
|
+
rmSync2(HARD_STOP_PATH, { force: true });
|
|
1812
|
+
return true;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/lib/hard-stop-guard.ts
|
|
1816
|
+
function ensureNotHardStopped(action) {
|
|
1817
|
+
if (!isHardStopActive()) return true;
|
|
1818
|
+
console.error(chalk4.red.bold(`
|
|
1819
|
+
\u{1F6D1} HARD STOP \uD65C\uC131 \u2014 '${action}' \uC744(\uB97C) \uC2E4\uD589\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`));
|
|
1820
|
+
const reason = readHardStopReason();
|
|
1821
|
+
if (reason) console.error(chalk4.dim(` \uC0AC\uC720: ${reason.replace(/\s*\n\s*/g, " ")}`));
|
|
1822
|
+
console.error(chalk4.dim(" \uD574\uC81C: vhk resume --confirm (\uC0AC\uB78C\uC774 \uC9C1\uC811 \uC2E4\uD589)"));
|
|
1823
|
+
process.exitCode = 1;
|
|
1824
|
+
return false;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// src/lib/version.ts
|
|
1828
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1829
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1830
|
+
import { fileURLToPath } from "url";
|
|
1831
|
+
|
|
1832
|
+
// src/lib/read-json.ts
|
|
1833
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1834
|
+
function stripBom(text) {
|
|
1835
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1836
|
+
}
|
|
1837
|
+
function readJsonFile(filePath) {
|
|
1838
|
+
const raw = stripBom(readFileSync3(filePath, "utf-8"));
|
|
1839
|
+
return JSON.parse(raw);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// src/lib/version.ts
|
|
1843
|
+
function getVhkVersion() {
|
|
1844
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
1845
|
+
for (const pkgPath of [
|
|
1846
|
+
join4(dir, "../../package.json"),
|
|
1847
|
+
join4(dir, "../package.json")
|
|
1848
|
+
]) {
|
|
1849
|
+
try {
|
|
1850
|
+
if (existsSync3(pkgPath)) {
|
|
1851
|
+
const pkg = readJsonFile(pkgPath);
|
|
1852
|
+
if (pkg.version) return pkg.version;
|
|
1853
|
+
}
|
|
1854
|
+
} catch {
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
return "0.0.0";
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// src/commands/deploy.ts
|
|
1862
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1863
|
+
import chalk5 from "chalk";
|
|
1698
1864
|
import inquirer2 from "inquirer";
|
|
1699
1865
|
|
|
1700
1866
|
// src/lib/exec.ts
|
|
@@ -1794,7 +1960,7 @@ var PLATFORMS = {
|
|
|
1794
1960
|
function detectPlatform() {
|
|
1795
1961
|
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
1796
1962
|
for (const file of config.detectFiles) {
|
|
1797
|
-
if (
|
|
1963
|
+
if (existsSync4(file)) return key;
|
|
1798
1964
|
}
|
|
1799
1965
|
}
|
|
1800
1966
|
return null;
|
|
@@ -1803,11 +1969,11 @@ function isCLIAvailable(cmd, checkArgs) {
|
|
|
1803
1969
|
return safeExecFile(cmd, checkArgs).ok;
|
|
1804
1970
|
}
|
|
1805
1971
|
async function deploy() {
|
|
1806
|
-
console.log(
|
|
1807
|
-
console.log(
|
|
1972
|
+
console.log(chalk5.bold("\n\u{1F680} " + t("deploy.title")));
|
|
1973
|
+
console.log(chalk5.gray("\u2500".repeat(40)));
|
|
1808
1974
|
let platform = detectPlatform();
|
|
1809
1975
|
if (platform) {
|
|
1810
|
-
console.log(
|
|
1976
|
+
console.log(chalk5.cyan(`
|
|
1811
1977
|
\u{1F50D} \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${PLATFORMS[platform].name}`));
|
|
1812
1978
|
} else {
|
|
1813
1979
|
const { selected } = await inquirer2.prompt([
|
|
@@ -1826,9 +1992,9 @@ async function deploy() {
|
|
|
1826
1992
|
}
|
|
1827
1993
|
const config = PLATFORMS[platform];
|
|
1828
1994
|
if (!isCLIAvailable(config.command, config.checkArgs)) {
|
|
1829
|
-
console.log(
|
|
1995
|
+
console.log(chalk5.red(`
|
|
1830
1996
|
\u274C ${config.name} CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
1831
|
-
console.log(
|
|
1997
|
+
console.log(chalk5.yellow(` \u2192 ${config.installHint}`));
|
|
1832
1998
|
return;
|
|
1833
1999
|
}
|
|
1834
2000
|
const { confirm } = await inquirer2.prompt([
|
|
@@ -1840,15 +2006,15 @@ async function deploy() {
|
|
|
1840
2006
|
}
|
|
1841
2007
|
]);
|
|
1842
2008
|
if (!confirm) {
|
|
1843
|
-
console.log(
|
|
2009
|
+
console.log(chalk5.gray("\uCDE8\uC18C\uB428"));
|
|
1844
2010
|
return;
|
|
1845
2011
|
}
|
|
1846
|
-
console.log(
|
|
2012
|
+
console.log(chalk5.cyan(`
|
|
1847
2013
|
${t("deploy.deploying")}
|
|
1848
2014
|
`));
|
|
1849
2015
|
const result = safeExecFileStream(config.command, config.commandArgs);
|
|
1850
2016
|
if (result.ok) {
|
|
1851
|
-
console.log(
|
|
2017
|
+
console.log(chalk5.green(`
|
|
1852
2018
|
\u2705 ${t("deploy.success")}`));
|
|
1853
2019
|
printNextStep({
|
|
1854
2020
|
message: "\uBC30\uD3EC \uC644\uB8CC! \uC0AC\uC774\uD2B8\uB97C \uD655\uC778\uD558\uC138\uC694.",
|
|
@@ -1856,16 +2022,16 @@ ${t("deploy.deploying")}
|
|
|
1856
2022
|
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
1857
2023
|
});
|
|
1858
2024
|
} else {
|
|
1859
|
-
console.log(
|
|
2025
|
+
console.log(chalk5.red(`
|
|
1860
2026
|
\u274C ${t("deploy.failed")}`));
|
|
1861
|
-
console.log(
|
|
2027
|
+
console.log(chalk5.red(result.err));
|
|
1862
2028
|
}
|
|
1863
2029
|
}
|
|
1864
2030
|
|
|
1865
2031
|
// src/commands/env.ts
|
|
1866
|
-
import { existsSync as
|
|
1867
|
-
import { join as
|
|
1868
|
-
import
|
|
2032
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, readdirSync } from "fs";
|
|
2033
|
+
import { join as join5 } from "path";
|
|
2034
|
+
import chalk6 from "chalk";
|
|
1869
2035
|
function parseEnvKeys(content) {
|
|
1870
2036
|
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
|
|
1871
2037
|
}
|
|
@@ -1881,7 +2047,7 @@ function loadDefinedEnvKeys(dir = ".") {
|
|
|
1881
2047
|
if (!name.startsWith(".env")) continue;
|
|
1882
2048
|
if (name.endsWith(".example") || name.endsWith(".sample")) continue;
|
|
1883
2049
|
try {
|
|
1884
|
-
for (const k of parseEnvKeys(
|
|
2050
|
+
for (const k of parseEnvKeys(readFileSync4(join5(dir, name), "utf-8"))) keys.add(k);
|
|
1885
2051
|
} catch {
|
|
1886
2052
|
}
|
|
1887
2053
|
}
|
|
@@ -1889,36 +2055,37 @@ function loadDefinedEnvKeys(dir = ".") {
|
|
|
1889
2055
|
}
|
|
1890
2056
|
function ensureGitignore() {
|
|
1891
2057
|
const gitignorePath = ".gitignore";
|
|
1892
|
-
if (
|
|
1893
|
-
const content =
|
|
2058
|
+
if (existsSync5(gitignorePath)) {
|
|
2059
|
+
const content = readFileSync4(gitignorePath, "utf-8");
|
|
1894
2060
|
if (!content.split("\n").some((l) => l.trim() === ".env")) {
|
|
1895
|
-
|
|
1896
|
-
console.log(
|
|
2061
|
+
appendFileSync2(gitignorePath, "\n.env\n");
|
|
2062
|
+
console.log(chalk6.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
|
|
1897
2063
|
}
|
|
1898
2064
|
} else {
|
|
1899
|
-
|
|
1900
|
-
console.log(
|
|
2065
|
+
writeFileSync2(gitignorePath, ".env\nnode_modules/\ndist/\n");
|
|
2066
|
+
console.log(chalk6.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
|
|
1901
2067
|
}
|
|
1902
2068
|
}
|
|
1903
2069
|
async function env() {
|
|
1904
|
-
|
|
1905
|
-
console.log(
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
console.log(
|
|
2070
|
+
if (!ensureNotHardStopped("env")) return;
|
|
2071
|
+
console.log(chalk6.bold("\n\u{1F510} " + t("env.title")));
|
|
2072
|
+
console.log(chalk6.gray("\u2500".repeat(40)));
|
|
2073
|
+
if (!existsSync5(".env")) {
|
|
2074
|
+
console.log(chalk6.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2075
|
+
console.log(chalk6.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
|
|
1909
2076
|
return;
|
|
1910
2077
|
}
|
|
1911
|
-
const envContent =
|
|
2078
|
+
const envContent = readFileSync4(".env", "utf-8");
|
|
1912
2079
|
const keys = parseEnvKeys(envContent);
|
|
1913
2080
|
if (keys.length === 0) {
|
|
1914
|
-
console.log(
|
|
2081
|
+
console.log(chalk6.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
1915
2082
|
return;
|
|
1916
2083
|
}
|
|
1917
2084
|
const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
1918
|
-
|
|
1919
|
-
console.log(
|
|
2085
|
+
writeFileSync2(".env.example", exampleContent, "utf-8");
|
|
2086
|
+
console.log(chalk6.green(`
|
|
1920
2087
|
\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`));
|
|
1921
|
-
keys.forEach((k) => console.log(
|
|
2088
|
+
keys.forEach((k) => console.log(chalk6.gray(` ${k}`)));
|
|
1922
2089
|
ensureGitignore();
|
|
1923
2090
|
printNextStep({
|
|
1924
2091
|
message: ".env.example \uC0DD\uC131 \uC644\uB8CC!",
|
|
@@ -1927,51 +2094,39 @@ async function env() {
|
|
|
1927
2094
|
});
|
|
1928
2095
|
}
|
|
1929
2096
|
async function envCheck() {
|
|
1930
|
-
console.log(
|
|
1931
|
-
console.log(
|
|
1932
|
-
if (!
|
|
1933
|
-
console.log(
|
|
2097
|
+
console.log(chalk6.bold("\n\u{1F50D} " + t("env.checkTitle")));
|
|
2098
|
+
console.log(chalk6.gray("\u2500".repeat(40)));
|
|
2099
|
+
if (!existsSync5(".env.example")) {
|
|
2100
|
+
console.log(chalk6.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
|
|
1934
2101
|
return;
|
|
1935
2102
|
}
|
|
1936
|
-
const requiredKeys = parseEnvKeys(
|
|
2103
|
+
const requiredKeys = parseEnvKeys(readFileSync4(".env.example", "utf-8"));
|
|
1937
2104
|
const currentKeys = loadDefinedEnvKeys();
|
|
1938
2105
|
const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
|
|
1939
2106
|
const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
|
|
1940
|
-
console.log(
|
|
2107
|
+
console.log(chalk6.cyan(`
|
|
1941
2108
|
\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`));
|
|
1942
2109
|
if (missing.length === 0) {
|
|
1943
|
-
console.log(
|
|
2110
|
+
console.log(chalk6.green("\n\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!"));
|
|
1944
2111
|
} else {
|
|
1945
|
-
console.log(
|
|
2112
|
+
console.log(chalk6.red(`
|
|
1946
2113
|
\u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`));
|
|
1947
|
-
missing.forEach((k) => console.log(
|
|
2114
|
+
missing.forEach((k) => console.log(chalk6.red(` \u2022 ${k}`)));
|
|
1948
2115
|
process.exitCode = 1;
|
|
1949
2116
|
}
|
|
1950
2117
|
if (extra.length > 0) {
|
|
1951
|
-
console.log(
|
|
2118
|
+
console.log(chalk6.yellow(`
|
|
1952
2119
|
\u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`));
|
|
1953
|
-
extra.forEach((k) => console.log(
|
|
2120
|
+
extra.forEach((k) => console.log(chalk6.yellow(` \u2022 ${k}`)));
|
|
1954
2121
|
}
|
|
1955
2122
|
ensureGitignore();
|
|
1956
2123
|
}
|
|
1957
2124
|
|
|
1958
2125
|
// src/commands/publish.ts
|
|
1959
|
-
import { existsSync as
|
|
1960
|
-
import
|
|
2126
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
2127
|
+
import chalk7 from "chalk";
|
|
1961
2128
|
import inquirer3 from "inquirer";
|
|
1962
2129
|
import ora from "ora";
|
|
1963
|
-
|
|
1964
|
-
// src/lib/read-json.ts
|
|
1965
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
1966
|
-
function stripBom(text) {
|
|
1967
|
-
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1968
|
-
}
|
|
1969
|
-
function readJsonFile(filePath) {
|
|
1970
|
-
const raw = stripBom(readFileSync3(filePath, "utf-8"));
|
|
1971
|
-
return JSON.parse(raw);
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
// src/commands/publish.ts
|
|
1975
2130
|
function bumpVersion(current, type) {
|
|
1976
2131
|
const [major, minor, patch] = current.split(".").map((n) => parseInt(n, 10) || 0);
|
|
1977
2132
|
switch (type) {
|
|
@@ -2001,7 +2156,9 @@ function bumpClaudeMdVersion(content, newVersion) {
|
|
|
2001
2156
|
return content.replace(/(\*\*버전:\*\*\s*v)\d+\.\d+\.\d+/, `$1${newVersion}`);
|
|
2002
2157
|
}
|
|
2003
2158
|
function gitPostRelease(newVersion) {
|
|
2004
|
-
const filesToAdd =
|
|
2159
|
+
const filesToAdd = ["package.json"];
|
|
2160
|
+
if (existsSync6("CHANGELOG.md")) filesToAdd.push("CHANGELOG.md");
|
|
2161
|
+
if (existsSync6("CLAUDE.md")) filesToAdd.push("CLAUDE.md");
|
|
2005
2162
|
const add = safeExecFile("git", ["add", ...filesToAdd]);
|
|
2006
2163
|
if (!add.ok) {
|
|
2007
2164
|
return {
|
|
@@ -2058,28 +2215,28 @@ function publishPreflight() {
|
|
|
2058
2215
|
return { ...evaluatePublishPreflight(branch, trackedStatus, defaultBranch), branch, defaultBranch };
|
|
2059
2216
|
}
|
|
2060
2217
|
async function publish() {
|
|
2061
|
-
console.log(
|
|
2062
|
-
console.log(
|
|
2218
|
+
console.log(chalk7.bold("\n\u{1F4E6} " + t("publish.title")));
|
|
2219
|
+
console.log(chalk7.gray("\u2500".repeat(40)));
|
|
2063
2220
|
const pre = publishPreflight();
|
|
2064
2221
|
if (!pre.ok) {
|
|
2065
2222
|
const msg = pre.code === "wrong-branch" ? t("publish.preflightWrongBranch", pre.branch || "(detached)", pre.defaultBranch) : t("publish.preflightDirty");
|
|
2066
|
-
console.log(
|
|
2223
|
+
console.log(chalk7.red(`
|
|
2067
2224
|
\u274C ${msg}`));
|
|
2068
2225
|
return;
|
|
2069
2226
|
}
|
|
2070
|
-
if (!
|
|
2071
|
-
console.log(
|
|
2227
|
+
if (!existsSync6("package.json")) {
|
|
2228
|
+
console.log(chalk7.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2072
2229
|
return;
|
|
2073
2230
|
}
|
|
2074
2231
|
let pkg;
|
|
2075
2232
|
try {
|
|
2076
2233
|
pkg = readJsonFile("package.json");
|
|
2077
2234
|
} catch {
|
|
2078
|
-
console.log(
|
|
2235
|
+
console.log(chalk7.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
|
|
2079
2236
|
return;
|
|
2080
2237
|
}
|
|
2081
2238
|
const currentVersion = pkg.version || "0.0.0";
|
|
2082
|
-
console.log(
|
|
2239
|
+
console.log(chalk7.cyan(`
|
|
2083
2240
|
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${currentVersion}`));
|
|
2084
2241
|
const { bumpType } = await inquirer3.prompt([
|
|
2085
2242
|
{
|
|
@@ -2094,29 +2251,29 @@ async function publish() {
|
|
|
2094
2251
|
}
|
|
2095
2252
|
]);
|
|
2096
2253
|
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
2097
|
-
console.log(
|
|
2254
|
+
console.log(chalk7.cyan(`
|
|
2098
2255
|
\u{1F195} \uC0C8 \uBC84\uC804: v${newVersion}`));
|
|
2099
2256
|
pkg.version = newVersion;
|
|
2100
|
-
|
|
2101
|
-
console.log(
|
|
2102
|
-
const claudeMdOriginal =
|
|
2257
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
2258
|
+
console.log(chalk7.green("\u2705 package.json \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8"));
|
|
2259
|
+
const claudeMdOriginal = existsSync6("CLAUDE.md") ? readFileSync5("CLAUDE.md", "utf-8") : null;
|
|
2103
2260
|
if (claudeMdOriginal !== null) {
|
|
2104
2261
|
const bumped = bumpClaudeMdVersion(claudeMdOriginal, newVersion);
|
|
2105
2262
|
if (bumped !== claudeMdOriginal) {
|
|
2106
|
-
|
|
2107
|
-
console.log(
|
|
2263
|
+
writeFileSync3("CLAUDE.md", bumped, "utf-8");
|
|
2264
|
+
console.log(chalk7.green("\u2705 CLAUDE.md \uBC84\uC804\uC904 \uB3D9\uAE30\uD654"));
|
|
2108
2265
|
}
|
|
2109
2266
|
}
|
|
2110
2267
|
const rollbackVersion = () => {
|
|
2111
2268
|
pkg.version = currentVersion;
|
|
2112
|
-
|
|
2113
|
-
if (claudeMdOriginal !== null)
|
|
2269
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
2270
|
+
if (claudeMdOriginal !== null) writeFileSync3("CLAUDE.md", claudeMdOriginal, "utf-8");
|
|
2114
2271
|
};
|
|
2115
2272
|
const buildSpinner = ora(t("publish.building")).start();
|
|
2116
2273
|
const buildResult = safeExecFile("pnpm", ["build"]);
|
|
2117
2274
|
if (!buildResult.ok) {
|
|
2118
2275
|
buildSpinner.fail(t("publish.buildFailed"));
|
|
2119
|
-
console.log(
|
|
2276
|
+
console.log(chalk7.red(buildResult.err.slice(0, 500)));
|
|
2120
2277
|
rollbackVersion();
|
|
2121
2278
|
return;
|
|
2122
2279
|
}
|
|
@@ -2125,7 +2282,7 @@ async function publish() {
|
|
|
2125
2282
|
const testResult = safeExecFile("pnpm", ["test", "--run"]);
|
|
2126
2283
|
if (!testResult.ok) {
|
|
2127
2284
|
testSpinner.fail(t("publish.testFailed"));
|
|
2128
|
-
console.log(
|
|
2285
|
+
console.log(chalk7.red(testResult.err.slice(0, 500)));
|
|
2129
2286
|
rollbackVersion();
|
|
2130
2287
|
return;
|
|
2131
2288
|
}
|
|
@@ -2140,45 +2297,45 @@ async function publish() {
|
|
|
2140
2297
|
]);
|
|
2141
2298
|
if (!confirm) {
|
|
2142
2299
|
rollbackVersion();
|
|
2143
|
-
console.log(
|
|
2300
|
+
console.log(chalk7.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
|
|
2144
2301
|
return;
|
|
2145
2302
|
}
|
|
2146
|
-
console.log(
|
|
2303
|
+
console.log(chalk7.cyan(`
|
|
2147
2304
|
\u{1F4E4} ${t("publish.publishing")}`));
|
|
2148
|
-
console.log(
|
|
2305
|
+
console.log(chalk7.gray(" 2FA \uD65C\uC131\uD654 \uC2DC: OTP 6\uC790\uB9AC \uC785\uB825 \uB610\uB294 \uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D URL \uD074\uB9AD (Windows Hello / PIN \uC9C0\uC6D0)"));
|
|
2149
2306
|
const pubResult = safeExecFileStream("npm", ["publish", "--access", "public"]);
|
|
2150
2307
|
if (!pubResult.ok) {
|
|
2151
|
-
console.log(
|
|
2308
|
+
console.log(chalk7.red(`
|
|
2152
2309
|
\u2716 ${t("publish.publishFailed")}`));
|
|
2153
|
-
console.log(
|
|
2310
|
+
console.log(chalk7.red(pubResult.err.slice(0, 500)));
|
|
2154
2311
|
rollbackVersion();
|
|
2155
|
-
console.log(
|
|
2312
|
+
console.log(chalk7.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
|
|
2156
2313
|
return;
|
|
2157
2314
|
}
|
|
2158
|
-
console.log(
|
|
2315
|
+
console.log(chalk7.green(`
|
|
2159
2316
|
\u2714 ${t("publish.publishSuccess")}`));
|
|
2160
|
-
if (
|
|
2161
|
-
const cl =
|
|
2162
|
-
const date = (
|
|
2317
|
+
if (existsSync6("CHANGELOG.md")) {
|
|
2318
|
+
const cl = readFileSync5("CHANGELOG.md", "utf-8");
|
|
2319
|
+
const date = localDate();
|
|
2163
2320
|
const updated = insertChangelogStub(cl, newVersion, date);
|
|
2164
2321
|
if (updated !== cl) {
|
|
2165
|
-
|
|
2166
|
-
console.log(
|
|
2322
|
+
writeFileSync3("CHANGELOG.md", updated, "utf-8");
|
|
2323
|
+
console.log(chalk7.green(`\u2705 CHANGELOG.md \uC5D0 [${newVersion}] \uC2A4\uD141 \uCD94\uAC00 \u2014 \uBCF8\uBB38 \uBCF4\uAC15 \uD544\uC694`));
|
|
2167
2324
|
}
|
|
2168
2325
|
}
|
|
2169
2326
|
const git = gitPostRelease(newVersion);
|
|
2170
2327
|
if (git.warning) {
|
|
2171
|
-
console.log(
|
|
2328
|
+
console.log(chalk7.yellow(`
|
|
2172
2329
|
\u26A0\uFE0F ${git.warning}`));
|
|
2173
|
-
console.log(
|
|
2330
|
+
console.log(chalk7.dim(` npm \uBC30\uD3EC\uB294 \uC774\uBBF8 \uC131\uACF5\uD588\uC2B5\uB2C8\uB2E4 (v${newVersion}).`));
|
|
2174
2331
|
} else if (git.tagged && git.pushed) {
|
|
2175
|
-
console.log(
|
|
2332
|
+
console.log(chalk7.green(`
|
|
2176
2333
|
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131 + push \uC644\uB8CC`));
|
|
2177
2334
|
} else if (git.tagged) {
|
|
2178
|
-
console.log(
|
|
2335
|
+
console.log(chalk7.yellow(`
|
|
2179
2336
|
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131\uB428 (push\uB294 \uC218\uB3D9\uC73C\uB85C)`));
|
|
2180
2337
|
}
|
|
2181
|
-
console.log(
|
|
2338
|
+
console.log(chalk7.green.bold(`
|
|
2182
2339
|
\u{1F389} v${newVersion} \uBC30\uD3EC \uC644\uB8CC!`));
|
|
2183
2340
|
printNextStep({
|
|
2184
2341
|
message: "npm \uBC30\uD3EC \uC644\uB8CC!",
|
|
@@ -2188,13 +2345,13 @@ async function publish() {
|
|
|
2188
2345
|
}
|
|
2189
2346
|
|
|
2190
2347
|
// src/commands/audit.ts
|
|
2191
|
-
import { existsSync as
|
|
2192
|
-
import
|
|
2348
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2349
|
+
import chalk8 from "chalk";
|
|
2193
2350
|
import inquirer4 from "inquirer";
|
|
2194
2351
|
import ora2 from "ora";
|
|
2195
2352
|
function detectCurrentPM() {
|
|
2196
|
-
if (
|
|
2197
|
-
if (
|
|
2353
|
+
if (existsSync7("pnpm-lock.yaml")) return "pnpm";
|
|
2354
|
+
if (existsSync7("yarn.lock")) return "yarn";
|
|
2198
2355
|
return "npm";
|
|
2199
2356
|
}
|
|
2200
2357
|
function parseAuditOutput(output, pm) {
|
|
@@ -2234,24 +2391,24 @@ function runAuditFix(pm) {
|
|
|
2234
2391
|
return result.ok ? { ok: true } : { ok: false, err: result.err };
|
|
2235
2392
|
}
|
|
2236
2393
|
async function audit(autoFix = false) {
|
|
2237
|
-
console.log(
|
|
2238
|
-
console.log(
|
|
2394
|
+
console.log(chalk8.bold("\n\u{1F6E1}\uFE0F " + t("audit.title")));
|
|
2395
|
+
console.log(chalk8.gray("\u2500".repeat(40)));
|
|
2239
2396
|
const pm = detectCurrentPM();
|
|
2240
|
-
console.log(
|
|
2397
|
+
console.log(chalk8.cyan(`\u{1F4E6} \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${pm}`));
|
|
2241
2398
|
const spinner = ora2("\uBCF4\uC548 \uAC10\uC0AC \uC2E4\uD589 \uC911...").start();
|
|
2242
2399
|
const output = runAuditJson(pm);
|
|
2243
2400
|
spinner.stop();
|
|
2244
2401
|
const summary = parseAuditOutput(output, pm);
|
|
2245
2402
|
if (summary.total === 0) {
|
|
2246
|
-
console.log(
|
|
2403
|
+
console.log(chalk8.green.bold("\n\u{1F389} \uCDE8\uC57D\uC810\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4!"));
|
|
2247
2404
|
return;
|
|
2248
2405
|
}
|
|
2249
|
-
console.log(
|
|
2250
|
-
if (summary.critical > 0) console.log(
|
|
2251
|
-
if (summary.high > 0) console.log(
|
|
2252
|
-
if (summary.moderate > 0) console.log(
|
|
2253
|
-
if (summary.low > 0) console.log(
|
|
2254
|
-
console.log(
|
|
2406
|
+
console.log(chalk8.bold("\n\u{1F4CA} \uCDE8\uC57D\uC810 \uC694\uC57D:"));
|
|
2407
|
+
if (summary.critical > 0) console.log(chalk8.red(` \u{1F534} Critical: ${summary.critical}`));
|
|
2408
|
+
if (summary.high > 0) console.log(chalk8.red(` \u{1F7E0} High: ${summary.high}`));
|
|
2409
|
+
if (summary.moderate > 0) console.log(chalk8.yellow(` \u{1F7E1} Moderate: ${summary.moderate}`));
|
|
2410
|
+
if (summary.low > 0) console.log(chalk8.gray(` \u26AA Low: ${summary.low}`));
|
|
2411
|
+
console.log(chalk8.bold(`
|
|
2255
2412
|
\uCD1D ${summary.total}\uAC1C\uC758 \uCDE8\uC57D\uC810`));
|
|
2256
2413
|
const shouldRunFix = autoFix ? true : summary.critical > 0 || summary.high > 0 ? (await inquirer4.prompt([
|
|
2257
2414
|
{
|
|
@@ -2277,32 +2434,10 @@ async function audit(autoFix = false) {
|
|
|
2277
2434
|
});
|
|
2278
2435
|
}
|
|
2279
2436
|
|
|
2280
|
-
// src/lib/version.ts
|
|
2281
|
-
import { existsSync as existsSync6 } from "fs";
|
|
2282
|
-
import { dirname, join as join3 } from "path";
|
|
2283
|
-
import { fileURLToPath } from "url";
|
|
2284
|
-
function getVhkVersion() {
|
|
2285
|
-
const dir = dirname(fileURLToPath(import.meta.url));
|
|
2286
|
-
for (const pkgPath of [
|
|
2287
|
-
join3(dir, "../../package.json"),
|
|
2288
|
-
join3(dir, "../package.json")
|
|
2289
|
-
]) {
|
|
2290
|
-
try {
|
|
2291
|
-
if (existsSync6(pkgPath)) {
|
|
2292
|
-
const pkg = readJsonFile(pkgPath);
|
|
2293
|
-
if (pkg.version) return pkg.version;
|
|
2294
|
-
}
|
|
2295
|
-
} catch {
|
|
2296
|
-
continue;
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
return "0.0.0";
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
2437
|
// src/mcp/cli-path.ts
|
|
2303
|
-
import { existsSync as
|
|
2438
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2304
2439
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2305
|
-
import { dirname as
|
|
2440
|
+
import { dirname as dirname3, resolve } from "path";
|
|
2306
2441
|
function pickCliInvocation(globalAvailable, localCli, localExists) {
|
|
2307
2442
|
if (globalAvailable) return { bin: "vhk", prefixArgs: [], fallback: false };
|
|
2308
2443
|
if (localExists) return { bin: process.execPath, prefixArgs: [localCli], fallback: true };
|
|
@@ -2312,20 +2447,20 @@ function composeInvocation(cli, args) {
|
|
|
2312
2447
|
return { bin: cli.bin, args: [...cli.prefixArgs, ...args] };
|
|
2313
2448
|
}
|
|
2314
2449
|
function localCliPath() {
|
|
2315
|
-
const here =
|
|
2450
|
+
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
2316
2451
|
return resolve(here, "..", "index.js");
|
|
2317
2452
|
}
|
|
2318
2453
|
function resolveVhkCliInvocation() {
|
|
2319
2454
|
const globalAvailable = safeExecFile("vhk", ["--version"]).ok;
|
|
2320
2455
|
const local = localCliPath();
|
|
2321
|
-
return pickCliInvocation(globalAvailable, local,
|
|
2456
|
+
return pickCliInvocation(globalAvailable, local, existsSync8(local));
|
|
2322
2457
|
}
|
|
2323
2458
|
|
|
2324
2459
|
// src/mcp/server.ts
|
|
2325
2460
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2326
2461
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2327
2462
|
import { z } from "zod";
|
|
2328
|
-
import { existsSync as
|
|
2463
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, appendFileSync as appendFileSync3 } from "fs";
|
|
2329
2464
|
|
|
2330
2465
|
// src/lib/scan-secrets.ts
|
|
2331
2466
|
import fs7 from "fs";
|
|
@@ -2401,7 +2536,7 @@ import path6 from "path";
|
|
|
2401
2536
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
2402
2537
|
import fs5 from "fs";
|
|
2403
2538
|
import path5 from "path";
|
|
2404
|
-
import
|
|
2539
|
+
import chalk9 from "chalk";
|
|
2405
2540
|
function loadGitignore(rootDir) {
|
|
2406
2541
|
const ig = (0, import_ignore.default)();
|
|
2407
2542
|
const gitignorePath = path5.join(rootDir, ".gitignore");
|
|
@@ -2475,7 +2610,7 @@ function printSecurityWarnings(rootDir = process.cwd()) {
|
|
|
2475
2610
|
const result = checkProjectSecurity(rootDir);
|
|
2476
2611
|
if (result.ok) return true;
|
|
2477
2612
|
for (const w of result.warnings) {
|
|
2478
|
-
console.log(
|
|
2613
|
+
console.log(chalk9.yellow(` \u26A0\uFE0F ${w}`));
|
|
2479
2614
|
}
|
|
2480
2615
|
return false;
|
|
2481
2616
|
}
|
|
@@ -2616,6 +2751,14 @@ var SERVER_VERSION = getVhkVersion();
|
|
|
2616
2751
|
function isGitRepo() {
|
|
2617
2752
|
return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
|
|
2618
2753
|
}
|
|
2754
|
+
function hardStopBlocked(action) {
|
|
2755
|
+
if (!isHardStopActive()) return null;
|
|
2756
|
+
const reason = readHardStopReason();
|
|
2757
|
+
const text = `\u{1F6D1} HARD STOP \uD65C\uC131 \u2014 '${action}' \uC744(\uB97C) \uC2E4\uD589\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.` + (reason ? `
|
|
2758
|
+
\uC0AC\uC720: ${reason.replace(/\s*\n\s*/g, " ")}` : "") + `
|
|
2759
|
+
\uD574\uC81C: vhk resume --confirm (\uC0AC\uB78C\uC774 \uC9C1\uC811 \uC2E4\uD589)`;
|
|
2760
|
+
return { content: [{ type: "text", text }] };
|
|
2761
|
+
}
|
|
2619
2762
|
var ANSI_RE = /\x1B\[[0-9;?]*[ -/]*[@-~]/g;
|
|
2620
2763
|
function stripAnsi(s) {
|
|
2621
2764
|
return s.replace(ANSI_RE, "");
|
|
@@ -2649,6 +2792,8 @@ function createVhkMcpServer() {
|
|
|
2649
2792
|
}
|
|
2650
2793
|
},
|
|
2651
2794
|
async ({ message }) => {
|
|
2795
|
+
const blocked = hardStopBlocked("save");
|
|
2796
|
+
if (blocked) return blocked;
|
|
2652
2797
|
if (!isGitRepo()) {
|
|
2653
2798
|
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
2654
2799
|
}
|
|
@@ -2710,6 +2855,8 @@ ${preview}${more}
|
|
|
2710
2855
|
}
|
|
2711
2856
|
},
|
|
2712
2857
|
async ({ confirm }) => {
|
|
2858
|
+
const blocked = hardStopBlocked("undo");
|
|
2859
|
+
if (blocked) return blocked;
|
|
2713
2860
|
if (!isGitRepo()) {
|
|
2714
2861
|
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
2715
2862
|
}
|
|
@@ -2750,7 +2897,7 @@ ${last.out}
|
|
|
2750
2897
|
);
|
|
2751
2898
|
server.registerTool("status", { description: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uB300\uC2DC\uBCF4\uB4DC (\uBE0C\uB79C\uCE58/\uBCC0\uACBD\uC0AC\uD56D/\uCD5C\uADFC \uCEE4\uBC0B)" }, async () => {
|
|
2752
2899
|
const lines = [];
|
|
2753
|
-
if (
|
|
2900
|
+
if (existsSync9("package.json")) {
|
|
2754
2901
|
try {
|
|
2755
2902
|
const pkg = readJsonFile("package.json");
|
|
2756
2903
|
lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
|
|
@@ -2835,7 +2982,7 @@ ${last.out}
|
|
|
2835
2982
|
checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
|
|
2836
2983
|
const test = safeExecFile("pnpm", ["test", "--run"]);
|
|
2837
2984
|
checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
|
|
2838
|
-
if (
|
|
2985
|
+
if (existsSync9("package.json")) {
|
|
2839
2986
|
try {
|
|
2840
2987
|
const pkg = readJsonFile("package.json");
|
|
2841
2988
|
checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
|
|
@@ -2873,11 +3020,11 @@ ${last.out}
|
|
|
2873
3020
|
const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
|
|
2874
3021
|
const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
|
|
2875
3022
|
required.forEach((f) => {
|
|
2876
|
-
lines.push(` ${
|
|
3023
|
+
lines.push(` ${existsSync9(f) ? "\u2705" : "\u274C"} ${f}`);
|
|
2877
3024
|
});
|
|
2878
3025
|
lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
|
|
2879
3026
|
recommended.forEach((f) => {
|
|
2880
|
-
lines.push(` ${
|
|
3027
|
+
lines.push(` ${existsSync9(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
|
|
2881
3028
|
});
|
|
2882
3029
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2883
3030
|
});
|
|
@@ -2911,24 +3058,26 @@ ${log.out}` }] };
|
|
|
2911
3058
|
description: ".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore\uC5D0 .env \uC790\uB3D9 \uCD94\uAC00"
|
|
2912
3059
|
},
|
|
2913
3060
|
async () => {
|
|
2914
|
-
|
|
3061
|
+
const blocked = hardStopBlocked("env");
|
|
3062
|
+
if (blocked) return blocked;
|
|
3063
|
+
if (!existsSync9(".env")) {
|
|
2915
3064
|
return { content: [{ type: "text", text: "\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 .env\uB97C \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694." }] };
|
|
2916
3065
|
}
|
|
2917
|
-
const keys = parseEnvKeys(
|
|
3066
|
+
const keys = parseEnvKeys(readFileSync6(".env", "utf-8"));
|
|
2918
3067
|
if (keys.length === 0) {
|
|
2919
3068
|
return { content: [{ type: "text", text: "\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
2920
3069
|
}
|
|
2921
3070
|
const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
2922
|
-
|
|
3071
|
+
writeFileSync4(".env.example", exampleContent, "utf-8");
|
|
2923
3072
|
const gitignoreLines = [];
|
|
2924
|
-
if (
|
|
2925
|
-
const content =
|
|
3073
|
+
if (existsSync9(".gitignore")) {
|
|
3074
|
+
const content = readFileSync6(".gitignore", "utf-8");
|
|
2926
3075
|
if (!content.split("\n").some((l) => l.trim() === ".env")) {
|
|
2927
|
-
|
|
3076
|
+
appendFileSync3(".gitignore", "\n.env\n");
|
|
2928
3077
|
gitignoreLines.push("\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428");
|
|
2929
3078
|
}
|
|
2930
3079
|
} else {
|
|
2931
|
-
|
|
3080
|
+
writeFileSync4(".gitignore", ".env\nnode_modules/\ndist/\n");
|
|
2932
3081
|
gitignoreLines.push("\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)");
|
|
2933
3082
|
}
|
|
2934
3083
|
const lines = [`\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`, ...keys.map((k) => ` ${k}`)];
|
|
@@ -2942,11 +3091,11 @@ ${log.out}` }] };
|
|
|
2942
3091
|
description: "\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (.env.example \uAE30\uC900)"
|
|
2943
3092
|
},
|
|
2944
3093
|
async () => {
|
|
2945
|
-
if (!
|
|
3094
|
+
if (!existsSync9(".env.example")) {
|
|
2946
3095
|
return { content: [{ type: "text", text: "\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 env \uB3C4\uAD6C\uB97C \uC2E4\uD589\uD558\uC138\uC694." }] };
|
|
2947
3096
|
}
|
|
2948
|
-
const requiredKeys = parseEnvKeys(
|
|
2949
|
-
const currentKeys =
|
|
3097
|
+
const requiredKeys = parseEnvKeys(readFileSync6(".env.example", "utf-8"));
|
|
3098
|
+
const currentKeys = existsSync9(".env") ? parseEnvKeys(readFileSync6(".env", "utf-8")) : [];
|
|
2950
3099
|
const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
|
|
2951
3100
|
const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
|
|
2952
3101
|
const lines = [`\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`];
|
|
@@ -3062,7 +3211,7 @@ ${cliStatus}
|
|
|
3062
3211
|
description: "\uD604\uC7AC \uBC84\uC804 + bump \uD6C4\uBCF4 \uD45C\uC2DC (MCP \uBAA8\uB4DC: \uC2E4\uC81C npm publish \uBBF8\uC218\uD589 \u2014 `vhk publish` \uC548\uB0B4)"
|
|
3063
3212
|
},
|
|
3064
3213
|
async () => {
|
|
3065
|
-
if (!
|
|
3214
|
+
if (!existsSync9("package.json")) {
|
|
3066
3215
|
return { content: [{ type: "text", text: "\u274C package.json \uC5C6\uC74C." }] };
|
|
3067
3216
|
}
|
|
3068
3217
|
try {
|
|
@@ -3090,7 +3239,7 @@ ${cliStatus}
|
|
|
3090
3239
|
description: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uAC10\uC9C0 + \uC804\uD658 \uD6C4\uBCF4 \uAC00\uC6A9\uC131 (MCP \uBAA8\uB4DC: \uC2E4\uC81C \uC804\uD658 \uBBF8\uC218\uD589 \u2014 `vhk migrate <target>` \uC548\uB0B4)"
|
|
3091
3240
|
},
|
|
3092
3241
|
async () => {
|
|
3093
|
-
const current =
|
|
3242
|
+
const current = existsSync9("pnpm-lock.yaml") ? "pnpm" : existsSync9("yarn.lock") ? "yarn" : existsSync9("package-lock.json") ? "npm" : null;
|
|
3094
3243
|
const candidates = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current);
|
|
3095
3244
|
const lines = [`\uD604\uC7AC PM: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00 (lock \uD30C\uC77C \uC5C6\uC74C)"}`];
|
|
3096
3245
|
for (const pm of candidates) {
|
|
@@ -3222,6 +3371,7 @@ export {
|
|
|
3222
3371
|
ensureVhkIgnored,
|
|
3223
3372
|
listBackups,
|
|
3224
3373
|
restoreBackup,
|
|
3374
|
+
atomicWriteFile,
|
|
3225
3375
|
detectExistingRuleFiles,
|
|
3226
3376
|
buildAdoptedRules,
|
|
3227
3377
|
isInteractive,
|
|
@@ -3245,18 +3395,24 @@ export {
|
|
|
3245
3395
|
filterTrackedPaths,
|
|
3246
3396
|
stripBom,
|
|
3247
3397
|
readJsonFile,
|
|
3398
|
+
appendBlocker,
|
|
3399
|
+
getActiveBlockers,
|
|
3400
|
+
isHardStopActive,
|
|
3401
|
+
readHardStopReason,
|
|
3402
|
+
clearHardStop,
|
|
3403
|
+
ensureNotHardStopped,
|
|
3248
3404
|
NETWORK_EXEC_TIMEOUT_MS,
|
|
3249
3405
|
safeExecFile,
|
|
3250
3406
|
MAX_SCAN_FILE_BYTES,
|
|
3251
3407
|
MAX_SECRET_FINDINGS,
|
|
3252
3408
|
scanProjectForSecrets,
|
|
3253
3409
|
filterSevereFindings,
|
|
3410
|
+
getVhkVersion,
|
|
3254
3411
|
deploy,
|
|
3255
3412
|
env,
|
|
3256
3413
|
envCheck,
|
|
3257
3414
|
publish,
|
|
3258
3415
|
audit,
|
|
3259
|
-
getVhkVersion,
|
|
3260
3416
|
resolveVhkCliInvocation,
|
|
3261
3417
|
startMcpServer
|
|
3262
3418
|
};
|