456 Commits

Author SHA1 Message Date
altair823
06cfa1c151 chore(release): v0.2.2 — F7 + Quick Capture 스크롤 fix
- F7 (이미 main 병합): AI-primary due_date flow, 다중 후보 추출
- fix(quickcapture): textarea min-height: 0 + .card overflow: hidden 으로 hint 노출 보장

CHANGELOG.md / package.json 0.2.1 → 0.2.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.2
2026-04-26 23:25:53 +09:00
altair823
579450ef4f docs(spec): promote F7 AI-primary due date
신규 spec 파일 추가 (구현 결과 반영). dogfood-feedback.md 의 F7
헤더 🔬 drafting → 🚀 promoted 로 갱신. F1 spec 의 후속 리스트에
F7 링크 추가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:07:06 +09:00
altair823
723dccd61d feat(ai): AI-primary due_date flow — rule as prompt candidates only
Flow 반전 (F7-D 채택):
- 기존: rule.iso ?? ai.dueDate (rule 우선)
- 신규: ai.dueDate ?? null (AI 우선)

규칙은 parseAllCandidates 로 모든 매치를 추출 → prompt 에 후보
힌트로 주입. AI 가 종합 판단. AI 실패 시 due_date null (별 fallback 없음).

해결되는 케이스: '내일 모레' → AI 가 ambiguous 인지 → null.

PROMPT_VERSION → 3. GenerateInput.dueDateCandidates 신규.
buildPrompt(rawText, todayKst, candidates) — 빈 배열일 때 hint 섹션 생략.

Tests:
- AiWorker.test.ts — 'rule priority' 테스트 → 'AI dueDate wins' flip
- AiWorker.test.ts — passes todayKst 테스트 확장 (dueDateCandidates 도 검증)
- AiWorker.test.ts — 신규 'passes parseAllCandidates result as dueDateCandidates'
- LocalOllamaProvider.test.ts / ollama-golden.test.ts — generate 호출에 dueDateCandidates: [] 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:06:12 +09:00
altair823
1c72b64c2f feat(due-date): parseAllCandidates — extract all matches (text order)
기존 parseDueDate (first-match-wins) 는 backward compat 로 보존.
parseAllCandidates 가 모든 high/medium 매치를 text 순서로 반환 — F7
AI-primary flow 의 prompt 후보 주입 입력으로 사용.

Overlapping-span suppression: 다음 주 월요일 같은 케이스에서 rule 10
(전체) 이 rule 13 (다음 주 alone) 을 포함하면 후자 매치 제거.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:04:17 +09:00
altair823
2ee45bc53c docs(plan): F7 AI-primary due date 구현 계획 (D 채택) 2026-04-26 13:00:35 +09:00
altair823
742eec00f4 docs(feedback): add F7 — Due Date 규칙 파서 합성 표현 first-match-wins 한계
v0.2.1 dogfood 첫 실증 피드백. '내일 모레' → 내일로 잘못 잡힘.
규칙 파서 한계 3 (합성/양가 / 범위 / 모호) + 5 후보 방향 (A 화이트
리스트 / B 충돌 감지 / C UI 신호 / D AI 우선 / E 규칙 폐기).
1차 A+B 작은 PR 즉시 시도 가능, 2차 C UI 신호, 후속 D/E 결정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:56:32 +09:00
altair823
a38b6fdeea chore(release): bump version to 0.2.1 + CHANGELOG
8 항목 dogfood-feedback 로드맵 (F1·F2·F3·F4-E·F5·F6-L1·F6-L2·F6-L3·F4-C·F)
한 번에 흡수. migration v2 (due_date) + pre-migration snapshot.
단위 테스트 52 → 197. 신규 npm dep 0, 시스템 dep 추가는 git CLI (이미 사전 요구).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.1
2026-04-26 11:55:44 +09:00
altair823
72e69fb53a docs(spec): promote F4-C·F cue strengthening
F4 의 6개 cue 메커니즘 중 외부 신호 없이 즉시 구현 가능한 두 가지
(C 환경 앵커, F 정체성 고리) 를 묶어 promoted spec 으로 추출. A (잠금
hook), D (variable interval prompt), B (ambient if-then) 는 dogfood
soak 측정 결과를 본 뒤 결정.

