feat(retry): #2 AI retry 수동 trigger (v0.2.3 5/7) #17

Merged
altair823 merged 11 commits from feat/v023-ai-retry into main 2026-05-02 02:44:45 +00:00
Owner

Summary

v0.2.3 cut 7항목 중 5번째 — AiWorker 의 unreachable/timeout 실패는 attempts 증가 없이 무한 retry (15분 cap exponential), schema/other 만 max 3 후 markAiFailed (기존 정책). 사용자 '지금 AI 처리 (실패 N건)' 트레이/배너 trigger 로 모든 ai_status='failed' 노트 일괄 재투입.

선행: PR #13 (#7), #14 (#4), #15 (#5), #16 (#1).

Decisions (mini-brainstorm)

Q 결정
Q1 unreachable backoff cap A — 15분 exponential (30s→60s→120s→240s→480s→900s)
Q2 timeout 분류 A — unreachable 동일 (무한 retry)
Q3 per-note retry A — retry-all 만 (per-note v0.2.4)

Architecture

  • AiWorker.processJob 의 catch 분기 — classifyReason 결과로 unreachable/timeout 은 in-place loop + sleep + attempt -=1 (markAiFailed 안 함, ai_failed emit 안 함). schema/other 는 기존 max 3 경로.
  • unreachableBackoffStep 인스턴스 state, 성공 시 reset.
  • NoteRepository: findFailedIds + countFailed + retryAllFailed (atomic transaction, INSERT OR IGNORE pending_jobs) + setNextRunAt (attempts 변경 없음).
  • CaptureService.retryAllFailed — repo 호출 + per-id worker.enqueue + ai_retry_manual emit (failedCount ≥ 1 invariant).
  • IPC: inbox:retryAllFailed, inbox:failedCount.
  • store: failedCount + retryAllFailed action (낙관적 갱신 failedCount=0).
  • UI: FailedBanner (Inbox stack PendingBanner-ExpiryBanner 사이) + tray '지금 AI 처리 (실패 N건)' 9th callback.

roadmap deviation

§3 #2 In 의 'timeout 도 attempts 증가' 와 Q2=A (timeout=unreachable) 이 충돌. 의식적 — v0.2.4 dogfood 데이터로 영구 hang 케이스 식별 후 가다듬기.

Telemetry

  • ai_retry_manual { failedCount: int.positive() } — strict, ≥1 invariant. empty 결과 emit 안 함.
  • stats.md: AI 수동 재시도 N회 / 누적 M건.
  • 기존 ai_failed.reason 통계 — 자연히 schema/other 만 누적 (unreachable/timeout markAiFailed 안 호출).

Tests

18 신규 단위 (spec §6 의 17개 충족 + 1 over):

  • T1 Repo: 5 (findFailedIds / countFailed / retryAllFailed atomic / empty / setNextRunAt)
  • T2 AiWorker: 6 (unreachable / timeout / schema max 3 / other max 3 / backoff cap / step reset)
  • T3 telemetry zod + stats: 4 (3 events + 1 stats)
  • T4 CaptureService: 2 (정상 + empty)
  • T5 store: 1

Gates

  • typecheck 0 errors
  • 단위 362/362 (30 files)
  • e2e 1/1

