feat(tag-vocab): #3 태그 vocab — prompt + telemetry (v0.2.3 6/7) #18

Merged
altair823 merged 14 commits from feat/v023-tag-vocab into main 2026-05-02 03:53:04 +00:00
Owner

Summary

v0.2.3 dogfood feedback roadmap §3 #3 cut. AI prompt 에 vocab 주입 + per-tag hit/miss telemetry.

mini-brainstorm 4개 결정:

  • Q1=C: vocab pool = AI+user 통합 + kebab-case 필터
  • Q2=A: telemetry emit 단위 = 태그별
  • Q3=B: prompt 강도 = "Prefer" (우선)
  • Q4=A: 기존 노트 재처리 = 자연 진화 (X)

Changes

  • NoteRepository: getTopUsedTags(20) + getTagIdByName(name)
  • prompt.ts: PROMPT_VERSION 3 → 4, vocab 4번째 param, vocabBlock with "Prefer reusing"
  • InferenceProvider/LocalOllamaProvider: vocab passthrough
  • AiWorker: vocab fetch + per-tag hit/miss emit (after ai_succeeded, vocab snapshot frozen)
  • telemetry: tag_vocab_hit/miss zod schema (.strict for privacy) + stats 누적 + 적중률 summary
  • TelemetryService.EmitInput union 13 → 15

Spec & plan

  • Spec: docs/superpowers/specs/2026-05-02-v023-tag-vocab-design.md
  • Plan: docs/superpowers/plans/2026-05-02-v023-tag-vocab.md

Test Plan

  • typecheck 0
  • 단위 363 → 384 (+21)
  • e2e 1/1
  • dogfood: vocab 라인이 실제 Ollama 프롬프트에 들어가는지 확인
  • dogfood: 일정 시간 후 stats.md 의 태그 vocab 적중률 확인
## Summary v0.2.3 dogfood feedback roadmap §3 #3 cut. AI prompt 에 vocab 주입 + per-tag hit/miss telemetry. mini-brainstorm 4개 결정: - Q1=C: vocab pool = AI+user 통합 + kebab-case 필터 - Q2=A: telemetry emit 단위 = 태그별 - Q3=B: prompt 강도 = "Prefer" (우선) - Q4=A: 기존 노트 재처리 = 자연 진화 (X) ## Changes - NoteRepository: getTopUsedTags(20) + getTagIdByName(name) - prompt.ts: PROMPT_VERSION 3 → 4, vocab 4번째 param, vocabBlock with "Prefer reusing" - InferenceProvider/LocalOllamaProvider: vocab passthrough - AiWorker: vocab fetch + per-tag hit/miss emit (after ai_succeeded, vocab snapshot frozen) - telemetry: tag_vocab_hit/miss zod schema (.strict for privacy) + stats 누적 + 적중률 summary - TelemetryService.EmitInput union 13 → 15 ## Spec & plan - Spec: docs/superpowers/specs/2026-05-02-v023-tag-vocab-design.md - Plan: docs/superpowers/plans/2026-05-02-v023-tag-vocab.md ## Test Plan - [x] typecheck 0 - [x] 단위 363 → 384 (+21) - [x] e2e 1/1 - [ ] dogfood: vocab 라인이 실제 Ollama 프롬프트에 들어가는지 확인 - [ ] dogfood: 일정 시간 후 stats.md 의 태그 vocab 적중률 확인
altair823 added 13 commits 2026-05-02 03:41:13 +00:00
mini-brainstorm 4개 결정:
- Q1=C: vocab pool = AI+user 통합 + kebab-case 필터
- Q2=A: telemetry emit 단위 = 태그별 (per-tag hit/miss)
- Q3=B: prompt 강도 = "Prefer" (우선, MUST 아님)
- Q4=A: 기존 노트 재처리 = 자연 진화 (X)

핵심 invariant 6개 + privacy invariant + tests ≥19개 약속.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 task TDD plan:
T1 NoteRepository (getTopUsedTags + getTagIdByName, +7 cases)
T2 prompt.ts (PROMPT_VERSION 4 + vocab param, +4 cases, 신규 prompt.test.ts)
T3 InferenceProvider + LocalOllamaProvider (vocab passthrough, +1 case)
T4 telemetryEvents (zod schemas, +3 cases)
T5 telemetryStats (누적 + summary, +2 cases)
T6 TelemetryService EmitInput + narrowing 확장
T7 AiWorker (vocab fetch + per-tag emit, +4 cases)
T8 closure (gates + roadmap)

총 신규 단위 +21 (spec budget 19 + 2 surplus). 단위 363 → 382 (±5) 예상.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- getTopUsedTags(limit=20): top-N (count desc, id asc) + kebab-case 필터 + deleted_at 제외
- getTagIdByName(name): COLLATE NOCASE lookup
- AI+user source 통합 카운트 (Q1=C 결정)
- 단위 +7 cases (정렬, 필터, source 통합, deleted 제외, limit, getTagIdByName)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- M1: getTopUsedTags 의 LIMIT-then-filter 의미 docstring 명시
- M2: AI+user source 통합 테스트 강화 — 카운트 차이로 정렬 검증 (toContain 만으론 약함)
  updateUserAiFields 는 tags REPLACE 방식 (DELETE+reinsert) 이므로
  fallback 패턴 사용: 3개 노트 각 1태그, AI/user 혼합으로 design=2 > meeting=1 검증
