21 task plan: kebab-core 도메인 타입 (Citation::Code variant, SearchHit repo/code_lang, IngestReport skip counters, Metadata extension), 새 kebab-parse-code crate (lang/repo/skip 모듈, gix dep), kebab-source-fs gitignore+blacklist 통합, kebab-config [ingest.code] 절, kebab-cli --repo/--code-lang flag, wire schema JSON 갱신, frozen design doc 갱신, README/HANDOFF/SMOKE 갱신, task index. 각 task 가 5-step TDD cycle (test fail → impl → pass → commit). 코드 chunker 는 1A-1 에 없음 — 1A-2 에서 추가.
spec 의 Citation::Code 예시가 기존 5 variants 의 flat wire 형태와 안 맞아서 (`code: {...}` 중첩이 아니라 top-level field) 같이 fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
817 lines
40 KiB
Markdown
817 lines
40 KiB
Markdown
# kebab — Code Ingest Design
|
|
|
|
기준일: 2026-05-15
|
|
대상: kebab 워크스페이스를 **코드 corpus** 로 확장 (`Tier 1` AST per-language + `Tier 2` resource-aware + `Tier 3` paragraph fallback). frozen design doc `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` 의 후속이자, 그 §11 비-스코프 중 "모든 파일 포맷의 완벽한 parsing" 을 **부분적으로** 깨는 첫 spec. 단, multi-workspace / watch mode 같은 다른 비-스코프는 그대로 유지.
|
|
|
|
대상 사용자 시나리오: 한 부모 디렉토리 (`workspace.root`) 아래 *수십 개의 git repo* 를 clone 한 상태에서, 그 corpus 전체에 의미 검색 + RAG 를 한 곳에서 수행.
|
|
|
|
---
|
|
|
|
## 0. 동결된 결정 요약
|
|
|
|
| # | 결정 | 값 | 근거 |
|
|
|---|------|-----|------|
|
|
| C1 | 스코프 단위 | 한 `workspace.root` 아래 여러 repo (multi-workspace 안 함) | frozen Q10 / §11 그대로 — 부모 디렉토리 한 줄로 커버 |
|
|
| C2 | chunking 전략 | Tier 1 = AST per-language, Tier 2 = resource-aware, Tier 3 = paragraph + line-window | 의미 단위가 언어별로 다름. 일률 적용 불가 |
|
|
| C3 | embedding 모델 | 기존 `multilingual-e5-large` 유지 | 코드 + 문서 동일 벡터 공간 → cross-corpus 검색. embedding_version cascade 회피 |
|
|
| C4 | ignore 통합 | `.gitignore` 자동 honor + `.kebabignore` 추가 layer + 최소 built-in safety net | 사용자 mental model 자연스러움. `.gitignore` 가 source of truth |
|
|
| C5 | repo 인식 | `.git/` walk-up 자동 감지, identifier = dir 이름 | 단순 / deterministic / git remote 미설정 repo 도 안 깨짐 |
|
|
| C6 | branch 처리 | working tree only. branch 변경 후 ingest 는 blake3 hash 차이로 incremental reprocess | git history aware 색인은 §3 도메인 모델 크게 흔듦 — P+ |
|
|
| C7 | Citation variant | 새 `code` variant 도입 (line/page/region/caption/time/**code**) | 의미 분리 명확 — agent / consumer 분기 깔끔 |
|
|
| C8 | search hit 추가 | `SearchHit.repo`, `SearchHit.code_lang` (optional, additive minor) | repo 격리 / 통계 / filter |
|
|
| C9 | 새 filter | `--media code`, `--code-lang <list>`, `--repo <name>` | search ergonomics |
|
|
| C10 | chunker_version | per-language (`code-rust-ast-v1` 등) | 언어별 chunker 독립 진화, §9 cascade rule 깔끔 |
|
|
| C11 | crate 구조 | 새 crate `kebab-parse-code` (모든 언어 mod) + 기존 `kebab-chunk` 모듈 확장 | 22 crates 한 번만 증가. 언어 추가는 모듈 한 쌍 |
|
|
| C12 | symbol path | per-language convention (`mod::fn` / `pkg.cls.method` / `module/Class.method` …) | 각 언어 self-reference 관습 그대로 |
|
|
| C13 | RAG prompt | Phase 1A 는 `rag-v2` 유지. 측정 후 `rag-v3` 도입 검토 | YAGNI |
|
|
| C14 | 특수 파일 | manifest 류 (`Cargo.toml` 등) 는 파일 통째로 1 chunk (`manifest-file-v1`) | 작은 파일은 전체 보기가 더 유용 |
|
|
| C15 | Phase 분할 | 1A Rust → 1B Python+TS/JS → 1C Go+Java/Kotlin → 1D C/C++ → 2 Tier 2 → 3 Tier 3 | 점진 도입, dogfooding 가능 |
|
|
| C16 | built-in skip 최소 | 5 entries: `node_modules/` `target/` `__pycache__/` `.venv/` `venv/` `env/` | `.gitignore` 가 메인 — built-in 은 safety net |
|
|
| C17 | generated header sniff | `@generated` / `DO NOT EDIT` 등 marker 6 종 — 첫 ~500 byte read | 첫 도그푸딩 비용 차단 (protobuf 등) |
|
|
| C18 | size cap | `max_file_bytes = 262144` (256 KiB), `max_file_lines = 5000` default | 대용량 fixture / minified 차단 |
|
|
|
|
---
|
|
|
|
## 1. 스코프 + 비-스코프
|
|
|
|
### 1.1 스코프 (이 spec 으로 동결되는 것)
|
|
|
|
- 코드 / 설정 파일 ingest 파이프라인 (parse → chunk → embed → store → retrieve → answer)
|
|
- 새 Citation variant `code`
|
|
- 새 SearchHit 필드 (`repo`, `code_lang`)
|
|
- 새 search filter (`--media code`, `--code-lang`, `--repo`)
|
|
- 새 chunker_version 라벨 family (`code-{lang}-ast-v1`, `k8s-manifest-resource-v1`, `dockerfile-file-v1`, `manifest-file-v1`, `code-text-paragraph-v1`)
|
|
- 새 crate `kebab-parse-code`
|
|
- 기존 `kebab-chunk` 모듈 확장
|
|
- repo 자동 감지 + `metadata.repo` / `git_branch` / `git_commit`
|
|
- ignore 통합 정책 (`.gitignore` honor + `.kebabignore` + built-in)
|
|
- generated / vendored / size cap skip 정책
|
|
- IngestReport 카운트 분류 확장
|
|
- 새 config 절 `[ingest.code]`
|
|
- Phase 분할 (1A → 1B → 1C → 1D → 2 → 3)
|
|
|
|
### 1.2 비-스코프 (이 spec 으로 명시적으로 *안 다루는* 것)
|
|
|
|
- **Multi-workspace** — 여전히 single `workspace.root`. 사용자가 직접 부모 디렉토리 정렬.
|
|
- **Watch mode** — 여전히 명시 ingest 만.
|
|
- **git history aware indexing** — branch / commit 별 snapshot 색인 안 함. working tree 한 시점만.
|
|
- **LSP / go-to-definition / find-references** — 코드 *내비게이션* 은 IDE / CC 가 잘 함. kebab 은 *의미 검색* + *RAG* 만.
|
|
- **Code-specific embedding 모델** — Phase 2+ 측정 후 검토. 현재 spec 에선 e5-large 유지.
|
|
- **`rag-v3` (code-aware prompt)** — Phase 2+ 측정 후 검토.
|
|
- **서브모듈 / git worktree** — `.git/` 가 dir 인 normal repo 만 인식. submodule (`.git` file) 은 metadata.repo 만 null 또는 부모 repo 이름 fallback.
|
|
- **Cross-repo 의도적 dedup** — blake3 content hash 의 우연 dedup 만 존재. 명시적 dedup 로직 안 함.
|
|
- **`kebab://` URL handler** — frozen §11 그대로 P+.
|
|
|
|
---
|
|
|
|
## 2. Phase 분할 + 마일스톤
|
|
|
|
각 phase = 별도 task spec (`tasks/p10/p10-1a-1-code-ingest-framework.md` 등) + 별도 PR. **Phase 1A-1** 이 *프레임워크 일체* (새 crate skeleton, 새 Citation variant, repo metadata, 새 filter, ignore 정책 전체, skip 정책, IngestReport 세분화) 를 들고 들어가는 가장 무거운 phase. 1A-2 이후는 *언어 / chunker 추가* 만.
|
|
|
|
| Phase | 내용 | 새 crate / 모듈 | 새 chunker_version | 마일스톤 |
|
|
|-------|------|----------------|--------------------|----------|
|
|
| **1A-1** | 프레임워크 일체 — Citation `code` variant, SearchHit `repo`/`code_lang`, 새 filter (`--media code` / `--code-lang` / `--repo`), ignore 통합 정책, skip 정책 (built-in/generated/size), IngestReport 세분화, config `[ingest.code]` 절. `kebab-parse-code` crate **skeleton** (lang/repo/skip 모듈만, 언어 parser 없음) | `kebab-parse-code` 신설 — infrastructure only, language parser 모듈 없음 | *없음* (chunker 추가 0) | wire schema additive minor commit. 기존 markdown corpus 무영향 검증 (regression test). 코드 ingest 아직 활성 안 됨 |
|
|
| **1A-2** | Rust AST chunker 자체 + tree-sitter-rust 도입. Rust 파일 ingest 활성화 | 동일 crate 에 `rust.rs` parser 모듈 + `kebab-chunk/code_rust_ast_v1.rs` | `code-rust-ast-v1` | kebab 자기 자신 dogfooding 가능 |
|
|
| **1B** | Python + TS/JS AST ingest | 동일 crate 에 `python.rs` / `typescript.rs` / `javascript.rs` 모듈 + chunker 추가 | `code-python-ast-v1`, `code-ts-ast-v1`, `code-js-ast-v1` | 사내 ML 코드 + 웹 코드 검색 |
|
|
| **1C** | Go + Java + Kotlin AST ingest | 동일 crate 에 모듈 추가 | `code-go-ast-v1`, `code-java-ast-v1`, `code-kotlin-ast-v1` | 사내 backend 검색 |
|
|
| **1D** | C + C++ AST ingest | 동일 crate 에 모듈 추가 | `code-c-ast-v1`, `code-cpp-ast-v1` | system code 검색 (마지막) |
|
|
| **2** | Tier 2 resource-aware: k8s manifest + Dockerfile + 일반 manifest | 동일 crate 에 모듈 추가 | `k8s-manifest-resource-v1`, `dockerfile-file-v1`, `manifest-file-v1` | k8s 운영 / DevOps 검색 |
|
|
| **3** | Tier 3 fallback: shell + 미지원 확장자 | 동일 crate 에 모듈 추가 | `code-text-paragraph-v1` | 잡 텍스트 fallback |
|
|
|
|
**Phase 1A 가 1A-1 / 1A-2 로 쪼개진 이유**: 1A 가 들고 들어가는 *프레임워크 surface* (Citation variant, SearchHit 필드, filter 3종, skip 정책, config 절, IngestReport 세분화, 새 crate) 가 *언어 chunker 자체* 와 독립적으로 검증 가능. 1A-1 머지 후 기존 markdown corpus 가 *byte-level identical* 한 출력을 내는지 regression test 로 검증 — 코드 ingest 가 활성화되지 않은 상태에서 wire schema 변경 안전성을 별도 확인. 1A-2 는 Rust chunker 자체에만 집중, dogfooding 가능 지점 = 1A-2 머지.
|
|
|
|
**Binary version bump 트리거 정리**:
|
|
- **1A-1 머지**: bump 없음. wire 의 additive minor 변경 (CLAUDE.md "wire 의 additive minor 변경 은 backward-compat 이라 본 트리거에 해당 안 됨" 적용). 코드 ingest 미활성 — 사용자 도그푸드 surface 변경 없음.
|
|
- **1A-2 머지**: minor bump (예: `0.6` → `0.7`). 사용자 도그푸딩 가능 = bump 트리거.
|
|
- 이후 phase (1B/1C/1D/2/3) 의 bump 여부는 각 phase 의 task spec 에서 결정 — wire / flag 추가 없으면 patch bump.
|
|
|
|
---
|
|
|
|
## 3. 도메인 모델 영향
|
|
|
|
frozen design `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` 의 §3 (도메인 모델) 과 §2 (wire schema) 를 *additive minor* 로 확장. breaking 변경 없음. 영향 받는 frozen design 섹션은 [§10 cascade](#10-변경-영향--cascade) 에 정리.
|
|
|
|
### 3.1 새 Citation variant: `code`
|
|
|
|
frozen design §2.1 의 5 variant (`line` / `page` / `region` / `caption` / `time`) 에 `code` 추가 → 총 6 variant.
|
|
|
|
```json
|
|
{
|
|
"schema_version": "citation.v1",
|
|
"kind": "code",
|
|
"path": "kebab/crates/kebab-chunk/src/md_heading_v1.rs",
|
|
"uri": "kebab/crates/kebab-chunk/src/md_heading_v1.rs#L142-L168",
|
|
"line_start": 142,
|
|
"line_end": 168,
|
|
"symbol": "MdHeadingV1Chunker::chunk_doc",
|
|
"lang": "rust"
|
|
}
|
|
```
|
|
|
|
**Wire 형태 — flat**: 기존 5 variants 와 동일한 패턴 (`Citation::Line` 도 `start` / `end` / `section` 이 top-level, 중첩 없음). serde `#[serde(tag = "kind")]` 외부 tag enum 이라 variant 별 필드가 top-level 에 들어감.
|
|
|
|
`symbol` 은 nullable — Tier 1 AST chunk 면 채움, Tier 2/3 면 `null`.
|
|
`lang` 은 `--code-lang` filter 와 같은 식별자 (lowercase). null 가능.
|
|
|
|
기존 5 variant 와 마찬가지로 `path` + `uri` 는 항상 채움. `uri` 는 `path#L<start>-L<end>` (W3C Media Fragments) 그대로.
|
|
|
|
### 3.2 SearchHit 신규 optional 필드
|
|
|
|
frozen design §2.2 의 SearchHit 에 두 필드 추가, 모두 optional / nullable, additive minor:
|
|
|
|
```json
|
|
{
|
|
"schema_version": "search_hit.v1",
|
|
"rank": 1,
|
|
"score": 0.78,
|
|
"score_kind": "rrf",
|
|
"chunk_id": "...",
|
|
"doc_id": "...",
|
|
"doc_path": "kebab/crates/kebab-chunk/src/md_heading_v1.rs",
|
|
"heading_path": ["src", "md_heading_v1"],
|
|
"section_label": "MdHeadingV1Chunker::chunk_doc",
|
|
"snippet": "...",
|
|
"citation": { "kind": "code", "...": "citation.v1" },
|
|
|
|
"repo": "kebab", // ← 신규 optional. .git/ walk-up 결과.
|
|
"code_lang": "rust", // ← 신규 optional. Tier 1/2/3 모두 채움 (Tier 2 의 yaml 등 포함).
|
|
|
|
"retrieval": { "...": "..." },
|
|
"index_version": "v1.0",
|
|
"embedding_model": "multilingual-e5-large",
|
|
"chunker_version": "code-rust-ast-v1"
|
|
}
|
|
```
|
|
|
|
기존 consumer (Claude Code skill 등) 는 두 필드 미인지 시 무시 — backwards-compat.
|
|
|
|
Markdown / PDF / 이미지 hit 는 두 필드 모두 null. 코드 hit 도 *repo 외부 single-file ingest* (`kebab ingest-file`) 인 경우 `repo` null 가능.
|
|
|
|
### 3.3 chunker_version 명명 (per-language)
|
|
|
|
frozen design §3.2 의 `chunker_version` 라벨 family 확장. **per-language 독립** — 언어 chunker 버그 픽스가 다른 언어 chunks 무효화 안 함.
|
|
|
|
```text
|
|
기존:
|
|
md-heading-v1
|
|
pdf-page-v1
|
|
|
|
Phase 1A 추가:
|
|
code-rust-ast-v1
|
|
|
|
Phase 1B 추가:
|
|
code-python-ast-v1
|
|
code-ts-ast-v1
|
|
code-js-ast-v1
|
|
|
|
Phase 1C 추가:
|
|
code-go-ast-v1
|
|
code-java-ast-v1
|
|
code-kotlin-ast-v1
|
|
|
|
Phase 1D 추가:
|
|
code-c-ast-v1
|
|
code-cpp-ast-v1
|
|
|
|
Phase 2 추가:
|
|
k8s-manifest-resource-v1
|
|
dockerfile-file-v1
|
|
manifest-file-v1
|
|
|
|
Phase 3 추가:
|
|
code-text-paragraph-v1
|
|
```
|
|
|
|
cascade rule (frozen design §9):
|
|
- 한 언어 chunker 버그 픽스 → 해당 `code-{lang}-ast-vN` 만 bump → `embedding_records` 의 해당 chunk 만 invalidate → 다음 ingest 에서 해당 언어 파일만 reprocess.
|
|
- 공통 코드 (예: tree-sitter wrapper) 변경 → 영향 받는 모든 언어 chunker 동시 bump.
|
|
|
|
### 3.4 symbol path 포맷 (per-language convention)
|
|
|
|
`Citation.code.symbol` 의 값. 각 언어의 *self-reference 관습* 그대로.
|
|
|
|
| 언어 | 포맷 | 예시 |
|
|
|------|------|------|
|
|
| Rust | `mod::sub::fn_name`, `impl Type::method`, `Trait::method` | `chunk::md_heading_v1::MdHeadingV1Chunker::chunk_doc` |
|
|
| Python | `pkg.module.Class.method`, `pkg.module.func` | `kebab_eval.metrics.compute_mrr` |
|
|
| TS/JS | `module/Class.method`, `module/func`, `module/default` | `src/search/retriever/Retriever.search` |
|
|
| Go | `package.Func`, `package.(Receiver).Method` | `chunk.(*MdHeadingV1Chunker).ChunkDoc` |
|
|
| Java/Kotlin | `package.Class.method` | `com.kebab.chunk.MdHeadingV1Chunker.chunkDoc` |
|
|
| C | `func_name` | `parse_blocks` |
|
|
| C++ | `namespace::Class::method`, `namespace::func` | `kebab::chunk::MdHeadingV1Chunker::chunk_doc` |
|
|
|
|
**top-level scope** (top-level fn / struct / class 정의 외부의 code, 예: Rust `use` / Python `import` block) 는 `<top-level>` 로 표기. null 아님 — chunk 가 의미 단위 *없는* 영역임을 명시.
|
|
|
|
**module / namespace 만 있고 symbol 없는 경우** (예: Rust mod 선언만 모인 `lib.rs`): `<module>` 로 표기.
|
|
|
|
### 3.5 metadata 확장
|
|
|
|
frozen design §3.6 (Metadata / Provenance) 에 코드 ingest 시 채워지는 필드:
|
|
|
|
```rust
|
|
pub struct Metadata {
|
|
// 기존 필드 ...
|
|
pub lang: Option<String>, // BCP-47 (자연어). 코드 파일은 보통 null. 코드 안의 주석 dominant lang detection 안 함.
|
|
pub tags: Vec<String>,
|
|
// ...
|
|
|
|
// 신규 (코드 ingest)
|
|
pub repo: Option<String>, // .git/ walk-up 결과. dir 이름.
|
|
pub git_branch: Option<String>, // ingest 시점 HEAD branch.
|
|
pub git_commit: Option<String>, // ingest 시점 HEAD commit SHA (full 40 hex).
|
|
pub code_lang: Option<String>, // tree-sitter parser 이름과 매칭. lowercase.
|
|
}
|
|
```
|
|
|
|
`code_lang` 식별자 정규화 (이 spec 의 canonical 정의):
|
|
- Rust 파일 (`.rs`) → `rust`
|
|
- Python (`.py`, `.pyi`) → `python`
|
|
- TypeScript (`.ts`, `.tsx`) → `typescript`
|
|
- JavaScript (`.js`, `.jsx`, `.mjs`, `.cjs`) → `javascript`
|
|
- Go (`.go`) → `go`
|
|
- Java (`.java`) → `java`
|
|
- Kotlin (`.kt`, `.kts`) → `kotlin`
|
|
- C (`.c`, `.h`) → `c`
|
|
- C++ (`.cpp`, `.cc`, `.cxx`, `.hpp`, `.hh`, `.hxx`) → `cpp`
|
|
- YAML / k8s manifest (`.yaml`, `.yml`) → `yaml`
|
|
- Dockerfile (`Dockerfile`, `*.dockerfile`) → `dockerfile`
|
|
- TOML (`.toml`) → `toml`
|
|
- JSON (`.json`) → `json`
|
|
- Shell (`.sh`, `.bash`, `.zsh`) → `shell`
|
|
- Make (`Makefile`, `*.mk`) → `make`
|
|
- 미지원 / Tier 3 fallback → null
|
|
|
|
확장자 sniff 는 `kebab-parse-code` 의 단일 함수 `code_lang_for_path(path: &Path) -> Option<&'static str>` 에서 결정. 이 함수가 *유일한 source of truth*.
|
|
|
|
---
|
|
|
|
## 4. Wire schema v1 변경 (모두 additive minor)
|
|
|
|
### 4.1 변경 요약 표
|
|
|
|
| schema | 변경 | 영향 |
|
|
|--------|------|------|
|
|
| `citation.v1` | `kind = "code"` variant 추가 + `code: { line_start, line_end, symbol, lang }` 키 추가 | additive minor (기존 consumer 미인지 시 빠짐) |
|
|
| `search_hit.v1` | `repo`, `code_lang` 두 optional 필드 추가 | additive minor |
|
|
| `ingest_report.v1` | `skipped_generated`, `skipped_size_exceeded`, `skipped_builtin_blacklist`, `skipped_gitignore` 카운트 + `skip_examples` 추가 | additive minor |
|
|
| `schema.v1` | `media_breakdown` 에 `code` 카테고리 추가, 새 `code_lang_breakdown` 표 추가 | additive minor |
|
|
| `doctor.v1` | (변경 없음) | — |
|
|
| `answer.v1` | (Phase 1A 변경 없음. citation 객체가 code variant 일 수 있다는 점만 implicit) | — |
|
|
| `fetch_result.v1` | (변경 없음, kind=chunk / doc / span 그대로) | — |
|
|
|
|
### 4.2 JSON Schema 파일 수정 위치
|
|
|
|
```
|
|
docs/wire-schema/v1/
|
|
citation.schema.json ← code variant 추가
|
|
search_hit.schema.json ← repo / code_lang 추가
|
|
ingest_report.schema.json ← skip 카운트 + skip_examples 추가
|
|
schema.schema.json ← code_lang_breakdown 추가
|
|
```
|
|
|
|
각 schema 파일에 `"additionalProperties": false` 가 켜져 있으면 새 필드 정의 추가만으로 valid 가 안 됨 — 새 필드를 `properties` 에 명시하고 `required` 는 그대로 유지 (optional).
|
|
|
|
### 4.3 `--json` 출력 호환성 검증
|
|
|
|
Phase 1A 구현 시 기존 markdown corpus 의 hit / answer 가 *예전과 byte-level identical* 한 출력 내는지 단위 테스트 추가:
|
|
|
|
- `search_hit.v1` 의 `repo` / `code_lang` 필드는 markdown hit 에서 *output 에 등장하지 않음* (snake-case omit-null serialization).
|
|
- `ingest_report.v1` 의 새 카운트 필드는 코드 ingest 가 실행되지 않으면 `0` 으로 채워짐 (또는 omit-zero — task spec 단계에서 결정).
|
|
- `citation.v1` 의 `code` 키는 `kind != "code"` variant 에서 항상 absent.
|
|
|
|
---
|
|
|
|
## 5. Ingest 파이프라인 변경
|
|
|
|
### 5.1 Repo 자동 감지
|
|
|
|
```text
|
|
fn detect_repo(path: &Path) -> Option<RepoMeta> {
|
|
// path 의 부모 디렉토리에서 위로 .git/ (dir) 만날 때까지 walk.
|
|
// workspace.root 위로는 안 올라감 (boundary).
|
|
// .git/ 가 file 인 경우 (worktree marker / submodule) → metadata.repo = None,
|
|
// metadata.git_branch / commit = None.
|
|
// .git/ 가 dir 이면:
|
|
// - repo_name = .git/ 의 부모 dir 이름
|
|
// - branch = git symbolic-ref HEAD (없으면 detached HEAD → "detached")
|
|
// - commit = git rev-parse HEAD (40 hex 또는 None if empty repo)
|
|
}
|
|
```
|
|
|
|
`git` binary 호출 vs `gix` (gitoxide) library 사용 — task spec 에서 결정. 단 `git` binary 호출은 PATH 의존성 도입 (kebab 의 다른 곳엔 없음) → `gix` 선호.
|
|
|
|
repo 감지는 ingest 시 *파일당 한 번* 만 — repo 별 캐시 (in-memory HashMap) 로 같은 repo 의 두 번째 파일부터는 lookup hit.
|
|
|
|
### 5.2 ignore 통합 (`.gitignore` + `.kebabignore` + built-in)
|
|
|
|
**우선순위** (앞이 강함):
|
|
1. **Built-in safety net** — 항상 적용, 사용자 negate 가능 (`.kebabignore` 의 `!pattern`)
|
|
2. **`.gitignore`** — repo 의 `.gitignore` 자동 honor. nested `.gitignore` 도 적용 (디렉토리 단위 cascade).
|
|
3. **`.kebabignore`** — kebab 만의 추가 layer. workspace.root + 각 디렉토리 별 가능 (현재 동작 그대로).
|
|
|
|
**Built-in safety net (5 entries 만)**:
|
|
```text
|
|
**/node_modules/
|
|
**/target/
|
|
**/__pycache__/
|
|
**/.venv/
|
|
**/venv/
|
|
**/env/
|
|
```
|
|
|
|
`env/` 가 모호하지만 (사용자 자식 디렉토리가 우연히 "env" 일 수 있음) Python virtualenv 관습 강해서 포함. 사용자 override 는 `.kebabignore` 의 `!env/` 로.
|
|
|
|
**구현**:
|
|
- 기존 `kebab-source-fs` 의 `.kebabignore` 처리 코드를 확장.
|
|
- `ignore` crate (gitignore syntax) 그대로 사용. `.gitignore` + `.kebabignore` 를 같은 `Override` 빌더에 add — `ignore` crate 가 둘 다 표준으로 처리.
|
|
- built-in 은 hardcoded `WalkBuilder.add_custom_ignore_filename` 또는 코드 내 `OverrideBuilder` 로.
|
|
|
|
### 5.3 Generated / vendored skip 정책
|
|
|
|
**Generated header sniff** — `kebab-source-fs` 의 file scan 단계에서 *blake3 hash 계산 전* 에 실행 (incremental ingest 의 빠른 path 유지):
|
|
|
|
```text
|
|
fn is_generated_file(path: &Path) -> io::Result<bool> {
|
|
let mut buf = [0u8; 512];
|
|
let n = File::open(path)?.read(&mut buf)?;
|
|
let head = std::str::from_utf8(&buf[..n]).unwrap_or("");
|
|
|
|
// 줄 단위 markers — case-insensitive 매칭 (다양한 ecosystem 관습 수용).
|
|
head.lines().take(10).any(|line| {
|
|
let l = line.to_ascii_lowercase();
|
|
l.contains("@generated") ||
|
|
l.contains("code generated by") ||
|
|
l.contains("do not edit") ||
|
|
l.contains("do not modify") ||
|
|
l.contains("automatically generated") ||
|
|
l.contains("auto-generated") ||
|
|
l.contains("autogenerated")
|
|
})
|
|
}
|
|
```
|
|
|
|
비용: 파일당 1 read syscall (≤512 byte). 이미 `.gitignore` / built-in 으로 빠진 파일은 이 단계 도달 안 함.
|
|
|
|
**Skip 시 IngestReport 에 sample 등록** — 디버깅 용 (사용자 "왜 X 파일이 색인 안 됐지?" 시 즉시 답):
|
|
```json
|
|
{
|
|
"skip_examples": {
|
|
"generated": [
|
|
"kebab/crates/proto/src/api.pb.rs",
|
|
"..."
|
|
],
|
|
"size_exceeded": [
|
|
"vendor/data/large-fixture.json"
|
|
],
|
|
"builtin_blacklist": ["..."],
|
|
"gitignore": ["..."]
|
|
}
|
|
}
|
|
```
|
|
각 카테고리당 처음 5건만. CLI text 모드에서는 카운트만 표시, `--json` 이면 위 schema 그대로.
|
|
|
|
### 5.4 Size cap
|
|
|
|
```text
|
|
[ingest.code]
|
|
max_file_bytes = 262144 # 256 KiB
|
|
max_file_lines = 5000 # 둘 중 먼저 hit
|
|
```
|
|
|
|
- byte cap 은 `fs::metadata().len()` 한 번 — 매우 빠름.
|
|
- line cap 은 byte cap 통과 후 streaming read 로 5000 line 까지 count, 초과 시 skip.
|
|
- 둘 다 `IngestReport.skipped_size_exceeded` 로 카운트, `skip_examples.size_exceeded` 에 sample.
|
|
|
|
기본값 근거:
|
|
- 256 KiB → 보통 코드 파일 (Rust fn, Python class) 의 100배 이상. minified JS / 대용량 fixture / generated client 의 일반적 사이즈 (수 MB) 는 차단.
|
|
- 5000 line → 한 파일이 한 사람이 이해할 수 있는 한계 근처. 그 이상은 보통 generated.
|
|
|
|
사용자 override:
|
|
```toml
|
|
[ingest.code]
|
|
max_file_bytes = 1048576 # 1 MiB 로 풀고 싶을 때
|
|
max_file_lines = 20000
|
|
```
|
|
|
|
### 5.5 IngestReport 세분화
|
|
|
|
기존 `skipped_by_extension` 옆에 추가:
|
|
|
|
```json
|
|
{
|
|
"schema_version": "ingest_report.v1",
|
|
"indexed": 1234,
|
|
"unchanged": 5678,
|
|
"updated": 12,
|
|
"deleted": 3,
|
|
"skipped_by_extension": 45,
|
|
"skipped_gitignore": 2104,
|
|
"skipped_kebabignore": 8,
|
|
"skipped_builtin_blacklist": 567,
|
|
"skipped_generated": 89,
|
|
"skipped_size_exceeded": 4,
|
|
"skip_examples": {
|
|
"generated": ["..."],
|
|
"size_exceeded": ["..."],
|
|
"builtin_blacklist": ["..."],
|
|
"gitignore": ["..."]
|
|
},
|
|
"warnings": [],
|
|
"duration_ms": 12345
|
|
}
|
|
```
|
|
|
|
`skipped_by_extension` 은 *지원 안 되는 확장자* — 코드 ingest 후로는 Tier 3 fallback (`code-text-paragraph-v1`) 이 잡아내는 폭이 넓어져서 비율이 줄 것. Tier 3 도 못 잡는 binary 등이 남음.
|
|
|
|
human 출력 (TTY) 에서는 한 줄 요약:
|
|
```text
|
|
✓ indexed 1234 chunks (unchanged 5678, updated 12, deleted 3)
|
|
skipped: 2104 .gitignore, 567 built-in, 89 generated, 45 unsupported, 8 .kebabignore, 4 too-large
|
|
duration: 12.3s
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Crate 구조
|
|
|
|
### 6.1 새 crate `kebab-parse-code`
|
|
|
|
```text
|
|
crates/kebab-parse-code/
|
|
├── Cargo.toml
|
|
└── src/
|
|
├── lib.rs # 공통 entry, dispatch by extension
|
|
├── lang.rs # code_lang_for_path(), 식별자 정규화
|
|
├── repo.rs # detect_repo() — gix wrapper
|
|
├── skip.rs # generated header sniff, size cap
|
|
├── rust.rs # tree-sitter-rust → CanonicalDocument (Phase 1A)
|
|
├── python.rs # tree-sitter-python → ... (Phase 1B)
|
|
├── typescript.rs # ... (Phase 1B)
|
|
├── javascript.rs # ... (Phase 1B)
|
|
├── go.rs # ... (Phase 1C)
|
|
├── java.rs # ... (Phase 1C)
|
|
├── kotlin.rs # ... (Phase 1C)
|
|
├── c.rs # ... (Phase 1D)
|
|
├── cpp.rs # ... (Phase 1D)
|
|
├── yaml_k8s.rs # k8s manifest resource-aware (Phase 2)
|
|
├── dockerfile.rs # ... (Phase 2)
|
|
├── manifest.rs # Cargo.toml / package.json 1-chunk (Phase 2)
|
|
└── text_paragraph.rs # Tier 3 fallback (Phase 3)
|
|
```
|
|
|
|
**의존성**:
|
|
- 각 phase 별로 `tree-sitter-*` dep 추가. Phase 1A 는 `tree-sitter-rust` + `tree-sitter` (core) 만.
|
|
- `gix` (gitoxide) — Phase 1A 부터.
|
|
- `kebab-core`, `kebab-parse-types` (CanonicalDocument / Block / SourceSpan).
|
|
|
|
**의존성 제약** (frozen design §8 inheritance):
|
|
- `kebab-parse-code` 는 다른 `kebab-parse-*` 크레이트와 동일한 격리 규칙 — store / embed / llm / rag 직접 import 금지.
|
|
- UI crate (`kebab-cli` / `kebab-tui` / `kebab-mcp`) 는 이 crate 직접 import 금지. `kebab-app` facade 통해서만.
|
|
|
|
### 6.2 `kebab-chunk` 모듈 확장
|
|
|
|
```text
|
|
crates/kebab-chunk/src/
|
|
├── lib.rs # export 추가 (per phase 누적)
|
|
├── md_heading_v1.rs # 기존
|
|
├── pdf_page_v1.rs # 기존
|
|
├── code_rust_ast_v1.rs # Phase 1A
|
|
├── code_python_ast_v1.rs # Phase 1B
|
|
├── code_ts_ast_v1.rs # ...
|
|
├── code_js_ast_v1.rs # ...
|
|
├── code_go_ast_v1.rs # Phase 1C
|
|
├── code_java_ast_v1.rs # ...
|
|
├── code_kotlin_ast_v1.rs # ...
|
|
├── code_c_ast_v1.rs # Phase 1D
|
|
├── code_cpp_ast_v1.rs # ...
|
|
├── k8s_manifest_resource_v1.rs # Phase 2
|
|
├── dockerfile_file_v1.rs # ...
|
|
├── manifest_file_v1.rs # ...
|
|
└── code_text_paragraph_v1.rs # Phase 3
|
|
```
|
|
|
|
각 모듈 = 한 chunker 구현체 + `pub use` 로 lib 에 노출. 기존 패턴 (md_heading_v1 / pdf_page_v1) 그대로.
|
|
|
|
**Chunker trait 변경 없음** — 기존 `Chunker` trait (frozen §7.2) 가 `CanonicalDocument → Vec<Chunk>` 시그니처라 코드도 같은 trait 로 동작.
|
|
|
|
### 6.3 의존성 그래프 변경
|
|
|
|
```text
|
|
기존:
|
|
kebab-app → kebab-parse-md, kebab-parse-pdf, kebab-parse-image
|
|
→ kebab-chunk
|
|
→ ...
|
|
|
|
추가 (Phase 1A):
|
|
kebab-app → kebab-parse-code (신규)
|
|
→ kebab-chunk (모듈 추가)
|
|
```
|
|
|
|
추가 의존성:
|
|
- `kebab-app → kebab-parse-code`
|
|
- `kebab-parse-code → tree-sitter`, `tree-sitter-rust`, `gix`
|
|
- 빌드 영향: `kebab-parse-code` 추가 → workspace `cargo test -p` 단위 한 개 추가. `-j 1` 정책 (frozen CLAUDE.md) 그대로 적용.
|
|
|
|
### 6.4 `target/` 디스크 영향
|
|
|
|
frozen CLAUDE.md 에 "target/ 가 90 GB+ 까지 balloon" 경고 있음. 이 spec 으로 22 → 새 모듈들 추가 시 *integration test* 마다 새 binary linkage 추가 → 더 부풀어. **각 phase 머지 후 `cargo clean` 강제 권장** — CLAUDE.md 의 기존 rule 그대로 적용, phase 끝마다 명시.
|
|
|
|
---
|
|
|
|
## 7. Search / RAG 표면
|
|
|
|
### 7.1 새 search filter
|
|
|
|
`kebab search` 의 기존 filter (`--tag` / `--lang` / `--path-glob` / `--media` / `--ingested-after` / `--trust-min` / `--doc-id`) 에 세 종 추가:
|
|
|
|
```text
|
|
--media code # umbrella — 모든 code Tier 의 chunk
|
|
--code-lang <list> # 반복 / comma — rust,python 식. OR 매칭.
|
|
--repo <name> # 반복 가능. OR 매칭.
|
|
```
|
|
|
|
기존 정책 일관:
|
|
- 반복 가능 flag 는 OR 매칭 (`--repo kebab --repo other`).
|
|
- `--code-lang rs` 같은 alias 는 미지원 — *full identifier* (`rust`) 만. 일관성 위해.
|
|
- 모르는 `--code-lang` 값 → empty hits (`--media` 와 동일 정책).
|
|
- filter flags 간은 AND (`--media code --code-lang rust` → 코드이면서 Rust).
|
|
|
|
### 7.2 `kebab schema` stats 확장
|
|
|
|
frozen design §2.5 / p9-fb-37 의 `stats.media_breakdown` 에 `code` 카테고리 추가:
|
|
|
|
```json
|
|
{
|
|
"schema_version": "schema.v1",
|
|
"stats": {
|
|
"media_breakdown": {
|
|
"markdown": 1234,
|
|
"pdf": 56,
|
|
"image": 78,
|
|
"audio": 0,
|
|
"code": 4567, // ← 신규
|
|
"other": 12
|
|
},
|
|
"lang_breakdown": { // 기존 — 자연어
|
|
"ko": 1100,
|
|
"en": 234,
|
|
"null": 134
|
|
},
|
|
"code_lang_breakdown": { // ← 신규 — 프로그래밍 언어 (chunk 수)
|
|
"rust": 2345,
|
|
"python": 1234,
|
|
"typescript": 567,
|
|
"yaml": 89,
|
|
"go": 332
|
|
},
|
|
"repo_breakdown": { // ← 신규 — repo 별 chunk 수
|
|
"kebab": 1234,
|
|
"internal-api": 567,
|
|
"...": "..."
|
|
},
|
|
"index_bytes": 1234567890,
|
|
"stale_doc_count": 12
|
|
}
|
|
}
|
|
```
|
|
|
|
`repo_breakdown` 도 추가하기로 — 사용자가 "어느 repo 가 가장 많이 색인 됐지?" 확인 가능.
|
|
|
|
### 7.3 RAG prompt (Phase 1A 는 `rag-v2` 그대로)
|
|
|
|
Phase 1A 에서는 코드 chunk 가 *일반 도큐먼트* 로 prompt 에 들어감:
|
|
|
|
```text
|
|
[#1] (code: kebab::chunk::md_heading_v1::MdHeadingV1Chunker::chunk_doc)
|
|
fn chunk_doc(&self, doc: &CanonicalDocument) -> Result<Vec<Chunk>> {
|
|
...
|
|
}
|
|
|
|
[#2] (code: kebab::chunk::pdf_page_v1::PdfPageV1Chunker::chunk_doc)
|
|
...
|
|
```
|
|
|
|
prompt 의 source identifier 가 *file path + symbol* 둘 다 들어가게 — symbol 이 있으면 *symbol* 을 우선 표시, 없으면 file path.
|
|
|
|
`rag-v2` 의 기존 규칙 ("fact 인용 시 [#번호] 앞에 chunk 속 원문 큰따옴표") 은 코드에서 좀 어색할 수 있음 (코드의 큰따옴표는 string literal). 측정 후 어색하면 Phase 2+ 에서 `rag-v3` (code-aware) 도입.
|
|
|
|
### 7.4 `kebab inspect` / `kebab fetch` 영향
|
|
|
|
기존 `kebab inspect chunk <id>` 출력에서 `Citation::Code` variant 의 `symbol` / `code_lang` 표시. text 모드 출력 변경:
|
|
|
|
```text
|
|
chunk_id: abc123...
|
|
doc_path: kebab/crates/kebab-chunk/src/md_heading_v1.rs
|
|
line range: L142-L168
|
|
symbol: MdHeadingV1Chunker::chunk_doc ← 신규 (code variant 에서만)
|
|
code_lang: rust ← 신규
|
|
repo: kebab ← 신규
|
|
chunker_version: code-rust-ast-v1
|
|
```
|
|
|
|
`kebab fetch chunk` / `kebab fetch span` 은 변경 없음 — 본문 byte 그대로 반환.
|
|
|
|
---
|
|
|
|
## 8. Config 변경
|
|
|
|
### 8.1 신규 `[ingest.code]` 절
|
|
|
|
```toml
|
|
[ingest.code]
|
|
# Generated header sniff 활성화. 첫 ~500 byte 의 6 markers 중 하나 발견 시 skip.
|
|
skip_generated_header = true
|
|
|
|
# 파일당 max byte (bytes). 초과 시 skip.
|
|
max_file_bytes = 262144 # 256 KiB
|
|
|
|
# 파일당 max line. 초과 시 skip. byte cap 통과 후 검사.
|
|
max_file_lines = 5000
|
|
|
|
# 사용자 추가 skip 패턴. gitignore 문법. built-in / .gitignore / .kebabignore 외 추가.
|
|
extra_skip_globs = []
|
|
|
|
# AST chunk 가 너무 길 때 fallback line-window 적용 임계.
|
|
# 단일 fn / class 가 이 라인 수 넘으면 paragraph fallback 적용.
|
|
ast_chunk_max_lines = 200 # 단일 chunk 최대 라인
|
|
|
|
# Tier 3 fallback (paragraph + line-window) 시 line-window 사이즈.
|
|
fallback_lines_per_chunk = 80
|
|
fallback_lines_overlap = 20
|
|
```
|
|
|
|
기본값 근거:
|
|
- `skip_generated_header = true` — 안전 default. 미스 케이스 (사용자가 generated 도 색인 원함) 는 명시적 false.
|
|
- `max_file_bytes = 262144` — minified JS / 대용량 generated 차단 충분.
|
|
- `max_file_lines = 5000` — 한 사람이 한 번에 이해할 수 있는 코드 한계 근처.
|
|
- `ast_chunk_max_lines = 200` — 사람 인지 한계 + retrieval token budget.
|
|
- `fallback_lines_per_chunk = 80`, `overlap = 20` — RAG 컨벤션의 보수적 default.
|
|
|
|
### 8.2 기본값 + override 정책
|
|
|
|
- 모든 키 optional. 누락 시 위 default.
|
|
- `KEBAB_*` env override 안 지원 (이건 dev / debug 가 아닌 정책 설정).
|
|
- `--config <path>` 로 격리 테스트 가능 (XDG 의존 안 함).
|
|
|
|
### 8.3 `config.toml` 의 기존 `[workspace]` 절 영향
|
|
|
|
변경 없음. `workspace.root`, `exclude` 그대로. `.gitignore` / `.kebabignore` honor 정책은 *기본 동작* 으로 config 키 없이 active — 사용자가 끄고 싶으면 `.kebabignore` 의 `!pattern` 으로 override.
|
|
|
|
---
|
|
|
|
## 9. Tier 별 chunker 상세
|
|
|
|
### 9.1 Tier 1 — AST per-language
|
|
|
|
**입력**: `CanonicalDocument` with `Block::Code { lang: Some("rust"), code: "..." }`.
|
|
**출력**: `Vec<Chunk>` — 각 chunk 가 AST 의 *top-level 의미 단위* 또는 fallback unit.
|
|
|
|
**Rust 예시 (Phase 1A)**:
|
|
|
|
tree-sitter 의미 단위:
|
|
- `function_item` → 1 chunk
|
|
- `impl_item` 의 각 `function_item` → 1 chunk per method
|
|
- `struct_item` / `enum_item` / `trait_item` → 1 chunk (선언 + doc comment)
|
|
- `mod_item` 의 *내용물* → 재귀 분해
|
|
- top-level `use` / `extern crate` / `const` / `static` block → 한 chunk 로 모음 (`<top-level>` symbol)
|
|
|
|
**ast_chunk_max_lines 초과 시 fallback**:
|
|
- 단일 fn 이 200 line 넘으면 paragraph (blank-line) 기반으로 split.
|
|
- 각 sub-chunk 의 symbol 은 `function_name [part 1/N]` 식으로 표기.
|
|
- 이 동작은 `kebab-chunk/src/code_rust_ast_v1.rs` 내부에서.
|
|
|
|
**citation 의 line range**: tree-sitter node 의 `start_position.row` / `end_position.row` (0-indexed → +1 로 1-based).
|
|
|
|
**예시 input → output**:
|
|
```rust
|
|
// src/lib.rs
|
|
pub fn parse(input: &str) -> Result<Doc> {
|
|
// 50 lines
|
|
}
|
|
|
|
impl Chunker for Foo {
|
|
fn chunk_doc(&self, doc: &Doc) -> Vec<Chunk> {
|
|
// 80 lines
|
|
}
|
|
|
|
fn name(&self) -> &str { "foo-v1" }
|
|
}
|
|
```
|
|
|
|
→ chunks:
|
|
1. `parse`, lines 1-50, symbol = `parse`
|
|
2. `Foo::chunk_doc`, lines 53-132, symbol = `Foo::chunk_doc` (impl 의 method)
|
|
3. `Foo::name`, lines 134-134, symbol = `Foo::name`
|
|
|
|
### 9.2 Tier 2 — resource-aware
|
|
|
|
**k8s-manifest-resource-v1**:
|
|
- YAML multi-document split (`---` separator).
|
|
- 각 document 마다 1 chunk.
|
|
- chunk metadata: `kind: Deployment`, `apiVersion`, `metadata.name`, `metadata.namespace`.
|
|
- citation 의 `symbol` 필드: `<kind>/<namespace>/<name>` (e.g., `Deployment/prod/api-server`). namespace 없으면 `<kind>/<name>`.
|
|
- yaml 파싱 실패 (invalid YAML, 또는 k8s schema 가 아닌 일반 yaml) 시: `code-text-paragraph-v1` 로 fallback 처리.
|
|
|
|
**dockerfile-file-v1**:
|
|
- Dockerfile 전체 = 1 chunk.
|
|
- symbol = `<dockerfile>`.
|
|
- citation 의 line range = 1 ~ EOF.
|
|
- ARG / FROM / RUN / COPY / CMD 등은 chunk 내부 plain text 로 보존.
|
|
|
|
**manifest-file-v1** (Cargo.toml, package.json, pyproject.toml, go.mod, pom.xml, build.gradle, tsconfig.json 등):
|
|
- 파일 통째로 1 chunk.
|
|
- symbol = `<manifest>`.
|
|
- citation 의 line range = 1 ~ EOF.
|
|
|
|
### 9.3 Tier 3 — paragraph + line-window fallback
|
|
|
|
**code-text-paragraph-v1** — shell script, 미지원 확장자, AST 실패 시 fallback:
|
|
- 빈 줄 (blank line) 기준으로 paragraph 분할.
|
|
- paragraph 가 `fallback_lines_per_chunk` (default 80) 넘으면 line-window split with `fallback_lines_overlap` (default 20).
|
|
- symbol 은 null. citation 은 `Citation::Code { symbol: None, lang: Some("shell") }` 또는 lang 미지정.
|
|
|
|
---
|
|
|
|
## 10. 변경 영향 / cascade
|
|
|
|
### 10.1 Frozen design doc 갱신 (이 spec 머지와 동시)
|
|
|
|
`docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` 의 다음 섹션 갱신:
|
|
|
|
| 섹션 | 갱신 내용 |
|
|
|------|-----------|
|
|
| §0 동결된 결정 요약 | "코드 ingest 추가" 1줄. cross-link to 2026-05-15 spec |
|
|
| §2.1 Citation | 5 → 6 variants, `code` 추가 |
|
|
| §2.2 SearchHit | `repo`, `code_lang` optional 필드 |
|
|
| §2.4 IngestReport | skip 카운트 4종 + skip_examples |
|
|
| §2 schema.v1 (fb-37 추가분) | `code` media + `code_lang_breakdown` + `repo_breakdown` |
|
|
| §3.2 Versions / labels | chunker_version family 확장 (per-language pattern) |
|
|
| §3.6 Metadata | `repo`, `git_branch`, `git_commit`, `code_lang` 필드 |
|
|
| §8 모듈 경계 | `kebab-parse-code` 추가 + 의존성 규칙 inheritance |
|
|
| §11 동결 범위 | "code ingest" 가 더 이상 비-스코프 아님 명시. 단 multi-workspace / watch / history aware 는 그대로 비-스코프 |
|
|
|
|
### 10.2 cascade rule (frozen §9) 영향
|
|
|
|
- `parser_version` cascade: 각 phase 의 새 parser version (`code-rust-parse-v1` 등) 추가. 기존 markdown / pdf 무영향.
|
|
- `chunker_version` cascade: per-language 라벨 → 한 언어 chunker 변경이 다른 언어 chunks 무효화 안 함.
|
|
- `embedding_version` cascade: 변경 없음 (`multilingual-e5-large` 그대로).
|
|
- `prompt_template_version` cascade: Phase 1A 는 `rag-v2` 그대로 → 무영향.
|
|
- `index_version` cascade: SQLite DDL 변경 없으면 무영향. metadata.repo / git_branch / git_commit 필드는 *Metadata* 의 JSON blob 안에 추가 — DDL 변경 안 필요 (frozen §5 의 `documents.metadata_json TEXT` 가 free-form).
|
|
|
|
### 10.3 V00X migration?
|
|
|
|
SQLite DDL 변경 없음 → V00X migration 불요. `documents.metadata_json` 의 free-form 내부에 새 키 (`repo`, `git_branch`, `git_commit`, `code_lang`) 가 들어감. 기존 markdown / pdf chunk 들의 metadata_json 은 그대로.
|
|
|
|
### 10.4 Binary version bump
|
|
|
|
[§2 Phase 분할 표 하단 "Binary version bump 트리거 정리"](#2-phase-분할--마일스톤) 참조. 요지:
|
|
- **1A-1 머지** → bump 없음 (wire additive minor + 사용자 surface 변경 없음).
|
|
- **1A-2 머지** → minor bump (`0.6` → `0.7`, 사용자 도그푸딩 시작).
|
|
- 이후 phase 는 각 task spec 에서 결정 (wire / flag 추가 없으면 patch bump).
|
|
|
|
---
|
|
|
|
## 11. Open questions (Phase 1A task spec 단계에서 픽스)
|
|
|
|
이 spec 은 *프레임워크* 까지만 동결. 다음 항목은 Phase 1A 의 task spec 작성 시 결정:
|
|
|
|
1. **AST chunk 의 minimum size** — 5-line fn 도 한 chunk? 또는 minimum threshold (예: ≥ 10 line) 미만은 인접 fn 과 merge? *영향*: chunk 수 폭증 vs retrieval miss.
|
|
|
|
2. **doc_id 충돌 위험** — `Cargo.toml` 두 repo 의 content 가 우연히 동일 → blake3 hash 동일 → 같은 doc? frozen §4.2 의 ID recipe 확인 필요. *영향*: 한 doc 이 두 repo 에서 출처 표시. 해결: doc_id recipe 에 repo / path 포함 여부 확인.
|
|
|
|
3. **`--code-lang` 식별자 정규화 (canonical)** — `rust` / `python` / `typescript` 의 풀네임만 vs `rs` / `py` / `ts` 짧은 alias 도 허용? 이 spec 은 풀네임만 — task spec 에서 alias 매핑 명시.
|
|
|
|
4. **TUI surface 변경 시점** — Phase 1A 에 포함 vs 별도 Phase 4 (TUI code rendering)? *영향*: TUI 의 Library/Inspect 패널에서 code citation 의 symbol/lang/repo 렌더. 일단 Phase 1A 에 *최소 변경* (citation 표시) 만 포함, 별도 인터랙션 (예: `g` 키로 LSP 식 navigation) 은 P+.
|
|
|
|
5. **AST chunk symbol path 의 *depth 한계*** — Rust 의 nested impl / nested mod 가 깊으면 `outer::inner::deepest::method` 식 path 가 길어짐. 60 char cap + 중간 생략 (`outer::…::method`)? Phase 1A 의 task spec 에서 cap 정책 결정.
|
|
|
|
6. **`gix` 의 binary size 영향** — `kebab-parse-code` → `gix` dep 도입이 release binary 크기에 얼마나 영향? `git2` (libgit2) 는 C dep 이라 안 쓰기로 — `gix` 가 pure rust. binary size 영향 측정 후 결정.
|
|
|
|
7. **k8s manifest 의 `kind` 인식 범위** — `Deployment` / `Service` / `ConfigMap` 등 표준 외 *CRD* (custom resource) 처리? Phase 2 의 task spec 에서 결정. 일단 *모든 yaml document 의 `kind` 필드 그대로* 사용 (CRD 포함 자동 처리).
|
|
|
|
---
|
|
|
|
## 12. 다음 단계
|
|
|
|
1. **이 spec 의 사용자 검토** — 빠진 결정 / 모순 / 추가 우려 확인.
|
|
2. 검토 통과 시 `tasks/p10/` 디렉토리 신설 + `tasks/p10/INDEX.md` 추가 + `tasks/INDEX.md` 에 phase 10 entry.
|
|
3. **Phase 1A-1 task spec 작성** (먼저) — `tasks/p10/p10-1a-1-code-ingest-framework.md`. `contract_sections` 로 `[§2.1, §2.2, §2.4, §2 schema.v1, §3.6, §8, §11]` (chunker 추가 없음 — §3.2 chunker_version 갱신은 1A-2 와 함께).
|
|
4. **Phase 1A-2 task spec 작성** (1A-1 머지 후) — `tasks/p10/p10-1a-2-rust-ast-chunker.md`. `contract_sections` 로 `[§2.1 (code variant 실 사용), §3.2 (code-rust-ast-v1 추가), §3.4 (Rust symbol path)]`.
|
|
5. Frozen design doc (2026-04-27) 갱신을 *Phase 1A-1 PR* 에 동봉 (이 spec 의 §10.1 표 그대로, 단 §3.2 chunker_version 부분은 1A-2 에서).
|
|
6. writing-plans skill 로 Phase 1A-1 의 구현 계획 (작업 단위) 작성.
|
|
7. Phase 1A-1 머지 후 regression test 통과 확인 → Phase 1A-2 구현 계획 작성 → 머지 → kebab 자기 자신 dogfooding → 측정 → 다음 phase 진행 결정.
|
|
|
|
---
|
|
|
|
## 부록 A — 의사 결정 회의록 (이 spec 작성 시 사용자와의 brainstorming 요약)
|
|
|
|
이 spec 작성에 들어간 결정들의 *왜* 를 짧게 (감사용 / 미래 재고 시 참조):
|
|
|
|
- **시나리오**: "한 부모 dir 아래 수십 개 repo + 의미 검색 + RAG" — kebab 의 cross-corpus 가치를 코드까지 확장.
|
|
- **chunking 전략**: 사용자가 길 B (AST per-language) 명시 선택. 작성자 추천은 길 C (A 로 시작 측정 후 승급) 였으나 사용자 결정 존중.
|
|
- **언어 범위**: 사용자 초기 답 (Rust/Python/TS-JS/Go-Java-Kotlin/C/C++/Shell/Dockerfile/yaml) 을 Tier 1/2/3 으로 재분류 → AST 가 의미 있는 곳에만 AST 적용. 작성자 push back 결과.
|
|
- **embedding 모델**: e5-large 유지. cross-corpus 가치 + cascade 비용 회피.
|
|
- **Citation variant**: 사용자가 `(a)-2` (새 `code` variant) 선택. 작성자 추천은 `(a)-1` (line variant 재사용) 였으나 의미 분리 명확함이 결정 요인.
|
|
- **built-in blacklist**: 사용자가 *축소* 요청 → 5 entry 최종. `.gitignore` 가 source of truth, built-in 은 safety net 만.
|
|
- **Phase 분할**: 사용자가 "되도록 많은 디테일 spec → Phase 1A 부터 구현" 명시. 이 spec 이 그 프레임워크 동결, phase 별 구현은 별도 task spec.
|