@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 +8 -8
- package/dist/{chunk-RXDOM4QT.js → chunk-HCNU6K7D.js} +13 -2
- package/dist/index.js +527 -213
- package/dist/mcp/index.js +1 -1
- package/package.json +72 -72
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
|
|
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` | `기억` |
|
|
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건 → `
|
|
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 도구 **
|
|
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
|
-
- **컨텍스트/기록 (
|
|
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`
|
|
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개
|
|
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). 현재
|
|
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
|
|
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
|
-
{
|
|
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-
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
4448
|
-
mkdirSync as
|
|
4449
|
-
readFileSync as
|
|
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
|
|
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 =
|
|
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 (
|
|
4513
|
-
else if (
|
|
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(
|
|
4557
|
-
console.log(
|
|
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
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
);
|
|
4597
|
-
|
|
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
|
-
|
|
4671
|
-
|
|
4672
|
-
console.log(
|
|
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(
|
|
4675
|
-
console.log(
|
|
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(
|
|
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 (!
|
|
4715
|
-
console.log(chalk24.
|
|
4716
|
-
console.log(chalk24.gray(
|
|
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
|
|
4721
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
5345
|
-
writeFileSync10(
|
|
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
|
|
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 =
|
|
5495
|
-
var REPORT_PATH_REL =
|
|
5496
|
-
var REPORT_HTML_PATH_REL =
|
|
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(
|
|
5500
|
-
if (existsSync16(
|
|
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 =
|
|
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(
|
|
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 =
|
|
5935
|
+
const dir = join11(cwd, REPORT_DIR_REL);
|
|
5639
5936
|
mkdirSync11(dir, { recursive: true });
|
|
5640
|
-
const path14 =
|
|
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 =
|
|
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 =
|
|
5965
|
+
const htmlPath = join11(cwd, REPORT_HTML_PATH_REL);
|
|
5669
5966
|
try {
|
|
5670
|
-
mkdirSync11(
|
|
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
|
|
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 =
|
|
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
|
|
6024
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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(
|
|
6088
|
-
writeFileSync13(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
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("\
|
|
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)").
|
|
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
|
-
|
|
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
|
|
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
package/package.json
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@byh3071/vhk",
|
|
3
|
-
"version": "
|
|
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
|
+
}
|