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
7c85de065a
chore: workspace-wide cleanup — clippy::pedantic baseline + auto-fix
...
cut PR v0.18.0 전 마지막 정리. 사용자 요청: "전체 코드베이스를 깔끔하고 알아보기 쉽게".
## Workspace lints
- `Cargo.toml` 의 `[workspace.lints.clippy]` 에 `pedantic = "warn"` (priority -1) + 의도적 allow-list 추가:
- cast_possible_truncation / cast_possible_wrap / cast_sign_loss / cast_precision_loss — ONNX i64 / hash modular reduction 등 의도적 truncation.
- doc_markdown / missing_errors_doc / missing_panics_doc — cosmetic doc style.
- too_many_lines / module_name_repetitions / must_use_candidate / needless_pass_by_value / manual_let_else / items_after_statements / similar_names — informational only.
- format_collect / match_wildcard_for_single_variants / trivially_copy_pass_by_ref / unnecessary_wraps — intentional patterns (exhaustive match, future Result variants 등).
- default_trait_access — `Foo::default()` 가 idiomatic.
- float_cmp — NLI / RRF score 의 explicit threshold 비교 의도.
- struct_excessive_bools / case_sensitive_file_extension_comparisons / naive_bytecount / ignore_without_reason — domain-specific 의도.
- format_push_string / return_self_not_must_use / match_same_arms — builder / wire-label / hot-path 패턴 보존.
- needless_continue / used_underscore_binding / nonminimal_bool / unreadable_literal / many_single_char_names / doc_link_with_quotes / assigning_clones / collapsible_str_replace / trivial_regex / elidable_lifetime_names / range_plus_one / explicit_iter_loop / implicit_hasher / ref_option — remaining low-value style.
- 각 24 crate `Cargo.toml` 에 `[lints] workspace = true` 추가.
## Auto-fix
`cargo clippy --workspace --all-targets --fix` 적용 — 128 files changed, 552 insertions / 472 deletions. 주로:
- uninlined_format_args (~18): `format!("{}", x)` → `format!("{x}")`.
- redundant_closure_for_method_calls (~33): `.map(|x| x.foo())` → `.map(T::foo)`.
- 그 외 mechanical refactor.
## 검증
- `cargo clippy --workspace --all-targets -j 1 -- -D warnings` clean (pedantic + 모든 lint group).
- `cargo test --workspace --no-fail-fast -j 1` — **1293 tests pass + 1 pre-existing flaky fail** (`kebab-mcp::tools_call_ask_multi_hop::ask_tool_routes_multi_hop_true_to_decompose_first`, HOTFIX candidate, cleanup 무관). 회귀 0.
Wire 영향: 없음.
Behavior 영향: 없음 (mechanical refactor only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-26 03:01:58 +00:00
th-kim0823
ebbc3a46ae
feat(app): cursor encode/decode for paginated search (fb-34)
...
Opaque base64(JSON{offset, corpus_revision}). Mismatch or
malformed input returns ErrorV1 with code = stale_cursor.
base64 promoted to workspace dep.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-09 17:49:23 +09:00
cd2213e48d
feat(kebab-parse-image): P6-3 caption adapter — vision LM via trait
...
- 신규 모듈 `crates/kebab-parse-image/src/caption.rs` 추가:
• `caption_image(llm, bytes, lang_hint, cfg)` — `&dyn LanguageModel`
위에서 동작. 비전 LM (예: gemma4:e4b) 이 한 문장 객관 설명
출력. temperature=0 / seed=0 결정성.
• `apply_caption(llm, bytes, block, lang_hint, cfg, events)` —
`block.caption = Some(...)` 으로 채우고 ProvenanceKind::CaptionApplied
이벤트 1건 추가. `image.caption.enabled = false` 면 클린 no-op
(Ok(())). LM 실패 시 block.caption None 그대로 + events 미기록.
• 다운스케일 long-edge `[128, 1536]` 클램프. PNG passthrough hot
path 보존, 그 외는 단일 디코드 + PNG 재인코딩.
• 한국어 / 영어 프롬프트 분기 (lang_hint=\"ko\"/\"kor\" → 한국어).
• `ModelCaption.model_version = \"<provider>/<prompt_template_version>\"`
(예: \"ollama/caption-v1\") — prompt 또는 모델 회귀 감사 가능.
## kebab-core / kebab-llm-local 변경
- `kebab_core::GenerateRequest` 에 `images: Vec<String>` 필드 추가.
`#[serde(default)]` 으로 기존 wire 페이로드 / snapshot 호환.
- `kebab-llm-local::OllamaLanguageModel` 가 req.images 를 Ollama
`images: [base64, ...]` 와이어 필드로 라우팅.
`#[serde(skip_serializing_if = is_empty)]` 로 비어 있을 때 wire
shape 가 pre-P6-3 와 byte-identical.
## kebab-config
- 신규 `ImageCfg.caption: CaptionCfg`:
- `enabled: bool` (default false)
- `max_pixels: u32` (default 768, 클램프 [128, 1536])
- `prompt_template_version: String` (default \"caption-v1\")
- `KEBAB_IMAGE_CAPTION_{ENABLED,MAX_PIXELS,PROMPT_TEMPLATE_VERSION}`
3종 환경변수 추가.
## Spec deviations
`tasks/HOTFIXES.md` 2026-05-02 항목 추가:
- Symptom 1: spec p6-3 시그니처가 `&dyn LanguageModel` 인데 frozen
trait + GenerateRequest 가 vision 미지원. → trait 확장.
- Symptom 2: spec 의 cargo feature `caption` (default OFF at compile
time) → runtime gate 1개로 통합. base64/image/kebab-llm 외 추가
deps 없어 cargo feature 의 binary 절감 가치 미미.
p4-1 / p4-2 / p6-3 spec 의 amends 명시.
## 테스트
`cargo test -p kebab-parse-image --test caption` — 9건 + 1 ignored:
- feature gate (disabled → no-op / Err on direct call)
- happy path (block.caption Some + Provenance CaptionApplied)
- 빈 토큰 stream → empty text + caption.is_some()
- CapturingMock 으로 req.images 라우팅 검증 (base64 1개, decode 가능)
- 한국어 / 영어 프롬프트 분기 (CapturingMock 의 system 캡처)
- LM Err → block.caption None 유지 + events 미기록
- 결정성 (동일 mock 입력 → 동일 caption)
- max_pixels 클램프 (99999 → 1536, 4000×3000 PNG 다운스케일 검증)
- opt-in 통합 (실 192.168.0.47 Ollama / gemma4:e4b → \"The image is
a solid red color.\" 검증 완료, 4.3초)
`cargo test --workspace --no-fail-fast -j 1` 전체 pass.
`cargo clippy --workspace --all-targets -- -D warnings` pass.
## 의존성 경계
- 추가 deps: `kebab-llm` (trait 만), `base64` (이미 P6-2 에서 추가).
- dev-deps: `kebab-llm/mock` 으로 `MockLanguageModel`,
`kebab-llm-local` (통합 테스트 전용 — 런타임 deps 에는 없음).
- forbidden 침범 없음: `kebab-source-fs / parse-md / normalize /
chunk / store-* / embed* / search / rag / UI` 미참조.
contract: docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
sections: §3.4 ImageRefBlock.caption, §3.7a ModelCaption, §9.1
caption (model-generated, low trust).
2026-05-02 06:05:39 +00:00
4ed5536c92
feat(kebab-parse-image): P6-2 OCR adapter — Ollama-vision default
...
- 새 모듈 `crates/kebab-parse-image/src/ocr.rs` 추가. spec 의 `OcrEngine`
trait 그대로 + `OllamaVisionOcr` default 구현 + `apply_ocr` 헬퍼.
- `OllamaVisionOcr`: `<endpoint>/api/generate` 비스트리밍 호출,
`images: [base64]` 필드로 이미지 전달, 프롬프트는 언어 힌트
+ 화이트리스트 언어 목록 포함. 응답 prose 를 `OcrText.joined` 로,
prepared image 전체 영역 단일 region (confidence 1.0) 으로 wrap.
기본 모델 `gemma4:e4b`. endpoint 비어 있으면 `models.llm.endpoint`
로 fallback.
- 이미지 전처리: long-edge `config.image.ocr.max_pixels` (기본 1600,
256~4096 클램프) 초과 시 PNG 로 재인코딩 (image::imageops::resize,
Triangle filter). PNG 입력이 max 이내면 zero-copy passthrough.
- `apply_ocr` 는 OCR 성공 시 block.ocr 를 Some 으로 채우고
ProvenanceKind::OcrApplied 이벤트 추가. 실패 시 block.ocr 는
None 그대로 + provenance 미기록 (부분 상태 누출 금지).
- `kebab-config`: 새 `ImageCfg.ocr: OcrCfg` 블록 (enabled/engine/model
/endpoint/languages/max_pixels). `#[serde(default)]` 로 pre-P6
TOML 호환. `KEBAB_IMAGE_OCR_*` 환경변수 5종 추가.
## Spec deviation
원래 P6-2 spec 은 Tesseract 를 default OCR 엔진으로 지정했으나, dev /
CI 호스트에서 `libtesseract-dev` 시스템 패키지 설치를 피하려고
Ollama-vision 으로 default 를 교체. `OcrEngine` trait 추상화는 spec
그대로 보존 — Tesseract / Apple Vision / PaddleOCR 어댑터는 같은
trait 으로 추후 feature-gate 추가 가능. 자세한 내역은
`tasks/HOTFIXES.md` 2026-05-02 항목 참조.
Trust 측면: vision LM 은 hallucinate 가능. `OcrText.engine = "ollama-vision"`
필드로 consumer 가 엔진 별 신뢰 분기 가능.
## 테스트
- 신규 (`tests/ocr.rs`, 8 + 1 ignored):
- 200 happy → OcrText 디코딩 (joined / engine / engine_version /
region count / bbox / confidence)
- 빈 응답 → 빈 regions
- 5xx → Err with status + body 포함
- 200 error envelope → Err
- apply_ocr → block.ocr Some + Provenance OcrApplied 1건
- apply_ocr error → block.ocr None 유지 + events 미기록
- 4000×3000 PNG → max_pixels=1024 까지 다운스케일, aspect ratio 보존
- from_parts max_pixels 클램프
- opt-in `KEBAB_OCR_INTEGRATION=1` 통합 (실제 192.168.0.47 Ollama
`gemma4:e4b` 로 \"Hello World 2026\" 전사 검증 완료)
- 신규 (`src/ocr.rs` unit): truncate, build_prompt 언어/힌트 처리
- `kebab-config` 테스트 +3: defaults, env override, pre-P6 TOML 호환
전체: `cargo test -p kebab-parse-image` 28 pass + 1 ignored,
`cargo test -p kebab-config` 20 pass,
`cargo clippy --workspace --all-targets -- -D warnings` pass.
contract: docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
sections: §3.4 ImageRefBlock.ocr, §3.7a OcrText / OcrRegion, §9.1 OCR
vs caption provenance.
2026-05-02 05:38:24 +00:00
194dd34668
review(p6-1): 회차 1 지적 반영
...
- Cargo.toml: 미사용 deps 제거 (`serde`, `thiserror`) + dev-deps 의
`serde_json` 중복 선언 제거.
- src/lib.rs: 변수명 `decode_warning` → `dim_warning` (16k cap 초과
분기까지 포괄하므로 더 정확).
- src/exif_extract.rs: `ascii_field` / `u32_field` 의 dead-flexibility
`In` 인자 제거 (모든 호출이 `In::PRIMARY` 였음). 두 단 `if let` 을
Rust 2024 let-chain 으로 정리. EXIF 화이트리스트 출력 키를
workspace wire-schema 컨벤션에 맞춰 snake_case 로 통일
(`Make` → `make`, `DateTimeOriginal` → `date_time_original` 등).
- tests/common/mod.rs: 호출되지 않는 `fake_path` 헬퍼 + `Path` import
제거.
- tests/extractor.rs: snake_case 키로 assertion 갱신.
cargo test -p kebab-parse-image — 14건 모두 pass.
cargo clippy -p kebab-parse-image --all-targets -- -D warnings — pass.
2026-05-02 05:11:40 +00:00
d11a810119
feat(kebab-parse-image): P6-1 image extractor + EXIF whitelist
...
- 새 crate kebab-parse-image 추가 (workspace 19개째). MediaType::Image(_)
자산을 단일-블록 CanonicalDocument 로 변환하는 ImageExtractor 구현.
- parser_version "image-meta-v1" (§9 versioning).
- 본문은 Block::ImageRef 1건만 포함 — OCR / caption 필드는 None 으로
남겨 두고 P6-2 / P6-3 에서 채운다.
- EXIF 화이트리스트 (§9.1, PII 표면 최소화):
Make / Model / Software / DateTimeOriginal / Orientation /
GPSLatitude(+Ref) / GPSLongitude(+Ref). MakerNote / Thumbnail / 기타
태그는 폐기. DateTime 은 EXIF "YYYY:MM:DD HH:MM:SS" → ISO-8601 변환.
GPS DMS triple + N/S/E/W ref → signed decimal degree.
- 차원: image::ImageReader 헤더만 읽어 (w, h, format) 획득. 16k×16k cap
초과 또는 디코드 실패 → metadata.user.dimensions = null + Provenance
Warning 이벤트 (Err 아님). 포맷 자체 인식 실패 → anyhow::Error
(caller skip).
- SourceSpan::Region { 0, 0, w, h } 으로 전체 이미지 영역 표기. 결정성:
동일 bytes + 동일 parser_version → 동일 doc_id + block_id (§4.2 ID
recipe 그대로 사용).
- metadata.source_type = Reference, trust_level = Primary, lang = "und".
title = 확장자 제외 파일명, alt = 파일명.
- 의존성 경계 (§8): kebab-core 만 + image 0.25 (default features off,
png/jpeg/webp/gif/tiff 만), kamadak-exif 0.6, anyhow / serde /
serde_json / time / tracing / thiserror. kebab-source-fs · parse-md ·
store-* · embed* · llm* · rag · UI crate 미참조.
- 테스트 14개 (4 unit + 10 integration):
• PNG 차원 추출, JPEG EXIF GPS 추출 (DMS → decimal 변환 정확도 1e-6),
EXIF 없는 PNG → 빈 map, 손상 PNG → warning + null dims (panic 없음),
인식 불가 bytes → Err, 결정성, 스냅샷, supports() 매칭, media_type
불일치 거부.
• 픽스처는 in-memory 생성 (PNG 는 image crate, EXIF JPEG 는 kamadak
Writer 로 EXIF blob 만든 뒤 SOI 직후 APP1 splice) — 바이너리
fixture 커밋 없음.
- HEIC / RAW 는 spec 상 v1 out of scope (image crate 미지원, Apple
Vision sidecar 가 추후 P+ 에서 채움).
- tasks/p6/p6-1-image-extractor-exif.md status: planned → completed.
contract: docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
sections: §3.4 Block::ImageRef + ImageRefBlock, §3.7a OcrText /
ModelCaption stubs, §9.1 image extraction policy, §9 versioning.
2026-05-02 05:05:47 +00:00