Commit Graph

14 Commits

Author SHA1 Message Date
9a9eb2abd5 [Developer] #218 Real Gemma 4 E2B integration via flutter_gemma 0.16.5
Implements the OQ-1 follow-up to #215 v0.2.0: replace the placeholder
GemmaLlmService stub with a real flutter_gemma 0.16.5 backend driving
Gemma 4 E2B (litert-community/gemma-4-E2B-it-litert-lm, 2.41GB).

Highlights:
- GemmaLlmService.load → FlutterGemma.initialize + installModel.fromFile +
  getActiveModel; idempotent + FileSystemException on missing file.
- generateStructured uses Gemma 4 native function calling via
  createChat(tools: [Tool(...)], toolChoice: required). Stream parsed by
  collectFunctionCall — first FCR wins, ParallelFCR first-call wins,
  TextResponse/ThinkingResponse skipped, errors sanitized to prevent
  prompt leakage.
- main.dart wires _LazyLlmService adapter that resolves to GemmaLlmService
  when ModelLifecycle reports ready, MockLlmService otherwise.
- ai_providers.dart pins real model URL + SHA-256 (181938...39a63c).
- F2 hardening: ModelLifecycle.purge wraps each delete + meta remove in
  try/catch so a single OS-level flake cannot block opt-out.
- Android: INTERNET / FOREGROUND_SERVICE / POST_NOTIFICATIONS permissions
  + R8 proguard-rules.pro keeping MediaPipe / LiteRT / TFLite / protobuf
  JNI entry points (release builds otherwise crash on first inference).

Design-First: fn-gemma_llm_service.md updated to v2 — §C
(_appendSchemaInstruction) deprecated after reading flutter_gemma
0.16.5 source (Gemma 4 SDK injects tool declarations via template;
prompt-side append would double-wrap).

Tests:
- 10 new unit tests for collectFunctionCall covering all 8 fn-spec
  cases + 2 ParallelFunctionCallResponse paths.
- All 81 existing tests still pass.
- flutter analyze: 0 issues.

