@byh3071/vhk 1.9.0 → 2.0.2

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/README.md CHANGED
@@ -76,7 +76,7 @@ vhk gate # 퀵 5문항 — GO / 다듬기 / 다른 아이디어
76
76
  | 🎯 **Goals 체계** | 단계별 미션 + 게이트 스크립트로 AI가 목표를 스스로 추적 | `vhk goal init` |
77
77
  | ▶️ **자율 루프** | `goal next → 작업 → goal check → goal done`. FAIL 시 `vhk blocker` 수동 기록 → 블로커 3건 누적 시 HARD_STOP 자동 | `vhk goal next` |
78
78
  | 🚧 **HARD_STOP 안전장치** | 블로커 3건 누적 → `.vhk/HARD_STOP` 트립와이어. `vhk resume --confirm` 만 해제 | `vhk blocker "<증상>"` |
79
- | 🔌 **MCP 24 tool** | Cursor·Claude Desktop 등에서 vhk를 채팅으로 호출 | `vhk mcp-init` |
79
+ | 🔌 **MCP 25 tool** | Cursor·Claude Desktop 등에서 vhk를 채팅으로 호출 | `vhk mcp-init` |
80
80
  | 📋 **컨텍스트 영속화** | `.vhk/context.md` + `memory.json` + `brief.md` 로 세션 간 맥락 유지 | `vhk context` |
81
81
  | 🔀 **드리프트 감지** | 규칙 파일이 RULES.md와 어긋나거나 context가 코드보다 낡으면 자동 경고 (읽기전용) | `vhk doctor` |
82
82
 
@@ -172,12 +172,12 @@ vhk 기획 끝났고 바로 시작
172
172
  | `vhk update` | `업데이트` | VHK CLI 최신 버전으로 셀프 업데이트 |
173
173
  | `vhk context` | `맥락` | 프로젝트 트리·스택·CLI 명령 목록을 `.vhk/context.md`로 자동 생성 (AI 어시스턴트용). `--compact` 로 토큰 절감형(Active Goal + 최근 blockers/learnings/memories + 참조 링크) 출력 |
174
174
  | `vhk context-show` | `맥락보기` | 현재 컨텍스트 파일 내용 출력 |
175
- | `vhk memory` | `기억` | 결정사항 기억 관리 (`add` / `list` / `remove`, `.vhk/memory.json` 기반, 태그 지원) |
175
+ | `vhk memory` | `기억` | 기억 v2 4버킷(결정/실패/성공/패턴) 관리 (`add --type` / `list [--all]` / `remove` / `archive` / `resolve` / `unarchive` / `migrate`). 조회 명령(`memory list`·`context`·`brief`) 첫 실행 시 v1→v2 자동 마이그레이션 + `.v1.bak` 원본 백업. 손상 파일은 read 경로에서 보존(원자적 write) |
176
176
  | `vhk brief` | `브리핑` | 프로젝트 정보 + git 상태 + 결정사항 + 레퍼런스 통합 보고서 `.vhk/brief.md` |
177
177
  | `vhk goal` | `목표` | Goal 단계별 미션 관리 (`init` / `list` / `next` / `check` / `done`) — vspec/vooster 패턴 |
178
178
  | `vhk mission` | `미션` | 미션 계약(`set` / `check` / `clear`) — 작업 범위·금지선 선언·검증 (`.vhk/mission.json`, 경로 glob) |
179
179
  | `vhk blocker <설명>` | `블로커` | 블로커 1건 → `docs/state/blockers.md` append. 3건 누적 시 `.vhk/HARD_STOP` 자동 생성 |
180
- | `vhk learn <교훈>` | `교훈` | 교훈 1건 → `docs/state/learnings.md` append (memory.json 별도 SoT) |
180
+ | `vhk learn <교훈>` | `교훈` | 교훈 1건 → `memory.json` `failures.lesson` 단일 SoT (v2.0 통합 구 learnings.md 분리 폐지) |
181
181
  | `vhk resume --confirm` | `재개` | `.vhk/HARD_STOP` 해제 (사람 확인 필요, 자동 호출 금지) |
182
182
 
183
183
  ### 자율 루프 (v1.3+)
@@ -228,12 +228,12 @@ vhk mcp-init # .cursor/mcp.json 자동 생성
228
228
  # 예: "상태 알려줘" → Cursor가 vhk status 도구 호출
