docs(spec): per-list base branch and fetch before unit branch

This commit is contained in:
mika kuns
2026-10-01 10:16:32 +02:00
parent 0e9373c083
commit 3dc735b2af
@@ -1,6 +1,6 @@
# Unit-Branch + Draft-PR — Design # 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` **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` **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. | | 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. | | 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. | | 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 <basis>`, bevor der Unit-Branch angelegt wird. Der Unit-Branch startet von `origin/<basis>`, der lokale Basis-Branch wird best-effort per fast-forward nachgezogen. |
## 1. Branch-Naming (`WorktreeManager`) ## 1. Branch-Naming (`WorktreeManager`)
@@ -69,6 +71,37 @@ Vorgängers.
**Basis eines Childs** (`ResolveBaseCommitAsync`): Das ist immer der aktuelle Head des **Basis eines Childs** (`ResolveBaseCommitAsync`): Das ist immer der aktuelle Head des
Unit-Branches (`rev-parse <unitBranch>`). Der Sonderfall „nur bei `PlanningPhase.None`“ fällt weg. Unit-Branches (`rev-parse <unitBranch>`). 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 <base_branch>`, auch
wenn gerade ein anderer Branch ausgecheckt ist.
- **Firma:**
- Zuerst `git fetch origin <basis>`. `<basis>` 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/<basis>`.
- Dann wird der lokale `<basis>` best-effort nachgezogen:
- nicht ausgecheckt: `git fetch origin <basis>:<basis>` (lehnt einen nicht-fast-forward
selbst ab)
- ausgecheckt und sauber: `git -C <workingDir> merge --ff-only origin/<basis>`
- 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 `<basis>` 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 **Absorbieren eines Childs:** Neu ist `UnitBranchService.AbsorbChildAsync(childId)`, das in
`git -C <parentWorktree> merge --ff-only <childBranch>` mündet. `git -C <parentWorktree> merge --ff-only <childBranch>` mündet.
- Die Kette ist seriell (`BlockedByTaskId`), und das Child startet vom Head des Unit-Branches. - 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/<branch>&searchCriteria.status=active`. - `GET …/pullrequests?searchCriteria.sourceRefName=refs/heads/<branch>&searchCriteria.status=active`.
Gibt es schon einen PR, wird er wiederverwendet (idempotent, auch wenn der Skill in der Gibt es schon einen PR, wird er wiederverwendet (idempotent, auch wenn der Skill in der
Session schon einen angelegt hat). Session schon einen angelegt hat).
- Sonst `POST …/pullrequests` mit `isDraft: true` und - Sonst `POST …/pullrequests` mit `isDraft: true` und `targetRefName = <basis>`. Das ist
`targetRefName = <defaultBranch des Repos>` (`GET …/repositories/<repo>`). `list_config.base_branch`, sonst `defaultBranch` aus `GET …/repositories/<repo>`.
- Die API-Version fällt auf die zurück, die der Server in seiner Fehlermeldung nennt, wie im - Die API-Version fällt auf die zurück, die der Server in seiner Fehlermeldung nennt, wie im
Skript. Skript.
4. Die **PR-URL** wird gespeichert, in der neuen nullable Spalte `worktrees.pull_request_url` 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. PR-API ist ADO-spezifisch.
- Aufräumen von Worktree und lokalem Branch, wenn ein Firmen-Task auf `Done` gesetzt wird. Das - Aufräumen von Worktree und lokalem Branch, wenn ein Firmen-Task auf `Done` gesetzt wird. Das
geht über die bestehende Worktree-Übersicht. geht über die bestehende Worktree-Übersicht.
- `git fetch` vor dem Anlegen des Unit-Branches. Die Basis ist der lokale `main`-HEAD wie heute, - `git fetch` in privaten Repos. Die Basis bleibt dort lokal.
siehe offene Frage.
- Eine UI für `company_remote_pattern`. - Eine UI für `company_remote_pattern`.
## 7. Tests ## 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 - `AzureDevOpsClient` wird gefakt: PR anlegen, vorhandenen PR wiederverwenden, Verify rot
heißt kein Push, PR-Fehler nach dem Push. heißt kein Push, PR-Fehler nach dem Push.
- **Remote-Parsing:** die Fälle aus dem `-SelfTest` des Skripts. - **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/<basis>`. 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 - **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. 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 <default>` und dann `origin/<default>` 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`.