Refs #218

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 15:18:08 +09:00
a1f3c5f85d [Architect] #218 Real Gemma 4 + flutter_gemma 0.16.5 design spec
- docs/design/218-gemma-real-integration/README.md (Draft) — 12 섹션 + AC 10
- docs/design/218-gemma-real-integration/fn-gemma_llm_service.md (Draft) — load/generateStructured/_appendSchemaInstruction/_collectFunctionCall 4 함수 명세
- 모델: Gemma 4 E2B QAT 모바일 (HF litert-community)
- flutter_gemma 0.16.5 + ModelType.gemma4 native function calling 확인
- 신규 ADR 발행 안 함 (ADR-0003 결정 #3 유지)
- 변경 범위: gemma_llm_service.dart 본문 교체, _kModelUrl/Sha 상수 치환, main.dart 조건부 override, AndroidManifest + ProGuard
- out of scope: #219 F1 / #220 F2 광범위 / #221 AC10 / #222 keystore

Refs #218
2026-06-12 14:54:28 +09:00
ed340839a0 [Documenter] #215 Reference + guide + design Approved
- docs/reference/215-ai-frame-suggest.md — v0.2.0 모듈/함수/Riverpod/meta_kv 사양
- docs/guides/ai-help-onboarding.md — AI 도움 켜기/끄기 사용자 가이드
- docs/design/215-gemma-frame-suggest/{README,fn-suggest_frame,fn-model_lifecycle}
  상태 Draft → Approved, 추적성 헤더에 실제 구현 파일/테스트 경로 + 레퍼런스/가이드 cross-link
- docs/README.md — 현재 발행된 문서 인덱스 섹션 추가

Refs #215
2026-06-12 13:32:29 +09:00
0d1db2d1fb [Release] #215 v0.2.0 — Phase 2-A on-device Gemma frame suggestion
Bump app version 0.1.0+1 → 0.2.0+2 (minor — new feature surface).
CHANGELOG.md 신설 — v0.2.0 (#215 Phase 2-A) + v0.1.0 (#204 Phase 1) 회고 기록.

릴리스 산출물:
- APK release 58.4MB (debug 키 서명 — production 키는 future issue)
- 71 tests pass / analyze 0 issues
- 9/10 AC PASS, AC10 (한국어 corpus) DEFER → OQ-1 후속

알려진 제약:
- _kModelUrlPlaceholder 상태 (OQ-1 미해결)
- F1: 60s idle auto-unload 미구현 (stub 라 무의미)
- F2: purge() File.delete try/catch 미감쌈 (도달 불가)

Refs #215
v0.2.0
2026-06-12 13:19:05 +09:00
71e8c3dd53 [Designer] #215 Polish AI settings + frame suggestion surfaces
- frame_suggestion_dialog: hide exception detail in error path, redesign
  candidate card as Card+InkWell with L2/L3 colored level badge (secondary
  vs primary), remove confidence % surface.
- settings_screen: download tile gains state label + colored progress text,
  rounded LinearProgressIndicator, FilledButton.tonalIcon for resume/retry.
  _friendlyError() maps internal codes (network:/http /stream:/sha mismatch)
  to user-readable Korean. Opt-in/out dialogs reorganized with _Bullet rows;
  beta disclaimer reworded; _describe() friendlier copy.

Polish only — no behavior change. analyze 0, 71 tests pass, APK 10.3s.

Refs #215
2026-06-12 13:07:30 +09:00
1e019c6dc7 [Developer] #215 AC2/AC4/AC6 fixes after QA reject
QA 1차 (커밋 6ab4c0d 검증) 에서 3건 AC 미충족 → 03-Developer 반려.
본 커밋은 그 3건을 해결한다.

AC6 — _AiSuggestButton 가시성 분기 분리:
- optIn=false → 숨김 유지
- optIn=true && !ready → 노출 + disabled + Tooltip("AI 도움을 먼저 켜주세요")
- optIn=true && ready → enabled
AC5 보완: 후보의 c.level 직접 전달 (이전 "나는" 휴리스틱 제거).
FrameSuggestionDialog.show 반환 타입 String? → FrameCandidate?.

AC4 — L2:2 + L3:1 분포 강제:
- few_shot prompt 에 "정확히 L2 2개 + L3 1개" 명시
- suggestFrame 결과를 _shapeDistribution(l2Quota=2, l3Quota=1) 로 후처리
- 부족분은 패딩 X (graceful: UI 가 더 적은 카드만 표시)

AC2 — 다운로드 진행률 + 일시정지/재개 UI:
- ModelDownloadController (StateNotifier<DownloadProgress?>)
  · start() / pause() / resume() / cancel()
  · pause() 는 subscription 만 cancel, .tmp + meta_kv 유지 → resume 시 Range header 로 이어받음
- AiSettingsController.setOptIn(true) → controller.start() 자동 호출
- SettingsScreen 에 _DownloadProgressTile 추가
  · LinearProgressIndicator + bytes/total + 일시정지/재개/다시 시도 토글

회귀 테스트 9건 신규:
- test/ui/ai_suggest_button_visibility_test.dart (4): AC6 4상태 (hidden / disabled+tooltip × missing/downloading / enabled)
- test/state/model_download_controller_test.dart (3): opt-in→start, pause→paused, cancel→idle
- test/domain/ai/suggest_frame_test.dart (+3): AC4 분포 케이스 3개 (기존 take(3) 테스트 대체)

검증:
- flutter analyze → No issues found
- flutter test → 71 tests pass (62 → 71, +9 신규)
- flutter build apk --debug → 성공 (8.8s)

OQ-1 (모델 URL+SHA) 미해결 유지. MockLlmService 기본 주입 + placeholder URL 다운로드는 여전히 실패하지만, UI/스트림 wiring 은 모두 검증됨.

Refs #215

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 12:30:16 +09:00
6ab4c0da7d [Developer] #215 AI frame-suggest vertical slice (mock LlmService)
설계서대로 구현. flutter_gemma 실제 통합은 OQ-1 (모델 URL+SHA) 확정 후.
v1은 LlmService 추상 + ModelLifecycle (다운로드/SHA/purge) + Riverpod
providers + 다이얼로그 + Settings 화면까지. main.dart 가 MockLlmService 를
override 해 모든 경로가 graceful (suggest 결과는 빈 리스트).

추가:
- lib/data/ai/{llm_service,gemma_llm_service,model_lifecycle}.dart
- lib/domain/ai/{frame_candidate,few_shot_builder,parse_response,suggest_frame}.dart
- lib/state/ai_providers.dart (aiSettings + modelAvailability + frameSuggestions)
- lib/ui/screens/settings_screen.dart (opt-in 토글 + 모델 상태 표시)
- lib/ui/widgets/frame_suggestion_dialog.dart (후보 3개 카드 + 다시 시도)
- HabitCreateScreen: "AI 제안" 버튼 (opt-in + ready 일 때만 노출)
- MetaDao.remove(key) 추가 (purge 용)

테스트 31개 신규 추가 (총 62개 통과):
- test/domain/ai/{suggest_frame, few_shot_builder, parse_response}_test.dart
- test/data/ai/model_lifecycle_test.dart (download/SHA/purge/availability)

flutter analyze 0 issue, flutter build apk --debug 통과.

Refs #215
2026-06-12 12:08:25 +09:00
d31b17f3e8 [Architect] #215 ADR-0003 + design spec for Gemma frame suggest
- ADR-0003: on-device LLM Gemma 4 E2B Q4_0 + flutter_gemma 도입 결정.
  5개 대안(클라우드/정적확장/Llama/E4B/APK번들) 기각 사유 명시.
- docs/design/215-gemma-frame-suggest/: 설계서 게이트 통과 산출물.
  README.md (12 섹션 전부 + AC10 + OQ6 + 함수 15개) +
  fn-suggest_frame.md (suggestFrame/buildFewShotPrompt/parseFrameCandidates) +
  fn-model_lifecycle.md (LlmService/GemmaLlmService/ModelLifecycle).
- graceful degradation 전면: AI 실패 시 throw 없이 빈 리스트 + 수동 입력 유지.
- LlmService 추상화로 도메인 ↔ flutter_gemma 경계 분리 (테스트 가능성).

Refs #215
2026-06-12 11:16:15 +09:00
8fe6a8f378 [Developer] #204 Phase 1 MVP — Flutter app skeleton complete
- Drift 21 tables (8 catalog + 11 user + habit_dose_variants + meta_kv)
  with R1~R10 CHECK constraints and 19 indexes
- 8 hand-crafted seed JSON catalogs in app/assets/seed/
  (refs 84, protocols 34, methodologies 21, frame_patterns 30,
   reward_menu_items 30, break_protocols 8, common_frames 5, diet_patterns 5)
- 6 domain functions: recommend_variant, compute_streak,
  validate_frame_level, active_habit_quota, weekly_minimum_ratio,
  seed_importer (transactional, idempotent)
- 4 vertical-slice Riverpod screens: HabitList, HabitCreate, CheckIn, Streak
- 31 unit tests passing; flutter analyze clean
- OQ-5 streak semantics: missing entry ≠ explicit blank
  (missing = end of history; only TrackerValue.blank triggers Never-miss-twice)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 10:33:03 +09:00
29befe4d97 [Architect] Refs #204 — apply OQ decisions: diet_pattern (19th), ADR-0002 normalize dose_variants
- OQ-1: dose_variants 정규화 결정을 ADR-0002 로 승격 (ADR-0001 = 왜, ADR-0002 = 어떻게).
- OQ-3: nutrition diet 패턴 5개를 별도 diet_pattern 카탈로그(19번째 SoT)로 분리.
  · 02-catalog §8 신규, 인덱스 IDX_diet_patterns_evidence / IDX_diet_patterns_kfit.
  · 05-seed: diet_patterns.json (5행) 추가, 로딩 순서 끝에 배치.
  · 04-migrations: v1 테이블 합계 = Catalog 8 + User 11 + 부속 1 + meta_kv = 21.
- README §2/§3/§6/§11 갱신: 18→19 SoT, AC-2 에 diet_pattern=5 검증 추가.
- README §12 OQ → Resolved Open Questions 표 (OQ-1~OQ-8 결정 결과).
- habit_dose_variant → habit_dose_variants 표기 통일.
- fn-weekly-minimum-ratio, 03-drift-schema-user 의 ADR-0002 cross-link.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 17:13:04 +09:00
b8e563176b [Architect] #204 design spec — Flutter bootstrap + 18 tables
Phase 1 설계서 작성 완료. docs/design/204-flutter-bootstrap/ 13 개 파일:
- README.md (12 섹션 모두 채움, 함수 19 개 명세, AC 16 항)
- 01-project-structure.md (feature-first + layer-first 하이브리드)
- 02-drift-schema-catalog.md (Catalog 7 테이블 Dart 정의)
- 03-drift-schema-user.md (User 11 테이블 + R1~R10 강제 매트릭스)
- 04-migrations.md (schemaVersion v1 + 인덱스 17 개)
- 05-seed-data.md (assets/seed/*.json + first-run import)
- 06-ux-contracts.md (체크인 R8 ≤ 60 초 흐름)
- fn-recommend-variant / fn-compute-streak / fn-weekly-minimum-ratio
- fn-validate-frame-level / fn-active-habit-quota / fn-seed-importer

핵심 결정: dose_variants 는 별도 habit_dose_variant 테이블로 정규화 (FK
무결성 + recommendVariant SQL 단순성). ADR-0002 승격 권장.

Refs #204

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 16:57:31 +09:00
bcfc6b2402 [Architect] Refs #204 — adopt dose_variants design (ADR-0001)
Resolves the long-pending dose variant decision before Phase 1 schema work:
- habit.dose_variants[] added (id/label/dose_text/context_tags/condition_tags/is_minimum), no cardinality cap.
- tracker_entry gains variant_id + context_snapshot{location,condition}.
- reflection gains weekly minimum_ratio (hint-only).
- data-model.md: R9 (≤4) retired; new R9 (no cap, UX-side enforcement) + R10 (weekly minimum_ratio hint, no threshold).
- docs/adr/0001-dose-variants.md captures rationale + alternatives.

Why now: Phase 1 (#204) needs the final schema shape before Drift DDL is drawn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 16:37:35 +09:00
957dd90725 Apply ch-bootstrap convention (partial)
Existing life-helper already has the Redmine project (id=12), 8 persona
categories, and Gitea remote, so this commit applies only the local-side
pieces of the cloud-handson convention:

- .claude/settings.json (safe-but-maximal permissions: allow-all + deny dangerous)
- .claude/agents/ (8 persona subagents: planner/architect/designer/developer/reviewer/qa/release/documenter)
- .claude/workflows/persona-pipeline.js
- CLAUDE.md (Design-First hard gate)
- docs/ skeleton (Diátaxis + ADR + design templates + QUEUE-PROTOCOL)
- scripts/enqueue.sh
- .env.example

.gitignore: switched .claude/ blanket-ignore to .claude/settings.local.json
so the new settings/agents/workflows actually commit. .env and nutrition/ stay ignored.

Existing root SoT files (huberman-protocols.md, habit-*.md, data-model.md,
schema/) are untouched.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-11 15:59:39 +09:00
3141bf005f Initial commit — life-helper Phase 1~5 완료 상태
4 SoT 모듈:
- huberman-protocols.md (v4, 29 Huberman 원자 프로토콜, 33 인용 검증 완료)
- habit-todo-methodologies.md (21 방법론 + 6 가드레일 + 5-Tier Reward + L0~L3 프레임)
- habit-breaking-protocols.md (끊기 8 카테고리 + 공통 5 프레임 + 의학적 면책)
- nutrition/ 별도 Gitea repo (joungmin/nutrition) — .gitignore 처리

데이터 모델:
- data-model.md — entity 관계 + R1~R8 운영 규칙 + 예시
- schema/ — JSON Schema 18개 (Draft 2020-12), R1~R8을 enum + application + UI 3층 강제

운영 가드레일:
- 동시 active build ≤ 3, break ≤ 1
- 일일 운영 ≤ 2분
- frame.level ∈ {L2, L3} (L0/L1 거부)
- tracker_entry.value = done/blank 2값
- phase = 6주 사이클
- reward는 누적 milestone(T0~T4)만, 매일 직후 X
- "Never miss twice"

진행 중: Dose Variant 설계 결정 대기 (SESSION-LOG.md Pending #1)
2026-06-08 14:47:17 +09:00