- strategy.md §2.3 (오늘 회상 surface) / §4.3 (F4 측정 인프라) / §8 (banner stack) 갱신 - typecheck 0 / 단위 403 / e2e 1 - v0.2.3 7/7 — 모든 cut 완료. 다음: v0.2.3 binary 빌드 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21 KiB
v0.2.2 Dogfood 피드백 로드맵 (#7→#6 → v0.2.3) 설계
작성일: 2026-05-01
저자: 김태현 (dlsrks0734@gmail.com)
문서 성격: v0.2.2 dogfood 중 발견된 7개 항목 (memory/project_v022_feedback.md #1~#6 + 본 brainstorm 에서 추가된 #7 telemetry) 의 순차 작업 로드맵. 본 문서는 순서·범위·게이트 만 정의하며, 각 항목 내부 설계는 항목별 mini-brainstorm + writing-plans 에서 결정.
선행 문서:
memory/project_v022_feedback.md(raw 피드백 6건)docs/superpowers/specs/2026-04-26-feedback-roadmap-design.md(v0.2.1 로드맵, 본 문서의 패턴 원형)docs/superpowers/specs/2026-04-24-inkling-vertical-slice-design.md(slice §1.1 invariant 4 — 본문 미기록)docs/superpowers/strategy/strategy.md(#6 에서 §2.3·§4.3·§8 동반 갱신 대상)
1. 결정 요약
| 결정 | 값 | 근거 |
|---|---|---|
| Cut 패턴 | 단일 cut v0.2.3 (7항목 한 묶음) | v0.2.1 패턴 반복. 항목 간 결합도 (특히 #7 → #4~#6 emit) 분리 시 회전 비용. |
| 우선순위 기준 | 데이터 안전 우선 (v0.2.1 패턴) | 측정 인프라 (#7) → schema migration v3 (#4) → 안전망 위에서 기능 진행. |
| 첫 항목 | #7 Telemetry skeleton | 다른 6 항목이 emit hook 만 추가. 측정 없는 기능 출시는 다음 cut 까지 1주 본인 라벨링으로 후퇴. |
| 항목당 게이트 | 머지 + 테스트 통과 (typecheck + 205+ 단위 + e2e smoke) | v0.2.2 기준선. |
| 다음 빌드 | v0.2.3 (7항목 모두 머지 후 단일 cut) | slice §7 strict-pin patch 증분. |
| 신규 dependency | 0 목표 | 모두 stdlib + 기존 better-sqlite3 / electron 으로 충분. |
| Schema 변경 | migration v3 — 3 컬럼 한 번에: deleted_at TEXT NULL, last_recalled_at TEXT NULL, recall_dismissed_at TEXT NULL |
#4 휴지통 + #6 회상 메타 한 묶음. 별 migration 두 번 회피. |
| Trash 와 export/backup | B 정책 — F6-L1 backup 포함 (byte-for-byte), F5 export 제외, F6-L3 import 시 deleted_at IS NOT NULL 우선 (삭제 보존) |
백업은 회복 용도, export 는 외부 노출 형식. |
| Decision-pending 처리 | 항목별 mini-brainstorm | 본 문서는 순서·In/Out 만, 항목 내부는 per-item. |
2. 순차 작업 순서
v0.2.2 ────────[ dogfood 동결, 병렬 진행 ]────────
│
개발 트랙 (main 직접 머지 또는 PR): │
① #7 Telemetry skeleton [작음, 인프라 1번] │
② #4 휴지통 + migration v3 [중, schema + 정책] │
③ #5 만료 추천 [작음, #4 destination]│
④ #1 Ollama 회복 polling [작음, 독립] │
⑤ #2 AI retry / 수동 trigger [중, AiWorker 정책] │
⑥ #3 태그 vocab 주입 [작음, 독립] │
⑦ #6 리마인드 1 spike [중, strategy 갱신] │
│
┌──────────┘
▼
v0.2.3 cut (단일)
│
▼
dogfood 재설치 + ≥ 1주 soak
│
▼
telemetry export → 분석 →
v0.2.4 brainstorm
2.1 순서 결정 근거
- #7 (1번) — 측정 인프라. 다른 항목이 emit hook 추가만 하도록 skeleton 먼저. Cross-cutting privacy invariant 강제도 1번에서 단위 테스트로 고정.
- #4 (2번) — schema migration v3 가 #6 회상 메타 컬럼 동반. 휴지통 invariant (
deleted_at IS NULL모든 active 쿼리) 가 다른 항목에 영향. 회복 안전망 (pre-v3 snapshot, v0.2.1 메커니즘) 위에서 진행. - #5 (3번) — #4 휴지통 destination 직접 소비. 같은 영역 (Inbox 상단 배너).
- #1 (4번) — #2 의 reliable health 의존성. polling 인프라 먼저.
- #2 (5번) — #1 health 위에서 retry/manual trigger 정책 변경. AiWorker 의 unreachable infinite retry 로 #1 polling 결과 활용.
- #3 (6번) — 독립 prompt 변경. PROMPT_VERSION 4. AI 영역 마지막에 묶어서 AiWorker 회귀 위험 격리.
- #6 (7번) — strategy.md 갱신 + RecallBanner 1 spike. last_recalled_at / recall_dismissed_at 사용. 가장 마지막에 두는 이유: 다른 항목 telemetry hook 이 모두 박혀야 #6 측정 가치가 살아남.
3. 항목당 In (PR 범위) / Out (deferred)
각 항목 PR 범위 라인. 세부 결정 (decision-pending) 은 항목 시작 시 mini-brainstorm.
#7 Telemetry skeleton (1번) ✓ 완료
In:
TelemetryService(src/main/services/TelemetryService.ts):emit(kind, payload)→ 비동기 append to<profileDir>/telemetry/events-YYYY-MM-DD.jsonl- 일자별 rotation (KST 자정), 14일 후 rolling 삭제
- write 실패 시 silent log only (앱 동작 영향 없음)
- 이벤트 zod schema:
{ ts: ISO string, kind: enum, payload: object }. payload shape 는 kind 별 fixed. - Privacy invariant (slice §1.1 invariant 4 강화): payload 에
raw_text/ai_title/ai_summary/user_intent/ 태그 name 포함 시 zod parser 거부. 단위 테스트로 고정. - 기본 emit hook 박기:
capture(CaptureService.submit 후):{ noteId, rawTextLength, hasMedia }ai_succeeded(AiWorker.processJob 성공):{ noteId, durationMs, attempts }ai_failed(AiWorker.processJob 실패):{ noteId, reason: "unreachable"|"schema"|"timeout"|"other", attempts }
- 트레이 메뉴 "사용 로그 내보내기...":
- 폴더 다이얼로그 →
events.jsonl(최근 14일 concat) +stats.md(집계 마크다운) zip stats.md내용: 항목별 일자별 카운트 표 + 핵심 ratio (AI 성공률, ollama uptime%, recall opened/shown, expired batched/shown 등)
- 폴더 다이얼로그 →
- 트레이 콜백 (main 내부 — 별도 IPC 채널 불필요)
- 단위 테스트: emit, rotation, privacy invariant 거부, stats 집계, export zip
Out: 자동 업로드 / 원격 telemetry (모두 로컬), 실시간 대시보드 UI, opt-out 토글 (로컬이라 불필요), 14일 보존 기간 사용자 설정
#4 휴지통 (2번) ✓ 완료
In:
- migration v3:
notes.deleted_at TEXT NULL+notes.last_recalled_at TEXT NULL+notes.recall_dismissed_at TEXT NULL(3 컬럼 한 번) NoteRepository:trash(id)(deleted_at = now()),restore(id)(deleted_at = NULL),emptyTrash()(hard delete + media 정리). 기존delete()는 deprecate 후emptyTrash내부에서만 호출.- Active 쿼리 일괄
WHERE deleted_at IS NULL추가:listNotes,countToday,findByTag, search, F5 export, AiWorkerloop()진입 시 deleted_at 체크 - 휴지통 UI: Inbox 상단 탭 ("Inbox · 휴지통(N)") — 정밀 위치는 mini-brainstorm
- 휴지통 비우기 confirm dialog ("N개 영구 삭제. 되돌릴 수 없음.")
- F5 export 가
deleted_at IS NOT NULL제외 - F6-L3 import 충돌 정책 추가: source 와 dest 중
deleted_at IS NOT NULL우선 (삭제 보존) - IPC:
inbox:trash/inbox:restore/inbox:emptyTrash/inbox:listTrash - Telemetry emit:
trash/restore/emptyTrash - 단위 테스트: trash/restore/emptyTrash, active query 제외, AiWorker skip, F5 export 제외, F6-L3 import 머지
Out: 자동 비우기 정책 (사용자 트리거만), 휴지통 검색, trash 안 노트 편집, 휴지통 UI 정밀 위치 (mini-brainstorm), per-note 영속 보호 플래그
#5 만료 추천 (3번) ✓ 완료
In:
NoteRepository.findExpiredCandidates({today}):WHERE due_date < today AND deleted_at IS NULL AND ai_status = 'done'- ORDER BY
created_at DESC
- Inbox 상단 만료 배너 (펼침 가능):
- "오늘 기준 만료 N개" 헤더
- 펼치면 노트 카드 리스트 + 체크박스 멀티선택
- "선택 휴지통" 버튼 → 일괄 trash + telemetry emit
- "오늘 그만" → in-memory snooze (자정 KST 리셋)
- IPC:
inbox:listExpired,inbox:trashBatch - Telemetry emit:
expired_banner_shown({ candidateCount }),expired_batch_trash({ count }) - 단위 테스트: 후보 query, 멀티선택 batch trash, snooze 동작, deleted_at 제외 확인
Out: 시스템 알림 surface, 별 페이지, snooze 영속화, "안 옮김" 가중치 감소, 만료 임박 (D-7) 추천
#1 Ollama 회복 (4번) ✓ 완료
In:
- HealthChecker 주기 polling (기본 60s — mini-brainstorm 에서 주기/backoff 확정):
runOnce()가 setInterval 로 자동 발화- 회복 시
onUpdatefire → 구독 (renderer OllamaBanner) 자동 갱신 - 실패 N회 후 polling 중단 정책 — mini-brainstorm
- 수동 "재확인" 버튼:
OllamaBanner+ 트레이 컨텍스트 메뉴 - IPC:
inbox:ollamaRecheck - Telemetry emit:
ollama_unreachable({ reason }),ollama_recovered({ downtimeMs }),ollama_recheck_manual({}) - 단위 테스트: polling fire, manual recheck, 회복 status 전이 + telemetry emit
Out: 사용자 설정 가능 polling 주기, 회복 toast 알림, 모델 정상성 (tags 외) 체크
#2 AI retry / 수동 trigger (5번) ✓ 완료
In:
AiWorker.processJob()정책 변경:- ollama unreachable 일 때
attempts증가 안 하고next_run_at만 backoff (무한 retry while unreachable) - schema fail / invalid response / timeout 만
attempts증가 (기존 max 3 유지) - reason 분류는
LocalOllamaProvider결과 + zod 결과로 결정
- ollama unreachable 일 때
markAiFailed한 노트 수동 re-enqueue 가능 (hard fail 도 회수 경로)- 트레이 + Inbox 메뉴 "지금 AI 처리 (실패 N건)" → 모든 ai_status='failed' → pending_jobs 재투입
FailedBanner(PendingBanner 형제, 실패 N건 + retry 버튼)- IPC:
inbox:retryAllFailed,inbox:failedCount - Telemetry emit:
ai_failed(#7 의 기본 hook 에 reason 분류 추가),ai_retry_manual({ failedCount }) - 단위 테스트: unreachable infinite retry, retry-all trigger, unreachable vs schema fail 구분, attempts 증가 정책
Out: per-note retry 버튼 (NoteCard), failed reason 별 차등 정책, retry progress UI, retry rate-limit
#3 태그 vocab (6번) ✓ 완료
In:
NoteRepository.getTopUsedTags(N=20):SELECT t.name, COUNT(*) c FROM tags t JOIN note_tags nt ON nt.tag_id=t.id JOIN notes n ON n.id=nt.note_id WHERE n.deleted_at IS NULL GROUP BY t.id ORDER BY c DESC LIMIT 20
buildPrompt()에 vocab 주입 라인:- "기존 태그를 우선 재사용. 새 태그는 vocab 에 없는 의미일 때만 만들기:" + kebab-case 리스트
- vocab 빈 케이스 (신규 사용자) → 라인 자체 생략
PROMPT_VERSION3 → 4- AI 응답 후 vocab hit/miss 분류 → telemetry emit
- Telemetry emit:
tag_vocab_hit({ tagId, vocabSize }),tag_vocab_miss({ vocabSize }) - 단위 테스트: vocab 합성, 빈 vocab, 길이 cap, prompt version bump, hit/miss 분류
Out: 임베딩 유사도 dedup, 사용자 controlled vocabulary 화이트리스트, 자동 normalize ("회의" ↔ "미팅"), top-N 튜닝, vocab cache invalidation 정책
#6 리마인드 1 spike (7번) ✓ 완료
In:
strategy.md§2.3 / §4.3 / §8 갱신: Capitalize 본격 진입, "오늘 회상" surface 정의, F4-A/B/D deferred 항목의 측정 인프라 마련 명시- Inbox 상단
RecallBanner— "오늘 회상해볼 노트" 1건 추천:- algo:
WHERE (last_recalled_at IS NULL OR last_recalled_at < date('now','-7 day')) AND (recall_dismissed_at IS NULL OR recall_dismissed_at < date('now','-30 day')) AND ai_status='done' AND deleted_at IS NULL AND (due_date IS NULL OR due_date >= today) ORDER BY created_at ASC LIMIT 1 - 사용자 액션 3개:
- "열어보기" → 노트 카드 스크롤 +
last_recalled_at = now() - "다음에" → in-memory snooze 1일 (영속화 X)
- "더 이상" →
recall_dismissed_at = now()
- "열어보기" → 노트 카드 스크롤 +
- algo:
- IPC:
inbox:listRecallCandidate,inbox:markRecallOpened,inbox:dismissRecall - Telemetry emit:
recall_shown({ noteId, ageDays }),recall_opened,recall_dismissed,recall_snoozed - 단위 테스트: algo selection, dismiss 만료 (30일 후 재추천), last_recalled 갱신, deleted_at 제외, 후보 0건 케이스
Out: 잠금해제 hook (F4-A), 무작위 토스트 (F4-D), ambient if-then (F4-B), 임베딩 유사도 추천 (#3 vocab 후속), spaced repetition (Leitner/SM-2), 다중 후보 추천
3.1 공통 게이트 (모든 항목)
각 항목 머지 전 필수:
npm run typecheck통과 (현재 0 에러)npm test통과 (현재 205/205, 항목 신규 단위 추가)npm run test:e2e통과 (현재 1/1)- 항목 신규 단위 테스트 ≥ 1개 (TDD)
- main 머지
4. 항목당 작업 흐름 + Cross-cutting
[항목 N 시작]
│
├─ mini-brainstorm ← decision-pending 답변
│ - 본 문서 §3 의 "Out" 후보 일부가 In 으로 승격 가능
│ - per-item spec doc → docs/superpowers/specs/2026-MM-DD-v023-<slug>.md
│
├─ writing-plans ← TDD 구현 계획
│
├─ 구현 (executing-plans 또는 직접)
│ - 브랜치: feat/v023-<slug> (예: feat/v023-trash, feat/v023-recall)
│ - 게이트 통과 후 main 머지
│
└─ 다음 항목 시작
4.1 Cross-cutting 정책
| 영역 | 정책 |
|---|---|
| 버전 관리 | 7개 모두 머지될 때까지 package.json 0.2.2 유지. v0.2.3 cut 은 7번 후 단일. |
| CHANGELOG | 기존 CHANGELOG.md 에 [0.2.3] section append (v0.2.2 에서 확립한 패턴). v0.2.3 cut 직전 한 번만 수정. |
| 브랜치 전략 | feat/v023-<slug> 단명. main 머지 후 삭제. 작은 항목 (#1, #3, #6 strategy 갱신) 은 main 직접 push 도 허용. |
| 테스트 추가 정책 | 항목당 최소 단위 1개. e2e smoke 영향 시 단언 동기화. AiWorker 변경 (#1, #2, #3) 은 integration (Ollama) 영향 시 검토. |
| Slice invariant 위반 시 | 본 로드맵 결과로 invariant 변경 — slice §1.1 §7 도 PR 안에 동봉 수정. |
| 신규 dependency | slice §7 strict-pin 그대로. 0 신규 dep 목표 — 위반 시 PR 안에 §7.2 갱신 + 합리화 동봉. |
| 로깅 정책 | slice §1.1 invariant 4 강화: telemetry payload 에 raw_text/title/summary/intent/tag name 포함 절대 금지. 위반 시 silent invariant 위반. #7 단위 테스트로 zod parser 가 거부. |
| Strategy.md 동반 갱신 | #6 항목 (7번) 에서만. 다른 항목은 strategy.md 미수정. |
| Schema invariant 추가 | deleted_at IS NOT NULL 노트는 모든 active 쿼리 (Inbox 리스트 / 카운트 / 검색 / 태그 필터 / AiWorker 처리 / F5 export) 에서 제외. F6-L1 backup 만 예외. 위반 시 dogfood-feedback 재오픈. |
5. v0.2.3 Cut 단계
7번 항목 머지 후:
[v0.2.2 dogfood 환경에서]
1. 트레이 → "지금 백업" 1회 클릭 ← F6-L1 첫 실증
2. 트레이 → "내보내기..." 1회 ← F5 schema-agnostic 백업
3. 트레이 → "사용 로그 내보내기..." 1회 ← #7 의 첫 실증 (없으면 v0.2.2 raw 데이터 손실)
4. Inkling 종료
[빌드 머신에서]
5. package.json version: 0.2.2 → 0.2.3
6. CHANGELOG.md 에 [0.2.3] section append
7. npm run dist
8. dist/Inkling Setup 0.2.3.exe 검증
[dogfood 머신에서]
9. Setup 0.2.3.exe 실행 → 같은 폴더에 설치
10. 첫 실행 → migration v3 자동 적용 (deleted_at + last_recalled_at + recall_dismissed_at)
11. 트레이 메뉴 "사용 로그 내보내기..." 항목 존재 확인
12. ≥ 1주 soak 시작
5.1 업그레이드 안전망
| 위험 | 완화 |
|---|---|
| migration v3 결함으로 DB 손상 | 2가지 복원 경로 (v0.2.1 부터): (a) <dbFile>.pre-v3.bak 자동 snapshot 으로 SQLite 복원 (v0.2.2 인스톨러 재설치 필요), (b) F5 export → v0.2.3 의 F6-L3 import 로 schema-agnostic 복원 (더 빠름) |
deleted_at IS NULL 누락 — 휴지통 노트가 active 쿼리에 새는 회귀 |
단위 테스트로 모든 active 쿼리 확인. 실수 시 dogfood-feedback 즉시 재오픈. |
| Telemetry payload 에 본문 누출 | TelemetryService.emit 의 zod parser 가 거부. CI 단위 테스트로 고정. |
| AiWorker unreachable infinite retry 가 큐 폭주 | next_run_at 의 backoff cap (15분) — mini-brainstorm 에서 확정. |
| 자동시작 토글 / 데이터 디렉터리 손실 | v0.2.1 동일 — HKCU\...\Run + <profileDir> 보존됨 |
6. 측정
6.1 로드맵 측정
| 메트릭 | 임계값 | 측정 방법 |
|---|---|---|
| 항목 평균 PR 사이즈 | < 800 lines diff | git log 통계 |
| 항목 평균 머지 간격 | < 5일 | git log 시간차 |
| 회귀 테스트 추가 | 항목당 ≥ 1개 단위 | tests/unit 카운트 |
| v0.2.3 cut 후 1주 데이터 손실 | 0회 | telemetry + 본인 라벨링 보강 |
| typecheck/test 회귀 | 0회 | CI · 로컬 |
| Telemetry 본문 누출 | 0건 | events.jsonl grep + zod parser |
6.2 dogfood soak 측정 (#7 의 본격 사용처)
stats.md 가 다음을 답해야 함:
| 질문 | 데이터 |
|---|---|
| AI 가 실제로 동작 중인가? | ai_succeeded / (ai_succeeded + ai_failed) ratio 일자별 |
| Ollama unreachable 빈도? | ollama_unreachable count + 평균 downtimeMs |
| 수동 trigger 가 쓰이고 있나? | ai_retry_manual / ollama_recheck_manual count |
| 휴지통이 회수 도구로 동작? | restore / trash ratio |
| 만료 추천이 nudging 으로 동작? | expired_batch_trash / expired_banner_shown ratio |
| 회상 spike 가 의미 있나? | recall_opened / recall_shown ratio + recall_dismissed count |
| Tag vocab 재사용? | tag_vocab_hit / (hit + miss) ratio (목표: 시간 흐름에 따라 상승) |
6.3 silent invariant 후보
본 로드맵 결과로 slice §1.3 종료 조건에 추가 권장:
"Telemetry 본문 누출 0회" — events.jsonl 의 어떤 payload 에도 raw_text/title/summary/intent/tag name 미포함. 발생 시 즉시 silent invariant 위반.
"
deleted_at IS NULL망각 0회" — active 쿼리 회귀 시 즉시 dogfood-feedback 재오픈.
이 추가는 #7 / #4 항목 머지 시 slice §1.3 동봉 수정.
7. 본 로드맵의 종료 조건
모두 만족해야 종결:
- #7·#4·#5·#1·#2·#3·#6 7개 항목 모두 main 머지
CHANGELOG.md [0.2.3]section +package.json0.2.3 + slice §1.3 silent invariant 2개 추가 동봉 갱신 완료- v0.2.3 cut → dogfood 머신 재설치 → migration v3 적용 확인 → 첫 실행 정상 + 트레이 메뉴 신규 항목 ("사용 로그 내보내기...") 동작 확인
- ≥ 1주 dogfood soak 완료 (데이터 손실 0회 + telemetry 본문 누출 0건 확인)
events.jsonl+stats.mdexport → 분석 → v0.2.4 brainstorm 진입
5 가 끝나면 본 로드맵 종결.
8. 미결정 항목 (각 항목 mini-brainstorm 에서 답변)
본 로드맵은 순서·In/Out 만 정의. 다음 결정들은 빨리 마주치게 됨:
- #7: events.jsonl rotation 주기 (자정 KST 확정), stats.md 집계 ratio 의 정확한 컬럼 list, payload schema 별 zod 파서 합성 정책, write 실패 시 백오프
- #4: 휴지통 UI 정밀 위치 (Inbox 탭 vs 트레이 별 윈도우 vs 별 페이지), 휴지통 비우기 confirm 카피, F5 export 의 trash 옵션 (제외 강제 vs 사용자 토글)
- #5: 후보
due_date_edited_by_user필터 여부 (수동 입력만 vs AI 자동 추출 포함), 만료 임박 (D-7) 포함 여부, 멀티선택 default 상태 (전체 선택 vs 비선택) - #1: polling 주기 (10/30/60s), 실패 N회 후 polling 중단, exponential backoff 적용
- #2: unreachable backoff cap (15분 후보), reason 분류 정밀도 (timeout vs unreachable 구분), per-note retry 승격 여부
- #3: top-N 값 (20 후보), vocab cache invalidation 정책 (write-through vs 매 prompt 시 fresh), 빈 vocab 임계값
- #6: dismiss 만료 30일 vs 14일 vs 60일, 후보 0건 시 RecallBanner 숨김 vs 빈 상태 카피
- #5+#6 coexistence: 둘 다 Inbox 상단 배너 noting. stack 순서 (만료 위 → 회상 아래 가 자연 — 시간 민감도 우선), 동시 N건 시 우선 표시 정책, 빈 상태 시 영역 collapse 여부. #5 → #6 순서 머지라 #6 mini-brainstorm 에서 #5 와 통합 layout 결정.
9. 변경 이력
| 일자 | 변경 |
|---|---|
| 2026-05-01 | 초안 — v0.2.2 dogfood 7항목 (#7 telemetry 신설 포함) 단일 cut 로드맵, 데이터 안전 우선 (C 채택), schema migration v3 3컬럼 한 묶음 (B 채택), trash↔backup/export B 정책, #6 = 1 spike 흡수 (B 채택). |