diff --git a/agents/vync-translator.md b/agents/vync-translator.md index 199e7c3..05bc5e2 100644 --- a/agents/vync-translator.md +++ b/agents/vync-translator.md @@ -33,11 +33,18 @@ permissionMode: bypassPermissions ## 반환 포맷 -**성공 시**: 한 줄 요약만 반환. 추가 설명 불필요. -- create: `"mindmap: 프로젝트 > [기획, 개발, 출시]"` -- read (변경 있음): `"변화 요약: 개발 프로세스에 테스팅이 추가되고, 기획의 리서치가 축소됨. 실행 중심으로 구조가 이동하는 방향."` -- read (변경 없음): `"mindmap: 프로젝트 > [기획, 개발] (변경 없음)"` -- update: `"updated: 개발 > [FE, BE, +테스트, +CI/CD]"` +**create**: 한 줄 요약. 예: `"mindmap: 프로젝트 > [기획, 개발, 출시]"` +**update**: 한 줄 요약. 예: `"updated: 개발 > [FE, BE, +테스트, +CI/CD]"` + +**read (변경 있음)**: 구조화 반환 (3-5줄) +``` +요약: <구조적 변경의 사실적 기술> +의도: <변경이 시사하는 사용자의 생각/판단 추론> +확신: <높음|중간|낮음> (<근거>) +제안: <추론 기반 다음 행동 제안, optional> +``` + +**read (변경 없음)**: `"요약: 변경 없음"` **실패 시**: `"error: <간략한 설명>"` 형식으로 반환. 예: `"error: 파일을 찾을 수 없습니다"`, `"error: JSON 검증 실패 — 중복 ID"` @@ -56,10 +63,17 @@ permissionMode: bypassPermissions ### Read 1. 전달받은 diff 결과를 분석 (직접 JSON 비교하지 않음!) -2. 대화 맥락과 결합하여 "생각의 변화"를 의미적으로 번역 -3. 반환: 변화의 의미 요약 - 예: "개발 프로세스에 테스팅이 추가되고, 기획의 리서치가 축소됨. 실행 중심으로 구조가 이동하는 방향." -4. 스냅샷 갱신은 vync diff가 이미 처리 (sub-agent에서 안 함) +2. **semanticHint가 있으면 이를 기반으로**, 없으면 구조+유형에서 직접 추론 +3. 대화 맥락과 결합하여 "사용자의 생각이 어떻게 변했는지" 번역 +4. 확신 레벨 판단: + - **높음**: mindmap 위계 변경, 명확한 추가/삭제 + - **중간**: 다중 동시 이동, 복합 변경 + - **낮음**: 의미 불분명한 소규모 변경 +5. 변화의 **방향성** 포착: + - 구조 변경이 시사하는 사고의 전환 (예: "독립 개념 → 종속 개념") + - 복합 변경에서 드러나는 의도 패턴 (예: "실행 중심으로 구조 이동") +6. 반환: 4-필드 구조화 (요약/의도/확신/제안) +7. 스냅샷 갱신은 vync diff가 이미 처리 (sub-agent에서 안 함) ### Update 1. 대화 맥락 + diff 이해 diff --git a/commands/vync.md b/commands/vync.md index 843bb7c..c7197a8 100644 --- a/commands/vync.md +++ b/commands/vync.md @@ -72,7 +72,13 @@ Agent({ }) ``` -5. Sub-agent의 의미적 번역을 대화에 통합 +5. Sub-agent의 구조화된 반환을 활용: + - **확신 높음**: 의도를 대화에 자연스럽게 반영 + 제안 전달 + 예: "기획의 세부 활동으로 리서치를 보시는 것 같은데, 개발 쪽도 정리할까요?" + - **확신 중간**: 추론 언급 + 확인 포함 + 예: "A-B, C-D로 묶으셨네요. 이 구분으로 진행할까요?" + - **확신 낮음**: 요약(사실)만 간략히 언급, 의도 추론은 하지 않음 + 예: "구조에 약간 변화가 있네요." ### Update diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 54f4c7b..809a0eb 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -363,7 +363,7 @@ Vync/ # nx monorepo (Drawnix 포크 기반) │ └── __tests__/ │ ├── init.test.ts # init 유닛 테스트 │ ├── open.test.ts # smart restart + vyncStop 유닛 테스트 -│ └── diff.test.ts # diff 엔진 유닛 테스트 (15개) [VYNC 추가: D-015] +│ └── diff.test.ts # diff 엔진 유닛 테스트 (31개: 구조적 diff 16 + semantic hint 15) [VYNC 추가: D-015, D-017] ├── bin/ │ └── vync.js # CLI 진입점 (CommonJS, tsx spawn) [VYNC 추가] ├── .claude-plugin/ # Claude Code 플러그인 메타 (marketplace 표준) [VYNC 추가] diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index d34e613..751e76d 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -26,6 +26,7 @@ | D-015 | [Diff 파이프라인: Hybrid Architecture](#d-015) | 확정 | 2026-03-11 | | D-016 | [Sub-agent 역할 확장: 시각화 전문가](#d-016) | 확정 | 2026-03-11 | | D-017 | [의미적 동기화: Semantic Sync](#d-017) | 확정 | 2026-03-13 | +| D-018 | [파일 유형 정책: 한 파일 = 한 시각화 유형](#d-018) | 확정 | 2026-03-13 | --- @@ -405,7 +406,37 @@ Level 3: Confirmed Intent 사용자 확인 (필요시에만) | 항상 명시적 확인 | 대화 흐름 중단, Vync 철학(D-007) 위배 | **핵심 원칙**: 오추론의 비용이 낮음 (자연어 정정 한마디). **자연스러움 > 정확도**. -**구현**: diff.ts semantic annotation + translator prompt 강화 + 반환 포맷 확장 (~80 LOC) +**구현 세부 결정 (S-1~S-4)**: +- **S-1**: 파일 레벨 유형 감지 (D-018에 의해 혼합 캔버스 구조적 방지) +- **S-2**: 복합 패턴은 다중 moved(그룹화)만 (Phase 1). moved+modified 결합은 관찰 후 결정. +- **S-3**: 확신 낮음 시 간략히 언급 ("구조에 약간 변화가 있네요"). 무시 안 함. +- **S-4**: enrichWithSemanticHints()를 computeDiff와 분리. 기존 테스트 무변경. + +**구현**: diff.ts semantic annotation + translator prompt 강화 + 반환 포맷 확장 (~95 LOC) **설계 문서**: `docs/plans/2026-03-13-semantic-sync-design.md` +**구현 계획**: `docs/plans/2026-03-13-semantic-sync.md` **재검토 조건**: semantic hint가 translator 추론 품질을 유의미하게 향상시키지 못할 경우 + +--- + +### D-018 + +**파일 유형 정책: 한 파일 = 한 시각화 유형** + +하나의 `.vync` 파일에는 하나의 시각화 유형만 포함한다. 다른 유형이 필요하면 별도 파일을 생성한다. + +- **mindmap** 파일에 flowchart 노드를 혼합하지 않음 +- 대화 중 다른 시각화 형식이 필요하면 `/vync create`로 새 파일 생성 +- vync-translator의 create/update 시 기존 파일의 유형을 유지 + +| 대안 | 기각 사유 | +|------|----------| +| 혼합 캔버스 허용 | 유형별 semantic annotation이 노드 레벨로 복잡해짐, 해석 안정성 저하 | +| 파일 내 섹션으로 유형 분리 | .vync 포맷에 섹션 개념 없음, 복잡성 증가 | + +**근거**: D-017(Semantic Sync)에서 파일 레벨 유형 감지를 채택한 전제 조건. 혼합 캔버스를 방지하면 시각화 유형 감지가 구조적으로 안전해지고, semantic annotation의 정확도가 보장됨. 멀티 파일/멀티 탭(D-014)이 이미 지원되므로 별도 파일 생성의 UX 비용이 낮음. + +**영향 범위**: vync-translator (create/update 행동 규칙), diff.ts (파일 레벨 유형 감지 근거) + +**재검토 조건**: 사용자가 한 캔버스에서 여러 유형을 동시에 필요로 하는 패턴이 빈번히 발생할 경우 diff --git a/docs/PLAN.md b/docs/PLAN.md index 87ec21a..edf9ba2 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -13,7 +13,7 @@ - Server Lifecycle Fix (PR #10): 완료 (2026-03-11) - macOS 코드 서명 + 공증: 완료 (2026-03-11) - Tab Bar "+" 버튼 수정 (PR #11): 구현 완료, PR 대기 (2026-03-12) -- Semantic Sync (D-017): 설계 완료, 구현 대기 (2026-03-13) — `docs/plans/2026-03-13-semantic-sync.md` +- Semantic Sync (D-017): 구현 완료, PR 대기 (2026-03-13) — `feat/semantic-sync` 브랜치 --- @@ -189,7 +189,7 @@ - [x] 7-P1.3 vync 커맨드 Read 섹션에 diff 컨텍스트 반영 - [x] 7-P1.4 설계문서 데이터 흐름 업데이트 - [x] `/vync create` → sub-agent → 한 줄 요약 반환 흐름 동작 -- [x] Install script가 에이전트 파일을 `~/.claude/agents/`에 설치 +- [x] Install script가 플러그인을 마켓플레이스 캐시에 설치 (`install.sh` → rsync) --- @@ -349,6 +349,35 @@ --- +## Semantic Sync (D-017/D-018) + +**목표**: diff의 구조적 변경에 시각화 유형별 semantic annotation을 추가하고, Sub-agent가 맥락과 결합하여 사용자 의도를 추론하는 파이프라인 개선. +**브랜치**: `feat/semantic-sync` +**설계**: `docs/plans/2026-03-13-semantic-sync-design.md` +**구현 계획**: `docs/plans/2026-03-13-semantic-sync.md` + +- [x] SS.1 DiffChange 타입에 `semanticHint` 필드 추가 +- [x] SS.2 `detectVizType()` — 파일 레벨 시각화 유형 감지 (S-1) +- [x] SS.3 `enrichWithSemanticHints()` — computeDiff와 분리된 semantic layer (S-4) +- [x] SS.4 다중 moved 그룹화 패턴 감지 (S-2) +- [x] SS.5 `formatDiffResult` hint 포함 출력 +- [x] SS.6 신규 테스트 15개 추가 (총 31 diff 테스트, 93 전체 PASS) +- [x] SS.7 translator prompt: Read 반환 4-필드 구조화 (요약/의도/확신/제안) +- [x] SS.8 translator prompt: Read 절차 해석 가이드 + 확신 레벨 판단 +- [x] SS.9 vync.md: 확신 레벨별 메인 세션 활용 가이드 (S-3) +- [x] SS.10 플러그인 캐시 동기화 완료 + +**완료 기준**: +- [x] `vync diff`가 mindmap에서 semanticHint 포함 출력 +- [x] translator가 Read 시 4-필드 구조화 반환 +- [x] 메인 세션이 확신 레벨별 행동 가이드 보유 +- [x] 93개 전체 테스트 PASS (diff 31개 포함) + +### 남은 작업 +- PR 생성 (feat/semantic-sync → develop) + +--- + ## 리스크 | 리스크 | 영향 | 완화 방안 | 평가 시점 | diff --git a/docs/plans/2026-03-13-semantic-sync-design.md b/docs/plans/2026-03-13-semantic-sync-design.md index 258a2ec..83dd2a3 100644 --- a/docs/plans/2026-03-13-semantic-sync-design.md +++ b/docs/plans/2026-03-13-semantic-sync-design.md @@ -266,7 +266,7 @@ translator: "요약: A→B 하위, C→D 하위로 이동\n의도: 쌍별 그룹 확신 낮음: ``` translator: "요약: 노드 3개 위치 약간 조정\n의도: 시각적 정리로 추정\n확신: 낮음\n제안: 없음" -→ 메인: (언급하지 않거나) "구조에 큰 변화는 없네요." +→ 메인: "구조에 약간 변화가 있네요." (간략히 언급, 의도 추론 없이) ``` --- @@ -292,8 +292,9 @@ Vync의 핵심 철학은 **대화 흐름을 끊지 않는 것**. 의도가 모 **Type C: 의미 판단 어려움** (낮은 확신) - 예: 레이아웃만 약간 조정 (diff가 감지 못하거나 layout 필드만 변경) -- 처리: 구조적 사실만 보고, 의도 추론 시도하지 않음 -- "구조 변경이 감지되지 않았습니다" 또는 사실만 전달 +- 처리: 구조적 사실(요약)만 **간략히 언급**, 의도 추론은 하지 않음 +- 예: "구조에 약간 변화가 있네요." — 변경 사실은 전달하되 해석 없이 +- **주의**: 완전히 무시하지 않음. 사용자의 의도적 변경을 놓치는 것보다 간략한 언급이 안전 ### 확인의 형태 @@ -324,7 +325,7 @@ Vync의 핵심 철학은 **대화 흐름을 끊지 않는 것**. 의도가 모 2. **단일 스냅샷 한계**: .lastread가 하나뿐이므로, "A→B→C" 3단계 변경에서 중간 단계 B를 놓침. 사용자가 여러 번 수정한 후 read하면 최종 결과만 보임. -3. **시각화 유형 정보 부재**: 현재 .vync 파일에 명시적 "type" 필드가 없음. elements의 구조(mindmap children vs geometry+arrow)로 유추해야 함. 혼합 캔버스(mindmap + flowchart)에서는 유형별 해석이 복잡해짐. +3. ~~**시각화 유형 정보 부재**~~: **D-018에 의해 해소**. "한 파일 = 한 시각화 유형" 원칙으로 혼합 캔버스를 구조적으로 방지. 파일 레벨 유형 감지(root 노드 type 확인)로 충분. ### LLM 추론 한계 @@ -340,14 +341,30 @@ Vync의 핵심 철학은 **대화 흐름을 끊지 않는 것**. 의도가 모 --- -## 7. 구현 로드맵 (이후 세션 참조) +## 7. 구현 세부 결정 (2026-03-13 확정) + +리뷰를 통해 확정된 구현 세부 결정: + +| ID | 항목 | 결정 | 근거 | +|----|------|------|------| +| S-1 | 시각화 유형 감지 단위 | **파일 레벨** | D-018("한 파일 = 한 유형")에 의해 혼합 캔버스 구조적 방지. generic 폴백으로 안전. | +| S-2 | 복합 패턴 범위 | **다중 moved(그룹화)만** | 가장 빈번하고 의미 명확. moved+modified 결합("재해석")은 실사용 관찰 후 결정. | +| S-3 | 확신 낮음 시 행동 | **간략히 언급** | 무시보다 안전. "구조에 약간 변화가 있네요" — 사용자가 이어서 말하거나 무시 가능. | +| S-4 | semanticHint 생성 위치 | **computeDiff와 분리** | enrichWithSemanticHints() 별도 함수. computeDiff 순수성 유지, 테스트 독립. | + +**관련 결정**: D-018 (한 파일 = 한 시각화 유형) — 이 설계에서 도출되어 별도 결정으로 등록됨. + +--- + +## 8. 구현 로드맵 (이후 세션 참조) ### Phase 1: diff.ts semantic annotation (~50 LOC) - DiffChange에 `semanticHint` 필드 추가 -- 시각화 유형 감지 로직 (elements 구조 기반) -- 유형별 hint 생성 규칙 적용 +- `detectVizType()` — 파일 레벨 유형 감지 (S-1) +- `enrichWithSemanticHints()` — computeDiff와 분리된 별도 함수 (S-4) +- 개별 hint 생성 + 다중 moved 그룹화만 (S-2) - formatDiffResult에 hint 포함 -- 기존 테스트 + semantic hint 테스트 추가 +- 기존 computeDiff 테스트 무변경 + enrichWithSemanticHints 테스트 추가 ### Phase 2: translator prompt 강화 (~30 LOC) - Read 절차에 해석 가이드 추가 (§3 해석 규칙 참조) @@ -362,7 +379,7 @@ Vync의 핵심 철학은 **대화 흐름을 끊지 않는 것**. 의도가 모 - 확신 레벨별 메인 세션 행동 가이드: - 높음 → 자연스럽게 반영 + 제안 활용 - 중간 → 추론 + 확인 포함 - - 낮음 → 사실만 전달 + - 낮음 → 사실만 간략히 언급 (S-3) - "추론 + 자연스러운 확인" 패턴 예시 ### 예상 변경 규모 @@ -372,7 +389,7 @@ Vync의 핵심 철학은 **대화 흐름을 끊지 않는 것**. 의도가 모 --- -## 8. 설계 결정 +## 9. 설계 결정 ### D-017: Semantic Sync (의미적 동기화) diff --git a/docs/plans/2026-03-13-semantic-sync-e2e-test.md b/docs/plans/2026-03-13-semantic-sync-e2e-test.md new file mode 100644 index 0000000..815c6e0 --- /dev/null +++ b/docs/plans/2026-03-13-semantic-sync-e2e-test.md @@ -0,0 +1,736 @@ +# Semantic Sync E2E 검증 절차서 + +> **목적**: diff → translator → 메인 세션 전체 파이프라인의 의미 추론 품질 검증 +> **방식**: 누적 시나리오 5라운드, Vync 로드맵 도메인 +> **전제**: `feat/semantic-sync` 브랜치, 플러그인 캐시 동기화 완료 +> **별도 세션 필요**: 이 테스트는 **새 세션**에서 실행 (translator 에이전트 디스커버리를 위해) + +--- + +## 평가 기준 + +| 항목 | PASS 조건 | +|------|----------| +| **F1: Hint 존재** | `vync diff` 출력에 `→` semanticHint 라인 포함 | +| **F2: 포맷 준수** | translator 반환이 `요약/의도/확신/제안` 4-필드 구조 | +| **F3: 확신 적절** | 확신 레벨이 변경의 모호성 수준과 일치 | +| **F4: 의도 정확** | 의도 필드가 실제 사용자 의도와 부합 | +| **F5: 제안 유용** | 제안이 대화 흐름에서 자연스럽게 활용 가능 | + +**전체 PASS**: 25개 중 20개 이상 (80%) + +--- + +## 사전 준비 + +### 0-1. 브랜치 확인 + +```bash +git checkout feat/semantic-sync +``` + +### 0-2. 플러그인 캐시 동기화 + +```bash +bash .claude-plugin/install.sh +``` + +### 0-3. 새 세션 시작 + +기존 세션을 종료하고 새 세션을 시작한다 (에이전트 디스커버리). + +### 0-4. 초기 mindmap 생성 + +새 세션에서 다음 대화로 시작: + +**유저**: +``` +Vync 프로젝트 로드맵을 정리하고 싶어. mindmap으로 만들어줘. + +구조: +- Vync 로드맵 (루트) + - 완료: 양방향 동기화, CLI + 플러그인, Electron 앱, Diff Pipeline + - 진행 중: Semantic Sync, Tab Bar 수정 + - 계획됨: MCP 서버, AI Agent, 변환 파이프라인 + - 미정: 다중 사용자, Document Package, 자동 업데이트 +``` + +**기대 행동**: 메인 세션이 `/vync create`를 실행 → translator가 mindmap 생성. + +**확인**: +- 브라우저에서 mindmap이 4개 카테고리, 13개 리프 노드로 렌더링되는지 확인 +- 파일 경로 확인: `.vync/roadmap.vync` (또는 translator가 선택한 이름) + +**생성된 파일 경로를 기록**: `__________.vync` (이후 라운드에서 사용) + +--- + +## Round 1: 단일 위계 변경 + +**난이도**: ★☆☆☆☆ | **기대 확신**: 높음 | **변경 타입**: moved × 1 + +### R1-처음: 대화 맥락 설정 + +**유저**: +``` +MCP 서버를 올해 안에 시작해야 할 것 같아. 지금 translator 경험이 한계가 있어서, +구조화된 Tool API가 있으면 시각화 품질이 확 올라갈 거야. +``` + +**메인 세션 기대 응답** (자연스러운 대화): +``` +MCP 서버가 들어가면 vync_describe_scene이나 vync_add_element 같은 +구조화된 도구를 쓸 수 있어서 확실히 편집 정밀도가 올라가겠네요. +우선순위를 올리는 게 맞을 것 같습니다. +``` + +### R1-브라우저 조작 + +브라우저에서: +1. "MCP 서버" 노드를 드래그하여 "계획됨" → "진행 중" 하위로 이동 + +### R1-중간: 트리거 + diff + translator 호출 + +**유저**: +``` +로드맵 업데이트했어, 확인해봐. +``` + +**메인 세션 내부 동작**: + +1단계 — diff 실행: +```bash +node "$VYNC_HOME/bin/vync.js" diff +``` + +기대 diff 출력: +``` +=== Vync Diff: roadmap.vync === + +현재 구조: + Vync 로드맵 + ├── 완료 + │ ├── 양방향 동기화 + │ ├── CLI + 플러그인 + │ ├── Electron 앱 + │ └── Diff Pipeline + ├── 진행 중 + │ ├── Semantic Sync + │ ├── Tab Bar 수정 + │ └── MCP 서버 + ├── 계획됨 + │ ├── AI Agent + │ └── 변환 파이프라인 + └── 미정 + ├── 다중 사용자 + ├── Document Package + └── 자동 업데이트 + +변경사항: + Moved: MCP 서버 — 계획됨 → 진행 중 + → 재분류: MCP 서버가 계획됨가 아닌 진행 중의 하위로 + +Snapshot updated. +``` + +**F1 체크포인트**: `→ 재분류:` 라인이 존재하는가? ☐ PASS / ☐ FAIL + +2단계 — translator 호출: +``` +Agent({ + description: "Vync read + translate diff", + subagent_type: "vync:vync-translator", + mode: "bypassPermissions", + prompt: `## 작업: Read +파일: + +## 대화 맥락 +Vync 프로젝트 로드맵을 mindmap으로 정리 중. 유저가 MCP 서버를 올해 안에 +시작해야 한다고 언급. translator 경험의 한계를 느끼고 있으며, 구조화된 Tool API가 +시각화 품질을 높일 것으로 기대. + +## 유저 피드백 (diff) +변경사항: + Moved: MCP 서버 — 계획됨 → 진행 중 + → 재분류: MCP 서버가 계획됨가 아닌 진행 중의 하위로 + +## 지시 +위 변경사항을 대화 맥락에 비춰 의미적으로 번역해줘.` +}) +``` + +기대 translator 반환: +``` +요약: MCP 서버가 계획됨에서 진행 중으로 이동됨 +의도: translator 기반 시각화의 한계를 인식하고, 구조화된 Tool API(MCP)로의 전환을 현재 작업 목록에 올린 것. 우선순위 상향. +확신: 높음 (단일 노드의 명확한 카테고리 이동, 대화에서 "올해 안에 시작" 명시) +제안: MCP 서버의 첫 단계로 Tool 설계(vync_read_file 등)를 세분화하면 작업 범위가 명확해질 것 +``` + +**F2 체크포인트**: 4-필드 구조인가? ☐ PASS / ☐ FAIL +**F3 체크포인트**: 확신 = 높음인가? ☐ PASS / ☐ FAIL +**F4 체크포인트**: "우선순위 상향" 의도를 포착했는가? ☐ PASS / ☐ FAIL + +### R1-마지막: 메인 세션 응답 + +확신 높음 → 의도를 대화에 자연스럽게 반영 + 제안 전달: + +**메인 세션 기대 응답**: +``` +MCP 서버를 진행 중으로 올리셨네요. 말씀하신 대로 Tool API가 있으면 +시각화 정밀도가 확 달라질 거예요. + +첫 단계로 Tool 설계(vync_read_file, vync_describe_scene 등)부터 +정리해볼까요? 로드맵에 세부 항목으로 추가할 수도 있어요. +``` + +**F5 체크포인트**: 제안이 자연스럽고 활용 가능한가? ☐ PASS / ☐ FAIL + +--- + +## Round 2: 추가 + 삭제 (동시) + +**난이도**: ★★☆☆☆ | **기대 확신**: 높음 | **변경 타입**: added × 2, removed × 1 + +### R2-처음: 대화 맥락 설정 + +**유저**: +``` +좋아, MCP 세부 작업을 로드맵에 넣자. Tool 설계랑 Transport 선택이 +첫 단계야. 그리고 변환 파이프라인은 MCP Tier 2 Tool로 흡수될 거라 +독립 항목으로는 필요 없어. +``` + +**메인 세션 기대 응답**: +``` +네, MCP 서버 하위에 세부 작업 추가하고, 변환 파이프라인은 독립 항목에서 +제거하는 게 맞겠네요. 브라우저에서 수정하시면 확인할게요. +``` + +### R2-브라우저 조작 + +브라우저에서: +1. "MCP 서버" 노드에 자식 추가: "Tool 설계" +2. "MCP 서버" 노드에 자식 추가: "Transport 선택" +3. "변환 파이프라인" 노드 삭제 + +### R2-중간: 트리거 + diff + translator 호출 + +**유저**: +``` +수정했어. +``` + +**메인 세션 내부 동작**: + +1단계 — diff 실행: +```bash +node "$VYNC_HOME/bin/vync.js" diff +``` + +기대 diff 출력 (핵심 부분): +``` +변경사항: + Added: Tool 설계 (under MCP 서버) + → 개념 추가: Tool 설계가 MCP 서버의 새 하위 요소로 + Added: Transport 선택 (under MCP 서버) + → 개념 추가: Transport 선택가 MCP 서버의 새 하위 요소로 + Removed: 변환 파이프라인 (was under 계획됨) + → 개념 제거: 변환 파이프라인가 계획됨에서 삭제됨 +``` + +**F1 체크포인트**: 3개 변경 모두 `→` hint 라인이 있는가? ☐ PASS / ☐ FAIL + +2단계 — translator 호출: +``` +Agent({ + description: "Vync read + translate diff", + subagent_type: "vync:vync-translator", + mode: "bypassPermissions", + prompt: `## 작업: Read +파일: + +## 대화 맥락 +Vync 로드맵 정리 중. 이전 라운드에서 MCP 서버를 진행 중으로 올림. +유저가 MCP 세부 작업으로 Tool 설계와 Transport 선택을 첫 단계로 지정. +변환 파이프라인은 MCP Tier 2 Tool로 흡수될 것이라 독립 항목 불필요하다고 판단. + +## 유저 피드백 (diff) +변경사항: + Added: Tool 설계 (under MCP 서버) + → 개념 추가: Tool 설계가 MCP 서버의 새 하위 요소로 + Added: Transport 선택 (under MCP 서버) + → 개념 추가: Transport 선택가 MCP 서버의 새 하위 요소로 + Removed: 변환 파이프라인 (was under 계획됨) + → 개념 제거: 변환 파이프라인가 계획됨에서 삭제됨 + +## 지시 +위 변경사항을 대화 맥락에 비춰 의미적으로 번역해줘.` +}) +``` + +기대 translator 반환: +``` +요약: MCP 서버에 Tool 설계, Transport 선택 추가. 변환 파이프라인 삭제. +의도: MCP 서버 작업을 구체적 단계로 세분화하면서, 변환 파이프라인을 MCP의 일부(Tier 2 Tool)로 흡수하여 독립 항목을 정리한 것. 계획의 구체화 + 중복 제거. +확신: 높음 (대화에서 명시적 언급, 변경이 대화 내용과 정확히 일치) +제안: Transport는 stdio(로컬)와 SSE(원격) 중 선택이 필요한데, Vync는 로컬 전용이니 stdio가 자연스러울 것 +``` + +**F2 체크포인트**: 4-필드 구조인가? ☐ PASS / ☐ FAIL +**F3 체크포인트**: 확신 = 높음인가? ☐ PASS / ☐ FAIL +**F4 체크포인트**: "세분화 + 흡수 정리" 의도를 포착했는가? ☐ PASS / ☐ FAIL + +### R2-마지막: 메인 세션 응답 + +확신 높음 → 의도를 반영하고 제안 전달: + +**메인 세션 기대 응답**: +``` +MCP 서버 아래 Tool 설계와 Transport 선택이 추가되고, 변환 파이프라인은 +정리됐네요. 말씀대로 MCP Tier 2에 포함될 거니까 독립 항목으로는 불필요하죠. + +Transport는 Vync가 로컬 전용이니 stdio가 자연스러울 것 같은데, +혹시 원격 접근도 고려하고 계신가요? +``` + +**F5 체크포인트**: 제안이 구체적이고 대화를 발전시키는가? ☐ PASS / ☐ FAIL + +--- + +## Round 3: 다중 이동 그룹화 (S-2 패턴) + +**난이도**: ★★★☆☆ | **기대 확신**: 중간 | **변경 타입**: moved × 3 (같은 목적지) + +### R3-처음: 대화 맥락 설정 + +**유저**: +``` +미정으로 둔 항목들을 다시 보니까, 사실 다 어느 정도 계획은 있는 거잖아. +다중 사용자도, Document Package도, 자동 업데이트도 언젠간 할 건데 +굳이 미정으로 분류할 필요가 있나 싶어. +``` + +**메인 세션 기대 응답**: +``` +맞아요, 시기의 차이일 뿐 방향은 정해진 항목들이죠. +미정에서 계획됨으로 옮기시면 될 것 같아요. +``` + +### R3-브라우저 조작 + +브라우저에서 (3개 노드를 순차적으로 드래그): +1. "다중 사용자" → "미정"에서 "계획됨" 하위로 이동 +2. "Document Package" → "미정"에서 "계획됨" 하위로 이동 +3. "자동 업데이트" → "미정"에서 "계획됨" 하위로 이동 + +### R3-중간: 트리거 + diff + translator 호출 + +**유저**: +``` +옮겼어. +``` + +**메인 세션 내부 동작**: + +1단계 — diff 실행: +```bash +node "$VYNC_HOME/bin/vync.js" diff +``` + +기대 diff 출력 (핵심 부분): +``` +변경사항: + Moved: 다중 사용자 — 미정 → 계획됨 + → 그룹화: [다중 사용자, Document Package, 자동 업데이트]가 계획됨 하위로 통합 + Moved: Document Package — 미정 → 계획됨 + → 그룹화: [다중 사용자, Document Package, 자동 업데이트]가 계획됨 하위로 통합 + Moved: 자동 업데이트 — 미정 → 계획됨 + → 그룹화: [다중 사용자, Document Package, 자동 업데이트]가 계획됨 하위로 통합 +``` + +**F1 체크포인트**: 3개 moved 모두 동일한 `그룹화` hint를 공유하는가? ☐ PASS / ☐ FAIL + +2단계 — translator 호출: +``` +Agent({ + description: "Vync read + translate diff", + subagent_type: "vync:vync-translator", + mode: "bypassPermissions", + prompt: `## 작업: Read +파일: + +## 대화 맥락 +Vync 로드맵 정리 중 (3라운드째). +R1: MCP 서버를 계획됨→진행 중으로 승격. +R2: MCP 하위에 Tool 설계/Transport 선택 추가, 변환 파이프라인 삭제. +현재: 유저가 미정 카테고리의 항목들(다중 사용자, Document Package, 자동 업데이트)이 +실제로는 계획이 있는 항목이라며, 미정/계획됨 구분이 불필요하다고 판단. + +## 유저 피드백 (diff) +변경사항: + Moved: 다중 사용자 — 미정 → 계획됨 + → 그룹화: [다중 사용자, Document Package, 자동 업데이트]가 계획됨 하위로 통합 + Moved: Document Package — 미정 → 계획됨 + → 그룹화: [다중 사용자, Document Package, 자동 업데이트]가 계획됨 하위로 통합 + Moved: 자동 업데이트 — 미정 → 계획됨 + → 그룹화: [다중 사용자, Document Package, 자동 업데이트]가 계획됨 하위로 통합 + +## 지시 +위 변경사항을 대화 맥락에 비춰 의미적으로 번역해줘.` +}) +``` + +기대 translator 반환: +``` +요약: 미정의 3개 항목(다중 사용자, Document Package, 자동 업데이트)이 모두 계획됨으로 이동 +의도: 미정/계획됨 카테고리 구분을 해소하는 정리. "할 건 할 건데 시기만 다를 뿐"이라는 판단으로, 불확실성 단계를 제거하고 모든 후속 작업을 동일 위상으로 격상 +확신: 중간 (3개 동시 이동이 일괄 정리 목적인 건 명확하나, 실제 우선순위 변경인지 단순 분류 정리인지는 불확실) +제안: 미정 카테고리가 비었는데, 제거하시겠어요? +``` + +**F2 체크포인트**: 4-필드 구조인가? ☐ PASS / ☐ FAIL +**F3 체크포인트**: 확신 = 중간인가? ☐ PASS / ☐ FAIL +**F4 체크포인트**: "카테고리 구분 해소" 의도를 포착했는가? ☐ PASS / ☐ FAIL + +### R3-마지막: 메인 세션 응답 + +확신 중간 → 추론 언급 + 확인 포함: + +**메인 세션 기대 응답**: +``` +미정에 있던 3개 항목을 모두 계획됨으로 옮기셨네요. +"할 건 정해진 건데 굳이 미정으로 둘 필요 없다"는 판단이시죠? + +미정 카테고리가 비었는데, 아예 제거할까요? +``` + +**F5 체크포인트**: 확인형 질문이 자연스러운가? ☐ PASS / ☐ FAIL + +--- + +## Round 4: 복합 변경 (moved + modified + added) + +**난이도**: ★★★★☆ | **기대 확신**: 중간 | **변경 타입**: moved × 1, modified × 1, added × 1 + +### R4-처음: 대화 맥락 설정 + +**유저**: +``` +AI Agent는 결국 MCP 위에서 돌아갈 거야. MCP 서버가 Tool을 제공하고, +AI Agent가 그 Tool을 조합하는 구조니까. +그리고 Semantic Sync는 거의 끝났으니 표시해두자. +E2E 검증을 다음 작업으로 추가해야 해. +``` + +**메인 세션 기대 응답**: +``` +AI Agent를 MCP 하위로 구조화하고, Semantic Sync 완료 표시, +E2E 검증 추가하면 되겠네요. 브라우저에서 수정해주세요. +``` + +### R4-브라우저 조작 + +브라우저에서 (3가지 타입의 변경): +1. "AI Agent"를 "계획됨" → "MCP 서버" 하위로 드래그 (moved) +2. "Semantic Sync" 텍스트를 더블클릭 → "Semantic Sync ✓"로 수정 (modified) +3. "진행 중" 아래에 새 자식 노드 "E2E 검증" 추가 (added) + +### R4-중간: 트리거 + diff + translator 호출 + +**유저**: +``` +다 수정했어, 3가지. +``` + +**메인 세션 내부 동작**: + +1단계 — diff 실행: +```bash +node "$VYNC_HOME/bin/vync.js" diff +``` + +기대 diff 출력 (핵심 부분): +``` +변경사항: + Moved: AI Agent — 계획됨 → MCP 서버 + → 재분류: AI Agent가 계획됨가 아닌 MCP 서버의 하위로 + Modified: "Semantic Sync" → "Semantic Sync ✓" + → 재정의: Semantic Sync → Semantic Sync ✓ + Added: E2E 검증 (under 진행 중) + → 개념 추가: E2E 검증가 진행 중의 새 하위 요소로 +``` + +**F1 체크포인트**: 3개 변경 모두 `→` hint 라인이 있는가? ☐ PASS / ☐ FAIL + +2단계 — translator 호출: +``` +Agent({ + description: "Vync read + translate diff", + subagent_type: "vync:vync-translator", + mode: "bypassPermissions", + prompt: `## 작업: Read +파일: + +## 대화 맥락 +Vync 로드맵 정리 중 (4라운드째). +R1: MCP 서버를 진행 중으로 승격. +R2: MCP 하위에 Tool 설계/Transport 선택 추가, 변환 파이프라인 삭제. +R3: 미정 항목 3개를 모두 계획됨으로 이동 (카테고리 구분 해소). +현재: 유저가 AI Agent는 MCP 위에서 돌아가는 구조라고 설명 (MCP=Tool 제공, Agent=Tool 조합). +Semantic Sync는 거의 완료. E2E 검증을 다음 작업으로 추가해야 한다고 언급. + +## 유저 피드백 (diff) +변경사항: + Moved: AI Agent — 계획됨 → MCP 서버 + → 재분류: AI Agent가 계획됨가 아닌 MCP 서버의 하위로 + Modified: "Semantic Sync" → "Semantic Sync ✓" + → 재정의: Semantic Sync → Semantic Sync ✓ + Added: E2E 검증 (under 진행 중) + → 개념 추가: E2E 검증가 진행 중의 새 하위 요소로 + +## 지시 +위 변경사항을 대화 맥락에 비춰 의미적으로 번역해줘.` +}) +``` + +기대 translator 반환: +``` +요약: AI Agent를 MCP 서버 하위로 이동, Semantic Sync에 완료 표시(✓), 진행 중에 E2E 검증 추가 +의도: 세 가지 독립적 정리가 동시에 이루어짐. (1) AI Agent를 MCP의 상위 계층이 아닌 하위 구성요소로 재정의 — MCP가 인프라, Agent가 그 위의 응용이라는 아키텍처 판단. (2) Semantic Sync 완료 상태 기록. (3) 다음 작업 항목 명시. +확신: 중간 (개별 변경은 명확하나, 세 변경의 통합 서사는 "MCP 중심 구조화 + 현재 상태 정리"로 해석 가능하나 확실하지 않음) +제안: MCP 서버 하위가 Tool 설계/Transport 선택/AI Agent로 3개가 되었는데, 이 순서가 실행 순서를 반영하는 건지요? +``` + +**F2 체크포인트**: 4-필드 구조인가? ☐ PASS / ☐ FAIL +**F3 체크포인트**: 확신 = 중간인가? ☐ PASS / ☐ FAIL +**F4 체크포인트**: "AI Agent = MCP 하위 구성요소" 관계를 포착했는가? ☐ PASS / ☐ FAIL +**F4 보너스**: "✓ = 완료 표시"로 해석했는가? ☐ PASS / ☐ FAIL + +### R4-마지막: 메인 세션 응답 + +확신 중간 → 추론 언급 + 확인: + +**메인 세션 기대 응답**: +``` +세 가지 정리가 됐네요. AI Agent를 MCP 하위로 넣으셨는데, +MCP가 Tool 인프라이고 Agent가 그 위의 응용 계층이라는 구조죠. + +MCP 서버 아래가 Tool 설계 → Transport 선택 → AI Agent 순서인데, +이게 실행 순서를 의미하는 건가요? +``` + +**F5 체크포인트**: 확인형 질문이 대화를 발전시키는가? ☐ PASS / ☐ FAIL + +--- + +## Round 5: 카테고리 재정의 (스트레스 테스트) + +**난이도**: ★★★★★ | **기대 확신**: 낮음~중간 | **변경 타입**: modified × 1, moved × 2, removed × 1 + +### R5-처음: 대화 맥락 설정 + +**유저**: +``` +지금 로드맵을 시간축(완료/진행중/계획)으로 나눴는데, 이게 맞나 싶어. +완료된 것도 사실 계속 유지보수해야 하잖아. 기능 영역별로 보는 게 나을 수도. +한번 바꿔볼게. +``` + +**메인 세션 기대 응답**: +``` +시간축에서 기능 영역 기반으로 전환하는 건 관점의 변화네요. +어떤 구조를 생각하고 계신지 궁금합니다. 수정해보세요. +``` + +### R5-브라우저 조작 + +브라우저에서 (4개 변경, 의도적으로 모호한 조합): +1. "완료" 텍스트를 더블클릭 → "기반 기술"로 수정 (modified — 카테고리 재정의) +2. "Diff Pipeline"을 "기반 기술" → "Semantic Sync ✓" 하위로 이동 (moved) +3. "Electron 앱"을 "기반 기술" → "자동 업데이트" 하위로 이동 (moved) +4. "미정" 노드 삭제 (removed — R3에서 이미 비어있음) + +### R5-중간: 트리거 + diff + translator 호출 + +**유저**: +``` +일단 좀 바꿔봤는데, 아직 확실하진 않아. +``` + +**메인 세션 내부 동작**: + +1단계 — diff 실행: +```bash +node "$VYNC_HOME/bin/vync.js" diff +``` + +기대 diff 출력 (핵심 부분): +``` +변경사항: + Modified: "완료" → "기반 기술" + → 재정의: 완료 → 기반 기술 + Moved: Diff Pipeline — 기반 기술 → Semantic Sync ✓ + → 재분류: Diff Pipeline가 기반 기술가 아닌 Semantic Sync ✓의 하위로 + Moved: Electron 앱 — 기반 기술 → 자동 업데이트 + → 재분류: Electron 앱가 기반 기술가 아닌 자동 업데이트의 하위로 + Removed: 미정 (was under Vync 로드맵) + → 개념 제거: 미정가 Vync 로드맵에서 삭제됨 +``` + +**F1 체크포인트**: 4개 변경 모두 `→` hint 라인이 있는가? ☐ PASS / ☐ FAIL + +2단계 — translator 호출: +``` +Agent({ + description: "Vync read + translate diff", + subagent_type: "vync:vync-translator", + mode: "bypassPermissions", + prompt: `## 작업: Read +파일: + +## 대화 맥락 +Vync 로드맵 정리 5라운드째. +R1: MCP 서버 진행 중 승격. R2: MCP 세부화 + 변환 파이프라인 삭제. +R3: 미정→계획됨 일괄 이동. R4: AI Agent→MCP 하위, Semantic Sync ✓, E2E 검증 추가. +현재: 유저가 시간축(완료/진행중/계획) 분류에 의문을 제기. "완료된 것도 유지보수해야 하니 +기능 영역별로 보는 게 나을 수도"라고 언급. 다만 "아직 확실하진 않다"고 덧붙임. + +## 유저 피드백 (diff) +변경사항: + Modified: "완료" → "기반 기술" + → 재정의: 완료 → 기반 기술 + Moved: Diff Pipeline — 기반 기술 → Semantic Sync ✓ + → 재분류: Diff Pipeline가 기반 기술가 아닌 Semantic Sync ✓의 하위로 + Moved: Electron 앱 — 기반 기술 → 자동 업데이트 + → 재분류: Electron 앱가 기반 기술가 아닌 자동 업데이트의 하위로 + Removed: 미정 (was under Vync 로드맵) + → 개념 제거: 미정가 Vync 로드맵에서 삭제됨 + +## 지시 +위 변경사항을 대화 맥락에 비춰 의미적으로 번역해줘.` +}) +``` + +기대 translator 반환 (이상적): +``` +요약: "완료"를 "기반 기술"로 재정의. Diff Pipeline을 Semantic Sync ✓ 하위로, Electron 앱을 자동 업데이트 하위로 이동. 미정 카테고리 삭제. +의도: 시간축 분류에서 기능 연관 기반 분류로 전환을 시도하는 것으로 보임. Diff Pipeline→Semantic Sync는 기술 계보 관계, Electron→자동 업데이트는 배포 플랫폼 연관을 표현한 것일 수 있음. 다만 유저 스스로 "확실하지 않다"고 했으므로 탐색적 재구조화. +확신: 낮음 (분류 체계 전환 시도이나, 이동 기준이 일관되지 않고 유저도 불확실성을 표현) +제안: 없음 +``` + +기대 translator 반환 (수용 가능한 최소): +``` +요약: 완료→기반 기술 재정의, Diff Pipeline/Electron 앱 이동, 미정 삭제 +의도: 분류 체계를 시간축에서 기능 관계 기반으로 변경하려는 시도 +확신: 낮음 (유저가 "확실하지 않다"고 명시) +``` + +**F2 체크포인트**: 4-필드 구조인가? ☐ PASS / ☐ FAIL +**F3 체크포인트**: 확신 = 낮음 또는 중간인가? ☐ PASS / ☐ FAIL +**F4 체크포인트**: "분류 체계 전환 시도"를 포착했는가? ☐ PASS / ☐ FAIL + +### R5-마지막: 메인 세션 응답 + +확신 낮음 → 요약(사실)만 간략히, 의도 추론 하지 않음: + +**메인 세션 기대 응답**: +``` +구조에 몇 가지 변화가 있네요. +"완료"를 "기반 기술"로 바꾸시고, 일부 항목을 재배치하셨어요. +``` + +**F5 체크포인트**: 사실 위주의 간략한 응답인가? (S-3 준수) ☐ PASS / ☐ FAIL + +--- + +## 최종 결과 기록 + +### 라운드별 채점표 + +| | F1 Hint | F2 포맷 | F3 확신 | F4 의도 | F5 제안 | 소계 | +|------|---------|---------|---------|---------|---------|------| +| R1 | ☐ | ☐ | ☐ | ☐ | ☐ | /5 | +| R2 | ☐ | ☐ | ☐ | ☐ | ☐ | /5 | +| R3 | ☐ | ☐ | ☐ | ☐ | ☐ | /5 | +| R4 | ☐ | ☐ | ☐ | ☐ | ☐ | /5 | +| R5 | ☐ | ☐ | ☐ | ☐ | ☐ | /5 | +| **합계** | | | | | | **/25** | + +### 판정 + +- **PASS**: 20/25 이상 (80%) +- **CONDITIONAL**: 15~19/25 (60~79%) — 특정 패턴에서 개선 필요 +- **FAIL**: 14/25 이하 (< 60%) — 근본적 재설계 필요 + +### 관찰 메모 + +``` +R1: +R2: +R3: +R4: +R5: +종합: +``` + +--- + +## 라운드별 누적 구조 변화 + +참고용. 각 라운드 후 기대되는 mindmap 구조. + +### 초기 (R0) +``` +Vync 로드맵 +├── 완료: 양방향 동기화, CLI + 플러그인, Electron 앱, Diff Pipeline +├── 진행 중: Semantic Sync, Tab Bar 수정 +├── 계획됨: MCP 서버, AI Agent, 변환 파이프라인 +└── 미정: 다중 사용자, Document Package, 자동 업데이트 +``` + +### R1 후 +``` +Vync 로드맵 +├── 완료: 양방향 동기화, CLI + 플러그인, Electron 앱, Diff Pipeline +├── 진행 중: Semantic Sync, Tab Bar 수정, MCP 서버 ← moved +├── 계획됨: AI Agent, 변환 파이프라인 +└── 미정: 다중 사용자, Document Package, 자동 업데이트 +``` + +### R2 후 +``` +Vync 로드맵 +├── 완료: 양방향 동기화, CLI + 플러그인, Electron 앱, Diff Pipeline +├── 진행 중: Semantic Sync, Tab Bar 수정, MCP 서버 > [Tool 설계, Transport 선택] ← added +├── 계획됨: AI Agent (변환 파이프라인 deleted) +└── 미정: 다중 사용자, Document Package, 자동 업데이트 +``` + +### R3 후 +``` +Vync 로드맵 +├── 완료: 양방향 동기화, CLI + 플러그인, Electron 앱, Diff Pipeline +├── 진행 중: Semantic Sync, Tab Bar 수정, MCP 서버 > [Tool 설계, Transport 선택] +├── 계획됨: AI Agent, 다중 사용자, Document Package, 자동 업데이트 ← moved ×3 +└── 미정: (empty) +``` + +### R4 후 +``` +Vync 로드맵 +├── 완료: 양방향 동기화, CLI + 플러그인, Electron 앱, Diff Pipeline +├── 진행 중: Semantic Sync ✓, Tab Bar 수정, MCP 서버 > [Tool 설계, Transport 선택, AI Agent], E2E 검증 +├── 계획됨: 다중 사용자, Document Package, 자동 업데이트 +└── 미정: (empty) +``` + +### R5 후 +``` +Vync 로드맵 +├── 기반 기술: 양방향 동기화, CLI + 플러그인 (2개 잔류) +├── 진행 중: Semantic Sync ✓ > [Diff Pipeline], Tab Bar 수정, MCP 서버 > [...], E2E 검증 +├── 계획됨: 다중 사용자, Document Package, 자동 업데이트 > [Electron 앱] +(미정 삭제됨) +``` diff --git a/docs/plans/2026-03-13-semantic-sync.md b/docs/plans/2026-03-13-semantic-sync.md index a0b1bd3..51882dd 100644 --- a/docs/plans/2026-03-13-semantic-sync.md +++ b/docs/plans/2026-03-13-semantic-sync.md @@ -2,13 +2,22 @@ ## Context -Vync diff 파이프라인이 구조적 변경("Moved: B — root → A")은 감지하지만, 사용자의 의미적 의도("B는 A에 종속되는 개념")를 안정적으로 추론하지 못함. D-017 설계 문서(`docs/plan/2026-03-13-semantic-sync-design.md`)에서 해석 레벨 체계, 시각화 유형별 규칙, 구조화된 반환 포맷, 모호성 처리 전략을 정의함. 이 계획은 해당 설계를 구현. +Vync diff 파이프라인이 구조적 변경("Moved: B — root → A")은 감지하지만, 사용자의 의미적 의도("B는 A에 종속되는 개념")를 안정적으로 추론하지 못함. D-017 설계 문서(`docs/plans/2026-03-13-semantic-sync-design.md`)에서 해석 레벨 체계, 시각화 유형별 규칙, 구조화된 반환 포맷, 모호성 처리 전략을 정의함. 이 계획은 해당 설계를 구현. **결정 사항:** - 해석 주체: Sub-agent 중심 유지 (D-013) - 반환 포맷: Read만 4-필드 구조화 (요약/의도/확신/제안), create/update 현행 유지 - 모호성: 추론 + 자연스러운 확인 +**구현 세부 결정 (2026-03-13 확정):** + +| # | 항목 | 결정 | 근거 | +|---|------|------|------| +| S-1 | 시각화 유형 감지 단위 | **파일 레벨** | 혼합 캔버스 자체를 방지 (→ D-018). generic 폴백으로 안전. | +| S-2 | 복합 패턴 범위 (Phase 1) | **다중 moved(그룹화)만** | 가장 빈번하고 의미 명확. moved+modified 결합은 관찰 후 결정. | +| S-3 | 확신 낮음 시 메인 세션 행동 | **간략히 언급** | "구조에 약간 변화가 있네요" — 무시보다 안전, noise 최소. | +| S-4 | semanticHint 생성 위치 | **computeDiff와 분리** | enrichWithSemanticHints() 별도 함수. 기존 테스트 무변경, semantic 테스트 독립. | + --- ## Phase 1: diff.ts semantic annotation (~50 LOC) @@ -26,18 +35,33 @@ interface DiffChange { } ``` -### 1-2. 시각화 유형 감지 함수 +### 1-2. 시각화 유형 감지 함수 (S-1: 파일 레벨) **File**: `tools/cli/diff.ts` (신규 함수) -elements의 root 노드 구조로 유형 판단: +elements의 root 노드 구조로 **파일 전체** 유형 판단: - `type === 'mindmap'` → mindmap - `type === 'geometry'` + arrow-line 존재 → flowchart -- 기타 → generic +- 기타 → generic (hint 생략) + +> **S-1 근거**: 혼합 캔버스는 D-018에 의해 구조적으로 방지됨. +> 새로운 시각화 유형이 필요하면 별도 파일을 생성하므로, 파일 레벨 감지로 충분. -### 1-3. semanticHint 생성 규칙 -**File**: `tools/cli/diff.ts` (computeDiff 내부 또는 별도 함수) +### 1-3. enrichWithSemanticHints 함수 (S-4: computeDiff와 분리) +**File**: `tools/cli/diff.ts` (신규 함수, computeDiff 외부) -Mindmap 규칙: +```typescript +function enrichWithSemanticHints( + changes: DiffChange[], + vizType: 'mindmap' | 'flowchart' | 'generic', + currentMap: Map, + snapshotMap: Map +): DiffChange[] +``` + +> **S-4 근거**: computeDiff는 순수 구조적 diff만 담당. semantic layer를 분리하여 +> 기존 테스트 무변경, semantic 테스트 독립 작성 가능. + +**개별 hint 생성 규칙** (Mindmap): - `moved` to child: `"위계 변경: {text}가 {toParent}의 하위 개념으로 재분류됨"` - `moved` to root: `"독립화: {text}가 {fromParent}에서 분리되어 독립 개념으로"` - `moved` sibling transfer: `"재분류: {text}가 {fromParent}가 아닌 {toParent}의 하위로"` @@ -45,10 +69,31 @@ Mindmap 규칙: - `removed`: `"개념 제거: {text}가 {parent}에서 삭제됨"` - `modified`: `"재정의: {oldText} → {newText}"` -복합 패턴 (computeDiff 후처리): +**복합 패턴** (S-2: 다중 moved만): - 여러 노드가 같은 parent로 moved → `"그룹화: [{texts}]가 {parent} 하위로 통합"` + - 개별 moved hint를 그룹화 hint로 교체 + +> **S-2 근거**: moved+modified 결합("재해석")은 Phase 1에서 제외. +> 실사용 데이터를 관찰한 후 필요시 추가. 현재는 각각 개별 hint로 표시. + +**Flowchart/Generic**: Phase 1에서는 generic과 동일하게 hint 생략. 필요시 후속 확장. -### 1-4. formatDiffResult에 hint 포함 +### 1-4. vyncDiff에서 enrichment 호출 +**File**: `tools/cli/diff.ts` (vyncDiff 함수 내) + +```typescript +// 기존 +let changes = computeDiff(currentElements, snapshotElements); + +// 추가 +const vizType = detectVizType(currentElements); +changes = enrichWithSemanticHints(changes, vizType, currentMap, snapshotMap); +``` + +> Note: currentMap/snapshotMap을 computeDiff 외부에서 접근해야 하므로, +> flattenElements 호출을 vyncDiff로 올리거나 computeDiff에서 map을 반환하는 구조 필요. + +### 1-5. formatDiffResult에 hint 포함 **File**: `tools/cli/diff.ts:262-291` 변경사항 출력에 semanticHint 추가: @@ -58,13 +103,17 @@ Mindmap 규칙: → 위계 변경: 리서치가 기획의 하위 개념으로 재분류됨 ``` -### 1-5. 테스트 추가 +### 1-6. 테스트 추가 **File**: `tools/cli/__tests__/diff.test.ts` +기존 `computeDiff` 테스트: **변경 없음** (S-4) + +신규 `enrichWithSemanticHints` 테스트: - 시각화 유형 감지 테스트 (mindmap/flowchart/generic) -- semanticHint 생성 테스트 (moved/added/removed/modified) -- 복합 패턴 테스트 (다중 노드 그룹화) -- formatDiffResult hint 포함 테스트 +- 개별 semanticHint 생성 (moved/added/removed/modified) +- 복합 패턴: 다중 moved 그룹화 (S-2) +- generic 유형: hint 생략 확인 +- formatDiffResult hint 포함 출력 --- @@ -113,7 +162,7 @@ Mindmap 규칙: ## Phase 3: 메인 세션 활용 패턴 (~15 LOC) -### 3-1. commands/vync.md Read 절차 확장 +### 3-1. commands/vync.md Read 절차 확장 (S-3: 확신 낮음 간략 언급) **File**: `commands/vync.md:59-75` Read 절차의 step 5 확장: @@ -123,10 +172,13 @@ Read 절차의 step 5 확장: 예: "기획의 세부 활동으로 리서치를 보시는 것 같은데, 개발 쪽도 정리할까요?" - **확신 중간**: 추론 언급 + 확인 포함 예: "A-B, C-D로 묶으셨네요. 이 구분으로 진행할까요?" - - **확신 낮음**: 요약만 전달, 의도 추론 언급 안 함 - 예: "구조에 큰 변화는 없네요." + - **확신 낮음**: 요약(사실)만 간략히 언급, 의도 추론은 하지 않음 + 예: "구조에 약간 변화가 있네요." ``` +> **S-3 근거**: 변경을 완전히 무시하면 사용자의 의도적 변경을 놓칠 위험. +> 간략히 언급하면 사용자가 필요시 이어서 말할 수 있고, 불필요하면 무시 가능. + --- ## Verification @@ -156,13 +208,15 @@ bash .claude-plugin/install.sh | File | Change | |------|--------| -| `tools/cli/diff.ts` | semanticHint 필드, 유형 감지, hint 생성 규칙, 포맷 | -| `tools/cli/__tests__/diff.test.ts` | semantic hint 테스트 추가 | -| `agents/vync-translator.md` | Read 반환 포맷 구조화, 해석 가이드 | -| `commands/vync.md` | Read 절차에 확신 레벨별 활용 가이드 | +| `tools/cli/diff.ts` | semanticHint 필드, detectVizType, enrichWithSemanticHints (신규), formatDiffResult 확장 | +| `tools/cli/__tests__/diff.test.ts` | enrichWithSemanticHints 테스트 추가 (기존 computeDiff 테스트 무변경) | +| `agents/vync-translator.md` | Read 반환 포맷 구조화, 해석 가이드, 한 파일=한 유형 원칙 (D-018) | +| `commands/vync.md` | Read 절차에 확신 레벨별 활용 가이드 (S-3 반영) | ## Reusable Functions - `flattenElements()` — `tools/cli/diff.ts:67` (기존, 재활용) - `findParentText()` — `tools/cli/diff.ts:124` (기존, 재활용) -- `computeDiff()` — `tools/cli/diff.ts:135` (확장) -- `formatDiffResult()` — `tools/cli/diff.ts:262` (확장) +- `computeDiff()` — `tools/cli/diff.ts:135` (**변경 없음**, S-4) +- `detectVizType()` — 신규, 파일 레벨 유형 감지 (S-1) +- `enrichWithSemanticHints()` — 신규, semantic layer 분리 (S-4) +- `formatDiffResult()` — `tools/cli/diff.ts:262` (확장, hint 포함) diff --git a/tools/cli/__tests__/diff.test.ts b/tools/cli/__tests__/diff.test.ts index 52e6b32..49bd8a1 100644 --- a/tools/cli/__tests__/diff.test.ts +++ b/tools/cli/__tests__/diff.test.ts @@ -2,7 +2,13 @@ import { describe, it, expect, afterEach, beforeEach } from 'vitest'; import fs from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; -import { computeDiff, vyncDiff, formatDiffResult } from '../diff.js'; +import { + computeDiff, + vyncDiff, + formatDiffResult, + detectVizType, + enrichWithSemanticHints, +} from '../diff.js'; // --- Helper: build mindmap element --- @@ -354,3 +360,328 @@ describe('formatDiffResult', () => { expect(output).not.toContain('Snapshot updated.'); }); }); + +// --- detectVizType tests --- + +describe('detectVizType', () => { + it('returns mindmap for mindmap root element', () => { + const elements = [ + mindmapEl('root', 'Project', [childEl('a', 'Design')], { + isRoot: true, + }), + ]; + expect(detectVizType(elements)).toBe('mindmap'); + }); + + it('returns flowchart for geometry with arrow-line', () => { + const elements = [ + { + id: 'g1', + type: 'geometry', + shape: 'rectangle', + text: { children: [{ text: 'Start' }] }, + children: [], + }, + { + id: 'a1', + type: 'arrow-line', + children: [], + }, + ]; + expect(detectVizType(elements)).toBe('flowchart'); + }); + + it('returns generic for geometry without arrow-line', () => { + const elements = [ + { + id: 'g1', + type: 'geometry', + shape: 'rectangle', + text: { children: [{ text: 'Box' }] }, + children: [], + }, + ]; + expect(detectVizType(elements)).toBe('generic'); + }); + + it('returns generic for empty elements', () => { + expect(detectVizType([])).toBe('generic'); + }); + + it('returns generic for unknown type', () => { + const elements = [{ id: 'x', type: 'custom', children: [] }]; + expect(detectVizType(elements)).toBe('generic'); + }); +}); + +// --- enrichWithSemanticHints tests --- + +describe('enrichWithSemanticHints', () => { + it('adds hint for moved node (to child — 위계 변경)', () => { + // "리서치" was under root, now under "기획" + const snapshot = [ + mindmapEl( + 'root', + '프로젝트', + [childEl('a', '기획'), childEl('b', '리서치')], + { isRoot: true } + ), + ]; + const current = [ + mindmapEl( + 'root', + '프로젝트', + [childEl('a', '기획', [childEl('b', '리서치')])], + { isRoot: true } + ), + ]; + const changes = computeDiff(current, snapshot); + const enriched = enrichWithSemanticHints( + changes, + 'mindmap', + current, + snapshot + ); + + expect(enriched).toHaveLength(1); + expect(enriched[0].kind).toBe('moved'); + expect(enriched[0].semanticHint).toContain('재분류'); + expect(enriched[0].semanticHint).toContain('리서치'); + expect(enriched[0].semanticHint).toContain('기획'); + }); + + it('adds hint for moved node to root (독립화)', () => { + const snapshot = [ + mindmapEl( + 'root', + '프로젝트', + [childEl('a', '기획', [childEl('b', '리서치')])], + { isRoot: true } + ), + ]; + const current = [ + mindmapEl('root', '프로젝트', [childEl('a', '기획')], { isRoot: true }), + { + id: 'b', + type: 'mindmap', + data: { topic: { children: [{ text: '리서치' }] } }, + children: [], + }, + ]; + const changes = computeDiff(current, snapshot); + const enriched = enrichWithSemanticHints( + changes, + 'mindmap', + current, + snapshot + ); + + const moved = enriched.find((c) => c.kind === 'moved'); + expect(moved).toBeDefined(); + expect(moved!.semanticHint).toContain('독립화'); + expect(moved!.semanticHint).toContain('리서치'); + }); + + it('adds hint for added node', () => { + const snapshot = [ + mindmapEl('root', '프로젝트', [childEl('a', '기획')], { isRoot: true }), + ]; + const current = [ + mindmapEl( + 'root', + '프로젝트', + [childEl('a', '기획'), childEl('b', '개발')], + { isRoot: true } + ), + ]; + const changes = computeDiff(current, snapshot); + const enriched = enrichWithSemanticHints( + changes, + 'mindmap', + current, + snapshot + ); + + expect(enriched).toHaveLength(1); + expect(enriched[0].semanticHint).toContain('개념 추가'); + expect(enriched[0].semanticHint).toContain('개발'); + expect(enriched[0].semanticHint).toContain('프로젝트'); + }); + + it('adds hint for removed node', () => { + const snapshot = [ + mindmapEl( + 'root', + '프로젝트', + [childEl('a', '기획'), childEl('b', '개발')], + { isRoot: true } + ), + ]; + const current = [ + mindmapEl('root', '프로젝트', [childEl('a', '기획')], { isRoot: true }), + ]; + const changes = computeDiff(current, snapshot); + const enriched = enrichWithSemanticHints( + changes, + 'mindmap', + current, + snapshot + ); + + expect(enriched).toHaveLength(1); + expect(enriched[0].semanticHint).toContain('개념 제거'); + expect(enriched[0].semanticHint).toContain('개발'); + }); + + it('adds hint for modified text', () => { + const snapshot = [ + mindmapEl('root', '프로젝트', [childEl('a', '설계')], { isRoot: true }), + ]; + const current = [ + mindmapEl('root', '프로젝트', [childEl('a', '아키텍처')], { + isRoot: true, + }), + ]; + const changes = computeDiff(current, snapshot); + const enriched = enrichWithSemanticHints( + changes, + 'mindmap', + current, + snapshot + ); + + expect(enriched).toHaveLength(1); + expect(enriched[0].semanticHint).toContain('재정의'); + expect(enriched[0].semanticHint).toContain('설계'); + expect(enriched[0].semanticHint).toContain('아키텍처'); + }); + + it('detects multi-moved grouping (S-2)', () => { + // Move "인터뷰" and "설문" under "리서치" + const snapshot = [ + mindmapEl( + 'root', + '프로젝트', + [childEl('a', '리서치'), childEl('b', '인터뷰'), childEl('c', '설문')], + { isRoot: true } + ), + ]; + const current = [ + mindmapEl( + 'root', + '프로젝트', + [ + childEl('a', '리서치', [ + childEl('b', '인터뷰'), + childEl('c', '설문'), + ]), + ], + { isRoot: true } + ), + ]; + const changes = computeDiff(current, snapshot); + const enriched = enrichWithSemanticHints( + changes, + 'mindmap', + current, + snapshot + ); + + // Both should have the same grouping hint + expect(enriched).toHaveLength(2); + expect(enriched[0].semanticHint).toContain('그룹화'); + expect(enriched[0].semanticHint).toContain('인터뷰'); + expect(enriched[0].semanticHint).toContain('설문'); + expect(enriched[0].semanticHint).toContain('리서치'); + expect(enriched[0].semanticHint).toBe(enriched[1].semanticHint); + }); + + it('skips hints for generic viz type', () => { + const snapshot = [ + mindmapEl('root', 'Project', [childEl('a', 'A')], { isRoot: true }), + ]; + const current = [ + mindmapEl('root', 'Project', [childEl('a', 'A'), childEl('b', 'B')], { + isRoot: true, + }), + ]; + const changes = computeDiff(current, snapshot); + const enriched = enrichWithSemanticHints( + changes, + 'generic', + current, + snapshot + ); + + expect(enriched).toHaveLength(1); + expect(enriched[0].semanticHint).toBeUndefined(); + }); + + it('skips hints for flowchart viz type', () => { + const snapshot = [ + mindmapEl('root', 'Project', [childEl('a', 'A')], { isRoot: true }), + ]; + const current = [ + mindmapEl('root', 'Project', [childEl('a', 'A'), childEl('b', 'B')], { + isRoot: true, + }), + ]; + const changes = computeDiff(current, snapshot); + const enriched = enrichWithSemanticHints( + changes, + 'flowchart', + current, + snapshot + ); + + expect(enriched).toHaveLength(1); + expect(enriched[0].semanticHint).toBeUndefined(); + }); +}); + +// --- formatDiffResult with semantic hints --- + +describe('formatDiffResult with semanticHint', () => { + it('includes hint line after change detail', () => { + const output = formatDiffResult({ + filePath: '/path/to/plan.vync', + tree: ' 프로젝트\n └── 기획\n └── 리서치', + changes: [ + { + kind: 'moved', + id: 'b', + text: '리서치', + detail: 'Moved: 리서치 — 프로젝트 → 기획', + semanticHint: '위계 변경: 리서치가 기획의 하위 개념으로 재분류됨', + }, + ], + hasChanges: true, + snapshotUpdated: true, + }); + + expect(output).toContain('Moved: 리서치 — 프로젝트 → 기획'); + expect(output).toContain( + '→ 위계 변경: 리서치가 기획의 하위 개념으로 재분류됨' + ); + }); + + it('omits hint line when no semanticHint', () => { + const output = formatDiffResult({ + filePath: '/path/to/plan.vync', + tree: ' Project', + changes: [ + { + kind: 'added', + id: 'b', + text: 'Dev', + detail: 'Added: Dev (under Project)', + }, + ], + hasChanges: true, + snapshotUpdated: true, + }); + + expect(output).toContain('Added: Dev (under Project)'); + expect(output).not.toContain('→'); + }); +}); diff --git a/tools/cli/diff.ts b/tools/cli/diff.ts index 0f5395c..421d145 100644 --- a/tools/cli/diff.ts +++ b/tools/cli/diff.ts @@ -17,8 +17,11 @@ interface DiffChange { id: string; text: string; detail: string; + semanticHint?: string; } +type VizType = 'mindmap' | 'flowchart' | 'generic'; + export interface DiffResult { filePath: string; tree: string; @@ -201,6 +204,107 @@ export function computeDiff( return changes; } +// --- Visualization type detection (S-1: file-level) --- + +export function detectVizType(elements: Record[]): VizType { + if (elements.length === 0) return 'generic'; + + const rootType = elements[0].type as string | undefined; + if (rootType === 'mindmap') return 'mindmap'; + + // Geometry with arrow-lines → flowchart + if (rootType === 'geometry') { + const hasArrow = elements.some( + (el) => (el.type as string) === 'arrow-line' + ); + return hasArrow ? 'flowchart' : 'generic'; + } + + return 'generic'; +} + +// --- Semantic hint enrichment (S-4: separated from computeDiff) --- + +export function enrichWithSemanticHints( + changes: DiffChange[], + vizType: VizType, + currentElements: Record[], + snapshotElements: Record[] +): DiffChange[] { + if (vizType === 'generic' || vizType === 'flowchart') return changes; + + // Build maps for parent lookup + const currentMap = new Map(); + const snapshotMap = new Map(); + flattenElements(currentElements, null, currentMap); + flattenElements(snapshotElements, null, snapshotMap); + + // S-2: Detect multi-moved grouping pattern first + const movedChanges = changes.filter((c) => c.kind === 'moved'); + const groupedByTarget = new Map(); + for (const mc of movedChanges) { + const node = currentMap.get(mc.id); + const parentKey = node?.parentId ?? 'root'; + const existing = groupedByTarget.get(parentKey) || []; + existing.push(mc); + groupedByTarget.set(parentKey, existing); + } + + // Find groups of 2+ moved to same parent + const groupedIds = new Set(); + const groupHints = new Map(); // changeId → group hint + for (const [parentKey, group] of groupedByTarget) { + if (group.length >= 2) { + const toParentText = findParentText( + currentMap, + parentKey === 'root' ? null : parentKey + ); + const texts = group.map((c) => c.text).join(', '); + const hint = `그룹화: [${texts}]가 ${toParentText} 하위로 통합`; + for (const c of group) { + groupedIds.add(c.id); + groupHints.set(c.id, hint); + } + } + } + + return changes.map((change) => { + let semanticHint: string | undefined; + + if (groupedIds.has(change.id)) { + semanticHint = groupHints.get(change.id); + } else if (change.kind === 'moved') { + const snapshot = snapshotMap.get(change.id); + const current = currentMap.get(change.id); + if (snapshot && current) { + const fromParent = findParentText(snapshotMap, snapshot.parentId); + const toParent = findParentText(currentMap, current.parentId); + if (!current.parentId) { + semanticHint = `독립화: ${change.text}가 ${fromParent}에서 분리되어 독립 개념으로`; + } else if (!snapshot.parentId) { + semanticHint = `위계 변경: ${change.text}가 ${toParent}의 하위 개념으로 재분류됨`; + } else { + semanticHint = `재분류: ${change.text}가 ${fromParent}가 아닌 ${toParent}의 하위로`; + } + } + } else if (change.kind === 'added') { + const node = currentMap.get(change.id); + const parentText = findParentText(currentMap, node?.parentId ?? null); + semanticHint = `개념 추가: ${change.text}가 ${parentText}의 새 하위 요소로`; + } else if (change.kind === 'removed') { + const node = snapshotMap.get(change.id); + const parentText = findParentText(snapshotMap, node?.parentId ?? null); + semanticHint = `개념 제거: ${change.text}가 ${parentText}에서 삭제됨`; + } else if (change.kind === 'modified') { + // detail already has "Modified: "old" → "new"" + const snapshot = snapshotMap.get(change.id); + semanticHint = `재정의: ${snapshot?.text ?? '?'} → ${change.text}`; + } + + return semanticHint ? { ...change, semanticHint } : change; + }); +} + // --- CLI entry point --- export async function vyncDiff( @@ -241,6 +345,13 @@ export async function vyncDiff( let changes: DiffChange[] = []; if (snapshotExists) { changes = computeDiff(currentElements, snapshotElements); + const vizType = detectVizType(currentElements); + changes = enrichWithSemanticHints( + changes, + vizType, + currentElements, + snapshotElements + ); } // Update snapshot (unless --no-snapshot) @@ -277,6 +388,9 @@ export function formatDiffResult(result: DiffResult): string { lines.push('변경사항:'); for (const change of result.changes) { lines.push(` ${change.detail}`); + if (change.semanticHint) { + lines.push(` → ${change.semanticHint}`); + } } } else { lines.push('변경사항: 없음');