Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
187 commits
Select commit Hold shift + click to select a range
ed2e025
docs: 1주차 보고서 작성
CommitTheKermit May 28, 2026
ba1493f
docs: 1주차 보고서 수정
CommitTheKermit Jun 4, 2026
4aa7038
Merge branch 'committhekermit' into ellipsis
CommitTheKermit Jun 4, 2026
8b4a7d7
docs: 2주차 보고서 작성
CommitTheKermit Jun 4, 2026
f168ae1
Merge remote-tracking branch 'origin/ellipsis' into ellipsis
CommitTheKermit Jun 4, 2026
d00f330
chore: 프로토타입, 데이터 크롤러, 수집한 데이터 첨부
CommitTheKermit Jun 4, 2026
fd9af01
feat(keybuddy): 키보드 추천 프론트엔드를 impl/keybuddy로 이관
CommitTheKermit Jun 9, 2026
71abb60
docs(keybuddy): 태그 파이프라인 추천 전환 Seed 명세 추가
CommitTheKermit Jun 9, 2026
2fd6005
feat(keybuddy): HardConstraint/SoftIntent 스키마 상수 모듈 추가
CommitTheKermit Jun 9, 2026
57b3bb6
feat(keybuddy): validateTagSchema 함수 구현 및 단위 테스트 추가
CommitTheKermit Jun 9, 2026
241b859
feat(keybuddy): extractRawTags 함수 및 단위 테스트 구현
CommitTheKermit Jun 9, 2026
07cfb7a
feat(keybuddy): sanitizeTags 함수 및 단위 테스트 추가
CommitTheKermit Jun 9, 2026
ee6874e
feat(keybuddy): extractAndValidateTags 파이프라인 통합 함수 구현
CommitTheKermit Jun 9, 2026
9b669eb
feat(keybuddy): checkLayoutViolation 함수 구현 및 단위테스트 추가
CommitTheKermit Jun 9, 2026
f16cd79
feat(keybuddy): 소프트태그 정적 규칙표 및 완전성 검사 구현 (Sub-AC 3-1)
CommitTheKermit Jun 9, 2026
a1e08ed
test(keybuddy): checkSwitchViolation 구현 및 단위테스트 추가
CommitTheKermit Jun 9, 2026
01e7654
feat(keybuddy): 단계별 선택 입력->하드 제약 순수 매핑 함수 구현 (Sub-AC 2-1-A)
CommitTheKermit Jun 9, 2026
e2149b6
feat(keybuddy): checkFormFactorViolation 함수 구현 및 단위테스트 추가
CommitTheKermit Jun 9, 2026
d338e61
feat(keybuddy): 키보드 데이터셋 스키마 추출 함수 구현
CommitTheKermit Jun 9, 2026
8f28f5f
feat(keybuddy): checkBudgetViolation 함수 구현 및 단위테스트 추가
CommitTheKermit Jun 9, 2026
20c48af
feat(keybuddy): 속성명 무결성 검사 함수 구현 및 테스트 추가
CommitTheKermit Jun 9, 2026
7e75932
feat(keybuddy): 소프트 의도 태그 단계별 순수 매핑 함수 구현 (Sub-AC 2-1-B)
CommitTheKermit Jun 9, 2026
89ec49a
feat(keybuddy): checkHardConstraintViolation 디스패처 함수 구현 (Sub-AC 4-1-5)
CommitTheKermit Jun 9, 2026
d31d0ae
feat(keybuddy): 속성 값 무결성 검사 함수 구현 (Sub-AC 3-2-c)
CommitTheKermit Jun 9, 2026
3acff13
feat(keybuddy): filterByHardConstraints 구현 및 단위테스트 추가
CommitTheKermit Jun 9, 2026
7520153
feat(keybuddy): 단계 번호 기반 TagSet 디스패처 함수 구현 (Sub-AC 2-1-C)
CommitTheKermit Jun 9, 2026
8281d4b
test(keybuddy): validateTagSchema 엣지 케이스 단위 테스트 추가
CommitTheKermit Jun 9, 2026
9362b89
test(keybuddy): selectionOptionConverter 추가 및 validateTagSchema 통과 단위…
CommitTheKermit Jun 9, 2026
3876f15
test(keybuddy): Sub-AC 2-3-a 선택 경로와 자유형 경로의 하드 필터 결과 동일성 검증
CommitTheKermit Jun 9, 2026
5fdfe99
test(keybuddy): Sub-AC 2-3-b 소프트태그 스코어 패리티 단위 테스트 추가
CommitTheKermit Jun 9, 2026
4714532
test(keybuddy): 검색 엔진 진입점 및 통합 테스트 추가 (Sub-AC 2-3-c)
CommitTheKermit Jun 9, 2026
7e7ad6e
feat(keybuddy): detectNoResult 함수 추가 및 단위테스트 작성
CommitTheKermit Jun 9, 2026
df232ec
test(keybuddy): 소프트 태그 점수 단조성 단위테스트 추가
CommitTheKermit Jun 9, 2026
488db16
test(keybuddy): getRelaxationOrder 함수 구현 및 단위 테스트 추가
CommitTheKermit Jun 9, 2026
006984a
feat(keybuddy): relaxAndSearch 함수 구현 및 단위 테스트 추가
CommitTheKermit Jun 9, 2026
4c5705c
test(keybuddy): 하드 제약 태그 추출 정확도 골드셋 테스트 추가 (Sub-AC 1)
CommitTheKermit Jun 9, 2026
0db0117
feat(keybuddy): Sub-AC 7-1 - 결과 객체 매칭 소프트태그 결정론 함수 추가
CommitTheKermit Jun 9, 2026
f822c03
test(keybuddy): 소프트 의도 태그 추출 정확도 골드셋 테스트 추가
CommitTheKermit Jun 9, 2026
7ce74be
feat(keybuddy): evaluateConstraint 단일 하드 제약 술어 함수 구현 (Sub-AC 7-2a)
CommitTheKermit Jun 9, 2026
93bfe4b
test(keybuddy): Sub-AC 2a - 정적 규칙표 맵 구조 골드셋 검증 테스트 추가
CommitTheKermit Jun 9, 2026
97ce96b
feat(keybuddy): buildConstraintStatusMap 구현 (Sub-AC 7-2b)
CommitTheKermit Jun 9, 2026
5169389
test(keybuddy): expandSoftTag 단위 테스트 추가 (Sub-AC 2b)
CommitTheKermit Jun 9, 2026
5a76f48
test(keybuddy): 하드 제약 필터 LLM 미호출 단위 테스트 추가 (Sub-AC 7.3.1)
CommitTheKermit Jun 9, 2026
7f28a22
test(keybuddy): Sub-AC 7.3.2 소프트 태그 스코어링 LLM 미호출 검증 테스트 추가
CommitTheKermit Jun 9, 2026
cb503f6
test(keybuddy): Sub-AC 7.3.3 결과 조합 모듈 LLM 호출 횟수 0 검증
CommitTheKermit Jun 9, 2026
685bec0
fix(keybuddy): Seed metadata.version 문자열 타입으로 수정
CommitTheKermit Jun 9, 2026
c164ab1
feat(keybuddy): filterByConstraintPredicates 술어 기반 필터 추가 (Sub-AC 3-1)
CommitTheKermit Jun 9, 2026
f6c54db
fix(keybuddy): 하드 제약 6종 enforcement 누락 수정 + 의도 골드셋
CommitTheKermit Jun 9, 2026
4b0eaa2
fix(keybuddy): npm run build(tsc) 통과하도록 타입 에러 정리
CommitTheKermit Jun 9, 2026
cc53814
feat(keybuddy): 태그 추출 견고화 + 개발용 콘솔 로그
CommitTheKermit Jun 9, 2026
aabe0b0
docs(keybuddy): 의도->다차원 속성 프로파일 하네싱 Seed 명세 추가
CommitTheKermit Jun 9, 2026
446aada
feat(keybuddy): 의도->다차원 프로파일 정적 문서 + 결정론 확장 (AC1/3/4/5)
CommitTheKermit Jun 9, 2026
925c905
feat(keybuddy): 의도 확장 검색 엔진 searchWithProfile (AC5/6)
CommitTheKermit Jun 9, 2026
87e3e90
feat(keybuddy): extractIntentInput - 의도+명시 제약 분리 추출 (AC2)
CommitTheKermit Jun 9, 2026
2dcf36d
feat(keybuddy): recommend를 의도 하네싱으로 배선 (AC7)
CommitTheKermit Jun 9, 2026
427ade2
docs(keybuddy): 의도 하네싱 전/후 정성 비교 리포트 + 생성기 (AC8)
CommitTheKermit Jun 9, 2026
2a4954b
docs(keybuddy): 자연어->태그 추출 플로우 문서 + README 현행화
CommitTheKermit Jun 9, 2026
8cbac42
fix(keybuddy): 명시 정숙 요구를 방향 인식으로 하드 강화
CommitTheKermit Jun 9, 2026
d8ed6df
feat(keybuddy): 추천 노출 상한 12 -> 30
CommitTheKermit Jun 9, 2026
059ce4e
chore: CodeRabbit 자동 코드 리뷰 설정 추가
CommitTheKermit Jun 9, 2026
77de0b2
chore: CodeRabbit 제거하고 Claude Code Action 자동 PR 리뷰로 전환
CommitTheKermit Jun 9, 2026
64c0a15
feat: keybuddy 프론트 추천 앱 추가
chohs4164 Jun 9, 2026
6be4d83
feat: Supabase Edge Function 추천 API 추가
chohs4164 Jun 9, 2026
79c4a3a
docs: Supabase 기반 실행 및 배포 방법 정리
chohs4164 Jun 9, 2026
013dff4
"Claude PR Assistant workflow"
CommitTheKermit Jun 9, 2026
1b1ca63
"Claude Code Review workflow"
CommitTheKermit Jun 9, 2026
d18d7cc
Merge pull request #2 from CommitTheKermit/add-claude-github-actions-…
CommitTheKermit Jun 9, 2026
4486d50
chore: 프로토타입, 데이터 크롤러, 추가 수집
CommitTheKermit Jun 9, 2026
070d9b6
fix: Supabase 추천 함수 호출 설정 수정
chohs4164 Jun 9, 2026
9a858ec
Merge branch 'ellipsis' of https://github.com/CommitTheKermit/android…
chohs4164 Jun 9, 2026
4ef1d46
chore: keybuddy Supabase 구조를 impl 경로로 정리
chohs4164 Jun 9, 2026
90cdbd8
Merge remote-tracking branch 'origin/main' into ellipsis
chohs4164 Jun 9, 2026
5b18fe1
fix: 추천 태그를 DB 속성 기반으로 수정
chohs4164 Jun 10, 2026
82e00a4
chore: 추천 모델을 gpt-5.4로 변경
chohs4164 Jun 10, 2026
4b3dc80
fix(keybuddy): PR 리뷰 반영 - LLM 프롬프트 오타·에러 처리·데드코드 정리
CommitTheKermit Jun 10, 2026
bb5ca39
Merge pull request #1 from CommitTheKermit/keybuddy-intent-harness
CommitTheKermit Jun 10, 2026
3422b7e
ci(review): Claude PR 리뷰를 라인별 inline 코멘트로 전환
CommitTheKermit Jun 10, 2026
ea1c0a5
Merge remote-tracking branch 'origin/ellipsis' into chohs4164
chohs4164 Jun 10, 2026
e2bf0c2
ci(review): claude-code-action OIDC 인증용 id-token 권한 추가
CommitTheKermit Jun 10, 2026
5788023
ci: Claude 리뷰 워크플로 OIDC 권한 추가
chohs4164 Jun 10, 2026
34ac4f5
fix: 추천 API 오류 처리 보강
chohs4164 Jun 10, 2026
0351b39
fix: 추천 API 리뷰 지적사항 반영
chohs4164 Jun 10, 2026
bdd612e
Merge pull request #4 from chohs4164/chohs4164
chohs4164 Jun 10, 2026
8f3216d
docs: keybuddy 실행 환경 설정 가이드 보강
chohs4164 Jun 10, 2026
9e9659f
Merge pull request #6 from chohs4164/docs-keybuddy-env-guide
chohs4164 Jun 10, 2026
9be0629
feat(keybuddy): 결과 화면에 구매하기 버튼과 별점 피드백 추가
CommitTheKermit Jun 10, 2026
68e7c45
docs: 프로젝트 메모리에 git 브랜치 작업 규칙 추가
CommitTheKermit Jun 10, 2026
0054deb
feat(keybuddy): 별점 0.5점 단위 선택/반 개 표시 지원
CommitTheKermit Jun 10, 2026
c619d3e
fix: 추천 결과 최대 개수 30개로 복원
chohs4164 Jun 10, 2026
67a43e5
Merge pull request #9 from chohs4164/fix-keybuddy-max-recommendations
chohs4164 Jun 10, 2026
74673c3
fix: 추천 후보와 출력 여유 확대
chohs4164 Jun 10, 2026
58bb89b
fix: 추천 요청 timeout 여유 확대
chohs4164 Jun 10, 2026
74df9b4
fix: 추천 응답 안정성 보강
chohs4164 Jun 10, 2026
b942d1c
Merge pull request #10 from CommitTheKermit/fix-keybuddy-recommendati…
chohs4164 Jun 10, 2026
92800d8
docs: 스위치 정보 catalog 정의
first-woosun Jun 10, 2026
6831ae2
docs: catalog와 유사한 스위치 이름 매핑을 위한 데이터
first-woosun Jun 10, 2026
9328c96
docs: 실제 UI에 표시할 스위치 데이터
first-woosun Jun 10, 2026
929ab28
feat: 키보드 스위치 옵션 매칭 및 가격비교 링크 수집 추가
first-woosun Jun 10, 2026
b9409ae
docs: 변경된 크롤러로 수집한 키보드 데이터
first-woosun Jun 10, 2026
e8d1aeb
docs: 변경된 크롤러로 수집한 키보드 데이터
first-woosun Jun 10, 2026
fce81ae
docs: 스위치 정보 테이블에 일치하는 스위치가 없는 키보드 로그
first-woosun Jun 10, 2026
a268fa9
docs: 크롤링 데이터에 대한 설명 추가
first-woosun Jun 10, 2026
f3007b2
chore: 최신 키보드 데이터 600개 동기화
first-woosun Jun 10, 2026
a5b076b
feat: 키보드 스위치 데이터 타입 확장
first-woosun Jun 10, 2026
c478df8
test: 데이터셋 스키마 boolean 및 null 처리 검증
first-woosun Jun 10, 2026
0d55871
chore: 추천 함수 키보드 타입 동기화
first-woosun Jun 10, 2026
3ea7e5d
feat: 스위치 그래프 계산 로직 추가
first-woosun Jun 10, 2026
d13e8bf
test: 스위치 그래프 계산 규칙 검증
first-woosun Jun 10, 2026
157bae8
feat: 결과 화면 단계 그래프 UI 추가
first-woosun Jun 10, 2026
d7d2a5c
fix(keybuddy): 결과 화면 리마운트/타이머 누수 수정 및 별점 hover 정밀화
CommitTheKermit Jun 11, 2026
65d2e71
feat: 검색 결과 카드 UI 및 제품 정보 연결
first-woosun Jun 11, 2026
216d212
fix: 추천 결과 부족분 후보로 보강
chohs4164 Jun 11, 2026
5cf1351
feat: 범용 스위치 그래프 매핑 추가
first-woosun Jun 11, 2026
d8dae11
Merge remote-tracking branch 'origin/ellipsis' into crawler
first-woosun Jun 11, 2026
dd076c5
chore: document edge function versioning
chohs4164 Jun 11, 2026
1b778a1
fix: address keybuddy review comments
chohs4164 Jun 11, 2026
963cec3
fix: address follow-up keybuddy reviews
chohs4164 Jun 11, 2026
d4456a5
fix: PR 리뷰 피드백 반영
first-woosun Jun 11, 2026
49663a2
fix: address remaining keybuddy reviews
chohs4164 Jun 11, 2026
43af4b2
fix: address latest keybuddy review comments
chohs4164 Jun 11, 2026
51f683f
fix: address keybuddy follow-up review comments
chohs4164 Jun 11, 2026
d997901
fix: refine keybuddy deploy and rate limit handling
chohs4164 Jun 11, 2026
329a7bb
fix: harden keybuddy function deploy workflow
chohs4164 Jun 11, 2026
7ebb873
fix(keybuddy): 결과 화면 메모이즈/클립보드 폴백 및 훅 참조 안정화
CommitTheKermit Jun 11, 2026
a6954e9
Update impl/keybuddy/frontend/src/App.tsx
CommitTheKermit Jun 11, 2026
af64b3b
Update impl/keybuddy/frontend/src/App.tsx
CommitTheKermit Jun 11, 2026
db91be4
Update impl/keybuddy/frontend/src/App.tsx
CommitTheKermit Jun 11, 2026
aa01419
Merge pull request #8 from CommitTheKermit/feat/keybuddy-buy-rating
CommitTheKermit Jun 11, 2026
4bca566
fix: typecheck keybuddy edge function deploy
chohs4164 Jun 11, 2026
d74e7c2
Merge pull request #11 from CommitTheKermit/fix-keybuddy-recommendati…
chohs4164 Jun 11, 2026
ae0ea55
fix: 검색 결과 제품 태그 표시 개선
first-woosun Jun 11, 2026
0943d36
fix: 미제공 키보드 무게 null 처리
first-woosun Jun 11, 2026
7bb156a
Merge remote-tracking branch 'origin/ellipsis' into crawler
first-woosun Jun 11, 2026
d40a2eb
Merge pull request #12 from CommitTheKermit/crawler
first-woosun Jun 11, 2026
fcd6f0d
Merge remote-tracking branch 'upstream/committhekermit' into tmp-merg…
CommitTheKermit Jun 11, 2026
aa8aef9
docs: 3주차 주간 보고서 작성
CommitTheKermit Jun 11, 2026
f612ec1
docs: 3주차 주간 보고서 수정
CommitTheKermit Jun 11, 2026
d13be88
fix: 검색 결과 필터 카운트 표시 개선
chohs4164 Jun 13, 2026
e6c7663
Merge pull request #13 from CommitTheKermit/fix-keybuddy-recommendati…
chohs4164 Jun 13, 2026
a4a1d7a
feat: 추천 결과 초보자 설명 추가
chohs4164 Jun 13, 2026
d342470
fix: 초보자 설명 리뷰 반영
chohs4164 Jun 13, 2026
0f7f706
Merge pull request #14 from CommitTheKermit/fix-keybuddy-recommendati…
chohs4164 Jun 13, 2026
3dab0db
feat(keybuddy): 홈 화면을 세그먼트 토글 레이아웃으로 재디자인
CommitTheKermit Jun 15, 2026
70cc5e9
docs: keybuddy 아키텍처와 개발 명령어로 CLAUDE.md 보강
CommitTheKermit Jun 15, 2026
df9bb8a
docs: 에이전트 지침을 AGENTS.md 단일 정본으로 통합
CommitTheKermit Jun 15, 2026
4cdad13
docs(knowledge): 레포 내장 지식 루프 가이드와 이식용 스크립트 추가
CommitTheKermit Jun 15, 2026
66b7b89
fix(keybuddy): 홈 재디자인 리뷰 반영
CommitTheKermit Jun 16, 2026
c38139b
fix(keybuddy): 홈 복귀/탭 전환 시 error·rating 상태 초기화
CommitTheKermit Jun 16, 2026
9325a55
refactor(keybuddy): 홈 화면 리뷰 반영 - 상수/헬퍼 추출
CommitTheKermit Jun 16, 2026
3319ca4
Merge pull request #16 from CommitTheKermit/feat/home-screen-redesign
CommitTheKermit Jun 16, 2026
0dc5d4f
docs(agents): CLAUDE.md 심볼릭 링크 주의 안내 추가
CommitTheKermit Jun 16, 2026
9b1f483
docs(agents): 심볼릭 링크 OS별 동작 차이 명시
CommitTheKermit Jun 16, 2026
a24540d
Merge pull request #17 from CommitTheKermit/docs/agents-ssot
CommitTheKermit Jun 16, 2026
3ce3843
docs: keybuddy 기술 문서를 레포 루트 docs/로 일원화
CommitTheKermit Jun 16, 2026
9cb84d3
docs(keybuddy): README 후보 압축 개수 40개로 정정 + 루트 docs 안내 추가
CommitTheKermit Jun 16, 2026
d56918d
docs: 루트 README에 keybuddy 구현 현황 반영
CommitTheKermit Jun 16, 2026
44a43a0
fix(knowledge): 지식 루프 스크립트 견고성과 문서 정확성 보완
CommitTheKermit Jun 17, 2026
28a04b7
Merge pull request #18 from CommitTheKermit/docs/in-repo-knowledge-loop
CommitTheKermit Jun 17, 2026
3ff26be
feat(keybuddy): 결정론 의도 하네스 UI 연결 및 추천 점수순 상위 3개 제한
CommitTheKermit Jun 17, 2026
25d2b41
Merge pull request #19 from CommitTheKermit/docs/keybuddy-docs-root
CommitTheKermit Jun 17, 2026
8e9b000
docs: 구현 현황 단계별 선택 서술을 실제 동작에 맞게 수정
CommitTheKermit Jun 17, 2026
21219bf
Merge pull request #20 from CommitTheKermit/docs/readme-impl-status
CommitTheKermit Jun 17, 2026
508e9ee
feat: improve guided survey experience
chohs4164 Jun 17, 2026
cf22dd9
fix(keybuddy): PR 리뷰 반영 - 추출 정제·파싱 견고성·테스트/문서 정합
CommitTheKermit Jun 17, 2026
880df2f
fix: address guided survey review feedback
chohs4164 Jun 17, 2026
6386b47
Merge pull request #22 from CommitTheKermit/codex-guided-survey-visuals
chohs4164 Jun 17, 2026
e96e80f
Merge pull request #21 from CommitTheKermit/feat/intent-harness-top3
CommitTheKermit Jun 17, 2026
9257764
feat(keybuddy): 구매 클릭·추천 별점 Supabase 이벤트 수집
CommitTheKermit Jun 17, 2026
9eaa1a0
fix: 단계별 선택 결과 완화 제거
chohs4164 Jun 17, 2026
32258fa
Merge pull request #23 from CommitTheKermit/fix/guided-strict-filter
chohs4164 Jun 17, 2026
f29bec9
feat(crawler): add YouTube media enrichment
first-woosun Jun 17, 2026
76b791f
data(catalog): sync YouTube media links
first-woosun Jun 17, 2026
a4ea26f
docs: document YouTube media enrichment workflow
first-woosun Jun 17, 2026
3f719c6
fix: 이벤트 수집 리뷰 피드백 반영
chohs4164 Jun 17, 2026
ee9bdab
Merge pull request #26 from CommitTheKermit/youtube_crawler
first-woosun Jun 17, 2026
67ca25b
빈 자연어 추출 결과 추천 차단
chohs4164 Jun 17, 2026
429635b
fix: 검색 결과 자연어 조건 전체 표시 (#27)
chohs4164 Jun 17, 2026
5651f0c
chore: 키보드 데이터 추가
first-woosun Jun 17, 2026
5da9b53
refactor: 스위치 이름 조회 필드를 rawSwitchName으로 변경
first-woosun Jun 17, 2026
f5391ab
Merge pull request #28 from CommitTheKermit/reinforce_switch_catalog
first-woosun Jun 17, 2026
f5514fa
fix: 비기계식 키보드 레벨미터 고정
first-woosun Jun 17, 2026
6470a07
Merge pull request #29 from CommitTheKermit/contact_method_levelmeter
first-woosun Jun 17, 2026
adf0e16
Merge branch 'ellipsis' into feat/supabase-event-collection
CommitTheKermit Jun 18, 2026
5494f92
Merge pull request #24 from CommitTheKermit/feat/supabase-event-colle…
CommitTheKermit Jun 18, 2026
1d501de
feat: 구매 버튼에 가격 비교 링크 연결 (#30)
chohs4164 Jun 18, 2026
8a4f35e
docs: 4주차 주간 보고서 작성
CommitTheKermit Jun 18, 2026
d7312dc
Merge branch 'committhekermit' into ellipsis
CommitTheKermit Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
.DS_Store
__pycache__/
*.py[cod]
.venv/

# Local scratch files created during setup.
/android-mini-projects/
Expand Down
34 changes: 26 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,39 +50,57 @@ cd impl && python3 crawl.py
cd keybuddy/frontend && npm run sync:data # output/keyboards.json -> 프론트 + Edge Function 사본
```

## 아키텍처: 두 개의 추천 경로
## 아키텍처: 추천 경로 (현행 + 레거시)

keybuddy의 핵심은 **추천 엔진이 두 갈래로 존재**한다는 점이다. 혼동하지 말 것.
> **2026-06 갱신**: 결정론 의도 하네스가 UI에 배선됐다. 현행 프로덕션은 "Edge Function 태그추출(OpenAI) + 클라이언트 결정론 검색" 하이브리드이고, 과거의 "Edge Function 전체 추천 생성"과 "클라이언트 claude 태그추출"은 코드만 남은 레거시다. 혼동하지 말 것.

1. **현재 UI에 연결된 경로 (production)**: `App.tsx` → `lib/recommend.ts`(HTTP fetch) → Supabase Edge Function `recommend/index.ts`. Edge Function이 키워드 스코어링(`scoreKeyboard`)으로 후보를 40개 이하로 압축한 뒤 **OpenAI**(`gpt-5.4`)에 넘겨 추천 사유를 생성한다. `OPENAI_API_KEY`는 브라우저에 절대 내려가지 않고 Supabase secret에만 존재한다 (`recommend.ts`는 `apikey` 헤더만 보냄).
**현행 프로덕션 경로 (UI 연결)**: `App.tsx` → `lib/recommend.ts`

2. **결정론적 의도 하네스 (lib/, 테스트로만 검증됨 - 아직 UI 미연결)**: `extractRawTags.ts`(유일한 LLM 호출, `claude-sonnet-4-6`로 자연어→의도+명시제약 번역) → `intentProfile.ts`(의도를 차원별 태그로 정적 확장) → `intentSearch.ts`/`searchEngine.ts`(하드필터 + 소프트 스코어링 + 무결과 시 제약 완화). 설계 의도는 `docs/tag-extraction-flow.md` 참고. **"LLM은 번역만, 태그 확장·검색은 전부 결정론"**이 핵심 불변식이며, 같은 입력→같은 출력을 보장한다.
- **freeform(자연어)**: `recommend.ts`가 `{mode:'extract'}`로 Edge Function `recommend/index.ts`를 호출하면, Edge Function이 **OpenAI**(`gpt-5.4`, `OPENAI_MODEL`로 override)로 자연어를 `{intents, hardConstraints, softIntentTags}`로 **태그추출만** 한다. 이후 클라이언트가 `expandIntents`(intentProfile) → `searchWithProfile`(intentSearch)로 **결정론 검색**하고 점수순 상위 3개로 압축한다.
- **guided(단계선택)**: 네트워크 0회. `selectionOptionConverter`가 답변을 태그로 변환한 뒤 위와 동일한 결정론 검색을 탄다.
- `OPENAI_API_KEY`는 브라우저에 절대 내려가지 않고 Supabase secret에만 존재한다(`recommend.ts`는 `apikey` 헤더만 보냄). 양쪽 모두 **"LLM은 번역(또는 0회)만, 태그 확장·검색·랭킹은 전부 결정론"**이라는 불변식을 지켜 같은 입력→같은 출력을 보장한다.

`docs/tag-extraction-flow.md`는 경로 2를 "현재 흐름"으로 서술하지만 실제 `recommend.ts`는 경로 1(Edge Function)을 호출한다. 경로 2는 구축·테스트 완료됐으나 아직 `recommend.ts`에 배선되지 않았다 - 이 갭이 TODO의 "키보드 에이전트 직접 설계" 작업 대상이다.
**레거시 (코드만 존재, 현 UI 미사용)**:

### lib/ 파이프라인 핵심 모듈 (경로 2)
- Edge Function 전체 추천 생성 모드: `scoreKeyboard`로 후보를 압축한 뒤 `gpt-5.4`가 후보 중 직접 선택·사유 생성 → `composeRecommendations`. `index.ts`에 남아있으나 현재 UI는 호출하지 않는다(해당 분기 주석 "현재 UI는 미사용" 참조).
- 클라이언트 `extractRawTags.ts`의 LLM 호출(`claude-sonnet-4-6`, `dangerouslyAllowBrowser`). 프로덕션 추출은 Edge Function extract(OpenAI)가 담당하므로 이 함수들은 현재 직접 호출되지 않는다(타입·테스트용).

- `extractRawTags.ts` - 자연어 → `ExtractedTags { hardConstraints, softIntentTags }` (LLM 1회)
`docs/tag-extraction-flow.md`는 이 결정론 하네스의 설계를 서술한다(문서는 클라이언트 claude 추출 기준으로 작성됐고, 실제 프로덕션 추출은 Edge Function의 OpenAI extract가 대체한다). 과거 이 문서가 가리키던 "미연결" 갭은 해소됐다.

### lib/ 결정론 파이프라인 핵심 모듈 (현행 검색 경로)

- `extractRawTags.ts` - 태그 정제(`sanitizeTags`)와 타입 정의. 브라우저 LLM 추출 함수(`extractRawTags`/`extractIntentInput`, `claude-sonnet-4-6`)도 있으나 현행 프로덕션은 Edge Function extract를 쓰므로 미사용(테스트용)
- `tagSchema.ts` - 소프트 의도 태그 어휘(controlled vocabulary)와 검증
- `softTagRules.ts` - 소프트 태그 → 키보드 매칭 술어(규칙 맵)
- `intentProfile.ts` - 고수준 의도(사무용/게이밍/휴대용)를 차원별 요구로 확장. 강도는 `필수`(하드 승격)/`선호`(소프트 점수)/`상관없음`. 명시 제약이 의도보다 우선.
- `hardFilter.ts` - 하드 제약 위반 키보드 제외 (위반 0건 보장)
- `softScorer.ts` - 소프트 태그 매칭 점수 → 랭킹
- `searchEngine.ts`/`intentSearch.ts` - 무결과 시 제약을 우선순위 역순으로 1개씩 완화 후 재검색(`HARD_CONSTRAINT_RELAXATION_ORDER`). `searchKeyboards`는 LEGACY, `searchWithProfile`이 신규 진입점.

### 이벤트 수집 (구매 클릭 / 추천 별점)

추천 가설 검증용 데이터를 모으기 위해 두 가지 사용자 행동을 Supabase에 적재한다(커머스 아님, 실결제 없음).

- `lib/session.ts` - 로그인 없는 익명 세션 식별자(localStorage UUID). 추천 -> 구매 클릭 -> 별점 흐름을 느슨하게 묶는다. PII 아님.
- `lib/events.ts` - `purchase_click`('구매하기' 버튼 클릭, payload `{product_code}`)과 `rating`(추천 전체 별점, payload `{rating}`)을 anon key로 PostgREST `/rest/v1/events`에 직접 insert. **best-effort**라 설정 누락/네트워크 실패는 삼키고 `false`만 반환해 UX를 막지 않는다.
- 스키마: `supabase/migrations/20260617000000_create_events.sql` - 단일 `events`(id, event_type, session_id, payload jsonb, created_at) + **insert-only RLS**(anon은 insert만, select/update/delete는 정책 부재로 기본 거부).
- 검증: `__tests__/events.test.ts`가 payload 형태와 PostgREST 요청/실패 동작을 단위검증. 실제 적재는 라이브 Supabase에서 수동 확인(아래 README 참고).

## 데이터 파이프라인

`crawl.py`는 다나와 **목록 페이지만** 조회한다(상세 페이지 요청 안 함, `DELAY_SEC` 레이트리밋 준수). 한 상품에 스위치 옵션이 여럿이면 제품-스위치 조합별 레코드로 펼치고 최종 **600개**로 제한(`TARGET_RECORDS`). 스위치 이름은 `src/data/switch_aliases.json` 규칙으로만 매칭하고, 매칭 실패는 추론하지 않고 `output/unmatched_switches.json`에 격리한다.

카탈로그 `keyboards.json`은 **두 군데에 사본**으로 존재한다(프론트 표시용 + Edge Function 후보용). 반드시 `npm run sync:data`로 함께 갱신해야 둘이 엇갈리지 않는다.

YouTube 타건 영상 링크는 공식 YouTube Data API로만 수집한다(YouTube HTML 검색/스크래핑 우회 금지). `impl/output/youtube_media_cache.json`은 누적 캐시이며, 다음 크롤링 때 캐시를 먼저 입히고 캐시 미스만 새로 검색한다. `keyboards.json`에는 영상 메타데이터 전체를 넣지 않고 `media_url`, `media_url_is_placeholder`만 반영한다. 제목·점수·채널 등 감사 정보는 `youtube_media_cache.json`/`youtube_media_report.json`에 둔다.

## 버전 관리

앱 버전의 단일 소스는 `frontend/package.json`의 `version`이다. `npm run build`/`deploy:function`이 `sync:function-version`으로 이 값을 Edge Function의 `version.ts`에 주입하며, Edge Function은 `GET /recommend`, 응답 `meta.version`, `X-Keybuddy-Version` 헤더로 노출한다. 배포 전 `npm version patch|minor|major --no-git-tag-version`으로 SemVer 증가.

## 테스트 규약

`src/__tests__/`에 36개 vitest 스위트가 있다. 특징적인 패턴:
`src/__tests__/`에 40개 vitest 스위트가 있다. 특징적인 패턴:

- **`*LlmNoCall.test.ts`** - 결정론 경로가 실제로 LLM을 호출하지 않음을 강제하는 불변식 테스트. lib/ 검색 로직 수정 시 이 보증을 깨지 말 것.
- **`*.goldset.test.ts`** - 정답셋 기반 정확도 회귀 테스트(하드제약/소프트의도 정확도).
Expand Down
156 changes: 156 additions & 0 deletions docs/youtube-media-enrichment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# YouTube 타건 영상 링크 보강

이 문서는 다나와 키보드 목록 크롤러가 수집한 제품-스위치 레코드에 YouTube 타건 영상
링크를 보강하는 설계와 운영 규칙을 정리한다.

## 원칙

- YouTube 링크는 공식 YouTube Data API로만 수집한다.
- YouTube 검색 HTML을 직접 파싱하거나 스크래핑으로 쿼터를 우회하지 않는다.
- 다나와 크롤링은 기존처럼 목록 페이지만 조회한다. YouTube 보강은 다나와 수집 이후의
별도 enrichment 단계다.
- `keyboards.json`에는 재생에 필요한 최소 필드만 넣는다.

## 데이터 계약

`impl/output/keyboards.json`의 각 레코드에는 기존 UI 계약을 유지해 아래 필드만 반영한다.

```json
{
"media_url": "https://www.youtube.com/watch?v=VIDEO_ID",
"media_url_is_placeholder": false
}
```

링크를 찾지 못했거나 아직 검색하지 않은 경우:

```json
{
"media_url": null,
"media_url_is_placeholder": true
}
```

영상 제목, 채널명, 점수, 검색어, 실패 이유 같은 감사 정보는 `keyboards.json`에 넣지
않고 별도 파일에 둔다.

## 파일 역할

- `impl/output/youtube_media_cache.json`: 누적 캐시. 다음 실행에서 먼저 읽어
`keyboards.json`에 다시 반영한다.
- `impl/output/youtube_media_report.json`: 최근 실행 리포트. 성공, 실패, 스킵, 쿼터 제한,
오류를 확인한다.
- `impl/output/keyboards.json`: 앱이 사용할 원본 카탈로그. YouTube 보강 후 저장된다.
- `impl/keybuddy/frontend/src/data/keyboards.json`: 프론트 표시용 사본.
- `impl/keybuddy/supabase/functions/recommend/keyboards.json`: Edge Function 추천 후보용 사본.

## 검색 키

검색어는 제품명과 스위치명을 조합한다.

```text
상품명 + raw_switch_name
```

`raw_switch_name`이 없으면 `switch_name`을 사용한다. 둘 다 없으면 YouTube 검색을 건너뛰고
placeholder 상태를 유지한다.

캐시 키는 `product_code`가 있으면 우선 사용한다.

```text
product_code:<product_code>::switch:<switch_label>
```

`product_code`가 없을 때만 정규화된 `product_name + switch_label`로 fallback한다.

## 실행

기본 크롤링은 YouTube API를 호출하지 않는다.

```bash
cd impl
python3 crawl.py
```

YouTube 링크를 새로 수집하려면 API 키와 limit을 지정한다.

```bash
cd impl
YOUTUBE_API_KEY=<key> python3 crawl.py --with-youtube --youtube-limit 90
```

새 검색 없이 기존 캐시만 현재 `keyboards.json`에 다시 입히려면:

```bash
cd impl
python3 crawl.py --with-youtube --youtube-limit 0
```

기존 캐시를 무시하고 다시 검색하려면:

```bash
cd impl
YOUTUBE_API_KEY=<key> python3 crawl.py --with-youtube --youtube-refresh --youtube-limit 90
```

## 이어서 수집되는 방식

매 실행은 다음 순서로 동작한다.

1. 다나와 목록에서 새 `collected` 레코드를 만든다.
2. `youtube_media_cache.json`을 읽는다.
3. 캐시에 있는 항목은 API 호출 없이 `media_url`을 바로 반영한다.
4. 캐시에 없는 항목만 `--youtube-limit` 범위 안에서 새로 검색한다.
5. 새 검색 결과를 캐시에 추가한다.
6. 캐시 반영분과 새 수집분이 합쳐진 상태로 `impl/output/keyboards.json`을 저장한다.

즉 `keyboards.json`을 이어붙이는 방식이 아니라, 매번 새 다나와 결과에 누적 캐시를 다시
입히는 방식이다. 다나와 상품명이나 옵션명이 바뀌어도 `product_code` 기반 캐시 키가
있으면 기존 링크를 재사용할 수 있다.

## 동기화

`impl/output/keyboards.json`을 갱신한 뒤에는 반드시 프론트와 Edge Function 사본을 함께
동기화한다.

```bash
cd impl/keybuddy/frontend
npm run sync:data
```

이 명령은 아래 두 파일을 함께 갱신한다.

```text
src/data/keyboards.json
../supabase/functions/recommend/keyboards.json
```

둘 중 하나만 갱신하면 프론트 표시와 추천 후보 데이터가 어긋날 수 있다.

## 검증

최소 검증:

```bash
impl/.venv/bin/python -m unittest discover -s impl -p 'test*.py'
```

데이터 동기화 확인:

```bash
cmp -s impl/output/keyboards.json impl/keybuddy/frontend/src/data/keyboards.json
cmp -s impl/output/keyboards.json impl/keybuddy/supabase/functions/recommend/keyboards.json
```

UI 검증:

- `media_url`이 있는 추천 카드에서 영상 버튼이 표시되는지 확인한다.
- 버튼 클릭 시 새 탭에서 YouTube 영상 페이지가 열리는지 확인한다.
- `media_url`이 없는 항목은 준비 중 상태로 표시되는지 확인한다.

## 주의

- API 키는 문서, 커밋, 프론트 `.env.local`에 남기지 않는다.
- `YOUTUBE_API_KEY`는 크롤러 실행 환경 변수로만 주입한다.
- YouTube API 쿼터를 고려해 기본 운영 limit은 하루 90건 정도로 둔다.
- 403 오류가 나면 쿼터 초과나 API key 제한 설정을 먼저 확인한다.
68 changes: 68 additions & 0 deletions impl/crawl.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
제품-스위치 조합별 레코드로 펼쳐 JSON과 CSV에 저장한다.
"""

import argparse
import csv
import json
import os
import re
import time
from collections import defaultdict
Expand All @@ -16,6 +18,11 @@
import requests
from bs4 import BeautifulSoup

try:
from .youtube_media import enrich_items_with_youtube
except ImportError:
from youtube_media import enrich_items_with_youtube

LIST_URL = "https://prod.danawa.com/list/"
LIST_AJAX_URL = "https://prod.danawa.com/list/ajax/getProductList.ajax.php"
CATEGORY_CODE = "112782"
Expand All @@ -28,6 +35,8 @@
SWITCH_DATA_DIR = BASE_DIR / "keybuddy" / "frontend" / "src" / "data"
SWITCHES_PATH = SWITCH_DATA_DIR / "switches.json"
SWITCH_ALIASES_PATH = SWITCH_DATA_DIR / "switch_aliases.json"
YOUTUBE_CACHE_PATH = OUTPUT_DIR / "youtube_media_cache.json"
YOUTUBE_REPORT_PATH = OUTPUT_DIR / "youtube_media_report.json"

# 기존 데이터셋의 완전성 기준은 유지한다. 새 스위치 필드는 결측을 허용한다.
REQUIRED_FIELDS = (
Expand Down Expand Up @@ -636,8 +645,47 @@ def save_results(collected: list[dict]) -> None:
print(f"미매칭 스위치: {unmatched_path} ({len(unmatched)}종)")


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="다나와 키보드 목록 크롤러")
parser.add_argument(
"--with-youtube",
action="store_true",
help="공식 YouTube Data API로 타건 영상 링크를 보강합니다.",
)
parser.add_argument(
"--youtube-limit",
type=int,
default=90,
help="이번 실행에서 새로 검색할 YouTube query 수입니다. 캐시 hit는 제외합니다.",
)
parser.add_argument(
"--youtube-refresh",
action="store_true",
help="기존 YouTube cache를 무시하고 새로 검색합니다.",
)
parser.add_argument(
"--youtube-cache-path",
type=Path,
default=YOUTUBE_CACHE_PATH,
help="YouTube 검색 결과 cache JSON 경로입니다.",
)
parser.add_argument(
"--youtube-report-path",
type=Path,
default=YOUTUBE_REPORT_PATH,
help="YouTube 검색 리포트 JSON 경로입니다.",
)
return parser.parse_args()


def main():
args = parse_args()
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
youtube_limit = max(0, args.youtube_limit)
youtube_api_key = os.environ.get("YOUTUBE_API_KEY", "").strip()
if args.with_youtube and youtube_limit > 0 and not youtube_api_key:
raise SystemExit("YOUTUBE_API_KEY 환경변수가 필요합니다.")

switches, aliases = load_switch_data()

collected = []
Expand Down Expand Up @@ -696,6 +744,26 @@ def main():
page += 1
time.sleep(DELAY_SEC)

if args.with_youtube:
youtube_stats = enrich_items_with_youtube(
collected,
api_key=youtube_api_key,
cache_path=args.youtube_cache_path,
report_path=args.youtube_report_path,
search_limit=youtube_limit,
refresh=args.youtube_refresh,
)
print(
"YouTube 보강: "
f"cache {youtube_stats['cache_hits']}, "
f"검색 {youtube_stats['api_searches']}, "
f"매칭 {youtube_stats['matched']}, "
f"미발견 {youtube_stats['not_found']}, "
f"스킵 {youtube_stats['skipped']}, "
f"제한 {youtube_stats['limited']}, "
f"오류 {youtube_stats['errors']}"
)

save_results(collected)
print(f"\n상품 {product_count}개에서 제품-스위치 조합 {len(collected)}개 수집 완료")

Expand Down
41 changes: 41 additions & 0 deletions impl/keybuddy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ keybuddy/
- `docs/tag-extraction-flow.md` - 자연어를 의도/제약 태그로 번역하고 결정론적으로 확장하는 흐름
- `docs/intent-harness-before-after.md` - 의도 하네스 적용 전/후 정성 비교
- `docs/deployment-version-management.md` - 배포 및 버전 관리 규칙
- `docs/youtube-media-enrichment.md` - YouTube 타건 영상 링크 수집·캐시·동기화 운영 규칙

## 크롤링 데이터와 스위치 매칭

Expand All @@ -58,6 +59,19 @@ keybuddy/
`null`로 저장합니다. `media_url`은 실제 자료를 확보하기 전까지 `null`로 저장하고
`media_url_is_placeholder`로 준비 중 상태를 표시합니다.

타건 영상 링크는 기본 크롤링에는 포함하지 않고, 공식 YouTube Data API 키가 있을 때만
선택적으로 보강합니다. 검색어는 `상품명 + raw_switch_name`(없으면 `switch_name`)이며,
결과는 `../output/youtube_media_cache.json`에 누적합니다. 다음 실행에서는 캐시를 먼저
입혀 이미 수집한 링크를 `keyboards.json`에 다시 반영하고, 캐시에 없는 항목만 새로
검색합니다.

```bash
cd impl
YOUTUBE_API_KEY=<key> python3 crawl.py --with-youtube --youtube-limit 90
```

`--youtube-limit 0`은 새 검색 없이 기존 캐시만 반영할 때 사용합니다.

## 요청 흐름

```text
Expand Down Expand Up @@ -193,6 +207,33 @@ VITE_SUPABASE_URL=https://your-project-ref.supabase.co
VITE_SUPABASE_ANON_KEY=your-supabase-publishable-key
```

#### 이벤트 수집 테이블 적용

구매 클릭/추천 별점을 수집하려면 `events` 테이블 마이그레이션을 한 번 적용해야 합니다.
(같은 `VITE_SUPABASE_URL`/`VITE_SUPABASE_ANON_KEY`를 쓰는 프론트가 PostgREST로 직접 insert 합니다.)

```bash
cd impl/keybuddy
supabase db push --project-ref your-project-ref
```

CLI를 쓰지 않는다면 Supabase 대시보드 SQL Editor에
`supabase/migrations/20260617000000_create_events.sql` 내용을 붙여 실행해도 됩니다.
이 마이그레이션은 anon에게 **insert만** 허용하는 RLS를 걸어, 이벤트는 적재만 되고
브라우저로 다시 조회되지 않습니다.
별점 이벤트는 한 추천 결과 화면에서 최초 1회만 적재하며, DB 정책에서도 `rating`
payload가 `0.5` 이상 `5.0` 이하인 숫자인 경우만 허용합니다.

**수동 적재 확인**: 프론트에서 추천을 받은 뒤 ① '구매하기' 버튼 클릭 ② 별점 클릭을 하고,
대시보드 SQL Editor에서 다음으로 행이 쌓였는지 확인합니다.

```sql
select event_type, session_id, payload, created_at
from public.events
order by created_at desc
limit 10;
```

## 로컬 실행

프론트:
Expand Down
Loading
Loading