- N1: SQL "COUNT(*) c" → "COUNT(*) AS c" (countFailed 패턴과 일관)
- N2: kebab-case regex 모듈 상수 KEBAB_CASE_RE 로 hoist

skip: N3 (test 헬퍼 — verbosity 경미), N4 (it 블록 분리 — 코드베이스 패턴 유지)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- PROMPT_VERSION 3 → 4 (marker bump, retry 트리거 X)
- buildPrompt 4번째 param vocab: string[] = []
- vocab.length > 0 시 "Existing vocabulary tags" + "Prefer reusing" 라인 추가
- vocab=[] 시 라인 자체 생략 (Q3=B 결정)
- 단위 +4 cases (신규 prompt.test.ts)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- M1: prompt.test.ts test 4 변수명 candidateIdx → headerIdx (실제 anchor 가 'Today's date' 헤더)
- N1: prompt.ts return 직전 self-delimited block 컨벤션 1줄 코멘트

skip: N2 (PROMPT_VERSION 테스트 redundancy nit — harmless guard), N3 (vocab dedup/normalize — Task 1 caller 책임)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- GenerateInput.vocab?: string[] (optional, 미전달 시 빈 배열 처리)
- LocalOllamaProvider.generate 가 input.vocab ?? [] 를 buildPrompt 4th 인자로
- 단위 +1 case (vocab → prompt body)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TagVocabHitPayload { tagId: int>0, vocabSize: int>=0 } .strict()
- TagVocabMissPayload { vocabSize: int>=0 } .strict()
- TelemetryEventSchema union 13 → 15
- 단위 +3 cases (hit accept, miss accept, hit extra field 거부)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- DailyRow +2 cols (tag_vocab_hit, tag_vocab_miss)
- accumulators + branches
- table 컬럼 +2
- summary "- 태그 vocab: hit/miss = N/M (적중률 X%)" 또는 "(데이터 없음)"
- 단위 +2 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- EmitInput union 13 → 15
- narrowing guards (noteId 없는 kind 분기) 에 tag_vocab_hit/miss 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- processJob 가 generate 직전 repo.getTopUsedTags(20) fetch
- provider.generate 에 vocab 전달 (LocalOllamaProvider 가 prompt 에 주입)
- ai_succeeded emit 후 per-tag 분류 → tag_vocab_hit/miss emit
  - hit: vocabSet.has + getTagIdByName lookup → { tagId, vocabSize }
  - miss: { vocabSize }
- AiTelemetryEmitter union 4종 (ai_succeeded/ai_failed/tag_vocab_hit/tag_vocab_miss)
- 단위 +4 cases (vocab passthrough, hit+miss, vocab=[] all miss, per-tag emit count)
- collectingTelemetry mock → AiTelemetryEmitter 타입 적용 (typecheck 통과)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- nit1: tag_vocab_hit/miss 테스트 payload cast dedupe (한 번에 typed 바인딩)
- nit2: { kind: string; payload: unknown } 반복을 EmittedEvent 타입 alias 로 hoist

skip: Minor1 (serial await — ai_succeeded 와 패턴 일관), Nit3 (magic number VOCAB_TOP_N — v0.2.4 backlog), Nit4 (한국어 코멘트 — 기존 코드와 일관)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- typecheck 0 / 단위 384 / e2e 1
- v0.2.3 6/7 (#3 태그 vocab 머지)
- 다음: #6 리마인드 spike (마지막 항목)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
altair823 changed title from feat(tag-vocab): #3 �±� vocab ? prompt + telemetry (v0.2.3 6/7) to feat(tag-vocab): #3 태그 vocab — prompt + telemetry (v0.2.3 6/7) 2026-05-02 03:43:04 +00:00
Author
Owner

Round 1 self-review (controller-side)

항목 결과
Critical 0
Important 1 (i1: 중복 태그 hit 인플레이션)
Minor 2 (m1: per-tag serial await, m2: LIMIT-then-filter 테스트 갭)
Nit 2 (n1: prompt 빈 줄 cosmetic, n2: positive vs nonnegative 스타일)

i1 — 중복 태그 hit 인플레이션 (AiWorker.ts:170-185)

res.tags['design', 'design', 'meeting'] 같이 중복 들어올 가능성. updateAiResultINSERT OR IGNORE 로 dedup 하지만 telemetry emit 루프는 안 함 → 같은 tagId 가 2번 카운트되어 hit-rate 통계 왜곡.

Fix: for (const tagName of new Set(res.tags)) 로 dedup. 테스트 추가 (중복 태그 응답 시 emit 1회만).

m2 — LIMIT-then-filter 테스트 갭 (fix)

docstring 에 명시된 "limit 보다 짧을 수 있음" 동작이 테스트로 lock 되지 않음. 테스트 1개 추가.

m1 + n1 + n2 (skip)

  • m1: ai_succeeded 와 동일 직렬 emit — 패턴 일관성 우선, 성능은 v0.2.4
  • n1: 모델 출력 영향 없는 cosmetic
  • n2: tagId 는 AUTOINCREMENT 1+ 라 positive() 가 정확

Plan

i1 + m2 fix 후 round 2 자체 verify.

Spec compliance

  • mini-brainstorm Q1=C, Q2=A, Q3=B, Q4=A 모두
  • privacy invariant
  • 6 invariants from spec §3.1 모두

Verdict

APPROVE WITH FIX — i1 + m2 inline 수정 후 round 2.

## Round 1 self-review (controller-side) | 항목 | 결과 | |---|---| | Critical | 0 | | Important | 1 (i1: 중복 태그 hit 인플레이션) | | Minor | 2 (m1: per-tag serial await, m2: LIMIT-then-filter 테스트 갭) | | Nit | 2 (n1: prompt 빈 줄 cosmetic, n2: positive vs nonnegative 스타일) | ### i1 — 중복 태그 hit 인플레이션 (`AiWorker.ts:170-185`) `res.tags` 가 `['design', 'design', 'meeting']` 같이 중복 들어올 가능성. `updateAiResult` 는 `INSERT OR IGNORE` 로 dedup 하지만 telemetry emit 루프는 안 함 → 같은 tagId 가 2번 카운트되어 hit-rate 통계 왜곡. **Fix**: `for (const tagName of new Set(res.tags))` 로 dedup. 테스트 추가 (중복 태그 응답 시 emit 1회만). ### m2 — LIMIT-then-filter 테스트 갭 (fix) docstring 에 명시된 "limit 보다 짧을 수 있음" 동작이 테스트로 lock 되지 않음. 테스트 1개 추가. ### m1 + n1 + n2 (skip) - m1: `ai_succeeded` 와 동일 직렬 emit — 패턴 일관성 우선, 성능은 v0.2.4 - n1: 모델 출력 영향 없는 cosmetic - n2: `tagId` 는 AUTOINCREMENT 1+ 라 `positive()` 가 정확 ### Plan i1 + m2 fix 후 round 2 자체 verify. ### Spec compliance - mini-brainstorm Q1=C, Q2=A, Q3=B, Q4=A 모두 ✅ - privacy invariant ✅ - 6 invariants from spec §3.1 모두 ✅ ### Verdict **APPROVE WITH FIX** — i1 + m2 inline 수정 후 round 2.
altair823 added 1 commit 2026-05-02 03:50:05 +00:00
- i1 (Important): AiWorker per-tag emit 루프에 res.tags Set dedup
  AI 가 같은 태그 중복 응답 시 hit count 2번 emit 되던 통계 왜곡 수정
  + 테스트 1개 (중복 태그 1 hit + 1 miss 검증)
- m2 (Minor): NoteRepository.getTopUsedTags LIMIT-then-filter 테스트 갭
  + 테스트 1개 (limit=3 + 한글 1 + kebab 2 → 결과 length=2 lock-in)

skip: m1 (per-tag serial await — ai_succeeded 패턴 일관),
      n1 (prompt 빈 줄 cosmetic), n2 (tagId positive — AUTOINCREMENT 1+)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author
Owner

Round 2 — APPROVE

항목 결과
Round 1 fix commit d8621d5
i1 (dedup) for (const tagName of new Set(res.tags)) + 테스트 1개
m2 (LIMIT-then-filter) 테스트 1개 lock-in
Round 2 verdict APPROVE (0 new issue)
Gates typecheck 0 / 단위 384 → 386 (+2) / e2e 1/1

skip 항목 (round 1 결정):

  • m1: serial await — ai_succeeded 와 패턴 일관, 성능은 v0.2.4 backlog
  • n1: prompt 빈 줄 cosmetic — 모델 출력 영향 없음
  • n2: tagId positive() — AUTOINCREMENT 1+ 라 정확

머지 후 알려줘. closure 단계 (local main sync + 브랜치 정리 + memory backlog) 진행.

## Round 2 — APPROVE | 항목 | 결과 | |---|---| | Round 1 fix commit | `d8621d5` | | i1 (dedup) | ✅ `for (const tagName of new Set(res.tags))` + 테스트 1개 | | m2 (LIMIT-then-filter) | ✅ 테스트 1개 lock-in | | Round 2 verdict | **APPROVE** (0 new issue) | | Gates | typecheck 0 / 단위 384 → **386** (+2) / e2e 1/1 | skip 항목 (round 1 결정): - m1: serial await — `ai_succeeded` 와 패턴 일관, 성능은 v0.2.4 backlog - n1: prompt 빈 줄 cosmetic — 모델 출력 영향 없음 - n2: tagId `positive()` — AUTOINCREMENT 1+ 라 정확 머지 후 알려줘. closure 단계 (local main sync + 브랜치 정리 + memory backlog) 진행.
altair823 merged commit 3c9326d6ec into main 2026-05-02 03:53:04 +00:00
altair823 deleted branch feat/v023-tag-vocab 2026-05-02 03:53:05 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/inkling#18