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