@byh3071/vhk 2.4.1 → 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-Y7SJVHGS.js → chunk-WZH5CA4H.js} +284 -130
- package/dist/index.js +1372 -964
- 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,30 +1726,128 @@ ${ko.sync.done}`));
|
|
|
1692
1726
|
});
|
|
1693
1727
|
}
|
|
1694
1728
|
|
|
1729
|
+
// src/lib/hard-stop-guard.ts
|
|
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
|
+
|
|
1695
1827
|
// src/lib/version.ts
|
|
1696
|
-
import { existsSync as
|
|
1697
|
-
import { dirname, join as
|
|
1828
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1829
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1698
1830
|
import { fileURLToPath } from "url";
|
|
1699
1831
|
|
|
1700
1832
|
// src/lib/read-json.ts
|
|
1701
|
-
import { readFileSync as
|
|
1833
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1702
1834
|
function stripBom(text) {
|
|
1703
1835
|
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1704
1836
|
}
|
|
1705
1837
|
function readJsonFile(filePath) {
|
|
1706
|
-
const raw = stripBom(
|
|
1838
|
+
const raw = stripBom(readFileSync3(filePath, "utf-8"));
|
|
1707
1839
|
return JSON.parse(raw);
|
|
1708
1840
|
}
|
|
1709
1841
|
|
|
1710
1842
|
// src/lib/version.ts
|
|
1711
1843
|
function getVhkVersion() {
|
|
1712
|
-
const dir =
|
|
1844
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
1713
1845
|
for (const pkgPath of [
|
|
1714
|
-
|
|
1715
|
-
|
|
1846
|
+
join4(dir, "../../package.json"),
|
|
1847
|
+
join4(dir, "../package.json")
|
|
1716
1848
|
]) {
|
|
1717
1849
|
try {
|
|
1718
|
-
if (
|
|
1850
|
+
if (existsSync3(pkgPath)) {
|
|
1719
1851
|
const pkg = readJsonFile(pkgPath);
|
|
1720
1852
|
if (pkg.version) return pkg.version;
|
|
1721
1853
|
}
|
|
@@ -1727,8 +1859,8 @@ function getVhkVersion() {
|
|
|
1727
1859
|
}
|
|
1728
1860
|
|
|
1729
1861
|
// src/commands/deploy.ts
|
|
1730
|
-
import { existsSync as
|
|
1731
|
-
import
|
|
1862
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1863
|
+
import chalk5 from "chalk";
|
|
1732
1864
|
import inquirer2 from "inquirer";
|
|
1733
1865
|
|
|
1734
1866
|
// src/lib/exec.ts
|
|
@@ -1828,7 +1960,7 @@ var PLATFORMS = {
|
|
|
1828
1960
|
function detectPlatform() {
|
|
1829
1961
|
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
1830
1962
|
for (const file of config.detectFiles) {
|
|
1831
|
-
if (
|
|
1963
|
+
if (existsSync4(file)) return key;
|
|
1832
1964
|
}
|
|
1833
1965
|
}
|
|
1834
1966
|
return null;
|
|
@@ -1837,11 +1969,11 @@ function isCLIAvailable(cmd, checkArgs) {
|
|
|
1837
1969
|
return safeExecFile(cmd, checkArgs).ok;
|
|
1838
1970
|
}
|
|
1839
1971
|
async function deploy() {
|
|
1840
|
-
console.log(
|
|
1841
|
-
console.log(
|
|
1972
|
+
console.log(chalk5.bold("\n\u{1F680} " + t("deploy.title")));
|
|
1973
|
+
console.log(chalk5.gray("\u2500".repeat(40)));
|
|
1842
1974
|
let platform = detectPlatform();
|
|
1843
1975
|
if (platform) {
|
|
1844
|
-
console.log(
|
|
1976
|
+
console.log(chalk5.cyan(`
|
|
1845
1977
|
\u{1F50D} \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${PLATFORMS[platform].name}`));
|
|
1846
1978
|
} else {
|
|
1847
1979
|
const { selected } = await inquirer2.prompt([
|
|
@@ -1860,9 +1992,9 @@ async function deploy() {
|
|
|
1860
1992
|
}
|
|
1861
1993
|
const config = PLATFORMS[platform];
|
|
1862
1994
|
if (!isCLIAvailable(config.command, config.checkArgs)) {
|
|
1863
|
-
console.log(
|
|
1995
|
+
console.log(chalk5.red(`
|
|
1864
1996
|
\u274C ${config.name} CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
1865
|
-
console.log(
|
|
1997
|
+
console.log(chalk5.yellow(` \u2192 ${config.installHint}`));
|
|
1866
1998
|
return;
|
|
1867
1999
|
}
|
|
1868
2000
|
const { confirm } = await inquirer2.prompt([
|
|
@@ -1874,15 +2006,15 @@ async function deploy() {
|
|
|
1874
2006
|
}
|
|
1875
2007
|
]);
|
|
1876
2008
|
if (!confirm) {
|
|
1877
|
-
console.log(
|
|
2009
|
+
console.log(chalk5.gray("\uCDE8\uC18C\uB428"));
|
|
1878
2010
|
return;
|
|
1879
2011
|
}
|
|
1880
|
-
console.log(
|
|
2012
|
+
console.log(chalk5.cyan(`
|
|
1881
2013
|
${t("deploy.deploying")}
|
|
1882
2014
|
`));
|
|
1883
2015
|
const result = safeExecFileStream(config.command, config.commandArgs);
|
|
1884
2016
|
if (result.ok) {
|
|
1885
|
-
console.log(
|
|
2017
|
+
console.log(chalk5.green(`
|
|
1886
2018
|
\u2705 ${t("deploy.success")}`));
|
|
1887
2019
|
printNextStep({
|
|
1888
2020
|
message: "\uBC30\uD3EC \uC644\uB8CC! \uC0AC\uC774\uD2B8\uB97C \uD655\uC778\uD558\uC138\uC694.",
|
|
@@ -1890,16 +2022,16 @@ ${t("deploy.deploying")}
|
|
|
1890
2022
|
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
1891
2023
|
});
|
|
1892
2024
|
} else {
|
|
1893
|
-
console.log(
|
|
2025
|
+
console.log(chalk5.red(`
|
|
1894
2026
|
\u274C ${t("deploy.failed")}`));
|
|
1895
|
-
console.log(
|
|
2027
|
+
console.log(chalk5.red(result.err));
|
|
1896
2028
|
}
|
|
1897
2029
|
}
|
|
1898
2030
|
|
|
1899
2031
|
// src/commands/env.ts
|
|
1900
|
-
import { existsSync as
|
|
1901
|
-
import { join as
|
|
1902
|
-
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";
|
|
1903
2035
|
function parseEnvKeys(content) {
|
|
1904
2036
|
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
|
|
1905
2037
|
}
|
|
@@ -1915,7 +2047,7 @@ function loadDefinedEnvKeys(dir = ".") {
|
|
|
1915
2047
|
if (!name.startsWith(".env")) continue;
|
|
1916
2048
|
if (name.endsWith(".example") || name.endsWith(".sample")) continue;
|
|
1917
2049
|
try {
|
|
1918
|
-
for (const k of parseEnvKeys(
|
|
2050
|
+
for (const k of parseEnvKeys(readFileSync4(join5(dir, name), "utf-8"))) keys.add(k);
|
|
1919
2051
|
} catch {
|
|
1920
2052
|
}
|
|
1921
2053
|
}
|
|
@@ -1923,36 +2055,37 @@ function loadDefinedEnvKeys(dir = ".") {
|
|
|
1923
2055
|
}
|
|
1924
2056
|
function ensureGitignore() {
|
|
1925
2057
|
const gitignorePath = ".gitignore";
|
|
1926
|
-
if (
|
|
1927
|
-
const content =
|
|
2058
|
+
if (existsSync5(gitignorePath)) {
|
|
2059
|
+
const content = readFileSync4(gitignorePath, "utf-8");
|
|
1928
2060
|
if (!content.split("\n").some((l) => l.trim() === ".env")) {
|
|
1929
|
-
|
|
1930
|
-
console.log(
|
|
2061
|
+
appendFileSync2(gitignorePath, "\n.env\n");
|
|
2062
|
+
console.log(chalk6.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
|
|
1931
2063
|
}
|
|
1932
2064
|
} else {
|
|
1933
|
-
|
|
1934
|
-
console.log(
|
|
2065
|
+
writeFileSync2(gitignorePath, ".env\nnode_modules/\ndist/\n");
|
|
2066
|
+
console.log(chalk6.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
|
|
1935
2067
|
}
|
|
1936
2068
|
}
|
|
1937
2069
|
async function env() {
|
|
1938
|
-
|
|
1939
|
-
console.log(
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
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."));
|
|
1943
2076
|
return;
|
|
1944
2077
|
}
|
|
1945
|
-
const envContent =
|
|
2078
|
+
const envContent = readFileSync4(".env", "utf-8");
|
|
1946
2079
|
const keys = parseEnvKeys(envContent);
|
|
1947
2080
|
if (keys.length === 0) {
|
|
1948
|
-
console.log(
|
|
2081
|
+
console.log(chalk6.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
1949
2082
|
return;
|
|
1950
2083
|
}
|
|
1951
2084
|
const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
1952
|
-
|
|
1953
|
-
console.log(
|
|
2085
|
+
writeFileSync2(".env.example", exampleContent, "utf-8");
|
|
2086
|
+
console.log(chalk6.green(`
|
|
1954
2087
|
\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`));
|
|
1955
|
-
keys.forEach((k) => console.log(
|
|
2088
|
+
keys.forEach((k) => console.log(chalk6.gray(` ${k}`)));
|
|
1956
2089
|
ensureGitignore();
|
|
1957
2090
|
printNextStep({
|
|
1958
2091
|
message: ".env.example \uC0DD\uC131 \uC644\uB8CC!",
|
|
@@ -1961,37 +2094,37 @@ async function env() {
|
|
|
1961
2094
|
});
|
|
1962
2095
|
}
|
|
1963
2096
|
async function envCheck() {
|
|
1964
|
-
console.log(
|
|
1965
|
-
console.log(
|
|
1966
|
-
if (!
|
|
1967
|
-
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."));
|
|
1968
2101
|
return;
|
|
1969
2102
|
}
|
|
1970
|
-
const requiredKeys = parseEnvKeys(
|
|
2103
|
+
const requiredKeys = parseEnvKeys(readFileSync4(".env.example", "utf-8"));
|
|
1971
2104
|
const currentKeys = loadDefinedEnvKeys();
|
|
1972
2105
|
const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
|
|
1973
2106
|
const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
|
|
1974
|
-
console.log(
|
|
2107
|
+
console.log(chalk6.cyan(`
|
|
1975
2108
|
\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`));
|
|
1976
2109
|
if (missing.length === 0) {
|
|
1977
|
-
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!"));
|
|
1978
2111
|
} else {
|
|
1979
|
-
console.log(
|
|
2112
|
+
console.log(chalk6.red(`
|
|
1980
2113
|
\u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`));
|
|
1981
|
-
missing.forEach((k) => console.log(
|
|
2114
|
+
missing.forEach((k) => console.log(chalk6.red(` \u2022 ${k}`)));
|
|
1982
2115
|
process.exitCode = 1;
|
|
1983
2116
|
}
|
|
1984
2117
|
if (extra.length > 0) {
|
|
1985
|
-
console.log(
|
|
2118
|
+
console.log(chalk6.yellow(`
|
|
1986
2119
|
\u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`));
|
|
1987
|
-
extra.forEach((k) => console.log(
|
|
2120
|
+
extra.forEach((k) => console.log(chalk6.yellow(` \u2022 ${k}`)));
|
|
1988
2121
|
}
|
|
1989
2122
|
ensureGitignore();
|
|
1990
2123
|
}
|
|
1991
2124
|
|
|
1992
2125
|
// src/commands/publish.ts
|
|
1993
|
-
import { existsSync as
|
|
1994
|
-
import
|
|
2126
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
2127
|
+
import chalk7 from "chalk";
|
|
1995
2128
|
import inquirer3 from "inquirer";
|
|
1996
2129
|
import ora from "ora";
|
|
1997
2130
|
function bumpVersion(current, type) {
|
|
@@ -2024,8 +2157,8 @@ function bumpClaudeMdVersion(content, newVersion) {
|
|
|
2024
2157
|
}
|
|
2025
2158
|
function gitPostRelease(newVersion) {
|
|
2026
2159
|
const filesToAdd = ["package.json"];
|
|
2027
|
-
if (
|
|
2028
|
-
if (
|
|
2160
|
+
if (existsSync6("CHANGELOG.md")) filesToAdd.push("CHANGELOG.md");
|
|
2161
|
+
if (existsSync6("CLAUDE.md")) filesToAdd.push("CLAUDE.md");
|
|
2029
2162
|
const add = safeExecFile("git", ["add", ...filesToAdd]);
|
|
2030
2163
|
if (!add.ok) {
|
|
2031
2164
|
return {
|
|
@@ -2082,28 +2215,28 @@ function publishPreflight() {
|
|
|
2082
2215
|
return { ...evaluatePublishPreflight(branch, trackedStatus, defaultBranch), branch, defaultBranch };
|
|
2083
2216
|
}
|
|
2084
2217
|
async function publish() {
|
|
2085
|
-
console.log(
|
|
2086
|
-
console.log(
|
|
2218
|
+
console.log(chalk7.bold("\n\u{1F4E6} " + t("publish.title")));
|
|
2219
|
+
console.log(chalk7.gray("\u2500".repeat(40)));
|
|
2087
2220
|
const pre = publishPreflight();
|
|
2088
2221
|
if (!pre.ok) {
|
|
2089
2222
|
const msg = pre.code === "wrong-branch" ? t("publish.preflightWrongBranch", pre.branch || "(detached)", pre.defaultBranch) : t("publish.preflightDirty");
|
|
2090
|
-
console.log(
|
|
2223
|
+
console.log(chalk7.red(`
|
|
2091
2224
|
\u274C ${msg}`));
|
|
2092
2225
|
return;
|
|
2093
2226
|
}
|
|
2094
|
-
if (!
|
|
2095
|
-
console.log(
|
|
2227
|
+
if (!existsSync6("package.json")) {
|
|
2228
|
+
console.log(chalk7.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2096
2229
|
return;
|
|
2097
2230
|
}
|
|
2098
2231
|
let pkg;
|
|
2099
2232
|
try {
|
|
2100
2233
|
pkg = readJsonFile("package.json");
|
|
2101
2234
|
} catch {
|
|
2102
|
-
console.log(
|
|
2235
|
+
console.log(chalk7.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
|
|
2103
2236
|
return;
|
|
2104
2237
|
}
|
|
2105
2238
|
const currentVersion = pkg.version || "0.0.0";
|
|
2106
|
-
console.log(
|
|
2239
|
+
console.log(chalk7.cyan(`
|
|
2107
2240
|
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${currentVersion}`));
|
|
2108
2241
|
const { bumpType } = await inquirer3.prompt([
|
|
2109
2242
|
{
|
|
@@ -2118,29 +2251,29 @@ async function publish() {
|
|
|
2118
2251
|
}
|
|
2119
2252
|
]);
|
|
2120
2253
|
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
2121
|
-
console.log(
|
|
2254
|
+
console.log(chalk7.cyan(`
|
|
2122
2255
|
\u{1F195} \uC0C8 \uBC84\uC804: v${newVersion}`));
|
|
2123
2256
|
pkg.version = newVersion;
|
|
2124
|
-
|
|
2125
|
-
console.log(
|
|
2126
|
-
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;
|
|
2127
2260
|
if (claudeMdOriginal !== null) {
|
|
2128
2261
|
const bumped = bumpClaudeMdVersion(claudeMdOriginal, newVersion);
|
|
2129
2262
|
if (bumped !== claudeMdOriginal) {
|
|
2130
|
-
|
|
2131
|
-
console.log(
|
|
2263
|
+
writeFileSync3("CLAUDE.md", bumped, "utf-8");
|
|
2264
|
+
console.log(chalk7.green("\u2705 CLAUDE.md \uBC84\uC804\uC904 \uB3D9\uAE30\uD654"));
|
|
2132
2265
|
}
|
|
2133
2266
|
}
|
|
2134
2267
|
const rollbackVersion = () => {
|
|
2135
2268
|
pkg.version = currentVersion;
|
|
2136
|
-
|
|
2137
|
-
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");
|
|
2138
2271
|
};
|
|
2139
2272
|
const buildSpinner = ora(t("publish.building")).start();
|
|
2140
2273
|
const buildResult = safeExecFile("pnpm", ["build"]);
|
|
2141
2274
|
if (!buildResult.ok) {
|
|
2142
2275
|
buildSpinner.fail(t("publish.buildFailed"));
|
|
2143
|
-
console.log(
|
|
2276
|
+
console.log(chalk7.red(buildResult.err.slice(0, 500)));
|
|
2144
2277
|
rollbackVersion();
|
|
2145
2278
|
return;
|
|
2146
2279
|
}
|
|
@@ -2149,7 +2282,7 @@ async function publish() {
|
|
|
2149
2282
|
const testResult = safeExecFile("pnpm", ["test", "--run"]);
|
|
2150
2283
|
if (!testResult.ok) {
|
|
2151
2284
|
testSpinner.fail(t("publish.testFailed"));
|
|
2152
|
-
console.log(
|
|
2285
|
+
console.log(chalk7.red(testResult.err.slice(0, 500)));
|
|
2153
2286
|
rollbackVersion();
|
|
2154
2287
|
return;
|
|
2155
2288
|
}
|
|
@@ -2164,45 +2297,45 @@ async function publish() {
|
|
|
2164
2297
|
]);
|
|
2165
2298
|
if (!confirm) {
|
|
2166
2299
|
rollbackVersion();
|
|
2167
|
-
console.log(
|
|
2300
|
+
console.log(chalk7.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
|
|
2168
2301
|
return;
|
|
2169
2302
|
}
|
|
2170
|
-
console.log(
|
|
2303
|
+
console.log(chalk7.cyan(`
|
|
2171
2304
|
\u{1F4E4} ${t("publish.publishing")}`));
|
|
2172
|
-
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)"));
|
|
2173
2306
|
const pubResult = safeExecFileStream("npm", ["publish", "--access", "public"]);
|
|
2174
2307
|
if (!pubResult.ok) {
|
|
2175
|
-
console.log(
|
|
2308
|
+
console.log(chalk7.red(`
|
|
2176
2309
|
\u2716 ${t("publish.publishFailed")}`));
|
|
2177
|
-
console.log(
|
|
2310
|
+
console.log(chalk7.red(pubResult.err.slice(0, 500)));
|
|
2178
2311
|
rollbackVersion();
|
|
2179
|
-
console.log(
|
|
2312
|
+
console.log(chalk7.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
|
|
2180
2313
|
return;
|
|
2181
2314
|
}
|
|
2182
|
-
console.log(
|
|
2315
|
+
console.log(chalk7.green(`
|
|
2183
2316
|
\u2714 ${t("publish.publishSuccess")}`));
|
|
2184
|
-
if (
|
|
2185
|
-
const cl =
|
|
2186
|
-
const date = (
|
|
2317
|
+
if (existsSync6("CHANGELOG.md")) {
|
|
2318
|
+
const cl = readFileSync5("CHANGELOG.md", "utf-8");
|
|
2319
|
+
const date = localDate();
|
|
2187
2320
|
const updated = insertChangelogStub(cl, newVersion, date);
|
|
2188
2321
|
if (updated !== cl) {
|
|
2189
|
-
|
|
2190
|
-
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`));
|
|
2191
2324
|
}
|
|
2192
2325
|
}
|
|
2193
2326
|
const git = gitPostRelease(newVersion);
|
|
2194
2327
|
if (git.warning) {
|
|
2195
|
-
console.log(
|
|
2328
|
+
console.log(chalk7.yellow(`
|
|
2196
2329
|
\u26A0\uFE0F ${git.warning}`));
|
|
2197
|
-
console.log(
|
|
2330
|
+
console.log(chalk7.dim(` npm \uBC30\uD3EC\uB294 \uC774\uBBF8 \uC131\uACF5\uD588\uC2B5\uB2C8\uB2E4 (v${newVersion}).`));
|
|
2198
2331
|
} else if (git.tagged && git.pushed) {
|
|
2199
|
-
console.log(
|
|
2332
|
+
console.log(chalk7.green(`
|
|
2200
2333
|
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131 + push \uC644\uB8CC`));
|
|
2201
2334
|
} else if (git.tagged) {
|
|
2202
|
-
console.log(
|
|
2335
|
+
console.log(chalk7.yellow(`
|
|
2203
2336
|
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131\uB428 (push\uB294 \uC218\uB3D9\uC73C\uB85C)`));
|
|
2204
2337
|
}
|
|
2205
|
-
console.log(
|
|
2338
|
+
console.log(chalk7.green.bold(`
|
|
2206
2339
|
\u{1F389} v${newVersion} \uBC30\uD3EC \uC644\uB8CC!`));
|
|
2207
2340
|
printNextStep({
|
|
2208
2341
|
message: "npm \uBC30\uD3EC \uC644\uB8CC!",
|
|
@@ -2212,13 +2345,13 @@ async function publish() {
|
|
|
2212
2345
|
}
|
|
2213
2346
|
|
|
2214
2347
|
// src/commands/audit.ts
|
|
2215
|
-
import { existsSync as
|
|
2216
|
-
import
|
|
2348
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2349
|
+
import chalk8 from "chalk";
|
|
2217
2350
|
import inquirer4 from "inquirer";
|
|
2218
2351
|
import ora2 from "ora";
|
|
2219
2352
|
function detectCurrentPM() {
|
|
2220
|
-
if (
|
|
2221
|
-
if (
|
|
2353
|
+
if (existsSync7("pnpm-lock.yaml")) return "pnpm";
|
|
2354
|
+
if (existsSync7("yarn.lock")) return "yarn";
|
|
2222
2355
|
return "npm";
|
|
2223
2356
|
}
|
|
2224
2357
|
function parseAuditOutput(output, pm) {
|
|
@@ -2258,24 +2391,24 @@ function runAuditFix(pm) {
|
|
|
2258
2391
|
return result.ok ? { ok: true } : { ok: false, err: result.err };
|
|
2259
2392
|
}
|
|
2260
2393
|
async function audit(autoFix = false) {
|
|
2261
|
-
console.log(
|
|
2262
|
-
console.log(
|
|
2394
|
+
console.log(chalk8.bold("\n\u{1F6E1}\uFE0F " + t("audit.title")));
|
|
2395
|
+
console.log(chalk8.gray("\u2500".repeat(40)));
|
|
2263
2396
|
const pm = detectCurrentPM();
|
|
2264
|
-
console.log(
|
|
2397
|
+
console.log(chalk8.cyan(`\u{1F4E6} \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${pm}`));
|
|
2265
2398
|
const spinner = ora2("\uBCF4\uC548 \uAC10\uC0AC \uC2E4\uD589 \uC911...").start();
|
|
2266
2399
|
const output = runAuditJson(pm);
|
|
2267
2400
|
spinner.stop();
|
|
2268
2401
|
const summary = parseAuditOutput(output, pm);
|
|
2269
2402
|
if (summary.total === 0) {
|
|
2270
|
-
console.log(
|
|
2403
|
+
console.log(chalk8.green.bold("\n\u{1F389} \uCDE8\uC57D\uC810\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4!"));
|
|
2271
2404
|
return;
|
|
2272
2405
|
}
|
|
2273
|
-
console.log(
|
|
2274
|
-
if (summary.critical > 0) console.log(
|
|
2275
|
-
if (summary.high > 0) console.log(
|
|
2276
|
-
if (summary.moderate > 0) console.log(
|
|
2277
|
-
if (summary.low > 0) console.log(
|
|
2278
|
-
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(`
|
|
2279
2412
|
\uCD1D ${summary.total}\uAC1C\uC758 \uCDE8\uC57D\uC810`));
|
|
2280
2413
|
const shouldRunFix = autoFix ? true : summary.critical > 0 || summary.high > 0 ? (await inquirer4.prompt([
|
|
2281
2414
|
{
|
|
@@ -2302,9 +2435,9 @@ async function audit(autoFix = false) {
|
|
|
2302
2435
|
}
|
|
2303
2436
|
|
|
2304
2437
|
// src/mcp/cli-path.ts
|
|
2305
|
-
import { existsSync as
|
|
2438
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2306
2439
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2307
|
-
import { dirname as
|
|
2440
|
+
import { dirname as dirname3, resolve } from "path";
|
|
2308
2441
|
function pickCliInvocation(globalAvailable, localCli, localExists) {
|
|
2309
2442
|
if (globalAvailable) return { bin: "vhk", prefixArgs: [], fallback: false };
|
|
2310
2443
|
if (localExists) return { bin: process.execPath, prefixArgs: [localCli], fallback: true };
|
|
@@ -2314,20 +2447,20 @@ function composeInvocation(cli, args) {
|
|
|
2314
2447
|
return { bin: cli.bin, args: [...cli.prefixArgs, ...args] };
|
|
2315
2448
|
}
|
|
2316
2449
|
function localCliPath() {
|
|
2317
|
-
const here =
|
|
2450
|
+
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
2318
2451
|
return resolve(here, "..", "index.js");
|
|
2319
2452
|
}
|
|
2320
2453
|
function resolveVhkCliInvocation() {
|
|
2321
2454
|
const globalAvailable = safeExecFile("vhk", ["--version"]).ok;
|
|
2322
2455
|
const local = localCliPath();
|
|
2323
|
-
return pickCliInvocation(globalAvailable, local,
|
|
2456
|
+
return pickCliInvocation(globalAvailable, local, existsSync8(local));
|
|
2324
2457
|
}
|
|
2325
2458
|
|
|
2326
2459
|
// src/mcp/server.ts
|
|
2327
2460
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2328
2461
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2329
2462
|
import { z } from "zod";
|
|
2330
|
-
import { existsSync as
|
|
2463
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, appendFileSync as appendFileSync3 } from "fs";
|
|
2331
2464
|
|
|
2332
2465
|
// src/lib/scan-secrets.ts
|
|
2333
2466
|
import fs7 from "fs";
|
|
@@ -2403,7 +2536,7 @@ import path6 from "path";
|
|
|
2403
2536
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
2404
2537
|
import fs5 from "fs";
|
|
2405
2538
|
import path5 from "path";
|
|
2406
|
-
import
|
|
2539
|
+
import chalk9 from "chalk";
|
|
2407
2540
|
function loadGitignore(rootDir) {
|
|
2408
2541
|
const ig = (0, import_ignore.default)();
|
|
2409
2542
|
const gitignorePath = path5.join(rootDir, ".gitignore");
|
|
@@ -2477,7 +2610,7 @@ function printSecurityWarnings(rootDir = process.cwd()) {
|
|
|
2477
2610
|
const result = checkProjectSecurity(rootDir);
|
|
2478
2611
|
if (result.ok) return true;
|
|
2479
2612
|
for (const w of result.warnings) {
|
|
2480
|
-
console.log(
|
|
2613
|
+
console.log(chalk9.yellow(` \u26A0\uFE0F ${w}`));
|
|
2481
2614
|
}
|
|
2482
2615
|
return false;
|
|
2483
2616
|
}
|
|
@@ -2618,6 +2751,14 @@ var SERVER_VERSION = getVhkVersion();
|
|
|
2618
2751
|
function isGitRepo() {
|
|
2619
2752
|
return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
|
|
2620
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
|
+
}
|
|
2621
2762
|
var ANSI_RE = /\x1B\[[0-9;?]*[ -/]*[@-~]/g;
|
|
2622
2763
|
function stripAnsi(s) {
|
|
2623
2764
|
return s.replace(ANSI_RE, "");
|
|
@@ -2651,6 +2792,8 @@ function createVhkMcpServer() {
|
|
|
2651
2792
|
}
|
|
2652
2793
|
},
|
|
2653
2794
|
async ({ message }) => {
|
|
2795
|
+
const blocked = hardStopBlocked("save");
|
|
2796
|
+
if (blocked) return blocked;
|
|
2654
2797
|
if (!isGitRepo()) {
|
|
2655
2798
|
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
2656
2799
|
}
|
|
@@ -2712,6 +2855,8 @@ ${preview}${more}
|
|
|
2712
2855
|
}
|
|
2713
2856
|
},
|
|
2714
2857
|
async ({ confirm }) => {
|
|
2858
|
+
const blocked = hardStopBlocked("undo");
|
|
2859
|
+
if (blocked) return blocked;
|
|
2715
2860
|
if (!isGitRepo()) {
|
|
2716
2861
|
return { content: [{ type: "text", text: "\u274C git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4." }] };
|
|
2717
2862
|
}
|
|
@@ -2752,7 +2897,7 @@ ${last.out}
|
|
|
2752
2897
|
);
|
|
2753
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 () => {
|
|
2754
2899
|
const lines = [];
|
|
2755
|
-
if (
|
|
2900
|
+
if (existsSync9("package.json")) {
|
|
2756
2901
|
try {
|
|
2757
2902
|
const pkg = readJsonFile("package.json");
|
|
2758
2903
|
lines.push(`\u{1F4E6} \uD504\uB85C\uC81D\uD2B8: ${pkg.name ?? "(\uC774\uB984 \uC5C6\uC74C)"} v${pkg.version ?? "?"}`);
|
|
@@ -2837,7 +2982,7 @@ ${last.out}
|
|
|
2837
2982
|
checks.push(build.ok ? "\u2705 \uBE4C\uB4DC \uC131\uACF5" : "\u274C \uBE4C\uB4DC \uC2E4\uD328");
|
|
2838
2983
|
const test = safeExecFile("pnpm", ["test", "--run"]);
|
|
2839
2984
|
checks.push(test.ok ? "\u2705 \uD14C\uC2A4\uD2B8 \uD1B5\uACFC" : "\u274C \uD14C\uC2A4\uD2B8 \uC2E4\uD328");
|
|
2840
|
-
if (
|
|
2985
|
+
if (existsSync9("package.json")) {
|
|
2841
2986
|
try {
|
|
2842
2987
|
const pkg = readJsonFile("package.json");
|
|
2843
2988
|
checks.push(`\u{1F4E6} \uBC84\uC804: ${pkg.version}`);
|
|
@@ -2875,11 +3020,11 @@ ${last.out}
|
|
|
2875
3020
|
const recommended = ["CLAUDE.md", ".cursorrules", "docs/PRD.md", "docs/ARCHITECTURE.md"];
|
|
2876
3021
|
const lines = ["\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uC810\uAC80", "", "\uD544\uC218:"];
|
|
2877
3022
|
required.forEach((f) => {
|
|
2878
|
-
lines.push(` ${
|
|
3023
|
+
lines.push(` ${existsSync9(f) ? "\u2705" : "\u274C"} ${f}`);
|
|
2879
3024
|
});
|
|
2880
3025
|
lines.push("", "\uAD8C\uC7A5 (VHK \uD558\uB124\uC2A4):");
|
|
2881
3026
|
recommended.forEach((f) => {
|
|
2882
|
-
lines.push(` ${
|
|
3027
|
+
lines.push(` ${existsSync9(f) ? "\u2705" : "\u26A0\uFE0F"} ${f}`);
|
|
2883
3028
|
});
|
|
2884
3029
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2885
3030
|
});
|
|
@@ -2913,24 +3058,26 @@ ${log.out}` }] };
|
|
|
2913
3058
|
description: ".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore\uC5D0 .env \uC790\uB3D9 \uCD94\uAC00"
|
|
2914
3059
|
},
|
|
2915
3060
|
async () => {
|
|
2916
|
-
|
|
3061
|
+
const blocked = hardStopBlocked("env");
|
|
3062
|
+
if (blocked) return blocked;
|
|
3063
|
+
if (!existsSync9(".env")) {
|
|
2917
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." }] };
|
|
2918
3065
|
}
|
|
2919
|
-
const keys = parseEnvKeys(
|
|
3066
|
+
const keys = parseEnvKeys(readFileSync6(".env", "utf-8"));
|
|
2920
3067
|
if (keys.length === 0) {
|
|
2921
3068
|
return { content: [{ type: "text", text: "\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }] };
|
|
2922
3069
|
}
|
|
2923
3070
|
const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
2924
|
-
|
|
3071
|
+
writeFileSync4(".env.example", exampleContent, "utf-8");
|
|
2925
3072
|
const gitignoreLines = [];
|
|
2926
|
-
if (
|
|
2927
|
-
const content =
|
|
3073
|
+
if (existsSync9(".gitignore")) {
|
|
3074
|
+
const content = readFileSync6(".gitignore", "utf-8");
|
|
2928
3075
|
if (!content.split("\n").some((l) => l.trim() === ".env")) {
|
|
2929
|
-
|
|
3076
|
+
appendFileSync3(".gitignore", "\n.env\n");
|
|
2930
3077
|
gitignoreLines.push("\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428");
|
|
2931
3078
|
}
|
|
2932
3079
|
} else {
|
|
2933
|
-
|
|
3080
|
+
writeFileSync4(".gitignore", ".env\nnode_modules/\ndist/\n");
|
|
2934
3081
|
gitignoreLines.push("\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)");
|
|
2935
3082
|
}
|
|
2936
3083
|
const lines = [`\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`, ...keys.map((k) => ` ${k}`)];
|
|
@@ -2944,11 +3091,11 @@ ${log.out}` }] };
|
|
|
2944
3091
|
description: "\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (.env.example \uAE30\uC900)"
|
|
2945
3092
|
},
|
|
2946
3093
|
async () => {
|
|
2947
|
-
if (!
|
|
3094
|
+
if (!existsSync9(".env.example")) {
|
|
2948
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." }] };
|
|
2949
3096
|
}
|
|
2950
|
-
const requiredKeys = parseEnvKeys(
|
|
2951
|
-
const currentKeys =
|
|
3097
|
+
const requiredKeys = parseEnvKeys(readFileSync6(".env.example", "utf-8"));
|
|
3098
|
+
const currentKeys = existsSync9(".env") ? parseEnvKeys(readFileSync6(".env", "utf-8")) : [];
|
|
2952
3099
|
const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
|
|
2953
3100
|
const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
|
|
2954
3101
|
const lines = [`\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`];
|
|
@@ -3064,7 +3211,7 @@ ${cliStatus}
|
|
|
3064
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)"
|
|
3065
3212
|
},
|
|
3066
3213
|
async () => {
|
|
3067
|
-
if (!
|
|
3214
|
+
if (!existsSync9("package.json")) {
|
|
3068
3215
|
return { content: [{ type: "text", text: "\u274C package.json \uC5C6\uC74C." }] };
|
|
3069
3216
|
}
|
|
3070
3217
|
try {
|
|
@@ -3092,7 +3239,7 @@ ${cliStatus}
|
|
|
3092
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)"
|
|
3093
3240
|
},
|
|
3094
3241
|
async () => {
|
|
3095
|
-
const current =
|
|
3242
|
+
const current = existsSync9("pnpm-lock.yaml") ? "pnpm" : existsSync9("yarn.lock") ? "yarn" : existsSync9("package-lock.json") ? "npm" : null;
|
|
3096
3243
|
const candidates = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current);
|
|
3097
3244
|
const lines = [`\uD604\uC7AC PM: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00 (lock \uD30C\uC77C \uC5C6\uC74C)"}`];
|
|
3098
3245
|
for (const pm of candidates) {
|
|
@@ -3224,6 +3371,7 @@ export {
|
|
|
3224
3371
|
ensureVhkIgnored,
|
|
3225
3372
|
listBackups,
|
|
3226
3373
|
restoreBackup,
|
|
3374
|
+
atomicWriteFile,
|
|
3227
3375
|
detectExistingRuleFiles,
|
|
3228
3376
|
buildAdoptedRules,
|
|
3229
3377
|
isInteractive,
|
|
@@ -3247,6 +3395,12 @@ export {
|
|
|
3247
3395
|
filterTrackedPaths,
|
|
3248
3396
|
stripBom,
|
|
3249
3397
|
readJsonFile,
|
|
3398
|
+
appendBlocker,
|
|
3399
|
+
getActiveBlockers,
|
|
3400
|
+
isHardStopActive,
|
|
3401
|
+
readHardStopReason,
|
|
3402
|
+
clearHardStop,
|
|
3403
|
+
ensureNotHardStopped,
|
|
3250
3404
|
NETWORK_EXEC_TIMEOUT_MS,
|
|
3251
3405
|
safeExecFile,
|
|
3252
3406
|
MAX_SCAN_FILE_BYTES,
|