feat(dogfood): kebab reset --orphans-only — explicit complement to PR #148 sweep #149
Reference in New Issue
Block a user
Delete Branch "feat/dogfood-reset-orphans-only"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
요약
PR #148 (v0.10.0) 의 자연스러운 보완.
sweep_deleted_files는 보수적 (fs 에 살아있으면 scope 외라도 purge 안 함). 사용자가 명시적으로 reconcile 원할 때 호출하는 explicit complement 명령.사용 시나리오
config.workspace.include좁힘 또는exclude넓힘으로 일부 디렉토리가 walker scope 외가 된 경우동작
--orphans-onlyflag (--data-only/--vector-only/--config-only패턴 일관)scanned_pathsstored_paths = sqlite.all_workspace_paths()orphans = stored - scanned(fs existence 검사 안 함 — explicit reconcile)purge_deleted_workspace_path(PR #148 fn) + vector storedelete_by_chunk_ids[y/N]. non-TTY 는--yes필수.orphans purged: N또는no orphaned docs found.ResetReport additive
pub orphans_purged: u32pub purged_paths: Vec<WorkspacePath>(deterministic sorted)#[serde(default)](back-compat)안전 보장 (PR #148 invariants 유지)
purge_deleted_workspace_path의SELECT COUNT(*) FROM documents WHERE asset_id = ?가 1 이상이면 asset row 보존변경 (5 파일)
crates/kebab-app/src/reset.rs:ResetScope::OrphansOnly+enumerate_orphans+execute_orphans_only+ report 필드crates/kebab-app/src/lib.rs:enumerate_orphans재exportcrates/kebab-cli/src/main.rs:--orphans-onlyflag + confirm promptcrates/kebab-cli/src/wire.rs: test fixture 갱신 (orphans_purged: 0+purged_paths: [])crates/kebab-app/tests/reset_orphans.rs: 회귀 통합 테스트 1건회귀 테스트
reset_orphans_only_purges_out_of_scope_docs: ingest a/b/c.rs → narrow config to a.rs only →execute(OrphansOnly)→orphans_purged=2,purged_paths=[b.rs, c.rs], fs 에 file 여전히 존재 (purge 는 store 만), 2nd reset →orphans_purged=0(idempotent).검증
cargo test -p kebab-app --test reset_orphans→ 1/1cargo test -p kebab-app --test file_deletion_auto_purge→ 2/2 (regression 없음)cargo test -p kebab-app --test twin_files_idempotent→ 1/1 (regression 없음, twin-file 보호 유지)cargo test -p kebab-app --lib→ 52/52cargo test -p kebab-cli→ all pass-D warningsclean영향
회차 1 — 구현 품질은 탄탄하나 wire schema 문서 + README 동기화 2건 수정 필요.
전반 평가
PR #148 의 보수적
sweep_deleted_files와 의도적으로 짝을 이루는 '명시적 퍼지' 커맨드 — 설계 의도가 명확하고 기존purge_deleted_workspace_path를 그대로 재사용한 점이 깔끔합니다.#[serde(default)]로 back-compat read 를 지킨 것과,execute_orphans_only가enumerate_orphans를 한 번 더 호출해 fresh snapshot 을 사용하는 구조(race window 를 confirm 단계가 아닌 execute 단계의 snapshot 이 authoritative 하도록)도 올바릅니다.Blocking (수정 필요)
docs/wire-schema/v1/reset_report.schema.json미갱신 —scopeenum 에"orphans_only"없음,orphans_purged/purged_paths두 필드도 누락. 외부 통합(CLI skill, agent)이 schema validation 을 쓰면 즉시 파싱 오류.kebab reset --orphans-only가 표에 없음.Non-blocking (선택)
enumerate_orphans의stored.is_empty()조기 반환에 의도 설명 주석 한 줄 추가.>= 3조건에TestEnv가 고립된 TempDir 임을 명시하는 주석.테스트 결과
README 명령 테이블 미갱신: CLAUDE.md 의 docs-sync 규칙에 따라 새 CLI 플래그는 동일 PR 에서 README 를 갱신해야 합니다. line 81 의 reset 행에
--orphans-only설명이 없습니다.예시 추가 문안:
@@ -99,0 +135,4 @@/// Returns the list sorted for deterministic output. Called twice by the/// CLI path (once for the confirm UI preview, once inside `execute`);/// the double scan is acceptable for a rare destructive operation.pub fn enumerate_orphans(cfg: &Config) -> Result<Vec<WorkspacePath>> {0-orphan 조기 반환:
stored.is_empty()이면 바로Ok(vec![])를 반환하는데, walker 스캔도 생략됩니다. 정확하지만 주석이 없어 의도가 불분명합니다. 아래 한 줄 주석으로 독자에게 이유를 알려주는 게 좋겠습니다:어시션 여유 범위 의도 명확히:
first.new >= 3의 주석 설명이 있어 좋습니다. 그런데 fixture 워크스페이스에 얼마나 많은.rs파일이 있을 수 있는지 불확실해서, 나중에 test isolation 이 깨질 위험이 있습니다. 이미 쓰인TestEnv::lexical_only()이 격리된 TempDir 를 주는 경우라면 fixture leak이 없을 텐데, 혹시wide_scope가env.workspace_root가 아닌 더 넓은 경로를 스캔하지는 않는지 확인해두면 안심이 됩니다. 현재 코드(root: env.workspace_root.clone())는 OK로 보이지만, 주석에TestEnv가 고립된 TempDir 임을 명시하면 미래 독자가>= 3조건의 의도를 즉시 이해합니다.@@ -1115,0 +1131,4 @@if !*yes {use std::io::IsTerminal;if !std::io::stdin().is_terminal() {non-TTY + 0 orphan 시 불필요한 오류: 현재 흐름에서
--orphans-only+ non-TTY +--yes없음이면, orphan이 0개더라도bail!합니다. 오류 메시지가 "destructive"라고 표현하는데, orphan이 없을 때는 실제로 아무것도 삭제되지 않아 다소 과도합니다. 필수 수정 사항은 아니지만, 향후 스크립팅 UX를 위해orphan_paths.is_empty()면 bail 없이 no-op 리포트를 바로 내보내는 것도 고려해볼 수 있습니다. 현재 동작(항상 bail)도 data-safety 관점에서 명확하므로 tradeoff 판단은 작성자에게 위임합니다.scopeenum 누락 + 신규 필드 미등록:"orphans_only"값이scopeenum 에 없고,orphans_purged/purged_paths두 필드도 schema에 아예 없습니다. 외부 통합(Claude Code skill 등)이 이 schema를 validation에 쓸 경우--orphans-only결과를 파싱하면 바로 오류가 납니다.최소 수정:
#[serde(default)]덕분에 back-compat read는 이미 OK — schema만 따라잡으면 됩니다.회차 2 — APPROVE
라운드 1 에서 요청한 두 가지 수정 사항이
749c6ae에서 모두 정확히 반영되었습니다.검증 결과
docs/wire-schema/v1/reset_report.schema.jsonscopeenum 에"orphans_only"추가 ✓orphans_purged:integer, minimum 0, default 0—required배열 밖 (back-compat) ✓purged_paths:array of string, default []— 동일하게 optional ✓#[serde(rename_all = "snake_case")]로 RustOrphansOnly→"orphans_only\"직렬화와 완벽 일치 ✓README.md**\kebab reset --orphans-only`**` 추가, scope 설명("scope 밖 stored doc 정리, fs file 안 건드림") 명확 ✓추가 확인 (fresh pass)
wire_reset()는serde_json::to_value(r)패스스루 —#[serde(default)]필드가 자동으로 직렬화에 포함됨 ✓kebab resetsynopsis 가--orphans-only를 아직 포함하지 않지만, 이 부분은 라운드 1 지적 범위 밖이며 동일 줄의 prose 가 이미 새 플래그를 다루고 있어 blocking 사항 아님 (후속 dogfood PR 에서 정리 가능).README.md+reset_report.schema.json두 개뿐 — scope 이탈 없음 ✓라운드 1 지적 사항이 정확하게 해소되었습니다. APPROVE.
설명이 간결하고 핵심적 — "fs 의 file 은 건드리지 않음" 한 줄로
--orphans-only의 비파괴성을 명확히 전달."orphans_only"가 scope enum 에 정확히 추가됨.#[serde(rename_all = "snake_case")]로 RustOrphansOnly→"orphans_only"직렬화와 완벽 일치.