Bump spec + plan to v0.3: gemma4:26b + LAN Ollama endpoint

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) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-04-25 11:39:56 +09:00
parent ccc166917a
commit 0541d44b7e
2 changed files with 34 additions and 28 deletions

View File

@@ -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 (
<div className="banner warn">
@@ -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.

View File

@@ -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<GenerateResult>;
healthCheck(): Promise<HealthResult>;
}
@@ -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.