@byh3071/vhk 2.0.1 → 2.1.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.
@@ -913,6 +913,11 @@ var ko = {
913
913
  blockerTitle: "\u{1F6D1} Blocker \uAE30\uB85D",
914
914
  learnTitle: "\u{1F9E0} Learning \uAE30\uB85D",
915
915
  resumeTitle: "\u25B6\uFE0F HARD_STOP \uD574\uC81C"
916
+ },
917
+ pattern: {
918
+ detectTitle: "\uD328\uD134 \uAC10\uC9C0",
919
+ listTitle: "\uD328\uD134 \uBAA9\uB85D",
920
+ dismissTitle: "\uD328\uD134 dismiss"
916
921
  }
917
922
  };
918
923
  function lookup(path7) {
@@ -2996,6 +3001,36 @@ ${cliStatus}
2996
3001
  { description: ".cursor/mcp.json \uC0DD\uC131/\uAC31\uC2E0 \u2014 vhk MCP \uC11C\uBC84 \uB4F1\uB85D (Cursor \uC7AC\uC2DC\uC791 \uD544\uC694)" },
2997
3002
  async () => runVhkCli(["mcp-init"], "mcp-init")
2998
3003
  );
3004
+ server.registerTool(
3005
+ "pattern-detect",
3006
+ {
3007
+ description: "active failures+successes 2\uCD95 \uBD84\uC11D \u2192 avoid/reinforce \uD6C4\uBCF4 \uAC10\uC9C0 \xB7 patterns[] \uAC31\uC2E0 (Goal 19)",
3008
+ inputSchema: {
3009
+ min: z.number().int().min(1).optional().describe("\uC784\uACC4 \uD69F\uC218 (\uAE30\uBCF8 3)")
3010
+ }
3011
+ },
3012
+ async ({ min }) => {
3013
+ const args = ["pattern", "detect", "--json"];
3014
+ if (min !== void 0) args.push("--min", String(min));
3015
+ return runVhkCli(args, "pattern detect");
3016
+ }
3017
+ );
3018
+ server.registerTool(
3019
+ "pattern-list",
3020
+ {
3021
+ description: "\uD328\uD134 \uD6C4\uBCF4 \uBAA9\uB85D \uC870\uD68C (avoid/reinforce \xB7 \uD65C\uC131 \uAE30\uBCF8) \u2014 Goal 19",
3022
+ inputSchema: {
3023
+ kind: z.enum(["avoid", "reinforce"]).optional().describe("\uC885\uB958 \uD544\uD130"),
3024
+ all: z.boolean().optional().describe("\uBCF4\uAD00(archived) \uD3EC\uD568")
3025
+ }
3026
+ },
3027
+ async ({ kind, all }) => {
3028
+ const args = ["pattern", "list", "--json"];
3029
+ if (kind) args.push("--kind", kind);
3030
+ if (all) args.push("--all");
3031
+ return runVhkCli(args, "pattern list");
3032
+ }
3033
+ );
2999
3034
  return server;
3000
3035
  }
3001
3036
  async function startMcpServer() {
package/dist/index.js CHANGED
@@ -43,12 +43,13 @@ import {
43
43
  stripBom,
44
44
  sync,
45
45
  t
46
- } from "./chunk-HCNU6K7D.js";
46
+ } from "./chunk-ODNKCGUG.js";
47
47
 
48
48
  // src/index.ts
49
49
  import { Command, Help } from "commander";
50
- import { pathToFileURL } from "url";
51
- import chalk35 from "chalk";
50
+ import { fileURLToPath as fileURLToPath4 } from "url";
51
+ import fs13 from "fs";
52
+ import chalk36 from "chalk";
52
53
  import inquirer14 from "inquirer";
53
54
 
54
55
  // src/lib/nlp-router.ts
@@ -57,6 +58,7 @@ function normalize(input) {
57
58
  }
