a73x

specs/012-patch-branch-refactor/tasks.md

Ref:   Size: 9.5 KiB

# Tasks: Patch-as-Branch Refactor

**Input**: Design documents from `/specs/012-patch-branch-refactor/`
**Prerequisites**: plan.md, spec.md, data-model.md, contracts/cli-commands.md, research.md

**Tests**: User prefers TDD — test tasks included.

**Organization**: Tasks grouped by user story for independent implementation and testing.

## Format: `[ID] [P?] [Story] Description`

- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)

## Phase 1: Setup (Event Schema Change)

**Purpose**: Add `branch` field to the event model — foundational for all user stories

- [ ] T001 Add optional `branch: Option<String>` field with `#[serde(default, skip_serializing_if = "Option::is_none")]` to `Action::PatchCreate` in `src/event.rs`
- [ ] T002 Add `branch: Option<String>` field to `PatchState` struct in `src/state.rs`
- [ ] T003 Populate `PatchState.branch` from `PatchCreate` event's `branch` field in `PatchState::from_ref()` in `src/state.rs`

**Checkpoint**: Existing tests pass — the new optional field doesn't break deserialization of old events

---

## Phase 2: Foundational (Branch Resolution Infrastructure)

**Purpose**: Core helper methods that all user stories depend on

- [ ] T004 Add method `PatchState::resolve_head(&self, repo: &Repository) -> Result<git2::Oid, Error>` in `src/state.rs` — if `branch` is `Some`, resolve `refs/heads/{branch}` via `repo.refname_to_id()`; if `None`, use `repo.revparse_single(&self.head_commit)`
- [ ] T005 Add method `PatchState::staleness(&self, repo: &Repository) -> Result<(usize, usize), Error>` in `src/state.rs` — uses `repo.graph_ahead_behind(branch_tip, base_tip)` to return (ahead, behind)
- [ ] T006 Add method `PatchState::is_branch_based(&self) -> bool` in `src/state.rs` — returns `self.branch.is_some()`
- [ ] T007 Write tests for `resolve_head()` with both branch-based and OID-based patches in `src/state.rs` tests module or `tests/`
- [ ] T008 Write tests for `staleness()` with up-to-date and outdated scenarios in `tests/`

**Checkpoint**: Helper methods work for both old and new patch formats

---

## Phase 3: User Story 1 — Create Patch from Branch (Priority: P1) 🎯 MVP

**Goal**: `patch create` references a branch instead of a commit OID

**Independent Test**: Create a branch with commits, run `patch create`, verify patch lists showing branch name

### Tests for User Story 1

- [ ] T009 [P] [US1] Write test: creating a patch from current branch populates `branch` field in DAG event in `tests/`
- [ ] T010 [P] [US1] Write test: creating a patch from detached HEAD auto-creates a branch in `tests/`
- [ ] T011 [P] [US1] Write test: creating a duplicate patch for same branch returns error in `tests/`
- [ ] T012 [P] [US1] Write test: creating a patch when on base branch returns error in `tests/`

### Implementation for User Story 1

- [ ] T013 [US1] Change `--head` to `--branch` (`-B`) in `PatchCmd::Create`, keep `--head` as hidden deprecated alias in `src/cli.rs`
- [ ] T014 [US1] Update `patch::create()` signature: replace `head_commit: &str` with `branch: &str` parameter in `src/patch.rs`
- [ ] T015 [US1] In `patch::create()`, resolve branch to tip OID, store both `branch` and `head_commit` (tip at creation) in `PatchCreate` event in `src/patch.rs`
- [ ] T016 [US1] Add duplicate branch check in `patch::create()` — scan open patches for matching `branch` field in `src/patch.rs`
- [ ] T017 [US1] Update dispatch in `src/lib.rs`: resolve `--branch` default to current branch name from `repo.head()`, error if on base branch, auto-create branch for detached HEAD
- [ ] T018 [US1] Update `patch show` output to display branch name and staleness info (ahead/behind) in `src/lib.rs`

**Checkpoint**: `patch create` works with branches; `patch show` displays branch name and staleness

---

## Phase 4: User Story 2 — Staleness and Diff via Git (Priority: P2)

**Goal**: `patch show` reports staleness, `patch diff` uses three-dot semantics

**Independent Test**: Create a patch, advance base, verify staleness reported and diff uses merge-base

### Tests for User Story 2

- [ ] T019 [P] [US2] Write test: `patch show` on up-to-date patch shows "0 behind" in `tests/`
- [ ] T020 [P] [US2] Write test: `patch show` on outdated patch shows correct behind count in `tests/`
- [ ] T021 [P] [US2] Write test: `patch diff` uses merge-base for branch-based patches in `tests/`

### Implementation for User Story 2

- [ ] T022 [US2] Update `generate_diff()` in `src/patch.rs`: for branch-based patches, compute merge-base between base tip and branch tip, diff merge-base tree against branch tip tree
- [ ] T023 [US2] Update `patch::show()` to use `resolve_head()` instead of stored `head_commit` for diff/display in `src/patch.rs`

**Checkpoint**: Staleness and diff always reflect current branch state

