fix(dogfood): schema.repo_breakdown + workspace.include walker enforcement (dogfood-discovered) #145
Reference in New Issue
Block a user
Delete Branch "fix/dogfood-bugs-schema-walker-incremental"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
요약
Multi-root dogfooding (kebab-docs + httpx + zod + lodash) 으로 발견된 production bug 2건 묶음 fix. 둘 다 1A-1 / 1A-2 origin (advertised surface 가 실제로 작동 안 함):
schema.v1.repo_breakdown항상{}빈 BTreeMap — Task 9 (1A-2) 가code_lang_breakdownsibling 만 populate,repo_breakdown은schema.rs:171placeholderBTreeMap::new()그대로.workspace.includewalker 완전 무시 —connector.rs:log_scope_include_warning가 "handled by extractor router" 라고 안내했으나 router 부재. 사용자가include = ["**/*.md"]설정해도 모든 파일 색인.3번째 bug (asset dedup 으로 idempotent re-ingest 깨짐 = 27 docs Updated) 는 schema migration (V005) 동반이라 분량 / risk 분리 위해 별도 후속 PR 으로 처리 예정.
(1)
repo_breakdownfixTask 9 의
code_lang_breakdown패턴 그대로 mirror —metadata_json -> '$.repo'IN-list,BTreeMap<String, u32>반환.crates/kebab-store-sqlite/src/store.rs:pub fn repo_breakdown()추가crates/kebab-app/src/schema.rs:171:BTreeMap::new()→store.repo_breakdown()?repo_breakdown_counts_by_repo)(2)
workspace.includewalker enforcementsemantics:
includeempty Vec → 모든 파일 통과 (backward-compat)includenon-empty → 1개 이상 패턴 매치하는 파일만 통과 (allow-list)globset::GlobSetbuild → walker per-file filter.crates/kebab-source-fs/src/walker.rs:WalkOverrides.include필드 +build_include_globset()+build_overrides()signature 확장 + allow-list 검사crates/kebab-source-fs/src/connector.rs:log_scope_include_warning삭제 +build_overrides호출에&scope.include전달crates/kebab-source-fs/Cargo.toml:globset직접 depinclude_empty_accepts_all_files/include_nonempty_is_allowlist/include_and_exclude_are_anded)검증
cargo test -p kebab-store-sqlite --lib→ 20/20 (repo_breakdown_counts_by_repo통과)cargo test -p kebab-app --lib schema→ 2/2 (regression 없음)cargo test -p kebab-source-fs→ 50/50 (42 unit + 3 새 통합 + 2 snapshot + 3 symlink)cargo clippy -p kebab-store-sqlite -p kebab-app -p kebab-source-fs --all-targets -- -D warningsclean영향
repo_breakdown필드의 정상 동작 복원).다음 PR (별도)
Asset deduplication ⊥ workspace_path uniqueness —
assets테���블의UNIQUE(asset_id)가 동일 content twin files (e.g. 빈__init__.py, zod logo PDF/JPG 중복, AGENTS.md ↔ CLAUDE.md) 에서 충돌해workspace_path컬럼을 덮어씀. 재ingest 시try_skip_unchanged가get_asset_by_workspace_path(path1)결과 None 받아 unchanged 판정 실패 → 모든 twin file 이 매 ingest 마다Updated. Fix: V005 migration +UNIQUE(workspace_path)인덱스 추가 + UPSERT key 변경. 분량 큰 별도 PR 진행 예정.🤖 Generated with Claude Code
회차 1 — 두 버그 모두 깔끔하게 수정됨. APPROVE.
Fix 1: repo_breakdown
Task 9 가 남긴 BTreeMap::new() placeholder 를 store 쿼리로 교체. code_lang_breakdown 과 byte-level 미러 구조 — alias/WHERE/query_map/context 문자열 패턴 완전 일치. 향후 code_lang_breakdown 에 수정이 생겨도 동일 패턴만 적용하면 됨. 테스트는 Some-repo doc counted / None-repo doc excluded / 총 키 1개 세 가지를 모두 검증.
Fix 2: workspace.include 허용 목록
빈 Vec → 전체 통과(하위 호환), 비어 있지 않으면 파일이 패턴 중 하나라도 일치해야 accept. include 체크를 is_file() 블록 안에만 적용해 디렉토리는 항상 통과 — docs/** 패턴이 의도대로 동작. GlobBuilder(literal_separator=true) 로 * 와 ** 의 / 경계 semantics 를 gitignore 관행과 일치시킴. log_scope_include_warning 전체 제거로 dead code 없음. 통합 테스트 3개(empty/non-empty/AND-with-exclude)가 모두 pass.
Cross-cutting
경계 확인: kebab-store-sqlite, kebab-app, kebab-source-fs 3개 crate 만 변경. globset direct dep 추가 1줄(Cargo.lock 포함) — 타당. 금지 cross-crate dep 없음. 두 커밋 모두 cargo test -p 통과.
[칭찬]
log_scope_include_warning전체 제���. 오해를 유발하는 'handled by extractor router' 주석이 사라져 코드가 더 정직해짐. dead code 남은 것 없음.[칭찬]
include체크가if entry.file_type().is_file()블록 안에만 있어 디렉토리는 항상 통과 —docs/**같은 패턴을 써도 walker 가 해당 디렉토리에 정상 진입함. 올바른 설계.[확인]
rel은path.strip_prefix(root)로 얻은 root-relative path.GlobSet::is_match(rel)에 넘기는 path 가 절대 경로가 아니라 상대 경로라는 점이**/*.md같은 패턴과 정확히 맞물림.literal_separator=true로 컴파일했으므로*는/를 건너지 않고**만 건넘 — gitignore 관행과 일치. 이상 없음.@@ -0,0 +50,4 @@};let assets = conn.scan(&scope).unwrap();let names: Vec<_> = assets.iter().map(|a| a.workspace_path.0.clone()).collect();assert!(names.contains(&"a.md".to_string()), "a.md missing; got: {names:?}");[칭찬] 3개 테스트가 orthogonal 케이스를 분리해서 다룸: (1) empty → 전부 통과, (2) non-empty → allow-list, (3) include AND exclude AND-ing.
assert_eq!(names.len(), N)로 총 파일 수도 검증해 spurious file 이 섞이는 경우를 잡아냄.[칭찬]
code_lang_breakdown과 byte-identical 미러 — alias 명(rp), WHERE 절, query_map 형태, context 문자열 패턴 모두 일치. 향후code_lang_breakdown에 버그 수정이 들어오면 이쪽에도 같은 패턴을 적용하기만 하면 됨. 이상 없음.[칭찬] 테스트가 두 케이스를 정확히 검증함: (a)
repo = "my-repo"문서가 count 1 로 집계되고, (b)repo: null문서가 키"null"로 나타나지 않으며, (c) 총 키 1개만 존재.WHERE rp IS NOT NULLsemantics 를 빠짐없이 커버.