617 Commits

Author SHA1 Message Date
fdf09c369c refactor(config): PR #207 회차 1 반영 — from_file toml::Value 단일 파싱 2026-06-04 13:33:07 +00:00
15e6918cef feat(config): env 이름 보존 RHS 갱신 + pdf paddle 신규 env 6키
apply_env whitelist 의 키 문자열(LHS) 전부 불변, 대입 대상만 self.ingest.*
(불변식 #2). KEBAB_PDF_OCR_{DET_MODEL,REC_MODEL,DICT,SCORE_THRESH,
UNCLIP_RATIO,MAX_BOXES} 신규(image.ocr paddle 패턴 대칭).
게이트: clippy --workspace --all-targets 0, kebab-config/app/eval 테스트 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:56:25 +00:00
a8ec354188 test(config): v3 무손실 골든 — 사용자 실제 v2 config relocation+멱등
사용자 실제 config(주석·대안 줄·score_gate=0.3000…1192 포함)를 fixture 로.
값·주석 보존 + v3 파싱 일치 + 멱등 검증.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:48:52 +00:00
2686a4f27d feat(config): from_file load 시 v2→v3 메모리 내 자동 변환(디스크 미변경)
schema_version < CURRENT 이면 migrate_document 경유로 메모리에서 변환 후 파싱.
디스크 파일은 불변(갱신은 kebab config migrate). 일회성 warn. 불변식 #3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:47:59 +00:00
25e94feab8 feat(config): step_2_to_3 — 미디어 테이블 [ingest.*] relocation + pdf paddle 값 보존
move_table(decor 포함 통째 이동) + move_indexing_keys(병렬도 키) +
copy_image_paddle_to_pdf(v2 비대칭 보존). CURRENT_SCHEMA_VERSION=3.
section_comment 를 ingest.* 경로로 갱신. 멱등.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:47:14 +00:00
7b7330cdf2 feat(config): per-option 인라인 주석(key_comment) — init/reconcile 부착
annotate_table 의 leaf 분기 추가: 스칼라/배열 키 값 뒤에 한 줄 주석 suffix.
dotted path → 주석 매핑(workspace.root, ocr.model, request_timeout 등).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:45:46 +00:00
3d45994693 refactor(config): signature paddle 경로 미디어화 + 바이트 불변 골든
ocr_engine_version_for_sig 가 det/rec/dict 를 호출자(미디어별)로부터 받도록
인자화 — image 는 [ingest.image.ocr], pdf 는 [ingest.pdf.ocr]. v2 의 pdf↔image
paddle 비대칭 제거. engine_version_for_paths 신설(kebab-parse-image). 출력
문자열은 값 기반이라 v2 와 바이트 동일(불변식 #1). test seam + 골든 추가.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:44:27 +00:00
d5c69f6715 refactor(config): v3 경로 call-site sweep (kebab-app/kebab-eval/kebab-parse-image)
부모 경로에 .ingest 삽입(leaf 구조체 불변). src + 테스트 call-site 전부.
kebab-cli 테스트의 v2 TOML fixture 는 from_file 자동변환(T6) 경로 검증용으로 유지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:40:06 +00:00
148c8b7040 refactor(config): v3 레이아웃 — 미디어 ingest 통합 + pdf paddle 대칭 + float 직렬화
Config 의 indexing/chunking/image/pdf top-level 필드를 ingest: IngestCfg
하나로 통합. leaf 구조체는 불변, 부모 경로만 [ingest.*] 하위로 이동.
PdfOcrCfg 에 paddle 대칭 6키(det/rec/dict/score_thresh/unclip_ratio/
max_boxes) 추가. ser_f32_clean 으로 f32 직렬화 정리(0.3000000119→0.3).
apply_env RHS 를 self.ingest.* 로 갱신(env 키 문자열 LHS 불변).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:37:09 +00:00
f3a7222ec5 fix(ocr): PR #206 round-1 리뷰 반영 — 골든 CI 테스트 + PDF 튜닝 문서 + threshold const + mutex 복구
- [MEDIUM] 골든 CI 단위테스트 2건 추가: ctc_greedy_decode_golden (argmax_idx
  one-hot → decoded 문자열 검증), det_box_score_golden (box_score/unclip_rect
  golden corner 검증). 모델/ONNX 불요, CI 상주.
  ctc_greedy_decode를 자유 함수(ctc_greedy_decode_with_dict)로 추출하여 테스트
  가능하게 함.
- [MEDIUM] PDF paddle 튜닝 비대칭 문서화: build_pdf_ocr_engine에 paddle-onnx가
  image.ocr.* 사용(pdf.ocr.* 아님) 이유 명시 + PdfOcrCfg.engine 필드 doc 갱신.
- [MEDIUM] DBNet 이진화 매직넘버 0.3 → DET_BIN_THRESH const 추출 + score_thresh
  기본값 느슨한 이유 1줄 주석.
- [LOW] Mutex poison 복구: det/rec .expect("poisoned") →
  .unwrap_or_else(PoisonError::into_inner). 자산 panic이 ingest abort 안 되도록.
- [LOW] DetBox.score dead field 제거 (box_score 결과는 필터에만 사용, 저장 불요).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 09:13:27 +00:00
3d5bb599e3 feat(ocr): bundle PP-OCRv5 ONNX models (det 4.7MB + rec 13MB)
paddle-onnx engine assets — committed as plain binary blobs (git-lfs not
installed on this host; see .gitattributes for the LFS migration recipe).
NOTICE (Apache-2.0) + korean_dict.txt already tracked. Loaded by default from
this dir or KEBAB_IMAGE_OCR_MODEL_DIR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 08:36:34 +00:00
375a0693e4 chore(ocr): T11/T12 — clippy clean + docs + v0.27.0 bump
T11: fix 12 clippy lints in paddle_onnx.rs/paddle_e2e.rs (doc overindent,
finish_non_exhaustive, map_or_else, RangeInclusive::contains, cast_lossless,
is_some_and, usize::from). Full-workspace clippy -D warnings = 0.

Smoke (paddle-onnx, real binary): clean_paragraph OCR verbatim-correct, real
per-region confidence (0.99/0.96/0.95), FTS5 lexical hit on Korean(검색)+
English(embedding), parser_version folds |ocr:1:paddle-onnx:<ver>. Big page
<4s inference (5.6s ingest incl. one-time session load).

T12: README [image.ocr].engine + ARCHITECTURE OCR row + SMOKE paddle-onnx config
+ HANDOFF + HOTFIXES dated entry. Workspace version 0.26.2 → 0.27.0 (minor:
new engine value + config keys). .gitattributes: onnx as plain blobs (no git-lfs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 08:36:19 +00:00
8cc4e6d563 fix(ocr): T10/T11 — unclip edge-offset (CER 0.26→0.005) + e2e gate + error tests
Root cause found at T11 e2e: unclip_rect pushed corners radially from the
centroid. For a wide/short text box the diagonal is near-horizontal, so the box
barely grew in height and clipped character tops (ㄷ→ㄴ, 다→나). Rewrote unclip
as a proper per-edge polygon offset along the rect's own (u,v) axes — height and
width each grow by 2*distance, matching PaddleOCR pyclipper.

Result (synthetic-ocr-bench, real inference): mean gate CER 0.2585 → 0.0049
(clean_paragraph/korean_heavy/numbers_table/tech_terms = 0.0), beating the
0.976 PoC baseline. Big page 3.9s < 5s.

T10: dict-length-mismatch construction error + undecodable-bytes recognize error.
T11 e2e: tests/paddle_e2e.rs CER<=0.05 gate (skips cleanly when assets absent).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 08:22:47 +00:00
901416d8e9 feat(ocr): T7-T9 — config overrides + engine factory + signature cascade
T7: OcrCfg gains det_model/rec_model/dict overrides + score_thresh/
unclip_ratio/max_boxes (serde default, KEBAB_IMAGE_OCR_* env). OnnxPaddleOcr::new
threads them via ModelPaths::from_config.
T8: build_image_ocr_engine / build_pdf_ocr_engine factories return
Box<dyn OcrEngine>; match on engine string (ollama-vision|paddle-onnx|err).
ImagePipeline.ocr_engine + pdf_ocr_engine signatures switched to &dyn OcrEngine.
OcrEngine gains model() for the progress label.
T9: ingest_config_signature image/pdf branches emit |ocr:1:{engine}:{engine_version}
(memoized blake3 per asset-triple, m3-safe). Unit tests (a)(b)(c) added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 08:15:30 +00:00
b706e3e88c feat(ocr): T2-T6 OnnxPaddleOcr core engine — det/rec ONNX + DBNet postproc + CTC
PP-OCRv5 ONNX OCR engine on the pinned ort rc.9 (no Python, no oar-ocr dep).
Implements the recognize() pipeline end-to-end (compiles + unit-tested):

- T2: OnnxPaddleOcr skeleton, OcrEngine impl, det/rec Session loaded once
  (Mutex-wrapped → Send+Sync), engine_version = blake3(det+rec+dict) cached
  once at construction, dict bounds-check (11945 lines vs 11947 rec classes).
- T2 preproc: det ImageNet mean/std NCHW + limit_side_len 960 → ×32 round
  (golden 192x900→896x192 pinned); rec height-48 keep-aspect, (x-0.5)/0.5.
- T3 det postproc: threshold 0.3 → imageproc contours → min-area rect via
  pure-Rust rotating calipers + convex hull → mean-prob box-score filter →
  pure-Rust unclip(ratio 1.5). No clipper2/OpenCV.
- T4 crop+rectify: corner ordering + bilinear perspective warp to horizontal.
- T5 rec+CTC: greedy decode with the T0a-confirmed mapping
  (idx0=blank, 1..=11945=dict[idx-1], 11946=space), rec-class bounds-check.
- T6 assembly: reading-order OcrText with per-region bbox + real confidence.

Unit tests (4 pass): det_target_dims golden, convex hull, min-area rect,
unclip expansion. Large *.onnx assets stay untracked pending T12 LFS decision.

Remaining: T7 config overrides, T8 factory (4 sites), T9 signature cascade,
T10 error matrix, T11 gates (clippy/e2e CER), T12 docs+bump+PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 07:52:39 +00:00
8f8d3a4100 feat(ocr): T0a/T0/T1 — golden harness(CTC blank=0 도출) + deps(ort rc.9) + dict/NOTICE
T0a: onnxruntime 직접 골든 하네스 → CTC blank/dict 매핑 경험 확정(gt CER 0.000).
T0: 모델 번들 dict+NOTICE(.onnx 는 T12 LFS 결정까지 워크트리 보관).
T1: ort(download-binaries)+imageproc 추가, cargo tree ort rc.9 단일 확인.
2026-06-04 07:43:53 +00:00
03b0745e9d test(ingest): config invalidation e2e + parser_version assert 갱신
- config_invalidation.rs(신규): 동일config=전skip / 청킹변경=md+code재색인 /
  [ingest.code]변경=코드만 / search변경=재색인0 (회귀가드) end-to-end.
- code_ingest_smoke / pdf_pipeline: 저장 parser_version 이 이제
  "{base}|{sig}" composite 라, exact assert 를 base 접두사(split('|').next()) 비교로 갱신.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:14:15 +00:00
e7cb20990a feat(ingest): ingest 설정 변경 시 영향 자산 자동 재색인 (signature 폴딩)
ingest 산출에 영향 주는 설정(청킹/이미지 OCR·caption/pdf.ocr/[ingest.code])의
결정적 서명을 effective parser_version 에 폴딩 → 변경 시 --force-reingest 없이
영향 자산만 자동 재색인.

- ingest_config_signature(config, media_type): per-type 산출-영향 설정만 직렬화.
  비산출 설정(search/rag/ui/log + max_pixels/languages/timeout)은 제외.
- effective_parser_version(config, asset, base) = "{base}|{signature}".
- md/image/pdf/code 경로: composite 를 (a) try_skip_unchanged 비교값,
  (b) persist 전 canonical.parser_version override 에 사용.
- doc_id 는 base parser_version 으로 계속 파생 → 설정 변경에도 안정(orphan churn 회피).
- code Tier-3 fallback 은 bare "none-v1" sentinel 유지(skip bypass 의존).
- 단위테스트 8: 결정성/청킹=전타입/이미지·pdf·code 토글/무관설정 회귀가드.

spec: docs/superpowers/specs/2026-06-03-ocr-toggle-invalidation-spec.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:14:06 +00:00
6c9c8df43e chore(version): 0.27.0 → 0.26.1 — 새 bump 규칙상 patch
진행 로그 개선은 검색·색인 결과 불변 + 새 명령/플래그/config 없음 + additive-only
wire(asset_phase)라 CLAUDE.md 신규 규칙(기능/인터페이스 변경=minor, 없으면 patch)상
patch 가 맞음. version·라벨·HOTFIXES 헤더를 0.26.1 로 정정.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:02:16 +00:00
4918983d9c chore(ingest): PR #204 회차1 리뷰 반영 — 버전 라벨 v0.26.0 → v0.27.0
신규 진행로깅 표면(asset_phase / ocr_ms / caption_ms + progress.rs heartbeat·
slowest 주석)이 v0.26.0 으로 잘못 표기돼 있던 것을 v0.27.0(실제 추가 버전)으로
정정. wire schema 의 "추가 버전" 정확성(외부 통합 참조). 로직 변경 없음(주석/doc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:57:17 +00:00
aeaa18a564 feat(ingest): 진행 로그 개선 — 파일명/phase/heartbeat/slowest 요약
OCR/caption 켜진 볼트 ingest 가 중간부터 느릴 때 TTY 진행바가 파일명·phase·
모델·경과시간을 안 보여 "멈춤"처럼 보이던 문제 해결.
- 신규 wire AssetPhase{idx,total,phase,model} + AssetTimings.ocr_ms/caption_ms
  (additive, ingest_progress.v1 유지)
- app: apply_ocr/apply_caption/embed 진입 시 AssetPhase emit + ocr/caption 시간 측정
- cli: TTY 진행바에 현재 파일명 + phase(model) + asset 경과초(heartbeat),
  종료 시 최장 소요 파일 top-5 요약(quiet 여도 출력, --json 미출력)
- wire schema / README / HANDOFF / HOTFIXES 동기화, version 0.26.0 → 0.27.0

검증(리더): clippy 0, kebab-app/cli 61그룹·parse-image/tui 14그룹 0실패(-j8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:52:26 +00:00
72c99c452c feat(config,app): embedding provider=ollama 배선 + endpoint, version 0.26.0
kebab-config: EmbeddingModelCfg.endpoint: Option<String>(serde default, ollama용,
None→models.llm.endpoint 폴백) + provider 문서에 ollama + env
KEBAB_MODELS_EMBEDDING_ENDPOINT. kebab-app embedder(): provider match 에 ollama
분기(facade 경유). workspace member += kebab-embed-ollama, app dep 추가.
version 0.25.0 → 0.26.0(minor, +Cargo.lock) — 신규 임베딩 백엔드/모델은 CLAUDE.md
§Release 의 surface 변경 트리거.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 04:59:23 +00:00
cbcae69abf feat(embed): candle 모델 레지스트리 + arctic-embed-l-v2.0 (CLS pooling)
e5 하드코딩(HF_MODEL/SUPPORTED_MODEL/mean/query:+passage:) → 모델 레지스트리
EmbedModelSpec{name,hf_repo,pooling,query_prefix,doc_prefix,dim,version_tag}.
e5(mean, query:/passage:) + arctic(CLS, query:/무접두어). pooling 모델별 분기
(mean=attention-mask-weighted / CLS=hidden[:,0,:]), tokenize/forward/L2 공유.
arctic pooling=CLS 는 HF 1_Pooling/config.json(pooling_mode_cls_token:true) 확인.
model_version 은 arctic 일 때 +arctic-cls 태그(embedding_version cascade 트리거);
e5 는 fastembed-e5 호환(NUMA 드롭인) 위해 plain config.version 유지.

correctness 게이트: tests/arctic_ollama_parity.rs (#[ignore], live Ollama) —
candle arctic vs Ollama snowflake-arctic-embed2 per-sentence 코사인>0.99.
수동 실측 cosine_min=0.999984 (recall@10 130 재현 보장).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 04:59:11 +00:00
7505645008 feat(embed): kebab-embed-ollama 신규 크레이트 — Ollama /api/embed Embedder
arctic-embed-l-v2.0 의 폴백 백엔드(측정에 쓴 경로 그대로). reqwest::blocking
POST {endpoint}/api/embed {model,input:[...]} → embeddings. batch 48 +
fail-soft 재시도 3, 결과 L2 정규화(Ollama raw 반환 → 일관성), dim 검증.
query/doc prefix 는 모델 태그로 추론(arctic-embed→query:/무접두어, e5→query:/passage:).
model_version=ollama:{model}. endpoint=models.embedding.endpoint ?? models.llm.endpoint.
wiremock 테스트 3종(L2 정규화/dim mismatch/empty no-op) + 단위 5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 04:59:11 +00:00
e03d03cb26 test: 별칭 전용 테스트 삭제 + 영향 테스트/fixture 갱신
kebab-search/tests/lexical.rs 의 alias 채널 테스트 + insert_chunk_with_aliases
헬퍼 제거(body 회수 회귀 테스트로 대체). Chunk 리터럴 aliases: None 제거
(embedding_records_fk/idempotency/inspect). chunk 스냅샷 fixture 의 aliases
키 제거. config_migrate 는 ingest.code 앵커로, corpus_revision/search_lexical
주석은 V013 비-bump 명시로 갱신.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:37:58 +00:00
a48c405826 refactor(wire): ExpansionProgress 이벤트 + 렌더 제거
IngestEvent::ExpansionProgress variant + 직렬화 테스트 제거(AssetChunked/
AssetTimings 유지). CLI/TUI 의 expansion 렌더 제거, AssetTimings 한 줄에서
expand 세그먼트 제거. ingest_progress.v1 schema 의 expansion_progress kind
제거, expansion_ms 설명을 "값 0 유지"로 갱신.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:37:44 +00:00
21e02d8a93 refactor(app): ingest 별칭 생성·캐시·sentinel 벡터 루프 제거
ingest_one_asset 의 청크당 별칭 LLM 생성·derivation_cache 조회/저장·
embed_aliases sentinel 벡터(`{orig}#alias#N`) upsert 루프 제거.
expansion_ms 는 wire 호환 위해 0 고정. alias_sentinel_ids_to_delete 와
orphan purge 3개 호출부를 본문 chunk_id 직접 삭제로 단순화.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:37:43 +00:00
a64c31ee94 refactor(search): alias lexical arm 제거
run_alias_query / merge_body_alias 제거, 검색을 body_rows 직접 사용으로
단순화. build_match_string_for_column 의 column 매개변수 인라인(text 고정).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:36:56 +00:00
ec96648956 refactor(config): ExpansionCfg + [ingest.expansion] 제거
IngestExpansionCfg struct + IngestCfg.expansion 필드 + Default +
KEBAB_INGEST_EXPANSION_* env 파싱 + 테스트 제거. migrate.rs 의
ingest.expansion 섹션 주석 제거 — config migrate 테스트는 ingest.code 앵커로
정정(forward-compat: 기존 [ingest.expansion] 섹션은 serde 가 무시).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:36:56 +00:00
ecaf224381 refactor(chunk): Chunk 생성부의 aliases 리터럴 + store 컬럼 제거
kebab-chunk/* AST·md·tier2·pdf chunker 의 aliases: None 리터럴 삭제,
store-sqlite documents.rs chunks INSERT 컬럼/바인딩 + get_chunk 매핑에서
aliases 제거.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:36:44 +00:00
b1c5feb3f3 refactor(core): Chunk.aliases 필드 제거
doc-side expansion(별칭) 제거 — Chunk 의 aliases: Option<String> 필드와
serde default 테스트 제거. Metadata.aliases(Vec, 문서 메타)는 유지.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:36:44 +00:00
8bfa4ba76e fix(ingest-progress): 리뷰 반영 — store_ms 경계 정정 + 중복 expansion 프레임 가드
- store_ms 에서 stale-vector orphan purge(LanceDB I/O) 제거 → embed/vector phase
  (embed_ms)로 이동. store_ms 가 이제 SQLite put_* 만 의미(진단 정확도; 편집
  재색인 시 920ms 오귀속 제거). purge 는 여전히 unconditional + upsert 이전.
- 최종 expansion_progress 프레임을 done != last_done 로 가드 (throttle 배수 시
  중복 프레임 + chunks==0 시 0/0 프레임 제거).
- schema/HOTFIXES: store_ms/embed_ms 설명 정정 + dangling IMPL_REPORT 참조 제거.

clippy -D warnings 0, test 312 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:49:02 +00:00
a48b055358 feat(ingest): asset 내부 phase 진행 로깅 (asset_chunked/expansion_progress/asset_timings) + v0.24.0
asset(문서) 단위뿐이던 ingest 진행 이벤트에 문서 내부 phase 가시성을 추가.
큰 문서가 expansion(별칭 LLM, 청크당 순차)으로 수십 분 걸려도 진행바가
1/N 에 멈춘 듯 보이던 문제 해결.

wire ingest_progress.v1 additive (backward-compat):
- asset_chunked {idx,total,chunks} — 청킹 직후, markdown/image/pdf 전 경로
- expansion_progress {idx,total,done,chunks} — expansion 루프 스로틀
  (25청크 또는 1s, 종료 시 done==chunks). 캐시 히트도 done 에 포함
- asset_timings {idx,total,parse_ms,chunk_ms,expansion_ms,embed_ms,store_ms}
  — markdown 경로 phase별 wall-clock

설계: timing 은 kebab_core::IngestItem(wire-stable) 변경을 피해 신규
AssetTimings 이벤트로 ingest_one_asset 가 직접 emit (AssetFinished 무변경).

CLI(progress.rs): 진행바 sub-message(→ N chunks / 별칭 확장 done/chunks) +
asset 종료 시 phase timing 한 줄(fmt_ms). TUI reducer no-op arm.

검증: clippy -D warnings exit 0; cargo test -p kebab-app -p kebab-cli
312 passed/0 failed. ordering-invariant 테스트 재작성 + 신규 직렬화 테스트.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 13:58:27 +00:00
581e1d5d55 feat(cli): ingest 시 임베딩 백엔드/디바이스 한 줄 표시 + README KB 이전 문서 (v0.23.1)
- kebab-cli ingest: 시작 시 `임베딩 백엔드: <provider> (Metal/GPU 빌드|CPU) · 모델 …`
  를 stderr 로 표시 (--json/--quiet 억제). Metal 표기는 cfg!(feature=embed_metal)
  기반; 확정 런타임 디바이스는 kb.log(`candle device = …`).
- README: '외부 계산 + 로컬 검색' 절에 복사 대상(kebab.sqlite/sqlite, lancedb/vector_dir)
  + [storage] config 키 + models/assets 복사 불필요 + 동일 버전/모델 조건 + rsync 예시.
- 버전 0.23.0 → 0.23.1 (CLI 출력 + 문서만, 동작/schema 불변).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 12:25:45 +00:00
369aeb3d24 feat(embed): candle Metal (Apple Silicon GPU) opt-in build feature + v0.23.0
- kebab-embed-candle: `metal` feature → candle metal backend; select_device()
  picks Device::new_metal(0) (CPU fallback) under the feature, else Device::Cpu.
  .contiguous() before to_vec2 (Metal rejects strided views; CPU tolerates).
- feature passthrough: kebab-app/embed_metal → kebab-cli/embed_metal.
  Build on macOS: cargo build --release --features embed_metal.
- default (non-metal) path unchanged: clippy 0, candle units + thread_cap + parity pass.
- README + HOTFIXES: Mac-GPU-ingest → copy sqlite+lancedb → server CPU-query workflow.
- version 0.22.0 → 0.23.0 (opt-in build surface).

macOS-only compile; Metal execution/speed/parity validated by user on M4 Pro
(not buildable on the Linux CI/dev machine).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:37:08 +00:00
6ec4e6809f fix(embed-candle): address round-1 review
- commit track-spec + meta-spec/plan into branch (HIGH: dangling `amends:` ref)
- inline parity evidence (cosine 1.0, max_abs_diff 2.01e-7) into HOTFIXES +
  release notes; drop refs to deleted IMPL_REPORT/SPIKE_REPORT (MEDIUM)
- model guard: reject non-e5-large `model` before the 2GB download so
  model_id() can't mislabel vectors (MEDIUM) + unit test
- parity test now covers BOTH query: and passage: prefixes (MEDIUM)
- guard encodings.first() index; document zero-attention/pooling invariant;
  clarify embed_batch prefixing doc (LOW)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:54:20 +00:00
8f7b6ee538 feat(embed): candle 임베딩 provider (NUMA-안전, opt-in) + v0.22.0
duo-socket NUMA 서버에서 fastembed(onnxruntime)가 intra-op 스레드를 48개로
하드코딩해 NUMA 힙 손상 → double-free 로 ingest 가 죽는 문제를 회피하기 위해,
같은 multilingual-e5-large 모델을 순수 Rust(candle)로 돌리는 opt-in 임베딩
provider 를 추가한다.

- 신규 crate kebab-embed-candle: CandleEmbedder (kebab_core::Embedder).
  hf-hub safetensors → XLMRobertaModel forward → mask mean-pool → L2 → e5
  prefix. candle 의존성 트리를 이 crate 에 격리 (core/config 외 kebab-* 의존 0).
- 스레드 캡: [models.embedding].num_threads + env KEBAB_EMBED_THREADS →
  글로벌 rayon 풀 1회 캡 (NUMA-안전 레버).
- kebab-app::embedder() 가 provider 분기 (fastembed/onnx/"" → 기존 경로 불변,
  candle → CandleEmbedder, 미지값 → 에러).
- Phase 0 스파이크 crate 제거 (production 흡수).
- 버전 0.21.1 → 0.22.0 (신규 config surface, pre-1.0 minor bump).

패리티: cosine_min=1.000000, max abs diff=2.01e-7 (< 1e-5) → embedding_version
유지, 재색인 0. fastembed default 동작/벡터 불변. wire schema 변경 없음.

검증(파일+exit code): clippy -D warnings EXIT=0(warning 0), test EXIT=0
(candle unit 5 + thread_cap rayon=4 + config 68), parity #[ignore] EXIT=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:52:25 +00:00
76841af7d3 spike(embed-candle): candle e5-large 타당성 검증 — VERDICT PASS
Track 1 / Phase 0 격리 스파이크. candle(순수 Rust)로
intfloat/multilingual-e5-large 를 돌려 기존 onnxruntime
FastembedEmbedder 와 비교.

결과:
- 패리티: 한/영 10문장 cosine min=mean=1.000000 (완전 일치)
- padding_idx: XLM-R 규약 정상 (소스 + 패리티 이중 확인)
- 스레드 제어: RAYON_NUM_THREADS=4 로 컴퓨트 스레드 12→4 캡 확인
  (fastembed 4.9.1 의 48-하드코딩+override불가 문제 구조적 부재)
- latency: batch=32 candle 2.161s vs fastembed 0.536s (~4×, 4 vs 12 스레드)

→ candle 본 구현 진행 권고 (GREEN). 상세 SPIKE_REPORT.md.

candle 의존성은 crates/spike-embed-candle 에만 격리. 프로덕션
crate 동작 변경 없음. 결정적 NUMA 검증은 그 듀얼소켓 서버에서
사용자 실행 필요 (meta-spec §4.3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:23:51 +00:00
4b4a4c0b32 fix(config): init 헤더에 지원 확장자 상세 목록 유지
annotated_default_document 의 HEADER 가 기존 init 헤더의 '처리 가능한 형식'
상세 목록(.md / .png .jpg .jpeg / .pdf)을 보존하도록 복원. p9-fb-25 의
init_template 계약(지원 확장자 안내) 유지.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 12:46:45 +00:00
f2cc325cf3 feat(cli): kebab config migrate 서브커맨드 + wire config_migration.v1
- Cmd::Config { Migrate { --dry-run } }, --json 시 config_migration.v1.
- wire_config_migration (ConfigMigrationReport 가 schema_version 자체 보유).
- schema.rs WIRE_SCHEMAS 에 config_migration.v1 등록 + JSON schema 파일.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 12:09:31 +00:00
b7e022a5e3 feat(app): config migrate facade + init 주석 공유 + doctor 체크
- config_migrate_with_config_path: 백업(.bak)+atomic write(tmp→rename)+dry-run,
  round-trip 검증으로 실패 시 원본 보존. ConfigMigrationReport 반환.
- init_workspace 가 annotated_default_document() 사용(섹션 주석 포함).
- doctor 에 config_migration 체크 추가(미동기 시 ok=false + hint).
- tests/config_migrate.rs 4개(백업/atomic/dry-run/멱등/doctor) 통과.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 12:09:31 +00:00
bd7c4fd7ef feat(config): config 마이그레이션 엔진 (reconcile + step 체인)
- toml_edit 0.22 의존성 추가
- migrate.rs: CURRENT_SCHEMA_VERSION=2, annotated_default_document(주석
  카탈로그 공유 원천), reconcile(빠진 섹션/키 주석과 함께 추가, 값 불가침),
  step_1_to_2(workspace.include 제거), migrate_document(step+reconcile+stamp)
- schema_version default 1 → 2
- 56 tests green, clippy -D warnings clean

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 12:09:31 +00:00
4dcb4a45d6 feat(config): migrate 모듈 스캐폴딩 + toml_edit 의존성
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 11:41:32 +00:00
2619b7bff7 test(chunk): AST snapshot fixture에 aliases:null 필드 반영
Chunk 구조체에 aliases 필드가 추가된(별칭 인프라) 뒤 chunk-*-ast-v1
snapshot fixture 들이 미갱신 상태로 남아 drift FAIL 이었다. chunk_id·
text·policy_hash·tokenized 는 전부 불변 — 직렬화에 "aliases": null 한
필드만 추가됐다(청크 생성 로직 무변경, 회귀 아님). UPDATE_SNAPSHOTS=1 로
10개 fixture(code c/cpp/go/java/js/kotlin/python/rust/ts + long_section)
재베이크.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 09:57:16 +00:00
e9b520216e fix(expansion): per-alias sentinel orphan cleanup + 캐시 견고성 (PR #195 리뷰)
MAJOR: 별칭 dense 벡터의 chunk_id 가 레거시 단일 `{id}#alias` 에서 줄별
`{id}#alias#0`, `#alias#1`, … 로 바뀌었으나 orphan cleanup 이 단일 sentinel
하나만 삭제해 `#alias#N` 벡터가 LanceDB / embedding_records 에 누수됐다.

- kebab-app: `alias_sentinel_ids_to_delete` 헬퍼 추가(접근법 A) — 본문 +
  legacy `{id}#alias` + `{id}#alias#0`..`{id}#alias#{max-1}` 를 모두 delete-set
  에 포함. max=expansion.max_aliases_per_chunk(= parse_aliases 의 하드 cap)와
  일치. parser-bump / edited-asset / deleted-file 세 LanceDB cleanup 경로 모두
  이 헬퍼를 사용.
- kebab-store-sqlite: embedding_records 명시 DELETE 4 경로(put_chunks /
  purge_*_except_doc_id / purge_orphan_at_workspace_path /
  purge_deleted_workspace_path)를 정확 일치(`|| '#alias'`)에서 `{id}#alias%`
  프리픽스 LIKE 로 전환. 본문 chunk_id 는 32자 hex 라 LIKE 와일드카드 없음.

MINOR 1: alias 캐시 히트 시 비-UTF8 payload 를 미스로 강등(재생성 분기로)
— embedding 경로의 decode-실패→미스 강등과 동작 일치.
MINOR 2: embedding version_key 맨 앞에 kind 토큰("doc") 추가 — 임베더가
kind 별 프리픽스를 붙이므로 미래에 query 임베딩이 같은 캐시를 타도 충돌 방지.

회귀 테스트:
- kebab-app: alias_sentinel_ids_to_delete 단위 테스트 2건.
- kebab-store-sqlite: per-alias sentinel embedding_records 가 세 cleanup
  경로 모두에서 사라지는지 핀하는 통합 테스트 3건.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 09:14:34 +00:00
a8fd76499c feat(expansion): doc-side expansion 별칭 개별 dense 벡터 + 파생물 캐시(V012)
별칭을 줄별 개별 dense 벡터(sentinel `{chunk}#alias#N`)로 색인하고
boilerplate 청크는 별칭 생성을 skip. 묶음 1벡터 방식은 평균화로 특정
표현이 희석돼 오히려 회귀(13/18)했던 것을 폐기. 변형 일관성 14/18 →
16/18, mean_spread@10 0.222 → 0.111 (나무위키 ~1000 문서 CS corpus).
`kebab-core::strip_alias_suffix` 가 suffix 형과 per-alias 형 둘 다 처리.

파생물 캐시(V012): embedding 벡터 + 별칭 LLM 결과를 청크 내용 해시
키로 캐싱해 재색인 시 내용 불변 청크의 재계산을 skip. cache_key =
blake3(kind ‖ text_blake3 ‖ version_key)[:32], version_key 에
model/prompt/dimensions 포함 → §9 cascade 와 정합(버전 bump 시 자동
miss). 측정: 정답 3개 cold 1879s → warm 13s ≈ 145배. 순수 가산이라
corpus_revision bump 없음. search/ask 는 kebab.sqlite+lancedb 만으로
동작 → 외부 서버 색인 후 DB 만 복사하는 이식 워크플로 가능.

V012 schema migration + 신규 surface 로 workspace version 0.20.2 →
0.21.0 (minor) bump. README/HANDOFF/ARCHITECTURE/HOTFIXES sync.
known limitation: stack·svm 설명형 2개 잔존 + grounded 판정이 부분
인용을 grounded 로 오분류(후속 후보).

측정 상세: docs/superpowers/handoffs/2026-05-31-namu-wiki-alias-cache-study.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 08:24:04 +00:00
0282a81c67 fix(store): CASCADE 대체 4번째 경로 + V011 CHECK 복원 (Task 4.5 리뷰)
리뷰 MAJOR: purge_document_at_workspace_path_except_doc_id(parser-bump 경로)에
원본+sentinel embedding_records 명시 DELETE 누락 → tombstone 누적. 추가 +
회귀 테스트. MINOR: V011 status CHECK(pending/committed/tombstone) 복원.
NIT: foreign_keys PRAGMA no-op 주석.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 14:02:46 +00:00
f3587b7143 feat(store): filter_chunks sentinel 별칭 candidate strip (committed 통과)
LanceDB 후보의 sentinel chunk_id({orig}#alias)는 chunks JOIN 에서 탈락해
VectorRetriever strip 이전에 사라진다. candidate 를 kebab_core::strip_alias_suffix
로 원본 chunk_id 로 strip 해 IN-list/JOIN 에 넣어(committed 판정은 원본 body chunk
기준) 통과시키되, 반환은 입력 candidate 형태(sentinel 유지) — VectorRetriever 가
그 sentinel 을 받아 strip+dedup 한다. SQL replace 대신 (b) Rust strip 채택(명확).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 13:41:28 +00:00
483b1ec06b feat(store): V011 embedding_records FK 제거 + CASCADE 대체 명시 DELETE (sentinel 별칭 벡터)
별칭 dense 벡터를 sentinel chunk_id({orig}#alias)로 색인하려면 chunks 에 없는
chunk_id 가 embedding_records 에 들어가야 한다. V001 의 chunk_id REFERENCES chunks
ON DELETE CASCADE FK 가 이를 SQLite 787 로 막으므로 테이블을 FK 없이 재생성한다.
status/vector_committed(V003) + 3개 인덱스 보존, chunks_bd_tombstone_embeddings
trigger 무수정. DROP→RENAME 시 dangling trigger 재파싱을 피하려 legacy_alter_table=ON.

사라진 CASCADE 는 put_chunks + purge 두 경로(purge_orphan_at_workspace_path,
purge_deleted_workspace_path)의 명시 DELETE 로 대체 — chunks 삭제 직전 원본 +
{id}#alias sentinel embedding_records 를 함께 정리. corpus_revision baseline 2→3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 13:41:20 +00:00
b56469f010 fix(core): clippy uninlined_format_args — strip_alias 테스트 (리뷰 MAJOR-1)
workspace clippy --all-targets -D warnings 게이트 통과. format! 인자 인라인.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 11:24:04 +00:00