---

## Phase 5: User Story 3 — Merge as Git Merge (Priority: P3)

**Goal**: `patch merge` performs a standard git merge of the branch into base

**Independent Test**: Create and approve a patch, merge it, verify branch merged into base

### Tests for User Story 3

- [ ] T024 [P] [US3] Write test: merging a branch-based patch performs git merge and updates DAG in `tests/`
- [ ] T025 [P] [US3] Write test: merging a conflicting patch reports error in `tests/`

### Implementation for User Story 3

- [ ] T026 [US3] Update `patch::merge()` to use `resolve_head()` for branch-based patches instead of stored `head_commit` in `src/patch.rs`

**Checkpoint**: Merge uses live branch state, producing standard git merge commits

---

## Phase 6: User Story 4 — Implicit Revision (Priority: P4)

**Goal**: Branch updates automatically reflected without explicit revise

**Independent Test**: Create a patch, push new commit to branch, verify `patch show` reflects it

### Tests for User Story 4

- [ ] T027 [P] [US4] Write test: after pushing a commit to the branch, `patch show` and `patch diff` reflect the new commit without revise in `tests/`

### Implementation for User Story 4

- [ ] T028 [US4] No implementation needed — `resolve_head()` already resolves live. Verify `patch revise` still works for explicit notes in `src/patch.rs`

**Checkpoint**: Updating the branch IS revising the patch

---

## Phase 7: User Story 5 — Backward Compatibility (Priority: P5)

**Goal**: Old-format patches (no `branch` field) continue to work

**Independent Test**: Load a repo with old-format patches, verify list/show/diff/merge all work

### Tests for User Story 5

- [ ] T029 [P] [US5] Write test: old-format patch without `branch` field deserializes and displays correctly in `tests/`
- [ ] T030 [P] [US5] Write test: old-format patch diff uses stored `head_commit` in `tests/`
- [ ] T031 [P] [US5] Write test: old-format patch merge uses stored `head_commit` in `tests/`

### Implementation for User Story 5

- [ ] T032 [US5] Verify all code paths have `is_branch_based()` guards — no implementation changes expected if Phase 2 was done correctly

**Checkpoint**: Old and new format patches coexist without issues

---

## Phase 8: TUI Updates

**Purpose**: Dashboard reflects branch-based patches

- [ ] T033 [P] Update patch detail view in `src/tui.rs` to show branch name instead of head commit for branch-based patches
- [ ] T034 [P] Update patch detail view in `src/tui.rs` to show staleness (ahead/behind) info
- [ ] T035 Show "branch not found" in patch detail when branch has been deleted in `src/tui.rs`

---

## Phase 9: Polish & Cross-Cutting Concerns

- [ ] T036 Update `patch import` to create a branch and use branch-based `patch::create()` in `src/patch.rs`
- [ ] T037 Run `cargo clippy` and fix any warnings
- [ ] T038 Run full test suite and verify all tests pass

---

## Dependencies & Execution Order

### Phase Dependencies

- **Setup (Phase 1)**: No dependencies — event schema change first
- **Foundational (Phase 2)**: Depends on Phase 1 (branch field exists)
- **US1 (Phase 3)**: Depends on Phase 2 (resolve_head exists)
- **US2 (Phase 4)**: Depends on Phase 3 (branch-based patches can be created)
- **US3 (Phase 5)**: Depends on Phase 2 (resolve_head exists)
- **US4 (Phase 6)**: Depends on Phase 3 (branch-based patches exist)
- **US5 (Phase 7)**: Depends on Phase 2 (resolve_head handles both formats)
- **TUI (Phase 8)**: Depends on Phase 3 (branch field in PatchState)
- **Polish (Phase 9)**: Depends on all phases

### Parallel Opportunities

- US3 and US4 can run in parallel after US1 completes
- US5 can run in parallel with US2/US3/US4
- All test tasks within a phase marked [P] can run in parallel
- TUI updates (Phase 8) can run in parallel with US3/US4/US5

---

## Implementation Strategy

### MVP First (User Story 1 Only)

1. Complete Phase 1: Event schema change
2. Complete Phase 2: Branch resolution infrastructure
3. Complete Phase 3: Branch-based patch creation
4. **STOP and VALIDATE**: Create patches from branches, verify listing and display

### Incremental Delivery

1. Setup + Foundational → Branch resolution ready
2. Add US1 → Branch-based patch creation works (MVP!)
3. Add US2 → Staleness and three-dot diff
4. Add US3 → Git-native merge
5. Add US4 → Implicit revision (mostly free)
6. Add US5 → Backward compat verified
7. TUI + Polish → Complete

---

## Notes

- Total tasks: 38
- Tasks per story: Setup=3, Foundation=5, US1=10, US2=5, US3=3, US4=2, US5=4, TUI=3, Polish=3
- Parallel opportunities: Test tasks within phases, US3/US4/US5 after US1
- Suggested MVP scope: Phase 1 + Phase 2 + Phase 3 (branch-based creation)
- Key risk: CLI flag change (`--head` to `--branch`) may affect existing scripts/workflows