From 7bbd2c0cbfcddbabb698de9154f62a08a26b059d Mon Sep 17 00:00:00 2001
From: th-kim0823
Date: Fri, 15 May 2026 17:41:26 +0900
Subject: [PATCH] docs(p10-1a-1): wire schema + frozen design +
README/HANDOFF/SMOKE + task index
Co-Authored-By: Claude Opus 4.7 (1M context)
---
HANDOFF.md | 1 +
README.md | 7 ++-
docs/SMOKE.md | 5 ++
.../2026-04-27-kebab-final-form-design.md | 47 +++++++++++++++++--
docs/wire-schema/v1/citation.schema.json | 3 +-
docs/wire-schema/v1/ingest_report.schema.json | 16 ++++++-
docs/wire-schema/v1/schema.schema.json | 10 ++++
docs/wire-schema/v1/search_hit.schema.json | 4 +-
tasks/INDEX.md | 10 ++++
tasks/p10/INDEX.md | 13 +++++
tasks/p10/p10-1a-1-code-ingest-framework.md | 31 ++++++++++++
11 files changed, 139 insertions(+), 8 deletions(-)
create mode 100644 tasks/p10/INDEX.md
create mode 100644 tasks/p10/p10-1a-1-code-ingest-framework.md
diff --git a/HANDOFF.md b/HANDOFF.md
index b799ad7..07048a8 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -20,6 +20,7 @@ P0–P5 + P6 + P7 + P9-1/2/3/4 (Library / Search / Ask / Inspect) 머지 완료.
| **P7** | PDF text + page citation | `kebab-parse-pdf` | P5 | ✅ 완료 (3/3 component, page-level chunker + ingest wiring) |
| **P8** | 음성 transcription + timestamp citation | `kebab-parse-audio` | P5 | ⏸ 보류 (whisper-rs 시스템 dep brainstorm 필요) |
| **P9** | TUI + desktop app | `kebab-tui`, `kebab-desktop` | P5 | 🟡 진행 (4/5 component — P9-1/2/3/4 완료 [Library / Search / Ask / Inspect], P9-5 desktop 예정 · 도그푸딩 피드백 **20/20 ✅**) |
+| **10** | code ingest framework | `kebab-parse-code` | P5 | 🟡 진행 중 (1A-1 머지 직전) — 1A-1 머지 시점 wire schema additive minor + 새 crate kebab-parse-code skeleton 동결, 실제 code chunker 는 1A-2 부터 |
P0~P5 직렬. P6~P9 P5 이후 병렬 가능.
diff --git a/README.md b/README.md
index e4b27f4..0b12a8f 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ kebab doctor
|------|------|
| `kebab init` | XDG 경로에 데이터 디렉토리 + config.toml 생성 |
| `kebab ingest []` | Markdown / 이미지 / PDF 색인 (idempotent). TTY 에서는 stderr 진행 바, non-TTY (CI / pipe) 는 stderr 한 줄씩, `--json` 은 stdout 에 `ingest_progress.v1` 라인 streaming 후 마지막에 `ingest_report.v1`. Ctrl-C 한 번이면 현재 asset 마무리 후 abort (부분 commit 보존, idempotent re-run), 두 번째 Ctrl-C 는 hard exit. Markdown title 이 frontmatter 에 없어도 첫 H1 → H2 → 첫 paragraph 80 자 → 파일명 순으로 자동 채움 (parser_version `md-frontmatter-v2`) — 기존 색인된 doc 도 다음 ingest 에서 새 title 로 갱신. **Incremental** (p9-fb-23): 두 번째 이후의 ingest 는 변하지 않은 doc (blake3 + parser/chunker/embedder version 모두 동일) 의 parse/chunk/embed/vector upsert 를 자동 스킵. final summary 에 `N unchanged` 카운트 표시. `--force-reingest` 로 skip 무시 강제 재처리. **지원 형식** (extractor 자동 결정 — config 에 명시 불가): Markdown (`.md`), 이미지 (`.png` / `.jpg` / `.jpeg`, OCR + caption), PDF (`.pdf`). 다른 확장자는 자동 skip — `IngestItem.warnings` 에 사유 (`"unsupported media type: .docx"` 등), `IngestReport.skipped_by_extension` 에 카운트 분류, CLI / TUI summary 에 breakdown 표시. |
-| `kebab search --mode {lexical,vector,hybrid} "" [--no-cache] [--max-tokens N] [--snippet-chars N] [--cursor ] [--tag T] [--lang L] [--path-glob G] [--trust-min LEVEL] [--media TYPE] [--ingested-after RFC3339] [--doc-id ID] [--trace] [--bulk]` | 검색. hybrid는 RRF fusion, citation 포함. 같은 process 안에서 동일 query (NFKC + trim + lowercase 정규화) 반복 시 in-process LRU 캐시 hit (capacity = `[search] cache_capacity`, default 256). `--no-cache` 로 강제 bypass — 디버깅용. ingest commit 발생 시 `kv['corpus_revision']` bump 으로 모든 entry 자동 stale. **`--max-tokens` / `--snippet-chars` / `--cursor` (p9-fb-34)** — agent budget controls. `--json` 출력은 `search_response.v1` wrapper (`{hits, next_cursor, truncated}`) — pre-fb-34 의 bare array 와 호환 안 됨. mismatched cursor → `error.v1.code = stale_cursor`. **filter flags (p9-fb-36):** `--tag` 는 반복 가능 flag (`--tag rust --tag async`) 로 OR 매칭, `--media` 는 `,` 구분 다중 값 OR 매칭, 나머지 flags 간은 AND 조합. `--trust-min` 은 `primary\|secondary\|generated` 중 하나 (해당 level 이상 포함). `--ingested-after` 는 RFC3339 UTC — 파싱 실패 시 `error.v1.code = config_invalid` (exit 2). `--media md` 는 `markdown` alias 로 정규화. 알 수 없는 `--media` 값은 무조건 empty hits (오류 아님). **`--trace` (p9-fb-37)** — `search_response.v1.trace` 에 lexical / vector pre-fusion 후보 + RRF union + per-stage timing (`lexical_ms` / `vector_ms` / `fusion_ms` / `total_ms`) 노출. trace 요청은 캐시 우회 (`--no-cache` 없이도 항상 cold). **`--bulk` (p9-fb-42)** — stdin ndjson 으로 N query 한 번에 실행. `--json` 면 stdout per-query ndjson (`bulk_search_item.v1`) + stderr summary (`bulk_summary: total=N succeeded=S failed=F`). Cap 100. agent 가 query decomposition 후 sub-query 일괄 실행 시 single round-trip — App instance 재사용으로 캐시 / embedder cold-start 비용 한 번만. Per-query failure 는 item 의 `error` (error.v1) 에 격리, 다른 query 계속 진행. |
+| `kebab search --mode {lexical,vector,hybrid} "" [--no-cache] [--max-tokens N] [--snippet-chars N] [--cursor ] [--tag T] [--lang L] [--path-glob G] [--trust-min LEVEL] [--media TYPE] [--ingested-after RFC3339] [--doc-id ID] [--trace] [--bulk] [--repo NAME ...] [--code-lang LIST] [--media code]` | 검색. hybrid는 RRF fusion, citation 포함. 같은 process 안에서 동일 query (NFKC + trim + lowercase 정규화) 반복 시 in-process LRU 캐시 hit (capacity = `[search] cache_capacity`, default 256). `--no-cache` 로 강제 bypass — 디버깅용. ingest commit 발생 시 `kv['corpus_revision']` bump 으로 모든 entry 자동 stale. **`--max-tokens` / `--snippet-chars` / `--cursor` (p9-fb-34)** — agent budget controls. `--json` 출력은 `search_response.v1` wrapper (`{hits, next_cursor, truncated}`) — pre-fb-34 의 bare array 와 호환 안 됨. mismatched cursor → `error.v1.code = stale_cursor`. **filter flags (p9-fb-36):** `--tag` 는 반복 가능 flag (`--tag rust --tag async`) 로 OR 매칭, `--media` 는 `,` 구분 다중 값 OR 매칭, 나머지 flags 간은 AND 조합. `--trust-min` 은 `primary\|secondary\|generated` 중 하나 (해당 level 이상 포함). `--ingested-after` 는 RFC3339 UTC — 파싱 실패 시 `error.v1.code = config_invalid` (exit 2). `--media md` 는 `markdown` alias 로 정규화. 알 수 없는 `--media` 값은 무조건 empty hits (오류 아님). **`--trace` (p9-fb-37)** — `search_response.v1.trace` 에 lexical / vector pre-fusion 후보 + RRF union + per-stage timing (`lexical_ms` / `vector_ms` / `fusion_ms` / `total_ms`) 노출. trace 요청은 캐시 우회 (`--no-cache` 없이도 항상 cold). **`--bulk` (p9-fb-42)** — stdin ndjson 으로 N query 한 번에 실행. `--json` 면 stdout per-query ndjson (`bulk_search_item.v1`) + stderr summary (`bulk_summary: total=N succeeded=S failed=F`). Cap 100. agent 가 query decomposition 후 sub-query 일괄 실행 시 single round-trip — App instance 재사용으로 캐시 / embedder cold-start 비용 한 번만. Per-query failure 는 item 의 `error` (error.v1) 에 격리, 다른 query 계속 진행. **code corpus filters (p10-1A-1):** `--repo` 는 반복 가능 (`--repo kebab --repo other`) OR 매칭. `--code-lang` 는 반복 또는 comma 다중 값 (`--code-lang rust,python`), 알 수 없는 값은 빈 hits. `--media code` 는 Tier 1/2/3 모든 code chunk 포함. 1A-1 시점에서는 indexed 된 code chunk 가 없어 filter 가 항상 빈 결과 — 1A-2 (Rust AST chunker) 머지 이후 실효. |
| `kebab list docs` | 색인된 문서 목록 |
| `kebab inspect doc ` / `kebab inspect chunk ` | raw record 보기 |
| `kebab fetch chunk [--context N]` / `kebab fetch doc [--max-tokens N]` / `kebab fetch span [--max-tokens N]` | (p9-fb-35) verbatim text fetch from indexed corpus. wire = `fetch_result.v1` (kind discriminator). chunk: target + ±N ordinal-context chunks. doc: full normalized markdown. span: 1-based line range (PDF/audio rejected as `error.v1.code = span_not_supported`). chars/4 budget on doc/span. |
@@ -184,6 +184,11 @@ flowchart TB
- `dimensions` (default `1024`) — 모델의 embedding 차원. config 와 LanceDB stored dim 불일치 시 검색 결과 0 건 (orphan table). 모델 변경 시 `kebab reset --vector-only && kebab ingest` 로 vector index 재구축 권장.
- `[ui] theme = "dark" | "light"` 로 TUI 팔레트 선택 (default `"dark"`, 알 수 없는 값은 dark fallback).
- `[search] stale_threshold_days = 30` (p9-fb-32) — search hit / RAG citation 의 `stale` 플래그 기준 (default 30 일, `0` 으로 비활성화). 옛 config 의 `workspace.include = [...]` 은 silently 무시 + 단발 deprecation warning (p9-fb-25).
+- `[ingest.code]` (p10-1A-1) — code ingest 의 skip 정책 + chunker 기본값.
+ - `skip_generated_header = true` — 첫 ~512 byte 의 generated marker (`@generated` / `DO NOT EDIT` 등) 감지 시 skip.
+ - `max_file_bytes = 262144` (256 KiB) / `max_file_lines = 5000` — 파일당 cap, 초과 시 skip.
+ - `extra_skip_globs = []` — 사용자 추가 skip 패턴 (`.gitignore` 문법).
+ - `.gitignore` honor: 자동 적용. `.kebabignore` 는 추가 layer. 우선순위: built-in safety net (`node_modules/` / `target/` / `__pycache__/` / `.venv/` / `venv/` / `env/`) > `.gitignore` > `.kebabignore`.
- `[rag] prompt_template_version` (default `"rag-v2"`) — RAG system prompt version. `"rag-v1"` 은 legacy backwards-compat (사용자 명시 시 유지). v2 강화 규칙: (1) fact 인용 시 [#번호] 앞에 chunk 속 원문 큰따옴표 표기, (2) 학습 지식 동원 금지, (3) 근거 모호 시 "확실하지 않다" 명시.
- `--config ` flag — 임시 워크스페이스 / 격리 테스트 시 사용. CLI / TUI 모두 honor.
- `KEBAB_*` env — 일부 키 override (`KEBAB_RAG_SCORE_GATE`, `KEBAB_EVAL_GOLDEN`, `KEBAB_COMMIT_HASH` 등).
diff --git a/docs/SMOKE.md b/docs/SMOKE.md
index ca6c7a7..e09dfb8 100644
--- a/docs/SMOKE.md
+++ b/docs/SMOKE.md
@@ -113,6 +113,11 @@ max_context_tokens = 6000
[ui]
theme = "dark" # p9-fb-14 — TUI palette ("dark" / "light", default "dark")
+
+[ingest.code]
+skip_generated_header = true
+max_file_bytes = 262144
+max_file_lines = 5000
```
`KEBAB_*` 환경변수로 override 가능 (`KEBAB_MODELS_LLM_MODEL=gemma4:26b kebab …` 등). 자세한 키 목록은 `crates/kebab-config/src/lib.rs` 의 `apply_env` 매치 암. `KEBAB_READONLY=1` — write-path 비활성화 (CI 안전망). `KEBAB_PROGRESS=plain` — non-TTY 환경에서 진행 상황을 plain 한 줄씩 stderr 출력 (spinner 대신).
diff --git a/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
index 666e79f..0ee1a41 100644
--- a/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
+++ b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
@@ -37,6 +37,7 @@ related_tasks: ../../../tasks/INDEX.md
| – | ignore | gitignore 문법 + `.kebabignore` | 익숙함 |
| – | 에러 | thiserror per crate, anyhow at boundary | 추적성 + UX |
| – | sync | watch=false default | v1 명시 ingest |
+| C+ | code ingest 추가 | Tier 1/2/3 fan-out, e5-large 유지, 새 Citation `code` variant | 2026-05-15 spec |
---
@@ -168,12 +169,12 @@ $ kebab search "Markdown chunking 규칙"
`docs/wire-schema/v1/*.schema.json` 으로 동결. internal Rust struct ↔ wire 변환은 `From`/`TryFrom`. 모든 wire 객체는 `schema_version` 필드 필수.
-### 2.1 Citation (5 variants — discriminated by `kind`)
+### 2.1 Citation (6 variants — discriminated by `kind`)
```json
{
"schema_version": "citation.v1",
- "kind": "line|page|region|caption|time",
+ "kind": "line|page|region|caption|time|code",
"path": "notes/rust/kebab.md",
"uri": "notes/rust/kebab.md#L12-L34",
@@ -181,7 +182,20 @@ $ kebab search "Markdown chunking 규칙"
"page": { "page": 13, "section": "Experiment Setup" },
"region": { "x": 120, "y": 40, "w": 520, "h": 180 },
"caption": { "model": "qwen2.5-vl:7b" },
- "time": { "start_ms": 822000, "end_ms": 850000, "speaker": "S1" }
+ "time": { "start_ms": 822000, "end_ms": 850000, "speaker": "S1" },
+ "code": { "start": 10, "end": 42, "lang": "rust", "repo": "kebab", "symbol": "fn ingest" }
+}
+```
+
+code variant example (p10-1A-1):
+
+```json
+{
+ "schema_version": "citation.v1",
+ "kind": "code",
+ "path": "crates/kebab-app/src/ingest.rs",
+ "uri": "crates/kebab-app/src/ingest.rs#L10-L42",
+ "code": { "start": 10, "end": 42, "lang": "rust", "repo": "kebab", "symbol": "fn ingest" }
}
```
@@ -213,7 +227,9 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는
},
"index_version": "v1.0",
"embedding_model": "multilingual-e5-large",
- "chunker_version": "md-heading-v1"
+ "chunker_version": "md-heading-v1",
+ "repo": null, // p10-1A-1: optional, omitted when null (code corpus only)
+ "code_lang": null // p10-1A-1: optional, omitted when null (code corpus only)
}
```
@@ -297,6 +313,17 @@ Per-query failure 는 `bulk_search_item.v1.error` (error.v1) 에 격리, 다른
"scope": { "root": "/home/altair/KnowledgeBase", "include": ["**/*.md"], "exclude": [".git/**"] },
"scanned": 142, "new": 12, "updated": 3, "skipped": 127, "errors": 0,
"duration_ms": 4231,
+ "skipped_gitignore": 40,
+ "skipped_kebabignore": 5,
+ "skipped_builtin_blacklist": 80,
+ "skipped_generated": 2,
+ "skipped_size_exceeded": 1,
+ "skip_examples": {
+ "generated": ["crates/kebab-app/src/generated.rs"],
+ "size_exceeded": ["crates/kebab-app/fixtures/huge.rs"],
+ "builtin_blacklist": ["target/release/kebab"],
+ "gitignore": ["node_modules/lodash/index.js"]
+ },
"items": [
{
"kind": "new|updated|skipped|error",
@@ -434,6 +461,8 @@ pub struct PromptTemplateVersion(pub String);
pub struct SchemaVersion(pub &'static str);
```
+Note: `chunker_version` family extended in phase 10 (per-language pattern, see 2026-05-15 spec §3.3 for canonical list). Each new language AST chunker registers its own `ChunkerVersion` label (e.g. `code-rust-ast-v1`, `code-python-ast-v1`). The existing `md-heading-v1` / `pdf-page-v1` labels are unaffected.
+
### 3.3 RawAsset
```rust
@@ -577,6 +606,11 @@ pub struct Metadata {
pub trust_level: TrustLevel,
pub user_id_alias: Option,
pub user: serde_json::Map,
+ // p10-1A-1: code corpus fields — None for non-code assets.
+ pub repo: Option, // git repo name (top-level dir or remote basename)
+ pub git_branch: Option, // HEAD branch name at ingest time
+ pub git_commit: Option, // HEAD commit SHA (short, 12 chars) at ingest time
+ pub code_lang: Option, // lowercase language name (e.g. "rust", "python")
}
pub enum SourceType { Markdown, Note, Paper, Reference, Inbox }
@@ -1370,8 +1404,11 @@ pub trait JobRepo {
kebab-cli, kebab-tui, kebab-desktop
└─> kebab-app
├─> kebab-source-fs
+ │ └─> kebab-parse-code (p10-1A-1: lang detect / repo detect / skip policy)
├─> kebab-parse-md / kebab-parse-pdf / kebab-parse-image / kebab-parse-audio
│ └─> kebab-parse-types (parser intermediate)
+ ├─> kebab-parse-code
+ │ └─> kebab-core (domain types only — NO store/embed/llm/rag/UI)
├─> kebab-normalize
│ └─> kebab-parse-types
├─> kebab-chunk
@@ -1555,6 +1592,8 @@ agent 가 분기). HTTP-SSE transport 는 fb-29 deferral 따라 P+. classify
- real-time collab
- enterprise auth
+코드 ingest 는 더 이상 비-스코프 아님 (2026-05-15 spec). 단 multi-workspace / watch mode / history aware (git blame 기반 citation, diff-aware re-chunking) 는 그대로 비-스코프.
+
---
## 12. 다음 단계
diff --git a/docs/wire-schema/v1/citation.schema.json b/docs/wire-schema/v1/citation.schema.json
index 30ef875..404e77e 100644
--- a/docs/wire-schema/v1/citation.schema.json
+++ b/docs/wire-schema/v1/citation.schema.json
@@ -7,7 +7,7 @@
"required": ["schema_version", "kind", "path", "uri", "indexed_at", "stale"],
"properties": {
"schema_version": { "const": "citation.v1" },
- "kind": { "enum": ["line", "page", "region", "caption", "time"] },
+ "kind": { "enum": ["line", "page", "region", "caption", "time", "code"] },
"path": { "type": "string" },
"uri": { "type": "string" },
"line": { "type": "object" },
@@ -15,6 +15,7 @@
"region": { "type": "object" },
"caption": { "type": "object" },
"time": { "type": "object" },
+ "code": { "type": "object" },
"indexed_at": { "type": "string", "format": "date-time" },
"stale": { "type": "boolean" }
}
diff --git a/docs/wire-schema/v1/ingest_report.schema.json b/docs/wire-schema/v1/ingest_report.schema.json
index aeb2e67..92ed1f1 100644
--- a/docs/wire-schema/v1/ingest_report.schema.json
+++ b/docs/wire-schema/v1/ingest_report.schema.json
@@ -38,6 +38,20 @@
},
"description": "p9-fb-25: per-extension skip count. Key = lowercase extension without leading dot (e.g. 'docx'). Files without extension key under ''."
},
- "items": { "type": ["array", "null"] }
+ "items": { "type": ["array", "null"] },
+ "skipped_gitignore": { "type": "integer", "minimum": 0 },
+ "skipped_kebabignore": { "type": "integer", "minimum": 0 },
+ "skipped_builtin_blacklist": { "type": "integer", "minimum": 0 },
+ "skipped_generated": { "type": "integer", "minimum": 0 },
+ "skipped_size_exceeded": { "type": "integer", "minimum": 0 },
+ "skip_examples": {
+ "type": "object",
+ "properties": {
+ "generated": { "type": "array", "items": { "type": "string" }, "maxItems": 5 },
+ "size_exceeded": { "type": "array", "items": { "type": "string" }, "maxItems": 5 },
+ "builtin_blacklist": { "type": "array", "items": { "type": "string" }, "maxItems": 5 },
+ "gitignore": { "type": "array", "items": { "type": "string" }, "maxItems": 5 }
+ }
+ }
}
}
diff --git a/docs/wire-schema/v1/schema.schema.json b/docs/wire-schema/v1/schema.schema.json
index 5b46e7e..6e610b1 100644
--- a/docs/wire-schema/v1/schema.schema.json
+++ b/docs/wire-schema/v1/schema.schema.json
@@ -78,6 +78,16 @@
"type": "integer",
"minimum": 0,
"description": "p9-fb-37: docs whose updated_at exceeds config.search.stale_threshold_days. 0 when threshold=0."
+ },
+ "code_lang_breakdown": {
+ "type": "object",
+ "description": "p10-1A-1: per-language code chunk count. Key = lowercase language name (e.g. 'rust', 'python'). Populated after 1A-2 lands; empty on markdown-only corpora.",
+ "additionalProperties": { "type": "integer", "minimum": 0 }
+ },
+ "repo_breakdown": {
+ "type": "object",
+ "description": "p10-1A-1: per-repo code chunk count. Key = repo name as detected by kebab-parse-code::repo. Empty on markdown-only corpora.",
+ "additionalProperties": { "type": "integer", "minimum": 0 }
}
}
}
diff --git a/docs/wire-schema/v1/search_hit.schema.json b/docs/wire-schema/v1/search_hit.schema.json
index 88256e1..db4fa97 100644
--- a/docs/wire-schema/v1/search_hit.schema.json
+++ b/docs/wire-schema/v1/search_hit.schema.json
@@ -42,6 +42,8 @@
"embedding_model": { "type": ["string", "null"] },
"chunker_version": { "type": "string" },
"indexed_at": { "type": "string", "format": "date-time" },
- "stale": { "type": "boolean" }
+ "stale": { "type": "boolean" },
+ "repo": { "type": ["string", "null"] },
+ "code_lang": { "type": ["string", "null"] }
}
}
diff --git a/tasks/INDEX.md b/tasks/INDEX.md
index 7a874b3..00969e0 100644
--- a/tasks/INDEX.md
+++ b/tasks/INDEX.md
@@ -35,6 +35,7 @@ P0~P5 는 직렬. P6~P9 는 P5 이후 병렬 가능.
| P7 | [phase-7-pdf.md](phase-7-pdf.md) | PDF text + page citation | kebab-parse-pdf | P5 |
| P8 | [phase-8-audio.md](phase-8-audio.md) | 음성 transcription + timestamp citation | kebab-parse-audio | P5 |
| P9 | [phase-9-ui.md](phase-9-ui.md) | TUI + desktop app | kebab-tui, kebab-desktop | P5 |
+| P10 | [p10/INDEX.md](p10/INDEX.md) | Code ingest framework + AST chunkers | kebab-parse-code, kebab-source-fs (code walk) | P5 |
## Component task decomposition (per phase)
@@ -137,6 +138,15 @@ P0~P5 는 직렬. P6~P9 는 P5 이후 병렬 가능.
- [p9-fb-41 multi-hop reasoning](p9/p9-fb-41-multi-hop-reasoning.md) — ⏳ 미구현, brainstorm 필요 (XL, eval 인프라 선행)
- [p9-fb-42 bulk multi-query + re-rank hint](p9/p9-fb-42-bulk-multi-query-rerank.md) — ✅ 머지 (2026-05-10) — bulk only, rerank hint deferred
+- P10 — [p10/](p10/) — code ingest (multi-task, sub-indexed in [p10/INDEX.md](p10/INDEX.md))
+ - [p10-1A-1 code ingest framework](p10/p10-1a-1-code-ingest-framework.md) — 🟡 진행 중
+ - p10-1A-2 Rust AST chunker — ⏳
+ - p10-1B Python + TS/JS AST chunkers — ⏳
+ - p10-1C Go + Java + Kotlin AST chunkers — ⏳
+ - p10-1D C + C++ AST chunkers — ⏳
+ - p10-2 Tier 2 resource-aware — ⏳
+ - p10-3 Tier 3 paragraph + line-window fallback — ⏳
+
## Post-merge 핫픽스
머지 후 발견된 버그들과 그 follow-up PR들은 [HOTFIXES.md](HOTFIXES.md)에 dated 로그로 기록한다. 원래 task spec은 frozen 상태로 두고, post-merge 동작 변경은 HOTFIXES.md를 source of truth로 본다.
diff --git a/tasks/p10/INDEX.md b/tasks/p10/INDEX.md
new file mode 100644
index 0000000..8d0017a
--- /dev/null
+++ b/tasks/p10/INDEX.md
@@ -0,0 +1,13 @@
+# Phase 10 — Code Ingest
+
+| ID | Subject | Status |
+|----|---------|--------|
+| 1A-1 | code ingest framework (wire schema, parse-code crate skeleton, filter flags, skip policy, config 절) | 🟡 진행 중 |
+| 1A-2 | Rust AST chunker | ⏳ |
+| 1B | Python + TS/JS AST chunkers | ⏳ |
+| 1C | Go + Java + Kotlin AST chunkers | ⏳ |
+| 1D | C + C++ AST chunkers | ⏳ |
+| 2 | Tier 2 resource-aware (k8s / Dockerfile / manifest) | ⏳ |
+| 3 | Tier 3 paragraph + line-window fallback | ⏳ |
+
+Design: [2026-05-15-kebab-code-ingest-design.md](../../docs/superpowers/specs/2026-05-15-kebab-code-ingest-design.md)
diff --git a/tasks/p10/p10-1a-1-code-ingest-framework.md b/tasks/p10/p10-1a-1-code-ingest-framework.md
new file mode 100644
index 0000000..188c9e0
--- /dev/null
+++ b/tasks/p10/p10-1a-1-code-ingest-framework.md
@@ -0,0 +1,31 @@
+# p10-1A-1 — code ingest framework
+
+**Status:** 🟡 진행 중
+**Contract sections:** §2.1 (Citation `code` variant), §2.2 (SearchHit repo/code_lang), §2.4 (IngestReport skip counters), §2 schema.v1 (code_lang_breakdown + repo_breakdown), §3.6 (Metadata fields), §8 (kebab-parse-code crate boundary), §11 (code ingest no longer 비-스코프).
+**Design:** [2026-05-15-kebab-code-ingest-design.md](../../docs/superpowers/specs/2026-05-15-kebab-code-ingest-design.md) §1A-1.
+**Plan:** [2026-05-15-p10-1a-1-code-ingest-framework.md](../../docs/superpowers/plans/2026-05-15-p10-1a-1-code-ingest-framework.md).
+
+## Goal
+
+Land the *framework surface* for code ingest — wire schema (additive minor), CLI filter flags, ignore policy, skip policy infrastructure, `kebab-parse-code` crate skeleton, `[ingest.code]` config section — without enabling any code chunker. 1A-2 plugs in the Rust AST chunker on top.
+
+## Acceptance criteria
+
+- `cargo test --workspace --no-fail-fast -j 1` passes.
+- Regression test (`wire_search_hit_no_code_fields`, `wire_citation_5_variants_unchanged`) passes — markdown corpus wire output unchanged.
+- `cargo clippy --workspace --all-targets -- -D warnings` passes.
+- `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` updated per design §10.1.
+- README + HANDOFF + SMOKE updated.
+
+## Allowed dependencies
+
+- `kebab-parse-code` may depend on `kebab-core`, `anyhow`, `gix`. NOT on store / embed / llm / rag / UI.
+- Source-fs may depend on `kebab-parse-code`.
+
+## Forbidden dependencies
+
+- UI crates (cli / mcp / tui) must NOT import `kebab-parse-code` directly.
+
+## Risks / notes
+
+- `.gitignore` honor changes existing behavior for markdown corpora whose files live in gitignored areas. Regression test covers the standard case (no overlap). If a user reports missing docs after 1A-1 lands, log to HOTFIXES.