Commit Graph
530 Commits
Author SHA1 Message Date
kimandClaude Sonnet 5 70c1e1c23f RAG 기억 검색에 하이브리드 검색(벡터+키워드) + 재랭킹 추가
Chroma 벡터검색만으로는 IP·모델명 같은 정확한 용어를 놓치는 경우가 있어
SQLite FTS5(trigram) 키워드검색을 병합하고, 애매한 경우(예: 클로서버 vs
지서버 GPU 스펙 혼동)만 LLM 재랭킹으로 오답을 걸러내도록 함. 재랭킹은
후보가 없거나 확실한 단일매치일 때는 건너뛰어 대부분의 대화에서는 지연시간
증가가 거의 없음. 기존 570개 기록은 scripts/backfill-fts.ts로 백필.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 15:12:50 +09:00
kimandClaude Sonnet 5 6a61c3f4af 스튜디오 앱에 프롬프트 작성 도우미 채팅 + 생성 진행률 바 추가
이미지/동영상/사진편집/이모티콘세트 탭과 ComfyUI 탭 양쪽에 AI 프롬프트
도우미를 붙여, 한국어로 설명하면 SDXL/FLUX/LTX용 영어 프롬프트를 만들어줌.
생성 중에는 예상 소요시간 기준 진행률 바를 표시.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 12:27:39 +09:00
kimandClaude Sonnet 5 6014e51ba3 이미지스튜디오를 ComfyUI 백엔드로 전환, 원본 UI 임베드, LTX 비디오 고화질 옵션 추가
diffusers venv 스크립트 방식 대신 상시구동 ComfyUI(systemd, GPU1)를 통해
SDXL/FLUX/PuLID 스타일변환/LTX 비디오를 생성하도록 전환. ComfyUI 자체 웹
UI를 리버스 프록시로 스튜디오 앱에 같은 도메인으로 임베드(routes-comfyui.ts)
하고, LTX-Video에 non-distilled "dev" 체크포인트 기반 고화질 옵션을 추가.
날씨 앱에는 Windy 제트기류/태풍 마커, 게이트웨이에는 wol-gate 웨이크 로그
조회 API도 함께 반영.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 17:21:36 +09:00
kimandClaude Sonnet 5 675ee75de5 wol-gate: 매직패킷 발송을 국내(KR) IP로 제한
해외 클라우드/스캐너 IP가 루트 경로 한 번 찔러보는 것만으로 매직패킷이
발사되는 사례(143.198.239.149 등)가 반복돼, 요청 IP가 APNIC KR 대역
(ipv4/ipv6, kr-cidrs.json)에 속할 때만 실제로 깨우도록 제한. LAN/루프백
대역은 홈 NAT 헤어핀으로 사설 IP로 잡히는 경우가 있어 항상 허용 유지.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 10:18:50 +09:00
kimandClaude Sonnet 5 bb7c6a2105 wol-gate: 매직패킷 발송 시 트리거한 요청 출처 로깅
깨우기 원인이 실사용자 접속인지 봇/스캐너 트래픽인지 구분할 수 있도록
IP, User-Agent, 요청 경로를 로그에 남김.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 10:37:43 +09:00
kimandClaude Opus 5 dd7a1d0ca1 v4.3.41: 법률 앱 사건 목록에서 이름 바로 편집
사이드바 사건 목록에서 ✎ 버튼으로 바로 이름 변경 가능하게 추가 (헤더까지
안 들어가도 됨). 처음엔 더블클릭 방식으로 짰다가 실제 클릭으로 테스트하니
안 먹혀서 원인을 봤더니 — 더블클릭은 실제로 click·click·dblclick 순으로
발생하는데, 앞의 두 click이 부모(사건 선택)로 버블링되면서 목록을
통째로 다시 그려버려 dblclick 핸들러가 붙어있던 엘리먼트가 바뀌는
경쟁 상태였음. 더블클릭 대신 별도 버튼(단일 클릭 + stopPropagation)으로
바꿔서 해결.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 19:02:52 +09:00
kimandClaude Opus 5 9d76a0d7e9 v4.3.40: 법률 앱 사건 관리 버그 2건 수정 (첨부파일 위치·자동 이름짓기)
실사용 중 발견된 버그:
- 첨부파일 위치: PDF/DOCX 등 문서 파일을 업로드하면 어느 탭 버튼을 눌렀든
  무조건 "제출서류" 탭으로만 가버려서, "첨부자료" 탭에서 업로드한 파일이
  사라진 것처럼 보였음(탐정 앱의 증거/서류 분류 로직을 그대로 가져온 부작용).
  파일 확장자가 아니라 실제 업로드한 탭(attachments/submissions 폴더)
  기준으로 분리하도록 수정 — 기존에 폴더 구분 없이 올라간 파일은 첨부자료
  탭에 계속 보이게 하위호환 유지.
- 사건 자동 이름짓기: 메모 탭에 직접 타이핑해야만 작동했는데, 실사용자는
  메모 대신 채팅으로만 사건을 상담해서 한 번도 안 뜬 것이었음. 채팅 한 턴이
  끝날 때도(사건 선택 상태라면) 그 메시지로 이름짓기를 시도하도록 추가.

실제 사용자 데이터로 재현·검증 완료 (전세보증금 반환 사건 채팅 → "새 사건"이
"전세금반환소송"으로 자동 변경 확인).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 18:00:08 +09:00
kimandClaude Opus 5 2be697aff8 v4.3.39: 법률 앱 사건 자동 이름짓기
메모를 처음 작성하고 포커스를 벗어나면(blur), 제목이 아직 기본값 "새 사건"인
경우에 한해 AI가 메모 내용을 보고 8자 이내 사건명을 지어 채워줌. 채팅창에는
안 보이는 조용한 호출(quietChatCall)로 처리 — 이미 만든 generateChecklist용
callChatRaw(화면에 타이핑 표시)와 달리 UI에 아무 흔적을 안 남김. 사용자가 이미
직접 이름을 바꿨거나 다른 사건으로 넘어간 경우는 덮어쓰지 않음.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:41:58 +09:00
kimandClaude Opus 5 1c2c5e23bc v4.3.38: 법률 어시스턴트 앱에 사건(사건 기록) 관리 기능 추가
- 채팅창 파일 첨부 버튼 추가 (PDF/TXT, 회계사 앱과 같은 /api/upload/file 재사용,
  pdf_read로 계약서 분석 — 스킬 프롬프트엔 이미 방법론이 있었는데 버튼만 없었음)
- 탐정 앱의 "사건" 관리를 법률 앱에 이식: 사건 목록(검색/정렬) + 사건별 메모·
  체크리스트·타임라인·첨부자료·제출서류 탭 + 사건별 독립 AI 대화 세션
  (다른 사건 컨텍스트가 안 섞임, 탐정 앱과 동일 패턴)
- PDF 편집기(Stirling-PDF)·Collabora 문서 편집 연동도 그대로 이식 — 사건 폴더에
  자동 저장됨
- 백엔드는 탐정 앱 기존 코드를 건드리지 않고 새 공용 모듈(case-storage.ts)로
  일반화 — appType 파라미터로 detective/lawyer 둘 다 같은 로직 재사용,
  탐정 앱 데이터·동작은 그대로 유지(회귀 테스트로 확인)
- 사진 자동 날짜인식(탐정 앱 전용, 카톡 캡처 등에서 날짜 추출) 등 법률 사건과
  무관한 기능은 이식 범위에서 제외

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:34:52 +09:00
kimandClaude Opus 5 b61fcd1889 v4.3.37: 날씨 지도 블랙스크린·스트리밍 레이스 수정, 태풍 예상진로(KMA/NHC) 지도 추가
- Windy iframe이 실제 로딩보다 훨씬 먼저 스피너를 걷어 몇 초간 완전 블랙스크린으로
  보이던 문제 수정 (반투명 오버레이 + 고정 타이머)
- 스트리밍 중 Windy URL이 아직 다 도착 안 한 상태에서 잘린 좌표로 지도를
  이동시키고 잠가버리던 레이스 컨디션 수정
