diff --git a/Cargo.lock b/Cargo.lock index a50a47e..52a0e3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4371,6 +4371,7 @@ dependencies = [ "tempfile", "thiserror 2.0.18", "toml", + "toml_edit 0.22.27", "tracing", ] diff --git a/crates/kebab-config/Cargo.toml b/crates/kebab-config/Cargo.toml index 914f576..b92b1f6 100644 --- a/crates/kebab-config/Cargo.toml +++ b/crates/kebab-config/Cargo.toml @@ -15,6 +15,7 @@ serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } toml = "0.8" +toml_edit = "0.22" dirs = "5" # p9-fb-05: warn-log when current_dir() fails (chroot, deleted cwd) # during workspace.root resolution. diff --git a/crates/kebab-config/src/lib.rs b/crates/kebab-config/src/lib.rs index 568ff45..aa69ef2 100644 --- a/crates/kebab-config/src/lib.rs +++ b/crates/kebab-config/src/lib.rs @@ -9,6 +9,7 @@ use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; mod paths; +pub mod migrate; pub use paths::{expand_path, expand_path_with_base}; /// Signal: `Config::from_file` / `Config::load` failed due to missing path, diff --git a/crates/kebab-config/src/migrate.rs b/crates/kebab-config/src/migrate.rs new file mode 100644 index 0000000..8985f06 --- /dev/null +++ b/crates/kebab-config/src/migrate.rs @@ -0,0 +1,47 @@ +//! config.toml 마이그레이션 엔진 (순수 변환, I/O 없음). +//! +//! 두 메커니즘: (1) reconciliation — default 구조에 있고 사용자 파일에 +//! 없는 섹션/키를 주석과 함께 추가. (2) step 체인 — schema_version 기반 +//! non-additive 변환(deprecated 제거 등). 자세한 계약은 spec +//! `docs/superpowers/specs/2026-05-31-config-migration-design.md`. + +use serde::Serialize; + +/// 현재 바이너리가 이해하는 config 스키마 버전. 마이그레이션 완료 시 +/// 사용자 파일의 `schema_version` 을 이 값으로 stamp 한다. +pub const CURRENT_SCHEMA_VERSION: u32 = 2; + +/// 한 번의 마이그레이션에서 발생한 개별 변경. +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct MigrationChange { + pub kind: ChangeKind, + /// dotted path, 예: `ingest.expansion`, `workspace.include`. + pub path: String, + /// 사람·wire 용 한 줄 설명. + pub detail: String, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ChangeKind { + AddedSection, + AddedKey, + RemovedDeprecated, +} + +/// 마이그레이션 결과 요약(순수 변환 단계 산출). I/O 계층이 backup_path +/// 등을 채워 wire 로 내보낸다. +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct MigrationOutcome { + pub from_schema_version: u32, + pub to_schema_version: u32, + pub changes: Vec, + /// 변환 후 직렬화된 새 문서 텍스트(멱등 시 입력과 동일). + pub new_text: String, +} + +impl MigrationOutcome { + pub fn changed(&self) -> bool { + !self.changes.is_empty() + } +}