Skip to content

BUILD-REPORT-FLT139

Command-bar branch resolution (wildcard + auto) + worktree-history view.

Worktree: C:\Users\fives\source\repos\fmc-flt139 Branch: wt/WT-6022/FLT-139 Built on: origin/master @ 0f62d10 (FLT-135 command bar).

Contract

Feature 1 — branch chip (wildcard + auto)

  • BranchResolver (src/FleetMissionControl.Api/Services/BranchResolver.cs) — pure/static, mirrors RepoResolver. Resolve(text, overrideBranch, ticketKey, targetEntry, leaseLookup, now):
  • Always builds a fuzzy-scored candidate pool from the target PC's RepoPcEntry.Worktrees + CurrentBranch (BuildPool/TextOverlap) — this pool is returned as Candidates on every path, so the wildcard dropdown always has something to offer regardless of which strategy picked the chip's value.
  • AUTO (default when a ticket is resolved): reuse an existing wt/WT-xxxx/<ticket> worktree/branch (BranchNamesTicket — last path segment == ticket key) if one exists; else propose a fresh wt/WT-xxxx/<ticket> name (IsNew=true, WorktreePath=null, nothing created — dispatch still needs an already-indexed worktree path, same as pre-FLT-139).
  • WILDCARD (no ticket known): fuzzy-rank every candidate against the free text; two scores within AmbiguityMargin (0.15, same constant RepoResolver uses) never guesses (ambiguous-wildcard).
  • Hard safety (unconditional, every return path): IsProtected("master"/"main") can never come back Auto=true — override, reuse, wildcard, and the current-branch fallback all route through BuildWarning/explicit checks that force RequiresConfirm=true + a Warning string. A dirty or currently-leased worktree gets the same treatment (never silently auto-picked).
  • ValidateNotMaster(branch, allowMasterDispatch) — the dispatch-time gate, unit-tested directly (this codebase's convention: test services, not minimal-API lambdas — see FollowUpFlowTests). Treats a blank branch as the implied "master" default so the pre-FLT-139 Branch ?? "master" behavior can't silently bypass the guard.
  • WorktreeIdentityReader (Services/WorktreeIdentityReader.cs) — best-effort, LOCAL-ONLY read of worktree-identity's ~/.claude/worktree-identity/registry.json (unsigned, per-machine, no cross-PC sync — unlike RepoRegistryReader's signed fleet-wide inventory). Fail-closed: any read/parse error returns empty, never throws. IsLive(entry, now) mirrors worktree-identity's own Registry.IsLiveOwner minus the PID-liveness leg.
  • Contracts (Contracts/CommandBarContracts.cs): BranchChip + BranchCandidateDto added to CommandResolution; CommandResolveRequest.Target (which PC to scope worktree lookup to) and CommandResolveOverrides.Branch added; CommandDispatchRequest.AllowMasterDispatch added.
  • CommandResolverService now takes WorktreeIdentityReader identity and assembles the branch chip after the repo chip (needs repoResult.RepoId + request.Target to find the RepoPcEntry), using ticketChip.Key for the AUTO path's ticket match.
  • CommandEndpoints.MapCommandEndpoints dispatch: calls BranchResolver.ValidateNotMaster before resolving the repo path; on failure returns 400. The pre-existing Branch ?? "master" default is now computed explicitly (string.IsNullOrWhiteSpace(body.Branch) ? "master" : ...) so the guard sees the same value that goes into AgentRunRequest.Branch — no daylight between what's validated and what's dispatched.
  • Frontend (frontend/src/app/components/command-bar.component.ts): new branch chip (auto/manual badge, confidence %, warning line, wildcard <select> populated from r.branch.candidates). Changing the target PC re-resolves and clears any branch override (onTargetChange). Deliberate P1 simplification: the UI never sends allowMasterDispatchcanDispatch() hard-disables Confirm whenever the branch chip resolves to master/main (isProtectedBranch), so forcing a master dispatch is not reachable from this command bar at all in P1 (only a direct API call with the explicit flag can do it).

