fix(config): use XDG-standard paths on macOS to prevent DataOnly reset deleting config
dirs::config_dir() and dirs::data_dir() both return ~/Library/Application Support on macOS, so data_dir == config parent dir. ResetScope::DataOnly removes data_dir and silently deletes config.toml along with it. Fix: bypass dirs crate fallback for config/data/cache dirs; use $HOME/.config, $HOME/.local/share, $HOME/.cache directly (XDG standard). xdg_state_dir already used this pattern. dirs::home_dir() still used for portability. Migration: Config::load(None) auto-copies legacy ~/Library/Application Support/kebab/config.toml to the new ~/.config/kebab/ on first run and prints a migration notice to stderr.
This commit is contained in:
@@ -34,6 +34,7 @@ P0~P5 직렬. P6~P9 P5 이후 병렬 가능.
|
||||
- **2026-05-07 fb-26 (progress.rs)** — `Aborted` unconditional writeln (TTY duplicate) + `Completed` TTY no summary fixed; `KEBAB_PROGRESS=plain` env + quiet suppression added
|
||||
- **2026-05-07 fb-28 (main.rs)** — `--readonly` (KEBAB_READONLY) blocks Ingest/IngestFile/IngestStdin/Reset; `--quiet` suppresses progress stderr; error.v1 code: "readonly_mode"
|
||||
|
||||
- **2026-05-07 macOS XDG path collision (config 사라지는 버그)** — `dirs` crate 가 macOS 에서 `config_dir()` 과 `data_dir()` 둘 다 `~/Library/Application Support/` 반환 → `reset --data-only` 가 config 파일까지 삭제. Fix: `~/.config`, `~/.local/share`, `~/.cache` 직접 사용. 새 경로: config `~/.config/kebab/`, data `~/.local/share/kebab/`, cache `~/.cache/kebab/`. `Config::load(None)` 이 macOS legacy path 에서 자동 마이그레이션. 자세한 내용: `tasks/HOTFIXES.md`.
|
||||
- **2026-05-07 P9 post-도그푸딩 (p9-fb-31)** — `kebab ingest-file <path>` + `kebab ingest-stdin --title <T>` 두 신규 subcommand + MCP tool `ingest_file` / `ingest_stdin` (4 → 6 tool). agent 가 fetch 한 web markdown / 외부 file 을 KB 에 즉시 저장. workspace 외부 file 은 `<workspace.root>/_external/<blake3-12>.<ext>` 로 copy (deterministic 명명 → idempotent). `_external/` 디렉토리 첫 생성 시 `.kebabignore` 자동 append (walk 무한 루프 방지). stdin 은 markdown 전용 + flag (`--title`, `--source-uri`) → frontmatter 자동 prepend. .kebabignore 매치 시 stderr warn 후 진행 (explicit ingest = bypass intent). fb-30 의 v1 read-only MCP 정책 변경 — 첫 mutation tool 도입. spec: `tasks/p9/p9-fb-31-single-file-stdin-ingest.md`. design: `docs/superpowers/specs/2026-05-07-p9-fb-31-single-file-stdin-ingest-design.md`.
|
||||
- **2026-05-07 P9 post-도그푸딩 (p9-fb-30)** — `kebab mcp` 신규 subcommand + new crate `kebab-mcp` (lib only) — stdio JSON-RPC server. 4 read-only tool (`search` / `ask` / `schema` / `doctor`) 가 `kebab-app` facade 위에 build. rmcp 1.6 SDK 채택, manual `tools/list` + `tools/call` dispatch (rmcp 의 `#[tool_router]` 매크로 대신). `error_classify` 모듈을 `kebab-cli` → `kebab-app::error_wire` 로 promotion (UI crate 끼리 import 회피, facade 룰 준수). `ErrorV1` 에 `schema_version: String` 필드 추가 — kebab-mcp 의 직접 serialize 경로에서도 wire 정합. `KebabAppState` 가 `(Config, Option<PathBuf>)` carry — doctor tool 의 path-aware behavior 위해. ask + search arm 의 `tokio::task::spawn_blocking` wrap — `OllamaLanguageModel` 의 reqwest blocking client 가 async 안에서 panic 회피. capability flag `mcp_server` `false` → `true`. agent integration MVP 완성 — Claude Code / Cursor / OpenAI Agents 등 host-agnostic 사용 가능. spec: `tasks/p9/p9-fb-30-mcp-server.md`. design: `docs/superpowers/specs/2026-05-07-p9-fb-30-mcp-server-design.md`.
|
||||
- **P3-5 / P4-3 `--config` 누락** — `kebab-cli` 가 `--config <path>` 를 honor 하려면 `kebab_app::*_with_config` companion 을 호출해야 함. 두 번 같은 모양으로 회귀했음.
|
||||
|
||||
@@ -393,6 +393,25 @@ impl Config {
|
||||
if p.exists() {
|
||||
Self::from_file(&p)?
|
||||
} else {
|
||||
// macOS migration: if the new XDG path is absent but the
|
||||
// old ~/Library/Application Support/kebab/config.toml exists,
|
||||
// copy it to the new location so the user doesn't lose settings.
|
||||
if let Some(legacy) = Self::macos_legacy_config_path() {
|
||||
if legacy.exists() && !p.exists() {
|
||||
if let Some(parent) = p.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
if std::fs::copy(&legacy, &p).is_ok() {
|
||||
eprintln!(
|
||||
"kebab: migrated config {} → {}",
|
||||
legacy.display(),
|
||||
p.display()
|
||||
);
|
||||
return Self::from_file(&p)
|
||||
.map(|c| c.apply_env(&std::env::vars().collect()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::defaults()
|
||||
}
|
||||
}
|
||||
@@ -634,8 +653,11 @@ impl Config {
|
||||
return PathBuf::from(custom).join("kebab").join("config.toml");
|
||||
}
|
||||
}
|
||||
match dirs::config_dir() {
|
||||
Some(d) => d.join("kebab").join("config.toml"),
|
||||
// Always use XDG-standard ~/.config regardless of platform.
|
||||
// macOS dirs::config_dir() returns ~/Library/Application Support which
|
||||
// collides with data_dir() — DataOnly reset would delete config too.
|
||||
match dirs::home_dir() {
|
||||
Some(h) => h.join(".config").join("kebab").join("config.toml"),
|
||||
None => PathBuf::from("./kebab/config.toml"),
|
||||
}
|
||||
}
|
||||
@@ -647,8 +669,9 @@ impl Config {
|
||||
return PathBuf::from(custom).join("kebab");
|
||||
}
|
||||
}
|
||||
match dirs::data_dir() {
|
||||
Some(d) => d.join("kebab"),
|
||||
// Always use XDG-standard ~/.local/share regardless of platform.
|
||||
match dirs::home_dir() {
|
||||
Some(h) => h.join(".local").join("share").join("kebab"),
|
||||
None => PathBuf::from("./kebab-data"),
|
||||
}
|
||||
}
|
||||
@@ -660,8 +683,9 @@ impl Config {
|
||||
return PathBuf::from(custom).join("kebab");
|
||||
}
|
||||
}
|
||||
match dirs::cache_dir() {
|
||||
Some(d) => d.join("kebab"),
|
||||
// Always use XDG-standard ~/.cache regardless of platform.
|
||||
match dirs::home_dir() {
|
||||
Some(h) => h.join(".cache").join("kebab"),
|
||||
None => PathBuf::from("./kebab-cache"),
|
||||
}
|
||||
}
|
||||
@@ -680,6 +704,25 @@ impl Config {
|
||||
}
|
||||
PathBuf::from("./kebab-state")
|
||||
}
|
||||
|
||||
/// macOS legacy config path: `~/Library/Application Support/kebab/config.toml`.
|
||||
/// Returns `None` on non-macOS or when home dir is unavailable.
|
||||
/// Used for one-time migration to the XDG-standard location.
|
||||
fn macos_legacy_config_path() -> Option<PathBuf> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
dirs::home_dir().map(|h| {
|
||||
h.join("Library")
|
||||
.join("Application Support")
|
||||
.join("kebab")
|
||||
.join("config.toml")
|
||||
})
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a permissive boolean — `1` / `true` / `yes` (case-insensitive)
|
||||
|
||||
@@ -14,6 +14,20 @@ historical contract that was implemented; this file accumulates the
|
||||
deltas so phase 5+ readers can find the live behavior without diffing
|
||||
git history.
|
||||
|
||||
## 2026-05-07 (2)
|
||||
|
||||
### macOS XDG path collision: `data_dir` == `config_dir` → DataOnly reset deletes config
|
||||
|
||||
- **File**: `crates/kebab-config/src/lib.rs`
|
||||
- **Root cause**: `dirs` crate 가 macOS 에서 `config_dir()` 과 `data_dir()` 모두 `~/Library/Application Support/` 반환. `ResetScope::DataOnly` 가 `data_dir` 을 삭제하면 config 파일까지 함께 삭제됨.
|
||||
- **Fix**: `xdg_config_path`, `xdg_data_dir`, `xdg_cache_dir` 의 `dirs` fallback 제거 → `$HOME/.config`, `$HOME/.local/share`, `$HOME/.cache` 직접 사용 (XDG 표준, 플랫폼 무관).
|
||||
- **Migration**: `Config::load(None)` 에서 새 경로 없고 macOS legacy (`~/Library/Application Support/kebab/config.toml`) 있으면 자동 copy + stderr 안내.
|
||||
- **New paths** (macOS):
|
||||
- config: `~/.config/kebab/config.toml` (was `~/Library/Application Support/kebab/config.toml`)
|
||||
- data: `~/.local/share/kebab/` (was `~/Library/Application Support/kebab/`)
|
||||
- cache: `~/.cache/kebab/` (was `~/Library/Caches/kebab/`)
|
||||
- state: `~/.local/state/kebab/` (unchanged)
|
||||
|
||||
## 2026-05-07
|
||||
|
||||
### fb-26: ingest 로그 `Aborted` 무조건 writeln + `Completed` TTY 요약 없음
|
||||
|
||||
Reference in New Issue
Block a user