From 0541d44b7ef70fd16de520353f88247781c987bc Mon Sep 17 00:00:00 2001 From: altair823 Date: Sat, 25 Apr 2026 11:39:56 +0900 Subject: [PATCH] Bump spec + plan to v0.3: gemma4:26b + LAN Ollama endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model swap: gemma4:9b has no official checkpoint; LAN server (192.168.0.47:11434) ships gemma4:26b (25.8B Q4_K_M), which fits the §6.5 26B cap and the "high-quality" tier. Updates §1.1, §2.1 diagram, §3.1 ai_provider example, §4.1 provider name, §4.2 request body, §4.6 health check, §5.4 copy catalog, plan Task 12 (mock + default model), Task 30 main entry, Task 33 dogfood intro. Endpoint: LocalOllamaProvider now configurable via constructor opts. Task 30 main entry and integration test read INKLING_OLLAMA_ENDPOINT from env; unset falls back to localhost. LAN Ollama moves from out-of-scope to in-scope via endpoint injection; only a separate LanOllamaProvider class remains deferred. Perf target §6.4: RTX 3060-referenced <30s target no longer valid under 26B Q4_K_M + LAN. Set to <60s provisional; §8 adds a new open issue to measure real p95 during first dogfood week and confirm. §7.4 adds INKLING_OLLAMA_ENDPOINT env setup and recommends Volta over nvm-windows for Windows Node 24.15.0 install. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-04-24-inkling-vertical-slice.md | 28 ++++++++------- ...026-04-24-inkling-vertical-slice-design.md | 34 ++++++++++--------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/docs/superpowers/plans/2026-04-24-inkling-vertical-slice.md b/docs/superpowers/plans/2026-04-24-inkling-vertical-slice.md index c972a0b..205eed2 100644 --- a/docs/superpowers/plans/2026-04-24-inkling-vertical-slice.md +++ b/docs/superpowers/plans/2026-04-24-inkling-vertical-slice.md @@ -2,11 +2,11 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Plan version:** v0.2 (Strategy integration). Updated 2026-04-25. +**Plan version:** v0.3 (Gemma4:26b + LAN endpoint). Updated 2026-04-25. -**Goal:** Implement Inkling Vertical Slice v0.2 — a Windows/macOS Electron desktop app that captures text + pasted images via a global hotkey, persists them to SQLite, asynchronously generates Korean title/summary/tags via a local Ollama (`gemma4:9b`) provider, fires an OS native "post-submit reward" toast, displays results in an Inbox with AI-proposal labels, an inline "intent banner" (Strategy §2.2) after AI completes, a Weekly Continuity streak (7 notes/week, recovery-friendly copy), and a recovery toast on returning after a 7+ day gap. +**Goal:** Implement Inkling Vertical Slice v0.3 — a Windows/macOS Electron desktop app that captures text + pasted images via a global hotkey, persists them to SQLite, asynchronously generates Korean title/summary/tags via an Ollama provider (`gemma4:26b`, endpoint configurable via `INKLING_OLLAMA_ENDPOINT`, defaults to `localhost:11434`), fires an OS native "post-submit reward" toast, displays results in an Inbox with AI-proposal labels, an inline "intent banner" (Strategy §2.2) after AI completes, a Weekly Continuity streak (7 notes/week, recovery-friendly copy), and a recovery toast on returning after a 7+ day gap. -**Architecture:** Electron main/renderer split with typed IPC. Main process hosts services (HotkeyService, CaptureService, NoteRepository, MediaStore, AiWorker, ContinuityService, NotificationService, IntentService) around a single `better-sqlite3` database per profile. Renderer is React + Zustand with two windows (Inbox, QuickCapture). AI calls go through an `InferenceProvider` interface whose only slice implementation is `LocalOllamaProvider` (HTTP JSON to `localhost:11434`). +**Architecture:** Electron main/renderer split with typed IPC. Main process hosts services (HotkeyService, CaptureService, NoteRepository, MediaStore, AiWorker, ContinuityService, NotificationService, IntentService) around a single `better-sqlite3` database per profile. Renderer is React + Zustand with two windows (Inbox, QuickCapture). AI calls go through an `InferenceProvider` interface whose only slice implementation is `LocalOllamaProvider` (HTTP JSON; endpoint from `INKLING_OLLAMA_ENDPOINT` env var with `localhost:11434` fallback; model `gemma4:26b`). **Tech Stack:** Node.js 24.15.0 LTS · Electron 41 · electron-vite 2 · React 19 · TypeScript 6 · Zustand 5 · better-sqlite3 12 · zod 4 · undici 8 (HTTP mocking) · Vitest 4 · @playwright/test 1.59 · electron-log 5. @@ -1118,7 +1118,7 @@ export class NoteRepository { const { id } = repo.create({ rawText: '원문' }); repo.updateAiResult(id, { title: '제목', summary: '1줄\n2줄\n3줄', - tags: ['api-timeout', 'meeting'], provider: 'local-ollama/gemma4:9b' + tags: ['api-timeout', 'meeting'], provider: 'local-ollama/gemma4:26b' }); const note = repo.findById(id)!; expect(note.aiStatus).toBe('done'); @@ -1768,11 +1768,11 @@ describe('LocalOllamaProvider', () => { it('healthCheck ok=true when model present', async () => { mock.get('http://localhost:11434').intercept({ path: '/api/tags', method: 'GET' }).reply(200, { - models: [{ name: 'gemma4:9b' }] + models: [{ name: 'gemma4:26b' }] }); const h = await new LocalOllamaProvider().healthCheck(); expect(h.ok).toBe(true); - expect(h.model).toBe('gemma4:9b'); + expect(h.model).toBe('gemma4:26b'); }); it('healthCheck ok=false when missing', async () => { @@ -1781,7 +1781,7 @@ describe('LocalOllamaProvider', () => { }); const h = await new LocalOllamaProvider().healthCheck(); expect(h.ok).toBe(false); - expect(h.reason).toMatch(/gemma4:9b/); + expect(h.reason).toMatch(/gemma4:26b/); }); it('healthCheck ok=false on connection error', async () => { @@ -1824,7 +1824,7 @@ export class LocalOllamaProvider implements InferenceProvider { constructor(opts: LocalOllamaOptions = {}) { this.endpoint = opts.endpoint ?? 'http://localhost:11434'; - this.model = opts.model ?? 'gemma4:9b'; + this.model = opts.model ?? 'gemma4:26b'; this.timeoutMs = opts.timeoutMs ?? 120_000; this.temperature = opts.temperature ?? 0.2; this.numPredict = opts.numPredict ?? 512; @@ -1887,7 +1887,9 @@ import { LocalOllamaProvider } from '@main/ai/LocalOllamaProvider.js'; const skip = process.env.INKLING_INTEGRATION !== '1'; describe.skipIf(skip)('LocalOllamaProvider integration', () => { - const provider = new LocalOllamaProvider(); + const provider = new LocalOllamaProvider({ + endpoint: process.env.INKLING_OLLAMA_ENDPOINT + }); beforeAll(async () => { const h = await provider.healthCheck(); @@ -3577,7 +3579,7 @@ export function OllamaBanner(): React.ReactElement | null { if (status.ok) return null; const isMissing = status.reason?.includes('not installed'); const message = isMissing - ? '`ollama pull gemma4:9b` 실행 후 앱을 재시작해주세요.' + ? '`ollama pull gemma4:26b` 실행 후 앱을 재시작해주세요.' : 'Inkling 정리가 잠시 멈췄습니다. Ollama를 실행해주세요.'; return (
@@ -3664,7 +3666,9 @@ app.whenReady().then(async () => { const continuity = new ContinuityService(db); const intent = new IntentService(repo); - const provider = new LocalOllamaProvider(); + const provider = new LocalOllamaProvider({ + endpoint: process.env.INKLING_OLLAMA_ENDPOINT + }); const health = new HealthChecker(provider); void health.runOnce().then((h) => logger.info('ai.health', h)); @@ -3831,7 +3835,7 @@ git commit -m "test(e2e): smoke test verifying v0.2 inbox empty state" - [ ] **Step 3: Manual dogfood checklist (Strategy + base flows)** -Start Ollama with `gemma4:9b`. Then: +Ensure `gemma4:26b` is reachable at the endpoint defined by `INKLING_OLLAMA_ENDPOINT` (or `http://localhost:11434` if unset). Then: - [ ] Press `Ctrl+Shift+J` from another app → QuickCapture opens within ~100ms with placeholder "지금 머릿속에 있는 것 한 줄. 정리는 나중입니다." - [ ] Type "회의 중 A프로젝트 API 타임아웃 재발. 재현 로그 확보 예정." → Ctrl+Enter → window closes. diff --git a/docs/superpowers/specs/2026-04-24-inkling-vertical-slice-design.md b/docs/superpowers/specs/2026-04-24-inkling-vertical-slice-design.md index b865d16..78d7639 100644 --- a/docs/superpowers/specs/2026-04-24-inkling-vertical-slice-design.md +++ b/docs/superpowers/specs/2026-04-24-inkling-vertical-slice-design.md @@ -1,6 +1,6 @@ - # Inkling — Vertical Slice v0.2 설계 문서 + # Inkling — Vertical Slice v0.3 설계 문서 -**작성일:** 2026-04-24 (v0.1) · **개정:** 2026-04-25 (v0.2, Strategy 통합) +**작성일:** 2026-04-24 (v0.1) · **개정:** 2026-04-25 (v0.2, Strategy 통합) · 2026-04-25 (v0.3, gemma4:26b + LAN endpoint) **저자:** 김태현 (dlsrks0734@gmail.com) **대상 기획서:** `inkling.md` v1.4 **참조 문서:** `docs/superpowers/strategy/strategy.md` (심리학 기반 습관화 전략) @@ -39,7 +39,7 @@ Slice는 dogfood만을 목적으로 하며, 패키징·서명·배포·캘린더 - **플랫폼:** Windows + macOS Electron 빌드. Windows를 dogfood 우선으로 하고 macOS는 빌드 통과 및 기본 동작 수준을 유지. - **Quick Capture:** 전역 단축키 `Ctrl/Cmd+Shift+J`, 단일 입력창. 텍스트 + 클립보드 붙여넣기 이미지 입력. - **저장:** 로컬 SQLite 단일 프로필(`default`). 미디어는 프로필 디렉터리 `media/` 하위 파일로 저장. -- **AI 파이프라인:** 비동기 처리. `LocalOllamaProvider` 1개 구현체. `gemma4:9b` 표준 티어 고정. 출력은 제목 + 3줄 요약 + 태그 최대 3개. +- **AI 파이프라인:** 비동기 처리. `LocalOllamaProvider` 1개 구현체, endpoint는 `INKLING_OLLAMA_ENDPOINT` 환경변수 또는 생성자 인자로 주입(미지정 시 `http://localhost:11434` 폴백). `gemma4:26b` 고품질 티어 고정(공식 9B 체크포인트 부재; 스펙 §6.5 26B 상한 내). 출력은 제목 + 3줄 요약 + 태그 최대 3개. - **Inbox:** 날짜 내림차순 리스트. 카드에 제목·요약·태그·원문·미디어 썸네일. AI 필드 인라인 편집 및 노트 삭제 지원. 원문은 읽기 전용. - **즉시 보상 토스트 (Strategy §4.1):** Quick Capture 저장 직후 OS 네이티브 알림으로 회전 카피 1개 표시 ("이 생각은 이제 Inkling이 들고 있습니다." 등 4종). - **AI 제안형 라벨 (Strategy §7-원칙4):** AI가 생성한 제목·태그 옆에 작은 "AI" 회색 라벨. 사용자가 편집한 필드에서는 라벨이 사라져 "내 메모" 정체성 식별 가능. @@ -54,7 +54,7 @@ Slice는 dogfood만을 목적으로 하며, 패키징·서명·배포·캘린더 - 관련 메모 링크 / 프로젝트 자동 분류 / 벡터 검색 - 푸시·스케줄러·캘린더 어댑터·모닝/이브닝 프롬프트 - 다중 프로필 / 비밀번호 잠금 / 마스킹 레이어 -- LAN Ollama Provider / 외부 API Provider / BYOK 키 관리 +- 별도 `LanOllamaProvider` 클래스 / 외부 API Provider / BYOK 키 관리 (LAN Ollama는 `LocalOllamaProvider` endpoint 주입 방식으로 slice 범위 내) - Confluence 내보내기 / 프로필 zip 내보내기·재가져오기 - 주간 회고 / 뱃지 / 온보딩 위자드 - 코드 서명·공증·자동 업데이트·배포 파이프라인 @@ -108,7 +108,7 @@ Strategy 문서에서 추가로 명시 이관 (Strategy 절 번호와 함께): └──────────────────────────────────────────┘ │ HTTP ┌──────────────▼───────────────────────────┐ -│ Ollama (localhost:11434, gemma4:9b) │ +│ Ollama ({endpoint}, gemma4:26b) │ └──────────────────────────────────────────┘ ``` @@ -183,7 +183,7 @@ CREATE TABLE notes ( ai_status TEXT NOT NULL CHECK (ai_status IN ('pending','done','failed')), ai_error TEXT, -- 실패 사유 (디버그용) - ai_provider TEXT, -- 'local-ollama/gemma4:9b' + ai_provider TEXT, -- 'local-ollama/gemma4:26b' ai_generated_at TEXT, -- ISO8601 title_edited_by_user INTEGER NOT NULL DEFAULT 0 -- 0/1, AI 라벨 표시 토글 CHECK (title_edited_by_user IN (0,1)), @@ -274,7 +274,7 @@ CREATE TABLE schema_migrations ( ```ts interface InferenceProvider { - readonly name: string; // 'local-ollama/gemma4:9b' + readonly name: string; // 'local-ollama/gemma4:26b' generate(input: GenerateInput): Promise; healthCheck(): Promise; } @@ -298,8 +298,8 @@ interface HealthResult { ### 4.2 LocalOllamaProvider 구현 -- 엔드포인트: `POST http://localhost:11434/api/generate` -- 요청 본문: `{model: "gemma4:9b", prompt: <§4.3>, format: "json", stream: false, options: {temperature: 0.2, num_predict: 512}}` +- 엔드포인트: `POST {endpoint}/api/generate`. `endpoint`는 생성자 인자(`{endpoint: string}`)로 주입하며, 미지정 시 `http://localhost:11434`로 폴백. dogfood에서 LAN Ollama를 쓰기 위해 Task 30 main 엔트리에서 `process.env.INKLING_OLLAMA_ENDPOINT`를 전달. +- 요청 본문: `{model: "gemma4:26b", prompt: <§4.3>, format: "json", stream: false, options: {temperature: 0.2, num_predict: 512}}` - 타임아웃: **120초** (AbortController). - 응답 파싱: `response` 필드를 JSON.parse한 뒤 zod 스키마로 검증. 실패 시 상위에 throw(AiWorker가 재시도를 결정). @@ -346,8 +346,8 @@ Rules: ### 4.6 헬스 체크 (앱 기동 시 1회) -- `GET /api/tags` 호출로 `gemma4:9b`가 설치되어 있는지 확인. -- 모델 미설치 → Inbox 상단 배너: "`ollama pull gemma4:9b` 실행 후 앱을 재시작하세요." +- `GET /api/tags` 호출로 `gemma4:26b`가 설치되어 있는지 확인. +- 모델 미설치 → Inbox 상단 배너: "`ollama pull gemma4:26b` 실행 후 앱을 재시작하세요." - 엔드포인트 무응답 → 배너: "Ollama가 실행되지 않았습니다. `ollama serve`를 실행해주세요." Capture는 계속 허용되며 노트는 `pending` 상태로 누적된다. - 자동 pull은 slice에서 제공하지 않는다. @@ -530,7 +530,7 @@ Slice는 온보딩 위자드를 제공하지 않는다. | 즉시 보상 토스트 (회전 4종) | (§5.1 참조) | | IntentBanner 프롬프트 (회전 4종) | (§5.2 참조) | | Ollama 미실행 배너 | Inkling 정리가 잠시 멈췄습니다. Ollama를 실행해주세요. | -| Ollama 모델 미설치 배너 | `ollama pull gemma4:9b` 실행 후 앱을 재시작해주세요. | +| Ollama 모델 미설치 배너 | `ollama pull gemma4:26b` 실행 후 앱을 재시작해주세요. | | 단축키 등록 실패 배너 | 단축키 `Ctrl+Shift+J`를 다른 앱이 사용 중입니다. 충돌 앱을 닫거나 FAQ를 확인하세요. | ### 5.5 앱 종료·재시작 @@ -599,7 +599,7 @@ Slice는 온보딩 위자드를 제공하지 않는다. |------|----------| | 단축키 → Quick Capture 창 표시 | < 100ms | | Submit → 창 닫힘 | < 300ms | -| AI 완료 (gemma4:9b, 로컬, 입력 ≤ 500자, RTX 3060 12GB 기준) | < 30s (초과해도 failed 아님) | +| AI 완료 (gemma4:26b Q4_K_M, LAN, 입력 ≤ 500자) | **< 60s 잠정** (서버 GPU·LAN 지연 실측 후 §8에서 확정; 초과해도 failed 아님) | | Inbox 50건 렌더 | < 200ms | --- @@ -640,9 +640,10 @@ Slice는 온보딩 위자드를 제공하지 않는다. ### 7.4 개발 환경 전제 -- Windows 개발 머신에 **nvm-windows** 또는 **Volta**로 Node 24 LTS 설치. macOS/Linux는 **nvm** 사용. +- Windows 개발 머신에 **Volta**(권장) 또는 **nvm-windows**로 Node 24 LTS 설치. macOS/Linux는 **nvm** 사용. +- **AI Provider 엔드포인트:** `INKLING_OLLAMA_ENDPOINT` 환경변수로 Ollama 서버 URL 지정 (예: `http://192.168.0.47:11434`). 미지정 시 `http://localhost:11434` 폴백. LAN 서버 사용 시 User 환경변수에 등록 후 앱 실행. - 저장소 최초 체크아웃 흐름: - 1. `nvm install` → `.nvmrc` 기반 설치·활성화. + 1. `volta install node@24.15.0` 또는 `nvm install` → `.nvmrc` 기반 설치·활성화. 2. `npm ci` → 락파일에 고정된 정확한 트리 복원. 3. 구현 착수 시점의 실제 버전이 §7.2 표와 어긋나면 이 문서를 PR로 갱신한다. @@ -653,7 +654,8 @@ Slice는 온보딩 위자드를 제공하지 않는다. 기존 항목: - **단축키 충돌 시 변경 UI**: 설정 화면이 slice에 없으므로 FAQ/환경 변수로 임시 대응. 후속 spec에서 정식 설정 UI로 해결. - **AI 결과 수동 재처리 버튼**: dogfood 중 프롬프트 튜닝 편의가 필요하면 개발자 전용 debug 메뉴로 먼저 도입 검토. -- **Gemma 4 모델 가용성**: 설계 시점(2026-04) 공식 체크포인트 상태를 별도 확인. 미출시면 `gemma3:9b` 등 동세대 대체 모델로 임시 운용하고 스펙을 업데이트. +- **Gemma 4 모델 가용성 (확인 완료 2026-04-25)**: 공식 `gemma4:9b` 체크포인트 부재 확인. LAN 서버(`192.168.0.47:11434`)에 설치된 `gemma4:26b` (25.8B Q4_K_M)로 표준 티어 운용. 스펙 §6.5의 26B 상한과 "고품질" 티어 기준 적합. 기획서(`inkling.md`) §6.5의 "표준=9b" 권장 문구는 9B 공식 체크포인트 출시 여부 재확인 후 별도 조정. +- **§6.4 AI 완료 p95 실측 (신규)**: 26B Q4_K_M + LAN 조합의 실측 레이턴시 확보 후 p95 목표 확정. 잠정 < 60s. dogfood 첫 주 중 측정 후 본 문서에 기록. - **Electron 패키징 옵션 결정**: `electron-builder` vs `electron-forge`. Slice 바이너리는 dev 빌드로 충분하므로 후속 배포 spec에서 결정. - **Node 24 LTS → 이후 LTS 전환**: Node 24 Maintenance 진입 시점(로드맵상 2027년대)에 다음 짝수 메이저로 이관. 시점 도래 시 별도 spec.