Feature 2 — worktree-history view

  • WorktreeHistoryProjector (Services/WorktreeHistoryProjector.cs) — pure, assembles WorktreeHistoryResponse from a RepoPcEntry + WorktreeIdentityReader entries; no I/O of its own (both readers already own their fail-closed reads).
  • GET /api/repos/{repoId}/worktrees?target={pc} (Program.cs, next to the existing GET /repos) — RequireFleetAuth (per the FLT-139 spec; unlike the plain /repos GET, this one also surfaces WT-id lease/session context). 400 when target is missing. Registry-only, fail-closed, never a filesystem walk.
  • Contracts (Contracts/WorktreeHistoryContracts.cs): WorktreeIdentityEntry (a lean local mirror of worktree-identity's RegistryEntry — deliberately not a 1:1 copy, since that tool owns its own evolving contract), WorktreeHistoryEntry, WorktreeHistoryResponse.
  • Frontend (frontend/src/app/components/worktree-history.component.ts) — mirrors TranscriptPanelComponent's backdrop+panel structure and load-on-input-change pattern. Shows main checkout (path/branch/dirty) + each worktree (path, branch, dirty flag, WT-id + description + ticket when the identity registry resolved one, live-lease flag, last activity). Opened via a ▤ worktrees button on the command bar's repo chip once a repo is resolved.

Files

Backend — new: - src/FleetMissionControl.Api/Services/BranchResolver.cs - src/FleetMissionControl.Api/Services/WorktreeIdentityReader.cs - src/FleetMissionControl.Api/Services/WorktreeHistoryProjector.cs - src/FleetMissionControl.Api/Contracts/WorktreeHistoryContracts.cs - tests/FleetMissionControl.Api.Tests/BranchResolverTests.cs - tests/FleetMissionControl.Api.Tests/WorktreeIdentityReaderTests.cs - tests/FleetMissionControl.Api.Tests/WorktreeHistoryProjectorTests.cs

Backend — modified: - src/FleetMissionControl.Api/Contracts/CommandBarContracts.cs - src/FleetMissionControl.Api/Services/CommandResolverService.cs - src/FleetMissionControl.Api/Endpoints/CommandEndpoints.cs - src/FleetMissionControl.Api/Program.cs - tests/FleetMissionControl.Api.Tests/CommandResolverServiceTests.cs

Frontend — new: - frontend/src/app/components/worktree-history.component.ts - frontend/src/app/components/worktree-history.component.spec.ts

Frontend — modified: - frontend/src/app/components/command-bar.component.ts - frontend/src/app/components/command-bar.component.spec.ts - frontend/src/app/core/command.service.ts - frontend/src/app/api/fleet-api.generated.ts (regenerated) - openapi/FleetMissionControl.Api.json (regenerated)

Gate output (exact)

dotnet build -warnaserror

Build succeeded.
    0 Warning(s)
    0 Error(s)

dotnet test

Passed!  - Failed:     0, Passed:   524, Skipped:     0, Total:   524, Duration: 4 s - FleetMissionControl.Api.Tests.dll (net10.0)
(488 pre-existing + 36 new: 20 BranchResolverTests, 9 WorktreeIdentityReaderTests, 6 WorktreeHistoryProjectorTests, 3 new CommandResolverServiceTests branch-chip cases.)

npx ng build

Application bundle generation complete. [22.955 seconds]
Output location: C:\Users\fives\source\repos\fmc-flt139\frontend\dist\frontend
Clean, no errors.

npx ng lint

Linting "frontend"...
All files pass linting.

npx vitest run

Test Files  22 passed (22)
     Tests  247 passed (247)
(new: 8 worktree-history.component.spec.ts tests + 4 new command-bar.component.spec.ts tests for the branch chip/master-guard/target-change/worktree-history-open cases.)

npm run check:drift — ran generate:client, diffed against pre-FLT-139 HEAD (expected non-empty at this point since the feature isn't committed yet); confirmed idempotent (a second generate:client produced no further changes to the already-regenerated file). Both regenerated files (fleet-api.generated.ts, openapi/FleetMissionControl.Api.json) are committed in this same commit, so check:drift is clean against the landed tree.

Master-guard proof

  • BranchResolverTests.ValidateNotMaster_rejects_master_by_default / ValidateNotMaster_rejects_a_blank_branch_as_the_implied_master_default / ValidateNotMaster_allows_master_when_explicitly_overridden — the dispatch-time gate itself.
  • BranchResolverTests.Override_naming_master_is_never_auto_even_though_explicit, A_wildcard_pick_landing_on_master_is_never_auto — the chip-resolution side never returns Auto=true for master/main on any path.
  • CommandResolverServiceTests.Branch_chip_never_auto_selects_master_even_when_it_is_the_only_candidate — end-to-end through the orchestrator.
  • command-bar.component.spec.ts: 'FLT-139 hard gate: a branch chip resolved to master disables Confirm dispatch entirely' — the frontend never even offers the click.
  • Endpoint change in CommandEndpoints.cs: the pre-existing default-to-"master" behavior (Branch = body.Branch ?? "master") is now explicitly re-derived and passed through ValidateNotMaster first — a caller that omits Branch entirely (every pre-FLT-139 caller) is now rejected with 400 unless it also sets AllowMasterDispatch: true. This is an intentional behavior change: closing the exact hole the isolation policy exists to prevent (a dispatch with no branch silently landing on master).

Deferred to P2

  • Cross-PC worktree-identity sync. WorktreeIdentityReader only ever sees the registry on the machine the API process is running on — a target PC other than the API host gets zero identity enrichment (never fabricated, just absent). Syncing this the way repo-inventory is synced (signed per-PC files under the shared OneDrive dir) is real scanner-side work, same category as the already-acknowledged RepoRegistryReader scanner TODO.
  • Fresh-worktree proposal is informational only. BranchResolver's fresh-worktree source proposes a wt/WT-xxxx/<ticket> name but creates nothing — dispatch still requires an already-indexed worktree path (pre-existing RepoRegistryReader.Resolve behavior, untouched). Actually creating the worktree + minting its real WT-xxxx id via worktree-identity is a separate write path this ticket doesn't touch.
  • No allowMasterDispatch UI affordance. The command bar can't force a master dispatch at all in P1 (see above) — if a legitimate emergency ever needs it, that's a deliberate, separate, reviewed addition, not a checkbox slipped in here.