@byh3071/vhk 1.7.0 → 1.8.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/index.js +485 -34
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
// src/index.ts
|
|
49
49
|
import { Command, Help } from "commander";
|
|
50
50
|
import { pathToFileURL } from "url";
|
|
51
|
-
import
|
|
51
|
+
import chalk34 from "chalk";
|
|
52
52
|
import inquirer13 from "inquirer";
|
|
53
53
|
|
|
54
54
|
// src/lib/nlp-router.ts
|
|
@@ -123,6 +123,12 @@ var RULES = [
|
|
|
123
123
|
confidence: "high",
|
|
124
124
|
test: (t2) => /검증\s*묶음|사전\s*검증|저장\s*전\s*(검증|확인)|^verify$/.test(t2)
|
|
125
125
|
},
|
|
126
|
+
{
|
|
127
|
+
command: "review",
|
|
128
|
+
explanation: "\uC801\uB300\uC801 \uC790\uAE30\uAC80\uC99D \u2014 \uC99D\uAC70\uB85C \uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC (vhk review)",
|
|
129
|
+
confidence: "high",
|
|
130
|
+
test: (t2) => /적대\s*검증|자기\s*검증|거짓\s*완료|완료\s*심문|^review$|^검토$/.test(t2)
|
|
131
|
+
},
|
|
126
132
|
{
|
|
127
133
|
command: "init",
|
|
128
134
|
explanation: "\uBB38\uC11C/\uD558\uB124\uC2A4 \uD30C\uC77C\uB9CC \uC0DD\uC131 (vhk init) \u2014 git/MCP/context\uB294 \uC81C\uC678",
|
|
@@ -468,6 +474,8 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
468
474
|
"\uBAA8\uB4DC",
|
|
469
475
|
"verify",
|
|
470
476
|
"\uC0AC\uC804\uC810\uAC80",
|
|
477
|
+
"review",
|
|
478
|
+
"\uAC80\uD1A0",
|
|
471
479
|
"help"
|
|
472
480
|
]);
|
|
473
481
|
function isOptionToken(token) {
|
|
@@ -505,7 +513,7 @@ function detectNaturalLanguageInput(argv) {
|
|
|
505
513
|
}
|
|
506
514
|
|
|
507
515
|
// src/lib/nlp-run.ts
|
|
508
|
-
import
|
|
516
|
+
import chalk32 from "chalk";
|
|
509
517
|
import inquirer12 from "inquirer";
|
|
510
518
|
|
|
511
519
|
// src/commands/gate.ts
|
|
@@ -2661,7 +2669,9 @@ function generateGateScript(id) {
|
|
|
2661
2669
|
" process.exit(1)",
|
|
2662
2670
|
"}",
|
|
2663
2671
|
"",
|
|
2664
|
-
"
|
|
2672
|
+
"// BOM-safe \uC77D\uAE30: PowerShell Set-Content -Encoding utf8 \uC758 UTF-8 BOM \uC81C\uAC70(\uC5C6\uC73C\uBA74 throw).",
|
|
2673
|
+
"const readJson = (p) => { const t = readFileSync(p, 'utf-8'); return JSON.parse(t.charCodeAt(0) === 0xfeff ? t.slice(1) : t) }",
|
|
2674
|
+
"const pkg = existsSync('package.json') ? readJson('package.json') : {}",
|
|
2665
2675
|
"const scripts = pkg.scripts ?? {}",
|
|
2666
2676
|
"const pm = existsSync('pnpm-lock.yaml') ? 'pnpm' : existsSync('yarn.lock') ? 'yarn' : 'npm'",
|
|
2667
2677
|
"const skipDeep = process.env.VHK_GATES_SKIP_DEEP === '1'",
|
|
@@ -5030,7 +5040,7 @@ function readCloudConfig(rootDir) {
|
|
|
5030
5040
|
const p = path12.join(rootDir, VHK_DIR2, CLOUD_CONFIG_FILE);
|
|
5031
5041
|
if (!fs11.existsSync(p)) return null;
|
|
5032
5042
|
try {
|
|
5033
|
-
const parsed =
|
|
5043
|
+
const parsed = readJsonFile(p);
|
|
5034
5044
|
if (parsed && typeof parsed.gistId === "string" && parsed.gistId) {
|
|
5035
5045
|
return { gistId: parsed.gistId };
|
|
5036
5046
|
}
|
|
@@ -5364,9 +5374,116 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
5364
5374
|
import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
5365
5375
|
import { join as join10 } from "path";
|
|
5366
5376
|
import chalk30 from "chalk";
|
|
5377
|
+
|
|
5378
|
+
// src/commands/verify-report.ts
|
|
5379
|
+
function escapeHtml(text) {
|
|
5380
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5381
|
+
}
|
|
5382
|
+
var STATUS_COLOR = {
|
|
5383
|
+
PASS: { bg: "#16a34a", fg: "#ffffff", label: "PASS" },
|
|
5384
|
+
WARN: { bg: "#d97706", fg: "#ffffff", label: "WARN" },
|
|
5385
|
+
FAIL: { bg: "#dc2626", fg: "#ffffff", label: "FAIL" }
|
|
5386
|
+
};
|
|
5387
|
+
var GATE_STATUS = {
|
|
5388
|
+
pass: { color: "#16a34a", label: "\uD1B5\uACFC" },
|
|
5389
|
+
fail: { color: "#dc2626", label: "\uC2E4\uD328" },
|
|
5390
|
+
skip: { color: "#d97706", label: "\uAC74\uB108\uB700" }
|
|
5391
|
+
};
|
|
5392
|
+
function renderGateRow(g) {
|
|
5393
|
+
const s = GATE_STATUS[g.status];
|
|
5394
|
+
const exit = g.exitCode === null ? "\u2014" : String(g.exitCode);
|
|
5395
|
+
const detail = g.detail ? escapeHtml(g.detail) : "";
|
|
5396
|
+
return ` <tr>
|
|
5397
|
+
<td class="gate-label">${escapeHtml(g.label)}</td>
|
|
5398
|
+
<td><span class="gate-status" style="color:${s.color}">${s.label}</span></td>
|
|
5399
|
+
<td class="gate-exit">${escapeHtml(exit)}</td>
|
|
5400
|
+
<td class="gate-detail">${detail}</td>
|
|
5401
|
+
</tr>`;
|
|
5402
|
+
}
|
|
5403
|
+
function renderReportHtml(report) {
|
|
5404
|
+
const c = STATUS_COLOR[report.status];
|
|
5405
|
+
const s = report.summary;
|
|
5406
|
+
const rows = report.gates.map(renderGateRow).join("\n");
|
|
5407
|
+
const actions = report.nextActions.map((a) => ` <li>${escapeHtml(a)}</li>`).join("\n");
|
|
5408
|
+
return `<!DOCTYPE html>
|
|
5409
|
+
<html lang="ko">
|
|
5410
|
+
<head>
|
|
5411
|
+
<meta charset="utf-8">
|
|
5412
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5413
|
+
<title>vhk verify \uB9AC\uD3EC\uD2B8 \u2014 ${c.label}</title>
|
|
5414
|
+
<style>
|
|
5415
|
+
:root { color-scheme: light dark; }
|
|
5416
|
+
* { box-sizing: border-box; }
|
|
5417
|
+
body {
|
|
5418
|
+
margin: 0; padding: 2rem 1rem;
|
|
5419
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans KR", sans-serif;
|
|
5420
|
+
background: #f8fafc; color: #0f172a; line-height: 1.55;
|
|
5421
|
+
}
|
|
5422
|
+
.wrap { max-width: 760px; margin: 0 auto; }
|
|
5423
|
+
header { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
|
|
5424
|
+
h1 { font-size: 1.25rem; margin: 0; font-weight: 700; }
|
|
5425
|
+
.badge {
|
|
5426
|
+
display: inline-block; padding: 0.35rem 1rem; border-radius: 9999px;
|
|
5427
|
+
font-weight: 800; font-size: 1rem; letter-spacing: 0.05em;
|
|
5428
|
+
background: ${c.bg}; color: ${c.fg};
|
|
5429
|
+
}
|
|
5430
|
+
.summary { color: #475569; font-size: 0.95rem; margin: 0.25rem 0 1.5rem; }
|
|
5431
|
+
.summary strong { color: #0f172a; }
|
|
5432
|
+
table { width: 100%; border-collapse: collapse; margin-bottom: 1.75rem; font-size: 0.95rem; }
|
|
5433
|
+
th, td { text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid #e2e8f0; }
|
|
5434
|
+
th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: #64748b; }
|
|
5435
|
+
.gate-label { font-weight: 600; }
|
|
5436
|
+
.gate-status { font-weight: 700; }
|
|
5437
|
+
.gate-exit { font-variant-numeric: tabular-nums; color: #475569; }
|
|
5438
|
+
.gate-detail { color: #64748b; font-size: 0.88rem; }
|
|
5439
|
+
h2 { font-size: 1rem; margin: 0 0 0.5rem; }
|
|
5440
|
+
ul { margin: 0 0 1.75rem; padding-left: 1.25rem; }
|
|
5441
|
+
li { margin: 0.2rem 0; }
|
|
5442
|
+
footer { color: #94a3b8; font-size: 0.8rem; border-top: 1px solid #e2e8f0; padding-top: 0.75rem; }
|
|
5443
|
+
@media (prefers-color-scheme: dark) {
|
|
5444
|
+
body { background: #0f172a; color: #e2e8f0; }
|
|
5445
|
+
.summary { color: #94a3b8; } .summary strong { color: #e2e8f0; }
|
|
5446
|
+
th { color: #94a3b8; } th, td { border-color: #1e293b; }
|
|
5447
|
+
.gate-exit { color: #94a3b8; } .gate-detail { color: #94a3b8; }
|
|
5448
|
+
footer { color: #64748b; border-color: #1e293b; }
|
|
5449
|
+
}
|
|
5450
|
+
</style>
|
|
5451
|
+
</head>
|
|
5452
|
+
<body>
|
|
5453
|
+
<div class="wrap">
|
|
5454
|
+
<header>
|
|
5455
|
+
<h1>\u{1F50E} vhk verify \uB9AC\uD3EC\uD2B8</h1>
|
|
5456
|
+
<span class="badge">${c.label}</span>
|
|
5457
|
+
</header>
|
|
5458
|
+
<p class="summary">
|
|
5459
|
+
\uAC8C\uC774\uD2B8 <strong>${s.total}</strong>\uAC1C \u2014 \uD1B5\uACFC ${s.pass} / \uC2E4\uD328 ${s.fail} / \uAC74\uB108\uB700 ${s.skip}
|
|
5460
|
+
</p>
|
|
5461
|
+
<table>
|
|
5462
|
+
<thead>
|
|
5463
|
+
<tr><th>\uAC8C\uC774\uD2B8</th><th>\uC0C1\uD0DC</th><th>\uC885\uB8CC\uCF54\uB4DC</th><th>\uBE44\uACE0</th></tr>
|
|
5464
|
+
</thead>
|
|
5465
|
+
<tbody>
|
|
5466
|
+
${rows}
|
|
5467
|
+
</tbody>
|
|
5468
|
+
</table>
|
|
5469
|
+
<h2>\uB2E4\uC74C \uD589\uB3D9</h2>
|
|
5470
|
+
<ul>
|
|
5471
|
+
${actions}
|
|
5472
|
+
</ul>
|
|
5473
|
+
<footer>
|
|
5474
|
+
\uC0DD\uC131: ${escapeHtml(report.generatedAt)} \xB7 \uB0A0\uC9DC: ${escapeHtml(report.date)} \xB7 schema v${report.schemaVersion}
|
|
5475
|
+
</footer>
|
|
5476
|
+
</div>
|
|
5477
|
+
</body>
|
|
5478
|
+
</html>
|
|
5479
|
+
`;
|
|
5480
|
+
}
|
|
5481
|
+
|
|
5482
|
+
// src/commands/verify.ts
|
|
5367
5483
|
var REPORT_SCHEMA_VERSION = 1;
|
|
5368
5484
|
var REPORT_DIR_REL = join10(".vhk", "reports");
|
|
5369
5485
|
var REPORT_PATH_REL = join10(REPORT_DIR_REL, "latest.json");
|
|
5486
|
+
var REPORT_HTML_PATH_REL = join10(REPORT_DIR_REL, "latest.html");
|
|
5370
5487
|
var SHIM = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
5371
5488
|
function detectPm(cwd) {
|
|
5372
5489
|
if (existsSync16(join10(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
@@ -5523,9 +5640,67 @@ var STATUS_BADGE = {
|
|
|
5523
5640
|
WARN: chalk30.yellow.bold("WARN"),
|
|
5524
5641
|
FAIL: chalk30.red.bold("FAIL")
|
|
5525
5642
|
};
|
|
5643
|
+
async function renderVerifyReport(cwd, opts) {
|
|
5644
|
+
const jsonPath = join10(cwd, REPORT_PATH_REL);
|
|
5645
|
+
let report;
|
|
5646
|
+
if (existsSync16(jsonPath)) {
|
|
5647
|
+
try {
|
|
5648
|
+
report = readJsonFile(jsonPath);
|
|
5649
|
+
} catch {
|
|
5650
|
+
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."));
|
|
5651
|
+
report = verifyEvidence(cwd).report;
|
|
5652
|
+
}
|
|
5653
|
+
} else {
|
|
5654
|
+
console.log(chalk30.dim(" latest.json \uC5C6\uC74C \u2014 verify 1\uD68C \uC120\uC2E4\uD589\uC73C\uB85C \uC99D\uAC70\uB97C \uB9CC\uB4ED\uB2C8\uB2E4."));
|
|
5655
|
+
report = verifyEvidence(cwd).report;
|
|
5656
|
+
}
|
|
5657
|
+
const html = renderReportHtml(report);
|
|
5658
|
+
const htmlPath = join10(cwd, REPORT_HTML_PATH_REL);
|
|
5659
|
+
try {
|
|
5660
|
+
mkdirSync11(join10(cwd, REPORT_DIR_REL), { recursive: true });
|
|
5661
|
+
writeFileSync11(htmlPath, html, "utf-8");
|
|
5662
|
+
} catch (e) {
|
|
5663
|
+
console.error(
|
|
5664
|
+
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)}`)
|
|
5665
|
+
);
|
|
5666
|
+
console.error(chalk30.dim(" \uD574\uB2F9 \uACBD\uB85C\uC758 \uC4F0\uAE30 \uAD8C\uD55C\uC744 \uD655\uC778\uD558\uC138\uC694."));
|
|
5667
|
+
process.exitCode = 1;
|
|
5668
|
+
return;
|
|
5669
|
+
}
|
|
5670
|
+
console.log(chalk30.bold("\n\u{1F50E} \uAC80\uC99D \uB9AC\uD3EC\uD2B8 (verify --report)"));
|
|
5671
|
+
console.log(` \uACB0\uACFC: ${STATUS_BADGE[report.status]}`);
|
|
5672
|
+
console.log(chalk30.dim(` \u{1F4C4} HTML: ${REPORT_HTML_PATH_REL}`));
|
|
5673
|
+
process.exitCode = report.status === "FAIL" ? 1 : 0;
|
|
5674
|
+
if (opts.open) {
|
|
5675
|
+
if (isInteractive()) openReportInBrowser(htmlPath);
|
|
5676
|
+
else console.log(chalk30.dim(" (\uBE44\uB300\uD654\uD615/CI/MCP \u2014 --open \uC790\uB3D9 \uC2A4\uD0B5)"));
|
|
5677
|
+
return;
|
|
5678
|
+
}
|
|
5679
|
+
printNextStep({
|
|
5680
|
+
message: "\uB9AC\uD3EC\uD2B8 \uC0DD\uC131 \uC644\uB8CC. \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uB824\uBA74:",
|
|
5681
|
+
command: "vhk verify --report --open",
|
|
5682
|
+
cursorHint: "\uB9AC\uD3EC\uD2B8 \uC5F4\uC5B4\uC918"
|
|
5683
|
+
});
|
|
5684
|
+
}
|
|
5685
|
+
function openReportInBrowser(filePath) {
|
|
5686
|
+
let result;
|
|
5687
|
+
if (process.platform === "darwin") {
|
|
5688
|
+
result = safeExecFile("open", [filePath]);
|
|
5689
|
+
} else if (process.platform === "win32") {
|
|
5690
|
+
result = safeExecFile("rundll32.exe", ["url.dll,FileProtocolHandler", filePath]);
|
|
5691
|
+
} else {
|
|
5692
|
+
result = safeExecFile("xdg-open", [filePath]);
|
|
5693
|
+
}
|
|
5694
|
+
if (result.ok) console.log(chalk30.green(" \u2705 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
5695
|
+
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."));
|
|
5696
|
+
}
|
|
5526
5697
|
async function verify(opts = {}) {
|
|
5527
5698
|
if (!ensureNotHardStopped("verify")) return;
|
|
5528
5699
|
const cwd = process.cwd();
|
|
5700
|
+
if (opts.report) {
|
|
5701
|
+
await renderVerifyReport(cwd, opts);
|
|
5702
|
+
return;
|
|
5703
|
+
}
|
|
5529
5704
|
const { report, path: path14 } = verifyEvidence(cwd);
|
|
5530
5705
|
if (opts.json) {
|
|
5531
5706
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -5564,6 +5739,276 @@ async function verify(opts = {}) {
|
|
|
5564
5739
|
}
|
|
5565
5740
|
}
|
|
5566
5741
|
|
|
5742
|
+
// src/commands/review.ts
|
|
5743
|
+
import { existsSync as existsSync17, writeFileSync as writeFileSync12 } from "fs";
|
|
5744
|
+
import { join as join11 } from "path";
|
|
5745
|
+
import chalk31 from "chalk";
|
|
5746
|
+
var GOALS_DIR2 = "goals";
|
|
5747
|
+
var COVERAGE_MIN = 0.5;
|
|
5748
|
+
var STALE_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
5749
|
+
var REVIEW_DISCLAIMER = [
|
|
5750
|
+
"\u26A0\uFE0F \uC774 \uD310\uC815\uC740 \uBCF4\uC7A5\uC774 \uC544\uB2C8\uB77C \uC2E0\uB8B0\uB3C4 \uC2E0\uD638\uC785\uB2C8\uB2E4 \u2014 \uD1B5\uACFC\uD574\uB3C4 \uAC70\uC9D3\uC644\uB8CC \uAC00\uB2A5\uC131\uC740 \uB0A8\uC2B5\uB2C8\uB2E4.",
|
|
5751
|
+
' \xB7 \uAE30\uB2A5 \uACE0\uC720 \uC644\uB8CC\uC870\uAC74\uC740 \uAC8C\uC774\uD2B8 \uD0A4\uC6CC\uB4DC(tsc/test/build/secure)\uC5D0 \uB9E4\uD551\uB418\uC9C0 \uC54A\uC73C\uBA74 "\uBBF8\uAC80\uC99D"\uC73C\uB85C \uB0A8\uC2B5\uB2C8\uB2E4.',
|
|
5752
|
+
" \xB7 \uC99D\uAC70(latest.json)\uB294 commit/goal \uBC14\uC778\uB529\uC774 \uC5C6\uC5B4 \uC2E0\uC120\uB3C4\uB294 \uC0DD\uC131\uC2DC\uAC01\uC73C\uB85C\uB9CC \uCD94\uC815\uD569\uB2C8\uB2E4 \u2014 \uCF54\uB4DC \uBCC0\uACBD \uD6C4\uC5D4 vhk verify \uC7AC\uC2E4\uD589 \uD544\uC694.",
|
|
5753
|
+
" \xB7 git diff \uBBF8\uC0AC\uC6A9(v0) \u2014 \uAE30\uC874 \uD14C\uC2A4\uD2B8\uAC00 green \uC774\uC5B4\uB3C4 \uC774\uBC88 \uBCC0\uACBD\uC744 \uCEE4\uBC84\uD558\uC9C0 \uBABB\uD588\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
|
5754
|
+
].join("\n");
|
|
5755
|
+
function parseCompletionChecks(body) {
|
|
5756
|
+
const lines = body.split(/\r?\n/);
|
|
5757
|
+
const out = [];
|
|
5758
|
+
let inSection = false;
|
|
5759
|
+
for (const raw of lines) {
|
|
5760
|
+
const line = raw.trimEnd();
|
|
5761
|
+
const heading = line.match(/^#{1,6}\s+(.*)$/);
|
|
5762
|
+
if (heading) {
|
|
5763
|
+
inSection = /completion\s*check/i.test(heading[1]);
|
|
5764
|
+
continue;
|
|
5765
|
+
}
|
|
5766
|
+
if (!inSection) continue;
|
|
5767
|
+
const box = line.match(/^\s*-\s*\[([ xX])\]\s*(.+)$/);
|
|
5768
|
+
if (box) out.push({ text: box[2].trim(), checked: box[1].toLowerCase() === "x" });
|
|
5769
|
+
}
|
|
5770
|
+
return out;
|
|
5771
|
+
}
|
|
5772
|
+
function impliedGates(text) {
|
|
5773
|
+
if (/게이트|공통\s*게이트|goal\s*check/i.test(text)) {
|
|
5774
|
+
return ["typecheck", "test", "build", "secure"];
|
|
5775
|
+
}
|
|
5776
|
+
const gates = [];
|
|
5777
|
+
if (/tsc|typecheck|타입\s*체크/i.test(text)) gates.push("typecheck");
|
|
5778
|
+
if (/테스트|test|회귀|vitest/i.test(text)) gates.push("test");
|
|
5779
|
+
if (/빌드|build/i.test(text)) gates.push("build");
|
|
5780
|
+
if (/시크릿|secret|secure|누출|보안\s*스캔/i.test(text)) gates.push("secure");
|
|
5781
|
+
return gates;
|
|
5782
|
+
}
|
|
5783
|
+
function assessFreshness(report, nowMs) {
|
|
5784
|
+
const generatedAt = report?.generatedAt ?? null;
|
|
5785
|
+
const parsed = generatedAt ? Date.parse(generatedAt) : NaN;
|
|
5786
|
+
if (!Number.isFinite(parsed)) {
|
|
5787
|
+
return { generatedAt, ageMs: null, stale: true, confirmed: false, note: "\uC0DD\uC131\uC2DC\uAC01 \uBD88\uBA85 \u2014 \uC2E0\uC120\uB3C4 \uBBF8\uD655\uC778" };
|
|
5788
|
+
}
|
|
5789
|
+
const ageMs = nowMs - parsed;
|
|
5790
|
+
const stale = ageMs > STALE_AGE_MS || ageMs < 0;
|
|
5791
|
+
const mins = Math.round(ageMs / 6e4);
|
|
5792
|
+
const human = mins >= 120 ? `${Math.round(mins / 60)}\uC2DC\uAC04` : `${mins}\uBD84`;
|
|
5793
|
+
return {
|
|
5794
|
+
generatedAt,
|
|
5795
|
+
ageMs,
|
|
5796
|
+
stale,
|
|
5797
|
+
confirmed: true,
|
|
5798
|
+
note: stale ? `\uC99D\uAC70\uAC00 ${human} \uC804 \uC0DD\uC131(\uB610\uB294 \uBBF8\uB798\uC2DC\uAC01) \u2014 \uCF54\uB4DC \uBCC0\uACBD \uC2DC \uBB34\uD6A8, vhk verify \uC7AC\uC2E4\uD589 \uAD8C\uC7A5` : `\uC99D\uAC70 ${human} \uC804 \uC0DD\uC131`
|
|
5799
|
+
};
|
|
5800
|
+
}
|
|
5801
|
+
function crossCheck(checks, goalStatus, report, nowMs) {
|
|
5802
|
+
const suspicions = [];
|
|
5803
|
+
const gaps = [];
|
|
5804
|
+
const gateById = /* @__PURE__ */ new Map();
|
|
5805
|
+
if (report) for (const g of report.gates) gateById.set(g.id, g);
|
|
5806
|
+
const freshness = assessFreshness(report, nowMs);
|
|
5807
|
+
if (goalStatus === "DONE" && report && report.status === "FAIL") {
|
|
5808
|
+
suspicions.push({
|
|
5809
|
+
check: "goal status = DONE",
|
|
5810
|
+
reason: "verify \uC804\uCCB4 \uACB0\uACFC\uAC00 FAIL \u2014 \uC644\uB8CC \uC120\uC5B8\uACFC \uC99D\uAC70\uAC00 \uBAA8\uC21C(\uAC70\uC9D3\uC644\uB8CC \uAC15\uD55C \uC758\uC2EC)."
|
|
5811
|
+
});
|
|
5812
|
+
}
|
|
5813
|
+
const checked = checks.filter((c) => c.checked);
|
|
5814
|
+
let mappedCount = 0;
|
|
5815
|
+
for (const c of checked) {
|
|
5816
|
+
const gates = impliedGates(c.text);
|
|
5817
|
+
if (gates.length === 0) {
|
|
5818
|
+
gaps.push({ check: c.text, note: "\uBBF8\uAC80\uC99D \u2014 \uAC8C\uC774\uD2B8 \uD0A4\uC6CC\uB4DC \uB9E4\uD551 \uBD88\uAC00(\uAE30\uB2A5 \uC644\uB8CC\uC870\uAC74, \uC218\uB3D9 \uD655\uC778 \uD544\uC694)." });
|
|
5819
|
+
continue;
|
|
5820
|
+
}
|
|
5821
|
+
mappedCount++;
|
|
5822
|
+
for (const gid of gates) {
|
|
5823
|
+
const g = gateById.get(gid);
|
|
5824
|
+
if (!report || !g) {
|
|
5825
|
+
suspicions.push({ check: c.text, reason: `${gid} \uAC8C\uC774\uD2B8 \uC99D\uAC70 \uC5C6\uC74C(latest.json \uBD80\uC7AC/\uBBF8\uC2E4\uD589) \u2014 \uCCB4\uD06C\uB428\uC774\uB098 \uB4B7\uBC1B\uCE68 \uBABB \uD568.` });
|
|
5826
|
+
} else if (g.status === "fail") {
|
|
5827
|
+
suspicions.push({ check: c.text, reason: `${gid} \uAC8C\uC774\uD2B8 FAIL(\uC885\uB8CC\uCF54\uB4DC ${g.exitCode ?? "?"}) \u2014 \uCCB4\uD06C\uB428\uACFC \uBAA8\uC21C.` });
|
|
5828
|
+
} else if (g.status === "skip") {
|
|
5829
|
+
suspicions.push({ check: c.text, reason: `${gid} \uAC8C\uC774\uD2B8 skip \u2014 \uAC80\uC99D\uC774 \uBCC0\uACBD\uC744 \uC548 \uAC74\uB4DC\uB838\uC744 \uC218 \uC788\uC74C(\uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC).` });
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
const coverage = checked.length === 0 ? 0 : mappedCount / checked.length;
|
|
5834
|
+
let confidence;
|
|
5835
|
+
if (checked.length === 0 || suspicions.length > 0) {
|
|
5836
|
+
confidence = "low";
|
|
5837
|
+
} else if (gaps.length > 0 || coverage < COVERAGE_MIN || !freshness.confirmed || freshness.stale) {
|
|
5838
|
+
confidence = "medium";
|
|
5839
|
+
} else {
|
|
5840
|
+
confidence = "high";
|
|
5841
|
+
}
|
|
5842
|
+
let reprompt;
|
|
5843
|
+
if (checked.length === 0) {
|
|
5844
|
+
reprompt = "\uCCB4\uD06C\uB41C \uC644\uB8CC\uC870\uAC74\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC644\uB8CC \uC8FC\uC7A5\uC774 \uC5C6\uC5B4 \uC2EC\uBB38\uD560 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4(vacuous).";
|
|
5845
|
+
} else if (suspicions.length > 0) {
|
|
5846
|
+
reprompt = "\uB2E4\uC74C \uC644\uB8CC\uC870\uAC74\uC774 \uC99D\uAC70\uC640 \uBAA8\uC21C\uB418\uAC70\uB098 \uC99D\uAC70\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4:\n" + suspicions.map((s) => ` - ${s.check} \u2192 ${s.reason}`).join("\n") + "\n\uAC01 \uD56D\uBAA9\uC758 \uC2E4\uC81C \uC99D\uAC70(\uAC8C\uC774\uD2B8 \uD1B5\uACFC/\uCD94\uAC00\uB41C \uD14C\uC2A4\uD2B8/\uBCC0\uACBD \uD30C\uC77C)\uB97C \uC81C\uC2DC\uD558\uAC70\uB098, \uCDA9\uC871 \uBABB \uD558\uBA74 done \uC744 \uCCA0\uD68C\uD558\uC138\uC694.";
|
|
5847
|
+
} else if (gaps.length > 0) {
|
|
5848
|
+
reprompt = "\uB2E4\uC74C \uC644\uB8CC\uC870\uAC74\uC740 \uAC8C\uC774\uD2B8 \uC99D\uAC70\uB85C \uC790\uB3D9 \uD655\uC778\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4(\uBBF8\uAC80\uC99D \u2014 \uC218\uB3D9 \uD655\uC778 \uD544\uC694):\n" + gaps.map((g) => ` - ${g.check}`).join("\n");
|
|
5849
|
+
} else {
|
|
5850
|
+
reprompt = "\uCCB4\uD06C\uB41C \uBAA8\uB4E0 \uC644\uB8CC\uC870\uAC74\uC774 \uAC8C\uC774\uD2B8 \uC99D\uAC70\uB85C \uB4B7\uBC1B\uCE68\uB429\uB2C8\uB2E4(\uB2E8, \uBCF4\uC7A5\uC740 \uC544\uB2D8).";
|
|
5851
|
+
}
|
|
5852
|
+
return {
|
|
5853
|
+
confidence,
|
|
5854
|
+
coverage,
|
|
5855
|
+
checkedCount: checked.length,
|
|
5856
|
+
mappedCount,
|
|
5857
|
+
unmappedCount: gaps.length,
|
|
5858
|
+
freshness,
|
|
5859
|
+
suspicions,
|
|
5860
|
+
gaps,
|
|
5861
|
+
disclaimer: REVIEW_DISCLAIMER,
|
|
5862
|
+
reprompt
|
|
5863
|
+
};
|
|
5864
|
+
}
|
|
5865
|
+
function resolveGoal(optId, goals) {
|
|
5866
|
+
let id;
|
|
5867
|
+
if (optId !== void 0) {
|
|
5868
|
+
const n = Number(optId);
|
|
5869
|
+
id = Number.isFinite(n) ? n : null;
|
|
5870
|
+
} else {
|
|
5871
|
+
id = selectActiveId(goals);
|
|
5872
|
+
}
|
|
5873
|
+
if (id === null) return null;
|
|
5874
|
+
return goals.find((g) => g.frontmatter.id === id) ?? null;
|
|
5875
|
+
}
|
|
5876
|
+
var CONFIDENCE_LABEL = {
|
|
5877
|
+
low: chalk31.red.bold("\uB0AE\uC74C (\uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC \uB610\uB294 \uC644\uB8CC \uC8FC\uC7A5 \uC5C6\uC74C)"),
|
|
5878
|
+
medium: chalk31.yellow.bold("\uC911\uAC04 (\uCEE4\uBC84\uB9AC\uC9C0/\uC2E0\uC120\uB3C4 \uBD80\uC871 \u2014 \uC99D\uAC70 \uC5C6\uC74C \u2260 \uD1B5\uACFC)"),
|
|
5879
|
+
high: chalk31.green.bold("\uB192\uC74C (\uC758\uC2EC 0 + \uCEE4\uBC84\uB9AC\uC9C0\xB7\uC2E0\uC120\uB3C4 \uCDA9\uBD84 \u2014 \uB2E8 \uBCF4\uC7A5 \uC544\uB2D8)")
|
|
5880
|
+
};
|
|
5881
|
+
async function review(opts = {}) {
|
|
5882
|
+
if (!ensureNotHardStopped("review")) return;
|
|
5883
|
+
const cwd = process.cwd();
|
|
5884
|
+
const goals = listGoals(GOALS_DIR2);
|
|
5885
|
+
if (goals.length === 0) {
|
|
5886
|
+
console.error(chalk31.yellow(" \u26A0\uFE0F goals/ \uC5D0 goal \uC774 \uC5C6\uC2B5\uB2C8\uB2E4. vhk goal init \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694."));
|
|
5887
|
+
process.exitCode = 1;
|
|
5888
|
+
return;
|
|
5889
|
+
}
|
|
5890
|
+
const goal = resolveGoal(opts.id, goals);
|
|
5891
|
+
if (!goal || typeof goal.frontmatter.id !== "number") {
|
|
5892
|
+
console.error(chalk31.red(` \u274C \uB300\uC0C1 goal \uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4${opts.id ? ` (--id ${opts.id})` : " (active goal \uC5C6\uC74C)"}.`));
|
|
5893
|
+
process.exitCode = 1;
|
|
5894
|
+
return;
|
|
5895
|
+
}
|
|
5896
|
+
const goalId = goal.frontmatter.id;
|
|
5897
|
+
const goalStatus = goal.frontmatter.status ?? "NOT_STARTED";
|
|
5898
|
+
const checks = parseCompletionChecks(goal.body);
|
|
5899
|
+
if (opts.id === void 0 && goalStatus === "NOT_STARTED") {
|
|
5900
|
+
console.error(chalk31.yellow(` \u26A0\uFE0F active goal ${goalId} \uAC00 NOT_STARTED \u2014 \uC644\uB8CC \uC8FC\uC7A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uAC80\uC99D \uB300\uC0C1\uC740 --id \uB85C \uC9C0\uC815\uD558\uC138\uC694.`));
|
|
5901
|
+
}
|
|
5902
|
+
const jsonPath = join11(cwd, REPORT_PATH_REL);
|
|
5903
|
+
if (!existsSync17(jsonPath)) {
|
|
5904
|
+
console.error(chalk31.yellow(` \u26A0\uFE0F \uC99D\uAC70 \uBD80\uC7AC \u2014 ${REPORT_PATH_REL} \uC5C6\uC74C. review \uB97C \uC911\uB2E8\uD569\uB2C8\uB2E4(\uC0C8 \uC99D\uAC70 \uC548 \uB9CC\uB4E6).`));
|
|
5905
|
+
printNextStep({
|
|
5906
|
+
message: "\uC99D\uAC70(latest.json)\uAC00 \uC788\uC5B4\uC57C review \uAC00 \uAD50\uCC28\uAC80\uC99D\uD569\uB2C8\uB2E4:",
|
|
5907
|
+
command: "vhk verify",
|
|
5908
|
+
cursorHint: "\uBA3C\uC800 \uAC80\uC99D \uB3CC\uB824\uC918"
|
|
5909
|
+
});
|
|
5910
|
+
process.exitCode = 1;
|
|
5911
|
+
return;
|
|
5912
|
+
}
|
|
5913
|
+
let report;
|
|
5914
|
+
try {
|
|
5915
|
+
report = readJsonFile(jsonPath);
|
|
5916
|
+
} catch {
|
|
5917
|
+
console.error(chalk31.red(` \u274C ${REPORT_PATH_REL} \uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(\uC190\uC0C1). vhk verify \uB85C \uC7AC\uC0DD\uC131\uD558\uC138\uC694.`));
|
|
5918
|
+
process.exitCode = 1;
|
|
5919
|
+
return;
|
|
5920
|
+
}
|
|
5921
|
+
const analysis = crossCheck(checks, goalStatus, report, Date.now());
|
|
5922
|
+
const result = {
|
|
5923
|
+
...analysis,
|
|
5924
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5925
|
+
goalId,
|
|
5926
|
+
goalStatus,
|
|
5927
|
+
reportStatus: report.status
|
|
5928
|
+
};
|
|
5929
|
+
console.log(chalk31.bold(`
|
|
5930
|
+
\u{1F52C} \uC801\uB300\uC801 \uC790\uAE30\uAC80\uC99D (review) \u2014 Goal ${goalId}`));
|
|
5931
|
+
console.log(chalk31.gray("\u2500".repeat(44)));
|
|
5932
|
+
console.log(
|
|
5933
|
+
chalk31.dim(
|
|
5934
|
+
` goal status: ${goalStatus} \xB7 verify: ${report.status} \xB7 \uCCB4\uD06C\uB41C \uC644\uB8CC\uC870\uAC74 ${result.checkedCount}\uAC1C (\uB9E4\uD551 ${result.mappedCount} / \uBBF8\uAC80\uC99D ${result.unmappedCount}, coverage ${result.coverage * 100 | 0}%)`
|
|
5935
|
+
)
|
|
5936
|
+
);
|
|
5937
|
+
console.log(chalk31.dim(` \uC99D\uAC70 \uC2E0\uC120\uB3C4: ${result.freshness.note}`));
|
|
5938
|
+
if (result.checkedCount === 0) {
|
|
5939
|
+
console.log(chalk31.yellow("\n \u26AA \uC644\uB8CC \uC8FC\uC7A5 \uC5C6\uC74C(\uCCB4\uD06C\uB41C \uC644\uB8CC\uC870\uAC74 0\uAC1C) \u2014 \uC2EC\uBB38\uD560 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4(vacuous)."));
|
|
5940
|
+
}
|
|
5941
|
+
if (result.suspicions.length > 0) {
|
|
5942
|
+
console.log(chalk31.red.bold(`
|
|
5943
|
+
\u{1F6A9} \uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC ${result.suspicions.length}\uAC74`));
|
|
5944
|
+
for (const s of result.suspicions) console.log(chalk31.red(` \u2717 ${s.check}
|
|
5945
|
+
\u21B3 ${s.reason}`));
|
|
5946
|
+
}
|
|
5947
|
+
if (result.gaps.length > 0) {
|
|
5948
|
+
console.log(chalk31.yellow.bold(`
|
|
5949
|
+
\u26A0\uFE0F \uBBF8\uAC80\uC99D(unmapped) ${result.gaps.length}\uAC74 \u2014 \uAC8C\uC774\uD2B8\uB85C \uC790\uB3D9 \uD655\uC778 \uBD88\uAC00`));
|
|
5950
|
+
for (const g of result.gaps) console.log(chalk31.yellow(` ? ${g.check}`));
|
|
5951
|
+
}
|
|
5952
|
+
if (result.checkedCount > 0 && result.suspicions.length === 0 && result.gaps.length === 0) {
|
|
5953
|
+
console.log(chalk31.green("\n \u2713 \uCCB4\uD06C\uB41C \uC644\uB8CC\uC870\uAC74\uC774 \uBAA8\uB450 \uAC8C\uC774\uD2B8 \uC99D\uAC70\uB85C \uB4B7\uBC1B\uCE68\uB428."));
|
|
5954
|
+
}
|
|
5955
|
+
console.log(`
|
|
5956
|
+
\uC2E0\uB8B0\uB3C4: ${CONFIDENCE_LABEL[result.confidence]}`);
|
|
5957
|
+
console.log(chalk31.yellow(`
|
|
5958
|
+
${result.disclaimer}`));
|
|
5959
|
+
let mergeOk = false;
|
|
5960
|
+
try {
|
|
5961
|
+
const merged = { ...report, review: result };
|
|
5962
|
+
writeFileSync12(jsonPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
5963
|
+
mergeOk = true;
|
|
5964
|
+
console.log(chalk31.dim(` \u{1F4C4} \uD310\uC815 \uBCD1\uD569: ${REPORT_PATH_REL} (review \uC139\uC158)`));
|
|
5965
|
+
} catch (e) {
|
|
5966
|
+
console.error(chalk31.red(` \u274C review \uD310\uC815 \uAE30\uB85D \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`));
|
|
5967
|
+
}
|
|
5968
|
+
if (!mergeOk) {
|
|
5969
|
+
process.exitCode = 1;
|
|
5970
|
+
printNextStep({
|
|
5971
|
+
message: "review \uD310\uC815 \uAE30\uB85D \uC2E4\uD328(exit 1) \u2014 .vhk/reports \uC4F0\uAE30 \uAD8C\uD55C \uD655\uC778 \uD6C4 \uC7AC\uC2E4\uD589:",
|
|
5972
|
+
command: "vhk review",
|
|
5973
|
+
cursorHint: "\uAD8C\uD55C \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uAC80\uD1A0\uD574\uC918"
|
|
5974
|
+
});
|
|
5975
|
+
return;
|
|
5976
|
+
}
|
|
5977
|
+
const vacuous = result.checkedCount === 0;
|
|
5978
|
+
const cleanHigh = result.suspicions.length === 0 && result.gaps.length === 0 && result.confidence === "high";
|
|
5979
|
+
process.exitCode = vacuous || cleanHigh ? 0 : 1;
|
|
5980
|
+
if (vacuous) {
|
|
5981
|
+
printNextStep({
|
|
5982
|
+
message: "\uC644\uB8CC \uC8FC\uC7A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC644\uB8CC\uC870\uAC74\uC744 \uCC44\uC6B4 \uB4A4 \uAC80\uC99D\uD558\uC138\uC694:",
|
|
5983
|
+
command: "vhk verify",
|
|
5984
|
+
cursorHint: "\uC644\uB8CC\uC870\uAC74 \uCC44\uC6CC\uC918"
|
|
5985
|
+
});
|
|
5986
|
+
} else if (result.suspicions.length > 0) {
|
|
5987
|
+
console.log(chalk31.dim("\n AI \uC7AC\uC9C8\uBB38 \uD504\uB86C\uD504\uD2B8:"));
|
|
5988
|
+
console.log(chalk31.cyan(result.reprompt.split("\n").map((l) => ` ${l}`).join("\n")));
|
|
5989
|
+
printNextStep({
|
|
5990
|
+
message: "\uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC\uC73C\uB85C \uC2E4\uD328(exit 1) \u2014 \uC99D\uAC70 \uBCF4\uAC15 \uD6C4 \uB2E4\uC2DC \uAC80\uC99D\uD558\uC138\uC694:",
|
|
5991
|
+
command: "vhk verify",
|
|
5992
|
+
cursorHint: "\uC758\uC2EC \uD56D\uBAA9 \uC99D\uAC70 \uBCF4\uAC15\uD574\uC918",
|
|
5993
|
+
alternative: result.suspicions[0].reason
|
|
5994
|
+
});
|
|
5995
|
+
} else if (cleanHigh) {
|
|
5996
|
+
printNextStep({
|
|
5997
|
+
message: "\uC2EC\uBB38 \uD1B5\uACFC(\uC2E0\uB8B0\uB3C4 \uB192\uC74C, \uBCF4\uC7A5 \uC544\uB2D8). \uC644\uB8CC \uCC98\uB9AC\uD558\uB824\uBA74:",
|
|
5998
|
+
command: `vhk goal done --id ${goalId}`,
|
|
5999
|
+
cursorHint: "goal \uC644\uB8CC \uCC98\uB9AC\uD574\uC918"
|
|
6000
|
+
});
|
|
6001
|
+
} else {
|
|
6002
|
+
console.log(chalk31.dim("\n AI \uC7AC\uC9C8\uBB38 \uD504\uB86C\uD504\uD2B8:"));
|
|
6003
|
+
console.log(chalk31.cyan(result.reprompt.split("\n").map((l) => ` ${l}`).join("\n")));
|
|
6004
|
+
printNextStep({
|
|
6005
|
+
message: `\uC99D\uAC70 \uBD88\uCDA9\uBD84(\uC2E0\uB8B0\uB3C4 ${result.confidence}) \u2014 goal done \uAE08\uC9C0. \uCEE4\uBC84\uB9AC\uC9C0/\uC2E0\uC120\uB3C4 \uBCF4\uAC15 \uD6C4 \uC7AC\uAC80\uC99D:`,
|
|
6006
|
+
command: "vhk verify",
|
|
6007
|
+
cursorHint: "\uC99D\uAC70 \uBCF4\uAC15 \uD6C4 \uB2E4\uC2DC \uAC80\uC99D\uD574\uC918"
|
|
6008
|
+
});
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
6011
|
+
|
|
5567
6012
|
// src/lib/risk-policy.ts
|
|
5568
6013
|
var HIGH_RISK_ACTIONS = [
|
|
5569
6014
|
"undo",
|
|
@@ -5729,6 +6174,8 @@ async function dispatchNlpRoute(route, input) {
|
|
|
5729
6174
|
return mode();
|
|
5730
6175
|
case "verify":
|
|
5731
6176
|
return verify();
|
|
6177
|
+
case "review":
|
|
6178
|
+
return review();
|
|
5732
6179
|
}
|
|
5733
6180
|
}
|
|
5734
6181
|
var STATE_CHANGING_COMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -5742,14 +6189,14 @@ function requiresConfirmation(route) {
|
|
|
5742
6189
|
async function runNaturalLanguageRoute(input) {
|
|
5743
6190
|
const route = routeNaturalLanguage(input);
|
|
5744
6191
|
if (!route) {
|
|
5745
|
-
console.log(
|
|
6192
|
+
console.log(chalk32.yellow(`
|
|
5746
6193
|
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
5747
6194
|
`));
|
|
5748
6195
|
return;
|
|
5749
6196
|
}
|
|
5750
6197
|
console.log("");
|
|
5751
|
-
console.log(
|
|
5752
|
-
console.log(
|
|
6198
|
+
console.log(chalk32.cyan(` \u{1F4AC} "${input}"`));
|
|
6199
|
+
console.log(chalk32.cyan(` \u2192 ${route.explanation}`));
|
|
5753
6200
|
if (requiresConfirmation(route)) {
|
|
5754
6201
|
const { confirm } = await inquirer12.prompt([{
|
|
5755
6202
|
type: "confirm",
|
|
@@ -5758,7 +6205,7 @@ async function runNaturalLanguageRoute(input) {
|
|
|
5758
6205
|
default: true
|
|
5759
6206
|
}]);
|
|
5760
6207
|
if (!confirm) {
|
|
5761
|
-
console.log(
|
|
6208
|
+
console.log(chalk32.dim(` ${ko.nlp.menuHint}`));
|
|
5762
6209
|
return;
|
|
5763
6210
|
}
|
|
5764
6211
|
}
|
|
@@ -5767,7 +6214,7 @@ async function runNaturalLanguageRoute(input) {
|
|
|
5767
6214
|
if (riskAction) {
|
|
5768
6215
|
await runGuarded(
|
|
5769
6216
|
riskAction,
|
|
5770
|
-
{ channel: "nl", approved: false, log: (m) => console.log(
|
|
6217
|
+
{ channel: "nl", approved: false, log: (m) => console.log(chalk32.yellow(` ${m}`)) },
|
|
5771
6218
|
() => dispatchNlpRoute(route, input)
|
|
5772
6219
|
);
|
|
5773
6220
|
return;
|
|
@@ -5776,77 +6223,77 @@ async function runNaturalLanguageRoute(input) {
|
|
|
5776
6223
|
}
|
|
5777
6224
|
|
|
5778
6225
|
// src/commands/agent.ts
|
|
5779
|
-
import
|
|
6226
|
+
import chalk33 from "chalk";
|
|
5780
6227
|
function activeGoalId() {
|
|
5781
6228
|
const goals = listGoals("goals");
|
|
5782
6229
|
const id = selectActiveId(goals);
|
|
5783
6230
|
return id ?? void 0;
|
|
5784
6231
|
}
|
|
5785
6232
|
async function blocker(description) {
|
|
5786
|
-
console.log(
|
|
6233
|
+
console.log(chalk33.bold(`
|
|
5787
6234
|
${ko.agent.blockerTitle}
|
|
5788
6235
|
`));
|
|
5789
6236
|
if (!description || !description.trim()) {
|
|
5790
|
-
console.log(
|
|
5791
|
-
console.log(
|
|
6237
|
+
console.log(chalk33.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
|
|
6238
|
+
console.log(chalk33.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
|
|
5792
6239
|
process.exitCode = 1;
|
|
5793
6240
|
return;
|
|
5794
6241
|
}
|
|
5795
6242
|
const goalId = activeGoalId();
|
|
5796
6243
|
const r = appendBlocker(description, goalId);
|
|
5797
|
-
console.log(
|
|
6244
|
+
console.log(chalk33.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
|
|
5798
6245
|
if (r.hardStopTripped) {
|
|
5799
|
-
console.log(
|
|
5800
|
-
console.log(
|
|
6246
|
+
console.log(chalk33.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
|
|
6247
|
+
console.log(chalk33.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
|
|
5801
6248
|
process.exitCode = 2;
|
|
5802
6249
|
}
|
|
5803
6250
|
}
|
|
5804
6251
|
async function learn(lesson) {
|
|
5805
|
-
console.log(
|
|
6252
|
+
console.log(chalk33.bold(`
|
|
5806
6253
|
${ko.agent.learnTitle}
|
|
5807
6254
|
`));
|
|
5808
6255
|
if (!lesson || !lesson.trim()) {
|
|
5809
|
-
console.log(
|
|
5810
|
-
console.log(
|
|
6256
|
+
console.log(chalk33.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
|
|
6257
|
+
console.log(chalk33.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
|
|
5811
6258
|
process.exitCode = 1;
|
|
5812
6259
|
return;
|
|
5813
6260
|
}
|
|
5814
6261
|
const goalId = activeGoalId();
|
|
5815
6262
|
appendLearning(lesson, goalId);
|
|
5816
|
-
console.log(
|
|
6263
|
+
console.log(chalk33.green(" \u2705 learnings.md append."));
|
|
5817
6264
|
console.log(
|
|
5818
|
-
|
|
6265
|
+
chalk33.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
|
|
5819
6266
|
);
|
|
5820
6267
|
}
|
|
5821
6268
|
async function resume(opts = {}) {
|
|
5822
|
-
console.log(
|
|
6269
|
+
console.log(chalk33.bold(`
|
|
5823
6270
|
${ko.agent.resumeTitle}
|
|
5824
6271
|
`));
|
|
5825
6272
|
if (!isHardStopActive()) {
|
|
5826
|
-
console.log(
|
|
6273
|
+
console.log(chalk33.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
|
|
5827
6274
|
return;
|
|
5828
6275
|
}
|
|
5829
6276
|
const reason = readHardStopReason();
|
|
5830
6277
|
if (reason) {
|
|
5831
|
-
console.log(
|
|
5832
|
-
console.log(
|
|
6278
|
+
console.log(chalk33.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
|
|
6279
|
+
console.log(chalk33.dim(` ${reason.split("\n").join("\n ")}`));
|
|
5833
6280
|
console.log("");
|
|
5834
6281
|
}
|
|
5835
6282
|
if (!opts.confirm) {
|
|
5836
6283
|
console.log(
|
|
5837
|
-
|
|
6284
|
+
chalk33.red(
|
|
5838
6285
|
" \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
|
|
5839
6286
|
)
|
|
5840
6287
|
);
|
|
5841
|
-
console.log(
|
|
6288
|
+
console.log(chalk33.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
|
|
5842
6289
|
process.exitCode = 1;
|
|
5843
6290
|
return;
|
|
5844
6291
|
}
|
|
5845
6292
|
const removed = clearHardStop();
|
|
5846
6293
|
if (removed) {
|
|
5847
|
-
console.log(
|
|
6294
|
+
console.log(chalk33.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
|
|
5848
6295
|
} else {
|
|
5849
|
-
console.log(
|
|
6296
|
+
console.log(chalk33.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
|
|
5850
6297
|
}
|
|
5851
6298
|
}
|
|
5852
6299
|
|
|
@@ -5867,7 +6314,7 @@ async function guardCli(action, approved, run) {
|
|
|
5867
6314
|
}]);
|
|
5868
6315
|
return ok;
|
|
5869
6316
|
},
|
|
5870
|
-
log: (m) => console.log(
|
|
6317
|
+
log: (m) => console.log(chalk34.yellow(` ${m}`))
|
|
5871
6318
|
},
|
|
5872
6319
|
run
|
|
5873
6320
|
);
|
|
@@ -5880,7 +6327,7 @@ async function guardCliDefer(action, approved, run) {
|
|
|
5880
6327
|
approved,
|
|
5881
6328
|
// TTY 면 통과(명령이 자체 확인), 비대화형은 confirm 불가 → 가드가 차단.
|
|
5882
6329
|
confirm: async () => !!process.stdout.isTTY,
|
|
5883
|
-
log: (m) => console.log(
|
|
6330
|
+
log: (m) => console.log(chalk34.yellow(` ${m}`))
|
|
5884
6331
|
},
|
|
5885
6332
|
run
|
|
5886
6333
|
);
|
|
@@ -5919,6 +6366,7 @@ var KO_ALIASES = {
|
|
|
5919
6366
|
memory: "\uAE30\uC5B5",
|
|
5920
6367
|
brief: "\uBE0C\uB9AC\uD551",
|
|
5921
6368
|
goal: "\uBAA9\uD45C",
|
|
6369
|
+
review: "\uAC80\uD1A0",
|
|
5922
6370
|
blocker: "\uBE14\uB85C\uCEE4",
|
|
5923
6371
|
learn: "\uAD50\uD6C8",
|
|
5924
6372
|
resume: "\uC7AC\uAC1C"
|
|
@@ -6040,9 +6488,12 @@ program.command("context").alias("\uB9E5\uB77D").option("--compact", "\uD1A0\uD0
|
|
|
6040
6488
|
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) => {
|
|
6041
6489
|
await mode(target);
|
|
6042
6490
|
});
|
|
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) => {
|
|
6491
|
+
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) => {
|
|
6044
6492
|
await verify(opts);
|
|
6045
6493
|
});
|
|
6494
|
+
program.command("review").alias("\uAC80\uD1A0").option("--id <id>", "\uB300\uC0C1 goal id (\uC5C6\uC73C\uBA74 active goal)").description("\uC801\uB300\uC801 \uC790\uAE30\uAC80\uC99D \u2014 latest.json \u2194 goal \uC644\uB8CC\uC870\uAC74 \uAD50\uCC28\uAC80\uC99D (\uAC70\uC9D3\uC644\uB8CC \uC758\uC2EC \uD0D0\uC9C0, \uBCF4\uC7A5 \uC544\uB2D8)").action(async (opts) => {
|
|
6495
|
+
await review(opts);
|
|
6496
|
+
});
|
|
6046
6497
|
program.command("context-show").alias("\uB9E5\uB77D\uBCF4\uAE30").description("\uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uB0B4\uC6A9 \uCD9C\uB825").action(async () => {
|
|
6047
6498
|
await contextShow();
|
|
6048
6499
|
});
|
|
@@ -6157,9 +6608,9 @@ if (isMainModule) {
|
|
|
6157
6608
|
}
|
|
6158
6609
|
} catch (err) {
|
|
6159
6610
|
if (isPromptAbortError(err)) {
|
|
6160
|
-
console.error(
|
|
6611
|
+
console.error(chalk34.yellow("\n \u26A0\uFE0F \uB300\uD654\uD615 \uC785\uB825\uC774 \uCDE8\uC18C/\uC885\uB8CC\uB410\uC2B5\uB2C8\uB2E4. (\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C\uB294 \uD574\uB2F9 \uBA85\uB839\uC744 \uC4F8 \uC218 \uC5C6\uC5B4\uC694)"));
|
|
6161
6612
|
} else {
|
|
6162
|
-
console.error(
|
|
6613
|
+
console.error(chalk34.red(`
|
|
6163
6614
|
\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
6164
6615
|
}
|
|
6165
6616
|
process.exitCode = 1;
|
package/package.json
CHANGED