diff --git a/docs/superpowers/specs/2026-10-01-unit-branch-draft-pr-design.md b/docs/superpowers/specs/2026-10-01-unit-branch-draft-pr-design.md index de7b073f..1720e796 100644 --- a/docs/superpowers/specs/2026-10-01-unit-branch-draft-pr-design.md +++ b/docs/superpowers/specs/2026-10-01-unit-branch-draft-pr-design.md @@ -1,6 +1,6 @@ # Unit-Branch + Draft-PR — Design -**Datum:** 2026-10-01 · **Status:** Entwurf, Review durch Mika ausstehend +**Datum:** 2026-10-01 · **Status:** abgenommen (mit Basis-Branch pro Liste + Fetch) **Diagramme:** Obsidian `07 - Plattform & Technik/ClaudeDo/ClaudeDo - Branch und PR Ablauf.md` **Ersetzt:** die offene Entscheidung aus `2026-08-06-planning-chain-fork-base-design.md` @@ -33,6 +33,8 @@ Vorgängers. | PR-Automatik | Kein Auto-PR bei einem Statuswechsel. Automatisiert wird über den Merge-Helper, dessen `approve` dann den PR erzeugt. | | ADO-Zugang | Eigener PAT (Scope Code Read & Write). Er wird per `DpapiTokenStore` in `~/.claudeDo/azdo.pat` gespeichert, ohne Abhängigkeit auf das work-conventions-Plugin. | | dependsOn über Units | Wartet bewusst auf den echten Merge. Abhängige Arbeit gehört in eine Unit mit einem PR. | +| Basis-Branch | Pro Liste einstellbar (`list_config.base_branch`, z.B. `dev` für die LagerApp). Leer heißt: privat der gerade ausgecheckte Branch des Main-Checkouts (wie heute), Firma der Default-Branch des ADO-Repos. Gilt als Basis des Unit-Branches, als PR-Ziel und als Vorauswahl des privaten Merge-Ziels. | +| Basis aktualisieren | Firmen-Repo: `git fetch origin `, bevor der Unit-Branch angelegt wird. Der Unit-Branch startet von `origin/`, der lokale Basis-Branch wird best-effort per fast-forward nachgezogen. | ## 1. Branch-Naming (`WorktreeManager`) @@ -69,6 +71,37 @@ Vorgängers. **Basis eines Childs** (`ResolveBaseCommitAsync`): Das ist immer der aktuelle Head des Unit-Branches (`rev-parse `). Der Sonderfall „nur bei `PlanningPhase.None`“ fällt weg. +**Basis eines Unit-Roots** (Einzeltask, Parent): +- **Privat:** `list_config.base_branch`, sonst der ausgecheckte Branch des Main-Checkouts. Heute + ist es `RevParseHeadAsync`. Ist `base_branch` gesetzt, wird es `rev-parse `, auch + wenn gerade ein anderer Branch ausgecheckt ist. +- **Firma:** + - Zuerst `git fetch origin `. `` ist `base_branch`, sonst der Default-Branch des + Repos per ADO-API, ersatzweise `git symbolic-ref refs/remotes/origin/HEAD`. + - Der Unit-Branch startet von `origin/`. + - Dann wird der lokale `` best-effort nachgezogen: + - nicht ausgecheckt: `git fetch origin :` (lehnt einen nicht-fast-forward + selbst ab) + - ausgecheckt und sauber: `git -C merge --ff-only origin/` + - schmutzig oder divergiert: Warnung ins Log, kein Eingriff in den Checkout + - Schlägt der Fetch fehl (offline, keine Credentials), wird gewarnt und es geht mit dem + lokalen `` weiter. Der Task scheitert daran nicht. + - Der Fetch nutzt dieselbe PAT-Übergabe wie der Push (Abschnitt 3). +- Der Fetch läuft einmal pro Unit-Root, nicht pro Child. Children starten vom Unit-Branch. + +**Spalte `list_config.base_branch`** (nullable string, Migration): +- Sie wird im Listeneinstellungs-Modal gepflegt, als Freitext mit Vorschlägen aus + `ListLocalBranchesAsync`, und in `get_list_config` mitgeliefert. +- ⚠️ `ListRepository.SetConfigAsync` kopiert verbatim (siehe `TicketProjectId`). Jeder Schreiber + muss den Wert durchreichen, sonst setzt ihn z.B. `set_list_config` still zurück: + `WorkerHub.UpdateListConfig`, `ConfigMcpTools`, alle Stellen, die eine frische + `ListConfigEntity` bauen. +- `UpdateListConfigDto` braucht ihn tri-state wie `TicketProjectId`, damit ein Aufrufer, der das + Feld nicht kennt, es nicht löscht. +- **Merge-Ziel privat:** `TaskMergeService.GetTargetsAsync` setzt `DefaultBranch` auf + `base_branch`, falls gesetzt. Das leere Ziel (`ApproveAndMergeAsync:1077`, `PreviewAsync:910`) + löst ebenfalls auf `base_branch` auf, sonst wie heute. + **Absorbieren eines Childs:** Neu ist `UnitBranchService.AbsorbChildAsync(childId)`, das in `git -C merge --ff-only ` mündet. - Die Kette ist seriell (`BlockedByTaskId`), und das Child startet vom Head des Unit-Branches. @@ -119,8 +152,8 @@ einem Folge-Task, sobald keine solchen Units mehr offen sind. - `GET …/pullrequests?searchCriteria.sourceRefName=refs/heads/&searchCriteria.status=active`. Gibt es schon einen PR, wird er wiederverwendet (idempotent, auch wenn der Skill in der Session schon einen angelegt hat). - - Sonst `POST …/pullrequests` mit `isDraft: true` und - `targetRefName = ` (`GET …/repositories/`). + - Sonst `POST …/pullrequests` mit `isDraft: true` und `targetRefName = `. Das ist + `list_config.base_branch`, sonst `defaultBranch` aus `GET …/repositories/`. - Die API-Version fällt auf die zurück, die der Server in seiner Fehlermeldung nennt, wie im Skript. 4. Die **PR-URL** wird gespeichert, in der neuen nullable Spalte `worktrees.pull_request_url` @@ -181,8 +214,7 @@ Die Prompts in `PromptFiles` (merge und merge_final) bekommen einen Firmen-Repo- PR-API ist ADO-spezifisch. - Aufräumen von Worktree und lokalem Branch, wenn ein Firmen-Task auf `Done` gesetzt wird. Das geht über die bestehende Worktree-Übersicht. -- `git fetch` vor dem Anlegen des Unit-Branches. Die Basis ist der lokale `main`-HEAD wie heute, - siehe offene Frage. +- `git fetch` in privaten Repos. Die Basis bleibt dort lokal. - Eine UI für `company_remote_pattern`. ## 7. Tests @@ -204,12 +236,14 @@ Keine Tests mit der echten Claude-CLI und keine gegen das echte ADO. - `AzureDevOpsClient` wird gefakt: PR anlegen, vorhandenen PR wiederverwenden, Verify rot heißt kein Push, PR-Fehler nach dem Push. - **Remote-Parsing:** die Fälle aus dem `-SelfTest` des Skripts. +- **Basis:** + - `base_branch` gesetzt: Der Unit-Root startet davon, auch wenn ein anderer Branch ausgecheckt + ist. + - Firma: Nach dem Fetch startet der Unit-Root von `origin/`. Das testet ein Bare-Repo, + das einen Commit voraus ist. + - Der lokale Basis-Branch wird nachgezogen, ein schmutziger Checkout bleibt unangetastet. + - Ein Fetch-Fehler bricht den Task nicht. + - `SetConfigAsync`, `set_list_config` und `UpdateListConfig` erhalten `base_branch`. - **Manueller Smoke-Test (Mika):** PAT eintragen, Test-Button. Ein Ticket-Task in einem echten Firmen-Repo bis zum Draft-PR durchspielen, danach eine Planning-Unit mit 2 Children. -## Offene Frage - -- **Basis im Firmen-Repo:** Der lokale `main` ist in Firmen-Repos oft veraltet. Vorschlag für - später: `git fetch origin ` und dann `origin/` als Basis des Unit-Branches. - In v1 ist das draußen. Wenn es dich jetzt schon stört, ist es ein kleiner Zusatz in - `ResolveBaseCommitAsync`.