@byh3071/vhk 1.6.6 → 1.7.0
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 +200 -21
- 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";
|
|
@@ -5359,30 +5360,208 @@ async function mode(target) {
|
|
|
5359
5360
|
}
|
|
5360
5361
|
|
|
5361
5362
|
// src/commands/verify.ts
|
|
5363
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
5364
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
5365
|
+
import { join as join10 } from "path";
|
|
5362
5366
|
import chalk30 from "chalk";
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5367
|
+
var REPORT_SCHEMA_VERSION = 1;
|
|
5368
|
+
var REPORT_DIR_REL = join10(".vhk", "reports");
|
|
5369
|
+
var REPORT_PATH_REL = join10(REPORT_DIR_REL, "latest.json");
|
|
5370
|
+
var SHIM = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
5371
|
+
function detectPm(cwd) {
|
|
5372
|
+
if (existsSync16(join10(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5373
|
+
if (existsSync16(join10(cwd, "yarn.lock"))) return "yarn";
|
|
5374
|
+
return "npm";
|
|
5375
|
+
}
|
|
5376
|
+
function execGate(cmd, args, cwd) {
|
|
5377
|
+
let bin = cmd;
|
|
5378
|
+
let argv = args;
|
|
5379
|
+
if (process.platform === "win32" && SHIM.has(cmd)) {
|
|
5380
|
+
bin = "cmd.exe";
|
|
5381
|
+
argv = ["/d", "/s", "/c", `${cmd}.cmd`, ...args];
|
|
5382
|
+
}
|
|
5383
|
+
try {
|
|
5384
|
+
execFileSync4(bin, argv, {
|
|
5385
|
+
cwd,
|
|
5386
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5387
|
+
encoding: "utf-8",
|
|
5388
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
5389
|
+
timeout: 6e5,
|
|
5390
|
+
killSignal: "SIGTERM"
|
|
5391
|
+
});
|
|
5392
|
+
return { exitCode: 0, out: "" };
|
|
5393
|
+
} catch (e) {
|
|
5394
|
+
const err = e;
|
|
5395
|
+
const exitCode = typeof err.status === "number" ? err.status : 1;
|
|
5396
|
+
const out = ((err.stdout?.toString?.() ?? "") + (err.stderr?.toString?.() ?? "")).trim();
|
|
5397
|
+
return { exitCode, out };
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
function runScriptGate(id, label, cwd, pm, argvFor) {
|
|
5401
|
+
const argv = argvFor(pm);
|
|
5402
|
+
if (!argv) {
|
|
5403
|
+
return { id, label, status: "skip", exitCode: null, skipped: true, detail: "\uD574\uB2F9 \uC2A4\uD06C\uB9BD\uD2B8/\uC124\uC815 \uC5C6\uC74C \u2014 skip(WARN)" };
|
|
5404
|
+
}
|
|
5405
|
+
const { exitCode } = execGate(pm, argv, cwd);
|
|
5406
|
+
return {
|
|
5407
|
+
id,
|
|
5408
|
+
label,
|
|
5409
|
+
status: exitCode === 0 ? "pass" : "fail",
|
|
5410
|
+
exitCode,
|
|
5411
|
+
skipped: false,
|
|
5412
|
+
detail: exitCode === 0 ? void 0 : `\uC885\uB8CC\uCF54\uB4DC ${exitCode}`
|
|
5413
|
+
};
|
|
5414
|
+
}
|
|
5415
|
+
function readPackageScripts(cwd) {
|
|
5416
|
+
const pkgPath = join10(cwd, "package.json");
|
|
5417
|
+
if (!existsSync16(pkgPath)) return {};
|
|
5418
|
+
try {
|
|
5419
|
+
const pkg = readJsonFile(pkgPath);
|
|
5420
|
+
return pkg.scripts ?? {};
|
|
5421
|
+
} catch {
|
|
5422
|
+
return {};
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
function runGates(cwd) {
|
|
5426
|
+
const scripts = readPackageScripts(cwd);
|
|
5427
|
+
const pm = detectPm(cwd);
|
|
5428
|
+
const gates = [];
|
|
5429
|
+
gates.push(
|
|
5430
|
+
runScriptGate("typecheck", "tsc --noEmit", cwd, pm, () => {
|
|
5431
|
+
if (scripts.typecheck) return ["run", "typecheck"];
|
|
5432
|
+
if (existsSync16(join10(cwd, "tsconfig.json"))) return pm === "npm" ? ["exec", "--", "tsc", "--noEmit"] : ["exec", "tsc", "--noEmit"];
|
|
5433
|
+
return null;
|
|
5434
|
+
})
|
|
5435
|
+
);
|
|
5436
|
+
gates.push(
|
|
5437
|
+
runScriptGate("test", "test:run", cwd, pm, () => {
|
|
5438
|
+
if (scripts["test:run"]) return ["run", "test:run"];
|
|
5439
|
+
if (scripts.test && /vitest/.test(scripts.test)) return ["run", "test", "--", "--run"];
|
|
5440
|
+
if (scripts.test) return ["run", "test"];
|
|
5441
|
+
return null;
|
|
5442
|
+
})
|
|
5443
|
+
);
|
|
5444
|
+
gates.push(
|
|
5445
|
+
runScriptGate("build", "build", cwd, pm, () => scripts.build ? ["run", "build"] : null)
|
|
5446
|
+
);
|
|
5447
|
+
gates.push(runSecureGate(cwd));
|
|
5448
|
+
return gates;
|
|
5449
|
+
}
|
|
5450
|
+
function runSecureGate(cwd) {
|
|
5451
|
+
try {
|
|
5452
|
+
const severe = filterSevereFindings(scanProjectForSecrets(cwd).findings);
|
|
5453
|
+
const n = severe.length;
|
|
5454
|
+
return {
|
|
5455
|
+
id: "secure",
|
|
5456
|
+
label: "secure scan",
|
|
5457
|
+
status: n === 0 ? "pass" : "fail",
|
|
5458
|
+
exitCode: n === 0 ? 0 : 1,
|
|
5459
|
+
skipped: false,
|
|
5460
|
+
detail: n === 0 ? void 0 : `severe \uC2DC\uD06C\uB9BF ${n}\uAC74 (\uAC12 \uBBF8\uAE30\uB85D \u2014 vhk secure scan \uC73C\uB85C \uD655\uC778)`
|
|
5461
|
+
};
|
|
5462
|
+
} catch (e) {
|
|
5463
|
+
return {
|
|
5464
|
+
id: "secure",
|
|
5465
|
+
label: "secure scan",
|
|
5466
|
+
status: "fail",
|
|
5467
|
+
exitCode: 1,
|
|
5468
|
+
skipped: false,
|
|
5469
|
+
detail: `\uC2A4\uCE94 \uC2E4\uD589 \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`
|
|
5470
|
+
};
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
function aggregateStatus(gates) {
|
|
5474
|
+
if (gates.some((g) => g.status === "fail")) return "FAIL";
|
|
5475
|
+
if (gates.some((g) => g.status === "skip")) return "WARN";
|
|
5476
|
+
return "PASS";
|
|
5370
5477
|
}
|
|
5371
|
-
|
|
5372
|
-
|
|
5478
|
+
function buildNextActions(gates) {
|
|
5479
|
+
const actions = [];
|
|
5480
|
+
for (const g of gates) {
|
|
5481
|
+
if (g.status === "fail") {
|
|
5482
|
+
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");
|
|
5483
|
+
else actions.push(`${g.label} \uC2E4\uD328(\uC885\uB8CC\uCF54\uB4DC ${g.exitCode}) \u2014 \uB85C\uADF8 \uD655\uC778 \uD6C4 \uC218\uC815`);
|
|
5484
|
+
} else if (g.status === "skip") {
|
|
5485
|
+
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`);
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
if (actions.length === 0) actions.push("\uAC80\uC99D \uD1B5\uACFC \u2014 vhk save \uB85C \uC800\uC7A5\uD558\uC138\uC694.");
|
|
5489
|
+
return actions;
|
|
5490
|
+
}
|
|
5491
|
+
function buildReport(gates, generatedAt, date) {
|
|
5492
|
+
const summary = {
|
|
5493
|
+
total: gates.length,
|
|
5494
|
+
pass: gates.filter((g) => g.status === "pass").length,
|
|
5495
|
+
fail: gates.filter((g) => g.status === "fail").length,
|
|
5496
|
+
skip: gates.filter((g) => g.status === "skip").length
|
|
5497
|
+
};
|
|
5498
|
+
return {
|
|
5499
|
+
schemaVersion: REPORT_SCHEMA_VERSION,
|
|
5500
|
+
generatedAt,
|
|
5501
|
+
date,
|
|
5502
|
+
status: aggregateStatus(gates),
|
|
5503
|
+
summary,
|
|
5504
|
+
gates,
|
|
5505
|
+
nextActions: buildNextActions(gates)
|
|
5506
|
+
};
|
|
5507
|
+
}
|
|
5508
|
+
function verifyEvidence(cwd = process.cwd()) {
|
|
5509
|
+
const gates = runGates(cwd);
|
|
5510
|
+
const report = buildReport(gates, (/* @__PURE__ */ new Date()).toISOString(), localDate());
|
|
5511
|
+
const dir = join10(cwd, REPORT_DIR_REL);
|
|
5512
|
+
mkdirSync11(dir, { recursive: true });
|
|
5513
|
+
const path14 = join10(cwd, REPORT_PATH_REL);
|
|
5514
|
+
writeFileSync11(path14, JSON.stringify(report, null, 2) + "\n", "utf-8");
|
|
5515
|
+
try {
|
|
5516
|
+
ensureVhkIgnored(cwd, "reports/");
|
|
5517
|
+
} catch {
|
|
5518
|
+
}
|
|
5519
|
+
return { report, path: REPORT_PATH_REL };
|
|
5520
|
+
}
|
|
5521
|
+
var STATUS_BADGE = {
|
|
5522
|
+
PASS: chalk30.green.bold("PASS"),
|
|
5523
|
+
WARN: chalk30.yellow.bold("WARN"),
|
|
5524
|
+
FAIL: chalk30.red.bold("FAIL")
|
|
5525
|
+
};
|
|
5526
|
+
async function verify(opts = {}) {
|
|
5527
|
+
if (!ensureNotHardStopped("verify")) return;
|
|
5528
|
+
const cwd = process.cwd();
|
|
5529
|
+
const { report, path: path14 } = verifyEvidence(cwd);
|
|
5530
|
+
if (opts.json) {
|
|
5531
|
+
console.log(JSON.stringify(report, null, 2));
|
|
5532
|
+
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
5533
|
+
return;
|
|
5534
|
+
}
|
|
5535
|
+
console.log(chalk30.bold("\n\u{1F50E} \uAC80\uC99D \uBB36\uC74C (verify)"));
|
|
5373
5536
|
console.log(chalk30.gray("\u2500".repeat(40)));
|
|
5374
5537
|
const mode2 = readConfig().safetyMode;
|
|
5375
5538
|
console.log(chalk30.dim(` \uD604\uC7AC Safety Mode: ${mode2} \u2014 ${SAFETY_MODE_DESC[mode2]}`));
|
|
5376
|
-
|
|
5377
|
-
for (const
|
|
5378
|
-
|
|
5539
|
+
const icon = (s2) => s2 === "pass" ? chalk30.green("\u2713") : s2 === "fail" ? chalk30.red("\u2717") : chalk30.yellow("\u2298");
|
|
5540
|
+
for (const g of report.gates) {
|
|
5541
|
+
const tail = g.detail ? chalk30.dim(` \u2014 ${g.detail}`) : "";
|
|
5542
|
+
console.log(` ${icon(g.status)} ${g.label}${tail}`);
|
|
5543
|
+
}
|
|
5544
|
+
const s = report.summary;
|
|
5545
|
+
console.log(
|
|
5546
|
+
`
|
|
5547
|
+
\uACB0\uACFC: ${STATUS_BADGE[report.status]} ` + chalk30.dim(`(pass ${s.pass} / fail ${s.fail} / skip ${s.skip}, \uCD1D ${s.total})`)
|
|
5548
|
+
);
|
|
5549
|
+
console.log(chalk30.dim(` \u{1F4C4} \uC99D\uAC70: ${path14}`));
|
|
5550
|
+
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
5551
|
+
if (report.status === "FAIL") {
|
|
5552
|
+
printNextStep({
|
|
5553
|
+
message: "\uAC80\uC99D \uC2E4\uD328 \u2014 \uC544\uB798\uB97C \uBA3C\uC800 \uACE0\uCE58\uC138\uC694:",
|
|
5554
|
+
command: "vhk verify",
|
|
5555
|
+
cursorHint: "\uAC80\uC99D \uB2E4\uC2DC \uB3CC\uB824\uC918",
|
|
5556
|
+
alternative: report.nextActions[0]
|
|
5557
|
+
});
|
|
5558
|
+
} else {
|
|
5559
|
+
printNextStep({
|
|
5560
|
+
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:",
|
|
5561
|
+
command: "vhk save",
|
|
5562
|
+
cursorHint: "\uC800\uC7A5\uD574\uC918"
|
|
5563
|
+
});
|
|
5379
5564
|
}
|
|
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
5565
|
}
|
|
5387
5566
|
|
|
5388
5567
|
// src/lib/risk-policy.ts
|
|
@@ -5861,8 +6040,8 @@ program.command("context").alias("\uB9E5\uB77D").option("--compact", "\uD1A0\uD0
|
|
|
5861
6040
|
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
6041
|
await mode(target);
|
|
5863
6042
|
});
|
|
5864
|
-
program.command("verify").alias("\uC0AC\uC804\uC810\uAC80").
|
|
5865
|
-
await verify();
|
|
6043
|
+
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)").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) => {
|
|
6044
|
+
await verify(opts);
|
|
5866
6045
|
});
|
|
5867
6046
|
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
6047
|
await contextShow();
|
package/dist/mcp/index.js
CHANGED
package/package.json
CHANGED