feat(fb-31): single-file / stdin ingest — agent on-demand 저장 #111
Reference in New Issue
Block a user
Delete Branch "feat/p9-fb-31-single-file-stdin-ingest"
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?
요약
kebab ingest-file <path>+kebab ingest-stdin --title <T>신규 subcommand 2개. agent (Claude Code via MCP) / 사람 사용자 모두 단일 외부 file 또는 stdin 본문을 KB 에 저장.ingest_file+ingest_stdin추가 (4 → 6). fb-30 v1 read-only 정책 변경 — 첫 mutation surface (의도된 진화).kebab ingest(전체 walk) 무영향.변경 사항
feat/p9-fb-31-single-file-stdin-ingest14 commits, 24 files (+1121/-13).새 모듈 / facade
crates/kebab-app/src/external.rs(신규) —ensure_external_dir,ensure_kebabignore_entry(idempotent + 누락 trailing newline 처리),copy_to_external(blake3 12-prefix 명명, idempotent),inject_frontmatter(YAML 더블쿼트 escape,---시작 시 error). 14 unit tests.kebab-app::ingest_file_with_config(cfg, &Path)(신규 facade) —_external/<hash12>.<ext>copy +.kebabignore자동 line append + 단일 asset 으로 기존ingest_with_config_opts재사용 (SourceScope include filter).kebab-app::ingest_stdin_with_config(cfg, body, title, source_uri)(신규 facade) —inject_frontmatter후ingest_file_with_config위임.#[doc(hidden)] pub fn(CLAUDE.md 의*_with_config컨벤션 준수).CLI 변경
Cmd::IngestFile { path: PathBuf }+ arm.Cmd::IngestStdin { title: String, source_uri: Option<String> }+ arm + stdin read.MCP 변경
crates/kebab-mcp/src/tools/ingest_file.rs+ingest_stdin.rs신규.build_tools_vec()4 → 6,call_tool두 신규 arm (spawn_tool helper 재사용).KebabAppState::config_path활용해 doctor tool 과 동일 path-aware 동작._external/policy<workspace.root>/_external/. 첫 ingest-file / ingest-stdin 시 자동 생성.<blake3-12>.<ext>deterministic — 동일 content 재 ingest 면 idempotent (incremental ingest 가 unchanged 처리)..kebabignore첫 생성 시_external/line 자동 append (walk re-ingestion 무한 루프 방지)..kebabignore매치 시 stderr warn 후 진행 (explicit ingest = bypass intent, 별--force-ignoreflag 불필요).Wire schema 영향
없음. 모두 기존 schema 재사용 (
ingest_report.v1/error.v1). source_uri 는 frontmatter →Document.metadatafree-form map 자동 흐름.테스트
cargo clippy --workspace --all-targets -- -D warningsclean.cargo test --workspace -j 1— fb-31 신규 테스트 모두 PASS. 2 reset.rs 실패는 pre-existing env-dependent (XDG_CONFIG_HOME) — fb-31 무관./tmp/kebab-fb31-final): ingest-file + ingest-stdin 모두 new=1,_external/2 files,.kebabignore_external/line, MCP tools/list 6 tools.Spec contract
_external/subdirectory 절 추가 (실제 §6.7 — 기존 §6 sub-section 이 6.6 까지 채워져 있어 §6.7 위치, spec stub 의 §6.3 명시는 deviation, HOTFIXES 명시).tasks/p9/p9-fb-31-single-file-stdin-ingest.mdstatusopen→completed.--mediaflag → 실제 v1 markdown only (deviation 없음 — spec 자체에 markdown 전용 명시).Release trigger (impl PR 머지 후)
별 PR — 0.3.1 → 0.3.2 patch (
chore/bump-v0.3.2branch + PR +gitea-release v0.3.2). fb-27 / fb-30 precedent 동일.surface 추가 (신규 subcommand + 신규 MCP tool) 지만 wire schema 변경 없고 backwards-compat 완전 — pre-1.0 patch policy 일관 (fb-30 도 0.3.1 patch 였음).
Final review fixes (commit
dc24cb3)ingest_stdin_with_config에#[doc(hidden)]추가 (CLAUDE.md 컨벤션).Since v0.4.0→Since v0.3.1(실제 MCP shipped in fb-30 release).tools_call_ingest_fileidempotency test 추가 (spec 의 2 test 요구 사항 충족).Single-file ingest entry. Copies bytes to _external/<hash12>.<ext> via crate::external::copy_to_external, runs the per-medium pipeline on that single asset (reuses ingest_with_config_opts via a SourceScope { root: _external/, include: [<filename>], exclude: config.workspace.exclude }). `.kebabignore` matches log a stderr warn line and proceed (explicit ingest is bypass intent). Internal helper `check_kebabignore_match` uses the `ignore` crate's GitignoreBuilder. Returns the standard IngestReport (incremental ingest from fb-23 handles re-ingest as `unchanged`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>회차 1 — fb-31 직조 단단. 14 commit 의 layered story (external module + facade + tests + CLI + MCP + docs) 자연. facade rule 준수, spawn_tool 재사용, _external/ 정책 (deterministic blake3 명명 + .kebabignore auto-add idempotent + 누락 trailing newline 처리) 명확. fb-30 mutation surface 도입을 첫 PR 로 매끄럽게.
5 nit/follow-up:
copy_to_external의 ext lowercase normalize — caller-side 가 깔끔.ingest_file_with_config의 unsupported extension early error — agent UX gap (현재 silent skip).ingest_stdin_with_config의 ensure 3-helper 중복 호출 — defer 또는 주석.inject_frontmatter의 두 검사 가독성 정리 — 단순화 옵션.inject_frontmatterdoc 의 yaml_quote 처리 명시 — clarity.테스트 커버리지 양호 (~22 신규), clippy clean, manual smoke 5 시나리오 정상. agent integration 다음 단계 (mutation tools) 으로 적합. 0.3.2 patch cut trigger 충족.
@@ -0,0 +64,4 @@) -> Result<PathBuf> {let hash = blake3::hash(bytes);let hex = hash.to_hex();let prefix = &hex.as_str()[..12];[normalize]
ext인자가 caller 의 path 에서 그대로 옴 —/tmp/file.PDF면 ext ="PDF"(대문자). 결과 filename<hash>.PDF. 기존 ingest pipeline 의 extension 검사가 lowercase 가정한다면 silently skip 가능.수정:
또는 caller (ingest_file_with_config) 에서
ext.to_lowercase()후 전달. 후자는ingest_stdin의"md"literal 도 영향 안 받음. caller-side 가 더 깔끔.@@ -0,0 +83,4 @@title: &str,source_uri: Option<&str>,) -> Result<String> {if body.trim_start().starts_with("---\n") || body.trim_start().starts_with("---\r\n") {[edge]
inject_frontmatter의trim_start().starts_with("---\n")+"---\r\n"두 검사 —trim_start()가 이미 모든 leading whitespace 제거하므로 첫 케이스 ("---\n") 와 두 번째 ("---\r\n") 모두 결과적으로"---"로 시작하면 매치.실제로 두 검사 OR 가 정확히 같은 set 잡지는 않음 —
"---a"같은 "3 hyphen + non-newline" 케이스는 둘 다 false. 의도된 동작? frontmatter 의 canonical form 은---\n(또는---\r\n) 라 OK. 단순화 가능:or
body.trim_start().lines().next() == Some("---")로 더 명확. 현 코드 functional 정확 — 가독성 nit only.@@ -0,0 +87,4 @@anyhow::bail!("stdin already has frontmatter; use `kebab ingest-file` for files with metadata");}[doc]
inject_frontmatterdoc 에 title YAML 처리 명시 권장 — agent 가title: "He said \"hi\""같은 이상한 입력 넣을 때 internal escape 가 무엇 하는지 명시. 한 줄:별 task 아님 — doc clarity nit.
[UX gap] ext 가 지원 안 되는 형식 (예
.docx/.txt/.epub) 일 때 silently 진행 → ingest pipeline 이IngestReport.skipped_by_extension: {"docx": 1}으로 카운트,new=0. agent 입장에서 "성공한 것 같은데 KB 에 안 들어감" 혼란.명시적 early error 권장:
또는 fail-soft: warn + return
IngestReportwith skipped_by_extension. 본 PR 머지 차단 아님 — HOTFIXES Known-limitation 한 줄 추가도 OK.[redundancy]
ingest_stdin_with_config가ensure_external_dir+ensure_kebabignore_entry+copy_to_external호출 후ingest_file_with_config위임 — 위임 함수도 같은 3 helper 호출. helper 3개 모두 idempotent 이라 functional 영향 없지만 stdin 호출 당 stat / file open / hash compute 가 두 번씩 발생.간결화: stdin 은 wrapped string 만 만들고 그대로 임시 path 에 dump → ingest_file 위임. ingest_file 이 한 번만 ensure/copy. 또는 현 형태 유지하고 doc-comment 에 "intentional double-call (idempotency safe, ~ms overhead)" 명시.
현 v1 scale 에서 의미 있는 perf 차이 아님 — defer / 주석 추가 권장.
회차 2 — 회차 1 의 5 nit 모두 정확 반영. 추가 actionable 없음.
확인:
ingest_file_with_config의 ext lowercase normalize (caller-side,let ext = ext_raw.to_lowercase()).SUPPORTED_EXTSearly bail (.docx등) + 신규 testingest_file_errors_on_unsupported_extension— agent UX gap 해소.ingest_stdin_with_config의 의도된 double-call 명시 doc comment 추가.inject_frontmatter의headsingle binding + CR-only 케이스 추가.inject_frontmatterdoc 의 yaml_quote escape contract 명시.테스트 모두 통과 (external 14 unit + ingest_file 4 integration). clippy --workspace clean. 머지 가능.
회차 3 — 후속 doc commit (
47bfd51) review.3 file 변경 (+493/-3):
docs/mcp-usage.md(~486 line) — agent integration 종합 가이드.내용 확인:
--configthread 예시.코드 변경 없음 (doc only). 회차 2 APPROVE 의 코드 검증 그대로 유효.
actionable 없음. 머지 가능.