Commit Graph

427 Commits

Author SHA1 Message Date
altair823
9fef2edb6e feat(ollama): ProviderHolder + AiWorker/HealthChecker refactor (v0.2.3.1)
- ProviderHolder: mutable holder + listeners, indirection layer
- AiWorker: constructor InferenceProvider → ProviderHolder
  this.provider.x → this.holder.get().x 전환
- HealthChecker: 동일 패턴
- src/main/index.ts: provider 를 ProviderHolder 로 감싸서 생성
- 기존 AiWorker / HealthChecker 테스트의 constructor 호출에 ProviderHolder wrap
- 단위 +2 cases (ProviderHolder)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:32:20 +09:00
altair823
c77c30be83 feat(ollama): LocalOllamaProvider — abort() + AbortController instance field (v0.2.3.1)
- abortController 가 method-local 에서 private instance field 로 이동
- public abort() 메서드 — 외부에서 in-flight generate 강제 중단
- ProviderHolder.replace() 시 호출되어 endpoint 변경 즉시 반영
- 단위 +2 cases (abort cancellation, model 파라미터)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:26:48 +09:00
altair823
de895b8fec feat(settings): SettingsService — JSON 영속화 + zod 검증 (v0.2.3.1)
- `<profileDir>/settings.json` atomic write (temp + rename)
- 손상 JSON / 파일 없음 → 빈 객체 fallback (no throw)
- in-memory cache (load 1회 file read)
- zod .strict() schema for ollama { endpoint: URL, model: string }
- 단위 +6 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:23:32 +09:00
altair823
71ec79ae19 docs(ollama-settings): v0.2.3.1 plan — 7 tasks TDD + 10 단위 cases
T1 SettingsService (JSON 영속화 + zod, +6 cases)
T2 LocalOllamaProvider abort + model param (+2 cases)
T3 ProviderHolder + AiWorker/HealthChecker refactor (+2 cases)
T4 index 부팅 + IPC + preload + types
T5 OllamaSettingsModal + App.tsx + OllamaBanner 링크
T6 트레이 메뉴 "Ollama 설정..."
T7 Closure (version 0.2.3 → 0.2.3.1 + gates)

총 신규 단위 +10. 단위 403 → 413.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:21:00 +09:00
altair823
97ca119b55 docs(ollama-settings): v0.2.3.1 spec — in-app endpoint/model 설정
mini-brainstorm 3개 결정:
- Q1=B: Endpoint + Model 둘 다 포함
- Q2=A: Freetext input (dropdown 은 v0.2.4 영역)
- Q3=B: JSON file (`<profileDir>/settings.json`, migration v4 회피)

자명 결정 (질문 없이 패턴):
- precedence: settings > env > default
- in-flight: AbortController abort + provider re-create
- UI: 트레이 + OllamaBanner 진입점, React modal
- validation: save 전 healthCheck

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:17:23 +09:00
altair823
b259734aa0 docs(backlog): v0.2.4 backlog memory → repo 이동
v0.2.3 cut 7항목 동안 final reviewer + PR review 에서 발견된 minor/nit
중 의도적 deferred 38건 누적. 기존엔 user-level memory 에만 있어
사용자가 직접 보거나 편집 어려움 → repo 안으로 lift.

dogfood 1주 soak 동안 user 가 직접 prune / 우선순위 표시 / 새 항목 추가
가능. v0.2.4 brainstorm 진입 시 본 doc 가 1차 backlog reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:18:07 +09:00
altair823
5fc694c57b hotfix(build): publish: null 추가 — Mac 빌드 시 updateInfoBuilder crash 회피
PR #20 직후 Mac arm64 dist 시도 중 발견:
- Cannot detect repository by .git/config 경고 (3회)
- ⨯ Cannot read properties of null (reading 'channel')
  at computeChannelNames (updateInfoBuilder.ts:47:74)

원인: electron-builder 가 auto-update 메타파일 (latest-mac.yml) 생성 시
publish config 또는 git remote 에서 채널 정보 추론 실패 → null 접근 crash.
DMG 자체는 빌드 성공 (dist/Inkling-0.2.3-arm64.dmg) — 후처리 단계 crash.

Fix: build.publish = null 명시 — auto-update 메커니즘 미사용 (개인 dogfood)
이라 latest-mac.yml / latest.yml 생성 단계 skip. Windows 빌드도 동일 경고
3회 떴는데 이번 fix 로 함께 사라짐.

