diff --git a/crates/kebab-cli/src/main.rs b/crates/kebab-cli/src/main.rs index a5f393f..362662d 100644 --- a/crates/kebab-cli/src/main.rs +++ b/crates/kebab-cli/src/main.rs @@ -326,7 +326,7 @@ fn run(cli: &Cli) -> anyhow::Result<()> { let cfg = kebab_config::Config::load(cli.config.as_deref())?; let scope = kebab_core::SourceScope { root: root.clone().unwrap_or_else(|| PathBuf::from(&cfg.workspace.root)), - include: cfg.workspace.include.clone(), + include: Vec::new(), exclude: cfg.workspace.exclude.clone(), }; diff --git a/crates/kebab-config/src/lib.rs b/crates/kebab-config/src/lib.rs index 27d0d44..65e76fb 100644 --- a/crates/kebab-config/src/lib.rs +++ b/crates/kebab-config/src/lib.rs @@ -51,7 +51,6 @@ pub struct Config { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct WorkspaceCfg { pub root: String, - pub include: Vec, pub exclude: Vec, } @@ -251,7 +250,6 @@ impl Config { schema_version: 1, workspace: WorkspaceCfg { root: "~/KnowledgeBase".to_string(), - include: vec!["**/*.md".to_string()], exclude: vec![ ".git/**".to_string(), "node_modules/**".to_string(), @@ -396,6 +394,29 @@ impl Config { /// than the user's `cwd`. pub fn from_file(path: &Path) -> anyhow::Result { let text = std::fs::read_to_string(path)?; + + // p9-fb-25: probe for the legacy `workspace.include` key — if + // present, emit a one-shot deprecation warning. Detection uses + // raw `toml::Value` lookup; the warning fires via a process- + // level OnceLock so a long-running TUI / CLI run doesn't spam + // the log on every Config::load. + if let Ok(value) = toml::from_str::(&text) { + if value + .get("workspace") + .and_then(|v| v.get("include")) + .is_some() + { + static DEPRECATION_FIRED: std::sync::OnceLock<()> = std::sync::OnceLock::new(); + DEPRECATION_FIRED.get_or_init(|| { + tracing::warn!( + target: "kebab-config", + config = %path.display(), + "deprecated config: `workspace.include` 필드는 더 이상 사용되지 않습니다 (p9-fb-25). 처리 가능한 형식 (md / png / jpg / pdf) 은 extractor 가 자동 결정. 다음 버전부터 config 갱신 권장." + ); + }); + } + } + let mut cfg: Self = toml::from_str(&text)?; cfg.source_dir = path.parent().map(Path::to_path_buf); Ok(cfg) @@ -868,6 +889,32 @@ max_context_tokens = 8000 assert_eq!(c.image, ImageCfg::defaults()); } + /// p9-fb-25: legacy config with `workspace.include = [...]` must + /// still deserialize cleanly (silent unknown-field acceptance). + #[test] + fn legacy_include_field_is_ignored_silently() { + let mut cfg = Config::defaults(); + cfg.workspace.root = "/tmp/kebab-legacy".to_string(); + let mut toml_text = toml::to_string(&cfg).expect("default round-trips"); + // Inject a legacy `include = [...]` line into the [workspace] block. + toml_text = toml_text.replace( + "[workspace]", + "[workspace]\ninclude = [\"**/*.md\", \"**/*.txt\"]", + ); + let parsed: Result = toml::from_str(&toml_text); + assert!(parsed.is_ok(), "legacy include must not break load: {:?}", parsed.err()); + let cfg = parsed.unwrap(); + assert_eq!(cfg.workspace.root, "/tmp/kebab-legacy"); + } + + /// p9-fb-25: `WorkspaceCfg` must NOT have an `include` field. + /// Compile-time proof: exhaustive destructure. + #[test] + fn workspace_cfg_has_only_root_and_exclude_fields() { + let ws = Config::defaults().workspace; + let WorkspaceCfg { root: _, exclude: _ } = &ws; + } + #[test] fn xdg_paths_honor_env() { // Must restore env after the test to avoid polluting other tests. diff --git a/crates/kebab-tui/src/ingest_progress.rs b/crates/kebab-tui/src/ingest_progress.rs index 1e22e0d..6978f90 100644 --- a/crates/kebab-tui/src/ingest_progress.rs +++ b/crates/kebab-tui/src/ingest_progress.rs @@ -36,7 +36,7 @@ pub fn start_ingest(app: &mut App) -> anyhow::Result<()> { let cfg = app.config.clone(); let scope = SourceScope { root: std::path::PathBuf::from(&cfg.workspace.root), - include: cfg.workspace.include.clone(), + include: Vec::new(), exclude: cfg.workspace.exclude.clone(), }; let (tx, rx) = mpsc::channel::();