@byh3071/vhk 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -9
- package/dist/index.js +770 -482
- package/package.json +56 -56
package/dist/index.js
CHANGED
|
@@ -485,8 +485,7 @@ var require_ignore = __commonJS({
|
|
|
485
485
|
|
|
486
486
|
// src/index.ts
|
|
487
487
|
import { Command, Help } from "commander";
|
|
488
|
-
import
|
|
489
|
-
import inquirer7 from "inquirer";
|
|
488
|
+
import inquirer8 from "inquirer";
|
|
490
489
|
|
|
491
490
|
// src/lib/nlp-router.ts
|
|
492
491
|
function normalize(input) {
|
|
@@ -495,8 +494,8 @@ function normalize(input) {
|
|
|
495
494
|
var NLP_KEYWORDS = {
|
|
496
495
|
save: ["\uC800\uC7A5", "\uC138\uC774\uBE0C", "\uCEE4\uBC0B", "\uC62C\uB824", "\uC62C\uB9AC\uAE30", "\uD478\uC2DC", "push", "commit"],
|
|
497
496
|
undo: ["\uB418\uB3CC\uB824", "\uB418\uB3CC\uB9AC\uAE30", "\uCDE8\uC18C", "\uC6D0\uB798\uB300\uB85C", "\uB864\uBC31", "\uB9AC\uC14B", "reset", "rollback"],
|
|
498
|
-
status: ["\uC0C1\uD0DC", "\uD604\uD669", "\uC5B4\uB5BB\uAC8C", "\uC5B4\uB54C", "\uC9C0\uAE08"
|
|
499
|
-
diff: ["\uBCC0\uACBD", "\uBC14\uB010", "\uBB50\uBC14\uB01C", "\uCC28\uC774", "\uB2EC\uB77C\uC9C4", "\uC218\uC815\uB41C"]
|
|
497
|
+
status: ["\uC0C1\uD0DC", "\uD604\uD669", "\uC5B4\uB5BB\uAC8C", "\uC5B4\uB54C", "\uC9C0\uAE08"],
|
|
498
|
+
diff: ["\uBCC0\uACBD", "\uBC14\uB010", "\uBB50\uBC14\uB01C", "\uBC14\uB00C\uC5C8", "\uCC28\uC774", "\uB2EC\uB77C\uC9C4", "\uC218\uC815\uB41C"]
|
|
500
499
|
};
|
|
501
500
|
function matchesKeywords(text, command) {
|
|
502
501
|
const keywords = NLP_KEYWORDS[command];
|
|
@@ -524,11 +523,29 @@ var RULES = [
|
|
|
524
523
|
confidence: "high",
|
|
525
524
|
test: (t2) => /프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|하네스|초기화/.test(t2) || /^시작$/.test(t2)
|
|
526
525
|
},
|
|
526
|
+
{
|
|
527
|
+
command: "secure",
|
|
528
|
+
explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548)",
|
|
529
|
+
confidence: "high",
|
|
530
|
+
test: (t2) => /보안|시크릿|비밀|키\s*유출|secure|scan/.test(t2)
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
command: "check",
|
|
534
|
+
explanation: "\uADDC\uCE59 \uC810\uAC80 (vhk \uC810\uAC80)",
|
|
535
|
+
confidence: "high",
|
|
536
|
+
test: (t2) => /규칙.*(점검|위반)|린트|check|위반/.test(t2)
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
command: "doctor",
|
|
540
|
+
explanation: "\uD658\uACBD \uC810\uAC80 (vhk doctor)",
|
|
541
|
+
confidence: "high",
|
|
542
|
+
test: (t2) => /뭔가\s*안|안\s*돼|안돼|환경\s*(점검|진단|확인)|진단|doctor|설치.*확인|왜\s*안/.test(t2)
|
|
543
|
+
},
|
|
527
544
|
{
|
|
528
545
|
command: "diff",
|
|
529
546
|
explanation: "\uBCC0\uACBD\uC0AC\uD56D \uC694\uC57D (vhk diff)",
|
|
530
547
|
confidence: "high",
|
|
531
|
-
test: (t2) => (matchesKeywords(t2, "diff") || /^diff$/.test(t2) || /변경사항|수정\s*내역|차이\s
|
|
548
|
+
test: (t2) => (matchesKeywords(t2, "diff") || /^diff$/.test(t2) || /변경사항|수정\s*내역|차이\s*보|뭐\s*바뀌/.test(t2)) && !/저장|커밋|push|푸시|상태|현황|세이브|commit/.test(t2)
|
|
532
549
|
},
|
|
533
550
|
{
|
|
534
551
|
command: "undo",
|
|
@@ -540,7 +557,7 @@ var RULES = [
|
|
|
540
557
|
command: "status",
|
|
541
558
|
explanation: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uD655\uC778 (vhk \uC0C1\uD0DC)",
|
|
542
559
|
confidence: "high",
|
|
543
|
-
test: (t2) => matchesKeywords(t2, "status") || /^status$/.test(t2) || /브랜치.*(뭐|어디)|git\s*상태|동기화\s*상태/.test(t2)
|
|
560
|
+
test: (t2) => (matchesKeywords(t2, "status") || /^status$/.test(t2) || /브랜치.*(뭐|어디)|git\s*상태|동기화\s*상태|프로젝트\s*상태/.test(t2)) && !/보안|시크릿|규칙|점검|린트|환경|진단|doctor|secure|check|스캔|설치/.test(t2)
|
|
544
561
|
},
|
|
545
562
|
{
|
|
546
563
|
command: "save",
|
|
@@ -554,30 +571,12 @@ var RULES = [
|
|
|
554
571
|
confidence: "high",
|
|
555
572
|
test: (t2) => /오늘.*(정리|기록)|한\s*일|세션|회고|recap|정리해/.test(t2)
|
|
556
573
|
},
|
|
557
|
-
{
|
|
558
|
-
command: "doctor",
|
|
559
|
-
explanation: "\uD658\uACBD \uC810\uAC80 (vhk doctor)",
|
|
560
|
-
confidence: "high",
|
|
561
|
-
test: (t2) => /뭔가\s*안|안\s*돼|안돼|환경\s*(점검|진단)|진단|doctor|설치.*확인|왜\s*안/.test(t2)
|
|
562
|
-
},
|
|
563
574
|
{
|
|
564
575
|
command: "gate",
|
|
565
576
|
explanation: "\uC544\uC774\uB514\uC5B4 \uAC80\uC99D (vhk \uAC80\uC99D)",
|
|
566
577
|
confidence: "high",
|
|
567
578
|
test: (t2) => /아이디어|검증|gate|go\/refine|pain\s*point/.test(t2)
|
|
568
579
|
},
|
|
569
|
-
{
|
|
570
|
-
command: "secure",
|
|
571
|
-
explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548 scan)",
|
|
572
|
-
confidence: "high",
|
|
573
|
-
test: (t2) => /보안|시크릿|비밀|키\s*유출|secure|scan/.test(t2)
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
command: "check",
|
|
577
|
-
explanation: "\uADDC\uCE59 \uC810\uAC80 (vhk \uC810\uAC80)",
|
|
578
|
-
confidence: "high",
|
|
579
|
-
test: (t2) => /규칙.*(점검|위반)|린트|check|위반/.test(t2)
|
|
580
|
-
},
|
|
581
580
|
{
|
|
582
581
|
command: "sync",
|
|
583
582
|
explanation: "\uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654 (vhk \uADDC\uCE59)",
|
|
@@ -611,6 +610,69 @@ function extractNotionUrl(input) {
|
|
|
611
610
|
return m?.[0];
|
|
612
611
|
}
|
|
613
612
|
|
|
613
|
+
// src/lib/cli-args.ts
|
|
614
|
+
var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
615
|
+
"gate",
|
|
616
|
+
"\uAC80\uC99D",
|
|
617
|
+
"\uC544\uC774\uB514\uC5B4",
|
|
618
|
+
"init",
|
|
619
|
+
"\uC2DC\uC791",
|
|
620
|
+
"\uB9CC\uB4E4\uAE30",
|
|
621
|
+
"recap",
|
|
622
|
+
"\uC815\uB9AC",
|
|
623
|
+
"\uC624\uB298",
|
|
624
|
+
"sync",
|
|
625
|
+
"\uB9DE\uCD94\uAE30",
|
|
626
|
+
"\uADDC\uCE59",
|
|
627
|
+
"check",
|
|
628
|
+
"\uC810\uAC80",
|
|
629
|
+
"\uB9B0\uD2B8",
|
|
630
|
+
"secure",
|
|
631
|
+
"\uBCF4\uC548",
|
|
632
|
+
"scan",
|
|
633
|
+
"\uC2A4\uCE94",
|
|
634
|
+
"ship",
|
|
635
|
+
"\uBC30\uD3EC",
|
|
636
|
+
"\uB9B4\uB9AC\uC988",
|
|
637
|
+
"doctor",
|
|
638
|
+
"\uD658\uACBD",
|
|
639
|
+
"\uC9C4\uB2E8",
|
|
640
|
+
"save",
|
|
641
|
+
"\uC800\uC7A5",
|
|
642
|
+
"undo",
|
|
643
|
+
"\uB418\uB3CC\uB9AC\uAE30",
|
|
644
|
+
"status",
|
|
645
|
+
"\uC0C1\uD0DC",
|
|
646
|
+
"\uD604\uD669",
|
|
647
|
+
"diff",
|
|
648
|
+
"\uBCC0\uACBD",
|
|
649
|
+
"\uCC28\uC774",
|
|
650
|
+
"help"
|
|
651
|
+
]);
|
|
652
|
+
function isOptionToken(token) {
|
|
653
|
+
return token.startsWith("-");
|
|
654
|
+
}
|
|
655
|
+
function detectNaturalLanguageInput(argv) {
|
|
656
|
+
const rest = argv.slice(2);
|
|
657
|
+
if (rest.length === 0) return null;
|
|
658
|
+
const first = rest[0];
|
|
659
|
+
if (isOptionToken(first)) return null;
|
|
660
|
+
const input = rest.join(" ").trim();
|
|
661
|
+
if (!input) return null;
|
|
662
|
+
const firstIsKnown = KNOWN_COMMAND_TOKENS.has(first);
|
|
663
|
+
if (firstIsKnown && rest.length === 1) return null;
|
|
664
|
+
if (firstIsKnown && rest.slice(1).every(isOptionToken)) return null;
|
|
665
|
+
if (firstIsKnown && rest.length > 1) {
|
|
666
|
+
if (routeNaturalLanguage(input)) return input;
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
return input;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/lib/nlp-run.ts
|
|
673
|
+
import chalk16 from "chalk";
|
|
674
|
+
import inquirer7 from "inquirer";
|
|
675
|
+
|
|
614
676
|
// src/i18n/ko.ts
|
|
615
677
|
var ko = {
|
|
616
678
|
status: {
|
|
@@ -642,7 +704,15 @@ var ko = {
|
|
|
642
704
|
successLocal: "\uB85C\uCEEC \uC800\uC7A5 \uC644\uB8CC!",
|
|
643
705
|
noRemote: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 push\uB97C \uAC74\uB108\uB6F0\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
644
706
|
failed: "\uC800\uC7A5 \uC2E4\uD328",
|
|
645
|
-
|
|
707
|
+
stagedAfterFail: "\uCEE4\uBC0B\uC740 \uC2E4\uD328\uD588\uC9C0\uB9CC \uD30C\uC77C\uC740 \uC2A4\uD14C\uC774\uC9D5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uD655\uC778: git status / \uCDE8\uC18C: git reset HEAD",
|
|
708
|
+
securityWarnHeader: "\uC800\uC7A5 \uC804 \uBCF4\uC548 \uD655\uC778:",
|
|
709
|
+
secretsFound: (n) => `\uCF54\uB4DC\uC5D0\uC11C CRITICAL/HIGH \uC2DC\uD06C\uB9BF \uD328\uD134 ${n}\uAC74 \uAC10\uC9C0`,
|
|
710
|
+
secretsConfirm: "\uADF8\uB798\uB3C4 \uCEE4\uBC0B\xB7push\uB97C \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
711
|
+
cancelled: "\uC800\uC7A5\uC744 \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.",
|
|
712
|
+
pushFailed: "push \uC2E4\uD328 (\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uC644\uB8CC\uB428)",
|
|
713
|
+
commitOkPushFailed: "\uB85C\uCEEC \uCEE4\uBC0B\uC740 \uB410\uC9C0\uB9CC \uC6D0\uACA9 push\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. git push\uB97C \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.",
|
|
714
|
+
done: (n) => `${n}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC!`,
|
|
715
|
+
doneLocalOnly: (n) => `${n}\uAC1C \uD30C\uC77C \uB85C\uCEEC \uC800\uC7A5\uB428 (push\uB294 \uC2E4\uD328)`
|
|
646
716
|
},
|
|
647
717
|
undo: {
|
|
648
718
|
title: "\uB418\uB3CC\uB9AC\uAE30",
|
|
@@ -651,10 +721,14 @@ var ko = {
|
|
|
651
721
|
recentHeader: "\u{1F4CB} \uCD5C\uADFC \uCEE4\uBC0B:",
|
|
652
722
|
howMany: "\uBA87 \uAC1C\uC758 \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9B4\uAE4C\uC694?",
|
|
653
723
|
alreadyPushed: "\uC774 \uCEE4\uBC0B\uC740 \uC774\uBBF8 \uC6D0\uACA9\uC5D0 \uC62C\uB77C\uAC14\uC2B5\uB2C8\uB2E4. \uB418\uB3CC\uB9AC\uBA74 \uCDA9\uB3CC\uC774 \uC0DD\uAE38 \uC218 \uC788\uC5B4\uC694.",
|
|
724
|
+
noUpstreamWarning: "upstream \uBE0C\uB79C\uCE58\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 push\uD55C \uCEE4\uBC0B\uC77C \uC218 \uC788\uC5B4\uC694. \uB418\uB3CC\uB9B0 \uB4A4 force push\uAC00 \uD544\uC694\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
654
725
|
confirmMessage: "\uCD5C\uADFC \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9AC\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
726
|
+
confirmRisky: (n) => `\u26A0\uFE0F \uC704\uD5D8: \uCD5C\uADFC ${n}\uAC1C \uCEE4\uBC0B\uC744 soft reset\uD569\uB2C8\uB2E4. \uC6D0\uACA9\uACFC \uC5B4\uAE0B\uB0A0 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uACC4\uC18D\uD560\uAE4C\uC694?`,
|
|
655
727
|
cancelled: "\uCDE8\uC18C\uB428",
|
|
656
728
|
success: "\uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC! \uBCC0\uACBD\uC0AC\uD56D\uC740 \uADF8\uB300\uB85C \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.",
|
|
657
729
|
stagedHint: "\uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544 \uC788\uC5B4\uC694.",
|
|
730
|
+
rootCommit: "\uCCAB \uCEE4\uBC0B\uB9CC \uC788\uC5B4\uC11C \uB354 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
731
|
+
forcePushHint: "\uC6D0\uACA9\uACFC \uB9DE\uCD94\uB824\uBA74: git push --force-with-lease (\uD63C\uC790 \uC791\uC5C5\uD55C \uBE0C\uB79C\uCE58\uC5D0\uC11C\uB9CC, \uD300\uACFC \uD569\uC758 \uD6C4)",
|
|
658
732
|
failed: "\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328"
|
|
659
733
|
},
|
|
660
734
|
diff: {
|
|
@@ -1016,9 +1090,9 @@ ${ko.gate.verdictTitle}
|
|
|
1016
1090
|
|
|
1017
1091
|
// src/commands/init.ts
|
|
1018
1092
|
import inquirer2 from "inquirer";
|
|
1019
|
-
import
|
|
1020
|
-
import
|
|
1021
|
-
import
|
|
1093
|
+
import chalk5 from "chalk";
|
|
1094
|
+
import fs3 from "fs";
|
|
1095
|
+
import path3 from "path";
|
|
1022
1096
|
|
|
1023
1097
|
// src/templates/claude-md.ts
|
|
1024
1098
|
function CLAUDE_MD_TEMPLATE(name, _stack) {
|
|
@@ -1228,27 +1302,113 @@ function COMMANDS_MD_TEMPLATE() {
|
|
|
1228
1302
|
].join("\n");
|
|
1229
1303
|
}
|
|
1230
1304
|
|
|
1231
|
-
// src/
|
|
1305
|
+
// src/lib/check-secure.ts
|
|
1306
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
1307
|
+
import fs from "fs";
|
|
1308
|
+
import path from "path";
|
|
1232
1309
|
import chalk3 from "chalk";
|
|
1310
|
+
function loadGitignore(rootDir) {
|
|
1311
|
+
const ig = (0, import_ignore.default)();
|
|
1312
|
+
const gitignorePath = path.join(rootDir, ".gitignore");
|
|
1313
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1314
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
1315
|
+
ig.add(content);
|
|
1316
|
+
}
|
|
1317
|
+
return ig;
|
|
1318
|
+
}
|
|
1319
|
+
function isPathIgnored(ig, relativePath) {
|
|
1320
|
+
const normalized = relativePath.replace(/\\/g, "/");
|
|
1321
|
+
return ig.ignores(normalized);
|
|
1322
|
+
}
|
|
1323
|
+
function findExposedSensitiveFiles(rootDir, ig = loadGitignore(rootDir), maxDepth = 8) {
|
|
1324
|
+
const exposed = [];
|
|
1325
|
+
function walk(dir, depth) {
|
|
1326
|
+
if (depth > maxDepth) return;
|
|
1327
|
+
let entries;
|
|
1328
|
+
try {
|
|
1329
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1330
|
+
} catch {
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
for (const entry of entries) {
|
|
1334
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
1335
|
+
const fullPath = path.join(dir, entry.name);
|
|
1336
|
+
const rel = path.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
1337
|
+
if (entry.isDirectory()) {
|
|
1338
|
+
if (!isPathIgnored(ig, rel + "/")) walk(fullPath, depth + 1);
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
if (isSensitiveName(entry.name) && !isPathIgnored(ig, rel)) {
|
|
1342
|
+
exposed.push(rel);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
walk(rootDir, 0);
|
|
1347
|
+
return exposed;
|
|
1348
|
+
}
|
|
1349
|
+
function isSensitiveName(name) {
|
|
1350
|
+
const lower = name.toLowerCase();
|
|
1351
|
+
if (lower === ".env" || lower.startsWith(".env.")) return true;
|
|
1352
|
+
if (lower.endsWith(".pem") || lower.endsWith(".key")) return true;
|
|
1353
|
+
if (lower === "credentials.json" || lower === "secrets.json") return true;
|
|
1354
|
+
if (lower.startsWith("id_rsa")) return true;
|
|
1355
|
+
return false;
|
|
1356
|
+
}
|
|
1357
|
+
function checkProjectSecurity(rootDir = process.cwd()) {
|
|
1358
|
+
const gitignorePath = path.join(rootDir, ".gitignore");
|
|
1359
|
+
const missingGitignore = !fs.existsSync(gitignorePath);
|
|
1360
|
+
const ig = loadGitignore(rootDir);
|
|
1361
|
+
const exposedPaths = findExposedSensitiveFiles(rootDir, ig);
|
|
1362
|
+
const warnings = [];
|
|
1363
|
+
if (missingGitignore) {
|
|
1364
|
+
warnings.push(".gitignore \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBBFC\uAC10\uD55C \uD30C\uC77C\uC774 \uC2E4\uC218\uB85C \uC62C\uB77C\uAC08 \uC218 \uC788\uC5B4\uC694.");
|
|
1365
|
+
}
|
|
1366
|
+
if (exposedPaths.length > 0) {
|
|
1367
|
+
warnings.push(
|
|
1368
|
+
`ignore\uB418\uC9C0 \uC54A\uC740 \uBBFC\uAC10 \uD30C\uC77C ${exposedPaths.length}\uAC1C: ${exposedPaths.join(", ")}`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
return {
|
|
1372
|
+
ok: !missingGitignore && exposedPaths.length === 0,
|
|
1373
|
+
missingGitignore,
|
|
1374
|
+
exposedPaths,
|
|
1375
|
+
warnings
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
function printSecurityWarnings(rootDir = process.cwd()) {
|
|
1379
|
+
const result = checkProjectSecurity(rootDir);
|
|
1380
|
+
if (result.ok) return true;
|
|
1381
|
+
for (const w of result.warnings) {
|
|
1382
|
+
console.log(chalk3.yellow(` \u26A0\uFE0F ${w}`));
|
|
1383
|
+
}
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
function filterTrackedPaths(paths, rootDir = process.cwd()) {
|
|
1387
|
+
const ig = loadGitignore(rootDir);
|
|
1388
|
+
return paths.filter((p) => !isPathIgnored(ig, p.replace(/\\/g, "/")));
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// src/utils/logger.ts
|
|
1392
|
+
import chalk4 from "chalk";
|
|
1233
1393
|
var log = {
|
|
1234
|
-
success: (msg) => console.log(
|
|
1235
|
-
error: (msg) => console.log(
|
|
1236
|
-
warn: (msg) => console.log(
|
|
1237
|
-
info: (msg) => console.log(
|
|
1238
|
-
step: (msg) => console.log(
|
|
1394
|
+
success: (msg) => console.log(chalk4.green(`\u2705 ${msg}`)),
|
|
1395
|
+
error: (msg) => console.log(chalk4.red(`\u274C ${msg}`)),
|
|
1396
|
+
warn: (msg) => console.log(chalk4.yellow(`\u26A0\uFE0F ${msg}`)),
|
|
1397
|
+
info: (msg) => console.log(chalk4.blue(`\u2139\uFE0F ${msg}`)),
|
|
1398
|
+
step: (msg) => console.log(chalk4.bold(`
|
|
1239
1399
|
\u25B8 ${msg}`))
|
|
1240
1400
|
};
|
|
1241
1401
|
|
|
1242
1402
|
// src/utils/file.ts
|
|
1243
|
-
import
|
|
1244
|
-
import
|
|
1403
|
+
import fs2 from "fs";
|
|
1404
|
+
import path2 from "path";
|
|
1245
1405
|
function writeFile(filePath, content) {
|
|
1246
|
-
const dir =
|
|
1247
|
-
if (!
|
|
1248
|
-
|
|
1406
|
+
const dir = path2.dirname(filePath);
|
|
1407
|
+
if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
|
|
1408
|
+
fs2.writeFileSync(filePath, content, "utf-8");
|
|
1249
1409
|
}
|
|
1250
1410
|
function fileExists(filePath) {
|
|
1251
|
-
return
|
|
1411
|
+
return fs2.existsSync(filePath);
|
|
1252
1412
|
}
|
|
1253
1413
|
|
|
1254
1414
|
// src/lib/notion-import.ts
|
|
@@ -1440,13 +1600,14 @@ async function collectAnswers(options, defaults = {}) {
|
|
|
1440
1600
|
async function init(options = {}) {
|
|
1441
1601
|
const skipGate = Boolean(options.skipGate || options.fromNotion);
|
|
1442
1602
|
if (skipGate) {
|
|
1443
|
-
console.log(
|
|
1603
|
+
console.log(chalk5.dim(`
|
|
1444
1604
|
${ko.init.skipGate}
|
|
1445
1605
|
`));
|
|
1446
1606
|
}
|
|
1447
|
-
console.log(
|
|
1607
|
+
console.log(chalk5.bold(`
|
|
1448
1608
|
${ko.init.title}
|
|
1449
1609
|
`));
|
|
1610
|
+
printSecurityWarnings();
|
|
1450
1611
|
let prdContent = {};
|
|
1451
1612
|
const defaults = {};
|
|
1452
1613
|
if (options.fromNotion) {
|
|
@@ -1468,7 +1629,7 @@ ${ko.init.title}
|
|
|
1468
1629
|
process.exit(1);
|
|
1469
1630
|
}
|
|
1470
1631
|
const stack = STACK_PRESETS[answers.type];
|
|
1471
|
-
console.log(
|
|
1632
|
+
console.log(chalk5.dim(`
|
|
1472
1633
|
${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
1473
1634
|
`));
|
|
1474
1635
|
if (!options.yes) {
|
|
@@ -1487,7 +1648,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
1487
1648
|
const files = generateFiles(answers.name, answers.description, stack, prdContent);
|
|
1488
1649
|
log.step(ko.init.filesGenerating);
|
|
1489
1650
|
for (const [filePath, content] of Object.entries(files)) {
|
|
1490
|
-
const fullPath =
|
|
1651
|
+
const fullPath = path3.join(cwd, filePath);
|
|
1491
1652
|
if (fileExists(fullPath)) {
|
|
1492
1653
|
const { overwrite } = await inquirer2.prompt([{
|
|
1493
1654
|
type: "confirm",
|
|
@@ -1504,21 +1665,21 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
1504
1665
|
log.success(filePath);
|
|
1505
1666
|
}
|
|
1506
1667
|
await writeInitExtras(cwd);
|
|
1507
|
-
console.log(
|
|
1668
|
+
console.log(chalk5.bold.green(`
|
|
1508
1669
|
${ko.init.done}`));
|
|
1509
|
-
console.log(
|
|
1670
|
+
console.log(chalk5.dim(`
|
|
1510
1671
|
${ko.init.nextSteps}`));
|
|
1511
1672
|
if (options.fromNotion) {
|
|
1512
1673
|
console.log(` 1. ${ko.init.notionReviewHint}`);
|
|
1513
1674
|
console.log(` 2. ${ko.init.gitHintLabel}`);
|
|
1514
|
-
console.log(` ${
|
|
1675
|
+
console.log(` ${chalk5.cyan(ko.init.gitHintCommand)}`);
|
|
1515
1676
|
console.log(` 3. ${ko.init.startDev}
|
|
1516
1677
|
`);
|
|
1517
1678
|
} else {
|
|
1518
1679
|
console.log(` 1. ${ko.init.fillHint}`);
|
|
1519
1680
|
console.log(` 2. ${ko.init.prdHint}`);
|
|
1520
1681
|
console.log(` 3. ${ko.init.gitHintLabel}`);
|
|
1521
|
-
console.log(` ${
|
|
1682
|
+
console.log(` ${chalk5.cyan(ko.init.gitHintCommand)}`);
|
|
1522
1683
|
console.log(` 4. ${ko.init.startDev}
|
|
1523
1684
|
`);
|
|
1524
1685
|
}
|
|
@@ -1558,7 +1719,7 @@ function generateFiles(name, description, stack, prdContent = {}) {
|
|
|
1558
1719
|
};
|
|
1559
1720
|
}
|
|
1560
1721
|
var VHK_PACKAGE_SCRIPTS = {
|
|
1561
|
-
save: "
|
|
1722
|
+
save: "vhk save",
|
|
1562
1723
|
check: "vhk check",
|
|
1563
1724
|
scan: "vhk secure scan",
|
|
1564
1725
|
recap: "vhk recap",
|
|
@@ -1566,15 +1727,15 @@ var VHK_PACKAGE_SCRIPTS = {
|
|
|
1566
1727
|
doctor: "vhk doctor"
|
|
1567
1728
|
};
|
|
1568
1729
|
function enhancePackageScripts(projectDir) {
|
|
1569
|
-
const pkgPath =
|
|
1570
|
-
if (!
|
|
1571
|
-
const pkg = JSON.parse(
|
|
1730
|
+
const pkgPath = path3.join(projectDir, "package.json");
|
|
1731
|
+
if (!fs3.existsSync(pkgPath)) return false;
|
|
1732
|
+
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
1572
1733
|
pkg.scripts = { ...pkg.scripts, ...VHK_PACKAGE_SCRIPTS };
|
|
1573
|
-
|
|
1734
|
+
fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1574
1735
|
return true;
|
|
1575
1736
|
}
|
|
1576
1737
|
async function writeInitExtras(projectDir) {
|
|
1577
|
-
const commandsPath =
|
|
1738
|
+
const commandsPath = path3.join(projectDir, "COMMANDS.md");
|
|
1578
1739
|
if (fileExists(commandsPath)) {
|
|
1579
1740
|
const { overwrite } = await inquirer2.prompt([{
|
|
1580
1741
|
type: "confirm",
|
|
@@ -1599,37 +1760,13 @@ async function writeInitExtras(projectDir) {
|
|
|
1599
1760
|
|
|
1600
1761
|
// src/commands/recap.ts
|
|
1601
1762
|
import inquirer3 from "inquirer";
|
|
1602
|
-
import
|
|
1763
|
+
import chalk6 from "chalk";
|
|
1603
1764
|
import fs5 from "fs";
|
|
1604
1765
|
import path6 from "path";
|
|
1605
1766
|
|
|
1606
1767
|
// src/lib/git.ts
|
|
1607
1768
|
import path4 from "path";
|
|
1608
1769
|
import simpleGit from "simple-git";
|
|
1609
|
-
|
|
1610
|
-
// src/lib/check-secure.ts
|
|
1611
|
-
var import_ignore = __toESM(require_ignore(), 1);
|
|
1612
|
-
import fs3 from "fs";
|
|
1613
|
-
import path3 from "path";
|
|
1614
|
-
function loadGitignore(rootDir) {
|
|
1615
|
-
const ig = (0, import_ignore.default)();
|
|
1616
|
-
const gitignorePath = path3.join(rootDir, ".gitignore");
|
|
1617
|
-
if (fs3.existsSync(gitignorePath)) {
|
|
1618
|
-
const content = fs3.readFileSync(gitignorePath, "utf-8");
|
|
1619
|
-
ig.add(content);
|
|
1620
|
-
}
|
|
1621
|
-
return ig;
|
|
1622
|
-
}
|
|
1623
|
-
function isPathIgnored(ig, relativePath) {
|
|
1624
|
-
const normalized = relativePath.replace(/\\/g, "/");
|
|
1625
|
-
return ig.ignores(normalized);
|
|
1626
|
-
}
|
|
1627
|
-
function filterTrackedPaths(paths, rootDir = process.cwd()) {
|
|
1628
|
-
const ig = loadGitignore(rootDir);
|
|
1629
|
-
return paths.filter((p) => !isPathIgnored(ig, p.replace(/\\/g, "/")));
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
// src/lib/git.ts
|
|
1633
1770
|
var git = simpleGit();
|
|
1634
1771
|
function isNoiseRecapPath(filePath) {
|
|
1635
1772
|
const base = path4.basename(filePath);
|
|
@@ -1646,29 +1783,19 @@ function filterRecapFiles(files) {
|
|
|
1646
1783
|
const tracked = new Set(filterTrackedPaths(paths));
|
|
1647
1784
|
return files.filter((f) => tracked.has(f.file) && !isNoiseRecapPath(f.file));
|
|
1648
1785
|
}
|
|
1649
|
-
function
|
|
1650
|
-
if (
|
|
1651
|
-
if (
|
|
1652
|
-
if (workingDir === "R") return "renamed";
|
|
1786
|
+
function inferFileStatusFromDiff(insertions, deletions) {
|
|
1787
|
+
if (deletions > 0 && insertions === 0) return "deleted";
|
|
1788
|
+
if (insertions > 0 && deletions === 0) return "new";
|
|
1653
1789
|
return "modified";
|
|
1654
1790
|
}
|
|
1655
|
-
|
|
1656
|
-
const sinceDate = since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1657
|
-
const diffSummary = await git.diffSummary([`--since=${sinceDate}`]);
|
|
1658
|
-
const statusResult = await git.status();
|
|
1659
|
-
const statByFile = new Map(
|
|
1660
|
-
diffSummary.files.map((f) => [f.file, f])
|
|
1661
|
-
);
|
|
1791
|
+
function buildSessionDiffFromSummary(diffSummary) {
|
|
1662
1792
|
const files = filterRecapFiles(
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
status: fileStatus(f.working_dir)
|
|
1670
|
-
};
|
|
1671
|
-
})
|
|
1793
|
+
diffSummary.files.map((f) => ({
|
|
1794
|
+
file: f.file,
|
|
1795
|
+
insertions: f.insertions,
|
|
1796
|
+
deletions: f.deletions,
|
|
1797
|
+
status: inferFileStatusFromDiff(f.insertions, f.deletions)
|
|
1798
|
+
}))
|
|
1672
1799
|
);
|
|
1673
1800
|
return {
|
|
1674
1801
|
filesChanged: files.length,
|
|
@@ -1677,6 +1804,11 @@ async function getSessionDiff(since) {
|
|
|
1677
1804
|
files
|
|
1678
1805
|
};
|
|
1679
1806
|
}
|
|
1807
|
+
async function getSessionDiff(since) {
|
|
1808
|
+
const sinceDate = since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1809
|
+
const diffSummary = await git.diffSummary([`--since=${sinceDate}`]);
|
|
1810
|
+
return buildSessionDiffFromSummary(diffSummary);
|
|
1811
|
+
}
|
|
1680
1812
|
async function getRecentCommits(count = 10, since) {
|
|
1681
1813
|
const options = { maxCount: count };
|
|
1682
1814
|
if (since) options["--since"] = since;
|
|
@@ -1787,39 +1919,40 @@ function createAdrFile(cwd, title, context, decision, consequences) {
|
|
|
1787
1919
|
|
|
1788
1920
|
// src/commands/recap.ts
|
|
1789
1921
|
async function recap(options = {}) {
|
|
1790
|
-
console.log(
|
|
1922
|
+
console.log(chalk6.bold(`
|
|
1791
1923
|
${ko.recap.title}
|
|
1792
1924
|
`));
|
|
1793
1925
|
if (!await isGitRepo()) {
|
|
1794
|
-
console.log(
|
|
1926
|
+
console.log(chalk6.red(ko.recap.noRepo));
|
|
1795
1927
|
return;
|
|
1796
1928
|
}
|
|
1797
|
-
|
|
1929
|
+
printSecurityWarnings();
|
|
1930
|
+
console.log(chalk6.dim(`${ko.recap.analyzing}
|
|
1798
1931
|
`));
|
|
1799
1932
|
const since = options.since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1800
1933
|
const diff2 = await getSessionDiff(since);
|
|
1801
1934
|
const commits = await getRecentCommits(10, since);
|
|
1802
1935
|
if (diff2.filesChanged === 0 && commits.length === 0) {
|
|
1803
|
-
console.log(
|
|
1936
|
+
console.log(chalk6.yellow(ko.recap.noChanges));
|
|
1804
1937
|
return;
|
|
1805
1938
|
}
|
|
1806
|
-
console.log(
|
|
1807
|
-
console.log(` \uD30C\uC77C: ${
|
|
1808
|
-
console.log(` \uCD94\uAC00: ${
|
|
1939
|
+
console.log(chalk6.bold("\u{1F4CA} \uBCC0\uACBD \uC694\uC57D:"));
|
|
1940
|
+
console.log(` \uD30C\uC77C: ${chalk6.cyan(String(diff2.filesChanged))}\uAC1C \uBCC0\uACBD`);
|
|
1941
|
+
console.log(` \uCD94\uAC00: ${chalk6.green("+" + diff2.insertions)} / \uC0AD\uC81C: ${chalk6.red("-" + diff2.deletions)}`);
|
|
1809
1942
|
if (diff2.files.length > 0) {
|
|
1810
|
-
console.log(
|
|
1943
|
+
console.log(chalk6.dim("\n \uBCC0\uACBD \uD30C\uC77C:"));
|
|
1811
1944
|
diff2.files.slice(0, 15).forEach((f) => {
|
|
1812
|
-
const icon = f.status === "new" ?
|
|
1945
|
+
const icon = f.status === "new" ? chalk6.green("\u{1F195}") : f.status === "deleted" ? chalk6.red("\u{1F5D1}\uFE0F") : chalk6.yellow("\u270F\uFE0F");
|
|
1813
1946
|
console.log(` ${icon} ${f.file}`);
|
|
1814
1947
|
});
|
|
1815
1948
|
if (diff2.files.length > 15) {
|
|
1816
|
-
console.log(
|
|
1949
|
+
console.log(chalk6.dim(` ... \uC678 ${diff2.files.length - 15}\uAC1C`));
|
|
1817
1950
|
}
|
|
1818
1951
|
}
|
|
1819
1952
|
if (commits.length > 0) {
|
|
1820
|
-
console.log(
|
|
1953
|
+
console.log(chalk6.dim("\n \uCD5C\uADFC \uCEE4\uBC0B:"));
|
|
1821
1954
|
commits.slice(0, 5).forEach((c) => {
|
|
1822
|
-
console.log(
|
|
1955
|
+
console.log(chalk6.dim(` \u2022 ${c.message}`));
|
|
1823
1956
|
});
|
|
1824
1957
|
}
|
|
1825
1958
|
console.log("");
|
|
@@ -1887,11 +2020,11 @@ ${ko.recap.title}
|
|
|
1887
2020
|
fs5.writeFileSync(filePath, content, "utf-8");
|
|
1888
2021
|
const adrCandidates = detectAdrCandidates(diff2);
|
|
1889
2022
|
if (adrCandidates.length > 0) {
|
|
1890
|
-
console.log(
|
|
2023
|
+
console.log(chalk6.cyan.bold(`
|
|
1891
2024
|
${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
1892
2025
|
for (const candidate of adrCandidates) {
|
|
1893
|
-
console.log(
|
|
1894
|
-
candidate.files.forEach((f) => console.log(
|
|
2026
|
+
console.log(chalk6.cyan(` \u2022 ${candidate.title}: ${candidate.context}`));
|
|
2027
|
+
candidate.files.forEach((f) => console.log(chalk6.dim(` ${f}`)));
|
|
1895
2028
|
}
|
|
1896
2029
|
const { createAdr } = await inquirer3.prompt([{
|
|
1897
2030
|
type: "confirm",
|
|
@@ -1921,17 +2054,17 @@ ${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
|
1921
2054
|
adrAnswers.decision,
|
|
1922
2055
|
adrAnswers.consequences
|
|
1923
2056
|
);
|
|
1924
|
-
console.log(
|
|
2057
|
+
console.log(chalk6.green(` \u2705 ADR \uC0DD\uC131: ${path6.relative(process.cwd(), adrPath)}`));
|
|
1925
2058
|
}
|
|
1926
2059
|
}
|
|
1927
2060
|
}
|
|
1928
2061
|
const troubleshootingKeywords = /fix|bug|error|crash|hotfix|patch|revert|트러블|에러|버그|수정|핫픽스/i;
|
|
1929
2062
|
const troubleCommits = commits.filter((c) => troubleshootingKeywords.test(c.message));
|
|
1930
2063
|
if (troubleCommits.length > 0) {
|
|
1931
|
-
console.log(
|
|
2064
|
+
console.log(chalk6.yellow.bold(`
|
|
1932
2065
|
${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
1933
2066
|
troubleCommits.forEach((c) => {
|
|
1934
|
-
console.log(
|
|
2067
|
+
console.log(chalk6.dim(` \u2022 ${c.message}`));
|
|
1935
2068
|
});
|
|
1936
2069
|
const { createTroubleshoot } = await inquirer3.prompt([{
|
|
1937
2070
|
type: "confirm",
|
|
@@ -1982,12 +2115,12 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1982
2115
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1983
2116
|
].join("\n");
|
|
1984
2117
|
fs5.writeFileSync(tsFilePath, tsContent, "utf-8");
|
|
1985
|
-
console.log(
|
|
2118
|
+
console.log(chalk6.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${path6.relative(process.cwd(), tsFilePath)}`));
|
|
1986
2119
|
}
|
|
1987
2120
|
}
|
|
1988
|
-
console.log(
|
|
2121
|
+
console.log(chalk6.green.bold(`
|
|
1989
2122
|
${ko.recap.done}`));
|
|
1990
|
-
console.log(
|
|
2123
|
+
console.log(chalk6.dim(` \u{1F4C4} ${path6.relative(process.cwd(), filePath)}`));
|
|
1991
2124
|
const claudeMdPath = path6.join(process.cwd(), "CLAUDE.md");
|
|
1992
2125
|
if (fs5.existsSync(claudeMdPath)) {
|
|
1993
2126
|
const { updateClaude } = await inquirer3.prompt([{
|
|
@@ -2007,7 +2140,7 @@ ${ko.recap.done}`));
|
|
|
2007
2140
|
`- **\uB2E4\uC74C \uC561\uC158:** ${answers.nextTodo}`
|
|
2008
2141
|
);
|
|
2009
2142
|
fs5.writeFileSync(claudeMdPath, claudeContent, "utf-8");
|
|
2010
|
-
console.log(
|
|
2143
|
+
console.log(chalk6.green(" \u2705 CLAUDE.md \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC"));
|
|
2011
2144
|
}
|
|
2012
2145
|
}
|
|
2013
2146
|
const gitSaveCmd = process.platform === "win32" ? 'git add .; git commit -m "recap: \uC138\uC158 \uAE30\uB85D"' : 'git add . && git commit -m "recap: \uC138\uC158 \uAE30\uB85D"';
|
|
@@ -2019,7 +2152,7 @@ ${ko.recap.done}`));
|
|
|
2019
2152
|
}
|
|
2020
2153
|
|
|
2021
2154
|
// src/commands/sync.ts
|
|
2022
|
-
import
|
|
2155
|
+
import chalk7 from "chalk";
|
|
2023
2156
|
import fs6 from "fs";
|
|
2024
2157
|
import path7 from "path";
|
|
2025
2158
|
var CURSORRULES_KEYS = ["\uCF54\uB529 \uADDC\uCE59", "\uAE30\uC220 \uC2A4\uD0DD", "\uC544\uD0A4\uD14D\uCC98", "\uB514\uC790\uC778", "Anti-patterns", "\uCEE4\uBC0B"];
|
|
@@ -2089,32 +2222,32 @@ function toClaudeMd(sections, existing) {
|
|
|
2089
2222
|
return lines.join("\n");
|
|
2090
2223
|
}
|
|
2091
2224
|
async function sync() {
|
|
2092
|
-
console.log(
|
|
2225
|
+
console.log(chalk7.bold(`
|
|
2093
2226
|
${ko.sync.title}
|
|
2094
2227
|
`));
|
|
2095
2228
|
const cwd = process.cwd();
|
|
2096
2229
|
const rulesPath = path7.join(cwd, "RULES.md");
|
|
2097
2230
|
if (!fs6.existsSync(rulesPath)) {
|
|
2098
|
-
console.log(
|
|
2099
|
-
console.log(
|
|
2100
|
-
console.log(
|
|
2231
|
+
console.log(chalk7.yellow(ko.sync.noRules));
|
|
2232
|
+
console.log(chalk7.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
|
|
2233
|
+
console.log(chalk7.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
|
|
2101
2234
|
console.log("");
|
|
2102
|
-
console.log(
|
|
2103
|
-
console.log(
|
|
2104
|
-
console.log(
|
|
2105
|
-
console.log(
|
|
2106
|
-
console.log(
|
|
2107
|
-
console.log(
|
|
2235
|
+
console.log(chalk7.dim(" RULES.md \uAE30\uBCF8 \uAD6C\uC870:"));
|
|
2236
|
+
console.log(chalk7.dim(" ## \uD504\uB85C\uC81D\uD2B8 \uC815\uCCB4\uC131"));
|
|
2237
|
+
console.log(chalk7.dim(" ## \uAE30\uC220 \uC2A4\uD0DD"));
|
|
2238
|
+
console.log(chalk7.dim(" ## \uCF54\uB529 \uADDC\uCE59"));
|
|
2239
|
+
console.log(chalk7.dim(" ## \uAE30\uB85D \uADDC\uCE59"));
|
|
2240
|
+
console.log(chalk7.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
|
|
2108
2241
|
return;
|
|
2109
2242
|
}
|
|
2110
2243
|
const rulesContent = fs6.readFileSync(rulesPath, "utf-8");
|
|
2111
2244
|
const sections = parseRulesMd(rulesContent);
|
|
2112
|
-
console.log(
|
|
2245
|
+
console.log(chalk7.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
|
|
2113
2246
|
const firstLine = rulesContent.split("\n")[0];
|
|
2114
2247
|
const projectName = firstLine.replace(/^#\s*/, "").replace(/\s*—.*/, "").trim() || "Project";
|
|
2115
2248
|
const cursorrulesPath = path7.join(cwd, ".cursorrules");
|
|
2116
2249
|
fs6.writeFileSync(cursorrulesPath, toCursorrules(sections, projectName), "utf-8");
|
|
2117
|
-
console.log(
|
|
2250
|
+
console.log(chalk7.green(` ${ko.sync.cursorrulesDone}`));
|
|
2118
2251
|
const claudePath = path7.join(cwd, "CLAUDE.md");
|
|
2119
2252
|
const existingClaude = fs6.existsSync(claudePath) ? fs6.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
|
|
2120
2253
|
|
|
@@ -2124,11 +2257,11 @@ ${ko.sync.title}
|
|
|
2124
2257
|
- **\uB2E4\uC74C \uC561\uC158:** __FILL__
|
|
2125
2258
|
- **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
|
|
2126
2259
|
fs6.writeFileSync(claudePath, toClaudeMd(sections, existingClaude), "utf-8");
|
|
2127
|
-
console.log(
|
|
2128
|
-
console.log(
|
|
2260
|
+
console.log(chalk7.green(` ${ko.sync.claudeDone}`));
|
|
2261
|
+
console.log(chalk7.bold.green(`
|
|
2129
2262
|
${ko.sync.done}`));
|
|
2130
|
-
console.log(
|
|
2131
|
-
console.log(
|
|
2263
|
+
console.log(chalk7.dim(" RULES.md (\uC6D0\uBCF8) \u2192 .cursorrules + CLAUDE.md (\uC790\uB3D9 \uC0DD\uC131)"));
|
|
2264
|
+
console.log(chalk7.dim(" \uADDC\uCE59 \uBCC0\uACBD\uC740 \uD56D\uC0C1 RULES.md\uC5D0\uC11C\uB9CC \uD558\uC138\uC694."));
|
|
2132
2265
|
printNextStep({
|
|
2133
2266
|
message: "\uADDC\uCE59 \uB3D9\uAE30\uD654 \uC644\uB8CC! \uC774\uC81C Cursor\uAC00 \uC0C8 \uADDC\uCE59\uC744 \uB530\uB985\uB2C8\uB2E4.",
|
|
2134
2267
|
command: "vhk \uC810\uAC80",
|
|
@@ -2137,7 +2270,7 @@ ${ko.sync.done}`));
|
|
|
2137
2270
|
}
|
|
2138
2271
|
|
|
2139
2272
|
// src/commands/check.ts
|
|
2140
|
-
import
|
|
2273
|
+
import chalk8 from "chalk";
|
|
2141
2274
|
import path9 from "path";
|
|
2142
2275
|
import fs8 from "fs";
|
|
2143
2276
|
|
|
@@ -2196,15 +2329,6 @@ function parseRules(rulesPath) {
|
|
|
2196
2329
|
));
|
|
2197
2330
|
}
|
|
2198
2331
|
}
|
|
2199
|
-
if (/반드시|필수|항상|must|always|required/i.test(ruleText)) {
|
|
2200
|
-
rules.push({
|
|
2201
|
-
id: `required-${ruleIndex}`,
|
|
2202
|
-
section: currentSection,
|
|
2203
|
-
type: "custom",
|
|
2204
|
-
description: ruleText,
|
|
2205
|
-
check: () => []
|
|
2206
|
-
});
|
|
2207
|
-
}
|
|
2208
2332
|
}
|
|
2209
2333
|
return rules;
|
|
2210
2334
|
}
|
|
@@ -2305,22 +2429,22 @@ function escapeRegex(str) {
|
|
|
2305
2429
|
|
|
2306
2430
|
// src/commands/check.ts
|
|
2307
2431
|
async function check() {
|
|
2308
|
-
console.log(
|
|
2432
|
+
console.log(chalk8.bold(`
|
|
2309
2433
|
${ko.check.title}
|
|
2310
2434
|
`));
|
|
2311
2435
|
const cwd = process.cwd();
|
|
2312
2436
|
const rulesPath = path9.join(cwd, "RULES.md");
|
|
2313
2437
|
if (!fs8.existsSync(rulesPath)) {
|
|
2314
|
-
console.log(
|
|
2315
|
-
console.log(
|
|
2438
|
+
console.log(chalk8.yellow(ko.check.noRules));
|
|
2439
|
+
console.log(chalk8.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
2316
2440
|
return;
|
|
2317
2441
|
}
|
|
2318
2442
|
const rules = parseRules(rulesPath);
|
|
2319
|
-
console.log(
|
|
2443
|
+
console.log(chalk8.dim(` \u{1F4CF} ${rules.length}\uAC1C \uAC80\uC99D \uAC00\uB2A5\uD55C \uADDC\uCE59 \uAC10\uC9C0
|
|
2320
2444
|
`));
|
|
2321
2445
|
if (rules.length === 0) {
|
|
2322
|
-
console.log(
|
|
2323
|
-
console.log(
|
|
2446
|
+
console.log(chalk8.yellow(ko.check.noAutoRules));
|
|
2447
|
+
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."));
|
|
2324
2448
|
return;
|
|
2325
2449
|
}
|
|
2326
2450
|
const allViolations = [];
|
|
@@ -2328,13 +2452,13 @@ ${ko.check.title}
|
|
|
2328
2452
|
for (const rule of rules) {
|
|
2329
2453
|
const violations = rule.check(cwd);
|
|
2330
2454
|
if (violations.length === 0) {
|
|
2331
|
-
console.log(
|
|
2455
|
+
console.log(chalk8.green(` \u2705 ${rule.id}`) + chalk8.dim(` \u2014 ${rule.description.slice(0, 60)}`));
|
|
2332
2456
|
passCount++;
|
|
2333
2457
|
} else {
|
|
2334
|
-
console.log(
|
|
2458
|
+
console.log(chalk8.red(` \u274C ${rule.id}`) + chalk8.dim(` \u2014 ${violations.length}\uAC74 \uC704\uBC18`));
|
|
2335
2459
|
violations.forEach((v) => {
|
|
2336
|
-
const loc = v.file ?
|
|
2337
|
-
const icon = v.severity === "error" ?
|
|
2460
|
+
const loc = v.file ? chalk8.dim(` (${v.file}${v.line ? ":" + v.line : ""})`) : "";
|
|
2461
|
+
const icon = v.severity === "error" ? chalk8.red("\u2716") : v.severity === "warning" ? chalk8.yellow("\u26A0") : chalk8.blue("\u2139");
|
|
2338
2462
|
console.log(` ${icon} ${v.message}${loc}`);
|
|
2339
2463
|
});
|
|
2340
2464
|
allViolations.push(...violations);
|
|
@@ -2344,17 +2468,17 @@ ${ko.check.title}
|
|
|
2344
2468
|
const errors = allViolations.filter((v) => v.severity === "error").length;
|
|
2345
2469
|
const warnings = allViolations.filter((v) => v.severity === "warning").length;
|
|
2346
2470
|
if (allViolations.length === 0) {
|
|
2347
|
-
console.log(
|
|
2471
|
+
console.log(chalk8.green.bold(`${ko.check.allPassed} (${passCount}/${rules.length})`));
|
|
2348
2472
|
printNextStep({
|
|
2349
2473
|
message: "\uBAA8\uB4E0 \uADDC\uCE59 \uD1B5\uACFC! \uBCF4\uC548 \uC2A4\uCE94\uB3C4 \uD574\uBCFC\uAE4C\uC694?",
|
|
2350
2474
|
command: "vhk \uBCF4\uC548 scan",
|
|
2351
2475
|
cursorHint: "\uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB824\uC918"
|
|
2352
2476
|
});
|
|
2353
2477
|
} else {
|
|
2354
|
-
console.log(
|
|
2355
|
-
console.log(` \uADDC\uCE59: ${
|
|
2356
|
-
if (errors > 0) console.log(` ${
|
|
2357
|
-
if (warnings > 0) console.log(` ${
|
|
2478
|
+
console.log(chalk8.bold(ko.check.summary));
|
|
2479
|
+
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`);
|
|
2480
|
+
if (errors > 0) console.log(` ${chalk8.red(`\u2716 ${errors}\uAC1C \uC5D0\uB7EC`)}`);
|
|
2481
|
+
if (warnings > 0) console.log(` ${chalk8.yellow(`\u26A0 ${warnings}\uAC1C \uACBD\uACE0`)}`);
|
|
2358
2482
|
printNextStep({
|
|
2359
2483
|
message: "\uC704\uBC18 \uD56D\uBAA9\uC744 \uC218\uC815\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694.",
|
|
2360
2484
|
command: "vhk \uC810\uAC80",
|
|
@@ -2367,10 +2491,13 @@ ${ko.check.title}
|
|
|
2367
2491
|
}
|
|
2368
2492
|
|
|
2369
2493
|
// src/commands/secure.ts
|
|
2370
|
-
import
|
|
2371
|
-
import
|
|
2494
|
+
import chalk9 from "chalk";
|
|
2495
|
+
import fs11 from "fs";
|
|
2372
2496
|
import path11 from "path";
|
|
2373
2497
|
|
|
2498
|
+
// src/lib/scan-secrets.ts
|
|
2499
|
+
import fs10 from "fs";
|
|
2500
|
+
|
|
2374
2501
|
// src/lib/secret-patterns.ts
|
|
2375
2502
|
var SECRET_PATTERNS = [
|
|
2376
2503
|
{
|
|
@@ -2395,7 +2522,7 @@ var SECRET_PATTERNS = [
|
|
|
2395
2522
|
id: "notion-token",
|
|
2396
2523
|
name: "Notion Integration Token",
|
|
2397
2524
|
severity: "critical",
|
|
2398
|
-
pattern: /secret_[A-Za-z0-9]{
|
|
2525
|
+
pattern: /secret_[A-Za-z0-9]{40,50}/
|
|
2399
2526
|
},
|
|
2400
2527
|
{
|
|
2401
2528
|
id: "github-token",
|
|
@@ -2407,7 +2534,7 @@ var SECRET_PATTERNS = [
|
|
|
2407
2534
|
id: "openai-key",
|
|
2408
2535
|
name: "OpenAI API Key",
|
|
2409
2536
|
severity: "critical",
|
|
2410
|
-
pattern:
|
|
2537
|
+
pattern: /\bsk-(?:proj-|ant-api03-|live-)[A-Za-z0-9_-]{16,}\b/
|
|
2411
2538
|
},
|
|
2412
2539
|
{
|
|
2413
2540
|
id: "generic-api-key",
|
|
@@ -2509,69 +2636,88 @@ function walkProjectFiles(rootDir, onFile, ig = loadGitignore(rootDir)) {
|
|
|
2509
2636
|
walk(rootDir);
|
|
2510
2637
|
}
|
|
2511
2638
|
|
|
2512
|
-
// src/
|
|
2513
|
-
var
|
|
2639
|
+
// src/lib/scan-secrets.ts
|
|
2640
|
+
var MAX_SECRET_FINDINGS = 200;
|
|
2514
2641
|
var MAX_LINE_CHARS = 4e3;
|
|
2642
|
+
function globalPattern(pattern) {
|
|
2643
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
2644
|
+
return new RegExp(pattern.source, flags);
|
|
2645
|
+
}
|
|
2646
|
+
function findSecretsInLine(line, relPath, lineNum) {
|
|
2647
|
+
const found = [];
|
|
2648
|
+
const trimmed = line.trim();
|
|
2649
|
+
if (trimmed.startsWith("//") && trimmed.includes("example")) return found;
|
|
2650
|
+
if (trimmed.startsWith("#") && trimmed.includes("example")) return found;
|
|
2651
|
+
if (line.length > MAX_LINE_CHARS) return found;
|
|
2652
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
2653
|
+
const regex = globalPattern(pattern.pattern);
|
|
2654
|
+
for (const match of line.matchAll(regex)) {
|
|
2655
|
+
found.push({
|
|
2656
|
+
patternId: pattern.id,
|
|
2657
|
+
patternName: pattern.name,
|
|
2658
|
+
severity: pattern.severity,
|
|
2659
|
+
file: relPath,
|
|
2660
|
+
line: lineNum,
|
|
2661
|
+
match: maskSecret(match[0])
|
|
2662
|
+
});
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
return found;
|
|
2666
|
+
}
|
|
2667
|
+
function scanProjectForSecrets(cwd) {
|
|
2668
|
+
const findings = [];
|
|
2669
|
+
let scannedFiles = 0;
|
|
2670
|
+
let truncated = false;
|
|
2671
|
+
walkProjectFiles(cwd, (filePath, relPath) => {
|
|
2672
|
+
scannedFiles++;
|
|
2673
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
2674
|
+
const lines = content.split("\n");
|
|
2675
|
+
lines.forEach((line, idx) => {
|
|
2676
|
+
if (truncated) return;
|
|
2677
|
+
const lineFindings = findSecretsInLine(line, relPath, idx + 1);
|
|
2678
|
+
for (const f of lineFindings) {
|
|
2679
|
+
findings.push(f);
|
|
2680
|
+
if (findings.length >= MAX_SECRET_FINDINGS) {
|
|
2681
|
+
truncated = true;
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
});
|
|
2687
|
+
return { findings, scannedFiles, truncated };
|
|
2688
|
+
}
|
|
2689
|
+
function filterSevereFindings(findings) {
|
|
2690
|
+
return findings.filter((f) => f.severity === "critical" || f.severity === "high");
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// src/commands/secure.ts
|
|
2515
2694
|
async function secure() {
|
|
2516
|
-
console.log(
|
|
2695
|
+
console.log(chalk9.bold(`
|
|
2517
2696
|
${ko.secure.title}
|
|
2518
2697
|
`));
|
|
2519
2698
|
const cwd = process.cwd();
|
|
2520
|
-
const findings = [];
|
|
2521
|
-
let scannedFiles = 0;
|
|
2522
|
-
let truncated = false;
|
|
2523
2699
|
const gitignorePath = path11.join(cwd, ".gitignore");
|
|
2524
|
-
const hasGitignore =
|
|
2700
|
+
const hasGitignore = fs11.existsSync(gitignorePath);
|
|
2525
2701
|
if (!hasGitignore) {
|
|
2526
|
-
console.log(
|
|
2527
|
-
console.log(
|
|
2702
|
+
console.log(chalk9.yellow(` ${ko.secure.noGitignore}`));
|
|
2703
|
+
console.log(chalk9.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
|
|
2528
2704
|
} else {
|
|
2529
|
-
const gitignoreContent =
|
|
2705
|
+
const gitignoreContent = fs11.readFileSync(gitignorePath, "utf-8");
|
|
2530
2706
|
if (!gitignoreContent.includes(".env")) {
|
|
2531
|
-
console.log(
|
|
2532
|
-
console.log(
|
|
2707
|
+
console.log(chalk9.yellow(` ${ko.secure.noEnvInGitignore}`));
|
|
2708
|
+
console.log(chalk9.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
|
|
2533
2709
|
}
|
|
2534
2710
|
}
|
|
2535
|
-
console.log(
|
|
2711
|
+
console.log(chalk9.dim(` ${ko.secure.scanning}
|
|
2536
2712
|
`));
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
const content = fs10.readFileSync(filePath, "utf-8");
|
|
2540
|
-
const lines = content.split("\n");
|
|
2541
|
-
for (const pattern of SECRET_PATTERNS) {
|
|
2542
|
-
if (truncated) break;
|
|
2543
|
-
lines.forEach((line, idx) => {
|
|
2544
|
-
if (truncated) return;
|
|
2545
|
-
if (line.length > MAX_LINE_CHARS) return;
|
|
2546
|
-
const trimmed = line.trim();
|
|
2547
|
-
if (trimmed.startsWith("//") && trimmed.includes("example")) return;
|
|
2548
|
-
if (trimmed.startsWith("#") && trimmed.includes("example")) return;
|
|
2549
|
-
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
|
|
2550
|
-
let match;
|
|
2551
|
-
while ((match = regex.exec(line)) !== null) {
|
|
2552
|
-
findings.push({
|
|
2553
|
-
patternId: pattern.id,
|
|
2554
|
-
patternName: pattern.name,
|
|
2555
|
-
severity: pattern.severity,
|
|
2556
|
-
file: relPath,
|
|
2557
|
-
line: idx + 1,
|
|
2558
|
-
match: maskSecret(match[0])
|
|
2559
|
-
});
|
|
2560
|
-
if (findings.length >= MAX_FINDINGS) {
|
|
2561
|
-
truncated = true;
|
|
2562
|
-
return;
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
});
|
|
2566
|
-
}
|
|
2567
|
-
});
|
|
2568
|
-
console.log(chalk8.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC (lock\xB7node_modules\xB7>${MAX_SCAN_FILE_BYTES / 1024}KB \uC81C\uC678)`));
|
|
2713
|
+
const { findings, scannedFiles, truncated } = scanProjectForSecrets(cwd);
|
|
2714
|
+
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)`));
|
|
2569
2715
|
if (truncated) {
|
|
2570
|
-
console.log(
|
|
2716
|
+
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.`));
|
|
2571
2717
|
}
|
|
2572
2718
|
console.log("");
|
|
2573
2719
|
if (findings.length === 0) {
|
|
2574
|
-
console.log(
|
|
2720
|
+
console.log(chalk9.green.bold(` ${ko.secure.clean}`));
|
|
2575
2721
|
printNextStep({
|
|
2576
2722
|
message: "\uBCF4\uC548 \uC774\uC0C1 \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
|
|
2577
2723
|
command: "vhk \uC815\uB9AC",
|
|
@@ -2583,45 +2729,45 @@ ${ko.secure.title}
|
|
|
2583
2729
|
const high = findings.filter((f) => f.severity === "high");
|
|
2584
2730
|
const medium = findings.filter((f) => f.severity === "medium");
|
|
2585
2731
|
if (critical.length > 0) {
|
|
2586
|
-
console.log(
|
|
2732
|
+
console.log(chalk9.red.bold(` \u{1F6A8} CRITICAL \u2014 ${critical.length}\uAC74`));
|
|
2587
2733
|
critical.forEach((f) => {
|
|
2588
|
-
console.log(
|
|
2589
|
-
console.log(
|
|
2734
|
+
console.log(chalk9.red(` \u2716 ${f.patternName}`));
|
|
2735
|
+
console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
2590
2736
|
});
|
|
2591
2737
|
console.log("");
|
|
2592
2738
|
}
|
|
2593
2739
|
if (high.length > 0) {
|
|
2594
|
-
console.log(
|
|
2740
|
+
console.log(chalk9.yellow.bold(` \u26A0\uFE0F HIGH \u2014 ${high.length}\uAC74`));
|
|
2595
2741
|
high.forEach((f) => {
|
|
2596
|
-
console.log(
|
|
2597
|
-
console.log(
|
|
2742
|
+
console.log(chalk9.yellow(` \u26A0 ${f.patternName}`));
|
|
2743
|
+
console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
2598
2744
|
});
|
|
2599
2745
|
console.log("");
|
|
2600
2746
|
}
|
|
2601
2747
|
if (medium.length > 0) {
|
|
2602
|
-
console.log(
|
|
2748
|
+
console.log(chalk9.blue.bold(` \u2139 MEDIUM \u2014 ${medium.length}\uAC74`));
|
|
2603
2749
|
medium.forEach((f) => {
|
|
2604
|
-
console.log(
|
|
2605
|
-
console.log(
|
|
2750
|
+
console.log(chalk9.blue(` \u2139 ${f.patternName}`));
|
|
2751
|
+
console.log(chalk9.dim(` ${f.file}:${f.line} \u2192 ${f.match}`));
|
|
2606
2752
|
});
|
|
2607
2753
|
console.log("");
|
|
2608
2754
|
}
|
|
2609
|
-
console.log(
|
|
2610
|
-
console.log(` \uCD1D ${
|
|
2755
|
+
console.log(chalk9.bold(` ${ko.secure.summary}`));
|
|
2756
|
+
console.log(` \uCD1D ${chalk9.red(String(findings.length))}\uAC74 \uAC10\uC9C0 | CRITICAL: ${critical.length} | HIGH: ${high.length} | MEDIUM: ${medium.length}`);
|
|
2611
2757
|
console.log("");
|
|
2612
|
-
console.log(
|
|
2613
|
-
console.log(
|
|
2614
|
-
console.log(
|
|
2615
|
-
console.log(
|
|
2616
|
-
if (critical.length > 0) {
|
|
2758
|
+
console.log(chalk9.dim(" \u{1F4A1} \uC870\uCE58 \uBC29\uBC95:"));
|
|
2759
|
+
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"));
|
|
2760
|
+
console.log(chalk9.dim(" 2. git history\uC5D0\uC11C\uB3C4 \uC81C\uAC70: git filter-branch \uB610\uB294 BFG Repo-Cleaner"));
|
|
2761
|
+
console.log(chalk9.dim(" 3. \uC720\uCD9C\uB41C \uD0A4\uB294 \uC989\uC2DC \uD3D0\uAE30\uD558\uACE0 \uC7AC\uBC1C\uAE09\n"));
|
|
2762
|
+
if (critical.length > 0 || high.length > 0) {
|
|
2617
2763
|
process.exitCode = 1;
|
|
2618
2764
|
}
|
|
2619
2765
|
}
|
|
2620
2766
|
|
|
2621
2767
|
// src/commands/doctor.ts
|
|
2622
|
-
import
|
|
2768
|
+
import chalk10 from "chalk";
|
|
2623
2769
|
import { execSync } from "child_process";
|
|
2624
|
-
import
|
|
2770
|
+
import fs12 from "fs";
|
|
2625
2771
|
import path12 from "path";
|
|
2626
2772
|
import { fileURLToPath } from "url";
|
|
2627
2773
|
function checkCommand(name, command, hint) {
|
|
@@ -2640,8 +2786,8 @@ function getVhkVersion() {
|
|
|
2640
2786
|
];
|
|
2641
2787
|
for (const pkgPath of candidates) {
|
|
2642
2788
|
try {
|
|
2643
|
-
if (
|
|
2644
|
-
const pkg = JSON.parse(
|
|
2789
|
+
if (fs12.existsSync(pkgPath)) {
|
|
2790
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
2645
2791
|
return pkg.version;
|
|
2646
2792
|
}
|
|
2647
2793
|
} catch {
|
|
@@ -2651,7 +2797,7 @@ function getVhkVersion() {
|
|
|
2651
2797
|
return void 0;
|
|
2652
2798
|
}
|
|
2653
2799
|
async function doctor() {
|
|
2654
|
-
console.log(
|
|
2800
|
+
console.log(chalk10.bold(`
|
|
2655
2801
|
${ko.doctor.title}
|
|
2656
2802
|
`));
|
|
2657
2803
|
const checks = [
|
|
@@ -2663,22 +2809,22 @@ ${ko.doctor.title}
|
|
|
2663
2809
|
let allOk = true;
|
|
2664
2810
|
for (const check2 of checks) {
|
|
2665
2811
|
if (check2.ok) {
|
|
2666
|
-
console.log(
|
|
2812
|
+
console.log(chalk10.green(` \u2705 ${check2.name}`) + chalk10.dim(` \u2014 ${check2.version}`));
|
|
2667
2813
|
} else {
|
|
2668
|
-
console.log(
|
|
2669
|
-
console.log(
|
|
2814
|
+
console.log(chalk10.red(` \u274C ${check2.name} \uC5C6\uC74C`));
|
|
2815
|
+
console.log(chalk10.dim(` \u2192 ${check2.hint}`));
|
|
2670
2816
|
allOk = false;
|
|
2671
2817
|
}
|
|
2672
2818
|
}
|
|
2673
2819
|
console.log("");
|
|
2674
2820
|
const vhkVersion = getVhkVersion();
|
|
2675
2821
|
if (vhkVersion) {
|
|
2676
|
-
console.log(
|
|
2822
|
+
console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(` \u2014 v${vhkVersion}`));
|
|
2677
2823
|
} else {
|
|
2678
|
-
console.log(
|
|
2824
|
+
console.log(chalk10.green(" \u2705 VHK") + chalk10.dim(" \u2014 \uC124\uCE58\uB428"));
|
|
2679
2825
|
}
|
|
2680
2826
|
console.log("");
|
|
2681
|
-
console.log(
|
|
2827
|
+
console.log(chalk10.bold(` ${ko.doctor.projectFiles}`));
|
|
2682
2828
|
const cwd = process.cwd();
|
|
2683
2829
|
const projectFiles = [
|
|
2684
2830
|
{ name: "RULES.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
|
|
@@ -2688,32 +2834,32 @@ ${ko.doctor.title}
|
|
|
2688
2834
|
{ name: ".env", hint: ".gitignore\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778" }
|
|
2689
2835
|
];
|
|
2690
2836
|
for (const file of projectFiles) {
|
|
2691
|
-
const exists =
|
|
2837
|
+
const exists = fs12.existsSync(path12.join(cwd, file.name));
|
|
2692
2838
|
if (exists) {
|
|
2693
|
-
console.log(
|
|
2839
|
+
console.log(chalk10.green(` \u2705 ${file.name}`));
|
|
2694
2840
|
if (file.name === ".env") {
|
|
2695
2841
|
const gitignorePath = path12.join(cwd, ".gitignore");
|
|
2696
|
-
if (
|
|
2697
|
-
const gitignore =
|
|
2842
|
+
if (fs12.existsSync(gitignorePath)) {
|
|
2843
|
+
const gitignore = fs12.readFileSync(gitignorePath, "utf-8");
|
|
2698
2844
|
if (!gitignore.includes(".env")) {
|
|
2699
|
-
console.log(
|
|
2845
|
+
console.log(chalk10.yellow(` ${ko.doctor.envNotIgnored}`));
|
|
2700
2846
|
}
|
|
2701
2847
|
}
|
|
2702
2848
|
}
|
|
2703
2849
|
} else {
|
|
2704
|
-
console.log(
|
|
2850
|
+
console.log(chalk10.dim(` \u2B1A ${file.name}`) + chalk10.dim(` \u2014 ${file.hint}`));
|
|
2705
2851
|
}
|
|
2706
2852
|
}
|
|
2707
2853
|
console.log("");
|
|
2708
2854
|
if (allOk) {
|
|
2709
|
-
console.log(
|
|
2855
|
+
console.log(chalk10.green.bold(` ${ko.doctor.allOk}`));
|
|
2710
2856
|
printNextStep({
|
|
2711
2857
|
message: ko.doctor.nextOkMessage,
|
|
2712
2858
|
command: "vhk \uC2DC\uC791",
|
|
2713
2859
|
cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
|
|
2714
2860
|
});
|
|
2715
2861
|
} else {
|
|
2716
|
-
console.log(
|
|
2862
|
+
console.log(chalk10.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
|
|
2717
2863
|
printNextStep({
|
|
2718
2864
|
message: ko.doctor.nextRetryMessage,
|
|
2719
2865
|
command: "vhk doctor",
|
|
@@ -2724,9 +2870,9 @@ ${ko.doctor.title}
|
|
|
2724
2870
|
}
|
|
2725
2871
|
|
|
2726
2872
|
// src/commands/ship.ts
|
|
2727
|
-
import
|
|
2873
|
+
import chalk11 from "chalk";
|
|
2728
2874
|
import inquirer4 from "inquirer";
|
|
2729
|
-
import
|
|
2875
|
+
import fs13 from "fs";
|
|
2730
2876
|
import path13 from "path";
|
|
2731
2877
|
var CHECKLIST = [
|
|
2732
2878
|
{ id: "build", questionKey: "checkBuild", hintKey: "hintBuild" },
|
|
@@ -2740,29 +2886,29 @@ function sanitizeVersion(version) {
|
|
|
2740
2886
|
return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
|
|
2741
2887
|
}
|
|
2742
2888
|
async function ship() {
|
|
2743
|
-
console.log(
|
|
2889
|
+
console.log(chalk11.bold(`
|
|
2744
2890
|
${ko.ship.title}
|
|
2745
2891
|
`));
|
|
2746
2892
|
const cwd = process.cwd();
|
|
2747
|
-
console.log(
|
|
2893
|
+
console.log(chalk11.cyan.bold(` ${ko.ship.checklist}
|
|
2748
2894
|
`));
|
|
2749
2895
|
const { passed } = await inquirer4.prompt([{
|
|
2750
2896
|
type: "checkbox",
|
|
2751
2897
|
name: "passed",
|
|
2752
2898
|
message: ko.ship.checkboxPrompt,
|
|
2753
2899
|
choices: CHECKLIST.map((c) => ({
|
|
2754
|
-
name: `${ko.ship[c.questionKey]} ${
|
|
2900
|
+
name: `${ko.ship[c.questionKey]} ${chalk11.dim(`(${ko.ship[c.hintKey]})`)}`,
|
|
2755
2901
|
value: c.id
|
|
2756
2902
|
}))
|
|
2757
2903
|
}]);
|
|
2758
2904
|
const allPassed = passed.length === CHECKLIST.length;
|
|
2759
2905
|
const skipped = CHECKLIST.filter((c) => !passed.includes(c.id));
|
|
2760
2906
|
if (!allPassed) {
|
|
2761
|
-
console.log(
|
|
2907
|
+
console.log(chalk11.yellow(`
|
|
2762
2908
|
${ko.ship.incompleteHeader}`));
|
|
2763
2909
|
skipped.forEach((s) => {
|
|
2764
|
-
console.log(
|
|
2765
|
-
console.log(
|
|
2910
|
+
console.log(chalk11.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
|
|
2911
|
+
console.log(chalk11.dim(` \u2192 ${ko.ship[s.hintKey]}`));
|
|
2766
2912
|
});
|
|
2767
2913
|
const { proceed } = await inquirer4.prompt([{
|
|
2768
2914
|
type: "confirm",
|
|
@@ -2779,13 +2925,13 @@ ${ko.ship.title}
|
|
|
2779
2925
|
return;
|
|
2780
2926
|
}
|
|
2781
2927
|
} else {
|
|
2782
|
-
console.log(
|
|
2928
|
+
console.log(chalk11.green(`
|
|
2783
2929
|
${ko.ship.allPassed}
|
|
2784
2930
|
`));
|
|
2785
2931
|
}
|
|
2786
|
-
console.log(
|
|
2932
|
+
console.log(chalk11.cyan.bold(` ${ko.ship.retro}
|
|
2787
2933
|
`));
|
|
2788
|
-
console.log(
|
|
2934
|
+
console.log(chalk11.dim(` ${ko.ship.versionHint}`));
|
|
2789
2935
|
const retro = await inquirer4.prompt([
|
|
2790
2936
|
{ type: "input", name: "version", message: ko.ship.versionPrompt },
|
|
2791
2937
|
{ type: "input", name: "whatWentWell", message: ko.ship.questionWell },
|
|
@@ -2794,7 +2940,7 @@ ${ko.ship.title}
|
|
|
2794
2940
|
{ type: "input", name: "nextVersion", message: ko.ship.questionNext }
|
|
2795
2941
|
]);
|
|
2796
2942
|
const buildLogDir = path13.join(cwd, "docs", "build-log");
|
|
2797
|
-
if (!
|
|
2943
|
+
if (!fs13.existsSync(buildLogDir)) fs13.mkdirSync(buildLogDir, { recursive: true });
|
|
2798
2944
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2799
2945
|
const versionSlug = sanitizeVersion(retro.version);
|
|
2800
2946
|
const fileName = `${today}-v${versionSlug}.md`;
|
|
@@ -2827,8 +2973,8 @@ ${ko.ship.title}
|
|
|
2827
2973
|
"---",
|
|
2828
2974
|
`*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
2829
2975
|
].join("\n");
|
|
2830
|
-
|
|
2831
|
-
console.log(
|
|
2976
|
+
fs13.writeFileSync(filePath, content, "utf-8");
|
|
2977
|
+
console.log(chalk11.green(`
|
|
2832
2978
|
${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
|
|
2833
2979
|
printNextStep({
|
|
2834
2980
|
message: ko.ship.deployMessage,
|
|
@@ -2839,16 +2985,63 @@ ${ko.ship.title}
|
|
|
2839
2985
|
}
|
|
2840
2986
|
|
|
2841
2987
|
// src/commands/save.ts
|
|
2842
|
-
import { execFileSync
|
|
2843
|
-
import
|
|
2988
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2989
|
+
import chalk12 from "chalk";
|
|
2844
2990
|
import ora from "ora";
|
|
2845
2991
|
import inquirer5 from "inquirer";
|
|
2846
|
-
|
|
2847
|
-
|
|
2992
|
+
|
|
2993
|
+
// src/lib/git-porcelain.ts
|
|
2994
|
+
function normalizePorcelain(raw) {
|
|
2995
|
+
return raw.replace(/\r\n/g, "\n").trimEnd();
|
|
2996
|
+
}
|
|
2997
|
+
function parsePorcelainLines(raw) {
|
|
2998
|
+
return normalizePorcelain(raw).split("\n").filter(Boolean);
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
// src/lib/git-repo.ts
|
|
3002
|
+
import { execFileSync } from "child_process";
|
|
3003
|
+
function getGitRoot(cwd = process.cwd()) {
|
|
3004
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
3005
|
+
encoding: "utf-8",
|
|
3006
|
+
cwd,
|
|
3007
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3008
|
+
}).trim();
|
|
3009
|
+
}
|
|
3010
|
+
function gitOut(args, cwd) {
|
|
3011
|
+
return execFileSync("git", args, {
|
|
3012
|
+
encoding: "utf-8",
|
|
3013
|
+
cwd,
|
|
3014
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3015
|
+
});
|
|
3016
|
+
}
|
|
3017
|
+
function gitRun(args, cwd) {
|
|
3018
|
+
execFileSync("git", args, { stdio: "pipe", cwd });
|
|
3019
|
+
}
|
|
3020
|
+
function getExecErrorMessage(err) {
|
|
3021
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
3022
|
+
const stderr = err.stderr;
|
|
3023
|
+
if (Buffer.isBuffer(stderr)) return stderr.toString("utf-8").trim();
|
|
3024
|
+
if (typeof stderr === "string") return stderr.trim();
|
|
3025
|
+
}
|
|
3026
|
+
return err instanceof Error ? err.message : String(err);
|
|
2848
3027
|
}
|
|
2849
|
-
function
|
|
2850
|
-
|
|
3028
|
+
function hasGitRemote(cwd) {
|
|
3029
|
+
try {
|
|
3030
|
+
return gitOut(["remote"], cwd).trim().length > 0;
|
|
3031
|
+
} catch {
|
|
3032
|
+
return false;
|
|
3033
|
+
}
|
|
2851
3034
|
}
|
|
3035
|
+
function countLocalCommits(cwd) {
|
|
3036
|
+
try {
|
|
3037
|
+
const out = gitOut(["rev-list", "--count", "HEAD"], cwd).trim();
|
|
3038
|
+
return parseInt(out, 10) || 0;
|
|
3039
|
+
} catch {
|
|
3040
|
+
return 0;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
// src/commands/save.ts
|
|
2852
3045
|
function formatDefaultCommitMessage(date = /* @__PURE__ */ new Date()) {
|
|
2853
3046
|
const y = date.getFullYear();
|
|
2854
3047
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
@@ -2864,24 +3057,49 @@ function statusIcon(code) {
|
|
|
2864
3057
|
return "\u{1F4C4}";
|
|
2865
3058
|
}
|
|
2866
3059
|
async function save() {
|
|
2867
|
-
console.log(
|
|
3060
|
+
console.log(chalk12.bold(`
|
|
2868
3061
|
\u{1F4BE} ${t("save.title")}`));
|
|
2869
|
-
console.log(
|
|
3062
|
+
console.log(chalk12.gray("\u2500".repeat(40)));
|
|
3063
|
+
let gitRoot;
|
|
2870
3064
|
try {
|
|
2871
|
-
|
|
3065
|
+
execFileSync2("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3066
|
+
gitRoot = getGitRoot();
|
|
2872
3067
|
} catch {
|
|
2873
|
-
console.log(
|
|
3068
|
+
console.log(chalk12.red(`\u274C ${t("save.notGitRepo")}`));
|
|
2874
3069
|
return;
|
|
2875
3070
|
}
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
3071
|
+
console.log(chalk12.cyan(`
|
|
3072
|
+
\u{1F512} ${t("save.securityWarnHeader")}`));
|
|
3073
|
+
printSecurityWarnings(gitRoot);
|
|
3074
|
+
const severe = filterSevereFindings(scanProjectForSecrets(gitRoot).findings);
|
|
3075
|
+
if (severe.length > 0) {
|
|
3076
|
+
console.log(chalk12.red(`
|
|
3077
|
+
\u26A0\uFE0F ${t("save.secretsFound", severe.length)}`));
|
|
3078
|
+
severe.slice(0, 5).forEach((f) => {
|
|
3079
|
+
console.log(chalk12.dim(` ${f.file}:${f.line} \u2014 ${f.patternName}`));
|
|
3080
|
+
});
|
|
3081
|
+
if (severe.length > 5) {
|
|
3082
|
+
console.log(chalk12.dim(` ... \uC678 ${severe.length - 5}\uAC74 (vhk \uBCF4\uC548 scan)`));
|
|
3083
|
+
}
|
|
3084
|
+
const { proceed } = await inquirer5.prompt([{
|
|
3085
|
+
type: "confirm",
|
|
3086
|
+
name: "proceed",
|
|
3087
|
+
message: t("save.secretsConfirm"),
|
|
3088
|
+
default: false
|
|
3089
|
+
}]);
|
|
3090
|
+
if (!proceed) {
|
|
3091
|
+
console.log(chalk12.gray(t("save.cancelled")));
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
const lines = parsePorcelainLines(gitOut(["status", "--porcelain"], gitRoot));
|
|
3096
|
+
if (lines.length === 0) {
|
|
3097
|
+
console.log(chalk12.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
|
|
2879
3098
|
return;
|
|
2880
3099
|
}
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
files.forEach((line) => {
|
|
3100
|
+
console.log(chalk12.cyan(`
|
|
3101
|
+
\u{1F4CB} ${t("save.filesHeader", lines.length)}`));
|
|
3102
|
+
lines.forEach((line) => {
|
|
2885
3103
|
const code = line.substring(0, 2);
|
|
2886
3104
|
const name = line.substring(3);
|
|
2887
3105
|
console.log(` ${statusIcon(code)} ${name}`);
|
|
@@ -2893,43 +3111,62 @@ async function save() {
|
|
|
2893
3111
|
default: formatDefaultCommitMessage()
|
|
2894
3112
|
}]);
|
|
2895
3113
|
const spinner = ora(t("save.saving")).start();
|
|
3114
|
+
let didAdd = false;
|
|
2896
3115
|
try {
|
|
2897
|
-
gitRun(["add", "."]);
|
|
2898
|
-
|
|
3116
|
+
gitRun(["add", "."], gitRoot);
|
|
3117
|
+
didAdd = true;
|
|
3118
|
+
gitRun(["commit", "-m", message], gitRoot);
|
|
2899
3119
|
spinner.text = t("save.pushing");
|
|
2900
|
-
|
|
2901
|
-
gitRun(["push"]);
|
|
2902
|
-
spinner.succeed(t("save.successWithPush"));
|
|
2903
|
-
} catch {
|
|
3120
|
+
if (!hasGitRemote(gitRoot)) {
|
|
2904
3121
|
spinner.succeed(t("save.successLocal"));
|
|
2905
|
-
console.log(
|
|
3122
|
+
console.log(chalk12.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
|
|
3123
|
+
} else {
|
|
3124
|
+
try {
|
|
3125
|
+
gitRun(["push"], gitRoot);
|
|
3126
|
+
spinner.succeed(t("save.successWithPush"));
|
|
3127
|
+
} catch (pushErr) {
|
|
3128
|
+
spinner.fail(t("save.pushFailed"));
|
|
3129
|
+
console.log(chalk12.red(getExecErrorMessage(pushErr)));
|
|
3130
|
+
console.log(chalk12.yellow(`
|
|
3131
|
+
\u{1F4A1} ${t("save.commitOkPushFailed")}`));
|
|
3132
|
+
process.exitCode = 1;
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
if (process.exitCode !== 1) {
|
|
3136
|
+
console.log(chalk12.green(`
|
|
3137
|
+
\u2705 ${t("save.done", lines.length)}`));
|
|
3138
|
+
} else {
|
|
3139
|
+
console.log(chalk12.green(`
|
|
3140
|
+
\u2705 ${t("save.doneLocalOnly", lines.length)}`));
|
|
2906
3141
|
}
|
|
2907
|
-
console.log(chalk11.green(`
|
|
2908
|
-
\u2705 ${t("save.done", files.length)}`));
|
|
2909
3142
|
} catch (err) {
|
|
2910
3143
|
spinner.fail(t("save.failed"));
|
|
2911
|
-
|
|
2912
|
-
|
|
3144
|
+
console.log(chalk12.red(getExecErrorMessage(err)));
|
|
3145
|
+
if (didAdd) {
|
|
3146
|
+
try {
|
|
3147
|
+
const staged = gitOut(["diff", "--cached", "--stat"], gitRoot).trim();
|
|
3148
|
+
if (staged) {
|
|
3149
|
+
console.log(chalk12.yellow(`
|
|
3150
|
+
\u{1F4A1} ${t("save.stagedAfterFail")}`));
|
|
3151
|
+
}
|
|
3152
|
+
} catch {
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
2913
3155
|
process.exitCode = 1;
|
|
2914
3156
|
}
|
|
2915
3157
|
}
|
|
2916
3158
|
|
|
2917
3159
|
// src/commands/undo.ts
|
|
2918
|
-
import { execFileSync as
|
|
2919
|
-
import
|
|
3160
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
3161
|
+
import chalk13 from "chalk";
|
|
2920
3162
|
import inquirer6 from "inquirer";
|
|
2921
|
-
function gitOut2(args) {
|
|
2922
|
-
return execFileSync2("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
2923
|
-
}
|
|
2924
|
-
function gitRun2(args) {
|
|
2925
|
-
execFileSync2("git", args, { stdio: "pipe" });
|
|
2926
|
-
}
|
|
2927
3163
|
function parseRecentCommits(logOutput) {
|
|
2928
3164
|
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2929
3165
|
}
|
|
2930
|
-
function countUnpushedCommits() {
|
|
3166
|
+
function countUnpushedCommits(gitRoot) {
|
|
3167
|
+
const cwd = gitRoot ?? process.cwd();
|
|
2931
3168
|
try {
|
|
2932
|
-
const out =
|
|
3169
|
+
const out = gitOut(["rev-list", "--count", "@{u}..HEAD"], cwd).trim();
|
|
2933
3170
|
return parseInt(out, 10) || 0;
|
|
2934
3171
|
} catch {
|
|
2935
3172
|
return -1;
|
|
@@ -2939,29 +3176,36 @@ function willUndoPushedCommits(undoCount, unpushedCount) {
|
|
|
2939
3176
|
if (unpushedCount < 0) return false;
|
|
2940
3177
|
return undoCount > unpushedCount;
|
|
2941
3178
|
}
|
|
3179
|
+
function isUndoRisky(undoCount, unpushedCount, hasRemote) {
|
|
3180
|
+
if (willUndoPushedCommits(undoCount, unpushedCount)) return true;
|
|
3181
|
+
if (unpushedCount < 0 && hasRemote) return true;
|
|
3182
|
+
return false;
|
|
3183
|
+
}
|
|
2942
3184
|
async function undo() {
|
|
2943
|
-
console.log(
|
|
3185
|
+
console.log(chalk13.bold(`
|
|
2944
3186
|
\u23EA ${t("undo.title")}`));
|
|
2945
|
-
console.log(
|
|
3187
|
+
console.log(chalk13.gray("\u2500".repeat(40)));
|
|
3188
|
+
let gitRoot;
|
|
2946
3189
|
try {
|
|
2947
|
-
|
|
3190
|
+
execFileSync3("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3191
|
+
gitRoot = getGitRoot();
|
|
2948
3192
|
} catch {
|
|
2949
|
-
console.log(
|
|
3193
|
+
console.log(chalk13.red(`\u274C ${t("undo.notGitRepo")}`));
|
|
2950
3194
|
return;
|
|
2951
3195
|
}
|
|
2952
3196
|
let logOutput;
|
|
2953
3197
|
try {
|
|
2954
|
-
logOutput =
|
|
3198
|
+
logOutput = gitOut(["log", "--oneline", "-5"], gitRoot).trim();
|
|
2955
3199
|
} catch {
|
|
2956
|
-
console.log(
|
|
3200
|
+
console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
2957
3201
|
return;
|
|
2958
3202
|
}
|
|
2959
3203
|
const commits = parseRecentCommits(logOutput);
|
|
2960
3204
|
if (commits.length === 0) {
|
|
2961
|
-
console.log(
|
|
3205
|
+
console.log(chalk13.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
2962
3206
|
return;
|
|
2963
3207
|
}
|
|
2964
|
-
console.log(
|
|
3208
|
+
console.log(chalk13.cyan(`
|
|
2965
3209
|
${t("undo.recentHeader")}`));
|
|
2966
3210
|
commits.forEach((c, i) => {
|
|
2967
3211
|
console.log(` ${i === 0 ? "\u{1F449}" : " "} ${c}`);
|
|
@@ -2976,135 +3220,68 @@ ${t("undo.recentHeader")}`));
|
|
|
2976
3220
|
max: maxUndo
|
|
2977
3221
|
}]);
|
|
2978
3222
|
const undoCount = Math.min(Math.max(1, count || 1), maxUndo);
|
|
2979
|
-
const
|
|
2980
|
-
if (
|
|
2981
|
-
console.log(
|
|
3223
|
+
const headCount = countLocalCommits(gitRoot);
|
|
3224
|
+
if (undoCount >= headCount) {
|
|
3225
|
+
console.log(chalk13.yellow(`
|
|
3226
|
+
\u{1F4ED} ${t("undo.rootCommit")}`));
|
|
3227
|
+
return;
|
|
3228
|
+
}
|
|
3229
|
+
const unpushed = countUnpushedCommits(gitRoot);
|
|
3230
|
+
const remote = hasGitRemote(gitRoot);
|
|
3231
|
+
const risky = isUndoRisky(undoCount, unpushed, remote);
|
|
3232
|
+
if (risky) {
|
|
3233
|
+
if (unpushed < 0) {
|
|
3234
|
+
console.log(chalk13.red(`
|
|
3235
|
+
\u26A0\uFE0F ${t("undo.noUpstreamWarning")}`));
|
|
3236
|
+
} else {
|
|
3237
|
+
console.log(chalk13.red(`
|
|
2982
3238
|
\u26A0\uFE0F ${t("undo.alreadyPushed")}`));
|
|
3239
|
+
}
|
|
2983
3240
|
}
|
|
2984
3241
|
const { confirm } = await inquirer6.prompt([{
|
|
2985
3242
|
type: "confirm",
|
|
2986
3243
|
name: "confirm",
|
|
2987
|
-
message: t("undo.
|
|
3244
|
+
message: risky ? t("undo.confirmRisky", undoCount) : t("undo.confirmMessage"),
|
|
2988
3245
|
default: false
|
|
2989
3246
|
}]);
|
|
2990
3247
|
if (!confirm) {
|
|
2991
|
-
console.log(
|
|
3248
|
+
console.log(chalk13.gray(t("undo.cancelled")));
|
|
2992
3249
|
return;
|
|
2993
3250
|
}
|
|
2994
3251
|
try {
|
|
2995
|
-
|
|
2996
|
-
console.log(
|
|
2997
|
-
\u2705 ${t("undo.success"
|
|
2998
|
-
console.log(
|
|
3252
|
+
gitRun(["reset", "--soft", `HEAD~${undoCount}`], gitRoot);
|
|
3253
|
+
console.log(chalk13.green(`
|
|
3254
|
+
\u2705 ${t("undo.success")}`));
|
|
3255
|
+
console.log(chalk13.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
|
|
3256
|
+
if (risky) {
|
|
3257
|
+
console.log(chalk13.yellow(`
|
|
3258
|
+
\u{1F4A1} ${t("undo.forcePushHint")}`));
|
|
3259
|
+
}
|
|
2999
3260
|
} catch (err) {
|
|
3000
|
-
console.log(
|
|
3261
|
+
console.log(chalk13.red(`\u274C ${t("undo.failed")}`));
|
|
3001
3262
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3002
|
-
console.log(
|
|
3263
|
+
console.log(chalk13.red(msg));
|
|
3003
3264
|
process.exitCode = 1;
|
|
3004
3265
|
}
|
|
3005
3266
|
}
|
|
3006
3267
|
|
|
3007
|
-
// src/commands/diff.ts
|
|
3008
|
-
import { execFileSync as execFileSync3, execSync as execSync4 } from "child_process";
|
|
3009
|
-
import chalk13 from "chalk";
|
|
3010
|
-
function gitOut3(args) {
|
|
3011
|
-
try {
|
|
3012
|
-
return execFileSync3("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3013
|
-
} catch {
|
|
3014
|
-
return "";
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
function parseDiffStat(stat) {
|
|
3018
|
-
const files = [];
|
|
3019
|
-
const lines = stat.split("\n");
|
|
3020
|
-
for (const line of lines) {
|
|
3021
|
-
const match = line.match(/^\s*(.+?)\s*\|\s*(\d+)/);
|
|
3022
|
-
if (!match) continue;
|
|
3023
|
-
const name = match[1].trim();
|
|
3024
|
-
if (name.includes("changed") || name.includes("file")) continue;
|
|
3025
|
-
const plusMatch = line.match(/(\++)/);
|
|
3026
|
-
const minusMatch = line.match(/(\-+)/);
|
|
3027
|
-
files.push({
|
|
3028
|
-
name,
|
|
3029
|
-
additions: plusMatch ? plusMatch[1].length : 0,
|
|
3030
|
-
deletions: minusMatch ? minusMatch[1].length : 0
|
|
3031
|
-
});
|
|
3032
|
-
}
|
|
3033
|
-
return files;
|
|
3034
|
-
}
|
|
3035
|
-
function summarizeNumstat(numstat) {
|
|
3036
|
-
let totalAdd = 0;
|
|
3037
|
-
let totalDel = 0;
|
|
3038
|
-
let fileCount = 0;
|
|
3039
|
-
for (const line of numstat.split("\n").filter(Boolean)) {
|
|
3040
|
-
const [add, del] = line.split(" ");
|
|
3041
|
-
if (add === void 0 || del === void 0) continue;
|
|
3042
|
-
totalAdd += parseInt(add, 10) || 0;
|
|
3043
|
-
totalDel += parseInt(del, 10) || 0;
|
|
3044
|
-
fileCount++;
|
|
3045
|
-
}
|
|
3046
|
-
return { fileCount, totalAdd, totalDel };
|
|
3047
|
-
}
|
|
3048
|
-
function printFile(f) {
|
|
3049
|
-
const adds = f.additions > 0 ? chalk13.green(`+${f.additions}`) : "";
|
|
3050
|
-
const dels = f.deletions > 0 ? chalk13.red(`-${f.deletions}`) : "";
|
|
3051
|
-
const change = [adds, dels].filter(Boolean).join(" ");
|
|
3052
|
-
console.log(` ${f.name} ${change}`);
|
|
3053
|
-
}
|
|
3054
|
-
async function diff() {
|
|
3055
|
-
console.log(chalk13.bold(`
|
|
3056
|
-
\u{1F50D} ${t("diff.title")}`));
|
|
3057
|
-
console.log(chalk13.gray("\u2500".repeat(40)));
|
|
3058
|
-
try {
|
|
3059
|
-
execSync4("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
3060
|
-
} catch {
|
|
3061
|
-
console.log(chalk13.red(`\u274C ${t("diff.notGitRepo")}`));
|
|
3062
|
-
return;
|
|
3063
|
-
}
|
|
3064
|
-
const unstaged = gitOut3(["diff", "--stat"]);
|
|
3065
|
-
const staged = gitOut3(["diff", "--cached", "--stat"]);
|
|
3066
|
-
const untracked = gitOut3(["ls-files", "--others", "--exclude-standard"]);
|
|
3067
|
-
if (!unstaged && !staged && !untracked) {
|
|
3068
|
-
console.log(chalk13.green(`
|
|
3069
|
-
\u2705 ${t("diff.noChanges")}`));
|
|
3070
|
-
return;
|
|
3071
|
-
}
|
|
3072
|
-
if (staged) {
|
|
3073
|
-
console.log(chalk13.cyan(`
|
|
3074
|
-
${t("diff.stagedHeader")}`));
|
|
3075
|
-
parseDiffStat(staged).forEach((f) => printFile(f));
|
|
3076
|
-
}
|
|
3077
|
-
if (unstaged) {
|
|
3078
|
-
console.log(chalk13.cyan(`
|
|
3079
|
-
${t("diff.unstagedHeader")}`));
|
|
3080
|
-
parseDiffStat(unstaged).forEach((f) => printFile(f));
|
|
3081
|
-
}
|
|
3082
|
-
if (untracked) {
|
|
3083
|
-
const files = untracked.split("\n").filter(Boolean);
|
|
3084
|
-
console.log(chalk13.cyan(`
|
|
3085
|
-
${t("diff.untrackedHeader", files.length)}`));
|
|
3086
|
-
files.forEach((f) => console.log(` ${chalk13.green("+")} ${f}`));
|
|
3087
|
-
}
|
|
3088
|
-
const numstat = gitOut3(["diff", "--numstat", "HEAD"]);
|
|
3089
|
-
if (numstat) {
|
|
3090
|
-
const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
|
|
3091
|
-
console.log(chalk13.cyan(`
|
|
3092
|
-
${t("diff.summaryHeader")}`));
|
|
3093
|
-
console.log(` ${t("diff.filesLine", fileCount)}`);
|
|
3094
|
-
console.log(` \uCD94\uAC00: ${chalk13.green(`+${totalAdd}`)}\uC904`);
|
|
3095
|
-
console.log(` \uC0AD\uC81C: ${chalk13.red(`-${totalDel}`)}\uC904`);
|
|
3096
|
-
}
|
|
3097
|
-
console.log("");
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
3268
|
// src/commands/status.ts
|
|
3101
|
-
import { execFileSync as execFileSync4
|
|
3102
|
-
import
|
|
3269
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
3270
|
+
import fs15 from "fs";
|
|
3103
3271
|
import path14 from "path";
|
|
3104
3272
|
import chalk14 from "chalk";
|
|
3105
|
-
|
|
3106
|
-
|
|
3273
|
+
|
|
3274
|
+
// src/lib/read-json.ts
|
|
3275
|
+
import fs14 from "fs";
|
|
3276
|
+
function stripBom(text) {
|
|
3277
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
3278
|
+
}
|
|
3279
|
+
function readJsonFile(filePath) {
|
|
3280
|
+
const raw = stripBom(fs14.readFileSync(filePath, "utf-8"));
|
|
3281
|
+
return JSON.parse(raw);
|
|
3107
3282
|
}
|
|
3283
|
+
|
|
3284
|
+
// src/commands/status.ts
|
|
3108
3285
|
function countFileChanges(porcelain) {
|
|
3109
3286
|
const lines = porcelain.split("\n").filter(Boolean);
|
|
3110
3287
|
let staged = 0;
|
|
@@ -3143,9 +3320,9 @@ function parseRecentCommitLines(logOutput) {
|
|
|
3143
3320
|
}
|
|
3144
3321
|
function readProjectPackage(cwd = process.cwd()) {
|
|
3145
3322
|
const pkgPath = path14.join(cwd, "package.json");
|
|
3146
|
-
if (!
|
|
3323
|
+
if (!fs15.existsSync(pkgPath)) return null;
|
|
3147
3324
|
try {
|
|
3148
|
-
const pkg =
|
|
3325
|
+
const pkg = readJsonFile(pkgPath);
|
|
3149
3326
|
if (!pkg.name && !pkg.version) return null;
|
|
3150
3327
|
return {
|
|
3151
3328
|
name: pkg.name ?? "(no name)",
|
|
@@ -3155,9 +3332,9 @@ function readProjectPackage(cwd = process.cwd()) {
|
|
|
3155
3332
|
return null;
|
|
3156
3333
|
}
|
|
3157
3334
|
}
|
|
3158
|
-
function getSyncCounts() {
|
|
3335
|
+
function getSyncCounts(gitRoot) {
|
|
3159
3336
|
try {
|
|
3160
|
-
const out =
|
|
3337
|
+
const out = gitOut(["rev-list", "--left-right", "--count", "HEAD...@{u}"], gitRoot);
|
|
3161
3338
|
return parseSyncCounts(out);
|
|
3162
3339
|
} catch {
|
|
3163
3340
|
return { ahead: 0, behind: 0, hasUpstream: false };
|
|
@@ -3167,24 +3344,26 @@ async function status() {
|
|
|
3167
3344
|
console.log(chalk14.bold(`
|
|
3168
3345
|
\u{1F4CA} ${t("status.title")}`));
|
|
3169
3346
|
console.log(chalk14.gray("\u2500".repeat(40)));
|
|
3347
|
+
let gitRoot;
|
|
3170
3348
|
try {
|
|
3171
|
-
|
|
3349
|
+
execFileSync4("git", ["rev-parse", "--is-inside-work-tree"], { stdio: "pipe" });
|
|
3350
|
+
gitRoot = getGitRoot();
|
|
3172
3351
|
} catch {
|
|
3173
3352
|
console.log(chalk14.red(`\u274C ${t("status.notGitRepo")}`));
|
|
3174
3353
|
return;
|
|
3175
3354
|
}
|
|
3176
3355
|
let branch;
|
|
3177
3356
|
try {
|
|
3178
|
-
branch =
|
|
3357
|
+
branch = gitOut(["branch", "--show-current"], gitRoot).trim() || t("status.detached");
|
|
3179
3358
|
} catch {
|
|
3180
3359
|
branch = t("status.unknownBranch");
|
|
3181
3360
|
}
|
|
3182
|
-
const porcelain =
|
|
3361
|
+
const porcelain = normalizePorcelain(gitOut(["status", "--porcelain"], gitRoot));
|
|
3183
3362
|
const counts = countFileChanges(porcelain);
|
|
3184
|
-
const sync2 = getSyncCounts();
|
|
3363
|
+
const sync2 = getSyncCounts(gitRoot);
|
|
3185
3364
|
let commits = [];
|
|
3186
3365
|
try {
|
|
3187
|
-
commits = parseRecentCommitLines(
|
|
3366
|
+
commits = parseRecentCommitLines(gitOut(["log", "--oneline", "-3"], gitRoot).trim());
|
|
3188
3367
|
} catch {
|
|
3189
3368
|
commits = [];
|
|
3190
3369
|
}
|
|
@@ -3216,6 +3395,158 @@ async function status() {
|
|
|
3216
3395
|
console.log("");
|
|
3217
3396
|
}
|
|
3218
3397
|
|
|
3398
|
+
// src/commands/diff.ts
|
|
3399
|
+
import { execFileSync as execFileSync5, execSync as execSync2 } from "child_process";
|
|
3400
|
+
import chalk15 from "chalk";
|
|
3401
|
+
function gitOut2(args) {
|
|
3402
|
+
try {
|
|
3403
|
+
return execFileSync5("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3404
|
+
} catch {
|
|
3405
|
+
return "";
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
function parseDiffStat(stat) {
|
|
3409
|
+
const files = [];
|
|
3410
|
+
const lines = stat.split("\n");
|
|
3411
|
+
for (const line of lines) {
|
|
3412
|
+
const match = line.match(/^\s*(.+?)\s*\|\s*(\d+)/);
|
|
3413
|
+
if (!match) continue;
|
|
3414
|
+
const name = match[1].trim();
|
|
3415
|
+
if (name.includes("changed") || name.includes("file")) continue;
|
|
3416
|
+
const plusMatch = line.match(/(\++)/);
|
|
3417
|
+
const minusMatch = line.match(/(\-+)/);
|
|
3418
|
+
files.push({
|
|
3419
|
+
name,
|
|
3420
|
+
additions: plusMatch ? plusMatch[1].length : 0,
|
|
3421
|
+
deletions: minusMatch ? minusMatch[1].length : 0
|
|
3422
|
+
});
|
|
3423
|
+
}
|
|
3424
|
+
return files;
|
|
3425
|
+
}
|
|
3426
|
+
function summarizeNumstat(numstat) {
|
|
3427
|
+
let totalAdd = 0;
|
|
3428
|
+
let totalDel = 0;
|
|
3429
|
+
let fileCount = 0;
|
|
3430
|
+
for (const line of numstat.split("\n").filter(Boolean)) {
|
|
3431
|
+
const [add, del] = line.split(" ");
|
|
3432
|
+
if (add === void 0 || del === void 0) continue;
|
|
3433
|
+
totalAdd += parseInt(add, 10) || 0;
|
|
3434
|
+
totalDel += parseInt(del, 10) || 0;
|
|
3435
|
+
fileCount++;
|
|
3436
|
+
}
|
|
3437
|
+
return { fileCount, totalAdd, totalDel };
|
|
3438
|
+
}
|
|
3439
|
+
function printFile(f) {
|
|
3440
|
+
const adds = f.additions > 0 ? chalk15.green(`+${f.additions}`) : "";
|
|
3441
|
+
const dels = f.deletions > 0 ? chalk15.red(`-${f.deletions}`) : "";
|
|
3442
|
+
const change = [adds, dels].filter(Boolean).join(" ");
|
|
3443
|
+
console.log(` ${f.name} ${change}`);
|
|
3444
|
+
}
|
|
3445
|
+
async function diff() {
|
|
3446
|
+
console.log(chalk15.bold(`
|
|
3447
|
+
\u{1F50D} ${t("diff.title")}`));
|
|
3448
|
+
console.log(chalk15.gray("\u2500".repeat(40)));
|
|
3449
|
+
try {
|
|
3450
|
+
execSync2("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
3451
|
+
} catch {
|
|
3452
|
+
console.log(chalk15.red(`\u274C ${t("diff.notGitRepo")}`));
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
const unstaged = gitOut2(["diff", "--stat"]);
|
|
3456
|
+
const staged = gitOut2(["diff", "--cached", "--stat"]);
|
|
3457
|
+
const untracked = gitOut2(["ls-files", "--others", "--exclude-standard"]);
|
|
3458
|
+
if (!unstaged && !staged && !untracked) {
|
|
3459
|
+
console.log(chalk15.green(`
|
|
3460
|
+
\u2705 ${t("diff.noChanges")}`));
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
if (staged) {
|
|
3464
|
+
console.log(chalk15.cyan(`
|
|
3465
|
+
${t("diff.stagedHeader")}`));
|
|
3466
|
+
parseDiffStat(staged).forEach((f) => printFile(f));
|
|
3467
|
+
}
|
|
3468
|
+
if (unstaged) {
|
|
3469
|
+
console.log(chalk15.cyan(`
|
|
3470
|
+
${t("diff.unstagedHeader")}`));
|
|
3471
|
+
parseDiffStat(unstaged).forEach((f) => printFile(f));
|
|
3472
|
+
}
|
|
3473
|
+
if (untracked) {
|
|
3474
|
+
const files = untracked.split("\n").filter(Boolean);
|
|
3475
|
+
console.log(chalk15.cyan(`
|
|
3476
|
+
${t("diff.untrackedHeader", files.length)}`));
|
|
3477
|
+
files.forEach((f) => console.log(` ${chalk15.green("+")} ${f}`));
|
|
3478
|
+
}
|
|
3479
|
+
const numstat = gitOut2(["diff", "--numstat", "HEAD"]);
|
|
3480
|
+
if (numstat) {
|
|
3481
|
+
const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
|
|
3482
|
+
console.log(chalk15.cyan(`
|
|
3483
|
+
${t("diff.summaryHeader")}`));
|
|
3484
|
+
console.log(` ${t("diff.filesLine", fileCount)}`);
|
|
3485
|
+
console.log(` \uCD94\uAC00: ${chalk15.green(`+${totalAdd}`)}\uC904`);
|
|
3486
|
+
console.log(` \uC0AD\uC81C: ${chalk15.red(`-${totalDel}`)}\uC904`);
|
|
3487
|
+
}
|
|
3488
|
+
console.log("");
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
// src/lib/nlp-run.ts
|
|
3492
|
+
async function dispatchNlpRoute(route, input) {
|
|
3493
|
+
switch (route.command) {
|
|
3494
|
+
case "gate":
|
|
3495
|
+
return gate();
|
|
3496
|
+
case "init":
|
|
3497
|
+
return init({
|
|
3498
|
+
skipGate: route.args?.includes("--skip-gate"),
|
|
3499
|
+
fromNotion: route.args?.includes("--from-notion") ? extractNotionUrl(input) : void 0
|
|
3500
|
+
});
|
|
3501
|
+
case "recap":
|
|
3502
|
+
return recap({});
|
|
3503
|
+
case "sync":
|
|
3504
|
+
return sync();
|
|
3505
|
+
case "check":
|
|
3506
|
+
return check();
|
|
3507
|
+
case "secure":
|
|
3508
|
+
return secure();
|
|
3509
|
+
case "ship":
|
|
3510
|
+
return ship();
|
|
3511
|
+
case "doctor":
|
|
3512
|
+
return doctor();
|
|
3513
|
+
case "save":
|
|
3514
|
+
return save();
|
|
3515
|
+
case "undo":
|
|
3516
|
+
return undo();
|
|
3517
|
+
case "status":
|
|
3518
|
+
return status();
|
|
3519
|
+
case "diff":
|
|
3520
|
+
return diff();
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
async function runNaturalLanguageRoute(input) {
|
|
3524
|
+
const route = routeNaturalLanguage(input);
|
|
3525
|
+
if (!route) {
|
|
3526
|
+
console.log(chalk16.yellow(`
|
|
3527
|
+
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
3528
|
+
`));
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
console.log("");
|
|
3532
|
+
console.log(chalk16.cyan(` \u{1F4AC} "${input}"`));
|
|
3533
|
+
console.log(chalk16.cyan(` \u2192 ${route.explanation}`));
|
|
3534
|
+
if (route.confidence === "low") {
|
|
3535
|
+
const { confirm } = await inquirer7.prompt([{
|
|
3536
|
+
type: "confirm",
|
|
3537
|
+
name: "confirm",
|
|
3538
|
+
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
3539
|
+
default: true
|
|
3540
|
+
}]);
|
|
3541
|
+
if (!confirm) {
|
|
3542
|
+
console.log(chalk16.dim(` ${ko.nlp.menuHint}`));
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
console.log("");
|
|
3547
|
+
await dispatchNlpRoute(route, input);
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3219
3550
|
// src/index.ts
|
|
3220
3551
|
var program = new Command();
|
|
3221
3552
|
var defaultHelp = new Help();
|
|
@@ -3233,7 +3564,7 @@ var KO_ALIASES = {
|
|
|
3233
3564
|
status: "\uC0C1\uD0DC",
|
|
3234
3565
|
diff: "\uBCC0\uACBD"
|
|
3235
3566
|
};
|
|
3236
|
-
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.5.
|
|
3567
|
+
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.5.2");
|
|
3237
3568
|
program.configureHelp({
|
|
3238
3569
|
formatHelp(cmd, helper) {
|
|
3239
3570
|
if (cmd.parent) {
|
|
@@ -3259,7 +3590,7 @@ program.command("init").alias("\uC2DC\uC791").alias("\uB9CC\uB4E4\uAE30").descri
|
|
|
3259
3590
|
program.command("recap").alias("\uC815\uB9AC").alias("\uC624\uB298").description("\uC624\uB298 \uD55C \uC77C \uC815\uB9AC + ADR/\uD2B8\uB7EC\uBE14\uC288\uD305 \uC790\uB3D9 \uBD84\uB9AC").option("--since <date>", "\uBD84\uC11D \uC2DC\uC791\uC77C (YYYY-MM-DD)").action(recap);
|
|
3260
3591
|
program.command("sync").alias("\uB9DE\uCD94\uAE30").alias("\uADDC\uCE59").description("RULES.md \u2192 .cursorrules + CLAUDE.md \uB3D9\uAE30\uD654").action(sync);
|
|
3261
3592
|
program.command("check").alias("\uC810\uAC80").alias("\uB9B0\uD2B8").description("RULES.md \uADDC\uCE59 \uC810\uAC80 \u2014 \uCF54\uB4DC \uC704\uBC18 \uAC80\uC0AC").action(check);
|
|
3262
|
-
var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C");
|
|
3593
|
+
var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C \u2014 scan: \uC2DC\uD06C\uB9BF\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC").action(secure);
|
|
3263
3594
|
secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
|
|
3264
3595
|
program.command("ship").alias("\uBC30\uD3EC").alias("\uB9B4\uB9AC\uC988").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
|
|
3265
3596
|
program.command("doctor").alias("\uD658\uACBD").alias("\uC9C4\uB2E8").description("\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 \u2014 Node/Git/npm \uC0C1\uD0DC \uD655\uC778").action(doctor);
|
|
@@ -3274,62 +3605,14 @@ program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\
|
|
|
3274
3605
|
});
|
|
3275
3606
|
program.command("diff").alias("\uBCC0\uACBD").alias("\uCC28\uC774").description("Git \uBCC0\uACBD\uC0AC\uD56D \uD55C\uAD6D\uC5B4 \uC694\uC57D (staged / unstaged / \uC0C8 \uD30C\uC77C)").action(diff);
|
|
3276
3607
|
program.on("command:*", async (operands) => {
|
|
3277
|
-
const
|
|
3278
|
-
const
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
console.log(chalk15.cyan(` \u{1F4AC} "${input}"`));
|
|
3282
|
-
console.log(chalk15.cyan(` \u2192 ${route.explanation}`));
|
|
3283
|
-
if (route.confidence === "low") {
|
|
3284
|
-
const { confirm } = await inquirer7.prompt([{
|
|
3285
|
-
type: "confirm",
|
|
3286
|
-
name: "confirm",
|
|
3287
|
-
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
3288
|
-
default: true
|
|
3289
|
-
}]);
|
|
3290
|
-
if (!confirm) {
|
|
3291
|
-
console.log(chalk15.dim(` ${ko.nlp.menuHint}`));
|
|
3292
|
-
return;
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
console.log("");
|
|
3296
|
-
switch (route.command) {
|
|
3297
|
-
case "gate":
|
|
3298
|
-
return gate();
|
|
3299
|
-
case "init":
|
|
3300
|
-
return init({
|
|
3301
|
-
skipGate: route.args?.includes("--skip-gate"),
|
|
3302
|
-
fromNotion: route.args?.includes("--from-notion") ? extractNotionUrl(input) : void 0
|
|
3303
|
-
});
|
|
3304
|
-
case "recap":
|
|
3305
|
-
return recap({});
|
|
3306
|
-
case "sync":
|
|
3307
|
-
return sync();
|
|
3308
|
-
case "check":
|
|
3309
|
-
return check();
|
|
3310
|
-
case "secure":
|
|
3311
|
-
return secure();
|
|
3312
|
-
case "ship":
|
|
3313
|
-
return ship();
|
|
3314
|
-
case "doctor":
|
|
3315
|
-
return doctor();
|
|
3316
|
-
case "save":
|
|
3317
|
-
return save();
|
|
3318
|
-
case "undo":
|
|
3319
|
-
return undo();
|
|
3320
|
-
case "status":
|
|
3321
|
-
return status();
|
|
3322
|
-
case "diff":
|
|
3323
|
-
return diff();
|
|
3324
|
-
}
|
|
3325
|
-
}
|
|
3326
|
-
console.log(chalk15.yellow(`
|
|
3327
|
-
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
3328
|
-
`));
|
|
3608
|
+
const unknown = operands[0] ?? "";
|
|
3609
|
+
const rest = operands.slice(1);
|
|
3610
|
+
const input = [unknown, ...rest].join(" ").trim();
|
|
3611
|
+
await runNaturalLanguageRoute(input);
|
|
3329
3612
|
});
|
|
3330
3613
|
program.action(async () => {
|
|
3331
3614
|
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
|
|
3332
|
-
const { choice } = await
|
|
3615
|
+
const { choice } = await inquirer8.prompt([{
|
|
3333
3616
|
type: "list",
|
|
3334
3617
|
name: "choice",
|
|
3335
3618
|
message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
|
@@ -3375,4 +3658,9 @@ program.action(async () => {
|
|
|
3375
3658
|
return diff();
|
|
3376
3659
|
}
|
|
3377
3660
|
});
|
|
3378
|
-
|
|
3661
|
+
var nlInput = detectNaturalLanguageInput(process.argv);
|
|
3662
|
+
if (nlInput !== null) {
|
|
3663
|
+
await runNaturalLanguageRoute(nlInput);
|
|
3664
|
+
} else {
|
|
3665
|
+
await program.parseAsync(process.argv);
|
|
3666
|
+
}
|