# Task 5 Report: Branch/Worktree Slug Uniqueness via task_id

## Status

COMPLETE

## Commit SHA

`7b5347c`

## Test Summary

`uv run pytest tests/git/ -q` — 377 passed, 0 failed.

New tests (`tests/git/test_slug_uniqueness.py`): 2 passed.
Pre-existing tests: all 375 passed (no regressions).

## What Was Done

### 1. `claw_forge/git/slug.py`

Added `task_id: str | None = None` keyword-only parameter to `make_branch_name`.
When given, computes `suffix = f"-{task_id[:6]}"` and subtracts `len(suffix)` from
the description budget before slugifying. `task_id=None` produces identical output to
the pre-change function (backward compatible, confirmed by test).

### 2. `claw_forge/orchestrator/task_handler.py` (line 750)

Added `task_id=task_node.id` to the `_make_branch_name(...)` call. The `.removeprefix`
on the result is unaffected.

### 3. `claw_forge/git/cleanup.py` (line 437–442)

Added `task_id=t.get("id")` to `make_branch_name(...)` in `_build_slug_to_task_map`.
The task dict key `"id"` is confirmed correct — the state service serializes `"id": task.id`
which is the same identifier as `task_node.id` used in the dispatcher.

### 4. `tests/git/test_slug_uniqueness.py` (new)

Verbatim from the brief:
- `test_task_id_disambiguates_colliding_descriptions` — two tasks with different `task_id`s
  and similar descriptions produce distinct branches ending in `-abc123` / `-zzz999`.
- `test_no_task_id_is_backward_compatible` — `make_branch_name("Add JWT authentication", "auth")`
  still equals `"feat/auth-jwt-authentication"`.

### 5. `tests/git/test_cleanup_stale_salvage.py` (updated — called out)

Three integration tests created worktrees with hardcoded old-format slugs (no `task_id`),
then passed tasks with `"id"` values. After the change, `_build_slug_to_task_map` generates
slugs that include `task_id[:6]`, so the worktree directory name no longer matched the
map key — the worktrees fell into the orphan path (which salvages any worktree with commits),
causing two `preserve`-expecting tests to fail.

Fix: each test now imports `make_branch_name` and computes the slug from it (with `task_id`)
before calling `_make_stale_worktree`. The worktree path and the `ours` filter both use the
computed slug. This makes the coupling between dispatcher and cleanup explicit in the test
and will stay correct if the slug formula changes again.

## Concerns

None. The coupling between `task_handler.py` and `cleanup.py` via the slug formula is now
enforced by the stale-salvage integration tests rather than being a hidden invariant.
