@byh3071/vhk 1.9.0 → 2.0.1

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,7 +43,7 @@ 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";
@@ -183,11 +183,20 @@ var RULES = [
183
183
  confidence: "high",
184
184
  test: (t2) => /감사|취약점|audit|vulnerability|보안\s*감사|보안\s*취약|의존성\s*취약/.test(t2)
185
185
  },
186
+ // memory 마이그레이션은 패키지매니저 migrate 보다 **먼저** 평가 — "기억/메모리 마이그레이트" 가
187
+ // pnpm 전환(vhk migrate)으로 새지 않도록. 기억/메모리/memory 한정이라 bare "마이그레이트"는 안 가로챔.
188
+ {
189
+ command: "memory",
190
+ args: ["migrate"],
191
+ explanation: "memory.json v1 \u2192 v2 \uB9C8\uC774\uADF8\uB808\uC774\uC158 (vhk memory migrate)",
192
+ confidence: "high",
193
+ test: (t2) => /(기억|메모리|memory)\s*(을|를)?\s*(마이그레이|migrat)/.test(t2)
194
+ },
186
195
  {
187
196
  command: "migrate",
188
197
  explanation: "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uC804\uD658 (vhk migrate)",
189
198
  confidence: "high",
190
- test: (t2) => /전환|마이그레이트|migrate|패키지\s*매니저|npm.*pnpm|pnpm.*npm|yarn.*전환|npm.*전환|pnpm.*전환/.test(t2)
199
+ test: (t2) => /전환|마이그레이(트|션)|migrate|패키지\s*매니저|npm.*pnpm|pnpm.*npm|yarn.*전환|npm.*전환|pnpm.*전환/.test(t2) && !/(기억|메모리|memory)\s*(을|를)?\s*(마이그레이|migrat)/.test(t2)
191
200
  },
