@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
package/dist/index.js
CHANGED
|
@@ -5,25 +5,31 @@ import {
|
|
|
5
5
|
MAX_SECRET_FINDINGS,
|
|
6
6
|
NETWORK_EXEC_TIMEOUT_MS,
|
|
7
7
|
__toESM,
|
|
8
|
+
appendBlocker,
|
|
9
|
+
atomicWriteFile,
|
|
8
10
|
audit,
|
|
9
11
|
buildAdoptedRules,
|
|
10
12
|
checkContextDrift,
|
|
11
13
|
checkRuleDrift,
|
|
14
|
+
clearHardStop,
|
|
12
15
|
countLocalCommits,
|
|
13
16
|
deploy,
|
|
14
17
|
detectExistingRuleFiles,
|
|
15
18
|
ensureInteractive,
|
|
19
|
+
ensureNotHardStopped,
|
|
16
20
|
ensureVhkIgnored,
|
|
17
21
|
env,
|
|
18
22
|
envCheck,
|
|
19
23
|
filterSevereFindings,
|
|
20
24
|
filterTrackedPaths,
|
|
25
|
+
getActiveBlockers,
|
|
21
26
|
getExecErrorMessage,
|
|
22
27
|
getGitRoot,
|
|
23
28
|
getVhkVersion,
|
|
24
29
|
gitOut,
|
|
25
30
|
gitRun,
|
|
26
31
|
hasGitRemote,
|
|
32
|
+
isHardStopActive,
|
|
27
33
|
isInteractive,
|
|
28
34
|
isPromptAbortError,
|
|
29
35
|
ko,
|
|
@@ -34,6 +40,7 @@ import {
|
|
|
34
40
|
printSecurityWarnings,
|
|
35
41
|
promptOrDefault,
|
|
36
42
|
publish,
|
|
43
|
+
readHardStopReason,
|
|
37
44
|
readJsonFile,
|
|
38
45
|
require_ignore,
|
|
39
46
|
restoreBackup,
|
|
@@ -43,13 +50,13 @@ import {
|
|
|
43
50
|
stripBom,
|
|
44
51
|
sync,
|
|
45
52
|
t
|
|
46
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-WZH5CA4H.js";
|
|
47
54
|
|
|
48
55
|
// src/index.ts
|
|
49
56
|
import { Command, Help } from "commander";
|
|
50
57
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
51
58
|
import fs14 from "fs";
|
|
52
|
-
import
|
|
59
|
+
import chalk39 from "chalk";
|
|
53
60
|
import inquirer15 from "inquirer";
|
|
54
61
|
|
|
55
62
|
// src/lib/nlp-router.ts
|
|
@@ -471,6 +478,8 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
471
478
|
{ name: "context", desc: "\uD504\uB85C\uC81D\uD2B8 \uB9E5\uB77D \uD30C\uC77C \uC0DD\uC131 (.vhk/context.md)" },
|
|
472
479
|
{ name: "mode", desc: "Safety Mode \uC870\uD68C/\uBCC0\uACBD (lite|standard|strict)" },
|
|
473
480
|
{ name: "verify", desc: "\uAC80\uC99D \uAC8C\uC774\uD2B8 \uC2E4\uD589 + \uC99D\uAC70 \uAE30\uB85D" },
|
|
481
|
+
{ name: "preflight", desc: "\uCD9C\uACE0 \uC804 \uC548\uC804\uC810\uAC80 (2FA\xB7shim\xB7env\xB7lint\xB7\uD0C0\uC785\xB7\uD14C\uC2A4\uD2B8\xB7git, \uCE58\uBA85 \uC2DC \uCC28\uB2E8)" },
|
|
482
|
+
{ name: "standup", desc: "\uC544\uCE68 \uBE0C\uB9AC\uD551 (\uC5B4\uC81C \uD55C \uC77C + \uC624\uB298 \uCD94\uCC9C goal + \uBBF8\uD574\uACB0)" },
|
|
474
483
|
{ name: "review", desc: "\uC801\uB300\uC801 \uC790\uAE30\uAC80\uC99D (\uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC \uD0D0\uC9C0)" },
|
|
475
484
|
{ name: "mission", desc: "\uBBF8\uC158 \uACC4\uC57D \u2014 \uC791\uC5C5 \uBAA9\uD45C\xB7\uD5C8\uC6A9/\uAE08\uC9C0 \uBC94\uC704 \uC120\uC5B8\xB7\uAC80\uC99D" },
|
|
476
485
|
{ name: "context-show", desc: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uB0B4\uC6A9 \uCD9C\uB825" },
|
|
@@ -575,6 +584,10 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
575
584
|
"\uBAA8\uB4DC",
|
|
576
585
|
"verify",
|
|
577
586
|
"\uC0AC\uC804\uC810\uAC80",
|
|
587
|
+
"preflight",
|
|
588
|
+
"\uCD9C\uACE0\uC810\uAC80",
|
|
589
|
+
"standup",
|
|
590
|
+
"\uC544\uCE68",
|
|
578
591
|
"review",
|
|
579
592
|
"\uAC80\uD1A0",
|
|
580
593
|
"mission",
|
|
@@ -622,7 +635,7 @@ function detectNaturalLanguageInput(argv) {
|
|
|
622
635
|
}
|
|
623
636
|
|
|
624
637
|
// src/lib/nlp-run.ts
|
|
625
|
-
import
|
|
638
|
+
import chalk35 from "chalk";
|
|
626
639
|
import inquirer14 from "inquirer";
|
|
627
640
|
|
|
628
641
|
// src/commands/gate.ts
|
|
@@ -1565,7 +1578,7 @@ async function writeInitExtras(projectDir, noninteractive = false) {
|
|
|
1565
1578
|
|
|
1566
1579
|
// src/commands/recap.ts
|
|
1567
1580
|
import inquirer3 from "inquirer";
|
|
1568
|
-
import
|
|
1581
|
+
import chalk4 from "chalk";
|
|
1569
1582
|
import fs4 from "fs";
|
|
1570
1583
|
import path5 from "path";
|
|
1571
1584
|
|
|
@@ -1610,10 +1623,13 @@ function buildSessionDiffFromSummary(diffSummary) {
|
|
|
1610
1623
|
};
|
|
1611
1624
|
}
|
|
1612
1625
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
1626
|
+
function withMidnight(d) {
|
|
1627
|
+
return /\d{1,2}:\d{2}/.test(d) ? d : `${d} 00:00:00`;
|
|
1628
|
+
}
|
|
1613
1629
|
async function getSessionDiff(since) {
|
|
1614
1630
|
const sinceDate = since || localDate();
|
|
1615
1631
|
try {
|
|
1616
|
-
const boundary = (await git.raw(["rev-list", "-1", `--before=${sinceDate}`, "HEAD"])).trim();
|
|
1632
|
+
const boundary = (await git.raw(["rev-list", "-1", `--before=${withMidnight(sinceDate)}`, "HEAD"])).trim();
|
|
1617
1633
|
const base = boundary || EMPTY_TREE_SHA;
|
|
1618
1634
|
const diffSummary = await git.diffSummary([`${base}..HEAD`]);
|
|
1619
1635
|
const normalized = diffSummary.files.map((f) => ({
|
|
@@ -1632,7 +1648,7 @@ async function getSessionDiff(since) {
|
|
|
1632
1648
|
}
|
|
1633
1649
|
async function getRecentCommits(count = 10, since) {
|
|
1634
1650
|
const options = { maxCount: count };
|
|
1635
|
-
if (since) options["--since"] = since;
|
|
1651
|
+
if (since) options["--since"] = withMidnight(since);
|
|
1636
1652
|
try {
|
|
1637
1653
|
const log2 = await git.log(options);
|
|
1638
1654
|
return log2.all.map((entry) => ({
|
|
@@ -1750,147 +1766,48 @@ function createAdrFile(cwd, title, context2, decision, consequences) {
|
|
|
1750
1766
|
return filePath;
|
|
1751
1767
|
}
|
|
1752
1768
|
|
|
1753
|
-
// src/lib/hard-stop-guard.ts
|
|
1754
|
-
import chalk4 from "chalk";
|
|
1755
|
-
|
|
1756
|
-
// src/lib/state-files.ts
|
|
1757
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, rmSync } from "fs";
|
|
1758
|
-
import { join as join2 } from "path";
|
|
1759
|
-
var STATE_DIR = "docs/state";
|
|
1760
|
-
var BLOCKERS_PATH = join2(STATE_DIR, "blockers.md");
|
|
1761
|
-
var LEARNINGS_PATH = join2(STATE_DIR, "learnings.md");
|
|
1762
|
-
var VHK_DIR = ".vhk";
|
|
1763
|
-
var HARD_STOP_PATH = join2(VHK_DIR, "HARD_STOP");
|
|
1764
|
-
var HARD_STOP_BLOCKER_THRESHOLD = 3;
|
|
1765
|
-
function ensureStateDir() {
|
|
1766
|
-
mkdirSync(STATE_DIR, { recursive: true });
|
|
1767
|
-
}
|
|
1768
|
-
function ensureVhkDir() {
|
|
1769
|
-
mkdirSync(VHK_DIR, { recursive: true });
|
|
1770
|
-
}
|
|
1771
|
-
function isoDate() {
|
|
1772
|
-
return localDate();
|
|
1773
|
-
}
|
|
1774
|
-
var ACTIVE_BLOCKER_RE = /^- (?!~~)\[/;
|
|
1775
|
-
function countActiveBlockers(content) {
|
|
1776
|
-
let count = 0;
|
|
1777
|
-
for (const line of content.split(/\r?\n/)) {
|
|
1778
|
-
if (ACTIVE_BLOCKER_RE.test(line)) count++;
|
|
1779
|
-
}
|
|
1780
|
-
return count;
|
|
1781
|
-
}
|
|
1782
|
-
function appendBlocker(description, goalId) {
|
|
1783
|
-
ensureStateDir();
|
|
1784
|
-
const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
|
|
1785
|
-
const line = `- [${isoDate()} ${tag}] ${description.trim()}`;
|
|
1786
|
-
if (!existsSync(BLOCKERS_PATH)) {
|
|
1787
|
-
writeFileSync(
|
|
1788
|
-
BLOCKERS_PATH,
|
|
1789
|
-
`# Blockers
|
|
1790
|
-
|
|
1791
|
-
_Append-only. \uD574\uACB0 \uD56D\uBAA9\uC740 ~~\uCDE8\uC18C\uC120~~\uC73C\uB85C \uD45C\uAE30._
|
|
1792
|
-
|
|
1793
|
-
${line}
|
|
1794
|
-
`,
|
|
1795
|
-
"utf-8"
|
|
1796
|
-
);
|
|
1797
|
-
} else {
|
|
1798
|
-
appendFileSync(BLOCKERS_PATH, `${line}
|
|
1799
|
-
`, "utf-8");
|
|
1800
|
-
}
|
|
1801
|
-
const current = readFileSync(BLOCKERS_PATH, "utf-8");
|
|
1802
|
-
const count = countActiveBlockers(current);
|
|
1803
|
-
let hardStopTripped = false;
|
|
1804
|
-
if (count >= HARD_STOP_BLOCKER_THRESHOLD && !existsSync(HARD_STOP_PATH)) {
|
|
1805
|
-
writeHardStop(`auto: ${count} active blockers (threshold ${HARD_STOP_BLOCKER_THRESHOLD})`);
|
|
1806
|
-
hardStopTripped = true;
|
|
1807
|
-
}
|
|
1808
|
-
return { count, hardStopTripped };
|
|
1809
|
-
}
|
|
1810
|
-
function getActiveBlockers(limit = 3) {
|
|
1811
|
-
if (!existsSync(BLOCKERS_PATH)) return [];
|
|
1812
|
-
const lines = readFileSync(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
|
|
1813
|
-
const entries = lines.filter((l) => ACTIVE_BLOCKER_RE.test(l));
|
|
1814
|
-
return entries.slice(-limit);
|
|
1815
|
-
}
|
|
1816
|
-
function writeHardStop(reason) {
|
|
1817
|
-
ensureVhkDir();
|
|
1818
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1819
|
-
writeFileSync(HARD_STOP_PATH, `${ts}
|
|
1820
|
-
${reason}
|
|
1821
|
-
`, "utf-8");
|
|
1822
|
-
}
|
|
1823
|
-
function isHardStopActive() {
|
|
1824
|
-
return existsSync(HARD_STOP_PATH);
|
|
1825
|
-
}
|
|
1826
|
-
function readHardStopReason() {
|
|
1827
|
-
if (!existsSync(HARD_STOP_PATH)) return null;
|
|
1828
|
-
try {
|
|
1829
|
-
return readFileSync(HARD_STOP_PATH, "utf-8").trim();
|
|
1830
|
-
} catch {
|
|
1831
|
-
return null;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
function clearHardStop() {
|
|
1835
|
-
if (!existsSync(HARD_STOP_PATH)) return false;
|
|
1836
|
-
rmSync(HARD_STOP_PATH, { force: true });
|
|
1837
|
-
return true;
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
// src/lib/hard-stop-guard.ts
|
|
1841
|
-
function ensureNotHardStopped(action) {
|
|
1842
|
-
if (!isHardStopActive()) return true;
|
|
1843
|
-
console.error(chalk4.red.bold(`
|
|
1844
|
-
\u{1F6D1} HARD STOP \uD65C\uC131 \u2014 '${action}' \uC744(\uB97C) \uC2E4\uD589\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`));
|
|
1845
|
-
const reason = readHardStopReason();
|
|
1846
|
-
if (reason) console.error(chalk4.dim(` \uC0AC\uC720: ${reason.replace(/\s*\n\s*/g, " ")}`));
|
|
1847
|
-
console.error(chalk4.dim(" \uD574\uC81C: vhk resume --confirm (\uC0AC\uB78C\uC774 \uC9C1\uC811 \uC2E4\uD589)"));
|
|
1848
|
-
process.exitCode = 1;
|
|
1849
|
-
return false;
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
1769
|
// src/commands/recap.ts
|
|
1853
1770
|
async function recap(options = {}) {
|
|
1854
1771
|
if (!ensureNotHardStopped("recap")) return;
|
|
1855
|
-
console.log(
|
|
1772
|
+
console.log(chalk4.bold(`
|
|
1856
1773
|
${ko.recap.title}
|
|
1857
1774
|
`));
|
|
1858
1775
|
if (!await isGitRepo()) {
|
|
1859
|
-
console.log(
|
|
1776
|
+
console.log(chalk4.red(ko.recap.noRepo));
|
|
1860
1777
|
return;
|
|
1861
1778
|
}
|
|
1862
1779
|
if (!await hasAnyCommits()) {
|
|
1863
|
-
console.log(
|
|
1864
|
-
console.log(
|
|
1780
|
+
console.log(chalk4.yellow("\u26A0\uFE0F \uC544\uC9C1 \uCEE4\uBC0B\uC774 \uC5C6\uC5B4\uC694."));
|
|
1781
|
+
console.log(chalk4.gray(" \uD30C\uC77C\uC744 \uCD94\uAC00\uD558\uACE0 `vhk save` \uB610\uB294 `git commit`\uC73C\uB85C \uCCAB \uCEE4\uBC0B\uC744 \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
1865
1782
|
return;
|
|
1866
1783
|
}
|
|
1867
1784
|
printSecurityWarnings();
|
|
1868
|
-
console.log(
|
|
1785
|
+
console.log(chalk4.dim(`${ko.recap.analyzing}
|
|
1869
1786
|
`));
|
|
1870
1787
|
const since = options.since || localDate();
|
|
1871
1788
|
const diff2 = await getSessionDiff(since);
|
|
1872
1789
|
const commits = await getRecentCommits(10, since);
|
|
1873
1790
|
if (diff2.filesChanged === 0 && commits.length === 0) {
|
|
1874
|
-
console.log(
|
|
1791
|
+
console.log(chalk4.yellow(ko.recap.noChanges));
|
|
1875
1792
|
return;
|
|
1876
1793
|
}
|
|
1877
|
-
console.log(
|
|
1878
|
-
console.log(` \uD30C\uC77C: ${
|
|
1879
|
-
console.log(` \uCD94\uAC00: ${
|
|
1794
|
+
console.log(chalk4.bold("\u{1F4CA} \uBCC0\uACBD \uC694\uC57D:"));
|
|
1795
|
+
console.log(` \uD30C\uC77C: ${chalk4.cyan(String(diff2.filesChanged))}\uAC1C \uBCC0\uACBD`);
|
|
1796
|
+
console.log(` \uCD94\uAC00: ${chalk4.green("+" + diff2.insertions)} / \uC0AD\uC81C: ${chalk4.red("-" + diff2.deletions)}`);
|
|
1880
1797
|
if (diff2.files.length > 0) {
|
|
1881
|
-
console.log(
|
|
1798
|
+
console.log(chalk4.dim("\n \uBCC0\uACBD \uD30C\uC77C:"));
|
|
1882
1799
|
diff2.files.slice(0, 15).forEach((f) => {
|
|
1883
|
-
const icon = f.status === "new" ?
|
|
1800
|
+
const icon = f.status === "new" ? chalk4.green("\u{1F195}") : f.status === "deleted" ? chalk4.red("\u{1F5D1}\uFE0F") : chalk4.yellow("\u270F\uFE0F");
|
|
1884
1801
|
console.log(` ${icon} ${f.file}`);
|
|
1885
1802
|
});
|
|
1886
1803
|
if (diff2.files.length > 15) {
|
|
1887
|
-
console.log(
|
|
1804
|
+
console.log(chalk4.dim(` ... \uC678 ${diff2.files.length - 15}\uAC1C`));
|
|
1888
1805
|
}
|
|
1889
1806
|
}
|
|
1890
1807
|
if (commits.length > 0) {
|
|
1891
|
-
console.log(
|
|
1808
|
+
console.log(chalk4.dim("\n \uCD5C\uADFC \uCEE4\uBC0B:"));
|
|
1892
1809
|
commits.slice(0, 5).forEach((c) => {
|
|
1893
|
-
console.log(
|
|
1810
|
+
console.log(chalk4.dim(` \u2022 ${c.message}`));
|
|
1894
1811
|
});
|
|
1895
1812
|
}
|
|
1896
1813
|
console.log("");
|
|
@@ -1959,11 +1876,11 @@ ${ko.recap.title}
|
|
|
1959
1876
|
fs4.writeFileSync(filePath, content, "utf-8");
|
|
1960
1877
|
const adrCandidates = detectAdrCandidates(diff2);
|
|
1961
1878
|
if (adrCandidates.length > 0) {
|
|
1962
|
-
console.log(
|
|
1879
|
+
console.log(chalk4.cyan.bold(`
|
|
1963
1880
|
${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
1964
1881
|
for (const candidate of adrCandidates) {
|
|
1965
|
-
console.log(
|
|
1966
|
-
candidate.files.forEach((f) => console.log(
|
|
1882
|
+
console.log(chalk4.cyan(` \u2022 ${candidate.title}: ${candidate.context}`));
|
|
1883
|
+
candidate.files.forEach((f) => console.log(chalk4.dim(` ${f}`)));
|
|
1967
1884
|
}
|
|
1968
1885
|
const { createAdr } = await inquirer3.prompt([{
|
|
1969
1886
|
type: "confirm",
|
|
@@ -1993,17 +1910,17 @@ ${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
|
1993
1910
|
adrAnswers.decision,
|
|
1994
1911
|
adrAnswers.consequences
|
|
1995
1912
|
);
|
|
1996
|
-
console.log(
|
|
1913
|
+
console.log(chalk4.green(` \u2705 ADR \uC0DD\uC131: ${path5.relative(process.cwd(), adrPath)}`));
|
|
1997
1914
|
}
|
|
1998
1915
|
}
|
|
1999
1916
|
}
|
|
2000
1917
|
const troubleshootingKeywords = /fix|bug|error|crash|hotfix|patch|revert|트러블|에러|버그|수정|핫픽스/i;
|
|
2001
1918
|
const troubleCommits = commits.filter((c) => troubleshootingKeywords.test(c.message));
|
|
2002
1919
|
if (troubleCommits.length > 0) {
|
|
2003
|
-
console.log(
|
|
1920
|
+
console.log(chalk4.yellow.bold(`
|
|
2004
1921
|
${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
2005
1922
|
troubleCommits.forEach((c) => {
|
|
2006
|
-
console.log(
|
|
1923
|
+
console.log(chalk4.dim(` \u2022 ${c.message}`));
|
|
2007
1924
|
});
|
|
2008
1925
|
const { createTroubleshoot } = await inquirer3.prompt([{
|
|
2009
1926
|
type: "confirm",
|
|
@@ -2054,12 +1971,12 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
2054
1971
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
2055
1972
|
].join("\n");
|
|
2056
1973
|
fs4.writeFileSync(tsFilePath, tsContent, "utf-8");
|
|
2057
|
-
console.log(
|
|
1974
|
+
console.log(chalk4.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${path5.relative(process.cwd(), tsFilePath)}`));
|
|
2058
1975
|
}
|
|
2059
1976
|
}
|
|
2060
|
-
console.log(
|
|
1977
|
+
console.log(chalk4.green.bold(`
|
|
2061
1978
|
${ko.recap.done}`));
|
|
2062
|
-
console.log(
|
|
1979
|
+
console.log(chalk4.dim(` \u{1F4C4} ${path5.relative(process.cwd(), filePath)}`));
|
|
2063
1980
|
const claudeMdPath = path5.join(process.cwd(), "CLAUDE.md");
|
|
2064
1981
|
if (fs4.existsSync(claudeMdPath)) {
|
|
2065
1982
|
const { updateClaude } = await inquirer3.prompt([{
|
|
@@ -2079,7 +1996,7 @@ ${ko.recap.done}`));
|
|
|
2079
1996
|
`- **\uB2E4\uC74C \uC561\uC158:** ${answers.nextTodo}`
|
|
2080
1997
|
);
|
|
2081
1998
|
fs4.writeFileSync(claudeMdPath, claudeContent, "utf-8");
|
|
2082
|
-
console.log(
|
|
1999
|
+
console.log(chalk4.green(" \u2705 CLAUDE.md \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC"));
|
|
2083
2000
|
}
|
|
2084
2001
|
}
|
|
2085
2002
|
const gitSaveCmd = process.platform === "win32" ? 'git add .; git commit -m "recap: \uC138\uC158 \uAE30\uB85D"' : 'git add . && git commit -m "recap: \uC138\uC158 \uAE30\uB85D"';
|
|
@@ -2091,7 +2008,7 @@ ${ko.recap.done}`));
|
|
|
2091
2008
|
}
|
|
2092
2009
|
|
|
2093
2010
|
// src/commands/check.ts
|
|
2094
|
-
import
|
|
2011
|
+
import chalk6 from "chalk";
|
|
2095
2012
|
import path7 from "path";
|
|
2096
2013
|
import fs6 from "fs";
|
|
2097
2014
|
|
|
@@ -2128,7 +2045,7 @@ function parseRules(rulesPath) {
|
|
|
2128
2045
|
}
|
|
2129
2046
|
const banToken = extractBanToken(ruleText);
|
|
2130
2047
|
if (banToken) {
|
|
2131
|
-
rules.push(createContentRule(`ban-L${lineNo}`, currentSection, ruleText, banToken
|
|
2048
|
+
rules.push(createContentRule(`ban-L${lineNo}`, currentSection, ruleText, banToken));
|
|
2132
2049
|
}
|
|
2133
2050
|
}
|
|
2134
2051
|
return rules;
|
|
@@ -2169,15 +2086,22 @@ function createNamingRule(id, section, desc, convention) {
|
|
|
2169
2086
|
if (!fs5.existsSync(srcDir)) return violations;
|
|
2170
2087
|
walkFiles(srcDir, (filePath) => {
|
|
2171
2088
|
const name = path6.basename(filePath, path6.extname(filePath));
|
|
2089
|
+
const exempt = ["index", "vite.config", "tsconfig"];
|
|
2090
|
+
if (exempt.includes(name)) return;
|
|
2172
2091
|
if (convention === "kebab-case" && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2092
|
+
violations.push({
|
|
2093
|
+
ruleId: id,
|
|
2094
|
+
severity: "warning",
|
|
2095
|
+
message: `\uD30C\uC77C\uBA85\uC774 kebab-case\uAC00 \uC544\uB2D8: ${name}`,
|
|
2096
|
+
file: path6.relative(cwd, filePath)
|
|
2097
|
+
});
|
|
2098
|
+
} else if (convention === "camelCase" && !/^[a-z][a-zA-Z0-9]*$/.test(name)) {
|
|
2099
|
+
violations.push({
|
|
2100
|
+
ruleId: id,
|
|
2101
|
+
severity: "warning",
|
|
2102
|
+
message: `\uD30C\uC77C\uBA85\uC774 camelCase\uAC00 \uC544\uB2D8: ${name}`,
|
|
2103
|
+
file: path6.relative(cwd, filePath)
|
|
2104
|
+
});
|
|
2181
2105
|
}
|
|
2182
2106
|
});
|
|
2183
2107
|
return violations;
|
|
@@ -2203,7 +2127,14 @@ function createStructureRule(id, section, desc, expectedPath) {
|
|
|
2203
2127
|
}
|
|
2204
2128
|
};
|
|
2205
2129
|
}
|
|
2206
|
-
function
|
|
2130
|
+
function codePortionForScan(line) {
|
|
2131
|
+
const t2 = line.trimStart();
|
|
2132
|
+
if (t2.startsWith("//") || t2.startsWith("*") || t2.startsWith("/*")) return "";
|
|
2133
|
+
const noStr = line.replace(/(['"`])(?:\\.|(?!\1).)*?\1/g, "");
|
|
2134
|
+
const ci = noStr.indexOf("//");
|
|
2135
|
+
return ci >= 0 ? noStr.slice(0, ci) : noStr;
|
|
2136
|
+
}
|
|
2137
|
+
function createContentRule(id, section, desc, pattern) {
|
|
2207
2138
|
return {
|
|
2208
2139
|
id,
|
|
2209
2140
|
section,
|
|
@@ -2219,11 +2150,11 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
2219
2150
|
const fileContent = fs5.readFileSync(filePath, "utf-8");
|
|
2220
2151
|
const fileLines = fileContent.split("\n");
|
|
2221
2152
|
fileLines.forEach((line, idx) => {
|
|
2222
|
-
if (regex.test(line)) {
|
|
2153
|
+
if (regex.test(codePortionForScan(line))) {
|
|
2223
2154
|
violations.push({
|
|
2224
2155
|
ruleId: id,
|
|
2225
|
-
severity:
|
|
2226
|
-
message:
|
|
2156
|
+
severity: "error",
|
|
2157
|
+
message: `\uAE08\uC9C0 \uD328\uD134 \uBC1C\uACAC: \`${pattern}\``,
|
|
2227
2158
|
file: path6.relative(cwd, filePath),
|
|
2228
2159
|
line: idx + 1
|
|
2229
2160
|
});
|
|
@@ -2253,13 +2184,13 @@ function escapeRegex(str) {
|
|
|
2253
2184
|
}
|
|
2254
2185
|
|
|
2255
2186
|
// src/commands/goal.ts
|
|
2256
|
-
import { existsSync as
|
|
2257
|
-
import { join as
|
|
2258
|
-
import
|
|
2187
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
|
|
2188
|
+
import { join as join3 } from "path";
|
|
2189
|
+
import chalk5 from "chalk";
|
|
2259
2190
|
|
|
2260
2191
|
// src/lib/goal-frontmatter.ts
|
|
2261
|
-
import { existsSync
|
|
2262
|
-
import { join as
|
|
2192
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
2193
|
+
import { join as join2 } from "path";
|
|
2263
2194
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
2264
2195
|
function parseFrontmatter(content) {
|
|
2265
2196
|
const normalized = stripBom(content);
|
|
@@ -2292,9 +2223,9 @@ function parseSimpleYaml(yaml) {
|
|
|
2292
2223
|
return out;
|
|
2293
2224
|
}
|
|
2294
2225
|
function parseGoalFile(filePath) {
|
|
2295
|
-
if (!
|
|
2226
|
+
if (!existsSync(filePath)) return null;
|
|
2296
2227
|
try {
|
|
2297
|
-
const content =
|
|
2228
|
+
const content = readFileSync(filePath, "utf-8");
|
|
2298
2229
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
2299
2230
|
return { filePath, frontmatter, body };
|
|
2300
2231
|
} catch {
|
|
@@ -2302,7 +2233,7 @@ function parseGoalFile(filePath) {
|
|
|
2302
2233
|
}
|
|
2303
2234
|
}
|
|
2304
2235
|
function listGoals(goalsDir) {
|
|
2305
|
-
if (!
|
|
2236
|
+
if (!existsSync(goalsDir)) return [];
|
|
2306
2237
|
let entries;
|
|
2307
2238
|
try {
|
|
2308
2239
|
entries = readdirSync(goalsDir);
|
|
@@ -2313,7 +2244,7 @@ function listGoals(goalsDir) {
|
|
|
2313
2244
|
for (const name of entries) {
|
|
2314
2245
|
if (!name.endsWith(".md")) continue;
|
|
2315
2246
|
if (name === "_meta.md") continue;
|
|
2316
|
-
const fp =
|
|
2247
|
+
const fp = join2(goalsDir, name);
|
|
2317
2248
|
try {
|
|
2318
2249
|
if (!statSync(fp).isFile()) continue;
|
|
2319
2250
|
} catch {
|
|
@@ -2329,7 +2260,7 @@ function listGoals(goalsDir) {
|
|
|
2329
2260
|
return parsed;
|
|
2330
2261
|
}
|
|
2331
2262
|
function findSkippedGoalFiles(goalsDir) {
|
|
2332
|
-
if (!
|
|
2263
|
+
if (!existsSync(goalsDir)) return [];
|
|
2333
2264
|
let entries;
|
|
2334
2265
|
try {
|
|
2335
2266
|
entries = readdirSync(goalsDir);
|
|
@@ -2340,7 +2271,7 @@ function findSkippedGoalFiles(goalsDir) {
|
|
|
2340
2271
|
for (const name of entries) {
|
|
2341
2272
|
if (!name.endsWith(".md")) continue;
|
|
2342
2273
|
if (name === "_meta.md") continue;
|
|
2343
|
-
const fp =
|
|
2274
|
+
const fp = join2(goalsDir, name);
|
|
2344
2275
|
try {
|
|
2345
2276
|
if (!statSync(fp).isFile()) continue;
|
|
2346
2277
|
} catch {
|
|
@@ -2409,7 +2340,7 @@ ${body}`;
|
|
|
2409
2340
|
|
|
2410
2341
|
// src/commands/goal.ts
|
|
2411
2342
|
var GOALS_DIR = "goals";
|
|
2412
|
-
var
|
|
2343
|
+
var STATE_DIR = "docs/state";
|
|
2413
2344
|
var SCRIPTS_DIR = "scripts";
|
|
2414
2345
|
var STATUS_ICON = {
|
|
2415
2346
|
NOT_STARTED: "\u26AA",
|
|
@@ -2435,14 +2366,14 @@ function resolveGoalId(optId, goals) {
|
|
|
2435
2366
|
return selectActiveId(goals);
|
|
2436
2367
|
}
|
|
2437
2368
|
async function goalList() {
|
|
2438
|
-
console.log(
|
|
2369
|
+
console.log(chalk5.bold(`
|
|
2439
2370
|
${ko.goal.listTitle}
|
|
2440
2371
|
`));
|
|
2441
2372
|
const goals = listGoals(GOALS_DIR);
|
|
2442
2373
|
const skipped = findSkippedGoalFiles(GOALS_DIR);
|
|
2443
2374
|
if (goals.length === 0) {
|
|
2444
|
-
console.log(
|
|
2445
|
-
console.log(
|
|
2375
|
+
console.log(chalk5.yellow(" \u{1F4ED} goals/ \uB514\uB809\uD1A0\uB9AC\uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2376
|
+
console.log(chalk5.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
|
|
2446
2377
|
printSkippedGoalWarnings(skipped);
|
|
2447
2378
|
return;
|
|
2448
2379
|
}
|
|
@@ -2460,33 +2391,34 @@ ${ko.goal.listTitle}
|
|
|
2460
2391
|
const dups = findDuplicateIds(goals);
|
|
2461
2392
|
if (dups.length > 0) {
|
|
2462
2393
|
console.log("");
|
|
2463
|
-
console.log(
|
|
2394
|
+
console.log(chalk5.yellow(` ${ko.goal.duplicateId(dups.join(", "))}`));
|
|
2464
2395
|
}
|
|
2465
2396
|
printSkippedGoalWarnings(skipped);
|
|
2466
2397
|
}
|
|
2467
2398
|
function printSkippedGoalWarnings(skipped) {
|
|
2468
2399
|
if (skipped.length > 0) {
|
|
2469
2400
|
console.log("");
|
|
2470
|
-
console.log(
|
|
2401
|
+
console.log(chalk5.yellow(` ${ko.goal.skippedFiles(skipped.length)}`));
|
|
2471
2402
|
for (const s of skipped) {
|
|
2472
|
-
console.log(
|
|
2403
|
+
console.log(chalk5.yellow(` - goals/${s.file}: ${s.reason}`));
|
|
2473
2404
|
}
|
|
2474
|
-
console.log(
|
|
2405
|
+
console.log(chalk5.dim(" \uD544\uC218: type: goal + \uC22B\uC790 id. \uC2A4\uD0A4\uB9C8 \uC804\uCCB4: goals/_meta.md"));
|
|
2475
2406
|
}
|
|
2476
2407
|
}
|
|
2477
2408
|
async function goalNext() {
|
|
2478
|
-
|
|
2409
|
+
if (!ensureNotHardStopped("goal next")) return;
|
|
2410
|
+
console.log(chalk5.bold(`
|
|
2479
2411
|
${ko.goal.nextTitle}
|
|
2480
2412
|
`));
|
|
2481
2413
|
const goals = listGoals(GOALS_DIR);
|
|
2482
2414
|
if (goals.length === 0) {
|
|
2483
|
-
console.log(
|
|
2484
|
-
console.log(
|
|
2415
|
+
console.log(chalk5.yellow(" \u{1F4ED} \uC815\uC758\uB41C goal \uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2416
|
+
console.log(chalk5.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
|
|
2485
2417
|
return;
|
|
2486
2418
|
}
|
|
2487
2419
|
const activeId = selectActiveId(goals);
|
|
2488
2420
|
if (activeId === null) {
|
|
2489
|
-
console.log(
|
|
2421
|
+
console.log(chalk5.green(" \u{1F389} \uBAA8\uB4E0 goal \uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"));
|
|
2490
2422
|
return;
|
|
2491
2423
|
}
|
|
2492
2424
|
const active = goals.find((g) => g.frontmatter.id === activeId);
|
|
@@ -2505,10 +2437,10 @@ ${ko.goal.nextTitle}
|
|
|
2505
2437
|
"```",
|
|
2506
2438
|
""
|
|
2507
2439
|
].join("\n");
|
|
2508
|
-
|
|
2509
|
-
|
|
2440
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
2441
|
+
atomicWriteFile(join3(STATE_DIR, "next-task.md"), text);
|
|
2510
2442
|
console.log(
|
|
2511
|
-
|
|
2443
|
+
chalk5.green(
|
|
2512
2444
|
` \u2705 next-task.md \uAC31\uC2E0 \u2014 Goal ${activeId}: ${active.frontmatter.title ?? ""}`
|
|
2513
2445
|
)
|
|
2514
2446
|
);
|
|
@@ -2567,30 +2499,31 @@ var STATE_NEXT_TASK_TEMPLATE = "# Next Task\n\n```\nTASK: (vhk goal next \uB85C
|
|
|
2567
2499
|
var STATE_BLOCKERS_TEMPLATE = "# Blockers\n\n_Append-only. \uD574\uACB0 \uD56D\uBAA9\uC740 ~~\uCDE8\uC18C\uC120~~\uC73C\uB85C \uD45C\uAE30._\n";
|
|
2568
2500
|
var STATE_LEARNINGS_TEMPLATE = "# Learnings\n\n_Append-only. \uD55C \uC904 = \uD55C \uAD50\uD6C8._\n";
|
|
2569
2501
|
async function goalInit() {
|
|
2570
|
-
|
|
2502
|
+
if (!ensureNotHardStopped("goal init")) return;
|
|
2503
|
+
console.log(chalk5.bold(`
|
|
2571
2504
|
${ko.goal.initTitle}
|
|
2572
2505
|
`));
|
|
2573
2506
|
const targets = [
|
|
2574
|
-
{ path:
|
|
2575
|
-
{ path:
|
|
2576
|
-
{ path:
|
|
2577
|
-
{ path:
|
|
2507
|
+
{ path: join3(GOALS_DIR, "_meta.md"), content: META_TEMPLATE },
|
|
2508
|
+
{ path: join3(STATE_DIR, "next-task.md"), content: STATE_NEXT_TASK_TEMPLATE },
|
|
2509
|
+
{ path: join3(STATE_DIR, "blockers.md"), content: STATE_BLOCKERS_TEMPLATE },
|
|
2510
|
+
{ path: join3(STATE_DIR, "learnings.md"), content: STATE_LEARNINGS_TEMPLATE }
|
|
2578
2511
|
];
|
|
2579
|
-
|
|
2580
|
-
|
|
2512
|
+
mkdirSync(GOALS_DIR, { recursive: true });
|
|
2513
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
2581
2514
|
let created = 0;
|
|
2582
2515
|
let skipped = 0;
|
|
2583
2516
|
for (const t2 of targets) {
|
|
2584
|
-
if (
|
|
2585
|
-
console.log(
|
|
2517
|
+
if (existsSync2(t2.path)) {
|
|
2518
|
+
console.log(chalk5.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${t2.path}`));
|
|
2586
2519
|
skipped++;
|
|
2587
2520
|
} else {
|
|
2588
|
-
|
|
2589
|
-
console.log(
|
|
2521
|
+
atomicWriteFile(t2.path, t2.content);
|
|
2522
|
+
console.log(chalk5.green(` \u2713 created: ${t2.path}`));
|
|
2590
2523
|
created++;
|
|
2591
2524
|
}
|
|
2592
2525
|
}
|
|
2593
|
-
console.log(
|
|
2526
|
+
console.log(chalk5.bold(`
|
|
2594
2527
|
\u{1F4CA} created=${created} skipped=${skipped}`));
|
|
2595
2528
|
if (created > 0) {
|
|
2596
2529
|
printNextStep({
|
|
@@ -2601,10 +2534,10 @@ ${ko.goal.initTitle}
|
|
|
2601
2534
|
}
|
|
2602
2535
|
}
|
|
2603
2536
|
function findGateScript(id) {
|
|
2604
|
-
const mjs =
|
|
2605
|
-
if (
|
|
2606
|
-
const sh =
|
|
2607
|
-
if (
|
|
2537
|
+
const mjs = join3(SCRIPTS_DIR, `check-goal-${id}.mjs`);
|
|
2538
|
+
if (existsSync2(mjs)) return mjs;
|
|
2539
|
+
const sh = join3(SCRIPTS_DIR, `check-goal-${id}.sh`);
|
|
2540
|
+
if (existsSync2(sh)) return sh;
|
|
2608
2541
|
return null;
|
|
2609
2542
|
}
|
|
2610
2543
|
function runGate(scriptPath) {
|
|
@@ -2616,76 +2549,77 @@ function runGate(scriptPath) {
|
|
|
2616
2549
|
function warnIfBashOnWindows(scriptPath) {
|
|
2617
2550
|
if (process.platform === "win32" && scriptPath.endsWith(".sh")) {
|
|
2618
2551
|
console.log(
|
|
2619
|
-
|
|
2552
|
+
chalk5.yellow(
|
|
2620
2553
|
" \u26A0 Windows: .sh \uAC8C\uC774\uD2B8\uB294 bash \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. cross-platform .mjs \uB85C \uBC31\uD544\uD558\uC138\uC694 \u2192 vhk goal sync"
|
|
2621
2554
|
)
|
|
2622
2555
|
);
|
|
2623
2556
|
}
|
|
2624
2557
|
}
|
|
2625
2558
|
async function goalCheck(opts) {
|
|
2626
|
-
console.log(
|
|
2559
|
+
console.log(chalk5.bold(`
|
|
2627
2560
|
${ko.goal.checkTitle}
|
|
2628
2561
|
`));
|
|
2629
2562
|
const goals = listGoals(GOALS_DIR);
|
|
2630
2563
|
const id = resolveGoalId(opts.id, goals);
|
|
2631
2564
|
if (id === null) {
|
|
2632
2565
|
console.log(
|
|
2633
|
-
|
|
2566
|
+
chalk5.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
|
|
2634
2567
|
);
|
|
2635
2568
|
process.exitCode = 1;
|
|
2636
2569
|
return;
|
|
2637
2570
|
}
|
|
2638
2571
|
if (!goals.some((g) => g.frontmatter.id === id)) {
|
|
2639
|
-
console.log(
|
|
2572
|
+
console.log(chalk5.red(` \u274C ${ko.goal.notFound(id)}`));
|
|
2640
2573
|
process.exitCode = 1;
|
|
2641
2574
|
return;
|
|
2642
2575
|
}
|
|
2643
2576
|
const scriptPath = findGateScript(id);
|
|
2644
2577
|
if (!scriptPath) {
|
|
2645
2578
|
console.log(
|
|
2646
|
-
|
|
2579
|
+
chalk5.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: scripts/check-goal-${id}.{mjs,sh}`)
|
|
2647
2580
|
);
|
|
2648
2581
|
process.exitCode = 1;
|
|
2649
2582
|
return;
|
|
2650
2583
|
}
|
|
2651
2584
|
warnIfBashOnWindows(scriptPath);
|
|
2652
2585
|
const gate2 = runGate(scriptPath);
|
|
2653
|
-
console.log(
|
|
2586
|
+
console.log(chalk5.dim(` \u25B6 ${gate2.runner} ${scriptPath}
|
|
2654
2587
|
`));
|
|
2655
2588
|
if (gate2.out) console.log(gate2.out);
|
|
2656
2589
|
if (gate2.ok) {
|
|
2657
|
-
console.log(
|
|
2590
|
+
console.log(chalk5.green(`
|
|
2658
2591
|
\u2705 Goal ${id} \uAC8C\uC774\uD2B8 \uD1B5\uACFC`));
|
|
2659
2592
|
} else {
|
|
2660
|
-
console.log(
|
|
2593
|
+
console.log(chalk5.red(`
|
|
2661
2594
|
\u274C Goal ${id} \uAC8C\uC774\uD2B8 \uC2E4\uD328`));
|
|
2662
|
-
if (gate2.err && !gate2.out) console.log(
|
|
2595
|
+
if (gate2.err && !gate2.out) console.log(chalk5.dim(gate2.err.slice(0, 500)));
|
|
2663
2596
|
process.exitCode = 1;
|
|
2664
2597
|
}
|
|
2665
2598
|
}
|
|
2666
2599
|
async function goalDone(opts) {
|
|
2667
|
-
|
|
2600
|
+
if (!ensureNotHardStopped("goal done")) return;
|
|
2601
|
+
console.log(chalk5.bold(`
|
|
2668
2602
|
${ko.goal.doneTitle}
|
|
2669
2603
|
`));
|
|
2670
2604
|
const goals = listGoals(GOALS_DIR);
|
|
2671
2605
|
const id = resolveGoalId(opts.id, goals);
|
|
2672
2606
|
if (id === null) {
|
|
2673
2607
|
console.log(
|
|
2674
|
-
|
|
2608
|
+
chalk5.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
|
|
2675
2609
|
);
|
|
2676
2610
|
process.exitCode = 1;
|
|
2677
2611
|
return;
|
|
2678
2612
|
}
|
|
2679
2613
|
const target = goals.find((g) => g.frontmatter.id === id);
|
|
2680
2614
|
if (!target) {
|
|
2681
|
-
console.log(
|
|
2615
|
+
console.log(chalk5.red(` \u274C ${ko.goal.notFound(id)}`));
|
|
2682
2616
|
process.exitCode = 1;
|
|
2683
2617
|
return;
|
|
2684
2618
|
}
|
|
2685
2619
|
const scriptPath = findGateScript(id);
|
|
2686
2620
|
if (!scriptPath) {
|
|
2687
2621
|
console.log(
|
|
2688
|
-
|
|
2622
|
+
chalk5.red(
|
|
2689
2623
|
` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C \u2014 done \uCC98\uB9AC \uAC70\uBD80: scripts/check-goal-${id}.{mjs,sh}`
|
|
2690
2624
|
)
|
|
2691
2625
|
);
|
|
@@ -2694,12 +2628,12 @@ ${ko.goal.doneTitle}
|
|
|
2694
2628
|
}
|
|
2695
2629
|
warnIfBashOnWindows(scriptPath);
|
|
2696
2630
|
const gate2 = runGate(scriptPath);
|
|
2697
|
-
console.log(
|
|
2631
|
+
console.log(chalk5.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: ${gate2.runner} ${scriptPath}
|
|
2698
2632
|
`));
|
|
2699
2633
|
if (gate2.out) console.log(gate2.out);
|
|
2700
2634
|
if (!gate2.ok) {
|
|
2701
2635
|
console.log(
|
|
2702
|
-
|
|
2636
|
+
chalk5.red(
|
|
2703
2637
|
`
|
|
2704
2638
|
\u274C \uAC8C\uC774\uD2B8 \uC2E4\uD328 \u2014 frontmatter \uBCC0\uACBD \uC5C6\uC774 \uC885\uB8CC. (Forbidden: \uC2E4\uD328 = \uBCF4\uC874)`
|
|
2705
2639
|
)
|
|
@@ -2707,11 +2641,11 @@ ${ko.goal.doneTitle}
|
|
|
2707
2641
|
process.exitCode = 1;
|
|
2708
2642
|
return;
|
|
2709
2643
|
}
|
|
2710
|
-
const content =
|
|
2644
|
+
const content = readFileSync2(target.filePath, "utf-8");
|
|
2711
2645
|
const today = localDate();
|
|
2712
2646
|
const updated = updateFrontmatterStatus(content, "DONE", { completed: today });
|
|
2713
|
-
|
|
2714
|
-
console.log(
|
|
2647
|
+
atomicWriteFile(target.filePath, updated);
|
|
2648
|
+
console.log(chalk5.green(`
|
|
2715
2649
|
\u2705 Goal ${id} \u2192 DONE (completed: ${today})`));
|
|
2716
2650
|
printNextStep({
|
|
2717
2651
|
message: `Goal ${id} \uC644\uB8CC! \uB2E4\uC74C goal \uB85C:`,
|
|
@@ -2786,36 +2720,36 @@ function generateGateScript(id) {
|
|
|
2786
2720
|
].join("\n");
|
|
2787
2721
|
}
|
|
2788
2722
|
async function goalSync() {
|
|
2789
|
-
console.log(
|
|
2723
|
+
console.log(chalk5.bold(`
|
|
2790
2724
|
${ko.goal.syncTitle}
|
|
2791
2725
|
`));
|
|
2792
2726
|
const goals = listGoals(GOALS_DIR);
|
|
2793
2727
|
const result = { created: [], skipped: [] };
|
|
2794
2728
|
if (goals.length === 0) {
|
|
2795
2729
|
console.log(
|
|
2796
|
-
|
|
2730
|
+
chalk5.yellow(" \u{1F4ED} goals/ \uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694.")
|
|
2797
2731
|
);
|
|
2798
2732
|
return result;
|
|
2799
2733
|
}
|
|
2800
|
-
|
|
2734
|
+
mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
2801
2735
|
for (const g of goals) {
|
|
2802
2736
|
const id = g.frontmatter.id;
|
|
2803
2737
|
if (typeof id !== "number") continue;
|
|
2804
|
-
const target =
|
|
2805
|
-
if (
|
|
2806
|
-
console.log(
|
|
2738
|
+
const target = join3(SCRIPTS_DIR, `check-goal-${id}.mjs`);
|
|
2739
|
+
if (existsSync2(target)) {
|
|
2740
|
+
console.log(chalk5.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${target}`));
|
|
2807
2741
|
result.skipped.push(id);
|
|
2808
2742
|
continue;
|
|
2809
2743
|
}
|
|
2810
|
-
const shOnly =
|
|
2811
|
-
|
|
2744
|
+
const shOnly = existsSync2(join3(SCRIPTS_DIR, `check-goal-${id}.sh`));
|
|
2745
|
+
writeFileSync(target, generateGateScript(id), "utf-8");
|
|
2812
2746
|
console.log(
|
|
2813
|
-
|
|
2747
|
+
chalk5.green(` \u2713 created: ${target}${shOnly ? " (.sh \u2192 .mjs \uBC31\uD544, Windows 1\uAE09)" : ""}`)
|
|
2814
2748
|
);
|
|
2815
2749
|
result.created.push(id);
|
|
2816
2750
|
}
|
|
2817
2751
|
console.log(
|
|
2818
|
-
|
|
2752
|
+
chalk5.bold(`
|
|
2819
2753
|
\u{1F4CA} created=${result.created.length} skipped=${result.skipped.length}`)
|
|
2820
2754
|
);
|
|
2821
2755
|
if (result.created.length > 0) {
|
|
@@ -2836,22 +2770,22 @@ async function check(opts = {}) {
|
|
|
2836
2770
|
return checkRules();
|
|
2837
2771
|
}
|
|
2838
2772
|
async function checkRules() {
|
|
2839
|
-
console.log(
|
|
2773
|
+
console.log(chalk6.bold(`
|
|
2840
2774
|
${ko.check.title}
|
|
2841
2775
|
`));
|
|
2842
2776
|
const cwd = process.cwd();
|
|
2843
2777
|
const rulesPath = path7.join(cwd, "RULES.md");
|
|
2844
2778
|
if (!fs6.existsSync(rulesPath)) {
|
|
2845
|
-
console.log(
|
|
2846
|
-
console.log(
|
|
2779
|
+
console.log(chalk6.yellow(ko.check.noRules));
|
|
2780
|
+
console.log(chalk6.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
2847
2781
|
return;
|
|
2848
2782
|
}
|
|
2849
2783
|
const rules = parseRules(rulesPath);
|
|
2850
|
-
console.log(
|
|
2784
|
+
console.log(chalk6.dim(` \u{1F4CF} \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${rules.length}\uAC1C \uAC10\uC9C0 (\uB098\uBA38\uC9C0 \uADDC\uCE59\uC740 \uC218\uB3D9/\uB3C4\uAD6C \uD655\uC778)
|
|
2851
2785
|
`));
|
|
2852
2786
|
if (rules.length === 0) {
|
|
2853
|
-
console.log(
|
|
2854
|
-
console.log(
|
|
2787
|
+
console.log(chalk6.yellow(ko.check.noAutoRules));
|
|
2788
|
+
console.log(chalk6.dim(" RULES.md\uC5D0 \uD30C\uC77C \uC774\uB984\xB7\uD3F4\uB354 \uADDC\uCE59\uC744 \uC801\uC73C\uBA74 \uC790\uB3D9\uC73C\uB85C \uC810\uAC80\uD574\uC694."));
|
|
2855
2789
|
return;
|
|
2856
2790
|
}
|
|
2857
2791
|
const allViolations = [];
|
|
@@ -2859,14 +2793,14 @@ ${ko.check.title}
|
|
|
2859
2793
|
for (const rule of rules) {
|
|
2860
2794
|
const violations = rule.check(cwd);
|
|
2861
2795
|
if (violations.length === 0) {
|
|
2862
|
-
const patternHint = rule.type === "content" && rule.pattern ?
|
|
2863
|
-
console.log(
|
|
2796
|
+
const patternHint = rule.type === "content" && rule.pattern ? chalk6.dim(` [\uAC80\uC0AC: ${rule.pattern.source}]`) : "";
|
|
2797
|
+
console.log(chalk6.green(` \u2705 ${rule.id}`) + chalk6.dim(` \u2014 ${rule.description.slice(0, 60)}`) + patternHint);
|
|
2864
2798
|
passCount++;
|
|
2865
2799
|
} else {
|
|
2866
|
-
console.log(
|
|
2800
|
+
console.log(chalk6.red(` \u274C ${rule.id}`) + chalk6.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
|
|
2867
2801
|
violations.forEach((v) => {
|
|
2868
|
-
const loc = v.file ?
|
|
2869
|
-
const icon = v.severity === "error" ?
|
|
2802
|
+
const loc = v.file ? chalk6.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
|
|
2803
|
+
const icon = v.severity === "error" ? chalk6.red("\u2716") : v.severity === "warning" ? chalk6.yellow("\u26A0") : chalk6.blue("\u2139");
|
|
2870
2804
|
console.log(` ${icon} ${v.message}${loc}`);
|
|
2871
2805
|
});
|
|
2872
2806
|
allViolations.push(...violations);
|
|
@@ -2876,18 +2810,18 @@ ${ko.check.title}
|
|
|
2876
2810
|
const errors = allViolations.filter((v) => v.severity === "error").length;
|
|
2877
2811
|
const warnings = allViolations.filter((v) => v.severity === "warning").length;
|
|
2878
2812
|
if (allViolations.length === 0) {
|
|
2879
|
-
console.log(
|
|
2880
|
-
console.log(
|
|
2813
|
+
console.log(chalk6.green.bold(`\u2705 \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${passCount}\uAC1C \uD1B5\uACFC`));
|
|
2814
|
+
console.log(chalk6.dim(" (RULES.md \uC758 \uB098\uBA38\uC9C0 \uADDC\uCE59\uC740 \uCF54\uB4DC \uC790\uB3D9 \uAC80\uC0AC \uBD88\uAC00 \u2014 \uC9C1\uC811/\uB3C4\uAD6C\uB85C \uD655\uC778\uD558\uC138\uC694.)"));
|
|
2881
2815
|
printNextStep({
|
|
2882
2816
|
message: "\uBAA8\uB4E0 \uADDC\uCE59 \uD1B5\uACFC! \uBCF4\uC548 \uC2A4\uCE94\uB3C4 \uD574\uBCFC\uAE4C\uC694?",
|
|
2883
2817
|
command: "vhk \uBCF4\uC548 scan",
|
|
2884
2818
|
cursorHint: "\uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB824\uC918"
|
|
2885
2819
|
});
|
|
2886
2820
|
} else {
|
|
2887
|
-
console.log(
|
|
2888
|
-
console.log(` \uADDC\uCE59: ${
|
|
2889
|
-
if (errors > 0) console.log(` ${
|
|
2890
|
-
if (warnings > 0) console.log(` ${
|
|
2821
|
+
console.log(chalk6.bold(ko.check.summary));
|
|
2822
|
+
console.log(` \uADDC\uCE59: ${chalk6.cyan(String(rules.length))}\uAC1C | \uD1B5\uACFC: ${chalk6.green(String(passCount))}\uAC1C | \uC704\uBC18: ${chalk6.red(String(allViolations.length))}\uAC74`);
|
|
2823
|
+
if (errors > 0) console.log(` ${chalk6.red(`\u2716 ${errors}\uAC1C \uC5D0\uB7EC`)}`);
|
|
2824
|
+
if (warnings > 0) console.log(` ${chalk6.yellow(`\u26A0 ${warnings}\uAC1C \uACBD\uACE0`)}`);
|
|
2891
2825
|
printNextStep({
|
|
2892
2826
|
message: "\uC704\uBC18 \uD56D\uBAA9\uC744 \uC218\uC815\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
|
|
2893
2827
|
command: "vhk \uC810\uAC80",
|
|
@@ -2900,36 +2834,36 @@ ${ko.check.title}
|
|
|
2900
2834
|
}
|
|
2901
2835
|
|
|
2902
2836
|
// src/commands/secure.ts
|
|
2903
|
-
import
|
|
2837
|
+
import chalk7 from "chalk";
|
|
2904
2838
|
import fs7 from "fs";
|
|
2905
2839
|
import path8 from "path";
|
|
2906
2840
|
async function secure() {
|
|
2907
|
-
console.log(
|
|
2841
|
+
console.log(chalk7.bold(`
|
|
2908
2842
|
${ko.secure.title}
|
|
2909
2843
|
`));
|
|
2910
2844
|
const cwd = process.cwd();
|
|
2911
2845
|
const gitignorePath = path8.join(cwd, ".gitignore");
|
|
2912
2846
|
const hasGitignore = fs7.existsSync(gitignorePath);
|
|
2913
2847
|
if (!hasGitignore) {
|
|
2914
|
-
console.log(
|
|
2915
|
-
console.log(
|
|
2848
|
+
console.log(chalk7.yellow(` ${ko.secure.noGitignore}`));
|
|
2849
|
+
console.log(chalk7.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
|
|
2916
2850
|
} else {
|
|
2917
2851
|
const gitignoreContent = fs7.readFileSync(gitignorePath, "utf-8");
|
|
2918
2852
|
if (!gitignoreContent.includes(".env")) {
|
|
2919
|
-
console.log(
|
|
2920
|
-
console.log(
|
|
2853
|
+
console.log(chalk7.yellow(` ${ko.secure.noEnvInGitignore}`));
|
|
2854
|
+
console.log(chalk7.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
|
|
2921
2855
|
}
|
|
2922
2856
|
}
|
|
2923
|
-
console.log(
|
|
2857
|
+
console.log(chalk7.dim(` ${ko.secure.scanning}
|
|
2924
2858
|
`));
|
|
2925
2859
|
const { findings, scannedFiles, truncated } = scanProjectForSecrets(cwd);
|
|
2926
|
-
console.log(
|
|
2860
|
+
console.log(chalk7.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC (lock\xB7node_modules\xB7>${MAX_SCAN_FILE_BYTES / 1024}KB \uC81C\uC678)`));
|
|
2927
2861
|
if (truncated) {
|
|
2928
|
-
console.log(
|
|
2862
|
+
console.log(chalk7.yellow(` \u26A0\uFE0F \uACB0\uACFC ${MAX_SECRET_FINDINGS}\uAC74\uC5D0\uC11C \uCD9C\uB825\uC744 \uC81C\uD55C\uD588\uC2B5\uB2C8\uB2E4. lock \uD30C\uC77C \uB4F1\uC740 \uC790\uB3D9 \uC81C\uC678\uB429\uB2C8\uB2E4.`));
|
|
2929
2863
|
}
|
|
2930
2864
|
console.log("");
|
|
2931
2865
|
if (findings.length === 0) {
|
|
2932
|
-
console.log(
|
|
2866
|
+
console.log(chalk7.green.bold(` ${ko.secure.clean}`));
|
|
2933
2867
|
printNextStep({
|
|
2934
2868
|
message: "\uBCF4\uC548 \uC774\uC0C1 \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
|
|
2935
2869
|
command: "vhk \uC815\uB9AC",
|
|
@@ -2941,46 +2875,239 @@ ${ko.secure.title}
|
|
|
2941
2875
|
const high = findings.filter((f) => f.severity === "high");
|
|
2942
2876
|
const medium = findings.filter((f) => f.severity === "medium");
|
|
2943
2877
|
if (critical.length > 0) {
|
|
2944
|
-
console.log(
|
|
2878
|
+
console.log(chalk7.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
|
|
2945
2879
|
critical.forEach((f) => {
|
|
2946
|
-
console.log(
|
|
2947
|
-
console.log(
|
|
2880
|
+
console.log(chalk7.red(` \u2716 ${f.patternName}`));
|
|
2881
|
+
console.log(chalk7.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
2948
2882
|
});
|
|
2949
2883
|
console.log("");
|
|
2950
2884
|
}
|
|
2951
2885
|
if (high.length > 0) {
|
|
2952
|
-
console.log(
|
|
2886
|
+
console.log(chalk7.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
|
|
2953
2887
|
high.forEach((f) => {
|
|
2954
|
-
console.log(
|
|
2955
|
-
console.log(
|
|
2888
|
+
console.log(chalk7.yellow(` \u26A0 ${f.patternName}`));
|
|
2889
|
+
console.log(chalk7.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
2956
2890
|
});
|
|
2957
2891
|
console.log("");
|
|
2958
2892
|
}
|
|
2959
2893
|
if (medium.length > 0) {
|
|
2960
|
-
console.log(
|
|
2894
|
+
console.log(chalk7.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
|
|
2961
2895
|
medium.forEach((f) => {
|
|
2962
|
-
console.log(
|
|
2963
|
-
console.log(
|
|
2896
|
+
console.log(chalk7.blue(` \u2139 ${f.patternName}`));
|
|
2897
|
+
console.log(chalk7.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
2964
2898
|
});
|
|
2965
2899
|
console.log("");
|
|
2966
2900
|
}
|
|
2967
|
-
console.log(
|
|
2968
|
-
console.log(` \uCD1D ${
|
|
2901
|
+
console.log(chalk7.bold(` ${ko.secure.summary}`));
|
|
2902
|
+
console.log(` \uCD1D ${chalk7.red(String(findings.length))}\uAC74 \uAC10\uC9C0 | CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length}`);
|
|
2969
2903
|
console.log("");
|
|
2970
|
-
console.log(
|
|
2971
|
-
console.log(
|
|
2972
|
-
console.log(
|
|
2973
|
-
console.log(
|
|
2904
|
+
console.log(chalk7.dim(" \u{1F4A1} \uC870\uCE58 \uBC29\uBC95:"));
|
|
2905
|
+
console.log(chalk7.dim(" 1. \uD574\uB2F9 \uD30C\uC77C\uC5D0\uC11C \uC2DC\uD06C\uB9BF\uC744 \uC81C\uAC70\uD558\uACE0 \uD658\uACBD\uBCC0\uC218\uB85C \uC774\uB3D9"));
|
|
2906
|
+
console.log(chalk7.dim(" 2. git history\uC5D0\uC11C\uB3C4 \uC81C\uAC70: git filter-branch \uB610\uB294 BFG Repo-Cleaner"));
|
|
2907
|
+
console.log(chalk7.dim(" 3. \uC720\uCD9C\uB41C \uD0A4\uB294 \uC989\uC2DC \uD3D0\uAE30\uD558\uACE0 \uC7AC\uBC1C\uAE09\n"));
|
|
2974
2908
|
if (critical.length > 0 || high.length > 0) {
|
|
2975
2909
|
process.exitCode = 1;
|
|
2976
2910
|
}
|
|
2977
2911
|
}
|
|
2978
2912
|
|
|
2979
2913
|
// src/commands/doctor.ts
|
|
2980
|
-
import
|
|
2914
|
+
import chalk8 from "chalk";
|
|
2981
2915
|
import fs9 from "fs";
|
|
2982
2916
|
import path10 from "path";
|
|
2983
2917
|
import { fileURLToPath } from "url";
|
|
2918
|
+
import os2 from "os";
|
|
2919
|
+
|
|
2920
|
+
// src/doctor/runner.ts
|
|
2921
|
+
async function runDiagnostics(diags, opts, deps) {
|
|
2922
|
+
return Promise.all(
|
|
2923
|
+
diags.map(async (fn) => {
|
|
2924
|
+
try {
|
|
2925
|
+
return await fn(opts, deps);
|
|
2926
|
+
} catch (e) {
|
|
2927
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2928
|
+
return { name: fn.name || "diagnostic", status: "fail", value: "\uC9C4\uB2E8 \uC2E4\uD328", advice: msg.slice(0, 200) };
|
|
2929
|
+
}
|
|
2930
|
+
})
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
// src/doctor/report.ts
|
|
2935
|
+
function diagIcon(status2) {
|
|
2936
|
+
return status2 === "ok" ? "\u{1F7E2}" : status2 === "warn" ? "\u{1F7E1}" : status2 === "fail" ? "\u{1F534}" : "\u26AA";
|
|
2937
|
+
}
|
|
2938
|
+
function formatDiagnostics(diags) {
|
|
2939
|
+
const lines = [];
|
|
2940
|
+
for (const d of diags) {
|
|
2941
|
+
lines.push(` ${diagIcon(d.status)} ${d.name.padEnd(8)} ${d.value}`);
|
|
2942
|
+
}
|
|
2943
|
+
const advices = diags.filter((d) => d.status !== "ok" && d.advice);
|
|
2944
|
+
if (advices.length > 0) {
|
|
2945
|
+
lines.push("");
|
|
2946
|
+
lines.push(" \uAD8C\uC7A5 \uC870\uCE58:");
|
|
2947
|
+
for (const d of advices) lines.push(` - ${d.name}: ${d.advice}`);
|
|
2948
|
+
}
|
|
2949
|
+
return lines;
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// src/lib/preflight.ts
|
|
2953
|
+
var check2 = (name, status2, detail, severity) => ({ name, status: status2, detail, severity });
|
|
2954
|
+
function nodeMeetsShimSafe(version) {
|
|
2955
|
+
const m = version.replace(/^v/, "").split(".");
|
|
2956
|
+
const major = Number(m[0]);
|
|
2957
|
+
const minor = Number(m[1] ?? 0);
|
|
2958
|
+
if (!Number.isFinite(major)) return false;
|
|
2959
|
+
if (major > 21) return true;
|
|
2960
|
+
if (major === 21) return minor >= 7;
|
|
2961
|
+
if (major === 20) return minor >= 12;
|
|
2962
|
+
return false;
|
|
2963
|
+
}
|
|
2964
|
+
function checkNpmAuth(run) {
|
|
2965
|
+
const r = run("npm", ["whoami"]);
|
|
2966
|
+
if (r.ok && r.out.trim()) {
|
|
2967
|
+
return check2("2FA / npm", "pass", `logged in as ${r.out.trim()}`, "high");
|
|
2968
|
+
}
|
|
2969
|
+
return check2("2FA / npm", "warn", "npm \uB85C\uADF8\uC778 \uD544\uC694 (npm login). publish \uC2DC 2FA OTP\uB294 \uC0AC\uB78C\uC774 \uC9C1\uC811 \uC785\uB825", "high");
|
|
2970
|
+
}
|
|
2971
|
+
function checkShim(nodeVersion) {
|
|
2972
|
+
if (nodeMeetsShimSafe(nodeVersion)) {
|
|
2973
|
+
return check2("shim", "pass", `Node ${nodeVersion} (safe)`, "high");
|
|
2974
|
+
}
|
|
2975
|
+
return check2("shim", "warn", `Node ${nodeVersion} \u2014 .cmd shim CVE \uCDE8\uC57D(20.12+/21.7+ \uAD8C\uC7A5)`, "high");
|
|
2976
|
+
}
|
|
2977
|
+
function checkLint(run, hasLinter) {
|
|
2978
|
+
if (!hasLinter) {
|
|
2979
|
+
return check2("lint", "skip", "eslint \uBBF8\uC124\uC815 \u2014 \uB9B0\uD2B8 \uC2A4\uD0B5", "critical");
|
|
2980
|
+
}
|
|
2981
|
+
const r = run("npx", ["eslint", "."]);
|
|
2982
|
+
return r.ok ? check2("lint", "pass", "0 errors", "critical") : check2("lint", "fail", "eslint \uC624\uB958 \u2014 \uCD9C\uB825 \uD655\uC778", "critical");
|
|
2983
|
+
}
|
|
2984
|
+
function checkTypecheck(run) {
|
|
2985
|
+
const r = run("npx", ["tsc", "--noEmit"]);
|
|
2986
|
+
return r.ok ? check2("typecheck", "pass", "tsc --noEmit pass", "critical") : check2("typecheck", "fail", "tsc \uD0C0\uC785 \uC624\uB958 \u2014 \uCD9C\uB825 \uD655\uC778", "critical");
|
|
2987
|
+
}
|
|
2988
|
+
function checkTests(run, opts) {
|
|
2989
|
+
const args = opts.full ? ["vitest", "--run"] : ["vitest", "--changed", "--run"];
|
|
2990
|
+
const r = run("npx", args);
|
|
2991
|
+
return r.ok ? check2("tests", "pass", opts.full ? "\uC804\uCCB4 \uD1B5\uACFC" : "\uBCC0\uACBD\uBD84 \uD1B5\uACFC(\uCE90\uC2DC \uC2A4\uD0B5)", "critical") : check2("tests", "fail", "\uD14C\uC2A4\uD2B8 \uC2E4\uD328 \u2014 \uCD9C\uB825 \uD655\uC778", "critical");
|
|
2992
|
+
}
|
|
2993
|
+
function checkGitClean(run) {
|
|
2994
|
+
const r = run("git", ["status", "--porcelain"]);
|
|
2995
|
+
if (!r.ok) return check2("git", "warn", "git \uC0C1\uD0DC \uD655\uC778 \uC2E4\uD328", "normal");
|
|
2996
|
+
const lines = r.out.split("\n").filter((l) => l.trim());
|
|
2997
|
+
return lines.length === 0 ? check2("git", "pass", "clean", "normal") : check2("git", "warn", `uncommitted ${lines.length} files`, "normal");
|
|
2998
|
+
}
|
|
2999
|
+
function checkBranch(run) {
|
|
3000
|
+
const r = run("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
3001
|
+
const branch = r.out.trim();
|
|
3002
|
+
if (branch === "main" || branch === "master") {
|
|
3003
|
+
return check2("branch", "warn", `${branch} \uC9C1\uC811 \u2014 feature \uBE0C\uB79C\uCE58 \uAD8C\uC7A5(\uD55C PR \uD55C goal)`, "normal");
|
|
3004
|
+
}
|
|
3005
|
+
return check2("branch", "pass", branch || "(unknown)", "normal");
|
|
3006
|
+
}
|
|
3007
|
+
function summarizePreflight(checks) {
|
|
3008
|
+
const s = { blocked: false, passed: 0, warned: 0, failed: 0, skipped: 0 };
|
|
3009
|
+
for (const c of checks) {
|
|
3010
|
+
if (c.status === "pass") s.passed++;
|
|
3011
|
+
else if (c.status === "warn") s.warned++;
|
|
3012
|
+
else if (c.status === "fail") s.failed++;
|
|
3013
|
+
else if (c.status === "skip") s.skipped++;
|
|
3014
|
+
if (c.status === "fail" && c.severity === "critical") s.blocked = true;
|
|
3015
|
+
}
|
|
3016
|
+
return s;
|
|
3017
|
+
}
|
|
3018
|
+
function detectHasLinter(input) {
|
|
3019
|
+
return input.hasLintScript || input.hasEslintConfig;
|
|
3020
|
+
}
|
|
3021
|
+
function statusIcon(c) {
|
|
3022
|
+
if (c.status === "pass") return "\u{1F7E2}";
|
|
3023
|
+
if (c.status === "fail") return "\u{1F534}";
|
|
3024
|
+
if (c.status === "skip") return "\u26AA";
|
|
3025
|
+
return c.severity === "high" ? "\u{1F7E0}" : "\u{1F7E1}";
|
|
3026
|
+
}
|
|
3027
|
+
function runPreflight(opts, deps) {
|
|
3028
|
+
return [
|
|
3029
|
+
checkNpmAuth(deps.run),
|
|
3030
|
+
checkShim(deps.nodeVersion),
|
|
3031
|
+
deps.worktreeEnv(),
|
|
3032
|
+
checkLint(deps.run, deps.hasLinter),
|
|
3033
|
+
checkTypecheck(deps.run),
|
|
3034
|
+
checkTests(deps.run, { full: opts.full }),
|
|
3035
|
+
checkGitClean(deps.run),
|
|
3036
|
+
checkBranch(deps.run)
|
|
3037
|
+
];
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
// src/doctor/diagnostics/node.ts
|
|
3041
|
+
function diagNode(_opts, deps) {
|
|
3042
|
+
const reach = deps.run("node", ["--version"]);
|
|
3043
|
+
if (!reach.ok) {
|
|
3044
|
+
return {
|
|
3045
|
+
name: "Node",
|
|
3046
|
+
status: "fail",
|
|
3047
|
+
value: "PATH \uBBF8\uC778\uC2DD",
|
|
3048
|
+
advice: "node \uAC00 PATH \uC5D0 \uC5C6\uC74C \u2014 \uC178/\uD658\uACBD\uBCC0\uC218(nvm\xB7volta \uCD08\uAE30\uD654) \uD655\uC778"
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
const v = deps.nodeVersion;
|
|
3052
|
+
if (nodeMeetsShimSafe(v)) {
|
|
3053
|
+
return { name: "Node", status: "ok", value: `${v} (shim-safe)` };
|
|
3054
|
+
}
|
|
3055
|
+
return {
|
|
3056
|
+
name: "Node",
|
|
3057
|
+
status: "warn",
|
|
3058
|
+
value: v,
|
|
3059
|
+
advice: "Node 20.12+ / 21.7+ \uAD8C\uC7A5 (.cmd shim CVE-2024-27980)"
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
// src/doctor/diagnostics/npm.ts
|
|
3064
|
+
function diagNpm(_opts, deps) {
|
|
3065
|
+
const r = deps.run("npm", ["--version"]);
|
|
3066
|
+
if (r.ok && r.out.trim()) {
|
|
3067
|
+
return { name: "npm", status: "ok", value: r.out.trim().split("\n")[0] };
|
|
3068
|
+
}
|
|
3069
|
+
return {
|
|
3070
|
+
name: "npm",
|
|
3071
|
+
status: "fail",
|
|
3072
|
+
value: "\uC5C6\uC74C",
|
|
3073
|
+
advice: "Node.js \uC7AC\uC124\uCE58 \uB610\uB294 PATH \uD655\uC778 (publish\xB7\uC5C5\uB370\uC774\uD2B8\uAC00 npm \uC758\uC874)"
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
// src/doctor/diagnostics/pnpm.ts
|
|
3078
|
+
function diagPnpm(_opts, deps) {
|
|
3079
|
+
const r = deps.run("pnpm", ["--version"]);
|
|
3080
|
+
if (r.ok && r.out.trim()) {
|
|
3081
|
+
return { name: "pnpm", status: "ok", value: r.out.trim().split("\n")[0] };
|
|
3082
|
+
}
|
|
3083
|
+
return { name: "pnpm", status: "fail", value: "\uC5C6\uC74C", advice: "\uC124\uCE58: npm i -g pnpm" };
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// src/doctor/diagnostics/git.ts
|
|
3087
|
+
function diagGit(_opts, deps) {
|
|
3088
|
+
const ver = deps.run("git", ["--version"]);
|
|
3089
|
+
if (!ver.ok) {
|
|
3090
|
+
return { name: "git", status: "fail", value: "\uC5C6\uC74C", advice: "\uC124\uCE58: https://git-scm.com" };
|
|
3091
|
+
}
|
|
3092
|
+
const v = ver.out.trim().replace(/^git version /, "");
|
|
3093
|
+
const name = deps.run("git", ["config", "user.name"]);
|
|
3094
|
+
const email = deps.run("git", ["config", "user.email"]);
|
|
3095
|
+
const configured = name.ok && name.out.trim() !== "" && email.ok && email.out.trim() !== "";
|
|
3096
|
+
if (configured) {
|
|
3097
|
+
return { name: "git", status: "ok", value: `${v} (user configured)` };
|
|
3098
|
+
}
|
|
3099
|
+
return {
|
|
3100
|
+
name: "git",
|
|
3101
|
+
status: "warn",
|
|
3102
|
+
value: v,
|
|
3103
|
+
advice: "git config user.name / user.email \uC124\uC815 \uAD8C\uC7A5"
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
// src/doctor/diagnostics/os.ts
|
|
3108
|
+
function diagOs(_opts, deps) {
|
|
3109
|
+
return { name: "OS", status: "ok", value: `${deps.platform} ${deps.osRelease}` };
|
|
3110
|
+
}
|
|
2984
3111
|
|
|
2985
3112
|
// src/lib/version-check.ts
|
|
2986
3113
|
import fs8 from "fs";
|
|
@@ -3064,12 +3191,6 @@ function getUpdateInfo(now = Date.now()) {
|
|
|
3064
3191
|
}
|
|
3065
3192
|
|
|
3066
3193
|
// src/commands/doctor.ts
|
|
3067
|
-
function checkCommand(name, command, hint) {
|
|
3068
|
-
const result = safeExecFile(command, ["--version"]);
|
|
3069
|
-
if (!result.ok) return { name, command, ok: false, hint };
|
|
3070
|
-
const version = result.out.split("\n")[0];
|
|
3071
|
-
return { name, command, version, ok: true, hint };
|
|
3072
|
-
}
|
|
3073
3194
|
function getVhkVersion2() {
|
|
3074
3195
|
const dir = path10.dirname(fileURLToPath(import.meta.url));
|
|
3075
3196
|
const candidates = [
|
|
@@ -3089,43 +3210,41 @@ function getVhkVersion2() {
|
|
|
3089
3210
|
return void 0;
|
|
3090
3211
|
}
|
|
3091
3212
|
async function doctor(opts = {}) {
|
|
3092
|
-
console.log(
|
|
3213
|
+
console.log(chalk8.bold(`
|
|
3093
3214
|
${ko.doctor.title}
|
|
3094
3215
|
`));
|
|
3095
|
-
const
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
}
|
|
3110
|
-
}
|
|
3216
|
+
const run = (cmd, args) => {
|
|
3217
|
+
const r = safeExecFile(cmd, args);
|
|
3218
|
+
return r.ok ? { ok: true, out: r.out } : { ok: false, out: r.out, err: r.err };
|
|
3219
|
+
};
|
|
3220
|
+
const deps = {
|
|
3221
|
+
run,
|
|
3222
|
+
nodeVersion: process.version,
|
|
3223
|
+
platform: process.platform,
|
|
3224
|
+
osRelease: os2.release()
|
|
3225
|
+
};
|
|
3226
|
+
const diags = await runDiagnostics([diagNode, diagNpm, diagPnpm, diagGit, diagOs], opts, deps);
|
|
3227
|
+
for (const line of formatDiagnostics(diags)) console.log(line);
|
|
3228
|
+
const anyFail = diags.some((d) => d.status === "fail");
|
|
3229
|
+
const warnCount = diags.filter((d) => d.status === "warn").length;
|
|
3111
3230
|
console.log("");
|
|
3112
3231
|
const vhkVersion = getVhkVersion2();
|
|
3113
3232
|
if (vhkVersion) {
|
|
3114
|
-
console.log(
|
|
3233
|
+
console.log(chalk8.green(" \u2705 VHK") + chalk8.dim(` \u2014 v${vhkVersion}`));
|
|
3115
3234
|
} else {
|
|
3116
|
-
console.log(
|
|
3235
|
+
console.log(chalk8.green(" \u2705 VHK") + chalk8.dim(" \u2014 \uC124\uCE58\uB428"));
|
|
3117
3236
|
}
|
|
3118
3237
|
if (vhkVersion) {
|
|
3119
3238
|
const latest = fetchLatestNpmVersion("@byh3071/vhk");
|
|
3120
3239
|
if (latest) recordLatest(latest);
|
|
3121
3240
|
if (latest && compareSemver(latest, vhkVersion) > 0) {
|
|
3122
|
-
console.log(
|
|
3241
|
+
console.log(chalk8.yellow(` ${ko.doctor.updateAvailable(latest)}`));
|
|
3123
3242
|
} else if (latest) {
|
|
3124
|
-
console.log(
|
|
3243
|
+
console.log(chalk8.dim(` ${ko.doctor.updateCurrent}`));
|
|
3125
3244
|
}
|
|
3126
3245
|
}
|
|
3127
3246
|
console.log("");
|
|
3128
|
-
console.log(
|
|
3247
|
+
console.log(chalk8.bold(` ${ko.doctor.projectFiles}`));
|
|
3129
3248
|
const cwd = process.cwd();
|
|
3130
3249
|
const projectFiles = [
|
|
3131
3250
|
{ name: "RULES.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
|
|
@@ -3137,67 +3256,74 @@ ${ko.doctor.title}
|
|
|
3137
3256
|
for (const file of projectFiles) {
|
|
3138
3257
|
const exists = fs9.existsSync(path10.join(cwd, file.name));
|
|
3139
3258
|
if (exists) {
|
|
3140
|
-
console.log(
|
|
3259
|
+
console.log(chalk8.green(` \u2705 ${file.name}`));
|
|
3141
3260
|
if (file.name === ".env") {
|
|
3142
3261
|
const gitignorePath = path10.join(cwd, ".gitignore");
|
|
3143
3262
|
if (fs9.existsSync(gitignorePath)) {
|
|
3144
3263
|
const gitignore = fs9.readFileSync(gitignorePath, "utf-8");
|
|
3145
3264
|
if (!gitignore.includes(".env")) {
|
|
3146
|
-
console.log(
|
|
3265
|
+
console.log(chalk8.yellow(` ${ko.doctor.envNotIgnored}`));
|
|
3147
3266
|
}
|
|
3148
3267
|
}
|
|
3149
3268
|
}
|
|
3150
3269
|
} else if (file.name === ".env" && fs9.existsSync(path10.join(cwd, ".env.local"))) {
|
|
3151
|
-
console.log(
|
|
3270
|
+
console.log(chalk8.green(" \u2705 .env.local") + chalk8.dim(" \u2014 \uB85C\uCEF4 env \uC0AC\uC6A9 \uC911 (.env \uC5C6\uC5B4\uB3C4 \uC815\uC0C1)"));
|
|
3152
3271
|
} else {
|
|
3153
|
-
console.log(
|
|
3272
|
+
console.log(chalk8.dim(` \u26AB ${file.name}`) + chalk8.dim(` \u2014 ${file.hint}`));
|
|
3154
3273
|
}
|
|
3155
3274
|
}
|
|
3156
3275
|
console.log("");
|
|
3157
|
-
console.log(
|
|
3276
|
+
console.log(chalk8.bold(` ${ko.doctor.driftTitle}`));
|
|
3158
3277
|
const ruleDrift = checkRuleDrift(cwd);
|
|
3159
3278
|
let ruleDrifted = false;
|
|
3160
3279
|
if (!ruleDrift.checked) {
|
|
3161
|
-
console.log(
|
|
3280
|
+
console.log(chalk8.dim(` ${ko.doctor.driftNoRules}`));
|
|
3162
3281
|
} else {
|
|
3163
3282
|
const drifted = ruleDrift.results.filter((r) => r.status === "drifted");
|
|
3164
3283
|
if (drifted.length === 0) {
|
|
3165
|
-
console.log(
|
|
3284
|
+
console.log(chalk8.green(` ${ko.doctor.driftRuleClean}`));
|
|
3166
3285
|
} else {
|
|
3167
|
-
console.log(
|
|
3286
|
+
console.log(chalk8.yellow(` ${ko.doctor.driftRuleWarn(drifted.map((d) => d.path).join(", "))}`));
|
|
3168
3287
|
ruleDrifted = true;
|
|
3169
3288
|
}
|
|
3170
3289
|
}
|
|
3171
3290
|
const ctxDrift = checkContextDrift(cwd);
|
|
3172
3291
|
if (ctxDrift.checked && ctxDrift.stale) {
|
|
3173
|
-
console.log(
|
|
3292
|
+
console.log(chalk8.yellow(` ${ko.doctor.driftContextWarn}`));
|
|
3174
3293
|
}
|
|
3175
3294
|
console.log("");
|
|
3176
|
-
if (
|
|
3177
|
-
console.log(
|
|
3178
|
-
printNextStep({
|
|
3179
|
-
message: ko.doctor.nextOkMessage,
|
|
3180
|
-
command: "vhk \uC2DC\uC791",
|
|
3181
|
-
cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
|
|
3182
|
-
});
|
|
3183
|
-
} else {
|
|
3184
|
-
console.log(chalk9.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
|
|
3295
|
+
if (anyFail) {
|
|
3296
|
+
console.log(chalk8.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
|
|
3185
3297
|
printNextStep({
|
|
3186
3298
|
message: ko.doctor.nextRetryMessage,
|
|
3187
3299
|
command: "vhk doctor",
|
|
3188
3300
|
cursorHint: "\uD658\uACBD \uB2E4\uC2DC \uC810\uAC80\uD574\uC918"
|
|
3189
3301
|
});
|
|
3190
3302
|
process.exitCode = 1;
|
|
3303
|
+
} else if (warnCount > 0) {
|
|
3304
|
+
console.log(chalk8.yellow.bold(` ${ko.doctor.warnSummary(warnCount)}`));
|
|
3305
|
+
printNextStep({
|
|
3306
|
+
message: "\uC704 \uAD8C\uC7A5 \uC870\uCE58 \uD655\uC778 (\uD544\uC218\uB294 \uC544\uB2D8).",
|
|
3307
|
+
command: "vhk doctor",
|
|
3308
|
+
cursorHint: "\uD658\uACBD \uB2E4\uC2DC \uC810\uAC80\uD574\uC918"
|
|
3309
|
+
});
|
|
3310
|
+
} else {
|
|
3311
|
+
console.log(chalk8.green.bold(` ${ko.doctor.allOk}`));
|
|
3312
|
+
printNextStep({
|
|
3313
|
+
message: ko.doctor.nextOkMessage,
|
|
3314
|
+
command: "vhk \uC2DC\uC791",
|
|
3315
|
+
cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
|
|
3316
|
+
});
|
|
3191
3317
|
}
|
|
3192
3318
|
if (opts.strict && ruleDrifted) {
|
|
3193
3319
|
console.log("");
|
|
3194
|
-
console.log(
|
|
3320
|
+
console.log(chalk8.red.bold(" \u274C --strict: \uADDC\uCE59 \uB4DC\uB9AC\uD504\uD2B8 \uBC1C\uACAC \u2192 \uC2E4\uD328 \uCC98\uB9AC (vhk sync \uB85C \uB3D9\uAE30\uD654 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589)"));
|
|
3195
3321
|
process.exitCode = 1;
|
|
3196
3322
|
}
|
|
3197
3323
|
}
|
|
3198
3324
|
|
|
3199
3325
|
// src/commands/ship.ts
|
|
3200
|
-
import
|
|
3326
|
+
import chalk9 from "chalk";
|
|
3201
3327
|
import inquirer4 from "inquirer";
|
|
3202
3328
|
import fs10 from "fs";
|
|
3203
3329
|
import path11 from "path";
|
|
@@ -3238,29 +3364,29 @@ function updateChangelogUnreleased(cwd, version, date) {
|
|
|
3238
3364
|
async function ship() {
|
|
3239
3365
|
if (!ensureNotHardStopped("ship")) return;
|
|
3240
3366
|
if (!ensureInteractive("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\xB7\uD68C\uACE0\uB294 \uB300\uD654\uD615 \uC785\uB825\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. PowerShell \uB4F1 TTY \uC5D0\uC11C \uC2E4\uD589\uD558\uC138\uC694.")) return;
|
|
3241
|
-
console.log(
|
|
3367
|
+
console.log(chalk9.bold(`
|
|
3242
3368
|
${ko.ship.title}
|
|
3243
3369
|
`));
|
|
3244
3370
|
const cwd = process.cwd();
|
|
3245
|
-
console.log(
|
|
3371
|
+
console.log(chalk9.cyan.bold(` ${ko.ship.checklist}
|
|
3246
3372
|
`));
|
|
3247
3373
|
const { passed } = await inquirer4.prompt([{
|
|
3248
3374
|
type: "checkbox",
|
|
3249
3375
|
name: "passed",
|
|
3250
3376
|
message: ko.ship.checkboxPrompt,
|
|
3251
3377
|
choices: CHECKLIST.map((c) => ({
|
|
3252
|
-
name: `${ko.ship[c.questionKey]} ${
|
|
3378
|
+
name: `${ko.ship[c.questionKey]} ${chalk9.dim(`(${ko.ship[c.hintKey]})`)}`,
|
|
3253
3379
|
value: c.id
|
|
3254
3380
|
}))
|
|
3255
3381
|
}]);
|
|
3256
3382
|
const allPassed = passed.length === CHECKLIST.length;
|
|
3257
3383
|
const skipped = CHECKLIST.filter((c) => !passed.includes(c.id));
|
|
3258
3384
|
if (!allPassed) {
|
|
3259
|
-
console.log(
|
|
3385
|
+
console.log(chalk9.yellow(`
|
|
3260
3386
|
${ko.ship.incompleteHeader}`));
|
|
3261
3387
|
skipped.forEach((s) => {
|
|
3262
|
-
console.log(
|
|
3263
|
-
console.log(
|
|
3388
|
+
console.log(chalk9.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
|
|
3389
|
+
console.log(chalk9.dim(` \u2192 ${ko.ship[s.hintKey]}`));
|
|
3264
3390
|
});
|
|
3265
3391
|
const { proceed } = await inquirer4.prompt([{
|
|
3266
3392
|
type: "confirm",
|
|
@@ -3277,13 +3403,13 @@ ${ko.ship.title}
|
|
|
3277
3403
|
return;
|
|
3278
3404
|
}
|
|
3279
3405
|
} else {
|
|
3280
|
-
console.log(
|
|
3406
|
+
console.log(chalk9.green(`
|
|
3281
3407
|
${ko.ship.allPassed}
|
|
3282
3408
|
`));
|
|
3283
3409
|
}
|
|
3284
|
-
console.log(
|
|
3410
|
+
console.log(chalk9.cyan.bold(` ${ko.ship.retro}
|
|
3285
3411
|
`));
|
|
3286
|
-
console.log(
|
|
3412
|
+
console.log(chalk9.dim(` ${ko.ship.versionHint}`));
|
|
3287
3413
|
const retro = await inquirer4.prompt([
|
|
3288
3414
|
{ type: "input", name: "version", message: ko.ship.versionPrompt },
|
|
3289
3415
|
{ type: "input", name: "whatWentWell", message: ko.ship.questionWell },
|
|
@@ -3326,7 +3452,7 @@ ${ko.ship.title}
|
|
|
3326
3452
|
`*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
3327
3453
|
].join("\n");
|
|
3328
3454
|
fs10.writeFileSync(filePath, content, "utf-8");
|
|
3329
|
-
console.log(
|
|
3455
|
+
console.log(chalk9.green(`
|
|
3330
3456
|
${ko.ship.buildLogDone(path11.relative(cwd, filePath))}`));
|
|
3331
3457
|
const changelogResult = updateChangelogUnreleased(cwd, versionSlug, today);
|
|
3332
3458
|
if (changelogResult.status === "updated") {
|
|
@@ -3346,7 +3472,7 @@ ${ko.ship.title}
|
|
|
3346
3472
|
|
|
3347
3473
|
// src/commands/save.ts
|
|
3348
3474
|
import { execFileSync } from "child_process";
|
|
3349
|
-
import
|
|
3475
|
+
import chalk10 from "chalk";
|
|
3350
3476
|
import ora from "ora";
|
|
3351
3477
|
import inquirer5 from "inquirer";
|
|
3352
3478
|
|
|
@@ -3367,36 +3493,36 @@ function formatDefaultCommitMessage(date = /* @__PURE__ */ new Date()) {
|
|
|
3367
3493
|
const min = String(date.getMinutes()).padStart(2, "0");
|
|
3368
3494
|
return `\u2728 vhk save: ${y}-${m}-${d} ${h}:${min}`;
|
|
3369
3495
|
}
|
|
3370
|
-
function
|
|
3496
|
+
function statusIcon2(code) {
|
|
3371
3497
|
if (code.includes("M")) return "\u270F\uFE0F";
|
|
3372
3498
|
if (code.includes("A") || code.includes("?")) return "\u2795";
|
|
3373
3499
|
if (code.includes("D")) return "\u{1F5D1}\uFE0F";
|
|
3374
3500
|
return "\u{1F4C4}";
|
|
3375
3501
|
}
|
|
3376
3502
|
async function save() {
|
|
3377
|
-
console.log(
|
|
3503
|
+
console.log(chalk10.bold(`
|
|
3378
3504
|
\u{1F4BE} ${t("save.title")}`));
|
|
3379
|
-
console.log(
|
|
3505
|
+
console.log(chalk10.gray("\u2500".repeat(40)));
|
|
3380
3506
|
let gitRoot;
|
|
3381
3507
|
try {
|
|
3382
3508
|
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3383
3509
|
gitRoot = getGitRoot();
|
|
3384
3510
|
} catch {
|
|
3385
|
-
console.log(
|
|
3511
|
+
console.log(chalk10.red(`\u274C ${t("save.notGitRepo")}`));
|
|
3386
3512
|
return;
|
|
3387
3513
|
}
|
|
3388
|
-
console.log(
|
|
3514
|
+
console.log(chalk10.cyan(`
|
|
3389
3515
|
\u{1F512} ${t("save.securityWarnHeader")}`));
|
|
3390
3516
|
printSecurityWarnings(gitRoot);
|
|
3391
3517
|
const severe = filterSevereFindings(scanProjectForSecrets(gitRoot).findings);
|
|
3392
3518
|
if (severe.length > 0) {
|
|
3393
|
-
console.log(
|
|
3519
|
+
console.log(chalk10.red(`
|
|
3394
3520
|
\u26A0\uFE0F ${t("save.secretsFound", severe.length)}`));
|
|
3395
3521
|
severe.slice(0, 5).forEach((f) => {
|
|
3396
|
-
console.log(
|
|
3522
|
+
console.log(chalk10.dim(` ${f.file}:${f.line} \u2014 ${f.patternName}`));
|
|
3397
3523
|
});
|
|
3398
3524
|
if (severe.length > 5) {
|
|
3399
|
-
console.log(
|
|
3525
|
+
console.log(chalk10.dim(` ... \uC678 ${severe.length - 5}\uAC74 (vhk \uBCF4\uC548 scan)`));
|
|
3400
3526
|
}
|
|
3401
3527
|
const proceed = await promptOrDefault(
|
|
3402
3528
|
async () => (await inquirer5.prompt([{
|
|
@@ -3409,21 +3535,21 @@ async function save() {
|
|
|
3409
3535
|
// 비대화형 = 시크릿 커밋 안 함 (안전)
|
|
3410
3536
|
);
|
|
3411
3537
|
if (!proceed) {
|
|
3412
|
-
console.log(
|
|
3538
|
+
console.log(chalk10.gray(t("save.cancelled")));
|
|
3413
3539
|
return;
|
|
3414
3540
|
}
|
|
3415
3541
|
}
|
|
3416
3542
|
const lines = parsePorcelainLines(gitOut(["status", "--porcelain"], gitRoot));
|
|
3417
3543
|
if (lines.length === 0) {
|
|
3418
|
-
console.log(
|
|
3544
|
+
console.log(chalk10.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
|
|
3419
3545
|
return;
|
|
3420
3546
|
}
|
|
3421
|
-
console.log(
|
|
3547
|
+
console.log(chalk10.cyan(`
|
|
3422
3548
|
\u{1F4CB} ${t("save.filesHeader", lines.length)}`));
|
|
3423
3549
|
lines.forEach((line) => {
|
|
3424
3550
|
const code = line.substring(0, 2);
|
|
3425
3551
|
const name = line.substring(3);
|
|
3426
|
-
console.log(` ${
|
|
3552
|
+
console.log(` ${statusIcon2(code)} ${name}`);
|
|
3427
3553
|
});
|
|
3428
3554
|
const message = await promptOrDefault(
|
|
3429
3555
|
async () => (await inquirer5.prompt([{
|
|
@@ -3443,21 +3569,21 @@ async function save() {
|
|
|
3443
3569
|
spinner.text = t("save.pushing");
|
|
3444
3570
|
if (!hasGitRemote(gitRoot)) {
|
|
3445
3571
|
spinner.succeed(t("save.successLocal"));
|
|
3446
|
-
console.log(
|
|
3572
|
+
console.log(chalk10.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
|
|
3447
3573
|
} else {
|
|
3448
3574
|
try {
|
|
3449
3575
|
gitRun(["push"], gitRoot);
|
|
3450
3576
|
spinner.succeed(t("save.successWithPush"));
|
|
3451
3577
|
} catch (pushErr) {
|
|
3452
3578
|
spinner.fail(t("save.pushFailed"));
|
|
3453
|
-
console.log(
|
|
3454
|
-
console.log(
|
|
3579
|
+
console.log(chalk10.red(getExecErrorMessage(pushErr)));
|
|
3580
|
+
console.log(chalk10.yellow(`
|
|
3455
3581
|
\u{1F4A1} ${t("save.commitOkPushFailed")}`));
|
|
3456
3582
|
process.exitCode = 1;
|
|
3457
3583
|
}
|
|
3458
3584
|
}
|
|
3459
3585
|
if (process.exitCode !== 1) {
|
|
3460
|
-
console.log(
|
|
3586
|
+
console.log(chalk10.green(`
|
|
3461
3587
|
\u2705 ${t("save.done", lines.length)}`));
|
|
3462
3588
|
printNextStep({
|
|
3463
3589
|
message: t("save.nextOkMessage"),
|
|
@@ -3465,7 +3591,7 @@ async function save() {
|
|
|
3465
3591
|
cursorHint: t("save.nextOkCursor")
|
|
3466
3592
|
});
|
|
3467
3593
|
} else {
|
|
3468
|
-
console.log(
|
|
3594
|
+
console.log(chalk10.green(`
|
|
3469
3595
|
\u2705 ${t("save.doneLocalOnly", lines.length)}`));
|
|
3470
3596
|
printNextStep({
|
|
3471
3597
|
message: t("save.nextPushFailMessage"),
|
|
@@ -3475,12 +3601,12 @@ async function save() {
|
|
|
3475
3601
|
}
|
|
3476
3602
|
} catch (err) {
|
|
3477
3603
|
spinner.fail(t("save.failed"));
|
|
3478
|
-
console.log(
|
|
3604
|
+
console.log(chalk10.red(getExecErrorMessage(err)));
|
|
3479
3605
|
if (didAdd) {
|
|
3480
3606
|
try {
|
|
3481
3607
|
const staged = gitOut(["diff", "--cached", "--stat"], gitRoot).trim();
|
|
3482
3608
|
if (staged) {
|
|
3483
|
-
console.log(
|
|
3609
|
+
console.log(chalk10.yellow(`
|
|
3484
3610
|
\u{1F4A1} ${t("save.stagedAfterFail")}`));
|
|
3485
3611
|
}
|
|
3486
3612
|
} catch {
|
|
@@ -3492,7 +3618,7 @@ async function save() {
|
|
|
3492
3618
|
|
|
3493
3619
|
// src/commands/undo.ts
|
|
3494
3620
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3495
|
-
import
|
|
3621
|
+
import chalk11 from "chalk";
|
|
3496
3622
|
import inquirer6 from "inquirer";
|
|
3497
3623
|
function parseRecentCommits(logOutput) {
|
|
3498
3624
|
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -3516,30 +3642,30 @@ function isUndoRisky(undoCount, unpushedCount, hasRemote) {
|
|
|
3516
3642
|
return false;
|
|
3517
3643
|
}
|
|
3518
3644
|
async function undo() {
|
|
3519
|
-
console.log(
|
|
3645
|
+
console.log(chalk11.bold(`
|
|
3520
3646
|
\u23EA ${t("undo.title")}`));
|
|
3521
|
-
console.log(
|
|
3647
|
+
console.log(chalk11.gray("\u2500".repeat(40)));
|
|
3522
3648
|
let gitRoot;
|
|
3523
3649
|
try {
|
|
3524
3650
|
execFileSync2("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3525
3651
|
gitRoot = getGitRoot();
|
|
3526
3652
|
} catch {
|
|
3527
|
-
console.log(
|
|
3653
|
+
console.log(chalk11.red(`\u274C ${t("undo.notGitRepo")}`));
|
|
3528
3654
|
return;
|
|
3529
3655
|
}
|
|
3530
3656
|
let logOutput;
|
|
3531
3657
|
try {
|
|
3532
3658
|
logOutput = gitOut(["log", "--oneline", "-5"], gitRoot).trim();
|
|
3533
3659
|
} catch {
|
|
3534
|
-
console.log(
|
|
3660
|
+
console.log(chalk11.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
3535
3661
|
return;
|
|
3536
3662
|
}
|
|
3537
3663
|
const commits = parseRecentCommits(logOutput);
|
|
3538
3664
|
if (commits.length === 0) {
|
|
3539
|
-
console.log(
|
|
3665
|
+
console.log(chalk11.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
3540
3666
|
return;
|
|
3541
3667
|
}
|
|
3542
|
-
console.log(
|
|
3668
|
+
console.log(chalk11.cyan(`
|
|
3543
3669
|
${t("undo.recentHeader")}`));
|
|
3544
3670
|
commits.forEach((c, i) => {
|
|
3545
3671
|
console.log(` ${i === 0 ? "\u{1F449}" : " "} ${c}`);
|
|
@@ -3556,7 +3682,7 @@ ${t("undo.recentHeader")}`));
|
|
|
3556
3682
|
const undoCount = Math.min(Math.max(1, count || 1), maxUndo);
|
|
3557
3683
|
const headCount = countLocalCommits(gitRoot);
|
|
3558
3684
|
if (undoCount >= headCount) {
|
|
3559
|
-
console.log(
|
|
3685
|
+
console.log(chalk11.yellow(`
|
|
3560
3686
|
\u{1F4ED} ${t("undo.rootCommit")}`));
|
|
3561
3687
|
return;
|
|
3562
3688
|
}
|
|
@@ -3565,10 +3691,10 @@ ${t("undo.recentHeader")}`));
|
|
|
3565
3691
|
const risky = isUndoRisky(undoCount, unpushed, remote);
|
|
3566
3692
|
if (risky) {
|
|
3567
3693
|
if (unpushed < 0) {
|
|
3568
|
-
console.log(
|
|
3694
|
+
console.log(chalk11.red(`
|
|
3569
3695
|
\u26A0\uFE0F ${t("undo.noUpstreamWarning")}`));
|
|
3570
3696
|
} else {
|
|
3571
|
-
console.log(
|
|
3697
|
+
console.log(chalk11.red(`
|
|
3572
3698
|
\u26A0\uFE0F ${t("undo.alreadyPushed")}`));
|
|
3573
3699
|
}
|
|
3574
3700
|
}
|
|
@@ -3579,16 +3705,16 @@ ${t("undo.recentHeader")}`));
|
|
|
3579
3705
|
default: false
|
|
3580
3706
|
}]);
|
|
3581
3707
|
if (!confirm) {
|
|
3582
|
-
console.log(
|
|
3708
|
+
console.log(chalk11.gray(t("undo.cancelled")));
|
|
3583
3709
|
return;
|
|
3584
3710
|
}
|
|
3585
3711
|
try {
|
|
3586
3712
|
gitRun(["reset", "--soft", `HEAD~${undoCount}`], gitRoot);
|
|
3587
|
-
console.log(
|
|
3713
|
+
console.log(chalk11.green(`
|
|
3588
3714
|
\u2705 ${t("undo.success")}`));
|
|
3589
|
-
console.log(
|
|
3715
|
+
console.log(chalk11.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
|
|
3590
3716
|
if (risky) {
|
|
3591
|
-
console.log(
|
|
3717
|
+
console.log(chalk11.yellow(`
|
|
3592
3718
|
\u{1F4A1} ${t("undo.forcePushHint")}`));
|
|
3593
3719
|
}
|
|
3594
3720
|
printNextStep({
|
|
@@ -3597,35 +3723,35 @@ ${t("undo.recentHeader")}`));
|
|
|
3597
3723
|
cursorHint: t("undo.nextCursor")
|
|
3598
3724
|
});
|
|
3599
3725
|
} catch (err) {
|
|
3600
|
-
console.log(
|
|
3726
|
+
console.log(chalk11.red(`\u274C ${t("undo.failed")}`));
|
|
3601
3727
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3602
|
-
console.log(
|
|
3728
|
+
console.log(chalk11.red(msg));
|
|
3603
3729
|
process.exitCode = 1;
|
|
3604
3730
|
}
|
|
3605
3731
|
}
|
|
3606
3732
|
|
|
3607
3733
|
// src/commands/restore.ts
|
|
3608
|
-
import
|
|
3734
|
+
import chalk12 from "chalk";
|
|
3609
3735
|
import inquirer7 from "inquirer";
|
|
3610
3736
|
async function restore(id) {
|
|
3611
|
-
console.log(
|
|
3737
|
+
console.log(chalk12.bold(`
|
|
3612
3738
|
${ko.restore.title}`));
|
|
3613
|
-
console.log(
|
|
3614
|
-
console.log(
|
|
3739
|
+
console.log(chalk12.gray("\u2500".repeat(40)));
|
|
3740
|
+
console.log(chalk12.dim(` ${ko.restore.notGitNote}`));
|
|
3615
3741
|
const cwd = process.cwd();
|
|
3616
3742
|
const backups = listBackups(cwd);
|
|
3617
3743
|
if (backups.length === 0) {
|
|
3618
|
-
console.log(
|
|
3744
|
+
console.log(chalk12.yellow(`
|
|
3619
3745
|
${ko.restore.noBackups}`));
|
|
3620
3746
|
return;
|
|
3621
3747
|
}
|
|
3622
3748
|
let targetId = id;
|
|
3623
3749
|
if (!targetId) {
|
|
3624
3750
|
if (!process.stdout.isTTY) {
|
|
3625
|
-
console.log(
|
|
3751
|
+
console.log(chalk12.cyan(`
|
|
3626
3752
|
${ko.restore.listHeader}`));
|
|
3627
3753
|
for (const b of backups) console.log(` ${b.id} (${b.files.length}\uAC1C \uD30C\uC77C)`);
|
|
3628
|
-
console.log(
|
|
3754
|
+
console.log(chalk12.yellow(`
|
|
3629
3755
|
${ko.restore.nonTtyHint}`));
|
|
3630
3756
|
return;
|
|
3631
3757
|
}
|
|
@@ -3644,16 +3770,16 @@ ${ko.restore.nonTtyHint}`));
|
|
|
3644
3770
|
}
|
|
3645
3771
|
try {
|
|
3646
3772
|
const restored = restoreBackup(targetId, cwd);
|
|
3647
|
-
console.log(
|
|
3773
|
+
console.log(chalk12.green(`
|
|
3648
3774
|
${ko.restore.restored(restored.length, targetId)}`));
|
|
3649
|
-
for (const r of restored) console.log(
|
|
3775
|
+
for (const r of restored) console.log(chalk12.gray(` ${r}`));
|
|
3650
3776
|
printNextStep({
|
|
3651
3777
|
message: "\uBC31\uC5C5 \uBCF5\uC6D0 \uC644\uB8CC! \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694.",
|
|
3652
3778
|
command: "vhk diff",
|
|
3653
3779
|
cursorHint: "\uBCC0\uACBD\uC0AC\uD56D \uBCF4\uC5EC\uC918"
|
|
3654
3780
|
});
|
|
3655
3781
|
} catch {
|
|
3656
|
-
console.log(
|
|
3782
|
+
console.log(chalk12.red(`
|
|
3657
3783
|
${ko.restore.notFound(targetId)}`));
|
|
3658
3784
|
process.exitCode = 1;
|
|
3659
3785
|
}
|
|
@@ -3663,7 +3789,7 @@ ${ko.restore.notFound(targetId)}`));
|
|
|
3663
3789
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3664
3790
|
import fs11 from "fs";
|
|
3665
3791
|
import path12 from "path";
|
|
3666
|
-
import
|
|
3792
|
+
import chalk13 from "chalk";
|
|
3667
3793
|
function countFileChanges(porcelain) {
|
|
3668
3794
|
const lines = porcelain.split("\n").filter(Boolean);
|
|
3669
3795
|
let staged = 0;
|
|
@@ -3738,15 +3864,15 @@ function getSyncCounts(gitRoot) {
|
|
|
3738
3864
|
}
|
|
3739
3865
|
}
|
|
3740
3866
|
async function status() {
|
|
3741
|
-
console.log(
|
|
3867
|
+
console.log(chalk13.bold(`
|
|
3742
3868
|
\u{1F4CA} ${t("status.title")}`));
|
|
3743
|
-
console.log(
|
|
3869
|
+
console.log(chalk13.gray("\u2500".repeat(40)));
|
|
3744
3870
|
let gitRoot;
|
|
3745
3871
|
try {
|
|
3746
3872
|
execFileSync3("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3747
3873
|
gitRoot = getGitRoot();
|
|
3748
3874
|
} catch {
|
|
3749
|
-
console.log(
|
|
3875
|
+
console.log(chalk13.red(`\u274C ${t("status.notGitRepo")}`));
|
|
3750
3876
|
return;
|
|
3751
3877
|
}
|
|
3752
3878
|
let branch;
|
|
@@ -3765,29 +3891,29 @@ async function status() {
|
|
|
3765
3891
|
commits = [];
|
|
3766
3892
|
}
|
|
3767
3893
|
const pkg = readProjectPackage();
|
|
3768
|
-
console.log(
|
|
3769
|
-
\u{1F33F} ${t("status.branch")}`) +
|
|
3894
|
+
console.log(chalk13.cyan(`
|
|
3895
|
+
\u{1F33F} ${t("status.branch")}`) + chalk13.white(` ${branch}`));
|
|
3770
3896
|
console.log(
|
|
3771
|
-
|
|
3897
|
+
chalk13.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk13.white(
|
|
3772
3898
|
` staged ${counts.staged} \xB7 unstaged ${counts.unstaged} \xB7 untracked ${counts.untracked}`
|
|
3773
3899
|
)
|
|
3774
3900
|
);
|
|
3775
|
-
console.log(
|
|
3901
|
+
console.log(chalk13.cyan(`
|
|
3776
3902
|
\u{1F4CB} ${t("status.recentCommits", commits.length)}`));
|
|
3777
3903
|
if (commits.length === 0) {
|
|
3778
|
-
console.log(
|
|
3904
|
+
console.log(chalk13.dim(` ${t("status.noCommits")}`));
|
|
3779
3905
|
} else {
|
|
3780
|
-
commits.forEach((c) => console.log(` ${
|
|
3906
|
+
commits.forEach((c) => console.log(` ${chalk13.dim("\u2022")} ${c}`));
|
|
3781
3907
|
}
|
|
3782
3908
|
console.log(
|
|
3783
|
-
|
|
3784
|
-
\u{1F504} ${t("status.remote")}`) +
|
|
3909
|
+
chalk13.cyan(`
|
|
3910
|
+
\u{1F504} ${t("status.remote")}`) + chalk13.white(` ${formatSyncLabel(sync2)}`)
|
|
3785
3911
|
);
|
|
3786
|
-
console.log(
|
|
3912
|
+
console.log(chalk13.gray("\n" + "\u2500".repeat(40)));
|
|
3787
3913
|
if (pkg) {
|
|
3788
|
-
console.log(
|
|
3914
|
+
console.log(chalk13.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk13.white(` ${pkg.name} v${pkg.version}`));
|
|
3789
3915
|
} else {
|
|
3790
|
-
console.log(
|
|
3916
|
+
console.log(chalk13.dim(`\u{1F4E6} ${t("status.noPackage")}`));
|
|
3791
3917
|
}
|
|
3792
3918
|
const hasChanges = counts.staged + counts.unstaged + counts.untracked > 0;
|
|
3793
3919
|
printNextStep(selectStatusNextStep(hasChanges));
|
|
@@ -3795,7 +3921,7 @@ async function status() {
|
|
|
3795
3921
|
}
|
|
3796
3922
|
|
|
3797
3923
|
// src/commands/diff.ts
|
|
3798
|
-
import
|
|
3924
|
+
import chalk14 from "chalk";
|
|
3799
3925
|
function gitOut2(args) {
|
|
3800
3926
|
const r = safeExecFile("git", args);
|
|
3801
3927
|
return r.ok ? r.out : "";
|
|
@@ -3832,67 +3958,67 @@ function summarizeNumstat(numstat) {
|
|
|
3832
3958
|
return { fileCount, totalAdd, totalDel };
|
|
3833
3959
|
}
|
|
3834
3960
|
function printFile(f) {
|
|
3835
|
-
const adds = f.additions > 0 ?
|
|
3836
|
-
const dels = f.deletions > 0 ?
|
|
3961
|
+
const adds = f.additions > 0 ? chalk14.green(`+${f.additions}`) : "";
|
|
3962
|
+
const dels = f.deletions > 0 ? chalk14.red(`-${f.deletions}`) : "";
|
|
3837
3963
|
const change = [adds, dels].filter(Boolean).join(" ");
|
|
3838
3964
|
console.log(` ${f.name} ${change}`);
|
|
3839
3965
|
}
|
|
3840
3966
|
async function diff() {
|
|
3841
|
-
console.log(
|
|
3967
|
+
console.log(chalk14.bold(`
|
|
3842
3968
|
\u{1F50D} ${t("diff.title")}`));
|
|
3843
|
-
console.log(
|
|
3969
|
+
console.log(chalk14.gray("\u2500".repeat(40)));
|
|
3844
3970
|
if (!safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok) {
|
|
3845
|
-
console.log(
|
|
3971
|
+
console.log(chalk14.red(`\u274C ${t("diff.notGitRepo")}`));
|
|
3846
3972
|
return;
|
|
3847
3973
|
}
|
|
3848
3974
|
const unstaged = gitOut2(["diff", "--stat"]);
|
|
3849
3975
|
const staged = gitOut2(["diff", "--cached", "--stat"]);
|
|
3850
3976
|
const untracked = gitOut2(["ls-files", "--others", "--exclude-standard"]);
|
|
3851
3977
|
if (!unstaged && !staged && !untracked) {
|
|
3852
|
-
console.log(
|
|
3978
|
+
console.log(chalk14.green(`
|
|
3853
3979
|
\u2705 ${t("diff.noChanges")}`));
|
|
3854
3980
|
return;
|
|
3855
3981
|
}
|
|
3856
3982
|
if (staged) {
|
|
3857
|
-
console.log(
|
|
3983
|
+
console.log(chalk14.cyan(`
|
|
3858
3984
|
${t("diff.stagedHeader")}`));
|
|
3859
3985
|
parseDiffStat(staged).forEach((f) => printFile(f));
|
|
3860
3986
|
}
|
|
3861
3987
|
if (unstaged) {
|
|
3862
|
-
console.log(
|
|
3988
|
+
console.log(chalk14.cyan(`
|
|
3863
3989
|
${t("diff.unstagedHeader")}`));
|
|
3864
3990
|
parseDiffStat(unstaged).forEach((f) => printFile(f));
|
|
3865
3991
|
}
|
|
3866
3992
|
if (untracked) {
|
|
3867
3993
|
const files = untracked.split("\n").filter(Boolean);
|
|
3868
|
-
console.log(
|
|
3994
|
+
console.log(chalk14.cyan(`
|
|
3869
3995
|
${t("diff.untrackedHeader", files.length)}`));
|
|
3870
|
-
files.forEach((f) => console.log(` ${
|
|
3996
|
+
files.forEach((f) => console.log(` ${chalk14.green("+")} ${f}`));
|
|
3871
3997
|
}
|
|
3872
3998
|
const numstat = gitOut2(["diff", "--numstat", "HEAD"]);
|
|
3873
3999
|
if (numstat) {
|
|
3874
4000
|
const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
|
|
3875
|
-
console.log(
|
|
4001
|
+
console.log(chalk14.cyan(`
|
|
3876
4002
|
${t("diff.summaryHeader")}`));
|
|
3877
4003
|
console.log(` ${t("diff.filesLine", fileCount)}`);
|
|
3878
|
-
console.log(` \uCD94\uAC00: ${
|
|
3879
|
-
console.log(` \uC0AD\uC81C: ${
|
|
4004
|
+
console.log(` \uCD94\uAC00: ${chalk14.green(`+${totalAdd}`)}\uC904`);
|
|
4005
|
+
console.log(` \uC0AD\uC81C: ${chalk14.red(`-${totalDel}`)}\uC904`);
|
|
3880
4006
|
}
|
|
3881
4007
|
console.log("");
|
|
3882
4008
|
}
|
|
3883
4009
|
|
|
3884
4010
|
// src/commands/mcp-init.ts
|
|
3885
|
-
import { existsSync as
|
|
3886
|
-
import { join as
|
|
4011
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4012
|
+
import { join as join4, dirname } from "path";
|
|
3887
4013
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3888
|
-
import
|
|
4014
|
+
import chalk15 from "chalk";
|
|
3889
4015
|
function resolveMcpEntryPoint() {
|
|
3890
4016
|
try {
|
|
3891
4017
|
const here = fileURLToPath2(import.meta.url);
|
|
3892
4018
|
const dir = dirname(here);
|
|
3893
4019
|
for (const rel of [["mcp", "index.js"], ["..", "mcp", "index.js"]]) {
|
|
3894
|
-
const candidate =
|
|
3895
|
-
if (
|
|
4020
|
+
const candidate = join4(dir, ...rel);
|
|
4021
|
+
if (existsSync3(candidate)) return candidate;
|
|
3896
4022
|
}
|
|
3897
4023
|
} catch {
|
|
3898
4024
|
}
|
|
@@ -3900,17 +4026,17 @@ function resolveMcpEntryPoint() {
|
|
|
3900
4026
|
const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
|
|
3901
4027
|
if (typeof url === "string") {
|
|
3902
4028
|
const p = fileURLToPath2(url);
|
|
3903
|
-
if (
|
|
4029
|
+
if (existsSync3(p)) return p;
|
|
3904
4030
|
}
|
|
3905
4031
|
} catch {
|
|
3906
4032
|
}
|
|
3907
4033
|
try {
|
|
3908
|
-
const pkgPath =
|
|
3909
|
-
if (
|
|
4034
|
+
const pkgPath = join4(process.cwd(), "package.json");
|
|
4035
|
+
if (existsSync3(pkgPath)) {
|
|
3910
4036
|
const pkg = readJsonFile(pkgPath);
|
|
3911
4037
|
if (pkg.name === "@byh3071/vhk") {
|
|
3912
|
-
const local =
|
|
3913
|
-
if (
|
|
4038
|
+
const local = join4(process.cwd(), "dist", "mcp", "index.js");
|
|
4039
|
+
if (existsSync3(local)) return local;
|
|
3914
4040
|
}
|
|
3915
4041
|
}
|
|
3916
4042
|
} catch {
|
|
@@ -3925,31 +4051,31 @@ function resolveVhkMcpEntry() {
|
|
|
3925
4051
|
return { command: "vhk-mcp", args: [] };
|
|
3926
4052
|
}
|
|
3927
4053
|
async function mcpInit() {
|
|
3928
|
-
console.log(
|
|
3929
|
-
console.log(
|
|
3930
|
-
const cursorDir =
|
|
3931
|
-
if (!
|
|
3932
|
-
|
|
4054
|
+
console.log(chalk15.bold("\n\u{1F50C} " + t("mcp.initTitle")));
|
|
4055
|
+
console.log(chalk15.gray("\u2500".repeat(40)));
|
|
4056
|
+
const cursorDir = join4(process.cwd(), ".cursor");
|
|
4057
|
+
if (!existsSync3(cursorDir)) {
|
|
4058
|
+
mkdirSync2(cursorDir, { recursive: true });
|
|
3933
4059
|
}
|
|
3934
|
-
const configPath =
|
|
4060
|
+
const configPath = join4(cursorDir, "mcp.json");
|
|
3935
4061
|
const vhkEntry = resolveVhkMcpEntry();
|
|
3936
4062
|
let config;
|
|
3937
|
-
if (
|
|
4063
|
+
if (existsSync3(configPath)) {
|
|
3938
4064
|
try {
|
|
3939
4065
|
const parsed = readJsonFile(configPath);
|
|
3940
4066
|
config = {
|
|
3941
4067
|
mcpServers: { ...parsed.mcpServers ?? {}, vhk: vhkEntry }
|
|
3942
4068
|
};
|
|
3943
4069
|
} catch {
|
|
3944
|
-
console.log(
|
|
4070
|
+
console.log(chalk15.yellow("\u26A0\uFE0F \uAE30\uC874 .cursor/mcp.json \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uC0C8 \uD30C\uC77C\uB85C \uB36E\uC5B4\uC501\uB2C8\uB2E4."));
|
|
3945
4071
|
config = { mcpServers: { vhk: vhkEntry } };
|
|
3946
4072
|
}
|
|
3947
4073
|
} else {
|
|
3948
4074
|
config = { mcpServers: { vhk: vhkEntry } };
|
|
3949
4075
|
}
|
|
3950
|
-
|
|
3951
|
-
console.log(
|
|
3952
|
-
console.log(
|
|
4076
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4077
|
+
console.log(chalk15.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
|
|
4078
|
+
console.log(chalk15.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
|
|
3953
4079
|
console.log(` ${configPath}`);
|
|
3954
4080
|
printNextStep({
|
|
3955
4081
|
message: t("mcp.nextMessage"),
|
|
@@ -3959,8 +4085,8 @@ async function mcpInit() {
|
|
|
3959
4085
|
}
|
|
3960
4086
|
|
|
3961
4087
|
// src/commands/design.ts
|
|
3962
|
-
import { existsSync as
|
|
3963
|
-
import
|
|
4088
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4089
|
+
import chalk16 from "chalk";
|
|
3964
4090
|
import inquirer8 from "inquirer";
|
|
3965
4091
|
var PALETTES = [
|
|
3966
4092
|
{
|
|
@@ -4013,7 +4139,7 @@ var PALETTES = [
|
|
|
4013
4139
|
}
|
|
4014
4140
|
];
|
|
4015
4141
|
function hasTailwind() {
|
|
4016
|
-
return
|
|
4142
|
+
return existsSync4("tailwind.config.js") || existsSync4("tailwind.config.ts") || existsSync4("tailwind.config.mjs") || existsSync4("tailwind.config.cjs");
|
|
4017
4143
|
}
|
|
4018
4144
|
function isTailwindV4Deps(deps) {
|
|
4019
4145
|
if (deps["@tailwindcss/vite"] || deps["@tailwindcss/postcss"]) return true;
|
|
@@ -4063,8 +4189,9 @@ export default vhkColors
|
|
|
4063
4189
|
`;
|
|
4064
4190
|
}
|
|
4065
4191
|
async function design() {
|
|
4066
|
-
|
|
4067
|
-
console.log(
|
|
4192
|
+
if (!ensureNotHardStopped("design")) return;
|
|
4193
|
+
console.log(chalk16.bold("\n\u{1F3A8} " + t("design.title")));
|
|
4194
|
+
console.log(chalk16.gray("\u2500".repeat(40)));
|
|
4068
4195
|
if (!ensureInteractive("\uCEEC\uB7EC \uD314\uB808\uD2B8 \uC120\uD0DD\uC740 \uB300\uD654\uD615\uC73C\uB85C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4.")) return;
|
|
4069
4196
|
const { paletteIndex } = await inquirer8.prompt([
|
|
4070
4197
|
{
|
|
@@ -4078,12 +4205,12 @@ async function design() {
|
|
|
4078
4205
|
}
|
|
4079
4206
|
]);
|
|
4080
4207
|
const palette = PALETTES[paletteIndex];
|
|
4081
|
-
console.log(
|
|
4208
|
+
console.log(chalk16.cyan(`
|
|
4082
4209
|
\u{1F3A8} \uC120\uD0DD\uB41C \uD314\uB808\uD2B8: ${palette.name}`));
|
|
4083
4210
|
const v4 = hasTailwindV4();
|
|
4084
4211
|
const targetPath = v4 ? "src/styles/theme.css" : hasTailwind() ? "src/styles/vhk-colors.ts" : "src/styles/tokens.css";
|
|
4085
4212
|
const content = v4 ? generateTailwindV4Theme(palette) : hasTailwind() ? generateTailwindExtend(palette) : generateCSSTokens(palette);
|
|
4086
|
-
if (
|
|
4213
|
+
if (existsSync4(targetPath)) {
|
|
4087
4214
|
const { overwrite } = await inquirer8.prompt([{
|
|
4088
4215
|
type: "confirm",
|
|
4089
4216
|
name: "overwrite",
|
|
@@ -4091,24 +4218,24 @@ async function design() {
|
|
|
4091
4218
|
default: false
|
|
4092
4219
|
}]);
|
|
4093
4220
|
if (!overwrite) {
|
|
4094
|
-
console.log(
|
|
4221
|
+
console.log(chalk16.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
|
|
4095
4222
|
return;
|
|
4096
4223
|
}
|
|
4097
4224
|
}
|
|
4098
|
-
|
|
4099
|
-
|
|
4225
|
+
mkdirSync3("src/styles", { recursive: true });
|
|
4226
|
+
writeFileSync3(targetPath, content, "utf-8");
|
|
4100
4227
|
if (v4) {
|
|
4101
|
-
console.log(
|
|
4102
|
-
console.log(
|
|
4103
|
-
console.log(
|
|
4228
|
+
console.log(chalk16.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (Tailwind v4 @theme)"));
|
|
4229
|
+
console.log(chalk16.gray(' \uC9C4\uC785 CSS(\uC608: src/index.css)\uC5D0 `@import "./styles/theme.css";` \uCD94\uAC00 \u2192 bg-primary \uB4F1 \uC720\uD2F8 \uC0AC\uC6A9.'));
|
|
4230
|
+
console.log(chalk16.gray(" \uB2E4\uD06C \uD1A0\uAE00: \uB8E8\uD2B8 <html>/<body> \uC5D0 `.dark` \uD074\uB798\uC2A4 on/off."));
|
|
4104
4231
|
} else if (hasTailwind()) {
|
|
4105
|
-
console.log(
|
|
4106
|
-
console.log(
|
|
4232
|
+
console.log(chalk16.green("\n\u2705 src/styles/vhk-colors.ts \uC0DD\uC131"));
|
|
4233
|
+
console.log(chalk16.gray(" tailwind.config\uC758 extend.colors\uC5D0 import \uD574\uC11C \uC0AC\uC6A9\uD558\uC138\uC694."));
|
|
4107
4234
|
} else {
|
|
4108
|
-
console.log(
|
|
4109
|
-
console.log(
|
|
4235
|
+
console.log(chalk16.green("\n\u2705 src/styles/tokens.css \uC0DD\uC131"));
|
|
4236
|
+
console.log(chalk16.gray(" HTML\uC5D0 <link>\uB85C \uCD94\uAC00\uD558\uAC70\uB098 CSS\uC5D0\uC11C @import \uD558\uC138\uC694."));
|
|
4110
4237
|
}
|
|
4111
|
-
console.log(
|
|
4238
|
+
console.log(chalk16.bold("\n\u{1F308} \uCEEC\uB7EC \uBBF8\uB9AC\uBCF4\uAE30:"));
|
|
4112
4239
|
for (const [key, value] of Object.entries(palette.colors)) {
|
|
4113
4240
|
console.log(` ${key.padEnd(12)} ${value}`);
|
|
4114
4241
|
}
|
|
@@ -4123,8 +4250,8 @@ async function designPalette() {
|
|
|
4123
4250
|
}
|
|
4124
4251
|
|
|
4125
4252
|
// src/commands/theme.ts
|
|
4126
|
-
import { existsSync as
|
|
4127
|
-
import
|
|
4253
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
4254
|
+
import chalk17 from "chalk";
|
|
4128
4255
|
import inquirer9 from "inquirer";
|
|
4129
4256
|
function generateDarkCSS() {
|
|
4130
4257
|
return `/* vhk theme \u2014 \uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC CSS \uBCC0\uC218 */
|
|
@@ -4181,11 +4308,12 @@ export function initTheme(): void {
|
|
|
4181
4308
|
`;
|
|
4182
4309
|
}
|
|
4183
4310
|
async function theme(options) {
|
|
4184
|
-
|
|
4185
|
-
console.log(
|
|
4311
|
+
if (!ensureNotHardStopped("theme")) return;
|
|
4312
|
+
console.log(chalk17.bold("\n\u{1F319} " + t("theme.title")));
|
|
4313
|
+
console.log(chalk17.gray("\u2500".repeat(40)));
|
|
4186
4314
|
const cssPath = "src/styles/theme.css";
|
|
4187
4315
|
const togglePath = "src/lib/theme-toggle.ts";
|
|
4188
|
-
const conflicts = [cssPath, togglePath].filter((p) =>
|
|
4316
|
+
const conflicts = [cssPath, togglePath].filter((p) => existsSync5(p));
|
|
4189
4317
|
if (conflicts.length > 0) {
|
|
4190
4318
|
const overwrite = options?.yes === true ? true : await promptOrDefault(
|
|
4191
4319
|
async () => (await inquirer9.prompt([{
|
|
@@ -4198,21 +4326,21 @@ async function theme(options) {
|
|
|
4198
4326
|
false
|
|
4199
4327
|
);
|
|
4200
4328
|
if (!overwrite) {
|
|
4201
|
-
console.log(
|
|
4329
|
+
console.log(chalk17.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0. (\uBE44\uB300\uD654\uD615\uC774\uBA74 --yes \uB85C \uB36E\uC5B4\uC4F0\uAE30)"));
|
|
4202
4330
|
return;
|
|
4203
4331
|
}
|
|
4204
4332
|
}
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
console.log(
|
|
4209
|
-
|
|
4210
|
-
console.log(
|
|
4211
|
-
console.log(
|
|
4212
|
-
console.log(
|
|
4213
|
-
console.log(
|
|
4214
|
-
console.log(
|
|
4215
|
-
console.log(
|
|
4333
|
+
mkdirSync4("src/styles", { recursive: true });
|
|
4334
|
+
mkdirSync4("src/lib", { recursive: true });
|
|
4335
|
+
writeFileSync4(cssPath, generateDarkCSS(), "utf-8");
|
|
4336
|
+
console.log(chalk17.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"));
|
|
4337
|
+
writeFileSync4(togglePath, generateToggleUtil(), "utf-8");
|
|
4338
|
+
console.log(chalk17.green("\u2705 src/lib/theme-toggle.ts \uC0DD\uC131 (\uD1A0\uAE00 \uC720\uD2F8\uB9AC\uD2F0)"));
|
|
4339
|
+
console.log(chalk17.bold("\n\u{1F4D6} \uC0AC\uC6A9\uBC95:"));
|
|
4340
|
+
console.log(chalk17.gray(" 1. theme.css\uB97C \uAE00\uB85C\uBC8C \uC2A4\uD0C0\uC77C\uC5D0 \uCD94\uAC00"));
|
|
4341
|
+
console.log(chalk17.gray(' 2. import { initTheme, toggleTheme } from "./lib/theme-toggle"'));
|
|
4342
|
+
console.log(chalk17.gray(" 3. \uC571 \uC9C4\uC785\uC810\uC5D0\uC11C initTheme() \uD638\uCD9C"));
|
|
4343
|
+
console.log(chalk17.gray(" 4. \uD1A0\uAE00 \uBC84\uD2BC\uC5D0\uC11C toggleTheme() \uD638\uCD9C"));
|
|
4216
4344
|
printNextStep({
|
|
4217
4345
|
message: "\uD14C\uB9C8 \uC124\uC815 \uC644\uB8CC!",
|
|
4218
4346
|
command: "vhk ref list",
|
|
@@ -4221,11 +4349,11 @@ async function theme(options) {
|
|
|
4221
4349
|
}
|
|
4222
4350
|
|
|
4223
4351
|
// src/commands/ref.ts
|
|
4224
|
-
import { existsSync as
|
|
4225
|
-
import
|
|
4352
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
|
|
4353
|
+
import chalk18 from "chalk";
|
|
4226
4354
|
var REFS_PATH = ".vhk/refs.json";
|
|
4227
4355
|
function loadRefs() {
|
|
4228
|
-
if (!
|
|
4356
|
+
if (!existsSync6(REFS_PATH)) return [];
|
|
4229
4357
|
try {
|
|
4230
4358
|
const parsed = readJsonFile(REFS_PATH);
|
|
4231
4359
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -4234,28 +4362,29 @@ function loadRefs() {
|
|
|
4234
4362
|
}
|
|
4235
4363
|
}
|
|
4236
4364
|
function saveRefs(refs) {
|
|
4237
|
-
|
|
4238
|
-
|
|
4365
|
+
mkdirSync5(".vhk", { recursive: true });
|
|
4366
|
+
atomicWriteFile(REFS_PATH, JSON.stringify(refs, null, 2) + "\n");
|
|
4239
4367
|
}
|
|
4240
4368
|
async function refAdd(url, memo = "") {
|
|
4241
|
-
|
|
4242
|
-
console.log(
|
|
4369
|
+
if (!ensureNotHardStopped("ref add")) return;
|
|
4370
|
+
console.log(chalk18.bold("\n\u{1F517} " + t("ref.addTitle")));
|
|
4371
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
4243
4372
|
if (!url) {
|
|
4244
|
-
console.log(
|
|
4245
|
-
console.log(
|
|
4373
|
+
console.log(chalk18.red("\u274C URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
|
|
4374
|
+
console.log(chalk18.gray(' \uC608: vhk ref add https://example.com --memo "\uCC38\uACE0 \uC0AC\uC774\uD2B8"'));
|
|
4246
4375
|
return;
|
|
4247
4376
|
}
|
|
4248
4377
|
const refs = loadRefs();
|
|
4249
4378
|
if (refs.some((r) => r.url === url)) {
|
|
4250
|
-
console.log(
|
|
4379
|
+
console.log(chalk18.yellow("\u26A0\uFE0F \uC774\uBBF8 \uC800\uC7A5\uB41C URL\uC785\uB2C8\uB2E4."));
|
|
4251
4380
|
return;
|
|
4252
4381
|
}
|
|
4253
4382
|
refs.push({ url, memo, addedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4254
4383
|
saveRefs(refs);
|
|
4255
|
-
console.log(
|
|
4384
|
+
console.log(chalk18.green(`
|
|
4256
4385
|
\u2705 \uB808\uD37C\uB7F0\uC2A4 \uCD94\uAC00\uB428 (#${refs.length})`));
|
|
4257
|
-
console.log(
|
|
4258
|
-
if (memo) console.log(
|
|
4386
|
+
console.log(chalk18.cyan(` ${url}`));
|
|
4387
|
+
if (memo) console.log(chalk18.gray(` \u{1F4DD} ${memo}`));
|
|
4259
4388
|
printNextStep({
|
|
4260
4389
|
message: "\uB808\uD37C\uB7F0\uC2A4 \uC800\uC7A5 \uC644\uB8CC!",
|
|
4261
4390
|
command: "vhk ref list",
|
|
@@ -4263,22 +4392,22 @@ async function refAdd(url, memo = "") {
|
|
|
4263
4392
|
});
|
|
4264
4393
|
}
|
|
4265
4394
|
async function refList() {
|
|
4266
|
-
console.log(
|
|
4267
|
-
console.log(
|
|
4395
|
+
console.log(chalk18.bold("\n\u{1F4DA} " + t("ref.listTitle")));
|
|
4396
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
4268
4397
|
const refs = loadRefs();
|
|
4269
4398
|
if (refs.length === 0) {
|
|
4270
|
-
console.log(
|
|
4271
|
-
console.log(
|
|
4399
|
+
console.log(chalk18.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uB808\uD37C\uB7F0\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4400
|
+
console.log(chalk18.gray(' vhk ref add <url> --memo "\uBA54\uBAA8"\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
|
|
4272
4401
|
return;
|
|
4273
4402
|
}
|
|
4274
|
-
console.log(
|
|
4403
|
+
console.log(chalk18.cyan(`
|
|
4275
4404
|
\uCD1D ${refs.length}\uAC1C\uC758 \uB808\uD37C\uB7F0\uC2A4:
|
|
4276
4405
|
`));
|
|
4277
4406
|
refs.forEach((ref, index) => {
|
|
4278
4407
|
const date = new Date(ref.addedAt).toLocaleDateString("ko-KR");
|
|
4279
|
-
console.log(
|
|
4280
|
-
if (ref.memo) console.log(
|
|
4281
|
-
console.log(
|
|
4408
|
+
console.log(chalk18.white(` [${index + 1}] ${ref.url}`));
|
|
4409
|
+
if (ref.memo) console.log(chalk18.gray(` \u{1F4DD} ${ref.memo}`));
|
|
4410
|
+
console.log(chalk18.gray(` \u{1F4C5} ${date}`));
|
|
4282
4411
|
console.log("");
|
|
4283
4412
|
});
|
|
4284
4413
|
}
|
|
@@ -4286,7 +4415,7 @@ async function refOpen(indexStr) {
|
|
|
4286
4415
|
const refs = loadRefs();
|
|
4287
4416
|
const idx = parseInt(indexStr, 10) - 1;
|
|
4288
4417
|
if (Number.isNaN(idx) || idx < 0 || idx >= refs.length) {
|
|
4289
|
-
console.log(
|
|
4418
|
+
console.log(chalk18.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${refs.length || 0})`));
|
|
4290
4419
|
return;
|
|
4291
4420
|
}
|
|
4292
4421
|
const ref = refs[idx];
|
|
@@ -4294,14 +4423,14 @@ async function refOpen(indexStr) {
|
|
|
4294
4423
|
try {
|
|
4295
4424
|
parsed = new URL(ref.url);
|
|
4296
4425
|
} catch {
|
|
4297
|
-
console.log(
|
|
4426
|
+
console.log(chalk18.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 URL: ${ref.url}`));
|
|
4298
4427
|
return;
|
|
4299
4428
|
}
|
|
4300
4429
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
4301
|
-
console.log(
|
|
4430
|
+
console.log(chalk18.red(`\u274C http(s) URL\uB9CC \uC5F4 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (${parsed.protocol})`));
|
|
4302
4431
|
return;
|
|
4303
4432
|
}
|
|
4304
|
-
console.log(
|
|
4433
|
+
console.log(chalk18.cyan(`
|
|
4305
4434
|
\u{1F310} \uC5F4\uAE30: ${ref.url}`));
|
|
4306
4435
|
let result;
|
|
4307
4436
|
if (process.platform === "darwin") {
|
|
@@ -4312,19 +4441,19 @@ async function refOpen(indexStr) {
|
|
|
4312
4441
|
result = safeExecFile("xdg-open", [ref.url]);
|
|
4313
4442
|
}
|
|
4314
4443
|
if (result.ok) {
|
|
4315
|
-
console.log(
|
|
4444
|
+
console.log(chalk18.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
4316
4445
|
} else {
|
|
4317
|
-
console.log(
|
|
4446
|
+
console.log(chalk18.yellow("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. URL\uC744 \uC9C1\uC811 \uBC29\uBB38\uD574\uC8FC\uC138\uC694."));
|
|
4318
4447
|
}
|
|
4319
4448
|
}
|
|
4320
4449
|
|
|
4321
4450
|
// src/commands/harness.ts
|
|
4322
|
-
import { existsSync as
|
|
4323
|
-
import
|
|
4451
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4452
|
+
import chalk19 from "chalk";
|
|
4324
4453
|
import ora2 from "ora";
|
|
4325
4454
|
function detectPM() {
|
|
4326
|
-
if (
|
|
4327
|
-
if (
|
|
4455
|
+
if (existsSync7("pnpm-lock.yaml")) return "pnpm";
|
|
4456
|
+
if (existsSync7("yarn.lock")) return "yarn";
|
|
4328
4457
|
return "npm";
|
|
4329
4458
|
}
|
|
4330
4459
|
function pmRun(pm, script) {
|
|
@@ -4342,14 +4471,14 @@ function detectChecks() {
|
|
|
4342
4471
|
const pm = detectPM();
|
|
4343
4472
|
if (s.lint) {
|
|
4344
4473
|
checks.push({ name: "lint", bin: pm, args: pmRun(pm, "lint") });
|
|
4345
|
-
} else if (
|
|
4474
|
+
} else if (existsSync7(".eslintrc.js") || existsSync7(".eslintrc.json") || existsSync7("eslint.config.js")) {
|
|
4346
4475
|
checks.push({ name: "lint", bin: "npx", args: ["eslint", ".", "--ext", ".ts,.tsx"] });
|
|
4347
4476
|
}
|
|
4348
4477
|
if (s["type-check"]) {
|
|
4349
4478
|
checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "type-check") });
|
|
4350
4479
|
} else if (s.typecheck) {
|
|
4351
4480
|
checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "typecheck") });
|
|
4352
|
-
} else if (
|
|
4481
|
+
} else if (existsSync7("tsconfig.json")) {
|
|
4353
4482
|
checks.push({ name: "type-check", bin: "npx", args: ["tsc", "--noEmit"] });
|
|
4354
4483
|
}
|
|
4355
4484
|
if (s.test) {
|
|
@@ -4362,32 +4491,32 @@ function detectChecks() {
|
|
|
4362
4491
|
}
|
|
4363
4492
|
async function harness() {
|
|
4364
4493
|
if (!ensureNotHardStopped("harness")) return;
|
|
4365
|
-
console.log(
|
|
4366
|
-
console.log(
|
|
4494
|
+
console.log(chalk19.bold("\n\u{1F527} " + t("harness.title")));
|
|
4495
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
4367
4496
|
const checks = detectChecks();
|
|
4368
4497
|
if (checks.length === 0) {
|
|
4369
|
-
console.log(
|
|
4370
|
-
console.log(
|
|
4498
|
+
console.log(chalk19.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4499
|
+
console.log(chalk19.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
|
|
4371
4500
|
return;
|
|
4372
4501
|
}
|
|
4373
|
-
console.log(
|
|
4502
|
+
console.log(chalk19.cyan(`
|
|
4374
4503
|
\u{1F3C3} ${checks.length}\uAC1C \uC810\uAC80 \uC2DC\uC791:
|
|
4375
4504
|
`));
|
|
4376
4505
|
const results = [];
|
|
4377
|
-
for (const
|
|
4378
|
-
const display = `${
|
|
4379
|
-
const spinner = ora2(`${
|
|
4506
|
+
for (const check3 of checks) {
|
|
4507
|
+
const display = `${check3.bin} ${check3.args.join(" ")}`;
|
|
4508
|
+
const spinner = ora2(`${check3.name} \uC2E4\uD589 \uC911...`).start();
|
|
4380
4509
|
const start2 = Date.now();
|
|
4381
|
-
const result = safeExecFile(
|
|
4510
|
+
const result = safeExecFile(check3.bin, check3.args);
|
|
4382
4511
|
const duration = Date.now() - start2;
|
|
4383
4512
|
const sec = (duration / 1e3).toFixed(1);
|
|
4384
4513
|
if (result.ok) {
|
|
4385
|
-
spinner.succeed(`${
|
|
4386
|
-
results.push({ name:
|
|
4514
|
+
spinner.succeed(`${check3.name} ${chalk19.gray(`(${sec}s)`)}`);
|
|
4515
|
+
results.push({ name: check3.name, command: display, passed: true, duration });
|
|
4387
4516
|
} else {
|
|
4388
|
-
spinner.fail(`${
|
|
4517
|
+
spinner.fail(`${check3.name} ${chalk19.gray(`(${sec}s)`)}`);
|
|
4389
4518
|
results.push({
|
|
4390
|
-
name:
|
|
4519
|
+
name: check3.name,
|
|
4391
4520
|
command: display,
|
|
4392
4521
|
passed: false,
|
|
4393
4522
|
duration,
|
|
@@ -4395,22 +4524,22 @@ async function harness() {
|
|
|
4395
4524
|
});
|
|
4396
4525
|
}
|
|
4397
4526
|
}
|
|
4398
|
-
console.log(
|
|
4399
|
-
console.log(
|
|
4527
|
+
console.log(chalk19.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
|
|
4528
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
4400
4529
|
for (const r of results) {
|
|
4401
|
-
const icon = r.passed ?
|
|
4530
|
+
const icon = r.passed ? chalk19.green("\u2705") : chalk19.red("\u274C");
|
|
4402
4531
|
const sec = (r.duration / 1e3).toFixed(1);
|
|
4403
|
-
console.log(` ${icon} ${r.name.padEnd(15)} ${
|
|
4532
|
+
console.log(` ${icon} ${r.name.padEnd(15)} ${chalk19.gray(`${sec}s`)}`);
|
|
4404
4533
|
}
|
|
4405
4534
|
const passed = results.filter((r) => r.passed).length;
|
|
4406
4535
|
const all = passed === results.length;
|
|
4407
|
-
console.log(
|
|
4536
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
4408
4537
|
if (all) {
|
|
4409
|
-
console.log(
|
|
4538
|
+
console.log(chalk19.green.bold(`
|
|
4410
4539
|
\u{1F389} \uC804\uCCB4 \uD1B5\uACFC! (${passed}/${results.length})`));
|
|
4411
4540
|
} else {
|
|
4412
4541
|
console.log(
|
|
4413
|
-
|
|
4542
|
+
chalk19.red.bold(`
|
|
4414
4543
|
\u26A0\uFE0F ${results.length - passed}\uAC1C \uC2E4\uD328 (${passed}/${results.length} \uD1B5\uACFC)`)
|
|
4415
4544
|
);
|
|
4416
4545
|
process.exitCode = 1;
|
|
@@ -4423,8 +4552,8 @@ async function harness() {
|
|
|
4423
4552
|
}
|
|
4424
4553
|
|
|
4425
4554
|
// src/commands/migrate.ts
|
|
4426
|
-
import { existsSync as
|
|
4427
|
-
import
|
|
4555
|
+
import { existsSync as existsSync8, unlinkSync, rmSync } from "fs";
|
|
4556
|
+
import chalk20 from "chalk";
|
|
4428
4557
|
import inquirer10 from "inquirer";
|
|
4429
4558
|
import ora3 from "ora";
|
|
4430
4559
|
var LOCK_FILES = {
|
|
@@ -4433,19 +4562,19 @@ var LOCK_FILES = {
|
|
|
4433
4562
|
pnpm: "pnpm-lock.yaml"
|
|
4434
4563
|
};
|
|
4435
4564
|
function detectCurrentPM() {
|
|
4436
|
-
if (
|
|
4437
|
-
if (
|
|
4438
|
-
if (
|
|
4565
|
+
if (existsSync8("pnpm-lock.yaml")) return "pnpm";
|
|
4566
|
+
if (existsSync8("yarn.lock")) return "yarn";
|
|
4567
|
+
if (existsSync8("package-lock.json")) return "npm";
|
|
4439
4568
|
return null;
|
|
4440
4569
|
}
|
|
4441
4570
|
function isCLIAvailable(pm) {
|
|
4442
4571
|
return safeExecFile(pm, ["--version"]).ok;
|
|
4443
4572
|
}
|
|
4444
4573
|
async function migrate(target) {
|
|
4445
|
-
console.log(
|
|
4446
|
-
console.log(
|
|
4574
|
+
console.log(chalk20.bold("\n\u{1F504} " + t("migrate.title")));
|
|
4575
|
+
console.log(chalk20.gray("\u2500".repeat(40)));
|
|
4447
4576
|
const current = detectCurrentPM();
|
|
4448
|
-
console.log(
|
|
4577
|
+
console.log(chalk20.cyan(`
|
|
4449
4578
|
\uD604\uC7AC \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00"}`));
|
|
4450
4579
|
let targetPM;
|
|
4451
4580
|
if (target && ["npm", "yarn", "pnpm"].includes(target)) {
|
|
@@ -4463,14 +4592,14 @@ async function migrate(target) {
|
|
|
4463
4592
|
targetPM = selected;
|
|
4464
4593
|
}
|
|
4465
4594
|
if (targetPM === current) {
|
|
4466
|
-
console.log(
|
|
4595
|
+
console.log(chalk20.yellow(`
|
|
4467
4596
|
\u26A0\uFE0F \uC774\uBBF8 ${targetPM}\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4.`));
|
|
4468
4597
|
return;
|
|
4469
4598
|
}
|
|
4470
4599
|
if (!isCLIAvailable(targetPM)) {
|
|
4471
|
-
console.log(
|
|
4600
|
+
console.log(chalk20.red(`
|
|
4472
4601
|
\u274C ${targetPM}\uC774 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
4473
|
-
console.log(
|
|
4602
|
+
console.log(chalk20.yellow(` npm i -g ${targetPM}`));
|
|
4474
4603
|
return;
|
|
4475
4604
|
}
|
|
4476
4605
|
const { confirm } = await inquirer10.prompt([
|
|
@@ -4482,18 +4611,18 @@ async function migrate(target) {
|
|
|
4482
4611
|
}
|
|
4483
4612
|
]);
|
|
4484
4613
|
if (!confirm) {
|
|
4485
|
-
console.log(
|
|
4614
|
+
console.log(chalk20.gray("\uCDE8\uC18C\uB428"));
|
|
4486
4615
|
return;
|
|
4487
4616
|
}
|
|
4488
4617
|
const cleanup = ora3("\uAE30\uC874 lock \uD30C\uC77C \uC815\uB9AC \uC911...").start();
|
|
4489
4618
|
for (const lockFile of Object.values(LOCK_FILES)) {
|
|
4490
|
-
if (
|
|
4619
|
+
if (existsSync8(lockFile)) {
|
|
4491
4620
|
unlinkSync(lockFile);
|
|
4492
4621
|
}
|
|
4493
4622
|
}
|
|
4494
|
-
if (
|
|
4623
|
+
if (existsSync8("node_modules")) {
|
|
4495
4624
|
cleanup.text = "node_modules \uC0AD\uC81C \uC911...";
|
|
4496
|
-
|
|
4625
|
+
rmSync("node_modules", { recursive: true, force: true });
|
|
4497
4626
|
}
|
|
4498
4627
|
cleanup.succeed("\uAE30\uC874 \uD30C\uC77C \uC815\uB9AC \uC644\uB8CC");
|
|
4499
4628
|
const install = ora3(`${targetPM} install \uC2E4\uD589 \uC911...`).start();
|
|
@@ -4502,10 +4631,10 @@ async function migrate(target) {
|
|
|
4502
4631
|
install.succeed(`${targetPM} install \uC644\uB8CC!`);
|
|
4503
4632
|
} else {
|
|
4504
4633
|
install.fail(`${targetPM} install \uC2E4\uD328`);
|
|
4505
|
-
console.log(
|
|
4634
|
+
console.log(chalk20.red(installResult.err.slice(0, 300)));
|
|
4506
4635
|
return;
|
|
4507
4636
|
}
|
|
4508
|
-
console.log(
|
|
4637
|
+
console.log(chalk20.green.bold(`
|
|
4509
4638
|
\u{1F389} ${current ?? "\uC774\uC804"} \u2192 ${targetPM} \uC804\uD658 \uC644\uB8CC!`));
|
|
4510
4639
|
printNextStep({
|
|
4511
4640
|
message: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 \uC644\uB8CC!",
|
|
@@ -4515,17 +4644,17 @@ async function migrate(target) {
|
|
|
4515
4644
|
}
|
|
4516
4645
|
|
|
4517
4646
|
// src/commands/update.ts
|
|
4518
|
-
import { existsSync as
|
|
4519
|
-
import { dirname as dirname2, join as
|
|
4647
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4648
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
4520
4649
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4521
|
-
import
|
|
4650
|
+
import chalk21 from "chalk";
|
|
4522
4651
|
import ora4 from "ora";
|
|
4523
4652
|
var PACKAGE = "@byh3071/vhk";
|
|
4524
4653
|
function getCurrentVersion() {
|
|
4525
4654
|
const dir = dirname2(fileURLToPath3(import.meta.url));
|
|
4526
|
-
for (const pkgPath of [
|
|
4655
|
+
for (const pkgPath of [join5(dir, "../package.json"), join5(dir, "../../package.json")]) {
|
|
4527
4656
|
try {
|
|
4528
|
-
if (
|
|
4657
|
+
if (existsSync9(pkgPath)) {
|
|
4529
4658
|
const pkg = readJsonFile(pkgPath);
|
|
4530
4659
|
if (pkg.version) return pkg.version;
|
|
4531
4660
|
}
|
|
@@ -4548,33 +4677,33 @@ function isUpToDate(current, latest) {
|
|
|
4548
4677
|
return cc >= lc;
|
|
4549
4678
|
}
|
|
4550
4679
|
async function update() {
|
|
4551
|
-
console.log(
|
|
4552
|
-
console.log(
|
|
4680
|
+
console.log(chalk21.bold("\n\u2B06\uFE0F " + t("update.title")));
|
|
4681
|
+
console.log(chalk21.gray("\u2500".repeat(40)));
|
|
4553
4682
|
const current = getCurrentVersion();
|
|
4554
|
-
console.log(
|
|
4683
|
+
console.log(chalk21.cyan(`
|
|
4555
4684
|
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${current}`));
|
|
4556
4685
|
const spinner = ora4("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...").start();
|
|
4557
4686
|
const latest = getLatestVersion();
|
|
4558
4687
|
if (latest) recordLatest(latest);
|
|
4559
4688
|
if (!latest) {
|
|
4560
4689
|
spinner.fail("\uCD5C\uC2E0 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
4561
|
-
console.log(
|
|
4562
|
-
console.log(
|
|
4690
|
+
console.log(chalk21.yellow(" \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uAC70\uB098 \uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
|
|
4691
|
+
console.log(chalk21.gray(` npm update -g ${PACKAGE}`));
|
|
4563
4692
|
return;
|
|
4564
4693
|
}
|
|
4565
4694
|
spinner.stop();
|
|
4566
|
-
console.log(
|
|
4695
|
+
console.log(chalk21.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
|
|
4567
4696
|
if (isUpToDate(current, latest)) {
|
|
4568
|
-
console.log(
|
|
4697
|
+
console.log(chalk21.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
|
|
4569
4698
|
return;
|
|
4570
4699
|
}
|
|
4571
4700
|
const updateSpinner = ora4(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC911...`).start();
|
|
4572
4701
|
const upd = safeExecFile("npm", ["update", "-g", PACKAGE]);
|
|
4573
4702
|
if (upd.ok) {
|
|
4574
4703
|
updateSpinner.succeed(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`);
|
|
4575
|
-
console.log(
|
|
4704
|
+
console.log(chalk21.green.bold(`
|
|
4576
4705
|
\u{1F389} VHK CLI v${latest} \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
|
|
4577
|
-
console.log(
|
|
4706
|
+
console.log(chalk21.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
|
|
4578
4707
|
printNextStep({
|
|
4579
4708
|
message: t("update.nextOkMessage"),
|
|
4580
4709
|
command: "vhk --version",
|
|
@@ -4582,9 +4711,9 @@ async function update() {
|
|
|
4582
4711
|
});
|
|
4583
4712
|
} else {
|
|
4584
4713
|
updateSpinner.fail("\uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
|
|
4585
|
-
console.log(
|
|
4586
|
-
console.log(
|
|
4587
|
-
console.log(
|
|
4714
|
+
console.log(chalk21.red(upd.err.slice(0, 300)));
|
|
4715
|
+
console.log(chalk21.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
|
|
4716
|
+
console.log(chalk21.gray(` npm update -g ${PACKAGE}`));
|
|
4588
4717
|
printNextStep({
|
|
4589
4718
|
message: t("update.nextFailMessage"),
|
|
4590
4719
|
command: "vhk doctor",
|
|
@@ -4595,21 +4724,21 @@ async function update() {
|
|
|
4595
4724
|
|
|
4596
4725
|
// src/commands/context.ts
|
|
4597
4726
|
import {
|
|
4598
|
-
existsSync as
|
|
4599
|
-
mkdirSync as
|
|
4600
|
-
readFileSync as
|
|
4727
|
+
existsSync as existsSync11,
|
|
4728
|
+
mkdirSync as mkdirSync7,
|
|
4729
|
+
readFileSync as readFileSync4,
|
|
4601
4730
|
readdirSync as readdirSync2,
|
|
4602
4731
|
statSync as statSync2,
|
|
4603
|
-
writeFileSync as
|
|
4732
|
+
writeFileSync as writeFileSync6
|
|
4604
4733
|
} from "fs";
|
|
4605
|
-
import { join as join8 } from "path";
|
|
4606
|
-
import chalk24 from "chalk";
|
|
4607
|
-
|
|
4608
|
-
// src/commands/memory.ts
|
|
4609
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, copyFileSync, readFileSync as readFileSync4, renameSync, rmSync as rmSync3 } from "fs";
|
|
4610
4734
|
import { join as join7 } from "path";
|
|
4611
4735
|
import chalk23 from "chalk";
|
|
4612
|
-
|
|
4736
|
+
|
|
4737
|
+
// src/commands/memory.ts
|
|
4738
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5, copyFileSync, readFileSync as readFileSync3, renameSync, rmSync as rmSync2 } from "fs";
|
|
4739
|
+
import { join as join6 } from "path";
|
|
4740
|
+
import chalk22 from "chalk";
|
|
4741
|
+
var MEMORY_PATH_REL = join6(".vhk", "memory.json");
|
|
4613
4742
|
var MEMORY_SCHEMA_VERSION = 2;
|
|
4614
4743
|
function emptyV2() {
|
|
4615
4744
|
return { schemaVersion: MEMORY_SCHEMA_VERSION, decisions: [], failures: [], successes: [], patterns: [] };
|
|
@@ -4670,8 +4799,8 @@ function migrateMemory(rawMemory, rawLearnings) {
|
|
|
4670
4799
|
return v2;
|
|
4671
4800
|
}
|
|
4672
4801
|
function readRaw(cwd) {
|
|
4673
|
-
const p =
|
|
4674
|
-
if (!
|
|
4802
|
+
const p = join6(cwd, MEMORY_PATH_REL);
|
|
4803
|
+
if (!existsSync10(p)) return { kind: "missing" };
|
|
4675
4804
|
try {
|
|
4676
4805
|
return { kind: "parsed", value: readJsonFile(p) };
|
|
4677
4806
|
} catch {
|
|
@@ -4679,24 +4808,24 @@ function readRaw(cwd) {
|
|
|
4679
4808
|
}
|
|
4680
4809
|
}
|
|
4681
4810
|
function warnUnreadable(cwd) {
|
|
4682
|
-
const p =
|
|
4683
|
-
console.error(
|
|
4811
|
+
const p = join6(cwd, MEMORY_PATH_REL);
|
|
4812
|
+
console.error(chalk22.red(`
|
|
4684
4813
|
\u26A0\uFE0F ${MEMORY_PATH_REL} \uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC190\uC0C1/\uBD80\uBD84 \uC4F0\uAE30 \uC758\uC2EC).`));
|
|
4685
|
-
console.error(
|
|
4686
|
-
console.error(
|
|
4814
|
+
console.error(chalk22.yellow(` \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uACE0 \uBE48 \uBA54\uBAA8\uB9AC\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4 \u2014 \uC6D0\uBCF8 \uBCF4\uC874\uB428.`));
|
|
4815
|
+
console.error(chalk22.dim(` \uD655\uC778/\uBCF5\uAD6C: ${p} (\uBC31\uC5C5: ${p}.bak / ${p}.v1.bak)`));
|
|
4687
4816
|
}
|
|
4688
4817
|
function warnUnrecognized(cwd) {
|
|
4689
|
-
const p =
|
|
4690
|
-
console.error(
|
|
4818
|
+
const p = join6(cwd, MEMORY_PATH_REL);
|
|
4819
|
+
console.error(chalk22.red(`
|
|
4691
4820
|
\u26A0\uFE0F ${MEMORY_PATH_REL} \uAC00 \uC778\uC2DD \uAC00\uB2A5\uD55C \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 (v1 \uBC30\uC5F4/v2 \uAC1D\uCCB4 \uC544\uB2D8).`));
|
|
4692
|
-
console.error(
|
|
4693
|
-
console.error(
|
|
4821
|
+
console.error(chalk22.yellow(` \uBBF8\uB798 \uC2A4\uD0A4\uB9C8/\uC218\uB3D9 \uD3B8\uC9D1 \uC758\uC2EC \u2014 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(\uC6D0\uBCF8 \uBCF4\uC874). \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4.`));
|
|
4822
|
+
console.error(chalk22.dim(` \uD655\uC778: ${p}`));
|
|
4694
4823
|
}
|
|
4695
4824
|
function readLearningsRaw(cwd) {
|
|
4696
|
-
const p =
|
|
4697
|
-
if (!
|
|
4825
|
+
const p = join6(cwd, "docs", "state", "learnings.md");
|
|
4826
|
+
if (!existsSync10(p)) return void 0;
|
|
4698
4827
|
try {
|
|
4699
|
-
return stripBom(
|
|
4828
|
+
return stripBom(readFileSync3(p, "utf-8"));
|
|
4700
4829
|
} catch {
|
|
4701
4830
|
return void 0;
|
|
4702
4831
|
}
|
|
@@ -4714,7 +4843,7 @@ function readMemory(cwd = process.cwd()) {
|
|
|
4714
4843
|
try {
|
|
4715
4844
|
writeMemory(cwd, v2);
|
|
4716
4845
|
} catch {
|
|
4717
|
-
console.error(
|
|
4846
|
+
console.error(chalk22.yellow(` (v2 \uC601\uAD6C\uD654 \uBCF4\uB958 \u2014 ${MEMORY_PATH_REL} \uC7A0\uAE08 \uC758\uC2EC. \uC774\uBC88\uC5D4 \uBA54\uBAA8\uB9AC\uC0C1\uC73C\uB85C\uB9CC \uC9C4\uD589)`));
|
|
4718
4847
|
}
|
|
4719
4848
|
return v2;
|
|
4720
4849
|
}
|
|
@@ -4741,12 +4870,12 @@ function isActive(e) {
|
|
|
4741
4870
|
return e.status !== "archived" && e.status !== "resolved";
|
|
4742
4871
|
}
|
|
4743
4872
|
function writeMemory(cwd, mem) {
|
|
4744
|
-
const p =
|
|
4745
|
-
|
|
4746
|
-
if (
|
|
4873
|
+
const p = join6(cwd, MEMORY_PATH_REL);
|
|
4874
|
+
mkdirSync6(join6(cwd, ".vhk"), { recursive: true });
|
|
4875
|
+
if (existsSync10(p)) {
|
|
4747
4876
|
const cur = readRaw(cwd);
|
|
4748
4877
|
const curIsV2 = cur.kind === "parsed" && isV2(cur.value);
|
|
4749
|
-
if (cur.kind !== "error" && !curIsV2 && !
|
|
4878
|
+
if (cur.kind !== "error" && !curIsV2 && !existsSync10(p + ".v1.bak")) {
|
|
4750
4879
|
try {
|
|
4751
4880
|
copyFileSync(p, p + ".v1.bak");
|
|
4752
4881
|
} catch {
|
|
@@ -4760,12 +4889,12 @@ function writeMemory(cwd, mem) {
|
|
|
4760
4889
|
}
|
|
4761
4890
|
}
|
|
4762
4891
|
const tmpPath = p + ".tmp";
|
|
4763
|
-
|
|
4892
|
+
writeFileSync5(tmpPath, JSON.stringify(mem, null, 2) + "\n", "utf-8");
|
|
4764
4893
|
try {
|
|
4765
4894
|
renameSync(tmpPath, p);
|
|
4766
4895
|
} catch (err) {
|
|
4767
4896
|
try {
|
|
4768
|
-
|
|
4897
|
+
rmSync2(tmpPath, { force: true });
|
|
4769
4898
|
} catch {
|
|
4770
4899
|
}
|
|
4771
4900
|
throw err;
|
|
@@ -4791,28 +4920,29 @@ function orderedAll(mem) {
|
|
|
4791
4920
|
}
|
|
4792
4921
|
var VALID_BUCKETS = ["decision", "failure", "success"];
|
|
4793
4922
|
async function memoryAdd(content, opts = {}) {
|
|
4794
|
-
|
|
4795
|
-
console.log(
|
|
4923
|
+
if (!ensureNotHardStopped("memory add")) return;
|
|
4924
|
+
console.log(chalk22.bold("\n\u{1F9E0} " + t("memory.addTitle")));
|
|
4925
|
+
console.log(chalk22.gray("\u2500".repeat(40)));
|
|
4796
4926
|
if (!content || !content.trim()) {
|
|
4797
|
-
console.log(
|
|
4798
|
-
console.log(
|
|
4927
|
+
console.log(chalk22.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
|
|
4928
|
+
console.log(chalk22.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9" --type decision'));
|
|
4799
4929
|
process.exitCode = 1;
|
|
4800
4930
|
return;
|
|
4801
4931
|
}
|
|
4802
4932
|
const typeRaw = opts.type ?? "decision";
|
|
4803
4933
|
if (!VALID_BUCKETS.includes(typeRaw)) {
|
|
4804
|
-
console.log(
|
|
4934
|
+
console.log(chalk22.red(`\u274C --type \uC740 decision|failure|success \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uBC1B\uC740 \uAC12: ${typeRaw}).`));
|
|
4805
4935
|
process.exitCode = 1;
|
|
4806
4936
|
return;
|
|
4807
4937
|
}
|
|
4808
4938
|
const type = typeRaw;
|
|
4809
4939
|
if (type === "decision" && (opts.why || opts.lesson)) {
|
|
4810
|
-
console.log(
|
|
4940
|
+
console.log(chalk22.yellow("\u26A0\uFE0F --why/--lesson \uC740 --type failure|success \uC5D0\uC11C\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4 \u2014 decision \uC5D0\uC11C\uB294 \uBB34\uC2DC\uB428."));
|
|
4811
4941
|
}
|
|
4812
4942
|
const cwd = process.cwd();
|
|
4813
4943
|
const loaded = loadForMutation(cwd);
|
|
4814
4944
|
if (!loaded.ok) {
|
|
4815
|
-
console.log(
|
|
4945
|
+
console.log(chalk22.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uC800\uC7A5 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874). \uBC31\uC5C5 \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
|
|
4816
4946
|
process.exitCode = 1;
|
|
4817
4947
|
return;
|
|
4818
4948
|
}
|
|
@@ -4828,34 +4958,34 @@ async function memoryAdd(content, opts = {}) {
|
|
|
4828
4958
|
else if (type === "success") mem.successes.push({ ...base, why: opts.why });
|
|
4829
4959
|
else mem.decisions.push(base);
|
|
4830
4960
|
writeMemory(cwd, mem);
|
|
4831
|
-
console.log(
|
|
4961
|
+
console.log(chalk22.green(`
|
|
4832
4962
|
\u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (${type} #${base.id})`));
|
|
4833
|
-
console.log(
|
|
4963
|
+
console.log(chalk22.cyan(` \u{1F4DD} ${base.content}`));
|
|
4834
4964
|
printNextStep({ message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!", command: "vhk memory list", cursorHint: "\uAE30\uC5B5 \uBAA9\uB85D \uBCF4\uC5EC\uC918" });
|
|
4835
4965
|
}
|
|
4836
4966
|
var STATUS_ICON2 = { active: "\u{1F7E2}", resolved: "\u2705", archived: "\u{1F4E6}" };
|
|
4837
4967
|
var BUCKET_LABEL = { decision: "\uACB0\uC815", failure: "\uC2E4\uD328", success: "\uC131\uACF5" };
|
|
4838
4968
|
async function memoryList(opts = {}) {
|
|
4839
|
-
console.log(
|
|
4840
|
-
console.log(
|
|
4969
|
+
console.log(chalk22.bold("\n\u{1F9E0} " + t("memory.listTitle")));
|
|
4970
|
+
console.log(chalk22.gray("\u2500".repeat(40)));
|
|
4841
4971
|
const mem = readMemory(process.cwd());
|
|
4842
4972
|
const all = orderedAll(mem);
|
|
4843
4973
|
const visible = all.map((x, i) => ({ ...x, n: i + 1 })).filter((x) => (opts.all || isActive(x.entry)) && (!opts.type || x.bucket === opts.type));
|
|
4844
4974
|
if (visible.length === 0) {
|
|
4845
|
-
console.log(
|
|
4846
|
-
console.log(
|
|
4975
|
+
console.log(chalk22.yellow("\n\u{1F4ED} \uD45C\uC2DC\uD560 \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4976
|
+
console.log(chalk22.gray(' vhk memory add "\uB0B4\uC6A9" --type decision|failure|success'));
|
|
4847
4977
|
return;
|
|
4848
4978
|
}
|
|
4849
|
-
console.log(
|
|
4979
|
+
console.log(chalk22.cyan(`
|
|
4850
4980
|
${visible.length}\uAC1C${opts.all ? " (\uBCF4\uAD00 \uD3EC\uD568)" : " (\uD65C\uC131)"}:
|
|
4851
4981
|
`));
|
|
4852
4982
|
for (const x of visible) {
|
|
4853
4983
|
const e = x.entry;
|
|
4854
4984
|
const fail = e;
|
|
4855
4985
|
console.log(` [${x.n}] ${STATUS_ICON2[e.status] ?? "\u{1F7E2}"} (${BUCKET_LABEL[x.bucket]}) ${e.content || (fail.lesson ? "\u{1F4A1} " + fail.lesson : "(\uB0B4\uC6A9 \uC5C6\uC74C)")}`);
|
|
4856
|
-
if (fail.lesson && e.content) console.log(
|
|
4857
|
-
if (fail.why) console.log(
|
|
4858
|
-
if (e.tags.length > 0) console.log(
|
|
4986
|
+
if (fail.lesson && e.content) console.log(chalk22.dim(` \u{1F4A1} \uAD50\uD6C8: ${fail.lesson}`));
|
|
4987
|
+
if (fail.why) console.log(chalk22.dim(` \u21B3 ${fail.why}`));
|
|
4988
|
+
if (e.tags.length > 0) console.log(chalk22.blue(` \u{1F3F7}\uFE0F ${e.tags.join(", ")}`));
|
|
4859
4989
|
}
|
|
4860
4990
|
}
|
|
4861
4991
|
function resolveIndex(indexStr, len) {
|
|
@@ -4864,10 +4994,11 @@ function resolveIndex(indexStr, len) {
|
|
|
4864
4994
|
return idx;
|
|
4865
4995
|
}
|
|
4866
4996
|
async function memoryRemove(indexStr) {
|
|
4997
|
+
if (!ensureNotHardStopped("memory remove")) return;
|
|
4867
4998
|
const cwd = process.cwd();
|
|
4868
4999
|
const loaded = loadForMutation(cwd);
|
|
4869
5000
|
if (!loaded.ok) {
|
|
4870
|
-
console.log(
|
|
5001
|
+
console.log(chalk22.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uC0AD\uC81C \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
|
|
4871
5002
|
process.exitCode = 1;
|
|
4872
5003
|
return;
|
|
4873
5004
|
}
|
|
@@ -4875,7 +5006,7 @@ async function memoryRemove(indexStr) {
|
|
|
4875
5006
|
const all = orderedAll(mem);
|
|
4876
5007
|
const idx = resolveIndex(indexStr, all.length);
|
|
4877
5008
|
if (idx === null) {
|
|
4878
|
-
console.log(
|
|
5009
|
+
console.log(chalk22.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${all.length || 0})`));
|
|
4879
5010
|
process.exitCode = 1;
|
|
4880
5011
|
return;
|
|
4881
5012
|
}
|
|
@@ -4884,14 +5015,14 @@ async function memoryRemove(indexStr) {
|
|
|
4884
5015
|
const pos = list.findIndex((e) => e === entry);
|
|
4885
5016
|
if (pos >= 0) list.splice(pos, 1);
|
|
4886
5017
|
writeMemory(cwd, mem);
|
|
4887
|
-
console.log(
|
|
4888
|
-
console.log(
|
|
5018
|
+
console.log(chalk22.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
|
|
5019
|
+
console.log(chalk22.gray(` ${entry.content || entry.lesson || entry.id}`));
|
|
4889
5020
|
}
|
|
4890
5021
|
function resolveEntryForMutation(indexStr) {
|
|
4891
5022
|
const cwd = process.cwd();
|
|
4892
5023
|
const loaded = loadForMutation(cwd);
|
|
4893
5024
|
if (!loaded.ok) {
|
|
4894
|
-
console.log(
|
|
5025
|
+
console.log(chalk22.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uC791\uC5C5 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
|
|
4895
5026
|
process.exitCode = 1;
|
|
4896
5027
|
return null;
|
|
4897
5028
|
}
|
|
@@ -4899,7 +5030,7 @@ function resolveEntryForMutation(indexStr) {
|
|
|
4899
5030
|
const all = orderedAll(mem);
|
|
4900
5031
|
const idx = resolveIndex(indexStr, all.length);
|
|
4901
5032
|
if (idx === null) {
|
|
4902
|
-
console.log(
|
|
5033
|
+
console.log(chalk22.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${all.length || 0})`));
|
|
4903
5034
|
process.exitCode = 1;
|
|
4904
5035
|
return null;
|
|
4905
5036
|
}
|
|
@@ -4909,39 +5040,42 @@ function entryLabel(entry) {
|
|
|
4909
5040
|
return entry.content || entry.lesson || entry.id;
|
|
4910
5041
|
}
|
|
4911
5042
|
async function memoryArchive(indexStr) {
|
|
5043
|
+
if (!ensureNotHardStopped("memory archive")) return;
|
|
4912
5044
|
const r = resolveEntryForMutation(indexStr);
|
|
4913
5045
|
if (!r) return;
|
|
4914
5046
|
r.entry.status = "archived";
|
|
4915
5047
|
r.entry.archivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4916
5048
|
delete r.entry.resolvedAt;
|
|
4917
5049
|
writeMemory(r.cwd, r.mem);
|
|
4918
|
-
console.log(
|
|
5050
|
+
console.log(chalk22.green(`
|
|
4919
5051
|
\u{1F4E6} \uBCF4\uAD00\uB428: ${entryLabel(r.entry)}`));
|
|
4920
|
-
console.log(
|
|
5052
|
+
console.log(chalk22.dim(" (\uD328\uD134 \uAC10\uC9C0\xB7\uC9C4\uD654\uC5D0\uC11C \uC81C\uC678\uB429\uB2C8\uB2E4 \u2014 \uC120\uC21C\uD658). \uB418\uB3CC\uB9AC\uAE30: vhk memory unarchive <\uBC88\uD638>"));
|
|
4921
5053
|
}
|
|
4922
5054
|
async function memoryResolve(indexStr) {
|
|
5055
|
+
if (!ensureNotHardStopped("memory resolve")) return;
|
|
4923
5056
|
const r = resolveEntryForMutation(indexStr);
|
|
4924
5057
|
if (!r) return;
|
|
4925
5058
|
r.entry.status = "resolved";
|
|
4926
5059
|
r.entry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4927
5060
|
delete r.entry.archivedAt;
|
|
4928
5061
|
writeMemory(r.cwd, r.mem);
|
|
4929
|
-
console.log(
|
|
5062
|
+
console.log(chalk22.green(`
|
|
4930
5063
|
\u2705 \uD574\uACB0\uB428: ${entryLabel(r.entry)}`));
|
|
4931
|
-
console.log(
|
|
5064
|
+
console.log(chalk22.dim(" (vhk memory list --all \uB85C \uD655\uC778. \uB418\uB3CC\uB9AC\uAE30: vhk memory unarchive <\uBC88\uD638>)"));
|
|
4932
5065
|
}
|
|
4933
5066
|
async function memoryUnarchive(indexStr) {
|
|
5067
|
+
if (!ensureNotHardStopped("memory unarchive")) return;
|
|
4934
5068
|
const r = resolveEntryForMutation(indexStr);
|
|
4935
5069
|
if (!r) return;
|
|
4936
5070
|
if (isActive(r.entry)) {
|
|
4937
|
-
console.log(
|
|
5071
|
+
console.log(chalk22.dim(` \uC774\uBBF8 \uD65C\uC131 \uD56D\uBAA9\uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C: ${entryLabel(r.entry)}`));
|
|
4938
5072
|
return;
|
|
4939
5073
|
}
|
|
4940
5074
|
r.entry.status = "active";
|
|
4941
5075
|
delete r.entry.archivedAt;
|
|
4942
5076
|
delete r.entry.resolvedAt;
|
|
4943
5077
|
writeMemory(r.cwd, r.mem);
|
|
4944
|
-
console.log(
|
|
5078
|
+
console.log(chalk22.green(`
|
|
4945
5079
|
\u{1F7E2} \uD65C\uC131\uC73C\uB85C \uBCF5\uAD6C\uB428: ${entryLabel(r.entry)}`));
|
|
4946
5080
|
}
|
|
4947
5081
|
async function memoryMigrate() {
|
|
@@ -4949,33 +5083,33 @@ async function memoryMigrate() {
|
|
|
4949
5083
|
const raw = readRaw(cwd);
|
|
4950
5084
|
if (raw.kind === "error") {
|
|
4951
5085
|
warnUnreadable(cwd);
|
|
4952
|
-
console.log(
|
|
5086
|
+
console.log(chalk22.red(" \u274C \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC911\uB2E8 (\uC190\uC0C1 \uC758\uC2EC). \uC6D0\uBCF8 \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
|
|
4953
5087
|
process.exitCode = 1;
|
|
4954
5088
|
return;
|
|
4955
5089
|
}
|
|
4956
5090
|
if (raw.kind === "parsed" && isV2(raw.value)) {
|
|
4957
|
-
console.log(
|
|
5091
|
+
console.log(chalk22.dim(" \uC774\uBBF8 memory schema v2 \uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C(\uBA71\uB4F1)."));
|
|
4958
5092
|
return;
|
|
4959
5093
|
}
|
|
4960
5094
|
if (raw.kind === "parsed" && !Array.isArray(raw.value)) {
|
|
4961
5095
|
warnUnrecognized(cwd);
|
|
4962
|
-
console.log(
|
|
5096
|
+
console.log(chalk22.red(" \u274C v1(\uD3C9\uBA74 \uBC30\uC5F4) \uD615\uC2DD\uC774 \uC544\uB2C8\uB77C \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uB300\uC0C1\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uC911\uB2E8(\uC6D0\uBCF8 \uBCF4\uC874)."));
|
|
4963
5097
|
process.exitCode = 1;
|
|
4964
5098
|
return;
|
|
4965
5099
|
}
|
|
4966
5100
|
const learnings = readLearningsRaw(cwd);
|
|
4967
5101
|
const hadFile = raw.kind === "parsed";
|
|
4968
5102
|
if (raw.kind === "missing" && !learnings) {
|
|
4969
|
-
console.log(
|
|
5103
|
+
console.log(chalk22.yellow(" \u2139\uFE0F \uB9C8\uC774\uADF8\uB808\uC774\uC158\uD560 v1 memory.json / learnings.md \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C."));
|
|
4970
5104
|
return;
|
|
4971
5105
|
}
|
|
4972
5106
|
const v2 = migrateMemory(raw.kind === "parsed" ? raw.value : null, learnings);
|
|
4973
5107
|
writeMemory(cwd, v2);
|
|
4974
5108
|
const backupNote = hadFile ? " (.v1.bak \uC6D0\uBCF8 \uC601\uAD6C \uBC31\uC5C5)" : " (\uC2E0\uADDC \uC0DD\uC131 \u2014 \uC6D0\uBCF8 \uC5C6\uC74C, \uBC31\uC5C5 \uC5C6\uC74C)";
|
|
4975
|
-
console.log(
|
|
5109
|
+
console.log(chalk22.green(`
|
|
4976
5110
|
\u2705 memory.json \u2192 v2 \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC644\uB8CC${backupNote}`));
|
|
4977
5111
|
console.log(
|
|
4978
|
-
|
|
5112
|
+
chalk22.dim(
|
|
4979
5113
|
` decisions ${v2.decisions.length} \xB7 failures ${v2.failures.length} \xB7 successes ${v2.successes.length}` + (learnings ? " (learnings.md \uAD50\uD6C8 \uD761\uC218 \u2014 \uC774\uD6C4 vhk learn \uC740 memory \uC5D0 \uAE30\uB85D)" : "")
|
|
4980
5114
|
)
|
|
4981
5115
|
);
|
|
@@ -5045,7 +5179,7 @@ function buildTree(dir, prefix = "", maxDepth = 3, depth = 0) {
|
|
|
5045
5179
|
filtered.forEach((entry, index) => {
|
|
5046
5180
|
const isLast = index === filtered.length - 1;
|
|
5047
5181
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
5048
|
-
const fullPath =
|
|
5182
|
+
const fullPath = join7(dir, entry);
|
|
5049
5183
|
const stat = statSync2(fullPath);
|
|
5050
5184
|
const isDir = stat.isDirectory();
|
|
5051
5185
|
lines.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
|
|
@@ -5077,8 +5211,8 @@ function extractTechStack() {
|
|
|
5077
5211
|
else if (all.jest) stack["\uD14C\uC2A4\uD2B8"] = "jest";
|
|
5078
5212
|
if (all.commander) stack["CLI"] = "commander";
|
|
5079
5213
|
if (all.inquirer) stack["\uC778\uD130\uB799\uD2F0\uBE0C"] = "inquirer";
|
|
5080
|
-
if (
|
|
5081
|
-
else if (
|
|
5214
|
+
if (existsSync11("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
|
|
5215
|
+
else if (existsSync11("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
|
|
5082
5216
|
else stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "npm";
|
|
5083
5217
|
if (pkg.name) stack["\uD328\uD0A4\uC9C0 \uC774\uB984"] = pkg.name;
|
|
5084
5218
|
if (pkg.version) stack["\uBC84\uC804"] = pkg.version;
|
|
@@ -5091,8 +5225,8 @@ function getVhkCommands() {
|
|
|
5091
5225
|
}
|
|
5092
5226
|
async function context(opts = {}) {
|
|
5093
5227
|
const compact = opts.compact === true;
|
|
5094
|
-
console.log(
|
|
5095
|
-
console.log(
|
|
5228
|
+
console.log(chalk23.bold("\n\u{1F9E0} " + t("context.title")));
|
|
5229
|
+
console.log(chalk23.gray("\u2500".repeat(40)));
|
|
5096
5230
|
const stack = extractTechStack();
|
|
5097
5231
|
const tree = buildTree(".", "", compact ? 2 : 3).join("\n");
|
|
5098
5232
|
const commands = getVhkCommands();
|
|
@@ -5196,12 +5330,12 @@ async function context(opts = {}) {
|
|
|
5196
5330
|
} catch {
|
|
5197
5331
|
}
|
|
5198
5332
|
lines.push("");
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
console.log(
|
|
5333
|
+
mkdirSync7(".vhk", { recursive: true });
|
|
5334
|
+
writeFileSync6(CONTEXT_PATH, lines.join("\n"), "utf-8");
|
|
5335
|
+
console.log(chalk23.green(`
|
|
5202
5336
|
\u2705 ${CONTEXT_PATH} \uC0DD\uC131 \uC644\uB8CC!`));
|
|
5203
|
-
console.log(
|
|
5204
|
-
console.log(
|
|
5337
|
+
console.log(chalk23.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
|
|
5338
|
+
console.log(chalk23.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
|
|
5205
5339
|
printNextStep({
|
|
5206
5340
|
message: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC!",
|
|
5207
5341
|
command: "vhk context-show",
|
|
@@ -5209,33 +5343,33 @@ async function context(opts = {}) {
|
|
|
5209
5343
|
});
|
|
5210
5344
|
}
|
|
5211
5345
|
async function contextShow() {
|
|
5212
|
-
console.log(
|
|
5213
|
-
console.log(
|
|
5214
|
-
if (!
|
|
5215
|
-
console.log(
|
|
5216
|
-
console.log(
|
|
5346
|
+
console.log(chalk23.bold("\n\u{1F4C4} " + t("context.showTitle")));
|
|
5347
|
+
console.log(chalk23.gray("\u2500".repeat(40)));
|
|
5348
|
+
if (!existsSync11(CONTEXT_PATH)) {
|
|
5349
|
+
console.log(chalk23.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
5350
|
+
console.log(chalk23.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
|
|
5217
5351
|
return;
|
|
5218
5352
|
}
|
|
5219
|
-
const content =
|
|
5353
|
+
const content = readFileSync4(CONTEXT_PATH, "utf-8");
|
|
5220
5354
|
console.log("\n" + content);
|
|
5221
5355
|
}
|
|
5222
5356
|
|
|
5223
5357
|
// src/commands/brief.ts
|
|
5224
|
-
import { existsSync as
|
|
5225
|
-
import
|
|
5358
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, readFileSync as readFileSync5 } from "fs";
|
|
5359
|
+
import chalk24 from "chalk";
|
|
5226
5360
|
var BRIEF_PATH = ".vhk/brief.md";
|
|
5227
5361
|
function readProjectIdentity() {
|
|
5228
5362
|
const out = {};
|
|
5229
5363
|
try {
|
|
5230
|
-
if (
|
|
5231
|
-
const r =
|
|
5364
|
+
if (existsSync12("RULES.md")) {
|
|
5365
|
+
const r = readFileSync5("RULES.md", "utf-8");
|
|
5232
5366
|
const m = r.split("\n")[0].match(/^#\s*(.+?)(?:\s*—.*)?$/);
|
|
5233
5367
|
if (m) out.name = m[1].trim();
|
|
5234
5368
|
const d = r.match(/한 줄 설명:\s*(.+)/);
|
|
5235
5369
|
if (d) out.description = d[1].trim();
|
|
5236
5370
|
}
|
|
5237
|
-
if (!out.name &&
|
|
5238
|
-
const m =
|
|
5371
|
+
if (!out.name && existsSync12("CLAUDE.md")) {
|
|
5372
|
+
const m = readFileSync5("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
|
|
5239
5373
|
if (m) out.name = m[1].trim();
|
|
5240
5374
|
}
|
|
5241
5375
|
} catch {
|
|
@@ -5247,8 +5381,8 @@ function git2(args) {
|
|
|
5247
5381
|
return result.ok ? result.out : "";
|
|
5248
5382
|
}
|
|
5249
5383
|
async function brief() {
|
|
5250
|
-
console.log(
|
|
5251
|
-
console.log(
|
|
5384
|
+
console.log(chalk24.bold("\n\u{1F4CB} " + t("brief.title")));
|
|
5385
|
+
console.log(chalk24.gray("\u2500".repeat(40)));
|
|
5252
5386
|
const lines = [];
|
|
5253
5387
|
lines.push("# \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551");
|
|
5254
5388
|
lines.push("");
|
|
@@ -5294,7 +5428,7 @@ async function brief() {
|
|
|
5294
5428
|
}
|
|
5295
5429
|
} catch {
|
|
5296
5430
|
}
|
|
5297
|
-
if (
|
|
5431
|
+
if (existsSync12(".vhk/refs.json")) {
|
|
5298
5432
|
try {
|
|
5299
5433
|
const refs = readJsonFile(".vhk/refs.json");
|
|
5300
5434
|
if (Array.isArray(refs) && refs.length > 0) {
|
|
@@ -5322,10 +5456,10 @@ async function brief() {
|
|
|
5322
5456
|
lines.push("");
|
|
5323
5457
|
lines.push("_VHK CLI \uBE0C\uB9AC\uD551_");
|
|
5324
5458
|
lines.push("");
|
|
5325
|
-
|
|
5326
|
-
|
|
5459
|
+
mkdirSync8(".vhk", { recursive: true });
|
|
5460
|
+
writeFileSync7(BRIEF_PATH, lines.join("\n"), "utf-8");
|
|
5327
5461
|
console.log("\n" + lines.join("\n"));
|
|
5328
|
-
console.log(
|
|
5462
|
+
console.log(chalk24.green(`
|
|
5329
5463
|
\u2705 ${BRIEF_PATH} \uC800\uC7A5 \uC644\uB8CC`));
|
|
5330
5464
|
printNextStep({
|
|
5331
5465
|
message: "\uBE0C\uB9AC\uD551 \uC0DD\uC131 \uC644\uB8CC!",
|
|
@@ -5335,9 +5469,9 @@ async function brief() {
|
|
|
5335
5469
|
}
|
|
5336
5470
|
|
|
5337
5471
|
// src/commands/work.ts
|
|
5338
|
-
import { existsSync as
|
|
5339
|
-
import { join as
|
|
5340
|
-
import
|
|
5472
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync6 } from "fs";
|
|
5473
|
+
import { join as join8 } from "path";
|
|
5474
|
+
import chalk25 from "chalk";
|
|
5341
5475
|
|
|
5342
5476
|
// src/lib/clipboard.ts
|
|
5343
5477
|
import { spawnSync } from "child_process";
|
|
@@ -5366,26 +5500,26 @@ function runWithInput(cmd, args, input) {
|
|
|
5366
5500
|
}
|
|
5367
5501
|
|
|
5368
5502
|
// src/commands/work.ts
|
|
5369
|
-
var
|
|
5503
|
+
var VHK_DIR = ".vhk";
|
|
5370
5504
|
function gitShort() {
|
|
5371
5505
|
const r = safeExecFile("git", ["status", "--short"]);
|
|
5372
5506
|
return r.ok ? r.out.trim() : "";
|
|
5373
5507
|
}
|
|
5374
5508
|
function ensureVhkProject() {
|
|
5375
|
-
if (
|
|
5376
|
-
console.log(
|
|
5377
|
-
console.log(
|
|
5509
|
+
if (existsSync13("CLAUDE.md") || existsSync13(VHK_DIR) || existsSync13("goals")) return true;
|
|
5510
|
+
console.log(chalk25.yellow(" \u26A0\uFE0F \uC5EC\uAE30\uB294 VHK \uD504\uB85C\uC81D\uD2B8 \uD3F4\uB354\uAC00 \uC544\uB2CC \uAC83 \uAC19\uC544\uC694."));
|
|
5511
|
+
console.log(chalk25.dim(" VHK \uD504\uB85C\uC81D\uD2B8 \uD3F4\uB354\uC5D0\uC11C \uC2E4\uD589\uD558\uAC70\uB098, vhk start \uB85C \uC0C8\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
|
|
5378
5512
|
return false;
|
|
5379
5513
|
}
|
|
5380
5514
|
function passHardStop() {
|
|
5381
5515
|
if (!isHardStopActive()) {
|
|
5382
|
-
console.log(
|
|
5516
|
+
console.log(chalk25.green(" \u2705 HARD_STOP \uC5C6\uC74C"));
|
|
5383
5517
|
return true;
|
|
5384
5518
|
}
|
|
5385
|
-
console.log(
|
|
5519
|
+
console.log(chalk25.red.bold("\n\u{1F6D1} HARD STOP \uD65C\uC131 (.vhk/HARD_STOP) \u2014 \uC790\uB3D9\uD654\uB97C \uC911\uB2E8\uD569\uB2C8\uB2E4."));
|
|
5386
5520
|
const reason = readHardStopReason();
|
|
5387
|
-
if (reason) console.log(
|
|
5388
|
-
console.log(
|
|
5521
|
+
if (reason) console.log(chalk25.red(` \uC0AC\uC720: ${reason.replace(/\s*\n\s*/g, " ")}`));
|
|
5522
|
+
console.log(chalk25.yellow(" \uD574\uC81C\uB294 \uC0AC\uB78C\uC774 \uC9C1\uC811: vhk resume --confirm"));
|
|
5389
5523
|
return false;
|
|
5390
5524
|
}
|
|
5391
5525
|
function activeGoalLine() {
|
|
@@ -5399,7 +5533,7 @@ function activeGoalLine() {
|
|
|
5399
5533
|
}
|
|
5400
5534
|
function printGit(git3) {
|
|
5401
5535
|
if (!git3) {
|
|
5402
|
-
console.log(
|
|
5536
|
+
console.log(chalk25.dim(" (\uBCC0\uACBD\uB41C \uD30C\uC77C \uC5C6\uC74C \u2014 \uAE68\uB057\uD55C \uC0C1\uD0DC)"));
|
|
5403
5537
|
return;
|
|
5404
5538
|
}
|
|
5405
5539
|
for (const line of git3.split(/\r?\n/)) console.log(` ${line}`);
|
|
@@ -5460,24 +5594,24 @@ function buildHandoffPrompt(git3) {
|
|
|
5460
5594
|
function emitPrompt(prompt, fileName, label) {
|
|
5461
5595
|
let savedPath = "";
|
|
5462
5596
|
try {
|
|
5463
|
-
|
|
5464
|
-
savedPath =
|
|
5465
|
-
|
|
5597
|
+
mkdirSync9(VHK_DIR, { recursive: true });
|
|
5598
|
+
savedPath = join8(VHK_DIR, fileName);
|
|
5599
|
+
writeFileSync8(savedPath, prompt, "utf-8");
|
|
5466
5600
|
} catch {
|
|
5467
5601
|
savedPath = "";
|
|
5468
5602
|
}
|
|
5469
5603
|
const copied = copyToClipboard(prompt);
|
|
5470
5604
|
if (copied) {
|
|
5471
|
-
console.log(
|
|
5605
|
+
console.log(chalk25.green(`
|
|
5472
5606
|
\u{1F4CB} Claude\uC5D0\uAC8C \uC904 '${label}'\uC744 \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD588\uC2B5\uB2C8\uB2E4! \u2705`));
|
|
5473
|
-
if (savedPath) console.log(
|
|
5607
|
+
if (savedPath) console.log(chalk25.dim(` (\uC0AC\uBCF8 \uC800\uC7A5: ${savedPath})`));
|
|
5474
5608
|
} else {
|
|
5475
|
-
console.log(
|
|
5609
|
+
console.log(chalk25.yellow(`
|
|
5476
5610
|
\u26A0\uFE0F \uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC\uC5D0 \uC2E4\uD328\uD588\uC5B4\uC694 \u2014 \uC544\uB798 \uD504\uB86C\uD504\uD2B8\uB97C \uC9C1\uC811 \uBCF5\uC0AC\uD558\uC138\uC694:`));
|
|
5477
|
-
if (savedPath) console.log(
|
|
5478
|
-
console.log(
|
|
5611
|
+
if (savedPath) console.log(chalk25.dim(` (\uD30C\uC77C\uB85C\uB3C4 \uC800\uC7A5\uB428: ${savedPath} \u2014 \uC5F4\uC5B4\uC11C \uBCF5\uC0AC \uAC00\uB2A5)`));
|
|
5612
|
+
console.log(chalk25.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5479
5613
|
console.log(prompt);
|
|
5480
|
-
console.log(
|
|
5614
|
+
console.log(chalk25.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5481
5615
|
}
|
|
5482
5616
|
}
|
|
5483
5617
|
async function refreshContextQuietly() {
|
|
@@ -5494,31 +5628,31 @@ async function refreshContextQuietly() {
|
|
|
5494
5628
|
}
|
|
5495
5629
|
}
|
|
5496
5630
|
async function work() {
|
|
5497
|
-
console.log(
|
|
5631
|
+
console.log(chalk25.bold(`
|
|
5498
5632
|
${ko.work.workTitle}`));
|
|
5499
|
-
console.log(
|
|
5633
|
+
console.log(chalk25.gray("\u2500".repeat(40)));
|
|
5500
5634
|
if (!ensureVhkProject()) return;
|
|
5501
5635
|
if (!passHardStop()) return;
|
|
5502
5636
|
const git3 = gitShort();
|
|
5503
5637
|
console.log("");
|
|
5504
|
-
console.log(
|
|
5638
|
+
console.log(chalk25.cyan("\u{1F4CB} \uBCC0\uACBD\uB41C \uD30C\uC77C"));
|
|
5505
5639
|
printGit(git3);
|
|
5506
5640
|
console.log("");
|
|
5507
|
-
console.log(
|
|
5641
|
+
console.log(chalk25.cyan("\u{1F4D6} \uADDC\uCE59"));
|
|
5508
5642
|
console.log(" \xB7 CLAUDE.md \uAC00 1\uC21C\uC704 \uADDC\uCE59\uC785\uB2C8\uB2E4.");
|
|
5509
|
-
console.log(
|
|
5643
|
+
console.log(chalk25.dim(" \xB7 AGENTS.md \uB294 Codex/\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8\uC6A9 \uCC38\uACE0 \uADDC\uCE59\uC785\uB2C8\uB2E4."));
|
|
5510
5644
|
const goalLine = activeGoalLine();
|
|
5511
5645
|
console.log("");
|
|
5512
|
-
console.log(
|
|
5646
|
+
console.log(chalk25.cyan(`\u{1F3AF} \uD604\uC7AC \uBAA9\uD45C: ${goalLine}`));
|
|
5513
5647
|
const refreshed = await refreshContextQuietly();
|
|
5514
|
-
console.log(
|
|
5515
|
-
const nextTaskPath =
|
|
5516
|
-
if (
|
|
5648
|
+
console.log(chalk25.dim(refreshed ? "\u2699\uFE0F .vhk/context.md \uAC31\uC2E0\uB428" : "\u2699\uFE0F .vhk/context.md \uAC31\uC2E0 \uC0DD\uB7B5(\uAC74\uB108\uB700)"));
|
|
5649
|
+
const nextTaskPath = join8("docs", "state", "next-task.md");
|
|
5650
|
+
if (existsSync13(nextTaskPath)) {
|
|
5517
5651
|
try {
|
|
5518
|
-
const lines =
|
|
5652
|
+
const lines = readFileSync6(nextTaskPath, "utf-8").split(/\r?\n/).slice(0, 12);
|
|
5519
5653
|
console.log("");
|
|
5520
|
-
console.log(
|
|
5521
|
-
for (const l of lines) console.log(
|
|
5654
|
+
console.log(chalk25.cyan("\u{1F4DD} \uB2E4\uC74C \uD560 \uC77C (next-task.md)"));
|
|
5655
|
+
for (const l of lines) console.log(chalk25.dim(` ${l}`));
|
|
5522
5656
|
} catch {
|
|
5523
5657
|
}
|
|
5524
5658
|
}
|
|
@@ -5530,19 +5664,19 @@ ${ko.work.workTitle}`));
|
|
|
5530
5664
|
});
|
|
5531
5665
|
}
|
|
5532
5666
|
async function workHandoff() {
|
|
5533
|
-
console.log(
|
|
5667
|
+
console.log(chalk25.bold(`
|
|
5534
5668
|
${ko.work.handoffTitle}`));
|
|
5535
|
-
console.log(
|
|
5669
|
+
console.log(chalk25.gray("\u2500".repeat(40)));
|
|
5536
5670
|
if (!ensureVhkProject()) return;
|
|
5537
5671
|
if (!passHardStop()) return;
|
|
5538
5672
|
const git3 = gitShort();
|
|
5539
5673
|
console.log("");
|
|
5540
|
-
console.log(
|
|
5674
|
+
console.log(chalk25.cyan("\u{1F4CB} \uBC14\uB010 \uD30C\uC77C"));
|
|
5541
5675
|
printGit(git3);
|
|
5542
5676
|
const prompt = buildHandoffPrompt(git3);
|
|
5543
5677
|
emitPrompt(prompt, "handoff-prompt.md", "\uC911\uB2E8 \uC815\uB9AC \uD504\uB86C\uD504\uD2B8");
|
|
5544
5678
|
console.log("");
|
|
5545
|
-
console.log(
|
|
5679
|
+
console.log(chalk25.dim("\u{1F4A1} \uC774 \uBA85\uB839\uC740 \uCEE4\uBC0B\xB7\uC0AD\uC81C\uB97C \uC808\uB300 \uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC815\uB9AC\xB7\uD310\uB2E8\uC740 Claude \uAC00 \uD569\uB2C8\uB2E4."));
|
|
5546
5680
|
printNextStep({
|
|
5547
5681
|
message: "\uD130\uBBF8\uB110\uC5D0\uC11C claude \uB97C \uC2E4\uD589\uD55C \uB4A4, Ctrl+V \uB85C \uBD99\uC5EC\uB123\uACE0 Enter \uD558\uC138\uC694.",
|
|
5548
5682
|
command: "claude"
|
|
@@ -5550,11 +5684,11 @@ ${ko.work.handoffTitle}`));
|
|
|
5550
5684
|
}
|
|
5551
5685
|
|
|
5552
5686
|
// src/commands/start.ts
|
|
5553
|
-
import
|
|
5687
|
+
import chalk26 from "chalk";
|
|
5554
5688
|
import inquirer11 from "inquirer";
|
|
5555
5689
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
5556
|
-
import { existsSync as
|
|
5557
|
-
import { join as
|
|
5690
|
+
import { existsSync as existsSync14 } from "fs";
|
|
5691
|
+
import { join as join9 } from "path";
|
|
5558
5692
|
var VHK_FOOTPRINT_FILES = [
|
|
5559
5693
|
"CLAUDE.md",
|
|
5560
5694
|
".cursorrules",
|
|
@@ -5563,7 +5697,7 @@ var VHK_FOOTPRINT_FILES = [
|
|
|
5563
5697
|
"docs/PRD.md"
|
|
5564
5698
|
];
|
|
5565
5699
|
function detectExistingFootprint(cwd) {
|
|
5566
|
-
return VHK_FOOTPRINT_FILES.filter((rel) =>
|
|
5700
|
+
return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join9(cwd, rel)));
|
|
5567
5701
|
}
|
|
5568
5702
|
async function runGitInit(cwd) {
|
|
5569
5703
|
try {
|
|
@@ -5592,21 +5726,21 @@ async function runStep(label, fn) {
|
|
|
5592
5726
|
}
|
|
5593
5727
|
}
|
|
5594
5728
|
async function start(options = {}) {
|
|
5595
|
-
console.log(
|
|
5729
|
+
console.log(chalk26.bold(`
|
|
5596
5730
|
${ko.start.title}
|
|
5597
5731
|
`));
|
|
5598
|
-
console.log(
|
|
5599
|
-
console.log(
|
|
5600
|
-
console.log(
|
|
5601
|
-
console.log(
|
|
5602
|
-
console.log(
|
|
5732
|
+
console.log(chalk26.dim(ko.start.intro));
|
|
5733
|
+
console.log(chalk26.dim(` ${ko.start.step1}`));
|
|
5734
|
+
console.log(chalk26.dim(` ${ko.start.step2}`));
|
|
5735
|
+
console.log(chalk26.dim(` ${ko.start.step3}`));
|
|
5736
|
+
console.log(chalk26.dim(` ${ko.start.step4}`));
|
|
5603
5737
|
console.log();
|
|
5604
5738
|
const cwd = process.cwd();
|
|
5605
5739
|
const footprint = detectExistingFootprint(cwd);
|
|
5606
5740
|
if (footprint.length > 0 && !options.yes) {
|
|
5607
|
-
console.log(
|
|
5608
|
-
for (const f of footprint) console.log(
|
|
5609
|
-
console.log(
|
|
5741
|
+
console.log(chalk26.yellow("\u26A0\uFE0F \uC774\uBBF8 VHK \uC124\uCE58 \uD754\uC801\uC774 \uAC10\uC9C0\uB410\uC5B4\uC694:"));
|
|
5742
|
+
for (const f of footprint) console.log(chalk26.dim(` - ${f}`));
|
|
5743
|
+
console.log(chalk26.dim(" \uACC4\uC18D \uC9C4\uD589\uD558\uBA74 \uC77C\uBD80 \uD30C\uC77C(`.cursor/mcp.json`, `.vhk/context.md`)\uC740 \uAC31\uC2E0\xB7\uB36E\uC5B4\uC4F0\uAE30\uB429\uB2C8\uB2E4."));
|
|
5610
5744
|
const { proceedExisting } = await inquirer11.prompt([{
|
|
5611
5745
|
type: "confirm",
|
|
5612
5746
|
name: "proceedExisting",
|
|
@@ -5644,7 +5778,7 @@ ${ko.start.title}
|
|
|
5644
5778
|
await runStep("[3/4] vhk mcp-init", () => mcpInit());
|
|
5645
5779
|
log.step(ko.start.step4Header);
|
|
5646
5780
|
await runStep("[4/4] vhk context", () => context());
|
|
5647
|
-
console.log(
|
|
5781
|
+
console.log(chalk26.bold.green(`
|
|
5648
5782
|
${ko.start.allDone}
|
|
5649
5783
|
`));
|
|
5650
5784
|
printNextStep({
|
|
@@ -5655,9 +5789,9 @@ ${ko.start.allDone}
|
|
|
5655
5789
|
|
|
5656
5790
|
// src/commands/cloud.ts
|
|
5657
5791
|
import fs13 from "fs";
|
|
5658
|
-
import
|
|
5792
|
+
import os3 from "os";
|
|
5659
5793
|
import path14 from "path";
|
|
5660
|
-
import
|
|
5794
|
+
import chalk27 from "chalk";
|
|
5661
5795
|
|
|
5662
5796
|
// src/lib/vhk-cloud.ts
|
|
5663
5797
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
@@ -5675,7 +5809,7 @@ var DEFAULT_CLOUD_EXCLUDES = [
|
|
|
5675
5809
|
".gitignore"
|
|
5676
5810
|
// .vhk/ 내부 gitignore
|
|
5677
5811
|
];
|
|
5678
|
-
var
|
|
5812
|
+
var VHK_DIR2 = ".vhk";
|
|
5679
5813
|
var CLOUD_CONFIG_FILE = "cloud.json";
|
|
5680
5814
|
function loadVhkignore(rootDir) {
|
|
5681
5815
|
const ig = (0, import_ignore.default)();
|
|
@@ -5687,7 +5821,7 @@ function loadVhkignore(rootDir) {
|
|
|
5687
5821
|
return ig;
|
|
5688
5822
|
}
|
|
5689
5823
|
function collectVhkFiles(rootDir, ig = loadVhkignore(rootDir)) {
|
|
5690
|
-
const vhkDir = path13.join(rootDir,
|
|
5824
|
+
const vhkDir = path13.join(rootDir, VHK_DIR2);
|
|
5691
5825
|
let entries;
|
|
5692
5826
|
try {
|
|
5693
5827
|
entries = fs12.readdirSync(vhkDir, { withFileTypes: true });
|
|
@@ -5706,7 +5840,7 @@ function partitionGistFiles(gistFiles, ig) {
|
|
|
5706
5840
|
return { keep, excluded };
|
|
5707
5841
|
}
|
|
5708
5842
|
function readCloudConfig(rootDir) {
|
|
5709
|
-
const p = path13.join(rootDir,
|
|
5843
|
+
const p = path13.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
|
|
5710
5844
|
if (!fs12.existsSync(p)) return null;
|
|
5711
5845
|
try {
|
|
5712
5846
|
const parsed = readJsonFile(p);
|
|
@@ -5719,7 +5853,7 @@ function readCloudConfig(rootDir) {
|
|
|
5719
5853
|
}
|
|
5720
5854
|
}
|
|
5721
5855
|
function writeCloudConfig(rootDir, config) {
|
|
5722
|
-
const vhkDir = path13.join(rootDir,
|
|
5856
|
+
const vhkDir = path13.join(rootDir, VHK_DIR2);
|
|
5723
5857
|
fs12.mkdirSync(vhkDir, { recursive: true });
|
|
5724
5858
|
const p = path13.join(vhkDir, CLOUD_CONFIG_FILE);
|
|
5725
5859
|
fs12.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
@@ -5749,14 +5883,14 @@ ${CLOUD_CONFIG_FILE}
|
|
|
5749
5883
|
function ensureGhReady() {
|
|
5750
5884
|
const ver = safeExecFile("gh", ["--version"]);
|
|
5751
5885
|
if (!ver.ok) {
|
|
5752
|
-
console.log(
|
|
5753
|
-
console.log(
|
|
5886
|
+
console.log(chalk27.red(` ${ko.cloud.noGh}`));
|
|
5887
|
+
console.log(chalk27.dim(" \uC124\uCE58: https://cli.github.com/ (\uC124\uCE58 \uD6C4 `gh auth login`)"));
|
|
5754
5888
|
return false;
|
|
5755
5889
|
}
|
|
5756
5890
|
const auth = safeExecFile("gh", ["auth", "status"]);
|
|
5757
5891
|
if (!auth.ok) {
|
|
5758
|
-
console.log(
|
|
5759
|
-
console.log(
|
|
5892
|
+
console.log(chalk27.red(` ${ko.cloud.noAuth}`));
|
|
5893
|
+
console.log(chalk27.dim(" \uC2E4\uD589: gh auth login (gist \uAD8C\uD55C \uD544\uC694)"));
|
|
5760
5894
|
return false;
|
|
5761
5895
|
}
|
|
5762
5896
|
return true;
|
|
@@ -5769,26 +5903,27 @@ function parseGistId(output) {
|
|
|
5769
5903
|
return null;
|
|
5770
5904
|
}
|
|
5771
5905
|
async function cloudPush() {
|
|
5772
|
-
|
|
5906
|
+
if (!ensureNotHardStopped("cloud push")) return;
|
|
5907
|
+
console.log(chalk27.bold(`
|
|
5773
5908
|
${ko.cloud.pushTitle}
|
|
5774
5909
|
`));
|
|
5775
5910
|
const cwd = process.cwd();
|
|
5776
|
-
if (!fs13.existsSync(path14.join(cwd,
|
|
5777
|
-
console.log(
|
|
5911
|
+
if (!fs13.existsSync(path14.join(cwd, VHK_DIR2))) {
|
|
5912
|
+
console.log(chalk27.yellow(` ${ko.cloud.noVhkDir}`));
|
|
5778
5913
|
return;
|
|
5779
5914
|
}
|
|
5780
5915
|
const ig = loadVhkignore(cwd);
|
|
5781
5916
|
const files = collectVhkFiles(cwd, ig);
|
|
5782
5917
|
if (files.length === 0) {
|
|
5783
|
-
console.log(
|
|
5918
|
+
console.log(chalk27.yellow(` ${ko.cloud.nothingToSync}`));
|
|
5784
5919
|
return;
|
|
5785
5920
|
}
|
|
5786
5921
|
if (!ensureGhReady()) {
|
|
5787
5922
|
process.exitCode = 1;
|
|
5788
5923
|
return;
|
|
5789
5924
|
}
|
|
5790
|
-
const filePaths = files.map((f) => path14.join(cwd,
|
|
5791
|
-
console.log(
|
|
5925
|
+
const filePaths = files.map((f) => path14.join(cwd, VHK_DIR2, f));
|
|
5926
|
+
console.log(chalk27.dim(` \u{1F4E6} \uBC31\uC5C5 \uB300\uC0C1 ${files.length}\uAC1C: ${files.join(", ")}
|
|
5792
5927
|
`));
|
|
5793
5928
|
const existing = readCloudConfig(cwd);
|
|
5794
5929
|
const desc = `vhk .vhk backup \u2014 ${path14.basename(cwd)}`;
|
|
@@ -5800,8 +5935,8 @@ ${ko.cloud.pushTitle}
|
|
|
5800
5935
|
const args = gistFiles.includes(name) ? ["gist", "edit", existing.gistId, "-f", name, src] : ["gist", "edit", existing.gistId, "-a", src];
|
|
5801
5936
|
const res2 = safeExecFile("gh", args);
|
|
5802
5937
|
if (!res2.ok) {
|
|
5803
|
-
console.log(
|
|
5804
|
-
console.log(
|
|
5938
|
+
console.log(chalk27.red(` ${ko.cloud.pushFail}: ${name}`));
|
|
5939
|
+
console.log(chalk27.dim(` ${res2.err}`));
|
|
5805
5940
|
process.exitCode = 1;
|
|
5806
5941
|
return;
|
|
5807
5942
|
}
|
|
@@ -5816,15 +5951,15 @@ ${ko.cloud.pushTitle}
|
|
|
5816
5951
|
if (!purgeFailed.includes(name)) purgeFailed.push(name);
|
|
5817
5952
|
}
|
|
5818
5953
|
}
|
|
5819
|
-
console.log(
|
|
5820
|
-
console.log(
|
|
5954
|
+
console.log(chalk27.green.bold(` ${ko.cloud.pushDone}`));
|
|
5955
|
+
console.log(chalk27.dim(` gist: ${existing.gistId} (\uAC31\uC2E0)`));
|
|
5821
5956
|
if (excluded.length > 0) {
|
|
5822
5957
|
const purged = excluded.filter((n) => !purgeFailed.includes(n));
|
|
5823
5958
|
if (purged.length > 0) {
|
|
5824
|
-
console.log(
|
|
5959
|
+
console.log(chalk27.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${purged.length}\uAC1C gist \uC5D0\uC11C \uC81C\uAC70: ${purged.join(", ")}`));
|
|
5825
5960
|
}
|
|
5826
5961
|
if (purgeFailed.length > 0) {
|
|
5827
|
-
console.log(
|
|
5962
|
+
console.log(chalk27.yellow(` \u26A0\uFE0F \uC81C\uC678 \uB300\uC0C1 \uC81C\uAC70 \uC2E4\uD328: ${purgeFailed.join(", ")} (\uC218\uB3D9 \uC81C\uAC70 \uAD8C\uC7A5 \u2014 pull \uC2DC\uC5D4 \uBCF5\uC6D0 \uC548 \uB428)`));
|
|
5828
5963
|
}
|
|
5829
5964
|
}
|
|
5830
5965
|
printPushNext();
|
|
@@ -5832,32 +5967,32 @@ ${ko.cloud.pushTitle}
|
|
|
5832
5967
|
}
|
|
5833
5968
|
const res = safeExecFile("gh", ["gist", "create", "--desc", desc, ...filePaths]);
|
|
5834
5969
|
if (!res.ok) {
|
|
5835
|
-
console.log(
|
|
5836
|
-
console.log(
|
|
5970
|
+
console.log(chalk27.red(` ${ko.cloud.pushFail}`));
|
|
5971
|
+
console.log(chalk27.dim(` ${res.err || res.out}`));
|
|
5837
5972
|
process.exitCode = 1;
|
|
5838
5973
|
return;
|
|
5839
5974
|
}
|
|
5840
5975
|
const gistId = parseGistId(res.out);
|
|
5841
5976
|
if (!gistId) {
|
|
5842
|
-
console.log(
|
|
5843
|
-
console.log(
|
|
5977
|
+
console.log(chalk27.red(` ${ko.cloud.pushFail} \u2014 gist id \uD30C\uC2F1 \uC2E4\uD328`));
|
|
5978
|
+
console.log(chalk27.dim(` \uCD9C\uB825: ${res.out}`));
|
|
5844
5979
|
process.exitCode = 1;
|
|
5845
5980
|
return;
|
|
5846
5981
|
}
|
|
5847
5982
|
writeCloudConfig(cwd, { gistId });
|
|
5848
|
-
console.log(
|
|
5849
|
-
console.log(
|
|
5983
|
+
console.log(chalk27.green.bold(` ${ko.cloud.pushDone}`));
|
|
5984
|
+
console.log(chalk27.dim(` gist: ${gistId} (\uC2E0\uADDC, secret) \u2192 .vhk/cloud.json \uC800\uC7A5`));
|
|
5850
5985
|
printPushNext();
|
|
5851
5986
|
}
|
|
5852
5987
|
async function cloudPull(gistIdArg) {
|
|
5853
|
-
console.log(
|
|
5988
|
+
console.log(chalk27.bold(`
|
|
5854
5989
|
${ko.cloud.pullTitle}
|
|
5855
5990
|
`));
|
|
5856
5991
|
const cwd = process.cwd();
|
|
5857
5992
|
const gistId = gistIdArg || readCloudConfig(cwd)?.gistId;
|
|
5858
5993
|
if (!gistId) {
|
|
5859
|
-
console.log(
|
|
5860
|
-
console.log(
|
|
5994
|
+
console.log(chalk27.yellow(` ${ko.cloud.noGistId}`));
|
|
5995
|
+
console.log(chalk27.dim(" \uC0AC\uC6A9\uBC95: vhk cloud pull <gistId> (\uB610\uB294 cloud.json \uC774 \uC788\uB294 \uACF3\uC5D0\uC11C \uC2E4\uD589)"));
|
|
5861
5996
|
return;
|
|
5862
5997
|
}
|
|
5863
5998
|
if (!ensureGhReady()) {
|
|
@@ -5866,34 +6001,34 @@ ${ko.cloud.pullTitle}
|
|
|
5866
6001
|
}
|
|
5867
6002
|
const allNames = listGistFiles(gistId);
|
|
5868
6003
|
if (allNames.length === 0) {
|
|
5869
|
-
console.log(
|
|
6004
|
+
console.log(chalk27.red(` ${ko.cloud.pullFail} \u2014 gist \uBE44\uC5C8\uAC70\uB098 \uC811\uADFC \uBD88\uAC00: ${gistId}`));
|
|
5870
6005
|
process.exitCode = 1;
|
|
5871
6006
|
return;
|
|
5872
6007
|
}
|
|
5873
6008
|
const { keep: names, excluded: skipped } = partitionGistFiles(allNames, loadVhkignore(cwd));
|
|
5874
6009
|
if (skipped.length > 0) {
|
|
5875
|
-
console.log(
|
|
6010
|
+
console.log(chalk27.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${skipped.length}\uAC1C \uBCF5\uC6D0 \uC2A4\uD0B5: ${skipped.join(", ")}`));
|
|
5876
6011
|
}
|
|
5877
6012
|
if (names.length === 0) {
|
|
5878
|
-
console.log(
|
|
6013
|
+
console.log(chalk27.yellow(` \uBCF5\uC6D0 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (gist \uD30C\uC77C\uC774 \uBAA8\uB450 \uC81C\uC678 \uADDC\uCE59\uC5D0 \uD574\uB2F9).`));
|
|
5879
6014
|
return;
|
|
5880
6015
|
}
|
|
5881
|
-
const vhkDir = path14.join(cwd,
|
|
6016
|
+
const vhkDir = path14.join(cwd, VHK_DIR2);
|
|
5882
6017
|
fs13.mkdirSync(vhkDir, { recursive: true });
|
|
5883
6018
|
let restored = 0;
|
|
5884
6019
|
for (const name of names) {
|
|
5885
6020
|
const res = safeExecFile("gh", ["gist", "view", gistId, "-f", name, "--raw"]);
|
|
5886
6021
|
if (!res.ok) {
|
|
5887
|
-
console.log(
|
|
5888
|
-
console.log(
|
|
6022
|
+
console.log(chalk27.red(` ${ko.cloud.pullFail}: ${name}`));
|
|
6023
|
+
console.log(chalk27.dim(` ${res.err}`));
|
|
5889
6024
|
continue;
|
|
5890
6025
|
}
|
|
5891
6026
|
fs13.writeFileSync(path14.join(vhkDir, name), ensureTrailingNewline(res.out), "utf-8");
|
|
5892
6027
|
restored++;
|
|
5893
6028
|
}
|
|
5894
6029
|
writeCloudConfig(cwd, { gistId });
|
|
5895
|
-
console.log(
|
|
5896
|
-
console.log(
|
|
6030
|
+
console.log(chalk27.green.bold(` ${ko.cloud.pullDone}`));
|
|
6031
|
+
console.log(chalk27.dim(` ${restored}\uAC1C \uD30C\uC77C \uBCF5\uC6D0 (gist: ${gistId})`));
|
|
5897
6032
|
printNextStep({
|
|
5898
6033
|
message: "\uD074\uB77C\uC6B0\uB4DC\uC5D0\uC11C .vhk/ \uBCF5\uC6D0 \uC644\uB8CC!",
|
|
5899
6034
|
command: "vhk \uB9E5\uB77D",
|
|
@@ -5905,7 +6040,7 @@ function purgeExcludedFromGist(gistId, names) {
|
|
|
5905
6040
|
const body = JSON.stringify({
|
|
5906
6041
|
files: Object.fromEntries(names.map((n) => [n, null]))
|
|
5907
6042
|
});
|
|
5908
|
-
const tmp = path14.join(
|
|
6043
|
+
const tmp = path14.join(os3.tmpdir(), `vhk-gist-purge-${process.pid}.json`);
|
|
5909
6044
|
try {
|
|
5910
6045
|
fs13.writeFileSync(tmp, body, "utf-8");
|
|
5911
6046
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
@@ -5941,7 +6076,7 @@ function printPushNext() {
|
|
|
5941
6076
|
}
|
|
5942
6077
|
|
|
5943
6078
|
// src/commands/help.ts
|
|
5944
|
-
import
|
|
6079
|
+
import chalk28 from "chalk";
|
|
5945
6080
|
var QUICK_ACTIONS = [
|
|
5946
6081
|
{ say: "\uC0C1\uD0DC \uC54C\uB824\uC918", does: "vhk status" },
|
|
5947
6082
|
{ say: "\uBB50 \uBC14\uB00C\uC5C8\uC5B4?", does: "vhk diff" },
|
|
@@ -5955,21 +6090,21 @@ var QUICK_ACTIONS = [
|
|
|
5955
6090
|
{ say: "\uC804\uCCB4 \uBA85\uB839\uC5B4 \uBCF4\uAE30", does: "vhk --help" }
|
|
5956
6091
|
];
|
|
5957
6092
|
function quickActions() {
|
|
5958
|
-
console.log(
|
|
5959
|
-
console.log(
|
|
6093
|
+
console.log(chalk28.bold("\n\u{1F9ED} VHK \u2014 \uC774\uB807\uAC8C \uB9D0\uD558\uBA74 \uB429\uB2C8\uB2E4 (quick actions)"));
|
|
6094
|
+
console.log(chalk28.gray("\u2500".repeat(40)));
|
|
5960
6095
|
for (const a of QUICK_ACTIONS) {
|
|
5961
|
-
console.log(` "${
|
|
6096
|
+
console.log(` "${chalk28.cyan(a.say)}" \u2192 ${chalk28.dim(a.does)}`);
|
|
5962
6097
|
}
|
|
5963
|
-
console.log(
|
|
6098
|
+
console.log(chalk28.gray("\n \uC804\uCCB4 \uBA85\uB839\uC740 `vhk --help` \uB610\uB294 COMMANDS.md \uB97C \uBCF4\uC138\uC694."));
|
|
5964
6099
|
console.log("");
|
|
5965
6100
|
}
|
|
5966
6101
|
|
|
5967
6102
|
// src/commands/mode.ts
|
|
5968
|
-
import
|
|
6103
|
+
import chalk29 from "chalk";
|
|
5969
6104
|
|
|
5970
6105
|
// src/lib/config.ts
|
|
5971
|
-
import { existsSync as
|
|
5972
|
-
import { join as
|
|
6106
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
6107
|
+
import { join as join10 } from "path";
|
|
5973
6108
|
|
|
5974
6109
|
// src/lib/safety-mode.ts
|
|
5975
6110
|
var SAFETY_MODES = ["lite", "standard", "strict"];
|
|
@@ -5985,11 +6120,11 @@ function isSafetyMode(value) {
|
|
|
5985
6120
|
|
|
5986
6121
|
// src/lib/config.ts
|
|
5987
6122
|
var CONFIG_DIR = ".vhk";
|
|
5988
|
-
var CONFIG_PATH =
|
|
6123
|
+
var CONFIG_PATH = join10(CONFIG_DIR, "config.json");
|
|
5989
6124
|
var DEFAULT_CONFIG = { safetyMode: DEFAULT_SAFETY_MODE };
|
|
5990
6125
|
function readConfig(rootDir = process.cwd()) {
|
|
5991
|
-
const full =
|
|
5992
|
-
if (!
|
|
6126
|
+
const full = join10(rootDir, CONFIG_PATH);
|
|
6127
|
+
if (!existsSync15(full)) return { ...DEFAULT_CONFIG };
|
|
5993
6128
|
try {
|
|
5994
6129
|
const raw = readJsonFile(full);
|
|
5995
6130
|
return {
|
|
@@ -6000,23 +6135,23 @@ function readConfig(rootDir = process.cwd()) {
|
|
|
6000
6135
|
}
|
|
6001
6136
|
}
|
|
6002
6137
|
function writeConfig(config, rootDir = process.cwd()) {
|
|
6003
|
-
|
|
6004
|
-
|
|
6138
|
+
mkdirSync10(join10(rootDir, CONFIG_DIR), { recursive: true });
|
|
6139
|
+
writeFileSync9(join10(rootDir, CONFIG_PATH), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
6005
6140
|
}
|
|
6006
6141
|
|
|
6007
6142
|
// src/commands/mode.ts
|
|
6008
6143
|
async function mode(target) {
|
|
6009
|
-
console.log(
|
|
6010
|
-
console.log(
|
|
6144
|
+
console.log(chalk29.bold("\n\u{1F6E1}\uFE0F Safety Mode"));
|
|
6145
|
+
console.log(chalk29.gray("\u2500".repeat(40)));
|
|
6011
6146
|
const current = readConfig().safetyMode;
|
|
6012
6147
|
if (!target) {
|
|
6013
|
-
console.log(
|
|
6014
|
-
\uD604\uC7AC \uBAA8\uB4DC: ${
|
|
6015
|
-
console.log(
|
|
6148
|
+
console.log(chalk29.cyan(`
|
|
6149
|
+
\uD604\uC7AC \uBAA8\uB4DC: ${chalk29.bold(current)}`));
|
|
6150
|
+
console.log(chalk29.dim(` ${SAFETY_MODE_DESC[current]}`));
|
|
6016
6151
|
console.log("");
|
|
6017
6152
|
for (const m of SAFETY_MODES) {
|
|
6018
6153
|
const mark = m === current ? "\u25CF" : "\u25CB";
|
|
6019
|
-
console.log(` ${mark} ${m.padEnd(9)} ${
|
|
6154
|
+
console.log(` ${mark} ${m.padEnd(9)} ${chalk29.dim(SAFETY_MODE_DESC[m])}`);
|
|
6020
6155
|
}
|
|
6021
6156
|
printNextStep({
|
|
6022
6157
|
message: "\uBAA8\uB4DC\uB97C \uBC14\uAFB8\uB824\uBA74:",
|
|
@@ -6026,23 +6161,23 @@ async function mode(target) {
|
|
|
6026
6161
|
return;
|
|
6027
6162
|
}
|
|
6028
6163
|
if (!isSafetyMode(target)) {
|
|
6029
|
-
console.log(
|
|
6164
|
+
console.log(chalk29.red(`
|
|
6030
6165
|
\u274C \uC54C \uC218 \uC5C6\uB294 \uBAA8\uB4DC: ${target}`));
|
|
6031
|
-
console.log(
|
|
6166
|
+
console.log(chalk29.dim(` \uAC00\uB2A5: ${SAFETY_MODES.join(" | ")}`));
|
|
6032
6167
|
process.exitCode = 1;
|
|
6033
6168
|
return;
|
|
6034
6169
|
}
|
|
6035
6170
|
writeConfig({ ...readConfig(), safetyMode: target });
|
|
6036
|
-
console.log(
|
|
6037
|
-
\u2705 Safety Mode \u2192 ${
|
|
6038
|
-
console.log(
|
|
6171
|
+
console.log(chalk29.green(`
|
|
6172
|
+
\u2705 Safety Mode \u2192 ${chalk29.bold(target)}`));
|
|
6173
|
+
console.log(chalk29.dim(` ${SAFETY_MODE_DESC[target]}`));
|
|
6039
6174
|
}
|
|
6040
6175
|
|
|
6041
6176
|
// src/commands/verify.ts
|
|
6042
6177
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
6043
|
-
import { existsSync as
|
|
6044
|
-
import { join as
|
|
6045
|
-
import
|
|
6178
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11 } from "fs";
|
|
6179
|
+
import { join as join11 } from "path";
|
|
6180
|
+
import chalk30 from "chalk";
|
|
6046
6181
|
|
|
6047
6182
|
// src/commands/verify-report.ts
|
|
6048
6183
|
function escapeHtml(text) {
|
|
@@ -6150,13 +6285,13 @@ ${actions}
|
|
|
6150
6285
|
|
|
6151
6286
|
// src/commands/verify.ts
|
|
6152
6287
|
var REPORT_SCHEMA_VERSION = 1;
|
|
6153
|
-
var REPORT_DIR_REL =
|
|
6154
|
-
var REPORT_PATH_REL =
|
|
6155
|
-
var REPORT_HTML_PATH_REL =
|
|
6288
|
+
var REPORT_DIR_REL = join11(".vhk", "reports");
|
|
6289
|
+
var REPORT_PATH_REL = join11(REPORT_DIR_REL, "latest.json");
|
|
6290
|
+
var REPORT_HTML_PATH_REL = join11(REPORT_DIR_REL, "latest.html");
|
|
6156
6291
|
var SHIM = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
6157
6292
|
function detectPm(cwd) {
|
|
6158
|
-
if (
|
|
6159
|
-
if (
|
|
6293
|
+
if (existsSync16(join11(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
6294
|
+
if (existsSync16(join11(cwd, "yarn.lock"))) return "yarn";
|
|
6160
6295
|
return "npm";
|
|
6161
6296
|
}
|
|
6162
6297
|
function execGate(cmd, args, cwd) {
|
|
@@ -6199,8 +6334,8 @@ function runScriptGate(id, label, cwd, pm, argvFor) {
|
|
|
6199
6334
|
};
|
|
6200
6335
|
}
|
|
6201
6336
|
function readPackageScripts(cwd) {
|
|
6202
|
-
const pkgPath =
|
|
6203
|
-
if (!
|
|
6337
|
+
const pkgPath = join11(cwd, "package.json");
|
|
6338
|
+
if (!existsSync16(pkgPath)) return {};
|
|
6204
6339
|
try {
|
|
6205
6340
|
const pkg = readJsonFile(pkgPath);
|
|
6206
6341
|
return pkg.scripts ?? {};
|
|
@@ -6215,7 +6350,7 @@ function runGates(cwd) {
|
|
|
6215
6350
|
gates.push(
|
|
6216
6351
|
runScriptGate("typecheck", "tsc --noEmit", cwd, pm, () => {
|
|
6217
6352
|
if (scripts.typecheck) return ["run", "typecheck"];
|
|
6218
|
-
if (
|
|
6353
|
+
if (existsSync16(join11(cwd, "tsconfig.json"))) return pm === "npm" ? ["exec", "--", "tsc", "--noEmit"] : ["exec", "tsc", "--noEmit"];
|
|
6219
6354
|
return null;
|
|
6220
6355
|
})
|
|
6221
6356
|
);
|
|
@@ -6294,10 +6429,10 @@ function buildReport(gates, generatedAt, date) {
|
|
|
6294
6429
|
function verifyEvidence(cwd = process.cwd()) {
|
|
6295
6430
|
const gates = runGates(cwd);
|
|
6296
6431
|
const report = buildReport(gates, (/* @__PURE__ */ new Date()).toISOString(), localDate());
|
|
6297
|
-
const dir =
|
|
6298
|
-
|
|
6299
|
-
const path15 =
|
|
6300
|
-
|
|
6432
|
+
const dir = join11(cwd, REPORT_DIR_REL);
|
|
6433
|
+
mkdirSync11(dir, { recursive: true });
|
|
6434
|
+
const path15 = join11(cwd, REPORT_PATH_REL);
|
|
6435
|
+
atomicWriteFile(path15, JSON.stringify(report, null, 2) + "\n");
|
|
6301
6436
|
try {
|
|
6302
6437
|
ensureVhkIgnored(cwd, "reports/");
|
|
6303
6438
|
} catch {
|
|
@@ -6305,44 +6440,44 @@ function verifyEvidence(cwd = process.cwd()) {
|
|
|
6305
6440
|
return { report, path: REPORT_PATH_REL };
|
|
6306
6441
|
}
|
|
6307
6442
|
var STATUS_BADGE = {
|
|
6308
|
-
PASS:
|
|
6309
|
-
WARN:
|
|
6310
|
-
FAIL:
|
|
6443
|
+
PASS: chalk30.green.bold("PASS"),
|
|
6444
|
+
WARN: chalk30.yellow.bold("WARN"),
|
|
6445
|
+
FAIL: chalk30.red.bold("FAIL")
|
|
6311
6446
|
};
|
|
6312
6447
|
async function renderVerifyReport(cwd, opts) {
|
|
6313
|
-
const jsonPath =
|
|
6448
|
+
const jsonPath = join11(cwd, REPORT_PATH_REL);
|
|
6314
6449
|
let report;
|
|
6315
|
-
if (
|
|
6450
|
+
if (existsSync16(jsonPath)) {
|
|
6316
6451
|
try {
|
|
6317
6452
|
report = readJsonFile(jsonPath);
|
|
6318
6453
|
} catch {
|
|
6319
|
-
console.log(
|
|
6454
|
+
console.log(chalk30.yellow(" \u26A0\uFE0F \uAE30\uC874 latest.json \uC190\uC0C1 \u2014 verify \uC7AC\uC2E4\uD589\uC73C\uB85C \uC99D\uAC70\uB97C \uB2E4\uC2DC \uB9CC\uB4ED\uB2C8\uB2E4."));
|
|
6320
6455
|
report = verifyEvidence(cwd).report;
|
|
6321
6456
|
}
|
|
6322
6457
|
} else {
|
|
6323
|
-
console.log(
|
|
6458
|
+
console.log(chalk30.dim(" latest.json \uC5C6\uC74C \u2014 verify 1\uD68C \uC120\uC2E4\uD589\uC73C\uB85C \uC99D\uAC70\uB97C \uB9CC\uB4ED\uB2C8\uB2E4."));
|
|
6324
6459
|
report = verifyEvidence(cwd).report;
|
|
6325
6460
|
}
|
|
6326
6461
|
const html = renderReportHtml(report);
|
|
6327
|
-
const htmlPath =
|
|
6462
|
+
const htmlPath = join11(cwd, REPORT_HTML_PATH_REL);
|
|
6328
6463
|
try {
|
|
6329
|
-
|
|
6330
|
-
|
|
6464
|
+
mkdirSync11(join11(cwd, REPORT_DIR_REL), { recursive: true });
|
|
6465
|
+
atomicWriteFile(htmlPath, html);
|
|
6331
6466
|
} catch (e) {
|
|
6332
6467
|
console.error(
|
|
6333
|
-
|
|
6468
|
+
chalk30.red(` \u274C \uB9AC\uD3EC\uD2B8 HTML \uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (${REPORT_HTML_PATH_REL}): ${e instanceof Error ? e.message : String(e)}`)
|
|
6334
6469
|
);
|
|
6335
|
-
console.error(
|
|
6470
|
+
console.error(chalk30.dim(" \uD574\uB2F9 \uACBD\uB85C\uC758 \uC4F0\uAE30 \uAD8C\uD55C\uC744 \uD655\uC778\uD558\uC138\uC694."));
|
|
6336
6471
|
process.exitCode = 1;
|
|
6337
6472
|
return;
|
|
6338
6473
|
}
|
|
6339
|
-
console.log(
|
|
6474
|
+
console.log(chalk30.bold("\n\u{1F50E} \uAC80\uC99D \uB9AC\uD3EC\uD2B8 (verify --report)"));
|
|
6340
6475
|
console.log(` \uACB0\uACFC: ${STATUS_BADGE[report.status]}`);
|
|
6341
|
-
console.log(
|
|
6476
|
+
console.log(chalk30.dim(` \u{1F4C4} HTML: ${REPORT_HTML_PATH_REL}`));
|
|
6342
6477
|
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
6343
6478
|
if (opts.open) {
|
|
6344
6479
|
if (isInteractive()) openReportInBrowser(htmlPath);
|
|
6345
|
-
else console.log(
|
|
6480
|
+
else console.log(chalk30.dim(" (\uBE44\uB300\uD654\uD615/CI/MCP \u2014 --open \uC790\uB3D9 \uC2A4\uD0B5)"));
|
|
6346
6481
|
return;
|
|
6347
6482
|
}
|
|
6348
6483
|
printNextStep({
|
|
@@ -6360,8 +6495,8 @@ function openReportInBrowser(filePath) {
|
|
|
6360
6495
|
} else {
|
|
6361
6496
|
result = safeExecFile("xdg-open", [filePath]);
|
|
6362
6497
|
}
|
|
6363
|
-
if (result.ok) console.log(
|
|
6364
|
-
else console.log(
|
|
6498
|
+
if (result.ok) console.log(chalk30.green(" \u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
6499
|
+
else console.log(chalk30.yellow(" \u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC704 \uD30C\uC77C\uC744 \uC9C1\uC811 \uC5EC\uC138\uC694."));
|
|
6365
6500
|
}
|
|
6366
6501
|
async function verify(opts = {}) {
|
|
6367
6502
|
if (!ensureNotHardStopped("verify")) return;
|
|
@@ -6376,21 +6511,21 @@ async function verify(opts = {}) {
|
|
|
6376
6511
|
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
6377
6512
|
return;
|
|
6378
6513
|
}
|
|
6379
|
-
console.log(
|
|
6380
|
-
console.log(
|
|
6514
|
+
console.log(chalk30.bold("\n\u{1F50E} \uAC80\uC99D \uBB36\uC74C (verify)"));
|
|
6515
|
+
console.log(chalk30.gray("\u2500".repeat(40)));
|
|
6381
6516
|
const mode2 = readConfig().safetyMode;
|
|
6382
|
-
console.log(
|
|
6383
|
-
const icon = (s2) => s2 === "pass" ?
|
|
6517
|
+
console.log(chalk30.dim(` \uD604\uC7AC Safety Mode: ${mode2} \u2014 ${SAFETY_MODE_DESC[mode2]}`));
|
|
6518
|
+
const icon = (s2) => s2 === "pass" ? chalk30.green("\u2713") : s2 === "fail" ? chalk30.red("\u2717") : chalk30.yellow("\u2298");
|
|
6384
6519
|
for (const g of report.gates) {
|
|
6385
|
-
const tail = g.detail ?
|
|
6520
|
+
const tail = g.detail ? chalk30.dim(` \u2014 ${g.detail}`) : "";
|
|
6386
6521
|
console.log(` ${icon(g.status)} ${g.label}${tail}`);
|
|
6387
6522
|
}
|
|
6388
6523
|
const s = report.summary;
|
|
6389
6524
|
console.log(
|
|
6390
6525
|
`
|
|
6391
|
-
\uACB0\uACFC: ${STATUS_BADGE[report.status]} ` +
|
|
6526
|
+
\uACB0\uACFC: ${STATUS_BADGE[report.status]} ` + chalk30.dim(`(pass ${s.pass} / fail ${s.fail} / skip ${s.skip}, \uCD1D ${s.total})`)
|
|
6392
6527
|
);
|
|
6393
|
-
console.log(
|
|
6528
|
+
console.log(chalk30.dim(` \u{1F4C4} \uC99D\uAC70: ${path15}`));
|
|
6394
6529
|
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
6395
6530
|
if (report.status === "FAIL") {
|
|
6396
6531
|
printNextStep({
|
|
@@ -6409,9 +6544,9 @@ async function verify(opts = {}) {
|
|
|
6409
6544
|
}
|
|
6410
6545
|
|
|
6411
6546
|
// src/commands/review.ts
|
|
6412
|
-
import { existsSync as
|
|
6413
|
-
import { join as
|
|
6414
|
-
import
|
|
6547
|
+
import { existsSync as existsSync17 } from "fs";
|
|
6548
|
+
import { join as join12 } from "path";
|
|
6549
|
+
import chalk31 from "chalk";
|
|
6415
6550
|
var GOALS_DIR2 = "goals";
|
|
6416
6551
|
var COVERAGE_MIN = 0.5;
|
|
6417
6552
|
var STALE_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -6543,22 +6678,22 @@ function resolveGoal(optId, goals) {
|
|
|
6543
6678
|
return goals.find((g) => g.frontmatter.id === id) ?? null;
|
|
6544
6679
|
}
|
|
6545
6680
|
var CONFIDENCE_LABEL = {
|
|
6546
|
-
low:
|
|
6547
|
-
medium:
|
|
6548
|
-
high:
|
|
6681
|
+
low: chalk31.red.bold("\uB0AE\uC74C (\uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC \uB610\uB294 \uC644\uB8CC \uC8FC\uC7A5 \uC5C6\uC74C)"),
|
|
6682
|
+
medium: chalk31.yellow.bold("\uC911\uAC04 (\uCEE4\uBC84\uB9AC\uC9C0/\uC2E0\uC120\uB3C4 \uBD80\uC871 \u2014 \uC99D\uAC70 \uC5C6\uC74C \u2260 \uD1B5\uACFC)"),
|
|
6683
|
+
high: chalk31.green.bold("\uB192\uC74C (\uC758\uC2EC 0 + \uCEE4\uBC84\uB9AC\uC9C0\xB7\uC2E0\uC120\uB3C4 \uCDA9\uBD84 \u2014 \uB2E8 \uBCF4\uC7A5 \uC544\uB2D8)")
|
|
6549
6684
|
};
|
|
6550
6685
|
async function review(opts = {}) {
|
|
6551
6686
|
if (!ensureNotHardStopped("review")) return;
|
|
6552
6687
|
const cwd = process.cwd();
|
|
6553
6688
|
const goals = listGoals(GOALS_DIR2);
|
|
6554
6689
|
if (goals.length === 0) {
|
|
6555
|
-
console.error(
|
|
6690
|
+
console.error(chalk31.yellow(" \u26A0\uFE0F goals/ \uC5D0 goal \uC774 \uC5C6\uC2B5\uB2C8\uB2E4. vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
|
|
6556
6691
|
process.exitCode = 1;
|
|
6557
6692
|
return;
|
|
6558
6693
|
}
|
|
6559
6694
|
const goal = resolveGoal(opts.id, goals);
|
|
6560
6695
|
if (!goal || typeof goal.frontmatter.id !== "number") {
|
|
6561
|
-
console.error(
|
|
6696
|
+
console.error(chalk31.red(` \u274C \uB300\uC0C1 goal \uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4${opts.id ? ` (--id ${opts.id})` : " (active goal \uC5C6\uC74C)"}.`));
|
|
6562
6697
|
process.exitCode = 1;
|
|
6563
6698
|
return;
|
|
6564
6699
|
}
|
|
@@ -6566,11 +6701,11 @@ async function review(opts = {}) {
|
|
|
6566
6701
|
const goalStatus = goal.frontmatter.status ?? "NOT_STARTED";
|
|
6567
6702
|
const checks = parseCompletionChecks(goal.body);
|
|
6568
6703
|
if (opts.id === void 0 && goalStatus === "NOT_STARTED") {
|
|
6569
|
-
console.error(
|
|
6704
|
+
console.error(chalk31.yellow(` \u26A0\uFE0F active goal ${goalId} \uAC00 NOT_STARTED \u2014 \uC644\uB8CC \uC8FC\uC7A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uAC80\uC99D \uB300\uC0C1\uC740 --id \uB85C \uC9C0\uC815\uD558\uC138\uC694.`));
|
|
6570
6705
|
}
|
|
6571
|
-
const jsonPath =
|
|
6572
|
-
if (!
|
|
6573
|
-
console.error(
|
|
6706
|
+
const jsonPath = join12(cwd, REPORT_PATH_REL);
|
|
6707
|
+
if (!existsSync17(jsonPath)) {
|
|
6708
|
+
console.error(chalk31.yellow(` \u26A0\uFE0F \uC99D\uAC70 \uBD80\uC7AC \u2014 ${REPORT_PATH_REL} \uC5C6\uC74C. review \uB97C \uC911\uB2E8\uD569\uB2C8\uB2E4(\uC0C8 \uC99D\uAC70 \uC548 \uB9CC\uB4E6).`));
|
|
6574
6709
|
printNextStep({
|
|
6575
6710
|
message: "\uC99D\uAC70(latest.json)\uAC00 \uC788\uC5B4\uC57C review \uAC00 \uAD50\uCC28\uAC80\uC99D\uD569\uB2C8\uB2E4:",
|
|
6576
6711
|
command: "vhk verify",
|
|
@@ -6583,7 +6718,7 @@ async function review(opts = {}) {
|
|
|
6583
6718
|
try {
|
|
6584
6719
|
report = readJsonFile(jsonPath);
|
|
6585
6720
|
} catch {
|
|
6586
|
-
console.error(
|
|
6721
|
+
console.error(chalk31.red(` \u274C ${REPORT_PATH_REL} \uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(\uC190\uC0C1). vhk verify \uB85C \uC7AC\uC0DD\uC131\uD558\uC138\uC694.`));
|
|
6587
6722
|
process.exitCode = 1;
|
|
6588
6723
|
return;
|
|
6589
6724
|
}
|
|
@@ -6595,44 +6730,44 @@ async function review(opts = {}) {
|
|
|
6595
6730
|
goalStatus,
|
|
6596
6731
|
reportStatus: report.status
|
|
6597
6732
|
};
|
|
6598
|
-
console.log(
|
|
6733
|
+
console.log(chalk31.bold(`
|
|
6599
6734
|
\u{1F52C} \uC801\uB300\uC801 \uC790\uAE30\uAC80\uC99D (review) \u2014 Goal ${goalId}`));
|
|
6600
|
-
console.log(
|
|
6735
|
+
console.log(chalk31.gray("\u2500".repeat(44)));
|
|
6601
6736
|
console.log(
|
|
6602
|
-
|
|
6737
|
+
chalk31.dim(
|
|
6603
6738
|
` goal status: ${goalStatus} \xB7 verify: ${report.status} \xB7 \uCCB4\uD06C\uB41C \uC644\uB8CC\uC870\uAC74 ${result.checkedCount}\uAC1C (\uB9E4\uD551 ${result.mappedCount} / \uBBF8\uAC80\uC99D ${result.unmappedCount}, coverage ${result.coverage * 100 | 0}%)`
|
|
6604
6739
|
)
|
|
6605
6740
|
);
|
|
6606
|
-
console.log(
|
|
6741
|
+
console.log(chalk31.dim(` \uC99D\uAC70 \uC2E0\uC120\uB3C4: ${result.freshness.note}`));
|
|
6607
6742
|
if (result.checkedCount === 0) {
|
|
6608
|
-
console.log(
|
|
6743
|
+
console.log(chalk31.yellow("\n \u26AA \uC644\uB8CC \uC8FC\uC7A5 \uC5C6\uC74C(\uCCB4\uD06C\uB41C \uC644\uB8CC\uC870\uAC74 0\uAC1C) \u2014 \uC2EC\uBB38\uD560 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4(vacuous)."));
|
|
6609
6744
|
}
|
|
6610
6745
|
if (result.suspicions.length > 0) {
|
|
6611
|
-
console.log(
|
|
6746
|
+
console.log(chalk31.red.bold(`
|
|
6612
6747
|
\u{1F6A9} \uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC ${result.suspicions.length}\uAC74`));
|
|
6613
|
-
for (const s of result.suspicions) console.log(
|
|
6748
|
+
for (const s of result.suspicions) console.log(chalk31.red(` \u2717 ${s.check}
|
|
6614
6749
|
\u21B3 ${s.reason}`));
|
|
6615
6750
|
}
|
|
6616
6751
|
if (result.gaps.length > 0) {
|
|
6617
|
-
console.log(
|
|
6752
|
+
console.log(chalk31.yellow.bold(`
|
|
6618
6753
|
\u26A0\uFE0F \uBBF8\uAC80\uC99D(unmapped) ${result.gaps.length}\uAC74 \u2014 \uAC8C\uC774\uD2B8\uB85C \uC790\uB3D9 \uD655\uC778 \uBD88\uAC00`));
|
|
6619
|
-
for (const g of result.gaps) console.log(
|
|
6754
|
+
for (const g of result.gaps) console.log(chalk31.yellow(` ? ${g.check}`));
|
|
6620
6755
|
}
|
|
6621
6756
|
if (result.checkedCount > 0 && result.suspicions.length === 0 && result.gaps.length === 0) {
|
|
6622
|
-
console.log(
|
|
6757
|
+
console.log(chalk31.green("\n \u2713 \uCCB4\uD06C\uB41C \uC644\uB8CC\uC870\uAC74\uC774 \uBAA8\uB450 \uAC8C\uC774\uD2B8 \uC99D\uAC70\uB85C \uB4B7\uBC1B\uCE68\uB428."));
|
|
6623
6758
|
}
|
|
6624
6759
|
console.log(`
|
|
6625
6760
|
\uC2E0\uB8B0\uB3C4: ${CONFIDENCE_LABEL[result.confidence]}`);
|
|
6626
|
-
console.log(
|
|
6761
|
+
console.log(chalk31.yellow(`
|
|
6627
6762
|
${result.disclaimer}`));
|
|
6628
6763
|
let mergeOk = false;
|
|
6629
6764
|
try {
|
|
6630
6765
|
const merged = { ...report, review: result };
|
|
6631
|
-
|
|
6766
|
+
atomicWriteFile(jsonPath, JSON.stringify(merged, null, 2) + "\n");
|
|
6632
6767
|
mergeOk = true;
|
|
6633
|
-
console.log(
|
|
6768
|
+
console.log(chalk31.dim(` \u{1F4C4} \uD310\uC815 \uBCD1\uD569: ${REPORT_PATH_REL} (review \uC139\uC158)`));
|
|
6634
6769
|
} catch (e) {
|
|
6635
|
-
console.error(
|
|
6770
|
+
console.error(chalk31.red(` \u274C review \uD310\uC815 \uAE30\uB85D \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`));
|
|
6636
6771
|
}
|
|
6637
6772
|
if (!mergeOk) {
|
|
6638
6773
|
process.exitCode = 1;
|
|
@@ -6653,8 +6788,8 @@ ${result.disclaimer}`));
|
|
|
6653
6788
|
cursorHint: "\uC644\uB8CC\uC870\uAC74 \uCC44\uC6CC\uC918"
|
|
6654
6789
|
});
|
|
6655
6790
|
} else if (result.suspicions.length > 0) {
|
|
6656
|
-
console.log(
|
|
6657
|
-
console.log(
|
|
6791
|
+
console.log(chalk31.dim("\n AI \uC7AC\uC9C8\uBB38 \uD504\uB86C\uD504\uD2B8:"));
|
|
6792
|
+
console.log(chalk31.cyan(result.reprompt.split("\n").map((l) => ` ${l}`).join("\n")));
|
|
6658
6793
|
printNextStep({
|
|
6659
6794
|
message: "\uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC\uC73C\uB85C \uC2E4\uD328(exit 1) \u2014 \uC99D\uAC70 \uBCF4\uAC15 \uD6C4 \uB2E4\uC2DC \uAC80\uC99D\uD558\uC138\uC694:",
|
|
6660
6795
|
command: "vhk verify",
|
|
@@ -6668,8 +6803,8 @@ ${result.disclaimer}`));
|
|
|
6668
6803
|
cursorHint: "goal \uC644\uB8CC \uCC98\uB9AC\uD574\uC918"
|
|
6669
6804
|
});
|
|
6670
6805
|
} else {
|
|
6671
|
-
console.log(
|
|
6672
|
-
console.log(
|
|
6806
|
+
console.log(chalk31.dim("\n AI \uC7AC\uC9C8\uBB38 \uD504\uB86C\uD504\uD2B8:"));
|
|
6807
|
+
console.log(chalk31.cyan(result.reprompt.split("\n").map((l) => ` ${l}`).join("\n")));
|
|
6673
6808
|
printNextStep({
|
|
6674
6809
|
message: `\uC99D\uAC70 \uBD88\uCDA9\uBD84(\uC2E0\uB8B0\uB3C4 ${result.confidence}) \u2014 goal done \uAE08\uC9C0. \uCEE4\uBC84\uB9AC\uC9C0/\uC2E0\uC120\uB3C4 \uBCF4\uAC15 \uD6C4 \uC7AC\uAC80\uC99D:`,
|
|
6675
6810
|
command: "vhk verify",
|
|
@@ -6679,12 +6814,12 @@ ${result.disclaimer}`));
|
|
|
6679
6814
|
}
|
|
6680
6815
|
|
|
6681
6816
|
// src/commands/mission.ts
|
|
6682
|
-
import { existsSync as
|
|
6683
|
-
import { join as
|
|
6684
|
-
import
|
|
6817
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync12, rmSync as rmSync3 } from "fs";
|
|
6818
|
+
import { join as join13 } from "path";
|
|
6819
|
+
import chalk32 from "chalk";
|
|
6685
6820
|
import inquirer12 from "inquirer";
|
|
6686
6821
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
6687
|
-
var MISSION_PATH_REL =
|
|
6822
|
+
var MISSION_PATH_REL = join13(".vhk", "mission.json");
|
|
6688
6823
|
var MISSION_SCHEMA_VERSION = 1;
|
|
6689
6824
|
var MISSION_DISCLAIMER = "\u26A0\uFE0F mission check \uB294 \uACBD\uB85C glob \uAE30\uC900\uC785\uB2C8\uB2E4 \u2014 objective \uC758\uBBF8 \uBD80\uD569\uC740 \uAC80\uC99D\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(\uC2E0\uB8B0\uB3C4 \uC2E0\uD638, \uBCF4\uC7A5 \uC544\uB2D8).";
|
|
6690
6825
|
function globToRegExp(glob) {
|
|
@@ -6732,8 +6867,8 @@ function checkMission(changedFiles, mission) {
|
|
|
6732
6867
|
return { violations, warnings, disclaimer: MISSION_DISCLAIMER };
|
|
6733
6868
|
}
|
|
6734
6869
|
function readMission(cwd = process.cwd()) {
|
|
6735
|
-
const p =
|
|
6736
|
-
if (!
|
|
6870
|
+
const p = join13(cwd, MISSION_PATH_REL);
|
|
6871
|
+
if (!existsSync18(p)) return null;
|
|
6737
6872
|
try {
|
|
6738
6873
|
const m = readJsonFile(p);
|
|
6739
6874
|
if (m && typeof m.objective === "string") return m;
|
|
@@ -6743,8 +6878,8 @@ function readMission(cwd = process.cwd()) {
|
|
|
6743
6878
|
}
|
|
6744
6879
|
}
|
|
6745
6880
|
function writeMission(cwd, mission) {
|
|
6746
|
-
|
|
6747
|
-
|
|
6881
|
+
mkdirSync12(join13(cwd, ".vhk"), { recursive: true });
|
|
6882
|
+
atomicWriteFile(join13(cwd, MISSION_PATH_REL), JSON.stringify(mission, null, 2) + "\n");
|
|
6748
6883
|
}
|
|
6749
6884
|
async function collectChangedFiles(cwd) {
|
|
6750
6885
|
const status2 = await simpleGit3(cwd).status();
|
|
@@ -6765,7 +6900,7 @@ async function missionSet(opts = {}) {
|
|
|
6765
6900
|
objective = ans.obj.trim();
|
|
6766
6901
|
}
|
|
6767
6902
|
if (!objective) {
|
|
6768
|
-
console.error(
|
|
6903
|
+
console.error(chalk32.red(' \u274C objective \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. --objective "..." \uB85C \uC9C0\uC815\uD558\uC138\uC694(\uBE44\uB300\uD654\uD615).'));
|
|
6769
6904
|
process.exitCode = 1;
|
|
6770
6905
|
return;
|
|
6771
6906
|
}
|
|
@@ -6784,12 +6919,12 @@ async function missionSet(opts = {}) {
|
|
|
6784
6919
|
try {
|
|
6785
6920
|
writeMission(cwd, mission);
|
|
6786
6921
|
} catch (e) {
|
|
6787
|
-
console.error(
|
|
6922
|
+
console.error(chalk32.red(` \u274C mission.json \uAE30\uB85D \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`));
|
|
6788
6923
|
process.exitCode = 1;
|
|
6789
6924
|
return;
|
|
6790
6925
|
}
|
|
6791
|
-
console.log(
|
|
6792
|
-
console.log(
|
|
6926
|
+
console.log(chalk32.bold("\n\u{1F3AF} \uBBF8\uC158 \uACC4\uC57D \uC800\uC7A5"));
|
|
6927
|
+
console.log(chalk32.dim(` \u{1F4C4} ${MISSION_PATH_REL}`));
|
|
6793
6928
|
console.log(` objective: ${mission.objective}`);
|
|
6794
6929
|
console.log(` scope: ${mission.scope.length ? mission.scope.join(", ") : "(\uC81C\uD55C \uC5C6\uC74C)"}`);
|
|
6795
6930
|
console.log(` forbidden: ${mission.forbidden.length ? mission.forbidden.join(", ") : "(\uC5C6\uC74C)"}`);
|
|
@@ -6799,64 +6934,65 @@ async function missionShow() {
|
|
|
6799
6934
|
const cwd = process.cwd();
|
|
6800
6935
|
const mission = readMission(cwd);
|
|
6801
6936
|
if (!mission) {
|
|
6802
|
-
console.error(
|
|
6937
|
+
console.error(chalk32.yellow(" \u26A0\uFE0F \uBBF8\uC158 \uACC4\uC57D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (.vhk/mission.json)."));
|
|
6803
6938
|
printNextStep({ message: "\uBA3C\uC800 \uBBF8\uC158\uC744 \uC120\uC5B8\uD558\uC138\uC694:", command: 'vhk mission set --objective "..."', cursorHint: "\uBBF8\uC158 \uC815\uD574\uC918" });
|
|
6804
6939
|
process.exitCode = 1;
|
|
6805
6940
|
return;
|
|
6806
6941
|
}
|
|
6807
|
-
console.log(
|
|
6942
|
+
console.log(chalk32.bold("\n\u{1F3AF} \uD604\uC7AC \uBBF8\uC158 \uACC4\uC57D"));
|
|
6808
6943
|
console.log(` objective: ${mission.objective}`);
|
|
6809
6944
|
console.log(` scope: ${mission.scope.length ? mission.scope.join(", ") : "(\uC81C\uD55C \uC5C6\uC74C)"}`);
|
|
6810
6945
|
console.log(` forbidden: ${mission.forbidden.length ? mission.forbidden.join(", ") : "(\uC5C6\uC74C)"}`);
|
|
6811
|
-
console.log(
|
|
6946
|
+
console.log(chalk32.dim(` \uC0DD\uC131 ${mission.createdAt} \xB7 \uAC31\uC2E0 ${mission.updatedAt}`));
|
|
6812
6947
|
}
|
|
6813
6948
|
async function missionCheck() {
|
|
6814
6949
|
const cwd = process.cwd();
|
|
6815
6950
|
const mission = readMission(cwd);
|
|
6816
6951
|
if (!mission) {
|
|
6817
|
-
console.error(
|
|
6952
|
+
console.error(chalk32.yellow(" \u26A0\uFE0F \uBBF8\uC158 \uACC4\uC57D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uBA3C\uC800 vhk mission set \uC73C\uB85C \uC120\uC5B8\uD558\uC138\uC694."));
|
|
6818
6953
|
process.exitCode = 1;
|
|
6819
6954
|
return;
|
|
6820
6955
|
}
|
|
6821
6956
|
const changed = await collectChangedFiles(cwd);
|
|
6822
6957
|
const result = checkMission(changed, mission);
|
|
6823
|
-
console.log(
|
|
6824
|
-
console.log(
|
|
6958
|
+
console.log(chalk32.bold("\n\u{1F3AF} \uBBF8\uC158 \uACC4\uC57D \uAC80\uC99D (mission check)"));
|
|
6959
|
+
console.log(chalk32.dim(` objective: ${mission.objective} \xB7 \uBCC0\uACBD \uD30C\uC77C ${changed.length}\uAC1C`));
|
|
6825
6960
|
if (result.violations.length > 0) {
|
|
6826
|
-
console.log(
|
|
6961
|
+
console.log(chalk32.red.bold(`
|
|
6827
6962
|
\u{1F6AB} forbidden \uC704\uBC18 ${result.violations.length}\uAC74`));
|
|
6828
|
-
for (const v of result.violations) console.log(
|
|
6963
|
+
for (const v of result.violations) console.log(chalk32.red(` \u2717 ${v.file} (\uAE08\uC9C0: ${v.pattern})`));
|
|
6829
6964
|
}
|
|
6830
6965
|
if (result.warnings.length > 0) {
|
|
6831
|
-
console.log(
|
|
6966
|
+
console.log(chalk32.yellow.bold(`
|
|
6832
6967
|
\u26A0\uFE0F scope \uBC16 \uBCC0\uACBD ${result.warnings.length}\uAC74 (\uACBD\uACE0)`));
|
|
6833
|
-
for (const w of result.warnings) console.log(
|
|
6968
|
+
for (const w of result.warnings) console.log(chalk32.yellow(` ? ${w.file}`));
|
|
6834
6969
|
}
|
|
6835
6970
|
if (result.violations.length === 0 && result.warnings.length === 0) {
|
|
6836
|
-
console.log(
|
|
6971
|
+
console.log(chalk32.green("\n \u2713 \uBCC0\uACBD\uC774 \uACC4\uC57D(scope/forbidden) \uC548\uC785\uB2C8\uB2E4."));
|
|
6837
6972
|
}
|
|
6838
|
-
console.log(
|
|
6973
|
+
console.log(chalk32.yellow(`
|
|
6839
6974
|
${result.disclaimer}`));
|
|
6840
6975
|
process.exitCode = result.violations.length > 0 ? 1 : 0;
|
|
6841
6976
|
}
|
|
6842
6977
|
async function missionClear() {
|
|
6978
|
+
if (!ensureNotHardStopped("mission clear")) return;
|
|
6843
6979
|
const cwd = process.cwd();
|
|
6844
|
-
const p =
|
|
6845
|
-
if (!
|
|
6846
|
-
console.log(
|
|
6980
|
+
const p = join13(cwd, MISSION_PATH_REL);
|
|
6981
|
+
if (!existsSync18(p)) {
|
|
6982
|
+
console.log(chalk32.dim(" \uBBF8\uC158 \uACC4\uC57D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC9C0\uC6B8 \uAC83 \uC5C6\uC74C."));
|
|
6847
6983
|
return;
|
|
6848
6984
|
}
|
|
6849
6985
|
try {
|
|
6850
|
-
|
|
6851
|
-
console.log(
|
|
6986
|
+
rmSync3(p);
|
|
6987
|
+
console.log(chalk32.green(" \u2705 \uBBF8\uC158 \uACC4\uC57D \uC0AD\uC81C\uB428 (.vhk/mission.json)."));
|
|
6852
6988
|
} catch (e) {
|
|
6853
|
-
console.error(
|
|
6989
|
+
console.error(chalk32.red(` \u274C \uC0AD\uC81C \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`));
|
|
6854
6990
|
process.exitCode = 1;
|
|
6855
6991
|
}
|
|
6856
6992
|
}
|
|
6857
6993
|
|
|
6858
6994
|
// src/commands/pattern.ts
|
|
6859
|
-
import
|
|
6995
|
+
import chalk33 from "chalk";
|
|
6860
6996
|
var MIN_TAG_FREQ = 3;
|
|
6861
6997
|
var STOPWORDS = /* @__PURE__ */ new Set(["the", "a", "an", "is", "are", "and", "or", "in", "on", "at", "to", "for", "of", "with", "it", "was", "be"]);
|
|
6862
6998
|
function tokenize(text) {
|
|
@@ -6974,14 +7110,14 @@ function reconcilePatterns(patterns, candidates, now) {
|
|
|
6974
7110
|
async function patternDetect(opts = {}) {
|
|
6975
7111
|
const minFreq = opts.min !== void 0 ? parseInt(opts.min, 10) : MIN_TAG_FREQ;
|
|
6976
7112
|
if (!Number.isFinite(minFreq) || minFreq < 1) {
|
|
6977
|
-
console.log(
|
|
7113
|
+
console.log(chalk33.red("\u274C --min \uC740 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4."));
|
|
6978
7114
|
process.exitCode = 1;
|
|
6979
7115
|
return;
|
|
6980
7116
|
}
|
|
6981
7117
|
const cwd = process.cwd();
|
|
6982
7118
|
const loaded = loadForMutation(cwd);
|
|
6983
7119
|
if (!loaded.ok) {
|
|
6984
|
-
console.log(
|
|
7120
|
+
console.log(chalk33.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uAC10\uC9C0 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874). \uBC31\uC5C5 \uD655\uC778 \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694."));
|
|
6985
7121
|
process.exitCode = 1;
|
|
6986
7122
|
return;
|
|
6987
7123
|
}
|
|
@@ -6997,26 +7133,26 @@ async function patternDetect(opts = {}) {
|
|
|
6997
7133
|
console.log(JSON.stringify(active2, null, 2));
|
|
6998
7134
|
return;
|
|
6999
7135
|
}
|
|
7000
|
-
console.log(
|
|
7001
|
-
console.log(
|
|
7002
|
-
console.log(
|
|
7003
|
-
console.log(
|
|
7136
|
+
console.log(chalk33.bold("\n\u{1F50D} " + t("pattern.detectTitle")));
|
|
7137
|
+
console.log(chalk33.gray("\u2500".repeat(40)));
|
|
7138
|
+
console.log(chalk33.dim(` \uC784\uACC4: ${minFreq}\uD68C \uC774\uC0C1 \xB7 active failures+successes \uC785\uB825`));
|
|
7139
|
+
console.log(chalk33.dim(` \uD6C4\uBCF4: ${candidates.length}\uAC1C \uAC10\uC9C0 (\uCD94\uAC00 ${added} / \uAC31\uC2E0 ${updated})`));
|
|
7004
7140
|
const active = mem.patterns.filter((p) => p.status !== "archived");
|
|
7005
7141
|
if (active.length === 0) {
|
|
7006
|
-
console.log(
|
|
7007
|
-
console.log(
|
|
7008
|
-
console.log(
|
|
7142
|
+
console.log(chalk33.yellow("\n\u{1F4ED} \uC784\uACC4 \uC774\uC0C1 \uBC18\uBCF5 \uD328\uD134\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
7143
|
+
console.log(chalk33.gray(` failures/successes \uAC00 ${minFreq}\uAC1C \uC774\uC0C1 \uC313\uC774\uBA74 \uAC10\uC9C0\uB429\uB2C8\uB2E4.`));
|
|
7144
|
+
console.log(chalk33.gray(" --min N \uC73C\uB85C \uC784\uACC4\uB97C \uB0AE\uCD9C \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
|
|
7009
7145
|
return;
|
|
7010
7146
|
}
|
|
7011
|
-
console.log(
|
|
7147
|
+
console.log(chalk33.cyan(`
|
|
7012
7148
|
\uD328\uD134 \uD6C4\uBCF4 ${active.length}\uAC1C:
|
|
7013
7149
|
`));
|
|
7014
7150
|
for (const p of active.slice(0, 20)) {
|
|
7015
7151
|
const icon = p.kind === "avoid" ? "\u26A0\uFE0F " : "\u2705";
|
|
7016
7152
|
console.log(` [${p.id}] ${icon} (${p.kind}/${p.axis}) "${p.signal}" \u2014 ${p.count}\uAC74`);
|
|
7017
7153
|
const preview = p.sources.slice(0, 5).join(", ") + (p.sources.length > 5 ? ` \uC678 ${p.sources.length - 5}\uAC74` : "");
|
|
7018
|
-
console.log(
|
|
7019
|
-
console.log(
|
|
7154
|
+
console.log(chalk33.dim(` \uADFC\uAC70: ${preview}`));
|
|
7155
|
+
console.log(chalk33.dim(` ${p.summary}`));
|
|
7020
7156
|
}
|
|
7021
7157
|
printNextStep({
|
|
7022
7158
|
message: `\uD328\uD134 \uAC10\uC9C0 \uC644\uB8CC! ${active.length}\uAC1C \uD6C4\uBCF4.`,
|
|
@@ -7026,8 +7162,8 @@ async function patternDetect(opts = {}) {
|
|
|
7026
7162
|
});
|
|
7027
7163
|
}
|
|
7028
7164
|
async function patternList(opts = {}) {
|
|
7029
|
-
console.log(
|
|
7030
|
-
console.log(
|
|
7165
|
+
console.log(chalk33.bold("\n\u{1F50D} " + t("pattern.listTitle")));
|
|
7166
|
+
console.log(chalk33.gray("\u2500".repeat(40)));
|
|
7031
7167
|
const mem = readMemory(process.cwd());
|
|
7032
7168
|
let patterns = mem.patterns;
|
|
7033
7169
|
if (!opts.all) patterns = patterns.filter((p) => p.status !== "archived");
|
|
@@ -7039,51 +7175,52 @@ async function patternList(opts = {}) {
|
|
|
7039
7175
|
return;
|
|
7040
7176
|
}
|
|
7041
7177
|
if (patterns.length === 0) {
|
|
7042
|
-
console.log(
|
|
7043
|
-
console.log(
|
|
7178
|
+
console.log(chalk33.yellow("\n\u{1F4ED} \uD45C\uC2DC\uD560 \uD328\uD134\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
7179
|
+
console.log(chalk33.gray(" vhk pattern detect \uB85C \uAC10\uC9C0 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
|
|
7044
7180
|
return;
|
|
7045
7181
|
}
|
|
7046
|
-
console.log(
|
|
7182
|
+
console.log(chalk33.cyan(`
|
|
7047
7183
|
${patterns.length}\uAC1C${opts.all ? " (\uBCF4\uAD00 \uD3EC\uD568)" : " (\uD65C\uC131)"}:
|
|
7048
7184
|
`));
|
|
7049
7185
|
for (const p of patterns) {
|
|
7050
7186
|
const icon = p.kind === "avoid" ? "\u26A0\uFE0F " : "\u2705";
|
|
7051
7187
|
const archived = p.status === "archived" ? "\u{1F4E6} " : "";
|
|
7052
7188
|
console.log(` [${p.id}] ${archived}${icon} (${p.kind}/${p.axis}) "${p.signal}" \u2014 ${p.count}\uAC74`);
|
|
7053
|
-
if (p.summary) console.log(
|
|
7054
|
-
if (p.tags?.length) console.log(
|
|
7189
|
+
if (p.summary) console.log(chalk33.dim(` ${p.summary}`));
|
|
7190
|
+
if (p.tags?.length) console.log(chalk33.blue(` \u{1F3F7}\uFE0F ${p.tags.join(", ")}`));
|
|
7055
7191
|
}
|
|
7056
7192
|
}
|
|
7057
7193
|
async function patternDismiss(idStr) {
|
|
7194
|
+
if (!ensureNotHardStopped("pattern dismiss")) return;
|
|
7058
7195
|
if (!idStr?.trim()) {
|
|
7059
|
-
console.log(
|
|
7196
|
+
console.log(chalk33.red("\u274C \uD328\uD134 id \uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694. \uC608: vhk pattern dismiss p1"));
|
|
7060
7197
|
process.exitCode = 1;
|
|
7061
7198
|
return;
|
|
7062
7199
|
}
|
|
7063
7200
|
const cwd = process.cwd();
|
|
7064
7201
|
const loaded = loadForMutation(cwd);
|
|
7065
7202
|
if (!loaded.ok) {
|
|
7066
|
-
console.log(
|
|
7203
|
+
console.log(chalk33.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 dismiss \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
|
|
7067
7204
|
process.exitCode = 1;
|
|
7068
7205
|
return;
|
|
7069
7206
|
}
|
|
7070
7207
|
const mem = loaded.mem;
|
|
7071
7208
|
const pattern = mem.patterns.find((p) => p.id === idStr.trim());
|
|
7072
7209
|
if (!pattern) {
|
|
7073
|
-
console.log(
|
|
7074
|
-
console.log(
|
|
7210
|
+
console.log(chalk33.red(`\u274C \uD328\uD134 '${idStr}' \uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
|
|
7211
|
+
console.log(chalk33.gray(" vhk pattern list --all \uB85C \uBAA9\uB85D \uD655\uC778."));
|
|
7075
7212
|
process.exitCode = 1;
|
|
7076
7213
|
return;
|
|
7077
7214
|
}
|
|
7078
7215
|
if (pattern.status === "archived") {
|
|
7079
|
-
console.log(
|
|
7216
|
+
console.log(chalk33.dim(` \uC774\uBBF8 \uBCF4\uAD00\uB41C \uD328\uD134\uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C: ${pattern.id}`));
|
|
7080
7217
|
return;
|
|
7081
7218
|
}
|
|
7082
7219
|
pattern.status = "archived";
|
|
7083
7220
|
writeMemory(cwd, mem);
|
|
7084
|
-
console.log(
|
|
7221
|
+
console.log(chalk33.green(`
|
|
7085
7222
|
\u{1F4E6} \uD328\uD134 dismiss(\uBCF4\uAD00)\uB428: [${pattern.id}] ${pattern.summary ?? pattern.signal}`));
|
|
7086
|
-
console.log(
|
|
7223
|
+
console.log(chalk33.dim(" \uC624\uD0D0\uC73C\uB85C \uD310\uB2E8. detect \uC7AC\uC2E4\uD589 \uC2DC \uC7AC\uC81C\uC548 \uC548 \uB428."));
|
|
7087
7224
|
printNextStep({
|
|
7088
7225
|
message: "\uD328\uD134 dismiss \uC644\uB8CC!",
|
|
7089
7226
|
command: "vhk pattern list",
|
|
@@ -7092,11 +7229,11 @@ async function patternDismiss(idStr) {
|
|
|
7092
7229
|
}
|
|
7093
7230
|
|
|
7094
7231
|
// src/commands/evolve.ts
|
|
7095
|
-
import { existsSync as
|
|
7096
|
-
import { join as
|
|
7097
|
-
import
|
|
7232
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync13, writeFileSync as writeFileSync10, readFileSync as readFileSync7, copyFileSync as copyFileSync2, renameSync as renameSync2, rmSync as rmSync4 } from "fs";
|
|
7233
|
+
import { join as join14 } from "path";
|
|
7234
|
+
import chalk34 from "chalk";
|
|
7098
7235
|
import inquirer13 from "inquirer";
|
|
7099
|
-
var QUEUE_PATH_REL =
|
|
7236
|
+
var QUEUE_PATH_REL = join14(".vhk", "evolve", "queue.json");
|
|
7100
7237
|
function buildDraft(p) {
|
|
7101
7238
|
const axisLabel = p.axis === "tag" ? `\uD0DC\uADF8 '${p.signal}'` : `\uD0A4\uC6CC\uB4DC '${p.signal}'`;
|
|
7102
7239
|
const countDesc = `${p.count}\uAC74 \uBC18\uBCF5`;
|
|
@@ -7140,10 +7277,10 @@ function stripBomStr(s) {
|
|
|
7140
7277
|
return s.charCodeAt(0) === 65279 ? s.slice(1) : s;
|
|
7141
7278
|
}
|
|
7142
7279
|
function readQueue(cwd) {
|
|
7143
|
-
const p =
|
|
7144
|
-
if (!
|
|
7280
|
+
const p = join14(cwd, QUEUE_PATH_REL);
|
|
7281
|
+
if (!existsSync19(p)) return { version: 1, items: [] };
|
|
7145
7282
|
try {
|
|
7146
|
-
const raw = stripBomStr(
|
|
7283
|
+
const raw = stripBomStr(readFileSync7(p, "utf-8"));
|
|
7147
7284
|
const parsed = JSON.parse(raw);
|
|
7148
7285
|
if (!parsed || !Array.isArray(parsed.items)) return { version: 1, items: [] };
|
|
7149
7286
|
return parsed;
|
|
@@ -7152,15 +7289,15 @@ function readQueue(cwd) {
|
|
|
7152
7289
|
}
|
|
7153
7290
|
}
|
|
7154
7291
|
function writeQueue(cwd, queue) {
|
|
7155
|
-
const p =
|
|
7156
|
-
|
|
7292
|
+
const p = join14(cwd, QUEUE_PATH_REL);
|
|
7293
|
+
mkdirSync13(join14(cwd, ".vhk", "evolve"), { recursive: true });
|
|
7157
7294
|
const tmpPath = p + ".tmp";
|
|
7158
|
-
|
|
7295
|
+
writeFileSync10(tmpPath, JSON.stringify(queue, null, 2) + "\n", "utf-8");
|
|
7159
7296
|
try {
|
|
7160
7297
|
renameSync2(tmpPath, p);
|
|
7161
7298
|
} catch (err) {
|
|
7162
7299
|
try {
|
|
7163
|
-
|
|
7300
|
+
rmSync4(tmpPath, { force: true });
|
|
7164
7301
|
} catch {
|
|
7165
7302
|
}
|
|
7166
7303
|
throw err;
|
|
@@ -7184,8 +7321,8 @@ function checkApplyRef(pattern, queueItems) {
|
|
|
7184
7321
|
}
|
|
7185
7322
|
async function evolveSuggest(opts = {}) {
|
|
7186
7323
|
const cwd = process.cwd();
|
|
7187
|
-
if (!
|
|
7188
|
-
console.log(
|
|
7324
|
+
if (!existsSync19(join14(cwd, "RULES.md"))) {
|
|
7325
|
+
console.log(chalk34.yellow("\n\u26A0\uFE0F " + t("evolve.noRules")));
|
|
7189
7326
|
process.exitCode = 1;
|
|
7190
7327
|
return;
|
|
7191
7328
|
}
|
|
@@ -7196,10 +7333,10 @@ async function evolveSuggest(opts = {}) {
|
|
|
7196
7333
|
if (newItems.length === 0 && !opts.json) {
|
|
7197
7334
|
const activeAvoid = patterns.filter((p) => p.kind === "avoid" && p.status === "active");
|
|
7198
7335
|
if (activeAvoid.length === 0) {
|
|
7199
|
-
console.log(
|
|
7336
|
+
console.log(chalk34.yellow("\n\u{1F4ED} " + t("evolve.noPatterns")));
|
|
7200
7337
|
return;
|
|
7201
7338
|
}
|
|
7202
|
-
console.log(
|
|
7339
|
+
console.log(chalk34.dim("\n " + t("evolve.allSuggested")));
|
|
7203
7340
|
return;
|
|
7204
7341
|
}
|
|
7205
7342
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7212,16 +7349,16 @@ async function evolveSuggest(opts = {}) {
|
|
|
7212
7349
|
console.log(JSON.stringify(pending2, null, 2));
|
|
7213
7350
|
return;
|
|
7214
7351
|
}
|
|
7215
|
-
console.log(
|
|
7216
|
-
console.log(
|
|
7217
|
-
console.log(
|
|
7352
|
+
console.log(chalk34.bold("\n\u{1F504} " + t("evolve.suggestTitle")));
|
|
7353
|
+
console.log(chalk34.gray("\u2500".repeat(40)));
|
|
7354
|
+
console.log(chalk34.dim(" " + t("evolve.newCandidates", newItems.length)));
|
|
7218
7355
|
const pending = queue.items.filter((i) => i.status === "pending");
|
|
7219
|
-
console.log(
|
|
7356
|
+
console.log(chalk34.cyan(`
|
|
7220
7357
|
\uD6C4\uBCF4 ${pending.length}\uAC1C:
|
|
7221
7358
|
`));
|
|
7222
7359
|
for (const item of pending) {
|
|
7223
7360
|
console.log(` [${item.id}] (${item.status}) \uD328\uD134 ${item.patternId} \u2192 rule`);
|
|
7224
|
-
console.log(
|
|
7361
|
+
console.log(chalk34.dim(` \uCD08\uC548: ${item.draft}`));
|
|
7225
7362
|
}
|
|
7226
7363
|
printNextStep({
|
|
7227
7364
|
message: `\uC9C4\uD654 \uD6C4\uBCF4 ${pending.length}\uAC1C \uC0DD\uC131\uB428!`,
|
|
@@ -7242,11 +7379,11 @@ async function evolveList(opts = {}) {
|
|
|
7242
7379
|
console.log(JSON.stringify(items, null, 2));
|
|
7243
7380
|
return;
|
|
7244
7381
|
}
|
|
7245
|
-
console.log(
|
|
7246
|
-
console.log(
|
|
7382
|
+
console.log(chalk34.bold("\n\u{1F504} " + t("evolve.listTitle")));
|
|
7383
|
+
console.log(chalk34.gray("\u2500".repeat(40)));
|
|
7247
7384
|
if (items.length === 0) {
|
|
7248
|
-
console.log(
|
|
7249
|
-
console.log(
|
|
7385
|
+
console.log(chalk34.yellow("\n\u{1F4ED} " + t("evolve.noQueue")));
|
|
7386
|
+
console.log(chalk34.gray(" " + t("evolve.suggestHint")));
|
|
7250
7387
|
return;
|
|
7251
7388
|
}
|
|
7252
7389
|
const STATUS_ICON3 = {
|
|
@@ -7254,68 +7391,69 @@ async function evolveList(opts = {}) {
|
|
|
7254
7391
|
rejected: "\u274C",
|
|
7255
7392
|
applied: "\u2705"
|
|
7256
7393
|
};
|
|
7257
|
-
console.log(
|
|
7394
|
+
console.log(chalk34.cyan(`
|
|
7258
7395
|
${items.length}\uAC1C:
|
|
7259
7396
|
`));
|
|
7260
7397
|
for (const item of items) {
|
|
7261
7398
|
console.log(` [${item.id}] ${STATUS_ICON3[item.status]} (${item.status}) \u2192 ${item.draft}`);
|
|
7262
|
-
if (item.appliedAt) console.log(
|
|
7399
|
+
if (item.appliedAt) console.log(chalk34.dim(` \uBC18\uC601: ${item.appliedAt}`));
|
|
7263
7400
|
}
|
|
7264
7401
|
}
|
|
7265
7402
|
async function evolveApply(idStr) {
|
|
7403
|
+
if (!ensureNotHardStopped("evolve apply")) return;
|
|
7266
7404
|
if (!ensureInteractive("apply\uB294 TTY \uD655\uC778\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uD130\uBBF8\uB110\uC5D0\uC11C \uC9C1\uC811 \uC2E4\uD589\uD558\uC138\uC694.")) return;
|
|
7267
7405
|
const cwd = process.cwd();
|
|
7268
|
-
const rulesPath =
|
|
7269
|
-
if (!
|
|
7270
|
-
console.log(
|
|
7406
|
+
const rulesPath = join14(cwd, "RULES.md");
|
|
7407
|
+
if (!existsSync19(rulesPath)) {
|
|
7408
|
+
console.log(chalk34.red("\n\u274C " + t("evolve.noRules")));
|
|
7271
7409
|
process.exitCode = 1;
|
|
7272
7410
|
return;
|
|
7273
7411
|
}
|
|
7274
7412
|
const queue = readQueue(cwd);
|
|
7275
7413
|
const item = queue.items.find((i) => i.id === idStr?.trim());
|
|
7276
7414
|
if (!item) {
|
|
7277
|
-
console.log(
|
|
7415
|
+
console.log(chalk34.red("\n\u274C " + t("evolve.notFound", idStr ?? "")));
|
|
7278
7416
|
process.exitCode = 1;
|
|
7279
7417
|
return;
|
|
7280
7418
|
}
|
|
7281
7419
|
if (item.status === "applied") {
|
|
7282
|
-
console.log(
|
|
7420
|
+
console.log(chalk34.yellow("\n\u26A0\uFE0F " + t("evolve.alreadyApplied")));
|
|
7283
7421
|
process.exitCode = 1;
|
|
7284
7422
|
return;
|
|
7285
7423
|
}
|
|
7286
7424
|
const hasUnresolved = queue.items.some((i) => i.status === "applied");
|
|
7287
7425
|
if (hasUnresolved) {
|
|
7288
|
-
console.log(
|
|
7426
|
+
console.log(chalk34.red("\n\u274C " + t("evolve.pendingApplyExists")));
|
|
7289
7427
|
process.exitCode = 1;
|
|
7290
7428
|
return;
|
|
7291
7429
|
}
|
|
7292
7430
|
const memLoaded = loadForMutation(cwd);
|
|
7293
7431
|
if (!memLoaded.ok) {
|
|
7294
|
-
console.log(
|
|
7432
|
+
console.log(chalk34.red("\n\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 apply \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
|
|
7295
7433
|
process.exitCode = 1;
|
|
7296
7434
|
return;
|
|
7297
7435
|
}
|
|
7298
7436
|
const srcPattern = memLoaded.mem.patterns.find((p) => p.id === item.patternId);
|
|
7299
7437
|
const refResult = checkApplyRef(srcPattern, queue.items);
|
|
7300
7438
|
if (refResult === "dismissed") {
|
|
7301
|
-
console.log(
|
|
7439
|
+
console.log(chalk34.red("\n\u274C " + t("evolve.dismissed")));
|
|
7302
7440
|
process.exitCode = 1;
|
|
7303
7441
|
return;
|
|
7304
7442
|
}
|
|
7305
7443
|
if (refResult === "already-applied") {
|
|
7306
|
-
console.log(
|
|
7444
|
+
console.log(chalk34.red("\n\u274C " + t("evolve.alreadyAppliedPattern")));
|
|
7307
7445
|
process.exitCode = 1;
|
|
7308
7446
|
return;
|
|
7309
7447
|
}
|
|
7310
|
-
const rulesContent =
|
|
7448
|
+
const rulesContent = readFileSync7(rulesPath, "utf-8");
|
|
7311
7449
|
if (isDuplicateRule(rulesContent, item.draft)) {
|
|
7312
|
-
console.log(
|
|
7450
|
+
console.log(chalk34.yellow("\n\u26A0\uFE0F " + t("evolve.duplicateRule", item.draft)));
|
|
7313
7451
|
return;
|
|
7314
7452
|
}
|
|
7315
|
-
console.log(
|
|
7316
|
-
console.log(
|
|
7317
|
-
console.log(
|
|
7318
|
-
console.log(
|
|
7453
|
+
console.log(chalk34.bold("\n\u{1F504} " + t("evolve.applyTitle")));
|
|
7454
|
+
console.log(chalk34.gray("\u2500".repeat(40)));
|
|
7455
|
+
console.log(chalk34.cyan("\n\uCD94\uAC00\uB420 \uB8F0 \uCD08\uC548:"));
|
|
7456
|
+
console.log(chalk34.white(` ${item.draft}`));
|
|
7319
7457
|
const { editedDraft } = await inquirer13.prompt([{
|
|
7320
7458
|
type: "input",
|
|
7321
7459
|
name: "editedDraft",
|
|
@@ -7330,22 +7468,22 @@ async function evolveApply(idStr) {
|
|
|
7330
7468
|
default: false
|
|
7331
7469
|
}]);
|
|
7332
7470
|
if (!confirmed) {
|
|
7333
|
-
console.log(
|
|
7471
|
+
console.log(chalk34.dim(" \uCDE8\uC18C\uB428."));
|
|
7334
7472
|
return;
|
|
7335
7473
|
}
|
|
7336
7474
|
const backupPath = rulesPath + ".bak";
|
|
7337
7475
|
copyFileSync2(rulesPath, backupPath);
|
|
7338
7476
|
const appendContent = "\n" + editedDraft + "\n";
|
|
7339
7477
|
try {
|
|
7340
|
-
|
|
7478
|
+
writeFileSync10(rulesPath, rulesContent + appendContent, "utf-8");
|
|
7341
7479
|
await sync({ yes: true });
|
|
7342
7480
|
} catch (err) {
|
|
7343
7481
|
try {
|
|
7344
7482
|
copyFileSync2(backupPath, rulesPath);
|
|
7345
7483
|
} catch {
|
|
7346
7484
|
}
|
|
7347
|
-
console.error(
|
|
7348
|
-
console.error(
|
|
7485
|
+
console.error(chalk34.red("\n\u274C RULES.md \uBC18\uC601 \uC911 \uC624\uB958 \u2014 \uC6D0\uBCF8 \uBCF5\uC6D0\uB428. \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
|
|
7486
|
+
console.error(chalk34.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
7349
7487
|
process.exitCode = 1;
|
|
7350
7488
|
return;
|
|
7351
7489
|
}
|
|
@@ -7361,12 +7499,12 @@ async function evolveApply(idStr) {
|
|
|
7361
7499
|
p.status = "archived";
|
|
7362
7500
|
writeMemory(cwd, memLoaded.mem);
|
|
7363
7501
|
} else {
|
|
7364
|
-
console.error(
|
|
7502
|
+
console.error(chalk34.yellow(" \u26A0\uFE0F \uC18C\uC2A4 \uD328\uD134 archived \uCC98\uB9AC \uC2E4\uD328 (memory.json \uC190\uC0C1 \uC758\uC2EC). \uB8F0\uC740 \uBC18\uC601\uB410\uC2B5\uB2C8\uB2E4."));
|
|
7365
7503
|
}
|
|
7366
7504
|
}
|
|
7367
|
-
console.log(
|
|
7505
|
+
console.log(chalk34.green(`
|
|
7368
7506
|
\u2705 \uB8F0 \uBC18\uC601 \uC644\uB8CC! [${item.id}]`));
|
|
7369
|
-
console.log(
|
|
7507
|
+
console.log(chalk34.dim(" RULES.md\uC5D0 \uCD94\uAC00 + vhk sync \uC7AC\uC0DD\uC131\uB428"));
|
|
7370
7508
|
printNextStep({
|
|
7371
7509
|
message: "\uB8F0 \uBC18\uC601 \uC644\uB8CC!",
|
|
7372
7510
|
command: "vhk evolve list --status applied",
|
|
@@ -7375,29 +7513,30 @@ async function evolveApply(idStr) {
|
|
|
7375
7513
|
});
|
|
7376
7514
|
}
|
|
7377
7515
|
async function evolveReject(idStr) {
|
|
7516
|
+
if (!ensureNotHardStopped("evolve reject")) return;
|
|
7378
7517
|
const cwd = process.cwd();
|
|
7379
7518
|
const queue = readQueue(cwd);
|
|
7380
7519
|
const item = queue.items.find((i) => i.id === idStr?.trim());
|
|
7381
7520
|
if (!item) {
|
|
7382
|
-
console.log(
|
|
7521
|
+
console.log(chalk34.red("\n\u274C " + t("evolve.notFound", idStr ?? "")));
|
|
7383
7522
|
process.exitCode = 1;
|
|
7384
7523
|
return;
|
|
7385
7524
|
}
|
|
7386
7525
|
if (item.status === "rejected") {
|
|
7387
|
-
console.log(
|
|
7526
|
+
console.log(chalk34.dim(` \uC774\uBBF8 \uAE30\uAC01\uB41C \uD6C4\uBCF4\uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C: ${item.id}`));
|
|
7388
7527
|
return;
|
|
7389
7528
|
}
|
|
7390
7529
|
if (item.status === "applied") {
|
|
7391
|
-
console.log(
|
|
7530
|
+
console.log(chalk34.red(`
|
|
7392
7531
|
\u274C \uC774\uBBF8 \uBC18\uC601\uB41C \uD56D\uBAA9\uC740 \uAE30\uAC01\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 vhk evolve undo \uB85C \uB418\uB3CC\uB9AC\uC138\uC694: ${item.id}`));
|
|
7393
7532
|
process.exitCode = 1;
|
|
7394
7533
|
return;
|
|
7395
7534
|
}
|
|
7396
7535
|
item.status = "rejected";
|
|
7397
7536
|
writeQueue(cwd, queue);
|
|
7398
|
-
console.log(
|
|
7537
|
+
console.log(chalk34.green(`
|
|
7399
7538
|
\u274C \uD6C4\uBCF4 \uAE30\uAC01\uB428: [${item.id}] ${item.draft}`));
|
|
7400
|
-
console.log(
|
|
7539
|
+
console.log(chalk34.dim(" (A1: \uB2E4\uC74C suggest\uC5D0\uC11C \uC7AC\uC81C\uC548 \uC548 \uB428)"));
|
|
7401
7540
|
printNextStep({
|
|
7402
7541
|
message: "\uAE30\uAC01 \uC644\uB8CC!",
|
|
7403
7542
|
command: "vhk evolve list",
|
|
@@ -7405,24 +7544,25 @@ async function evolveReject(idStr) {
|
|
|
7405
7544
|
});
|
|
7406
7545
|
}
|
|
7407
7546
|
async function evolveUndo() {
|
|
7547
|
+
if (!ensureNotHardStopped("evolve undo")) return;
|
|
7408
7548
|
if (!ensureInteractive("undo\uB294 TTY \uD655\uC778\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uD130\uBBF8\uB110\uC5D0\uC11C \uC9C1\uC811 \uC2E4\uD589\uD558\uC138\uC694.")) return;
|
|
7409
7549
|
const cwd = process.cwd();
|
|
7410
7550
|
const queue = readQueue(cwd);
|
|
7411
7551
|
const applied = queue.items.filter((i) => i.status === "applied");
|
|
7412
7552
|
if (applied.length === 0) {
|
|
7413
|
-
console.log(
|
|
7553
|
+
console.log(chalk34.yellow("\n\u{1F4ED} " + t("evolve.noAppliedToUndo")));
|
|
7414
7554
|
return;
|
|
7415
7555
|
}
|
|
7416
7556
|
const last = applied.sort(
|
|
7417
7557
|
(a, b) => (b.appliedAt ?? "").localeCompare(a.appliedAt ?? "")
|
|
7418
7558
|
)[0];
|
|
7419
|
-
if (!last.rulesBackupPath || !
|
|
7420
|
-
console.log(
|
|
7559
|
+
if (!last.rulesBackupPath || !existsSync19(last.rulesBackupPath)) {
|
|
7560
|
+
console.log(chalk34.red("\n\u274C " + t("evolve.noBackup")));
|
|
7421
7561
|
process.exitCode = 1;
|
|
7422
7562
|
return;
|
|
7423
7563
|
}
|
|
7424
|
-
console.log(
|
|
7425
|
-
console.log(
|
|
7564
|
+
console.log(chalk34.bold("\n\u{1F504} " + t("evolve.undoTitle")));
|
|
7565
|
+
console.log(chalk34.dim(` \uB418\uB3CC\uB9B4 \uD56D\uBAA9: [${last.id}] ${last.draft}`));
|
|
7426
7566
|
const { confirmed } = await inquirer13.prompt([{
|
|
7427
7567
|
type: "confirm",
|
|
7428
7568
|
name: "confirmed",
|
|
@@ -7430,16 +7570,16 @@ async function evolveUndo() {
|
|
|
7430
7570
|
default: false
|
|
7431
7571
|
}]);
|
|
7432
7572
|
if (!confirmed) {
|
|
7433
|
-
console.log(
|
|
7573
|
+
console.log(chalk34.dim(" \uCDE8\uC18C\uB428."));
|
|
7434
7574
|
return;
|
|
7435
7575
|
}
|
|
7436
|
-
copyFileSync2(last.rulesBackupPath,
|
|
7576
|
+
copyFileSync2(last.rulesBackupPath, join14(cwd, "RULES.md"));
|
|
7437
7577
|
try {
|
|
7438
7578
|
await sync({ yes: true });
|
|
7439
7579
|
} catch (err) {
|
|
7440
|
-
console.error(
|
|
7441
|
-
console.error(
|
|
7442
|
-
console.error(
|
|
7580
|
+
console.error(chalk34.red("\n\u274C sync \uC7AC\uC2E4\uD589 \uC911 \uC624\uB958. RULES.md\uB294 \uBCF5\uC6D0\uB410\uC73C\uB098 .cursorrules \uB4F1 \uC7AC\uC0DD\uC131 \uC2E4\uD328."));
|
|
7581
|
+
console.error(chalk34.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
7582
|
+
console.error(chalk34.dim(" \uC218\uB3D9\uC73C\uB85C `vhk sync` \uC2E4\uD589\uD558\uC138\uC694."));
|
|
7443
7583
|
}
|
|
7444
7584
|
last.status = "pending";
|
|
7445
7585
|
delete last.appliedAt;
|
|
@@ -7453,7 +7593,7 @@ async function evolveUndo() {
|
|
|
7453
7593
|
writeMemory(cwd, memLoaded.mem);
|
|
7454
7594
|
}
|
|
7455
7595
|
}
|
|
7456
|
-
console.log(
|
|
7596
|
+
console.log(chalk34.green("\n\u2705 \uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC! RULES.md \uBCF5\uC6D0 + sync \uC7AC\uC2E4\uD589\uB428"));
|
|
7457
7597
|
printNextStep({
|
|
7458
7598
|
message: "\uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC!",
|
|
7459
7599
|
command: "vhk evolve list",
|
|
@@ -7652,14 +7792,14 @@ function requiresConfirmation(route) {
|
|
|
7652
7792
|
async function runNaturalLanguageRoute(input) {
|
|
7653
7793
|
const route = routeNaturalLanguage(input);
|
|
7654
7794
|
if (!route) {
|
|
7655
|
-
console.log(
|
|
7795
|
+
console.log(chalk35.yellow(`
|
|
7656
7796
|
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
7657
7797
|
`));
|
|
7658
7798
|
return;
|
|
7659
7799
|
}
|
|
7660
7800
|
console.log("");
|
|
7661
|
-
console.log(
|
|
7662
|
-
console.log(
|
|
7801
|
+
console.log(chalk35.cyan(` \u{1F4AC} "${input}"`));
|
|
7802
|
+
console.log(chalk35.cyan(` \u2192 ${route.explanation}`));
|
|
7663
7803
|
if (requiresConfirmation(route)) {
|
|
7664
7804
|
const { confirm } = await inquirer14.prompt([{
|
|
7665
7805
|
type: "confirm",
|
|
@@ -7668,7 +7808,7 @@ async function runNaturalLanguageRoute(input) {
|
|
|
7668
7808
|
default: true
|
|
7669
7809
|
}]);
|
|
7670
7810
|
if (!confirm) {
|
|
7671
|
-
console.log(
|
|
7811
|
+
console.log(chalk35.dim(` ${ko.nlp.menuHint}`));
|
|
7672
7812
|
return;
|
|
7673
7813
|
}
|
|
7674
7814
|
}
|
|
@@ -7677,7 +7817,7 @@ async function runNaturalLanguageRoute(input) {
|
|
|
7677
7817
|
if (riskAction) {
|
|
7678
7818
|
await runGuarded(
|
|
7679
7819
|
riskAction,
|
|
7680
|
-
{ channel: "nl", approved: false, log: (m) => console.log(
|
|
7820
|
+
{ channel: "nl", approved: false, log: (m) => console.log(chalk35.yellow(` ${m}`)) },
|
|
7681
7821
|
() => dispatchNlpRoute(route, input)
|
|
7682
7822
|
);
|
|
7683
7823
|
return;
|
|
@@ -7685,81 +7825,341 @@ async function runNaturalLanguageRoute(input) {
|
|
|
7685
7825
|
await dispatchNlpRoute(route, input);
|
|
7686
7826
|
}
|
|
7687
7827
|
|
|
7688
|
-
// src/commands/
|
|
7828
|
+
// src/commands/preflight.ts
|
|
7829
|
+
import chalk36 from "chalk";
|
|
7830
|
+
import { existsSync as existsSync21 } from "fs";
|
|
7831
|
+
import { join as join16 } from "path";
|
|
7832
|
+
|
|
7833
|
+
// src/lib/worktree-env.ts
|
|
7834
|
+
import { existsSync as existsSync20, readFileSync as readFileSync8 } from "fs";
|
|
7835
|
+
import { join as join15 } from "path";
|
|
7836
|
+
function parseEnvKeys(content) {
|
|
7837
|
+
const keys = [];
|
|
7838
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
7839
|
+
let line = raw.trim();
|
|
7840
|
+
if (!line || line.startsWith("#")) continue;
|
|
7841
|
+
if (line.startsWith("export ")) line = line.slice("export ".length).trim();
|
|
7842
|
+
const idx = line.indexOf("=");
|
|
7843
|
+
if (idx <= 0) continue;
|
|
7844
|
+
const key = line.slice(0, idx).trim();
|
|
7845
|
+
if (key) keys.push(key);
|
|
7846
|
+
}
|
|
7847
|
+
return keys;
|
|
7848
|
+
}
|
|
7849
|
+
function missingEnvKeys(required, present) {
|
|
7850
|
+
const have = new Set(present);
|
|
7851
|
+
return required.filter((k) => !have.has(k));
|
|
7852
|
+
}
|
|
7853
|
+
function checkWorktreeEnv(input) {
|
|
7854
|
+
const name = "worktree env";
|
|
7855
|
+
const severity = "critical";
|
|
7856
|
+
if (input.exampleContent === null) {
|
|
7857
|
+
return { name, status: "skip", detail: ".env.example \uC5C6\uC74C \u2014 \uD544\uC218 \uD0A4 \uBA85\uC138 \uC5C6\uC74C", severity };
|
|
7858
|
+
}
|
|
7859
|
+
const required = parseEnvKeys(input.exampleContent);
|
|
7860
|
+
if (required.length === 0) {
|
|
7861
|
+
return { name, status: "skip", detail: ".env.example \uC5D0 \uD544\uC218 \uD0A4 \uC5C6\uC74C", severity };
|
|
7862
|
+
}
|
|
7863
|
+
const present = input.envContent === null ? [] : parseEnvKeys(input.envContent);
|
|
7864
|
+
const missing = missingEnvKeys(required, present);
|
|
7865
|
+
if (missing.length === 0) {
|
|
7866
|
+
return { name, status: "pass", detail: `${required.length}/${required.length} keys present`, severity };
|
|
7867
|
+
}
|
|
7868
|
+
return {
|
|
7869
|
+
name,
|
|
7870
|
+
status: "fail",
|
|
7871
|
+
detail: `\uB204\uB77D ${missing.length}\uAC1C: ${missing.join(", ")}`,
|
|
7872
|
+
severity
|
|
7873
|
+
};
|
|
7874
|
+
}
|
|
7875
|
+
function checkWorktreeEnvDir(cwd) {
|
|
7876
|
+
const read = (p) => {
|
|
7877
|
+
const fp = join15(cwd, p);
|
|
7878
|
+
return existsSync20(fp) ? readFileSync8(fp, "utf-8") : null;
|
|
7879
|
+
};
|
|
7880
|
+
const exampleContent = read(".env.example");
|
|
7881
|
+
const envContent = read(".env.local") ?? read(".env");
|
|
7882
|
+
return checkWorktreeEnv({ exampleContent, envContent });
|
|
7883
|
+
}
|
|
7884
|
+
|
|
7885
|
+
// src/commands/preflight.ts
|
|
7886
|
+
var ESLINT_CONFIGS = [
|
|
7887
|
+
".eslintrc.js",
|
|
7888
|
+
".eslintrc.cjs",
|
|
7889
|
+
".eslintrc.json",
|
|
7890
|
+
".eslintrc.yml",
|
|
7891
|
+
"eslint.config.js",
|
|
7892
|
+
"eslint.config.mjs"
|
|
7893
|
+
];
|
|
7894
|
+
async function preflight(opts = {}) {
|
|
7895
|
+
if (!ensureNotHardStopped("preflight")) return;
|
|
7896
|
+
console.log(chalk36.bold(`
|
|
7897
|
+
${ko.preflight.title}
|
|
7898
|
+
`));
|
|
7899
|
+
const cwd = process.cwd();
|
|
7900
|
+
const run = (cmd, args) => {
|
|
7901
|
+
const r = safeExecFile(cmd, args);
|
|
7902
|
+
return r.ok ? { ok: true, out: r.out } : { ok: false, out: r.out, err: r.err };
|
|
7903
|
+
};
|
|
7904
|
+
let hasLintScript = false;
|
|
7905
|
+
try {
|
|
7906
|
+
const pkg = readJsonFile(join16(cwd, "package.json"));
|
|
7907
|
+
hasLintScript = !!pkg.scripts?.lint;
|
|
7908
|
+
} catch {
|
|
7909
|
+
}
|
|
7910
|
+
const hasEslintConfig = ESLINT_CONFIGS.some((f) => existsSync21(join16(cwd, f)));
|
|
7911
|
+
const hasLinter = detectHasLinter({ hasLintScript, hasEslintConfig });
|
|
7912
|
+
const mode2 = opts.publish ? "publish" : opts.pr ? "pr" : "default";
|
|
7913
|
+
const checks = runPreflight(
|
|
7914
|
+
{ full: opts.full, mode: mode2 },
|
|
7915
|
+
{
|
|
7916
|
+
run,
|
|
7917
|
+
nodeVersion: process.version,
|
|
7918
|
+
hasLinter,
|
|
7919
|
+
worktreeEnv: () => checkWorktreeEnvDir(cwd)
|
|
7920
|
+
}
|
|
7921
|
+
);
|
|
7922
|
+
for (const c of checks) {
|
|
7923
|
+
console.log(` ${statusIcon(c)} ${c.name.padEnd(14)} ${chalk36.dim(c.detail)}`);
|
|
7924
|
+
}
|
|
7925
|
+
const s = summarizePreflight(checks);
|
|
7926
|
+
console.log("");
|
|
7927
|
+
if (s.blocked) {
|
|
7928
|
+
console.log(chalk36.red.bold(` ${ko.preflight.resultBlocked(s.failed)}`));
|
|
7929
|
+
printNextStep({
|
|
7930
|
+
message: ko.preflight.nextBlocked,
|
|
7931
|
+
command: "vhk preflight",
|
|
7932
|
+
cursorHint: "preflight \uB2E4\uC2DC \uB3CC\uB824\uC918"
|
|
7933
|
+
});
|
|
7934
|
+
process.exitCode = 1;
|
|
7935
|
+
} else {
|
|
7936
|
+
console.log(chalk36.green.bold(` ${ko.preflight.resultPass(s.warned)}`));
|
|
7937
|
+
printNextStep({
|
|
7938
|
+
message: ko.preflight.nextPass,
|
|
7939
|
+
command: opts.publish ? "vhk publish" : "vhk save",
|
|
7940
|
+
cursorHint: "\uB2E4\uC74C \uB2E8\uACC4\uB85C"
|
|
7941
|
+
});
|
|
7942
|
+
}
|
|
7943
|
+
}
|
|
7944
|
+
|
|
7945
|
+
// src/commands/standup.ts
|
|
7689
7946
|
import chalk37 from "chalk";
|
|
7947
|
+
|
|
7948
|
+
// src/daily/lastActiveDay.ts
|
|
7949
|
+
function lastActiveDay(activityDates2, today) {
|
|
7950
|
+
let best = null;
|
|
7951
|
+
for (const d of activityDates2) {
|
|
7952
|
+
if (d < today && (best === null || d > best)) best = d;
|
|
7953
|
+
}
|
|
7954
|
+
return best;
|
|
7955
|
+
}
|
|
7956
|
+
|
|
7957
|
+
// src/daily/gitlog.ts
|
|
7958
|
+
function normalizeCommitDate(raw) {
|
|
7959
|
+
const m = raw.match(/(\d{4})-(\d{2})-(\d{2})/);
|
|
7960
|
+
return m ? `${m[1]}-${m[2]}-${m[3]}` : raw.slice(0, 10);
|
|
7961
|
+
}
|
|
7962
|
+
function commitsInRange(commits, range) {
|
|
7963
|
+
return commits.filter((c) => c.date >= range.start && c.date <= range.end);
|
|
7964
|
+
}
|
|
7965
|
+
function activityDates(commits) {
|
|
7966
|
+
return [...new Set(commits.map((c) => c.date))];
|
|
7967
|
+
}
|
|
7968
|
+
async function fetchRecentCommitSummaries(since, count = 200) {
|
|
7969
|
+
const raw = await getRecentCommits(count, since);
|
|
7970
|
+
return raw.map((c) => ({ hash: c.hash, message: c.message, date: normalizeCommitDate(c.date) }));
|
|
7971
|
+
}
|
|
7972
|
+
|
|
7973
|
+
// src/daily/goals.ts
|
|
7974
|
+
function slugFromPath(filePath) {
|
|
7975
|
+
const base = filePath.split(/[\\/]/).pop() ?? "";
|
|
7976
|
+
return base.replace(/\.md$/, "").replace(/^\d+-/, "");
|
|
7977
|
+
}
|
|
7978
|
+
function toState(g) {
|
|
7979
|
+
return {
|
|
7980
|
+
id: g.frontmatter.id,
|
|
7981
|
+
slug: slugFromPath(g.filePath),
|
|
7982
|
+
status: g.frontmatter.status ?? "NOT_STARTED"
|
|
7983
|
+
};
|
|
7984
|
+
}
|
|
7985
|
+
function toGoalStates(goals) {
|
|
7986
|
+
return goals.filter((g) => typeof g.frontmatter.id === "number").map(toState);
|
|
7987
|
+
}
|
|
7988
|
+
function recommendGoals(states) {
|
|
7989
|
+
return states.filter((s) => s.status === "NOT_STARTED" || s.status === "IN_PROGRESS");
|
|
7990
|
+
}
|
|
7991
|
+
function doneGoalsOnDay(goals, day) {
|
|
7992
|
+
return goals.filter((g) => g.frontmatter.status === "DONE" && g.frontmatter.completed === day).filter((g) => typeof g.frontmatter.id === "number").map(toState);
|
|
7993
|
+
}
|
|
7994
|
+
function unresolvedGoals(states) {
|
|
7995
|
+
return states.filter((s) => s.status === "BLOCKED").map((s) => `Goal ${s.id} (${s.slug}) \u2014 BLOCKED`);
|
|
7996
|
+
}
|
|
7997
|
+
|
|
7998
|
+
// src/daily/standup.ts
|
|
7999
|
+
function buildStandupReport(input) {
|
|
8000
|
+
const dates = [...activityDates(input.commits), ...input.devlogs.map((d) => d.date)];
|
|
8001
|
+
const last = lastActiveDay(dates, input.asOf);
|
|
8002
|
+
return {
|
|
8003
|
+
asOf: input.asOf,
|
|
8004
|
+
lastActiveDay: last,
|
|
8005
|
+
yesterday: {
|
|
8006
|
+
commits: last ? commitsInRange(input.commits, { start: last, end: last }) : [],
|
|
8007
|
+
devlogs: last ? input.devlogs.filter((d) => d.date === last) : [],
|
|
8008
|
+
doneGoals: last ? input.doneGoalsByDay(last) : []
|
|
8009
|
+
},
|
|
8010
|
+
todayRecommend: recommendGoals(input.goalStates),
|
|
8011
|
+
unresolved: unresolvedGoals(input.goalStates)
|
|
8012
|
+
};
|
|
8013
|
+
}
|
|
8014
|
+
async function runStandup(deps) {
|
|
8015
|
+
const asOf = deps?.asOf ?? localDate();
|
|
8016
|
+
let commits = [];
|
|
8017
|
+
try {
|
|
8018
|
+
commits = await fetchRecentCommitSummaries();
|
|
8019
|
+
} catch {
|
|
8020
|
+
commits = [];
|
|
8021
|
+
}
|
|
8022
|
+
let goals = [];
|
|
8023
|
+
try {
|
|
8024
|
+
goals = listGoals(deps?.goalsDir ?? "goals");
|
|
8025
|
+
} catch {
|
|
8026
|
+
goals = [];
|
|
8027
|
+
}
|
|
8028
|
+
const goalStates = toGoalStates(goals);
|
|
8029
|
+
return buildStandupReport({
|
|
8030
|
+
asOf,
|
|
8031
|
+
commits,
|
|
8032
|
+
goalStates,
|
|
8033
|
+
doneGoalsByDay: (day) => doneGoalsOnDay(goals, day),
|
|
8034
|
+
devlogs: []
|
|
8035
|
+
// Phase 2: Dev Log 노션 데이터소스 연동
|
|
8036
|
+
});
|
|
8037
|
+
}
|
|
8038
|
+
|
|
8039
|
+
// src/commands/standup.ts
|
|
8040
|
+
var WEEKDAYS = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
|
|
8041
|
+
function withWeekday(ymd) {
|
|
8042
|
+
const m = ymd.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
8043
|
+
if (!m) return ymd;
|
|
8044
|
+
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
8045
|
+
return `${ymd} (${WEEKDAYS[d.getDay()]})`;
|
|
8046
|
+
}
|
|
8047
|
+
async function standup() {
|
|
8048
|
+
const r = await runStandup();
|
|
8049
|
+
console.log(chalk37.bold(`
|
|
8050
|
+
${ko.standup.title(withWeekday(r.asOf))}
|
|
8051
|
+
`));
|
|
8052
|
+
console.log(chalk37.bold(` ${ko.standup.yesterday}`));
|
|
8053
|
+
if (r.lastActiveDay === null) {
|
|
8054
|
+
console.log(chalk37.dim(` ${ko.standup.noHistory}`));
|
|
8055
|
+
} else {
|
|
8056
|
+
console.log(chalk37.dim(` (\uB9C8\uC9C0\uB9C9 \uD65C\uB3D9\uC77C: ${r.lastActiveDay})`));
|
|
8057
|
+
const top = r.yesterday.commits.slice(0, 5);
|
|
8058
|
+
for (const c of top) console.log(` \u2022 ${c.message}`);
|
|
8059
|
+
if (r.yesterday.commits.length > top.length) {
|
|
8060
|
+
console.log(chalk37.dim(` \u2022 \u2026 \uC678 ${r.yesterday.commits.length - top.length}\uAC1C`));
|
|
8061
|
+
}
|
|
8062
|
+
console.log(chalk37.dim(` \u2022 ${ko.standup.commitsLine(r.yesterday.commits.length)}`));
|
|
8063
|
+
for (const dg of r.yesterday.doneGoals) console.log(` \u2705 Goal ${dg.id} (${dg.slug}) \uC644\uB8CC`);
|
|
8064
|
+
for (const dl of r.yesterday.devlogs) console.log(` \u{1F4DD} ${dl.title}`);
|
|
8065
|
+
}
|
|
8066
|
+
console.log("");
|
|
8067
|
+
console.log(chalk37.bold(` ${ko.standup.todayRecommend}`));
|
|
8068
|
+
if (r.todayRecommend.length === 0) {
|
|
8069
|
+
console.log(chalk37.green(" \u{1F389} \uBAA8\uB4E0 goal \uC644\uB8CC!"));
|
|
8070
|
+
} else {
|
|
8071
|
+
for (const g of r.todayRecommend) {
|
|
8072
|
+
const icon = g.status === "IN_PROGRESS" ? "\u{1F7E1}" : "\u26AA";
|
|
8073
|
+
console.log(` ${icon} Goal ${g.id} (${g.slug}) \u2014 ${g.status}`);
|
|
8074
|
+
}
|
|
8075
|
+
}
|
|
8076
|
+
if (r.unresolved.length > 0) {
|
|
8077
|
+
console.log("");
|
|
8078
|
+
console.log(chalk37.bold(` ${ko.standup.unresolved}`));
|
|
8079
|
+
for (const u of r.unresolved) console.log(chalk37.yellow(` \u2022 ${u}`));
|
|
8080
|
+
}
|
|
8081
|
+
printNextStep({
|
|
8082
|
+
message: "\uC624\uB298 \uC791\uC5C5 \uC2DC\uC791:",
|
|
8083
|
+
command: "vhk work",
|
|
8084
|
+
cursorHint: "\uC791\uC5C5 \uC2DC\uC791\uD560\uAC8C"
|
|
8085
|
+
});
|
|
8086
|
+
}
|
|
8087
|
+
|
|
8088
|
+
// src/commands/agent.ts
|
|
8089
|
+
import chalk38 from "chalk";
|
|
7690
8090
|
function activeGoalId() {
|
|
7691
8091
|
const goals = listGoals("goals");
|
|
7692
8092
|
const id = selectActiveId(goals);
|
|
7693
8093
|
return id ?? void 0;
|
|
7694
8094
|
}
|
|
7695
8095
|
async function blocker(description) {
|
|
7696
|
-
console.log(
|
|
8096
|
+
console.log(chalk38.bold(`
|
|
7697
8097
|
${ko.agent.blockerTitle}
|
|
7698
8098
|
`));
|
|
7699
8099
|
if (!description || !description.trim()) {
|
|
7700
|
-
console.log(
|
|
7701
|
-
console.log(
|
|
8100
|
+
console.log(chalk38.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
|
|
8101
|
+
console.log(chalk38.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
|
|
7702
8102
|
process.exitCode = 1;
|
|
7703
8103
|
return;
|
|
7704
8104
|
}
|
|
7705
8105
|
const goalId = activeGoalId();
|
|
7706
8106
|
const r = appendBlocker(description, goalId);
|
|
7707
|
-
console.log(
|
|
8107
|
+
console.log(chalk38.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
|
|
7708
8108
|
if (r.hardStopTripped) {
|
|
7709
|
-
console.log(
|
|
7710
|
-
console.log(
|
|
8109
|
+
console.log(chalk38.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
|
|
8110
|
+
console.log(chalk38.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
|
|
7711
8111
|
process.exitCode = 2;
|
|
7712
8112
|
}
|
|
7713
8113
|
}
|
|
7714
8114
|
async function learn(lesson) {
|
|
7715
|
-
console.log(
|
|
8115
|
+
console.log(chalk38.bold(`
|
|
7716
8116
|
${ko.agent.learnTitle}
|
|
7717
8117
|
`));
|
|
7718
8118
|
if (!lesson || !lesson.trim()) {
|
|
7719
|
-
console.log(
|
|
7720
|
-
console.log(
|
|
8119
|
+
console.log(chalk38.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
|
|
8120
|
+
console.log(chalk38.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
|
|
7721
8121
|
process.exitCode = 1;
|
|
7722
8122
|
return;
|
|
7723
8123
|
}
|
|
7724
8124
|
const goalId = activeGoalId();
|
|
7725
8125
|
const entry = recordLesson(process.cwd(), lesson, goalId);
|
|
7726
8126
|
if (!entry) {
|
|
7727
|
-
console.log(
|
|
8127
|
+
console.log(chalk38.red(" \u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uAD50\uD6C8 \uAE30\uB85D \uC911\uB2E8. \uC6D0\uBCF8/\uBC31\uC5C5 \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
|
|
7728
8128
|
process.exitCode = 1;
|
|
7729
8129
|
return;
|
|
7730
8130
|
}
|
|
7731
|
-
console.log(
|
|
7732
|
-
console.log(
|
|
8131
|
+
console.log(chalk38.green(` \u2705 \uAD50\uD6C8 \uAE30\uB85D \u2192 memory failures.lesson (${entry.id})`));
|
|
8132
|
+
console.log(chalk38.dim(" \uAD50\uD6C8\xB7\uACB0\uC815\xB7\uC2E4\uD328\xB7\uC131\uACF5 \uBAA8\uB450 vhk memory (\uB2E8\uC77C SoT). vhk memory list \uB85C \uD655\uC778."));
|
|
7733
8133
|
}
|
|
7734
8134
|
async function resume(opts = {}) {
|
|
7735
|
-
console.log(
|
|
8135
|
+
console.log(chalk38.bold(`
|
|
7736
8136
|
${ko.agent.resumeTitle}
|
|
7737
8137
|
`));
|
|
7738
8138
|
if (!isHardStopActive()) {
|
|
7739
|
-
console.log(
|
|
8139
|
+
console.log(chalk38.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
|
|
7740
8140
|
return;
|
|
7741
8141
|
}
|
|
7742
8142
|
const reason = readHardStopReason();
|
|
7743
8143
|
if (reason) {
|
|
7744
|
-
console.log(
|
|
7745
|
-
console.log(
|
|
8144
|
+
console.log(chalk38.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
|
|
8145
|
+
console.log(chalk38.dim(` ${reason.split("\n").join("\n ")}`));
|
|
7746
8146
|
console.log("");
|
|
7747
8147
|
}
|
|
7748
8148
|
if (!opts.confirm) {
|
|
7749
8149
|
console.log(
|
|
7750
|
-
|
|
8150
|
+
chalk38.red(
|
|
7751
8151
|
" \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
|
|
7752
8152
|
)
|
|
7753
8153
|
);
|
|
7754
|
-
console.log(
|
|
8154
|
+
console.log(chalk38.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
|
|
7755
8155
|
process.exitCode = 1;
|
|
7756
8156
|
return;
|
|
7757
8157
|
}
|
|
7758
8158
|
const removed = clearHardStop();
|
|
7759
8159
|
if (removed) {
|
|
7760
|
-
console.log(
|
|
8160
|
+
console.log(chalk38.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
|
|
7761
8161
|
} else {
|
|
7762
|
-
console.log(
|
|
8162
|
+
console.log(chalk38.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
|
|
7763
8163
|
}
|
|
7764
8164
|
}
|
|
7765
8165
|
|
|
@@ -7780,7 +8180,7 @@ async function guardCli(action, approved, run) {
|
|
|
7780
8180
|
}]);
|
|
7781
8181
|
return ok;
|
|
7782
8182
|
},
|
|
7783
|
-
log: (m) => console.log(
|
|
8183
|
+
log: (m) => console.log(chalk39.yellow(` ${m}`))
|
|
7784
8184
|
},
|
|
7785
8185
|
run
|
|
7786
8186
|
);
|
|
@@ -7793,7 +8193,7 @@ async function guardCliDefer(action, approved, run) {
|
|
|
7793
8193
|
approved,
|
|
7794
8194
|
// TTY 면 통과(명령이 자체 확인), 비대화형은 confirm 불가 → 가드가 차단.
|
|
7795
8195
|
confirm: async () => !!process.stdout.isTTY,
|
|
7796
|
-
log: (m) => console.log(
|
|
8196
|
+
log: (m) => console.log(chalk39.yellow(` ${m}`))
|
|
7797
8197
|
},
|
|
7798
8198
|
run
|
|
7799
8199
|
);
|
|
@@ -7832,6 +8232,8 @@ var KO_ALIASES = {
|
|
|
7832
8232
|
memory: "\uAE30\uC5B5",
|
|
7833
8233
|
brief: "\uBE0C\uB9AC\uD551",
|
|
7834
8234
|
goal: "\uBAA9\uD45C",
|
|
8235
|
+
preflight: "\uCD9C\uACE0\uC810\uAC80",
|
|
8236
|
+
standup: "\uC544\uCE68",
|
|
7835
8237
|
review: "\uAC80\uD1A0",
|
|
7836
8238
|
mission: "\uBBF8\uC158",
|
|
7837
8239
|
blocker: "\uBE14\uB85C\uCEE4",
|
|
@@ -7963,6 +8365,12 @@ program.command("mode [target]").alias("\uBAA8\uB4DC").description("Safety Mode
|
|
|
7963
8365
|
program.command("verify").alias("\uC0AC\uC804\uC810\uAC80").option("--json", "\uB9AC\uD3EC\uD2B8 JSON \uC744 stdout \uC73C\uB85C \uCD9C\uB825 (CI\uC6A9 \u2014 \uACBD\uB85C \uB300\uC2E0)").option("--report", "latest.json \uC744 \uC0AC\uB78C\uC6A9 \uC815\uC801 HTML(.vhk/reports/latest.html) \uB85C \uB80C\uB354 (\uC678\uBD80 \uC758\uC874 0)").option("--open", "\uB9AC\uD3EC\uD2B8 \uC0DD\uC131 \uD6C4 \uAE30\uBCF8 \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uAE30 (\uBE44\uB300\uD654\uD615/CI/MCP \uC790\uB3D9 \uC2A4\uD0B5)").description("\uAC80\uC99D \uAC8C\uC774\uD2B8(tsc/test/build/secure) \uC2E4\uC81C \uC2E4\uD589 + \uC99D\uAC70 \uAE30\uB85D (.vhk/reports/latest.json)").action(async (opts) => {
|
|
7964
8366
|
await verify(opts);
|
|
7965
8367
|
});
|
|
8368
|
+
program.command("preflight").alias("\uCD9C\uACE0\uC810\uAC80").option("--publish", "publish \uC9C1\uC804 \uC810\uAC80 (2FA\xB7\uBC84\uC804 \uAC15\uC870)").option("--pr", "PR \uC9C1\uC804 \uC810\uAC80 (lint\xB7\uD14C\uC2A4\uD2B8\xB7\uBE0C\uB79C\uCE58 \uAC15\uC870)").option("--full", "\uD14C\uC2A4\uD2B8 \uC804\uCCB4 \uC2E4\uD589 (--changed \uCE90\uC2DC \uBBF8\uC0AC\uC6A9)").description("\uCD9C\uACE0 \uC804 \uC548\uC804\uC810\uAC80 \u2014 2FA\xB7shim\xB7env\xB7lint\xB7\uD0C0\uC785\xB7\uD14C\uC2A4\uD2B8\xB7git 8\uAC1C \uD56D\uBAA9, \uCE58\uBA85 \uC2E4\uD328 \uC2DC \uCC28\uB2E8").action(async (opts) => {
|
|
8369
|
+
await preflight(opts);
|
|
8370
|
+
});
|
|
8371
|
+
program.command("standup").alias("\uC544\uCE68").description("\uC544\uCE68 \uBE0C\uB9AC\uD551 \u2014 \uC5B4\uC81C \uD55C \uC77C(\uB9C8\uC9C0\uB9C9 \uD65C\uB3D9\uC77C \uCEE4\uBC0B\xB7\uC644\uB8CC goal) + \uC624\uB298 \uCD94\uCC9C + \uBBF8\uD574\uACB0 (\uC77D\uAE30 \uC804\uC6A9)").action(async () => {
|
|
8372
|
+
await standup();
|
|
8373
|
+
});
|
|
7966
8374
|
program.command("review").alias("\uAC80\uD1A0").option("--id <id>", "\uB300\uC0C1 goal id (\uC5C6\uC73C\uBA74 active goal)").description("\uC801\uB300\uC801 \uC790\uAE30\uAC80\uC99D \u2014 latest.json \u2194 goal \uC644\uB8CC\uC870\uAC74 \uAD50\uCC28\uAC80\uC99D (\uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC \uD0D0\uC9C0, \uBCF4\uC7A5 \uC544\uB2D8)").action(async (opts) => {
|
|
7967
8375
|
await review(opts);
|
|
7968
8376
|
});
|
|
@@ -8085,13 +8493,13 @@ program.on("command:*", async (operands) => {
|
|
|
8085
8493
|
});
|
|
8086
8494
|
program.action(async () => {
|
|
8087
8495
|
const info = getUpdateInfo();
|
|
8088
|
-
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 " +
|
|
8496
|
+
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 " + chalk39.dim(`v${info.current}`));
|
|
8089
8497
|
if (info.updateAvailable && info.latest) {
|
|
8090
|
-
console.log(
|
|
8498
|
+
console.log(chalk39.yellow(`\u{1F195} \uC5C5\uB370\uC774\uD2B8 \uAC00\uB2A5: v${info.latest}`) + chalk39.dim(" \u2192 vhk update"));
|
|
8091
8499
|
}
|
|
8092
8500
|
const sample = QUICK_ACTIONS[0]?.say ?? "\uC0C1\uD0DC \uC54C\uB824\uC918";
|
|
8093
8501
|
console.log(
|
|
8094
|
-
|
|
8502
|
+
chalk39.dim("\u{1F4AC} \uBA85\uB839 \uC9C1\uC811 \uC785\uB825\uB3C4 \uB3FC\uC694 \u2014 \uC608: ") + chalk39.cyan("vhk status") + chalk39.dim(" \xB7 \uC790\uC5F0\uC5B4 OK: ") + chalk39.cyan(`"${sample}"`)
|
|
8095
8503
|
);
|
|
8096
8504
|
console.log("");
|
|
8097
8505
|
const choices = [
|
|
@@ -8171,9 +8579,9 @@ if (isMainModule) {
|
|
|
8171
8579
|
}
|
|
8172
8580
|
} catch (err) {
|
|
8173
8581
|
if (isPromptAbortError(err)) {
|
|
8174
|
-
console.error(
|
|
8582
|
+
console.error(chalk39.yellow("\n \u26A0\uFE0F \uB300\uD654\uD615 \uC785\uB825\uC774 \uCDE8\uC18C/\uC885\uB8CC\uB410\uC2B5\uB2C8\uB2E4. (\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C\uB294 \uD574\uB2F9 \uBA85\uB839\uC744 \uC4F8 \uC218 \uC5C6\uC5B4\uC694)"));
|
|
8175
8583
|
} else {
|
|
8176
|
-
console.error(
|
|
8584
|
+
console.error(chalk39.red(`
|
|
8177
8585
|
\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
8178
8586
|
}
|
|
8179
8587
|
process.exitCode = 1;
|