답변 출력 후 `근거:` 절 — `[N] <full path>#<fragment> (score=<s>)`
한 줄씩. spec p9-fb-20 의 핵심 (full path 가독성) 충족.
신규 flag:
- --show-citations: default ON. 답변 후 citation block 출력.
- --hide-citations: 답변 본문만 출력 (pipe 시 다른 도구가 trailing
metadata 안 받기 원할 때).
`--json` 모드 무영향 — citations 가 wire payload 에 항상 포함되므로
flag 가 영향 X (외부 wrapper 호환성).
spec p9-fb-20 의 \"TUI citation pane + jump (Enter/o editor jump,
i inspect)\" 부분은 본 PR scope 에서 제외 — TUI 의 기존
render_citations_or_explain (P9-3) 가 이미 citation list 표시,
추가 fold/jump 는 후속 task. 사용자 도그푸딩 priority 5위 의
핵심 = \"full path 가독성\" 이라 CLI block 만으로 충분.
Plan 갱신:
- p9-fb-20 status planned → in_progress. 머지 후 한 줄 commit
으로 completed flip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multi-turn ask pane. AskState 가 turns: Vec<Turn> + current_question
+ conversation_id + last_answer 로 재설계. answer area 가 transcript
형식 (Q1/A1, Q2/A2, ...) 로 갈음, 매 Enter 가 이전 turns 를 history
로 worker 에 전달 — RagPipeline::ask_with_history 호출.
신규 (kebab-tui::app):
- AskState 에 turns / current_question / conversation_id / last_answer
4 field 추가. 기존 answer field 제거 (last_answer 가 갈음).
신규 (kebab-tui::ask):
- spawn_ask_worker: 첫 submit 시 conversation_id 자동 생성
(conv_<unix_nanos_hex>), input → current_question, input clear.
history = turns.clone(), turn_index = turns.len(). worker 가
ask_with_history 호출 (kebab-app facade 가 _cancellable 통해
RagPipeline::ask_with_history 까지 thread).
- poll_worker: Answer 받으면 Turn { question: current_question,
answer, citations, created_at } 만들어 turns 에 push, last_answer
도 보존.
- handle_key_ask: Ctrl-L 가 turns + conversation_id 초기화 (in-flight
worker 는 그대로 finish — 결과는 새 conversation 의 stale turn 으로
silently 폐기, 사용자 의도와 일치).
- render_answer: 모든 completed turns + (있으면) in-flight turn
chronological 출력. Q/A 라벨 색상 구분 (Q cyan bold, A green bold).
in-flight answer 는 ▍ cursor + dim. transcript title 에 turn count.
- render_status / render_citations_or_explain: s.last_answer 사용.
Test:
- 17 PASS (3 신규: ctrl_l_clears_conversation_state /
render_transcript_shows_completed_turns_in_order /
render_streaming_inflight_turn_appears_below_completed_turns).
- 기존 14 회귀 0 (기존 s.answer → s.last_answer + Turn fixture
push).
README + HANDOFF: TUI 행에 multi-turn 동작 추가. spec status
planned → in_progress.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec PR #59 의 §3.8 multi-turn behaviour 구현. RAG facade 가 prior
turns 받아 prompt 에 prepend, retrieval query expansion 적용,
Answer 에 conversation_id / turn_index 채움.
신규 (kebab-core):
- Answer 에 conversation_id (Option<String>) / turn_index (Option<u32>)
field 추가. serde skip_serializing_if 로 single-shot 의 wire
output 변경 0 (기존 외부 wrapper 영향 없음).
- Turn struct (question + answer + citations + created_at).
- RefusalReason::LlmStreamAborted variant.
신규 (kebab-rag):
- AskOpts 에 history (Vec<Turn>) / conversation_id / turn_index 3 field.
- AskOpts::single_shot(mode) helper.
- RagPipeline::ask_with_history(query, history, conversation_id,
turn_index, opts) — combined opts 로 ask 호출.
- expand_query_with_history: history.last() 의 answer 첫 200 자
concat 해 SearchQuery.text 확장 (spec §3.8 의 \"cheap concat\";
LLM-based standalone-question rewriting 은 P+).
- serialize_history + remaining_history_budget_chars: spec 의 priority
enforcement — system+question 필수, retrieved chunks 가 차지한
뒤 남은 char budget 안에서 newest 우선, oldest drop.
- ask 본문: history 가 비어있지 않으면 [이전 대화] 블록을 user
prompt 위에 prepend. Answer 생성 site 3 곳 (정상 / NoChunks /
ScoreGate refuse) 모두 conversation_id / turn_index 채움.
신규 (kebab-store-sqlite):
- refusal_reason_label 가 LlmStreamAborted → 'llm_stream_aborted'.
기존 caller 변경 (single-shot 동작 동일):
- kebab-cli main.rs Cmd::Ask: AskOpts 에 history=Vec::new(),
conversation_id=None, turn_index=None 명시 (CLI multi-turn 은
p9-fb-18 의 --session/--repl 가 채움).
- kebab-tui src/ask.rs spawn site 동일 (multi-turn UI 는 p9-fb-16).
- kebab-eval runner.rs golden eval 동일 (single-shot per query).
- kebab-app tests/ask_smoke.rs / kebab-tui tests/ask.rs / kebab-rag
tests/pipeline.rs / kebab-eval metrics.rs Answer literal 갱신.
Test:
- 9 신규 lib unit (expand_query 4 / serialize_history 3 / remaining_budget 2).
- 기존 12 PASS 회귀 0.
Plan 갱신:
- p9-fb-15 status planned → in_progress. 머지 후 한 줄 commit
으로 completed flip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ctrl-C / Esc 가 ingest 를 즉시 중단. 현재 in-flight asset 마무리 후
이후 asset 미실행, IngestEvent::Aborted { partial_counts } 발신,
Ok(IngestReport) 정상 반환 (Err 아님). 부분 commit 보존, 다음 ingest
가 idempotent 재개.
신규 facade: kebab-app::ingest_with_config_cancellable(.., progress,
cancel: Option<Arc<AtomicBool>>). 기존 _progress 가 cancel=None
forwarding wrapper. asset loop 시작 boundary 마다 atomic load —
true 면 break + Aborted emit + 정상 종료. Lock 없음.
CLI: ctrlc crate 신규 dep. SIGINT handler 가 첫 신호에 cancel.store(true)
+ stderr hint, 두 번째 신호에 std::process::exit(130) (canonical SIGINT
exit code). install_sigint_cancel() helper 가 Arc<AtomicBool> 반환,
Cmd::Ingest 가 facade 에 전달.
TUI: IngestState 에 cancel: Arc<AtomicBool> field 추가 (회차 1 review
결과의 reshape 정확). start_ingest 가 둘 다 만들어 worker 에 clone
move. cancel_running_ingest(&app) helper — Esc / Ctrl-C 가
ingest 진행 중일 때만 cancel 우선, 그 외에는 quit.
Test:
- 3 facade integration (cancel-before / cancel-mid / no-cancel
default).
- 3 tui lib unit (cancel_running_ingest no-state / in-flight /
terminated).
Plan 갱신: p9-fb-04 status planned → in_progress. 머지 후 한 줄
commit 으로 completed flip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Library 의 `r` 키가 `kebab_app::ingest_with_config_progress` 를
spawned thread 에서 호출. run loop 가 매 frame 마다 progress channel
drain → 화면 하단 status bar 1 줄 갱신. blocking 하지 않음.
신규:
- crates/kebab-tui/src/app.rs: `IngestState` struct (rx + counts +
current_path + started_at + terminal_at + aborted + thread +
cancel_tx) + `App.ingest_state` slot + `TERMINAL_LINE_HOLD_SECS`.
- crates/kebab-tui/src/ingest_progress.rs: `start_ingest` (worker
spawn + channel allocation), `drain_progress` (try_recv loop),
`apply_event` (per-kind counter accumulation + Completed/Aborted
marking), `status_line` (사람-친화 텍스트), `ready_to_clear`
(3 초 hold).
- 키 cheatsheet: Library footer 에 `r=ingest` 추가.
Run loop:
- 매 tick `drain_progress` + `ready_to_clear` 체크 → terminal 후
3 초 경과 시 slot drop + worker 스레드 join + Library refresh
큐.
- Layout: ingest_state Some 일 때 footer 위에 status line 1 줄
추가 (있을 때만, 평시 영향 0).
- status line: scanning 중 / 진행 (idx/total %, current path,
elapsed) / 완료 (✓) / abort (✗) 4 모드.
Cancel wiring (p9-fb-04) 의 `IngestState.cancel_tx: Sender<()>`
slot 은 정의만 — 본 PR 에서 sender 보유, send 호출 X.
Test:
- 10 lib unit (apply_event 분기 5 / status_line 4 / ready_to_clear 2).
- 기존 15 tui test 회귀 0.
Plan 갱신:
- p9-fb-03: status `planned` → `in_progress`. 머지 후 한 줄
commit 으로 `completed` flip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`kebab ingest` 가 진행 상황을 사용자에게 보여주는 두 surface 추가:
- **사람 모드 (TTY)**: indicatif `ProgressBar` on stderr — scan 중에는
spinner, ScanCompleted 후 bar 로 전환, 매 asset 마다 message 갱신.
- **사람 모드 (non-TTY, CI/pipe)**: indicatif draw target 을 hidden
으로 두고 stderr 에 한 줄씩 (`ingest: scanning`, `ingest: 1/N path`,
`ingest: complete (...)`).
- **`--json` 모드**: stderr 비우고 stdout 에 line-delimited
`ingest_progress.v1` JSON 을 emit. 마지막 줄은 기존
`ingest_report.v1` 그대로 (외부 wrapper backward-compat).
구현:
- 신규 `crates/kebab-cli/src/progress.rs` — `ProgressMode::{Json,
Human { tty }}`, `ProgressDisplay` (background thread 가 channel
drain + 모드별 render), `now_rfc3339` helper. mode 가 무엇이든 ts
는 wire emit 시점에 stamp.
- `crates/kebab-cli/src/wire.rs` 에 `wire_ingest_progress` 추가.
serde tag (`kind`) 위에 `schema_version` + `ts` 두 필드 더해 spec
§2.4a wire shape 완성.
- `Cmd::Ingest` 핸들러: mpsc channel 만들고 background thread 가
display 돌리는 동안 main 이 `ingest_with_config_progress` 호출.
ingest 반환 시 Sender drop → display thread 정상 종료. join 후
최종 ingest_report 출력.
- 새 dep: `indicatif` 0.17 (TTY 전용 진행 바, non-TTY/--json 에서는
hidden draw target).
Test:
- 3 lib unit (mode resolution + RFC 3339 round-trip).
- 3 integration (--json line-delimited / non-TTY stderr text /
ts+kind 검증). 16 PASS 전체 회귀 0.
Plan 갱신:
- p9-fb-01: status `in_progress` → `completed` (PR #52 머지 후속).
- p9-fb-02: status `planned` → `in_progress`. 머지 후 별도 한 줄
commit 으로 `completed` flip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3-doc sync rule (CLAUDE.md): user-visible CLI surface change → README
and HANDOFF land in the same PR. ARCHITECTURE.md is not touched —
kebab reset doesn't move the crate graph or any locked-in technical
decision.
p9-fb-06 task 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
사용자 결정 (2026-05-02): \"README.md는 사용자가 가장 빠르게 이 앱을
사용할 수 있도록 하는 내용만 포함하자. mermaid 다이어그램으로 논리적인
아키텍처 다이어그램 하나 정도만 들어가면 충분할 것 같아\".
세 문서로 분리, audience 겹치지 않음:
1. **README.md (narrow)** — 사용자 first stop. Quick start / 명령 표 /
Mermaid 1개 (논리 아키텍처) / Configuration pointer / 비-목표 / 라이선스.
진척도 / crate 그래프 / 디렉토리 트리 / 핵심 결정 표 모두 빠짐.
2. **HANDOFF.md (신규)** — phase-level 진척 dashboard. Phase status table,
component count (33), \"다음 task 후보\" (P9-2/3/4/5, P8 보류), 머지 후
발견된 deviation 짧은 요약 (P3-5/P4-3 --config, P6-2 OCR, P6-3 caption,
P7-2 chunk_id, P7-3 storage UNIQUE, P9-1 ratatui generic). 본문 detail
은 tasks/HOTFIXES.md.
3. **docs/ARCHITECTURE.md (신규)** — crate 의존성 그래프, 디렉토리 트리,
핵심 기술 결정 표, 외부 AI 통합 절. README 의 Mermaid 가 여기로 링크.
CLAUDE.md 의 \"User-facing docs\" 절 갱신:
- 세 문서 audience 분리 명시.
- implementation PR 이 셋 다 sync 의무, spec PR 은 안 건드림.
- 갱신 trigger 별 (CLI / TUI / Configuration / phase epic / crate 추가 /
load-bearing deviation) 어느 문서를 손대는지 매핑.
- Out of scope (HOTFIXES detail / version cascade / per-task spec
rationale) 어디에도 안 적힘 명시.
CLAUDE.md `## Project` 절도 새 문서 layout 반영. 18 crates → ~20 crates.
Memory feedback 갱신 (`feedback_readme_sync_rule.md`) — 미래 conversation
에서 자동 적용.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>