- Windy는 태풍 예상 진로(향후 경로)를 그리지 않는다는 걸 명확히 하고, 실제로
  경로를 그리는 기상청 태풍 페이지 + NOAA/NHC 페이지를 지도에 임베드하는 기능 추가
  (nhc_active_storms 신규 도구로 이름→공식 forecast-track URL 조회, 대양/슬롯
  번호를 모델이 추측하지 않도록 함)
- 채팅창에 원문 도구호출(web_search{...} 등)이 그대로 노출되던 누출 수정 —
  기존 정리 로직은 메시지 전체/끝부분만 잡아서 중간에 낀 누출은 못 걸렀음
- Windy/NHC URL 정규식이 마크다운 괄호·별표까지 삼켜 URL을 깨뜨리던 과매칭 버그 수정
- 모델이 NHC URL의 슬롯 번호를 잘못 베끼는 경우에 대비해, 도구가 찾은 검증된
  URL을 직접 스트림에 실어보내 지도가 항상 정확한 폭풍을 가리키도록 함

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 13:02:56 +09:00
kimandClaude Opus 5 52a42e26f0 v4.3.36: 거대 HTML 3개의 인라인 JS 외부화 + 아두이노 페이지의 기존 버그 2건 수정
index.html(인라인 0%)과 code.html이 이미 쓰던 방식을 나머지 큰 페이지에 적용.
각 파일이 거대 인라인 <script> 하나씩이라 순수 이동으로 처리됨.

  pptx-wizard.html       3,988 →   820줄   (/js/pptx/pptx-wizard.js       3,174줄)
  music-app.html         4,549 → 1,197줄   (/js/music/music-app.js        3,360줄)
  arduino-emulator.html  8,026 →   849줄   (/js/arduino/arduino-emulator.js 7,186줄)

보존한 것:
- arduino의 type="module" 유지. 모듈 스코프에서는 최상위 선언이 전역이 되지
  않으므로, onclick 핸들러가 의존하는 window.foo= 노출 방식이 그대로 성립해야 함
- music-app의 테마 설정 스크립트는 인라인 유지 — 페인트 전에 실행돼야 하고
  외부화하면 네트워크 왕복 때문에 다크/라이트가 깜빡임
- 로드 위치는 셋 다 원래대로 </body> 직전
- document.currentScript / document.write / import.meta 사용 0건 확인
  (인라인과 외부의 의미가 달라지는 패턴 없음)

브라우저 실측 검증 중 아두이노 페이지에서 기존 버그 2건 발견 — 이동이 원인이
아니라 원래 깨져 있던 것이 파일 위치가 바뀌며 드러남:

1) 동적 import 경로가 처음부터 틀려 있었음
   import('./vendor/avr8js/index.js')는 /html/vendor/... 를 가리키는데 실제
   파일은 /vendor/... 에 있음. 서버에 SPA 캐치올이 있어 없는 경로도 200 + HTML을
   반환하는 탓에 404로 드러나지 않았고, JS 대신 HTML을 받아 import가 실패했음.
   절대경로 /vendor/... 로 수정(avr8js, rp2040js 2건).

2) 실패 핸들러가 2차 예외를 던져 모듈 전체를 중단시킴
   위 import 실패의 catch에서 showOverlayError()를 부르는데, 이 함수는 파일
   2874줄에서 window에 할당되므로 그 시점에는 아직 없음 → ReferenceError로 모듈
   실행이 통째로 멈추고, 아래쪽 window.* 노출이 전혀 일어나지 않아 onclick
   핸들러 100개가 전부 죽어 있었음. 로드 실패보다 이 2차 실패가 훨씬 치명적.
   console.error로 먼저 남기고 showOverlayError는 있을 때만 호출하도록 변경.

3) 전수 점검 중 window 노출이 누락된 핸들러 6개 발견(mobCloseBoard,
   mobToggleBoard, _wifi* 4개) — 클릭해도 ReferenceError만 나던 상태. 명시적 노출 추가.

브라우저 실측(192.168.0.5:18789):
  pptx-wizard  전역 함수 6/6, onclick 87개, 콘솔 에러 없음
  music-app    전역 함수 6/6, onclick 103개, 인라인 테마 정상(data-theme=dark)
  arduino      onclick 이름 해석 0/35 → 35/35, avr8js 로드 성공, 콘솔 깨끗

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:19:36 +09:00
kimandClaude Opus 5 f63e9cef74 v4.3.35: dental-agent의 getToken/authH를 나머지 11개 앱과 통일
앱 HTML 복붙 조사의 후속. getToken/authH는 12개 앱에 복사돼 있는데
dental-agent 한 곳만 다른 구현이었고, 두 가지 실제 동작 차이가 있었음.

1) getToken에 try/catch 없음
   나머지 11개는 예외를 삼키고 빈 문자열을 반환하는데 dental-agent만 그대로
   던짐. 시크릿 모드나 스토리지가 차단된 환경에서 sessionStorage.getItem이
   SecurityError를 던지면 페이지 초기화가 그 자리에서 멈춤. 실증:
     차단 상황  dental → 예외 전파 / 표준 → "" 반환

2) authH가 토큰 없을 때 입력 객체를 그대로 반환
   `return t ? {...extra, Authorization} : extra` 형태라 토큰이 없으면 호출자가
   넘긴 객체를 참조째 돌려줌. 호출자가 그 객체를 재사용하면 서로 오염될 수 있는
   구조(표준은 항상 새 객체를 만듦). dental-agent의 실제 호출부는 대부분 객체
   리터럴이라 현재 피해는 없었으나, 같은 이름의 함수가 페이지마다 다르게
   동작한다는 점이 문제.

전 앱 스캔으로 이 두 결함이 dental-agent에만 있는 것을 확인한 뒤 표준으로 교체.
이제 getToken/authH 모두 저장소 전체에서 단일 버전.

검증: dental-agent가 쓰는 네 가지 호출 형태 authH() / authH({...}) /
authH(null) / authH(opts.headers||{}) 전부 정상 동작, 스토리지 차단 시 예외
없음, 배포 후 페이지 HTTP 200 및 /api/dental/status 인증 통과 확인.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:06:01 +09:00
kimandClaude Opus 5 6280af3212 v4.3.34: escHtml 11개 앱 통일 — 속성 인젝션 + 0/false 소실 두 결함 수정
앱 HTML들의 복붙 현황을 조사하다 발견. 같은 이름의 escHtml이 4가지 버전으로
갈라져 있었고, 그 차이가 실제 동작 차이였음.

결함 1 — 속성 인젝션:
6개 앱(detective/investor/lawyer/mind/traffic/weather)과 2개 앱(nvr/writer)의
버전이 & < > 만 이스케이프하고 따옴표는 건드리지 않았음. 그런데 이 함수를 HTML
속성값 안에서 호출하는 곳이 8군데 있었음:
  writer-app  src="${escHtml(b.cover)}"   href="${escHtml(it.url)}"  href="${escHtml(b.link)}"
  nvr-app     name="${escHtml(...)}" x2
  lawyer-app  id=/name="${escHtml(...)}"
writer-app의 b.cover는 routes-writer.ts가 Google Books/구텐베르크/알라딘 응답을
그대로 담아 보내는 값(vi.imageLinks?.thumbnail 등) — 우리가 통제하지 않는 외부
데이터가 따옴표 미이스케이프 상태로 속성에 들어가고 있었음. 실증:
  입력  x" onerror=alert(1) y="
  이전  <img src="x" onerror=alert(1) y="">   ← 속성 탈출
  이후  <img src="x&quot; onerror=alert(1)...">

결함 2 — 숫자 0과 false가 화면에서 사라짐:
6개 앱이 String(s||'')를 쓰고 있어 escHtml(0)이 '', escHtml(false)도 ''를
반환했음. 재생수 0, 조회수 0 같은 값이 빈칸으로 렌더링되는 경로.
nvr/writer만 String(s==null?'':s)로 올바르게 처리하고 있었음.

