feat(dogfood): kebab reset --orphans-only — explicit complement to PR #148 sweep #149

Merged
altair823 merged 2 commits from feat/dogfood-reset-orphans-only into main 2026-05-20 07:50:45 +00:00
Owner

요약

PR #148 (v0.10.0) 의 자연스러운 보완. sweep_deleted_files 는 보수적 (fs 에 살아있으면 scope 외라도 purge 안 함). 사용자가 명시적으로 reconcile 원할 때 호출하는 explicit complement 명령.

사용 시나리오

  • config.workspace.include 좁힘 또는 exclude 넓힘으로 일부 디렉토리가 walker scope 외가 된 경우
  • 워크스페이스에서 서브-디렉토리를 옮기거나 별도 KB 로 분리한 후 cleanup

동작

  • --orphans-only flag (--data-only / --vector-only / --config-only 패턴 일관)
  • 동작:
    1. 현재 config 로 walker scan → scanned_paths
    2. stored_paths = sqlite.all_workspace_paths()
    3. orphans = stored - scanned (fs existence 검사 안 함 — explicit reconcile)
    4. 각 orphan path 에 대해 purge_deleted_workspace_path (PR #148 fn) + vector store delete_by_chunk_ids
  • Confirm UI (TTY): orphan count + sample 5 paths + [y/N]. non-TTY 는 --yes 필수.
  • Human summary: orphans purged: N 또는 no orphaned docs found.

ResetReport additive

  • pub orphans_purged: u32
  • pub purged_paths: Vec<WorkspacePath> (deterministic sorted)
  • 둘 다 #[serde(default)] (back-compat)

안전 보장 (PR #148 invariants 유지)

  • Twin-file 보호: purge_deleted_workspace_pathSELECT COUNT(*) FROM documents WHERE asset_id = ? 가 1 이상이면 asset row 보존
  • Vector store None (lexical-only) 케이스: vector delete 단계 skip
  • fs existence 검사 없음 = explicit "I know what I'm doing" 명령 의도

변경 (5 파일)

  • crates/kebab-app/src/reset.rs: ResetScope::OrphansOnly + enumerate_orphans + execute_orphans_only + report 필드
  • crates/kebab-app/src/lib.rs: enumerate_orphans 재export
  • crates/kebab-cli/src/main.rs: --orphans-only flag + confirm prompt
  • crates/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/1
  • cargo 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/52
  • cargo test -p kebab-cli → all pass
  • clippy -D warnings clean

영향

  • wire schema additive (back-compat).
  • frozen design 변경 없음.
  • 새 CLI flag 추가 → 사용자-가시 surface 확장.
## 요약 PR #148 (v0.10.0) 의 자연스러운 보완. `sweep_deleted_files` 는 보수적 (fs 에 살아있으면 scope 외라도 purge 안 함). 사용자가 명시적으로 reconcile 원할 때 호출하는 **explicit complement** 명령. ## 사용 시나리오 - `config.workspace.include` 좁힘 또는 `exclude` 넓힘으로 일부 디렉토리가 walker scope 외가 된 경우 - 워크스페이스에서 서브-디렉토리를 옮기거나 별도 KB 로 분리한 후 cleanup ## 동작 - `--orphans-only` flag (`--data-only` / `--vector-only` / `--config-only` 패턴 일관) - 동작: 1. 현재 config 로 walker scan → `scanned_paths` 2. `stored_paths = sqlite.all_workspace_paths()` 3. `orphans = stored - scanned` (**fs existence 검사 안 함** — explicit reconcile) 4. 각 orphan path 에 대해 `purge_deleted_workspace_path` (PR #148 fn) + vector store `delete_by_chunk_ids` - Confirm UI (TTY): orphan count + sample 5 paths + `[y/N]`. non-TTY 는 `--yes` 필수. - Human summary: `orphans purged: N` 또는 `no orphaned docs found`. ## ResetReport additive - `pub orphans_purged: u32` - `pub purged_paths: Vec<WorkspacePath>` (deterministic sorted) - 둘 다 `#[serde(default)]` (back-compat) ## 안전 보장 (PR #148 invariants 유지) - Twin-file 보호: `purge_deleted_workspace_path` 의 `SELECT COUNT(*) FROM documents WHERE asset_id = ?` 가 1 이상이면 asset row 보존 - Vector store None (lexical-only) 케이스: vector delete 단계 skip - fs existence 검사 없음 = explicit "I know what I'm doing" 명령 의도 ## 변경 (5 파일) - `crates/kebab-app/src/reset.rs`: `ResetScope::OrphansOnly` + `enumerate_orphans` + `execute_orphans_only` + report 필드 - `crates/kebab-app/src/lib.rs`: `enumerate_orphans` 재export - `crates/kebab-cli/src/main.rs`: `--orphans-only` flag + confirm prompt - `crates/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/1 - `cargo 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/52 - `cargo test -p kebab-cli` → all pass - clippy `-D warnings` clean ## 영향 - wire schema additive (back-compat). - frozen design 변경 없음. - 새 CLI flag 추가 → 사용자-가시 surface 확장.
altair823 added 1 commit 2026-05-20 07:39:10 +00:00
PR #148 auto-purges only filesystem-missing files (conservative — leaves
on-disk-but-out-of-scope docs alone for data safety). This is the explicit
complement: when the user has narrowed include / widened exclude / removed
a sub-directory from the workspace and WANTS the stored docs reconciled,
they invoke 'kebab reset --orphans-only'.

Confirm prompt with orphan count + sample paths; --yes required in
non-TTY. SQLite purge via existing purge_deleted_workspace_path (PR #148)
+ vector store delete_by_chunk_ids when configured. No fs existence
check — orphans-only is the explicit 'I know what I'm doing' variant.

dogfood follow-up to PR #148 (file deletion auto-purge).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 requested changes 2026-05-20 07:45:55 +00:00
claude-reviewer-01 left a comment
Member

회차 1 — 구현 품질은 탄탄하나 wire schema 문서 + README 동기화 2건 수정 필요.

전반 평가

PR #148 의 보수적 sweep_deleted_files 와 의도적으로 짝을 이루는 '명시적 퍼지' 커맨드 — 설계 의도가 명확하고 기존 purge_deleted_workspace_path 를 그대로 재사용한 점이 깔끔합니다. #[serde(default)] 로 back-compat read 를 지킨 것과, execute_orphans_onlyenumerate_orphans 를 한 번 더 호출해 fresh snapshot 을 사용하는 구조(race window 를 confirm 단계가 아닌 execute 단계의 snapshot 이 authoritative 하도록)도 올바릅니다.

Blocking (수정 필요)

  1. docs/wire-schema/v1/reset_report.schema.json 미갱신scope enum 에 "orphans_only" 없음, orphans_purged / purged_paths 두 필드도 누락. 외부 통합(CLI skill, agent)이 schema validation 을 쓰면 즉시 파싱 오류.
  2. README 명령 테이블 미갱신 — CLAUDE.md docs-sync 규칙: 새 CLI 플래그는 동일 PR 에서 README 를 함께 갱신. kebab reset --orphans-only 가 표에 없음.

Non-blocking (선택)

  • enumerate_orphansstored.is_empty() 조기 반환에 의도 설명 주석 한 줄 추가.
  • non-TTY + 0 orphan 시 bail 동작 UX 개선 여지 (data-safety 우선이면 현행 유지도 OK).
  • 테스트의 >= 3 조건에 TestEnv 가 고립된 TempDir 임을 명시하는 주석.

테스트 결과

cargo test -p kebab-app --test reset_orphans          → 1/1 PASS
cargo test -p kebab-app --test file_deletion_auto_purge → 2/2 PASS (PR #148 regression)
cargo test -p kebab-app --test twin_files_idempotent    → 1/1 PASS (PR #146 regression)
cargo test -p kebab-app --lib                           → 52/52 PASS
cargo test -p kebab-cli                                 → 3/3 PASS
cargo clippy -p kebab-core -p kebab-store-sqlite -p kebab-app -p kebab-cli --all-targets -- -D warnings → clean
회차 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 (수정 필요) 1. **`docs/wire-schema/v1/reset_report.schema.json` 미갱신** — `scope` enum 에 `"orphans_only"` 없음, `orphans_purged` / `purged_paths` 두 필드도 누락. 외부 통합(CLI skill, agent)이 schema validation 을 쓰면 즉시 파싱 오류. 2. **README 명령 테이블 미갱신** — CLAUDE.md docs-sync 규칙: 새 CLI 플래그는 동일 PR 에서 README 를 함께 갱신. `kebab reset --orphans-only` 가 표에 없음. ## Non-blocking (선택) - `enumerate_orphans` 의 `stored.is_empty()` 조기 반환에 의도 설명 주석 한 줄 추가. - non-TTY + 0 orphan 시 bail 동작 UX 개선 여지 (data-safety 우선이면 현행 유지도 OK). - 테스트의 `>= 3` 조건에 `TestEnv` 가 고립된 TempDir 임을 명시하는 주석. ## 테스트 결과 ``` cargo test -p kebab-app --test reset_orphans → 1/1 PASS cargo test -p kebab-app --test file_deletion_auto_purge → 2/2 PASS (PR #148 regression) cargo test -p kebab-app --test twin_files_idempotent → 1/1 PASS (PR #146 regression) cargo test -p kebab-app --lib → 52/52 PASS cargo test -p kebab-cli → 3/3 PASS cargo clippy -p kebab-core -p kebab-store-sqlite -p kebab-app -p kebab-cli --all-targets -- -D warnings → clean ```

README 명령 테이블 미갱신: CLAUDE.md 의 docs-sync 규칙에 따라 새 CLI 플래그는 동일 PR 에서 README 를 갱신해야 합니다. line 81 의 reset 행에 --orphans-only 설명이 없습니다.

예시 추가 문안:

--orphans-only — 현재 walker scope 에 없는 저장 문서만 store 에서 퍼지. 파일 시스템 수정 없음. TTY 면 count + sample 5개 confirm, 비-TTY 면 --yes 필수.

**README 명령 테이블 미갱신**: CLAUDE.md 의 docs-sync 규칙에 따라 새 CLI 플래그는 동일 PR 에서 README 를 갱신해야 합니다. line 81 의 reset 행에 `--orphans-only` 설명이 없습니다. 예시 추가 문안: > `--orphans-only` — 현재 walker scope 에 없는 저장 문서만 store 에서 퍼지. 파일 시스템 수정 없음. TTY 면 count + sample 5개 confirm, 비-TTY 면 `--yes` 필수.
@@ -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 스캔도 생략됩니다. 정확하지만 주석이 없어 의도가 불분명합니다. 아래 한 줄 주석으로 독자에게 이유를 알려주는 게 좋겠습니다:

// No stored docs → nothing can be an orphan; skip the walker scan.
**0-orphan 조기 반환**: `stored.is_empty()` 이면 바로 `Ok(vec![])` 를 반환하는데, walker 스캔도 생략됩니다. 정확하지만 주석이 없어 의도가 불분명합니다. 아래 한 줄 주석으로 독자에게 이유를 알려주는 게 좋겠습니다: ```rust // No stored docs → nothing can be an orphan; skip the walker scan. ```

어시션 여유 범위 의도 명확히: first.new >= 3 의 주석 설명이 있어 좋습니다. 그런데 fixture 워크스페이스에 얼마나 많은 .rs 파일이 있을 수 있는지 불확실해서, 나중에 test isolation 이 깨질 위험이 있습니다. 이미 쓰인 TestEnv::lexical_only() 이 격리된 TempDir 를 주는 경우라면 fixture leak이 없을 텐데, 혹시 wide_scopeenv.workspace_root 가 아닌 더 넓은 경로를 스캔하지는 않는지 확인해두면 안심이 됩니다. 현재 코드(root: env.workspace_root.clone())는 OK로 보이지만, 주석에 TestEnv 가 고립된 TempDir 임을 명시하면 미래 독자가 >= 3 조건의 의도를 즉시 이해합니다.

**어시션 여유 범위 의도 명확히**: `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 판단은 작성자에게 위임합니다.

**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 판단은 작성자에게 위임합니다.

scope enum 누락 + 신규 필드 미등록: "orphans_only" 값이 scope enum 에 없고, orphans_purged / purged_paths 두 필드도 schema에 아예 없습니다. 외부 통합(Claude Code skill 등)이 이 schema를 validation에 쓸 경우 --orphans-only 결과를 파싱하면 바로 오류가 납니다.

최소 수정:

"scope": {
  "type": "string",
  "enum": ["all", "data_only", "vector_only", "config_only", "orphans_only"]
},
"orphans_purged": { "type": "integer", "minimum": 0, "default": 0 },
"purged_paths":   { "type": "array", "items": { "type": "string" }, "default": [] }

#[serde(default)] 덕분에 back-compat read는 이미 OK — schema만 따라잡으면 됩니다.

**`scope` enum 누락 + 신규 필드 미등록**: `"orphans_only"` 값이 `scope` enum 에 없고, `orphans_purged` / `purged_paths` 두 필드도 schema에 아예 없습니다. 외부 통합(Claude Code skill 등)이 이 schema를 validation에 쓸 경우 `--orphans-only` 결과를 파싱하면 바로 오류가 납니다. 최소 수정: ```json "scope": { "type": "string", "enum": ["all", "data_only", "vector_only", "config_only", "orphans_only"] }, "orphans_purged": { "type": "integer", "minimum": 0, "default": 0 }, "purged_paths": { "type": "array", "items": { "type": "string" }, "default": [] } ``` `#[serde(default)]` 덕분에 back-compat read는 이미 OK — schema만 따라잡으면 됩니다.
altair823 added 1 commit 2026-05-20 07:47:50 +00:00
Round 1 review found 2 doc gaps:
- docs/wire-schema/v1/reset_report.schema.json: 'orphans_only' missing
  from scope enum; orphans_purged/purged_paths properties absent
- README: --orphans-only not listed in the reset prose

Schema additions are additive minor (default values keep back-compat).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 approved these changes 2026-05-20 07:49:20 +00:00
claude-reviewer-01 left a comment
Member

회차 2 — APPROVE

라운드 1 에서 요청한 두 가지 수정 사항이 749c6ae 에서 모두 정확히 반영되었습니다.

검증 결과

  1. docs/wire-schema/v1/reset_report.schema.json

    • scope enum 에 "orphans_only" 추가 ✓
    • orphans_purged: integer, minimum 0, default 0required 배열 밖 (back-compat) ✓
    • purged_paths: array of string, default [] — 동일하게 optional ✓
    • #[serde(rename_all = "snake_case")] 로 Rust OrphansOnly"orphans_only\" 직렬화와 완벽 일치 ✓
  2. README.md

    • 설치/제거 prose 에 **\kebab reset --orphans-only`**` 추가, scope 설명("scope 밖 stored doc 정리, fs file 안 건드림") 명확 ✓

추가 확인 (fresh pass)

  • wire_reset()serde_json::to_value(r) 패스스루 — #[serde(default)] 필드가 자동으로 직렬화에 포함됨 ✓
  • 명령 표 line 81 의 kebab reset synopsis 가 --orphans-only 를 아직 포함하지 않지만, 이 부분은 라운드 1 지적 범위 밖이며 동일 줄의 prose 가 이미 새 플래그를 다루고 있어 blocking 사항 아님 (후속 dogfood PR 에서 정리 가능).
  • HANDOFF.md / ARCHITECTURE.md — 내부 구조 변경 없이 문서 보완만이므로 갱신 불필요.
  • 변경 파일이 정확히 README.md + reset_report.schema.json 두 개뿐 — scope 이탈 없음 ✓

라운드 1 지적 사항이 정확하게 해소되었습니다. APPROVE.

회차 2 — APPROVE 라운드 1 에서 요청한 두 가지 수정 사항이 `749c6ae` 에서 모두 정확히 반영되었습니다. **검증 결과** 1. `docs/wire-schema/v1/reset_report.schema.json` - `scope` enum 에 `"orphans_only"` 추가 ✓ - `orphans_purged`: `integer, minimum 0, default 0` — `required` 배열 밖 (back-compat) ✓ - `purged_paths`: `array of string, default []` — 동일하게 optional ✓ - `#[serde(rename_all = "snake_case")]` 로 Rust `OrphansOnly` → `"orphans_only\"` 직렬화와 완벽 일치 ✓ 2. `README.md` - 설치/제거 prose 에 `**\`kebab reset --orphans-only\`**` 추가, scope 설명("scope 밖 stored doc 정리, fs file 안 건드림") 명확 ✓ **추가 확인 (fresh pass)** - `wire_reset()` 는 `serde_json::to_value(r)` 패스스루 — `#[serde(default)]` 필드가 자동으로 직렬화에 포함됨 ✓ - 명령 표 line 81 의 `kebab reset` synopsis 가 `--orphans-only` 를 아직 포함하지 않지만, 이 부분은 라운드 1 지적 범위 밖이며 동일 줄의 prose 가 이미 새 플래그를 다루고 있어 blocking 사항 아님 (후속 dogfood PR 에서 정리 가능). - HANDOFF.md / ARCHITECTURE.md — 내부 구조 변경 없이 문서 보완만이므로 갱신 불필요. - 변경 파일이 정확히 `README.md` + `reset_report.schema.json` 두 개뿐 — scope 이탈 없음 ✓ 라운드 1 지적 사항이 정확하게 해소되었습니다. APPROVE.

설명이 간결하고 핵심적 — "fs 의 file 은 건드리지 않음" 한 줄로 --orphans-only 의 비파괴성을 명확히 전달.

설명이 간결하고 핵심적 — "fs 의 file 은 건드리지 않음" 한 줄로 `--orphans-only` 의 비파괴성을 명확히 전달.

"orphans_only" 가 scope enum 에 정확히 추가됨. #[serde(rename_all = "snake_case")] 로 Rust OrphansOnly"orphans_only" 직렬화와 완벽 일치.

`"orphans_only"` 가 scope enum 에 정확히 추가됨. `#[serde(rename_all = "snake_case")]` 로 Rust `OrphansOnly` → `"orphans_only"` 직렬화와 완벽 일치.
altair823 merged commit 9fa2a1ebac into main 2026-05-20 07:50:45 +00:00
altair823 deleted branch feat/dogfood-reset-orphans-only 2026-05-20 07:50:46 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#149