fix(dogfood): schema.repo_breakdown + workspace.include walker enforcement (dogfood-discovered) #145

Merged
altair823 merged 2 commits from fix/dogfood-bugs-schema-walker-incremental into main 2026-05-20 05:19:01 +00:00
Owner

요약

Multi-root dogfooding (kebab-docs + httpx + zod + lodash) 으로 발견된 production bug 2건 묶음 fix. 둘 다 1A-1 / 1A-2 origin (advertised surface 가 실제로 작동 안 함):

  1. schema.v1.repo_breakdown 항상 {} 빈 BTreeMap — Task 9 (1A-2) 가 code_lang_breakdown sibling 만 populate, repo_breakdownschema.rs:171 placeholder BTreeMap::new() 그대로.
  2. workspace.include walker 완전 무시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_breakdown fix

Task 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()?
  • 회귀 테스트 1건 (repo_breakdown_counts_by_repo)

(2) workspace.include walker enforcement

semantics:

  • include empty Vec → 모든 파일 통과 (backward-compat)
  • include non-empty → 1개 이상 패턴 매치하는 파일만 통과 (allow-list)
  • exclude 와 AND 조합 (exclude 가 그 후 추가 거름)

globset::GlobSet build → 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 직접 dep
  • 회귀 테스트 3건 (include_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 warnings clean

영향

  • wire schema 변경 없음 (이미 노출된 repo_breakdown 필드의 정상 동작 복원).
  • frozen design 변경 없음.
  • 사용자 데이터 invalidation 없음 — schema breakdown query 와 walker 동작만 정상화.

다음 PR (별도)

Asset deduplication ⊥ workspace_path uniquenessassets 테���블의 UNIQUE(asset_id) 가 동일 content twin files (e.g. 빈 __init__.py, zod logo PDF/JPG 중복, AGENTS.md ↔ CLAUDE.md) 에서 충돌해 workspace_path 컬럼을 덮어씀. 재ingest 시 try_skip_unchangedget_asset_by_workspace_path(path1) 결과 None 받아 unchanged 판정 실패 → 모든 twin file 이 매 ingest 마다 Updated. Fix: V005 migration + UNIQUE(workspace_path) 인덱스 추가 + UPSERT key 변경. 분량 큰 별도 PR 진행 예정.

🤖 Generated with Claude Code

## 요약 Multi-root dogfooding (kebab-docs + httpx + zod + lodash) 으로 발견된 production bug 2건 묶음 fix. 둘 다 1A-1 / 1A-2 origin (advertised surface 가 실제로 작동 안 함): 1. **`schema.v1.repo_breakdown` 항상 `{}` 빈 BTreeMap** — Task 9 (1A-2) 가 `code_lang_breakdown` sibling 만 populate, `repo_breakdown` 은 `schema.rs:171` placeholder `BTreeMap::new()` 그대로. 2. **`workspace.include` walker 완전 무시** — `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_breakdown` fix Task 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()?` - 회귀 테스트 1건 (`repo_breakdown_counts_by_repo`) ## (2) `workspace.include` walker enforcement semantics: - `include` empty Vec → 모든 파일 통과 (backward-compat) - `include` non-empty → 1개 이상 패턴 매치하는 파일만 통과 (allow-list) - exclude 와 AND 조합 (exclude 가 그 후 추가 거름) `globset::GlobSet` build → 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` 직접 dep - 회귀 테스트 3건 (`include_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 warnings` clean ## 영향 - wire schema 변경 없음 (이미 노출된 `repo_breakdown` 필드의 정상 동작 복원). - frozen design 변경 없음. - 사용자 데이터 invalidation 없음 — schema breakdown query 와 walker 동작만 정상화. ## 다음 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](https://claude.com/claude-code)
altair823 added 2 commits 2026-05-20 05:16:08 +00:00
Dogfooding (PR #142 1B + multi-root corpus: kebab-docs + httpx + zod + lodash)
revealed schema.v1.repo_breakdown is always {} despite the 1A-2 Task 9
having added the code_lang_breakdown sibling. The schema.rs:171 placeholder
`BTreeMap::new()` was left in place. Mirror Task 9's code_lang_breakdown
query for the repo field — same metadata_json JSON-path pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
config.workspace.include was completely ignored by the walker — connector.rs
log_scope_include_warning literally said "handled by extractor router" but
no extractor router exists. Dogfooding (PR #142 1B + multi-root corpus
kebab-docs + httpx + zod + lodash) showed user-set include of code+md still
ingested 84 .png + 8 .pdf files.

Fix: walker treats scope.include as an allow-list — empty Vec preserves
backward-compat (all files pass), non-empty requires file path to match at
least one pattern (AND with the existing exclude rules). Removed the
misleading debug log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 approved these changes 2026-05-20 05:18:15 +00:00
claude-reviewer-01 left a comment
Member

회차 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 통과.

회차 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 남은 것 없음.

**[칭찬]** `log_scope_include_warning` 전체 제���. 오해를 유발하는 'handled by extractor router' 주석이 사라져 코드가 더 정직해짐. dead code 남은 것 없음.

[칭찬] include 체크가 if entry.file_type().is_file() 블록 안에만 있어 디렉토리는 항상 통과 — docs/** 같은 패턴을 써도 walker 가 해당 디렉토리에 정상 진입함. 올바른 설계.

**[칭찬]** `include` 체크가 `if entry.file_type().is_file()` 블록 안에만 있어 디렉토리는 항상 통과 — `docs/**` 같은 패턴을 써도 walker 가 해당 디렉토리에 정상 진입함. 올바른 설계.

[확인] relpath.strip_prefix(root) 로 얻은 root-relative path. GlobSet::is_match(rel) 에 넘기는 path 가 절대 경로가 아니라 상대 경로라는 점이 **/*.md 같은 패턴과 정확히 맞물림. literal_separator=true 로 컴파일했으므로 */ 를 건너지 않고 ** 만 건넘 — gitignore 관행과 일치. 이상 없음.

**[확인]** `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 이 섞이는 경우를 잡아냄.

**[칭찬]** 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 에 버그 수정이 들어오면 이쪽에도 같은 패턴을 적용하기만 하면 됨. 이상 없음.

**[칭찬]** `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 NULL semantics 를 빠짐없이 커버.

**[칭찬]** 테스트가 두 케이스를 정확히 검증함: (a) `repo = "my-repo"` 문서가 count 1 로 집계되고, (b) `repo: null` 문서가 키 `"null"` 로 나타나지 않으며, (c) 총 키 1개만 존재. `WHERE rp IS NOT NULL` semantics 를 빠짐없이 커버.
altair823 merged commit 0a2a7ae214 into main 2026-05-20 05:19:01 +00:00
altair823 deleted branch fix/dogfood-bugs-schema-walker-incremental 2026-05-20 05:19:02 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#145