From 4dcb4a45d6034395e159f68466a60604f00ef423 Mon Sep 17 00:00:00 2001 From: altair823 Date: Sun, 31 May 2026 11:41:32 +0000 Subject: [PATCH] =?UTF-8?q?feat(config):=20migrate=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=8A=A4=EC=BA=90=ED=8F=B4=EB=94=A9=20+=20toml=5Fedit=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- Cargo.lock | 1 + crates/kebab-config/Cargo.toml | 1 + crates/kebab-config/src/lib.rs | 1 + crates/kebab-config/src/migrate.rs | 47 ++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 crates/kebab-config/src/migrate.rs 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() + } +}