feat(v029): ai_status 'disabled' enum + CaptureService ai_enabled 분기 (skip pending_jobs)

- AiStatus enum 'disabled' 추가 — settings.ai_enabled=false 일 때 새 노트의 초기 status.
- m005 migration: ai_status CHECK 제약을 ('pending','done','failed','disabled') 로 relax.
  SQLite 가 ALTER COLUMN CHECK 미지원 → table recreate (notes_new INSERT SELECT DROP RENAME).
  기존 인덱스 (idx_notes_created_at, idx_notes_ai_status, idx_notes_deleted_at) 재생성.
- SettingsService schema 에 ai_enabled / onboarding_completed (optional) 추가 +
  isAiEnabled / setAiEnabled / isOnboardingCompleted / setOnboardingCompleted accessor.
  기본 fallback (ai_enabled=true, onboarding_completed=false) — 기존 settings.json 무영향.
- NoteRepository.create 가 optional aiStatus 받도록 — 'pending' 외 값일 때 pending_jobs skip.
  기존 caller (rawText 만 전달) 무영향.
- CaptureService deps 에 settings (좁은 AiEnabledSource 인터페이스) 추가.
  submit() 가 ai_enabled 조회 → false 면 ai_status='disabled' insert + enqueue skip.
  settings 미주입 시 기존 동작 (항상 enabled) 보존 — 테스트 케이스 무영향.
- main/index.ts wiring: settings: settingsSvc 주입.

Tests: 489 → 494 (CaptureService ai_enabled 2건 + m005 migration 3건). typecheck 0.
This commit is contained in:
altair823
2026-05-09 15:43:01 +09:00
parent facbf54025
commit fd839f6afe
9 changed files with 238 additions and 17 deletions

View File

@@ -8,7 +8,12 @@ const OllamaSettingsSchema = z.object({
}).strict();
const SettingsSchema = z.object({
ollama: OllamaSettingsSchema.optional()
ollama: OllamaSettingsSchema.optional(),
// v0.2.9 Cut B — AI-less mode toggle. 기존 settings 파일에 없으면 isAiEnabled() 가
// true 로 fallback (기본 enabled). zod default 는 file 이 존재 + 키 부재일 때만 적용 —
// load() 의 file-missing 분기에선 cache={} 라 isAiEnabled() 의 fallback 이 작동.
ai_enabled: z.boolean().optional(),
onboarding_completed: z.boolean().optional()
}).strict();
export type Settings = z.infer<typeof SettingsSchema>;
@@ -38,6 +43,37 @@ export class SettingsService {
const validated = OllamaSettingsSchema.parse(value);
const current = await this.load();
const next: Settings = { ...current, ollama: validated };
await this.persist(next);
}
/**
* v0.2.9 Cut B — AI-less mode 의 기본값은 enabled (true). 기존 settings 파일을
* 가진 사용자 (ai_enabled 키 부재) 도 무영향.
*/
async isAiEnabled(): Promise<boolean> {
const s = await this.load();
return s.ai_enabled ?? true;
}
async setAiEnabled(value: boolean): Promise<void> {
const current = await this.load();
const next: Settings = { ...current, ai_enabled: value };
await this.persist(next);
}
/** v0.2.9 Cut B — 첫 실행 onboarding completion 표지. 기본 false. */
async isOnboardingCompleted(): Promise<boolean> {
const s = await this.load();
return s.onboarding_completed ?? false;
}
async setOnboardingCompleted(value: boolean): Promise<void> {
const current = await this.load();
const next: Settings = { ...current, onboarding_completed: value };
await this.persist(next);
}
private async persist(next: Settings): Promise<void> {
await mkdir(dirname(this.filePath), { recursive: true });
const tmpPath = this.filePath + '.tmp';
await writeFile(tmpPath, JSON.stringify(next, null, 2), 'utf8');