F4 헤더를 🌱 raw → 🔬 drafting (C·E·F promoted) 로 갱신하고, F4 진행
상태에 두 promoted 경로를 명시.
2026-04-26 11:49:48 +09:00
altair823
bcd1151a24 feat(cue): IdentityCounter + tray refresh — 오늘 N번 잡아뒀다
F4-C·F (cue 강화) — Inkling 의 두 표면에 정체성 신호를 추가.

F4-C 환경 앵커 (tray):
- nativeImage 색 변경은 미지원이므로 색 뱃지 대신 tooltip + 메뉴 첫
  비활성 라벨로 대체. tooltip 은 항상 `Inkling — 오늘 N`,
  메뉴 첫 항목은 N>0 일 때 `오늘 N번 잡아둠` (비활성).
- `tray.ts` 가 `refreshTray(todayCount)` 를 export 하여 main 이
  60s interval + AiWorker.onUpdate hook 에서 갱신을 트리거.
- N=0 일 때는 라벨을 띄우지 않아 메뉴가 자연스럽게 시작.

F4-F 정체성 고리 (Inbox 헤더):
- ContinuityBadge 옆에 새 IdentityCounter 컴포넌트.
- N>0 → `오늘 N번 잡아뒀다` (정체성 강화 카피).
- N=0 → `오늘은 처음 한 줄?` (priming 카피로 첫 캡처 유도).
- 갱신은 `loadInitial` / `refreshMeta` (focus + note:updated) 경로
  공유 — 별도 IPC subscription 없음.

Wiring:
- `NoteRepository.countToday()` 를 `inbox:todayCount` IPC 로 노출.
- preload bridge `getTodayCount`, `InboxApi.getTodayCount()` 타입.
- 스토어에 `todayCount: number` 필드 추가, 두 메타 fetch 경로 모두에서 갱신.

스키마 변경 없음. 197/197 unit pass, 1/1 e2e pass.
2026-04-26 11:49:09 +09:00
altair823
cca3029b7e feat(repo): countToday(now?) — KST midnight bucket count
For the F4-C·F cue strengthening surfaces (tray tooltip + Inbox identity
counter), main + renderer need a single source of truth for "오늘 N번
잡아뒀다". Implements `NoteRepository.countToday(now?)` that computes the
half-open UTC interval covering the KST calendar date of `now` and counts
rows whose `created_at` falls inside.

`now` is injectable for deterministic tests across the KST/UTC boundary
(02:00 KST and 23:00 KST land on different UTC dates yet the same / a
different KST day). Four new cases cover empty DB, KST-day filtering,
KST-midnight crossover, and the default-arg branch.
2026-04-26 11:47:03 +09:00
altair823
5b6003bdcd docs(spec): promote F6-L2 git sync MVP + README setup section
- 신규 spec docs/superpowers/specs/2026-04-26-f6-l2-git-sync.md
    결정 표 + 범위 + 시스템 의존 + 동작 요약 + 후속 후보
- 2026-04-25-dogfood-feedback.md F6 진행 상태 line 갱신:
    L2 — 🌱 raw → 🚀 promoted (MVP)
- README.md '원격 백업 (선택, F6-L2)' 섹션:
    일회 설정 (git init + remote add + 자격증명) + 사용법

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:42:16 +09:00
altair823
66725dacae feat(sync): wire SyncService — tray '지금 동기화' + on-quit drain
- src/main/index.ts: SyncService instantiate with paths.profileDir + exportSvc
- 트레이 6번째 콜백 — 토스트로 not_configured / done / unchanged / failed 안내
- before-quit 훅에 sync drain 추가 (backup 완료 후, syncSvc.isConfigured() 인 경우만)
- src/main/tray.ts: 6번째 callback runSync, '지금 동기화' 메뉴 (내보내기/복원 다음)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:40:50 +09:00
altair823
eaf66e6c10 feat(sync): SyncService — F5 export + git add/commit/push to <profileDir>/sync/
F6-L2 MVP 의 오케스트레이터.

