review T9 flagged 2 service-layer defenses:
#1: deleteNote/restoreNote/permanentDeleteNote 의 idempotency. 이미 trash 인
노트를 trash 하거나, 이미 active 인 노트를 restore 하거나, 존재하지 않는 노트를
permanentDelete 시 telemetry 가 spurious 하게 emit → restore/trash ratio (T8)
오염. findById 가드로 의미 없는 emit skip.
#2: permanentDeleteNote 의 disk cleanup unguarded. store.deleteNoteDirectory
실패 시 (Windows file-lock 등) telemetry 가 영영 emit 안 되고 IPC 호출자가
이미 성공한 작업에 에러 propagate. emptyTrash 와 동일하게 try/catch 로 감싸
best-effort. orphan dir 은 future janitor 가 정리.
Tests: 12/12 still pass. typecheck 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T5 reviewer identified 2 reads outside NoteRepository that were missing the
'WHERE deleted_at IS NULL' filter, breaking the silent invariant beyond the
3 originally-listed methods.
- ContinuityService.get() now excludes trashed notes from streak / weekCount
/ lastNoteAt / recovery-toast math. A trashed note no longer counts toward
weekly streak (regression: streak felt fake after trash).
- NoteRepository.getPendingCount() now excludes trashed-but-still-pending
notes. trash() removes the pending_jobs row but leaves notes.ai_status='pending';
the count would have drifted upward as users trashed pending notes.
- MediaGc.run() gets an inline comment documenting why it intentionally does
NOT filter — trashed notes still own their media until permanentDelete /
emptyTrash. Removing here would defeat restore.
Also: migrations.due_date.test.ts had 2 brittle assertions
(latestVersion()===2, user_version===2) that broke with v3. Migration-system
version assertions belong in migrations.test.ts (already covered there);
m002-specific test keeps the due_date column assertion which is version-stable.
Tests: 245 → 265 (+20). typecheck 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.3 두 번째 항목의 mini-brainstorm 결과 lock.
UI=A (Inbox 탭 toggle), 필터=A (명시적 WHERE deleted_at IS NULL),
AiWorker race=C (pending_jobs cleanup + processJob 가드),
액션=B (per-card 영구 삭제 추가 — IPC 4채널 → 5채널, telemetry 3 → 4 events),
confirm/정렬/카드차이 모두 A.
self-review 후 ExportService/ImportService 충돌 정책 ambiguity 명시화.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #13 회차 1 리뷰의 actionable 1건 + suggestion 3건 반영.
- `AiWorker` 의 `attempts` 필드가 success/failure 경로에서 비대칭 의미 (0-index vs count) 였던 문제. 둘 다 `attempt + 1` (실제 시도 횟수, 1-based) 로 통일. stats markdown 의 평균/분포 해석이 일관됨.
- `Date.now()` 직접 호출이 `opts.now` DI 를 우회하던 두 곳을 `this.now().getTime()` 으로 교체. 추후 durationMs 분포 테스트 작성 가능.
- `TelemetryService.emit` 의 `this.now()` 두 번 호출을 한 번 캐시로 통합. KST 자정 경계에서 ts 와 파일명 일자 불일치 가능성 제거.
- `readAllRecent` 의 `n.slice(7, 17)` 매직 슬라이스를 정규식 capture 그룹으로 교체. prefix 변경 시 한 곳만 수정.
테스트: AiWorker 성공 케이스의 `attempts: 0` → `attempts: 1` 갱신.
게이트: typecheck 0 errors, 245/245 unit tests pass.
Deferred (v0.2.4 backlog): 'aborted' user-cancel false-positive, tray menu submenu 분리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add .catch(...) to telemetry.cleanupOldFiles fire-and-forget for consistency
with backup.runDaily pattern (M1 from T10 code review).
- Mark Roadmap §3 #7 as completed (✓).
- Correct spec: tray:exportTelemetry was never an IPC channel — tray callbacks
run in main process directly. Replace with "트레이 콜백 (main 내부)".
Closes v0.2.3 task 1 of 7. Next task: #4 휴지통 (migration v3).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The T9 full-file replacement accidentally dropped the inline comment
documenting why the count label is conditional on _todayCount > 0
(F4-C UX rationale). No behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Original 'counts events per KST day' test used UTC times that bucket
identically under both KST and naive UTC slice — would not catch a regression
where kstDate was replaced with ev.ts.slice(0,10). Add an explicit
near-midnight case (2026-05-01T15:30Z = 2026-05-02 00:30 KST) that fails
under naive UTC and passes under correct KST conversion.
6 tests pass (was 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Earlier test used '/proc/0/...' as the unwritable dir. On Windows this
resolved to 'C:\proc\0\...' and mkdir({recursive: true}) silently created
it — the silent code path was never exercised, plus filesystem side-effect
leaked outside the test tmpdir.
Replace with a path that points to an existing file (mkdir on a file path
fails on every platform). Also add a companion test that confirms silent
is opt-in: without {silent: true}, the same fs failure DOES throw.
7 tests pass (was 6).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.2 dogfood 7항목 (#7 telemetry 신설 + #1~#6) 단일 cut 로드맵.
데이터 안전 우선 (C 채택), schema migration v3 3컬럼 한 묶음 (B),
trash↔backup/export B 정책, #6 = 1 spike 흡수.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
신규 spec 파일 추가 (구현 결과 반영). dogfood-feedback.md 의 F7
헤더 🔬 drafting → 🚀 promoted 로 갱신. F1 spec 의 후속 리스트에
F7 링크 추가.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flow 반전 (F7-D 채택):
- 기존: rule.iso ?? ai.dueDate (rule 우선)
- 신규: ai.dueDate ?? null (AI 우선)
규칙은 parseAllCandidates 로 모든 매치를 추출 → prompt 에 후보
힌트로 주입. AI 가 종합 판단. AI 실패 시 due_date null (별 fallback 없음).
해결되는 케이스: '내일 모레' → AI 가 ambiguous 인지 → null.
PROMPT_VERSION → 3. GenerateInput.dueDateCandidates 신규.
buildPrompt(rawText, todayKst, candidates) — 빈 배열일 때 hint 섹션 생략.
Tests:
- AiWorker.test.ts — 'rule priority' 테스트 → 'AI dueDate wins' flip
- AiWorker.test.ts — passes todayKst 테스트 확장 (dueDateCandidates 도 검증)
- AiWorker.test.ts — 신규 'passes parseAllCandidates result as dueDateCandidates'
- LocalOllamaProvider.test.ts / ollama-golden.test.ts — generate 호출에 dueDateCandidates: [] 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 parseDueDate (first-match-wins) 는 backward compat 로 보존.
parseAllCandidates 가 모든 high/medium 매치를 text 순서로 반환 — F7
AI-primary flow 의 prompt 후보 주입 입력으로 사용.
Overlapping-span suppression: 다음 주 월요일 같은 케이스에서 rule 10
(전체) 이 rule 13 (다음 주 alone) 을 포함하면 후자 매치 제거.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.1 dogfood 첫 실증 피드백. '내일 모레' → 내일로 잘못 잡힘.
규칙 파서 한계 3 (합성/양가 / 범위 / 모호) + 5 후보 방향 (A 화이트
리스트 / B 충돌 감지 / C UI 신호 / D AI 우선 / E 규칙 폐기).
1차 A+B 작은 PR 즉시 시도 가능, 2차 C UI 신호, 후속 D/E 결정.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 항목 dogfood-feedback 로드맵 (F1·F2·F3·F4-E·F5·F6-L1·F6-L2·F6-L3·F4-C·F)
한 번에 흡수. migration v2 (due_date) + pre-migration snapshot.
단위 테스트 52 → 197. 신규 npm dep 0, 시스템 dep 추가는 git CLI (이미 사전 요구).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
F4 의 6개 cue 메커니즘 중 외부 신호 없이 즉시 구현 가능한 두 가지
(C 환경 앵커, F 정체성 고리) 를 묶어 promoted spec 으로 추출. A (잠금
hook), D (variable interval prompt), B (ambient if-then) 는 dogfood
soak 측정 결과를 본 뒤 결정.
F4 헤더를 🌱 raw → 🔬 drafting (C·E·F promoted) 로 갱신하고, F4 진행
상태에 두 promoted 경로를 명시.
F4-C·F (cue 강화) — Inkling 의 두 표면에 정체성 신호를 추가.
F4-C 환경 앵커 (tray):
- nativeImage 색 변경은 미지원이므로 색 뱃지 대신 tooltip + 메뉴 첫
비활성 라벨로 대체. tooltip 은 항상 `Inkling — 오늘 N`,
메뉴 첫 항목은 N>0 일 때 `오늘 N번 잡아둠` (비활성).
- `tray.ts` 가 `refreshTray(todayCount)` 를 export 하여 main 이
60s interval + AiWorker.onUpdate hook 에서 갱신을 트리거.
- N=0 일 때는 라벨을 띄우지 않아 메뉴가 자연스럽게 시작.
F4-F 정체성 고리 (Inbox 헤더):
- ContinuityBadge 옆에 새 IdentityCounter 컴포넌트.
- N>0 → `오늘 N번 잡아뒀다` (정체성 강화 카피).
- N=0 → `오늘은 처음 한 줄?` (priming 카피로 첫 캡처 유도).
- 갱신은 `loadInitial` / `refreshMeta` (focus + note:updated) 경로
공유 — 별도 IPC subscription 없음.
Wiring:
- `NoteRepository.countToday()` 를 `inbox:todayCount` IPC 로 노출.
- preload bridge `getTodayCount`, `InboxApi.getTodayCount()` 타입.
- 스토어에 `todayCount: number` 필드 추가, 두 메타 fetch 경로 모두에서 갱신.
스키마 변경 없음. 197/197 unit pass, 1/1 e2e pass.
For the F4-C·F cue strengthening surfaces (tray tooltip + Inbox identity
counter), main + renderer need a single source of truth for "오늘 N번
잡아뒀다". Implements `NoteRepository.countToday(now?)` that computes the
half-open UTC interval covering the KST calendar date of `now` and counts
rows whose `created_at` falls inside.
`now` is injectable for deterministic tests across the KST/UTC boundary
(02:00 KST and 23:00 KST land on different UTC dates yet the same / a
different KST day). Four new cases cover empty DB, KST-day filtering,
KST-midnight crossover, and the default-arg branch.
- 신규 spec docs/superpowers/specs/2026-04-26-f6-l2-git-sync.md
결정 표 + 범위 + 시스템 의존 + 동작 요약 + 후속 후보
- 2026-04-25-dogfood-feedback.md F6 진행 상태 line 갱신:
L2 — 🌱 raw → 🚀 promoted (MVP)
- README.md '원격 백업 (선택, F6-L2)' 섹션:
일회 설정 (git init + remote add + 자격증명) + 사용법
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
F6-L2 MVP 의 오케스트레이터.
- isConfigured(): syncDir 가 git repo + origin remote 있을 때만 true
- sync():
1) ExportService.export(<syncDir>, includeMedia: true) — F5 트리 그대로 덮어쓰기
2) git add -A
3) git commit -m "chore(notes): sync <ts>"
4) "nothing to commit" 이면 changed=false 로 정상 반환
5) git push (upstream 미설정이면 -u origin <branch> 자동)
- GitClient.push() 에 hasUpstream() + 자동 -u 추가 (첫 push 케이스)
- 5 vitest cases — bare local remote 로 push 검증, 두 번째 sync 는 변경 없음 확인
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>