Test plan

  • ollama 끄고 새 노트 → AiWorker 무한 retry (markAiFailed 안 함, ai_status=pending 유지). FailedBanner 미노출.
  • 잘못된 모델 시뮬레이션 (또는 schema 강제) → max 3 후 ai_status=failed → FailedBanner 1건 + tray '지금 AI 처리 (실패 1건)' 활성.
  • '재시도' 클릭 (banner 또는 tray) → confirm 없이 즉시 재투입 → PendingBanner N건 → 처리 진행.
  • 재시도 후 ai_succeeded 또는 다시 fail 시 FailedBanner 자동 갱신 (refreshTrayFailedCount).
  • app quit 시 health.stop + trayInterval clean (#1 cut 통합 핸들러).

Refs

  • spec: docs/superpowers/specs/2026-05-01-v023-ai-retry-design.md
  • plan: docs/superpowers/plans/2026-05-01-v023-ai-retry.md
  • roadmap: docs/superpowers/specs/2026-05-01-v023-feedback-roadmap-design.md §3 #2

🤖 Generated with Claude Code

## Summary v0.2.3 cut 7항목 중 5번째 — AiWorker 의 unreachable/timeout 실패는 attempts 증가 없이 무한 retry (15분 cap exponential), schema/other 만 max 3 후 markAiFailed (기존 정책). 사용자 '지금 AI 처리 (실패 N건)' 트레이/배너 trigger 로 모든 ai_status='failed' 노트 일괄 재투입. 선행: PR #13 (#7), #14 (#4), #15 (#5), #16 (#1). ## Decisions (mini-brainstorm) | Q | 결정 | |---|------| | Q1 unreachable backoff cap | A — 15분 exponential (30s→60s→120s→240s→480s→900s) | | Q2 timeout 분류 | A — unreachable 동일 (무한 retry) | | Q3 per-note retry | A — retry-all 만 (per-note v0.2.4) | ## Architecture - AiWorker.processJob 의 catch 분기 — classifyReason 결과로 unreachable/timeout 은 in-place loop + sleep + attempt -=1 (markAiFailed 안 함, ai_failed emit 안 함). schema/other 는 기존 max 3 경로. - unreachableBackoffStep 인스턴스 state, 성공 시 reset. - NoteRepository: findFailedIds + countFailed + retryAllFailed (atomic transaction, INSERT OR IGNORE pending_jobs) + setNextRunAt (attempts 변경 없음). - CaptureService.retryAllFailed — repo 호출 + per-id worker.enqueue + ai_retry_manual emit (failedCount ≥ 1 invariant). - IPC: inbox:retryAllFailed, inbox:failedCount. - store: failedCount + retryAllFailed action (낙관적 갱신 failedCount=0). - UI: FailedBanner (Inbox stack PendingBanner-ExpiryBanner 사이) + tray '지금 AI 처리 (실패 N건)' 9th callback. ## roadmap deviation §3 #2 In 의 'timeout 도 attempts 증가' 와 Q2=A (timeout=unreachable) 이 충돌. 의식적 — v0.2.4 dogfood 데이터로 영구 hang 케이스 식별 후 가다듬기. ## Telemetry - ai_retry_manual { failedCount: int.positive() } — strict, ≥1 invariant. empty 결과 emit 안 함. - stats.md: AI 수동 재시도 N회 / 누적 M건. - 기존 ai_failed.reason 통계 — 자연히 schema/other 만 누적 (unreachable/timeout markAiFailed 안 호출). ## Tests 18 신규 단위 (spec §6 의 17개 충족 + 1 over): - T1 Repo: 5 (findFailedIds / countFailed / retryAllFailed atomic / empty / setNextRunAt) - T2 AiWorker: 6 (unreachable / timeout / schema max 3 / other max 3 / backoff cap / step reset) - T3 telemetry zod + stats: 4 (3 events + 1 stats) - T4 CaptureService: 2 (정상 + empty) - T5 store: 1 ## Gates - typecheck 0 errors - 단위 362/362 (30 files) - e2e 1/1 ## Test plan - [ ] ollama 끄고 새 노트 → AiWorker 무한 retry (markAiFailed 안 함, ai_status=pending 유지). FailedBanner 미노출. - [ ] 잘못된 모델 시뮬레이션 (또는 schema 강제) → max 3 후 ai_status=failed → FailedBanner 1건 + tray '지금 AI 처리 (실패 1건)' 활성. - [ ] '재시도' 클릭 (banner 또는 tray) → confirm 없이 즉시 재투입 → PendingBanner N건 → 처리 진행. - [ ] 재시도 후 ai_succeeded 또는 다시 fail 시 FailedBanner 자동 갱신 (refreshTrayFailedCount). - [ ] app quit 시 health.stop + trayInterval clean (#1 cut 통합 핸들러). ## Refs - spec: docs/superpowers/specs/2026-05-01-v023-ai-retry-design.md - plan: docs/superpowers/plans/2026-05-01-v023-ai-retry.md - roadmap: docs/superpowers/specs/2026-05-01-v023-feedback-roadmap-design.md §3 #2 🤖 Generated with Claude Code
altair823 added 10 commits 2026-05-01 18:40:58 +00:00
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>
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>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
claude-reviewer-01 approved these changes 2026-05-01 18:45:35 +00:00
Dismissed
claude-reviewer-01 left a comment
Member

round 1: APPROVE 0 blocker. spec §10.1 7항목 매핑 100%. Q1/Q2/Q3 결정 코드 정확 반영. Q2 timeout=unreachable deviation 의식적 (spec §10.3 명기). 5건 코멘트 모두 minor/nit (비차단):

  • m1: tests/unit/NoteRepository.test.ts retryAllFailed OR IGNORE race-safe 테스트 추가 권장
  • m2: store.ts retryAllFailed 가 r.count 무시 의도 주석
  • n1: AiWorker.ts:173 step cap 명료화 (가드 또는 주석)
  • n2: AiWorker.ts:124 unreachable 분기는 max 무관 코멘트
  • n3: FailedBanner inline style — v0.2.4 backlog (banner theme)
    typecheck 0 / 단위 362/362 / e2e 1/1.
round 1: APPROVE 0 blocker. spec §10.1 7항목 매핑 100%. Q1/Q2/Q3 결정 코드 정확 반영. Q2 timeout=unreachable deviation 의식적 (spec §10.3 명기). 5건 코멘트 모두 minor/nit (비차단): - m1: tests/unit/NoteRepository.test.ts retryAllFailed OR IGNORE race-safe 테스트 추가 권장 - m2: store.ts retryAllFailed 가 r.count 무시 의도 주석 - n1: AiWorker.ts:173 step cap 명료화 (가드 또는 주석) - n2: AiWorker.ts:124 unreachable 분기는 max 무관 코멘트 - n3: FailedBanner inline style — v0.2.4 backlog (banner theme) typecheck 0 / 단위 362/362 / e2e 1/1.
altair823 added 1 commit 2026-05-01 18:47:12 +00:00
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>
claude-reviewer-01 approved these changes 2026-05-01 18:49:06 +00:00
claude-reviewer-01 left a comment
Member

round 2: 4건 round 1 fix 정확. m1 OR IGNORE race test 추가 (363 +1). m2 store.ts r.count discard 주석. n1 step cap if-가드. n2 max-comment. n3 v0.2.4 backlog (#24 banner theme cleanup 흡수). Gates: typecheck 0 / 단위 363/363 / e2e 1/1. 신규 regression 0.

round 2: 4건 round 1 fix 정확. m1 OR IGNORE race test 추가 (363 +1). m2 store.ts r.count discard 주석. n1 step cap if-가드. n2 max-comment. n3 v0.2.4 backlog (#24 banner theme cleanup 흡수). Gates: typecheck 0 / 단위 363/363 / e2e 1/1. 신규 regression 0.
altair823 merged commit dbbec38079 into main 2026-05-02 02:44:45 +00:00
altair823 deleted branch feat/v023-ai-retry 2026-05-02 02:44:47 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/inkling#17