검증: npm run dist:dir on Windows → "Cannot detect repository" 경고 사라짐, 정상 빌드.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.3
2026-05-02 16:15:47 +09:00
4e1f60cb7d Merge pull request 'hotfix(build): npm run dist Mac arm64 cross-platform 지원' (#20) from hotfix/arm-mac-build into main
Reviewed-on: #20
2026-05-02 07:06:23 +00:00
altair823
8cdffb2143 hotfix(build): npm run dist 가 Mac arm64 에서도 동작하도록 cross-platform
- dist / dist:dir 에서 --win --x64 제거 → electron-builder host-default
  (Windows 에선 win-x64, Mac 에선 mac-arm64 자동 선택)
- 명시적 강제 variant 추가: dist:win, dist:mac
- build.mac 블록 추가:
  - target: dmg / arch: arm64
  - category: productivity
  - identity: null (개인 dogfood, codesign skip)

검증:
- typecheck 0
- 단위 403/403
- npm run dist:dir on Windows: platform=win32 arch=x64 (회귀 X)

Mac arm64 빌드 시 첫 실행 시 "Apple 이 검증할 수 없음" 경고 → 우클릭 → 열기 (codesign 미적용 의도).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:04:20 +09:00
altair823
5d0f87c5fb chore(release): v0.2.3 — package.json + lock 버전 bump
v0.2.3 cut 7/7 완료 (PR #13/#14/#15/#16/#17/#18/#19) 후 binary 빌드.

빌드 결과: dist/Inkling Setup 0.2.3.exe (103.8MB, NSIS x64)
gates: typecheck 0 / 단위 403 / e2e 1

다음: dogfood 머신 핸드오프 → ≥1주 soak → telemetry export → v0.2.4 brainstorm

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:03:41 +09:00
cb29ef6f89 Merge pull request 'feat(recall): #6 리마인드 1 spike — RecallBanner + telemetry (v0.2.3 7/7 final)' (#19) from feat/v023-recall-spike into main
Reviewed-on: #19
2026-05-02 04:52:46 +00:00
altair823
61b6fa6c1f fix(recall): PR review round 1 — i1 race + m1~m4 + n2 (#6 v0.2.3)
- i1 (Important): RecallBanner shownIds → useRef (state setState 트리거 race 차단)
  store 의 recallShownIds 필드 제거 (dead — useRef 가 대체)
- m1 (Minor): snoozeRecall candidate-null race 코멘트 (의도적 emit skip 명시)
- m2 (Minor): dismissRecallNote 후 recallSnoozeUntilMs = null clear
- m3 (Minor): CaptureService.markRecallOpened 의 dead local 'before' inline check 로 제거
- m4 (Minor): RecallBanner title 빈 케이스 fallback '(제목 없음)'
- n2 (Nit): NoteCard id load-bearing 의미 1줄 코멘트

skip: n1 (KST 4번째 inline duplicate — 프로젝트 전반 패턴, v0.2.4 nextKstMidnightMs 통합),
      n3 (ipcMain.on vs handle — 다른 IPC 와 패턴 일관)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:38:52 +09:00
altair823
348e9ee402 chore(recall): #6 closure — strategy.md 갱신 + roadmap mark + 게이트 검증
- 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>
2026-05-02 13:30:49 +09:00
altair823
646fe7a7ab feat(recall): RecallBanner + App.tsx mount + NoteCard id (#6 v0.2.3)
- RecallBanner: 노트 제목 + N일 전 + 3 버튼 (열어보기/다음에/더 이상)
- 첫 렌더 시 emitRecallShown (recallShownIds Set 으로 per-session 1회 제약)
- snoozeUntilMs 만료 setTimeout (ExpiryBanner 패턴)
- 위치: ExpiryBanner 다음 (banner stack 끝)
- NoteCard 외곽 div 에 id="note-${note.id}" — "열어보기" scrollIntoView target
- 컬러 테마: 파랑 (#e8f0fe / #4a7ec0) — 다른 banner (적/황/적) 와 구별

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:28:58 +09:00
altair823
f4e1af83fe feat(recall): renderer store — recallCandidate + 4 actions (#6 v0.2.3)
- recallCandidate, recallSnoozeUntilMs, recallShownIds (Set) state
- loadInitial / refreshMeta 가 listRecallCandidate Promise.all 합류
- loadRecallCandidate / openRecall / dismissRecallNote / snoozeRecall actions
- snoozeRecall: KST 다음 자정 (snoozeExpired 패턴 일관) + emitRecallSnoozed
- openRecall / dismissRecallNote: API 호출 후 다음 후보 fetch
- 신규 store.recall.test.ts +3 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:25:49 +09:00
altair823
20394bf2a3 feat(recall): IPC + preload + InboxApi — 5 channels (#6 v0.2.3)
- ipcMain.handle: list/markOpened/dismiss/emitShown/emitSnoozed
- preload inboxApi: 5 entries (ipcRenderer.invoke)
- shared/types InboxApi: 5 method signatures

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:22:16 +09:00
altair823
0c59ce3715 feat(recall): CaptureService — 5 methods (list/open/dismiss/shown/snoozed) (#6 v0.2.3)
- listRecallCandidate(): repo.findRecallCandidate 위임
- markRecallOpened(id): last_recalled_at 갱신 + recall_opened emit
- dismissRecall(id): recall_dismissed_at 갱신 + recall_dismissed emit
- emitRecallShown(id): ageDays 계산 + recall_shown emit
- emitRecallSnoozed(id): recall_snoozed emit
- private computeAgeDays(note): last_recalled_at ?? created_at 기준 일수
- 단위 +4 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:20:44 +09:00
altair823
59cfb711cd feat(recall): telemetryStats + EmitInput — recall 누적 + 열림율 + 평균 ageDays (#6 v0.2.3)
- DailyRow +4 cols (recall_shown/opened/dismissed/snoozed)
- accumulators + 4 branches + recallAgeDaysSum
- table 컬럼 +4
- summary lines: "- 회상 추천: shown N / opened O / dismissed D / snoozed S (열림율 X%)"
                 "- 회상 평균 ageDays: avg"
- TelemetryService.EmitInput union 15 → 19
- 단위 +2 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:17:49 +09:00
altair823
b94e68238c feat(recall): telemetryEvents — recall_shown/opened/dismissed/snoozed zod schemas (#6 v0.2.3)
- RecallShownPayload { noteId, ageDays: int>=0 } .strict()
- recall_opened/dismissed/snoozed → NoteIdPayload 재사용
- TelemetryEventSchema union 15 → 19
- 단위 +3 cases (recall_shown valid, extra field 거부, opened/dismissed/snoozed valid)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:13:49 +09:00
altair823
0eb2e6282f feat(recall): NoteRepository — findRecallCandidate + markRecallOpened + dismissRecall (#6 v0.2.3)
- findRecallCandidate(): 7일+ 안 본 + 30일+ dismiss 만료 + ai='done' + 마감 안 임박 + LIMIT 1
- markRecallOpened(id, now): last_recalled_at 갱신
- dismissRecall(id, now): recall_dismissed_at 갱신
- KST 보정 SQL date('now','+9 hours')
- 단위 +5 cases (empty/recent/old/dismiss expiry/exclude variants)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:11:14 +09:00
altair823
746671059e docs(recall): #6 plan — 8 tasks TDD + 17 단위 cases (v0.2.3)
8 task TDD plan:
T1 NoteRepository (find/markOpened/dismiss, +5 cases)
T2 telemetryEvents (recall_shown 4 union members, +3 cases)
T3 telemetryStats + EmitInput union 19 (+2 cases)
T4 CaptureService (5 methods, +4 cases)
T5 IPC + preload + types (5 channels)
T6 Renderer store (recallCandidate + 4 actions, +3 cases)
T7 RecallBanner + App.tsx + NoteCard id
T8 closure (strategy.md + roadmap + gates)

총 신규 단위 +17. 단위 386 → 403 예상.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:08:32 +09:00
altair823
e6494b8778 docs(recall): #6 spec — RecallBanner + 4 telemetry events (v0.2.3)
mini-brainstorm 2개 결정:
- Q1=A: snooze in-memory (KST 다음 자정, ExpiryBanner 패턴 일관)
- Q2=B: ageDays = last_recalled_at ?? created_at 기준

자명 결정:
- Banner 위치: ExpiryBanner 다음 (stack 끝)
- 0건 시 null return
- "열어보기" 동작: scrollIntoView (NoteCard 항상 expanded)
- scroll target: id="note-${id}" (ref 시스템 복잡도 회피)

핵심 invariants 6개 + privacy invariant + tests 17개 약속.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:03:58 +09:00
3c9326d6ec Merge pull request 'feat(tag-vocab): #3 태그 vocab — prompt + telemetry (v0.2.3 6/7)' (#18) from feat/v023-tag-vocab into main
Reviewed-on: #18
2026-05-02 03:53:01 +00:00
altair823
d8621d55e0 fix(tag-vocab): PR review round 1 — i1 dedup + m2 test gap (#3 v0.2.3)
- i1 (Important): AiWorker per-tag emit 루프에 res.tags Set dedup
  AI 가 같은 태그 중복 응답 시 hit count 2번 emit 되던 통계 왜곡 수정
  + 테스트 1개 (중복 태그 1 hit + 1 miss 검증)
- m2 (Minor): NoteRepository.getTopUsedTags LIMIT-then-filter 테스트 갭
  + 테스트 1개 (limit=3 + 한글 1 + kebab 2 → 결과 length=2 lock-in)

skip: m1 (per-tag serial await — ai_succeeded 패턴 일관),
      n1 (prompt 빈 줄 cosmetic), n2 (tagId positive — AUTOINCREMENT 1+)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:49:36 +09:00
altair823
ff07738b02 chore(tag-vocab): #3 closure — gates verified + roadmap mark complete
- typecheck 0 / 단위 384 / e2e 1
- v0.2.3 6/7 (#3 태그 vocab 머지)
- 다음: #6 리마인드 spike (마지막 항목)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:37:30 +09:00
altair823
727eeb1919 fix(tag-vocab): T7 review nit 2건 — test 코드 ergonomics (#3 v0.2.3)
- nit1: tag_vocab_hit/miss 테스트 payload cast dedupe (한 번에 typed 바인딩)
- nit2: { kind: string; payload: unknown } 반복을 EmittedEvent 타입 alias 로 hoist

skip: Minor1 (serial await — ai_succeeded 와 패턴 일관), Nit3 (magic number VOCAB_TOP_N — v0.2.4 backlog), Nit4 (한국어 코멘트 — 기존 코드와 일관)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:36:16 +09:00
altair823
3e0f710c70 feat(tag-vocab): AiWorker — vocab fetch + per-tag hit/miss emit (#3 v0.2.3)
- processJob 가 generate 직전 repo.getTopUsedTags(20) fetch
- provider.generate 에 vocab 전달 (LocalOllamaProvider 가 prompt 에 주입)
- ai_succeeded emit 후 per-tag 분류 → tag_vocab_hit/miss emit
  - hit: vocabSet.has + getTagIdByName lookup → { tagId, vocabSize }
  - miss: { vocabSize }
- AiTelemetryEmitter union 4종 (ai_succeeded/ai_failed/tag_vocab_hit/tag_vocab_miss)
- 단위 +4 cases (vocab passthrough, hit+miss, vocab=[] all miss, per-tag emit count)
- collectingTelemetry mock → AiTelemetryEmitter 타입 적용 (typecheck 통과)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:33:16 +09:00
altair823
26f1db5626 feat(tag-vocab): TelemetryService EmitInput +tag_vocab_hit/miss + 테스트 narrowing 확장 (#3 v0.2.3)
- EmitInput union 13 → 15
- narrowing guards (noteId 없는 kind 분기) 에 tag_vocab_hit/miss 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:29:24 +09:00
altair823
973cb1d08d feat(tag-vocab): telemetryStats — hit/miss 누적 + summary 적중률 (#3 v0.2.3)
- DailyRow +2 cols (tag_vocab_hit, tag_vocab_miss)
- accumulators + branches
- table 컬럼 +2
- summary "- 태그 vocab: hit/miss = N/M (적중률 X%)" 또는 "(데이터 없음)"
- 단위 +2 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:27:22 +09:00
altair823
b81fc82621 feat(tag-vocab): telemetryEvents — tag_vocab_hit/miss zod schemas (#3 v0.2.3)
- TagVocabHitPayload { tagId: int>0, vocabSize: int>=0 } .strict()
- TagVocabMissPayload { vocabSize: int>=0 } .strict()
- TelemetryEventSchema union 13 → 15
- 단위 +3 cases (hit accept, miss accept, hit extra field 거부)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:23:31 +09:00
altair823
daa8507364 feat(tag-vocab): InferenceProvider.vocab + LocalOllamaProvider 전달 (#3 v0.2.3)
- GenerateInput.vocab?: string[] (optional, 미전달 시 빈 배열 처리)
- LocalOllamaProvider.generate 가 input.vocab ?? [] 를 buildPrompt 4th 인자로
- 단위 +1 case (vocab → prompt body)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:21:24 +09:00
altair823
896b374f56 fix(tag-vocab): T2 review minor/nit 2건 (#3 v0.2.3)
- M1: prompt.test.ts test 4 변수명 candidateIdx → headerIdx (실제 anchor 가 'Today's date' 헤더)
- N1: prompt.ts return 직전 self-delimited block 컨벤션 1줄 코멘트

skip: N2 (PROMPT_VERSION 테스트 redundancy nit — harmless guard), N3 (vocab dedup/normalize — Task 1 caller 책임)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:19:39 +09:00
altair823
134d59ddb4 feat(tag-vocab): prompt.ts — PROMPT_VERSION 4 + vocab parameter (#3 v0.2.3)
- PROMPT_VERSION 3 → 4 (marker bump, retry 트리거 X)
- buildPrompt 4번째 param vocab: string[] = []
- vocab.length > 0 시 "Existing vocabulary tags" + "Prefer reusing" 라인 추가
- vocab=[] 시 라인 자체 생략 (Q3=B 결정)
- 단위 +4 cases (신규 prompt.test.ts)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:17:17 +09:00
altair823
e2b16d44d7 fix(tag-vocab): T1 review minor/nit 4건 일괄 (#3 v0.2.3)
- M1: getTopUsedTags 의 LIMIT-then-filter 의미 docstring 명시
- M2: AI+user source 통합 테스트 강화 — 카운트 차이로 정렬 검증 (toContain 만으론 약함)
  updateUserAiFields 는 tags REPLACE 방식 (DELETE+reinsert) 이므로
  fallback 패턴 사용: 3개 노트 각 1태그, AI/user 혼합으로 design=2 > meeting=1 검증
- N1: SQL "COUNT(*) c" → "COUNT(*) AS c" (countFailed 패턴과 일관)
- N2: kebab-case regex 모듈 상수 KEBAB_CASE_RE 로 hoist

skip: N3 (test 헬퍼 — verbosity 경미), N4 (it 블록 분리 — 코드베이스 패턴 유지)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:14:53 +09:00
altair823
df8a53aec1 feat(tag-vocab): NoteRepository — getTopUsedTags + getTagIdByName (#3 v0.2.3)
- getTopUsedTags(limit=20): top-N (count desc, id asc) + kebab-case 필터 + deleted_at 제외
- getTagIdByName(name): COLLATE NOCASE lookup
- AI+user source 통합 카운트 (Q1=C 결정)
- 단위 +7 cases (정렬, 필터, source 통합, deleted 제외, limit, getTagIdByName)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:10:36 +09:00
altair823
853ca39c0d docs(tag-vocab): #3 plan — 8 tasks TDD + 21 단위 cases (v0.2.3)
8 task TDD plan:
T1 NoteRepository (getTopUsedTags + getTagIdByName, +7 cases)
T2 prompt.ts (PROMPT_VERSION 4 + vocab param, +4 cases, 신규 prompt.test.ts)
T3 InferenceProvider + LocalOllamaProvider (vocab passthrough, +1 case)
T4 telemetryEvents (zod schemas, +3 cases)
T5 telemetryStats (누적 + summary, +2 cases)
T6 TelemetryService EmitInput + narrowing 확장
T7 AiWorker (vocab fetch + per-tag emit, +4 cases)
T8 closure (gates + roadmap)

총 신규 단위 +21 (spec budget 19 + 2 surplus). 단위 363 → 382 (±5) 예상.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:07:28 +09:00
altair823
8206462ee4 docs(tag-vocab): #3 spec — vocab pool/telemetry/prompt 강도/재처리 결정 (v0.2.3)
mini-brainstorm 4개 결정:
- Q1=C: vocab pool = AI+user 통합 + kebab-case 필터
- Q2=A: telemetry emit 단위 = 태그별 (per-tag hit/miss)
- Q3=B: prompt 강도 = "Prefer" (우선, MUST 아님)
- Q4=A: 기존 노트 재처리 = 자연 진화 (X)

핵심 invariant 6개 + privacy invariant + tests ≥19개 약속.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:02:06 +09:00
dbbec38079 Merge pull request 'feat(retry): #2 AI retry 수동 trigger (v0.2.3 5/7)' (#17) from feat/v023-ai-retry into main
Reviewed-on: #17
2026-05-02 02:44:42 +00:00
altair823
8f56814186 fix(retry): review round 1 — minor/nit 4건 일괄 (#2 v0.2.3)
m1 — NoteRepository.test.ts 에 retryAllFailed OR IGNORE race-safe 회귀
가드 1 case 추가. failed 노트인데 pending_jobs row 가 이미 존재하는
비정상 race 상태 시뮬레이션 → INSERT OR IGNORE 라 duplicate 안 됨,
기존 attempts/next_run_at 보존.

m2 — store.retryAllFailed 의 r.count 무시 의도 주석 1줄.
단일 process (Electron) 환경 + 모든 ai_status='failed' 가 retry 대상이라
사용자 시점 카운트는 0 reset 가 정확.

n1 — AiWorker unreachableBackoffStep increment 명료화.
Math.min(..., length-1) → 명시적 if 가드 (step < length-1) 로 cap 도달 시
no-op 의도 가시화. 동작 동일.

n2 — AiWorker.processJob 의 max 의미 주석 1줄. unreachable/timeout 분기는
attempt -= 1 로 인덱스 stay 라 max 무관 — future maintainer 위해 명시.

n3 (FailedBanner inline style) 은 v0.2.4 backlog (banner theme cleanup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 03:47:08 +09:00
altair823
95bbe9cd22 chore(retry): #2 closure — gates verified + roadmap mark complete
- typecheck 0 errors
- 단위 362/362 (T1~T7 누적 18 신규)
- e2e 1/1
- roadmap §3 #2 ✓ 완료 마커

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 03:37:34 +09:00
altair823
e4a0be15ae feat(retry): tray '지금 AI 처리' 9th callback + main wiring (#2 v0.2.3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 03:36:44 +09:00
altair823
406a5e61f0 feat(retry): FailedBanner + App.tsx mount (#2 v0.2.3) 2026-05-02 03:34:09 +09:00
altair823
3ebd3bc9a5 feat(retry): store retryAllFailed action + failedCount (#2 v0.2.3) 2026-05-02 03:32:01 +09:00
altair823
6e5f3703d7 feat(retry): CaptureService.retryAllFailed + IPC 2 channels (#2 v0.2.3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 03:28:11 +09:00
altair823
12c267aabd feat(retry): telemetry ai_retry_manual + stats AI 수동 재시도 (#2 v0.2.3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 03:24:31 +09:00
altair823
449eb76683 feat(retry): AiWorker unreachable/timeout 무한 retry — 15분 cap (#2 v0.2.3) 2026-05-02 03:19:43 +09:00
altair823
2e3f0edffd feat(retry): NoteRepository — findFailedIds/countFailed/retryAllFailed/setNextRunAt (#2 v0.2.3) 2026-05-02 03:15:05 +09:00
altair823
821db4001d docs(plan): v0.2.3 #2 AI retry / 수동 trigger 구현 계획
8 task TDD 분할 + 단위 ≥ 18개 (spec §6 의 17개 충족 + 1 over):
- T1 NoteRepository — findFailedIds/countFailed/retryAllFailed/setNextRunAt
- T2 AiWorker unreachable/timeout 무한 retry (15분 cap)
- T3 telemetry ai_retry_manual + stats
- T4 CaptureService.retryAllFailed + IPC 2채널
- T5 store retryAllFailed action + failedCount
- T6 FailedBanner + App.tsx mount
- T7 tray '지금 AI 처리' 9th callback
- T8 closure

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 03:08:06 +09:00
altair823
f50cabcc62 docs(spec): v0.2.3 #2 AI retry / 수동 trigger design
mini-brainstorm 결정 3개:
- Q1=A unreachable backoff cap 15분 (30s→60s→120s→240s→480s→900s)
- Q2=A timeout 도 unreachable 동일 (무한 retry, attempts 증가 안 함)
- Q3=A retry-all 만 (per-note 버튼 v0.2.4)

AiWorker unreachable/timeout 무한 retry + schema/other max 3 유지
+ retryAllFailed atomic + FailedBanner (Inbox stack 4번째)
+ tray '지금 AI 처리 (실패 N건)' 9th callback
+ ai_retry_manual telemetry.

roadmap §3 #2 deviation 1건 (timeout) 의식적 — v0.2.4 dogfood 데이터로 영구 hang 케이스 식별 후 가다듬기.

T1-T8 작업 순서 + 단위 ≥ 17개.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 03:00:49 +09:00
37292f1a53 Merge pull request 'feat(ollama): #1 Ollama 회복 polling (v0.2.3 4/7)' (#16) from feat/v023-ollama into main
Reviewed-on: #16
2026-05-01 17:08:44 +00:00