- isConfigured(): syncDir 가 git repo + origin remote 있을 때만 true
- sync():
    1) ExportService.export(<syncDir>, includeMedia: true) — F5 트리 그대로 덮어쓰기
    2) git add -A
    3) git commit -m "chore(notes): sync <ts>"
    4) "nothing to commit" 이면 changed=false 로 정상 반환
    5) git push (upstream 미설정이면 -u origin <branch> 자동)
- GitClient.push() 에 hasUpstream() + 자동 -u 추가 (첫 push 케이스)
- 5 vitest cases — bare local remote 로 push 검증, 두 번째 sync 는 변경 없음 확인

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:39:42 +09:00
altair823
32c7becd47 feat(sync): GitClient — async wrapper for git CLI
얇은 git CLI 래퍼. F6-L2 sync MVP 의 빌딩 블록.

- run/isRepo/hasRemote/addAll/commit/push/currentBranch
- commit() 은 "nothing to commit" 을 changed=false 로 구분 (정상 path)
- 그 외 실패는 throw, exitCode + stderr 보존
- 8 vitest cases — empty file 로 GIT_CONFIG_GLOBAL/SYSTEM 격리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:37:43 +09:00
altair823
6310716fb7 docs(spec): promote F3 + F4-E copy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:31:22 +09:00
altair823
1e5847dcc6 docs: align strategy.md + slice §5.5 with F3 copy + F4-E Zeigarnik
strategy.md §1 heading and core sentence reframed to '머릿속에서
꺼내 두기' as the load-bearing user action (was '기억 구출').
§4.1 보상 카피 list updated to match shipped copy. Brief Zeigarnik-
effect rationale added per F4-E. §7 example updated.

Slice spec §5.5 카피 테이블 4개 항목 갱신 — code 와 spec 의 카피
드리프트 차단.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:30:37 +09:00
altair823
2d90a48621 feat(copy): replace '기억 구출' framing with 표면별 자연 동사 + Zeigarnik priming
F3: '구출' (rescue) is unnatural everyday Korean. Replace per-surface:
  - 트레이 '구출한 메모 보기' → '보관한 메모 보기'
  - 트레이 '기억 구출하기' → '한 줄 적기'
  - 토스트 #2 → '머릿속에서 꺼내 두었습니다.'
  - 토스트 #3 → '방금 한 줄 잡아뒀습니다.'
  - QC 힌트 'Ctrl+Enter 구출' → 'Ctrl+Enter 저장'
  - package.json description → 'local-first 한 줄 보관 도구'

F4-E (Zeigarnik priming): empty state copy reframed to evoke the
"unfinished thought tugging at memory" → "외재화로 해소" loop:
  - '첫 기억을 구출해보세요.' → '머릿속에 떠다니는 한 줄을 적어보세요.'

E2E smoke assertion updated to match. Slice §1.1 invariant 5
('실패/끊김/연속 실패' 금지) preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:29:31 +09:00
altair823
c4a7985456 docs(spec): promote F2 tag click
Marks F2 in dogfood-feedback as 🚀 promoted and adds the standalone
spec at docs/superpowers/specs/2026-04-26-f2-tag-click.md capturing
the mini-brainstorm decisions, scope, and follow-ups (multi-tag
filter, rename/merge, source preservation on undo).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:25:31 +09:00
altair823
8373f06045 feat(inbox): tag chip click = filter, separate × button + undo toast
Splits the tag chip into two actions per F2 dogfood feedback:
- short click on chip text → applies the tag to the inbox filter
  (Inbox header shows "🔎 필터: #tag (n개)" banner with ✕ 해제 button)
- × button on chip → immediately removes the tag and surfaces a
  module-level pub/sub undo toast for 5 seconds; clicking the toast
  restores the tag

`TagUndoToast` is a tiny self-contained component: `pushTagUndo()` from
NoteCard publishes an entry, the mounted `<TagUndoToast />` near the
end of `<App>` subscribes and renders it. Auto-dismiss after 5 s,
click-to-undo cancels the timer and runs the restore callback.