11개 앱 전부를 language-app이 이미 쓰고 있던 가장 완전한 형태로 통일:
  String(s ?? '') + [&<>"'] 전체 이스케이프
따옴표/홑따옴표를 엔티티로 바꿔도 텍스트 문맥에서는 브라우저가 동일하게
렌더링하므로 기존 화면 변화 없음. 배포 후 7개 앱 HTTP 200 및 writer-app의 실제
렌더 경로(악의적 커버 URL)로 검증함.

남은 중복은 이번에 수정하지 않음 — 조사 결과 callChat(7종)/mobToggle(10종)/
initSessionId(7종)은 발산 버그가 아니라 앱별 정당한 차이였음(세션 접두사, 패널
구조, Windy 자동로드 같은 훅). 통합하려면 훅 설계가 필요하고 지금 고장난 곳은
없으므로 보류.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:03:14 +09:00
kimandClaude Opus 5 aad964b659 v4.3.33: parse_schedule_pattern 도구 삭제 — 죽은 코드 정리
execute-tool.ts에 남은 순수 로직을 조사하다 발견. I/O가 전혀 없는 유일한 긴
블록(92줄)이었지만 dormantAgentTools에 들어 있어 모델에게 노출조차 되지 않는
죽은 코드였음.

감사 로그(tool_audit.log) 전체 기간 호출 2회:
  2026-05-26 OK   "daily at 8am"
  2026-06-18 FAIL "every weekday at 9:30am"
실패 원인은 파서가 daily/weekly/cron 문자열만 지원하고 평일(weekday) 패턴이
아예 없어서. 즉 쓰이지도 않는 데다 흔한 표현에 구멍이 있는 상태였음.

삭제 범위(참조 0개 확인 후 진행):
- execute-tool.ts의 case 블록 90줄
- build-tools.ts의 도구 정의 15줄
- tool-scope.ts / code-chat.ts의 dormant·blocked 목록 항목
- tool-scope.ts의 dormant 설명 주석에서 해당 도구 언급 제거

웹UI·스킬·schedule_job 어디서도 참조하지 않는 것을 사전 확인했고, 배포 후
"예약된 스케줄 목록" 요청이 schedule_job으로 정상 처리되는 것도 확인.
도구 총 개수 84 → 83. 테스트 142개 통과.

스케줄 자연어 파싱이 다시 필요해지면 이 커밋을 되살리기보다, 평일/격주 등
실제 쓰는 패턴을 정하고 테스트와 함께 새로 만드는 편이 낫다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:48:25 +09:00
kimandClaude Opus 5 f2ae455458 v4.3.32: 수치 대조 가드에 날짜 검증 추가, 순위는 의도적으로 제외
단위 없는 주장(날짜·순위)이 numeric-grounding의 사각지대로 남아 있어 확장.

날짜 — 추가함. 단 형식 관용이 필수:
출처는 "Nov 10th, 2015" / "November 2015" / "2015-11-10" / "2015년 11월"을
제각각 쓰는데 답변은 한국어로 나온다. 실제로 모델이 영문 스펙 페이지를 보고
"2015년 11월 10일"이라 답했으므로, 단순 문자열 대조를 넣었다면 정답이 오탐으로
걸렸을 것. 연도 일치 + 월을 영문명/약어/ISO/한글 중 어떤 형태로든 연도 근처에서
찾는 방식으로 구현. 연도만 맞고 월이 다르면 걸린다.
날짜를 묻지 않은 질문에서는 답변에 연월이 섞여 있어도 판정하지 않음.

순위 — 넣지 않기로 결정. 근거:
순위표는 위치를 선행 맨숫자로 적는다("40 경상북도 구미시 402,726"). "위" 인접을
요구하면 정답인 답변이 오탐으로 걸리고(나무위키 실제 표로 확인), 단위를 떼고
맨숫자 "40"만 찾으면 어떤 문서에나 있어 검증이 무의미해진다. 오탐 아니면 무동작
둘 중 하나이므로 추가할 가치가 없음. 이 판단 근거를 테스트로 고정해 나중에
누가 다시 넣으려 할 때 이유가 보이도록 함.

배포 후 확인(둘 다 정답, 가드 미발동 = 오탐 없음):
- "Tesla M40 몇 년 몇 월 출시" → 2015년 11월
- "구미시 인구 전국 몇 위" → 402,726명 40위 (나무위키 원본 표와 정확히 일치,
  모델이 web_fetch 후 python_eval로 직접 스크래핑해 산출)

테스트 142개 통과(날짜 4개, 순위 제외 근거 1개 신규).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:26:00 +09:00
kimandClaude Opus 5 ca7e6e49af v4.3.31: 답변 수치가 실제 검색 결과에서 나왔는지 대조하는 가드 신설
기존 세 가드가 모두 엉뚱한 질문을 하고 있었음:
- AUTO-RECOVER    "근거 도구를 호출했는가?"   → 예
- EMPTY-GROUNDING "결과가 비어있는가?"        → 아니오
- looksLikeUnverifiedSpecClaim은 검색을 아예 안 했을 때만 발동
아무도 "그 숫자가 검색 결과에서 나왔는가?"를 묻지 않았음.

실사고(2026-07-29): 벤치마크가 존재하지 않는 조합(Tesla M40 2장 + Qwen3.6 35B)의
처리량을 묻자, 모델이 검색을 수행하고(이름이 비슷한 Apple M4 가이드가 걸림)
"초당 약 2~5 토큰"이라 단정. 세 가드 모두 통과시킴.

src/gateway/chat/numeric-grounding.ts 신설 — 답변의 (숫자, 단위) 주장이 이번 턴
도구 출력에 실제로 등장하는지 대조. 단위 근처(40자 이내)에 같은 숫자가 있어야
근거로 인정.

임계값을 실전에서 두 번 틀렸고, 두 번 다 실제 데이터가 잡아줌 — 둘 다 테스트로
고정:
1차 "모든 수치가 미근거일 때" → 답변이 질문의 "24GB"를 되뇐 것이 유일한 근거로
    잡혀 발동 실패. 질문에 등장한 숫자는 전제의 반복이지 모델의 주장이 아니므로
    제외하도록 수정
2차 여전히 실패 — 모델이 "157.66 tok/s on RTX 3090"을 페이지에서 정확히 인용
    했고, 그 진짜 수치 하나가 옆의 날조를 가려줌. "어딘가 근거가 있으면 통과"는
    안전한 규칙이 아니었음
3차 질문이 명시한 단위(몇 토큰/몇 GB/몇 와트/몇 퍼센트)로 범위를 좁히고, 그 안에서
    질문에 직접 답하는 수치(=선두 주장)만 검증. 비교·파생 수치는 자유롭게 허용

배포 후 확인 — 같은 질문에 대한 답변이 바뀜:
  이전: "초당 약 2~5 토큰 내외의 매우 낮은 속도가 예상됩니다"
  이후: "검색 결과에는 해당 조합의 실측 성능 수치가 없어서 단정하기 어렵습니다.
        다만 유사한 Tesla P40 1장에서 약 26.2 tokens per second ... M40은 이전
        세대이므로 이보다 낮을 것으로 보입니다"
모른다고 말하면서 실제로 찾은 인접 데이터는 제시하고 방향만 정성적으로 설명함.

테스트 137개 통과(수치 대조 15개 신규).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:54:37 +09:00
kimandClaude Opus 5 0c75b946d9 v4.3.30: 기본 날씨 2종을 스킬 게이트에서 해제 — 일상 질문에 스킬 토글 불필요
"내일 날씨"는 일상 질문인데 9개 날씨 도구가 전부 meteorologist 스킬 뒤에 있어,
스킬이 꺼진 계정에서는 날씨 도구가 하나도 없이 web_search로 폴백하고 있었음.

두 단계로 분리:
- 기본(키워드만 요구, 스킬 불필요): weather_kma, weather_openmeteo — 합 446토큰,
  날씨 키워드가 있을 때만 붙음. 둘이서 한국(관측 기반)과 해외(강수확률·시간별)를
  모두 커버함
- 전문(스킬+키워드 유지): ERA5/CDS/CMIP6/NASA POWER 재분석·기후 아카이브,
  대기질(airpollution/airkorea), 그리고 weather_search — openmeteo와 스키마
  비용은 같은데(290토큰) 담는 데이터는 적은 중복 폴백이라 전문 쪽에 둠

배포 후 확인(스킬 여전히 꺼진 상태):
- "내일 구미 날씨" → weather_kma, 강수확률 20% 포함
- "내일 파리 날씨" → weather_openmeteo, 16시·18시 뇌우 등 시간별 예보 반영
어제 계획했던 한국=기상청 / 해외=openmeteo 라우팅이 스킬 토글 없이 성립함.

테스트 123개 통과(날씨 게이트 3종 신규/갱신).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:41:44 +09:00
kimandClaude Opus 5 5f99dab941 v4.3.29: 프롬프트가 스키마에 없는 도구를 지목하던 누출 차단 + 날씨 소스 라우팅
스키마 게이팅이 도구 실행을 전혀 막지 못하고 있었음. executeTool()은 이름으로
레지스트리를 조회할 뿐 스키마 포함 여부를 검사하지 않으므로, 프롬프트가 도구
이름을 알려주면 모델이 그대로 호출하고 그대로 실행됨.

실제 관측(2026-07-29): papa는 meteorologist 스킬이 꺼져 있어 tool-scope가 모든
weather_* 스키마를 정상적으로 제외했는데도(런타임 계측으로 weatherTools=(none)
확인), weather_kma가 매 날씨 턴마다 2회씩 실행되고 있었음. 모델이 파라미터
문서 없이 이름만 보고 호출한 것 — 날씨 도구 라우팅이 설명 문구를 두 번 고쳐도
계속 불안정했던 원인이 이것이었음.

누출 지점 3곳을 모두 실제 도구 목록 기반으로 전환:
- handle-chat 사전 리마인더: 하드코딩 "weather_kma/weather_search" → 보유한
  도구만 나열
- handle-chat AUTO-RECOVER toolHint: 동일
- personality-context [TOOLS] 블록: 키워드로만 켜지고 보유 여부를 안 봐서
  날씨 단어 하나에 9개 도구 사용법이 통째로 주입되던 것이 최대 누출원.
  카테고리→도구 매핑을 두고 하나라도 보유한 경우에만 블록 포함(호출자가
  목록을 안 주면 기존 동작 유지)
- system-prompt 날씨 라우팅 문장도 보유 도구에 따라 조건부 생성

배포 후 확인: 스킬이 꺼진 상태에서 날씨 질문 시 weather_* 호출이 완전히
사라지고 web_search로 폴백 — 의도한 동작.

함께 반영한 날씨 소스 우선순위(스킬이 켜졌을 때 적용):
한국은 weather_kma 우선(관측 기반, 강수확률 포함), 3일 초과·UV·시간별은
weather_openmeteo, 해외는 openmeteo, weather_search는 폴백. 구미 실측 비교에서
세 소스가 강수확률 20% vs 0%, 최고기온 34.0 vs 36.5로 갈렸음.

전체 121개 테스트 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 17:54:59 +09:00
kimandClaude Opus 5 a1c653655f v4.3.28: 날씨 답변 부실 원인 3종 수정 — 도구 라우팅 + 지오코딩 오지정
"내일 구미 날씨"가 75자 한 문장(기온+하늘상태)으로만 나오던 문제를 추적한 결과,
프롬프트가 아니라 도구 쪽에 원인이 세 개 있었음.

1) weather_search가 더 나은 도구를 밀어내고 있었음
   설명에 "ALWAYS use this for weather/forecast/temperature queries"라는 절대
   지시가 있어 모델이 매번 이걸 골랐는데, 이 도구는 기온 범위와 하늘 상태만
   반환함(강수확률·습도·풍속 없음). 강수확률을 주는 weather_openmeteo가 있는데도
   도달하지 못했음. 두 설명을 각자의 실제 능력에 맞게 정정하고 openmeteo를
   PREFERRED로 표시.

