feat(kebab-config + kebab-app): p9-fb-05 workspace.root path policy
도그푸딩 item 3 — `workspace.root` 의 허용 형식이 명문화 안 돼 사용자가
\"상대 경로면 어디 기준?\" 가 불명확. 이제 절대/tilde/env/상대 모두
지원하되, 상대 경로의 base 는 **config.toml 자체가 위치한 디렉토리**
(사용자의 cwd 와 무관) 로 일관 정책.
## 핵심 변경
- **`kebab_config::expand_path_with_base(raw, data_dir, base_dir)`**
신규. 기존 `expand_path` (tilde + env 만) 위에 relative-path
resolution 추가:
- tilde / 절대 / `${VAR}` 입력은 base_dir 무시 (이미 absolute)
- relative 입력만 `base_dir.join(...)` 로 절대화
- **`Config.source_dir: Option<PathBuf>`** 신규 (`#[serde(skip)]`).
`Config::from_file` / `load` 가 `path.parent()` 로 stamp. defaults
는 None (cwd fallback).
- **`Config::resolve_workspace_root()`** helper: source_dir 있으면
그것 기준, 없으면 cwd 기준.
- **callsite 정리**:
- `kebab-app::lib.rs` 의 3 군데 `expand_tilde(&app.config.workspace
.root)` → `app.config.resolve_workspace_root()`
- `kebab-app::init_workspace` 도 동일
- `kebab-source-fs::FsSourceConnector::new` → 동일
- kebab-source-fs 의 fork 된 local `expand_tilde` + `dirs_home`
헬퍼 제거 (kebab-config 가 canonical)
- **`kebab init`** 가 생성하는 `config.toml` 위에 path policy 안내
헤더 코멘트 prepend (절대/tilde/env/상대 + 상대 base = config dir).
기존 `expand_tilde` 가 kebab-app/lib.rs 에 한 군데 (storage.data_dir)
남음 — spec out-of-scope (\"expand_tilde 통일 P+\") 라 보류.
## 테스트
- `expand_path_with_base` 에 신규 4 unit (relative→base, absolute
ignores base, tilde ignores base, ${XDG} ignores base)
- 기존 27 kebab-config tests + workspace 전체 (`cargo test --workspace
--no-fail-fast -j 1` exit 0) 모두 통과
- `cargo clippy --workspace --all-targets -- -D warnings` clean
## 문서
- README Configuration 절: workspace.root 형식 + relative base 규칙
한 줄 추가
- HANDOFF: 2026-05-03 entry
- spec status planned → in_progress
## 영향
기존 사용자: 영향 없음 (defaults 의 `~/KnowledgeBase` 는 tilde-rooted,
relative path 분기 안 탐). 새 사용자가 `--config /tmp/cfg.toml` +
`root = "kb"` 같이 쓰면 cwd 무관하게 `/tmp/kb` 가 워크스페이스가 됨 —
이전엔 이 케이스가 cwd 기준이라 invisible foot-gun.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -123,13 +123,36 @@ pub fn init_workspace(force: bool) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let workspace_root = expand_tilde(&kebab_config::Config::defaults().workspace.root);
|
||||
let workspace_root = kebab_config::Config::defaults().resolve_workspace_root();
|
||||
std::fs::create_dir_all(&workspace_root)?;
|
||||
|
||||
if !cfg_path.exists() || force {
|
||||
let cfg = kebab_config::Config::defaults();
|
||||
let toml_text = toml::to_string_pretty(&cfg)?;
|
||||
std::fs::write(&cfg_path, toml_text)?;
|
||||
// p9-fb-05: prepend a header comment documenting the path
|
||||
// policy so a user editing this file knows what's allowed
|
||||
// for `workspace.root` (and how relative paths resolve).
|
||||
// The actual key lives inside `[workspace]` further down;
|
||||
// we keep the explanation up top because users skim header
|
||||
// comments first.
|
||||
let header = "\
|
||||
# kebab config — `~/.config/kebab/config.toml`.
|
||||
#
|
||||
# `workspace.root` accepts:
|
||||
# • absolute paths (`/home/me/KnowledgeBase`)
|
||||
# • tilde (`~/KnowledgeBase`) ← default
|
||||
# • env vars (`${XDG_DATA_HOME}/kebab`)
|
||||
# • relative paths (`./notes`, `notes`, `../shared/x`)
|
||||
# — relative paths resolve against the directory of THIS
|
||||
# config file, NOT the user's `cwd` at invocation time.
|
||||
#
|
||||
# Override individual keys at runtime with `KEBAB_*` env vars
|
||||
# (e.g. `KEBAB_WORKSPACE_ROOT=/tmp/test kebab ingest`).
|
||||
\n";
|
||||
let mut combined = String::with_capacity(header.len() + toml_text.len());
|
||||
combined.push_str(header);
|
||||
combined.push_str(&toml_text);
|
||||
std::fs::write(&cfg_path, combined)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -881,7 +904,9 @@ fn ingest_one_image_asset(
|
||||
// `~` / `${XDG_…}` expansion via the same helper the markdown
|
||||
// path uses, so a `~/KnowledgeBase` workspace.root resolves
|
||||
// identically across all media (HOTFIXES 2026-05-02 P9-4 follow-up).
|
||||
let workspace_root = expand_tilde(&app.config.workspace.root);
|
||||
// p9-fb-05: relative `workspace.root` resolves against the config
|
||||
// file's directory (Config.source_dir), not the user's cwd.
|
||||
let workspace_root = app.config.resolve_workspace_root();
|
||||
let ctx = ExtractContext {
|
||||
asset,
|
||||
workspace_root: &workspace_root,
|
||||
@@ -1185,7 +1210,9 @@ fn ingest_one_pdf_asset(
|
||||
|
||||
let extract_config = kebab_core::ExtractConfig::default();
|
||||
// `~` / `${XDG_…}` expansion (HOTFIXES 2026-05-02 P9-4 follow-up).
|
||||
let workspace_root = expand_tilde(&app.config.workspace.root);
|
||||
// p9-fb-05: relative `workspace.root` resolves against the config
|
||||
// file's directory (Config.source_dir), not the user's cwd.
|
||||
let workspace_root = app.config.resolve_workspace_root();
|
||||
let ctx = ExtractContext {
|
||||
asset,
|
||||
workspace_root: &workspace_root,
|
||||
|
||||
Reference in New Issue
Block a user