229
229
  ```
230
230
 
231
- 노출되는 MCP 도구 **24개** (v1.1):
231
+ 노출되는 MCP 도구 **25개** (v2.0 — `learn` 추가):
232
232
 
233
233
  - **git 워크플로 (8)**: `save`, `undo`, `status`, `diff`, `ship`, `doctor`, `check`, `recap`
234
234
  - **환경/규칙 (4)**: `env`, `env-check`, `sync`, `secure`
235
235
  - **품질/감사 (3)**: `audit`, `harness`, `mcp-init`
236
- - **컨텍스트/기록 (4)**: `context`, `context-show`, `memory-list`, `brief`
236
+ - **컨텍스트/기록 (5)**: `context`, `context-show`, `memory-list`, `brief`, `learn` (교훈 1줄 → memory v2 failures.lesson 기록, 유일한 쓰기 도구)
237
237
  - **dry-info (4)**: `deploy`, `publish`, `migrate`, `update` — 인터랙티브 본질이라 실제 실행 안 함, 진단/안내만
238
238
  - **레퍼런스 (1)**: `ref-list`
239
239
 
@@ -252,7 +252,7 @@ vhk mcp # stdio 서버 시작 (Cursor가 자동으로 호출)
252
252
  | 기능 | 설명 |
253
253
  |------|------|
254
254
  | **context** | 프로젝트 디렉토리 트리(3-depth) + 기술 스택(Next/Nuxt/Vue/Svelte/TS/Tailwind/tsup/Vite/...) 자동 감지 + 29개+ VHK 명령어 목록을 `.vhk/context.md` 마크다운으로 생성. AI 어시스턴트가 프로젝트 맥락을 즉시 파악 |
255
- | **memory** | `.vhk/memory.json` 결정사항 기억 관리. `add <content> --tags X,Y` / `list` / `remove <번호>`. NL은 list (add/remove는 인자 필수 → commander 전용) |
255
+ | **memory** | `.vhk/memory.json` 기억 v2 4버킷(결정/실패/성공/패턴) 관리. `add <content> --type decision\|failure\|success` / `list [--all]` / `remove` / `archive` / `resolve` / `unarchive` / `migrate`. 손상 파일은 read 경로에서 덮어쓰지 않고 보존(원자적 write + `.v1.bak`/`.bak` 백업). NL은 list·migrate (인자 필요한 add/remove/archive/resolve 는 commander 전용) |
256
256
  | **brief** | 프로젝트 정보 + git 상태(브랜치·마지막 커밋·미커밋 변경) + 최근 결정사항 + 레퍼런스를 한 화면에 + `.vhk/brief.md` 저장. `safeExecFile` 기반 (Windows .cmd shim 안전) |
257
257
  | **자연어 확장** | `"맥락 만들어줘"` → context · `"컨텍스트 보여줘"` → context-show · `"기억 목록"` → memory · `"프로젝트 브리핑 만들어줘"` / `"상태 요약"` → brief |
258
258
 
@@ -280,7 +280,7 @@ vhk brief # 세션 종료 시 상태 보고서
280
280
  - **공개 API 안정성**: 명령어 이름, CLI 인자, `.vhk/` 파일 포맷은 v2.0까지 breaking change 없음
281
281
  - **deprecation 절차**: 명령어/옵션 제거 전 1개 마이너 버전(1.x.0)에서 deprecation 경고
282
282
  - **i18n 키**: `ko.ts`의 `t()` 키 이름은 안정. 신규 키 누적, 기존 키 미제거
283
- - **MCP 서버 도구**: v1.0 GA 의 8개 baseline 도구(save/undo/status/diff/ship/doctor/check/recap) 인터페이스 안정 — v1.1 에서 16개 추가되어 총 24개
283
+ - **MCP 서버 도구**: v1.0 GA 의 8개 baseline 도구(save/undo/status/diff/ship/doctor/check/recap) 인터페이스 안정 — v1.1 에서 16개 추가(총 24개), v2.0 에서 `learn` 추가(총 25개)
284
284
 
285
285
  > **`vhk memory` vs Claude Code `auto memory`** — `vhk memory`는 **프로젝트 단위** 결정사항(`.vhk/memory.json`, 팀 공유). Claude Code의 `auto memory`는 **사용자 단위** (`~/.claude/projects/.../memory/`, 개인 컨텍스트). 둘은 별개.
286
286
 
@@ -331,7 +331,7 @@ vhk ref open 1 # 1번 레퍼런스를 브라우저로 열기
331
331
 
332
332
  | 기능 | 설명 |
333
333
  |------|------|
334
- | **MCP 서버** | `vhk mcp` — stdio MCP 서버 첫 도입 (v0.6.0 당시 8개 도구 — save/undo/status/diff/ship/doctor/check/recap). 현재 v1.6 기준 **24개** 로 확장 — 위 "Cursor와 MCP로 연동하기" 섹션 참조 |
334
+ | **MCP 서버** | `vhk mcp` — stdio MCP 서버 첫 도입 (v0.6.0 당시 8개 도구 — save/undo/status/diff/ship/doctor/check/recap). 현재 v2.0 기준 **25개** 로 확장 (`learn` 포함) — 위 "Cursor와 MCP로 연동하기" 섹션 참조 |
335
335
  | **mcp-init** | `vhk mcp-init` — Cursor `.cursor/mcp.json` 자동 생성. 재시작 한 번으로 연동 완료 |
336
336
  | **자연어 라우팅 확장** | `vhk mcp설정` → `vhk mcp-init` 별칭 |
337
337
  | **보안** | MCP save 도구의 shell injection 차단 — 모든 git 호출에 shell 미경유 `safeExecFile` 사용 |
@@ -1386,7 +1386,8 @@ function toAgentsMd(sections, projectName) {
1386
1386
  "## Loop Protocol",
1387
1387
  "- \uB8E8\uD504: `context \u2192 goal next \u2192 \uC791\uC5C5 \u2192 goal check \u2192 goal done`",
1388
1388
  "- \uC791\uC5C5 \uC2DC\uC791 \uC2DC `.vhk/HARD_STOP` \uD655\uC778 \u2014 \uC788\uC73C\uBA74 \uBAA8\uB4E0 \uC790\uB3D9\uD654 \uC989\uC2DC \uC911\uB2E8.",
1389
- "- active goal \uB9CC \uC791\uC5C5. `docs/state`(next-task/blockers/learnings)\uB294 SoT, append-only.",
1389
+ "- active goal \uB9CC \uC791\uC5C5. `docs/state`(next-task/blockers)\uB294 append-only.",
1390
+ "- \uAD50\uD6C8\xB7\uACB0\uC815\xB7\uC2E4\uD328\xB7\uC131\uACF5\uC740 `vhk memory`(memory v2 4\uBC84\uD0B7, \uB2E8\uC77C \uCD9C\uCC98).",
1390
1391
  "- \uAC8C\uC774\uD2B8(tsc / test:run / build) \uD1B5\uACFC\uD574\uC57C\uB9CC `vhk goal done`.",
1391
1392
  ""
1392
1393
  ];
@@ -2972,9 +2973,19 @@ ${cliStatus}
2972
2973
  );
2973
2974
  server.registerTool(
2974
2975
  "memory-list",
2975
- { description: "\uC800\uC7A5\uB41C \uACB0\uC815\uC0AC\uD56D(memory) \uBAA9\uB85D \uBCF4\uAE30 (add/remove \uB294 \uC778\uC790 \uD544\uC694 \u2192 CLI \uC804\uC6A9)" },
2976
+ {
2977
+ description: "memory v2 \uD65C\uC131 \uAE30\uC5B5 \uBAA9\uB85D (decisions/failures/successes \u2014 \uAE30\uBCF8 active \uB9CC). add/remove/archive/resolve/unarchive/migrate \uB294 \uC778\uC790\xB7\uC4F0\uAE30 \u2192 CLI \uC804\uC6A9"
2978
+ },
2976
2979
  async () => runVhkCli(["memory", "list"], "memory list")
2977
2980
  );
2981
+ server.registerTool(
2982
+ "learn",
2983
+ {
2984
+ description: "\uAD50\uD6C8 1\uC904 \uAE30\uB85D \u2192 memory v2 failures.lesson (\uB2E8\uC77C SoT, Evolution Loop \uD3D0\uD68C\uB85C)",
2985
+ inputSchema: { lesson: z.string().describe("\uAE30\uB85D\uD560 \uAD50\uD6C8 \uD55C \uC904") }
2986
+ },
2987
+ async ({ lesson }) => runVhkCli(["learn", lesson], "learn")
2988
+ );
2978
2989
  server.registerTool(
2979
2990
  "context-show",
2980
2991
  { description: ".vhk/context.md \uD30C\uC77C \uB0B4\uC6A9 \uBCF4\uAE30 (\uC5C6\uC73C\uBA74 `vhk context` \uC548\uB0B4)" },
package/dist/index.js CHANGED
@@ -43,11 +43,12 @@ import {
43
43
  stripBom,
44
44
  sync,
45
45
  t
46
- } from "./chunk-RXDOM4QT.js";
46
+ } from "./chunk-HCNU6K7D.js";
47
47
 
48
48
  // src/index.ts
49
49
  import { Command, Help } from "commander";
50
- import { pathToFileURL } from "url";
50
+ import { fileURLToPath as fileURLToPath4 } from "url";
51
+ import fs13 from "fs";
51
52
  import chalk35 from "chalk";
52
53
  import inquirer14 from "inquirer";
53
54
 
@@ -183,11 +184,20 @@ var RULES = [
183
184
  confidence: "high",
184
185
  test: (t2) => /감사|취약점|audit|vulnerability|보안\s*감사|보안\s*취약|의존성\s*취약/.test(t2)
185
186
  },
187
+ // memory 마이그레이션은 패키지매니저 migrate 보다 **먼저** 평가 — "기억/메모리 마이그레이트" 가
188
+ // pnpm 전환(vhk migrate)으로 새지 않도록. 기억/메모리/memory 한정이라 bare "마이그레이트"는 안 가로챔.
189
+ {
190
+ command: "memory",
191
+ args: ["migrate"],
192
+ explanation: "memory.json v1 \u2192 v2 \uB9C8\uC774\uADF8\uB808\uC774\uC158 (vhk memory migrate)",
193
+ confidence: "high",
194
+ test: (t2) => /(기억|메모리|memory)\s*(을|를)?\s*(마이그레이|migrat)/.test(t2)
195
+ },
186
196
  {
187
197
  command: "migrate",
188
198
  explanation: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 (vhk migrate)",
189
199
  confidence: "high",
190
- test: (t2) => /전환|마이그레이트|migrate|패키지\s*매니저|npm.*pnpm|pnpm.*npm|yarn.*전환|npm.*전환|pnpm.*전환/.test(t2)
200
+ test: (t2) => /전환|마이그레이(트|션)|migrate|패키지\s*매니저|npm.*pnpm|pnpm.*npm|yarn.*전환|npm.*전환|pnpm.*전환/.test(t2) && !/(기억|메모리|memory)\s*(을|를)?\s*(마이그레이|migrat)/.test(t2)
191
201
  },
192
202
  {
193
203
  command: "update",
@@ -211,7 +221,9 @@ var RULES = [
211
221
  command: "memory",
212
222
  explanation: "\uAE30\uC5B5 \uBAA9\uB85D \uC870\uD68C (vhk memory list)",
213
223
  confidence: "high",
214
- test: (t2) => /^기억$|기억\s*(목록|보|확인|뭐)|memory.*list|결정사항\s*(목록|확인|보여)/.test(t2) && !/(추가|add|삭제|remove|저장|기록해)/.test(t2)
224
+ // 보관(archive)/해결(resolve)/마이그레이션은 list 아니다 → **제외 토큰 한 곳**에서 오라우팅 차단.
225
+ // archive/resolve 는 <번호> 인자가 필요해 NL 미지원 → 매칭 안 되면 notMatched 가 정직(잘못된 list 실행 금지).
226
+ test: (t2) => /^기억$|기억\s*(목록|보|확인|뭐)|memory.*list|결정사항\s*(목록|확인|보여)/.test(t2) && !/(추가|add|삭제|remove|저장|기록해|보관|아카이브|archive|마이그레이|migrat|해결|복구)/.test(t2)
215
227
  },
216
228
  {
217
229
  command: "brief",
@@ -372,7 +384,7 @@ function extractNotionUrl(input) {
372
384
  var CONTAINER_SUBCOMMANDS = {
373
385
  goal: ["list", "next", "check", "init", "done", "sync"],
374
386
  ref: ["add", "list", "open"],
375
- memory: ["add", "list", "remove"],
387
+ memory: ["add", "list", "remove", "archive", "resolve", "unarchive", "migrate"],
376
388
  cloud: ["push", "pull"],
377
389
  secure: ["scan"],
378
390
  design: ["palette"],
@@ -1705,32 +1717,6 @@ ${line}
1705
1717
  }
1706
1718
  return { count, hardStopTripped };
1707
1719
  }
1708
- function appendLearning(lesson, goalId) {
1709
- ensureStateDir();
1710
- const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
1711
- const line = `- [${isoDate()} ${tag}] ${lesson.trim()}`;
1712
- if (!existsSync(LEARNINGS_PATH)) {
1713
- writeFileSync(
1714
- LEARNINGS_PATH,
1715
- `# Learnings
1716
-
1717
- _Append-only. \uD55C \uC904 = \uD55C \uAD50\uD6C8._
1718
-
1719
- ${line}
1720
- `,
1721
- "utf-8"
1722
- );
1723
- } else {
1724
- appendFileSync(LEARNINGS_PATH, `${line}
1725
- `, "utf-8");
1726
- }
1727
- }
1728
- function getRecentLearnings(limit = 3) {
1729
- if (!existsSync(LEARNINGS_PATH)) return [];
1730
- const lines = readFileSync(LEARNINGS_PATH, "utf-8").split(/\r?\n/);
1731
- const entries = lines.filter((l) => l.startsWith("- ["));
1732
- return entries.slice(-limit);
1733
- }
1734
1720
  function getActiveBlockers(limit = 3) {
1735
1721
  if (!existsSync(BLOCKERS_PATH)) return [];
1736
1722
  const lines = readFileSync(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
@@ -4444,15 +4430,432 @@ async function update() {
4444
4430
 
4445
4431
  // src/commands/context.ts
4446
4432
  import {
4447
- existsSync as existsSync11,
4448
- mkdirSync as mkdirSync7,
4449
- readFileSync as readFileSync4,
4433
+ existsSync as existsSync12,
4434
+ mkdirSync as mkdirSync8,
4435
+ readFileSync as readFileSync5,
4450
4436
  readdirSync as readdirSync2,
4451
4437
  statSync as statSync2,
4452
- writeFileSync as writeFileSync7
4438
+ writeFileSync as writeFileSync8
4453
4439
  } from "fs";
4440
+ import { join as join8 } from "path";
4441
+ import chalk24 from "chalk";
4442
+
4443
+ // src/commands/memory.ts
4444
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, copyFileSync, readFileSync as readFileSync4, renameSync, rmSync as rmSync3 } from "fs";
4454
4445
  import { join as join7 } from "path";
4455
4446
  import chalk23 from "chalk";
4447
+ var MEMORY_PATH_REL = join7(".vhk", "memory.json");
4448
+ var MEMORY_SCHEMA_VERSION = 2;
4449
+ function emptyV2() {
4450
+ return { schemaVersion: MEMORY_SCHEMA_VERSION, decisions: [], failures: [], successes: [], patterns: [] };
4451
+ }
4452
+ function isV2(raw) {
4453
+ return !!raw && typeof raw === "object" && raw.schemaVersion === 2;
4454
+ }
4455
+ var BUCKET_PREFIX = { decision: "d", failure: "f", success: "s" };
4456
+ function arr(v) {
4457
+ return Array.isArray(v) ? v : [];
4458
+ }
4459
+ function normalizeV2(raw) {
4460
+ return {
4461
+ schemaVersion: MEMORY_SCHEMA_VERSION,
4462
+ decisions: arr(raw.decisions),
4463
+ failures: arr(raw.failures),
4464
+ successes: arr(raw.successes),
4465
+ patterns: arr(raw.patterns)
4466
+ };
4467
+ }
4468
+ function parseLearnings(rawLearnings) {
4469
+ const out = [];
4470
+ for (const line of rawLearnings.split(/\r?\n/)) {
4471
+ const m = line.match(/^-\s*\[(\d{4}-\d{2}-\d{2})\s+([^\]]*)\]\s*(.+)$/);
4472
+ if (m) out.push({ date: m[1], tag: m[2].trim(), lesson: m[3].trim() });
4473
+ }
4474
+ return out;
4475
+ }
4476
+ function migrateMemory(rawMemory, rawLearnings) {
4477
+ if (isV2(rawMemory)) return normalizeV2(rawMemory);
4478
+ const v2 = emptyV2();
4479
+ if (Array.isArray(rawMemory)) {
4480
+ rawMemory.forEach((m, i) => {
4481
+ const item = m;
4482
+ if (item && typeof item.content === "string") {
4483
+ v2.decisions.push({
4484
+ id: `d${i + 1}`,
4485
+ content: item.content,
4486
+ tags: arr(item.tags),
4487
+ createdAt: typeof item.addedAt === "string" ? item.addedAt : "",
4488
+ status: "active"
4489
+ });
4490
+ }
4491
+ });
4492
+ }
4493
+ if (rawLearnings) {
4494
+ parseLearnings(rawLearnings).forEach((l, i) => {
4495
+ v2.failures.push({
4496
+ id: `f${i + 1}`,
4497
+ content: "",
4498
+ tags: l.tag ? [l.tag] : [],
4499
+ createdAt: l.date,
4500
+ status: "active",
4501
+ lesson: l.lesson
4502
+ });
4503
+ });
4504
+ }
4505
+ return v2;
4506
+ }
4507
+ function readRaw(cwd) {
4508
+ const p = join7(cwd, MEMORY_PATH_REL);
4509
+ if (!existsSync11(p)) return { kind: "missing" };
4510
+ try {
4511
+ return { kind: "parsed", value: readJsonFile(p) };
4512
+ } catch {
4513
+ return { kind: "error" };
4514
+ }
4515
+ }
4516
+ function warnUnreadable(cwd) {
4517
+ const p = join7(cwd, MEMORY_PATH_REL);
4518
+ console.error(chalk23.red(`
4519
+ \u26A0\uFE0F ${MEMORY_PATH_REL} \uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC190\uC0C1/\uBD80\uBD84 \uC4F0\uAE30 \uC758\uC2EC).`));
4520
+ console.error(chalk23.yellow(` \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uACE0 \uBE48 \uBA54\uBAA8\uB9AC\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4 \u2014 \uC6D0\uBCF8 \uBCF4\uC874\uB428.`));
4521
+ console.error(chalk23.dim(` \uD655\uC778/\uBCF5\uAD6C: ${p} (\uBC31\uC5C5: ${p}.bak / ${p}.v1.bak)`));
4522
+ }
4523
+ function warnUnrecognized(cwd) {
4524
+ const p = join7(cwd, MEMORY_PATH_REL);
4525
+ console.error(chalk23.red(`
4526
+ \u26A0\uFE0F ${MEMORY_PATH_REL} \uAC00 \uC778\uC2DD \uAC00\uB2A5\uD55C \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 (v1 \uBC30\uC5F4/v2 \uAC1D\uCCB4 \uC544\uB2D8).`));
4527
+ console.error(chalk23.yellow(` \uBBF8\uB798 \uC2A4\uD0A4\uB9C8/\uC218\uB3D9 \uD3B8\uC9D1 \uC758\uC2EC \u2014 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(\uC6D0\uBCF8 \uBCF4\uC874). \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4.`));
4528
+ console.error(chalk23.dim(` \uD655\uC778: ${p}`));
4529
+ }
4530
+ function readLearningsRaw(cwd) {
4531
+ const p = join7(cwd, "docs", "state", "learnings.md");
4532
+ if (!existsSync11(p)) return void 0;
4533
+ try {
4534
+ return stripBom(readFileSync4(p, "utf-8"));
4535
+ } catch {
4536
+ return void 0;
4537
+ }
4538
+ }
4539
+ function readMemory(cwd = process.cwd()) {
4540
+ const raw = readRaw(cwd);
4541
+ if (raw.kind === "error") {
4542
+ warnUnreadable(cwd);
4543
+ return emptyV2();
4544
+ }
4545
+ if (raw.kind === "parsed") {
4546
+ if (isV2(raw.value)) return normalizeV2(raw.value);
4547
+ if (Array.isArray(raw.value)) {
4548
+ const v2 = migrateMemory(raw.value, readLearningsRaw(cwd));
4549
+ try {
4550
+ writeMemory(cwd, v2);
4551
+ } catch {
4552
+ console.error(chalk23.yellow(` (v2 \uC601\uAD6C\uD654 \uBCF4\uB958 \u2014 ${MEMORY_PATH_REL} \uC7A0\uAE08 \uC758\uC2EC. \uC774\uBC88\uC5D4 \uBA54\uBAA8\uB9AC\uC0C1\uC73C\uB85C\uB9CC \uC9C4\uD589)`));
4553
+ }
4554
+ return v2;
4555
+ }
4556
+ warnUnrecognized(cwd);
4557
+ return emptyV2();
4558
+ }
4559
+ return migrateMemory(null, readLearningsRaw(cwd));
4560
+ }
4561
+ function loadForMutation(cwd) {
4562
+ const raw = readRaw(cwd);
4563
+ if (raw.kind === "error") {
4564
+ warnUnreadable(cwd);
4565
+ return { ok: false };
4566
+ }
4567
+ if (raw.kind === "parsed") {
4568
+ if (isV2(raw.value)) return { ok: true, mem: normalizeV2(raw.value) };
4569
+ if (Array.isArray(raw.value)) return { ok: true, mem: migrateMemory(raw.value, readLearningsRaw(cwd)) };
4570
+ warnUnrecognized(cwd);
4571
+ return { ok: false };
4572
+ }
4573
+ return { ok: true, mem: migrateMemory(null, readLearningsRaw(cwd)) };
4574
+ }
4575
+ function isActive(e) {
4576
+ return e.status !== "archived" && e.status !== "resolved";
4577
+ }
4578
+ function writeMemory(cwd, mem) {
4579
+ const p = join7(cwd, MEMORY_PATH_REL);
4580
+ mkdirSync7(join7(cwd, ".vhk"), { recursive: true });
4581
+ if (existsSync11(p)) {
4582
+ const cur = readRaw(cwd);
4583
+ const curIsV2 = cur.kind === "parsed" && isV2(cur.value);
4584
+ if (cur.kind !== "error" && !curIsV2 && !existsSync11(p + ".v1.bak")) {
4585
+ try {
4586
+ copyFileSync(p, p + ".v1.bak");
4587
+ } catch {
4588
+ }
4589
+ }
4590
+ if (cur.kind !== "error") {
4591
+ try {
4592
+ copyFileSync(p, p + ".bak");
4593
+ } catch {
4594
+ }
4595
+ }
4596
+ }
4597
+ const tmpPath = p + ".tmp";
4598
+ writeFileSync7(tmpPath, JSON.stringify(mem, null, 2) + "\n", "utf-8");
4599
+ try {
4600
+ renameSync(tmpPath, p);
4601
+ } catch (err) {
4602
+ try {
4603
+ rmSync3(tmpPath, { force: true });
4604
+ } catch {
4605
+ }
4606
+ throw err;
4607
+ }
4608
+ }
4609
+ function nextId(bucket, mem) {
4610
+ const prefix = BUCKET_PREFIX[bucket];
4611
+ const list = bucket === "decision" ? mem.decisions : bucket === "failure" ? mem.failures : mem.successes;
4612
+ const idRe = new RegExp(`^${prefix}(\\d+)$`);
4613
+ let max = 0;
4614
+ for (const e of list) {
4615
+ const m = e.id.match(idRe);
4616
+ if (m) max = Math.max(max, Number(m[1]));
4617
+ }
4618
+ return `${prefix}${max + 1}`;
4619
+ }
4620
+ function orderedAll(mem) {
4621
+ return [
4622
+ ...mem.decisions.map((entry) => ({ bucket: "decision", entry })),
4623
+ ...mem.failures.map((entry) => ({ bucket: "failure", entry })),
4624
+ ...mem.successes.map((entry) => ({ bucket: "success", entry }))
4625
+ ];
4626
+ }
4627
+ var VALID_BUCKETS = ["decision", "failure", "success"];
4628
+ async function memoryAdd(content, opts = {}) {
4629
+ console.log(chalk23.bold("\n\u{1F9E0} " + t("memory.addTitle")));
4630
+ console.log(chalk23.gray("\u2500".repeat(40)));
4631
+ if (!content || !content.trim()) {
4632
+ console.log(chalk23.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4633
+ console.log(chalk23.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9" --type decision'));
4634
+ process.exitCode = 1;
4635
+ return;
4636
+ }
4637
+ const typeRaw = opts.type ?? "decision";
4638
+ if (!VALID_BUCKETS.includes(typeRaw)) {
4639
+ console.log(chalk23.red(`\u274C --type \uC740 decision|failure|success \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uBC1B\uC740 \uAC12: ${typeRaw}).`));
4640
+ process.exitCode = 1;
4641
+ return;
4642
+ }
4643
+ const type = typeRaw;
4644
+ if (type === "decision" && (opts.why || opts.lesson)) {
4645
+ console.log(chalk23.yellow("\u26A0\uFE0F --why/--lesson \uC740 --type failure|success \uC5D0\uC11C\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4 \u2014 decision \uC5D0\uC11C\uB294 \uBB34\uC2DC\uB428."));
4646
+ }
4647
+ const cwd = process.cwd();
4648
+ const loaded = loadForMutation(cwd);
4649
+ if (!loaded.ok) {
4650
+ console.log(chalk23.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uC800\uC7A5 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874). \uBC31\uC5C5 \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
4651
+ process.exitCode = 1;
4652
+ return;
4653
+ }
4654
+ const mem = loaded.mem;
4655
+ const base = {
4656
+ id: nextId(type, mem),
4657
+ content: content.trim(),
4658
+ tags: opts.tags && opts.tags.length > 0 ? opts.tags : [],
4659
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4660
+ status: "active"
4661
+ };
4662
+ if (type === "failure") mem.failures.push({ ...base, why: opts.why, lesson: opts.lesson });
4663
+ else if (type === "success") mem.successes.push({ ...base, why: opts.why });
4664
+ else mem.decisions.push(base);
4665
+ writeMemory(cwd, mem);
4666
+ console.log(chalk23.green(`
4667
+ \u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (${type} #${base.id})`));
4668
+ console.log(chalk23.cyan(` \u{1F4DD} ${base.content}`));
4669
+ printNextStep({ message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!", command: "vhk memory list", cursorHint: "\uAE30\uC5B5 \uBAA9\uB85D \uBCF4\uC5EC\uC918" });
4670
+ }
4671
+ var STATUS_ICON2 = { active: "\u{1F7E2}", resolved: "\u2705", archived: "\u{1F4E6}" };
4672
+ var BUCKET_LABEL = { decision: "\uACB0\uC815", failure: "\uC2E4\uD328", success: "\uC131\uACF5" };
4673
+ async function memoryList(opts = {}) {
4674
+ console.log(chalk23.bold("\n\u{1F9E0} " + t("memory.listTitle")));
4675
+ console.log(chalk23.gray("\u2500".repeat(40)));
4676
+ const mem = readMemory(process.cwd());
4677
+ const all = orderedAll(mem);
4678
+ const visible = all.map((x, i) => ({ ...x, n: i + 1 })).filter((x) => (opts.all || isActive(x.entry)) && (!opts.type || x.bucket === opts.type));
4679
+ if (visible.length === 0) {
4680
+ console.log(chalk23.yellow("\n\u{1F4ED} \uD45C\uC2DC\uD560 \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4681
+ console.log(chalk23.gray(' vhk memory add "\uB0B4\uC6A9" --type decision|failure|success'));
4682
+ return;
4683
+ }
4684
+ console.log(chalk23.cyan(`
4685
+ ${visible.length}\uAC1C${opts.all ? " (\uBCF4\uAD00 \uD3EC\uD568)" : " (\uD65C\uC131)"}:
4686
+ `));
4687
+ for (const x of visible) {
4688
+ const e = x.entry;
4689
+ const fail = e;
4690
+ console.log(` [${x.n}] ${STATUS_ICON2[e.status] ?? "\u{1F7E2}"} (${BUCKET_LABEL[x.bucket]}) ${e.content || (fail.lesson ? "\u{1F4A1} " + fail.lesson : "(\uB0B4\uC6A9 \uC5C6\uC74C)")}`);
4691
+ if (fail.lesson && e.content) console.log(chalk23.dim(` \u{1F4A1} \uAD50\uD6C8: ${fail.lesson}`));
4692
+ if (fail.why) console.log(chalk23.dim(` \u21B3 ${fail.why}`));
4693
+ if (e.tags.length > 0) console.log(chalk23.blue(` \u{1F3F7}\uFE0F ${e.tags.join(", ")}`));
4694
+ }
4695
+ }
4696
+ function resolveIndex(indexStr, len) {
4697
+ const idx = parseInt(indexStr, 10) - 1;
4698
+ if (Number.isNaN(idx) || idx < 0 || idx >= len) return null;
4699
+ return idx;
4700
+ }
4701
+ async function memoryRemove(indexStr) {
4702
+ const cwd = process.cwd();
4703
+ const loaded = loadForMutation(cwd);
4704
+ if (!loaded.ok) {
4705
+ console.log(chalk23.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uC0AD\uC81C \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
4706
+ process.exitCode = 1;
4707
+ return;
4708
+ }
4709
+ const mem = loaded.mem;
4710
+ const all = orderedAll(mem);
4711
+ const idx = resolveIndex(indexStr, all.length);
4712
+ if (idx === null) {
4713
+ console.log(chalk23.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${all.length || 0})`));
4714
+ process.exitCode = 1;
4715
+ return;
4716
+ }
4717
+ const { bucket, entry } = all[idx];
4718
+ const list = bucket === "decision" ? mem.decisions : bucket === "failure" ? mem.failures : mem.successes;
4719
+ const pos = list.findIndex((e) => e === entry);
4720
+ if (pos >= 0) list.splice(pos, 1);
4721
+ writeMemory(cwd, mem);
4722
+ console.log(chalk23.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
4723
+ console.log(chalk23.gray(` ${entry.content || entry.lesson || entry.id}`));
4724
+ }
4725
+ function resolveEntryForMutation(indexStr) {
4726
+ const cwd = process.cwd();
4727
+ const loaded = loadForMutation(cwd);
4728
+ if (!loaded.ok) {
4729
+ console.log(chalk23.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uC791\uC5C5 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
4730
+ process.exitCode = 1;
4731
+ return null;
4732
+ }
4733
+ const mem = loaded.mem;
4734
+ const all = orderedAll(mem);
4735
+ const idx = resolveIndex(indexStr, all.length);
4736
+ if (idx === null) {
4737
+ console.log(chalk23.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${all.length || 0})`));
4738
+ process.exitCode = 1;
4739
+ return null;
4740
+ }
4741
+ return { cwd, mem, entry: all[idx].entry };
4742
+ }
4743
+ function entryLabel(entry) {
4744
+ return entry.content || entry.lesson || entry.id;
4745
+ }
4746
+ async function memoryArchive(indexStr) {
4747
+ const r = resolveEntryForMutation(indexStr);
4748
+ if (!r) return;
4749
+ r.entry.status = "archived";
4750
+ r.entry.archivedAt = (/* @__PURE__ */ new Date()).toISOString();
4751
+ delete r.entry.resolvedAt;
4752
+ writeMemory(r.cwd, r.mem);
4753
+ console.log(chalk23.green(`
4754
+ \u{1F4E6} \uBCF4\uAD00\uB428: ${entryLabel(r.entry)}`));
4755
+ console.log(chalk23.dim(" (\uD328\uD134 \uAC10\uC9C0\xB7\uC9C4\uD654\uC5D0\uC11C \uC81C\uC678\uB429\uB2C8\uB2E4 \u2014 \uC120\uC21C\uD658). \uB418\uB3CC\uB9AC\uAE30: vhk memory unarchive <\uBC88\uD638>"));
4756
+ }
4757
+ async function memoryResolve(indexStr) {
4758
+ const r = resolveEntryForMutation(indexStr);
4759
+ if (!r) return;
4760
+ r.entry.status = "resolved";
4761
+ r.entry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
4762
+ delete r.entry.archivedAt;
4763
+ writeMemory(r.cwd, r.mem);
4764
+ console.log(chalk23.green(`
4765
+ \u2705 \uD574\uACB0\uB428: ${entryLabel(r.entry)}`));
4766
+ console.log(chalk23.dim(" (vhk memory list --all \uB85C \uD655\uC778. \uB418\uB3CC\uB9AC\uAE30: vhk memory unarchive <\uBC88\uD638>)"));
4767
+ }
4768
+ async function memoryUnarchive(indexStr) {
4769
+ const r = resolveEntryForMutation(indexStr);
4770
+ if (!r) return;
4771
+ if (isActive(r.entry)) {
4772
+ console.log(chalk23.dim(` \uC774\uBBF8 \uD65C\uC131 \uD56D\uBAA9\uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C: ${entryLabel(r.entry)}`));
4773
+ return;
4774
+ }
4775
+ r.entry.status = "active";
4776
+ delete r.entry.archivedAt;
4777
+ delete r.entry.resolvedAt;
4778
+ writeMemory(r.cwd, r.mem);
4779
+ console.log(chalk23.green(`
4780
+ \u{1F7E2} \uD65C\uC131\uC73C\uB85C \uBCF5\uAD6C\uB428: ${entryLabel(r.entry)}`));
4781
+ }
4782
+ async function memoryMigrate() {
4783
+ const cwd = process.cwd();
4784
+ const raw = readRaw(cwd);
4785
+ if (raw.kind === "error") {
4786
+ warnUnreadable(cwd);
4787
+ console.log(chalk23.red(" \u274C \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC911\uB2E8 (\uC190\uC0C1 \uC758\uC2EC). \uC6D0\uBCF8 \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
4788
+ process.exitCode = 1;
4789
+ return;
4790
+ }
4791
+ if (raw.kind === "parsed" && isV2(raw.value)) {
4792
+ console.log(chalk23.dim(" \uC774\uBBF8 memory schema v2 \uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C(\uBA71\uB4F1)."));
4793
+ return;
4794
+ }
4795
+ if (raw.kind === "parsed" && !Array.isArray(raw.value)) {
4796
+ warnUnrecognized(cwd);
4797
+ console.log(chalk23.red(" \u274C v1(\uD3C9\uBA74 \uBC30\uC5F4) \uD615\uC2DD\uC774 \uC544\uB2C8\uB77C \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uB300\uC0C1\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uC911\uB2E8(\uC6D0\uBCF8 \uBCF4\uC874)."));
4798
+ process.exitCode = 1;
4799
+ return;
4800
+ }
4801
+ const learnings = readLearningsRaw(cwd);
4802
+ const hadFile = raw.kind === "parsed";
4803
+ if (raw.kind === "missing" && !learnings) {
4804
+ console.log(chalk23.yellow(" \u2139\uFE0F \uB9C8\uC774\uADF8\uB808\uC774\uC158\uD560 v1 memory.json / learnings.md \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C."));
4805
+ return;
4806
+ }
4807
+ const v2 = migrateMemory(raw.kind === "parsed" ? raw.value : null, learnings);
4808
+ writeMemory(cwd, v2);
4809
+ const backupNote = hadFile ? " (.v1.bak \uC6D0\uBCF8 \uC601\uAD6C \uBC31\uC5C5)" : " (\uC2E0\uADDC \uC0DD\uC131 \u2014 \uC6D0\uBCF8 \uC5C6\uC74C, \uBC31\uC5C5 \uC5C6\uC74C)";
4810
+ console.log(chalk23.green(`
4811
+ \u2705 memory.json \u2192 v2 \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC644\uB8CC${backupNote}`));
4812
+ console.log(
4813
+ chalk23.dim(
4814
+ ` decisions ${v2.decisions.length} \xB7 failures ${v2.failures.length} \xB7 successes ${v2.successes.length}` + (learnings ? " (learnings.md \uAD50\uD6C8 \uD761\uC218 \u2014 \uC774\uD6C4 vhk learn \uC740 memory \uC5D0 \uAE30\uB85D)" : "")
4815
+ )
4816
+ );
4817
+ }
4818
+ function activeMemoryLines(mem, limit = 5) {
4819
+ const lines = [];
4820
+ const fmt = (e) => {
4821
+ const f = e;
4822
+ const base = e.content || (f.lesson ? `\u{1F4A1} ${f.lesson}` : e.id);
4823
+ return e.content && f.lesson ? `${base} \u2014 \u{1F4A1} ${f.lesson}` : base;
4824
+ };
4825
+ const section = (label, list) => {
4826
+ const act = list.filter(isActive);
4827
+ if (act.length === 0) return;
4828
+ lines.push(`**${label}** (${act.length})`);
4829
+ for (const e of act.slice(-limit)) lines.push(`- ${fmt(e)}`);
4830
+ if (act.length > limit) lines.push(`- \u2026 \uC678 ${act.length - limit}\uAC1C`);
4831
+ lines.push("");
4832
+ };
4833
+ section("\uACB0\uC815 (decisions)", mem.decisions);
4834
+ section("\uC2E4\uD328\xB7\uAD50\uD6C8 (failures)", mem.failures);
4835
+ section("\uC131\uACF5 (successes)", mem.successes);
4836
+ const pats = mem.patterns.length;
4837
+ if (pats > 0) lines.push(`**\uD328\uD134 \uD6C4\uBCF4 (patterns)**: ${pats}\uAC1C \u2014 \`vhk pattern\``, "");
4838
+ return lines;
4839
+ }
4840
+ function recordLesson(cwd, lesson, goalId) {
4841
+ const loaded = loadForMutation(cwd);
4842
+ if (!loaded.ok) return null;
4843
+ const mem = loaded.mem;
4844
+ const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
4845
+ const entry = {
4846
+ id: nextId("failure", mem),
4847
+ content: "",
4848
+ tags: [tag],
4849
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4850
+ status: "active",
4851
+ lesson: lesson.trim()
4852
+ };
4853
+ mem.failures.push(entry);
4854
+ writeMemory(cwd, mem);
4855
+ return entry;
4856
+ }
4857
+
4858
+ // src/commands/context.ts
4456
4859
  var CONTEXT_PATH = ".vhk/context.md";
4457
4860
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
4458
4861
  "node_modules",
@@ -4477,7 +4880,7 @@ function buildTree(dir, prefix = "", maxDepth = 3, depth = 0) {
4477
4880
  filtered.forEach((entry, index) => {
4478
4881
  const isLast = index === filtered.length - 1;
4479
4882
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4480
- const fullPath = join7(dir, entry);
4883
+ const fullPath = join8(dir, entry);
4481
4884
  const stat = statSync2(fullPath);
4482
4885
  const isDir = stat.isDirectory();
4483
4886
  lines.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
@@ -4509,8 +4912,8 @@ function extractTechStack() {
4509
4912
  else if (all.jest) stack["\uD14C\uC2A4\uD2B8"] = "jest";
4510
4913
  if (all.commander) stack["CLI"] = "commander";
4511
4914
  if (all.inquirer) stack["\uC778\uD130\uB799\uD2F0\uBE0C"] = "inquirer";
4512
- if (existsSync11("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
4513
- else if (existsSync11("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
4915
+ if (existsSync12("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
4916
+ else if (existsSync12("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
4514
4917
  else stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "npm";
4515
4918
  if (pkg.name) stack["\uD328\uD0A4\uC9C0 \uC774\uB984"] = pkg.name;
4516
4919
  if (pkg.version) stack["\uBC84\uC804"] = pkg.version;
@@ -4553,8 +4956,8 @@ function getVhkCommands() {
4553
4956
  }
4554
4957
  async function context(opts = {}) {
4555
4958
  const compact = opts.compact === true;
4556
- console.log(chalk23.bold("\n\u{1F9E0} " + t("context.title")));
4557
- console.log(chalk23.gray("\u2500".repeat(40)));
4959
+ console.log(chalk24.bold("\n\u{1F9E0} " + t("context.title")));
4960
+ console.log(chalk24.gray("\u2500".repeat(40)));
4558
4961
  const stack = extractTechStack();
4559
4962
  const tree = buildTree(".", "", compact ? 2 : 3).join("\n");
4560
4963
  const commands = getVhkCommands();
@@ -4589,27 +4992,14 @@ async function context(opts = {}) {
4589
4992
  }
4590
4993
  lines.push("");
4591
4994
  }
4592
- if (existsSync11(".vhk/memory.json")) {
4593
- try {
4594
- const memories = readJsonFile(
4595
- ".vhk/memory.json"
4596
- );
4597
- if (Array.isArray(memories) && memories.length > 0) {
4598
- const recentMemories = memories.slice(-5);
4599
- lines.push("## \uC800\uC7A5\uB41C \uACB0\uC815\uC0AC\uD56D");
4600
- lines.push("");
4601
- if (memories.length > recentMemories.length) {
4602
- lines.push(`_\uCD5C\uADFC ${recentMemories.length}\uAC1C\uB9CC \uD45C\uC2DC (\uC804\uCCB4 ${memories.length}\uAC1C)_`);
4603
- lines.push("");
4604
- }
4605
- for (const m of recentMemories) {
4606
- const date = new Date(m.addedAt).toLocaleDateString("ko-KR");
4607
- lines.push(`- ${m.content} _(${date})_`);
4608
- }
4609
- lines.push("");
4610
- }
4611
- } catch {
4995
+ try {
4996
+ const memLines = activeMemoryLines(readMemory(process.cwd()));
4997
+ if (memLines.length > 0) {
4998
+ lines.push("## \uC800\uC7A5\uB41C \uAE30\uC5B5 (memory v2)");
4999
+ lines.push("");
5000
+ lines.push(...memLines);
4612
5001
  }
5002
+ } catch {
4613
5003
  }
4614
5004
  const goals = listGoals("goals");
4615
5005
  const activeId = selectActiveId(goals);
@@ -4626,13 +5016,6 @@ async function context(opts = {}) {
4626
5016
  lines.push("");
4627
5017
  }
4628
5018
  }
4629
- const recent = getRecentLearnings(3);
4630
- if (recent.length > 0) {
4631
- lines.push("## Recent Learnings");
4632
- lines.push("");
4633
- for (const r of recent) lines.push(r);
4634
- lines.push("");
4635
- }
4636
5019
  const activeBlockers = getActiveBlockers(3);
4637
5020
  if (activeBlockers.length > 0) {
4638
5021
  lines.push("## Active Blockers");
@@ -4667,12 +5050,12 @@ async function context(opts = {}) {
4667
5050
  } catch {
4668
5051
  }
4669
5052
  lines.push("");
4670
- mkdirSync7(".vhk", { recursive: true });
4671
- writeFileSync7(CONTEXT_PATH, lines.join("\n"), "utf-8");
4672
- console.log(chalk23.green(`
5053
+ mkdirSync8(".vhk", { recursive: true });
5054
+ writeFileSync8(CONTEXT_PATH, lines.join("\n"), "utf-8");
5055
+ console.log(chalk24.green(`
4673
5056
  \u2705 ${CONTEXT_PATH} \uC0DD\uC131 \uC644\uB8CC!`));
4674
- console.log(chalk23.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
4675
- console.log(chalk23.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
5057
+ console.log(chalk24.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
5058
+ console.log(chalk24.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
4676
5059
  printNextStep({
4677
5060
  message: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC!",
4678
5061
  command: "vhk context-show",
@@ -4680,110 +5063,33 @@ async function context(opts = {}) {
4680
5063
  });
4681
5064
  }
4682
5065
  async function contextShow() {
4683
- console.log(chalk23.bold("\n\u{1F4C4} " + t("context.showTitle")));
4684
- console.log(chalk23.gray("\u2500".repeat(40)));
4685
- if (!existsSync11(CONTEXT_PATH)) {
4686
- console.log(chalk23.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4687
- console.log(chalk23.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
4688
- return;
4689
- }
4690
- const content = readFileSync4(CONTEXT_PATH, "utf-8");
4691
- console.log("\n" + content);
4692
- }
4693
-
4694
- // src/commands/memory.ts
4695
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
4696
- import chalk24 from "chalk";
4697
- var MEMORY_PATH = ".vhk/memory.json";
4698
- function loadMemories() {
4699
- if (!existsSync12(MEMORY_PATH)) return [];
4700
- try {
4701
- const parsed = readJsonFile(MEMORY_PATH);
4702
- return Array.isArray(parsed) ? parsed : [];
4703
- } catch {
4704
- return [];
4705
- }
4706
- }
4707
- function saveMemories(memories) {
4708
- mkdirSync8(".vhk", { recursive: true });
4709
- writeFileSync8(MEMORY_PATH, JSON.stringify(memories, null, 2) + "\n", "utf-8");
4710
- }
4711
- async function memoryAdd(content, tags) {
4712
- console.log(chalk24.bold("\n\u{1F9E0} " + t("memory.addTitle")));
5066
+ console.log(chalk24.bold("\n\u{1F4C4} " + t("context.showTitle")));
4713
5067
  console.log(chalk24.gray("\u2500".repeat(40)));
4714
- if (!content) {
4715
- console.log(chalk24.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4716
- console.log(chalk24.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9\uD558\uAE30\uB85C \uACB0\uC815"'));
4717
- process.exitCode = 1;
4718
- return;
4719
- }
4720
- const memories = loadMemories();
4721
- memories.push({
4722
- content,
4723
- addedAt: (/* @__PURE__ */ new Date()).toISOString(),
4724
- tags: tags && tags.length > 0 ? tags : []
4725
- });
4726
- saveMemories(memories);
4727
- console.log(chalk24.green(`
4728
- \u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (#${memories.length})`));
4729
- console.log(chalk24.cyan(` \u{1F4DD} ${content}`));
4730
- printNextStep({
4731
- message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!",
4732
- command: "vhk memory list",
4733
- cursorHint: "\uAE30\uC5B5 \uBAA9\uB85D \uBCF4\uC5EC\uC918"
4734
- });
4735
- }
4736
- async function memoryList() {
4737
- console.log(chalk24.bold("\n\u{1F9E0} " + t("memory.listTitle")));
4738
- console.log(chalk24.gray("\u2500".repeat(40)));
4739
- const memories = loadMemories();
4740
- if (memories.length === 0) {
4741
- console.log(chalk24.yellow("\n\u{1F4ED} \uC800\uC7A5\uB41C \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4742
- console.log(chalk24.gray(' vhk memory add "\uB0B4\uC6A9"\uC73C\uB85C \uCD94\uAC00\uD558\uC138\uC694.'));
4743
- return;
4744
- }
4745
- console.log(chalk24.cyan(`
4746
- \uCD1D ${memories.length}\uAC1C\uC758 \uAE30\uC5B5:
4747
- `));
4748
- memories.forEach((m, index) => {
4749
- const date = new Date(m.addedAt).toLocaleDateString("ko-KR");
4750
- console.log(chalk24.white(` [${index + 1}] ${m.content}`));
4751
- if (m.tags && m.tags.length > 0) {
4752
- console.log(chalk24.blue(` \u{1F3F7}\uFE0F ${m.tags.join(", ")}`));
4753
- }
4754
- console.log(chalk24.gray(` \u{1F4C5} ${date}`));
4755
- console.log("");
4756
- });
4757
- }
4758
- async function memoryRemove(indexStr) {
4759
- const memories = loadMemories();
4760
- const idx = parseInt(indexStr, 10) - 1;
4761
- if (Number.isNaN(idx) || idx < 0 || idx >= memories.length) {
4762
- console.log(chalk24.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${memories.length || 0})`));
5068
+ if (!existsSync12(CONTEXT_PATH)) {
5069
+ console.log(chalk24.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
5070
+ console.log(chalk24.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
4763
5071
  return;
4764
5072
  }
4765
- const removed = memories.splice(idx, 1)[0];
4766
- saveMemories(memories);
4767
- console.log(chalk24.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
4768
- console.log(chalk24.gray(` ${removed.content}`));
5073
+ const content = readFileSync5(CONTEXT_PATH, "utf-8");
5074
+ console.log("\n" + content);
4769
5075
  }
4770
5076
 
4771
5077
  // src/commands/brief.ts
4772
- import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync5 } from "fs";
5078
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync6 } from "fs";
4773
5079
  import chalk25 from "chalk";
4774
5080
  var BRIEF_PATH = ".vhk/brief.md";
4775
5081
  function readProjectIdentity() {
4776
5082
  const out = {};
4777
5083
  try {
4778
5084
  if (existsSync13("RULES.md")) {
4779
- const r = readFileSync5("RULES.md", "utf-8");
5085
+ const r = readFileSync6("RULES.md", "utf-8");
4780
5086
  const m = r.split("\n")[0].match(/^#\s*(.+?)(?:\s*—.*)?$/);
4781
5087
  if (m) out.name = m[1].trim();
4782
5088
  const d = r.match(/한 줄 설명:\s*(.+)/);
4783
5089
  if (d) out.description = d[1].trim();
4784
5090
  }
4785
5091
  if (!out.name && existsSync13("CLAUDE.md")) {
4786
- const m = readFileSync5("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
5092
+ const m = readFileSync6("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
4787
5093
  if (m) out.name = m[1].trim();
4788
5094
  }
4789
5095
  } catch {
@@ -4833,22 +5139,14 @@ async function brief() {
4833
5139
  `- **\uBBF8\uCEE4\uBC0B \uBCC0\uACBD**: ${uncommitted ? `${uncommitted.split("\n").length}\uAC1C \uD30C\uC77C` : "\uC5C6\uC74C \u2705"}`
4834
5140
  );
4835
5141
  lines.push("");
4836
- if (existsSync13(".vhk/memory.json")) {
4837
- try {
4838
- const memories = readJsonFile(".vhk/memory.json");
4839
- if (Array.isArray(memories) && memories.length > 0) {
4840
- lines.push(`## \uC800\uC7A5\uB41C \uACB0\uC815\uC0AC\uD56D (${memories.length}\uAC1C)`);
4841
- lines.push("");
4842
- for (const m of memories.slice(-5)) {
4843
- lines.push(`- ${m.content}`);
4844
- }
4845
- if (memories.length > 5) {
4846
- lines.push(`- ... \uC678 ${memories.length - 5}\uAC1C`);
4847
- }
4848
- lines.push("");
4849
- }
4850
- } catch {
5142
+ try {
5143
+ const memLines = activeMemoryLines(readMemory(process.cwd()));
5144
+ if (memLines.length > 0) {
5145
+ lines.push("## \uC800\uC7A5\uB41C \uAE30\uC5B5 (memory v2)");
5146
+ lines.push("");
5147
+ lines.push(...memLines);
4851
5148
  }
5149
+ } catch {
4852
5150
  }
4853
5151
  if (existsSync13(".vhk/refs.json")) {
4854
5152
  try {
@@ -4895,7 +5193,7 @@ import chalk26 from "chalk";
4895
5193
  import inquirer11 from "inquirer";
4896
5194
  import { simpleGit as simpleGit2 } from "simple-git";
4897
5195
  import { existsSync as existsSync14 } from "fs";
4898
- import { join as join8 } from "path";
5196
+ import { join as join9 } from "path";
4899
5197
  var VHK_FOOTPRINT_FILES = [
4900
5198
  "CLAUDE.md",
4901
5199
  ".cursorrules",
@@ -4904,7 +5202,7 @@ var VHK_FOOTPRINT_FILES = [
4904
5202
  "docs/PRD.md"
4905
5203
  ];
4906
5204
  function detectExistingFootprint(cwd) {
4907
- return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join8(cwd, rel)));
5205
+ return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join9(cwd, rel)));
4908
5206
  }
4909
5207
  async function runGitInit(cwd) {
4910
5208
  try {
@@ -5310,7 +5608,7 @@ import chalk29 from "chalk";
5310
5608
 
5311
5609
  // src/lib/config.ts
5312
5610
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
5313
- import { join as join9 } from "path";
5611
+ import { join as join10 } from "path";
5314
5612
 
5315
5613
  // src/lib/safety-mode.ts
5316
5614
  var SAFETY_MODES = ["lite", "standard", "strict"];
@@ -5326,10 +5624,10 @@ function isSafetyMode(value) {
5326
5624
 
5327
5625
  // src/lib/config.ts
5328
5626
  var CONFIG_DIR = ".vhk";
5329
- var CONFIG_PATH = join9(CONFIG_DIR, "config.json");
5627
+ var CONFIG_PATH = join10(CONFIG_DIR, "config.json");
5330
5628
  var DEFAULT_CONFIG = { safetyMode: DEFAULT_SAFETY_MODE };
5331
5629
  function readConfig(rootDir = process.cwd()) {
5332
- const full = join9(rootDir, CONFIG_PATH);
5630
+ const full = join10(rootDir, CONFIG_PATH);
5333
5631
  if (!existsSync15(full)) return { ...DEFAULT_CONFIG };
5334
5632
  try {
5335
5633
  const raw = readJsonFile(full);
@@ -5341,8 +5639,8 @@ function readConfig(rootDir = process.cwd()) {
5341
5639
  }
5342
5640
  }
5343
5641
  function writeConfig(config, rootDir = process.cwd()) {
5344
- mkdirSync10(join9(rootDir, CONFIG_DIR), { recursive: true });
5345
- writeFileSync10(join9(rootDir, CONFIG_PATH), JSON.stringify(config, null, 2) + "\n", "utf-8");
5642
+ mkdirSync10(join10(rootDir, CONFIG_DIR), { recursive: true });
5643
+ writeFileSync10(join10(rootDir, CONFIG_PATH), JSON.stringify(config, null, 2) + "\n", "utf-8");
5346
5644
  }
5347
5645
 
5348
5646
  // src/commands/mode.ts
@@ -5382,7 +5680,7 @@ async function mode(target) {
5382
5680
  // src/commands/verify.ts
5383
5681
  import { execFileSync as execFileSync4 } from "child_process";
5384
5682
  import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
5385
- import { join as join10 } from "path";
5683
+ import { join as join11 } from "path";
5386
5684
  import chalk30 from "chalk";
5387
5685
 
5388
5686
  // src/commands/verify-report.ts
@@ -5491,13 +5789,13 @@ ${actions}
5491
5789
 
5492
5790
  // src/commands/verify.ts
5493
5791
  var REPORT_SCHEMA_VERSION = 1;
5494
- var REPORT_DIR_REL = join10(".vhk", "reports");
5495
- var REPORT_PATH_REL = join10(REPORT_DIR_REL, "latest.json");
5496
- var REPORT_HTML_PATH_REL = join10(REPORT_DIR_REL, "latest.html");
5792
+ var REPORT_DIR_REL = join11(".vhk", "reports");
5793
+ var REPORT_PATH_REL = join11(REPORT_DIR_REL, "latest.json");
5794
+ var REPORT_HTML_PATH_REL = join11(REPORT_DIR_REL, "latest.html");
5497
5795
  var SHIM = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
5498
5796
  function detectPm(cwd) {
5499
- if (existsSync16(join10(cwd, "pnpm-lock.yaml"))) return "pnpm";
5500
- if (existsSync16(join10(cwd, "yarn.lock"))) return "yarn";
5797
+ if (existsSync16(join11(cwd, "pnpm-lock.yaml"))) return "pnpm";
5798
+ if (existsSync16(join11(cwd, "yarn.lock"))) return "yarn";
5501
5799
  return "npm";
5502
5800
  }
5503
5801
  function execGate(cmd, args, cwd) {
@@ -5540,7 +5838,7 @@ function runScriptGate(id, label, cwd, pm, argvFor) {
5540
5838
  };
5541
5839
  }
5542
5840
  function readPackageScripts(cwd) {
5543
- const pkgPath = join10(cwd, "package.json");
5841
+ const pkgPath = join11(cwd, "package.json");
5544
5842
  if (!existsSync16(pkgPath)) return {};
5545
5843
  try {
5546
5844
  const pkg = readJsonFile(pkgPath);
@@ -5556,7 +5854,7 @@ function runGates(cwd) {
5556
5854
  gates.push(
5557
5855
  runScriptGate("typecheck", "tsc --noEmit", cwd, pm, () => {
5558
5856
  if (scripts.typecheck) return ["run", "typecheck"];
5559
- if (existsSync16(join10(cwd, "tsconfig.json"))) return pm === "npm" ? ["exec", "--", "tsc", "--noEmit"] : ["exec", "tsc", "--noEmit"];
5857
+ if (existsSync16(join11(cwd, "tsconfig.json"))) return pm === "npm" ? ["exec", "--", "tsc", "--noEmit"] : ["exec", "tsc", "--noEmit"];
5560
5858
  return null;
5561
5859
  })
5562
5860
  );
@@ -5635,9 +5933,9 @@ function buildReport(gates, generatedAt, date) {
5635
5933
  function verifyEvidence(cwd = process.cwd()) {
5636
5934
  const gates = runGates(cwd);
5637
5935
  const report = buildReport(gates, (/* @__PURE__ */ new Date()).toISOString(), localDate());
5638
- const dir = join10(cwd, REPORT_DIR_REL);
5936
+ const dir = join11(cwd, REPORT_DIR_REL);
5639
5937
  mkdirSync11(dir, { recursive: true });
5640
- const path14 = join10(cwd, REPORT_PATH_REL);
5938
+ const path14 = join11(cwd, REPORT_PATH_REL);
5641
5939
  writeFileSync11(path14, JSON.stringify(report, null, 2) + "\n", "utf-8");
5642
5940
  try {
5643
5941
  ensureVhkIgnored(cwd, "reports/");
@@ -5651,7 +5949,7 @@ var STATUS_BADGE = {
5651
5949
  FAIL: chalk30.red.bold("FAIL")
5652
5950
  };
5653
5951
  async function renderVerifyReport(cwd, opts) {
5654
- const jsonPath = join10(cwd, REPORT_PATH_REL);
5952
+ const jsonPath = join11(cwd, REPORT_PATH_REL);
5655
5953
  let report;
5656
5954
  if (existsSync16(jsonPath)) {
5657
5955
  try {
@@ -5665,9 +5963,9 @@ async function renderVerifyReport(cwd, opts) {
5665
5963
  report = verifyEvidence(cwd).report;
5666
5964
  }
5667
5965
  const html = renderReportHtml(report);
5668
- const htmlPath = join10(cwd, REPORT_HTML_PATH_REL);
5966
+ const htmlPath = join11(cwd, REPORT_HTML_PATH_REL);
5669
5967
  try {
5670
- mkdirSync11(join10(cwd, REPORT_DIR_REL), { recursive: true });
5968
+ mkdirSync11(join11(cwd, REPORT_DIR_REL), { recursive: true });
5671
5969
  writeFileSync11(htmlPath, html, "utf-8");
5672
5970
  } catch (e) {
5673
5971
  console.error(
@@ -5751,7 +6049,7 @@ async function verify(opts = {}) {
5751
6049
 
5752
6050
  // src/commands/review.ts
5753
6051
  import { existsSync as existsSync17, writeFileSync as writeFileSync12 } from "fs";
5754
- import { join as join11 } from "path";
6052
+ import { join as join12 } from "path";
5755
6053
  import chalk31 from "chalk";
5756
6054
  var GOALS_DIR2 = "goals";
5757
6055
  var COVERAGE_MIN = 0.5;
@@ -5909,7 +6207,7 @@ async function review(opts = {}) {
5909
6207
  if (opts.id === void 0 && goalStatus === "NOT_STARTED") {
5910
6208
  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.`));
5911
6209
  }
5912
- const jsonPath = join11(cwd, REPORT_PATH_REL);
6210
+ const jsonPath = join12(cwd, REPORT_PATH_REL);
5913
6211
  if (!existsSync17(jsonPath)) {
5914
6212
  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).`));
5915
6213
  printNextStep({
@@ -6020,12 +6318,12 @@ ${result.disclaimer}`));
6020
6318
  }
6021
6319
 
6022
6320
  // src/commands/mission.ts
6023
- import { existsSync as existsSync18, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, rmSync as rmSync3 } from "fs";
6024
- import { join as join12 } from "path";
6321
+ import { existsSync as existsSync18, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, rmSync as rmSync4 } from "fs";
6322
+ import { join as join13 } from "path";
6025
6323
  import chalk32 from "chalk";
6026
6324
  import inquirer12 from "inquirer";
6027
6325
  import { simpleGit as simpleGit3 } from "simple-git";
6028
- var MISSION_PATH_REL = join12(".vhk", "mission.json");
6326
+ var MISSION_PATH_REL = join13(".vhk", "mission.json");
6029
6327
  var MISSION_SCHEMA_VERSION = 1;
6030
6328
  var MISSION_DISCLAIMER = "\u26A0\uFE0F mission check \uB294 \uACBD\uB85C glob \uAE30\uC900\uC785\uB2C8\uB2E4 \u2014 objective \uC758\uBBF8 \uBD80\uD569\uC740 \uAC80\uC99D\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(\uC2E0\uB8B0\uB3C4 \uC2E0\uD638, \uBCF4\uC7A5 \uC544\uB2D8).";
6031
6329
  function globToRegExp(glob) {
@@ -6073,7 +6371,7 @@ function checkMission(changedFiles, mission) {
6073
6371
  return { violations, warnings, disclaimer: MISSION_DISCLAIMER };
6074
6372
  }
6075
6373
  function readMission(cwd = process.cwd()) {
6076
- const p = join12(cwd, MISSION_PATH_REL);
6374
+ const p = join13(cwd, MISSION_PATH_REL);
6077
6375
  if (!existsSync18(p)) return null;
6078
6376
  try {
6079
6377
  const m = readJsonFile(p);
@@ -6084,8 +6382,8 @@ function readMission(cwd = process.cwd()) {
6084
6382
  }
6085
6383
  }
6086
6384
  function writeMission(cwd, mission) {
6087
- mkdirSync12(join12(cwd, ".vhk"), { recursive: true });
6088
- writeFileSync13(join12(cwd, MISSION_PATH_REL), JSON.stringify(mission, null, 2) + "\n", "utf-8");
6385
+ mkdirSync12(join13(cwd, ".vhk"), { recursive: true });
6386
+ writeFileSync13(join13(cwd, MISSION_PATH_REL), JSON.stringify(mission, null, 2) + "\n", "utf-8");
6089
6387
  }
6090
6388
  async function collectChangedFiles(cwd) {
6091
6389
  const status2 = await simpleGit3(cwd).status();
@@ -6182,13 +6480,13 @@ async function missionCheck() {
6182
6480
  }
6183
6481
  async function missionClear() {
6184
6482
  const cwd = process.cwd();
6185
- const p = join12(cwd, MISSION_PATH_REL);
6483
+ const p = join13(cwd, MISSION_PATH_REL);
6186
6484
  if (!existsSync18(p)) {
6187
6485
  console.log(chalk32.dim(" \uBBF8\uC158 \uACC4\uC57D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC9C0\uC6B8 \uAC83 \uC5C6\uC74C."));
6188
6486
  return;
6189
6487
  }
6190
6488
  try {
6191
- rmSync3(p);
6489
+ rmSync4(p);
6192
6490
  console.log(chalk32.green(" \u2705 \uBBF8\uC158 \uACC4\uC57D \uC0AD\uC81C\uB428 (.vhk/mission.json)."));
6193
6491
  } catch (e) {
6194
6492
  console.error(chalk32.red(` \u274C \uC0AD\uC81C \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`));
@@ -6337,6 +6635,7 @@ async function dispatchNlpRoute(route, input) {
6337
6635
  case "context-show":
6338
6636
  return contextShow();
6339
6637
  case "memory":
6638
+ if (route.args?.[0] === "migrate") return memoryMigrate();
6340
6639
  return memoryList();
6341
6640
  case "brief":
6342
6641
  return brief();
@@ -6448,11 +6747,14 @@ ${ko.agent.learnTitle}
6448
6747
  return;
6449
6748
  }
6450
6749
  const goalId = activeGoalId();
6451
- appendLearning(lesson, goalId);
6452
- console.log(chalk34.green(" \u2705 learnings.md append."));
6453
- console.log(
6454
- chalk34.dim(" \uACB0\uC815\uC0AC\uD56D(decision)\uC740 `vhk memory add` \uB85C \uBCC4\uB3C4 \uAE30\uB85D \u2014 SoT \uBD84\uB9AC.")
6455
- );
6750
+ const entry = recordLesson(process.cwd(), lesson, goalId);
6751
+ 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."));
6753
+ process.exitCode = 1;
6754
+ return;
6755
+ }
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."));
6456
6758
  }
6457
6759
  async function resume(opts = {}) {
6458
6760
  console.log(chalk34.bold(`
@@ -6700,19 +7002,32 @@ missionCmd.command("clear").description("\uBBF8\uC158 \uACC4\uC57D \uC0AD\uC81C
6700
7002
  program.command("context-show").alias("\uB9E5\uB77D\uBCF4\uAE30").description("\uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uB0B4\uC6A9 \uCD9C\uB825").action(async () => {
6701
7003
  await contextShow();
6702
7004
  });
6703
- var memoryCmd = program.command("memory").alias("\uAE30\uC5B5").description("\uACB0\uC815\uC0AC\uD56D \uAE30\uC5B5 \uAD00\uB9AC (add / list / remove)").action(async () => {
7005
+ var memoryCmd = program.command("memory").alias("\uAE30\uC5B5").description("\uAE30\uC5B5 \uAD00\uB9AC v2 (decisions/failures/successes 4\uBC84\uD0B7) \u2014 add/list/remove/archive/resolve/unarchive/migrate").action(async () => {
6704
7006
  await memoryList();
6705
7007
  });
6706
- memoryCmd.command("add <content>").option("--tags <tags>", "\uD0DC\uADF8 (\uC27C\uD45C \uAD6C\uBD84)").description("\uACB0\uC815\uC0AC\uD56D \uAE30\uC5B5 \uC800\uC7A5").action(async (content, opts) => {
7008
+ memoryCmd.command("add <content>").option("--type <type>", "\uBC84\uD0B7: decision|failure|success (\uAE30\uBCF8 decision)").option("--tags <tags>", "\uD0DC\uADF8 (\uC27C\uD45C \uAD6C\uBD84)").option("--why <why>", "\uC6D0\uC778 (failure/success)").option("--lesson <lesson>", "\uAD50\uD6C8 (failure)").description("\uAE30\uC5B5 \uC800\uC7A5 (--type \uC73C\uB85C \uACB0\uC815/\uC2E4\uD328/\uC131\uACF5 \uAD6C\uBD84)").action(async (content, opts) => {
6707
7009
  const tags = opts.tags ? opts.tags.split(",").map((s) => s.trim()) : void 0;
6708
- await memoryAdd(content, tags);
7010
+ await memoryAdd(content, { type: opts.type, tags, why: opts.why, lesson: opts.lesson });
6709
7011
  });
6710
- memoryCmd.command("list").alias("\uBAA9\uB85D").description("\uC800\uC7A5\uB41C \uAE30\uC5B5 \uBAA9\uB85D").action(async () => {
6711
- await memoryList();
7012
+ memoryCmd.command("list").alias("\uBAA9\uB85D").option("--type <type>", "\uBC84\uD0B7 \uD544\uD130: decision|failure|success").option("--all", "\uBCF4\uAD00(archived)\xB7\uD574\uACB0(resolved) \uD3EC\uD568").description("\uC800\uC7A5\uB41C \uAE30\uC5B5 \uBAA9\uB85D (\uAE30\uBCF8 \uD65C\uC131\uB9CC)").action(async (opts) => {
7013
+ const type = opts.type === "decision" || opts.type === "failure" || opts.type === "success" ? opts.type : void 0;
7014
+ await memoryList({ type, all: opts.all });
6712
7015
  });
6713
7016
  memoryCmd.command("remove <index>").alias("\uC0AD\uC81C").description("\uAE30\uC5B5 \uC0AD\uC81C (1\uBD80\uD130 \uC2DC\uC791\uD558\uB294 \uBC88\uD638)").action(async (index) => {
6714
7017
  await memoryRemove(index);
6715
7018
  });
7019
+ memoryCmd.command("archive <index>").alias("\uBCF4\uAD00").description("\uAE30\uC5B5 \uBCF4\uAD00 (\uD65C\uC131\u2192archived, \uD328\uD134/\uC9C4\uD654\uC5D0\uC11C \uC81C\uC678)").action(async (index) => {
7020
+ await memoryArchive(index);
7021
+ });
7022
+ memoryCmd.command("resolve <index>").alias("\uD574\uACB0").description("\uAE30\uC5B5 \uD574\uACB0 \uD45C\uC2DC (\uD65C\uC131\u2192resolved, \uD328\uD134/\uC9C4\uD654\uC5D0\uC11C \uC81C\uC678)").action(async (index) => {
7023
+ await memoryResolve(index);
7024
+ });
7025
+ memoryCmd.command("unarchive <index>").alias("\uBCF5\uAD6C").description("\uBCF4\uAD00/\uD574\uACB0 \uD56D\uBAA9\uC744 \uB2E4\uC2DC \uD65C\uC131\uC73C\uB85C \uBCF5\uAD6C (archive/resolve \uC5ED\uC804)").action(async (index) => {
7026
+ await memoryUnarchive(index);
7027
+ });
7028
+ memoryCmd.command("migrate").alias("\uB9C8\uC774\uADF8\uB808\uC774\uC158").description("memory.json v1 \u2192 v2 \uB9C8\uC774\uADF8\uB808\uC774\uC158 (\uAE30\uC874 v1 \uC788\uC73C\uBA74 .v1.bak \uC6D0\uBCF8 \uBC31\uC5C5, \uBA71\uB4F1)").action(async () => {
7029
+ await memoryMigrate();
7030
+ });
6716
7031
  program.command("brief").alias("\uBE0C\uB9AC\uD551").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC694\uC57D \uBCF4\uACE0\uC11C \uC0DD\uC131 (.vhk/brief.md)").action(async () => {
6717
7032
  await brief();
6718
7033
  });
@@ -6740,7 +7055,7 @@ goalCmd.command("sync").alias("\uB3D9\uAE30\uD654").description("goals/*.md \uC2
6740
7055
  program.command("blocker <description>").alias("\uBE14\uB85C\uCEE4").description("\uBE14\uB85C\uCEE4 \uAE30\uB85D \u2192 docs/state/blockers.md append (3\uAC74 \uB204\uC801 \uC2DC HARD_STOP \uC790\uB3D9 \uC0DD\uC131)").action(async (description) => {
6741
7056
  await blocker(description);
6742
7057
  });
6743
- program.command("learn <lesson>").alias("\uAD50\uD6C8").description("\uAD50\uD6C8 \uAE30\uB85D \u2192 docs/state/learnings.md append (memory.json \uACFC \uBCC4\uB3C4 SoT)").action(async (lesson) => {
7058
+ program.command("learn <lesson>").alias("\uAD50\uD6C8").description("\uAD50\uD6C8 \uAE30\uB85D \u2192 memory v2 failures.lesson \uB2E8\uC77C SoT (v2.0 \uD1B5\uD569 \u2014 vhk memory list \uB85C \uD655\uC778)").action(async (lesson) => {
6744
7059
  await learn(lesson);
6745
7060
  });
6746
7061
  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) => {
@@ -6800,7 +7115,14 @@ program.action(async () => {
6800
7115
  return diff();
6801
7116
  }
6802
7117
  });
6803
- var isMainModule = !!process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
7118
+ var getRealPath = (p) => {
7119
+ try {
7120
+ return fs13.realpathSync(p);
7121
+ } catch {
7122
+ return p;
7123
+ }
7124
+ };
7125
+ var isMainModule = !!process.argv[1] && getRealPath(fileURLToPath4(import.meta.url)) === getRealPath(process.argv[1]);
6804
7126
  if (isMainModule) {
6805
7127
  try {
6806
7128
  const nlInput = detectNaturalLanguageInput(process.argv);
package/dist/mcp/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  resolveVhkCliInvocation,
4
4
  startMcpServer
5
- } from "../chunk-RXDOM4QT.js";
5
+ } from "../chunk-HCNU6K7D.js";
6
6
 
7
7
  // src/mcp/index.ts
8
8
  var cli = resolveVhkCliInvocation();
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "@byh3071/vhk",
3
- "version": "1.9.0",
4
- "description": "Vibe Harness Kit — AI 코딩 도구·기기를 바꿔도 규칙·맥락이 따라가는 포터빌리티 CLI (sync: Cursor·Claude·Windsurf·Copilot·Antigravity / cloud 백업)",
5
- "bin": {
6
- "vhk": "dist/index.js",
7
- "vhk-mcp": "dist/mcp/index.js"
8
- },
9
- "type": "module",
10
- "scripts": {
11
- "dev": "tsx src/index.ts",
12
- "build": "tsup",
13
- "test": "vitest",
14
- "test:run": "vitest --run",
15
- "prepublishOnly": "pnpm build && pnpm test:run",
16
- "save": "vhk save",
17
- "check": "vhk check",
18
- "scan": "vhk secure scan",
19
- "recap": "vhk recap",
20
- "ship": "vhk ship",
21
- "doctor": "vhk doctor"
22
- },
23
- "files": [
24
- "dist",
25
- "README.md",
26
- "LICENSE"
27
- ],
28
- "keywords": [
29
- "vibe-coding",
30
- "harness",
31
- "cli",
32
- "scaffold",
33
- "session-log",
34
- "rules-sync",
35
- "portability",
36
- "ai-coding",
37
- "cursor",
38
- "claude",
39
- "windsurf",
40
- "copilot",
41
- "context-sync"
42
- ],
43
- "author": "byh3071 <byh3071@gmail.com>",
44
- "license": "MIT",
45
- "repository": {
46
- "type": "git",
47
- "url": "git+https://github.com/byh3071-cpu/vhk.git"
48
- },
49
- "engines": {
50
- "node": ">=20"
51
- },
52
- "dependencies": {
53
- "@modelcontextprotocol/sdk": "^1.29.0",
54
- "@notionhq/client": "^5.22.0",
55
- "chalk": "^5.6.2",
56
- "commander": "^14.0.3",
57
- "handlebars": "^4.7.9",
58
- "inquirer": "^9.3.8",
59
- "ora": "^9.4.0",
60
- "simple-git": "^3.36.0",
61
- "zod": "^4.4.3"
62
- },
63
- "devDependencies": {
64
- "@types/inquirer": "^9.0.9",
65
- "@types/node": "^25.9.1",
66
- "ignore": "^7.0.5",
67
- "tsup": "^8.5.1",
68
- "tsx": "^4.22.3",
69
- "typescript": "^6.0.3",
70
- "vitest": "^4.1.7"
71
- }
72
- }
1
+ {
2
+ "name": "@byh3071/vhk",
3
+ "version": "2.0.2",
4
+ "description": "Vibe Harness Kit — AI 코딩 도구·기기를 바꿔도 규칙·맥락이 따라가는 포터빌리티 CLI (sync: Cursor·Claude·Windsurf·Copilot·Antigravity / cloud 백업)",
5
+ "bin": {
6
+ "vhk": "dist/index.js",
7
+ "vhk-mcp": "dist/mcp/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "dev": "tsx src/index.ts",
12
+ "build": "tsup",
13
+ "test": "vitest",
14
+ "test:run": "vitest --run",
15
+ "prepublishOnly": "pnpm build && pnpm test:run",
16
+ "save": "vhk save",
17
+ "check": "vhk check",
18
+ "scan": "vhk secure scan",
19
+ "recap": "vhk recap",
20
+ "ship": "vhk ship",
21
+ "doctor": "vhk doctor"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "keywords": [
29
+ "vibe-coding",
30
+ "harness",
31
+ "cli",
32
+ "scaffold",
33
+ "session-log",
34
+ "rules-sync",
35
+ "portability",
36
+ "ai-coding",
37
+ "cursor",
38
+ "claude",
39
+ "windsurf",
40
+ "copilot",
41
+ "context-sync"
42
+ ],
43
+ "author": "byh3071 <byh3071@gmail.com>",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/byh3071-cpu/vhk.git"
48
+ },
49
+ "engines": {
50
+ "node": ">=20"
51
+ },
52
+ "dependencies": {
53
+ "@modelcontextprotocol/sdk": "^1.29.0",
54
+ "@notionhq/client": "^5.22.0",
55
+ "chalk": "^5.6.2",
56
+ "commander": "^14.0.3",
57
+ "handlebars": "^4.7.9",
58
+ "inquirer": "^9.3.8",
59
+ "ora": "^9.4.0",
60
+ "simple-git": "^3.36.0",
61
+ "zod": "^4.4.3"
62
+ },
63
+ "devDependencies": {
64
+ "@types/inquirer": "^9.0.9",
65
+ "@types/node": "^25.9.1",
66
+ "ignore": "^7.0.5",
67
+ "tsup": "^8.5.1",
68
+ "tsx": "^4.22.3",
69
+ "typescript": "^6.0.3",
70
+ "vitest": "^4.1.7"
71
+ }
72
+ }