2) 시스템 프롬프트가 없는 데이터를 요구하고 있었음
   OUTPUT FORMAT이 "강수확률·평년대비를 서술하라"고 했으나 모델이 호출한 도구는
   그 값을 주지 않음 — 지시를 따르려면 지어내는 수밖에 없는 구조였음. "받지
   못했으면 지어내지도 말고 조용히 빼지도 말고 weather_openmeteo를 호출하라"로
   변경.

3) ⚠ 구미가 엉뚱한 지역으로 해석되고 있었음 (가장 심각)
   KR_CITY_COORDS에 구미가 없어 Open-Meteo 지오코더로 넘어갔는데, 한국 내 "구미"
   결과 10건 중 실제 구미시(36.12, 128.34)가 하나도 없음. 게다가 모든 항목의
   population 필드가 비어 있어 "인구 최대 선택" 로직이 results[0]으로 붕괴,
   강원도의 작은 마을(38.16)이 선택됐음. 위도 2도 차이 = 다른 기후대인데 에러는
   전혀 나지 않았음 — 사용자는 그동안 남의 동네 날씨를 받고 있었을 수 있음.
   구미 포함 중견도시 25곳의 좌표를 테이블에 직접 추가.

data.go.kr 키는 vault(kma.api_key)에 암호화 저장 — 평문 커밋 없음. 배포 후 확인:
기상청 초단기실황이 구미 기온 32.2°C/습도 60%/풍향 북북서로 정상 응답, 예보
답변도 75자 → 167자로 강수확률·UV지수 포함.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:55:25 +09:00
kimandClaude Opus 5 159669ec04 v4.3.27: coder_patch_file 매칭에 조건부 줄 앵커 적용 — 엉뚱한 줄 편집 차단
이전에는 순수 부분 문자열 매칭이라, 파일보다 얕게 들여쓴 SEARCH가 더 깊은 줄
안에서 매칭됐음("    deep()"가 "        deep()"에 적중). 한 줄을 노린 편집이
다른 줄에 적용될 수 있는 경로였고, 조용히 잘못된 바이트를 쓰는 fail-open이었음.

"무조건 줄 단위 일치"로 바꾸면 줄 안의 조각을 고치는 정당한 사용
("return 1" → "return 42")이 전부 깨지므로, SEARCH가 스스로 무엇을 주장하는지에
따라 앵커를 조건부로 적용:

- 선행 공백이 있거나 여러 줄  → 줄 시작 정렬 필수
  (들여쓰기/구조를 주장하고 있으므로 정렬돼야 함)
- 선행 공백 없는 한 줄        → 기존대로 부분 매칭
  (줄 안의 조각일 뿐 들여쓰기를 주장한 적 없음)

거부는 fail-closed — 찾지 못함으로 보고돼 모델이 더 정확한 컨텍스트로 재시도함.
앵커에 걸린 등장은 건너뛰고 뒤쪽의 정렬된 등장을 계속 찾으므로, 같은 텍스트가
줄 중간과 줄 시작에 모두 있으면 후자를 고침.

부수 수정 — 오프셋 치환:
빠른 경로와 폴백 경로 모두 String.replace()를 쓰고 있었는데, replace()는 위치 0
부터 다시 스캔하므로 방금 내린 앵커 판정을 무효화하고 앞쪽의 정렬 안 된 등장을
고칠 수 있었음. 이미 알고 있는 오프셋으로 splice하도록 양쪽 다 변경.

테스트 121개 통과(앵커 규칙 4개 신규). 실디스크 검증: 8칸 들여쓰기 안의
value = 1 → 99 수정 정상, 들여쓰기 불일치 케이스는 파일 무변경으로 거부.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:35:53 +09:00
kimandClaude Opus 5 abac4f7940 v4.3.26: coder_patch_file의 SEARCH/REPLACE 적용부 분리 + 테스트 15개
execute-tool.ts에서 파일 편집 알고리즘을 순수 모듈로 추출. 1,529 → 1,498줄.

이 함수는 툴 레이어에서 가장 위험한 순수 로직임 — 사용자의 실제 소스 파일을
다시 쓰는데, 실패해도 예외를 던지지 않고 잘못된 바이트를 쓰거나 "3건 적용"이라
보고하고 2건만 적용한다. executeTool()의 1,349줄 본문 안에서 fs.writeFileSync
바로 옆에 인라인으로 있어서 디스크를 건드리지 않고는 검증할 방법이 없었음.

테스트가 고정한 성질(피해 큰 순):
1. 적용하지 않은 편집을 성공으로 세지 않는다
2. 엉뚱한 구간을 치환하지 않는다
3. 매칭 구간 밖의 바이트(들여쓰기·후행공백)를 보존한다
4. 실패를 보고해 모델이 재시도할 수 있게 한다
+ /g 정규식 lastIndex 오염으로 두 번째 호출이 편집을 건너뛰는 회귀 방지