58
59
  var NLP_KEYWORDS = {
59
60
  save: ["\uC800\uC7A5", "\uC138\uC774\uBE0C", "\uCEE4\uBC0B", "\uC62C\uB824", "\uC62C\uB9AC\uAE30", "\uD478\uC2DC", "push", "commit"],
61
+ pattern: ["\uD328\uD134", "\uB418\uD480\uC774", "\uBC84\uB987", "pattern"],
60
62
  undo: ["\uB418\uB3CC\uB824", "\uB418\uB3CC\uB9AC\uAE30", "\uCDE8\uC18C", "\uC6D0\uB798\uB300\uB85C", "\uB864\uBC31", "\uB9AC\uC14B", "reset", "rollback"],
61
63
  status: ["\uC0C1\uD0DC", "\uD604\uD669", "\uC5B4\uB5BB\uAC8C", "\uC5B4\uB54C", "\uC9C0\uAE08"],
62
64
  diff: ["\uBCC0\uACBD", "\uBC14\uB010", "\uBB50\uBC14\uB01C", "\uBC14\uB00C\uC5C8", "\uCC28\uC774", "\uB2EC\uB77C\uC9C4", "\uC218\uC815\uB41C"]
@@ -320,6 +322,12 @@ var RULES = [
320
322
  confidence: "high",
321
323
  test: (t2) => /^출시$|출시\s*해|^publish$|퍼블리시|npm\s*(배포|출시)|버전\s*올|^릴리즈$|^release$/.test(t2) && !/체크|준비|회고/.test(t2)
322
324
  },
325
+ {
326
+ command: "pattern",
327
+ explanation: "\uD328\uD134 \uD6C4\uBCF4 \uBAA9\uB85D (vhk pattern list) \u2014 \uAC10\uC9C0\uB294 vhk pattern detect \uC9C1\uC811 \uC2E4\uD589",
328
+ confidence: "high",
329
+ test: (t2) => matchesKeywords(t2, "pattern") || /^pattern$/.test(t2)
330
+ },
323
331
  // NLP 규칙은 한국어 표현만 매칭. 영문 `goal <sub>` 은 commander 가 직접 처리하도록
324
332
  // 가로채기 금지 — vhk goal list / next / check / done 그대로 동작.
325
333
  {
@@ -389,7 +397,8 @@ var CONTAINER_SUBCOMMANDS = {
389
397
  design: ["palette"],
390
398
  env: ["check"],
391
399
  mode: ["lite", "standard", "strict"],
392
- mission: ["set", "check", "clear"]
400
+ mission: ["set", "check", "clear"],
401
+ pattern: ["detect", "list", "dismiss"]
393
402
  };
394
403
  var CONTAINER_ALIASES = {
395
404
  \uBAA9\uD45C: "goal",
@@ -400,7 +409,8 @@ var CONTAINER_ALIASES = {
400
409
  \uB514\uC790\uC778: "design",
401
410
  \uD658\uACBD\uBCC0\uC218: "env",
402
411
  \uBAA8\uB4DC: "mode",
403
- \uBBF8\uC158: "mission"
412
+ \uBBF8\uC158: "mission",
413
+ \uD328\uD134: "pattern"
404
414
  };
405
415
 
406
416
  // src/lib/cli-args.ts
@@ -534,7 +544,7 @@ function detectNaturalLanguageInput(argv) {
534
544
  }
535
545
 
536
546
  // src/lib/nlp-run.ts
537
- import chalk33 from "chalk";
547
+ import chalk34 from "chalk";
538
548
  import inquirer13 from "inquirer";
539
549
 
540
550
  // src/commands/gate.ts
@@ -774,7 +784,10 @@ function RULES_MD_TEMPLATE(name, description, stack) {
774
784
  "",
775
785
  "## \uAE30\uB85D \uADDC\uCE59",
776
786
  "- \uC138\uC158 \uC885\uB8CC \uC2DC docs/log/YYYY-MM-DD-{\uC791\uC5C5\uBA85}.md \uC0DD\uC131",
777
- "- \uAE30\uC220 \uC120\uD0DD \uC2DC docs/adr/ADR-{\uBC88\uD638}-{\uC81C\uBAA9}.md \uC0DD\uC131"
787
+ "- \uAE30\uC220 \uC120\uD0DD \uC2DC docs/adr/ADR-{\uBC88\uD638}-{\uC81C\uBAA9}.md \uC0DD\uC131",
788
+ '- \uAE30\uB2A5 \uC644\uC131 / \uC5D0\uB7EC \uD574\uACB0 / ADR / \uC138\uC158 \uC885\uB8CC \uC2DC Notion "\uBC14\uC774\uBE0C\uCF54\uB529 Dev Log" DB\uC5D0 1\uD589 \uC801\uC7AC (Notion MCP)',
789
+ '- \uC801\uC7AC \uC9C1\uC804 Dev Log DB \uC0C1\uB2E8 "AI \uC801\uC7AC \uADDC\uCE59" \uCF5C\uC544\uC6C3 \uD655\uC778 \u2014 \uC81C\uBAA9: (YYYY-MM-DD) \uD504\uB85C\uC81D\uD2B8\uBA85 - \uC81C\uBAA9',
790
+ "- \uD0DC\uADF8\uB294 \uAE30\uC874 \uC635\uC158\uB9CC \uC0AC\uC6A9, \uAC19\uC740 \uC791\uC5C5 \uC911\uBCF5 \uC801\uC7AC \uAE08\uC9C0(SoT Key)"
778
791
  ].join("\n");
779
792
  }
780
793
 
@@ -6493,6 +6506,245 @@ async function missionClear() {
6493
6506
  }
6494
6507
  }
6495
6508
 
6509
+ // src/commands/pattern.ts
6510
+ import chalk33 from "chalk";
6511
+ var MIN_TAG_FREQ = 3;
6512
+ var STOPWORDS = /* @__PURE__ */ new Set(["the", "a", "an", "is", "are", "and", "or", "in", "on", "at", "to", "for", "of", "with", "it", "was", "be"]);
6513
+ function tokenize(text) {
6514
+ return text.toLowerCase().replace(/[^가-힣a-z0-9\s]/g, " ").split(/\s+/).filter((tok) => tok.length >= 2 && !STOPWORDS.has(tok));
6515
+ }
6516
+ function sigOf(kind, axis, signal) {
6517
+ return `${kind}:${axis}:${signal}`;
6518
+ }
6519
+ function isActive2(e) {
6520
+ return e.status !== "archived" && e.status !== "resolved";
6521
+ }
6522
+ function detectCandidates(mem, minFreq) {
6523
+ const candidates = [];
6524
+ function processBucket(entries, kind, getText) {
6525
+ const active = entries.filter(isActive2);
6526
+ const tagMap = /* @__PURE__ */ new Map();
6527
+ for (const e of active) {
6528
+ const uniqueTags = new Set(e.tags ?? []);
6529
+ for (const tag of uniqueTags) {
6530
+ if (tag === "no-goal") continue;
6531
+ if (!tagMap.has(tag)) tagMap.set(tag, []);
6532
+ tagMap.get(tag).push(e.id);
6533
+ }
6534
+ }
6535
+ for (const [tag, sources] of tagMap) {
6536
+ if (sources.length >= minFreq) {
6537
+ const sourceSet = new Set(sources);
6538
+ const involved = active.filter((e) => sourceSet.has(e.id));
6539
+ const sourceTags = [...new Set(involved.flatMap((e) => e.tags ?? []))];
6540
+ candidates.push({
6541
+ kind,
6542
+ axis: "tag",
6543
+ signal: tag,
6544
+ count: sources.length,
6545
+ sources,
6546
+ summary: `[${kind}] \uD0DC\uADF8 '${tag}' ${sources.length}\uAC74 \uBC18\uBCF5`,
6547
+ sourceTags
6548
+ });
6549
+ }
6550
+ }
6551
+ const tokenMap = /* @__PURE__ */ new Map();
6552
+ for (const e of active) {
6553
+ const text = getText(e);
6554
+ if (!text) continue;
6555
+ const unique = new Set(tokenize(text));
6556
+ for (const tok of unique) {
6557
+ if (!tokenMap.has(tok)) tokenMap.set(tok, /* @__PURE__ */ new Set());
6558
+ tokenMap.get(tok).add(e.id);
6559
+ }
6560
+ }
6561
+ for (const [tok, sourceSet] of tokenMap) {
6562
+ if (sourceSet.size >= minFreq) {
6563
+ const sources = [...sourceSet];
6564
+ const involved = active.filter((e) => sourceSet.has(e.id));
6565
+ const sourceTags = [...new Set(involved.flatMap((e) => e.tags ?? []))];
6566
+ candidates.push({
6567
+ kind,
6568
+ axis: "keyword",
6569
+ signal: tok,
6570
+ count: sourceSet.size,
6571
+ sources,
6572
+ summary: `[${kind}] \uD0A4\uC6CC\uB4DC '${tok}' ${sourceSet.size}\uAC74 \uBB38\uC11C`,
6573
+ sourceTags
6574
+ });
6575
+ }
6576
+ }
6577
+ }
6578
+ const failText = (e) => {
6579
+ const f = e;
6580
+ return f.lesson ?? f.content ?? "";
6581
+ };
6582
+ processBucket(mem.failures, "avoid", failText);
6583
+ processBucket(mem.successes, "reinforce", (e) => e.content ?? "");
6584
+ return candidates.sort((a, b) => b.count - a.count || a.signal.localeCompare(b.signal));
6585
+ }
6586
+ function nextPatternId(mem) {
6587
+ const re = /^p(\d+)$/;
6588
+ let max = 0;
6589
+ for (const p of mem.patterns) {
6590
+ const m = p.id.match(re);
6591
+ if (m) max = Math.max(max, Number(m[1]));
6592
+ }
6593
+ return `p${max + 1}`;
6594
+ }
6595
+ async function patternDetect(opts = {}) {
6596
+ const minFreq = opts.min !== void 0 ? parseInt(opts.min, 10) : MIN_TAG_FREQ;
6597
+ if (!Number.isFinite(minFreq) || minFreq < 1) {
6598
+ console.log(chalk33.red("\u274C --min \uC740 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4."));
6599
+ process.exitCode = 1;
6600
+ return;
6601
+ }
6602
+ const cwd = process.cwd();
6603
+ const loaded = loadForMutation(cwd);
6604
+ if (!loaded.ok) {
6605
+ console.log(chalk33.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uAC10\uC9C0 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874). \uBC31\uC5C5 \uD655\uC778 \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694."));
6606
+ process.exitCode = 1;
6607
+ return;
6608
+ }
6609
+ const mem = loaded.mem;
6610
+ const candidates = detectCandidates(mem, minFreq);
6611
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6612
+ let added = 0, updated = 0;
6613
+ for (const c of candidates) {
6614
+ const sig = sigOf(c.kind, c.axis, c.signal);
6615
+ const existing = mem.patterns.find((p) => {
6616
+ const pp = p;
6617
+ const sigMatch = pp._sig === sig;
6618
+ const fieldMatch = pp.kind === c.kind && pp.axis === c.axis && pp.signal === c.signal;
6619
+ return (sigMatch || fieldMatch) && pp.status !== "archived";
6620
+ });
6621
+ if (existing) {
6622
+ existing._sig = sig;
6623
+ existing.count = c.count;
6624
+ existing.sources = c.sources;
6625
+ existing.summary = c.summary;
6626
+ existing.tags = c.sourceTags;
6627
+ updated++;
6628
+ } else {
6629
+ const entry = {
6630
+ id: nextPatternId(mem),
6631
+ kind: c.kind,
6632
+ axis: c.axis,
6633
+ signal: c.signal,
6634
+ count: c.count,
6635
+ sources: c.sources,
6636
+ summary: c.summary,
6637
+ createdAt: now,
6638
+ status: "active",
6639
+ tags: c.sourceTags,
6640
+ _sig: sig
6641
+ };
6642
+ mem.patterns.push(entry);
6643
+ added++;
6644
+ }
6645
+ }
6646
+ if (added > 0 || updated > 0) {
6647
+ writeMemory(cwd, mem);
6648
+ }
6649
+ if (opts.json) {
6650
+ const active2 = mem.patterns.filter((p) => p.status !== "archived");
6651
+ console.log(JSON.stringify(active2, null, 2));
6652
+ return;
6653
+ }
6654
+ console.log(chalk33.bold("\n\u{1F50D} " + t("pattern.detectTitle")));
6655
+ console.log(chalk33.gray("\u2500".repeat(40)));
6656
+ console.log(chalk33.dim(` \uC784\uACC4: ${minFreq}\uD68C \uC774\uC0C1 \xB7 active failures+successes \uC785\uB825`));
6657
+ console.log(chalk33.dim(` \uD6C4\uBCF4: ${candidates.length}\uAC1C \uAC10\uC9C0 (\uCD94\uAC00 ${added} / \uAC31\uC2E0 ${updated})`));
6658
+ const active = mem.patterns.filter((p) => p.status !== "archived");
6659
+ if (active.length === 0) {
6660
+ console.log(chalk33.yellow("\n\u{1F4ED} \uC784\uACC4 \uC774\uC0C1 \uBC18\uBCF5 \uD328\uD134\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
6661
+ console.log(chalk33.gray(` failures/successes \uAC00 ${minFreq}\uAC1C \uC774\uC0C1 \uC313\uC774\uBA74 \uAC10\uC9C0\uB429\uB2C8\uB2E4.`));
6662
+ console.log(chalk33.gray(" --min N \uC73C\uB85C \uC784\uACC4\uB97C \uB0AE\uCD9C \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
6663
+ return;
6664
+ }
6665
+ console.log(chalk33.cyan(`
6666
+ \uD328\uD134 \uD6C4\uBCF4 ${active.length}\uAC1C:
6667
+ `));
6668
+ for (const p of active.slice(0, 20)) {
6669
+ const icon = p.kind === "avoid" ? "\u26A0\uFE0F " : "\u2705";
6670
+ console.log(` [${p.id}] ${icon} (${p.kind}/${p.axis}) "${p.signal}" \u2014 ${p.count}\uAC74`);
6671
+ const preview = p.sources.slice(0, 5).join(", ") + (p.sources.length > 5 ? ` \uC678 ${p.sources.length - 5}\uAC74` : "");
6672
+ console.log(chalk33.dim(` \uADFC\uAC70: ${preview}`));
6673
+ console.log(chalk33.dim(` ${p.summary}`));
6674
+ }
6675
+ printNextStep({
6676
+ message: `\uD328\uD134 \uAC10\uC9C0 \uC644\uB8CC! ${active.length}\uAC1C \uD6C4\uBCF4.`,
6677
+ command: "vhk pattern list",
6678
+ cursorHint: "\uD328\uD134 \uBAA9\uB85D \uBCF4\uC5EC\uC918",
6679
+ alternative: "(Goal 20) vhk evolve \u2014 \uB2E4\uC74C \uB2E8\uACC4\uC5D0\uC11C \uBC18\uC601"
6680
+ });
6681
+ }
6682
+ async function patternList(opts = {}) {
6683
+ console.log(chalk33.bold("\n\u{1F50D} " + t("pattern.listTitle")));
6684
+ console.log(chalk33.gray("\u2500".repeat(40)));
6685
+ const mem = readMemory(process.cwd());
6686
+ let patterns = mem.patterns;
6687
+ if (!opts.all) patterns = patterns.filter((p) => p.status !== "archived");
6688
+ if (opts.kind === "avoid" || opts.kind === "reinforce") {
6689
+ patterns = patterns.filter((p) => p.kind === opts.kind);
6690
+ }
6691
+ if (opts.json) {
6692
+ console.log(JSON.stringify(patterns, null, 2));
6693
+ return;
6694
+ }
6695
+ if (patterns.length === 0) {
6696
+ console.log(chalk33.yellow("\n\u{1F4ED} \uD45C\uC2DC\uD560 \uD328\uD134\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
6697
+ console.log(chalk33.gray(" vhk pattern detect \uB85C \uAC10\uC9C0 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
6698
+ return;
6699
+ }
6700
+ console.log(chalk33.cyan(`
6701
+ ${patterns.length}\uAC1C${opts.all ? " (\uBCF4\uAD00 \uD3EC\uD568)" : " (\uD65C\uC131)"}:
6702
+ `));
6703
+ for (const p of patterns) {
6704
+ const icon = p.kind === "avoid" ? "\u26A0\uFE0F " : "\u2705";
6705
+ const archived = p.status === "archived" ? "\u{1F4E6} " : "";
6706
+ console.log(` [${p.id}] ${archived}${icon} (${p.kind}/${p.axis}) "${p.signal}" \u2014 ${p.count}\uAC74`);
6707
+ if (p.summary) console.log(chalk33.dim(` ${p.summary}`));
6708
+ if (p.tags?.length) console.log(chalk33.blue(` \u{1F3F7}\uFE0F ${p.tags.join(", ")}`));
6709
+ }
6710
+ }
6711
+ async function patternDismiss(idStr) {
6712
+ if (!idStr?.trim()) {
6713
+ console.log(chalk33.red("\u274C \uD328\uD134 id \uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694. \uC608: vhk pattern dismiss p1"));
6714
+ process.exitCode = 1;
6715
+ return;
6716
+ }
6717
+ const cwd = process.cwd();
6718
+ const loaded = loadForMutation(cwd);
6719
+ if (!loaded.ok) {
6720
+ console.log(chalk33.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 dismiss \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
6721
+ process.exitCode = 1;
6722
+ return;
6723
+ }
6724
+ const mem = loaded.mem;
6725
+ const pattern = mem.patterns.find((p) => p.id === idStr.trim());
6726
+ if (!pattern) {
6727
+ console.log(chalk33.red(`\u274C \uD328\uD134 '${idStr}' \uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
6728
+ console.log(chalk33.gray(" vhk pattern list --all \uB85C \uBAA9\uB85D \uD655\uC778."));
6729
+ process.exitCode = 1;
6730
+ return;
6731
+ }
6732
+ if (pattern.status === "archived") {
6733
+ console.log(chalk33.dim(` \uC774\uBBF8 \uBCF4\uAD00\uB41C \uD328\uD134\uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C: ${pattern.id}`));
6734
+ return;
6735
+ }
6736
+ pattern.status = "archived";
6737
+ writeMemory(cwd, mem);
6738
+ console.log(chalk33.green(`
6739
+ \u{1F4E6} \uD328\uD134 dismiss(\uBCF4\uAD00)\uB428: [${pattern.id}] ${pattern.summary ?? pattern.signal}`));
6740
+ console.log(chalk33.dim(" \uC624\uD0D0\uC73C\uB85C \uD310\uB2E8. detect \uC7AC\uC2E4\uD589 \uC2DC \uC7AC\uC81C\uC548 \uC548 \uB428."));
6741
+ printNextStep({
6742
+ message: "\uD328\uD134 dismiss \uC644\uB8CC!",
6743
+ command: "vhk pattern list",
6744
+ cursorHint: "\uB0A8\uC740 \uD328\uD134 \uBCF4\uC5EC\uC918"
6745
+ });
6746
+ }
6747
+
6496
6748
  // src/lib/risk-policy.ts
6497
6749
  var HIGH_RISK_ACTIONS = [
6498
6750
  "undo",
@@ -6663,6 +6915,8 @@ async function dispatchNlpRoute(route, input) {
6663
6915
  return review();
6664
6916
  case "mission":
6665
6917
  return missionShow();
6918
+ case "pattern":
6919
+ return patternList();
6666
6920
  }
6667
6921
  }
6668
6922
  var STATE_CHANGING_COMMANDS = /* @__PURE__ */ new Set([
@@ -6676,14 +6930,14 @@ function requiresConfirmation(route) {
6676
6930
  async function runNaturalLanguageRoute(input) {
6677
6931
  const route = routeNaturalLanguage(input);
6678
6932
  if (!route) {
6679
- console.log(chalk33.yellow(`
6933
+ console.log(chalk34.yellow(`
6680
6934
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
6681
6935
  `));
6682
6936
  return;
6683
6937
  }
6684
6938
  console.log("");
6685
- console.log(chalk33.cyan(` \u{1F4AC} "${input}"`));
6686
- console.log(chalk33.cyan(` \u2192 ${route.explanation}`));
6939
+ console.log(chalk34.cyan(` \u{1F4AC} "${input}"`));
6940
+ console.log(chalk34.cyan(` \u2192 ${route.explanation}`));
6687
6941
  if (requiresConfirmation(route)) {
6688
6942
  const { confirm } = await inquirer13.prompt([{
6689
6943
  type: "confirm",
@@ -6692,7 +6946,7 @@ async function runNaturalLanguageRoute(input) {
6692
6946
  default: true
6693
6947
  }]);
6694
6948
  if (!confirm) {
6695
- console.log(chalk33.dim(` ${ko.nlp.menuHint}`));
6949
+ console.log(chalk34.dim(` ${ko.nlp.menuHint}`));
6696
6950
  return;
6697
6951
  }
6698
6952
  }
@@ -6701,7 +6955,7 @@ async function runNaturalLanguageRoute(input) {
6701
6955
  if (riskAction) {
6702
6956
  await runGuarded(
6703
6957
  riskAction,
6704
- { channel: "nl", approved: false, log: (m) => console.log(chalk33.yellow(` ${m}`)) },
6958
+ { channel: "nl", approved: false, log: (m) => console.log(chalk34.yellow(` ${m}`)) },
6705
6959
  () => dispatchNlpRoute(route, input)
6706
6960
  );
6707
6961
  return;
@@ -6710,80 +6964,80 @@ async function runNaturalLanguageRoute(input) {
6710
6964
  }
6711
6965
 
6712
6966
  // src/commands/agent.ts
6713
- import chalk34 from "chalk";
6967
+ import chalk35 from "chalk";
6714
6968
  function activeGoalId() {
6715
6969
  const goals = listGoals("goals");
6716
6970
  const id = selectActiveId(goals);
6717
6971
  return id ?? void 0;
6718
6972
  }
6719
6973
  async function blocker(description) {
6720
- console.log(chalk34.bold(`
6974
+ console.log(chalk35.bold(`
6721
6975
  ${ko.agent.blockerTitle}
6722
6976
  `));
6723
6977
  if (!description || !description.trim()) {
6724
- console.log(chalk34.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
6725
- console.log(chalk34.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
6978
+ console.log(chalk35.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
6979
+ console.log(chalk35.dim(' \uC608: vhk blocker "tsc \uC5D0\uB7EC \u2014 simple-git \uD0C0\uC785 \uD638\uD658"'));
6726
6980
  process.exitCode = 1;
6727
6981
  return;
6728
6982
  }
6729
6983
  const goalId = activeGoalId();
6730
6984
  const r = appendBlocker(description, goalId);
6731
- console.log(chalk34.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
6985
+ console.log(chalk35.green(` \u2705 blocker \uAE30\uB85D (\uD604\uC7AC \uD65C\uC131 ${r.count}\uAC74)`));
6732
6986
  if (r.hardStopTripped) {
6733
- console.log(chalk34.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
6734
- console.log(chalk34.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
6987
+ console.log(chalk35.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
6988
+ console.log(chalk35.yellow(" \uC0AC\uB78C \uAC80\uD1A0 \uD6C4 `vhk resume --confirm` \uC73C\uB85C\uB9CC \uD574\uC81C."));
6735
6989
  process.exitCode = 2;
6736
6990
  }
6737
6991
  }
6738
6992
  async function learn(lesson) {
6739
- console.log(chalk34.bold(`
6993
+ console.log(chalk35.bold(`
6740
6994
  ${ko.agent.learnTitle}
6741
6995
  `));
6742
6996
  if (!lesson || !lesson.trim()) {
6743
- console.log(chalk34.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
6744
- console.log(chalk34.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
6997
+ console.log(chalk35.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
6998
+ console.log(chalk35.dim(' \uC608: vhk learn "PowerShell \uC5D0\uC11C\uB294 ; \uC0AC\uC6A9 (&& \uBBF8\uC9C0\uC6D0)"'));
6745
6999
  process.exitCode = 1;
6746
7000
  return;
6747
7001
  }
6748
7002
  const goalId = activeGoalId();
6749
7003
  const entry = recordLesson(process.cwd(), lesson, goalId);
6750
7004
  if (!entry) {
6751
- console.log(chalk34.red(" \u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uAD50\uD6C8 \uAE30\uB85D \uC911\uB2E8. \uC6D0\uBCF8/\uBC31\uC5C5 \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
7005
+ console.log(chalk35.red(" \u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uAD50\uD6C8 \uAE30\uB85D \uC911\uB2E8. \uC6D0\uBCF8/\uBC31\uC5C5 \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
6752
7006
  process.exitCode = 1;
6753
7007
  return;
6754
7008
  }
6755
- console.log(chalk34.green(` \u2705 \uAD50\uD6C8 \uAE30\uB85D \u2192 memory failures.lesson (${entry.id})`));
6756
- console.log(chalk34.dim(" \uAD50\uD6C8\xB7\uACB0\uC815\xB7\uC2E4\uD328\xB7\uC131\uACF5 \uBAA8\uB450 vhk memory (\uB2E8\uC77C SoT). vhk memory list \uB85C \uD655\uC778."));
7009
+ console.log(chalk35.green(` \u2705 \uAD50\uD6C8 \uAE30\uB85D \u2192 memory failures.lesson (${entry.id})`));
7010
+ console.log(chalk35.dim(" \uAD50\uD6C8\xB7\uACB0\uC815\xB7\uC2E4\uD328\xB7\uC131\uACF5 \uBAA8\uB450 vhk memory (\uB2E8\uC77C SoT). vhk memory list \uB85C \uD655\uC778."));
6757
7011
  }
6758
7012
  async function resume(opts = {}) {
6759
- console.log(chalk34.bold(`
7013
+ console.log(chalk35.bold(`
6760
7014
  ${ko.agent.resumeTitle}
6761
7015
  `));
6762
7016
  if (!isHardStopActive()) {
6763
- console.log(chalk34.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
7017
+ console.log(chalk35.dim(" HARD_STOP \uD65C\uC131 \uC544\uB2D8 \u2014 \uD560 \uC77C \uC5C6\uC74C."));
6764
7018
  return;
6765
7019
  }
6766
7020
  const reason = readHardStopReason();
6767
7021
  if (reason) {
6768
- console.log(chalk34.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
6769
- console.log(chalk34.dim(` ${reason.split("\n").join("\n ")}`));
7022
+ console.log(chalk35.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
7023
+ console.log(chalk35.dim(` ${reason.split("\n").join("\n ")}`));
6770
7024
  console.log("");
6771
7025
  }
6772
7026
  if (!opts.confirm) {
6773
7027
  console.log(
6774
- chalk34.red(
7028
+ chalk35.red(
6775
7029
  " \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
6776
7030
  )
6777
7031
  );
6778
- console.log(chalk34.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
7032
+ console.log(chalk35.yellow(" \uC0AC\uC720\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC: vhk resume --confirm"));
6779
7033
  process.exitCode = 1;
6780
7034
  return;
6781
7035
  }
6782
7036
  const removed = clearHardStop();
6783
7037
  if (removed) {
6784
- console.log(chalk34.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
7038
+ console.log(chalk35.green(" \u2705 HARD_STOP \uD574\uC81C. \uC790\uB3D9\uD654 \uC7AC\uAC1C \uAC00\uB2A5."));
6785
7039
  } else {
6786
- console.log(chalk34.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
7040
+ console.log(chalk35.dim(" \uD30C\uC77C\uC774 \uC774\uBBF8 \uC5C6\uC74C \u2014 no-op."));
6787
7041
  }
6788
7042
  }
6789
7043
 
@@ -6804,7 +7058,7 @@ async function guardCli(action, approved, run) {
6804
7058
  }]);
6805
7059
  return ok;
6806
7060
  },
6807
- log: (m) => console.log(chalk35.yellow(` ${m}`))
7061
+ log: (m) => console.log(chalk36.yellow(` ${m}`))
6808
7062
  },
6809
7063
  run
6810
7064
  );
@@ -6817,7 +7071,7 @@ async function guardCliDefer(action, approved, run) {
6817
7071
  approved,
6818
7072
  // TTY 면 통과(명령이 자체 확인), 비대화형은 confirm 불가 → 가드가 차단.
6819
7073
  confirm: async () => !!process.stdout.isTTY,
6820
- log: (m) => console.log(chalk35.yellow(` ${m}`))
7074
+ log: (m) => console.log(chalk36.yellow(` ${m}`))
6821
7075
  },
6822
7076
  run
6823
7077
  );
@@ -6860,7 +7114,8 @@ var KO_ALIASES = {
6860
7114
  mission: "\uBBF8\uC158",
6861
7115
  blocker: "\uBE14\uB85C\uCEE4",
6862
7116
  learn: "\uAD50\uD6C8",
6863
- resume: "\uC7AC\uAC1C"
7117
+ resume: "\uC7AC\uAC1C",
7118
+ pattern: "\uD328\uD134"
6864
7119
  };
6865
7120
  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(getVhkVersion());
6866
7121
  program.configureHelp({
@@ -7060,6 +7315,18 @@ program.command("learn <lesson>").alias("\uAD50\uD6C8").description("\uAD50\uD6C
7060
7315
  program.command("resume").alias("\uC7AC\uAC1C").option("--confirm", "\uC0AC\uB78C \uD655\uC778 \u2014 \uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0 (Forbidden \uC704\uBC18)").description(".vhk/HARD_STOP \uD574\uC81C (\uC0AC\uC6A9\uC790\uAC00 \uC0AC\uC720 \uD655\uC778 \uD6C4 --confirm \uD544\uC694)").action(async (opts) => {
7061
7316
  await guardCliDefer("resume", opts?.confirm === true, () => resume(opts));
7062
7317
  });
7318
+ var patternCmd = program.command("pattern").alias("\uD328\uD134").description("\uBC18\uBCF5 \uD328\uD134 \uAC10\uC9C0\xB7\uBAA9\uB85D\xB7dismiss (avoid/reinforce \uD6C4\uBCF4) \u2014 Goal 19").action(async () => {
7319
+ await patternList();
7320
+ });
7321
+ patternCmd.command("detect").alias("\uAC10\uC9C0").option("--min <n>", "\uC784\uACC4 \uD69F\uC218 (\uAE30\uBCF8 3)", "3").option("--json", "JSON \uCD9C\uB825 (CI/MCP\uC6A9)").description("active failures+successes 2\uCD95 \uBD84\uC11D \u2192 patterns[] \uAC31\uC2E0").action(async (opts) => {
7322
+ await patternDetect(opts);
7323
+ });
7324
+ patternCmd.command("list").alias("\uBAA9\uB85D").option("--kind <kind>", "avoid|reinforce \uD544\uD130").option("--all", "\uBCF4\uAD00(archived) \uD3EC\uD568").option("--json", "JSON \uCD9C\uB825 (CI/MCP\uC6A9)").description("\uD328\uD134 \uD6C4\uBCF4 \uBAA9\uB85D (\uAE30\uBCF8 \uD65C\uC131)").action(async (opts) => {
7325
+ await patternList(opts);
7326
+ });
7327
+ patternCmd.command("dismiss <id>").alias("\uBCF4\uAD00").description("\uC624\uD0D0 \uD328\uD134 dismiss (\u2192archived, \uC7AC\uC81C\uC548 \uC548 \uB428)").action(async (id) => {
7328
+ await patternDismiss(id);
7329
+ });
7063
7330
  program.on("command:*", async (operands) => {
7064
7331
  const unknown = operands[0] ?? "";
7065
7332
  const rest = operands.slice(1);
@@ -7114,7 +7381,14 @@ program.action(async () => {
7114
7381
  return diff();
7115
7382
  }
7116
7383
  });
7117
- var isMainModule = !!process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
7384
+ var getRealPath = (p) => {
7385
+ try {
7386
+ return fs13.realpathSync(p);
7387
+ } catch {
7388
+ return p;
7389
+ }
7390
+ };
7391
+ var isMainModule = !!process.argv[1] && getRealPath(fileURLToPath4(import.meta.url)) === getRealPath(process.argv[1]);
7118
7392
  if (isMainModule) {
7119
7393
  try {
7120
7394
  const nlInput = detectNaturalLanguageInput(process.argv);
@@ -7125,9 +7399,9 @@ if (isMainModule) {
7125
7399
  }
7126
7400
  } catch (err) {
7127
7401
  if (isPromptAbortError(err)) {
7128
- console.error(chalk35.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)"));
7402
+ console.error(chalk36.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)"));
7129
7403
  } else {
7130
- console.error(chalk35.red(`
7404
+ console.error(chalk36.red(`
7131
7405
  \u274C ${err instanceof Error ? err.message : String(err)}`));
7132
7406
  }
7133
7407
  process.exitCode = 1;
package/dist/mcp/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  resolveVhkCliInvocation,
4
4
  startMcpServer
5
- } from "../chunk-HCNU6K7D.js";
5
+ } from "../chunk-ODNKCGUG.js";
6
6
 
7
7
  // src/mcp/index.ts
8
8
  var cli = resolveVhkCliInvocation();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byh3071/vhk",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Vibe Harness Kit — AI 코딩 도구·기기를 바꿔도 규칙·맥락이 따라가는 포터빌리티 CLI (sync: Cursor·Claude·Windsurf·Copilot·Antigravity / cloud 백업)",
5
5
  "bin": {
6
6
  "vhk": "dist/index.js",