diff --git a/HANDOFF.md b/HANDOFF.md index 563b320..f9cee6b 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -46,6 +46,7 @@ P0~P5 직렬. P6~P9 P5 이후 병렬 가능. - **2026-05-02 P9 도그푸딩 후속 (p9-fb-04)** — ingest cooperative cancellation. `kebab-app::ingest_with_config_cancellable(.., cancel: Option>)` facade 추가, 기존 `_progress` 가 `cancel=None` forwarding. asset loop iter 시작 boundary 마다 cancel poll → true 면 break + `IngestEvent::Aborted { partial_counts }` + `Ok(IngestReport)` 정상 반환 (Err 아님). 부분 commit 보존, 다음 ingest 가 idempotent 재개. CLI Ctrl-C SIGINT handler (`ctrlc` crate) — 1회: cancel, 2회: hard exit (130). TUI Esc / Ctrl-C 가 cancel signal (in-flight 시), 그 외에는 quit. `IngestState` 에 `cancel: Arc` field 추가. spec: `tasks/p9/p9-fb-04-ingest-cancellation.md`. - **2026-05-02 P9 도그푸딩 후속 (spec PR #59 + p9-fb-15)** — RAG multi-turn 도입. frozen design §3.8 갱신 — `Answer` 에 `conversation_id` / `turn_index` optional field, 신규 `Turn` struct, `RefusalReason::LlmStreamAborted` variant. `kebab-rag::AskOpts` 에 `history: Vec` / `conversation_id` / `turn_index` 3 field 추가, 기존 caller 는 `Vec::new() / None` (single-shot 동작 동일). `RagPipeline::ask_with_history(query, history, conversation_id, turn_index, opts)` helper. prompt 빌드: `[이전 대화]` 블록을 user prompt 위에 prepend, newest-first, char budget (`cfg.rag.max_context_tokens * 4`) 안에서 oldest 부터 drop. retrieval query expansion: 직전 answer 첫 200 자 concat. wire schema `answer.v1` 에 두 필드 + `format: date-time` 추가. p9-fb-16 (TUI conversation UI) + p9-fb-17/18 (V004 storage + CLI session) 가 같은 facade 위에 build. spec: `tasks/p9/p9-fb-15-rag-multi-turn-core.md`. - **2026-05-02 P9 도그푸딩 후속 (p9-fb-16)** — TUI Ask conversation UI. `AskState` 가 `turns: Vec` + `current_question` + `conversation_id` + `last_answer` 로 재설계. answer area 가 transcript (`Q1/A1`, `Q2/A2`, ...) 로 갈음, 매 Enter 가 이전 turns 를 `history` 로 worker 에 전달 (`ask_with_history`). conversation_id 는 첫 submit 시 timestamp-based 자동 생성 (`conv_`). `Ctrl-L` 가 turns + conversation_id 초기화 (in-flight worker 는 그대로 finish, 결과는 새 conversation 의 stale turn 으로 silently 폐기). spec: `tasks/p9/p9-fb-16-tui-ask-conversation.md`. +- **2026-05-03 P9 도그푸딩 후속 (p9-fb-20)** — `kebab ask` 의 CLI citation block. 답변 출력 후 `근거:` 절 — `[N] # (score=)` 한 줄씩. `--show-citations` (default ON) / `--hide-citations` (pipe 시 답변 본문만) flag. `--json` 모드는 무영향 (citations 가 항상 wire payload 에 포함). spec p9-fb-20 의 \"TUI citation pane + jump\" 부분은 P9-3 의 기존 `render_citations_or_explain` 가 일부 cover — 추가 기능 (turn 별 fold + Enter/o jump + i inspect) 은 후속 task 로 미룸 (사용자 도그푸딩 priority 5위 의 핵심 = full path 가독성 = CLI block 으로 충족). spec: `tasks/p9/p9-fb-20-citation-surface.md`. ## 다음 task 후보 diff --git a/README.md b/README.md index 80c09a7..249f917 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ kebab doctor | `kebab search --mode {lexical,vector,hybrid} ""` | 검색. hybrid는 RRF fusion, citation 포함 | | `kebab list docs` | 색인된 문서 목록 | | `kebab inspect doc ` / `kebab inspect chunk ` | raw record 보기 | -| `kebab ask ""` | RAG 답변 + 근거 인용. 근거 부족 시 거절. Ollama 필요 | +| `kebab ask "" [--show-citations / --hide-citations]` | RAG 답변 + 근거 인용. 답변 후 `근거:` block 으로 full path / line range / score 한 줄씩 (default ON — `--hide-citations` 로 끄기, pipe 시 유용). 근거 부족 시 거절. Ollama 필요 | | `kebab doctor` | 설정/모델/DB 헬스 체크 | | `kebab tui` | Ratatui 셸 (Library + Search + Ask + Inspect 패널, desktop 진행 중). Library 에서 `r` 키로 background ingest 시작 — 화면 하단 status bar 가 진행 표시, 완료/abort 시 final 라인 잠시 유지 후 자동 hide. ingest 진행 중 `Esc` / `Ctrl-C` 가 cancel signal (그 외에는 quit). Ask 패널은 multi-turn — 같은 conversation 안에서 Q1/A1, Q2/A2 transcript 누적, 다음 질문이 이전 턴을 history 로 받아 답변. `Ctrl-L` 로 새 conversation 시작 | | `kebab reset [--all / --data-only / --vector-only / --config-only] [--yes]` | XDG 데이터 wipe. **Irreversible.** TTY 면 confirm prompt, 아니면 `--yes` 필수. `--vector-only` 는 SQLite `embedding_records` 도 함께 truncate (orphan 방지) | diff --git a/crates/kebab-cli/src/main.rs b/crates/kebab-cli/src/main.rs index 3c2c60c..a8ca2b1 100644 --- a/crates/kebab-cli/src/main.rs +++ b/crates/kebab-cli/src/main.rs @@ -99,6 +99,21 @@ enum Cmd { #[arg(long)] seed: Option, + + /// p9-fb-20: print the `근거:` block (full path / line range + /// / score, one per line) after the answer. Default on. + /// `--json` mode is unaffected — citations are always + /// included in the wire payload regardless of this flag. + #[arg(long, action = clap::ArgAction::SetTrue, + conflicts_with = "hide_citations", + default_value_t = true)] + show_citations: bool, + + /// p9-fb-20: opt out of the `근거:` block (sticky-overrides + /// `--show-citations`). Useful when piping the answer body + /// to another tool that doesn't want trailing metadata. + #[arg(long)] + hide_citations: bool, }, /// Wipe XDG data dirs (and optionally the Lance vector store) so the @@ -418,6 +433,8 @@ fn run(cli: &Cli) -> anyhow::Result<()> { explain, temperature, seed, + show_citations, + hide_citations, } => { let cfg = kebab_config::Config::load(cli.config.as_deref())?; let opts = kebab_app::AskOpts { @@ -442,6 +459,34 @@ fn run(cli: &Cli) -> anyhow::Result<()> { println!("{}", serde_json::to_string(&wire::wire_answer(&ans))?); } else { println!("{}", ans.answer); + // p9-fb-20: print the citation block after the + // answer body when --hide-citations is not set + // (--show-citations is the default). Skipped on + // refusal-with-zero-citations to avoid an empty + // `근거:` header. + let print_citations = *show_citations && !*hide_citations; + if print_citations && !ans.citations.is_empty() { + println!(); + println!("근거:"); + for (idx, c) in ans.citations.iter().enumerate() { + let marker = c + .marker + .clone() + .unwrap_or_else(|| format!("{}", idx + 1)); + println!(" [{}] {}", marker, c.citation.to_uri()); + } + // p9-fb-20: retrieval 메타는 citation 별 점수가 + // AnswerCitation 에 없는 (`top_score` 만 retrieval- + // 전체 max) 한계상 한 줄로 분리. per-citation score + // 노출은 facade + AnswerCitation 의 미래 확장 후. + println!( + "(retrieval: top_score={:.2}, k={}, used={}/{})", + ans.retrieval.top_score, + ans.retrieval.k, + ans.retrieval.chunks_used, + ans.retrieval.chunks_returned, + ); + } } // Refusal → exit 1. if !ans.grounded { diff --git a/tasks/p9/p9-fb-20-citation-surface.md b/tasks/p9/p9-fb-20-citation-surface.md index 1212251..6c81e7e 100644 --- a/tasks/p9/p9-fb-20-citation-surface.md +++ b/tasks/p9/p9-fb-20-citation-surface.md @@ -3,7 +3,7 @@ phase: P9 component: kebab-cli + kebab-tui task_id: p9-fb-20 title: "Citation full path + scrollable pane (CLI block + TUI pane + jump)" -status: planned +status: in_progress depends_on: [p9-fb-09, p9-fb-16] unblocks: [] contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md