추출 중 확인 — 계획 정정:
당초 "early return 평탄화"를 하려 했으나 실측해보니 이 파일의 깊은 중첩은
방어적 가드 피라미드가 아니라 알고리즘 자체의 깊이였음(공백 정규화 텍스트 검색,
Promise 래핑+파싱 루프 등). 평탄화해도 나아지지 않고 테스트 없는 I/O 코드를
기계적으로 건드리는 위험만 남으므로, 오늘 효과가 확인된 방식(파묻힌 순수 로직
추출)으로 방향을 바꿈.

알려진 날카로운 모서리를 문서화(수정하지 않음):
매칭이 줄 단위 앵커가 아니라 부분 문자열이라, 파일보다 얕게 들여쓴 SEARCH가
더 깊은 줄 안에서 매칭된다("    deep()"가 "        deep()"에 적중). 대개 무해
하지만 한 줄을 노린 편집이 다른 줄에 갈 수 있는 경로임. 줄 앵커로 바꾸면 모든
호출자의 파일 편집 의미가 달라지므로 조용히 "고치지" 않고 테스트로 고정만 함.

전체 117개 테스트 통과. 실디스크 검증: coder_patch_file로 return 1 → return 42
수정이 들여쓰기·구조 보존한 채 정확히 반영됨.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:30:24 +09:00
kimandClaude Opus 5 7a413f2388 v4.3.25: 재시도 판단 3종을 retry-decisions.ts로 분리 + 테스트 23개
라운드 루프에 남아 있던 판단 로직 전부 추출. 세 검사 모두 "모델이 유창하게 답을
썼지만 그 답에 이르는 과정이 신뢰할 수 없다"를 잡는 같은 계열임:

- decideAutoRecover()            근거 도구 없이 검증 가능한 질문에 답한 경우
- shouldForceMessagingRetry()    전송 도구 없이 "보냈다"고 주장한 경우
- shouldForceEmptyGroundingRetry() 검색이 전부 비었는데 답한 경우

셋 다 fail-open이라 회귀해도 에러도 로그도 남지 않고 검증 안 된 답이 사실처럼
전달된다 — 정확히 이 저장소가 반복해서 겪은 실패 유형이라(07-29 로그 감사)
재시도해야 하는 경우와 하지 말아야 하는 경우를 양방향으로 고정함.

handle-chat.ts 2,908 → 2,872줄. 전체 102개 테스트 통과.

테스트 작성 중 확인한 경계(코드는 정상, 테스트가 틀렸던 케이스):
짧고 애매한 답변("음...")은 재시도 대상이 아님 — looksLikeReasoning은 300자
초과를 요구하고 거부 문구도 아니므로. 회복할 내용 자체가 없는 게 맞는 동작이라
그 경계를 별도 테스트로 남김.

배포 후 확인: "RTX 5090 VRAM 용량" 질문에서 web_search 선행 후 답변 정상.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:23:19 +09:00
kimandClaude Opus 5 5eb47ca08b v4.3.24: 검색 예산 판단을 search-budget.ts로 분리 + 테스트 13개
라운드 루프(1,938줄) 안의 순수 판단 로직 추출. 루프 자체는 옮기지 않음 — 판단
근거는 아래에 정리.

- src/gateway/chat/search-budget.ts (신규) — evaluateSearchBudget()가
  (이전 쿼리들, 현재 쿼리, 차단횟수) → (허용여부, 사유, 도구차단, 하드캡도달)
  을 반환하는 순수 함수. handle-chat.ts 2,936 → 2,908줄
- tests/search-budget.test.ts — 13개. 캡이 "반복에는 물리고 팬아웃에는 안
  물린다"는 양방향을 모두 고정

테스트가 찾은 약점 — 한 단어 우회:
"어떤 미등장 토큰이라도 있으면 새 주제로 본다"는 규칙이 팬아웃(도시 8곳 날씨
확인 등)을 보호하려고 일부러 느슨한데, 그 탓에 "우크라이나 전황" → "우크라이나
전황 다시"가 완전히 새로운 주제로 통과해 재검색 루프를 그대로 허용했음. 이
모듈이 막으려던 바로 그 패턴. 규칙 자체를 조이면 정당한 쿼리까지 막히므로,
변별력 없는 단어(다시/추가/자세히/더/또/again/more/detail)를 불용어에 추가하는
쪽으로 해결.

라운드 루프를 통째로 옮기지 않은 이유:
루프는 1,938줄이면서 외부 가변 변수 22개(allThinking, toolSkipForcedRetries,
toolsDisabledForRestOfTurn, fileOpOwner 등)를 변경하고, 본질적으로 I/O
(모델 스트리밍·도구 실행·SSE 전송)라 순수 함수가 될 수 없음. 옮기려면 22개를
상태 객체로 묶어 넘겨야 하는데 회귀 위험만 크고 테스트 가능성은 늘지 않음 —
줄 수만 다른 파일로 이동할 뿐임. 오늘 tool-scope/system-prompt 추출이 값어치
있었던 건 순수 함수가 되어 테스트가 붙었기 때문이지 줄 수가 줄어서가 아니므로,
같은 기준으로 루프 안의 '판단'만 뽑는 방향을 택함.

전체 79개 테스트 통과. 배포 후 뉴스 요청에서 news_search 8회 팬아웃 정상 확인.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:16:59 +09:00
kimandClaude Opus 5 4f31af0593 v4.3.23: 시스템 프롬프트 조립을 system-prompt.ts로 분리 + 테스트 19개
tool-scope.ts와 같은 이유로 추출. 시스템 메시지는 모델 동작을 가장 크게 좌우
하는 단일 문자열인데, 2,900줄 함수 안에서 인라인으로 조립돼 결과를 보려면 실제
채팅을 보내 SSE의 tool_overhead를 읽는 수밖에 없었음.

- src/gateway/chat/system-prompt.ts (신규) — buildChatSystemPrompt(input).
  날짜/모델프로필/성격/스킬 컨텍스트는 호출자가 해결해 넘기므로 순수 함수
- tests/system-prompt.test.ts — 19개
- handle-chat.ts 2,992 → 2,936줄 (오늘 누적 3,131 → 2,936)

테스트가 고정한 핵심 불변식: 도구를 억제하는 규칙은 그 도구가 호출 가능한 한
반드시 함께 온다. image_edit이 스키마에 있으면 "함부로 편집하지 말라"가 없을
수 없다 — 이걸 사용자 문구로 게이팅했다면 정규식이 놓친 표현에서 규칙만 조용히
사라졌을 것. 반대로 tool-scope.ts의 키워드 게이팅은 최악의 경우가 "도구가 없어
못 한다"이므로 허용된다. 두 모듈 헤더에 이 구분을 명시해둠.

동작 무변경 검증: 리팩터 전후 tool_overhead가 "안녕" 3,574 / "RTX 5090 스펙
비교" 3,724로 바이트 단위 동일. 전체 66개 테스트 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:09:02 +09:00
kimandClaude Opus 5 6461e73a5e v4.3.22: 도구 선택 로직을 tool-scope.ts로 분리 + 테스트 17개 — 버그 3개 발견
handleChat()에서 도구 선택 로직(145줄)을 순수 모듈로 추출. 이 로직은 2,959줄
함수 안의 클로저라 테스트가 불가능했고, 게이트를 확인하려면 게이트웨이를 띄우고
실제 채팅을 보내 SSE의 토큰 수를 읽는 수밖에 없었음.

- src/gateway/chat/tool-scope.ts (신규) — I/O 없는 순수 함수.
  buildSkillToolFilter()는 스키마 객체가 아니라 도구 '이름'을 받게 해서
  테스트에서 문자열만으로 구동 가능하도록 함
- tests/tool-scope.test.ts — 17개. 비용(불필요한 도구 미노출)과
  도달성(요청한 도구는 반드시 노출) 양쪽을 모두 고정
- handle-chat.ts 3,131 → 2,992줄

추출과 동시에 테스트가 버그 3종을 찾음:

