@byh3071/vhk 2.0.2 → 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,13 +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
50
  import { fileURLToPath as fileURLToPath4 } from "url";
51
51
  import fs13 from "fs";
52
- import chalk35 from "chalk";
52
+ import chalk36 from "chalk";
53
53
  import inquirer14 from "inquirer";
54
54
 
55
55
  // src/lib/nlp-router.ts
@@ -58,6 +58,7 @@ function normalize(input) {
58
58
  }
59
59
  var NLP_KEYWORDS = {
60
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"],
61
62
  undo: ["\uB418\uB3CC\uB824", "\uB418\uB3CC\uB9AC\uAE30", "\uCDE8\uC18C", "\uC6D0\uB798\uB300\uB85C", "\uB864\uBC31", "\uB9AC\uC14B", "reset", "rollback"],
62
63
  status: ["\uC0C1\uD0DC", "\uD604\uD669", "\uC5B4\uB5BB\uAC8C", "\uC5B4\uB54C", "\uC9C0\uAE08"],
63
64
  diff: ["\uBCC0\uACBD", "\uBC14\uB010", "\uBB50\uBC14\uB01C", "\uBC14\uB00C\uC5C8", "\uCC28\uC774", "\uB2EC\uB77C\uC9C4", "\uC218\uC815\uB41C"]
@@ -321,6 +322,12 @@ var RULES = [
321
322
  confidence: "high",
322
323
  test: (t2) => /^출시$|출시\s*해|^publish$|퍼블리시|npm\s*(배포|출시)|버전\s*올|^릴리즈$|^release$/.test(t2) && !/체크|준비|회고/.test(t2)
323
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
+ },
324
331
  // NLP 규칙은 한국어 표현만 매칭. 영문 `goal <sub>` 은 commander 가 직접 처리하도록
325
332
  // 가로채기 금지 — vhk goal list / next / check / done 그대로 동작.
326
333
  {
@@ -390,7 +397,8 @@ var CONTAINER_SUBCOMMANDS = {
390
397
  design: ["palette"],
391
398
  env: ["check"],
392
399
  mode: ["lite", "standard", "strict"],
393
- mission: ["set", "check", "clear"]
400
+ mission: ["set", "check", "clear"],
401
+ pattern: ["detect", "list", "dismiss"]
394
402
  };
395
403
  var CONTAINER_ALIASES = {
396
404
  \uBAA9\uD45C: "goal",
@@ -401,7 +409,8 @@ var CONTAINER_ALIASES = {
401
409
  \uB514\uC790\uC778: "design",
402
410
  \uD658\uACBD\uBCC0\uC218: "env",
403
411
  \uBAA8\uB4DC: "mode",
404
- \uBBF8\uC158: "mission"
412
+ \uBBF8\uC158: "mission",
413
+ \uD328\uD134: "pattern"
405
414
  };
406
415
 
407
416
  // src/lib/cli-args.ts
@@ -535,7 +544,7 @@ function detectNaturalLanguageInput(argv) {
535
544
  }
536
545
 
537
546
  // src/lib/nlp-run.ts
538
- import chalk33 from "chalk";
547
+ import chalk34 from "chalk";
539
548
  import inquirer13 from "inquirer";
540
549
 
541
550
  // src/commands/gate.ts
@@ -775,7 +784,10 @@ function RULES_MD_TEMPLATE(name, description, stack) {
775
784
  "",
776
785
  "## \uAE30\uB85D \uADDC\uCE59",
777
786
  "- \uC138\uC158 \uC885\uB8CC \uC2DC docs/log/YYYY-MM-DD-{\uC791\uC5C5\uBA85}.md \uC0DD\uC131",
778
- "- \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)"
779
791
  ].join("\n");
780
792
  }
781
793
 
@@ -6494,6 +6506,245 @@ async function missionClear() {
6494
6506
  }
6495
6507
  }
6496
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
+
6497
6748
  // src/lib/risk-policy.ts
6498
6749
  var HIGH_RISK_ACTIONS = [
6499
6750
  "undo",
@@ -6664,6 +6915,8 @@ async function dispatchNlpRoute(route, input) {
6664
6915
  return review();
6665
6916
  case "mission":
6666
6917
  return missionShow();
6918
+ case "pattern":
6919
+ return patternList();
6667
6920
  }
6668
6921
  }
6669
6922
  var STATE_CHANGING_COMMANDS = /* @__PURE__ */ new Set([
@@ -6677,14 +6930,14 @@ function requiresConfirmation(route) {
6677
6930
  async function runNaturalLanguageRoute(input) {
6678
6931
  const route = routeNaturalLanguage(input);
6679
6932
  if (!route) {
6680
- console.log(chalk33.yellow(`
6933
+ console.log(chalk34.yellow(`
6681
6934
  \u2753 "${input}" \u2014 ${ko.nlp.notMatched}
6682
6935
  `));
6683
6936
  return;
6684
6937
  }
6685
6938
  console.log("");
6686
- console.log(chalk33.cyan(` \u{1F4AC} "${input}"`));
6687
- console.log(chalk33.cyan(` \u2192 ${route.explanation}`));
6939
+ console.log(chalk34.cyan(` \u{1F4AC} "${input}"`));
6940
+ console.log(chalk34.cyan(` \u2192 ${route.explanation}`));
6688
6941
  if (requiresConfirmation(route)) {
6689
6942
  const { confirm } = await inquirer13.prompt([{
6690
6943
  type: "confirm",
@@ -6693,7 +6946,7 @@ async function runNaturalLanguageRoute(input) {
6693
6946
  default: true
6694
6947
  }]);
6695
6948
  if (!confirm) {
6696
- console.log(chalk33.dim(` ${ko.nlp.menuHint}`));
6949
+ console.log(chalk34.dim(` ${ko.nlp.menuHint}`));
6697
6950
  return;
6698
6951
  }
6699
6952
  }
@@ -6702,7 +6955,7 @@ async function runNaturalLanguageRoute(input) {
6702
6955
  if (riskAction) {
6703
6956
  await runGuarded(
6704
6957
  riskAction,
6705
- { channel: "nl", approved: false, log: (m) => console.log(chalk33.yellow(` ${m}`)) },
6958
+ { channel: "nl", approved: false, log: (m) => console.log(chalk34.yellow(` ${m}`)) },
6706
6959
  () => dispatchNlpRoute(route, input)
6707
6960
  );
6708
6961
  return;
@@ -6711,80 +6964,80 @@ async function runNaturalLanguageRoute(input) {
6711
6964
  }
6712
6965
 
6713
6966
  // src/commands/agent.ts
6714
- import chalk34 from "chalk";
6967
+ import chalk35 from "chalk";
6715
6968
  function activeGoalId() {
6716
6969
  const goals = listGoals("goals");
6717
6970
  const id = selectActiveId(goals);
6718
6971
  return id ?? void 0;
6719
6972
  }
6720
6973
  async function blocker(description) {
6721
- console.log(chalk34.bold(`
6974
+ console.log(chalk35.bold(`
6722
6975
  ${ko.agent.blockerTitle}
6723
6976
  `));
6724
6977
  if (!description || !description.trim()) {
6725
- console.log(chalk34.red(" \u274C \uBE14\uB85C\uCEE4 \uC124\uBA85\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
6726
- 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"'));
6727
6980
  process.exitCode = 1;
6728
6981
  return;
6729
6982
  }
6730
6983
  const goalId = activeGoalId();
6731
6984
  const r = appendBlocker(description, goalId);
6732
- 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)`));
6733
6986
  if (r.hardStopTripped) {
6734
- console.log(chalk34.red.bold(" \u{1F6D1} HARD_STOP \uC790\uB3D9 \uC0DD\uC131 \u2014 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC911\uB2E8."));
6735
- 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."));
6736
6989
  process.exitCode = 2;
6737
6990
  }
6738
6991
  }
6739
6992
  async function learn(lesson) {
6740
- console.log(chalk34.bold(`
6993
+ console.log(chalk35.bold(`
6741
6994
  ${ko.agent.learnTitle}
6742
6995
  `));
6743
6996
  if (!lesson || !lesson.trim()) {
6744
- console.log(chalk34.red(" \u274C \uAD50\uD6C8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."));
6745
- 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)"'));
6746
6999
  process.exitCode = 1;
6747
7000
  return;
6748
7001
  }
6749
7002
  const goalId = activeGoalId();
6750
7003
  const entry = recordLesson(process.cwd(), lesson, goalId);
6751
7004
  if (!entry) {
6752
- 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."));
6753
7006
  process.exitCode = 1;
6754
7007
  return;
6755
7008
  }
6756
- console.log(chalk34.green(` \u2705 \uAD50\uD6C8 \uAE30\uB85D \u2192 memory failures.lesson (${entry.id})`));
6757
- 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."));
6758
7011
  }
6759
7012
  async function resume(opts = {}) {
6760
- console.log(chalk34.bold(`
7013
+ console.log(chalk35.bold(`
6761
7014
  ${ko.agent.resumeTitle}
6762
7015
  `));
6763
7016
  if (!isHardStopActive()) {
6764
- 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."));
6765
7018
  return;
6766
7019
  }
6767
7020
  const reason = readHardStopReason();
6768
7021
  if (reason) {
6769
- console.log(chalk34.yellow(" \u{1F4CB} HARD_STOP \uC0AC\uC720:"));
6770
- 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 ")}`));
6771
7024
  console.log("");
6772
7025
  }
6773
7026
  if (!opts.confirm) {
6774
7027
  console.log(
6775
- chalk34.red(
7028
+ chalk35.red(
6776
7029
  " \u274C --confirm \uD50C\uB798\uADF8 \uC5C6\uC774\uB294 \uD574\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC790\uB3D9 \uD638\uCD9C \uAE08\uC9C0)."
6777
7030
  )
6778
7031
  );
6779
- 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"));
6780
7033
  process.exitCode = 1;
6781
7034
  return;
6782
7035
  }
6783
7036
  const removed = clearHardStop();
6784
7037
  if (removed) {
6785
- 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."));
6786
7039
  } else {
6787
- 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."));
6788
7041
  }
6789
7042
  }
6790
7043
 
@@ -6805,7 +7058,7 @@ async function guardCli(action, approved, run) {
6805
7058
  }]);
6806
7059
  return ok;
6807
7060
  },
6808
- log: (m) => console.log(chalk35.yellow(` ${m}`))
7061
+ log: (m) => console.log(chalk36.yellow(` ${m}`))
6809
7062
  },
6810
7063
  run
6811
7064
  );
@@ -6818,7 +7071,7 @@ async function guardCliDefer(action, approved, run) {
6818
7071
  approved,
6819
7072
  // TTY 면 통과(명령이 자체 확인), 비대화형은 confirm 불가 → 가드가 차단.
6820
7073
  confirm: async () => !!process.stdout.isTTY,
6821
- log: (m) => console.log(chalk35.yellow(` ${m}`))
7074
+ log: (m) => console.log(chalk36.yellow(` ${m}`))
6822
7075
  },
6823
7076
  run
6824
7077
  );
@@ -6861,7 +7114,8 @@ var KO_ALIASES = {
6861
7114
  mission: "\uBBF8\uC158",
6862
7115
  blocker: "\uBE14\uB85C\uCEE4",
6863
7116
  learn: "\uAD50\uD6C8",
6864
- resume: "\uC7AC\uAC1C"
7117
+ resume: "\uC7AC\uAC1C",
7118
+ pattern: "\uD328\uD134"
6865
7119
  };
6866
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());
6867
7121
  program.configureHelp({
@@ -7061,6 +7315,18 @@ program.command("learn <lesson>").alias("\uAD50\uD6C8").description("\uAD50\uD6C
7061
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) => {
7062
7316
  await guardCliDefer("resume", opts?.confirm === true, () => resume(opts));
7063
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
+ });
7064
7330
  program.on("command:*", async (operands) => {
7065
7331
  const unknown = operands[0] ?? "";
7066
7332
  const rest = operands.slice(1);
@@ -7133,9 +7399,9 @@ if (isMainModule) {
7133
7399
  }
7134
7400
  } catch (err) {
7135
7401
  if (isPromptAbortError(err)) {
7136
- 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)"));
7137
7403
  } else {
7138
- console.error(chalk35.red(`
7404
+ console.error(chalk36.red(`
7139
7405
  \u274C ${err instanceof Error ? err.message : String(err)}`));
7140
7406
  }
7141
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.2",
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",