@byh3071/vhk 1.6.2 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ACJN723Q.js → chunk-53RJHPP6.js} +877 -226
- package/dist/index.js +894 -1250
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,33 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CONTEXT_GIT_MARKER,
|
|
3
4
|
MAX_SCAN_FILE_BYTES,
|
|
4
5
|
MAX_SECRET_FINDINGS,
|
|
5
6
|
NETWORK_EXEC_TIMEOUT_MS,
|
|
6
7
|
__toESM,
|
|
7
8
|
audit,
|
|
9
|
+
buildAdoptedRules,
|
|
10
|
+
checkContextDrift,
|
|
11
|
+
checkRuleDrift,
|
|
12
|
+
countLocalCommits,
|
|
8
13
|
deploy,
|
|
14
|
+
detectExistingRuleFiles,
|
|
9
15
|
env,
|
|
10
16
|
envCheck,
|
|
11
17
|
filterSevereFindings,
|
|
12
18
|
filterTrackedPaths,
|
|
19
|
+
getExecErrorMessage,
|
|
20
|
+
getGitRoot,
|
|
13
21
|
getVhkVersion,
|
|
22
|
+
gitOut,
|
|
23
|
+
gitRun,
|
|
24
|
+
hasGitRemote,
|
|
14
25
|
ko,
|
|
26
|
+
listBackups,
|
|
27
|
+
localDate,
|
|
28
|
+
printContextResumeHint,
|
|
15
29
|
printNextStep,
|
|
16
30
|
printSecurityWarnings,
|
|
17
31
|
publish,
|
|
18
32
|
readJsonFile,
|
|
19
33
|
require_ignore,
|
|
34
|
+
restoreBackup,
|
|
20
35
|
safeExecFile,
|
|
21
36
|
scanProjectForSecrets,
|
|
22
37
|
startMcpServer,
|
|
38
|
+
sync,
|
|
23
39
|
t
|
|
24
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-53RJHPP6.js";
|
|
25
41
|
|
|
26
42
|
// src/index.ts
|
|
27
43
|
import { Command, Help } from "commander";
|
|
28
44
|
import { pathToFileURL } from "url";
|
|
29
|
-
import
|
|
30
|
-
import
|
|
45
|
+
import chalk34 from "chalk";
|
|
46
|
+
import inquirer13 from "inquirer";
|
|
31
47
|
|
|
32
48
|
// src/lib/nlp-router.ts
|
|
33
49
|
function normalize(input) {
|
|
@@ -289,7 +305,8 @@ var RULES = [
|
|
|
289
305
|
explanation: "\uBAA9\uD45C \uAC8C\uC774\uD2B8 \uAC80\uC99D (vhk goal check)",
|
|
290
306
|
confidence: "high",
|
|
291
307
|
args: ["check"],
|
|
292
|
-
|
|
308
|
+
// '스크립트' 포함 시는 sync 의도(게이트 스크립트 생성) → check 가 가로채지 않게 제외.
|
|
309
|
+
test: (t2) => /목표\s*(점검|검증|체크)/.test(t2) && !/스크립트/.test(t2)
|
|
293
310
|
},
|
|
294
311
|
{
|
|
295
312
|
command: "goal",
|
|
@@ -304,6 +321,13 @@ var RULES = [
|
|
|
304
321
|
confidence: "high",
|
|
305
322
|
args: ["list"],
|
|
306
323
|
test: (t2) => /목표\s*(목록|리스트)/.test(t2)
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
command: "goal",
|
|
327
|
+
explanation: "\uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uB3D9\uAE30\uD654 (vhk goal sync)",
|
|
328
|
+
confidence: "high",
|
|
329
|
+
args: ["sync"],
|
|
330
|
+
test: (t2) => /(게이트|목표).*(스크립트|동기화)|체크\s*스크립트\s*(생성|만들)/.test(t2)
|
|
307
331
|
}
|
|
308
332
|
];
|
|
309
333
|
function routeNaturalLanguage(input) {
|
|
@@ -328,7 +352,7 @@ function extractNotionUrl(input) {
|
|
|
328
352
|
|
|
329
353
|
// src/lib/command-registry.ts
|
|
330
354
|
var CONTAINER_SUBCOMMANDS = {
|
|
331
|
-
goal: ["list", "next", "check", "init", "done"],
|
|
355
|
+
goal: ["list", "next", "check", "init", "done", "sync"],
|
|
332
356
|
ref: ["add", "list", "open"],
|
|
333
357
|
memory: ["add", "list", "remove"],
|
|
334
358
|
cloud: ["push", "pull"],
|
|
@@ -475,8 +499,8 @@ function detectNaturalLanguageInput(argv) {
|
|
|
475
499
|
}
|
|
476
500
|
|
|
477
501
|
// src/lib/nlp-run.ts
|
|
478
|
-
import
|
|
479
|
-
import
|
|
502
|
+
import chalk32 from "chalk";
|
|
503
|
+
import inquirer12 from "inquirer";
|
|
480
504
|
|
|
481
505
|
// src/commands/gate.ts
|
|
482
506
|
import inquirer from "inquirer";
|
|
@@ -614,11 +638,6 @@ import chalk3 from "chalk";
|
|
|
614
638
|
import fs2 from "fs";
|
|
615
639
|
import path2 from "path";
|
|
616
640
|
|
|
617
|
-
// src/lib/date.ts
|
|
618
|
-
function localDate(d = /* @__PURE__ */ new Date()) {
|
|
619
|
-
return d.toLocaleDateString("sv-SE");
|
|
620
|
-
}
|
|
621
|
-
|
|
622
641
|
// src/templates/claude-md.ts
|
|
623
642
|
function CLAUDE_MD_TEMPLATE(name, _stack) {
|
|
624
643
|
const d = localDate();
|
|
@@ -898,6 +917,8 @@ function VHK_GITIGNORE_TEMPLATE() {
|
|
|
898
917
|
"memory.json",
|
|
899
918
|
"refs.json",
|
|
900
919
|
"HARD_STOP",
|
|
920
|
+
"# secret gist \uD3EC\uC778\uD130 (gistId). \uACF5\uAC1C repo \uC5D0 \uCEE4\uBC0B\uB418\uBA74 \uBC31\uC5C5 gist \uAC00 \uB178\uCD9C\uB428 (VHK-022).",
|
|
921
|
+
"cloud.json",
|
|
901
922
|
"# sync \uB36E\uC5B4\uC4F0\uAE30 \uC804 \uC790\uB3D9 \uBC31\uC5C5 (\uB85C\uCEEC \uBCF5\uAD6C\uC6A9 \u2014 vhk restore). \uCD94\uC801/\uD074\uB77C\uC6B0\uB4DC \uC81C\uC678.",
|
|
902
923
|
"backups/",
|
|
903
924
|
""
|
|
@@ -1102,90 +1123,8 @@ async function fetchPrdFromNotion(urlOrId) {
|
|
|
1102
1123
|
};
|
|
1103
1124
|
}
|
|
1104
1125
|
|
|
1105
|
-
// src/lib/rules-import.ts
|
|
1106
|
-
import { existsSync, readFileSync } from "fs";
|
|
1107
|
-
import { join } from "path";
|
|
1108
|
-
var ADOPT_SOURCES = [
|
|
1109
|
-
".cursorrules",
|
|
1110
|
-
"CLAUDE.md",
|
|
1111
|
-
"AGENTS.md",
|
|
1112
|
-
".windsurfrules",
|
|
1113
|
-
".github/copilot-instructions.md"
|
|
1114
|
-
];
|
|
1115
|
-
var PREAMBLE_TITLE = "\uC11C\uBB38";
|
|
1116
|
-
function detectExistingRuleFiles(cwd) {
|
|
1117
|
-
const found = [];
|
|
1118
|
-
for (const rel of ADOPT_SOURCES) {
|
|
1119
|
-
const full = join(cwd, rel);
|
|
1120
|
-
if (existsSync(full)) {
|
|
1121
|
-
try {
|
|
1122
|
-
found.push({ path: rel, content: readFileSync(full, "utf-8") });
|
|
1123
|
-
} catch {
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
return found;
|
|
1128
|
-
}
|
|
1129
|
-
function splitSections(content) {
|
|
1130
|
-
const sections = [];
|
|
1131
|
-
let title = "";
|
|
1132
|
-
let buf = [];
|
|
1133
|
-
const preamble = [];
|
|
1134
|
-
let sawHeading = false;
|
|
1135
|
-
for (const line of content.split("\n")) {
|
|
1136
|
-
if (line.startsWith("## ")) {
|
|
1137
|
-
sawHeading = true;
|
|
1138
|
-
if (title) sections.push({ title, content: buf.join("\n").trim() });
|
|
1139
|
-
title = line.replace("## ", "").trim();
|
|
1140
|
-
buf = [];
|
|
1141
|
-
} else if (title) {
|
|
1142
|
-
buf.push(line);
|
|
1143
|
-
} else if (!sawHeading) {
|
|
1144
|
-
preamble.push(line);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
if (title) sections.push({ title, content: buf.join("\n").trim() });
|
|
1148
|
-
const pre = preamble.join("\n").trim();
|
|
1149
|
-
if (pre) sections.unshift({ title: PREAMBLE_TITLE, content: pre });
|
|
1150
|
-
return sections;
|
|
1151
|
-
}
|
|
1152
|
-
function buildAdoptedRules(files, projectName) {
|
|
1153
|
-
const order = [];
|
|
1154
|
-
const byTitle = /* @__PURE__ */ new Map();
|
|
1155
|
-
for (const file of files) {
|
|
1156
|
-
for (const sec of splitSections(file.content)) {
|
|
1157
|
-
let merged = byTitle.get(sec.title);
|
|
1158
|
-
if (!merged) {
|
|
1159
|
-
merged = { title: sec.title, parts: [] };
|
|
1160
|
-
byTitle.set(sec.title, merged);
|
|
1161
|
-
order.push(sec.title);
|
|
1162
|
-
}
|
|
1163
|
-
merged.parts.push({ source: file.path, content: sec.content });
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
const lines = [
|
|
1167
|
-
`# ${projectName} \u2014 Rules`,
|
|
1168
|
-
"",
|
|
1169
|
-
"> \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 \uB2E8\uC77C \uC18C\uC2A4(SoT). \uAE30\uC874 \uADDC\uCE59\uC744 `vhk init` adopt \uB85C \uAC00\uC838\uC654\uC2B5\uB2C8\uB2E4.",
|
|
1170
|
-
"> \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 \uC774 \uD30C\uC77C\uC5D0\uC11C\uB9CC \u2014 `vhk sync` \uB85C \uAC01 \uB3C4\uAD6C\uC5D0 \uC804\uD30C\uB429\uB2C8\uB2E4.",
|
|
1171
|
-
""
|
|
1172
|
-
];
|
|
1173
|
-
for (const title of order) {
|
|
1174
|
-
const merged = byTitle.get(title);
|
|
1175
|
-
const nonEmpty = merged.parts.filter((p) => p.content.trim());
|
|
1176
|
-
if (!nonEmpty.length) continue;
|
|
1177
|
-
lines.push(`## ${title}`);
|
|
1178
|
-
for (const part of nonEmpty) {
|
|
1179
|
-
lines.push(`<!-- \uCD9C\uCC98: ${part.source} -->`);
|
|
1180
|
-
lines.push(part.content);
|
|
1181
|
-
}
|
|
1182
|
-
lines.push("");
|
|
1183
|
-
}
|
|
1184
|
-
return lines.join("\n");
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
1126
|
// src/lib/stack-detect.ts
|
|
1188
|
-
import { join
|
|
1127
|
+
import { join } from "path";
|
|
1189
1128
|
function detectStackFromDeps(deps) {
|
|
1190
1129
|
const stack = [];
|
|
1191
1130
|
if (deps.next) stack.push("Next.js");
|
|
@@ -1208,7 +1147,7 @@ function detectStackFromDeps(deps) {
|
|
|
1208
1147
|
function detectProjectStack(cwd = ".") {
|
|
1209
1148
|
let pkg;
|
|
1210
1149
|
try {
|
|
1211
|
-
pkg = readJsonFile(
|
|
1150
|
+
pkg = readJsonFile(join(cwd, "package.json"));
|
|
1212
1151
|
} catch {
|
|
1213
1152
|
return null;
|
|
1214
1153
|
}
|
|
@@ -1241,23 +1180,30 @@ function resolveType(type) {
|
|
|
1241
1180
|
}
|
|
1242
1181
|
return type;
|
|
1243
1182
|
}
|
|
1183
|
+
function isNonInteractive(options) {
|
|
1184
|
+
return Boolean(options.yes) || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
1185
|
+
}
|
|
1186
|
+
var DEFAULT_TYPE = PROJECT_TYPES[0].value;
|
|
1244
1187
|
async function collectAnswers(options, defaults = {}) {
|
|
1188
|
+
const noninteractive = isNonInteractive(options);
|
|
1245
1189
|
const prompts = [];
|
|
1246
|
-
if (!
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1190
|
+
if (!noninteractive) {
|
|
1191
|
+
if (!options.name && !defaults.name) {
|
|
1192
|
+
prompts.push({ type: "input", name: "name", message: ko.init.projectName });
|
|
1193
|
+
}
|
|
1194
|
+
if (!options.description && !defaults.description) {
|
|
1195
|
+
prompts.push({ type: "input", name: "description", message: ko.init.description });
|
|
1196
|
+
}
|
|
1197
|
+
if (!options.type && !defaults.type) {
|
|
1198
|
+
prompts.push({ type: "list", name: "type", message: ko.init.projectType, choices: PROJECT_TYPES });
|
|
1199
|
+
}
|
|
1254
1200
|
}
|
|
1255
1201
|
const prompted = prompts.length ? await inquirer2.prompt(prompts) : {};
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
};
|
|
1202
|
+
const fallbackName = path2.basename(process.cwd()) || "my-project";
|
|
1203
|
+
const name = options.name || defaults.name || prompted.name || fallbackName;
|
|
1204
|
+
const description = options.description || defaults.description || prompted.description || `${name} \u2014 vhk \uD504\uB85C\uC81D\uD2B8`;
|
|
1205
|
+
const type = resolveType(options.type || defaults.type || prompted.type) ?? prompted.type ?? DEFAULT_TYPE;
|
|
1206
|
+
return { name, description, type };
|
|
1261
1207
|
}
|
|
1262
1208
|
async function init(options = {}) {
|
|
1263
1209
|
const skipGate = Boolean(options.skipGate || options.fromNotion);
|
|
@@ -1296,7 +1242,7 @@ ${ko.init.title}
|
|
|
1296
1242
|
console.log(chalk3.dim(`
|
|
1297
1243
|
${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
1298
1244
|
`));
|
|
1299
|
-
if (!options
|
|
1245
|
+
if (!isNonInteractive(options)) {
|
|
1300
1246
|
const { confirmStack } = await inquirer2.prompt([{
|
|
1301
1247
|
type: "confirm",
|
|
1302
1248
|
name: "confirmStack",
|
|
@@ -1310,7 +1256,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
1310
1256
|
}
|
|
1311
1257
|
const cwd = process.cwd();
|
|
1312
1258
|
let adoptedRules = null;
|
|
1313
|
-
if (!options
|
|
1259
|
+
if (!isNonInteractive(options) && !options.fromNotion) {
|
|
1314
1260
|
const existingRules = detectExistingRuleFiles(cwd);
|
|
1315
1261
|
if (existingRules.length > 0) {
|
|
1316
1262
|
const { adopt } = await inquirer2.prompt([{
|
|
@@ -1334,12 +1280,12 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
1334
1280
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1335
1281
|
const fullPath = path2.join(cwd, filePath);
|
|
1336
1282
|
if (fileExists(fullPath)) {
|
|
1337
|
-
const
|
|
1283
|
+
const overwrite = isNonInteractive(options) ? false : (await inquirer2.prompt([{
|
|
1338
1284
|
type: "confirm",
|
|
1339
1285
|
name: "overwrite",
|
|
1340
1286
|
message: ko.init.overwrite(filePath),
|
|
1341
1287
|
default: false
|
|
1342
|
-
}]);
|
|
1288
|
+
}])).overwrite;
|
|
1343
1289
|
if (!overwrite) {
|
|
1344
1290
|
log.warn(ko.init.skipped(filePath));
|
|
1345
1291
|
continue;
|
|
@@ -1348,7 +1294,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
1348
1294
|
writeFile(fullPath, content);
|
|
1349
1295
|
log.success(filePath);
|
|
1350
1296
|
}
|
|
1351
|
-
await writeInitExtras(cwd);
|
|
1297
|
+
await writeInitExtras(cwd, isNonInteractive(options));
|
|
1352
1298
|
console.log(chalk3.bold.green(`
|
|
1353
1299
|
${ko.init.done}`));
|
|
1354
1300
|
console.log(chalk3.dim(`
|
|
@@ -1462,16 +1408,16 @@ function projectHasTestScript(projectDir) {
|
|
|
1462
1408
|
return false;
|
|
1463
1409
|
}
|
|
1464
1410
|
}
|
|
1465
|
-
async function writeInitExtras(projectDir) {
|
|
1411
|
+
async function writeInitExtras(projectDir, noninteractive = false) {
|
|
1466
1412
|
const commandsPath = path2.join(projectDir, "COMMANDS.md");
|
|
1467
1413
|
const hasTest = projectHasTestScript(projectDir);
|
|
1468
1414
|
if (fileExists(commandsPath)) {
|
|
1469
|
-
const
|
|
1415
|
+
const overwrite = noninteractive ? false : (await inquirer2.prompt([{
|
|
1470
1416
|
type: "confirm",
|
|
1471
1417
|
name: "overwrite",
|
|
1472
1418
|
message: ko.init.overwrite("COMMANDS.md"),
|
|
1473
1419
|
default: false
|
|
1474
|
-
}]);
|
|
1420
|
+
}])).overwrite;
|
|
1475
1421
|
if (!overwrite) {
|
|
1476
1422
|
log.warn(ko.init.skipped("COMMANDS.md"));
|
|
1477
1423
|
} else {
|
|
@@ -1698,13 +1644,13 @@ function isPromptAbortError(err) {
|
|
|
1698
1644
|
import chalk5 from "chalk";
|
|
1699
1645
|
|
|
1700
1646
|
// src/lib/state-files.ts
|
|
1701
|
-
import { existsSync
|
|
1702
|
-
import { join as
|
|
1647
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, rmSync } from "fs";
|
|
1648
|
+
import { join as join2 } from "path";
|
|
1703
1649
|
var STATE_DIR = "docs/state";
|
|
1704
|
-
var BLOCKERS_PATH =
|
|
1705
|
-
var LEARNINGS_PATH =
|
|
1650
|
+
var BLOCKERS_PATH = join2(STATE_DIR, "blockers.md");
|
|
1651
|
+
var LEARNINGS_PATH = join2(STATE_DIR, "learnings.md");
|
|
1706
1652
|
var VHK_DIR = ".vhk";
|
|
1707
|
-
var HARD_STOP_PATH =
|
|
1653
|
+
var HARD_STOP_PATH = join2(VHK_DIR, "HARD_STOP");
|
|
1708
1654
|
var HARD_STOP_BLOCKER_THRESHOLD = 3;
|
|
1709
1655
|
function ensureStateDir() {
|
|
1710
1656
|
mkdirSync(STATE_DIR, { recursive: true });
|
|
@@ -1727,7 +1673,7 @@ function appendBlocker(description, goalId) {
|
|
|
1727
1673
|
ensureStateDir();
|
|
1728
1674
|
const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
|
|
1729
1675
|
const line = `- [${isoDate()} ${tag}] ${description.trim()}`;
|
|
1730
|
-
if (!
|
|
1676
|
+
if (!existsSync(BLOCKERS_PATH)) {
|
|
1731
1677
|
writeFileSync(
|
|
1732
1678
|
BLOCKERS_PATH,
|
|
1733
1679
|
`# Blockers
|
|
@@ -1742,10 +1688,10 @@ ${line}
|
|
|
1742
1688
|
appendFileSync(BLOCKERS_PATH, `${line}
|
|
1743
1689
|
`, "utf-8");
|
|
1744
1690
|
}
|
|
1745
|
-
const current =
|
|
1691
|
+
const current = readFileSync(BLOCKERS_PATH, "utf-8");
|
|
1746
1692
|
const count = countActiveBlockers(current);
|
|
1747
1693
|
let hardStopTripped = false;
|
|
1748
|
-
if (count >= HARD_STOP_BLOCKER_THRESHOLD && !
|
|
1694
|
+
if (count >= HARD_STOP_BLOCKER_THRESHOLD && !existsSync(HARD_STOP_PATH)) {
|
|
1749
1695
|
writeHardStop(`auto: ${count} active blockers (threshold ${HARD_STOP_BLOCKER_THRESHOLD})`);
|
|
1750
1696
|
hardStopTripped = true;
|
|
1751
1697
|
}
|
|
@@ -1755,7 +1701,7 @@ function appendLearning(lesson, goalId) {
|
|
|
1755
1701
|
ensureStateDir();
|
|
1756
1702
|
const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
|
|
1757
1703
|
const line = `- [${isoDate()} ${tag}] ${lesson.trim()}`;
|
|
1758
|
-
if (!
|
|
1704
|
+
if (!existsSync(LEARNINGS_PATH)) {
|
|
1759
1705
|
writeFileSync(
|
|
1760
1706
|
LEARNINGS_PATH,
|
|
1761
1707
|
`# Learnings
|
|
@@ -1772,14 +1718,14 @@ ${line}
|
|
|
1772
1718
|
}
|
|
1773
1719
|
}
|
|
1774
1720
|
function getRecentLearnings(limit = 3) {
|
|
1775
|
-
if (!
|
|
1776
|
-
const lines =
|
|
1721
|
+
if (!existsSync(LEARNINGS_PATH)) return [];
|
|
1722
|
+
const lines = readFileSync(LEARNINGS_PATH, "utf-8").split(/\r?\n/);
|
|
1777
1723
|
const entries = lines.filter((l) => l.startsWith("- ["));
|
|
1778
1724
|
return entries.slice(-limit);
|
|
1779
1725
|
}
|
|
1780
1726
|
function getActiveBlockers(limit = 3) {
|
|
1781
|
-
if (!
|
|
1782
|
-
const lines =
|
|
1727
|
+
if (!existsSync(BLOCKERS_PATH)) return [];
|
|
1728
|
+
const lines = readFileSync(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
|
|
1783
1729
|
const entries = lines.filter((l) => ACTIVE_BLOCKER_RE.test(l));
|
|
1784
1730
|
return entries.slice(-limit);
|
|
1785
1731
|
}
|
|
@@ -1791,18 +1737,18 @@ ${reason}
|
|
|
1791
1737
|
`, "utf-8");
|
|
1792
1738
|
}
|
|
1793
1739
|
function isHardStopActive() {
|
|
1794
|
-
return
|
|
1740
|
+
return existsSync(HARD_STOP_PATH);
|
|
1795
1741
|
}
|
|
1796
1742
|
function readHardStopReason() {
|
|
1797
|
-
if (!
|
|
1743
|
+
if (!existsSync(HARD_STOP_PATH)) return null;
|
|
1798
1744
|
try {
|
|
1799
|
-
return
|
|
1745
|
+
return readFileSync(HARD_STOP_PATH, "utf-8").trim();
|
|
1800
1746
|
} catch {
|
|
1801
1747
|
return null;
|
|
1802
1748
|
}
|
|
1803
1749
|
}
|
|
1804
1750
|
function clearHardStop() {
|
|
1805
|
-
if (!
|
|
1751
|
+
if (!existsSync(HARD_STOP_PATH)) return false;
|
|
1806
1752
|
rmSync(HARD_STOP_PATH, { force: true });
|
|
1807
1753
|
return true;
|
|
1808
1754
|
}
|
|
@@ -2060,543 +2006,17 @@ ${ko.recap.done}`));
|
|
|
2060
2006
|
});
|
|
2061
2007
|
}
|
|
2062
2008
|
|
|
2063
|
-
// src/commands/sync.ts
|
|
2064
|
-
import chalk7 from "chalk";
|
|
2065
|
-
import fs7 from "fs";
|
|
2066
|
-
import path8 from "path";
|
|
2067
|
-
import inquirer4 from "inquirer";
|
|
2068
|
-
|
|
2069
|
-
// src/lib/drift.ts
|
|
2070
|
-
import fs5 from "fs";
|
|
2071
|
-
import path6 from "path";
|
|
2072
|
-
|
|
2073
|
-
// src/lib/git-repo.ts
|
|
2074
|
-
import { execFileSync } from "child_process";
|
|
2075
|
-
function getGitRoot(cwd = process.cwd()) {
|
|
2076
|
-
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
2077
|
-
encoding: "utf-8",
|
|
2078
|
-
cwd,
|
|
2079
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2080
|
-
}).trim();
|
|
2081
|
-
}
|
|
2082
|
-
function gitOut(args, cwd) {
|
|
2083
|
-
return execFileSync("git", args, {
|
|
2084
|
-
encoding: "utf-8",
|
|
2085
|
-
cwd,
|
|
2086
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2087
|
-
});
|
|
2088
|
-
}
|
|
2089
|
-
function gitRun(args, cwd) {
|
|
2090
|
-
execFileSync("git", args, { stdio: "pipe", cwd });
|
|
2091
|
-
}
|
|
2092
|
-
function getExecErrorMessage(err) {
|
|
2093
|
-
if (err && typeof err === "object" && "stderr" in err) {
|
|
2094
|
-
const stderr = err.stderr;
|
|
2095
|
-
if (Buffer.isBuffer(stderr)) return stderr.toString("utf-8").trim();
|
|
2096
|
-
if (typeof stderr === "string") return stderr.trim();
|
|
2097
|
-
}
|
|
2098
|
-
return err instanceof Error ? err.message : String(err);
|
|
2099
|
-
}
|
|
2100
|
-
function hasGitRemote(cwd) {
|
|
2101
|
-
try {
|
|
2102
|
-
return gitOut(["remote"], cwd).trim().length > 0;
|
|
2103
|
-
} catch {
|
|
2104
|
-
return false;
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
function countLocalCommits(cwd) {
|
|
2108
|
-
try {
|
|
2109
|
-
const out = gitOut(["rev-list", "--count", "HEAD"], cwd).trim();
|
|
2110
|
-
return parseInt(out, 10) || 0;
|
|
2111
|
-
} catch {
|
|
2112
|
-
return 0;
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
// src/lib/drift.ts
|
|
2117
|
-
function normalizeForCompare(s) {
|
|
2118
|
-
return s.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n+$/, "\n");
|
|
2119
|
-
}
|
|
2120
|
-
function checkRuleDrift(rootDir) {
|
|
2121
|
-
const rulesPath = path6.join(rootDir, "RULES.md");
|
|
2122
|
-
if (!fs5.existsSync(rulesPath)) return { checked: false, results: [] };
|
|
2123
|
-
const rulesContent = fs5.readFileSync(rulesPath, "utf-8");
|
|
2124
|
-
const sections = parseRulesMd(rulesContent);
|
|
2125
|
-
const projectName = deriveProjectName(rulesContent);
|
|
2126
|
-
const results = [];
|
|
2127
|
-
for (const target of SYNC_TARGETS) {
|
|
2128
|
-
const fullPath = path6.join(rootDir, target.path);
|
|
2129
|
-
if (!fs5.existsSync(fullPath)) {
|
|
2130
|
-
results.push({ path: target.path, status: "missing" });
|
|
2131
|
-
continue;
|
|
2132
|
-
}
|
|
2133
|
-
const expected = normalizeForCompare(target.generate(sections, projectName));
|
|
2134
|
-
const actual = normalizeForCompare(fs5.readFileSync(fullPath, "utf-8"));
|
|
2135
|
-
results.push({ path: target.path, status: expected === actual ? "ok" : "drifted" });
|
|
2136
|
-
}
|
|
2137
|
-
return { checked: true, results };
|
|
2138
|
-
}
|
|
2139
|
-
var CONTEXT_GIT_MARKER = "vhk-context-git";
|
|
2140
|
-
var CONTEXT_PATH = ".vhk/context.md";
|
|
2141
|
-
function extractContextSha(content) {
|
|
2142
|
-
const m = content.match(new RegExp(`${CONTEXT_GIT_MARKER}:\\s*([0-9a-f]{7,40})`));
|
|
2143
|
-
return m ? m[1] : null;
|
|
2144
|
-
}
|
|
2145
|
-
var CONTEXT_SOURCE_PATHS = ["package.json", "goals", "docs/state/learnings.md"];
|
|
2146
|
-
function contextSourcesChanged(generatedSha, rootDir) {
|
|
2147
|
-
const content = gitOut(
|
|
2148
|
-
["diff", "--name-only", generatedSha, "HEAD", "--", ...CONTEXT_SOURCE_PATHS],
|
|
2149
|
-
rootDir
|
|
2150
|
-
).trim();
|
|
2151
|
-
if (content) return true;
|
|
2152
|
-
const structural = gitOut(
|
|
2153
|
-
["diff", "--name-only", "--diff-filter=ADR", generatedSha, "HEAD"],
|
|
2154
|
-
rootDir
|
|
2155
|
-
).trim();
|
|
2156
|
-
return structural.length > 0;
|
|
2157
|
-
}
|
|
2158
|
-
function checkContextDrift(rootDir) {
|
|
2159
|
-
const ctxPath = path6.join(rootDir, CONTEXT_PATH);
|
|
2160
|
-
if (!fs5.existsSync(ctxPath)) return { checked: false, stale: false };
|
|
2161
|
-
const generatedSha = extractContextSha(fs5.readFileSync(ctxPath, "utf-8"));
|
|
2162
|
-
if (!generatedSha) return { checked: false, stale: false };
|
|
2163
|
-
let currentSha;
|
|
2164
|
-
try {
|
|
2165
|
-
currentSha = gitOut(["rev-parse", "HEAD"], rootDir).trim();
|
|
2166
|
-
} catch {
|
|
2167
|
-
return { checked: false, stale: false };
|
|
2168
|
-
}
|
|
2169
|
-
if (!currentSha) return { checked: false, stale: false };
|
|
2170
|
-
if (currentSha.startsWith(generatedSha) || generatedSha.startsWith(currentSha)) {
|
|
2171
|
-
return { checked: true, stale: false, generatedSha, currentSha };
|
|
2172
|
-
}
|
|
2173
|
-
let stale;
|
|
2174
|
-
try {
|
|
2175
|
-
stale = contextSourcesChanged(generatedSha, rootDir);
|
|
2176
|
-
} catch {
|
|
2177
|
-
return { checked: false, stale: false };
|
|
2178
|
-
}
|
|
2179
|
-
return { checked: true, stale, generatedSha, currentSha };
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
// src/lib/backup.ts
|
|
2183
|
-
import fs6 from "fs";
|
|
2184
|
-
import path7 from "path";
|
|
2185
|
-
var BACKUPS_REL = path7.join(".vhk", "backups");
|
|
2186
|
-
var VHK_GITIGNORE_REL = path7.join(".vhk", ".gitignore");
|
|
2187
|
-
function fsSafeStamp(d) {
|
|
2188
|
-
return d.toISOString().replace(/[:.]/g, "-");
|
|
2189
|
-
}
|
|
2190
|
-
function ensureVhkIgnored(rootDir, ...entries) {
|
|
2191
|
-
const giPath = path7.join(rootDir, VHK_GITIGNORE_REL);
|
|
2192
|
-
fs6.mkdirSync(path7.dirname(giPath), { recursive: true });
|
|
2193
|
-
let content = fs6.existsSync(giPath) ? fs6.readFileSync(giPath, "utf-8") : "";
|
|
2194
|
-
const present = new Set(content.split("\n").map((l) => l.trim().replace(/\/$/, "")));
|
|
2195
|
-
const missing = entries.filter((e) => !present.has(e.trim().replace(/\/$/, "")));
|
|
2196
|
-
if (missing.length === 0) return;
|
|
2197
|
-
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
2198
|
-
content += missing.join("\n") + "\n";
|
|
2199
|
-
fs6.writeFileSync(giPath, content, "utf-8");
|
|
2200
|
-
}
|
|
2201
|
-
function walkRelFiles(baseDir, cur = baseDir) {
|
|
2202
|
-
const out = [];
|
|
2203
|
-
for (const entry of fs6.readdirSync(cur)) {
|
|
2204
|
-
const full = path7.join(cur, entry);
|
|
2205
|
-
if (fs6.statSync(full).isDirectory()) {
|
|
2206
|
-
out.push(...walkRelFiles(baseDir, full));
|
|
2207
|
-
} else {
|
|
2208
|
-
out.push(path7.relative(baseDir, full).split(path7.sep).join("/"));
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
return out;
|
|
2212
|
-
}
|
|
2213
|
-
function saveBackup(files, rootDir, stamp) {
|
|
2214
|
-
const baseId = stamp ?? fsSafeStamp(/* @__PURE__ */ new Date());
|
|
2215
|
-
let id = baseId;
|
|
2216
|
-
let n = 1;
|
|
2217
|
-
while (fs6.existsSync(path7.join(rootDir, BACKUPS_REL, id))) {
|
|
2218
|
-
id = `${baseId}-${String(n++).padStart(3, "0")}`;
|
|
2219
|
-
}
|
|
2220
|
-
const backupDir = path7.join(rootDir, BACKUPS_REL, id);
|
|
2221
|
-
const saved = [];
|
|
2222
|
-
for (const rel of files) {
|
|
2223
|
-
const src = path7.join(rootDir, rel);
|
|
2224
|
-
if (!fs6.existsSync(src)) continue;
|
|
2225
|
-
const dest = path7.join(backupDir, rel);
|
|
2226
|
-
fs6.mkdirSync(path7.dirname(dest), { recursive: true });
|
|
2227
|
-
fs6.copyFileSync(src, dest);
|
|
2228
|
-
saved.push(rel);
|
|
2229
|
-
}
|
|
2230
|
-
ensureVhkIgnored(rootDir, "backups/");
|
|
2231
|
-
return { id, dir: backupDir, files: saved };
|
|
2232
|
-
}
|
|
2233
|
-
function backupOrderKey(id) {
|
|
2234
|
-
const m = /^(.*Z)(?:-(\d+))?$/.exec(id);
|
|
2235
|
-
return m ? [m[1], m[2] ? parseInt(m[2], 10) : 0] : [id, 0];
|
|
2236
|
-
}
|
|
2237
|
-
function listBackups(rootDir) {
|
|
2238
|
-
const root = path7.join(rootDir, BACKUPS_REL);
|
|
2239
|
-
if (!fs6.existsSync(root)) return [];
|
|
2240
|
-
return fs6.readdirSync(root).filter((e) => fs6.statSync(path7.join(root, e)).isDirectory()).sort((a, b) => {
|
|
2241
|
-
const [ba, na] = backupOrderKey(a);
|
|
2242
|
-
const [bb, nb] = backupOrderKey(b);
|
|
2243
|
-
if (ba !== bb) return ba < bb ? 1 : -1;
|
|
2244
|
-
return nb - na;
|
|
2245
|
-
}).map((id) => {
|
|
2246
|
-
const dir = path7.join(root, id);
|
|
2247
|
-
return { id, dir, files: walkRelFiles(dir) };
|
|
2248
|
-
});
|
|
2249
|
-
}
|
|
2250
|
-
function restoreBackup(id, rootDir) {
|
|
2251
|
-
const backupDir = path7.join(rootDir, BACKUPS_REL, id);
|
|
2252
|
-
if (!fs6.existsSync(backupDir)) {
|
|
2253
|
-
throw new Error(`\uBC31\uC5C5 \uC5C6\uC74C: ${id}`);
|
|
2254
|
-
}
|
|
2255
|
-
const rels = walkRelFiles(backupDir);
|
|
2256
|
-
for (const rel of rels) {
|
|
2257
|
-
const src = path7.join(backupDir, rel);
|
|
2258
|
-
const dest = path7.join(rootDir, rel);
|
|
2259
|
-
fs6.mkdirSync(path7.dirname(dest), { recursive: true });
|
|
2260
|
-
fs6.copyFileSync(src, dest);
|
|
2261
|
-
}
|
|
2262
|
-
return rels;
|
|
2263
|
-
}
|
|
2264
|
-
function pruneBackups(keepN, rootDir) {
|
|
2265
|
-
const all = listBackups(rootDir);
|
|
2266
|
-
const toDelete = all.slice(Math.max(0, keepN));
|
|
2267
|
-
for (const b of toDelete) {
|
|
2268
|
-
fs6.rmSync(b.dir, { recursive: true, force: true });
|
|
2269
|
-
}
|
|
2270
|
-
return toDelete.map((b) => b.id);
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
// src/commands/sync.ts
|
|
2274
|
-
var CURSORRULES_KEYS = ["\uCF54\uB529 \uADDC\uCE59", "\uAE30\uC220 \uC2A4\uD0DD", "\uC544\uD0A4\uD14D\uCC98", "\uB514\uC790\uC778", "Anti-patterns", "\uCEE4\uBC0B"];
|
|
2275
|
-
var CLAUDE_MD_KEYS = ["\uAE30\uB85D", "\uB85C\uADF8", "ADR", "\uD2B8\uB7EC\uBE14\uC288\uD305", "TIL", "/done", "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8"];
|
|
2276
|
-
function findUnmappedSections(sections) {
|
|
2277
|
-
const allKeys = [...CURSORRULES_KEYS, ...CLAUDE_MD_KEYS];
|
|
2278
|
-
return sections.filter((s) => s.title !== PREAMBLE_TITLE && !allKeys.some((k) => s.title.includes(k))).map((s) => s.title);
|
|
2279
|
-
}
|
|
2280
|
-
function parseRulesMd(content) {
|
|
2281
|
-
const sections = [];
|
|
2282
|
-
const lines = content.split("\n");
|
|
2283
|
-
let currentTitle = "";
|
|
2284
|
-
let currentContent = [];
|
|
2285
|
-
for (const line of lines) {
|
|
2286
|
-
if (line.startsWith("## ")) {
|
|
2287
|
-
if (currentTitle) {
|
|
2288
|
-
sections.push({ title: currentTitle, content: currentContent.join("\n").trim() });
|
|
2289
|
-
}
|
|
2290
|
-
currentTitle = line.replace("## ", "").trim();
|
|
2291
|
-
currentContent = [];
|
|
2292
|
-
} else {
|
|
2293
|
-
currentContent.push(line);
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
if (currentTitle) {
|
|
2297
|
-
sections.push({ title: currentTitle, content: currentContent.join("\n").trim() });
|
|
2298
|
-
}
|
|
2299
|
-
return sections;
|
|
2300
|
-
}
|
|
2301
|
-
function buildCodingDoc(headerTitle, sections, projectName) {
|
|
2302
|
-
const codingSections = sections.filter(
|
|
2303
|
-
(s) => CURSORRULES_KEYS.some((k) => s.title.includes(k))
|
|
2304
|
-
);
|
|
2305
|
-
const lines = [
|
|
2306
|
-
`# ${projectName} \u2014 ${headerTitle}`,
|
|
2307
|
-
"",
|
|
2308
|
-
"> \uCF54\uB529/\uB514\uC790\uC778 \uC804\uC6A9. \uAE30\uB85D/\uC6B4\uC601 \u2192 CLAUDE.md \uCC38\uC870.",
|
|
2309
|
-
"> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
|
|
2310
|
-
"",
|
|
2311
|
-
"## \uD544\uC218 \uCC38\uC870",
|
|
2312
|
-
"- docs/PRD.md \xB7 docs/ARCHITECTURE.md \xB7 CLAUDE.md \xB7 RULES.md",
|
|
2313
|
-
""
|
|
2314
|
-
];
|
|
2315
|
-
for (const section of codingSections) {
|
|
2316
|
-
lines.push(`## ${section.title}`);
|
|
2317
|
-
lines.push(section.content);
|
|
2318
|
-
lines.push("");
|
|
2319
|
-
}
|
|
2320
|
-
return lines.join("\n");
|
|
2321
|
-
}
|
|
2322
|
-
function toCursorrules(sections, projectName) {
|
|
2323
|
-
return buildCodingDoc("Cursor Rules", sections, projectName);
|
|
2324
|
-
}
|
|
2325
|
-
function toWindsurfrules(sections, projectName) {
|
|
2326
|
-
return buildCodingDoc("Windsurf Rules", sections, projectName);
|
|
2327
|
-
}
|
|
2328
|
-
function toCopilotInstructions(sections, projectName) {
|
|
2329
|
-
return buildCodingDoc("GitHub Copilot Instructions", sections, projectName);
|
|
2330
|
-
}
|
|
2331
|
-
var ANTIGRAVITY_CHAR_LIMIT = 12e3;
|
|
2332
|
-
var ANTIGRAVITY_TRUNCATE_MARKER = "\n\n<!-- \u26A0\uFE0F Antigravity 12,000\uC790 \uC81C\uD55C\uC73C\uB85C \uC808\uC0AD\uB428 \u2014 \uC804\uCCB4 \uADDC\uCE59\uC740 RULES.md \uCC38\uC870 -->\n";
|
|
2333
|
-
function truncateForAntigravity(content, limit = ANTIGRAVITY_CHAR_LIMIT) {
|
|
2334
|
-
if (Buffer.byteLength(content, "utf8") <= limit) return content;
|
|
2335
|
-
const SAFETY = 200;
|
|
2336
|
-
const budget = limit - Buffer.byteLength(ANTIGRAVITY_TRUNCATE_MARKER, "utf8") - SAFETY;
|
|
2337
|
-
let lo = 0;
|
|
2338
|
-
let hi = content.length;
|
|
2339
|
-
while (lo < hi) {
|
|
2340
|
-
const mid = lo + hi + 1 >> 1;
|
|
2341
|
-
if (Buffer.byteLength(content.slice(0, mid), "utf8") <= budget) lo = mid;
|
|
2342
|
-
else hi = mid - 1;
|
|
2343
|
-
}
|
|
2344
|
-
const charCut = lo;
|
|
2345
|
-
let cut = content.lastIndexOf("\n## ", charCut);
|
|
2346
|
-
if (cut < charCut * 0.5) {
|
|
2347
|
-
const nl = content.lastIndexOf("\n", charCut);
|
|
2348
|
-
cut = nl > 0 ? nl : charCut;
|
|
2349
|
-
}
|
|
2350
|
-
return content.slice(0, cut).trimEnd() + ANTIGRAVITY_TRUNCATE_MARKER;
|
|
2351
|
-
}
|
|
2352
|
-
function toAntigravityRules(sections, projectName) {
|
|
2353
|
-
return truncateForAntigravity(buildCodingDoc("Antigravity Rules", sections, projectName));
|
|
2354
|
-
}
|
|
2355
|
-
var CLAUDE_AUTOGEN_BANNER = "> \u26A1 \uC544\uB798 \uADDC\uCE59 \uC139\uC158\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.";
|
|
2356
|
-
function toClaudeMd(sections, existing) {
|
|
2357
|
-
const recordSections = sections.filter(
|
|
2358
|
-
(s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k))
|
|
2359
|
-
);
|
|
2360
|
-
const cleaned = existing.split("\n").filter((line) => line.trim() !== CLAUDE_AUTOGEN_BANNER).join("\n");
|
|
2361
|
-
const statusMatch = cleaned.match(/## 현재 상태[\s\S]*?(?=\n## |$)/);
|
|
2362
|
-
const statusSection = statusMatch ? statusMatch[0].trimEnd() : "";
|
|
2363
|
-
const header = cleaned.split("## ")[0].trim();
|
|
2364
|
-
const lines = [
|
|
2365
|
-
header,
|
|
2366
|
-
"",
|
|
2367
|
-
statusSection,
|
|
2368
|
-
"",
|
|
2369
|
-
CLAUDE_AUTOGEN_BANNER,
|
|
2370
|
-
""
|
|
2371
|
-
];
|
|
2372
|
-
for (const section of recordSections) {
|
|
2373
|
-
lines.push(`## ${section.title}`);
|
|
2374
|
-
lines.push(section.content);
|
|
2375
|
-
lines.push("");
|
|
2376
|
-
}
|
|
2377
|
-
return lines.join("\n");
|
|
2378
|
-
}
|
|
2379
|
-
function toAgentsMd(sections, projectName) {
|
|
2380
|
-
const codingSections = sections.filter((s) => CURSORRULES_KEYS.some((k) => s.title.includes(k)));
|
|
2381
|
-
const recordSections = sections.filter((s) => CLAUDE_MD_KEYS.some((k) => s.title.includes(k)));
|
|
2382
|
-
const lines = [
|
|
2383
|
-
`# ${projectName} \u2014 AGENTS.md (\uC5D0\uC774\uC804\uD2B8 \uC791\uB3D9 \uADDC\uC57D)`,
|
|
2384
|
-
"",
|
|
2385
|
-
"> \u26A1 \uC774 \uD30C\uC77C\uC740 RULES.md\uC5D0\uC11C \uC790\uB3D9 \uC0DD\uC131\uB428 (vhk sync). \uC9C1\uC811 \uC218\uC815 \uAE08\uC9C0.",
|
|
2386
|
-
"> \uBE60\uB978 \uC2DC\uC791(\uD1A0\uD070 \uC808\uAC10): `docs/context/agent-compact.md` \uB97C \uBA3C\uC800 \uC77D\uC73C\uC138\uC694.",
|
|
2387
|
-
"",
|
|
2388
|
-
"## Loop Protocol",
|
|
2389
|
-
"- \uB8E8\uD504: `context \u2192 goal next \u2192 \uC791\uC5C5 \u2192 goal check \u2192 goal done`",
|
|
2390
|
-
"- \uC791\uC5C5 \uC2DC\uC791 \uC2DC `.vhk/HARD_STOP` \uD655\uC778 \u2014 \uC788\uC73C\uBA74 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC989\uC2DC \uC911\uB2E8.",
|
|
2391
|
-
"- active goal \uB9CC \uC791\uC5C5. `docs/state`(next-task/blockers/learnings)\uB294 SoT, append-only.",
|
|
2392
|
-
"- \uAC8C\uC774\uD2B8(tsc / test:run / build) \uD1B5\uACFC\uD574\uC57C\uB9CC `vhk goal done`.",
|
|
2393
|
-
""
|
|
2394
|
-
];
|
|
2395
|
-
for (const section of codingSections) {
|
|
2396
|
-
lines.push(`## ${section.title}`);
|
|
2397
|
-
lines.push(section.content);
|
|
2398
|
-
lines.push("");
|
|
2399
|
-
}
|
|
2400
|
-
for (const section of recordSections) {
|
|
2401
|
-
lines.push(`## ${section.title}`);
|
|
2402
|
-
lines.push(section.content);
|
|
2403
|
-
lines.push("");
|
|
2404
|
-
}
|
|
2405
|
-
return lines.join("\n");
|
|
2406
|
-
}
|
|
2407
|
-
function deriveProjectName(rulesContent) {
|
|
2408
|
-
const firstLine = rulesContent.split("\n")[0];
|
|
2409
|
-
return firstLine.replace(/^#\s*/, "").replace(/\s*—.*/, "").trim() || "Project";
|
|
2410
|
-
}
|
|
2411
|
-
var SYNC_TARGETS = [
|
|
2412
|
-
{ path: ".cursorrules", generate: toCursorrules, doneMessage: ko.sync.cursorrulesDone },
|
|
2413
|
-
{ path: ".windsurfrules", generate: toWindsurfrules, doneMessage: ko.sync.windsurfDone },
|
|
2414
|
-
{ path: ".github/copilot-instructions.md", generate: toCopilotInstructions, doneMessage: ko.sync.copilotDone },
|
|
2415
|
-
{ path: ".agents/rules/vhk-rules.md", generate: toAntigravityRules, doneMessage: ko.sync.antigravityDone },
|
|
2416
|
-
// AGENTS.md — 6번째 타겟. 항목 1개 추가로 sync·드리프트·백업 가드가 자동 반영된다.
|
|
2417
|
-
{ path: "AGENTS.md", generate: toAgentsMd, doneMessage: ko.sync.agentsDone }
|
|
2418
|
-
];
|
|
2419
|
-
var BACKUP_KEEP = 10;
|
|
2420
|
-
var SYNCED_MARKER_REL = path8.join(".vhk", ".synced");
|
|
2421
|
-
function buildSyncPlan(rootDir, sections, projectName) {
|
|
2422
|
-
const plan = [];
|
|
2423
|
-
for (const target of SYNC_TARGETS) {
|
|
2424
|
-
const fullPath = path8.join(rootDir, target.path);
|
|
2425
|
-
const exists = fs7.existsSync(fullPath);
|
|
2426
|
-
const newContent = target.generate(sections, projectName);
|
|
2427
|
-
const drift = exists ? normalizeForCompare(fs7.readFileSync(fullPath, "utf-8")) !== normalizeForCompare(newContent) : false;
|
|
2428
|
-
plan.push({ path: target.path, newContent, doneMessage: target.doneMessage, exists, drift });
|
|
2429
|
-
}
|
|
2430
|
-
const claudePath = path8.join(rootDir, "CLAUDE.md");
|
|
2431
|
-
const claudeExists = fs7.existsSync(claudePath);
|
|
2432
|
-
const existingClaude = claudeExists ? fs7.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
|
|
2433
|
-
|
|
2434
|
-
## \uD604\uC7AC \uC0C1\uD0DC
|
|
2435
|
-
- **Phase:** **FILL**
|
|
2436
|
-
- **\uBE14\uB85C\uCEE4:** \uC5C6\uC74C
|
|
2437
|
-
- **\uB2E4\uC74C \uC561\uC158:** **FILL**
|
|
2438
|
-
- **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${localDate()}`;
|
|
2439
|
-
const claudeNew = toClaudeMd(sections, existingClaude);
|
|
2440
|
-
const claudeDrift = claudeExists ? normalizeForCompare(existingClaude) !== normalizeForCompare(claudeNew) : false;
|
|
2441
|
-
plan.push({
|
|
2442
|
-
path: "CLAUDE.md",
|
|
2443
|
-
newContent: claudeNew,
|
|
2444
|
-
doneMessage: ko.sync.claudeDone,
|
|
2445
|
-
exists: claudeExists,
|
|
2446
|
-
drift: claudeDrift
|
|
2447
|
-
});
|
|
2448
|
-
return plan;
|
|
2449
|
-
}
|
|
2450
|
-
async function syncCore(rootDir, opts, confirmOverwrite) {
|
|
2451
|
-
const rulesContent = fs7.readFileSync(path8.join(rootDir, "RULES.md"), "utf-8");
|
|
2452
|
-
const sections = parseRulesMd(rulesContent);
|
|
2453
|
-
const projectName = deriveProjectName(rulesContent);
|
|
2454
|
-
const plan = buildSyncPlan(rootDir, sections, projectName);
|
|
2455
|
-
const firstSync = !fs7.existsSync(path8.join(rootDir, SYNCED_MARKER_REL));
|
|
2456
|
-
const unmapped = findUnmappedSections(sections);
|
|
2457
|
-
if (opts.dryRun) {
|
|
2458
|
-
return {
|
|
2459
|
-
dryRun: true,
|
|
2460
|
-
firstSync,
|
|
2461
|
-
backupId: null,
|
|
2462
|
-
backedUp: [],
|
|
2463
|
-
written: [],
|
|
2464
|
-
skipped: [],
|
|
2465
|
-
truncated: [],
|
|
2466
|
-
plan,
|
|
2467
|
-
unmapped
|
|
2468
|
-
};
|
|
2469
|
-
}
|
|
2470
|
-
const toBackup = plan.filter((p) => p.exists && (p.drift || firstSync)).map((p) => p.path);
|
|
2471
|
-
let backupId = null;
|
|
2472
|
-
let backedUp = [];
|
|
2473
|
-
if (toBackup.length) {
|
|
2474
|
-
const info = saveBackup(toBackup, rootDir);
|
|
2475
|
-
pruneBackups(BACKUP_KEEP, rootDir);
|
|
2476
|
-
backupId = info.id;
|
|
2477
|
-
backedUp = info.files;
|
|
2478
|
-
}
|
|
2479
|
-
const drifted = plan.filter((p) => p.drift);
|
|
2480
|
-
const overwriteDrift = drifted.length ? await confirmOverwrite(drifted) : true;
|
|
2481
|
-
const written = [];
|
|
2482
|
-
const skipped = [];
|
|
2483
|
-
const truncated = [];
|
|
2484
|
-
for (const item of plan) {
|
|
2485
|
-
if (item.drift && !overwriteDrift) {
|
|
2486
|
-
skipped.push(item.path);
|
|
2487
|
-
continue;
|
|
2488
|
-
}
|
|
2489
|
-
const fullPath = path8.join(rootDir, item.path);
|
|
2490
|
-
fs7.mkdirSync(path8.dirname(fullPath), { recursive: true });
|
|
2491
|
-
fs7.writeFileSync(fullPath, item.newContent, "utf-8");
|
|
2492
|
-
written.push(item.path);
|
|
2493
|
-
if (item.newContent.includes("Antigravity 12,000\uC790 \uC81C\uD55C\uC73C\uB85C \uC808\uC0AD\uB428")) {
|
|
2494
|
-
truncated.push(item.path);
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
fs7.mkdirSync(path8.join(rootDir, ".vhk"), { recursive: true });
|
|
2498
|
-
fs7.writeFileSync(path8.join(rootDir, SYNCED_MARKER_REL), (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
|
|
2499
|
-
ensureVhkIgnored(rootDir, ".synced");
|
|
2500
|
-
return { dryRun: false, firstSync, backupId, backedUp, written, skipped, truncated, plan, unmapped };
|
|
2501
|
-
}
|
|
2502
|
-
async function sync(opts = {}) {
|
|
2503
|
-
console.log(chalk7.bold(`
|
|
2504
|
-
${ko.sync.title}
|
|
2505
|
-
`));
|
|
2506
|
-
const cwd = process.cwd();
|
|
2507
|
-
const rulesPath = path8.join(cwd, "RULES.md");
|
|
2508
|
-
if (!fs7.existsSync(rulesPath)) {
|
|
2509
|
-
console.log(chalk7.yellow(ko.sync.noRules));
|
|
2510
|
-
console.log(chalk7.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
|
|
2511
|
-
console.log(chalk7.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
|
|
2512
|
-
console.log("");
|
|
2513
|
-
console.log(chalk7.dim(" RULES.md \uAE30\uBCF8 \uAD6C\uC870:"));
|
|
2514
|
-
console.log(chalk7.dim(" ## \uD504\uB85C\uC81D\uD2B8 \uC815\uCCB4\uC131"));
|
|
2515
|
-
console.log(chalk7.dim(" ## \uAE30\uC220 \uC2A4\uD0DD"));
|
|
2516
|
-
console.log(chalk7.dim(" ## \uCF54\uB529 \uADDC\uCE59"));
|
|
2517
|
-
console.log(chalk7.dim(" ## \uAE30\uB85D \uADDC\uCE59"));
|
|
2518
|
-
console.log(chalk7.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
|
|
2519
|
-
return;
|
|
2520
|
-
}
|
|
2521
|
-
const sections = parseRulesMd(fs7.readFileSync(rulesPath, "utf-8"));
|
|
2522
|
-
console.log(chalk7.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
|
|
2523
|
-
const interactive = !!process.stdout.isTTY && !opts.yes;
|
|
2524
|
-
const confirmOverwrite = async (drifted) => {
|
|
2525
|
-
if (!interactive) return true;
|
|
2526
|
-
for (const d of drifted) console.log(chalk7.yellow(` ${ko.sync.driftWarn(d.path)}`));
|
|
2527
|
-
const { confirm } = await inquirer4.prompt([
|
|
2528
|
-
{
|
|
2529
|
-
type: "confirm",
|
|
2530
|
-
name: "confirm",
|
|
2531
|
-
message: ko.sync.driftConfirm(drifted.length),
|
|
2532
|
-
default: false
|
|
2533
|
-
}
|
|
2534
|
-
]);
|
|
2535
|
-
return confirm;
|
|
2536
|
-
};
|
|
2537
|
-
const result = await syncCore(cwd, opts, confirmOverwrite);
|
|
2538
|
-
if (result.unmapped.length) {
|
|
2539
|
-
console.error(
|
|
2540
|
-
chalk7.yellow(
|
|
2541
|
-
` \u26A0\uFE0F ${result.unmapped.length}\uAC1C \uC139\uC158\uC774 \uC5B4\uB290 \uD0C0\uAE43\uC5D0\uB3C4 \uB9E4\uD551 \uC548 \uB3FC \uC0B0\uCD9C\uBB3C\uC5D0\uC11C \uC81C\uC678\uB428: ${result.unmapped.join(", ")}
|
|
2542
|
-
(\uCF54\uB529 \uADDC\uCE59/\uAE30\uC220 \uC2A4\uD0DD/\uCEE4\uBC0B/\uAE30\uB85D \uB4F1 \uD45C\uC900 \uC81C\uBAA9\uC744 \uC4F0\uAC70\uB098, \uC774 \uC139\uC158\uC740 RULES.md \uC5D0\uB9CC \uBCF4\uC874\uB429\uB2C8\uB2E4.)`
|
|
2543
|
-
)
|
|
2544
|
-
);
|
|
2545
|
-
}
|
|
2546
|
-
if (result.dryRun) {
|
|
2547
|
-
console.log(chalk7.cyan(`
|
|
2548
|
-
${ko.sync.dryRunHeader}`));
|
|
2549
|
-
for (const item of result.plan) {
|
|
2550
|
-
console.log(ko.sync.dryRunWouldWrite(item.path, item.exists && item.drift));
|
|
2551
|
-
}
|
|
2552
|
-
const wouldBackup = result.plan.filter((p) => p.exists && (p.drift || result.firstSync)).map((p) => p.path);
|
|
2553
|
-
if (wouldBackup.length) {
|
|
2554
|
-
console.log(chalk7.dim(`
|
|
2555
|
-
\uBC31\uC5C5 \uC608\uC815(${wouldBackup.length}): ${wouldBackup.join(", ")}`));
|
|
2556
|
-
}
|
|
2557
|
-
return;
|
|
2558
|
-
}
|
|
2559
|
-
if (result.backupId) {
|
|
2560
|
-
if (result.firstSync) console.log(chalk7.cyan(` ${ko.sync.firstSync}`));
|
|
2561
|
-
if (!process.stdout.isTTY) {
|
|
2562
|
-
console.log(chalk7.yellow(` ${ko.sync.nonTtyAuto(result.backedUp.length, result.backupId)}`));
|
|
2563
|
-
} else {
|
|
2564
|
-
console.log(chalk7.cyan(` ${ko.sync.backupSaved(result.backedUp.length, result.backupId)}`));
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
for (const p of result.written) {
|
|
2568
|
-
const item = result.plan.find((i) => i.path === p);
|
|
2569
|
-
if (item) console.log(chalk7.green(` ${item.doneMessage}`));
|
|
2570
|
-
}
|
|
2571
|
-
for (const _ of result.truncated) {
|
|
2572
|
-
console.log(chalk7.yellow(` \u26A0\uFE0F ${ko.sync.antigravityTruncated}`));
|
|
2573
|
-
}
|
|
2574
|
-
for (const p of result.skipped) {
|
|
2575
|
-
console.log(chalk7.gray(` ${ko.sync.skipped(p)}`));
|
|
2576
|
-
}
|
|
2577
|
-
console.log(chalk7.bold.green(`
|
|
2578
|
-
${ko.sync.done}`));
|
|
2579
|
-
console.log(chalk7.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md + .windsurfrules"));
|
|
2580
|
-
console.log(chalk7.dim(" + .github/copilot-instructions.md + .agents/rules/vhk-rules.md (\uC790\uB3D9 \uC0DD\uC131)"));
|
|
2581
|
-
console.log(chalk7.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
|
|
2582
|
-
printNextStep({
|
|
2583
|
-
message: "\uADDC\uCE59 \uB3D9\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C Cursor\uAC00 \uC0C8 \uADDC\uCE59\uC744 \uB530\uB985\uB2C8\uB2E4.",
|
|
2584
|
-
command: "vhk \uC810\uAC80",
|
|
2585
|
-
cursorHint: "\uADDC\uCE59 \uC810\uAC80\uD574\uC918"
|
|
2586
|
-
});
|
|
2587
|
-
}
|
|
2588
|
-
|
|
2589
2009
|
// src/commands/check.ts
|
|
2590
|
-
import
|
|
2591
|
-
import
|
|
2592
|
-
import
|
|
2010
|
+
import chalk8 from "chalk";
|
|
2011
|
+
import path7 from "path";
|
|
2012
|
+
import fs6 from "fs";
|
|
2593
2013
|
|
|
2594
2014
|
// src/lib/rules-parser.ts
|
|
2595
|
-
import
|
|
2596
|
-
import
|
|
2015
|
+
import fs5 from "fs";
|
|
2016
|
+
import path6 from "path";
|
|
2597
2017
|
function parseRules(rulesPath) {
|
|
2598
|
-
if (!
|
|
2599
|
-
const content =
|
|
2018
|
+
if (!fs5.existsSync(rulesPath)) return [];
|
|
2019
|
+
const content = fs5.readFileSync(rulesPath, "utf-8");
|
|
2600
2020
|
const lines = content.split("\n");
|
|
2601
2021
|
const rules = [];
|
|
2602
2022
|
let currentSection = "";
|
|
@@ -2661,17 +2081,17 @@ function createNamingRule(id, section, desc, convention) {
|
|
|
2661
2081
|
description: desc,
|
|
2662
2082
|
check: (cwd) => {
|
|
2663
2083
|
const violations = [];
|
|
2664
|
-
const srcDir =
|
|
2665
|
-
if (!
|
|
2084
|
+
const srcDir = path6.join(cwd, "src");
|
|
2085
|
+
if (!fs5.existsSync(srcDir)) return violations;
|
|
2666
2086
|
walkFiles(srcDir, (filePath) => {
|
|
2667
|
-
const name =
|
|
2087
|
+
const name = path6.basename(filePath, path6.extname(filePath));
|
|
2668
2088
|
if (convention === "kebab-case" && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
2669
2089
|
if (!["index", "vite.config", "tsconfig"].includes(name)) {
|
|
2670
2090
|
violations.push({
|
|
2671
2091
|
ruleId: id,
|
|
2672
2092
|
severity: "warning",
|
|
2673
2093
|
message: `\uD30C\uC77C\uBA85\uC774 kebab-case\uAC00 \uC544\uB2D8: ${name}`,
|
|
2674
|
-
file:
|
|
2094
|
+
file: path6.relative(cwd, filePath)
|
|
2675
2095
|
});
|
|
2676
2096
|
}
|
|
2677
2097
|
}
|
|
@@ -2687,8 +2107,8 @@ function createStructureRule(id, section, desc, expectedPath) {
|
|
|
2687
2107
|
type: "structure",
|
|
2688
2108
|
description: desc,
|
|
2689
2109
|
check: (cwd) => {
|
|
2690
|
-
const fullPath =
|
|
2691
|
-
if (!
|
|
2110
|
+
const fullPath = path6.join(cwd, expectedPath);
|
|
2111
|
+
if (!fs5.existsSync(fullPath)) {
|
|
2692
2112
|
return [{
|
|
2693
2113
|
ruleId: id,
|
|
2694
2114
|
severity: "error",
|
|
@@ -2708,11 +2128,11 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
2708
2128
|
pattern: new RegExp(escapeRegex(pattern), "i"),
|
|
2709
2129
|
check: (cwd) => {
|
|
2710
2130
|
const violations = [];
|
|
2711
|
-
const srcDir =
|
|
2712
|
-
if (!
|
|
2131
|
+
const srcDir = path6.join(cwd, "src");
|
|
2132
|
+
if (!fs5.existsSync(srcDir)) return violations;
|
|
2713
2133
|
const regex = new RegExp(escapeRegex(pattern), "i");
|
|
2714
2134
|
walkFiles(srcDir, (filePath) => {
|
|
2715
|
-
const fileContent =
|
|
2135
|
+
const fileContent = fs5.readFileSync(filePath, "utf-8");
|
|
2716
2136
|
const fileLines = fileContent.split("\n");
|
|
2717
2137
|
fileLines.forEach((line, idx) => {
|
|
2718
2138
|
if (regex.test(line)) {
|
|
@@ -2720,7 +2140,7 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
2720
2140
|
ruleId: id,
|
|
2721
2141
|
severity: type === "banned" ? "error" : "warning",
|
|
2722
2142
|
message: type === "banned" ? `\uAE08\uC9C0 \uD328\uD134 \uBC1C\uACAC: \`${pattern}\`` : `\uD544\uC218 \uD328\uD134 \uB204\uB77D: \`${pattern}\``,
|
|
2723
|
-
file:
|
|
2143
|
+
file: path6.relative(cwd, filePath),
|
|
2724
2144
|
line: idx + 1
|
|
2725
2145
|
});
|
|
2726
2146
|
}
|
|
@@ -2732,9 +2152,9 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
2732
2152
|
};
|
|
2733
2153
|
}
|
|
2734
2154
|
function walkFiles(dir, callback) {
|
|
2735
|
-
const entries =
|
|
2155
|
+
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
2736
2156
|
for (const entry of entries) {
|
|
2737
|
-
const fullPath =
|
|
2157
|
+
const fullPath = path6.join(dir, entry.name);
|
|
2738
2158
|
if (entry.isDirectory()) {
|
|
2739
2159
|
if (!["node_modules", ".git", "dist", ".next"].includes(entry.name)) {
|
|
2740
2160
|
walkFiles(fullPath, callback);
|
|
@@ -2749,13 +2169,13 @@ function escapeRegex(str) {
|
|
|
2749
2169
|
}
|
|
2750
2170
|
|
|
2751
2171
|
// src/commands/goal.ts
|
|
2752
|
-
import { existsSync as
|
|
2753
|
-
import { join as
|
|
2754
|
-
import
|
|
2172
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
|
|
2173
|
+
import { join as join4 } from "path";
|
|
2174
|
+
import chalk7 from "chalk";
|
|
2755
2175
|
|
|
2756
2176
|
// src/lib/goal-frontmatter.ts
|
|
2757
|
-
import { existsSync as
|
|
2758
|
-
import { join as
|
|
2177
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
2178
|
+
import { join as join3 } from "path";
|
|
2759
2179
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
2760
2180
|
function parseFrontmatter(content) {
|
|
2761
2181
|
const m = content.match(FRONTMATTER_RE);
|
|
@@ -2787,9 +2207,9 @@ function parseSimpleYaml(yaml) {
|
|
|
2787
2207
|
return out;
|
|
2788
2208
|
}
|
|
2789
2209
|
function parseGoalFile(filePath) {
|
|
2790
|
-
if (!
|
|
2210
|
+
if (!existsSync2(filePath)) return null;
|
|
2791
2211
|
try {
|
|
2792
|
-
const content =
|
|
2212
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
2793
2213
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
2794
2214
|
return { filePath, frontmatter, body };
|
|
2795
2215
|
} catch {
|
|
@@ -2797,7 +2217,7 @@ function parseGoalFile(filePath) {
|
|
|
2797
2217
|
}
|
|
2798
2218
|
}
|
|
2799
2219
|
function listGoals(goalsDir) {
|
|
2800
|
-
if (!
|
|
2220
|
+
if (!existsSync2(goalsDir)) return [];
|
|
2801
2221
|
let entries;
|
|
2802
2222
|
try {
|
|
2803
2223
|
entries = readdirSync(goalsDir);
|
|
@@ -2808,7 +2228,7 @@ function listGoals(goalsDir) {
|
|
|
2808
2228
|
for (const name of entries) {
|
|
2809
2229
|
if (!name.endsWith(".md")) continue;
|
|
2810
2230
|
if (name === "_meta.md") continue;
|
|
2811
|
-
const fp =
|
|
2231
|
+
const fp = join3(goalsDir, name);
|
|
2812
2232
|
try {
|
|
2813
2233
|
if (!statSync(fp).isFile()) continue;
|
|
2814
2234
|
} catch {
|
|
@@ -2823,6 +2243,36 @@ function listGoals(goalsDir) {
|
|
|
2823
2243
|
parsed.sort((a, b) => a.frontmatter.id - b.frontmatter.id);
|
|
2824
2244
|
return parsed;
|
|
2825
2245
|
}
|
|
2246
|
+
function findSkippedGoalFiles(goalsDir) {
|
|
2247
|
+
if (!existsSync2(goalsDir)) return [];
|
|
2248
|
+
let entries;
|
|
2249
|
+
try {
|
|
2250
|
+
entries = readdirSync(goalsDir);
|
|
2251
|
+
} catch {
|
|
2252
|
+
return [];
|
|
2253
|
+
}
|
|
2254
|
+
const out = [];
|
|
2255
|
+
for (const name of entries) {
|
|
2256
|
+
if (!name.endsWith(".md")) continue;
|
|
2257
|
+
if (name === "_meta.md") continue;
|
|
2258
|
+
const fp = join3(goalsDir, name);
|
|
2259
|
+
try {
|
|
2260
|
+
if (!statSync(fp).isFile()) continue;
|
|
2261
|
+
} catch {
|
|
2262
|
+
continue;
|
|
2263
|
+
}
|
|
2264
|
+
const g = parseGoalFile(fp);
|
|
2265
|
+
if (!g) continue;
|
|
2266
|
+
const fm = g.frontmatter;
|
|
2267
|
+
if (fm.type === "goal" && typeof fm.id === "number") continue;
|
|
2268
|
+
if (fm.type === "meta") continue;
|
|
2269
|
+
const looksLikeGoal = fm.type === "goal" || "id" in fm || "status" in fm || "priority" in fm || "title" in fm;
|
|
2270
|
+
if (!looksLikeGoal) continue;
|
|
2271
|
+
const reason = fm.type !== "goal" ? "type: goal \uB204\uB77D (frontmatter \uC5D0 'type: goal' \uD544\uC694)" : "id \uAC00 \uC22B\uC790\uAC00 \uC544\uB2D8 ('id: 1' \uCC98\uB7FC \uC22B\uC790\uB9CC)";
|
|
2272
|
+
out.push({ file: name, reason });
|
|
2273
|
+
}
|
|
2274
|
+
return out;
|
|
2275
|
+
}
|
|
2826
2276
|
function findDuplicateIds(goals) {
|
|
2827
2277
|
const counts = /* @__PURE__ */ new Map();
|
|
2828
2278
|
for (const g of goals) {
|
|
@@ -2900,13 +2350,15 @@ function resolveGoalId(optId, goals) {
|
|
|
2900
2350
|
return selectActiveId(goals);
|
|
2901
2351
|
}
|
|
2902
2352
|
async function goalList() {
|
|
2903
|
-
console.log(
|
|
2353
|
+
console.log(chalk7.bold(`
|
|
2904
2354
|
${ko.goal.listTitle}
|
|
2905
2355
|
`));
|
|
2906
2356
|
const goals = listGoals(GOALS_DIR);
|
|
2357
|
+
const skipped = findSkippedGoalFiles(GOALS_DIR);
|
|
2907
2358
|
if (goals.length === 0) {
|
|
2908
|
-
console.log(
|
|
2909
|
-
console.log(
|
|
2359
|
+
console.log(chalk7.yellow(" \u{1F4ED} goals/ \uB514\uB809\uD1A0\uB9AC\uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2360
|
+
console.log(chalk7.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
|
|
2361
|
+
printSkippedGoalWarnings(skipped);
|
|
2910
2362
|
return;
|
|
2911
2363
|
}
|
|
2912
2364
|
for (const g of goals) {
|
|
@@ -2923,22 +2375,33 @@ ${ko.goal.listTitle}
|
|
|
2923
2375
|
const dups = findDuplicateIds(goals);
|
|
2924
2376
|
if (dups.length > 0) {
|
|
2925
2377
|
console.log("");
|
|
2926
|
-
console.log(
|
|
2378
|
+
console.log(chalk7.yellow(` ${ko.goal.duplicateId(dups.join(", "))}`));
|
|
2379
|
+
}
|
|
2380
|
+
printSkippedGoalWarnings(skipped);
|
|
2381
|
+
}
|
|
2382
|
+
function printSkippedGoalWarnings(skipped) {
|
|
2383
|
+
if (skipped.length > 0) {
|
|
2384
|
+
console.log("");
|
|
2385
|
+
console.log(chalk7.yellow(` ${ko.goal.skippedFiles(skipped.length)}`));
|
|
2386
|
+
for (const s of skipped) {
|
|
2387
|
+
console.log(chalk7.yellow(` - goals/${s.file}: ${s.reason}`));
|
|
2388
|
+
}
|
|
2389
|
+
console.log(chalk7.dim(" \uD544\uC218: type: goal + \uC22B\uC790 id. \uC2A4\uD0A4\uB9C8 \uC804\uCCB4: goals/_meta.md"));
|
|
2927
2390
|
}
|
|
2928
2391
|
}
|
|
2929
2392
|
async function goalNext() {
|
|
2930
|
-
console.log(
|
|
2393
|
+
console.log(chalk7.bold(`
|
|
2931
2394
|
${ko.goal.nextTitle}
|
|
2932
2395
|
`));
|
|
2933
2396
|
const goals = listGoals(GOALS_DIR);
|
|
2934
2397
|
if (goals.length === 0) {
|
|
2935
|
-
console.log(
|
|
2936
|
-
console.log(
|
|
2398
|
+
console.log(chalk7.yellow(" \u{1F4ED} \uC815\uC758\uB41C goal \uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2399
|
+
console.log(chalk7.dim(" vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
|
|
2937
2400
|
return;
|
|
2938
2401
|
}
|
|
2939
2402
|
const activeId = selectActiveId(goals);
|
|
2940
2403
|
if (activeId === null) {
|
|
2941
|
-
console.log(
|
|
2404
|
+
console.log(chalk7.green(" \u{1F389} \uBAA8\uB4E0 goal \uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"));
|
|
2942
2405
|
return;
|
|
2943
2406
|
}
|
|
2944
2407
|
const active = goals.find((g) => g.frontmatter.id === activeId);
|
|
@@ -2958,9 +2421,9 @@ ${ko.goal.nextTitle}
|
|
|
2958
2421
|
""
|
|
2959
2422
|
].join("\n");
|
|
2960
2423
|
mkdirSync2(STATE_DIR2, { recursive: true });
|
|
2961
|
-
writeFileSync2(
|
|
2424
|
+
writeFileSync2(join4(STATE_DIR2, "next-task.md"), text, "utf-8");
|
|
2962
2425
|
console.log(
|
|
2963
|
-
|
|
2426
|
+
chalk7.green(
|
|
2964
2427
|
` \u2705 next-task.md \uAC31\uC2E0 \u2014 Goal ${activeId}: ${active.frontmatter.title ?? ""}`
|
|
2965
2428
|
)
|
|
2966
2429
|
);
|
|
@@ -2979,35 +2442,70 @@ version: v0.1
|
|
|
2979
2442
|
## Forbidden Actions (\uC804\uC5ED)
|
|
2980
2443
|
|
|
2981
2444
|
- (\uD574\uB2F9 \uC0AC\uD56D)
|
|
2445
|
+
|
|
2446
|
+
## Goal \uD30C\uC77C \uC2A4\uD0A4\uB9C8 (\uD544\uB3C5 \u2014 VHK-021)
|
|
2447
|
+
|
|
2448
|
+
\`vhk goal list/next/check/done\` \uB294 \`goals/*.md\`(\uC774 \`_meta.md\` \uC81C\uC678) \uC911 \uC544\uB798
|
|
2449
|
+
frontmatter \uB97C \uB9CC\uC871\uD558\uB294 \uD30C\uC77C\uB9CC goal \uB85C \uC778\uC2DD\uD55C\uB2E4. **\uD558\uB098\uB77C\uB3C4 \uC5B4\uAE0B\uB098\uBA74 \uC870\uC6A9\uD788 \uBB34\uC2DC**\uB418\uBA70
|
|
2450
|
+
\`vhk goal list\` \uAC00 \uACBD\uACE0\uB85C \uC54C\uB824\uC900\uB2E4.
|
|
2451
|
+
|
|
2452
|
+
| \uD544\uB4DC | \uD544\uC218 | \uAC12 |
|
|
2453
|
+
| --- | --- | --- |
|
|
2454
|
+
| \`type\` | \u2705 | \`goal\` (\uBB38\uC790\uC5F4 \uADF8\uB300\uB85C) |
|
|
2455
|
+
| \`id\` | \u2705 | **\uC22B\uC790\uB9CC** (\`1\`, \`2\` \u2026 \u2014 \`G1\` \uAC19\uC740 \uBB38\uC790\uC5F4 \u274C) |
|
|
2456
|
+
| \`status\` | \u2705 | \`NOT_STARTED\` | \`IN_PROGRESS\` | \`DONE\` | \`BLOCKED\` |
|
|
2457
|
+
| \`priority\` | \uAD8C\uC7A5 | \`P0\` | \`P1\` | \`P2\` |
|
|
2458
|
+
| \`title\` | \uAD8C\uC7A5 | \uD55C \uC904 \uC81C\uBAA9 |
|
|
2459
|
+
|
|
2460
|
+
\uD30C\uC77C\uBA85 \uADDC\uCE59: \`goals/<id>-<name>.md\` (\uC608: \`goals/1-login.md\`).
|
|
2461
|
+
|
|
2462
|
+
### \uC0C8 goal \uD15C\uD50C\uB9BF (\uBCF5\uBD99)
|
|
2463
|
+
|
|
2464
|
+
\`\`\`markdown
|
|
2465
|
+
---
|
|
2466
|
+
vhk_format: 1
|
|
2467
|
+
type: goal
|
|
2468
|
+
id: 1
|
|
2469
|
+
title: \uB85C\uADF8\uC778 \uAE30\uB2A5
|
|
2470
|
+
status: NOT_STARTED
|
|
2471
|
+
priority: P0
|
|
2472
|
+
---
|
|
2473
|
+
|
|
2474
|
+
# Goal 1: \uB85C\uADF8\uC778 \uAE30\uB2A5
|
|
2475
|
+
|
|
2476
|
+
## \uBC30\uACBD / \uB3D9\uC791 / Completion Check ...
|
|
2477
|
+
\`\`\`
|
|
2478
|
+
|
|
2479
|
+
\uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8\uB294 \`vhk goal sync\` \uB85C \`scripts/check-goal-<id>.mjs\` \uB97C \uBC31\uD544\uD55C\uB2E4.
|
|
2982
2480
|
`;
|
|
2983
2481
|
var STATE_NEXT_TASK_TEMPLATE = "# Next Task\n\n```\nTASK: (vhk goal next \uB85C \uC790\uB3D9 \uAC31\uC2E0)\n```\n";
|
|
2984
2482
|
var STATE_BLOCKERS_TEMPLATE = "# Blockers\n\n_Append-only. \uD574\uACB0 \uD56D\uBAA9\uC740 ~~\uCDE8\uC18C\uC120~~\uC73C\uB85C \uD45C\uAE30._\n";
|
|
2985
2483
|
var STATE_LEARNINGS_TEMPLATE = "# Learnings\n\n_Append-only. \uD55C \uC904 = \uD55C \uAD50\uD6C8._\n";
|
|
2986
2484
|
async function goalInit() {
|
|
2987
|
-
console.log(
|
|
2485
|
+
console.log(chalk7.bold(`
|
|
2988
2486
|
${ko.goal.initTitle}
|
|
2989
2487
|
`));
|
|
2990
2488
|
const targets = [
|
|
2991
|
-
{ path:
|
|
2992
|
-
{ path:
|
|
2993
|
-
{ path:
|
|
2994
|
-
{ path:
|
|
2489
|
+
{ path: join4(GOALS_DIR, "_meta.md"), content: META_TEMPLATE },
|
|
2490
|
+
{ path: join4(STATE_DIR2, "next-task.md"), content: STATE_NEXT_TASK_TEMPLATE },
|
|
2491
|
+
{ path: join4(STATE_DIR2, "blockers.md"), content: STATE_BLOCKERS_TEMPLATE },
|
|
2492
|
+
{ path: join4(STATE_DIR2, "learnings.md"), content: STATE_LEARNINGS_TEMPLATE }
|
|
2995
2493
|
];
|
|
2996
2494
|
mkdirSync2(GOALS_DIR, { recursive: true });
|
|
2997
2495
|
mkdirSync2(STATE_DIR2, { recursive: true });
|
|
2998
2496
|
let created = 0;
|
|
2999
2497
|
let skipped = 0;
|
|
3000
2498
|
for (const t2 of targets) {
|
|
3001
|
-
if (
|
|
3002
|
-
console.log(
|
|
2499
|
+
if (existsSync3(t2.path)) {
|
|
2500
|
+
console.log(chalk7.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${t2.path}`));
|
|
3003
2501
|
skipped++;
|
|
3004
2502
|
} else {
|
|
3005
2503
|
writeFileSync2(t2.path, t2.content, "utf-8");
|
|
3006
|
-
console.log(
|
|
2504
|
+
console.log(chalk7.green(` \u2713 created: ${t2.path}`));
|
|
3007
2505
|
created++;
|
|
3008
2506
|
}
|
|
3009
2507
|
}
|
|
3010
|
-
console.log(
|
|
2508
|
+
console.log(chalk7.bold(`
|
|
3011
2509
|
\u{1F4CA} created=${created} skipped=${skipped}`));
|
|
3012
2510
|
if (created > 0) {
|
|
3013
2511
|
printNextStep({
|
|
@@ -3018,10 +2516,10 @@ ${ko.goal.initTitle}
|
|
|
3018
2516
|
}
|
|
3019
2517
|
}
|
|
3020
2518
|
function findGateScript(id) {
|
|
3021
|
-
const mjs =
|
|
3022
|
-
if (
|
|
3023
|
-
const sh =
|
|
3024
|
-
if (
|
|
2519
|
+
const mjs = join4(SCRIPTS_DIR, `check-goal-${id}.mjs`);
|
|
2520
|
+
if (existsSync3(mjs)) return mjs;
|
|
2521
|
+
const sh = join4(SCRIPTS_DIR, `check-goal-${id}.sh`);
|
|
2522
|
+
if (existsSync3(sh)) return sh;
|
|
3025
2523
|
return null;
|
|
3026
2524
|
}
|
|
3027
2525
|
function runGate(scriptPath) {
|
|
@@ -3030,82 +2528,93 @@ function runGate(scriptPath) {
|
|
|
3030
2528
|
const r = safeExecFile(runner, [scriptPath]);
|
|
3031
2529
|
return { ok: r.ok, out: r.out, err: r.ok ? "" : r.err, runner };
|
|
3032
2530
|
}
|
|
2531
|
+
function warnIfBashOnWindows(scriptPath) {
|
|
2532
|
+
if (process.platform === "win32" && scriptPath.endsWith(".sh")) {
|
|
2533
|
+
console.log(
|
|
2534
|
+
chalk7.yellow(
|
|
2535
|
+
" \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"
|
|
2536
|
+
)
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
3033
2540
|
async function goalCheck(opts) {
|
|
3034
|
-
console.log(
|
|
2541
|
+
console.log(chalk7.bold(`
|
|
3035
2542
|
${ko.goal.checkTitle}
|
|
3036
2543
|
`));
|
|
3037
2544
|
const goals = listGoals(GOALS_DIR);
|
|
3038
2545
|
const id = resolveGoalId(opts.id, goals);
|
|
3039
2546
|
if (id === null) {
|
|
3040
2547
|
console.log(
|
|
3041
|
-
|
|
2548
|
+
chalk7.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
|
|
3042
2549
|
);
|
|
3043
2550
|
process.exitCode = 1;
|
|
3044
2551
|
return;
|
|
3045
2552
|
}
|
|
3046
2553
|
if (!goals.some((g) => g.frontmatter.id === id)) {
|
|
3047
|
-
console.log(
|
|
2554
|
+
console.log(chalk7.red(` \u274C ${ko.goal.notFound(id)}`));
|
|
3048
2555
|
process.exitCode = 1;
|
|
3049
2556
|
return;
|
|
3050
2557
|
}
|
|
3051
2558
|
const scriptPath = findGateScript(id);
|
|
3052
2559
|
if (!scriptPath) {
|
|
3053
2560
|
console.log(
|
|
3054
|
-
|
|
2561
|
+
chalk7.red(` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C: scripts/check-goal-${id}.{mjs,sh}`)
|
|
3055
2562
|
);
|
|
3056
2563
|
process.exitCode = 1;
|
|
3057
2564
|
return;
|
|
3058
2565
|
}
|
|
2566
|
+
warnIfBashOnWindows(scriptPath);
|
|
3059
2567
|
const gate2 = runGate(scriptPath);
|
|
3060
|
-
console.log(
|
|
2568
|
+
console.log(chalk7.dim(` \u25B6 ${gate2.runner} ${scriptPath}
|
|
3061
2569
|
`));
|
|
3062
2570
|
if (gate2.out) console.log(gate2.out);
|
|
3063
2571
|
if (gate2.ok) {
|
|
3064
|
-
console.log(
|
|
2572
|
+
console.log(chalk7.green(`
|
|
3065
2573
|
\u2705 Goal ${id} \uAC8C\uC774\uD2B8 \uD1B5\uACFC`));
|
|
3066
2574
|
} else {
|
|
3067
|
-
console.log(
|
|
2575
|
+
console.log(chalk7.red(`
|
|
3068
2576
|
\u274C Goal ${id} \uAC8C\uC774\uD2B8 \uC2E4\uD328`));
|
|
3069
|
-
if (gate2.err && !gate2.out) console.log(
|
|
2577
|
+
if (gate2.err && !gate2.out) console.log(chalk7.dim(gate2.err.slice(0, 500)));
|
|
3070
2578
|
process.exitCode = 1;
|
|
3071
2579
|
}
|
|
3072
2580
|
}
|
|
3073
2581
|
async function goalDone(opts) {
|
|
3074
|
-
console.log(
|
|
2582
|
+
console.log(chalk7.bold(`
|
|
3075
2583
|
${ko.goal.doneTitle}
|
|
3076
2584
|
`));
|
|
3077
2585
|
const goals = listGoals(GOALS_DIR);
|
|
3078
2586
|
const id = resolveGoalId(opts.id, goals);
|
|
3079
2587
|
if (id === null) {
|
|
3080
2588
|
console.log(
|
|
3081
|
-
|
|
2589
|
+
chalk7.yellow(" \u26A0 \uB300\uC0C1 goal \uC744 \uACB0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (--id \uBA85\uC2DC \uB610\uB294 active goal \uD544\uC694).")
|
|
3082
2590
|
);
|
|
3083
2591
|
process.exitCode = 1;
|
|
3084
2592
|
return;
|
|
3085
2593
|
}
|
|
3086
2594
|
const target = goals.find((g) => g.frontmatter.id === id);
|
|
3087
2595
|
if (!target) {
|
|
3088
|
-
console.log(
|
|
2596
|
+
console.log(chalk7.red(` \u274C ${ko.goal.notFound(id)}`));
|
|
3089
2597
|
process.exitCode = 1;
|
|
3090
2598
|
return;
|
|
3091
2599
|
}
|
|
3092
2600
|
const scriptPath = findGateScript(id);
|
|
3093
2601
|
if (!scriptPath) {
|
|
3094
2602
|
console.log(
|
|
3095
|
-
|
|
2603
|
+
chalk7.red(
|
|
3096
2604
|
` \u274C \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uC5C6\uC74C \u2014 done \uCC98\uB9AC \uAC70\uBD80: scripts/check-goal-${id}.{mjs,sh}`
|
|
3097
2605
|
)
|
|
3098
2606
|
);
|
|
3099
2607
|
process.exitCode = 1;
|
|
3100
2608
|
return;
|
|
3101
2609
|
}
|
|
2610
|
+
warnIfBashOnWindows(scriptPath);
|
|
3102
2611
|
const gate2 = runGate(scriptPath);
|
|
3103
|
-
console.log(
|
|
2612
|
+
console.log(chalk7.dim(` \u25B6 \uAC8C\uC774\uD2B8 \uAC80\uC99D: ${gate2.runner} ${scriptPath}
|
|
3104
2613
|
`));
|
|
3105
2614
|
if (gate2.out) console.log(gate2.out);
|
|
3106
2615
|
if (!gate2.ok) {
|
|
3107
2616
|
console.log(
|
|
3108
|
-
|
|
2617
|
+
chalk7.red(
|
|
3109
2618
|
`
|
|
3110
2619
|
\u274C \uAC8C\uC774\uD2B8 \uC2E4\uD328 \u2014 frontmatter \uBCC0\uACBD \uC5C6\uC774 \uC885\uB8CC. (Forbidden: \uC2E4\uD328 = \uBCF4\uC874)`
|
|
3111
2620
|
)
|
|
@@ -3113,11 +2622,11 @@ ${ko.goal.doneTitle}
|
|
|
3113
2622
|
process.exitCode = 1;
|
|
3114
2623
|
return;
|
|
3115
2624
|
}
|
|
3116
|
-
const content =
|
|
2625
|
+
const content = readFileSync3(target.filePath, "utf-8");
|
|
3117
2626
|
const today = localDate();
|
|
3118
2627
|
const updated = updateFrontmatterStatus(content, "DONE", { completed: today });
|
|
3119
2628
|
writeFileSync2(target.filePath, updated, "utf-8");
|
|
3120
|
-
console.log(
|
|
2629
|
+
console.log(chalk7.green(`
|
|
3121
2630
|
\u2705 Goal ${id} \u2192 DONE (completed: ${today})`));
|
|
3122
2631
|
printNextStep({
|
|
3123
2632
|
message: `Goal ${id} \uC644\uB8CC! \uB2E4\uC74C goal \uB85C:`,
|
|
@@ -3125,6 +2634,112 @@ ${ko.goal.doneTitle}
|
|
|
3125
2634
|
cursorHint: "\uB2E4\uC74C goal \uC54C\uB824\uC918"
|
|
3126
2635
|
});
|
|
3127
2636
|
}
|
|
2637
|
+
function generateGateScript(id) {
|
|
2638
|
+
const ID = String(id);
|
|
2639
|
+
return [
|
|
2640
|
+
"#!/usr/bin/env node",
|
|
2641
|
+
`// scripts/check-goal-${ID}.mjs \u2014 \uC790\uB3D9 \uC0DD\uC131 (vhk goal sync).`,
|
|
2642
|
+
"// \uAE30\uBCF8 \uAC8C\uC774\uD2B8 = typecheck + (lint) + test + build. goal \uACE0\uC720 \uAC80\uC99D\uC740 \uC544\uB798 \uAD6C\uC5ED\uC5D0 \uCD94\uAC00.",
|
|
2643
|
+
"// sync \uC7AC\uC2E4\uD589\uD574\uB3C4 \uAE30\uC874 \uD30C\uC77C\uC740 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (idempotent).",
|
|
2644
|
+
"//",
|
|
2645
|
+
"// Env: VHK_GATES_SKIP_DEEP=1 \u2192 test + build \uC2A4\uD0B5 (\uBE60\uB978 typecheck-only \uD328\uC2A4)",
|
|
2646
|
+
"",
|
|
2647
|
+
"import { execFileSync } from 'node:child_process'",
|
|
2648
|
+
"import { existsSync, readFileSync } from 'node:fs'",
|
|
2649
|
+
"",
|
|
2650
|
+
"const SHIM = new Set(['pnpm', 'npm', 'npx', 'yarn'])",
|
|
2651
|
+
"function run(cmd, args) {",
|
|
2652
|
+
" let bin = cmd, argv = args",
|
|
2653
|
+
" if (process.platform === 'win32' && SHIM.has(cmd)) {",
|
|
2654
|
+
" // Windows: .cmd shim \uC9C1\uC811 spawn \uC740 Node CVE-2024-27980 \uC73C\uB85C EINVAL \u2192 cmd.exe \uB798\uD551.",
|
|
2655
|
+
" bin = 'cmd.exe'; argv = ['/d', '/s', '/c', cmd + '.cmd', ...args]",
|
|
2656
|
+
" }",
|
|
2657
|
+
" try {",
|
|
2658
|
+
" // maxBuffer \uC0C1\uD5A5: \uD070 \uBE4C\uB4DC/\uD14C\uC2A4\uD2B8 \uB85C\uADF8(>1MB)\uC5D0\uC11C \uC131\uACF5\uD574\uB3C4 ENOBUFS \uAC70\uC9D3\uC2E4\uD328 \uBC29\uC9C0.",
|
|
2659
|
+
" execFileSync(bin, argv, { stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 })",
|
|
2660
|
+
" return true",
|
|
2661
|
+
" } catch (e) {",
|
|
2662
|
+
" const out = (e?.stdout?.toString() ?? '') + (e?.stderr?.toString() ?? '')",
|
|
2663
|
+
" if (out.trim()) console.log(out.split('\\n').slice(-25).join('\\n'))",
|
|
2664
|
+
" return false",
|
|
2665
|
+
" }",
|
|
2666
|
+
"}",
|
|
2667
|
+
"",
|
|
2668
|
+
"if (existsSync('.vhk/HARD_STOP')) {",
|
|
2669
|
+
` console.log('\u{1F6D1} .vhk/HARD_STOP detected \u2014 refusing to run goal ${ID} gate.')`,
|
|
2670
|
+
" process.exit(1)",
|
|
2671
|
+
"}",
|
|
2672
|
+
"",
|
|
2673
|
+
"const pkg = existsSync('package.json') ? JSON.parse(readFileSync('package.json', 'utf-8')) : {}",
|
|
2674
|
+
"const scripts = pkg.scripts ?? {}",
|
|
2675
|
+
"const pm = existsSync('pnpm-lock.yaml') ? 'pnpm' : existsSync('yarn.lock') ? 'yarn' : 'npm'",
|
|
2676
|
+
"const skipDeep = process.env.VHK_GATES_SKIP_DEEP === '1'",
|
|
2677
|
+
"let pass = true",
|
|
2678
|
+
`const gate = (label, ok) => { console.log('[goal ${ID}] ' + label + ': ' + (ok ? '\u2713' : '\u2717')); if (!ok) pass = false }`,
|
|
2679
|
+
"const must = (cond, label) => { console.log((cond ? ' \u2713 ' : ' \u2717 ') + label); if (!cond) pass = false }",
|
|
2680
|
+
"",
|
|
2681
|
+
"// typecheck (\uC2A4\uD06C\uB9BD\uD2B8 \uC6B0\uC120, \uC5C6\uC73C\uBA74 tsc --noEmit)",
|
|
2682
|
+
"if (scripts.typecheck) gate('typecheck', run(pm, ['run', 'typecheck']))",
|
|
2683
|
+
"else if (existsSync('tsconfig.json')) gate('tsc --noEmit', run(pm, pm === 'npm' ? ['exec', '--', 'tsc', '--noEmit'] : ['exec', 'tsc', '--noEmit']))",
|
|
2684
|
+
"if (scripts.lint) gate('lint', run(pm, ['run', 'lint']))",
|
|
2685
|
+
"if (!skipDeep) {",
|
|
2686
|
+
" if (scripts['test:run']) gate('test', run(pm, ['run', 'test:run']))",
|
|
2687
|
+
" else if (scripts.test && /vitest/.test(scripts.test)) gate('test', run(pm, ['run', 'test', '--', '--run']))",
|
|
2688
|
+
" else if (scripts.test) gate('test', run(pm, ['run', 'test']))",
|
|
2689
|
+
" if (scripts.build) gate('build', run(pm, ['run', 'build']))",
|
|
2690
|
+
"}",
|
|
2691
|
+
"",
|
|
2692
|
+
`// \u2500\u2500\u2500 goal ${ID} \uACE0\uC720 \uAC80\uC99D (\uC9C1\uC811 \uCD94\uAC00) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
2693
|
+
"// const read = (p) => existsSync(p) ? readFileSync(p, 'utf-8') : null",
|
|
2694
|
+
"// must(read('src/foo.ts')?.includes('bar'), 'foo.ts \uC5D0 bar \uC874\uC7AC')",
|
|
2695
|
+
"",
|
|
2696
|
+
`if (pass) { console.log('\u2705 goal ${ID} gate passes'); process.exit(0) }`,
|
|
2697
|
+
`console.log('\u274C goal ${ID} gate failed'); process.exit(1)`,
|
|
2698
|
+
""
|
|
2699
|
+
].join("\n");
|
|
2700
|
+
}
|
|
2701
|
+
async function goalSync() {
|
|
2702
|
+
console.log(chalk7.bold(`
|
|
2703
|
+
${ko.goal.syncTitle}
|
|
2704
|
+
`));
|
|
2705
|
+
const goals = listGoals(GOALS_DIR);
|
|
2706
|
+
const result = { created: [], skipped: [] };
|
|
2707
|
+
if (goals.length === 0) {
|
|
2708
|
+
console.log(
|
|
2709
|
+
chalk7.yellow(" \u{1F4ED} goals/ \uC5D0 goal \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694.")
|
|
2710
|
+
);
|
|
2711
|
+
return result;
|
|
2712
|
+
}
|
|
2713
|
+
mkdirSync2(SCRIPTS_DIR, { recursive: true });
|
|
2714
|
+
for (const g of goals) {
|
|
2715
|
+
const id = g.frontmatter.id;
|
|
2716
|
+
if (typeof id !== "number") continue;
|
|
2717
|
+
const target = join4(SCRIPTS_DIR, `check-goal-${id}.mjs`);
|
|
2718
|
+
if (existsSync3(target)) {
|
|
2719
|
+
console.log(chalk7.gray(` \u2298 skip (\uC774\uBBF8 \uC874\uC7AC): ${target}`));
|
|
2720
|
+
result.skipped.push(id);
|
|
2721
|
+
continue;
|
|
2722
|
+
}
|
|
2723
|
+
const shOnly = existsSync3(join4(SCRIPTS_DIR, `check-goal-${id}.sh`));
|
|
2724
|
+
writeFileSync2(target, generateGateScript(id), "utf-8");
|
|
2725
|
+
console.log(
|
|
2726
|
+
chalk7.green(` \u2713 created: ${target}${shOnly ? " (.sh \u2192 .mjs \uBC31\uD544, Windows 1\uAE09)" : ""}`)
|
|
2727
|
+
);
|
|
2728
|
+
result.created.push(id);
|
|
2729
|
+
}
|
|
2730
|
+
console.log(
|
|
2731
|
+
chalk7.bold(`
|
|
2732
|
+
\u{1F4CA} created=${result.created.length} skipped=${result.skipped.length}`)
|
|
2733
|
+
);
|
|
2734
|
+
if (result.created.length > 0) {
|
|
2735
|
+
printNextStep({
|
|
2736
|
+
message: `\uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 ${result.created.length}\uAC1C \uC0DD\uC131 (goal ${result.created.join(", ")}). \uAC80\uC99D\uD558\uB824\uBA74:`,
|
|
2737
|
+
command: `vhk goal check --id ${result.created[0]}`,
|
|
2738
|
+
cursorHint: `goal ${result.created[0]} \uAC8C\uC774\uD2B8 \uAC80\uC99D\uD574\uC918`
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
return result;
|
|
2742
|
+
}
|
|
3128
2743
|
|
|
3129
2744
|
// src/commands/check.ts
|
|
3130
2745
|
async function check(opts = {}) {
|
|
@@ -3134,22 +2749,22 @@ async function check(opts = {}) {
|
|
|
3134
2749
|
return checkRules();
|
|
3135
2750
|
}
|
|
3136
2751
|
async function checkRules() {
|
|
3137
|
-
console.log(
|
|
2752
|
+
console.log(chalk8.bold(`
|
|
3138
2753
|
${ko.check.title}
|
|
3139
2754
|
`));
|
|
3140
2755
|
const cwd = process.cwd();
|
|
3141
|
-
const rulesPath =
|
|
3142
|
-
if (!
|
|
3143
|
-
console.log(
|
|
3144
|
-
console.log(
|
|
2756
|
+
const rulesPath = path7.join(cwd, "RULES.md");
|
|
2757
|
+
if (!fs6.existsSync(rulesPath)) {
|
|
2758
|
+
console.log(chalk8.yellow(ko.check.noRules));
|
|
2759
|
+
console.log(chalk8.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
3145
2760
|
return;
|
|
3146
2761
|
}
|
|
3147
2762
|
const rules = parseRules(rulesPath);
|
|
3148
|
-
console.log(
|
|
2763
|
+
console.log(chalk8.dim(` \u{1F4CF} \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${rules.length}\uAC1C \uAC10\uC9C0 (\uB098\uBA38\uC9C0 \uADDC\uCE59\uC740 \uC218\uB3D9/\uB3C4\uAD6C \uD655\uC778)
|
|
3149
2764
|
`));
|
|
3150
2765
|
if (rules.length === 0) {
|
|
3151
|
-
console.log(
|
|
3152
|
-
console.log(
|
|
2766
|
+
console.log(chalk8.yellow(ko.check.noAutoRules));
|
|
2767
|
+
console.log(chalk8.dim(" RULES.md\uC5D0 \uD30C\uC77C \uC774\uB984\xB7\uD3F4\uB354 \uADDC\uCE59\uC744 \uC801\uC73C\uBA74 \uC790\uB3D9\uC73C\uB85C \uC810\uAC80\uD574\uC694."));
|
|
3153
2768
|
return;
|
|
3154
2769
|
}
|
|
3155
2770
|
const allViolations = [];
|
|
@@ -3157,14 +2772,14 @@ ${ko.check.title}
|
|
|
3157
2772
|
for (const rule of rules) {
|
|
3158
2773
|
const violations = rule.check(cwd);
|
|
3159
2774
|
if (violations.length === 0) {
|
|
3160
|
-
const patternHint = rule.type === "content" && rule.pattern ?
|
|
3161
|
-
console.log(
|
|
2775
|
+
const patternHint = rule.type === "content" && rule.pattern ? chalk8.dim(` [\uAC80\uC0AC: ${rule.pattern.source}]`) : "";
|
|
2776
|
+
console.log(chalk8.green(` \u2705 ${rule.id}`) + chalk8.dim(` \u2014 ${rule.description.slice(0, 60)}`) + patternHint);
|
|
3162
2777
|
passCount++;
|
|
3163
2778
|
} else {
|
|
3164
|
-
console.log(
|
|
2779
|
+
console.log(chalk8.red(` \u274C ${rule.id}`) + chalk8.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
|
|
3165
2780
|
violations.forEach((v) => {
|
|
3166
|
-
const loc = v.file ?
|
|
3167
|
-
const icon = v.severity === "error" ?
|
|
2781
|
+
const loc = v.file ? chalk8.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
|
|
2782
|
+
const icon = v.severity === "error" ? chalk8.red("\u2716") : v.severity === "warning" ? chalk8.yellow("\u26A0") : chalk8.blue("\u2139");
|
|
3168
2783
|
console.log(` ${icon} ${v.message}${loc}`);
|
|
3169
2784
|
});
|
|
3170
2785
|
allViolations.push(...violations);
|
|
@@ -3174,18 +2789,18 @@ ${ko.check.title}
|
|
|
3174
2789
|
const errors = allViolations.filter((v) => v.severity === "error").length;
|
|
3175
2790
|
const warnings = allViolations.filter((v) => v.severity === "warning").length;
|
|
3176
2791
|
if (allViolations.length === 0) {
|
|
3177
|
-
console.log(
|
|
3178
|
-
console.log(
|
|
2792
|
+
console.log(chalk8.green.bold(`\u2705 \uC790\uB3D9 \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 ${passCount}\uAC1C \uD1B5\uACFC`));
|
|
2793
|
+
console.log(chalk8.dim(" (RULES.md \uC758 \uB098\uBA38\uC9C0 \uADDC\uCE59\uC740 \uCF54\uB4DC \uC790\uB3D9 \uAC80\uC0AC \uBD88\uAC00 \u2014 \uC9C1\uC811/\uB3C4\uAD6C\uB85C \uD655\uC778\uD558\uC138\uC694.)"));
|
|
3179
2794
|
printNextStep({
|
|
3180
2795
|
message: "\uBAA8\uB4E0 \uADDC\uCE59 \uD1B5\uACFC! \uBCF4\uC548 \uC2A4\uCE94\uB3C4 \uD574\uBCFC\uAE4C\uC694?",
|
|
3181
2796
|
command: "vhk \uBCF4\uC548 scan",
|
|
3182
2797
|
cursorHint: "\uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB824\uC918"
|
|
3183
2798
|
});
|
|
3184
2799
|
} else {
|
|
3185
|
-
console.log(
|
|
3186
|
-
console.log(` \uADDC\uCE59: ${
|
|
3187
|
-
if (errors > 0) console.log(` ${
|
|
3188
|
-
if (warnings > 0) console.log(` ${
|
|
2800
|
+
console.log(chalk8.bold(ko.check.summary));
|
|
2801
|
+
console.log(` \uADDC\uCE59: ${chalk8.cyan(String(rules.length))}\uAC1C | \uD1B5\uACFC: ${chalk8.green(String(passCount))}\uAC1C | \uC704\uBC18: ${chalk8.red(String(allViolations.length))}\uAC74`);
|
|
2802
|
+
if (errors > 0) console.log(` ${chalk8.red(`\u2716 ${errors}\uAC1C \uC5D0\uB7EC`)}`);
|
|
2803
|
+
if (warnings > 0) console.log(` ${chalk8.yellow(`\u26A0 ${warnings}\uAC1C \uACBD\uACE0`)}`);
|
|
3189
2804
|
printNextStep({
|
|
3190
2805
|
message: "\uC704\uBC18 \uD56D\uBAA9\uC744 \uC218\uC815\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
|
|
3191
2806
|
command: "vhk \uC810\uAC80",
|
|
@@ -3198,36 +2813,36 @@ ${ko.check.title}
|
|
|
3198
2813
|
}
|
|
3199
2814
|
|
|
3200
2815
|
// src/commands/secure.ts
|
|
3201
|
-
import
|
|
3202
|
-
import
|
|
3203
|
-
import
|
|
2816
|
+
import chalk9 from "chalk";
|
|
2817
|
+
import fs7 from "fs";
|
|
2818
|
+
import path8 from "path";
|
|
3204
2819
|
async function secure() {
|
|
3205
|
-
console.log(
|
|
2820
|
+
console.log(chalk9.bold(`
|
|
3206
2821
|
${ko.secure.title}
|
|
3207
2822
|
`));
|
|
3208
2823
|
const cwd = process.cwd();
|
|
3209
|
-
const gitignorePath =
|
|
3210
|
-
const hasGitignore =
|
|
2824
|
+
const gitignorePath = path8.join(cwd, ".gitignore");
|
|
2825
|
+
const hasGitignore = fs7.existsSync(gitignorePath);
|
|
3211
2826
|
if (!hasGitignore) {
|
|
3212
|
-
console.log(
|
|
3213
|
-
console.log(
|
|
2827
|
+
console.log(chalk9.yellow(` ${ko.secure.noGitignore}`));
|
|
2828
|
+
console.log(chalk9.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
|
|
3214
2829
|
} else {
|
|
3215
|
-
const gitignoreContent =
|
|
2830
|
+
const gitignoreContent = fs7.readFileSync(gitignorePath, "utf-8");
|
|
3216
2831
|
if (!gitignoreContent.includes(".env")) {
|
|
3217
|
-
console.log(
|
|
3218
|
-
console.log(
|
|
2832
|
+
console.log(chalk9.yellow(` ${ko.secure.noEnvInGitignore}`));
|
|
2833
|
+
console.log(chalk9.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
|
|
3219
2834
|
}
|
|
3220
2835
|
}
|
|
3221
|
-
console.log(
|
|
2836
|
+
console.log(chalk9.dim(` ${ko.secure.scanning}
|
|
3222
2837
|
`));
|
|
3223
2838
|
const { findings, scannedFiles, truncated } = scanProjectForSecrets(cwd);
|
|
3224
|
-
console.log(
|
|
2839
|
+
console.log(chalk9.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC (lock\xB7node_modules\xB7>${MAX_SCAN_FILE_BYTES / 1024}KB \uC81C\uC678)`));
|
|
3225
2840
|
if (truncated) {
|
|
3226
|
-
console.log(
|
|
2841
|
+
console.log(chalk9.yellow(` \u26A0\uFE0F \uACB0\uACFC ${MAX_SECRET_FINDINGS}\uAC74\uC5D0\uC11C \uCD9C\uB825\uC744 \uC81C\uD55C\uD588\uC2B5\uB2C8\uB2E4. lock \uD30C\uC77C \uB4F1\uC740 \uC790\uB3D9 \uC81C\uC678\uB429\uB2C8\uB2E4.`));
|
|
3227
2842
|
}
|
|
3228
2843
|
console.log("");
|
|
3229
2844
|
if (findings.length === 0) {
|
|
3230
|
-
console.log(
|
|
2845
|
+
console.log(chalk9.green.bold(` ${ko.secure.clean}`));
|
|
3231
2846
|
printNextStep({
|
|
3232
2847
|
message: "\uBCF4\uC548 \uC774\uC0C1 \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
|
|
3233
2848
|
command: "vhk \uC815\uB9AC",
|
|
@@ -3239,45 +2854,45 @@ ${ko.secure.title}
|
|
|
3239
2854
|
const high = findings.filter((f) => f.severity === "high");
|
|
3240
2855
|
const medium = findings.filter((f) => f.severity === "medium");
|
|
3241
2856
|
if (critical.length > 0) {
|
|
3242
|
-
console.log(
|
|
2857
|
+
console.log(chalk9.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
|
|
3243
2858
|
critical.forEach((f) => {
|
|
3244
|
-
console.log(
|
|
3245
|
-
console.log(
|
|
2859
|
+
console.log(chalk9.red(` \u2716 ${f.patternName}`));
|
|
2860
|
+
console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
3246
2861
|
});
|
|
3247
2862
|
console.log("");
|
|
3248
2863
|
}
|
|
3249
2864
|
if (high.length > 0) {
|
|
3250
|
-
console.log(
|
|
2865
|
+
console.log(chalk9.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
|
|
3251
2866
|
high.forEach((f) => {
|
|
3252
|
-
console.log(
|
|
3253
|
-
console.log(
|
|
2867
|
+
console.log(chalk9.yellow(` \u26A0 ${f.patternName}`));
|
|
2868
|
+
console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
3254
2869
|
});
|
|
3255
2870
|
console.log("");
|
|
3256
2871
|
}
|
|
3257
2872
|
if (medium.length > 0) {
|
|
3258
|
-
console.log(
|
|
2873
|
+
console.log(chalk9.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
|
|
3259
2874
|
medium.forEach((f) => {
|
|
3260
|
-
console.log(
|
|
3261
|
-
console.log(
|
|
2875
|
+
console.log(chalk9.blue(` \u2139 ${f.patternName}`));
|
|
2876
|
+
console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
3262
2877
|
});
|
|
3263
2878
|
console.log("");
|
|
3264
2879
|
}
|
|
3265
|
-
console.log(
|
|
3266
|
-
console.log(` \uCD1D ${
|
|
2880
|
+
console.log(chalk9.bold(` ${ko.secure.summary}`));
|
|
2881
|
+
console.log(` \uCD1D ${chalk9.red(String(findings.length))}\uAC74 \uAC10\uC9C0 | CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length}`);
|
|
3267
2882
|
console.log("");
|
|
3268
|
-
console.log(
|
|
3269
|
-
console.log(
|
|
3270
|
-
console.log(
|
|
3271
|
-
console.log(
|
|
2883
|
+
console.log(chalk9.dim(" \u{1F4A1} \uC870\uCE58 \uBC29\uBC95:"));
|
|
2884
|
+
console.log(chalk9.dim(" 1. \uD574\uB2F9 \uD30C\uC77C\uC5D0\uC11C \uC2DC\uD06C\uB9BF\uC744 \uC81C\uAC70\uD558\uACE0 \uD658\uACBD\uBCC0\uC218\uB85C \uC774\uB3D9"));
|
|
2885
|
+
console.log(chalk9.dim(" 2. git history\uC5D0\uC11C\uB3C4 \uC81C\uAC70: git filter-branch \uB610\uB294 BFG Repo-Cleaner"));
|
|
2886
|
+
console.log(chalk9.dim(" 3. \uC720\uCD9C\uB41C \uD0A4\uB294 \uC989\uC2DC \uD3D0\uAE30\uD558\uACE0 \uC7AC\uBC1C\uAE09\n"));
|
|
3272
2887
|
if (critical.length > 0 || high.length > 0) {
|
|
3273
2888
|
process.exitCode = 1;
|
|
3274
2889
|
}
|
|
3275
2890
|
}
|
|
3276
2891
|
|
|
3277
2892
|
// src/commands/doctor.ts
|
|
3278
|
-
import
|
|
3279
|
-
import
|
|
3280
|
-
import
|
|
2893
|
+
import chalk10 from "chalk";
|
|
2894
|
+
import fs8 from "fs";
|
|
2895
|
+
import path9 from "path";
|
|
3281
2896
|
import { fileURLToPath } from "url";
|
|
3282
2897
|
function checkCommand(name, command, hint) {
|
|
3283
2898
|
const result = safeExecFile(command, ["--version"]);
|
|
@@ -3286,14 +2901,14 @@ function checkCommand(name, command, hint) {
|
|
|
3286
2901
|
return { name, command, version, ok: true, hint };
|
|
3287
2902
|
}
|
|
3288
2903
|
function getVhkVersion2() {
|
|
3289
|
-
const dir =
|
|
2904
|
+
const dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
3290
2905
|
const candidates = [
|
|
3291
|
-
|
|
3292
|
-
|
|
2906
|
+
path9.join(dir, "../package.json"),
|
|
2907
|
+
path9.join(dir, "../../package.json")
|
|
3293
2908
|
];
|
|
3294
2909
|
for (const pkgPath of candidates) {
|
|
3295
2910
|
try {
|
|
3296
|
-
if (
|
|
2911
|
+
if (fs8.existsSync(pkgPath)) {
|
|
3297
2912
|
const pkg = readJsonFile(pkgPath);
|
|
3298
2913
|
return pkg.version;
|
|
3299
2914
|
}
|
|
@@ -3321,7 +2936,7 @@ function compareSemver(a, b) {
|
|
|
3321
2936
|
return a3 - b3;
|
|
3322
2937
|
}
|
|
3323
2938
|
async function doctor() {
|
|
3324
|
-
console.log(
|
|
2939
|
+
console.log(chalk10.bold(`
|
|
3325
2940
|
${ko.doctor.title}
|
|
3326
2941
|
`));
|
|
3327
2942
|
const checks = [
|
|
@@ -3333,30 +2948,30 @@ ${ko.doctor.title}
|
|
|
3333
2948
|
let allOk = true;
|
|
3334
2949
|
for (const check2 of checks) {
|
|
3335
2950
|
if (check2.ok) {
|
|
3336
|
-
console.log(
|
|
2951
|
+
console.log(chalk10.green(` \u2705 ${check2.name}`) + chalk10.dim(` \u2014 ${check2.version}`));
|
|
3337
2952
|
} else {
|
|
3338
|
-
console.log(
|
|
3339
|
-
console.log(
|
|
2953
|
+
console.log(chalk10.red(` \u274C ${check2.name} \uC5C6\uC74C`));
|
|
2954
|
+
console.log(chalk10.dim(` \u2192 ${check2.hint}`));
|
|
3340
2955
|
allOk = false;
|
|
3341
2956
|
}
|
|
3342
2957
|
}
|
|
3343
2958
|
console.log("");
|
|
3344
2959
|
const vhkVersion = getVhkVersion2();
|
|
3345
2960
|
if (vhkVersion) {
|
|
3346
|
-
console.log(
|
|
2961
|
+
console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(` \u2014 v${vhkVersion}`));
|
|
3347
2962
|
} else {
|
|
3348
|
-
console.log(
|
|
2963
|
+
console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(" \u2014 \uC124\uCE58\uB428"));
|
|
3349
2964
|
}
|
|
3350
2965
|
if (vhkVersion) {
|
|
3351
2966
|
const latest = fetchLatestNpmVersion("@byh3071/vhk");
|
|
3352
2967
|
if (latest && compareSemver(latest, vhkVersion) > 0) {
|
|
3353
|
-
console.log(
|
|
2968
|
+
console.log(chalk10.yellow(` ${ko.doctor.updateAvailable(latest)}`));
|
|
3354
2969
|
} else if (latest) {
|
|
3355
|
-
console.log(
|
|
2970
|
+
console.log(chalk10.dim(` ${ko.doctor.updateCurrent}`));
|
|
3356
2971
|
}
|
|
3357
2972
|
}
|
|
3358
2973
|
console.log("");
|
|
3359
|
-
console.log(
|
|
2974
|
+
console.log(chalk10.bold(` ${ko.doctor.projectFiles}`));
|
|
3360
2975
|
const cwd = process.cwd();
|
|
3361
2976
|
const projectFiles = [
|
|
3362
2977
|
{ name: "RULES.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
|
|
@@ -3366,51 +2981,51 @@ ${ko.doctor.title}
|
|
|
3366
2981
|
{ name: ".env", hint: ".gitignore\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778" }
|
|
3367
2982
|
];
|
|
3368
2983
|
for (const file of projectFiles) {
|
|
3369
|
-
const exists =
|
|
2984
|
+
const exists = fs8.existsSync(path9.join(cwd, file.name));
|
|
3370
2985
|
if (exists) {
|
|
3371
|
-
console.log(
|
|
2986
|
+
console.log(chalk10.green(` \u2705 ${file.name}`));
|
|
3372
2987
|
if (file.name === ".env") {
|
|
3373
|
-
const gitignorePath =
|
|
3374
|
-
if (
|
|
3375
|
-
const gitignore =
|
|
2988
|
+
const gitignorePath = path9.join(cwd, ".gitignore");
|
|
2989
|
+
if (fs8.existsSync(gitignorePath)) {
|
|
2990
|
+
const gitignore = fs8.readFileSync(gitignorePath, "utf-8");
|
|
3376
2991
|
if (!gitignore.includes(".env")) {
|
|
3377
|
-
console.log(
|
|
2992
|
+
console.log(chalk10.yellow(` ${ko.doctor.envNotIgnored}`));
|
|
3378
2993
|
}
|
|
3379
2994
|
}
|
|
3380
2995
|
}
|
|
3381
|
-
} else if (file.name === ".env" &&
|
|
3382
|
-
console.log(
|
|
2996
|
+
} else if (file.name === ".env" && fs8.existsSync(path9.join(cwd, ".env.local"))) {
|
|
2997
|
+
console.log(chalk10.green(" \u2705 .env.local") + chalk10.dim(" \u2014 \uB85C\uCEEC env \uC0AC\uC6A9 \uC911 (.env \uC5C6\uC5B4\uB3C4 \uC815\uC0C1)"));
|
|
3383
2998
|
} else {
|
|
3384
|
-
console.log(
|
|
2999
|
+
console.log(chalk10.dim(` \u2B1A ${file.name}`) + chalk10.dim(` \u2014 ${file.hint}`));
|
|
3385
3000
|
}
|
|
3386
3001
|
}
|
|
3387
3002
|
console.log("");
|
|
3388
|
-
console.log(
|
|
3003
|
+
console.log(chalk10.bold(` ${ko.doctor.driftTitle}`));
|
|
3389
3004
|
const ruleDrift = checkRuleDrift(cwd);
|
|
3390
3005
|
if (!ruleDrift.checked) {
|
|
3391
|
-
console.log(
|
|
3006
|
+
console.log(chalk10.dim(` ${ko.doctor.driftNoRules}`));
|
|
3392
3007
|
} else {
|
|
3393
3008
|
const drifted = ruleDrift.results.filter((r) => r.status === "drifted");
|
|
3394
3009
|
if (drifted.length === 0) {
|
|
3395
|
-
console.log(
|
|
3010
|
+
console.log(chalk10.green(` ${ko.doctor.driftRuleClean}`));
|
|
3396
3011
|
} else {
|
|
3397
|
-
console.log(
|
|
3012
|
+
console.log(chalk10.yellow(` ${ko.doctor.driftRuleWarn(drifted.map((d) => d.path).join(", "))}`));
|
|
3398
3013
|
}
|
|
3399
3014
|
}
|
|
3400
3015
|
const ctxDrift = checkContextDrift(cwd);
|
|
3401
3016
|
if (ctxDrift.checked && ctxDrift.stale) {
|
|
3402
|
-
console.log(
|
|
3017
|
+
console.log(chalk10.yellow(` ${ko.doctor.driftContextWarn}`));
|
|
3403
3018
|
}
|
|
3404
3019
|
console.log("");
|
|
3405
3020
|
if (allOk) {
|
|
3406
|
-
console.log(
|
|
3021
|
+
console.log(chalk10.green.bold(` ${ko.doctor.allOk}`));
|
|
3407
3022
|
printNextStep({
|
|
3408
3023
|
message: ko.doctor.nextOkMessage,
|
|
3409
3024
|
command: "vhk \uC2DC\uC791",
|
|
3410
3025
|
cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
|
|
3411
3026
|
});
|
|
3412
3027
|
} else {
|
|
3413
|
-
console.log(
|
|
3028
|
+
console.log(chalk10.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
|
|
3414
3029
|
printNextStep({
|
|
3415
3030
|
message: ko.doctor.nextRetryMessage,
|
|
3416
3031
|
command: "vhk doctor",
|
|
@@ -3421,10 +3036,10 @@ ${ko.doctor.title}
|
|
|
3421
3036
|
}
|
|
3422
3037
|
|
|
3423
3038
|
// src/commands/ship.ts
|
|
3424
|
-
import
|
|
3425
|
-
import
|
|
3426
|
-
import
|
|
3427
|
-
import
|
|
3039
|
+
import chalk11 from "chalk";
|
|
3040
|
+
import inquirer4 from "inquirer";
|
|
3041
|
+
import fs9 from "fs";
|
|
3042
|
+
import path10 from "path";
|
|
3428
3043
|
var CHECKLIST = [
|
|
3429
3044
|
{ id: "build", questionKey: "checkBuild", hintKey: "hintBuild" },
|
|
3430
3045
|
{ id: "test", questionKey: "checkTest", hintKey: "hintTest" },
|
|
@@ -3437,9 +3052,9 @@ function sanitizeVersion(version) {
|
|
|
3437
3052
|
return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
|
|
3438
3053
|
}
|
|
3439
3054
|
function updateChangelogUnreleased(cwd, version, date) {
|
|
3440
|
-
const changelogPath =
|
|
3441
|
-
if (!
|
|
3442
|
-
const content =
|
|
3055
|
+
const changelogPath = path10.join(cwd, "CHANGELOG.md");
|
|
3056
|
+
if (!fs9.existsSync(changelogPath)) return { status: "missing" };
|
|
3057
|
+
const content = fs9.readFileSync(changelogPath, "utf-8");
|
|
3443
3058
|
const unreleasedHeading = /^## \[Unreleased\][^\n]*$/m;
|
|
3444
3059
|
if (!unreleasedHeading.test(content)) return { status: "no-unreleased" };
|
|
3445
3060
|
const blankUnreleased = [
|
|
@@ -3456,36 +3071,36 @@ function updateChangelogUnreleased(cwd, version, date) {
|
|
|
3456
3071
|
`## [${version}] \u2014 ${date}`
|
|
3457
3072
|
].join("\n");
|
|
3458
3073
|
const updated = content.replace(unreleasedHeading, blankUnreleased);
|
|
3459
|
-
|
|
3074
|
+
fs9.writeFileSync(changelogPath, updated, "utf-8");
|
|
3460
3075
|
return { status: "updated", version };
|
|
3461
3076
|
}
|
|
3462
3077
|
async function ship() {
|
|
3463
3078
|
if (!ensureNotHardStopped("ship")) return;
|
|
3464
|
-
console.log(
|
|
3079
|
+
console.log(chalk11.bold(`
|
|
3465
3080
|
${ko.ship.title}
|
|
3466
3081
|
`));
|
|
3467
3082
|
const cwd = process.cwd();
|
|
3468
|
-
console.log(
|
|
3083
|
+
console.log(chalk11.cyan.bold(` ${ko.ship.checklist}
|
|
3469
3084
|
`));
|
|
3470
|
-
const { passed } = await
|
|
3085
|
+
const { passed } = await inquirer4.prompt([{
|
|
3471
3086
|
type: "checkbox",
|
|
3472
3087
|
name: "passed",
|
|
3473
3088
|
message: ko.ship.checkboxPrompt,
|
|
3474
3089
|
choices: CHECKLIST.map((c) => ({
|
|
3475
|
-
name: `${ko.ship[c.questionKey]} ${
|
|
3090
|
+
name: `${ko.ship[c.questionKey]} ${chalk11.dim(`(${ko.ship[c.hintKey]})`)}`,
|
|
3476
3091
|
value: c.id
|
|
3477
3092
|
}))
|
|
3478
3093
|
}]);
|
|
3479
3094
|
const allPassed = passed.length === CHECKLIST.length;
|
|
3480
3095
|
const skipped = CHECKLIST.filter((c) => !passed.includes(c.id));
|
|
3481
3096
|
if (!allPassed) {
|
|
3482
|
-
console.log(
|
|
3097
|
+
console.log(chalk11.yellow(`
|
|
3483
3098
|
${ko.ship.incompleteHeader}`));
|
|
3484
3099
|
skipped.forEach((s) => {
|
|
3485
|
-
console.log(
|
|
3486
|
-
console.log(
|
|
3100
|
+
console.log(chalk11.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
|
|
3101
|
+
console.log(chalk11.dim(` \u2192 ${ko.ship[s.hintKey]}`));
|
|
3487
3102
|
});
|
|
3488
|
-
const { proceed } = await
|
|
3103
|
+
const { proceed } = await inquirer4.prompt([{
|
|
3489
3104
|
type: "confirm",
|
|
3490
3105
|
name: "proceed",
|
|
3491
3106
|
message: ko.ship.proceedConfirm,
|
|
@@ -3500,26 +3115,26 @@ ${ko.ship.title}
|
|
|
3500
3115
|
return;
|
|
3501
3116
|
}
|
|
3502
3117
|
} else {
|
|
3503
|
-
console.log(
|
|
3118
|
+
console.log(chalk11.green(`
|
|
3504
3119
|
${ko.ship.allPassed}
|
|
3505
3120
|
`));
|
|
3506
3121
|
}
|
|
3507
|
-
console.log(
|
|
3122
|
+
console.log(chalk11.cyan.bold(` ${ko.ship.retro}
|
|
3508
3123
|
`));
|
|
3509
|
-
console.log(
|
|
3510
|
-
const retro = await
|
|
3124
|
+
console.log(chalk11.dim(` ${ko.ship.versionHint}`));
|
|
3125
|
+
const retro = await inquirer4.prompt([
|
|
3511
3126
|
{ type: "input", name: "version", message: ko.ship.versionPrompt },
|
|
3512
3127
|
{ type: "input", name: "whatWentWell", message: ko.ship.questionWell },
|
|
3513
3128
|
{ type: "input", name: "whatWentWrong", message: ko.ship.questionWrong },
|
|
3514
3129
|
{ type: "input", name: "learned", message: ko.ship.questionLearned },
|
|
3515
3130
|
{ type: "input", name: "nextVersion", message: ko.ship.questionNext }
|
|
3516
3131
|
]);
|
|
3517
|
-
const buildLogDir =
|
|
3518
|
-
if (!
|
|
3132
|
+
const buildLogDir = path10.join(cwd, "docs", "build-log");
|
|
3133
|
+
if (!fs9.existsSync(buildLogDir)) fs9.mkdirSync(buildLogDir, { recursive: true });
|
|
3519
3134
|
const today = localDate();
|
|
3520
3135
|
const versionSlug = sanitizeVersion(retro.version);
|
|
3521
3136
|
const fileName = `${today}-v${versionSlug}.md`;
|
|
3522
|
-
const filePath =
|
|
3137
|
+
const filePath = path10.join(buildLogDir, fileName);
|
|
3523
3138
|
const content = [
|
|
3524
3139
|
`# \uBE4C\uB4DC \uB85C\uADF8: v${versionSlug}`,
|
|
3525
3140
|
"",
|
|
@@ -3548,9 +3163,9 @@ ${ko.ship.title}
|
|
|
3548
3163
|
"---",
|
|
3549
3164
|
`*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
3550
3165
|
].join("\n");
|
|
3551
|
-
|
|
3552
|
-
console.log(
|
|
3553
|
-
${ko.ship.buildLogDone(
|
|
3166
|
+
fs9.writeFileSync(filePath, content, "utf-8");
|
|
3167
|
+
console.log(chalk11.green(`
|
|
3168
|
+
${ko.ship.buildLogDone(path10.relative(cwd, filePath))}`));
|
|
3554
3169
|
const changelogResult = updateChangelogUnreleased(cwd, versionSlug, today);
|
|
3555
3170
|
if (changelogResult.status === "updated") {
|
|
3556
3171
|
log.success(ko.ship.changelogUpdated(changelogResult.version));
|
|
@@ -3568,10 +3183,10 @@ ${ko.ship.title}
|
|
|
3568
3183
|
}
|
|
3569
3184
|
|
|
3570
3185
|
// src/commands/save.ts
|
|
3571
|
-
import { execFileSync
|
|
3572
|
-
import
|
|
3186
|
+
import { execFileSync } from "child_process";
|
|
3187
|
+
import chalk12 from "chalk";
|
|
3573
3188
|
import ora from "ora";
|
|
3574
|
-
import
|
|
3189
|
+
import inquirer5 from "inquirer";
|
|
3575
3190
|
|
|
3576
3191
|
// src/lib/git-porcelain.ts
|
|
3577
3192
|
function normalizePorcelain(raw) {
|
|
@@ -3597,54 +3212,54 @@ function statusIcon(code) {
|
|
|
3597
3212
|
return "\u{1F4C4}";
|
|
3598
3213
|
}
|
|
3599
3214
|
async function save() {
|
|
3600
|
-
console.log(
|
|
3215
|
+
console.log(chalk12.bold(`
|
|
3601
3216
|
\u{1F4BE} ${t("save.title")}`));
|
|
3602
|
-
console.log(
|
|
3217
|
+
console.log(chalk12.gray("\u2500".repeat(40)));
|
|
3603
3218
|
let gitRoot;
|
|
3604
3219
|
try {
|
|
3605
|
-
|
|
3220
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3606
3221
|
gitRoot = getGitRoot();
|
|
3607
3222
|
} catch {
|
|
3608
|
-
console.log(
|
|
3223
|
+
console.log(chalk12.red(`\u274C ${t("save.notGitRepo")}`));
|
|
3609
3224
|
return;
|
|
3610
3225
|
}
|
|
3611
|
-
console.log(
|
|
3226
|
+
console.log(chalk12.cyan(`
|
|
3612
3227
|
\u{1F512} ${t("save.securityWarnHeader")}`));
|
|
3613
3228
|
printSecurityWarnings(gitRoot);
|
|
3614
3229
|
const severe = filterSevereFindings(scanProjectForSecrets(gitRoot).findings);
|
|
3615
3230
|
if (severe.length > 0) {
|
|
3616
|
-
console.log(
|
|
3231
|
+
console.log(chalk12.red(`
|
|
3617
3232
|
\u26A0\uFE0F ${t("save.secretsFound", severe.length)}`));
|
|
3618
3233
|
severe.slice(0, 5).forEach((f) => {
|
|
3619
|
-
console.log(
|
|
3234
|
+
console.log(chalk12.dim(` ${f.file}:${f.line} \u2014 ${f.patternName}`));
|
|
3620
3235
|
});
|
|
3621
3236
|
if (severe.length > 5) {
|
|
3622
|
-
console.log(
|
|
3237
|
+
console.log(chalk12.dim(` ... \uC678 ${severe.length - 5}\uAC74 (vhk \uBCF4\uC548 scan)`));
|
|
3623
3238
|
}
|
|
3624
|
-
const { proceed } = await
|
|
3239
|
+
const { proceed } = await inquirer5.prompt([{
|
|
3625
3240
|
type: "confirm",
|
|
3626
3241
|
name: "proceed",
|
|
3627
3242
|
message: t("save.secretsConfirm"),
|
|
3628
3243
|
default: false
|
|
3629
3244
|
}]);
|
|
3630
3245
|
if (!proceed) {
|
|
3631
|
-
console.log(
|
|
3246
|
+
console.log(chalk12.gray(t("save.cancelled")));
|
|
3632
3247
|
return;
|
|
3633
3248
|
}
|
|
3634
3249
|
}
|
|
3635
3250
|
const lines = parsePorcelainLines(gitOut(["status", "--porcelain"], gitRoot));
|
|
3636
3251
|
if (lines.length === 0) {
|
|
3637
|
-
console.log(
|
|
3252
|
+
console.log(chalk12.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
|
|
3638
3253
|
return;
|
|
3639
3254
|
}
|
|
3640
|
-
console.log(
|
|
3255
|
+
console.log(chalk12.cyan(`
|
|
3641
3256
|
\u{1F4CB} ${t("save.filesHeader", lines.length)}`));
|
|
3642
3257
|
lines.forEach((line) => {
|
|
3643
3258
|
const code = line.substring(0, 2);
|
|
3644
3259
|
const name = line.substring(3);
|
|
3645
3260
|
console.log(` ${statusIcon(code)} ${name}`);
|
|
3646
3261
|
});
|
|
3647
|
-
const { message } = await
|
|
3262
|
+
const { message } = await inquirer5.prompt([{
|
|
3648
3263
|
type: "input",
|
|
3649
3264
|
name: "message",
|
|
3650
3265
|
message: t("save.commitMessage"),
|
|
@@ -3659,21 +3274,21 @@ async function save() {
|
|
|
3659
3274
|
spinner.text = t("save.pushing");
|
|
3660
3275
|
if (!hasGitRemote(gitRoot)) {
|
|
3661
3276
|
spinner.succeed(t("save.successLocal"));
|
|
3662
|
-
console.log(
|
|
3277
|
+
console.log(chalk12.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
|
|
3663
3278
|
} else {
|
|
3664
3279
|
try {
|
|
3665
3280
|
gitRun(["push"], gitRoot);
|
|
3666
3281
|
spinner.succeed(t("save.successWithPush"));
|
|
3667
3282
|
} catch (pushErr) {
|
|
3668
3283
|
spinner.fail(t("save.pushFailed"));
|
|
3669
|
-
console.log(
|
|
3670
|
-
console.log(
|
|
3284
|
+
console.log(chalk12.red(getExecErrorMessage(pushErr)));
|
|
3285
|
+
console.log(chalk12.yellow(`
|
|
3671
3286
|
\u{1F4A1} ${t("save.commitOkPushFailed")}`));
|
|
3672
3287
|
process.exitCode = 1;
|
|
3673
3288
|
}
|
|
3674
3289
|
}
|
|
3675
3290
|
if (process.exitCode !== 1) {
|
|
3676
|
-
console.log(
|
|
3291
|
+
console.log(chalk12.green(`
|
|
3677
3292
|
\u2705 ${t("save.done", lines.length)}`));
|
|
3678
3293
|
printNextStep({
|
|
3679
3294
|
message: t("save.nextOkMessage"),
|
|
@@ -3681,7 +3296,7 @@ async function save() {
|
|
|
3681
3296
|
cursorHint: t("save.nextOkCursor")
|
|
3682
3297
|
});
|
|
3683
3298
|
} else {
|
|
3684
|
-
console.log(
|
|
3299
|
+
console.log(chalk12.green(`
|
|
3685
3300
|
\u2705 ${t("save.doneLocalOnly", lines.length)}`));
|
|
3686
3301
|
printNextStep({
|
|
3687
3302
|
message: t("save.nextPushFailMessage"),
|
|
@@ -3691,12 +3306,12 @@ async function save() {
|
|
|
3691
3306
|
}
|
|
3692
3307
|
} catch (err) {
|
|
3693
3308
|
spinner.fail(t("save.failed"));
|
|
3694
|
-
console.log(
|
|
3309
|
+
console.log(chalk12.red(getExecErrorMessage(err)));
|
|
3695
3310
|
if (didAdd) {
|
|
3696
3311
|
try {
|
|
3697
3312
|
const staged = gitOut(["diff", "--cached", "--stat"], gitRoot).trim();
|
|
3698
3313
|
if (staged) {
|
|
3699
|
-
console.log(
|
|
3314
|
+
console.log(chalk12.yellow(`
|
|
3700
3315
|
\u{1F4A1} ${t("save.stagedAfterFail")}`));
|
|
3701
3316
|
}
|
|
3702
3317
|
} catch {
|
|
@@ -3707,9 +3322,9 @@ async function save() {
|
|
|
3707
3322
|
}
|
|
3708
3323
|
|
|
3709
3324
|
// src/commands/undo.ts
|
|
3710
|
-
import { execFileSync as
|
|
3711
|
-
import
|
|
3712
|
-
import
|
|
3325
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
3326
|
+
import chalk13 from "chalk";
|
|
3327
|
+
import inquirer6 from "inquirer";
|
|
3713
3328
|
function parseRecentCommits(logOutput) {
|
|
3714
3329
|
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3715
3330
|
}
|
|
@@ -3732,36 +3347,36 @@ function isUndoRisky(undoCount, unpushedCount, hasRemote) {
|
|
|
3732
3347
|
return false;
|
|
3733
3348
|
}
|
|
3734
3349
|
async function undo() {
|
|
3735
|
-
console.log(
|
|
3350
|
+
console.log(chalk13.bold(`
|
|
3736
3351
|
\u23EA ${t("undo.title")}`));
|
|
3737
|
-
console.log(
|
|
3352
|
+
console.log(chalk13.gray("\u2500".repeat(40)));
|
|
3738
3353
|
let gitRoot;
|
|
3739
3354
|
try {
|
|
3740
|
-
|
|
3355
|
+
execFileSync2("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3741
3356
|
gitRoot = getGitRoot();
|
|
3742
3357
|
} catch {
|
|
3743
|
-
console.log(
|
|
3358
|
+
console.log(chalk13.red(`\u274C ${t("undo.notGitRepo")}`));
|
|
3744
3359
|
return;
|
|
3745
3360
|
}
|
|
3746
3361
|
let logOutput;
|
|
3747
3362
|
try {
|
|
3748
3363
|
logOutput = gitOut(["log", "--oneline", "-5"], gitRoot).trim();
|
|
3749
3364
|
} catch {
|
|
3750
|
-
console.log(
|
|
3365
|
+
console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
3751
3366
|
return;
|
|
3752
3367
|
}
|
|
3753
3368
|
const commits = parseRecentCommits(logOutput);
|
|
3754
3369
|
if (commits.length === 0) {
|
|
3755
|
-
console.log(
|
|
3370
|
+
console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
3756
3371
|
return;
|
|
3757
3372
|
}
|
|
3758
|
-
console.log(
|
|
3373
|
+
console.log(chalk13.cyan(`
|
|
3759
3374
|
${t("undo.recentHeader")}`));
|
|
3760
3375
|
commits.forEach((c, i) => {
|
|
3761
3376
|
console.log(` ${i === 0 ? "\u{1F449}" : " "} ${c}`);
|
|
3762
3377
|
});
|
|
3763
3378
|
const maxUndo = commits.length;
|
|
3764
|
-
const { count } = await
|
|
3379
|
+
const { count } = await inquirer6.prompt([{
|
|
3765
3380
|
type: "number",
|
|
3766
3381
|
name: "count",
|
|
3767
3382
|
message: t("undo.howMany"),
|
|
@@ -3772,7 +3387,7 @@ ${t("undo.recentHeader")}`));
|
|
|
3772
3387
|
const undoCount = Math.min(Math.max(1, count || 1), maxUndo);
|
|
3773
3388
|
const headCount = countLocalCommits(gitRoot);
|
|
3774
3389
|
if (undoCount >= headCount) {
|
|
3775
|
-
console.log(
|
|
3390
|
+
console.log(chalk13.yellow(`
|
|
3776
3391
|
\u{1F4ED} ${t("undo.rootCommit")}`));
|
|
3777
3392
|
return;
|
|
3778
3393
|
}
|
|
@@ -3781,30 +3396,30 @@ ${t("undo.recentHeader")}`));
|
|
|
3781
3396
|
const risky = isUndoRisky(undoCount, unpushed, remote);
|
|
3782
3397
|
if (risky) {
|
|
3783
3398
|
if (unpushed < 0) {
|
|
3784
|
-
console.log(
|
|
3399
|
+
console.log(chalk13.red(`
|
|
3785
3400
|
\u26A0\uFE0F ${t("undo.noUpstreamWarning")}`));
|
|
3786
3401
|
} else {
|
|
3787
|
-
console.log(
|
|
3402
|
+
console.log(chalk13.red(`
|
|
3788
3403
|
\u26A0\uFE0F ${t("undo.alreadyPushed")}`));
|
|
3789
3404
|
}
|
|
3790
3405
|
}
|
|
3791
|
-
const { confirm } = await
|
|
3406
|
+
const { confirm } = await inquirer6.prompt([{
|
|
3792
3407
|
type: "confirm",
|
|
3793
3408
|
name: "confirm",
|
|
3794
3409
|
message: risky ? t("undo.confirmRisky", undoCount) : t("undo.confirmMessage"),
|
|
3795
3410
|
default: false
|
|
3796
3411
|
}]);
|
|
3797
3412
|
if (!confirm) {
|
|
3798
|
-
console.log(
|
|
3413
|
+
console.log(chalk13.gray(t("undo.cancelled")));
|
|
3799
3414
|
return;
|
|
3800
3415
|
}
|
|
3801
3416
|
try {
|
|
3802
3417
|
gitRun(["reset", "--soft", `HEAD~${undoCount}`], gitRoot);
|
|
3803
|
-
console.log(
|
|
3418
|
+
console.log(chalk13.green(`
|
|
3804
3419
|
\u2705 ${t("undo.success")}`));
|
|
3805
|
-
console.log(
|
|
3420
|
+
console.log(chalk13.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
|
|
3806
3421
|
if (risky) {
|
|
3807
|
-
console.log(
|
|
3422
|
+
console.log(chalk13.yellow(`
|
|
3808
3423
|
\u{1F4A1} ${t("undo.forcePushHint")}`));
|
|
3809
3424
|
}
|
|
3810
3425
|
printNextStep({
|
|
@@ -3813,39 +3428,39 @@ ${t("undo.recentHeader")}`));
|
|
|
3813
3428
|
cursorHint: t("undo.nextCursor")
|
|
3814
3429
|
});
|
|
3815
3430
|
} catch (err) {
|
|
3816
|
-
console.log(
|
|
3431
|
+
console.log(chalk13.red(`\u274C ${t("undo.failed")}`));
|
|
3817
3432
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3818
|
-
console.log(
|
|
3433
|
+
console.log(chalk13.red(msg));
|
|
3819
3434
|
process.exitCode = 1;
|
|
3820
3435
|
}
|
|
3821
3436
|
}
|
|
3822
3437
|
|
|
3823
3438
|
// src/commands/restore.ts
|
|
3824
|
-
import
|
|
3825
|
-
import
|
|
3439
|
+
import chalk14 from "chalk";
|
|
3440
|
+
import inquirer7 from "inquirer";
|
|
3826
3441
|
async function restore(id) {
|
|
3827
|
-
console.log(
|
|
3442
|
+
console.log(chalk14.bold(`
|
|
3828
3443
|
${ko.restore.title}`));
|
|
3829
|
-
console.log(
|
|
3830
|
-
console.log(
|
|
3444
|
+
console.log(chalk14.gray("\u2500".repeat(40)));
|
|
3445
|
+
console.log(chalk14.dim(` ${ko.restore.notGitNote}`));
|
|
3831
3446
|
const cwd = process.cwd();
|
|
3832
3447
|
const backups = listBackups(cwd);
|
|
3833
3448
|
if (backups.length === 0) {
|
|
3834
|
-
console.log(
|
|
3449
|
+
console.log(chalk14.yellow(`
|
|
3835
3450
|
${ko.restore.noBackups}`));
|
|
3836
3451
|
return;
|
|
3837
3452
|
}
|
|
3838
3453
|
let targetId = id;
|
|
3839
3454
|
if (!targetId) {
|
|
3840
3455
|
if (!process.stdout.isTTY) {
|
|
3841
|
-
console.log(
|
|
3456
|
+
console.log(chalk14.cyan(`
|
|
3842
3457
|
${ko.restore.listHeader}`));
|
|
3843
3458
|
for (const b of backups) console.log(` ${b.id} (${b.files.length}\uAC1C \uD30C\uC77C)`);
|
|
3844
|
-
console.log(
|
|
3459
|
+
console.log(chalk14.yellow(`
|
|
3845
3460
|
${ko.restore.nonTtyHint}`));
|
|
3846
3461
|
return;
|
|
3847
3462
|
}
|
|
3848
|
-
const { picked } = await
|
|
3463
|
+
const { picked } = await inquirer7.prompt([
|
|
3849
3464
|
{
|
|
3850
3465
|
type: "list",
|
|
3851
3466
|
name: "picked",
|
|
@@ -3860,26 +3475,26 @@ ${ko.restore.nonTtyHint}`));
|
|
|
3860
3475
|
}
|
|
3861
3476
|
try {
|
|
3862
3477
|
const restored = restoreBackup(targetId, cwd);
|
|
3863
|
-
console.log(
|
|
3478
|
+
console.log(chalk14.green(`
|
|
3864
3479
|
${ko.restore.restored(restored.length, targetId)}`));
|
|
3865
|
-
for (const r of restored) console.log(
|
|
3480
|
+
for (const r of restored) console.log(chalk14.gray(` ${r}`));
|
|
3866
3481
|
printNextStep({
|
|
3867
3482
|
message: "\uBC31\uC5C5 \uBCF5\uC6D0 \uC644\uB8CC! \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694.",
|
|
3868
3483
|
command: "vhk diff",
|
|
3869
3484
|
cursorHint: "\uBCC0\uACBD\uC0AC\uD56D \uBCF4\uC5EC\uC918"
|
|
3870
3485
|
});
|
|
3871
3486
|
} catch {
|
|
3872
|
-
console.log(
|
|
3487
|
+
console.log(chalk14.red(`
|
|
3873
3488
|
${ko.restore.notFound(targetId)}`));
|
|
3874
3489
|
process.exitCode = 1;
|
|
3875
3490
|
}
|
|
3876
3491
|
}
|
|
3877
3492
|
|
|
3878
3493
|
// src/commands/status.ts
|
|
3879
|
-
import { execFileSync as
|
|
3880
|
-
import
|
|
3881
|
-
import
|
|
3882
|
-
import
|
|
3494
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
3495
|
+
import fs10 from "fs";
|
|
3496
|
+
import path11 from "path";
|
|
3497
|
+
import chalk15 from "chalk";
|
|
3883
3498
|
function countFileChanges(porcelain) {
|
|
3884
3499
|
const lines = porcelain.split("\n").filter(Boolean);
|
|
3885
3500
|
let staged = 0;
|
|
@@ -3917,8 +3532,8 @@ function parseRecentCommitLines(logOutput) {
|
|
|
3917
3532
|
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3918
3533
|
}
|
|
3919
3534
|
function readProjectPackage(cwd = process.cwd()) {
|
|
3920
|
-
const pkgPath =
|
|
3921
|
-
if (!
|
|
3535
|
+
const pkgPath = path11.join(cwd, "package.json");
|
|
3536
|
+
if (!fs10.existsSync(pkgPath)) return null;
|
|
3922
3537
|
try {
|
|
3923
3538
|
const pkg = readJsonFile(pkgPath);
|
|
3924
3539
|
if (!pkg.name && !pkg.version) return null;
|
|
@@ -3954,15 +3569,15 @@ function getSyncCounts(gitRoot) {
|
|
|
3954
3569
|
}
|
|
3955
3570
|
}
|
|
3956
3571
|
async function status() {
|
|
3957
|
-
console.log(
|
|
3572
|
+
console.log(chalk15.bold(`
|
|
3958
3573
|
\u{1F4CA} ${t("status.title")}`));
|
|
3959
|
-
console.log(
|
|
3574
|
+
console.log(chalk15.gray("\u2500".repeat(40)));
|
|
3960
3575
|
let gitRoot;
|
|
3961
3576
|
try {
|
|
3962
|
-
|
|
3577
|
+
execFileSync3("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3963
3578
|
gitRoot = getGitRoot();
|
|
3964
3579
|
} catch {
|
|
3965
|
-
console.log(
|
|
3580
|
+
console.log(chalk15.red(`\u274C ${t("status.notGitRepo")}`));
|
|
3966
3581
|
return;
|
|
3967
3582
|
}
|
|
3968
3583
|
let branch;
|
|
@@ -3981,36 +3596,37 @@ async function status() {
|
|
|
3981
3596
|
commits = [];
|
|
3982
3597
|
}
|
|
3983
3598
|
const pkg = readProjectPackage();
|
|
3984
|
-
console.log(
|
|
3985
|
-
\u{1F33F} ${t("status.branch")}`) +
|
|
3599
|
+
console.log(chalk15.cyan(`
|
|
3600
|
+
\u{1F33F} ${t("status.branch")}`) + chalk15.white(` ${branch}`));
|
|
3986
3601
|
console.log(
|
|
3987
|
-
|
|
3602
|
+
chalk15.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk15.white(
|
|
3988
3603
|
` staged ${counts.staged} \xB7 unstaged ${counts.unstaged} \xB7 untracked ${counts.untracked}`
|
|
3989
3604
|
)
|
|
3990
3605
|
);
|
|
3991
|
-
console.log(
|
|
3606
|
+
console.log(chalk15.cyan(`
|
|
3992
3607
|
\u{1F4CB} ${t("status.recentCommits", commits.length)}`));
|
|
3993
3608
|
if (commits.length === 0) {
|
|
3994
|
-
console.log(
|
|
3609
|
+
console.log(chalk15.dim(` ${t("status.noCommits")}`));
|
|
3995
3610
|
} else {
|
|
3996
|
-
commits.forEach((c) => console.log(` ${
|
|
3611
|
+
commits.forEach((c) => console.log(` ${chalk15.dim("\u2022")} ${c}`));
|
|
3997
3612
|
}
|
|
3998
3613
|
console.log(
|
|
3999
|
-
|
|
4000
|
-
\u{1F504} ${t("status.remote")}`) +
|
|
3614
|
+
chalk15.cyan(`
|
|
3615
|
+
\u{1F504} ${t("status.remote")}`) + chalk15.white(` ${formatSyncLabel(sync2)}`)
|
|
4001
3616
|
);
|
|
4002
|
-
console.log(
|
|
3617
|
+
console.log(chalk15.gray("\n" + "\u2500".repeat(40)));
|
|
4003
3618
|
if (pkg) {
|
|
4004
|
-
console.log(
|
|
3619
|
+
console.log(chalk15.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk15.white(` ${pkg.name} v${pkg.version}`));
|
|
4005
3620
|
} else {
|
|
4006
|
-
console.log(
|
|
3621
|
+
console.log(chalk15.dim(`\u{1F4E6} ${t("status.noPackage")}`));
|
|
4007
3622
|
}
|
|
4008
3623
|
const hasChanges = counts.staged + counts.unstaged + counts.untracked > 0;
|
|
4009
3624
|
printNextStep(selectStatusNextStep(hasChanges));
|
|
3625
|
+
printContextResumeHint();
|
|
4010
3626
|
}
|
|
4011
3627
|
|
|
4012
3628
|
// src/commands/diff.ts
|
|
4013
|
-
import
|
|
3629
|
+
import chalk16 from "chalk";
|
|
4014
3630
|
function gitOut2(args) {
|
|
4015
3631
|
const r = safeExecFile("git", args);
|
|
4016
3632
|
return r.ok ? r.out : "";
|
|
@@ -4047,67 +3663,67 @@ function summarizeNumstat(numstat) {
|
|
|
4047
3663
|
return { fileCount, totalAdd, totalDel };
|
|
4048
3664
|
}
|
|
4049
3665
|
function printFile(f) {
|
|
4050
|
-
const adds = f.additions > 0 ?
|
|
4051
|
-
const dels = f.deletions > 0 ?
|
|
3666
|
+
const adds = f.additions > 0 ? chalk16.green(`+${f.additions}`) : "";
|
|
3667
|
+
const dels = f.deletions > 0 ? chalk16.red(`-${f.deletions}`) : "";
|
|
4052
3668
|
const change = [adds, dels].filter(Boolean).join(" ");
|
|
4053
3669
|
console.log(` ${f.name} ${change}`);
|
|
4054
3670
|
}
|
|
4055
3671
|
async function diff() {
|
|
4056
|
-
console.log(
|
|
3672
|
+
console.log(chalk16.bold(`
|
|
4057
3673
|
\u{1F50D} ${t("diff.title")}`));
|
|
4058
|
-
console.log(
|
|
3674
|
+
console.log(chalk16.gray("\u2500".repeat(40)));
|
|
4059
3675
|
if (!safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok) {
|
|
4060
|
-
console.log(
|
|
3676
|
+
console.log(chalk16.red(`\u274C ${t("diff.notGitRepo")}`));
|
|
4061
3677
|
return;
|
|
4062
3678
|
}
|
|
4063
3679
|
const unstaged = gitOut2(["diff", "--stat"]);
|
|
4064
3680
|
const staged = gitOut2(["diff", "--cached", "--stat"]);
|
|
4065
3681
|
const untracked = gitOut2(["ls-files", "--others", "--exclude-standard"]);
|
|
4066
3682
|
if (!unstaged && !staged && !untracked) {
|
|
4067
|
-
console.log(
|
|
3683
|
+
console.log(chalk16.green(`
|
|
4068
3684
|
\u2705 ${t("diff.noChanges")}`));
|
|
4069
3685
|
return;
|
|
4070
3686
|
}
|
|
4071
3687
|
if (staged) {
|
|
4072
|
-
console.log(
|
|
3688
|
+
console.log(chalk16.cyan(`
|
|
4073
3689
|
${t("diff.stagedHeader")}`));
|
|
4074
3690
|
parseDiffStat(staged).forEach((f) => printFile(f));
|
|
4075
3691
|
}
|
|
4076
3692
|
if (unstaged) {
|
|
4077
|
-
console.log(
|
|
3693
|
+
console.log(chalk16.cyan(`
|
|
4078
3694
|
${t("diff.unstagedHeader")}`));
|
|
4079
3695
|
parseDiffStat(unstaged).forEach((f) => printFile(f));
|
|
4080
3696
|
}
|
|
4081
3697
|
if (untracked) {
|
|
4082
3698
|
const files = untracked.split("\n").filter(Boolean);
|
|
4083
|
-
console.log(
|
|
3699
|
+
console.log(chalk16.cyan(`
|
|
4084
3700
|
${t("diff.untrackedHeader", files.length)}`));
|
|
4085
|
-
files.forEach((f) => console.log(` ${
|
|
3701
|
+
files.forEach((f) => console.log(` ${chalk16.green("+")} ${f}`));
|
|
4086
3702
|
}
|
|
4087
3703
|
const numstat = gitOut2(["diff", "--numstat", "HEAD"]);
|
|
4088
3704
|
if (numstat) {
|
|
4089
3705
|
const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
|
|
4090
|
-
console.log(
|
|
3706
|
+
console.log(chalk16.cyan(`
|
|
4091
3707
|
${t("diff.summaryHeader")}`));
|
|
4092
3708
|
console.log(` ${t("diff.filesLine", fileCount)}`);
|
|
4093
|
-
console.log(` \uCD94\uAC00: ${
|
|
4094
|
-
console.log(` \uC0AD\uC81C: ${
|
|
3709
|
+
console.log(` \uCD94\uAC00: ${chalk16.green(`+${totalAdd}`)}\uC904`);
|
|
3710
|
+
console.log(` \uC0AD\uC81C: ${chalk16.red(`-${totalDel}`)}\uC904`);
|
|
4095
3711
|
}
|
|
4096
3712
|
console.log("");
|
|
4097
3713
|
}
|
|
4098
3714
|
|
|
4099
3715
|
// src/commands/mcp-init.ts
|
|
4100
|
-
import { existsSync as
|
|
4101
|
-
import { join as
|
|
3716
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3717
|
+
import { join as join5, dirname } from "path";
|
|
4102
3718
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4103
|
-
import
|
|
3719
|
+
import chalk17 from "chalk";
|
|
4104
3720
|
function resolveMcpEntryPoint() {
|
|
4105
3721
|
try {
|
|
4106
3722
|
const here = fileURLToPath2(import.meta.url);
|
|
4107
3723
|
const dir = dirname(here);
|
|
4108
3724
|
for (const rel of [["mcp", "index.js"], ["..", "mcp", "index.js"]]) {
|
|
4109
|
-
const candidate =
|
|
4110
|
-
if (
|
|
3725
|
+
const candidate = join5(dir, ...rel);
|
|
3726
|
+
if (existsSync4(candidate)) return candidate;
|
|
4111
3727
|
}
|
|
4112
3728
|
} catch {
|
|
4113
3729
|
}
|
|
@@ -4115,17 +3731,17 @@ function resolveMcpEntryPoint() {
|
|
|
4115
3731
|
const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
|
|
4116
3732
|
if (typeof url === "string") {
|
|
4117
3733
|
const p = fileURLToPath2(url);
|
|
4118
|
-
if (
|
|
3734
|
+
if (existsSync4(p)) return p;
|
|
4119
3735
|
}
|
|
4120
3736
|
} catch {
|
|
4121
3737
|
}
|
|
4122
3738
|
try {
|
|
4123
|
-
const pkgPath =
|
|
4124
|
-
if (
|
|
3739
|
+
const pkgPath = join5(process.cwd(), "package.json");
|
|
3740
|
+
if (existsSync4(pkgPath)) {
|
|
4125
3741
|
const pkg = readJsonFile(pkgPath);
|
|
4126
3742
|
if (pkg.name === "@byh3071/vhk") {
|
|
4127
|
-
const local =
|
|
4128
|
-
if (
|
|
3743
|
+
const local = join5(process.cwd(), "dist", "mcp", "index.js");
|
|
3744
|
+
if (existsSync4(local)) return local;
|
|
4129
3745
|
}
|
|
4130
3746
|
}
|
|
4131
3747
|
} catch {
|
|
@@ -4140,31 +3756,31 @@ function resolveVhkMcpEntry() {
|
|
|
4140
3756
|
return { command: "vhk-mcp", args: [] };
|
|
4141
3757
|
}
|
|
4142
3758
|
async function mcpInit() {
|
|
4143
|
-
console.log(
|
|
4144
|
-
console.log(
|
|
4145
|
-
const cursorDir =
|
|
4146
|
-
if (!
|
|
3759
|
+
console.log(chalk17.bold("\n\u{1F50C} " + t("mcp.initTitle")));
|
|
3760
|
+
console.log(chalk17.gray("\u2500".repeat(40)));
|
|
3761
|
+
const cursorDir = join5(process.cwd(), ".cursor");
|
|
3762
|
+
if (!existsSync4(cursorDir)) {
|
|
4147
3763
|
mkdirSync3(cursorDir, { recursive: true });
|
|
4148
3764
|
}
|
|
4149
|
-
const configPath =
|
|
3765
|
+
const configPath = join5(cursorDir, "mcp.json");
|
|
4150
3766
|
const vhkEntry = resolveVhkMcpEntry();
|
|
4151
3767
|
let config;
|
|
4152
|
-
if (
|
|
3768
|
+
if (existsSync4(configPath)) {
|
|
4153
3769
|
try {
|
|
4154
3770
|
const parsed = readJsonFile(configPath);
|
|
4155
3771
|
config = {
|
|
4156
3772
|
mcpServers: { ...parsed.mcpServers ?? {}, vhk: vhkEntry }
|
|
4157
3773
|
};
|
|
4158
3774
|
} catch {
|
|
4159
|
-
console.log(
|
|
3775
|
+
console.log(chalk17.yellow("\u26A0\uFE0F \uAE30\uC874 .cursor/mcp.json \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uC0C8 \uD30C\uC77C\uB85C \uB36E\uC5B4\uC501\uB2C8\uB2E4."));
|
|
4160
3776
|
config = { mcpServers: { vhk: vhkEntry } };
|
|
4161
3777
|
}
|
|
4162
3778
|
} else {
|
|
4163
3779
|
config = { mcpServers: { vhk: vhkEntry } };
|
|
4164
3780
|
}
|
|
4165
3781
|
writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4166
|
-
console.log(
|
|
4167
|
-
console.log(
|
|
3782
|
+
console.log(chalk17.green("\n\u2705 Cursor MCP \uC124\uC815 \uC644\uB8CC!"));
|
|
3783
|
+
console.log(chalk17.cyan("\u{1F4C1} \uC0DD\uC131\uB41C \uD30C\uC77C:"));
|
|
4168
3784
|
console.log(` ${configPath}`);
|
|
4169
3785
|
printNextStep({
|
|
4170
3786
|
message: t("mcp.nextMessage"),
|
|
@@ -4174,9 +3790,9 @@ async function mcpInit() {
|
|
|
4174
3790
|
}
|
|
4175
3791
|
|
|
4176
3792
|
// src/commands/design.ts
|
|
4177
|
-
import { existsSync as
|
|
4178
|
-
import
|
|
4179
|
-
import
|
|
3793
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
3794
|
+
import chalk18 from "chalk";
|
|
3795
|
+
import inquirer8 from "inquirer";
|
|
4180
3796
|
var PALETTES = [
|
|
4181
3797
|
{
|
|
4182
3798
|
name: "Minimal",
|
|
@@ -4228,7 +3844,7 @@ var PALETTES = [
|
|
|
4228
3844
|
}
|
|
4229
3845
|
];
|
|
4230
3846
|
function hasTailwind() {
|
|
4231
|
-
return
|
|
3847
|
+
return existsSync5("tailwind.config.js") || existsSync5("tailwind.config.ts") || existsSync5("tailwind.config.mjs") || existsSync5("tailwind.config.cjs");
|
|
4232
3848
|
}
|
|
4233
3849
|
function isTailwindV4Deps(deps) {
|
|
4234
3850
|
if (deps["@tailwindcss/vite"] || deps["@tailwindcss/postcss"]) return true;
|
|
@@ -4278,10 +3894,10 @@ export default vhkColors
|
|
|
4278
3894
|
`;
|
|
4279
3895
|
}
|
|
4280
3896
|
async function design() {
|
|
4281
|
-
console.log(
|
|
4282
|
-
console.log(
|
|
3897
|
+
console.log(chalk18.bold("\n\u{1F3A8} " + t("design.title")));
|
|
3898
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
4283
3899
|
if (!ensureInteractive("\uCEEC\uB7EC \uD314\uB808\uD2B8 \uC120\uD0DD\uC740 \uB300\uD654\uD615\uC73C\uB85C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4.")) return;
|
|
4284
|
-
const { paletteIndex } = await
|
|
3900
|
+
const { paletteIndex } = await inquirer8.prompt([
|
|
4285
3901
|
{
|
|
4286
3902
|
type: "list",
|
|
4287
3903
|
name: "paletteIndex",
|
|
@@ -4293,37 +3909,37 @@ async function design() {
|
|
|
4293
3909
|
}
|
|
4294
3910
|
]);
|
|
4295
3911
|
const palette = PALETTES[paletteIndex];
|
|
4296
|
-
console.log(
|
|
3912
|
+
console.log(chalk18.cyan(`
|
|
4297
3913
|
\u{1F3A8} \uC120\uD0DD\uB41C \uD314\uB808\uD2B8: ${palette.name}`));
|
|
4298
3914
|
const v4 = hasTailwindV4();
|
|
4299
3915
|
const targetPath = v4 ? "src/styles/theme.css" : hasTailwind() ? "src/styles/vhk-colors.ts" : "src/styles/tokens.css";
|
|
4300
3916
|
const content = v4 ? generateTailwindV4Theme(palette) : hasTailwind() ? generateTailwindExtend(palette) : generateCSSTokens(palette);
|
|
4301
|
-
if (
|
|
4302
|
-
const { overwrite } = await
|
|
3917
|
+
if (existsSync5(targetPath)) {
|
|
3918
|
+
const { overwrite } = await inquirer8.prompt([{
|
|
4303
3919
|
type: "confirm",
|
|
4304
3920
|
name: "overwrite",
|
|
4305
3921
|
message: `${targetPath} \uC774\uBBF8 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?`,
|
|
4306
3922
|
default: false
|
|
4307
3923
|
}]);
|
|
4308
3924
|
if (!overwrite) {
|
|
4309
|
-
console.log(
|
|
3925
|
+
console.log(chalk18.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
|
|
4310
3926
|
return;
|
|
4311
3927
|
}
|
|
4312
3928
|
}
|
|
4313
3929
|
mkdirSync4("src/styles", { recursive: true });
|
|
4314
3930
|
writeFileSync4(targetPath, content, "utf-8");
|
|
4315
3931
|
if (v4) {
|
|
4316
|
-
console.log(
|
|
4317
|
-
console.log(
|
|
4318
|
-
console.log(
|
|
3932
|
+
console.log(chalk18.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (Tailwind v4 @theme)"));
|
|
3933
|
+
console.log(chalk18.gray(' \uC9C4\uC785 CSS(\uC608: src/index.css)\uC5D0 `@import "./styles/theme.css";` \uCD94\uAC00 \u2192 bg-primary \uB4F1 \uC720\uD2F8 \uC0AC\uC6A9.'));
|
|
3934
|
+
console.log(chalk18.gray(" \uB2E4\uD06C \uD1A0\uAE00: \uB8E8\uD2B8 <html>/<body> \uC5D0 `.dark` \uD074\uB798\uC2A4 on/off."));
|
|
4319
3935
|
} else if (hasTailwind()) {
|
|
4320
|
-
console.log(
|
|
4321
|
-
console.log(
|
|
3936
|
+
console.log(chalk18.green("\n\u2705 src/styles/vhk-colors.ts \uC0DD\uC131"));
|
|
3937
|
+
console.log(chalk18.gray(" tailwind.config\uC758 extend.colors\uC5D0 import \uD574\uC11C \uC0AC\uC6A9\uD558\uC138\uC694."));
|
|
4322
3938
|
} else {
|
|
4323
|
-
console.log(
|
|
4324
|
-
console.log(
|
|
3939
|
+
console.log(chalk18.green("\n\u2705 src/styles/tokens.css \uC0DD\uC131"));
|
|
3940
|
+
console.log(chalk18.gray(" HTML\uC5D0 <link>\uB85C \uCD94\uAC00\uD558\uAC70\uB098 CSS\uC5D0\uC11C @import \uD558\uC138\uC694."));
|
|
4325
3941
|
}
|
|
4326
|
-
console.log(
|
|
3942
|
+
console.log(chalk18.bold("\n\u{1F308} \uCEEC\uB7EC \uBBF8\uB9AC\uBCF4\uAE30:"));
|
|
4327
3943
|
for (const [key, value] of Object.entries(palette.colors)) {
|
|
4328
3944
|
console.log(` ${key.padEnd(12)} ${value}`);
|
|
4329
3945
|
}
|
|
@@ -4338,9 +3954,9 @@ async function designPalette() {
|
|
|
4338
3954
|
}
|
|
4339
3955
|
|
|
4340
3956
|
// src/commands/theme.ts
|
|
4341
|
-
import { existsSync as
|
|
4342
|
-
import
|
|
4343
|
-
import
|
|
3957
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
3958
|
+
import chalk19 from "chalk";
|
|
3959
|
+
import inquirer9 from "inquirer";
|
|
4344
3960
|
function generateDarkCSS() {
|
|
4345
3961
|
return `/* vhk theme \u2014 \uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC CSS \uBCC0\uC218 */
|
|
4346
3962
|
|
|
@@ -4396,13 +4012,13 @@ export function initTheme(): void {
|
|
|
4396
4012
|
`;
|
|
4397
4013
|
}
|
|
4398
4014
|
async function theme() {
|
|
4399
|
-
console.log(
|
|
4400
|
-
console.log(
|
|
4015
|
+
console.log(chalk19.bold("\n\u{1F319} " + t("theme.title")));
|
|
4016
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
4401
4017
|
const cssPath = "src/styles/theme.css";
|
|
4402
4018
|
const togglePath = "src/lib/theme-toggle.ts";
|
|
4403
|
-
const conflicts = [cssPath, togglePath].filter((p) =>
|
|
4019
|
+
const conflicts = [cssPath, togglePath].filter((p) => existsSync6(p));
|
|
4404
4020
|
if (conflicts.length > 0) {
|
|
4405
|
-
const { overwrite } = await
|
|
4021
|
+
const { overwrite } = await inquirer9.prompt([{
|
|
4406
4022
|
type: "confirm",
|
|
4407
4023
|
name: "overwrite",
|
|
4408
4024
|
message: `\uB2E4\uC74C \uD30C\uC77C\uC774 \uC774\uBBF8 \uC788\uC5B4\uC694. \uB36E\uC5B4\uC4F8\uAE4C\uC694?
|
|
@@ -4410,21 +4026,21 @@ async function theme() {
|
|
|
4410
4026
|
default: false
|
|
4411
4027
|
}]);
|
|
4412
4028
|
if (!overwrite) {
|
|
4413
|
-
console.log(
|
|
4029
|
+
console.log(chalk19.yellow("\n\u23ED\uFE0F \uC0DD\uC131 \uCDE8\uC18C \u2014 \uAE30\uC874 \uD30C\uC77C \uC720\uC9C0."));
|
|
4414
4030
|
return;
|
|
4415
4031
|
}
|
|
4416
4032
|
}
|
|
4417
4033
|
mkdirSync5("src/styles", { recursive: true });
|
|
4418
4034
|
mkdirSync5("src/lib", { recursive: true });
|
|
4419
4035
|
writeFileSync5(cssPath, generateDarkCSS(), "utf-8");
|
|
4420
|
-
console.log(
|
|
4036
|
+
console.log(chalk19.green("\n\u2705 src/styles/theme.css \uC0DD\uC131 (\uB2E4\uD06C/\uB77C\uC774\uD2B8 \uBAA8\uB4DC)"));
|
|
4421
4037
|
writeFileSync5(togglePath, generateToggleUtil(), "utf-8");
|
|
4422
|
-
console.log(
|
|
4423
|
-
console.log(
|
|
4424
|
-
console.log(
|
|
4425
|
-
console.log(
|
|
4426
|
-
console.log(
|
|
4427
|
-
console.log(
|
|
4038
|
+
console.log(chalk19.green("\u2705 src/lib/theme-toggle.ts \uC0DD\uC131 (\uD1A0\uAE00 \uC720\uD2F8\uB9AC\uD2F0)"));
|
|
4039
|
+
console.log(chalk19.bold("\n\u{1F4D6} \uC0AC\uC6A9\uBC95:"));
|
|
4040
|
+
console.log(chalk19.gray(" 1. theme.css\uB97C \uAE00\uB85C\uBC8C \uC2A4\uD0C0\uC77C\uC5D0 \uCD94\uAC00"));
|
|
4041
|
+
console.log(chalk19.gray(' 2. import { initTheme, toggleTheme } from "./lib/theme-toggle"'));
|
|
4042
|
+
console.log(chalk19.gray(" 3. \uC571 \uC9C4\uC785\uC810\uC5D0\uC11C initTheme() \uD638\uCD9C"));
|
|
4043
|
+
console.log(chalk19.gray(" 4. \uD1A0\uAE00 \uBC84\uD2BC\uC5D0\uC11C toggleTheme() \uD638\uCD9C"));
|
|
4428
4044
|
printNextStep({
|
|
4429
4045
|
message: "\uD14C\uB9C8 \uC124\uC815 \uC644\uB8CC!",
|
|
4430
4046
|
command: "vhk ref list",
|
|
@@ -4433,11 +4049,11 @@ async function theme() {
|
|
|
4433
4049
|
}
|
|
4434
4050
|
|
|
4435
4051
|
// src/commands/ref.ts
|
|
4436
|
-
import { existsSync as
|
|
4437
|
-
import
|
|
4052
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
4053
|
+
import chalk20 from "chalk";
|
|
4438
4054
|
var REFS_PATH = ".vhk/refs.json";
|
|
4439
4055
|
function loadRefs() {
|
|
4440
|
-
if (!
|
|
4056
|
+
if (!existsSync7(REFS_PATH)) return [];
|
|
4441
4057
|
try {
|
|
4442
4058
|
const parsed = readJsonFile(REFS_PATH);
|
|
4443
4059
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -4450,24 +4066,24 @@ function saveRefs(refs) {
|
|
|
4450
4066
|
writeFileSync6(REFS_PATH, JSON.stringify(refs, null, 2) + "\n", "utf-8");
|
|
4451
4067
|
}
|
|
4452
4068
|
async function refAdd(url, memo = "") {
|
|
4453
|
-
console.log(
|
|
4454
|
-
console.log(
|
|
4069
|
+
console.log(chalk20.bold("\n\u{1F517} " + t("ref.addTitle")));
|
|
4070
|
+
console.log(chalk20.gray("\u2500".repeat(40)));
|
|
4455
4071
|
if (!url) {
|
|
4456
|
-
console.log(
|
|
4457
|
-
console.log(
|
|
4072
|
+
console.log(chalk20.red("\u274C URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
|
|
4073
|
+
console.log(chalk20.gray(' \uC608: vhk ref add https://example.com --memo "\uCC38\uACE0 \uC0AC\uC774\uD2B8"'));
|
|
4458
4074
|
return;
|
|
4459
4075
|
}
|
|
4460
4076
|
const refs = loadRefs();
|
|
4461
4077
|
if (refs.some((r) => r.url === url)) {
|
|
4462
|
-
console.log(
|
|
4078
|
+
console.log(chalk20.yellow("\u26A0\uFE0F \uC774\uBBF8 \uC800\uC7A5\uB41C URL\uC785\uB2C8\uB2E4."));
|
|
4463
4079
|
return;
|
|
4464
4080
|
}
|
|
4465
4081
|
refs.push({ url, memo, addedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4466
4082
|
saveRefs(refs);
|
|
4467
|
-
console.log(
|
|
4083
|
+
console.log(chalk20.green(`
|
|
4468
4084
|
\u2705 \uB808\uD37C\uB7F0\uC2A4 \uCD94\uAC00\uB428 (#${refs.length})`));
|
|
4469
|
-
console.log(
|
|
4470
|
-
if (memo) console.log(
|
|
4085
|
+
console.log(chalk20.cyan(` ${url}`));
|
|
4086
|
+
if (memo) console.log(chalk20.gray(` \u{1F4DD} ${memo}`));
|
|
4471
4087
|
printNextStep({
|
|
4472
4088
|
message: "\uB808\uD37C\uB7F0\uC2A4 \uC800\uC7A5 \uC644\uB8CC!",
|
|
4473
4089
|
command: "vhk ref list",
|
|
@@ -4475,22 +4091,22 @@ async function refAdd(url, memo = "") {
|
|
|
4475
4091
|
});
|
|
4476
4092
|
}
|
|
4477
4093
|
async function refList() {
|
|
4478
|
-
console.log(
|
|
4479
|
-
console.log(
|
|
4094
|
+
console.log(chalk20.bold("\n\u{1F4DA} " + t("ref.listTitle")));
|
|
4095
|
+
console.log(chalk20.gray("\u2500".repeat(40)));
|
|
4480
4096
|
const refs = loadRefs();
|
|
4481
4097
|
if (refs.length === 0) {
|
|
4482
|
-
console.log(
|
|
4483
|
-
console.log(
|
|
4098
|
+
console.log(chalk20.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uB808\uD37C\uB7F0\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4099
|
+
console.log(chalk20.gray(' vhk ref add <url> --memo "\uBA54\uBAA8"\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
|
|
4484
4100
|
return;
|
|
4485
4101
|
}
|
|
4486
|
-
console.log(
|
|
4102
|
+
console.log(chalk20.cyan(`
|
|
4487
4103
|
\uCD1D ${refs.length}\uAC1C\uC758 \uB808\uD37C\uB7F0\uC2A4:
|
|
4488
4104
|
`));
|
|
4489
4105
|
refs.forEach((ref, index) => {
|
|
4490
4106
|
const date = new Date(ref.addedAt).toLocaleDateString("ko-KR");
|
|
4491
|
-
console.log(
|
|
4492
|
-
if (ref.memo) console.log(
|
|
4493
|
-
console.log(
|
|
4107
|
+
console.log(chalk20.white(` [${index + 1}] ${ref.url}`));
|
|
4108
|
+
if (ref.memo) console.log(chalk20.gray(` \u{1F4DD} ${ref.memo}`));
|
|
4109
|
+
console.log(chalk20.gray(` \u{1F4C5} ${date}`));
|
|
4494
4110
|
console.log("");
|
|
4495
4111
|
});
|
|
4496
4112
|
}
|
|
@@ -4498,7 +4114,7 @@ async function refOpen(indexStr) {
|
|
|
4498
4114
|
const refs = loadRefs();
|
|
4499
4115
|
const idx = parseInt(indexStr, 10) - 1;
|
|
4500
4116
|
if (Number.isNaN(idx) || idx < 0 || idx >= refs.length) {
|
|
4501
|
-
console.log(
|
|
4117
|
+
console.log(chalk20.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${refs.length || 0})`));
|
|
4502
4118
|
return;
|
|
4503
4119
|
}
|
|
4504
4120
|
const ref = refs[idx];
|
|
@@ -4506,14 +4122,14 @@ async function refOpen(indexStr) {
|
|
|
4506
4122
|
try {
|
|
4507
4123
|
parsed = new URL(ref.url);
|
|
4508
4124
|
} catch {
|
|
4509
|
-
console.log(
|
|
4125
|
+
console.log(chalk20.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 URL: ${ref.url}`));
|
|
4510
4126
|
return;
|
|
4511
4127
|
}
|
|
4512
4128
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
4513
|
-
console.log(
|
|
4129
|
+
console.log(chalk20.red(`\u274C http(s) URL\uB9CC \uC5F4 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (${parsed.protocol})`));
|
|
4514
4130
|
return;
|
|
4515
4131
|
}
|
|
4516
|
-
console.log(
|
|
4132
|
+
console.log(chalk20.cyan(`
|
|
4517
4133
|
\u{1F310} \uC5F4\uAE30: ${ref.url}`));
|
|
4518
4134
|
let result;
|
|
4519
4135
|
if (process.platform === "darwin") {
|
|
@@ -4524,19 +4140,19 @@ async function refOpen(indexStr) {
|
|
|
4524
4140
|
result = safeExecFile("xdg-open", [ref.url]);
|
|
4525
4141
|
}
|
|
4526
4142
|
if (result.ok) {
|
|
4527
|
-
console.log(
|
|
4143
|
+
console.log(chalk20.green("\u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
4528
4144
|
} else {
|
|
4529
|
-
console.log(
|
|
4145
|
+
console.log(chalk20.yellow("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. URL\uC744 \uC9C1\uC811 \uBC29\uBB38\uD574\uC8FC\uC138\uC694."));
|
|
4530
4146
|
}
|
|
4531
4147
|
}
|
|
4532
4148
|
|
|
4533
4149
|
// src/commands/harness.ts
|
|
4534
|
-
import { existsSync as
|
|
4535
|
-
import
|
|
4150
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4151
|
+
import chalk21 from "chalk";
|
|
4536
4152
|
import ora2 from "ora";
|
|
4537
4153
|
function detectPM() {
|
|
4538
|
-
if (
|
|
4539
|
-
if (
|
|
4154
|
+
if (existsSync8("pnpm-lock.yaml")) return "pnpm";
|
|
4155
|
+
if (existsSync8("yarn.lock")) return "yarn";
|
|
4540
4156
|
return "npm";
|
|
4541
4157
|
}
|
|
4542
4158
|
function pmRun(pm, script) {
|
|
@@ -4554,14 +4170,14 @@ function detectChecks() {
|
|
|
4554
4170
|
const pm = detectPM();
|
|
4555
4171
|
if (s.lint) {
|
|
4556
4172
|
checks.push({ name: "lint", bin: pm, args: pmRun(pm, "lint") });
|
|
4557
|
-
} else if (
|
|
4173
|
+
} else if (existsSync8(".eslintrc.js") || existsSync8(".eslintrc.json") || existsSync8("eslint.config.js")) {
|
|
4558
4174
|
checks.push({ name: "lint", bin: "npx", args: ["eslint", ".", "--ext", ".ts,.tsx"] });
|
|
4559
4175
|
}
|
|
4560
4176
|
if (s["type-check"]) {
|
|
4561
4177
|
checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "type-check") });
|
|
4562
4178
|
} else if (s.typecheck) {
|
|
4563
4179
|
checks.push({ name: "type-check", bin: pm, args: pmRun(pm, "typecheck") });
|
|
4564
|
-
} else if (
|
|
4180
|
+
} else if (existsSync8("tsconfig.json")) {
|
|
4565
4181
|
checks.push({ name: "type-check", bin: "npx", args: ["tsc", "--noEmit"] });
|
|
4566
4182
|
}
|
|
4567
4183
|
if (s.test) {
|
|
@@ -4574,15 +4190,15 @@ function detectChecks() {
|
|
|
4574
4190
|
}
|
|
4575
4191
|
async function harness() {
|
|
4576
4192
|
if (!ensureNotHardStopped("harness")) return;
|
|
4577
|
-
console.log(
|
|
4578
|
-
console.log(
|
|
4193
|
+
console.log(chalk21.bold("\n\u{1F527} " + t("harness.title")));
|
|
4194
|
+
console.log(chalk21.gray("\u2500".repeat(40)));
|
|
4579
4195
|
const checks = detectChecks();
|
|
4580
4196
|
if (checks.length === 0) {
|
|
4581
|
-
console.log(
|
|
4582
|
-
console.log(
|
|
4197
|
+
console.log(chalk21.yellow("\n\u26A0\uFE0F \uC2E4\uD589\uD560 \uC218 \uC788\uB294 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4198
|
+
console.log(chalk21.gray(" package.json\uC5D0 lint, test, build \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD574\uC8FC\uC138\uC694."));
|
|
4583
4199
|
return;
|
|
4584
4200
|
}
|
|
4585
|
-
console.log(
|
|
4201
|
+
console.log(chalk21.cyan(`
|
|
4586
4202
|
\u{1F3C3} ${checks.length}\uAC1C \uC810\uAC80 \uC2DC\uC791:
|
|
4587
4203
|
`));
|
|
4588
4204
|
const results = [];
|
|
@@ -4594,10 +4210,10 @@ async function harness() {
|
|
|
4594
4210
|
const duration = Date.now() - start2;
|
|
4595
4211
|
const sec = (duration / 1e3).toFixed(1);
|
|
4596
4212
|
if (result.ok) {
|
|
4597
|
-
spinner.succeed(`${check2.name} ${
|
|
4213
|
+
spinner.succeed(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
|
|
4598
4214
|
results.push({ name: check2.name, command: display, passed: true, duration });
|
|
4599
4215
|
} else {
|
|
4600
|
-
spinner.fail(`${check2.name} ${
|
|
4216
|
+
spinner.fail(`${check2.name} ${chalk21.gray(`(${sec}s)`)}`);
|
|
4601
4217
|
results.push({
|
|
4602
4218
|
name: check2.name,
|
|
4603
4219
|
command: display,
|
|
@@ -4607,22 +4223,22 @@ async function harness() {
|
|
|
4607
4223
|
});
|
|
4608
4224
|
}
|
|
4609
4225
|
}
|
|
4610
|
-
console.log(
|
|
4611
|
-
console.log(
|
|
4226
|
+
console.log(chalk21.bold("\n\u{1F4CA} \uD1B5\uD569 \uB9AC\uD3EC\uD2B8:"));
|
|
4227
|
+
console.log(chalk21.gray("\u2500".repeat(40)));
|
|
4612
4228
|
for (const r of results) {
|
|
4613
|
-
const icon = r.passed ?
|
|
4229
|
+
const icon = r.passed ? chalk21.green("\u2705") : chalk21.red("\u274C");
|
|
4614
4230
|
const sec = (r.duration / 1e3).toFixed(1);
|
|
4615
|
-
console.log(` ${icon} ${r.name.padEnd(15)} ${
|
|
4231
|
+
console.log(` ${icon} ${r.name.padEnd(15)} ${chalk21.gray(`${sec}s`)}`);
|
|
4616
4232
|
}
|
|
4617
4233
|
const passed = results.filter((r) => r.passed).length;
|
|
4618
4234
|
const all = passed === results.length;
|
|
4619
|
-
console.log(
|
|
4235
|
+
console.log(chalk21.gray("\u2500".repeat(40)));
|
|
4620
4236
|
if (all) {
|
|
4621
|
-
console.log(
|
|
4237
|
+
console.log(chalk21.green.bold(`
|
|
4622
4238
|
\u{1F389} \uC804\uCCB4 \uD1B5\uACFC! (${passed}/${results.length})`));
|
|
4623
4239
|
} else {
|
|
4624
4240
|
console.log(
|
|
4625
|
-
|
|
4241
|
+
chalk21.red.bold(`
|
|
4626
4242
|
\u26A0\uFE0F ${results.length - passed}\uAC1C \uC2E4\uD328 (${passed}/${results.length} \uD1B5\uACFC)`)
|
|
4627
4243
|
);
|
|
4628
4244
|
process.exitCode = 1;
|
|
@@ -4635,9 +4251,9 @@ async function harness() {
|
|
|
4635
4251
|
}
|
|
4636
4252
|
|
|
4637
4253
|
// src/commands/migrate.ts
|
|
4638
|
-
import { existsSync as
|
|
4639
|
-
import
|
|
4640
|
-
import
|
|
4254
|
+
import { existsSync as existsSync9, unlinkSync, rmSync as rmSync2 } from "fs";
|
|
4255
|
+
import chalk22 from "chalk";
|
|
4256
|
+
import inquirer10 from "inquirer";
|
|
4641
4257
|
import ora3 from "ora";
|
|
4642
4258
|
var LOCK_FILES = {
|
|
4643
4259
|
npm: "package-lock.json",
|
|
@@ -4645,26 +4261,26 @@ var LOCK_FILES = {
|
|
|
4645
4261
|
pnpm: "pnpm-lock.yaml"
|
|
4646
4262
|
};
|
|
4647
4263
|
function detectCurrentPM() {
|
|
4648
|
-
if (
|
|
4649
|
-
if (
|
|
4650
|
-
if (
|
|
4264
|
+
if (existsSync9("pnpm-lock.yaml")) return "pnpm";
|
|
4265
|
+
if (existsSync9("yarn.lock")) return "yarn";
|
|
4266
|
+
if (existsSync9("package-lock.json")) return "npm";
|
|
4651
4267
|
return null;
|
|
4652
4268
|
}
|
|
4653
4269
|
function isCLIAvailable(pm) {
|
|
4654
4270
|
return safeExecFile(pm, ["--version"]).ok;
|
|
4655
4271
|
}
|
|
4656
4272
|
async function migrate(target) {
|
|
4657
|
-
console.log(
|
|
4658
|
-
console.log(
|
|
4273
|
+
console.log(chalk22.bold("\n\u{1F504} " + t("migrate.title")));
|
|
4274
|
+
console.log(chalk22.gray("\u2500".repeat(40)));
|
|
4659
4275
|
const current = detectCurrentPM();
|
|
4660
|
-
console.log(
|
|
4276
|
+
console.log(chalk22.cyan(`
|
|
4661
4277
|
\uD604\uC7AC \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800: ${current ?? "\uAC10\uC9C0 \uBD88\uAC00"}`));
|
|
4662
4278
|
let targetPM;
|
|
4663
4279
|
if (target && ["npm", "yarn", "pnpm"].includes(target)) {
|
|
4664
4280
|
targetPM = target;
|
|
4665
4281
|
} else {
|
|
4666
4282
|
const choices = ["npm", "yarn", "pnpm"].filter((pm) => pm !== current).map((pm) => ({ name: pm, value: pm }));
|
|
4667
|
-
const { selected } = await
|
|
4283
|
+
const { selected } = await inquirer10.prompt([
|
|
4668
4284
|
{
|
|
4669
4285
|
type: "list",
|
|
4670
4286
|
name: "selected",
|
|
@@ -4675,17 +4291,17 @@ async function migrate(target) {
|
|
|
4675
4291
|
targetPM = selected;
|
|
4676
4292
|
}
|
|
4677
4293
|
if (targetPM === current) {
|
|
4678
|
-
console.log(
|
|
4294
|
+
console.log(chalk22.yellow(`
|
|
4679
4295
|
\u26A0\uFE0F \uC774\uBBF8 ${targetPM}\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4.`));
|
|
4680
4296
|
return;
|
|
4681
4297
|
}
|
|
4682
4298
|
if (!isCLIAvailable(targetPM)) {
|
|
4683
|
-
console.log(
|
|
4299
|
+
console.log(chalk22.red(`
|
|
4684
4300
|
\u274C ${targetPM}\uC774 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
4685
|
-
console.log(
|
|
4301
|
+
console.log(chalk22.yellow(` npm i -g ${targetPM}`));
|
|
4686
4302
|
return;
|
|
4687
4303
|
}
|
|
4688
|
-
const { confirm } = await
|
|
4304
|
+
const { confirm } = await inquirer10.prompt([
|
|
4689
4305
|
{
|
|
4690
4306
|
type: "confirm",
|
|
4691
4307
|
name: "confirm",
|
|
@@ -4694,16 +4310,16 @@ async function migrate(target) {
|
|
|
4694
4310
|
}
|
|
4695
4311
|
]);
|
|
4696
4312
|
if (!confirm) {
|
|
4697
|
-
console.log(
|
|
4313
|
+
console.log(chalk22.gray("\uCDE8\uC18C\uB428"));
|
|
4698
4314
|
return;
|
|
4699
4315
|
}
|
|
4700
4316
|
const cleanup = ora3("\uAE30\uC874 lock \uD30C\uC77C \uC815\uB9AC \uC911...").start();
|
|
4701
4317
|
for (const lockFile of Object.values(LOCK_FILES)) {
|
|
4702
|
-
if (
|
|
4318
|
+
if (existsSync9(lockFile)) {
|
|
4703
4319
|
unlinkSync(lockFile);
|
|
4704
4320
|
}
|
|
4705
4321
|
}
|
|
4706
|
-
if (
|
|
4322
|
+
if (existsSync9("node_modules")) {
|
|
4707
4323
|
cleanup.text = "node_modules \uC0AD\uC81C \uC911...";
|
|
4708
4324
|
rmSync2("node_modules", { recursive: true, force: true });
|
|
4709
4325
|
}
|
|
@@ -4714,10 +4330,10 @@ async function migrate(target) {
|
|
|
4714
4330
|
install.succeed(`${targetPM} install \uC644\uB8CC!`);
|
|
4715
4331
|
} else {
|
|
4716
4332
|
install.fail(`${targetPM} install \uC2E4\uD328`);
|
|
4717
|
-
console.log(
|
|
4333
|
+
console.log(chalk22.red(installResult.err.slice(0, 300)));
|
|
4718
4334
|
return;
|
|
4719
4335
|
}
|
|
4720
|
-
console.log(
|
|
4336
|
+
console.log(chalk22.green.bold(`
|
|
4721
4337
|
\u{1F389} ${current ?? "\uC774\uC804"} \u2192 ${targetPM} \uC804\uD658 \uC644\uB8CC!`));
|
|
4722
4338
|
printNextStep({
|
|
4723
4339
|
message: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 \uC644\uB8CC!",
|
|
@@ -4727,17 +4343,17 @@ async function migrate(target) {
|
|
|
4727
4343
|
}
|
|
4728
4344
|
|
|
4729
4345
|
// src/commands/update.ts
|
|
4730
|
-
import { existsSync as
|
|
4731
|
-
import { dirname as dirname2, join as
|
|
4346
|
+
import { existsSync as existsSync10 } from "fs";
|
|
4347
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
4732
4348
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4733
|
-
import
|
|
4349
|
+
import chalk23 from "chalk";
|
|
4734
4350
|
import ora4 from "ora";
|
|
4735
4351
|
var PACKAGE = "@byh3071/vhk";
|
|
4736
4352
|
function getCurrentVersion() {
|
|
4737
4353
|
const dir = dirname2(fileURLToPath3(import.meta.url));
|
|
4738
|
-
for (const pkgPath of [
|
|
4354
|
+
for (const pkgPath of [join6(dir, "../package.json"), join6(dir, "../../package.json")]) {
|
|
4739
4355
|
try {
|
|
4740
|
-
if (
|
|
4356
|
+
if (existsSync10(pkgPath)) {
|
|
4741
4357
|
const pkg = readJsonFile(pkgPath);
|
|
4742
4358
|
if (pkg.version) return pkg.version;
|
|
4743
4359
|
}
|
|
@@ -4760,32 +4376,32 @@ function isUpToDate(current, latest) {
|
|
|
4760
4376
|
return cc >= lc;
|
|
4761
4377
|
}
|
|
4762
4378
|
async function update() {
|
|
4763
|
-
console.log(
|
|
4764
|
-
console.log(
|
|
4379
|
+
console.log(chalk23.bold("\n\u2B06\uFE0F " + t("update.title")));
|
|
4380
|
+
console.log(chalk23.gray("\u2500".repeat(40)));
|
|
4765
4381
|
const current = getCurrentVersion();
|
|
4766
|
-
console.log(
|
|
4382
|
+
console.log(chalk23.cyan(`
|
|
4767
4383
|
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${current}`));
|
|
4768
4384
|
const spinner = ora4("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...").start();
|
|
4769
4385
|
const latest = getLatestVersion();
|
|
4770
4386
|
if (!latest) {
|
|
4771
4387
|
spinner.fail("\uCD5C\uC2E0 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
4772
|
-
console.log(
|
|
4773
|
-
console.log(
|
|
4388
|
+
console.log(chalk23.yellow(" \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uAC70\uB098 \uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
|
|
4389
|
+
console.log(chalk23.gray(` npm update -g ${PACKAGE}`));
|
|
4774
4390
|
return;
|
|
4775
4391
|
}
|
|
4776
4392
|
spinner.stop();
|
|
4777
|
-
console.log(
|
|
4393
|
+
console.log(chalk23.cyan(`\u{1F195} \uCD5C\uC2E0 \uBC84\uC804: v${latest}`));
|
|
4778
4394
|
if (isUpToDate(current, latest)) {
|
|
4779
|
-
console.log(
|
|
4395
|
+
console.log(chalk23.green("\n\u2705 \uC774\uBBF8 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4!"));
|
|
4780
4396
|
return;
|
|
4781
4397
|
}
|
|
4782
4398
|
const updateSpinner = ora4(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC911...`).start();
|
|
4783
4399
|
const upd = safeExecFile("npm", ["update", "-g", PACKAGE]);
|
|
4784
4400
|
if (upd.ok) {
|
|
4785
4401
|
updateSpinner.succeed(`v${latest}\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`);
|
|
4786
|
-
console.log(
|
|
4402
|
+
console.log(chalk23.green.bold(`
|
|
4787
4403
|
\u{1F389} VHK CLI v${latest} \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
|
|
4788
|
-
console.log(
|
|
4404
|
+
console.log(chalk23.gray(" \uBCC0\uACBD \uC0AC\uD56D\uC740 GitHub Releases\uB97C \uD655\uC778\uD558\uC138\uC694."));
|
|
4789
4405
|
printNextStep({
|
|
4790
4406
|
message: t("update.nextOkMessage"),
|
|
4791
4407
|
command: "vhk --version",
|
|
@@ -4793,9 +4409,9 @@ async function update() {
|
|
|
4793
4409
|
});
|
|
4794
4410
|
} else {
|
|
4795
4411
|
updateSpinner.fail("\uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
|
|
4796
|
-
console.log(
|
|
4797
|
-
console.log(
|
|
4798
|
-
console.log(
|
|
4412
|
+
console.log(chalk23.red(upd.err.slice(0, 300)));
|
|
4413
|
+
console.log(chalk23.yellow("\n\uC218\uB3D9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694:"));
|
|
4414
|
+
console.log(chalk23.gray(` npm update -g ${PACKAGE}`));
|
|
4799
4415
|
printNextStep({
|
|
4800
4416
|
message: t("update.nextFailMessage"),
|
|
4801
4417
|
command: "vhk doctor",
|
|
@@ -4806,16 +4422,16 @@ async function update() {
|
|
|
4806
4422
|
|
|
4807
4423
|
// src/commands/context.ts
|
|
4808
4424
|
import {
|
|
4809
|
-
existsSync as
|
|
4425
|
+
existsSync as existsSync11,
|
|
4810
4426
|
mkdirSync as mkdirSync7,
|
|
4811
|
-
readFileSync as
|
|
4427
|
+
readFileSync as readFileSync4,
|
|
4812
4428
|
readdirSync as readdirSync2,
|
|
4813
4429
|
statSync as statSync2,
|
|
4814
4430
|
writeFileSync as writeFileSync7
|
|
4815
4431
|
} from "fs";
|
|
4816
|
-
import { join as
|
|
4817
|
-
import
|
|
4818
|
-
var
|
|
4432
|
+
import { join as join7 } from "path";
|
|
4433
|
+
import chalk24 from "chalk";
|
|
4434
|
+
var CONTEXT_PATH = ".vhk/context.md";
|
|
4819
4435
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
4820
4436
|
"node_modules",
|
|
4821
4437
|
".git",
|
|
@@ -4839,7 +4455,7 @@ function buildTree(dir, prefix = "", maxDepth = 3, depth = 0) {
|
|
|
4839
4455
|
filtered.forEach((entry, index) => {
|
|
4840
4456
|
const isLast = index === filtered.length - 1;
|
|
4841
4457
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
4842
|
-
const fullPath =
|
|
4458
|
+
const fullPath = join7(dir, entry);
|
|
4843
4459
|
const stat = statSync2(fullPath);
|
|
4844
4460
|
const isDir = stat.isDirectory();
|
|
4845
4461
|
lines.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
|
|
@@ -4871,8 +4487,8 @@ function extractTechStack() {
|
|
|
4871
4487
|
else if (all.jest) stack["\uD14C\uC2A4\uD2B8"] = "jest";
|
|
4872
4488
|
if (all.commander) stack["CLI"] = "commander";
|
|
4873
4489
|
if (all.inquirer) stack["\uC778\uD130\uB799\uD2F0\uBE0C"] = "inquirer";
|
|
4874
|
-
if (
|
|
4875
|
-
else if (
|
|
4490
|
+
if (existsSync11("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
|
|
4491
|
+
else if (existsSync11("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
|
|
4876
4492
|
else stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "npm";
|
|
4877
4493
|
if (pkg.name) stack["\uD328\uD0A4\uC9C0 \uC774\uB984"] = pkg.name;
|
|
4878
4494
|
if (pkg.version) stack["\uBC84\uC804"] = pkg.version;
|
|
@@ -4915,8 +4531,8 @@ function getVhkCommands() {
|
|
|
4915
4531
|
}
|
|
4916
4532
|
async function context(opts = {}) {
|
|
4917
4533
|
const compact = opts.compact === true;
|
|
4918
|
-
console.log(
|
|
4919
|
-
console.log(
|
|
4534
|
+
console.log(chalk24.bold("\n\u{1F9E0} " + t("context.title")));
|
|
4535
|
+
console.log(chalk24.gray("\u2500".repeat(40)));
|
|
4920
4536
|
const stack = extractTechStack();
|
|
4921
4537
|
const tree = buildTree(".", "", compact ? 2 : 3).join("\n");
|
|
4922
4538
|
const commands = getVhkCommands();
|
|
@@ -4951,7 +4567,7 @@ async function context(opts = {}) {
|
|
|
4951
4567
|
}
|
|
4952
4568
|
lines.push("");
|
|
4953
4569
|
}
|
|
4954
|
-
if (
|
|
4570
|
+
if (existsSync11(".vhk/memory.json")) {
|
|
4955
4571
|
try {
|
|
4956
4572
|
const memories = readJsonFile(
|
|
4957
4573
|
".vhk/memory.json"
|
|
@@ -5030,11 +4646,11 @@ async function context(opts = {}) {
|
|
|
5030
4646
|
}
|
|
5031
4647
|
lines.push("");
|
|
5032
4648
|
mkdirSync7(".vhk", { recursive: true });
|
|
5033
|
-
writeFileSync7(
|
|
5034
|
-
console.log(
|
|
5035
|
-
\u2705 ${
|
|
5036
|
-
console.log(
|
|
5037
|
-
console.log(
|
|
4649
|
+
writeFileSync7(CONTEXT_PATH, lines.join("\n"), "utf-8");
|
|
4650
|
+
console.log(chalk24.green(`
|
|
4651
|
+
\u2705 ${CONTEXT_PATH} \uC0DD\uC131 \uC644\uB8CC!`));
|
|
4652
|
+
console.log(chalk24.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
|
|
4653
|
+
console.log(chalk24.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
|
|
5038
4654
|
printNextStep({
|
|
5039
4655
|
message: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC!",
|
|
5040
4656
|
command: "vhk context-show",
|
|
@@ -5042,23 +4658,23 @@ async function context(opts = {}) {
|
|
|
5042
4658
|
});
|
|
5043
4659
|
}
|
|
5044
4660
|
async function contextShow() {
|
|
5045
|
-
console.log(
|
|
5046
|
-
console.log(
|
|
5047
|
-
if (!
|
|
5048
|
-
console.log(
|
|
5049
|
-
console.log(
|
|
4661
|
+
console.log(chalk24.bold("\n\u{1F4C4} " + t("context.showTitle")));
|
|
4662
|
+
console.log(chalk24.gray("\u2500".repeat(40)));
|
|
4663
|
+
if (!existsSync11(CONTEXT_PATH)) {
|
|
4664
|
+
console.log(chalk24.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4665
|
+
console.log(chalk24.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
|
|
5050
4666
|
return;
|
|
5051
4667
|
}
|
|
5052
|
-
const content =
|
|
4668
|
+
const content = readFileSync4(CONTEXT_PATH, "utf-8");
|
|
5053
4669
|
console.log("\n" + content);
|
|
5054
4670
|
}
|
|
5055
4671
|
|
|
5056
4672
|
// src/commands/memory.ts
|
|
5057
|
-
import { existsSync as
|
|
5058
|
-
import
|
|
4673
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
4674
|
+
import chalk25 from "chalk";
|
|
5059
4675
|
var MEMORY_PATH = ".vhk/memory.json";
|
|
5060
4676
|
function loadMemories() {
|
|
5061
|
-
if (!
|
|
4677
|
+
if (!existsSync12(MEMORY_PATH)) return [];
|
|
5062
4678
|
try {
|
|
5063
4679
|
const parsed = readJsonFile(MEMORY_PATH);
|
|
5064
4680
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -5071,11 +4687,11 @@ function saveMemories(memories) {
|
|
|
5071
4687
|
writeFileSync8(MEMORY_PATH, JSON.stringify(memories, null, 2) + "\n", "utf-8");
|
|
5072
4688
|
}
|
|
5073
4689
|
async function memoryAdd(content, tags) {
|
|
5074
|
-
console.log(
|
|
5075
|
-
console.log(
|
|
4690
|
+
console.log(chalk25.bold("\n\u{1F9E0} " + t("memory.addTitle")));
|
|
4691
|
+
console.log(chalk25.gray("\u2500".repeat(40)));
|
|
5076
4692
|
if (!content) {
|
|
5077
|
-
console.log(
|
|
5078
|
-
console.log(
|
|
4693
|
+
console.log(chalk25.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
|
|
4694
|
+
console.log(chalk25.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9\uD558\uAE30\uB85C \uACB0\uC815"'));
|
|
5079
4695
|
process.exitCode = 1;
|
|
5080
4696
|
return;
|
|
5081
4697
|
}
|
|
@@ -5086,9 +4702,9 @@ async function memoryAdd(content, tags) {
|
|
|
5086
4702
|
tags: tags && tags.length > 0 ? tags : []
|
|
5087
4703
|
});
|
|
5088
4704
|
saveMemories(memories);
|
|
5089
|
-
console.log(
|
|
4705
|
+
console.log(chalk25.green(`
|
|
5090
4706
|
\u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (#${memories.length})`));
|
|
5091
|
-
console.log(
|
|
4707
|
+
console.log(chalk25.cyan(` \u{1F4DD} ${content}`));
|
|
5092
4708
|
printNextStep({
|
|
5093
4709
|
message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!",
|
|
5094
4710
|
command: "vhk memory list",
|
|
@@ -5096,24 +4712,24 @@ async function memoryAdd(content, tags) {
|
|
|
5096
4712
|
});
|
|
5097
4713
|
}
|
|
5098
4714
|
async function memoryList() {
|
|
5099
|
-
console.log(
|
|
5100
|
-
console.log(
|
|
4715
|
+
console.log(chalk25.bold("\n\u{1F9E0} " + t("memory.listTitle")));
|
|
4716
|
+
console.log(chalk25.gray("\u2500".repeat(40)));
|
|
5101
4717
|
const memories = loadMemories();
|
|
5102
4718
|
if (memories.length === 0) {
|
|
5103
|
-
console.log(
|
|
5104
|
-
console.log(
|
|
4719
|
+
console.log(chalk25.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4720
|
+
console.log(chalk25.gray(' vhk memory add "\uB0B4\uC6A9"\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
|
|
5105
4721
|
return;
|
|
5106
4722
|
}
|
|
5107
|
-
console.log(
|
|
4723
|
+
console.log(chalk25.cyan(`
|
|
5108
4724
|
\uCD1D ${memories.length}\uAC1C\uC758 \uAE30\uC5B5:
|
|
5109
4725
|
`));
|
|
5110
4726
|
memories.forEach((m, index) => {
|
|
5111
4727
|
const date = new Date(m.addedAt).toLocaleDateString("ko-KR");
|
|
5112
|
-
console.log(
|
|
4728
|
+
console.log(chalk25.white(` [${index + 1}] ${m.content}`));
|
|
5113
4729
|
if (m.tags && m.tags.length > 0) {
|
|
5114
|
-
console.log(
|
|
4730
|
+
console.log(chalk25.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
|
|
5115
4731
|
}
|
|
5116
|
-
console.log(
|
|
4732
|
+
console.log(chalk25.gray(` \u{1F4C5} ${date}`));
|
|
5117
4733
|
console.log("");
|
|
5118
4734
|
});
|
|
5119
4735
|
}
|
|
@@ -5121,31 +4737,31 @@ async function memoryRemove(indexStr) {
|
|
|
5121
4737
|
const memories = loadMemories();
|
|
5122
4738
|
const idx = parseInt(indexStr, 10) - 1;
|
|
5123
4739
|
if (Number.isNaN(idx) || idx < 0 || idx >= memories.length) {
|
|
5124
|
-
console.log(
|
|
4740
|
+
console.log(chalk25.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${memories.length || 0})`));
|
|
5125
4741
|
return;
|
|
5126
4742
|
}
|
|
5127
4743
|
const removed = memories.splice(idx, 1)[0];
|
|
5128
4744
|
saveMemories(memories);
|
|
5129
|
-
console.log(
|
|
5130
|
-
console.log(
|
|
4745
|
+
console.log(chalk25.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
|
|
4746
|
+
console.log(chalk25.gray(` ${removed.content}`));
|
|
5131
4747
|
}
|
|
5132
4748
|
|
|
5133
4749
|
// src/commands/brief.ts
|
|
5134
|
-
import { existsSync as
|
|
5135
|
-
import
|
|
4750
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync5 } from "fs";
|
|
4751
|
+
import chalk26 from "chalk";
|
|
5136
4752
|
var BRIEF_PATH = ".vhk/brief.md";
|
|
5137
4753
|
function readProjectIdentity() {
|
|
5138
4754
|
const out = {};
|
|
5139
4755
|
try {
|
|
5140
|
-
if (
|
|
5141
|
-
const r =
|
|
4756
|
+
if (existsSync13("RULES.md")) {
|
|
4757
|
+
const r = readFileSync5("RULES.md", "utf-8");
|
|
5142
4758
|
const m = r.split("\n")[0].match(/^#\s*(.+?)(?:\s*—.*)?$/);
|
|
5143
4759
|
if (m) out.name = m[1].trim();
|
|
5144
4760
|
const d = r.match(/한 줄 설명:\s*(.+)/);
|
|
5145
4761
|
if (d) out.description = d[1].trim();
|
|
5146
4762
|
}
|
|
5147
|
-
if (!out.name &&
|
|
5148
|
-
const m =
|
|
4763
|
+
if (!out.name && existsSync13("CLAUDE.md")) {
|
|
4764
|
+
const m = readFileSync5("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
|
|
5149
4765
|
if (m) out.name = m[1].trim();
|
|
5150
4766
|
}
|
|
5151
4767
|
} catch {
|
|
@@ -5157,8 +4773,8 @@ function git2(args) {
|
|
|
5157
4773
|
return result.ok ? result.out : "";
|
|
5158
4774
|
}
|
|
5159
4775
|
async function brief() {
|
|
5160
|
-
console.log(
|
|
5161
|
-
console.log(
|
|
4776
|
+
console.log(chalk26.bold("\n\u{1F4CB} " + t("brief.title")));
|
|
4777
|
+
console.log(chalk26.gray("\u2500".repeat(40)));
|
|
5162
4778
|
const lines = [];
|
|
5163
4779
|
lines.push("# \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB9AC\uD551");
|
|
5164
4780
|
lines.push("");
|
|
@@ -5195,7 +4811,7 @@ async function brief() {
|
|
|
5195
4811
|
`- **\uBBF8\uCEE4\uBC0B \uBCC0\uACBD**: ${uncommitted ? `${uncommitted.split("\n").length}\uAC1C \uD30C\uC77C` : "\uC5C6\uC74C \u2705"}`
|
|
5196
4812
|
);
|
|
5197
4813
|
lines.push("");
|
|
5198
|
-
if (
|
|
4814
|
+
if (existsSync13(".vhk/memory.json")) {
|
|
5199
4815
|
try {
|
|
5200
4816
|
const memories = readJsonFile(".vhk/memory.json");
|
|
5201
4817
|
if (Array.isArray(memories) && memories.length > 0) {
|
|
@@ -5212,7 +4828,7 @@ async function brief() {
|
|
|
5212
4828
|
} catch {
|
|
5213
4829
|
}
|
|
5214
4830
|
}
|
|
5215
|
-
if (
|
|
4831
|
+
if (existsSync13(".vhk/refs.json")) {
|
|
5216
4832
|
try {
|
|
5217
4833
|
const refs = readJsonFile(".vhk/refs.json");
|
|
5218
4834
|
if (Array.isArray(refs) && refs.length > 0) {
|
|
@@ -5243,7 +4859,7 @@ async function brief() {
|
|
|
5243
4859
|
mkdirSync9(".vhk", { recursive: true });
|
|
5244
4860
|
writeFileSync9(BRIEF_PATH, lines.join("\n"), "utf-8");
|
|
5245
4861
|
console.log("\n" + lines.join("\n"));
|
|
5246
|
-
console.log(
|
|
4862
|
+
console.log(chalk26.green(`
|
|
5247
4863
|
\u2705 ${BRIEF_PATH} \uC800\uC7A5 \uC644\uB8CC`));
|
|
5248
4864
|
printNextStep({
|
|
5249
4865
|
message: "\uBE0C\uB9AC\uD551 \uC0DD\uC131 \uC644\uB8CC!",
|
|
@@ -5253,11 +4869,11 @@ async function brief() {
|
|
|
5253
4869
|
}
|
|
5254
4870
|
|
|
5255
4871
|
// src/commands/start.ts
|
|
5256
|
-
import
|
|
5257
|
-
import
|
|
4872
|
+
import chalk27 from "chalk";
|
|
4873
|
+
import inquirer11 from "inquirer";
|
|
5258
4874
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
5259
|
-
import { existsSync as
|
|
5260
|
-
import { join as
|
|
4875
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4876
|
+
import { join as join8 } from "path";
|
|
5261
4877
|
var VHK_FOOTPRINT_FILES = [
|
|
5262
4878
|
"CLAUDE.md",
|
|
5263
4879
|
".cursorrules",
|
|
@@ -5266,7 +4882,7 @@ var VHK_FOOTPRINT_FILES = [
|
|
|
5266
4882
|
"docs/PRD.md"
|
|
5267
4883
|
];
|
|
5268
4884
|
function detectExistingFootprint(cwd) {
|
|
5269
|
-
return VHK_FOOTPRINT_FILES.filter((rel) =>
|
|
4885
|
+
return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join8(cwd, rel)));
|
|
5270
4886
|
}
|
|
5271
4887
|
async function runGitInit(cwd) {
|
|
5272
4888
|
try {
|
|
@@ -5295,22 +4911,22 @@ async function runStep(label, fn) {
|
|
|
5295
4911
|
}
|
|
5296
4912
|
}
|
|
5297
4913
|
async function start(options = {}) {
|
|
5298
|
-
console.log(
|
|
4914
|
+
console.log(chalk27.bold(`
|
|
5299
4915
|
${ko.start.title}
|
|
5300
4916
|
`));
|
|
5301
|
-
console.log(
|
|
5302
|
-
console.log(
|
|
5303
|
-
console.log(
|
|
5304
|
-
console.log(
|
|
5305
|
-
console.log(
|
|
4917
|
+
console.log(chalk27.dim(ko.start.intro));
|
|
4918
|
+
console.log(chalk27.dim(` ${ko.start.step1}`));
|
|
4919
|
+
console.log(chalk27.dim(` ${ko.start.step2}`));
|
|
4920
|
+
console.log(chalk27.dim(` ${ko.start.step3}`));
|
|
4921
|
+
console.log(chalk27.dim(` ${ko.start.step4}`));
|
|
5306
4922
|
console.log();
|
|
5307
4923
|
const cwd = process.cwd();
|
|
5308
4924
|
const footprint = detectExistingFootprint(cwd);
|
|
5309
4925
|
if (footprint.length > 0 && !options.yes) {
|
|
5310
|
-
console.log(
|
|
5311
|
-
for (const f of footprint) console.log(
|
|
5312
|
-
console.log(
|
|
5313
|
-
const { proceedExisting } = await
|
|
4926
|
+
console.log(chalk27.yellow("\u26A0\uFE0F \uC774\uBBF8 VHK \uC124\uCE58 \uD754\uC801\uC774 \uAC10\uC9C0\uB410\uC5B4\uC694:"));
|
|
4927
|
+
for (const f of footprint) console.log(chalk27.dim(` - ${f}`));
|
|
4928
|
+
console.log(chalk27.dim(" \uACC4\uC18D \uC9C4\uD589\uD558\uBA74 \uC77C\uBD80 \uD30C\uC77C(`.cursor/mcp.json`, `.vhk/context.md`)\uC740 \uAC31\uC2E0\xB7\uB36E\uC5B4\uC4F0\uAE30\uB429\uB2C8\uB2E4."));
|
|
4929
|
+
const { proceedExisting } = await inquirer11.prompt([{
|
|
5314
4930
|
type: "confirm",
|
|
5315
4931
|
name: "proceedExisting",
|
|
5316
4932
|
message: "\uADF8\uB798\uB3C4 \uB2E4\uC2DC \uB9C8\uBC95\uC0AC\uB97C \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
@@ -5321,7 +4937,7 @@ ${ko.start.title}
|
|
|
5321
4937
|
return;
|
|
5322
4938
|
}
|
|
5323
4939
|
} else if (!options.yes) {
|
|
5324
|
-
const { proceed } = await
|
|
4940
|
+
const { proceed } = await inquirer11.prompt([{
|
|
5325
4941
|
type: "confirm",
|
|
5326
4942
|
name: "proceed",
|
|
5327
4943
|
message: ko.start.confirmStart,
|
|
@@ -5347,7 +4963,7 @@ ${ko.start.title}
|
|
|
5347
4963
|
await runStep("[3/4] vhk mcp-init", () => mcpInit());
|
|
5348
4964
|
log.step(ko.start.step4Header);
|
|
5349
4965
|
await runStep("[4/4] vhk context", () => context());
|
|
5350
|
-
console.log(
|
|
4966
|
+
console.log(chalk27.bold.green(`
|
|
5351
4967
|
${ko.start.allDone}
|
|
5352
4968
|
`));
|
|
5353
4969
|
printNextStep({
|
|
@@ -5357,15 +4973,15 @@ ${ko.start.allDone}
|
|
|
5357
4973
|
}
|
|
5358
4974
|
|
|
5359
4975
|
// src/commands/cloud.ts
|
|
5360
|
-
import
|
|
4976
|
+
import fs12 from "fs";
|
|
5361
4977
|
import os from "os";
|
|
5362
|
-
import
|
|
5363
|
-
import
|
|
4978
|
+
import path13 from "path";
|
|
4979
|
+
import chalk28 from "chalk";
|
|
5364
4980
|
|
|
5365
4981
|
// src/lib/vhk-cloud.ts
|
|
5366
4982
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
5367
|
-
import
|
|
5368
|
-
import
|
|
4983
|
+
import fs11 from "fs";
|
|
4984
|
+
import path12 from "path";
|
|
5369
4985
|
var DEFAULT_CLOUD_EXCLUDES = [
|
|
5370
4986
|
"memory.json",
|
|
5371
4987
|
// 개인 의사결정 메모
|
|
@@ -5383,17 +4999,17 @@ var CLOUD_CONFIG_FILE = "cloud.json";
|
|
|
5383
4999
|
function loadVhkignore(rootDir) {
|
|
5384
5000
|
const ig = (0, import_ignore.default)();
|
|
5385
5001
|
ig.add(DEFAULT_CLOUD_EXCLUDES);
|
|
5386
|
-
const ignorePath =
|
|
5387
|
-
if (
|
|
5388
|
-
ig.add(
|
|
5002
|
+
const ignorePath = path12.join(rootDir, ".vhkignore");
|
|
5003
|
+
if (fs11.existsSync(ignorePath)) {
|
|
5004
|
+
ig.add(fs11.readFileSync(ignorePath, "utf-8"));
|
|
5389
5005
|
}
|
|
5390
5006
|
return ig;
|
|
5391
5007
|
}
|
|
5392
5008
|
function collectVhkFiles(rootDir, ig = loadVhkignore(rootDir)) {
|
|
5393
|
-
const vhkDir =
|
|
5009
|
+
const vhkDir = path12.join(rootDir, VHK_DIR2);
|
|
5394
5010
|
let entries;
|
|
5395
5011
|
try {
|
|
5396
|
-
entries =
|
|
5012
|
+
entries = fs11.readdirSync(vhkDir, { withFileTypes: true });
|
|
5397
5013
|
} catch {
|
|
5398
5014
|
return [];
|
|
5399
5015
|
}
|
|
@@ -5409,10 +5025,10 @@ function partitionGistFiles(gistFiles, ig) {
|
|
|
5409
5025
|
return { keep, excluded };
|
|
5410
5026
|
}
|
|
5411
5027
|
function readCloudConfig(rootDir) {
|
|
5412
|
-
const p =
|
|
5413
|
-
if (!
|
|
5028
|
+
const p = path12.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
|
|
5029
|
+
if (!fs11.existsSync(p)) return null;
|
|
5414
5030
|
try {
|
|
5415
|
-
const parsed = JSON.parse(
|
|
5031
|
+
const parsed = JSON.parse(fs11.readFileSync(p, "utf-8"));
|
|
5416
5032
|
if (parsed && typeof parsed.gistId === "string" && parsed.gistId) {
|
|
5417
5033
|
return { gistId: parsed.gistId };
|
|
5418
5034
|
}
|
|
@@ -5422,24 +5038,44 @@ function readCloudConfig(rootDir) {
|
|
|
5422
5038
|
}
|
|
5423
5039
|
}
|
|
5424
5040
|
function writeCloudConfig(rootDir, config) {
|
|
5425
|
-
const vhkDir =
|
|
5426
|
-
|
|
5427
|
-
const p =
|
|
5428
|
-
|
|
5041
|
+
const vhkDir = path12.join(rootDir, VHK_DIR2);
|
|
5042
|
+
fs11.mkdirSync(vhkDir, { recursive: true });
|
|
5043
|
+
const p = path12.join(vhkDir, CLOUD_CONFIG_FILE);
|
|
5044
|
+
fs11.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
5045
|
+
ensureCloudConfigIgnored(vhkDir);
|
|
5046
|
+
}
|
|
5047
|
+
function ensureCloudConfigIgnored(vhkDir) {
|
|
5048
|
+
const giPath = path12.join(vhkDir, ".gitignore");
|
|
5049
|
+
let content = "";
|
|
5050
|
+
try {
|
|
5051
|
+
if (fs11.existsSync(giPath)) content = fs11.readFileSync(giPath, "utf-8");
|
|
5052
|
+
} catch {
|
|
5053
|
+
return;
|
|
5054
|
+
}
|
|
5055
|
+
const already = content.split(/\r?\n/).some((l) => l.trim() === CLOUD_CONFIG_FILE);
|
|
5056
|
+
if (already) return;
|
|
5057
|
+
const block = `# secret gist \uD3EC\uC778\uD130 \u2014 \uCD94\uC801 \uAE08\uC9C0 (VHK-022)
|
|
5058
|
+
${CLOUD_CONFIG_FILE}
|
|
5059
|
+
`;
|
|
5060
|
+
const base = content.length === 0 ? "" : content.endsWith("\n") ? content : content + "\n";
|
|
5061
|
+
try {
|
|
5062
|
+
fs11.writeFileSync(giPath, base + block, "utf-8");
|
|
5063
|
+
} catch {
|
|
5064
|
+
}
|
|
5429
5065
|
}
|
|
5430
5066
|
|
|
5431
5067
|
// src/commands/cloud.ts
|
|
5432
5068
|
function ensureGhReady() {
|
|
5433
5069
|
const ver = safeExecFile("gh", ["--version"]);
|
|
5434
5070
|
if (!ver.ok) {
|
|
5435
|
-
console.log(
|
|
5436
|
-
console.log(
|
|
5071
|
+
console.log(chalk28.red(` ${ko.cloud.noGh}`));
|
|
5072
|
+
console.log(chalk28.dim(" \uC124\uCE58: https://cli.github.com/ (\uC124\uCE58 \uD6C4 `gh auth login`)"));
|
|
5437
5073
|
return false;
|
|
5438
5074
|
}
|
|
5439
5075
|
const auth = safeExecFile("gh", ["auth", "status"]);
|
|
5440
5076
|
if (!auth.ok) {
|
|
5441
|
-
console.log(
|
|
5442
|
-
console.log(
|
|
5077
|
+
console.log(chalk28.red(` ${ko.cloud.noAuth}`));
|
|
5078
|
+
console.log(chalk28.dim(" \uC2E4\uD589: gh auth login (gist \uAD8C\uD55C \uD544\uC694)"));
|
|
5443
5079
|
return false;
|
|
5444
5080
|
}
|
|
5445
5081
|
return true;
|
|
@@ -5452,29 +5088,29 @@ function parseGistId(output) {
|
|
|
5452
5088
|
return null;
|
|
5453
5089
|
}
|
|
5454
5090
|
async function cloudPush() {
|
|
5455
|
-
console.log(
|
|
5091
|
+
console.log(chalk28.bold(`
|
|
5456
5092
|
${ko.cloud.pushTitle}
|
|
5457
5093
|
`));
|
|
5458
5094
|
const cwd = process.cwd();
|
|
5459
|
-
if (!
|
|
5460
|
-
console.log(
|
|
5095
|
+
if (!fs12.existsSync(path13.join(cwd, VHK_DIR2))) {
|
|
5096
|
+
console.log(chalk28.yellow(` ${ko.cloud.noVhkDir}`));
|
|
5461
5097
|
return;
|
|
5462
5098
|
}
|
|
5463
5099
|
const ig = loadVhkignore(cwd);
|
|
5464
5100
|
const files = collectVhkFiles(cwd, ig);
|
|
5465
5101
|
if (files.length === 0) {
|
|
5466
|
-
console.log(
|
|
5102
|
+
console.log(chalk28.yellow(` ${ko.cloud.nothingToSync}`));
|
|
5467
5103
|
return;
|
|
5468
5104
|
}
|
|
5469
5105
|
if (!ensureGhReady()) {
|
|
5470
5106
|
process.exitCode = 1;
|
|
5471
5107
|
return;
|
|
5472
5108
|
}
|
|
5473
|
-
const filePaths = files.map((f) =>
|
|
5474
|
-
console.log(
|
|
5109
|
+
const filePaths = files.map((f) => path13.join(cwd, VHK_DIR2, f));
|
|
5110
|
+
console.log(chalk28.dim(` \u{1F4E6} \uBC31\uC5C5 \uB300\uC0C1 ${files.length}\uAC1C: ${files.join(", ")}
|
|
5475
5111
|
`));
|
|
5476
5112
|
const existing = readCloudConfig(cwd);
|
|
5477
|
-
const desc = `vhk .vhk backup \u2014 ${
|
|
5113
|
+
const desc = `vhk .vhk backup \u2014 ${path13.basename(cwd)}`;
|
|
5478
5114
|
if (existing) {
|
|
5479
5115
|
const gistFiles = listGistFiles(existing.gistId);
|
|
5480
5116
|
for (let i = 0; i < files.length; i++) {
|
|
@@ -5483,8 +5119,8 @@ ${ko.cloud.pushTitle}
|
|
|
5483
5119
|
const args = gistFiles.includes(name) ? ["gist", "edit", existing.gistId, "-f", name, src] : ["gist", "edit", existing.gistId, "-a", src];
|
|
5484
5120
|
const res2 = safeExecFile("gh", args);
|
|
5485
5121
|
if (!res2.ok) {
|
|
5486
|
-
console.log(
|
|
5487
|
-
console.log(
|
|
5122
|
+
console.log(chalk28.red(` ${ko.cloud.pushFail}: ${name}`));
|
|
5123
|
+
console.log(chalk28.dim(` ${res2.err}`));
|
|
5488
5124
|
process.exitCode = 1;
|
|
5489
5125
|
return;
|
|
5490
5126
|
}
|
|
@@ -5499,15 +5135,15 @@ ${ko.cloud.pushTitle}
|
|
|
5499
5135
|
if (!purgeFailed.includes(name)) purgeFailed.push(name);
|
|
5500
5136
|
}
|
|
5501
5137
|
}
|
|
5502
|
-
console.log(
|
|
5503
|
-
console.log(
|
|
5138
|
+
console.log(chalk28.green.bold(` ${ko.cloud.pushDone}`));
|
|
5139
|
+
console.log(chalk28.dim(` gist: ${existing.gistId} (\uAC31\uC2E0)`));
|
|
5504
5140
|
if (excluded.length > 0) {
|
|
5505
5141
|
const purged = excluded.filter((n) => !purgeFailed.includes(n));
|
|
5506
5142
|
if (purged.length > 0) {
|
|
5507
|
-
console.log(
|
|
5143
|
+
console.log(chalk28.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${purged.length}\uAC1C gist \uC5D0\uC11C \uC81C\uAC70: ${purged.join(", ")}`));
|
|
5508
5144
|
}
|
|
5509
5145
|
if (purgeFailed.length > 0) {
|
|
5510
|
-
console.log(
|
|
5146
|
+
console.log(chalk28.yellow(` \u26A0\uFE0F \uC81C\uC678 \uB300\uC0C1 \uC81C\uAC70 \uC2E4\uD328: ${purgeFailed.join(", ")} (\uC218\uB3D9 \uC81C\uAC70 \uAD8C\uC7A5 \u2014 pull \uC2DC\uC5D4 \uBCF5\uC6D0 \uC548 \uB428)`));
|
|
5511
5147
|
}
|
|
5512
5148
|
}
|
|
5513
5149
|
printPushNext();
|
|
@@ -5515,32 +5151,32 @@ ${ko.cloud.pushTitle}
|
|
|
5515
5151
|
}
|
|
5516
5152
|
const res = safeExecFile("gh", ["gist", "create", "--desc", desc, ...filePaths]);
|
|
5517
5153
|
if (!res.ok) {
|
|
5518
|
-
console.log(
|
|
5519
|
-
console.log(
|
|
5154
|
+
console.log(chalk28.red(` ${ko.cloud.pushFail}`));
|
|
5155
|
+
console.log(chalk28.dim(` ${res.err || res.out}`));
|
|
5520
5156
|
process.exitCode = 1;
|
|
5521
5157
|
return;
|
|
5522
5158
|
}
|
|
5523
5159
|
const gistId = parseGistId(res.out);
|
|
5524
5160
|
if (!gistId) {
|
|
5525
|
-
console.log(
|
|
5526
|
-
console.log(
|
|
5161
|
+
console.log(chalk28.red(` ${ko.cloud.pushFail} \u2014 gist id \uD30C\uC2F1 \uC2E4\uD328`));
|
|
5162
|
+
console.log(chalk28.dim(` \uCD9C\uB825: ${res.out}`));
|
|
5527
5163
|
process.exitCode = 1;
|
|
5528
5164
|
return;
|
|
5529
5165
|
}
|
|
5530
5166
|
writeCloudConfig(cwd, { gistId });
|
|
5531
|
-
console.log(
|
|
5532
|
-
console.log(
|
|
5167
|
+
console.log(chalk28.green.bold(` ${ko.cloud.pushDone}`));
|
|
5168
|
+
console.log(chalk28.dim(` gist: ${gistId} (\uC2E0\uADDC, secret) \u2192 .vhk/cloud.json \uC800\uC7A5`));
|
|
5533
5169
|
printPushNext();
|
|
5534
5170
|
}
|
|
5535
5171
|
async function cloudPull(gistIdArg) {
|
|
5536
|
-
console.log(
|
|
5172
|
+
console.log(chalk28.bold(`
|
|
5537
5173
|
${ko.cloud.pullTitle}
|
|
5538
5174
|
`));
|
|
5539
5175
|
const cwd = process.cwd();
|
|
5540
5176
|
const gistId = gistIdArg || readCloudConfig(cwd)?.gistId;
|
|
5541
5177
|
if (!gistId) {
|
|
5542
|
-
console.log(
|
|
5543
|
-
console.log(
|
|
5178
|
+
console.log(chalk28.yellow(` ${ko.cloud.noGistId}`));
|
|
5179
|
+
console.log(chalk28.dim(" \uC0AC\uC6A9\uBC95: vhk cloud pull <gistId> (\uB610\uB294 cloud.json \uC774 \uC788\uB294 \uACF3\uC5D0\uC11C \uC2E4\uD589)"));
|
|
5544
5180
|
return;
|
|
5545
5181
|
}
|
|
5546
5182
|
if (!ensureGhReady()) {
|
|
@@ -5549,34 +5185,34 @@ ${ko.cloud.pullTitle}
|
|
|
5549
5185
|
}
|
|
5550
5186
|
const allNames = listGistFiles(gistId);
|
|
5551
5187
|
if (allNames.length === 0) {
|
|
5552
|
-
console.log(
|
|
5188
|
+
console.log(chalk28.red(` ${ko.cloud.pullFail} \u2014 gist \uBE44\uC5C8\uAC70\uB098 \uC811\uADFC \uBD88\uAC00: ${gistId}`));
|
|
5553
5189
|
process.exitCode = 1;
|
|
5554
5190
|
return;
|
|
5555
5191
|
}
|
|
5556
5192
|
const { keep: names, excluded: skipped } = partitionGistFiles(allNames, loadVhkignore(cwd));
|
|
5557
5193
|
if (skipped.length > 0) {
|
|
5558
|
-
console.log(
|
|
5194
|
+
console.log(chalk28.dim(` \u{1F512} \uC81C\uC678 \uB300\uC0C1 ${skipped.length}\uAC1C \uBCF5\uC6D0 \uC2A4\uD0B5: ${skipped.join(", ")}`));
|
|
5559
5195
|
}
|
|
5560
5196
|
if (names.length === 0) {
|
|
5561
|
-
console.log(
|
|
5197
|
+
console.log(chalk28.yellow(` \uBCF5\uC6D0 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (gist \uD30C\uC77C\uC774 \uBAA8\uB450 \uC81C\uC678 \uADDC\uCE59\uC5D0 \uD574\uB2F9).`));
|
|
5562
5198
|
return;
|
|
5563
5199
|
}
|
|
5564
|
-
const vhkDir =
|
|
5565
|
-
|
|
5200
|
+
const vhkDir = path13.join(cwd, VHK_DIR2);
|
|
5201
|
+
fs12.mkdirSync(vhkDir, { recursive: true });
|
|
5566
5202
|
let restored = 0;
|
|
5567
5203
|
for (const name of names) {
|
|
5568
5204
|
const res = safeExecFile("gh", ["gist", "view", gistId, "-f", name, "--raw"]);
|
|
5569
5205
|
if (!res.ok) {
|
|
5570
|
-
console.log(
|
|
5571
|
-
console.log(
|
|
5206
|
+
console.log(chalk28.red(` ${ko.cloud.pullFail}: ${name}`));
|
|
5207
|
+
console.log(chalk28.dim(` ${res.err}`));
|
|
5572
5208
|
continue;
|
|
5573
5209
|
}
|
|
5574
|
-
|
|
5210
|
+
fs12.writeFileSync(path13.join(vhkDir, name), ensureTrailingNewline(res.out), "utf-8");
|
|
5575
5211
|
restored++;
|
|
5576
5212
|
}
|
|
5577
5213
|
writeCloudConfig(cwd, { gistId });
|
|
5578
|
-
console.log(
|
|
5579
|
-
console.log(
|
|
5214
|
+
console.log(chalk28.green.bold(` ${ko.cloud.pullDone}`));
|
|
5215
|
+
console.log(chalk28.dim(` ${restored}\uAC1C \uD30C\uC77C \uBCF5\uC6D0 (gist: ${gistId})`));
|
|
5580
5216
|
printNextStep({
|
|
5581
5217
|
message: "\uD074\uB77C\uC6B0\uB4DC\uC5D0\uC11C .vhk/ \uBCF5\uC6D0 \uC644\uB8CC!",
|
|
5582
5218
|
command: "vhk \uB9E5\uB77D",
|
|
@@ -5588,9 +5224,9 @@ function purgeExcludedFromGist(gistId, names) {
|
|
|
5588
5224
|
const body = JSON.stringify({
|
|
5589
5225
|
files: Object.fromEntries(names.map((n) => [n, null]))
|
|
5590
5226
|
});
|
|
5591
|
-
const tmp =
|
|
5227
|
+
const tmp = path13.join(os.tmpdir(), `vhk-gist-purge-${process.pid}.json`);
|
|
5592
5228
|
try {
|
|
5593
|
-
|
|
5229
|
+
fs12.writeFileSync(tmp, body, "utf-8");
|
|
5594
5230
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5595
5231
|
const res = safeExecFile(
|
|
5596
5232
|
"gh",
|
|
@@ -5602,7 +5238,7 @@ function purgeExcludedFromGist(gistId, names) {
|
|
|
5602
5238
|
return false;
|
|
5603
5239
|
} finally {
|
|
5604
5240
|
try {
|
|
5605
|
-
|
|
5241
|
+
fs12.unlinkSync(tmp);
|
|
5606
5242
|
} catch {
|
|
5607
5243
|
}
|
|
5608
5244
|
}
|
|
@@ -5624,7 +5260,7 @@ function printPushNext() {
|
|
|
5624
5260
|
}
|
|
5625
5261
|
|
|
5626
5262
|
// src/commands/help.ts
|
|
5627
|
-
import
|
|
5263
|
+
import chalk29 from "chalk";
|
|
5628
5264
|
var QUICK_ACTIONS = [
|
|
5629
5265
|
{ say: "\uC0C1\uD0DC \uC54C\uB824\uC918", does: "vhk status" },
|
|
5630
5266
|
{ say: "\uBB50 \uBC14\uB00C\uC5C8\uC5B4?", does: "vhk diff" },
|
|
@@ -5638,21 +5274,21 @@ var QUICK_ACTIONS = [
|
|
|
5638
5274
|
{ say: "\uC804\uCCB4 \uBA85\uB839\uC5B4 \uBCF4\uAE30", does: "vhk --help" }
|
|
5639
5275
|
];
|
|
5640
5276
|
function quickActions() {
|
|
5641
|
-
console.log(
|
|
5642
|
-
console.log(
|
|
5277
|
+
console.log(chalk29.bold("\n\u{1F9ED} VHK \u2014 \uC774\uB807\uAC8C \uB9D0\uD558\uBA74 \uB429\uB2C8\uB2E4 (quick actions)"));
|
|
5278
|
+
console.log(chalk29.gray("\u2500".repeat(40)));
|
|
5643
5279
|
for (const a of QUICK_ACTIONS) {
|
|
5644
|
-
console.log(` "${
|
|
5280
|
+
console.log(` "${chalk29.cyan(a.say)}" \u2192 ${chalk29.dim(a.does)}`);
|
|
5645
5281
|
}
|
|
5646
|
-
console.log(
|
|
5282
|
+
console.log(chalk29.gray("\n \uC804\uCCB4 \uBA85\uB839\uC740 `vhk --help` \uB610\uB294 COMMANDS.md \uB97C \uBCF4\uC138\uC694."));
|
|
5647
5283
|
console.log("");
|
|
5648
5284
|
}
|
|
5649
5285
|
|
|
5650
5286
|
// src/commands/mode.ts
|
|
5651
|
-
import
|
|
5287
|
+
import chalk30 from "chalk";
|
|
5652
5288
|
|
|
5653
5289
|
// src/lib/config.ts
|
|
5654
|
-
import { existsSync as
|
|
5655
|
-
import { join as
|
|
5290
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
5291
|
+
import { join as join9 } from "path";
|
|
5656
5292
|
|
|
5657
5293
|
// src/lib/safety-mode.ts
|
|
5658
5294
|
var SAFETY_MODES = ["lite", "standard", "strict"];
|
|
@@ -5668,11 +5304,11 @@ function isSafetyMode(value) {
|
|
|
5668
5304
|
|
|
5669
5305
|
// src/lib/config.ts
|
|
5670
5306
|
var CONFIG_DIR = ".vhk";
|
|
5671
|
-
var CONFIG_PATH =
|
|
5307
|
+
var CONFIG_PATH = join9(CONFIG_DIR, "config.json");
|
|
5672
5308
|
var DEFAULT_CONFIG = { safetyMode: DEFAULT_SAFETY_MODE };
|
|
5673
5309
|
function readConfig(rootDir = process.cwd()) {
|
|
5674
|
-
const full =
|
|
5675
|
-
if (!
|
|
5310
|
+
const full = join9(rootDir, CONFIG_PATH);
|
|
5311
|
+
if (!existsSync15(full)) return { ...DEFAULT_CONFIG };
|
|
5676
5312
|
try {
|
|
5677
5313
|
const raw = readJsonFile(full);
|
|
5678
5314
|
return {
|
|
@@ -5683,23 +5319,23 @@ function readConfig(rootDir = process.cwd()) {
|
|
|
5683
5319
|
}
|
|
5684
5320
|
}
|
|
5685
5321
|
function writeConfig(config, rootDir = process.cwd()) {
|
|
5686
|
-
mkdirSync10(
|
|
5687
|
-
writeFileSync10(
|
|
5322
|
+
mkdirSync10(join9(rootDir, CONFIG_DIR), { recursive: true });
|
|
5323
|
+
writeFileSync10(join9(rootDir, CONFIG_PATH), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
5688
5324
|
}
|
|
5689
5325
|
|
|
5690
5326
|
// src/commands/mode.ts
|
|
5691
5327
|
async function mode(target) {
|
|
5692
|
-
console.log(
|
|
5693
|
-
console.log(
|
|
5328
|
+
console.log(chalk30.bold("\n\u{1F6E1}\uFE0F Safety Mode"));
|
|
5329
|
+
console.log(chalk30.gray("\u2500".repeat(40)));
|
|
5694
5330
|
const current = readConfig().safetyMode;
|
|
5695
5331
|
if (!target) {
|
|
5696
|
-
console.log(
|
|
5697
|
-
\uD604\uC7AC \uBAA8\uB4DC: ${
|
|
5698
|
-
console.log(
|
|
5332
|
+
console.log(chalk30.cyan(`
|
|
5333
|
+
\uD604\uC7AC \uBAA8\uB4DC: ${chalk30.bold(current)}`));
|
|
5334
|
+
console.log(chalk30.dim(` ${SAFETY_MODE_DESC[current]}`));
|
|
5699
5335
|
console.log("");
|
|
5700
5336
|
for (const m of SAFETY_MODES) {
|
|
5701
5337
|
const mark = m === current ? "\u25CF" : "\u25CB";
|
|
5702
|
-
console.log(` ${mark} ${m.padEnd(9)} ${
|
|
5338
|
+
console.log(` ${mark} ${m.padEnd(9)} ${chalk30.dim(SAFETY_MODE_DESC[m])}`);
|
|
5703
5339
|
}
|
|
5704
5340
|
printNextStep({
|
|
5705
5341
|
message: "\uBAA8\uB4DC\uB97C \uBC14\uAFB8\uB824\uBA74:",
|
|
@@ -5709,20 +5345,20 @@ async function mode(target) {
|
|
|
5709
5345
|
return;
|
|
5710
5346
|
}
|
|
5711
5347
|
if (!isSafetyMode(target)) {
|
|
5712
|
-
console.log(
|
|
5348
|
+
console.log(chalk30.red(`
|
|
5713
5349
|
\u274C \uC54C \uC218 \uC5C6\uB294 \uBAA8\uB4DC: ${target}`));
|
|
5714
|
-
console.log(
|
|
5350
|
+
console.log(chalk30.dim(` \uAC00\uB2A5: ${SAFETY_MODES.join(" | ")}`));
|
|
5715
5351
|
process.exitCode = 1;
|
|
5716
5352
|
return;
|
|
5717
5353
|
}
|
|
5718
5354
|
writeConfig({ ...readConfig(), safetyMode: target });
|
|
5719
|
-
console.log(
|
|
5720
|
-
\u2705 Safety Mode \u2192 ${
|
|
5721
|
-
console.log(
|
|
5355
|
+
console.log(chalk30.green(`
|
|
5356
|
+
\u2705 Safety Mode \u2192 ${chalk30.bold(target)}`));
|
|
5357
|
+
console.log(chalk30.dim(` ${SAFETY_MODE_DESC[target]}`));
|
|
5722
5358
|
}
|
|
5723
5359
|
|
|
5724
5360
|
// src/commands/verify.ts
|
|
5725
|
-
import
|
|
5361
|
+
import chalk31 from "chalk";
|
|
5726
5362
|
function verificationChecklist() {
|
|
5727
5363
|
return [
|
|
5728
5364
|
"\uD0C0\uC785 \uCCB4\uD06C \u2014 pnpm exec tsc --noEmit",
|
|
@@ -5732,15 +5368,15 @@ function verificationChecklist() {
|
|
|
5732
5368
|
];
|
|
5733
5369
|
}
|
|
5734
5370
|
async function verify() {
|
|
5735
|
-
console.log(
|
|
5736
|
-
console.log(
|
|
5371
|
+
console.log(chalk31.bold("\n\u{1F50E} \uAC80\uC99D \uBB36\uC74C (verify \u2014 lite)"));
|
|
5372
|
+
console.log(chalk31.gray("\u2500".repeat(40)));
|
|
5737
5373
|
const mode2 = readConfig().safetyMode;
|
|
5738
|
-
console.log(
|
|
5739
|
-
console.log(
|
|
5374
|
+
console.log(chalk31.dim(` \uD604\uC7AC Safety Mode: ${mode2} \u2014 ${SAFETY_MODE_DESC[mode2]}`));
|
|
5375
|
+
console.log(chalk31.cyan("\n \uC704\uD5D8 \uC791\uC5C5/\uC800\uC7A5 \uC804 \uAD8C\uC7A5 \uAC80\uC99D:"));
|
|
5740
5376
|
for (const item of verificationChecklist()) {
|
|
5741
5377
|
console.log(` \u2610 ${item}`);
|
|
5742
5378
|
}
|
|
5743
|
-
console.log(
|
|
5379
|
+
console.log(chalk31.dim("\n \u203B \uBA54\uD0C0\uB7EC\uB108(\uC790\uB3D9 \uC2E4\uD589) \uC790\uB9AC \u2014 \uD604\uC7AC\uB294 \uBB36\uC74C \uC548\uB0B4\uB9CC(lite)."));
|
|
5744
5380
|
printNextStep({
|
|
5745
5381
|
message: "\uAC80\uC99D \uD1B5\uACFC \uD6C4 \uC800\uC7A5\uD558\uC138\uC694:",
|
|
5746
5382
|
command: "vhk save",
|
|
@@ -5894,6 +5530,10 @@ async function dispatchNlpRoute(route, input) {
|
|
|
5894
5530
|
if (sub === "next") return goalNext();
|
|
5895
5531
|
if (sub === "check") return goalCheck({});
|
|
5896
5532
|
if (sub === "done") return goalDone({});
|
|
5533
|
+
if (sub === "sync") {
|
|
5534
|
+
await goalSync();
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5897
5537
|
return goalList();
|
|
5898
5538
|
}
|
|
5899
5539
|
case "help":
|
|
@@ -5909,28 +5549,29 @@ var STATE_CHANGING_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
5909
5549
|
"init"
|
|
5910
5550
|
]);
|
|
5911
5551
|
function requiresConfirmation(route) {
|
|
5912
|
-
|
|
5552
|
+
const goalSync2 = route.command === "goal" && route.args?.[0] === "sync";
|
|
5553
|
+
return route.confidence === "low" || STATE_CHANGING_COMMANDS.has(route.command) || goalSync2;
|
|
5913
5554
|
}
|
|
5914
5555
|
async function runNaturalLanguageRoute(input) {
|
|
5915
5556
|
const route = routeNaturalLanguage(input);
|
|
5916
5557
|
if (!route) {
|
|
5917
|
-
console.log(
|
|
5558
|
+
console.log(chalk32.yellow(`
|
|
5918
5559
|
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
5919
5560
|
`));
|
|
5920
5561
|
return;
|
|
5921
5562
|
}
|
|
5922
5563
|
console.log("");
|
|
5923
|
-
console.log(
|
|
5924
|
-
console.log(
|
|
5564
|
+
console.log(chalk32.cyan(` \u{1F4AC} "${input}"`));
|
|
5565
|
+
console.log(chalk32.cyan(` \u2192 ${route.explanation}`));
|
|
5925
5566
|
if (requiresConfirmation(route)) {
|
|
5926
|
-
const { confirm } = await
|
|
5567
|
+
const { confirm } = await inquirer12.prompt([{
|
|
5927
5568
|
type: "confirm",
|
|
5928
5569
|
name: "confirm",
|
|
5929
5570
|
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
5930
5571
|
default: true
|
|
5931
5572
|
}]);
|
|
5932
5573
|
if (!confirm) {
|
|
5933
|
-
console.log(
|
|
5574
|
+
console.log(chalk32.dim(` ${ko.nlp.menuHint}`));
|
|
5934
5575
|
return;
|
|
5935
5576
|
}
|
|
5936
5577
|
}
|
|
@@ -5939,7 +5580,7 @@ async function runNaturalLanguageRoute(input) {
|
|
|
5939
5580
|
if (riskAction) {
|
|
5940
5581
|
await runGuarded(
|
|
5941
5582
|
riskAction,
|
|
5942
|
-
{ channel: "nl", approved: false, log: (m) => console.log(
|
|
5583
|
+
{ channel: "nl", approved: false, log: (m) => console.log(chalk32.yellow(` ${m}`)) },
|
|
5943
5584
|
() => dispatchNlpRoute(route, input)
|
|
5944
5585
|
);
|
|
5945
5586
|
return;
|
|
@@ -5948,77 +5589,77 @@ async function runNaturalLanguageRoute(input) {
|
|
|
5948
5589
|
}
|
|
5949
5590
|
|
|
5950
5591
|
// src/commands/agent.ts
|
|
5951
|
-
import
|
|
5592
|
+
import chalk33 from "chalk";
|
|
5952
5593
|
function activeGoalId() {
|
|
5953
5594
|
const goals = listGoals("goals");
|
|
5954
5595
|
const id = selectActiveId(goals);
|
|
5955
5596
|
return id ?? void 0;
|
|
5956
5597
|
}
|
|
5957
5598
|
async function blocker(description) {
|
|
5958
|
-
console.log(
|
|
5599
|
+
console.log(chalk33.bold(`
|
|
5959
5600
|
${ko.agent.blockerTitle}
|
|
5960
5601
|
`));
|
|
5961
5602
|
if (!description || !description.trim()) {
|
|
5962
|
-
console.log(
|
|
5963
|
-
console.log(
|
|
5603
|
+
console.log(chalk33.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
|
|
5604
|
+
console.log(chalk33.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
|
|
5964
5605
|
process.exitCode = 1;
|
|
5965
5606
|
return;
|
|
5966
5607
|
}
|
|
5967
5608
|
const goalId = activeGoalId();
|
|
5968
5609
|
const r = appendBlocker(description, goalId);
|
|
5969
|
-
console.log(
|
|
5610
|
+
console.log(chalk33.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
|
|
5970
5611
|
if (r.hardStopTripped) {
|
|
5971
|
-
console.log(
|
|
5972
|
-
console.log(
|
|
5612
|
+
console.log(chalk33.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
|
|
5613
|
+
console.log(chalk33.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
|
|
5973
5614
|
process.exitCode = 2;
|
|
5974
5615
|
}
|
|
5975
5616
|
}
|
|
5976
5617
|
async function learn(lesson) {
|
|
5977
|
-
console.log(
|
|
5618
|
+
console.log(chalk33.bold(`
|
|
5978
5619
|
${ko.agent.learnTitle}
|
|
5979
5620
|
`));
|
|
5980
5621
|
if (!lesson || !lesson.trim()) {
|
|
5981
|
-
console.log(
|
|
5982
|
-
console.log(
|
|
5622
|
+
console.log(chalk33.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
|
|
5623
|
+
console.log(chalk33.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
|
|
5983
5624
|
process.exitCode = 1;
|
|
5984
5625
|
return;
|
|
5985
5626
|
}
|
|
5986
5627
|
const goalId = activeGoalId();
|
|
5987
5628
|
appendLearning(lesson, goalId);
|
|
5988
|
-
console.log(
|
|
5629
|
+
console.log(chalk33.green(" \u2705 learnings.md append."));
|
|
5989
5630
|
console.log(
|
|
5990
|
-
|
|
5631
|
+
chalk33.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
|
|
5991
5632
|
);
|
|
5992
5633
|
}
|
|
5993
5634
|
async function resume(opts = {}) {
|
|
5994
|
-
console.log(
|
|
5635
|
+
console.log(chalk33.bold(`
|
|
5995
5636
|
${ko.agent.resumeTitle}
|
|
5996
5637
|
`));
|
|
5997
5638
|
if (!isHardStopActive()) {
|
|
5998
|
-
console.log(
|
|
5639
|
+
console.log(chalk33.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
|
|
5999
5640
|
return;
|
|
6000
5641
|
}
|
|
6001
5642
|
const reason = readHardStopReason();
|
|
6002
5643
|
if (reason) {
|
|
6003
|
-
console.log(
|
|
6004
|
-
console.log(
|
|
5644
|
+
console.log(chalk33.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
|
|
5645
|
+
console.log(chalk33.dim(` ${reason.split("\n").join("\n ")}`));
|
|
6005
5646
|
console.log("");
|
|
6006
5647
|
}
|
|
6007
5648
|
if (!opts.confirm) {
|
|
6008
5649
|
console.log(
|
|
6009
|
-
|
|
5650
|
+
chalk33.red(
|
|
6010
5651
|
" \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
|
|
6011
5652
|
)
|
|
6012
5653
|
);
|
|
6013
|
-
console.log(
|
|
5654
|
+
console.log(chalk33.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
|
|
6014
5655
|
process.exitCode = 1;
|
|
6015
5656
|
return;
|
|
6016
5657
|
}
|
|
6017
5658
|
const removed = clearHardStop();
|
|
6018
5659
|
if (removed) {
|
|
6019
|
-
console.log(
|
|
5660
|
+
console.log(chalk33.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
|
|
6020
5661
|
} else {
|
|
6021
|
-
console.log(
|
|
5662
|
+
console.log(chalk33.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
|
|
6022
5663
|
}
|
|
6023
5664
|
}
|
|
6024
5665
|
|
|
@@ -6031,7 +5672,7 @@ async function guardCli(action, approved, run) {
|
|
|
6031
5672
|
channel: "cli",
|
|
6032
5673
|
approved,
|
|
6033
5674
|
confirm: async () => {
|
|
6034
|
-
const { ok } = await
|
|
5675
|
+
const { ok } = await inquirer13.prompt([{
|
|
6035
5676
|
type: "confirm",
|
|
6036
5677
|
name: "ok",
|
|
6037
5678
|
message: `\u26A0\uFE0F \uC704\uD5D8 \uC791\uC5C5(${action})\uC744 \uC2E4\uD589\uD560\uAE4C\uC694?`,
|
|
@@ -6039,7 +5680,7 @@ async function guardCli(action, approved, run) {
|
|
|
6039
5680
|
}]);
|
|
6040
5681
|
return ok;
|
|
6041
5682
|
},
|
|
6042
|
-
log: (m) => console.log(
|
|
5683
|
+
log: (m) => console.log(chalk34.yellow(` ${m}`))
|
|
6043
5684
|
},
|
|
6044
5685
|
run
|
|
6045
5686
|
);
|
|
@@ -6052,7 +5693,7 @@ async function guardCliDefer(action, approved, run) {
|
|
|
6052
5693
|
approved,
|
|
6053
5694
|
// TTY 면 통과(명령이 자체 확인), 비대화형은 confirm 불가 → 가드가 차단.
|
|
6054
5695
|
confirm: async () => !!process.stdout.isTTY,
|
|
6055
|
-
log: (m) => console.log(
|
|
5696
|
+
log: (m) => console.log(chalk34.yellow(` ${m}`))
|
|
6056
5697
|
},
|
|
6057
5698
|
run
|
|
6058
5699
|
);
|
|
@@ -6234,7 +5875,7 @@ memoryCmd.command("remove <index>").alias("\uC0AD\uC81C").description("\uAE30\uC
|
|
|
6234
5875
|
program.command("brief").alias("\uBE0C\uB9AC\uD551").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC694\uC57D \uBCF4\uACE0\uC11C \uC0DD\uC131 (.vhk/brief.md)").action(async () => {
|
|
6235
5876
|
await brief();
|
|
6236
5877
|
});
|
|
6237
|
-
var goalCmd = program.command("goal").alias("\uBAA9\uD45C").description("Goal \uB2E8\uACC4\uBCC4 \uBBF8\uC158 \uAD00\uB9AC (init / list / next / check / done)").action(async () => {
|
|
5878
|
+
var goalCmd = program.command("goal").alias("\uBAA9\uD45C").description("Goal \uB2E8\uACC4\uBCC4 \uBBF8\uC158 \uAD00\uB9AC (init / list / next / check / done / sync)").action(async () => {
|
|
6238
5879
|
await goalList();
|
|
6239
5880
|
});
|
|
6240
5881
|
goalCmd.command("list").alias("\uBAA9\uB85D").description("goals/*.md \uBAA9\uB85D (id, status, priority, title)").action(async () => {
|
|
@@ -6246,12 +5887,15 @@ goalCmd.command("next").alias("\uB2E4\uC74C").description("active goal \uC790\uB
|
|
|
6246
5887
|
goalCmd.command("init").alias("\uCD08\uAE30\uD654").description("\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uC5D0 goals/ + docs/state/ \uC2A4\uCE90\uD3F4\uB529 (\uAE30\uC874 \uD30C\uC77C \uBCF4\uC874)").action(async () => {
|
|
6247
5888
|
await goalInit();
|
|
6248
5889
|
});
|
|
6249
|
-
goalCmd.command("check").alias("\uAC80\uC99D").option("--id <id>", "goal id \uC9C0\uC815 (\uC0DD\uB7B5 \uC2DC active goal)").description("scripts/check-goal-<id>.sh \uC2E4\uD589 + exit code \uC804\uB2EC").action(async (opts) => {
|
|
5890
|
+
goalCmd.command("check").alias("\uAC80\uC99D").option("--id <id>", "goal id \uC9C0\uC815 (\uC0DD\uB7B5 \uC2DC active goal)").description("scripts/check-goal-<id>.{mjs,sh} \uC2E4\uD589 + exit code \uC804\uB2EC (.mjs \uC6B0\uC120)").action(async (opts) => {
|
|
6250
5891
|
await goalCheck(opts);
|
|
6251
5892
|
});
|
|
6252
5893
|
goalCmd.command("done").alias("\uC644\uB8CC").option("--id <id>", "goal id \uC9C0\uC815 (\uC0DD\uB7B5 \uC2DC active goal)").description("\uAC8C\uC774\uD2B8 \uC7AC\uAC80\uC99D \u2192 \uD1B5\uACFC \uC2DC frontmatter status=DONE \uC73C\uB85C \uC804\uC774").action(async (opts) => {
|
|
6253
5894
|
await goalDone(opts);
|
|
6254
5895
|
});
|
|
5896
|
+
goalCmd.command("sync").alias("\uB3D9\uAE30\uD654").description("goals/*.md \uC2A4\uCE94 \u2192 \uB204\uB77D\uB41C check-goal-<id>.mjs \uAC8C\uC774\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8 \uBC31\uD544 (idempotent)").action(async () => {
|
|
5897
|
+
await goalSync();
|
|
5898
|
+
});
|
|
6255
5899
|
program.command("blocker <description>").alias("\uBE14\uB85C\uCEE4").description("\uBE14\uB85C\uCEE4 \uAE30\uB85D \u2192 docs/state/blockers.md append (3\uAC74 \uB204\uC801 \uC2DC HARD_STOP \uC790\uB3D9 \uC0DD\uC131)").action(async (description) => {
|
|
6256
5900
|
await blocker(description);
|
|
6257
5901
|
});
|
|
@@ -6269,7 +5913,7 @@ program.on("command:*", async (operands) => {
|
|
|
6269
5913
|
});
|
|
6270
5914
|
program.action(async () => {
|
|
6271
5915
|
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
|
|
6272
|
-
const { choice } = await
|
|
5916
|
+
const { choice } = await inquirer13.prompt([{
|
|
6273
5917
|
type: "list",
|
|
6274
5918
|
name: "choice",
|
|
6275
5919
|
message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
|
@@ -6326,9 +5970,9 @@ if (isMainModule) {
|
|
|
6326
5970
|
}
|
|
6327
5971
|
} catch (err) {
|
|
6328
5972
|
if (isPromptAbortError(err)) {
|
|
6329
|
-
console.error(
|
|
5973
|
+
console.error(chalk34.yellow("\n \u26A0\uFE0F \uB300\uD654\uD615 \uC785\uB825\uC774 \uCDE8\uC18C/\uC885\uB8CC\uB410\uC2B5\uB2C8\uB2E4. (\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C\uB294 \uD574\uB2F9 \uBA85\uB839\uC744 \uC4F8 \uC218 \uC5C6\uC5B4\uC694)"));
|
|
6330
5974
|
} else {
|
|
6331
|
-
console.error(
|
|
5975
|
+
console.error(chalk34.red(`
|
|
6332
5976
|
\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
6333
5977
|
}
|
|
6334
5978
|
process.exitCode = 1;
|