@byh3071/vhk 1.6.6 → 1.7.1
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-HLUFOT2T.js → chunk-GXFZ7JXX.js} +1 -0
- package/dist/index.js +369 -23
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
deploy,
|
|
14
14
|
detectExistingRuleFiles,
|
|
15
15
|
ensureInteractive,
|
|
16
|
+
ensureVhkIgnored,
|
|
16
17
|
env,
|
|
17
18
|
envCheck,
|
|
18
19
|
filterSevereFindings,
|
|
@@ -42,7 +43,7 @@ import {
|
|
|
42
43
|
stripBom,
|
|
43
44
|
sync,
|
|
44
45
|
t
|
|
45
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-GXFZ7JXX.js";
|
|
46
47
|
|
|
47
48
|
// src/index.ts
|
|
48
49
|
import { Command, Help } from "commander";
|
|
@@ -2660,7 +2661,9 @@ function generateGateScript(id) {
|
|
|
2660
2661
|
" process.exit(1)",
|
|
2661
2662
|
"}",
|
|
2662
2663
|
"",
|
|
2663
|
-
"
|
|
2664
|
+
"// BOM-safe \uC77D\uAE30: PowerShell Set-Content -Encoding utf8 \uC758 UTF-8 BOM \uC81C\uAC70(\uC5C6\uC73C\uBA74 throw).",
|
|
2665
|
+
"const readJson = (p) => { const t = readFileSync(p, 'utf-8'); return JSON.parse(t.charCodeAt(0) === 0xfeff ? t.slice(1) : t) }",
|
|
2666
|
+
"const pkg = existsSync('package.json') ? readJson('package.json') : {}",
|
|
2664
2667
|
"const scripts = pkg.scripts ?? {}",
|
|
2665
2668
|
"const pm = existsSync('pnpm-lock.yaml') ? 'pnpm' : existsSync('yarn.lock') ? 'yarn' : 'npm'",
|
|
2666
2669
|
"const skipDeep = process.env.VHK_GATES_SKIP_DEEP === '1'",
|
|
@@ -5029,7 +5032,7 @@ function readCloudConfig(rootDir) {
|
|
|
5029
5032
|
const p = path12.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
|
|
5030
5033
|
if (!fs11.existsSync(p)) return null;
|
|
5031
5034
|
try {
|
|
5032
|
-
const parsed =
|
|
5035
|
+
const parsed = readJsonFile(p);
|
|
5033
5036
|
if (parsed && typeof parsed.gistId === "string" && parsed.gistId) {
|
|
5034
5037
|
return { gistId: parsed.gistId };
|
|
5035
5038
|
}
|
|
@@ -5359,30 +5362,373 @@ async function mode(target) {
|
|
|
5359
5362
|
}
|
|
5360
5363
|
|
|
5361
5364
|
// src/commands/verify.ts
|
|
5365
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
5366
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
5367
|
+
import { join as join10 } from "path";
|
|
5362
5368
|
import chalk30 from "chalk";
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5369
|
+
|
|
5370
|
+
// src/commands/verify-report.ts
|
|
5371
|
+
function escapeHtml(text) {
|
|
5372
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5373
|
+
}
|
|
5374
|
+
var STATUS_COLOR = {
|
|
5375
|
+
PASS: { bg: "#16a34a", fg: "#ffffff", label: "PASS" },
|
|
5376
|
+
WARN: { bg: "#d97706", fg: "#ffffff", label: "WARN" },
|
|
5377
|
+
FAIL: { bg: "#dc2626", fg: "#ffffff", label: "FAIL" }
|
|
5378
|
+
};
|
|
5379
|
+
var GATE_STATUS = {
|
|
5380
|
+
pass: { color: "#16a34a", label: "\uD1B5\uACFC" },
|
|
5381
|
+
fail: { color: "#dc2626", label: "\uC2E4\uD328" },
|
|
5382
|
+
skip: { color: "#d97706", label: "\uAC74\uB108\uB700" }
|
|
5383
|
+
};
|
|
5384
|
+
function renderGateRow(g) {
|
|
5385
|
+
const s = GATE_STATUS[g.status];
|
|
5386
|
+
const exit = g.exitCode === null ? "\u2014" : String(g.exitCode);
|
|
5387
|
+
const detail = g.detail ? escapeHtml(g.detail) : "";
|
|
5388
|
+
return ` <tr>
|
|
5389
|
+
<td class="gate-label">${escapeHtml(g.label)}</td>
|
|
5390
|
+
<td><span class="gate-status" style="color:${s.color}">${s.label}</span></td>
|
|
5391
|
+
<td class="gate-exit">${escapeHtml(exit)}</td>
|
|
5392
|
+
<td class="gate-detail">${detail}</td>
|
|
5393
|
+
</tr>`;
|
|
5394
|
+
}
|
|
5395
|
+
function renderReportHtml(report) {
|
|
5396
|
+
const c = STATUS_COLOR[report.status];
|
|
5397
|
+
const s = report.summary;
|
|
5398
|
+
const rows = report.gates.map(renderGateRow).join("\n");
|
|
5399
|
+
const actions = report.nextActions.map((a) => ` <li>${escapeHtml(a)}</li>`).join("\n");
|
|
5400
|
+
return `<!DOCTYPE html>
|
|
5401
|
+
<html lang="ko">
|
|
5402
|
+
<head>
|
|
5403
|
+
<meta charset="utf-8">
|
|
5404
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5405
|
+
<title>vhk verify \uB9AC\uD3EC\uD2B8 \u2014 ${c.label}</title>
|
|
5406
|
+
<style>
|
|
5407
|
+
:root { color-scheme: light dark; }
|
|
5408
|
+
* { box-sizing: border-box; }
|
|
5409
|
+
body {
|
|
5410
|
+
margin: 0; padding: 2rem 1rem;
|
|
5411
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans KR", sans-serif;
|
|
5412
|
+
background: #f8fafc; color: #0f172a; line-height: 1.55;
|
|
5413
|
+
}
|
|
5414
|
+
.wrap { max-width: 760px; margin: 0 auto; }
|
|
5415
|
+
header { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
|
|
5416
|
+
h1 { font-size: 1.25rem; margin: 0; font-weight: 700; }
|
|
5417
|
+
.badge {
|
|
5418
|
+
display: inline-block; padding: 0.35rem 1rem; border-radius: 9999px;
|
|
5419
|
+
font-weight: 800; font-size: 1rem; letter-spacing: 0.05em;
|
|
5420
|
+
background: ${c.bg}; color: ${c.fg};
|
|
5421
|
+
}
|
|
5422
|
+
.summary { color: #475569; font-size: 0.95rem; margin: 0.25rem 0 1.5rem; }
|
|
5423
|
+
.summary strong { color: #0f172a; }
|
|
5424
|
+
table { width: 100%; border-collapse: collapse; margin-bottom: 1.75rem; font-size: 0.95rem; }
|
|
5425
|
+
th, td { text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid #e2e8f0; }
|
|
5426
|
+
th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: #64748b; }
|
|
5427
|
+
.gate-label { font-weight: 600; }
|
|
5428
|
+
.gate-status { font-weight: 700; }
|
|
5429
|
+
.gate-exit { font-variant-numeric: tabular-nums; color: #475569; }
|
|
5430
|
+
.gate-detail { color: #64748b; font-size: 0.88rem; }
|
|
5431
|
+
h2 { font-size: 1rem; margin: 0 0 0.5rem; }
|
|
5432
|
+
ul { margin: 0 0 1.75rem; padding-left: 1.25rem; }
|
|
5433
|
+
li { margin: 0.2rem 0; }
|
|
5434
|
+
footer { color: #94a3b8; font-size: 0.8rem; border-top: 1px solid #e2e8f0; padding-top: 0.75rem; }
|
|
5435
|
+
@media (prefers-color-scheme: dark) {
|
|
5436
|
+
body { background: #0f172a; color: #e2e8f0; }
|
|
5437
|
+
.summary { color: #94a3b8; } .summary strong { color: #e2e8f0; }
|
|
5438
|
+
th { color: #94a3b8; } th, td { border-color: #1e293b; }
|
|
5439
|
+
.gate-exit { color: #94a3b8; } .gate-detail { color: #94a3b8; }
|
|
5440
|
+
footer { color: #64748b; border-color: #1e293b; }
|
|
5441
|
+
}
|
|
5442
|
+
</style>
|
|
5443
|
+
</head>
|
|
5444
|
+
<body>
|
|
5445
|
+
<div class="wrap">
|
|
5446
|
+
<header>
|
|
5447
|
+
<h1>\u{1F50E} vhk verify \uB9AC\uD3EC\uD2B8</h1>
|
|
5448
|
+
<span class="badge">${c.label}</span>
|
|
5449
|
+
</header>
|
|
5450
|
+
<p class="summary">
|
|
5451
|
+
\uAC8C\uC774\uD2B8 <strong>${s.total}</strong>\uAC1C \u2014 \uD1B5\uACFC ${s.pass} / \uC2E4\uD328 ${s.fail} / \uAC74\uB108\uB700 ${s.skip}
|
|
5452
|
+
</p>
|
|
5453
|
+
<table>
|
|
5454
|
+
<thead>
|
|
5455
|
+
<tr><th>\uAC8C\uC774\uD2B8</th><th>\uC0C1\uD0DC</th><th>\uC885\uB8CC\uCF54\uB4DC</th><th>\uBE44\uACE0</th></tr>
|
|
5456
|
+
</thead>
|
|
5457
|
+
<tbody>
|
|
5458
|
+
${rows}
|
|
5459
|
+
</tbody>
|
|
5460
|
+
</table>
|
|
5461
|
+
<h2>\uB2E4\uC74C \uD589\uB3D9</h2>
|
|
5462
|
+
<ul>
|
|
5463
|
+
${actions}
|
|
5464
|
+
</ul>
|
|
5465
|
+
<footer>
|
|
5466
|
+
\uC0DD\uC131: ${escapeHtml(report.generatedAt)} \xB7 \uB0A0\uC9DC: ${escapeHtml(report.date)} \xB7 schema v${report.schemaVersion}
|
|
5467
|
+
</footer>
|
|
5468
|
+
</div>
|
|
5469
|
+
</body>
|
|
5470
|
+
</html>
|
|
5471
|
+
`;
|
|
5472
|
+
}
|
|
5473
|
+
|
|
5474
|
+
// src/commands/verify.ts
|
|
5475
|
+
var REPORT_SCHEMA_VERSION = 1;
|
|
5476
|
+
var REPORT_DIR_REL = join10(".vhk", "reports");
|
|
5477
|
+
var REPORT_PATH_REL = join10(REPORT_DIR_REL, "latest.json");
|
|
5478
|
+
var REPORT_HTML_PATH_REL = join10(REPORT_DIR_REL, "latest.html");
|
|
5479
|
+
var SHIM = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
5480
|
+
function detectPm(cwd) {
|
|
5481
|
+
if (existsSync16(join10(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5482
|
+
if (existsSync16(join10(cwd, "yarn.lock"))) return "yarn";
|
|
5483
|
+
return "npm";
|
|
5484
|
+
}
|
|
5485
|
+
function execGate(cmd, args, cwd) {
|
|
5486
|
+
let bin = cmd;
|
|
5487
|
+
let argv = args;
|
|
5488
|
+
if (process.platform === "win32" && SHIM.has(cmd)) {
|
|
5489
|
+
bin = "cmd.exe";
|
|
5490
|
+
argv = ["/d", "/s", "/c", `${cmd}.cmd`, ...args];
|
|
5491
|
+
}
|
|
5492
|
+
try {
|
|
5493
|
+
execFileSync4(bin, argv, {
|
|
5494
|
+
cwd,
|
|
5495
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5496
|
+
encoding: "utf-8",
|
|
5497
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
5498
|
+
timeout: 6e5,
|
|
5499
|
+
killSignal: "SIGTERM"
|
|
5500
|
+
});
|
|
5501
|
+
return { exitCode: 0, out: "" };
|
|
5502
|
+
} catch (e) {
|
|
5503
|
+
const err = e;
|
|
5504
|
+
const exitCode = typeof err.status === "number" ? err.status : 1;
|
|
5505
|
+
const out = ((err.stdout?.toString?.() ?? "") + (err.stderr?.toString?.() ?? "")).trim();
|
|
5506
|
+
return { exitCode, out };
|
|
5507
|
+
}
|
|
5370
5508
|
}
|
|
5371
|
-
|
|
5372
|
-
|
|
5509
|
+
function runScriptGate(id, label, cwd, pm, argvFor) {
|
|
5510
|
+
const argv = argvFor(pm);
|
|
5511
|
+
if (!argv) {
|
|
5512
|
+
return { id, label, status: "skip", exitCode: null, skipped: true, detail: "\uD574\uB2F9 \uC2A4\uD06C\uB9BD\uD2B8/\uC124\uC815 \uC5C6\uC74C \u2014 skip(WARN)" };
|
|
5513
|
+
}
|
|
5514
|
+
const { exitCode } = execGate(pm, argv, cwd);
|
|
5515
|
+
return {
|
|
5516
|
+
id,
|
|
5517
|
+
label,
|
|
5518
|
+
status: exitCode === 0 ? "pass" : "fail",
|
|
5519
|
+
exitCode,
|
|
5520
|
+
skipped: false,
|
|
5521
|
+
detail: exitCode === 0 ? void 0 : `\uC885\uB8CC\uCF54\uB4DC ${exitCode}`
|
|
5522
|
+
};
|
|
5523
|
+
}
|
|
5524
|
+
function readPackageScripts(cwd) {
|
|
5525
|
+
const pkgPath = join10(cwd, "package.json");
|
|
5526
|
+
if (!existsSync16(pkgPath)) return {};
|
|
5527
|
+
try {
|
|
5528
|
+
const pkg = readJsonFile(pkgPath);
|
|
5529
|
+
return pkg.scripts ?? {};
|
|
5530
|
+
} catch {
|
|
5531
|
+
return {};
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
function runGates(cwd) {
|
|
5535
|
+
const scripts = readPackageScripts(cwd);
|
|
5536
|
+
const pm = detectPm(cwd);
|
|
5537
|
+
const gates = [];
|
|
5538
|
+
gates.push(
|
|
5539
|
+
runScriptGate("typecheck", "tsc --noEmit", cwd, pm, () => {
|
|
5540
|
+
if (scripts.typecheck) return ["run", "typecheck"];
|
|
5541
|
+
if (existsSync16(join10(cwd, "tsconfig.json"))) return pm === "npm" ? ["exec", "--", "tsc", "--noEmit"] : ["exec", "tsc", "--noEmit"];
|
|
5542
|
+
return null;
|
|
5543
|
+
})
|
|
5544
|
+
);
|
|
5545
|
+
gates.push(
|
|
5546
|
+
runScriptGate("test", "test:run", cwd, pm, () => {
|
|
5547
|
+
if (scripts["test:run"]) return ["run", "test:run"];
|
|
5548
|
+
if (scripts.test && /vitest/.test(scripts.test)) return ["run", "test", "--", "--run"];
|
|
5549
|
+
if (scripts.test) return ["run", "test"];
|
|
5550
|
+
return null;
|
|
5551
|
+
})
|
|
5552
|
+
);
|
|
5553
|
+
gates.push(
|
|
5554
|
+
runScriptGate("build", "build", cwd, pm, () => scripts.build ? ["run", "build"] : null)
|
|
5555
|
+
);
|
|
5556
|
+
gates.push(runSecureGate(cwd));
|
|
5557
|
+
return gates;
|
|
5558
|
+
}
|
|
5559
|
+
function runSecureGate(cwd) {
|
|
5560
|
+
try {
|
|
5561
|
+
const severe = filterSevereFindings(scanProjectForSecrets(cwd).findings);
|
|
5562
|
+
const n = severe.length;
|
|
5563
|
+
return {
|
|
5564
|
+
id: "secure",
|
|
5565
|
+
label: "secure scan",
|
|
5566
|
+
status: n === 0 ? "pass" : "fail",
|
|
5567
|
+
exitCode: n === 0 ? 0 : 1,
|
|
5568
|
+
skipped: false,
|
|
5569
|
+
detail: n === 0 ? void 0 : `severe \uC2DC\uD06C\uB9BF ${n}\uAC74 (\uAC12 \uBBF8\uAE30\uB85D \u2014 vhk secure scan \uC73C\uB85C \uD655\uC778)`
|
|
5570
|
+
};
|
|
5571
|
+
} catch (e) {
|
|
5572
|
+
return {
|
|
5573
|
+
id: "secure",
|
|
5574
|
+
label: "secure scan",
|
|
5575
|
+
status: "fail",
|
|
5576
|
+
exitCode: 1,
|
|
5577
|
+
skipped: false,
|
|
5578
|
+
detail: `\uC2A4\uCE94 \uC2E4\uD589 \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
function aggregateStatus(gates) {
|
|
5583
|
+
if (gates.some((g) => g.status === "fail")) return "FAIL";
|
|
5584
|
+
if (gates.some((g) => g.status === "skip")) return "WARN";
|
|
5585
|
+
return "PASS";
|
|
5586
|
+
}
|
|
5587
|
+
function buildNextActions(gates) {
|
|
5588
|
+
const actions = [];
|
|
5589
|
+
for (const g of gates) {
|
|
5590
|
+
if (g.status === "fail") {
|
|
5591
|
+
if (g.id === "secure") actions.push("\uC2DC\uD06C\uB9BF \uC81C\uAC70 \uD6C4 \uC7AC\uAC80\uC99D \u2014 vhk secure scan \uC73C\uB85C \uC704\uCE58 \uD655\uC778");
|
|
5592
|
+
else actions.push(`${g.label} \uC2E4\uD328(\uC885\uB8CC\uCF54\uB4DC ${g.exitCode}) \u2014 \uB85C\uADF8 \uD655\uC778 \uD6C4 \uC218\uC815`);
|
|
5593
|
+
} else if (g.status === "skip") {
|
|
5594
|
+
actions.push(`${g.label} \uAC8C\uC774\uD2B8 \uC5C6\uC74C \u2014 package.json scripts \uC5D0 \uCD94\uAC00\uD558\uBA74 \uAC80\uC99D \uCEE4\uBC84\uB9AC\uC9C0 \u2191`);
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
if (actions.length === 0) actions.push("\uAC80\uC99D \uD1B5\uACFC \u2014 vhk save \uB85C \uC800\uC7A5\uD558\uC138\uC694.");
|
|
5598
|
+
return actions;
|
|
5599
|
+
}
|
|
5600
|
+
function buildReport(gates, generatedAt, date) {
|
|
5601
|
+
const summary = {
|
|
5602
|
+
total: gates.length,
|
|
5603
|
+
pass: gates.filter((g) => g.status === "pass").length,
|
|
5604
|
+
fail: gates.filter((g) => g.status === "fail").length,
|
|
5605
|
+
skip: gates.filter((g) => g.status === "skip").length
|
|
5606
|
+
};
|
|
5607
|
+
return {
|
|
5608
|
+
schemaVersion: REPORT_SCHEMA_VERSION,
|
|
5609
|
+
generatedAt,
|
|
5610
|
+
date,
|
|
5611
|
+
status: aggregateStatus(gates),
|
|
5612
|
+
summary,
|
|
5613
|
+
gates,
|
|
5614
|
+
nextActions: buildNextActions(gates)
|
|
5615
|
+
};
|
|
5616
|
+
}
|
|
5617
|
+
function verifyEvidence(cwd = process.cwd()) {
|
|
5618
|
+
const gates = runGates(cwd);
|
|
5619
|
+
const report = buildReport(gates, (/* @__PURE__ */ new Date()).toISOString(), localDate());
|
|
5620
|
+
const dir = join10(cwd, REPORT_DIR_REL);
|
|
5621
|
+
mkdirSync11(dir, { recursive: true });
|
|
5622
|
+
const path14 = join10(cwd, REPORT_PATH_REL);
|
|
5623
|
+
writeFileSync11(path14, JSON.stringify(report, null, 2) + "\n", "utf-8");
|
|
5624
|
+
try {
|
|
5625
|
+
ensureVhkIgnored(cwd, "reports/");
|
|
5626
|
+
} catch {
|
|
5627
|
+
}
|
|
5628
|
+
return { report, path: REPORT_PATH_REL };
|
|
5629
|
+
}
|
|
5630
|
+
var STATUS_BADGE = {
|
|
5631
|
+
PASS: chalk30.green.bold("PASS"),
|
|
5632
|
+
WARN: chalk30.yellow.bold("WARN"),
|
|
5633
|
+
FAIL: chalk30.red.bold("FAIL")
|
|
5634
|
+
};
|
|
5635
|
+
async function renderVerifyReport(cwd, opts) {
|
|
5636
|
+
const jsonPath = join10(cwd, REPORT_PATH_REL);
|
|
5637
|
+
let report;
|
|
5638
|
+
if (existsSync16(jsonPath)) {
|
|
5639
|
+
try {
|
|
5640
|
+
report = readJsonFile(jsonPath);
|
|
5641
|
+
} catch {
|
|
5642
|
+
console.log(chalk30.yellow(" \u26A0\uFE0F \uAE30\uC874 latest.json \uC190\uC0C1 \u2014 verify \uC7AC\uC2E4\uD589\uC73C\uB85C \uC99D\uAC70\uB97C \uB2E4\uC2DC \uB9CC\uB4ED\uB2C8\uB2E4."));
|
|
5643
|
+
report = verifyEvidence(cwd).report;
|
|
5644
|
+
}
|
|
5645
|
+
} else {
|
|
5646
|
+
console.log(chalk30.dim(" latest.json \uC5C6\uC74C \u2014 verify 1\uD68C \uC120\uC2E4\uD589\uC73C\uB85C \uC99D\uAC70\uB97C \uB9CC\uB4ED\uB2C8\uB2E4."));
|
|
5647
|
+
report = verifyEvidence(cwd).report;
|
|
5648
|
+
}
|
|
5649
|
+
const html = renderReportHtml(report);
|
|
5650
|
+
const htmlPath = join10(cwd, REPORT_HTML_PATH_REL);
|
|
5651
|
+
try {
|
|
5652
|
+
mkdirSync11(join10(cwd, REPORT_DIR_REL), { recursive: true });
|
|
5653
|
+
writeFileSync11(htmlPath, html, "utf-8");
|
|
5654
|
+
} catch (e) {
|
|
5655
|
+
console.error(
|
|
5656
|
+
chalk30.red(` \u274C \uB9AC\uD3EC\uD2B8 HTML \uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (${REPORT_HTML_PATH_REL}): ${e instanceof Error ? e.message : String(e)}`)
|
|
5657
|
+
);
|
|
5658
|
+
console.error(chalk30.dim(" \uD574\uB2F9 \uACBD\uB85C\uC758 \uC4F0\uAE30 \uAD8C\uD55C\uC744 \uD655\uC778\uD558\uC138\uC694."));
|
|
5659
|
+
process.exitCode = 1;
|
|
5660
|
+
return;
|
|
5661
|
+
}
|
|
5662
|
+
console.log(chalk30.bold("\n\u{1F50E} \uAC80\uC99D \uB9AC\uD3EC\uD2B8 (verify --report)"));
|
|
5663
|
+
console.log(` \uACB0\uACFC: ${STATUS_BADGE[report.status]}`);
|
|
5664
|
+
console.log(chalk30.dim(` \u{1F4C4} HTML: ${REPORT_HTML_PATH_REL}`));
|
|
5665
|
+
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
5666
|
+
if (opts.open) {
|
|
5667
|
+
if (isInteractive()) openReportInBrowser(htmlPath);
|
|
5668
|
+
else console.log(chalk30.dim(" (\uBE44\uB300\uD654\uD615/CI/MCP \u2014 --open \uC790\uB3D9 \uC2A4\uD0B5)"));
|
|
5669
|
+
return;
|
|
5670
|
+
}
|
|
5671
|
+
printNextStep({
|
|
5672
|
+
message: "\uB9AC\uD3EC\uD2B8 \uC0DD\uC131 \uC644\uB8CC. \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uB824\uBA74:",
|
|
5673
|
+
command: "vhk verify --report --open",
|
|
5674
|
+
cursorHint: "\uB9AC\uD3EC\uD2B8 \uC5F4\uC5B4\uC918"
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5677
|
+
function openReportInBrowser(filePath) {
|
|
5678
|
+
let result;
|
|
5679
|
+
if (process.platform === "darwin") {
|
|
5680
|
+
result = safeExecFile("open", [filePath]);
|
|
5681
|
+
} else if (process.platform === "win32") {
|
|
5682
|
+
result = safeExecFile("rundll32.exe", ["url.dll,FileProtocolHandler", filePath]);
|
|
5683
|
+
} else {
|
|
5684
|
+
result = safeExecFile("xdg-open", [filePath]);
|
|
5685
|
+
}
|
|
5686
|
+
if (result.ok) console.log(chalk30.green(" \u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
5687
|
+
else console.log(chalk30.yellow(" \u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC704 \uD30C\uC77C\uC744 \uC9C1\uC811 \uC5EC\uC138\uC694."));
|
|
5688
|
+
}
|
|
5689
|
+
async function verify(opts = {}) {
|
|
5690
|
+
if (!ensureNotHardStopped("verify")) return;
|
|
5691
|
+
const cwd = process.cwd();
|
|
5692
|
+
if (opts.report) {
|
|
5693
|
+
await renderVerifyReport(cwd, opts);
|
|
5694
|
+
return;
|
|
5695
|
+
}
|
|
5696
|
+
const { report, path: path14 } = verifyEvidence(cwd);
|
|
5697
|
+
if (opts.json) {
|
|
5698
|
+
console.log(JSON.stringify(report, null, 2));
|
|
5699
|
+
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
5700
|
+
return;
|
|
5701
|
+
}
|
|
5702
|
+
console.log(chalk30.bold("\n\u{1F50E} \uAC80\uC99D \uBB36\uC74C (verify)"));
|
|
5373
5703
|
console.log(chalk30.gray("\u2500".repeat(40)));
|
|
5374
5704
|
const mode2 = readConfig().safetyMode;
|
|
5375
5705
|
console.log(chalk30.dim(` \uD604\uC7AC Safety Mode: ${mode2} \u2014 ${SAFETY_MODE_DESC[mode2]}`));
|
|
5376
|
-
|
|
5377
|
-
for (const
|
|
5378
|
-
|
|
5706
|
+
const icon = (s2) => s2 === "pass" ? chalk30.green("\u2713") : s2 === "fail" ? chalk30.red("\u2717") : chalk30.yellow("\u2298");
|
|
5707
|
+
for (const g of report.gates) {
|
|
5708
|
+
const tail = g.detail ? chalk30.dim(` \u2014 ${g.detail}`) : "";
|
|
5709
|
+
console.log(` ${icon(g.status)} ${g.label}${tail}`);
|
|
5710
|
+
}
|
|
5711
|
+
const s = report.summary;
|
|
5712
|
+
console.log(
|
|
5713
|
+
`
|
|
5714
|
+
\uACB0\uACFC: ${STATUS_BADGE[report.status]} ` + chalk30.dim(`(pass ${s.pass} / fail ${s.fail} / skip ${s.skip}, \uCD1D ${s.total})`)
|
|
5715
|
+
);
|
|
5716
|
+
console.log(chalk30.dim(` \u{1F4C4} \uC99D\uAC70: ${path14}`));
|
|
5717
|
+
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
5718
|
+
if (report.status === "FAIL") {
|
|
5719
|
+
printNextStep({
|
|
5720
|
+
message: "\uAC80\uC99D \uC2E4\uD328 \u2014 \uC544\uB798\uB97C \uBA3C\uC800 \uACE0\uCE58\uC138\uC694:",
|
|
5721
|
+
command: "vhk verify",
|
|
5722
|
+
cursorHint: "\uAC80\uC99D \uB2E4\uC2DC \uB3CC\uB824\uC918",
|
|
5723
|
+
alternative: report.nextActions[0]
|
|
5724
|
+
});
|
|
5725
|
+
} else {
|
|
5726
|
+
printNextStep({
|
|
5727
|
+
message: report.status === "WARN" ? "\uAC80\uC99D \uD1B5\uACFC(\uC77C\uBD80 \uAC8C\uC774\uD2B8 skip). \uC800\uC7A5\uD558\uB824\uBA74:" : "\uAC80\uC99D \uD1B5\uACFC! \uC800\uC7A5\uD558\uB824\uBA74:",
|
|
5728
|
+
command: "vhk save",
|
|
5729
|
+
cursorHint: "\uC800\uC7A5\uD574\uC918"
|
|
5730
|
+
});
|
|
5379
5731
|
}
|
|
5380
|
-
console.log(chalk30.dim("\n \u203B \uBA54\uD0C0\uB7EC\uB108(\uC790\uB3D9 \uC2E4\uD589) \uC790\uB9AC \u2014 \uD604\uC7AC\uB294 \uBB36\uC74C \uC548\uB0B4\uB9CC(lite)."));
|
|
5381
|
-
printNextStep({
|
|
5382
|
-
message: "\uAC80\uC99D \uD1B5\uACFC \uD6C4 \uC800\uC7A5\uD558\uC138\uC694:",
|
|
5383
|
-
command: "vhk save",
|
|
5384
|
-
cursorHint: "\uC800\uC7A5\uD574\uC918"
|
|
5385
|
-
});
|
|
5386
5732
|
}
|
|
5387
5733
|
|
|
5388
5734
|
// src/lib/risk-policy.ts
|
|
@@ -5861,8 +6207,8 @@ program.command("context").alias("\uB9E5\uB77D").option("--compact", "\uD1A0\uD0
|
|
|
5861
6207
|
program.command("mode [target]").alias("\uBAA8\uB4DC").description("Safety Mode \uC870\uD68C/\uBCC0\uACBD (lite|standard|strict) \u2014 \uC704\uD5D8 \uC791\uC5C5 \uAC00\uB4DC \uAC15\uB3C4").action(async (target) => {
|
|
5862
6208
|
await mode(target);
|
|
5863
6209
|
});
|
|
5864
|
-
program.command("verify").alias("\uC0AC\uC804\uC810\uAC80").
|
|
5865
|
-
await verify();
|
|
6210
|
+
program.command("verify").alias("\uC0AC\uC804\uC810\uAC80").option("--json", "\uB9AC\uD3EC\uD2B8 JSON \uC744 stdout \uC73C\uB85C \uCD9C\uB825 (CI\uC6A9 \u2014 \uACBD\uB85C \uB300\uC2E0)").option("--report", "latest.json \uC744 \uC0AC\uB78C\uC6A9 \uC815\uC801 HTML(.vhk/reports/latest.html) \uB85C \uB80C\uB354 (\uC678\uBD80 \uC758\uC874 0)").option("--open", "\uB9AC\uD3EC\uD2B8 \uC0DD\uC131 \uD6C4 \uAE30\uBCF8 \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uAE30 (\uBE44\uB300\uD654\uD615/CI/MCP \uC790\uB3D9 \uC2A4\uD0B5)").description("\uAC80\uC99D \uAC8C\uC774\uD2B8(tsc/test/build/secure) \uC2E4\uC81C \uC2E4\uD589 + \uC99D\uAC70 \uAE30\uB85D (.vhk/reports/latest.json)").action(async (opts) => {
|
|
6211
|
+
await verify(opts);
|
|
5866
6212
|
});
|
|
5867
6213
|
program.command("context-show").alias("\uB9E5\uB77D\uBCF4\uAE30").description("\uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uB0B4\uC6A9 \uCD9C\uB825").action(async () => {
|
|
5868
6214
|
await contextShow();
|
package/dist/mcp/index.js
CHANGED
package/package.json
CHANGED