Bump spec + plan to v0.4: downsize gemma4:26b → gemma4:e4b
User reassessed model needs: 26b is overkill for slice-scope tasks (short Korean title + 3-line summary + ≤3 kebab-case tags). e4b (8.0B Q4_K_M, efficiency variant) sits closest to the original "표준 9b" tier in spec §6.5 and gives faster responses with adequate margin for the slice's simple structuring work. Spec changes: - Header bumped to v0.4 with new revision line. - §1.1 retiers AI 파이프라인 from "고품질 26B" to "표준 e4b". - §2.1 diagram, §3.1 ai_provider example, §4.1 provider name, §4.2 request body, §4.6 health check, §5.4 copy catalog: model string swapped 26b → e4b. - §6.4 perf target restored to <30s provisional (e4b is similar scale to original 9b reference, 60s margin no longer needed). - §8 open issue rewritten: e4b is the standard-tier substitute, with 26b/e2b documented as quality-up / speed-up fallback paths if dogfood reveals issues with Korean output or tag formatting. Plan changes: - Header bumped to v0.4 with restated Goal. - Architecture summary, Task 12 unit test mocks/expectations, LocalOllamaProvider default model, copy banner, Task 33 dogfood prerequisite: 26b → e4b throughout. Tier ladder for dogfood escalation (from §8): e2b (5.1B, speed) ← e4b (8.0B, default) → 26b (25.8B, quality) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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.3 (Gemma4:26b + LAN endpoint). Updated 2026-04-25.
|
||||
**Plan version:** v0.4 (gemma4:e4b standard tier, downsized from 26b). Updated 2026-04-25.
|
||||
|
||||
**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.
|
||||
**Goal:** Implement Inkling Vertical Slice v0.4 — 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:e4b`, 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; endpoint from `INKLING_OLLAMA_ENDPOINT` env var with `localhost:11434` fallback; model `gemma4:26b`).
|
||||
**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:e4b`).
|
||||
|
||||
**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:26b'
|
||||
tags: ['api-timeout', 'meeting'], provider: 'local-ollama/gemma4:e4b'
|
||||
});
|
||||
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:26b' }]
|
||||
models: [{ name: 'gemma4:e4b' }]
|
||||
});
|
||||
const h = await new LocalOllamaProvider().healthCheck();
|
||||
expect(h.ok).toBe(true);
|
||||
expect(h.model).toBe('gemma4:26b');
|
||||
expect(h.model).toBe('gemma4:e4b');
|
||||
});
|
||||
|
||||
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:26b/);
|
||||
expect(h.reason).toMatch(/gemma4:e4b/);
|
||||
});
|
||||
|
||||
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:26b';
|
||||
this.model = opts.model ?? 'gemma4:e4b';
|
||||
this.timeoutMs = opts.timeoutMs ?? 120_000;
|
||||
this.temperature = opts.temperature ?? 0.2;
|
||||
this.numPredict = opts.numPredict ?? 512;
|
||||
@@ -3579,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:26b` 실행 후 앱을 재시작해주세요.'
|
||||
? '`ollama pull gemma4:e4b` 실행 후 앱을 재시작해주세요.'
|
||||
: 'Inkling 정리가 잠시 멈췄습니다. Ollama를 실행해주세요.';
|
||||
return (
|
||||
<div className="banner warn">
|
||||
@@ -3835,7 +3835,7 @@ git commit -m "test(e2e): smoke test verifying v0.2 inbox empty state"
|
||||
|
||||
- [ ] **Step 3: Manual dogfood checklist (Strategy + base flows)**
|
||||
|
||||
Ensure `gemma4:26b` is reachable at the endpoint defined by `INKLING_OLLAMA_ENDPOINT` (or `http://localhost:11434` if unset). Then:
|
||||
Ensure `gemma4:e4b` 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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Inkling — Vertical Slice v0.3 설계 문서
|
||||
# Inkling — Vertical Slice v0.4 설계 문서
|
||||
|
||||
**작성일:** 2026-04-24 (v0.1) · **개정:** 2026-04-25 (v0.2, Strategy 통합) · 2026-04-25 (v0.3, gemma4:26b + LAN endpoint)
|
||||
**작성일:** 2026-04-24 (v0.1) · **개정:** 2026-04-25 (v0.2, Strategy 통합) · 2026-04-25 (v0.3, gemma4:26b + LAN endpoint) · 2026-04-25 (v0.4, gemma4:e4b 표준 티어로 다운사이즈)
|
||||
**저자:** 김태현 (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개 구현체, endpoint는 `INKLING_OLLAMA_ENDPOINT` 환경변수 또는 생성자 인자로 주입(미지정 시 `http://localhost:11434` 폴백). `gemma4:26b` 고품질 티어 고정(공식 9B 체크포인트 부재; 스펙 §6.5 26B 상한 내). 출력은 제목 + 3줄 요약 + 태그 최대 3개.
|
||||
- **AI 파이프라인:** 비동기 처리. `LocalOllamaProvider` 1개 구현체, endpoint는 `INKLING_OLLAMA_ENDPOINT` 환경변수 또는 생성자 인자로 주입(미지정 시 `http://localhost:11434` 폴백). `gemma4:e4b` (8.0B Q4_K_M, 효율 변형) 표준 티어 고정 — 공식 9B 체크포인트 부재 시 가장 근접한 대체. 출력은 제목 + 3줄 요약 + 태그 최대 3개.
|
||||
- **Inbox:** 날짜 내림차순 리스트. 카드에 제목·요약·태그·원문·미디어 썸네일. AI 필드 인라인 편집 및 노트 삭제 지원. 원문은 읽기 전용.
|
||||
- **즉시 보상 토스트 (Strategy §4.1):** Quick Capture 저장 직후 OS 네이티브 알림으로 회전 카피 1개 표시 ("이 생각은 이제 Inkling이 들고 있습니다." 등 4종).
|
||||
- **AI 제안형 라벨 (Strategy §7-원칙4):** AI가 생성한 제목·태그 옆에 작은 "AI" 회색 라벨. 사용자가 편집한 필드에서는 라벨이 사라져 "내 메모" 정체성 식별 가능.
|
||||
@@ -108,7 +108,7 @@ Strategy 문서에서 추가로 명시 이관 (Strategy 절 번호와 함께):
|
||||
└──────────────────────────────────────────┘
|
||||
│ HTTP
|
||||
┌──────────────▼───────────────────────────┐
|
||||
│ Ollama ({endpoint}, gemma4:26b) │
|
||||
│ Ollama ({endpoint}, gemma4:e4b) │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -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:26b'
|
||||
ai_provider TEXT, -- 'local-ollama/gemma4:e4b'
|
||||
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:26b'
|
||||
readonly name: string; // 'local-ollama/gemma4:e4b'
|
||||
generate(input: GenerateInput): Promise<GenerateResult>;
|
||||
healthCheck(): Promise<HealthResult>;
|
||||
}
|
||||
@@ -299,7 +299,7 @@ interface HealthResult {
|
||||
### 4.2 LocalOllamaProvider 구현
|
||||
|
||||
- 엔드포인트: `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}}`
|
||||
- 요청 본문: `{model: "gemma4:e4b", 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:26b`가 설치되어 있는지 확인.
|
||||
- 모델 미설치 → Inbox 상단 배너: "`ollama pull gemma4:26b` 실행 후 앱을 재시작하세요."
|
||||
- `GET /api/tags` 호출로 `gemma4:e4b`가 설치되어 있는지 확인.
|
||||
- 모델 미설치 → Inbox 상단 배너: "`ollama pull gemma4:e4b` 실행 후 앱을 재시작하세요."
|
||||
- 엔드포인트 무응답 → 배너: "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:26b` 실행 후 앱을 재시작해주세요. |
|
||||
| Ollama 모델 미설치 배너 | `ollama pull gemma4:e4b` 실행 후 앱을 재시작해주세요. |
|
||||
| 단축키 등록 실패 배너 | 단축키 `Ctrl+Shift+J`를 다른 앱이 사용 중입니다. 충돌 앱을 닫거나 FAQ를 확인하세요. |
|
||||
|
||||
### 5.5 앱 종료·재시작
|
||||
@@ -599,7 +599,7 @@ Slice는 온보딩 위자드를 제공하지 않는다.
|
||||
|------|----------|
|
||||
| 단축키 → Quick Capture 창 표시 | < 100ms |
|
||||
| Submit → 창 닫힘 | < 300ms |
|
||||
| AI 완료 (gemma4:26b Q4_K_M, LAN, 입력 ≤ 500자) | **< 60s 잠정** (서버 GPU·LAN 지연 실측 후 §8에서 확정; 초과해도 failed 아님) |
|
||||
| AI 완료 (gemma4:e4b Q4_K_M, LAN, 입력 ≤ 500자) | **< 30s 잠정** (서버 GPU·LAN 지연 실측 후 §8에서 확정; 초과해도 failed 아님) |
|
||||
| Inbox 50건 렌더 | < 200ms |
|
||||
|
||||
---
|
||||
@@ -654,7 +654,7 @@ Slice는 온보딩 위자드를 제공하지 않는다.
|
||||
기존 항목:
|
||||
- **단축키 충돌 시 변경 UI**: 설정 화면이 slice에 없으므로 FAQ/환경 변수로 임시 대응. 후속 spec에서 정식 설정 UI로 해결.
|
||||
- **AI 결과 수동 재처리 버튼**: dogfood 중 프롬프트 튜닝 편의가 필요하면 개발자 전용 debug 메뉴로 먼저 도입 검토.
|
||||
- **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 공식 체크포인트 출시 여부 재확인 후 별도 조정.
|
||||
- **Gemma 4 모델 가용성 (확인 완료 2026-04-25)**: 공식 `gemma4:9b` 체크포인트 부재 확인. LAN 서버(`192.168.0.47:11434`)에 설치된 `gemma4:e4b` (8.0B Q4_K_M, 효율 변형)로 표준 티어 운용 — 9B 표준 티어에 가장 근접. e variant는 동일 파라미터 클래스의 효율 변형으로, 한국어 출력 품질·kebab-case 태그 형식 준수가 dogfood 1주차 검증 대상. 품질 부족 확인 시 `gemma4:26b`(고품질, 25.8B Q4_K_M)로 상향, 속도 우선 시 `gemma4:e2b`(경량, 5.1B)로 하향. 기획서(`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.
|
||||
|
||||
Reference in New Issue
Block a user