192
201
  {
193
202
  command: "update",
@@ -211,7 +220,9 @@ var RULES = [
211
220
  command: "memory",
212
221
  explanation: "\uAE30\uC5B5 \uBAA9\uB85D \uC870\uD68C (vhk memory list)",
213
222
  confidence: "high",
214
- test: (t2) => /^기억$|기억\s*(목록|보|확인|뭐)|memory.*list|결정사항\s*(목록|확인|보여)/.test(t2) && !/(추가|add|삭제|remove|저장|기록해)/.test(t2)
223
+ // 보관(archive)/해결(resolve)/마이그레이션은 list 아니다 → **제외 토큰 한 곳**에서 오라우팅 차단.
224
+ // archive/resolve 는 <번호> 인자가 필요해 NL 미지원 → 매칭 안 되면 notMatched 가 정직(잘못된 list 실행 금지).
225
+ test: (t2) => /^기억$|기억\s*(목록|보|확인|뭐)|memory.*list|결정사항\s*(목록|확인|보여)/.test(t2) && !/(추가|add|삭제|remove|저장|기록해|보관|아카이브|archive|마이그레이|migrat|해결|복구)/.test(t2)
215
226
  },
216
227
  {
217
228
  command: "brief",
@@ -372,7 +383,7 @@ function extractNotionUrl(input) {
372
383
  var CONTAINER_SUBCOMMANDS = {
373
384
  goal: ["list", "next", "check", "init", "done", "sync"],
374
385
  ref: ["add", "list", "open"],
375
- memory: ["add", "list", "remove"],
386
+ memory: ["add", "list", "remove", "archive", "resolve", "unarchive", "migrate"],
376
387
  cloud: ["push", "pull"],
377
388
  secure: ["scan"],
378
389
  design: ["palette"],
@@ -1705,32 +1716,6 @@ ${line}
1705
1716
  }
1706
1717
  return { count, hardStopTripped };
1707
1718
  }
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
1719
  function getActiveBlockers(limit = 3) {
1735
1720
  if (!existsSync(BLOCKERS_PATH)) return [];
1736
1721
  const lines = readFileSync(BLOCKERS_PATH, "utf-8").split(/\r?\n/);
@@ -4444,15 +4429,432 @@ async function update() {
4444
4429
 
4445
4430
  // src/commands/context.ts
4446
4431
  import {
4447
- existsSync as existsSync11,
4448
- mkdirSync as mkdirSync7,
4449
- readFileSync as readFileSync4,
4432
+ existsSync as existsSync12,
4433
+ mkdirSync as mkdirSync8,
4434
+ readFileSync as readFileSync5,
4450
4435
  readdirSync as readdirSync2,
4451
4436
  statSync as statSync2,
4452
- writeFileSync as writeFileSync7
4437
+ writeFileSync as writeFileSync8
4453
4438
  } from "fs";
4439
+ import { join as join8 } from "path";
4440
+ import chalk24 from "chalk";
4441
+
4442
+ // src/commands/memory.ts
4443
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, copyFileSync, readFileSync as readFileSync4, renameSync, rmSync as rmSync3 } from "fs";
4454
4444
  import { join as join7 } from "path";
4455
4445
  import chalk23 from "chalk";
4446
+ var MEMORY_PATH_REL = join7(".vhk", "memory.json");
4447
+ var MEMORY_SCHEMA_VERSION = 2;
4448
+ function emptyV2() {
4449
+ return { schemaVersion: MEMORY_SCHEMA_VERSION, decisions: [], failures: [], successes: [], patterns: [] };
4450
+ }
4451
+ function isV2(raw) {
4452
+ return !!raw && typeof raw === "object" && raw.schemaVersion === 2;
4453
+ }
4454
+ var BUCKET_PREFIX = { decision: "d", failure: "f", success: "s" };
4455
+ function arr(v) {
4456
+ return Array.isArray(v) ? v : [];
4457
+ }
4458
+ function normalizeV2(raw) {
4459
+ return {
4460
+ schemaVersion: MEMORY_SCHEMA_VERSION,
4461
+ decisions: arr(raw.decisions),
4462
+ failures: arr(raw.failures),
4463
+ successes: arr(raw.successes),
4464
+ patterns: arr(raw.patterns)
4465
+ };
4466
+ }
4467
+ function parseLearnings(rawLearnings) {
4468
+ const out = [];
4469
+ for (const line of rawLearnings.split(/\r?\n/)) {
4470
+ const m = line.match(/^-\s*\[(\d{4}-\d{2}-\d{2})\s+([^\]]*)\]\s*(.+)$/);
4471
+ if (m) out.push({ date: m[1], tag: m[2].trim(), lesson: m[3].trim() });
4472
+ }
4473
+ return out;
4474
+ }
4475
+ function migrateMemory(rawMemory, rawLearnings) {
4476
+ if (isV2(rawMemory)) return normalizeV2(rawMemory);
4477
+ const v2 = emptyV2();
4478
+ if (Array.isArray(rawMemory)) {
4479
+ rawMemory.forEach((m, i) => {
4480
+ const item = m;
4481
+ if (item && typeof item.content === "string") {
4482
+ v2.decisions.push({
4483
+ id: `d${i + 1}`,
4484
+ content: item.content,
4485
+ tags: arr(item.tags),
4486
+ createdAt: typeof item.addedAt === "string" ? item.addedAt : "",
4487
+ status: "active"
4488
+ });
4489
+ }
4490
+ });
4491
+ }
4492
+ if (rawLearnings) {
4493
+ parseLearnings(rawLearnings).forEach((l, i) => {
4494
+ v2.failures.push({
4495
+ id: `f${i + 1}`,
4496
+ content: "",
4497
+ tags: l.tag ? [l.tag] : [],
4498
+ createdAt: l.date,
4499
+ status: "active",
4500
+ lesson: l.lesson
4501
+ });
4502
+ });
4503
+ }
4504
+ return v2;
4505
+ }
4506
+ function readRaw(cwd) {
4507
+ const p = join7(cwd, MEMORY_PATH_REL);
4508
+ if (!existsSync11(p)) return { kind: "missing" };
4509
+ try {
4510
+ return { kind: "parsed", value: readJsonFile(p) };
4511
+ } catch {
4512
+ return { kind: "error" };
4513
+ }
4514
+ }
4515
+ function warnUnreadable(cwd) {
4516
+ const p = join7(cwd, MEMORY_PATH_REL);
4517
+ console.error(chalk23.red(`
4518
+ \u26A0\uFE0F ${MEMORY_PATH_REL} \uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC190\uC0C1/\uBD80\uBD84 \uC4F0\uAE30 \uC758\uC2EC).`));
4519
+ 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.`));
4520
+ console.error(chalk23.dim(` \uD655\uC778/\uBCF5\uAD6C: ${p} (\uBC31\uC5C5: ${p}.bak / ${p}.v1.bak)`));
4521
+ }
4522
+ function warnUnrecognized(cwd) {
4523
+ const p = join7(cwd, MEMORY_PATH_REL);
4524
+ console.error(chalk23.red(`
4525
+ \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).`));
4526
+ 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.`));
4527
+ console.error(chalk23.dim(` \uD655\uC778: ${p}`));
4528
+ }
4529
+ function readLearningsRaw(cwd) {
4530
+ const p = join7(cwd, "docs", "state", "learnings.md");
4531
+ if (!existsSync11(p)) return void 0;
4532
+ try {
4533
+ return stripBom(readFileSync4(p, "utf-8"));
4534
+ } catch {
4535
+ return void 0;
4536
+ }
4537
+ }
4538
+ function readMemory(cwd = process.cwd()) {
4539
+ const raw = readRaw(cwd);
4540
+ if (raw.kind === "error") {
4541
+ warnUnreadable(cwd);
4542
+ return emptyV2();
4543
+ }
4544
+ if (raw.kind === "parsed") {
4545
+ if (isV2(raw.value)) return normalizeV2(raw.value);
4546
+ if (Array.isArray(raw.value)) {
4547
+ const v2 = migrateMemory(raw.value, readLearningsRaw(cwd));
4548
+ try {
4549
+ writeMemory(cwd, v2);
4550
+ } catch {
4551
+ 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)`));
4552
+ }
4553
+ return v2;
4554
+ }
4555
+ warnUnrecognized(cwd);
4556
+ return emptyV2();
4557
+ }
4558
+ return migrateMemory(null, readLearningsRaw(cwd));
4559
+ }
4560
+ function loadForMutation(cwd) {
4561
+ const raw = readRaw(cwd);
4562
+ if (raw.kind === "error") {
4563
+ warnUnreadable(cwd);
4564
+ return { ok: false };
4565
+ }
4566
+ if (raw.kind === "parsed") {
4567
+ if (isV2(raw.value)) return { ok: true, mem: normalizeV2(raw.value) };
4568
+ if (Array.isArray(raw.value)) return { ok: true, mem: migrateMemory(raw.value, readLearningsRaw(cwd)) };
4569
+ warnUnrecognized(cwd);
4570
+ return { ok: false };
4571
+ }
4572
+ return { ok: true, mem: migrateMemory(null, readLearningsRaw(cwd)) };
4573
+ }
4574
+ function isActive(e) {
4575
+ return e.status !== "archived" && e.status !== "resolved";
4576
+ }
4577
+ function writeMemory(cwd, mem) {
4578
+ const p = join7(cwd, MEMORY_PATH_REL);
4579
+ mkdirSync7(join7(cwd, ".vhk"), { recursive: true });
4580
+ if (existsSync11(p)) {
4581
+ const cur = readRaw(cwd);
4582
+ const curIsV2 = cur.kind === "parsed" && isV2(cur.value);
4583
+ if (cur.kind !== "error" && !curIsV2 && !existsSync11(p + ".v1.bak")) {
4584
+ try {
4585
+ copyFileSync(p, p + ".v1.bak");
4586
+ } catch {
4587
+ }
4588
+ }
4589
+ if (cur.kind !== "error") {
4590
+ try {
4591
+ copyFileSync(p, p + ".bak");
4592
+ } catch {
4593
+ }
4594
+ }
4595
+ }
4596
+ const tmpPath = p + ".tmp";
4597
+ writeFileSync7(tmpPath, JSON.stringify(mem, null, 2) + "\n", "utf-8");
4598
+ try {
4599
+ renameSync(tmpPath, p);
4600
+ } catch (err) {
4601
+ try {
4602
+ rmSync3(tmpPath, { force: true });
4603
+ } catch {
4604
+ }
4605
+ throw err;
4606
+ }
4607
+ }
4608
+ function nextId(bucket, mem) {
4609
+ const prefix = BUCKET_PREFIX[bucket];
4610
+ const list = bucket === "decision" ? mem.decisions : bucket === "failure" ? mem.failures : mem.successes;
4611
+ const idRe = new RegExp(`^${prefix}(\\d+)$`);
4612
+ let max = 0;
4613
+ for (const e of list) {
4614
+ const m = e.id.match(idRe);
4615
+ if (m) max = Math.max(max, Number(m[1]));
4616
+ }
4617
+ return `${prefix}${max + 1}`;
4618
+ }
4619
+ function orderedAll(mem) {
4620
+ return [
4621
+ ...mem.decisions.map((entry) => ({ bucket: "decision", entry })),
4622
+ ...mem.failures.map((entry) => ({ bucket: "failure", entry })),
4623
+ ...mem.successes.map((entry) => ({ bucket: "success", entry }))
4624
+ ];
4625
+ }
4626
+ var VALID_BUCKETS = ["decision", "failure", "success"];
4627
+ async function memoryAdd(content, opts = {}) {
4628
+ console.log(chalk23.bold("\n\u{1F9E0} " + t("memory.addTitle")));
4629
+ console.log(chalk23.gray("\u2500".repeat(40)));
4630
+ if (!content || !content.trim()) {
4631
+ console.log(chalk23.red("\u274C \uAE30\uC5B5\uD560 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694."));
4632
+ console.log(chalk23.gray(' \uC608: vhk memory add "API\uB294 tRPC \uC0AC\uC6A9" --type decision'));
4633
+ process.exitCode = 1;
4634
+ return;
4635
+ }
4636
+ const typeRaw = opts.type ?? "decision";
4637
+ if (!VALID_BUCKETS.includes(typeRaw)) {
4638
+ console.log(chalk23.red(`\u274C --type \uC740 decision|failure|success \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uBC1B\uC740 \uAC12: ${typeRaw}).`));
4639
+ process.exitCode = 1;
4640
+ return;
4641
+ }
4642
+ const type = typeRaw;
4643
+ if (type === "decision" && (opts.why || opts.lesson)) {
4644
+ 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."));
4645
+ }
4646
+ const cwd = process.cwd();
4647
+ const loaded = loadForMutation(cwd);
4648
+ if (!loaded.ok) {
4649
+ 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."));
4650
+ process.exitCode = 1;
4651
+ return;
4652
+ }
4653
+ const mem = loaded.mem;
4654
+ const base = {
4655
+ id: nextId(type, mem),
4656
+ content: content.trim(),
4657
+ tags: opts.tags && opts.tags.length > 0 ? opts.tags : [],
4658
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4659
+ status: "active"
4660
+ };
4661
+ if (type === "failure") mem.failures.push({ ...base, why: opts.why, lesson: opts.lesson });
4662
+ else if (type === "success") mem.successes.push({ ...base, why: opts.why });
4663
+ else mem.decisions.push(base);
4664
+ writeMemory(cwd, mem);
4665
+ console.log(chalk23.green(`
4666
+ \u2705 \uAE30\uC5B5 \uC800\uC7A5\uB428 (${type} #${base.id})`));
4667
+ console.log(chalk23.cyan(` \u{1F4DD} ${base.content}`));
4668
+ printNextStep({ message: "\uAE30\uC5B5 \uC800\uC7A5 \uC644\uB8CC!", command: "vhk memory list", cursorHint: "\uAE30\uC5B5 \uBAA9\uB85D \uBCF4\uC5EC\uC918" });
4669
+ }
4670
+ var STATUS_ICON2 = { active: "\u{1F7E2}", resolved: "\u2705", archived: "\u{1F4E6}" };
4671
+ var BUCKET_LABEL = { decision: "\uACB0\uC815", failure: "\uC2E4\uD328", success: "\uC131\uACF5" };
4672
+ async function memoryList(opts = {}) {
4673
+ console.log(chalk23.bold("\n\u{1F9E0} " + t("memory.listTitle")));
4674
+ console.log(chalk23.gray("\u2500".repeat(40)));
4675
+ const mem = readMemory(process.cwd());
4676
+ const all = orderedAll(mem);
4677
+ const visible = all.map((x, i) => ({ ...x, n: i + 1 })).filter((x) => (opts.all || isActive(x.entry)) && (!opts.type || x.bucket === opts.type));
4678
+ if (visible.length === 0) {
4679
+ console.log(chalk23.yellow("\n\u{1F4ED} \uD45C\uC2DC\uD560 \uAE30\uC5B5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
4680
+ console.log(chalk23.gray(' vhk memory add "\uB0B4\uC6A9" --type decision|failure|success'));
4681
+ return;
4682
+ }
4683
+ console.log(chalk23.cyan(`
4684
+ ${visible.length}\uAC1C${opts.all ? " (\uBCF4\uAD00 \uD3EC\uD568)" : " (\uD65C\uC131)"}:
4685
+ `));
4686
+ for (const x of visible) {
4687
+ const e = x.entry;
4688
+ const fail = e;
4689
+ 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)")}`);
4690
+ if (fail.lesson && e.content) console.log(chalk23.dim(` \u{1F4A1} \uAD50\uD6C8: ${fail.lesson}`));
4691
+ if (fail.why) console.log(chalk23.dim(` \u21B3 ${fail.why}`));
4692
+ if (e.tags.length > 0) console.log(chalk23.blue(` \u{1F3F7}\uFE0F ${e.tags.join(", ")}`));
4693
+ }
4694
+ }
4695
+ function resolveIndex(indexStr, len) {
4696
+ const idx = parseInt(indexStr, 10) - 1;
4697
+ if (Number.isNaN(idx) || idx < 0 || idx >= len) return null;
4698
+ return idx;
4699
+ }
4700
+ async function memoryRemove(indexStr) {
4701
+ const cwd = process.cwd();
4702
+ const loaded = loadForMutation(cwd);
4703
+ if (!loaded.ok) {
4704
+ console.log(chalk23.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uC0AD\uC81C \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
4705
+ process.exitCode = 1;
4706
+ return;
4707
+ }
4708
+ const mem = loaded.mem;
4709
+ const all = orderedAll(mem);
4710
+ const idx = resolveIndex(indexStr, all.length);
4711
+ if (idx === null) {
4712
+ console.log(chalk23.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${all.length || 0})`));
4713
+ process.exitCode = 1;
4714
+ return;
4715
+ }
4716
+ const { bucket, entry } = all[idx];
4717
+ const list = bucket === "decision" ? mem.decisions : bucket === "failure" ? mem.failures : mem.successes;
4718
+ const pos = list.findIndex((e) => e === entry);
4719
+ if (pos >= 0) list.splice(pos, 1);
4720
+ writeMemory(cwd, mem);
4721
+ console.log(chalk23.green("\n\u2705 \uAE30\uC5B5 \uC0AD\uC81C\uB428:"));
4722
+ console.log(chalk23.gray(` ${entry.content || entry.lesson || entry.id}`));
4723
+ }
4724
+ function resolveEntryForMutation(indexStr) {
4725
+ const cwd = process.cwd();
4726
+ const loaded = loadForMutation(cwd);
4727
+ if (!loaded.ok) {
4728
+ console.log(chalk23.red("\u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uC791\uC5C5 \uC911\uB2E8 (\uC6D0\uBCF8 \uBCF4\uC874)."));
4729
+ process.exitCode = 1;
4730
+ return null;
4731
+ }
4732
+ const mem = loaded.mem;
4733
+ const all = orderedAll(mem);
4734
+ const idx = resolveIndex(indexStr, all.length);
4735
+ if (idx === null) {
4736
+ console.log(chalk23.red(`\u274C \uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBC88\uD638\uC785\uB2C8\uB2E4. (1~${all.length || 0})`));
4737
+ process.exitCode = 1;
4738
+ return null;
4739
+ }
4740
+ return { cwd, mem, entry: all[idx].entry };
4741
+ }
4742
+ function entryLabel(entry) {
4743
+ return entry.content || entry.lesson || entry.id;
4744
+ }
4745
+ async function memoryArchive(indexStr) {
4746
+ const r = resolveEntryForMutation(indexStr);
4747
+ if (!r) return;
4748
+ r.entry.status = "archived";
4749
+ r.entry.archivedAt = (/* @__PURE__ */ new Date()).toISOString();
4750
+ delete r.entry.resolvedAt;
4751
+ writeMemory(r.cwd, r.mem);
4752
+ console.log(chalk23.green(`
4753
+ \u{1F4E6} \uBCF4\uAD00\uB428: ${entryLabel(r.entry)}`));
4754
+ 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>"));
4755
+ }
4756
+ async function memoryResolve(indexStr) {
4757
+ const r = resolveEntryForMutation(indexStr);
4758
+ if (!r) return;
4759
+ r.entry.status = "resolved";
4760
+ r.entry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
4761
+ delete r.entry.archivedAt;
4762
+ writeMemory(r.cwd, r.mem);
4763
+ console.log(chalk23.green(`
4764
+ \u2705 \uD574\uACB0\uB428: ${entryLabel(r.entry)}`));
4765
+ console.log(chalk23.dim(" (vhk memory list --all \uB85C \uD655\uC778. \uB418\uB3CC\uB9AC\uAE30: vhk memory unarchive <\uBC88\uD638>)"));
4766
+ }
4767
+ async function memoryUnarchive(indexStr) {
4768
+ const r = resolveEntryForMutation(indexStr);
4769
+ if (!r) return;
4770
+ if (isActive(r.entry)) {
4771
+ console.log(chalk23.dim(` \uC774\uBBF8 \uD65C\uC131 \uD56D\uBAA9\uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C: ${entryLabel(r.entry)}`));
4772
+ return;
4773
+ }
4774
+ r.entry.status = "active";
4775
+ delete r.entry.archivedAt;
4776
+ delete r.entry.resolvedAt;
4777
+ writeMemory(r.cwd, r.mem);
4778
+ console.log(chalk23.green(`
4779
+ \u{1F7E2} \uD65C\uC131\uC73C\uB85C \uBCF5\uAD6C\uB428: ${entryLabel(r.entry)}`));
4780
+ }
4781
+ async function memoryMigrate() {
4782
+ const cwd = process.cwd();
4783
+ const raw = readRaw(cwd);
4784
+ if (raw.kind === "error") {
4785
+ warnUnreadable(cwd);
4786
+ 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."));
4787
+ process.exitCode = 1;
4788
+ return;
4789
+ }
4790
+ if (raw.kind === "parsed" && isV2(raw.value)) {
4791
+ console.log(chalk23.dim(" \uC774\uBBF8 memory schema v2 \uC785\uB2C8\uB2E4 \u2014 \uBCC0\uACBD \uC5C6\uC74C(\uBA71\uB4F1)."));
4792
+ return;
4793
+ }
4794
+ if (raw.kind === "parsed" && !Array.isArray(raw.value)) {
4795
+ warnUnrecognized(cwd);
4796
+ 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)."));
4797
+ process.exitCode = 1;
4798
+ return;
4799
+ }
4800
+ const learnings = readLearningsRaw(cwd);
4801
+ const hadFile = raw.kind === "parsed";
4802
+ if (raw.kind === "missing" && !learnings) {
4803
+ 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."));
4804
+ return;
4805
+ }
4806
+ const v2 = migrateMemory(raw.kind === "parsed" ? raw.value : null, learnings);
4807
+ writeMemory(cwd, v2);
4808
+ const backupNote = hadFile ? " (.v1.bak \uC6D0\uBCF8 \uC601\uAD6C \uBC31\uC5C5)" : " (\uC2E0\uADDC \uC0DD\uC131 \u2014 \uC6D0\uBCF8 \uC5C6\uC74C, \uBC31\uC5C5 \uC5C6\uC74C)";
4809
+ console.log(chalk23.green(`
4810
+ \u2705 memory.json \u2192 v2 \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC644\uB8CC${backupNote}`));
4811
+ console.log(
4812
+ chalk23.dim(
4813
+ ` 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)" : "")
4814
+ )
4815
+ );
4816
+ }
4817
+ function activeMemoryLines(mem, limit = 5) {
4818
+ const lines = [];
4819
+ const fmt = (e) => {
4820
+ const f = e;
4821
+ const base = e.content || (f.lesson ? `\u{1F4A1} ${f.lesson}` : e.id);
4822
+ return e.content && f.lesson ? `${base} \u2014 \u{1F4A1} ${f.lesson}` : base;
4823
+ };
4824
+ const section = (label, list) => {
4825
+ const act = list.filter(isActive);
4826
+ if (act.length === 0) return;
4827
+ lines.push(`**${label}** (${act.length})`);
4828
+ for (const e of act.slice(-limit)) lines.push(`- ${fmt(e)}`);
4829
+ if (act.length > limit) lines.push(`- \u2026 \uC678 ${act.length - limit}\uAC1C`);
4830
+ lines.push("");
4831
+ };
4832
+ section("\uACB0\uC815 (decisions)", mem.decisions);
4833
+ section("\uC2E4\uD328\xB7\uAD50\uD6C8 (failures)", mem.failures);
4834
+ section("\uC131\uACF5 (successes)", mem.successes);
4835
+ const pats = mem.patterns.length;
4836
+ if (pats > 0) lines.push(`**\uD328\uD134 \uD6C4\uBCF4 (patterns)**: ${pats}\uAC1C \u2014 \`vhk pattern\``, "");
4837
+ return lines;
4838
+ }
4839
+ function recordLesson(cwd, lesson, goalId) {
4840
+ const loaded = loadForMutation(cwd);
4841
+ if (!loaded.ok) return null;
4842
+ const mem = loaded.mem;
4843
+ const tag = goalId !== void 0 ? `goal-${goalId}` : "no-goal";
4844
+ const entry = {
4845
+ id: nextId("failure", mem),
4846
+ content: "",
4847
+ tags: [tag],
4848
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4849
+ status: "active",
4850
+ lesson: lesson.trim()
4851
+ };
4852
+ mem.failures.push(entry);
4853
+ writeMemory(cwd, mem);
4854
+ return entry;
4855
+ }
4856
+
4857
+ // src/commands/context.ts
4456
4858
  var CONTEXT_PATH = ".vhk/context.md";
4457
4859
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
4458
4860
  "node_modules",
@@ -4477,7 +4879,7 @@ function buildTree(dir, prefix = "", maxDepth = 3, depth = 0) {
4477
4879
  filtered.forEach((entry, index) => {
4478
4880
  const isLast = index === filtered.length - 1;
4479
4881
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4480
- const fullPath = join7(dir, entry);
4882
+ const fullPath = join8(dir, entry);
4481
4883
  const stat = statSync2(fullPath);
4482
4884
  const isDir = stat.isDirectory();
4483
4885
  lines.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
@@ -4509,8 +4911,8 @@ function extractTechStack() {
4509
4911
  else if (all.jest) stack["\uD14C\uC2A4\uD2B8"] = "jest";
4510
4912
  if (all.commander) stack["CLI"] = "commander";
4511
4913
  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";
4914
+ if (existsSync12("pnpm-lock.yaml")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "pnpm";
4915
+ else if (existsSync12("yarn.lock")) stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "yarn";
4514
4916
  else stack["\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800"] = "npm";
4515
4917
  if (pkg.name) stack["\uD328\uD0A4\uC9C0 \uC774\uB984"] = pkg.name;
4516
4918
  if (pkg.version) stack["\uBC84\uC804"] = pkg.version;
@@ -4553,8 +4955,8 @@ function getVhkCommands() {
4553
4955
  }
4554
4956
  async function context(opts = {}) {
4555
4957
  const compact = opts.compact === true;
4556
- console.log(chalk23.bold("\n\u{1F9E0} " + t("context.title")));
4557
- console.log(chalk23.gray("\u2500".repeat(40)));
4958
+ console.log(chalk24.bold("\n\u{1F9E0} " + t("context.title")));
4959
+ console.log(chalk24.gray("\u2500".repeat(40)));
4558
4960
  const stack = extractTechStack();
4559
4961
  const tree = buildTree(".", "", compact ? 2 : 3).join("\n");
4560
4962
  const commands = getVhkCommands();
@@ -4589,27 +4991,14 @@ async function context(opts = {}) {
4589
4991
  }
4590
4992
  lines.push("");
4591
4993
  }
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 {
4994
+ try {
4995
+ const memLines = activeMemoryLines(readMemory(process.cwd()));
4996
+ if (memLines.length > 0) {
4997
+ lines.push("## \uC800\uC7A5\uB41C \uAE30\uC5B5 (memory v2)");
4998
+ lines.push("");
4999
+ lines.push(...memLines);
4612
5000
  }
5001
+ } catch {
4613
5002
  }
4614
5003
  const goals = listGoals("goals");
4615
5004
  const activeId = selectActiveId(goals);
@@ -4626,13 +5015,6 @@ async function context(opts = {}) {
4626
5015
  lines.push("");
4627
5016
  }
4628
5017
  }
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
5018
  const activeBlockers = getActiveBlockers(3);
4637
5019
  if (activeBlockers.length > 0) {
4638
5020
  lines.push("## Active Blockers");
@@ -4667,12 +5049,12 @@ async function context(opts = {}) {
4667
5049
  } catch {
4668
5050
  }
4669
5051
  lines.push("");
4670
- mkdirSync7(".vhk", { recursive: true });
4671
- writeFileSync7(CONTEXT_PATH, lines.join("\n"), "utf-8");
4672
- console.log(chalk23.green(`
5052
+ mkdirSync8(".vhk", { recursive: true });
5053
+ writeFileSync8(CONTEXT_PATH, lines.join("\n"), "utf-8");
5054
+ console.log(chalk24.green(`
4673
5055
  \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."));
5056
+ console.log(chalk24.gray(` \uAE30\uC220 \uC2A4\uD0DD ${Object.keys(stack).length}\uAC1C \uAC10\uC9C0`));
5057
+ console.log(chalk24.gray(" AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC5D0\uAC8C \uC774 \uD30C\uC77C\uC744 \uCC38\uC870\uD558\uAC8C \uD558\uC138\uC694."));
4676
5058
  printNextStep({
4677
5059
  message: "\uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC!",
4678
5060
  command: "vhk context-show",
@@ -4680,110 +5062,33 @@ async function context(opts = {}) {
4680
5062
  });
4681
5063
  }
4682
5064
  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")));
5065
+ console.log(chalk24.bold("\n\u{1F4C4} " + t("context.showTitle")));
4713
5066
  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;
5067
+ if (!existsSync12(CONTEXT_PATH)) {
5068
+ console.log(chalk24.yellow("\n\u26A0\uFE0F \uCEE8\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
5069
+ console.log(chalk24.gray(" vhk context\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
4718
5070
  return;
4719
5071
  }
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})`));
4763
- return;
4764
- }
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}`));
5072
+ const content = readFileSync5(CONTEXT_PATH, "utf-8");
5073
+ console.log("\n" + content);
4769
5074
  }
4770
5075
 
4771
5076
  // src/commands/brief.ts
4772
- import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync5 } from "fs";
5077
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync6 } from "fs";
4773
5078
  import chalk25 from "chalk";
4774
5079
  var BRIEF_PATH = ".vhk/brief.md";
4775
5080
  function readProjectIdentity() {
4776
5081
  const out = {};
4777
5082
  try {
4778
5083
  if (existsSync13("RULES.md")) {
4779
- const r = readFileSync5("RULES.md", "utf-8");
5084
+ const r = readFileSync6("RULES.md", "utf-8");
4780
5085
  const m = r.split("\n")[0].match(/^#\s*(.+?)(?:\s*—.*)?$/);
4781
5086
  if (m) out.name = m[1].trim();
4782
5087
  const d = r.match(/한 줄 설명:\s*(.+)/);
4783
5088
  if (d) out.description = d[1].trim();
4784
5089
  }
4785
5090
  if (!out.name && existsSync13("CLAUDE.md")) {
4786
- const m = readFileSync5("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
5091
+ const m = readFileSync6("CLAUDE.md", "utf-8").match(/#\s*기록 규칙\s*\((.+?)\)/);
4787
5092
  if (m) out.name = m[1].trim();
4788
5093
  }
4789
5094
  } catch {
@@ -4833,22 +5138,14 @@ async function brief() {
4833
5138
  `- **\uBBF8\uCEE4\uBC0B \uBCC0\uACBD**: ${uncommitted ? `${uncommitted.split("\n").length}\uAC1C \uD30C\uC77C` : "\uC5C6\uC74C \u2705"}`
4834
5139
  );
4835
5140
  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 {
5141
+ try {
5142
+ const memLines = activeMemoryLines(readMemory(process.cwd()));
5143
+ if (memLines.length > 0) {
5144
+ lines.push("## \uC800\uC7A5\uB41C \uAE30\uC5B5 (memory v2)");
5145
+ lines.push("");
5146
+ lines.push(...memLines);
4851
5147
  }
5148
+ } catch {
4852
5149
  }
4853
5150
  if (existsSync13(".vhk/refs.json")) {
4854
5151
  try {
@@ -4895,7 +5192,7 @@ import chalk26 from "chalk";
4895
5192
  import inquirer11 from "inquirer";
4896
5193
  import { simpleGit as simpleGit2 } from "simple-git";
4897
5194
  import { existsSync as existsSync14 } from "fs";
4898
- import { join as join8 } from "path";
5195
+ import { join as join9 } from "path";
4899
5196
  var VHK_FOOTPRINT_FILES = [
4900
5197
  "CLAUDE.md",
4901
5198
  ".cursorrules",
@@ -4904,7 +5201,7 @@ var VHK_FOOTPRINT_FILES = [
4904
5201
  "docs/PRD.md"
4905
5202
  ];
4906
5203
  function detectExistingFootprint(cwd) {
4907
- return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join8(cwd, rel)));
5204
+ return VHK_FOOTPRINT_FILES.filter((rel) => existsSync14(join9(cwd, rel)));
4908
5205
  }
4909
5206
  async function runGitInit(cwd) {
4910
5207
  try {
@@ -5310,7 +5607,7 @@ import chalk29 from "chalk";
5310
5607
 
5311
5608
  // src/lib/config.ts
5312
5609
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
5313
- import { join as join9 } from "path";
5610
+ import { join as join10 } from "path";
5314
5611
 
5315
5612
  // src/lib/safety-mode.ts
5316
5613
  var SAFETY_MODES = ["lite", "standard", "strict"];
@@ -5326,10 +5623,10 @@ function isSafetyMode(value) {
5326
5623
 
5327
5624
  // src/lib/config.ts
5328
5625
  var CONFIG_DIR = ".vhk";
5329
- var CONFIG_PATH = join9(CONFIG_DIR, "config.json");
5626
+ var CONFIG_PATH = join10(CONFIG_DIR, "config.json");
5330
5627
  var DEFAULT_CONFIG = { safetyMode: DEFAULT_SAFETY_MODE };
5331
5628
  function readConfig(rootDir = process.cwd()) {
5332
- const full = join9(rootDir, CONFIG_PATH);
5629
+ const full = join10(rootDir, CONFIG_PATH);
5333
5630
  if (!existsSync15(full)) return { ...DEFAULT_CONFIG };
5334
5631
  try {
5335
5632
  const raw = readJsonFile(full);
@@ -5341,8 +5638,8 @@ function readConfig(rootDir = process.cwd()) {
5341
5638
  }
5342
5639
  }
5343
5640
  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");
5641
+ mkdirSync10(join10(rootDir, CONFIG_DIR), { recursive: true });
5642
+ writeFileSync10(join10(rootDir, CONFIG_PATH), JSON.stringify(config, null, 2) + "\n", "utf-8");
5346
5643
  }
5347
5644
 
5348
5645
  // src/commands/mode.ts
@@ -5382,7 +5679,7 @@ async function mode(target) {
5382
5679
  // src/commands/verify.ts
5383
5680
  import { execFileSync as execFileSync4 } from "child_process";
5384
5681
  import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
5385
- import { join as join10 } from "path";
5682
+ import { join as join11 } from "path";
5386
5683
  import chalk30 from "chalk";
5387
5684
 
5388
5685
  // src/commands/verify-report.ts
@@ -5491,13 +5788,13 @@ ${actions}
5491
5788
 
5492
5789
  // src/commands/verify.ts
5493
5790
  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");
5791
+ var REPORT_DIR_REL = join11(".vhk", "reports");
5792
+ var REPORT_PATH_REL = join11(REPORT_DIR_REL, "latest.json");
5793
+ var REPORT_HTML_PATH_REL = join11(REPORT_DIR_REL, "latest.html");
5497
5794
  var SHIM = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
5498
5795
  function detectPm(cwd) {
5499
- if (existsSync16(join10(cwd, "pnpm-lock.yaml"))) return "pnpm";
5500
- if (existsSync16(join10(cwd, "yarn.lock"))) return "yarn";
5796
+ if (existsSync16(join11(cwd, "pnpm-lock.yaml"))) return "pnpm";
5797
+ if (existsSync16(join11(cwd, "yarn.lock"))) return "yarn";
5501
5798
  return "npm";
5502
5799
  }
5503
5800
  function execGate(cmd, args, cwd) {
@@ -5540,7 +5837,7 @@ function runScriptGate(id, label, cwd, pm, argvFor) {
5540
5837
  };
5541
5838
  }
5542
5839
  function readPackageScripts(cwd) {
5543
- const pkgPath = join10(cwd, "package.json");
5840
+ const pkgPath = join11(cwd, "package.json");
5544
5841
  if (!existsSync16(pkgPath)) return {};
5545
5842
  try {
5546
5843
  const pkg = readJsonFile(pkgPath);
@@ -5556,7 +5853,7 @@ function runGates(cwd) {
5556
5853
  gates.push(
5557
5854
  runScriptGate("typecheck", "tsc --noEmit", cwd, pm, () => {
5558
5855
  if (scripts.typecheck) return ["run", "typecheck"];
5559
- if (existsSync16(join10(cwd, "tsconfig.json"))) return pm === "npm" ? ["exec", "--", "tsc", "--noEmit"] : ["exec", "tsc", "--noEmit"];
5856
+ if (existsSync16(join11(cwd, "tsconfig.json"))) return pm === "npm" ? ["exec", "--", "tsc", "--noEmit"] : ["exec", "tsc", "--noEmit"];
5560
5857
  return null;
5561
5858
  })
5562
5859
  );
@@ -5635,9 +5932,9 @@ function buildReport(gates, generatedAt, date) {
5635
5932
  function verifyEvidence(cwd = process.cwd()) {
5636
5933
  const gates = runGates(cwd);
5637
5934
  const report = buildReport(gates, (/* @__PURE__ */ new Date()).toISOString(), localDate());
5638
- const dir = join10(cwd, REPORT_DIR_REL);
5935
+ const dir = join11(cwd, REPORT_DIR_REL);
5639
5936
  mkdirSync11(dir, { recursive: true });
5640
- const path14 = join10(cwd, REPORT_PATH_REL);
5937
+ const path14 = join11(cwd, REPORT_PATH_REL);
5641
5938
  writeFileSync11(path14, JSON.stringify(report, null, 2) + "\n", "utf-8");
5642
5939
  try {
5643
5940
  ensureVhkIgnored(cwd, "reports/");
@@ -5651,7 +5948,7 @@ var STATUS_BADGE = {
5651
5948
  FAIL: chalk30.red.bold("FAIL")
5652
5949
  };
5653
5950
  async function renderVerifyReport(cwd, opts) {
5654
- const jsonPath = join10(cwd, REPORT_PATH_REL);
5951
+ const jsonPath = join11(cwd, REPORT_PATH_REL);
5655
5952
  let report;
5656
5953
  if (existsSync16(jsonPath)) {
5657
5954
  try {
@@ -5665,9 +5962,9 @@ async function renderVerifyReport(cwd, opts) {
5665
5962
  report = verifyEvidence(cwd).report;
5666
5963
  }
5667
5964
  const html = renderReportHtml(report);
5668
- const htmlPath = join10(cwd, REPORT_HTML_PATH_REL);
5965
+ const htmlPath = join11(cwd, REPORT_HTML_PATH_REL);
5669
5966
  try {
5670
- mkdirSync11(join10(cwd, REPORT_DIR_REL), { recursive: true });
5967
+ mkdirSync11(join11(cwd, REPORT_DIR_REL), { recursive: true });
5671
5968
  writeFileSync11(htmlPath, html, "utf-8");
5672
5969
  } catch (e) {
5673
5970
  console.error(
@@ -5751,7 +6048,7 @@ async function verify(opts = {}) {
5751
6048
 
5752
6049
  // src/commands/review.ts
5753
6050
  import { existsSync as existsSync17, writeFileSync as writeFileSync12 } from "fs";
5754
- import { join as join11 } from "path";
6051
+ import { join as join12 } from "path";
5755
6052
  import chalk31 from "chalk";
5756
6053
  var GOALS_DIR2 = "goals";
5757
6054
  var COVERAGE_MIN = 0.5;
@@ -5909,7 +6206,7 @@ async function review(opts = {}) {
5909
6206
  if (opts.id === void 0 && goalStatus === "NOT_STARTED") {
5910
6207
  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
6208
  }
5912
- const jsonPath = join11(cwd, REPORT_PATH_REL);
6209
+ const jsonPath = join12(cwd, REPORT_PATH_REL);
5913
6210
  if (!existsSync17(jsonPath)) {
5914
6211
  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
6212
  printNextStep({
@@ -6020,12 +6317,12 @@ ${result.disclaimer}`));
6020
6317
  }
6021
6318
 
6022
6319
  // 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";
6320
+ import { existsSync as existsSync18, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, rmSync as rmSync4 } from "fs";
6321
+ import { join as join13 } from "path";
6025
6322
  import chalk32 from "chalk";
6026
6323
  import inquirer12 from "inquirer";
6027
6324
  import { simpleGit as simpleGit3 } from "simple-git";
6028
- var MISSION_PATH_REL = join12(".vhk", "mission.json");
6325
+ var MISSION_PATH_REL = join13(".vhk", "mission.json");
6029
6326
  var MISSION_SCHEMA_VERSION = 1;
6030
6327
  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
6328
  function globToRegExp(glob) {
@@ -6073,7 +6370,7 @@ function checkMission(changedFiles, mission) {
6073
6370
  return { violations, warnings, disclaimer: MISSION_DISCLAIMER };
6074
6371
  }
6075
6372
  function readMission(cwd = process.cwd()) {
6076
- const p = join12(cwd, MISSION_PATH_REL);
6373
+ const p = join13(cwd, MISSION_PATH_REL);
6077
6374
  if (!existsSync18(p)) return null;
6078
6375
  try {
6079
6376
  const m = readJsonFile(p);
@@ -6084,8 +6381,8 @@ function readMission(cwd = process.cwd()) {
6084
6381
  }
6085
6382
  }
6086
6383
  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");
6384
+ mkdirSync12(join13(cwd, ".vhk"), { recursive: true });
6385
+ writeFileSync13(join13(cwd, MISSION_PATH_REL), JSON.stringify(mission, null, 2) + "\n", "utf-8");
6089
6386
  }
6090
6387
  async function collectChangedFiles(cwd) {
6091
6388
  const status2 = await simpleGit3(cwd).status();
@@ -6182,13 +6479,13 @@ async function missionCheck() {
6182
6479
  }
6183
6480
  async function missionClear() {
6184
6481
  const cwd = process.cwd();
6185
- const p = join12(cwd, MISSION_PATH_REL);
6482
+ const p = join13(cwd, MISSION_PATH_REL);
6186
6483
  if (!existsSync18(p)) {
6187
6484
  console.log(chalk32.dim(" \uBBF8\uC158 \uACC4\uC57D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC9C0\uC6B8 \uAC83 \uC5C6\uC74C."));
6188
6485
  return;
6189
6486
  }
6190
6487
  try {
6191
- rmSync3(p);
6488
+ rmSync4(p);
6192
6489
  console.log(chalk32.green(" \u2705 \uBBF8\uC158 \uACC4\uC57D \uC0AD\uC81C\uB428 (.vhk/mission.json)."));
6193
6490
  } catch (e) {
6194
6491
  console.error(chalk32.red(` \u274C \uC0AD\uC81C \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`));
@@ -6337,6 +6634,7 @@ async function dispatchNlpRoute(route, input) {
6337
6634
  case "context-show":
6338
6635
  return contextShow();
6339
6636
  case "memory":
6637
+ if (route.args?.[0] === "migrate") return memoryMigrate();
6340
6638
  return memoryList();
6341
6639
  case "brief":
6342
6640
  return brief();
@@ -6448,11 +6746,14 @@ ${ko.agent.learnTitle}
6448
6746
  return;
6449
6747
  }
6450
6748
  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
- );
6749
+ const entry = recordLesson(process.cwd(), lesson, goalId);
6750
+ if (!entry) {
6751
+ console.log(chalk34.red(" \u274C memory.json \uC190\uC0C1 \uC758\uC2EC \u2014 \uAD50\uD6C8 \uAE30\uB85D \uC911\uB2E8. \uC6D0\uBCF8/\uBC31\uC5C5 \uD655\uC778 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."));
6752
+ process.exitCode = 1;
6753
+ return;
6754
+ }
6755
+ console.log(chalk34.green(` \u2705 \uAD50\uD6C8 \uAE30\uB85D \u2192 memory failures.lesson (${entry.id})`));
6756
+ console.log(chalk34.dim(" \uAD50\uD6C8\xB7\uACB0\uC815\xB7\uC2E4\uD328\xB7\uC131\uACF5 \uBAA8\uB450 vhk memory (\uB2E8\uC77C SoT). vhk memory list \uB85C \uD655\uC778."));
6456
6757
  }
6457
6758
  async function resume(opts = {}) {
6458
6759
  console.log(chalk34.bold(`
@@ -6700,19 +7001,32 @@ missionCmd.command("clear").description("\uBBF8\uC158 \uACC4\uC57D \uC0AD\uC81C
6700
7001
  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
7002
  await contextShow();
6702
7003
  });
6703
- var memoryCmd = program.command("memory").alias("\uAE30\uC5B5").description("\uACB0\uC815\uC0AC\uD56D \uAE30\uC5B5 \uAD00\uB9AC (add / list / remove)").action(async () => {
7004
+ 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
7005
  await memoryList();
6705
7006
  });
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) => {
7007
+ 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
7008
  const tags = opts.tags ? opts.tags.split(",").map((s) => s.trim()) : void 0;
6708
- await memoryAdd(content, tags);
7009
+ await memoryAdd(content, { type: opts.type, tags, why: opts.why, lesson: opts.lesson });
6709
7010
  });
6710
- memoryCmd.command("list").alias("\uBAA9\uB85D").description("\uC800\uC7A5\uB41C \uAE30\uC5B5 \uBAA9\uB85D").action(async () => {
6711
- await memoryList();
7011
+ 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) => {
7012
+ const type = opts.type === "decision" || opts.type === "failure" || opts.type === "success" ? opts.type : void 0;
7013
+ await memoryList({ type, all: opts.all });
6712
7014
  });
6713
7015
  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
7016
  await memoryRemove(index);
6715
7017
  });
7018
+ 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) => {
7019
+ await memoryArchive(index);
7020
+ });
7021
+ 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) => {
7022
+ await memoryResolve(index);
7023
+ });
7024
+ 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) => {
7025
+ await memoryUnarchive(index);
7026
+ });
7027
+ 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 () => {
7028
+ await memoryMigrate();
7029
+ });
6716
7030
  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
7031
  await brief();
6718
7032
  });
@@ -6740,7 +7054,7 @@ goalCmd.command("sync").alias("\uB3D9\uAE30\uD654").description("goals/*.md \uC2
6740
7054
  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
7055
  await blocker(description);
6742
7056
  });
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) => {
7057
+ 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
7058
  await learn(lesson);
6745
7059
  });
6746
7060
  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) => {
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.1",
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
+ }