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, mirrorsRepoResolver.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 asCandidateson 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 freshwt/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 backAuto=true— override, reuse, wildcard, and the current-branch fallback all route throughBuildWarning/explicit checks that forceRequiresConfirm=true+ aWarningstring. 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 — seeFollowUpFlowTests). Treats a blank branch as the implied "master" default so the pre-FLT-139Branch ?? "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 — unlikeRepoRegistryReader's signed fleet-wide inventory). Fail-closed: any read/parse error returns empty, never throws.IsLive(entry, now)mirrors worktree-identity's ownRegistry.IsLiveOwnerminus the PID-liveness leg.- Contracts (
Contracts/CommandBarContracts.cs):BranchChip+BranchCandidateDtoadded toCommandResolution;CommandResolveRequest.Target(which PC to scope worktree lookup to) andCommandResolveOverrides.Branchadded;CommandDispatchRequest.AllowMasterDispatchadded. CommandResolverServicenow takesWorktreeIdentityReader identityand assembles the branch chip after the repo chip (needsrepoResult.RepoId+request.Targetto find theRepoPcEntry), usingticketChip.Keyfor the AUTO path's ticket match.CommandEndpoints.MapCommandEndpointsdispatch: callsBranchResolver.ValidateNotMasterbefore resolving the repo path; on failure returns 400. The pre-existingBranch ?? "master"default is now computed explicitly (string.IsNullOrWhiteSpace(body.Branch) ? "master" : ...) so the guard sees the same value that goes intoAgentRunRequest.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 fromr.branch.candidates). Changing the target PC re-resolves and clears any branch override (onTargetChange). Deliberate P1 simplification: the UI never sendsallowMasterDispatch—canDispatch()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, assemblesWorktreeHistoryResponsefrom aRepoPcEntry+WorktreeIdentityReaderentries; 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 existingGET /repos) —RequireFleetAuth(per the FLT-139 spec; unlike the plain/reposGET, this one also surfaces WT-id lease/session context). 400 whentargetis missing. Registry-only, fail-closed, never a filesystem walk.- Contracts (
Contracts/WorktreeHistoryContracts.cs):WorktreeIdentityEntry(a lean local mirror of worktree-identity'sRegistryEntry— 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) — mirrorsTranscriptPanelComponent'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▤ worktreesbutton 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)
npx ng build
Application bundle generation complete. [22.955 seconds]
Output location: C:\Users\fives\source\repos\fmc-flt139\frontend\dist\frontend
npx ng lint
Linting "frontend"...
All files pass linting.
npx vitest run
Test Files 22 passed (22)
Tests 247 passed (247)
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 returnsAuto=truefor 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 throughValidateNotMasterfirst — a caller that omitsBranchentirely (every pre-FLT-139 caller) is now rejected with 400 unless it also setsAllowMasterDispatch: 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.
WorktreeIdentityReaderonly ever sees the registry on the machine the API process is running on — atargetPC other than the API host gets zero identity enrichment (never fabricated, just absent). Syncing this the wayrepo-inventoryis synced (signed per-PC files under the shared OneDrive dir) is real scanner-side work, same category as the already-acknowledgedRepoRegistryReaderscanner TODO. - Fresh-worktree proposal is informational only.
BranchResolver'sfresh-worktreesource proposes awt/WT-xxxx/<ticket>name but creates nothing — dispatch still requires an already-indexed worktree path (pre-existingRepoRegistryReader.Resolvebehavior, untouched). Actually creating the worktree + minting its realWT-xxxxid via worktree-identity is a separate write path this ticket doesn't touch. - No
allowMasterDispatchUI 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.