AI vs user tags share the same behaviour — only the chip background
colour distinguishes them, matching the F2 decision table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:25:24 +09:00
altair823
aad9d403ce feat(inbox): tagFilter store + pure selectFilteredNotes + tests
Adds `tagFilter: string | null` and `setTagFilter` to the inbox store, plus
an extracted pure `selectFilteredNotes` selector so unit tests can import it
under vitest's `node` environment without dragging `api.ts` (which touches
`window.inkling` at module load).

Tests cover four cases: null filter passes through, single-tag match,
no-match empty result, and any-tag-matches semantics.

F2 dogfood feedback step 1/3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:25:13 +09:00
altair823
ab68b19144 docs(spec): promote F1 due date
Extracted to own spec with mini-brainstorm decisions captured.
F1 in dogfood-feedback.md marked 🚀 promoted with link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:18:38 +09:00
altair823
9ab70868d1 feat(due-date): NoteCard badge + edit + IPC
Inline 📅 YYYY-MM-DD badge appears in NoteCard between summary and
tags. Click to edit (HTML date input). Past dates: gray + line-through.
AI label shown when not user-edited (mirrors title/summary AI badge
policy). Empty state shows '📅 마감일 추가' link in gray.

New IPC inbox:setDueDate routes to NoteRepository.setDueDate which
sets due_date_edited_by_user=1 (per slice §1.1 invariant 2 — user
edit blocks future AI overwrite). Preload bridge + InboxApi type
extended.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:17:57 +09:00
altair823
adae90eb61 feat(ai): AiWorker merges rule parser + AI due_date
GenerateInput gains todayKst field. AiWorker computes KST-aligned
date once per job, runs parseDueDate on rawText, calls provider.generate
with todayKst, then merges: rule.iso wins if matched (deterministic),
else AI's due_date, else null. Logs dueDateSource (rule|ai|none) for
debugging. now() injection for testability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:14:46 +09:00
altair823
4ee135dcd6 feat(ai): zod due_date field + prompt {{TODAY_KST}} injection
AiResponse extends with dueDate: string|null. zod regex
^\d{4}-\d{2}-\d{2}$, follow-up roundtrip check coerces invalid
dates (2026-13-99 etc.) to null. PROMPT_VERSION → 2: prompt now
takes todayKst arg, asks model to extract due_date as ISO or null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:12:45 +09:00
altair823
95ba1653d7 feat(due-date): pure rule parser for Korean date expressions
Regex + KST math, returns ISO YYYY-MM-DD or null. 14 high-confidence
rules (literal date, N월 N일, MM/DD, N일/주/개월 뒤, 모레/내일/글피/오늘,
다음/이번 주 X요일, 다음 달). Ambiguous tokens (월말, 주말, 퇴근 전,
시각) return iso=null + confidence='medium' so caller (AiWorker)
can defer to AI. 26+ golden fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:09:51 +09:00
altair823
0bb6c12bbb feat(db): migration v2 — due_date columns + pre-migration snapshot
ALTER TABLE notes adds due_date TEXT + due_date_edited_by_user INTEGER.
openDb takes <dbFile>.pre-v<N>.bak before running migrations
(F6-L1 follow-up #4 — preserves recoverable state if migration fails).
NoteRepository: updateAiResult accepts dueDate?, setDueDate +
edited-flag CASE WHEN guard mirroring title/summary pattern.
Note interface gains dueDate + dueDateEditedByUser fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:05:44 +09:00
altair823
cfd34c352b docs(plan): F1 due date 구현 계획 (6 tasks, migration v2) 2026-04-26 11:01:37 +09:00
altair823
9407f398c8 docs(spec): promote F6-L3 import
Extracted to own spec. F6 진행 상태 라인 갱신.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:57:14 +09:00
altair823
e728a11e09 feat(import): wire ImportService — tray '백업에서 복원...' + preview dialog
Tray gets 5th callback. Directory chooser → preview (count of
new/skip/forked + media) → confirm message box → run. Slice §1.1
copy policy preserved (no '실패'/'끊김'). Notification on
success/failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:56:29 +09:00
altair823
d76cca68df feat(import): ImportService with conflict policy + media copy
Three-state outcome per note: inserted (new id), skipped (id+rawText
match), forked (id match but rawText differs → new uuidv7 to preserve
raw_text invariant from slice §1.1). Media files copied into
MediaStore convention <profileDir>/media/{noteId}/{n}.{ext} with
new media DB rows.

NoteRepository.importNote handles full provenance: ai_status='done',
ai_provider, ai_generated_at, edited flags, intent fields, tags
with source preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:55:13 +09:00
altair823
e8587c1986 feat(import): pure parser for F5 export format
parseExportNote reverses composeMarkdown — minimal YAML parser
covering only the variants F5 emits (plain, single-quoted, block
scalar, tag/image lists). Body extraction strips h1 + blockquote +
image refs to recover rawText. Round-trip tested against
exportFormat.composeMarkdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:53:29 +09:00
altair823
fe6bbd3104 docs(plan): F6-L3 import 구현 계획 (4 tasks) 2026-04-26 10:50:01 +09:00
altair823
f4d78456ae docs(spec): promote F5 export
Extracted to own spec with mini-brainstorm decisions captured.
F5 in dogfood-feedback.md marked 🚀 promoted with link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:46:37 +09:00
altair823
27666178a2 feat(export): wire ExportService — tray '내보내기...' menu + dialog
Tray now has 4th callback that opens directory chooser, exports all
notes via ExportService with includeMedia=true default. Dialog
message warns about raw_text plain-text + recommends private location.
Native toast on success/failure with note + media counts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:44:38 +09:00
altair823
9fdfd6610c feat(export): ExportService writing frontmatter tree + media + manifest
ExportService composes pure exportFormat layer + reads from
NoteRepository.listAll (new, asc-ordered) + MediaStore.absolutePath
(new helper). Writes notes/{date-id8-slug.md}, media/{id8__n.ext},
index.jsonl, manifest.json, README.md to user-picked dir.
6 unit tests against tmp dirs + :memory: DB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:42:43 +09:00
altair823
8e09464d5e feat(export): pure frontmatter + slug + markdown + jsonl + manifest composers
Pure compose layer for F5 (Export). slugifyTitle, composeFilename,
composeFrontmatter, composeMarkdown, composeIndexJsonl, composeManifest
+ ExportNote/ExportNoteMedia/ExportNoteTag types. No fs deps.
24 unit tests covering normal cases + edge cases (null title,
forbidden chars, multiline summary needing block scalar, colon
needing single-quote, image numbering by id8__n.ext).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:39:32 +09:00
altair823
c3b650058a docs(plan): F5 export 구현 계획 (4 tasks) 2026-04-26 10:36:17 +09:00
altair823
9d63e95173 docs(spec): F6-L1 후속 항목 4개 추가 (final code review 반영)
final reviewer 가 식별한 forward-looking polish 4건을 후속 리스트에
명시. 4번째는 F1 (Due Date 마이그레이션 v2) PR 시 즉시 반영 권장:
openDb() 가 마이그레이션을 BackupService 인스턴스화 전 호출하므로
마이그레이션 결함 시 첫 실행 직전 상태 회수 불가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:17:13 +09:00
altair823
d6ead8f1c6 docs(spec): promote F6-L1 local snapshot
Extracted to its own spec, dogfood-feedback.md F6 header reflects
L1 promoted status while L2/L3 remain raw.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:13:06 +09:00
altair823
06817f2b0b feat(backup): wire BackupService — whenReady + before-quit + tray
Instantiate BackupService at app.whenReady, run daily snapshot then
again before quit (synchronous-blocking via preventDefault). Tray menu
gets '지금 백업' entry that triggers manual runDaily with native
toast feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:10:49 +09:00
altair823
4898e13308 feat(backup): runDaily() with .last-snapshot marker + rotate after snapshot
Skips when marker matches today's KST date. Marker written after
successful snapshot, before rotation. lastSnapshotAt() exposed for UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:08:30 +09:00
altair823
a728434b2e fix(backup): cleanup orphan .tmp on db.backup() failure + concurrency note
Code review I1: wrap snapshot's backup+rename in try/catch, unlink
orphan tmp file on failure so the next run does not encounter a
confusing 'existing file is not a database' error from sqlite.

Code review I2: JSDoc note that snapshot() is not safe for concurrent
calls — callers should serialize via runDaily()'s marker.

New unit test injects a fake db whose backup() rejects after a partial
write, asserts no .tmp / .sqlite remains.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:49:01 +09:00
altair823
714dd3fc9f feat(backup): atomic SQLite snapshot to inkling-YYYY-MM-DD.sqlite
KST date filename, tmp+rename atomic write, mkdir on demand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:44:28 +09:00
altair823
603588cc4f chore(backup): polish — boundary test, roundtrip lock-in, precompute today
Code reviewer minor nitpicks:
- Add test for inkling-2026-02-30.sqlite (locks roundtrip-validation contract)
- Add test for weekly window inclusive at oldest boundary
- Precompute today=startOfDayUtc(now) once outside the loop, pass to helpers

No behavior change; tests added cover existing semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:13:47 +09:00
altair823
902bc30adc chore(backup): rename WEEKLY_WINDOW_COUNT, document anchor+4 semantic
Spec reviewer flagged the weekly window keeps 5 Mondays (anchor + 4
prior), not 4 as the plan prose said. Code preserved (tests pass);
constant renamed and comment made honest about the actual semantic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:10:37 +09:00
altair823
5e8e652ee0 feat(backup): GFS retention policy (pure)
14 daily + 4 weekly (Mondays) + 6 monthly (1st). Future-dated files
preserved (clock skew). Unrecognized filenames ignored (no delete).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:07:09 +09:00
altair823
7973ea5046 docs(plan): F6-L1 local snapshot 구현 계획 (TDD, 5 tasks)
Task 1: 순수 GFS retention 함수 + 7 단위 테스트
Task 2: BackupService.snapshot() — KST 날짜·tmp+rename 원자성 + 6 단위 테스트
Task 3: runDaily() — .last-snapshot 마커 + lastSnapshotAt + 7 단위 테스트
Task 4: main/index.ts wiring (whenReady + before-quit) + tray '지금 백업'
Task 5: F6-L1 promotion (별 spec 분기 + dogfood-feedback.md 상태 갱신)

backup 위치: <profileDir>/backups/ (mini-brainstorm 결과 A 채택).
스키마 변경 0, 외부 dep 0. better-sqlite3.backup() API 가정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:51:27 +09:00
altair823
6d3df0273e docs(spec): F1~F6 dogfood 피드백 로드맵 v0.2.1 설계
8개 항목 순차 작업 (F6-L1 → F5 → F6-L3 → F1 → F2 → F3+F4-E →
F6-L2 → F4-C·F) + 데이터 안전 우선 + 머지+테스트 게이트 + 단일
v0.2.1 cut 후 dogfood 재설치 + 1주 soak. F4-A·D 는 측정 후
별 brainstorm 으로 deferred. 항목별 mini-brainstorm 에서
decision-pending 답변하는 라이프사이클.

본 spec 은 순서·범위·게이트만 정의, 항목 내부 설계는 per-item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:42:46 +09:00
altair823
2e709652fe docs(feedback): add F6 — 메모 데이터 백업 + 복원 (3-layer)
L1 로컬 원자 스냅샷 (db.backup + GFS 로테이션) + L2 git remote 마크다운
동기화 (F5 형식 그대로 추적, SQLite 바이너리 push 회피) + L3 F5+import.
gitea 자체 호스팅 인프라 활용 가능. L2 는 별 spec, L1+L3 은 슬라이스 후속.
'데이터 손실 0회' 를 slice §1.3 silent invariant 후보로 제안.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:29:49 +09:00
altair823
830c2beed3 docs(feedback): add F5 — 마크다운 일괄 export (RAG 활용)
dogfood 외부 회수 채널 부재. RAG 친화 형식으로
notes/{date}-{id8}-{slug}.md (frontmatter+본문) +
index.jsonl + manifest.json + media/ 트리 권장. 스키마는
현행으로 충분, ExportService 신규 + 트레이 메뉴 1개. F4-H5
(외부 회수) 측정의 dependency 가 됨.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:23:43 +09:00