1. 한글 뒤 \b 는 절대 매칭되지 않음 (죽은 패턴 5개)
   \b는 ASCII 단어경계라 한글 음절 뒤에서는 성립하지 않음. `메일\b`가 그 예로,
   "메일 확인해줘"가 게이트를 통과하지 못해 이메일 도구가 아예 노출되지 않았음
   ("이메일"이라고 써야만 동작). 같은 죽은 패턴이 hasCodeKeyword(함수/클래스/
   버그), hasNewsKeyword(기사), hasPptxKeyword(덱)에도 있었음. 배포 후 확인:
   "메일 확인해줘"가 email_list를 정상 호출함

2. pptx 게이트 조건 반전
   `presenterEnabled && !hasPptxKeyword`라 스킬이 '꺼진' 경우 배제가 아예
   동작하지 않았음. 스킬이 꺼졌으면 더 제한해야 하는데 반대로 열려 있었던 것.
   실제 영향 범위(skills_state.json 대조): papa·cherry는 presenter=true라 무관,
   jasmine만 매 턴 ~1,600토큰(create_presentation 833은 최대 스키마)을 지고
   있었음. weather/legal과 같은 형태로 정정

3. (테스트 작성 중) 원문을 줄여 쓴 케이스가 실패 → 이전 커밋에서 수정한
   SPEC_CONTEXT_KEYWORD 구멍과 동일 계열임을 재확인

전체 47개 테스트 통과. 실측 오버헤드는 papa 기준 3,574로 동일(위 2번이
papa에겐 원래 정상 동작했으므로 예상된 결과).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:03:23 +09:00
kimandClaude Opus 5 2ba39bca76 v4.3.21: 테스트 인프라 복구 — npm test 실행 가능하게 + 회귀 테스트 30개
tests/ 디렉터리가 비어 있었고 package.json의 두 스크립트가 삭제된 파일을
가리킨 채 방치돼 있었음:
- test    → tests/test-v2.ts (존재하지 않음)
- gateway → src/gateway/server-v2.ts (v4.3.5에서 삭제됨)

즉 npm test가 몇 달간 실행 자체가 불가능한 상태였고, 그 대가로 프롬프트 게이트
정규식을 고칠 때마다 node -e 임시 스크립트를 손으로 짜고 버리는 일이 반복됐음
(2026-07-29 하루에만 5회). 회귀 방지는 하나도 남지 않았음.

- Node 22 내장 러너(node:test) + tsx 사용, 테스트 프레임워크 의존성 없음
- tests/prompt-gates.test.ts (25) — 모델 동작을 강제/억제하는 정규식 게이트 전반
- tests/usage-log.test.ts (5)  — Ollama 외 provider의 토큰 집계 단일 지점
- tests/README.md — 무엇을 왜 테스트하는지, 케이스 작성 규칙, 다음 확장 대상
- 전체 30개 통과, 0.6초

부수 성과 — 테스트가 실제 버그를 찾음:
케이스를 쓰면서 프로덕션 원문을 한 단어(GPU) 줄여 썼더니 실패했고, 그게 진짜
구멍이었음. "토큰 처리 속도 손실이 약 15%~25%"처럼 하드웨어 명사가 없는 처리량
날조는 SPEC_CONTEXT_KEYWORD에 걸리지 않아 그대로 통과하고 있었음 — 원문에
우연히 들어있던 "GPU" 덕에 잡히던 것. 토큰/대역폭/추론/처리속도를 키워드에
추가해 막고, 그 케이스를 테스트로 고정함(강수확률·할인율 오탐 없음 확인).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:29:02 +09:00
kimandClaude Opus 5 8a121457ff v4.3.20-3: ollama_web_* 도구 키워드 게이팅 — 오버헤드 214토큰 절감
ollama_web_search / ollama_web_fetch가 매 턴 무조건 스키마에 포함되고 있었음.
두 도구는 자기 description에 "뉴스·정보 검색은 반드시 web_search를 사용. 이
툴은 web_search가 없는 환경에서만 fallback으로 사용"이라고 적혀 있어서, 모델
에게 "이 도구를 쓰지 말라"고 알리는 데 214토큰을 쓰던 셈. 게다가 그 폴백
체인은 이미 executeWebSearch 내부에서 서버가 처리하므로 모델이 직접 부를
이유가 없음.

삭제가 아니라 게이팅을 택함: personality-context.ts에 "올라마로 검색" 류
표현에만 뜨는 ollama_web 힌트 블록이 이미 있어서, 도구를 제거하면 그 힌트가
존재하지 않는 도구를 설명하게 됨. 같은 표현으로 게이트해 평소엔 0토큰,
명시 요청 시엔 정상 동작하도록 함.

실측: 3,788 → 3,574. "올라마로 검색해줘" 요청에서는 4,980으로 도구가 정상
복귀하는 것도 확인.

조사 중 정정: eonet_events/satellite_snapshot과 run_command는 이미
skillToolFilter에 게이트가 있었음(각각 hasSatelliteKeyword, hasCodeKeyword).
초기 분석에서 필터 전체를 보지 못해 "무조건 켜짐"으로 잘못 판단했던 것을
바로잡음 — 실제 always-on은 8개(1,109토큰)뿐이며 07-26 스코핑 작업이
이미 대부분을 처리해둔 상태.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:18:06 +09:00
kimandClaude Opus 5 37257801ed v4.3.20-2: 도구 전용 프롬프트 블록 4개 조건부 전환 — 오버헤드 542토큰 절감
특정 도구를 제약하려고 존재하는 블록들이 도구 유무와 무관하게 매 턴 무조건
주입되고 있었음. 기존 BROWSER RULE이 쓰던 도구 목록 기반 게이팅을 그대로 확장.

조건부로 전환한 블록(측정 정적 비용):
- IMAGE EDITING RULE (90)      → image_edit 있을 때만
- IMAGE/VIDEO GENERATION (225) → image_generate/video_generate 있을 때만
- CODE OUTPUT (154)            → coder_* 있을 때만
- PACKAGE INSTALL (79)         → shell/run_command 있을 때만

실측: tool_overhead 4,330 → 3,788 (-542, 12.5%). 서로 다른 두 질문에서
동일하게 542 감소 확인.

메시지 문구가 아니라 도구 존재 여부로 게이트한 것이 핵심. "업로드한 사진을
함부로 편집하지 말라" 같은 억제 규칙은 그 도구가 호출 가능한 동안 항상 떠
있어야 하며, 정규식 문구 매칭으로 게이트하면 정규식이 놓친 표현에서 규칙이
사라진다 — 오늘 환각 조사에서 확인한 것과 같은 실패 모드.

CHEMISTRY NOTATION(180)은 의도적으로 무조건 유지: 도구가 아니라 출력 형식을
제약하므로 키로 삼을 도구가 없고, 치과/의학 질문은 화학 키워드 없이도 모델이
구조식을 그리게 만들 수 있어 문구 기반 게이팅이 위험함.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:08:48 +09:00
kimandClaude Opus 5 75b35eede1 v4.3.20-1: USER.md에 클로서버 대시보드 주소 확정 기록
모델이 "클로서버.local"이나 ":3000" 같은 존재하지 않는 주소를 지어내던 것을
막기 위해, 실제 주소(192.168.0.5:18789 / localhost:18789)와 함께 "그 값들은
실재하지 않으니 답변에 쓰지 말 것"을 USER.md에 명시.

USER.md는 벡터 메모리(Chroma)로 대체된 것이 아니라 병행 사용됨 — 벡터 쪽은
관련도 기반이라 사용자가 주소를 명시적으로 묻지 않으면 유사도 컷을 못 넘고,
Chroma 조회 실패 시엔 조용히 빈 값이 되므로, 항상 떠 있어야 하는 이런 사실은
전량 주입되는 USER.md에 있어야 한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 14:59:20 +09:00
kimandClaude Opus 5 e85ebe6608 v4.3.20: 미커밋 자산 일괄 정리 + 유저 워크스페이스/세션토큰 gitignore
그동안 쌓여 있던 미커밋 파일들을 정리. 소스·자산만 올리고, 개인 데이터와
런타임 상태는 gitignore로 차단.

추가된 자산:
- web-ui/audiomass, web-ui/wavacity — 자체 호스팅 오디오 에디터
- web-ui/drums — 드럼 샘플
- wol-gate — WOL 프록시 컨테이너(Dockerfile + server.js)
- glm-1m-fix — GLM 1M 컨텍스트 설치 스크립트(linux/windows)
- RENODE_FIXES_SUMMARY*.md, CCTV정보_서울특별시.csv, .claude/plans

