diff --git a/README.md b/README.md index de5dabf..c19e8b7 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ kebab doctor 모든 명령에 `--json` 플래그. 출력은 frozen wire schema v1 (`schema_version` 항상 포함, 예: `ingest_report.v1`, `ingest_progress.v1`, `search_hit.v1`, `answer.v1`, `doctor.v1`, `reset_report.v1`, `schema.v1`). `--json` 모드에서 fatal error 는 stderr 에 `error.v1` ndjson 으로 emit (exit code 0/1/2/3 unchanged). +글로벌 플래그: `--readonly` (또는 `KEBAB_READONLY=1`) — 모든 write-path 명령 (`ingest` / `ingest-file` / `ingest-stdin` / `reset`) 을 비활성화, exit 1. `--quiet` — 진행 바 / hint 등 human-readable stderr 억제 (exit code / stdout 출력은 그대로). `KEBAB_PROGRESS=plain` — TTY 가 없는 환경에서도 진행 상황을 plain-text 한 줄씩 stderr 로 출력 (spinner 대신). + ## 논리 아키텍처 ```mermaid diff --git a/crates/kebab-cli/src/main.rs b/crates/kebab-cli/src/main.rs index f7ad9af..7832485 100644 --- a/crates/kebab-cli/src/main.rs +++ b/crates/kebab-cli/src/main.rs @@ -1,4 +1,4 @@ -//! `kb` — command-line interface. Each subcommand maps 1:1 to a `kb-app` +//! `kebab` — command-line interface. Each subcommand maps 1:1 to a `kebab-app` //! function. Exit codes per design §10. use std::path::PathBuf; @@ -659,7 +659,9 @@ fn run(cli: &Cli) -> anyhow::Result<()> { ); } if !confirm_destructive(scope, &paths, bytes)? { - eprintln!("aborted."); + if !cli.quiet { + eprintln!("aborted."); + } return Ok(()); } } diff --git a/crates/kebab-cli/tests/cli_readonly_quiet.rs b/crates/kebab-cli/tests/cli_readonly_quiet.rs index 2e5ac6d..26ab305 100644 --- a/crates/kebab-cli/tests/cli_readonly_quiet.rs +++ b/crates/kebab-cli/tests/cli_readonly_quiet.rs @@ -63,6 +63,22 @@ fn readonly_flag_blocks_ingest_file() { assert!(stderr.contains("readonly mode"), "stderr: {stderr}"); } +#[test] +fn readonly_flag_blocks_ingest_stdin() { + let (tmp, _ws) = fixture_workspace(); + let out = Command::new(kebab_bin()) + .args(["--readonly", "ingest-stdin", "--title", "test"]) + .env("KEBAB_READONLY", "1") + .envs(xdg_envs(tmp.path())) + .stdin(std::process::Stdio::null()) + .output() + .unwrap(); + + assert_eq!(out.status.code(), Some(1), "expected exit 1"); + let stderr = String::from_utf8_lossy(&out.stderr); + assert!(stderr.contains("readonly mode"), "stderr: {stderr}"); +} + #[test] fn readonly_flag_blocks_reset() { let (tmp, _ws) = fixture_workspace(); diff --git a/docs/SMOKE.md b/docs/SMOKE.md index 84af2bd..43bd6ef 100644 --- a/docs/SMOKE.md +++ b/docs/SMOKE.md @@ -114,7 +114,7 @@ max_context_tokens = 6000 theme = "dark" # p9-fb-14 — TUI palette ("dark" / "light", default "dark") ``` -`KEBAB_*` 환경변수로 override 가능 (`KEBAB_MODELS_LLM_MODEL=gemma4:26b kebab …` 등). 자세한 키 목록은 `crates/kebab-config/src/lib.rs` 의 `apply_env` 매치 암. +`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 대신). ## 명령 시퀀스