docs(worker): document company mode (fetch, push, draft PR) and its open smoke tests
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Review, merge & conflict resolution
|
||||
|
||||
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
|
||||
> Last verified against `44d94caa` (2026-10-01), which made the parent's worktree the unit
|
||||
> branch (children fast-forwarded into it).
|
||||
> Last verified against `0e6a48e2` (2026-10-01): parent's worktree is the unit branch (children
|
||||
> fast-forwarded into it); in a company repo approve ends in a pushed branch + draft PR.
|
||||
> Planning mode renders per file and `DiffLinesView` is retired.
|
||||
> Drift check: `git log --oneline 20bce9b..HEAD -- src/ClaudeDo.Worker/Lifecycle src/ClaudeDo.Worker/State src/ClaudeDo.Worker/Planning src/ClaudeDo.Ui/ViewModels/Conflicts src/ClaudeDo.Worker/External`
|
||||
> Stable structure only (no line numbers). See docs/explore-notes/README.md.
|
||||
@@ -28,6 +28,19 @@ There is no separate "Merge all" entry.
|
||||
- **Child of a unit** (parent has an `Active` worktree) → `ApproveAndMergeAsync` absorbs it into
|
||||
the unit branch (`UnitBranchService.AbsorbChildAsync`, ff-only) instead of merging to the target.
|
||||
- **No active worktree** (sandbox run) → straight to `Done`.
|
||||
- **Company repo** (`PullRequestPublisher.IsCompanyRepoAsync`, origin matches
|
||||
`company_remote_pattern`, checked live) with an `Active` worktree → `ApproveCompanyAsync`
|
||||
instead of a merge, after the planning-parent gate and the child absorb: missing PAT / non-ADO
|
||||
remote is `blocked` before anything runs, then the verify command in the **worktree**
|
||||
(`skipVerify` ignored — no local merge to defer it to; red = `verify_failed`, nothing pushed),
|
||||
then `PullRequestPublisher.PublishAsync`: `git push -u origin <branch>` (PAT via `GIT_CONFIG_*`
|
||||
env, `AzureDevOpsClient.GitAuthEnv`), find-or-create the draft PR, store
|
||||
`worktrees.pull_request_url`, `WorktreeUpdated`. Result `pr_created` / `pr_updated` + URL
|
||||
(`MergeResult.PullRequestUrl`; MCP puts it in `mergeMessage`). Task stays `WaitingForReview`,
|
||||
worktree `Active`, ticket `InBearbeitung`; `Done` is the user's manual toggle after the ADO
|
||||
merge. A PR failure after a successful push is `blocked` with `gepusht, PR fehlgeschlagen: …`.
|
||||
⚠️ Only approve branches — `merge_task`/the merge modal and the legacy-unit orchestrator still
|
||||
merge locally in a company repo.
|
||||
|
||||
Review transitions all live in `TaskStateService`: `SubmitForReviewAsync`,
|
||||
`SubmitForChildrenAsync`, `ApproveReviewAsync`, `RejectToQueueAsync`, `RejectToIdleAsync`,
|
||||
|
||||
@@ -74,6 +74,24 @@ Bleibt manuell zu prüfen (UI-Pfad, nie sichtbar gelaufen):
|
||||
- [ ] Base-URL leeren → Menüeintrag verschwindet, keine Netzwerkaufrufe mehr.
|
||||
- [ ] Sichtprüfung des Settings-Tabs + des Ticket-Projekt-Dropdowns (nie gerendert gesehen).
|
||||
|
||||
## Offene Verifikation (2026-10-01, Unit-Branch Teil 3: Firmen-Modus / Draft-PR)
|
||||
|
||||
Build + gefilterte Tests grün (Worker: AzureDevOps/CompanyFetchBase/CompanyApprove/CommitMessageBuilder,
|
||||
Ui: TicketSettingsTab/MergeSectionCompanyRepo, Data: PromptFiles, Localization). **Nie gegen echtes ADO,
|
||||
nicht visuell geprüft:**
|
||||
|
||||
- [ ] Sichtprüfung: Settings-Reiter heißt „Externe Dienste", Abschnitt Azure DevOps (PAT, Test, Löschen);
|
||||
Detail-Pane im Firmen-Repo: kein Ziel-Dropdown, Button „Draft-PR erstellen" → nach dem Approve
|
||||
„PR aktualisieren" + Button „Pull Request öffnen"; Approve bleibt hinter „Diff öffnen" gegated.
|
||||
- [ ] PAT eintragen, „Verbindung testen" — meldet Repo + Default-Branch der ersten Firmen-Liste.
|
||||
- [ ] Ticket-Task in einem echten Firmen-Repo bis zum Draft-PR: Branch `<typ>/<ticket>-<slug>` startet
|
||||
von frisch gefetchtem `origin/<basis>`, Commit ohne `ClaudeDo-Task:`-Trailer, PR-Titel
|
||||
`…, Resolves #<id>`, Beschreibung mit Ticket-Link (Projekt-Id-Lookup über `GET /api/ticket/{id}`
|
||||
ist nur gegen den Stub getestet) und `Geprüft:`-Zeile, Task bleibt WaitingForReview.
|
||||
- [ ] Zweiter Approve nach Reject mit Feedback → `pr_updated`, derselbe PR bekommt die neuen Commits.
|
||||
- [ ] Planning-Unit mit 2 Children im Firmen-Repo → ein PR für die ganze Unit.
|
||||
- [ ] API-Version-Fallback (Server meldet „supports is X") ist nur gegen den Stub getestet.
|
||||
|
||||
## Offene Verifikation (2026-10-01, Ticket-Import über alle Projekte)
|
||||
|
||||
Build + Unit-Tests grün, **nicht visuell/live verifiziert**:
|
||||
|
||||
@@ -36,6 +36,8 @@ Worker/
|
||||
Prime/ — "Prime Claude" schedules: PrimeScheduler, PrimeRunner, PrimePrompts,
|
||||
DailyPrepPrompt, PrimeScheduleValidation, NextDueCalculator, PrimeScheduleSignal
|
||||
Online/ — optional Online Inbox sync (off by default; zero network when disabled)
|
||||
AzureDevOps/ — company-repo push + draft PR: AzureDevOpsClient (REST, port of pr-create.ps1),
|
||||
PullRequestPublisher (push, find-or-create draft PR, pull_request_url)
|
||||
Tickets/ — Bandel ticket-system integration (per-list import + status write-back),
|
||||
inactive without ticket_api_base_url: TicketSystemConfig/TicketClientFactory,
|
||||
BandelTicketClient, TicketDtos, TicketStatusMap, TicketStatusSync,
|
||||
@@ -151,8 +153,8 @@ Full flow, invariants, and model/effort/max-turns resolution (including the low-
|
||||
- **ClaudeProcess** — spawns `claude -p --output-format stream-json --verbose --permission-mode auto` (or whatever app settings specify). Prompt via stdin, NDJSON from stdout. CancellationToken kills the process tree. Resolves `_cfg.ClaudeBin` via `ExecutableResolver` (`ClaudeDo.Data`, shared with `ClaudeCliPreflight` and the Installer's checks) before spawning — a `.cmd`/`.bat` shim (e.g. an npm-installed `claude`) is launched through `cmd.exe /c` since `UseShellExecute = false` can't exec a shim directly; a resolved `.exe` starts exactly as before. Throws if nothing resolves. *(Not yet on `main` — see `Environment Checks` in `ClaudeDo.Installer/CLAUDE.md`.)*
|
||||
- **ClaudeArgsBuilder** — `--model`, `--effort`, `--max-turns`, `--append-system-prompt`, `--agents`, `--json-schema`, `--resume`
|
||||
- **StreamAnalyzer** — parses NDJSON; extracts session_id, token counts, turn counts, result text, structured output. Replaced MessageParser.
|
||||
- **WorktreeManager** — worktrees on `<typ>/<nummer>-<slug>` branches, same leaf as the folder (`NameFor`, slug capped at 40 chars, umlauts transliterated). Typ from `BranchTypeFor` (`feat`→`feature`, `fix/refactor/docs/test/chore` as-is, anything else `chore`). Nummer = ticket id (own `TicketRef` or the parent's), else the task number — in a company repo (`CompanyRepo`, origin matches `company_remote_pattern`) without a ticket no number at all. A taken folder/branch name gets an 8-char task-id suffix, so the self-heal never force-deletes a foreign branch. Base = `rev-parse <unit branch>` for a child whose parent has an Active worktree, else `list_config.base_branch`, else the checked-out HEAD. Commits with semantic messages, updates DB with head commit + diff stats
|
||||
- **CommitMessageBuilder** — `{commitType}(slug): title\n\ndescription\n\nClaudeDo-Task: taskId`; `BuildMerge` is the merge-commit variant (`{commitType}(slug): merge title` + trailer). **Every merge caller passes a blank commit message on purpose** — `TaskMergeService` fills in `BuildMerge` from the task's commit type and its list's name, which is the only place that knows both. Don't reintroduce a caller-side literal.
|
||||
- **WorktreeManager** — worktrees on `<typ>/<nummer>-<slug>` branches, same leaf as the folder (`NameFor`, slug capped at 40 chars, umlauts transliterated). Typ from `BranchTypeFor` (`feat`→`feature`, `fix/refactor/docs/test/chore` as-is, anything else `chore`). Nummer = ticket id (own `TicketRef` or the parent's), else the task number — in a company repo (`CompanyRepo`, origin matches `company_remote_pattern`) without a ticket no number at all. A taken folder/branch name gets an 8-char task-id suffix, so the self-heal never force-deletes a foreign branch. Base = `rev-parse <unit branch>` for a child whose parent has an Active worktree, else `list_config.base_branch`, else the checked-out HEAD — in a company repo a unit root first fetches its basis (see Azure DevOps below). Commits with semantic messages, updates DB with head commit + diff stats
|
||||
- **CommitMessageBuilder** — `{commitType}(slug): title\n\ndescription\n\nClaudeDo-Task: taskId` (no trailer in company repos — the convention forbids signature trailers and nothing reads it); `BuildMerge` is the merge-commit variant (`{commitType}(slug): merge title` + trailer). **Every merge caller passes a blank commit message on purpose** — `TaskMergeService` fills in `BuildMerge` from the task's commit type and its list's name, which is the only place that knows both. Don't reintroduce a caller-side literal.
|
||||
- **TaskResetService** — discards a failed task's worktree, resets the row to Idle, preserves run history
|
||||
- **AgentFileService** — manages `~/.claudeDo/agents/*.md`; list/refresh via SignalR
|
||||
- **LogWriter** — async StreamWriter wrapper, auto-creates parent dirs
|
||||
@@ -273,10 +275,10 @@ non-obvious, behaviour-changing. A fixed bug is git history, not a finding.
|
||||
- `db_path`, `sandbox_root`, `log_root`
|
||||
- `worktree_root_strategy` (`sibling` | `central`), `central_worktree_root`
|
||||
- `queue_backstop_interval_ms` (30000) — also the gate/throttle recovery timer
|
||||
- `company_remote_pattern` (`/_git/`) — Teilstring der origin-URL, an dem `CompanyRepo` ein Firmen-Repo erkennt; nur per Hand editierbar.
|
||||
- `company_remote_pattern` (`/_git/`) — Teilstring der origin-URL, an dem `CompanyRepo` ein Firmen-Repo erkennt; nur per Hand editierbar. Schaltet Fetch vor dem Unit-Root, Approve = Push + Draft-PR und Commits ohne Trailer (siehe Azure DevOps).
|
||||
- `signalr_port` (47821), `claude_bin`, `ticket_api_base_url` — the two UI-writable fields
|
||||
(`claude_bin`: Settings → Dateien → CLAUDE CLI, hub `GetClaudeBin`/`SetClaudeBin`;
|
||||
`ticket_api_base_url`: Settings → Ticketsystem, hub `GetTicketSettings`/`SetTicketApiBaseUrl`).
|
||||
`ticket_api_base_url`: Settings → Externe Dienste, hub `GetTicketSettings`/`SetTicketApiBaseUrl`).
|
||||
Both edit the DI-singleton `WorkerConfig` in place *and* persist via their own `SaveKey` call
|
||||
(`SaveClaudeBin`/`SaveTicketApiBaseUrl`), so each applies without a restart. Every other field
|
||||
here is hand-edit only.
|
||||
@@ -341,6 +343,39 @@ distinguish the two singletons). The token never touches `worker.config.json` or
|
||||
`TicketStatusMap.NameOf` supplies the canonical name.
|
||||
⚠️ The base URL must be the **IP** — `bandelapis.fb-tuning.local` still refuses the connection.
|
||||
|
||||
## Azure DevOps (company repos)
|
||||
|
||||
Active when origin matches `company_remote_pattern` (live `git remote get-url`, no cache). The PAT
|
||||
(scope Code Read & Write) is DPAPI-encrypted at `~/.claudeDo/azdo.pat` (DI marker
|
||||
`AzureDevOpsPatStore`), set via Settings → Externe Dienste (hub `GetAzureDevOpsSettings` /
|
||||
`SetAzureDevOpsToken` / `ClearAzureDevOpsToken` / `TestAzureDevOpsConnection` — the test reads the
|
||||
first list whose origin matches and parses). Deliberately independent of the work-conventions
|
||||
plugin's PAT file.
|
||||
|
||||
- **`AzureDevOpsClient`** — the only HTTP surface. `ParseRemote` splits at `/_git/` (API base =
|
||||
everything before it, embedded credentials stripped, http(s) only). `GitAuthEnv(pat)` is the
|
||||
env for every git network call: `GIT_TERMINAL_PROMPT=0`, `GCM_INTERACTIVE=never`, PAT as
|
||||
`http.extraHeader` via `GIT_CONFIG_COUNT/KEY_0/VALUE_0` — never on a command line. API version
|
||||
starts at 6.0 and retries once with the version an out-of-range error names. ⚠️ A rejected PAT
|
||||
is HTTP **203** + an HTML login page, which `IsSuccessStatusCode` counts as success — handled
|
||||
explicitly.
|
||||
- **Fetch before a unit root** (`WorktreeManager.FetchCompanyBaseAsync`, once per unit root, never
|
||||
per child): basis = `base_branch` → ADO `defaultBranch` → `origin/HEAD`; `git fetch origin
|
||||
+refs/heads/<b>:refs/remotes/origin/<b>`; the worktree starts from `origin/<b>`. Local `<b>` is
|
||||
pulled along best-effort: not checked out → `git fetch . origin/<b>:<b>` (no network, git
|
||||
refuses a non-ff), checked out + clean → `merge --ff-only`, dirty/diverged → warning only. A
|
||||
failed fetch warns and continues from the local branch — it never fails the task.
|
||||
- **Approve** → `TaskMergeService.ApproveCompanyAsync` → `PullRequestPublisher`; details in
|
||||
[review-merge](../../docs/explore-notes/review-merge.md). PR title
|
||||
`<type>(<list-slug>): <title>, Resolves #<ticket>`; description
|
||||
`Resolves [#id](https://ticket.fb-tuning.local/board/project/<p>/ticket/<id>)` (project id = walk
|
||||
`GET /api/ticket/{id}` up until `parentTicketID` is null — a ticket's parent is its project, a
|
||||
task's parent is a ticket, live-checked 2026-10-01; unknown → plain `Resolves #id`), the task
|
||||
description, `Geprüft: …` with only what actually ran. Target = `base_branch`, else the ADO
|
||||
default branch.
|
||||
- Not done on purpose: PR status polling / auto-`Done`, other PR providers, worktree cleanup on
|
||||
`Done`, fetch in private repos, a UI for `company_remote_pattern`.
|
||||
|
||||
## Notes
|
||||
|
||||
- The worker runs standalone — start it separately from the UI. Loopback only (127.0.0.1).
|
||||
|
||||
Reference in New Issue
Block a user