수정:
- edge_tts_synth.py: rate 인자 추가(말하기 속도 조절)
- .smallclaw/config.json: google(gemini) provider 등록 — 키는 vault 참조
- assets/SmallClaw*.png 삭제분 반영

gitignore (중요):
- .smallclaw/users/*/workspace/ 전체 차단. 기존 패턴이 memory/uploads와 일부
  확장자만 막아서 detective/(고소장·통화녹취·의사소견서·가족관계증명서),
  generated-media/, music/, pptx/ 등 앱 생성 개인 데이터 403MB가 노출돼 있었음
- .smallclaw/active-sessions.json 차단 — 살아있는 bearer 토큰과 admin 역할이
  평문으로 들어있어 커밋 시 게이트웨이 관리자 자격증명이 그대로 공개됨
- google-usage.json, config.json.bak-*, workspace/memory·weather-maps·
  task_result_*, voice/ 등 런타임 상태도 함께 차단

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 14:55:37 +09:00
kimandClaude Opus 5 8359901cac v4.3.19-26: 환각 방어 전역화 + 답변 길이 압박 완화 + 토큰 로깅 구멍 수정
실사용 로그 감사(07-29 GPU/PCIe 대화)에서 발견한 문제들 일괄 수정.

환각 방어:
- looksLikeUnverifiedSpecClaim이 놓치던 패턴 2개 추가 — 단위 없는
  "15토큰"(strict tier), 퍼센트 벤치마크 "약 5%"(ambiguous tier,
  SPEC_CONTEXT_KEYWORD 게이트로 오탐 방지)
- forceToolChoiceOnLiveData를 모델별 opt-in → 전역 기본 ON + opt-out.
  모델을 수시로 바꾸는데 profiles에 등재된 2개만 방어되던 구멍이 원인
  (프로필 없던 gemini-3.5-flash-lite가 tok/s 날조 + 없는 제품명 창작)

답변 길이:
- "Keep responses SHORT" 1-2 → 2-4문장, 대화/실행 답변으로 범위 한정.
  정보성 답변(뉴스·날씨·설명·비교)은 OUTPUT FORMAT을 따르도록 명시
- 불확실성 표현은 길이 제한 면제. 1-2문장 압박이 "모른다"고 말할 자리를
  없애 환각을 확신형 한 줄로 포장하던 것이 실측으로 확인됨
- 뉴스는 도구가 반환한 기사 전부 표기(3건 체리픽 금지, 8-10행 목표)

토큰 로깅:
- logUsage가 ollama-adapter 내부 private이라 Ollama 경유만 기록되고,
  openai-compat은 usage 필드를 추출조차 안 하고 버리던 문제. 비용 비교
  대상인 Gemini가 통째로 누락돼 공짜처럼 보이는 상태였음
- providers/usage-log.ts로 공용화 → openai-compat(chat/generate)과
  openai-codex(SSE response.completed)에서도 캡처·로깅·호출자 반환

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 14:44:21 +09:00
kimandClaude Sonnet 5 3bff465c08 v4.3.19-25: 탐정 앱 자체 WOPI 호스트로 NextCloud 없이 Collabora 직결
기존 col.applecherry.net Collabora 서버를 재사용하되, NextCloud(WebDAV
업로드/가져오기 왕복) 없이 홈클로 게이트웨이가 직접 WOPI 호스트 역할
수행 (CheckFileInfo/GetFile/PutFile). hwp는 사건 폴더 안에서 odt로
1회 변환 후 그 자리에서 바로 편집 — 타이핑하는 대로 사건 폴더에 즉시
저장되어 별도 "가져오기" 버튼이 필요 없어짐(dtFetchFromNextcloud 제거).
config.json에 collabora.baseUrl/publicUrl 추가. Express 5의
res.sendFile 이슈(이미 known) 재발 → createReadStream으로 우회.

실기기(col.applecherry.net + 실제 사건 고소장.odt)로 전체 라운드트립
검증 완료.

부수: 사이드바 사건목록이 빠른조사/법률질문 버튼 늘어나며 눌리는 문제
— 잘 안쓰는 버튼 정리(19개→11개)로 완화 + .dt-cases min-height 안전장치.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 14:18:18 +09:00
kimandClaude Sonnet 5 e180c3978e v4.3.19-24: 일별 메모리 추출 스크립트 깨진 import 경로 수정
memory-vector.ts가 src/gateway/memory/ 하위로 옮겨진 뒤 이 스크립트만
경로가 안 고쳐져서 MODULE_NOT_FOUND로 매일 새벽 1시 systemd 타이머가
3일 연속 즉시 실패하고 있었음(daily_extracts Chroma 컬렉션이 07-24
이후로 안 쌓이던 원인). 경로 수정 후 밀린 07-25~27 백필 확인.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 13:22:18 +09:00
kimandClaude Sonnet 5 8a7f6443fc v4.3.19-23: 글로벌 하트비트가 빈 워크스페이스를 보던 버그 수정
멀티유저 전환 후 USER.md/SOUL.md가 유저별 워크스페이스로 옮겨졌는데
HeartbeatRunner는 여전히 글로벌 워크스페이스(파일 없음)를 보고 있어서
30분마다 memory_read(user/soul)가 매번 "not found"로 실패하던 문제.
workspacePath를 papa 워크스페이스로, handleChat에 username도 함께
전달하도록 수정.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 13:22:09 +09:00
kimandClaude Sonnet 5 4c3d90c3d2 v4.3.19-22: 탐정 앱 증거자료 파일/폴더 다운로드 버튼 + 관련 사이트 링크 추가
파일별 다운로드(download 속성), 폴더 전체 zip 다운로드(archiver 신규
의존성 + /api/detective/files/download-folder 라우트) 추가. 사이드바에
법률 서식(대한법률구조공단)·법원 소송 서식(전자소송포털)·대법원 판례
검색(사법정보공개포털)·경찰청 링크 블록 신설.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 13:21:59 +09:00
kim e39c430485 v4.3.19-21: NextCloud 편집 버튼(🌐/⬇️)을 odt 파일에도 확장
hwp에서 가져온 odt 사본을 다시 편집하려 해도 버튼이 없었음 — 이제
.odt 파일도 hwp와 동일하게 🌐(NextCloud에서 열기)/⬇️(가져오기+PDF
변환) 대상이 됨. 백엔드는 이미 비-hwp 파일을 변환 없이 그대로
업로드/처리하도록 되어 있어서 프론트엔드 조건만 확장하면 됐음.
2026-07-27 23:43:52 +09:00
kim 667a539343 v4.3.19-20: NextCloud 가져오기에 odt→PDF 자동 변환 추가
h2orestart는 hwp 내보내기(쓰기)를 지원 안 해서 편집본을 진짜 hwp로
되돌릴 방법이 없음(리눅스에서 근본적으로 불가) — 대신 odt→PDF는
특별한 필터 없이 표준 변환이라 이걸로 실질적 요구(제출용 PDF)를 충족.

⬇️ 가져오기 시 odt와 함께 PDF도 자동 생성. 파일명은 "{이름}
(NextCloud변환).pdf"로 접미사를 붙여서, 같은 폴더에 이미 있는 원본
증거자료 PDF(예: 고소장.pdf)를 덮어쓰지 않도록 함 — 실제로 이 충돌
가능성을 발견하고 배포 직전에 수정함.
2026-07-27 23:32:43 +09:00
kim 6cfb2db5b1 v4.3.19-19: 탐정 앱 hwp 파일 NextCloud/Collabora 실시간 편집 연동 + 지도탭 누락 커밋 반영
주요 변경:
- 탐정 앱의 .hwp 파일에 🌐 버튼 추가: 서버가 자동으로 hwp→odt 변환(host의
  h2orestart 필터 사용) → 사용자 소유 NextCloud(cloud.applecherry.net)의
  homeclaw-detective/{caseId}/ 폴더로 WebDAV 업로드 → Collabora Online
  편집화면으로 리다이렉트. 타이핑하는 대로 자동 저장, 다운로드/재업로드 불필요.
- NextCloud 앱 비밀번호는 vault에 저장(nextcloud.appPassword), config.json엔
  평문 없음.
- (부수적, 별도 이슈) Stirling-PDF는 이 사건의 원본 PDF가 실제 폼필드가
  없는 인쇄용 레이아웃이라 텍스트 스트림 편집이 계속 실패해서 우회 경로로
  채택함.

추가로 이전 세션에서 커밋 안 하고 넘어간 weather-app.html의 지도탭
자동전환 fix(processWindyUrl 이동 시 mapLat/mapLon 동기화 + switchCenterTab)도
같이 반영됨.
2026-07-27 22:56:45 +09:00
kim ebb605bc24 v4.3.19-18: PDF 편집기 자동다운로드 되돌림 - 사용자가 원치 않음
작업 디렉토리에 로컬로 접근 가능해서 Stirling-PDF 업로드 대화상자에서
직접 파일을 찾아 선택하는 걸 선호함. 클릭할 때마다 자동 다운로드되는
건 불필요한 부작용이었음. 툴팁만 업로드 버튼에서 직접 선택하라고
안내하도록 수정.
2026-07-27 21:57:43 +09:00
kim ec62051251 v4.3.19-17: 탐정 앱 PDF 편집기 — 파일 자동 오픈 불가를 다운로드 보조로 완화
원인: /pdf-editor-open은 caseId만 전달하고 파일명은 전혀 안 넘김 —
Stirling-PDF(외부 PDF 편집기)의 실제 프론트엔드 번들을 직접 열어봤는데
"token" 쿼리파라미터 하나만 읽고 원격 파일 로드 기능 자체가 없음(자체
storage API는 있지만 별도 로그인 필요, 우리는 미설정). 즉 특정 파일을
자동으로 여는 건 우리 쪽에서 고칠 수 있는 버그가 아니라 외부 툴의
한계임을 확인.

대신 ✏️ 클릭 시 편집기 새창 열기와 동시에 그 파일을 다운로드시켜서,
사용자가 편집기 창에 끌어다 놓기만 하면 되도록 마찰을 최소화. 툴팁도
실제 동작을 정확히 설명하도록 수정.
2026-07-27 17:37:08 +09:00
kim bb0f44b0c3 v4.3.19-16: 탐정 앱 메모 textarea에서 Tab 키로 들여쓰기 가능하도록 수정
기본 브라우저 동작은 Tab을 누르면 포커스가 다음 요소로 넘어가버려서
메모 안에서 들여쓰기를 못 했음. keydown에서 Tab을 가로채 탭 문자를
커서 위치에 삽입하고 포커스는 textarea에 유지.
2026-07-27 15:19:37 +09:00
kim f49d7a903c v4.3.19-15: satellite_snapshot 이미지도 SSE로 직접 주입 (모델이 경로 오타내는 문제)
실사용 중 발견: 모델이 satellite_snapshot 결과의 /api/files/... 마크다운을
답변에 그대로 복사하지 않고 재작성하면서 /api/files/ 접두사를 빠뜨림 —
그러면 클라이언트 정규식이 매치 안 돼서 사진탭이 아예 안 열림.
weather_map_screenshot과 같은 패턴으로, 도구 결과의 마크다운을 그대로
SSE로 주입해서 모델의 재현 정확도에 의존하지 않게 함. 태풍 돌핀 실제
NASA 위성사진으로 재현·검증 완료.
2026-07-27 12:18:32 +09:00
kim ce9f556e84 v4.3.19-14: 위성사진 위치→지도 이동을 클라이언트에서 결정론적으로 처리
실제 테스트에서 발견: "지도 이동해줘" 후속 요청에 모델(deepseek-v4-flash)이
도구를 재호출하지 않고 이전 응답을 그대로 반복하는 경우가 많아 프롬프트
가드만으로는 신뢰도가 부족했음. 근본 해결로 사진 뷰어에 "지도에서 위치
보기" 버튼 추가 — satellite_snapshot 파일명에 이미 박혀있는 좌표를
그대로 파싱해 지도를 이동시키므로 모델 개입이 전혀 필요 없음.

부수적으로 execute-tool.ts에도 weather_map_screenshot 성공 시 그 URL을
SSE로 직접 주입하는 방어선 추가(모델이 실제로 도구를 호출했을 때 대비).
2026-07-27 12:11:44 +09:00
kim 0b9ce3d18a v4.3.19-13: Windy 지도 "이동" 후속요청 무시 버그 수정
실제 로그 분석 결과: "위치로 이동" 같은 후속 요청은 트리거 단어 목록에
없어서 모델이 Windy URL을 재출력하지 않고, 대신 weather_map_screenshot
(서빙 안 되는 모델 전용 스크린샷 도구) 결과를 [REDACTED-HE].jpg 같은
가짜 마크다운 이미지로 답변에 끼워넣음 — 실제 지도는 전혀 안 움직이고
깨진 이미지 링크만 표시됨. 후속 이동 요청도 트리거로 인정하고,
screenshot 도구 결과를 이미지로 삽입하지 말라는 가드 추가.
2026-07-27 11:54:51 +09:00
kim ced421cac9 v4.3.19-12: 스킬 프롬프트 소스 가독성 개선 — \uXXXX 이스케이프 → 실제 한글
accountant/lawyer/investor-app의 SKILL_CONTENT가 모든 한글을 6자리
유니코드 이스케이프(\uXXXX)로 저장하고 있어 소스 읽기가 사실상 불가능했음.
디코드→재인코딩 스크립트로 실제 UTF-8 텍스트로 변환, 디코드된 내용은
변환 전후 완전히 동일함을 스크립트로 검증. 기능 변화 없음, 가독성만 개선.
2026-07-27 10:54:39 +09:00
kim f615475618 v4.3.19-11: 어제 추가한 가드 텍스트 오탈자 수정
investor-app에 유니코드 이스케이프를 손으로 옮기다 "상호명이"가
"사이앨목이"로 깨졌던 것 수정, lawyer-app 조사 오류 2건 수정.
2026-07-27 10:48:10 +09:00
kim 5ac766381a v4.3.19-10: investor/lawyer/detective 스킬에 식별자 대조 가드 추가
날씨 스킬에서 태풍 번호 혼동 사고 확인 후, 같은 패턴(단일 소스에서
가져온 데이터를 다른 개체에 잘못 귀속) 위험이 큰 세 스킬에 가드 추가:
- investor: 종목코드/거래소/보통주-우선주/분기 대조
- lawyer: 법령 시행일·개정판, 판례 사건번호 전체 일치 대조
- detective: 동명이인·유사상호 — 추가 식별자 없이 귀속 금지
2026-07-27 10:38:22 +09:00
kim 72855ea77a v4.3.19-9: 날씨 스킬 프롬프트에 태풍 번호/이름 대조 가드 추가
실제 대화에서 KMA 통보문 단일 페이지만 보고 제13호 태풍 돌핀의 좌표를
제12호 태풍 노을(이미 광둥성 상륙·소멸)의 것으로 착각해 답한 사례 발견.
번호 대조 + 한/영 교차검색 지침 추가로 재발 방지.
2026-07-27 10:31:52 +09:00
kim 74af6fdfd3 v4.3.19-8: eonet_events 설명에 유럽/중동/아프리카 커버리지 부족 명시
EFFIS(유럽 산불 전용 DB) 공개 WFS를 붙여볼까 테스트했으나 GetCapabilities조차
35초+ 걸리다 500 에러 — 신뢰성 부족으로 보류. 대신 EONET이 북미 편향이라는
사실을 도구 설명에 적어 모델이 유럽 등 이벤트는 자동으로 web_search 우회하게 함.
2026-07-26 19:10:37 +09:00
kim 071e36110e v4.3.19-7: NASA EONET/Worldview 위성사진 기능 + weather-app 멀티탭 뷰어
- eonet_events/satellite_snapshot 도구 추가 (키 불필요 공개 API)
- weather-app.html: 위성사진 채팅 이미지 렌더링, 지도/사진 멀티탭 센터패널,
  확대/축소·드래그팬, localStorage+히스토리 이중 복원으로 새로고침 시
  탭 유지(하드리셋으로 localStorage가 지워져도 대화 이력에서 재구성)
2026-07-26 18:43:20 +09:00