From 38608722c9041d3a55cdc20364aec89946b2d9fc Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 13 Mar 2026 20:51:01 +0100 Subject: [PATCH 1/8] tests and fixes --- AGENTS.md | 2 + .../DeterministicAgentRuntimeClient.cs | 10 ++-- .../ProviderToolchainProbe.cs | 2 +- .../RuntimeFoundationCatalog.cs | 2 +- .../RuntimeFoundationDeterministicIdentity.cs | 34 +++++++++++++ DotPilot.Tests/AGENTS.md | 3 +- .../ApplicationShell/AppConfigTests.cs} | 4 +- .../ControlPlaneDomainContractsTests.cs | 5 +- .../HttpDiagnostics}/DebugHttpHandlerTests.cs | 2 +- .../RuntimeCommunicationProblemsTests.cs | 2 +- .../RuntimeFoundationCatalogTests.cs | 50 ++++++++++++++++++- .../Workbench}/PresentationViewModelTests.cs | 2 +- .../Workbench}/TemporaryWorkbenchDirectory.cs | 2 +- .../Workbench}/WorkbenchCatalogTests.cs | 2 +- DotPilot.UITests/AGENTS.md | 6 +-- .../Workbench/Given_WorkbenchShell.cs} | 6 ++- .../{ => Harness}/BoundedCleanup.cs | 2 +- .../{ => Harness}/BoundedCleanupTests.cs | 2 +- .../BrowserAutomationBootstrap.cs | 2 +- .../BrowserAutomationBootstrapTests.cs | 2 +- .../{ => Harness}/BrowserTestEnvironment.cs | 2 +- .../{ => Harness}/BrowserTestHost.cs | 2 +- .../{ => Harness}/BrowserTestHostTests.cs | 2 +- DotPilot.UITests/{ => Harness}/Constants.cs | 2 +- DotPilot.UITests/{ => Harness}/HarnessLog.cs | 2 +- DotPilot.UITests/{ => Harness}/TestBase.cs | 2 +- 26 files changed, 121 insertions(+), 33 deletions(-) create mode 100644 DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationDeterministicIdentity.cs rename DotPilot.Tests/{AppInfoTests.cs => Features/ApplicationShell/AppConfigTests.cs} (76%) rename DotPilot.Tests/{ => Features/ControlPlaneDomain}/ControlPlaneDomainContractsTests.cs (97%) rename DotPilot.Tests/{ => Features/HttpDiagnostics}/DebugHttpHandlerTests.cs (98%) rename DotPilot.Tests/{ => Features/RuntimeCommunication}/RuntimeCommunicationProblemsTests.cs (98%) rename DotPilot.Tests/{ => Features/RuntimeFoundation}/RuntimeFoundationCatalogTests.cs (79%) rename DotPilot.Tests/{ => Features/Workbench}/PresentationViewModelTests.cs (98%) rename DotPilot.Tests/{ => Features/Workbench}/TemporaryWorkbenchDirectory.cs (97%) rename DotPilot.Tests/{ => Features/Workbench}/WorkbenchCatalogTests.cs (97%) rename DotPilot.UITests/{Given_MainPage.cs => Features/Workbench/Given_WorkbenchShell.cs} (99%) rename DotPilot.UITests/{ => Harness}/BoundedCleanup.cs (98%) rename DotPilot.UITests/{ => Harness}/BoundedCleanupTests.cs (97%) rename DotPilot.UITests/{ => Harness}/BrowserAutomationBootstrap.cs (99%) rename DotPilot.UITests/{ => Harness}/BrowserAutomationBootstrapTests.cs (99%) rename DotPilot.UITests/{ => Harness}/BrowserTestEnvironment.cs (97%) rename DotPilot.UITests/{ => Harness}/BrowserTestHost.cs (99%) rename DotPilot.UITests/{ => Harness}/BrowserTestHostTests.cs (96%) rename DotPilot.UITests/{ => Harness}/Constants.cs (93%) rename DotPilot.UITests/{ => Harness}/HarnessLog.cs (95%) rename DotPilot.UITests/{ => Harness}/TestBase.cs (99%) diff --git a/AGENTS.md b/AGENTS.md index e258b64..c425795 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -143,6 +143,7 @@ For this app: - the repo-root lowercase `.editorconfig` is the source of truth for formatting, naming, style, and analyzer severity - local and CI build commands must pass `-warnaserror`; warnings are not an acceptable "green" build state in this repository - do not run parallel `dotnet` or `MSBuild` work that shares the same checkout, target outputs, or NuGet package cache; the multi-target Uno app must build serially in CI to avoid `Uno.Resizetizer` file-lock failures +- do not commit user-specific local paths, usernames, or machine-specific identifiers in tests, docs, snapshots, or fixtures; use neutral synthetic values so the repo stays portable and does not leak personal machine details - quality gates should prefer analyzer-backed build failures over separate one-off CI tools; for overloaded methods and maintainability drift, enable build-time analyzers such as `CA1502` instead of adding a formatting-only gate - `Directory.Build.props` owns the shared analyzer and warning policy for future projects - `Directory.Packages.props` owns centrally managed package versions @@ -150,6 +151,7 @@ For this app: - `DotPilot/DotPilot.csproj` keeps `GenerateDocumentationFile=true` with `CS1591` suppressed so `IDE0005` stays enforceable in CI across all target frameworks without inventing command-line-only build flags - architecture work must keep a vertical-slice shape: each feature owns its contracts, orchestration, and tests behind clear boundaries instead of growing a shared horizontal service layer - keep the Uno app project presentation-only; domain, runtime host, orchestration, integrations, and persistence code must live in separate class-library projects so UI composition does not mix with feature implementation +- structure both `DotPilot.Tests` and `DotPilot.UITests` by vertical slice and explicit harness boundaries; do not keep test files in one flat project-root pile - GitHub Actions workflows must use descriptive names and filenames that reflect their purpose; do not use a generic `ci.yml` catch-all because build validation and release automation are separate operator flows - GitHub Actions must be split into at least one validation workflow for normal builds/tests and one release workflow for CI-driven version resolution, release-note generation, desktop publishing, and GitHub Release publication - meaningful GitHub review comments must be evaluated and fixed when they still apply even if the original PR was closed; closed review threads are not a reason to ignore valid engineering feedback diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs b/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs index 9fec5b3..c6b6bdd 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using DotPilot.Core.Features.ControlPlaneDomain; using DotPilot.Core.Features.RuntimeCommunication; using DotPilot.Core.Features.RuntimeFoundation; @@ -8,7 +9,6 @@ namespace DotPilot.Runtime.Features.RuntimeFoundation; public sealed class DeterministicAgentRuntimeClient : IAgentRuntimeClient { private const string ApprovalKeyword = "approval"; - private const string DeterministicProviderDisplayName = "Deterministic Runtime Client"; private const string PlanSummary = "Planned the runtime foundation flow with contracts first, then communication, host lifecycle, and orchestration."; private const string ExecuteSummary = @@ -35,7 +35,7 @@ public ValueTask> ExecuteAsync(AgentTurnRequest request, Result.Fail( RuntimeCommunicationProblems.ProviderUnavailable( request.ProviderStatus, - DeterministicProviderDisplayName))); + ProviderToolchainNames.DeterministicClientDisplayName))); } return ValueTask.FromResult(request.Mode switch @@ -64,7 +64,7 @@ AgentExecutionMode.Execute when RequiresApproval(request.Prompt) => Result Result.Fail(RuntimeCommunicationProblems.OrchestrationUnavailable()), + _ => throw new UnreachableException(), }); } @@ -77,12 +77,12 @@ private static ArtifactDescriptor CreateArtifact(SessionId sessionId, string art { return new ArtifactDescriptor { - Id = ArtifactId.New(), + Id = RuntimeFoundationDeterministicIdentity.CreateArtifactId(sessionId, artifactName), SessionId = sessionId, Name = artifactName, Kind = artifactKind, RelativePath = artifactName, - CreatedAt = DateTimeOffset.UtcNow, + CreatedAt = RuntimeFoundationDeterministicIdentity.ArtifactCreatedAt, }; } } diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs b/DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs index 7065d50..5645132 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs @@ -26,7 +26,7 @@ status is ProviderConnectionStatus.Available return new ProviderDescriptor { - Id = ProviderId.New(), + Id = RuntimeFoundationDeterministicIdentity.CreateProviderId(commandName), DisplayName = displayName, CommandName = commandName, Status = status, diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs index 7989231..3e66006 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs @@ -72,7 +72,7 @@ private static IReadOnlyList CreateProviders() [ new ProviderDescriptor { - Id = ProviderId.New(), + Id = RuntimeFoundationDeterministicIdentity.CreateProviderId(ProviderToolchainNames.DeterministicClientCommandName), DisplayName = ProviderToolchainNames.DeterministicClientDisplayName, CommandName = ProviderToolchainNames.DeterministicClientCommandName, Status = ProviderConnectionStatus.Available, diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationDeterministicIdentity.cs b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationDeterministicIdentity.cs new file mode 100644 index 0000000..a06f8a4 --- /dev/null +++ b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationDeterministicIdentity.cs @@ -0,0 +1,34 @@ +using System.Security.Cryptography; +using System.Text; +using DotPilot.Core.Features.ControlPlaneDomain; + +namespace DotPilot.Runtime.Features.RuntimeFoundation; + +internal static class RuntimeFoundationDeterministicIdentity +{ + private const string ArtifactSeedPrefix = "runtime-foundation-artifact"; + private const string ProviderSeedPrefix = "runtime-foundation-provider"; + private const string SeedSeparator = "|"; + + public static DateTimeOffset ArtifactCreatedAt { get; } = new(2026, 3, 13, 0, 0, 0, TimeSpan.Zero); + + public static ArtifactId CreateArtifactId(SessionId sessionId, string artifactName) + { + return new(CreateGuid(string.Concat(ArtifactSeedPrefix, SeedSeparator, sessionId, SeedSeparator, artifactName))); + } + + public static ProviderId CreateProviderId(string commandName) + { + return new(CreateGuid(string.Concat(ProviderSeedPrefix, SeedSeparator, commandName))); + } + + private static Guid CreateGuid(string seed) + { + var hash = SHA256.HashData(Encoding.UTF8.GetBytes(seed)); + Span guidBytes = stackalloc byte[16]; + hash[..guidBytes.Length].CopyTo(guidBytes); + guidBytes[7] = (byte)((guidBytes[7] & 0x0F) | 0x80); + guidBytes[8] = (byte)((guidBytes[8] & 0x3F) | 0x80); + return new Guid(guidBytes); + } +} diff --git a/DotPilot.Tests/AGENTS.md b/DotPilot.Tests/AGENTS.md index d76d529..31a3c9b 100644 --- a/DotPilot.Tests/AGENTS.md +++ b/DotPilot.Tests/AGENTS.md @@ -11,7 +11,7 @@ Stack: `.NET 10`, `NUnit`, `FluentAssertions`, `coverlet.collector` ## Entry Points - `DotPilot.Tests.csproj` -- `AppInfoTests.cs` +- `Features/*` ## Boundaries @@ -21,6 +21,7 @@ Stack: `.NET 10`, `NUnit`, `FluentAssertions`, `coverlet.collector` - Prefer production-facing flows and public contracts over implementation-detail assertions. - Keep a deterministic in-repo test AI client available for CI so provider-independent agent flows remain testable even when Codex, Claude Code, or GitHub Copilot are unavailable. - Tests that require real provider CLIs or auth must detect availability and run only when the corresponding external toolchain is present. +- Organize test files by feature slice, with shared helpers living next to the slice that owns them instead of a flat project root. ## Local Commands diff --git a/DotPilot.Tests/AppInfoTests.cs b/DotPilot.Tests/Features/ApplicationShell/AppConfigTests.cs similarity index 76% rename from DotPilot.Tests/AppInfoTests.cs rename to DotPilot.Tests/Features/ApplicationShell/AppConfigTests.cs index cd80c3a..f9b9b0f 100644 --- a/DotPilot.Tests/AppInfoTests.cs +++ b/DotPilot.Tests/Features/ApplicationShell/AppConfigTests.cs @@ -1,6 +1,6 @@ -namespace DotPilot.Tests; +namespace DotPilot.Tests.Features.ApplicationShell; -public class AppInfoTests +public class AppConfigTests { [SetUp] public void Setup() diff --git a/DotPilot.Tests/ControlPlaneDomainContractsTests.cs b/DotPilot.Tests/Features/ControlPlaneDomain/ControlPlaneDomainContractsTests.cs similarity index 97% rename from DotPilot.Tests/ControlPlaneDomainContractsTests.cs rename to DotPilot.Tests/Features/ControlPlaneDomain/ControlPlaneDomainContractsTests.cs index 1593b0d..6b6d81a 100644 --- a/DotPilot.Tests/ControlPlaneDomainContractsTests.cs +++ b/DotPilot.Tests/Features/ControlPlaneDomain/ControlPlaneDomainContractsTests.cs @@ -1,9 +1,10 @@ using System.Text.Json; -namespace DotPilot.Tests; +namespace DotPilot.Tests.Features.ControlPlaneDomain; public class ControlPlaneDomainContractsTests { + private const string SyntheticWorkspaceRootPath = "/repo/dotPilot"; private static readonly DateTimeOffset CreatedAt = new(2026, 3, 13, 10, 15, 30, TimeSpan.Zero); private static readonly DateTimeOffset UpdatedAt = new(2026, 3, 13, 10, 45, 30, TimeSpan.Zero); private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web); @@ -97,7 +98,7 @@ private static ControlPlaneDomainEnvelope CreateEnvelope() { Id = WorkspaceId.New(), Name = "dotPilot", - RootPath = "/Users/ksemenenko/Developer/dotPilot", + RootPath = SyntheticWorkspaceRootPath, BranchName = "codex/issue-22-domain-model", }; diff --git a/DotPilot.Tests/DebugHttpHandlerTests.cs b/DotPilot.Tests/Features/HttpDiagnostics/DebugHttpHandlerTests.cs similarity index 98% rename from DotPilot.Tests/DebugHttpHandlerTests.cs rename to DotPilot.Tests/Features/HttpDiagnostics/DebugHttpHandlerTests.cs index b8a11f0..2c40652 100644 --- a/DotPilot.Tests/DebugHttpHandlerTests.cs +++ b/DotPilot.Tests/Features/HttpDiagnostics/DebugHttpHandlerTests.cs @@ -1,7 +1,7 @@ using System.Net; using DotPilot.Runtime.Features.HttpDiagnostics; -namespace DotPilot.Tests; +namespace DotPilot.Tests.Features.HttpDiagnostics; public class DebugHttpHandlerTests { diff --git a/DotPilot.Tests/RuntimeCommunicationProblemsTests.cs b/DotPilot.Tests/Features/RuntimeCommunication/RuntimeCommunicationProblemsTests.cs similarity index 98% rename from DotPilot.Tests/RuntimeCommunicationProblemsTests.cs rename to DotPilot.Tests/Features/RuntimeCommunication/RuntimeCommunicationProblemsTests.cs index 3706f9a..547b1e9 100644 --- a/DotPilot.Tests/RuntimeCommunicationProblemsTests.cs +++ b/DotPilot.Tests/Features/RuntimeCommunication/RuntimeCommunicationProblemsTests.cs @@ -1,4 +1,4 @@ -namespace DotPilot.Tests; +namespace DotPilot.Tests.Features.RuntimeCommunication; public class RuntimeCommunicationProblemsTests { diff --git a/DotPilot.Tests/RuntimeFoundationCatalogTests.cs b/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs similarity index 79% rename from DotPilot.Tests/RuntimeFoundationCatalogTests.cs rename to DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs index 2bf0d9c..be0177b 100644 --- a/DotPilot.Tests/RuntimeFoundationCatalogTests.cs +++ b/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs @@ -1,4 +1,4 @@ -namespace DotPilot.Tests; +namespace DotPilot.Tests.Features.RuntimeFoundation; public class RuntimeFoundationCatalogTests { @@ -8,6 +8,7 @@ public class RuntimeFoundationCatalogTests private const string ClaudeCommandName = "claude"; private const string GitHubCommandName = "gh"; private const string DeterministicClientStatusSummary = "Always available for in-repo and CI validation."; + private static readonly DateTimeOffset DeterministicArtifactCreatedAt = new(2026, 3, 13, 0, 0, 0, TimeSpan.Zero); [Test] public void CatalogGroupsEpicTwelveIntoFourSequencedSlices() @@ -71,6 +72,24 @@ public async Task DeterministicClientReturnsPlanArtifactsForPlanMode() artifact.Kind == ArtifactKind.Plan); } + [Test] + public async Task DeterministicClientProducesStableArtifactsForIdenticalRequests() + { + var client = new DeterministicAgentRuntimeClient(); + var request = CreateRequest("Run the provider-independent runtime flow.", AgentExecutionMode.Execute); + + var firstResult = await client.ExecuteAsync(request, CancellationToken.None); + var secondResult = await client.ExecuteAsync(request, CancellationToken.None); + var firstArtifact = firstResult.Value!.ProducedArtifacts.Should().ContainSingle().Subject; + var secondArtifact = secondResult.Value!.ProducedArtifacts.Should().ContainSingle().Subject; + + firstResult.IsSuccess.Should().BeTrue(); + secondResult.IsSuccess.Should().BeTrue(); + firstArtifact.Id.Should().Be(secondArtifact.Id); + firstArtifact.CreatedAt.Should().Be(DeterministicArtifactCreatedAt); + secondArtifact.CreatedAt.Should().Be(DeterministicArtifactCreatedAt); + } + [Test] public async Task DeterministicClientReturnsExecuteResultsWhenApprovalIsNotRequested() { @@ -133,6 +152,7 @@ public async Task DeterministicClientReturnsApprovedReviewResults() public async Task DeterministicClientReturnsProviderUnavailableProblemWhenProviderIsNotReady() { var client = new DeterministicAgentRuntimeClient(); + var snapshot = CreateCatalog().GetSnapshot(); var result = await client.ExecuteAsync( CreateRequest( @@ -146,6 +166,18 @@ public async Task DeterministicClientReturnsProviderUnavailableProblemWhenProvid result.HasProblem.Should().BeTrue(); problem.HasErrorCode(RuntimeCommunicationProblemCode.ProviderUnavailable).Should().BeTrue(); problem.StatusCode.Should().Be((int)System.Net.HttpStatusCode.ServiceUnavailable); + problem.Detail.Should().Contain(snapshot.DeterministicClientName); + } + + [Test] + public void DeterministicClientRejectsUnexpectedExecutionModes() + { + var client = new DeterministicAgentRuntimeClient(); + var invalidRequest = CreateRequest("Plan the runtime foundation rollout.", (AgentExecutionMode)int.MaxValue); + + var action = () => client.ExecuteAsync(invalidRequest, CancellationToken.None); + + action.Should().Throw(); } [TestCase(CodexCommandName)] @@ -165,6 +197,22 @@ public void ExternalToolchainVerificationRunsOnlyWhenTheCommandIsAvailable(strin provider.StatusSummary.Should().Contain("available"); } + [Test] + public void CatalogPreservesProviderIdentityAcrossSnapshotRefreshes() + { + var catalog = CreateCatalog(); + + var firstSnapshot = catalog.GetSnapshot(); + var secondSnapshot = catalog.GetSnapshot(); + + firstSnapshot.Providers.Should().HaveSameCount(secondSnapshot.Providers); + foreach (var firstProvider in firstSnapshot.Providers) + { + var secondProvider = secondSnapshot.Providers.Single(provider => provider.CommandName == firstProvider.CommandName); + firstProvider.Id.Should().Be(secondProvider.Id); + } + } + [Test] public void TypedIdentifiersProduceStableNonEmptyRepresentations() { diff --git a/DotPilot.Tests/PresentationViewModelTests.cs b/DotPilot.Tests/Features/Workbench/PresentationViewModelTests.cs similarity index 98% rename from DotPilot.Tests/PresentationViewModelTests.cs rename to DotPilot.Tests/Features/Workbench/PresentationViewModelTests.cs index e06e744..84787e1 100644 --- a/DotPilot.Tests/PresentationViewModelTests.cs +++ b/DotPilot.Tests/Features/Workbench/PresentationViewModelTests.cs @@ -1,7 +1,7 @@ using DotPilot.Presentation; using DotPilot.Runtime.Features.Workbench; -namespace DotPilot.Tests; +namespace DotPilot.Tests.Features.Workbench; public class PresentationViewModelTests { diff --git a/DotPilot.Tests/TemporaryWorkbenchDirectory.cs b/DotPilot.Tests/Features/Workbench/TemporaryWorkbenchDirectory.cs similarity index 97% rename from DotPilot.Tests/TemporaryWorkbenchDirectory.cs rename to DotPilot.Tests/Features/Workbench/TemporaryWorkbenchDirectory.cs index 7a37770..c772e26 100644 --- a/DotPilot.Tests/TemporaryWorkbenchDirectory.cs +++ b/DotPilot.Tests/Features/Workbench/TemporaryWorkbenchDirectory.cs @@ -1,4 +1,4 @@ -namespace DotPilot.Tests; +namespace DotPilot.Tests.Features.Workbench; internal sealed class TemporaryWorkbenchDirectory : IDisposable { diff --git a/DotPilot.Tests/WorkbenchCatalogTests.cs b/DotPilot.Tests/Features/Workbench/WorkbenchCatalogTests.cs similarity index 97% rename from DotPilot.Tests/WorkbenchCatalogTests.cs rename to DotPilot.Tests/Features/Workbench/WorkbenchCatalogTests.cs index 676a291..75f34ad 100644 --- a/DotPilot.Tests/WorkbenchCatalogTests.cs +++ b/DotPilot.Tests/Features/Workbench/WorkbenchCatalogTests.cs @@ -1,6 +1,6 @@ using DotPilot.Runtime.Features.Workbench; -namespace DotPilot.Tests; +namespace DotPilot.Tests.Features.Workbench; public class WorkbenchCatalogTests { diff --git a/DotPilot.UITests/AGENTS.md b/DotPilot.UITests/AGENTS.md index 68322c1..9228e28 100644 --- a/DotPilot.UITests/AGENTS.md +++ b/DotPilot.UITests/AGENTS.md @@ -11,9 +11,8 @@ Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven UI tests ## Entry Points - `DotPilot.UITests.csproj` -- `Constants.cs` -- `TestBase.cs` -- `Given_MainPage.cs` +- `Harness/*` +- `Features/*` ## Boundaries @@ -22,6 +21,7 @@ Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven UI tests - Treat browser-driver setup and app-launch prerequisites as part of the harness, not as assumptions inside individual tests. - The harness must make `dotnet test DotPilot.UITests/DotPilot.UITests.csproj` runnable without manual driver-path export and must fail loudly instead of silently skipping coverage. - Keep the harness direct and minimal; prefer the smallest deterministic setup needed to run the suite and return a real result. +- Organize files by feature slice and harness boundary: browser/bootstrap infrastructure under harness folders, feature-flow tests under the slice they verify. - Use the official `Uno` MCP documentation as the source of truth for `Uno.UITest` browser behavior, and align selectors with the documented WebAssembly automation mapping before changing the harness. - Do not manually launch the app or a standalone `browserwasm` host while working on this project; browser-path reproduction and debugging must go through `dotnet test` and the real `DotPilot.UITests` harness only. - UI tests must cover each feature's interactive elements, expected behaviors, and full operator flows instead of only a top-level smoke path. diff --git a/DotPilot.UITests/Given_MainPage.cs b/DotPilot.UITests/Features/Workbench/Given_WorkbenchShell.cs similarity index 99% rename from DotPilot.UITests/Given_MainPage.cs rename to DotPilot.UITests/Features/Workbench/Given_WorkbenchShell.cs index 3b26a5c..ddbf033 100644 --- a/DotPilot.UITests/Given_MainPage.cs +++ b/DotPilot.UITests/Features/Workbench/Given_WorkbenchShell.cs @@ -1,7 +1,9 @@ -namespace DotPilot.UITests; +using DotPilot.UITests.Harness; + +namespace DotPilot.UITests.Features.Workbench; [NonParallelizable] -public class GivenMainPage : TestBase +public class GivenWorkbenchShell : TestBase { private static readonly TimeSpan InitialScreenProbeTimeout = TimeSpan.FromSeconds(30); private static readonly TimeSpan ScreenTransitionTimeout = TimeSpan.FromSeconds(60); diff --git a/DotPilot.UITests/BoundedCleanup.cs b/DotPilot.UITests/Harness/BoundedCleanup.cs similarity index 98% rename from DotPilot.UITests/BoundedCleanup.cs rename to DotPilot.UITests/Harness/BoundedCleanup.cs index 988278b..ef499e1 100644 --- a/DotPilot.UITests/BoundedCleanup.cs +++ b/DotPilot.UITests/Harness/BoundedCleanup.cs @@ -1,4 +1,4 @@ -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; internal static class BoundedCleanup { diff --git a/DotPilot.UITests/BoundedCleanupTests.cs b/DotPilot.UITests/Harness/BoundedCleanupTests.cs similarity index 97% rename from DotPilot.UITests/BoundedCleanupTests.cs rename to DotPilot.UITests/Harness/BoundedCleanupTests.cs index a766d17..67305a1 100644 --- a/DotPilot.UITests/BoundedCleanupTests.cs +++ b/DotPilot.UITests/Harness/BoundedCleanupTests.cs @@ -1,4 +1,4 @@ -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; [TestFixture] public sealed class BoundedCleanupTests diff --git a/DotPilot.UITests/BrowserAutomationBootstrap.cs b/DotPilot.UITests/Harness/BrowserAutomationBootstrap.cs similarity index 99% rename from DotPilot.UITests/BrowserAutomationBootstrap.cs rename to DotPilot.UITests/Harness/BrowserAutomationBootstrap.cs index 9d151a5..863c814 100644 --- a/DotPilot.UITests/BrowserAutomationBootstrap.cs +++ b/DotPilot.UITests/Harness/BrowserAutomationBootstrap.cs @@ -5,7 +5,7 @@ using System.Text.Json; using System.Text.RegularExpressions; -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; internal static partial class BrowserAutomationBootstrap { diff --git a/DotPilot.UITests/BrowserAutomationBootstrapTests.cs b/DotPilot.UITests/Harness/BrowserAutomationBootstrapTests.cs similarity index 99% rename from DotPilot.UITests/BrowserAutomationBootstrapTests.cs rename to DotPilot.UITests/Harness/BrowserAutomationBootstrapTests.cs index afa42a4..5f70216 100644 --- a/DotPilot.UITests/BrowserAutomationBootstrapTests.cs +++ b/DotPilot.UITests/Harness/BrowserAutomationBootstrapTests.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; [TestFixture] public sealed class BrowserAutomationBootstrapTests diff --git a/DotPilot.UITests/BrowserTestEnvironment.cs b/DotPilot.UITests/Harness/BrowserTestEnvironment.cs similarity index 97% rename from DotPilot.UITests/BrowserTestEnvironment.cs rename to DotPilot.UITests/Harness/BrowserTestEnvironment.cs index 21b7a80..982f1f2 100644 --- a/DotPilot.UITests/BrowserTestEnvironment.cs +++ b/DotPilot.UITests/Harness/BrowserTestEnvironment.cs @@ -1,7 +1,7 @@ using System.Net; using System.Net.Sockets; -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; internal static class BrowserTestEnvironment { diff --git a/DotPilot.UITests/BrowserTestHost.cs b/DotPilot.UITests/Harness/BrowserTestHost.cs similarity index 99% rename from DotPilot.UITests/BrowserTestHost.cs rename to DotPilot.UITests/Harness/BrowserTestHost.cs index f704c24..cdaa657 100644 --- a/DotPilot.UITests/BrowserTestHost.cs +++ b/DotPilot.UITests/Harness/BrowserTestHost.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; internal static class BrowserTestHost { diff --git a/DotPilot.UITests/BrowserTestHostTests.cs b/DotPilot.UITests/Harness/BrowserTestHostTests.cs similarity index 96% rename from DotPilot.UITests/BrowserTestHostTests.cs rename to DotPilot.UITests/Harness/BrowserTestHostTests.cs index b894487..135b784 100644 --- a/DotPilot.UITests/BrowserTestHostTests.cs +++ b/DotPilot.UITests/Harness/BrowserTestHostTests.cs @@ -1,4 +1,4 @@ -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; [TestFixture] public sealed class BrowserTestHostTests diff --git a/DotPilot.UITests/Constants.cs b/DotPilot.UITests/Harness/Constants.cs similarity index 93% rename from DotPilot.UITests/Constants.cs rename to DotPilot.UITests/Harness/Constants.cs index 8822516..6687b32 100644 --- a/DotPilot.UITests/Constants.cs +++ b/DotPilot.UITests/Harness/Constants.cs @@ -1,4 +1,4 @@ -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; public class Constants { diff --git a/DotPilot.UITests/HarnessLog.cs b/DotPilot.UITests/Harness/HarnessLog.cs similarity index 95% rename from DotPilot.UITests/HarnessLog.cs rename to DotPilot.UITests/Harness/HarnessLog.cs index 78f2856..2cbe1c0 100644 --- a/DotPilot.UITests/HarnessLog.cs +++ b/DotPilot.UITests/Harness/HarnessLog.cs @@ -1,4 +1,4 @@ -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; internal static class HarnessLog { diff --git a/DotPilot.UITests/TestBase.cs b/DotPilot.UITests/Harness/TestBase.cs similarity index 99% rename from DotPilot.UITests/TestBase.cs rename to DotPilot.UITests/Harness/TestBase.cs index 2832d34..64d3ab5 100644 --- a/DotPilot.UITests/TestBase.cs +++ b/DotPilot.UITests/Harness/TestBase.cs @@ -1,5 +1,5 @@ -namespace DotPilot.UITests; +namespace DotPilot.UITests.Harness; [System.Diagnostics.CodeAnalysis.SuppressMessage( "Performance", From e0e7c824fea03bfaef98c8f8da2261fa6a879754 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 13 Mar 2026 20:51:51 +0100 Subject: [PATCH 2/8] remove plans --- CodeMetricsConfig.txt | 3 - issue-22-domain-model.plan.md | 110 ------------------ issue-23-communication-contracts.plan.md | 110 ------------------ vertical-slice-runtime-foundation.plan.md | 129 ---------------------- 4 files changed, 352 deletions(-) delete mode 100644 CodeMetricsConfig.txt delete mode 100644 issue-22-domain-model.plan.md delete mode 100644 issue-23-communication-contracts.plan.md delete mode 100644 vertical-slice-runtime-foundation.plan.md diff --git a/CodeMetricsConfig.txt b/CodeMetricsConfig.txt deleted file mode 100644 index e3bf457..0000000 --- a/CodeMetricsConfig.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Tighten maintainability checks enough to catch genuinely overloaded methods -# without turning the first pass into pure noise. -CA1502(Method): 15 diff --git a/issue-22-domain-model.plan.md b/issue-22-domain-model.plan.md deleted file mode 100644 index 71b7e72..0000000 --- a/issue-22-domain-model.plan.md +++ /dev/null @@ -1,110 +0,0 @@ -# Issue 22 Domain Model Plan - -## Goal - -Define the first stable `dotPilot` control-plane domain contracts for agents, sessions, fleets, providers, runtimes, tools, artifacts, telemetry, approvals, and evaluations so later runtime and UI slices can build on versionable, serialization-safe shapes. - -## Scope - -### In Scope - -- Introduce an issue-aligned domain slice in `DotPilot.Core` for the control-plane model behind issue `#22` -- Move typed identifiers and state enums out of the temporary runtime-foundation slice into the new domain slice -- Define serialization-safe records for agents, sessions, fleets, providers, runtimes, tool capabilities, approvals, artifacts, telemetry, and evaluations -- Update existing runtime-foundation contracts to depend on the new domain slice instead of owning domain primitives directly -- Update architecture and feature documentation so issue `#22` relationships are explicit and navigable -- Add automated tests for identifier semantics, DTO round-tripping, and the updated runtime-foundation composition path - -### Out Of Scope - -- Concrete Orleans grain implementations -- Microsoft Agent Framework runtime orchestration -- Live provider SDK adapters -- UI redesign beyond the minimum text or binding updates required by the new contract names - -## Constraints And Risks - -- Keep the Uno app presentation-only; the domain model must live outside `DotPilot` -- Preserve the already-reviewed runtime-foundation slice while extracting shared domain concepts from it -- Prefer versionable DTO shapes that remain safe for `System.Text.Json` serialization and future expansion -- Use modern stable `.NET 10` and `C#` features only when they improve clarity and maintainability -- Keep branch and type boundaries explicit so issue `#23`, `#24`, and `#25` can reference the new contracts directly -- Protect the deterministic CI baseline; external provider availability remains optional and self-gated - -## Testing Methodology - -- Baseline proof: run the full solution test command after this plan is created -- Contract proof: add focused tests for typed identifiers, default-safe DTO shapes, and JSON round-tripping of the new domain records -- Integration proof: rerun the runtime-foundation tests that consume the moved domain contracts -- UI proof: rerun the full UI suite to confirm the presentation host still renders the runtime foundation surfaces after the contract move -- Final proof: run repo-ordered validation with `format`, `build`, full `test`, and the coverage command -- Quality bar: domain contracts remain deterministic, future-facing, and serialization-safe; the full solution stays green; coverage remains at or above the prior baseline - -## Ordered Plan - -- [x] Step 1: Establish the full baseline for the new branch after the plan is prepared. - Verification: - - `dotnet test DotPilot.slnx` - Done when: this file records whether any failures predate the issue `#22` changes. - -- [x] Step 2: Introduce the `#22` control-plane domain slice in `DotPilot.Core`. - Verification: typed identifiers, shared state enums, and the new domain DTOs live under an issue-aligned feature folder instead of the runtime-foundation slice. - Done when: agents, sessions, fleets, providers, runtimes, tools, artifacts, telemetry, approvals, and evaluations all have stable contract shapes. - -- [x] Step 3: Rewire the runtime-foundation slice to depend on the new domain contracts. - Verification: `RuntimeFoundation` keeps its feature-specific contracts, but all reused domain primitives come from the `#22` domain slice. - Done when: runtime-foundation no longer owns cross-cutting control-plane primitives. - -- [x] Step 4: Update durable docs for issue `#22`. - Verification: architecture and feature docs show the new domain slice, its relationships, and how later issues build on it. - Done when: future agents can trace `#22` from docs without reverse-engineering the code. - -- [x] Step 5: Add or update automated tests for the new domain model and moved runtime dependencies. - Verification: tests cover identifier creation and formatting, JSON round-tripping, representative domain DTOs, and the runtime slice's continued behavior. - Done when: the moved contracts are protected by meaningful assertions instead of compile-only confidence. - -- [x] Step 6: Run final validation in repo order and record the results. - Verification: - - `dotnet format DotPilot.slnx --verify-no-changes` - - `dotnet build DotPilot.slnx -warnaserror` - - `dotnet test DotPilot.slnx` - - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` - Done when: the commands are green or any remaining blocker is explicitly documented here. - -- [x] Step 7: Create the PR for issue `#22`, then continue from a fresh branch for the next slice. - Verification: the branch and PR flow matches the enforced repo workflow after completing a slice. - Done when: the issue `#22` PR exists and follow-up work no longer accumulates on its review branch. - -## Validation Notes - -- `dotnet test DotPilot.slnx` passed with `0` failed, `34` passed, and `0` skipped on the new `codex/issue-22-domain-model` branch baseline. -- `DotPilot.Core/Features/ControlPlaneDomain/*` now owns typed identifiers, shared state enums, and stable DTOs for workspaces, agents, fleets, providers, runtimes, approvals, artifacts, telemetry, and evaluations. -- `DotPilot.Core/Features/RuntimeFoundation/RuntimeFoundationContracts.cs` now consumes `ProviderDescriptor` and `ArtifactDescriptor` from the `#22` domain slice instead of owning those cross-cutting shapes locally. -- `DotPilot.Runtime/Features/RuntimeFoundation/*` now builds provider readiness and deterministic runtime artifacts on top of the shared control-plane domain contracts. -- `docs/Features/control-plane-domain-model.md` documents the issue `#22` relationships and downstream issue references with a Mermaid map. -- `DotPilot.Tests/ControlPlaneDomainContractsTests.cs` adds identifier, JSON round-trip, and mixed provider/local-runtime session coverage for the new contract set. -- `dotnet format DotPilot.slnx --verify-no-changes` passed. -- `dotnet build DotPilot.slnx -warnaserror` passed with `0` warnings and `0` errors. -- `dotnet test DotPilot.slnx` passed with `0` failed, `37` passed, and `0` skipped. -- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` passed with `0` failed, `23` passed, and `0` skipped. -- Coverage from `DotPilot.Tests/TestResults/cb30b645-002f-45bf-af57-b224bd5073c1/coverage.cobertura.xml` is `99.43%` line coverage and `85.00%` branch coverage across the covered production surface. -- PR created: `https://github.com/managedcode/dotPilot/pull/74`. - -## Failing Tests Tracker - -- [x] Baseline solution verification - Failure symptom: none. - Root cause: baseline solution tests passed before the issue `#22` changes started. - Fix path: preserve the green baseline while moving shared domain primitives into the new slice. - Status: complete. - -## Final Validation Skills - -1. `mcaf-dotnet` -Reason: the change reshapes core contracts and must stay aligned with the repo .NET toolchain. - -2. `mcaf-testing` -Reason: domain DTOs and moved runtime dependencies need durable regression coverage. - -3. `mcaf-architecture-overview` -Reason: issue `#22` adds a new reusable slice that must appear in the architecture map. diff --git a/issue-23-communication-contracts.plan.md b/issue-23-communication-contracts.plan.md deleted file mode 100644 index 4f2f62d..0000000 --- a/issue-23-communication-contracts.plan.md +++ /dev/null @@ -1,110 +0,0 @@ -# Issue 23 Communication Contracts Plan - -## Goal - -Adopt `ManagedCode.Communication` for the first public runtime success and failure contracts so provider, runtime, orchestration, and policy slices can share one result language instead of inventing parallel abstractions. - -## Scope - -### In Scope - -- Add `ManagedCode.Communication` as a centrally managed dependency for the control-plane contract layer -- Introduce an issue-aligned communication slice in `DotPilot.Core` -- Define the first runtime result and problem categories for provider, policy, orchestration, validation, and environment failures -- Rewire the current runtime-foundation public contracts to use the communication result model -- Update the deterministic runtime path and tests to prove success and failure flows -- Update architecture and feature docs so issue `#23` is explicit and referenceable - -### Out Of Scope - -- Full provider adapter implementation -- End-user copywriting for every error state -- Orleans host implementation -- Agent Framework orchestration implementation - -## Constraints And Risks - -- The Uno app must stay presentation-only; communication contracts belong outside the UI host -- Avoid inventing a second result abstraction next to `ManagedCode.Communication` -- Keep the first result surface small and focused on public runtime boundaries -- Preserve deterministic CI-safe runtime coverage while adding richer failure contracts -- Keep the package addition explicit and centrally managed in `Directory.Packages.props` - -## Testing Methodology - -- Baseline proof: run the full solution test command after the plan is created -- Contract proof: add focused tests for success and failure result flows through the communication slice -- Integration proof: rerun the deterministic runtime tests after rewiring the public contract -- UI proof: rerun the full UI suite to confirm the updated public contract does not break the presentation host -- Final proof: run repo-ordered `format`, `build`, full `test`, and the coverage command -- Quality bar: the runtime boundary uses one explicit success/failure contract language, deterministic flows remain green, and coverage does not regress - -## Ordered Plan - -- [x] Step 1: Establish the baseline on the new branch after the plan is prepared. - Verification: - - `dotnet test DotPilot.slnx` - Done when: any pre-existing failure is tracked here before the issue `#23` changes begin. - -- [x] Step 2: Add `ManagedCode.Communication` and create the issue `#23` communication slice in `DotPilot.Core`. - Verification: the package is centrally managed and the new slice defines stable result/problem categories without leaking UI concerns. - Done when: public runtime communication contracts live in a dedicated feature folder and compile cleanly. - -- [x] Step 3: Rewire runtime-foundation public contracts and deterministic runtime flows to use the communication result model. - Verification: success and failure paths for the deterministic runtime client travel through the new communication boundary instead of raw ad hoc records. - Done when: runtime-foundation no longer exposes a custom parallel result abstraction. - -- [x] Step 4: Add or update automated tests for communication success, validation failure, approval pause, and environment-unavailable paths. - Verification: tests assert both success payloads and failure/problem categories through the public boundary. - Done when: the new communication slice is protected by realistic runtime-flow tests. - -- [x] Step 5: Update durable docs for issue `#23`. - Verification: architecture and feature docs show where the communication slice sits between the domain model and later provider/runtime implementations. - Done when: later slices can reference the result language directly from docs. - -- [x] Step 6: Run final validation in repo order and record the results. - Verification: - - `dotnet format DotPilot.slnx --verify-no-changes` - - `dotnet build DotPilot.slnx -warnaserror` - - `dotnet test DotPilot.slnx` - - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` - Done when: the commands are green or any remaining blocker is explicitly documented here. - -- [x] Step 7: Create the PR for issue `#23`, then continue from a fresh branch for the next slice. - Verification: the branch and PR flow matches the enforced repo workflow. - Done when: the issue `#23` PR exists and follow-up work no longer accumulates on its review branch. - -## Validation Notes - -- `dotnet test DotPilot.slnx` passed with `0` failed, `37` passed, and `0` skipped on the new `codex/issue-23-communication-contracts` branch baseline. -- `Directory.Packages.props` and `DotPilot.Core/DotPilot.Core.csproj` now add `ManagedCode.Communication` as the shared result/problem dependency for the core contract layer. -- `DotPilot.Core/Features/RuntimeCommunication/*` now owns typed communication problem codes and centralized `Problem` factories for validation, provider readiness, runtime-host availability, orchestration availability, and policy rejection. -- `DotPilot.Core/Features/RuntimeFoundation/IAgentRuntimeClient.cs` now exposes `ValueTask>`, so runtime public boundaries use `ManagedCode.Communication` instead of a parallel ad hoc result abstraction. -- `DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs` now returns explicit success and failure results for plan, execute, review, approval-pause, blank-prompt, and provider-unavailable paths. -- `DotPilot.Tests/RuntimeFoundationCatalogTests.cs` and `DotPilot.Tests/RuntimeCommunicationProblemsTests.cs` now cover success results, validation failures, provider-environment failures, and direct problem-code mappings. -- `docs/Features/runtime-communication-contracts.md` documents the issue `#23` result/problem language with a Mermaid flow. -- `dotnet format DotPilot.slnx --verify-no-changes` passed. -- `dotnet build DotPilot.slnx -warnaserror` passed with `0` warnings and `0` errors. -- `dotnet test DotPilot.slnx` passed with `0` failed, `49` passed, and `0` skipped. -- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` passed with `0` failed, `35` passed, and `0` skipped. -- Coverage from `DotPilot.Tests/TestResults/57d524aa-22ea-4a88-acb2-3757c3eb671c/coverage.cobertura.xml` is `99.20%` line coverage and `86.66%` branch coverage across the covered production surface. -- PR created: `https://github.com/managedcode/dotPilot/pull/75`. - -## Failing Tests Tracker - -- [x] Baseline solution verification - Failure symptom: none. - Root cause: baseline solution tests passed before the issue `#23` changes started. - Fix path: preserve the green baseline while introducing the communication result layer. - Status: complete. - -## Final Validation Skills - -1. `mcaf-dotnet` -Reason: the task changes shared contracts and package references in the .NET solution. - -2. `mcaf-testing` -Reason: communication boundaries need explicit regression coverage for success and failure flows. - -3. `mcaf-architecture-overview` -Reason: issue `#23` adds a new reusable contract slice that must appear in the architecture map. diff --git a/vertical-slice-runtime-foundation.plan.md b/vertical-slice-runtime-foundation.plan.md deleted file mode 100644 index 43cfe9a..0000000 --- a/vertical-slice-runtime-foundation.plan.md +++ /dev/null @@ -1,129 +0,0 @@ -# Vertical Slice Runtime Foundation Plan - -## Goal - -Reshape the solution so `DotPilot` stays a presentation-only Uno app, while the first runtime foundation for epic `#12` moves into separate vertical-slice class libraries with isolated contracts and composition points. - -## Scope - -### In Scope - -- Record durable governance rules for vertical slices, presentation-only Uno UI, and branch-after-PR workflow -- Update the architecture map to show the new solution boundaries and feature-slice direction -- Introduce a `DotPilot.Core` class library for non-UI contracts and feature-aligned slices that support issue `#12` -- Introduce any additional class library needed to keep runtime bootstrapping and service registration out of the Uno UI project -- Move the first runtime/domain contracts out of `DotPilot` and into slice-owned folders aligned with issues `#22`, `#23`, `#24`, and `#25` -- Rewire the Uno app to reference the new DLL boundaries through composition only -- Add or update automated tests that verify the moved contracts and composition surface - -### Out Of Scope - -- Full Orleans host implementation -- Full Microsoft Agent Framework integration -- Full provider adapters, persistence, telemetry, or Git tooling implementation -- Closing every open GitHub issue in one slice -- Replacing the current Uno shell layout - -## Constraints And Risks - -- Keep the existing `README.md` modification untouched because it predates this task. -- Respect the new architecture rule: each feature slice owns its contracts and orchestration; no new shared horizontal dump folders. -- Use the newest stable `.NET 10` and `C#` features supported by the pinned SDK when they improve clarity and reduce boilerplate. -- Keep `DotPilot` as the presentation host only; avoid reintroducing runtime/domain logic there during the refactor. -- Preserve the current Uno startup and navigation behavior while moving composition and contracts behind separate DLL boundaries. -- Keep changes small enough that the first PR is reviewable and can become the branch point for the next slice. -- UI tests remain mandatory even if this slice mostly changes structure. -- CI cannot rely on Codex, Claude Code, or GitHub Copilot being installed or authenticated, so provider-independent agent flows need a deterministic in-repo test AI client. -- Provider-specific tests must self-gate on real toolchain availability without weakening the provider-independent baseline. -- API-level and UI-level flow coverage are mandatory deliverables for this slice, not optional follow-up work. - -## Testing Methodology - -- Baseline proof: run the full solution test command after the plan is prepared to capture the real starting point. -- Focused proof for the refactor: run targeted contract and API-style tests for the new core/runtime composition surface and any updated app-startup tests. -- Provider-independent agent-flow proof: cover the new contracts and composition path with the in-repo test AI client so CI can validate the behavior without external CLIs. -- Provider-dependent proof: any tests that need real Codex, Claude Code, or GitHub Copilot must detect tool availability and execute only when the dependency is present. -- UI proof: add or extend browser tests so the changed surfaces verify each introduced interactive element and at least one complete end-to-end flow. -- Broader proof: run the solution build and test commands once the slice is wired through. -- Quality gates: `dotnet format DotPilot.slnx --verify-no-changes`, `dotnet build DotPilot.slnx -warnaserror`, `dotnet test DotPilot.slnx`, and the repo coverage command. -- Quality bar: the new class-library split compiles cleanly, the Uno app still starts through the normal composition path, provider-independent flows stay testable in CI, UI flows are exercised end to end, and no existing automated coverage regresses without an explicit documented reason. - -## Ordered Plan - -- [x] Step 1: Capture the durable workflow and architecture rules in root and local `AGENTS.md`. - Verification: root governance now requires vertical slices, presentation-only Uno UI, and a fresh branch after each PR; local `DotPilot/AGENTS.md` narrows the app boundary to presentation. - Done when: future agents can infer the intended architecture from governance alone. - -- [x] Step 2: Run the full relevant baseline to establish the real starting state before deeper refactoring. - Verification: - - `dotnet test DotPilot.slnx` - Done when: the plan records every pre-existing failure or risk exposed by the baseline. - -- [x] Step 3: Update `docs/Architecture.md` so the architecture map matches the new vertical-slice and multi-DLL direction for epic `#12`. - Verification: the architecture overview contains real module and contract diagrams for the Uno UI host, the new core/runtime libraries, and the first issue-aligned slices. - Done when: a new agent can scope `#12`, `#22`, `#23`, `#24`, and `#25` from the architecture map without reading the whole repo. - -- [x] Step 4: Create the new solution projects and wire them into `DotPilot.slnx`. - Verification: the solution contains a presentation-only `DotPilot` app plus separate class libraries for shared non-UI foundations. - Done when: the app project references the new libraries instead of owning non-UI contracts directly. - -- [x] Step 5: Move the first issue-aligned contracts into isolated vertical slices and add the minimal composition surface. - Verification: issue `#12` foundation types are grouped by feature slice, not by horizontal layer, and the Uno app consumes them through service registration or typed composition only. - Done when: the first runtime/domain contracts needed for `#22` to `#25` live outside the UI project with clear ownership. - -- [x] Step 6: Add or update automated API-style tests for the extracted contracts, app composition path, and provider-independent agent flows. - Verification: focused tests cover the new slice boundaries, identifiers/contracts, composition wiring, and the in-repo test AI client path with meaningful positive, negative, and edge-case assertions. - Done when: the refactor is protected by realistic non-UI flow tests instead of only compile success. - -- [x] Step 7: Add or extend UI tests for the affected surfaces. - Verification: browser tests assert the visible interactive elements introduced or rewired by this slice and exercise at least one full end-to-end operator flow through the changed area. - Done when: UI coverage proves the new slice shape from the operator surface, not just from non-UI tests. - -- [x] Step 8: Run final validation in repo order and record the results. - Verification: - - `dotnet format DotPilot.slnx --verify-no-changes` - - `dotnet build DotPilot.slnx -warnaserror` - - `dotnet test DotPilot.slnx` - - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` - Done when: the commands are green or any remaining blocker is explicitly documented in this file. - -- [x] Step 9: Create the PR for this slice, then open a fresh working branch for the next slice before continuing. - Verification: the branch and PR flow match the new repo rule. - Done when: the PR exists and follow-up work is no longer accumulating on the reviewed branch. - -## Validation Notes - -- `dotnet test DotPilot.slnx` passed with `0` failed, `14` passed, and `0` skipped. -- `docs/Architecture.md` now maps the Uno presentation host, `DotPilot.Core`, `DotPilot.Runtime`, and the first issue-aligned runtime foundation slice for epic `#12`. -- `DotPilot.slnx` now includes `DotPilot.Core` and `DotPilot.Runtime`, with `DotPilot` consuming runtime/domain behavior through project references and DI only. -- Runtime foundation contracts, typed identifiers, provider-probe logic, and the deterministic in-repo agent client now live outside the Uno project in slice-owned folders. -- UI coverage now includes the runtime foundation panel, slice/provider counts, the agent-builder runtime banner, and an end-to-end runtime foundation flow. -- UI harness verification found and fixed the browser-host bootstrap path so the suite produces a real result instead of stalling on nested browser setup. -- `dotnet format DotPilot.slnx --verify-no-changes` passed. -- `dotnet build DotPilot.slnx -warnaserror` passed with `0` warnings and `0` errors. -- `dotnet test DotPilot.slnx` passed with `0` failed, `34` passed, and `0` skipped. -- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` passed with `0` failed, `20` passed, and `0` skipped. -- Coverage from `DotPilot.Tests/TestResults/67ca7188-7eca-4fae-aa3e-893fd99a8e3c/coverage.cobertura.xml` is `99.29%` line coverage and `85.00%` branch coverage across the changed production surface. -- PR created: `https://github.com/managedcode/dotPilot/pull/73`. - -## Failing Tests Tracker - -- [x] Baseline solution verification - Failure symptom: none. - Root cause: baseline solution tests passed before the refactor started. - Fix path: keep the baseline green while introducing the new projects and slices. - Status: complete. - -## Final Validation Skills - -1. `mcaf-solution-governance` -Reason: the root and local agent rules must explicitly match the enforced architecture and branch workflow. - -2. `mcaf-architecture-overview` -Reason: the solution map must reflect the new DLL and slice boundaries. - -3. `mcaf-dotnet` -Reason: this slice changes project structure, app composition, and solution verification commands. - -4. `mcaf-testing` -Reason: moved contracts and composition boundaries need durable automated verification. From d2fa9aa3e26be073661bcc587e561ca856e3acf8 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 13 Mar 2026 23:08:37 +0100 Subject: [PATCH 3/8] tets --- AGENTS.md | 2 ++ DotPilot.UITests/AGENTS.md | 9 +++++---- .../Given_MainPage.cs} | 0 .../Harness/{ => Tests}/BoundedCleanupTests.cs | 0 .../{ => Tests}/BrowserAutomationBootstrapTests.cs | 0 .../Harness/{ => Tests}/BrowserTestHostTests.cs | 0 6 files changed, 7 insertions(+), 4 deletions(-) rename DotPilot.UITests/Features/{Workbench/Given_WorkbenchShell.cs => ApplicationShell/Given_MainPage.cs} (100%) rename DotPilot.UITests/Harness/{ => Tests}/BoundedCleanupTests.cs (100%) rename DotPilot.UITests/Harness/{ => Tests}/BrowserAutomationBootstrapTests.cs (100%) rename DotPilot.UITests/Harness/{ => Tests}/BrowserTestHostTests.cs (100%) diff --git a/AGENTS.md b/AGENTS.md index c425795..30e9ede 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -304,6 +304,7 @@ Local `AGENTS.md` files may tighten these values, but they must not loosen them - Hardcoded values are forbidden. - String literals are forbidden in implementation code. Declare them once as named constants, enums, configuration entries, or dedicated value objects, then reuse those symbols. - Avoid magic literals. Extract shared values into constants, enums, configuration, or dedicated types. +- Backlog metadata does not belong in product code: issue numbers, PR numbers, review language, and planning terminology must never appear in production runtime models, diagnostics, or user-facing text unless the feature explicitly exposes source-control metadata. - Design boundaries so real behaviour can be tested through public interfaces. - For `.NET`, the repo-root `.editorconfig` is the source of truth for formatting, naming, style, and analyzer severity. - Use nested `.editorconfig` files when they serve a clear subtree-specific purpose. Do not let IDE defaults, pipeline flags, and repo config disagree. @@ -362,6 +363,7 @@ Ask first: - Installing stale, non-canonical, or non-`mcaf-*` skills into the repo-local agent skill directory. - Moving root governance out of the repository root. - Mixing multiple `.NET` test frameworks in the active solution without a documented migration plan. +- Running build, test, or verification commands for file-only structural reorganizations when the user explicitly asked for folder cleanup without behavior changes. - Adding fallback paths or alternate harnesses that only make failures disappear in tests while the primary product path remains broken. - Switching desktop Uno pages into stacked or mobile-style responsive layouts during resize work unless the user explicitly asks for a different composition; desktop pages must stay desktop-first and protect geometry through sizing constraints instead. - Adding extra UI-test orchestration complexity when the actual goal is simply to run the tests and get an honest pass or fail result. diff --git a/DotPilot.UITests/AGENTS.md b/DotPilot.UITests/AGENTS.md index 9228e28..ecc31e9 100644 --- a/DotPilot.UITests/AGENTS.md +++ b/DotPilot.UITests/AGENTS.md @@ -11,8 +11,9 @@ Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven UI tests ## Entry Points - `DotPilot.UITests.csproj` -- `Harness/*` -- `Features/*` +- `Harness/Constants.cs` +- `Harness/TestBase.cs` +- `Features/ApplicationShell/Given_MainPage.cs` ## Boundaries @@ -21,7 +22,7 @@ Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven UI tests - Treat browser-driver setup and app-launch prerequisites as part of the harness, not as assumptions inside individual tests. - The harness must make `dotnet test DotPilot.UITests/DotPilot.UITests.csproj` runnable without manual driver-path export and must fail loudly instead of silently skipping coverage. - Keep the harness direct and minimal; prefer the smallest deterministic setup needed to run the suite and return a real result. -- Organize files by feature slice and harness boundary: browser/bootstrap infrastructure under harness folders, feature-flow tests under the slice they verify. +- Keep the file layout explicit: browser harness code belongs under `Harness/`, harness self-tests under `Harness/Tests/`, end-to-end slice coverage under `Features//`, and cross-slice operator flows under `Journeys/`. - Use the official `Uno` MCP documentation as the source of truth for `Uno.UITest` browser behavior, and align selectors with the documented WebAssembly automation mapping before changing the harness. - Do not manually launch the app or a standalone `browserwasm` host while working on this project; browser-path reproduction and debugging must go through `dotnet test` and the real `DotPilot.UITests` harness only. - UI tests must cover each feature's interactive elements, expected behaviors, and full operator flows instead of only a top-level smoke path. @@ -40,5 +41,5 @@ Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven UI tests ## Local Risks Or Protected Areas - The harness targets a browser flow and auto-starts the `net10.0-browserwasm` head on a loopback URI resolved by the harness; any driver discovery or bootstrap logic must stay deterministic across local and agent environments. -- `Constants.cs` and `TestBase.cs` define environment assumptions for every UI test; update them carefully and only when the automation target actually changes. +- `Harness/Constants.cs` and `Harness/TestBase.cs` define environment assumptions for every UI test; update them carefully and only when the automation target actually changes. - Every new UI capability should arrive with assertions for the visible controls it adds and at least one complete end-to-end flow through the affected surface. diff --git a/DotPilot.UITests/Features/Workbench/Given_WorkbenchShell.cs b/DotPilot.UITests/Features/ApplicationShell/Given_MainPage.cs similarity index 100% rename from DotPilot.UITests/Features/Workbench/Given_WorkbenchShell.cs rename to DotPilot.UITests/Features/ApplicationShell/Given_MainPage.cs diff --git a/DotPilot.UITests/Harness/BoundedCleanupTests.cs b/DotPilot.UITests/Harness/Tests/BoundedCleanupTests.cs similarity index 100% rename from DotPilot.UITests/Harness/BoundedCleanupTests.cs rename to DotPilot.UITests/Harness/Tests/BoundedCleanupTests.cs diff --git a/DotPilot.UITests/Harness/BrowserAutomationBootstrapTests.cs b/DotPilot.UITests/Harness/Tests/BrowserAutomationBootstrapTests.cs similarity index 100% rename from DotPilot.UITests/Harness/BrowserAutomationBootstrapTests.cs rename to DotPilot.UITests/Harness/Tests/BrowserAutomationBootstrapTests.cs diff --git a/DotPilot.UITests/Harness/BrowserTestHostTests.cs b/DotPilot.UITests/Harness/Tests/BrowserTestHostTests.cs similarity index 100% rename from DotPilot.UITests/Harness/BrowserTestHostTests.cs rename to DotPilot.UITests/Harness/Tests/BrowserTestHostTests.cs From e5c5b353bac730492d128817480f09f393057677 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 13 Mar 2026 23:19:14 +0100 Subject: [PATCH 4/8] fix pr-76 review followups and startup probing --- AGENTS.md | 3 + .../RuntimeFoundationCatalog.cs | 26 +++--- .../ToolchainCenter/ToolchainCenterCatalog.cs | 44 +++++++-- .../ToolchainCenter/ToolchainCommandProbe.cs | 74 ++++++++++++--- .../ToolchainProviderProfile.cs | 1 + .../ToolchainProviderProfiles.cs | 6 ++ .../ToolchainProviderSnapshotFactory.cs | 65 ++++++++----- .../RuntimeFoundationCatalogTests.cs | 51 ++--------- .../ToolchainCenterCatalogTests.cs | 16 +++- .../ToolchainCommandProbeTests.cs | 33 +++++++ .../ToolchainProviderSnapshotFactoryTests.cs | 91 ++++++++++++++++--- .../Workbench/PresentationViewModelTests.cs | 4 +- docs/Architecture.md | 2 - pr-76-review-followup.plan.md | 87 ++++++++++++++++++ 14 files changed, 383 insertions(+), 120 deletions(-) create mode 100644 pr-76-review-followup.plan.md diff --git a/AGENTS.md b/AGENTS.md index 30e9ede..e2fea4c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -152,6 +152,9 @@ For this app: - architecture work must keep a vertical-slice shape: each feature owns its contracts, orchestration, and tests behind clear boundaries instead of growing a shared horizontal service layer - keep the Uno app project presentation-only; domain, runtime host, orchestration, integrations, and persistence code must live in separate class-library projects so UI composition does not mix with feature implementation - structure both `DotPilot.Tests` and `DotPilot.UITests` by vertical slice and explicit harness boundaries; do not keep test files in one flat project-root pile +- GitHub is the backlog, not the product: use issues and PRs only to drive task scope and traceability, and never copy GitHub issue text, labels, workflow language, or tracker metadata into production code, runtime snapshots, or user-facing UI +- Desktop responsiveness is a product requirement: avoid synchronous probe, filesystem, network, or process work on UI-facing construction and navigation paths so the app stays fast and immediately reactive +- Do not invent a repo-specific product framing such as "workbench" unless the active issue or feature spec explicitly uses it; implement the app features described in the backlog instead of turning internal implementation language into the product narrative - GitHub Actions workflows must use descriptive names and filenames that reflect their purpose; do not use a generic `ci.yml` catch-all because build validation and release automation are separate operator flows - GitHub Actions must be split into at least one validation workflow for normal builds/tests and one release workflow for CI-driven version resolution, release-note generation, desktop publishing, and GitHub Release publication - meaningful GitHub review comments must be evaluated and fixed when they still apply even if the original PR was closed; closed review threads are not a reason to ignore valid engineering feedback diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs index 3e66006..944337b 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs @@ -1,38 +1,44 @@ using DotPilot.Core.Features.ControlPlaneDomain; using DotPilot.Core.Features.RuntimeFoundation; -using DotPilot.Runtime.Features.ToolchainCenter; - namespace DotPilot.Runtime.Features.RuntimeFoundation; public sealed class RuntimeFoundationCatalog : IRuntimeFoundationCatalog { private const string EpicSummary = - "Issue #12 is staged into isolated contracts, communication, host, and orchestration slices so the Uno workbench can stay presentation-only."; + "Runtime contracts, host sequencing, and orchestration seams stay isolated so the Uno app can remain presentation-only."; + private const string EpicLabelValue = "LOCAL RUNTIME READINESS"; private const string DeterministicProbePrompt = "Summarize the runtime foundation readiness for a local-first session that may require approval."; private const string DeterministicClientStatusSummary = "Always available for in-repo and CI validation."; + private const string DomainModelLabel = "DOMAIN"; private const string DomainModelName = "Domain contracts"; private const string DomainModelSummary = "Typed identifiers and durable agent, session, fleet, provider, and runtime contracts live outside the Uno app."; + private const string CommunicationLabel = "CONTRACTS"; private const string CommunicationName = "Communication contracts"; private const string CommunicationSummary = "Public result and problem boundaries are isolated so later provider and orchestration slices share one contract language."; + private const string HostLabel = "HOST"; private const string HostName = "Embedded host"; private const string HostSummary = "The Orleans host integration point is sequenced behind dedicated runtime contracts instead of being baked into page code."; + private const string OrchestrationLabel = "ORCHESTRATION"; private const string OrchestrationName = "Orchestration runtime"; private const string OrchestrationSummary = "Agent Framework integration is prepared as a separate slice that can plug into the embedded host without reshaping the UI layer."; + private readonly IReadOnlyList _providers; + + public RuntimeFoundationCatalog() => _providers = CreateProviders(); public RuntimeFoundationSnapshot GetSnapshot() { return new( - RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.EmbeddedAgentRuntimeHostEpic), + EpicLabelValue, EpicSummary, ProviderToolchainNames.DeterministicClientDisplayName, DeterministicProbePrompt, CreateSlices(), - CreateProviders()); + _providers); } private static IReadOnlyList CreateSlices() @@ -41,25 +47,25 @@ private static IReadOnlyList CreateSlices() [ new( RuntimeFoundationIssues.DomainModel, - RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.DomainModel), + DomainModelLabel, DomainModelName, DomainModelSummary, RuntimeSliceState.ReadyForImplementation), new( RuntimeFoundationIssues.CommunicationContracts, - RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.CommunicationContracts), + CommunicationLabel, CommunicationName, CommunicationSummary, RuntimeSliceState.Sequenced), new( RuntimeFoundationIssues.EmbeddedOrleansHost, - RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.EmbeddedOrleansHost), + HostLabel, HostName, HostSummary, RuntimeSliceState.Sequenced), new( RuntimeFoundationIssues.AgentFrameworkRuntime, - RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.AgentFrameworkRuntime), + OrchestrationLabel, OrchestrationName, OrchestrationSummary, RuntimeSliceState.Sequenced), @@ -79,8 +85,6 @@ private static IReadOnlyList CreateProviders() StatusSummary = DeterministicClientStatusSummary, RequiresExternalToolchain = false, }, - .. ToolchainProviderSnapshotFactory.Create(TimeProvider.System.GetUtcNow()) - .Select(snapshot => snapshot.Provider), ]; } } diff --git a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCenterCatalog.cs b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCenterCatalog.cs index 33d7fc2..bf2d6fd 100644 --- a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCenterCatalog.cs +++ b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCenterCatalog.cs @@ -4,25 +4,31 @@ namespace DotPilot.Runtime.Features.ToolchainCenter; public sealed class ToolchainCenterCatalog : IToolchainCenterCatalog, IDisposable { + private const string EpicLabelValue = "PRESESSION READINESS"; private const string EpicSummary = - "Issue #14 keeps provider installation, auth, diagnostics, configuration, and polling visible before the first live session."; + "Provider installation, launch checks, authentication, configuration, and refresh state stay visible before the first live session."; + private const string UiWorkstreamLabel = "SURFACE"; private const string UiWorkstreamName = "Toolchain Center UI"; private const string UiWorkstreamSummary = "The settings shell exposes a first-class desktop Toolchain Center with provider cards, detail panes, and operator actions."; + private const string DiagnosticsWorkstreamLabel = "DIAGNOSTICS"; private const string DiagnosticsWorkstreamName = "Connection diagnostics"; private const string DiagnosticsWorkstreamSummary = "Launch, connection, resume, tool access, and auth diagnostics stay attributable before live work starts."; + private const string ConfigurationWorkstreamLabel = "CONFIGURATION"; private const string ConfigurationWorkstreamName = "Secrets and environment"; private const string ConfigurationWorkstreamSummary = "Provider secrets, local overrides, and non-secret environment configuration stay visible without leaking values."; + private const string PollingWorkstreamLabel = "POLLING"; private const string PollingWorkstreamName = "Background polling"; private const string PollingWorkstreamSummary = - "Version and auth readiness refresh in the background so the workbench can surface stale state early."; + "Version and auth readiness refresh in the background so the app can surface stale state early."; private readonly TimeProvider _timeProvider; private readonly CancellationTokenSource _disposeTokenSource = new(); private readonly PeriodicTimer? _pollingTimer; private readonly Task _pollingTask; private ToolchainCenterSnapshot _snapshot; + private int _disposeState; public ToolchainCenterCatalog() : this(TimeProvider.System, startBackgroundPolling: true) @@ -46,13 +52,29 @@ public ToolchainCenterCatalog(TimeProvider timeProvider, bool startBackgroundPol } } - public ToolchainCenterSnapshot GetSnapshot() => _snapshot; + public ToolchainCenterSnapshot GetSnapshot() => Volatile.Read(ref _snapshot); public void Dispose() { + if (Interlocked.Exchange(ref _disposeState, 1) != 0) + { + return; + } + _disposeTokenSource.Cancel(); _pollingTimer?.Dispose(); + + try + { + _pollingTask.GetAwaiter().GetResult(); + } + catch (OperationCanceledException) + { + // Expected during shutdown. + } + _disposeTokenSource.Dispose(); + GC.SuppressFinalize(this); } private async Task PollAsync() @@ -66,13 +88,17 @@ private async Task PollAsync() { while (await _pollingTimer.WaitForNextTickAsync(_disposeTokenSource.Token)) { - _snapshot = EvaluateSnapshot(); + Volatile.Write(ref _snapshot, EvaluateSnapshot()); } } catch (OperationCanceledException) { // Expected during app shutdown. } + catch (ObjectDisposedException) when (_disposeTokenSource.IsCancellationRequested) + { + // Expected when the timer is disposed during shutdown. + } } private ToolchainCenterSnapshot EvaluateSnapshot() @@ -80,7 +106,7 @@ private ToolchainCenterSnapshot EvaluateSnapshot() var evaluatedAt = _timeProvider.GetUtcNow(); var providers = ToolchainProviderSnapshotFactory.Create(evaluatedAt); return new( - ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ToolchainCenterEpic), + EpicLabelValue, EpicSummary, CreateWorkstreams(), providers, @@ -95,22 +121,22 @@ private static IReadOnlyList CreateWorkstre [ new( ToolchainCenterIssues.ToolchainCenterUi, - ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ToolchainCenterUi), + UiWorkstreamLabel, UiWorkstreamName, UiWorkstreamSummary), new( ToolchainCenterIssues.ConnectionDiagnostics, - ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ConnectionDiagnostics), + DiagnosticsWorkstreamLabel, DiagnosticsWorkstreamName, DiagnosticsWorkstreamSummary), new( ToolchainCenterIssues.ProviderConfiguration, - ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ProviderConfiguration), + ConfigurationWorkstreamLabel, ConfigurationWorkstreamName, ConfigurationWorkstreamSummary), new( ToolchainCenterIssues.BackgroundPolling, - ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.BackgroundPolling), + PollingWorkstreamLabel, PollingWorkstreamName, PollingWorkstreamSummary), ]; diff --git a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs index 9100f8a..a9dd590 100644 --- a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs +++ b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs @@ -6,11 +6,15 @@ internal static class ToolchainCommandProbe { private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(2); private const string VersionSeparator = "version"; + private const string EmptyOutput = ""; public static string? ResolveExecutablePath(string commandName) => RuntimeFoundation.ProviderToolchainProbe.ResolveExecutablePath(commandName); public static string ReadVersion(string executablePath, IReadOnlyList arguments) + => ProbeVersion(executablePath, arguments).Version; + + public static ToolchainVersionProbeResult ProbeVersion(string executablePath, IReadOnlyList arguments) { ArgumentException.ThrowIfNullOrWhiteSpace(executablePath); ArgumentNullException.ThrowIfNull(arguments); @@ -18,7 +22,7 @@ public static string ReadVersion(string executablePath, IReadOnlyList ar var execution = Execute(executablePath, arguments); if (!execution.Succeeded) { - return string.Empty; + return ToolchainVersionProbeResult.Missing with { Launched = execution.Launched }; } var output = string.IsNullOrWhiteSpace(execution.StandardOutput) @@ -31,13 +35,15 @@ public static string ReadVersion(string executablePath, IReadOnlyList ar if (string.IsNullOrWhiteSpace(firstLine)) { - return string.Empty; + return ToolchainVersionProbeResult.Missing with { Launched = execution.Launched }; } var separatorIndex = firstLine.IndexOf(VersionSeparator, StringComparison.OrdinalIgnoreCase); - return separatorIndex >= 0 + var version = separatorIndex >= 0 ? firstLine[(separatorIndex + VersionSeparator.Length)..].Trim(' ', ':') : firstLine.Trim(); + + return new(execution.Launched, version); } public static bool CanExecute(string executablePath, IReadOnlyList arguments) @@ -64,22 +70,57 @@ private static ToolchainCommandExecution Execute(string executablePath, IReadOnl startInfo.ArgumentList.Add(argument); } - using var process = Process.Start(startInfo); + Process? process; + try + { + process = Process.Start(startInfo); + } + catch + { + return ToolchainCommandExecution.LaunchFailed; + } + if (process is null) { - return ToolchainCommandExecution.Failed; + return ToolchainCommandExecution.LaunchFailed; } - if (!process.WaitForExit((int)CommandTimeout.TotalMilliseconds)) + using (process) { - TryTerminate(process); - return ToolchainCommandExecution.Failed; + var standardOutputTask = process.StandardOutput.ReadToEndAsync(); + var standardErrorTask = process.StandardError.ReadToEndAsync(); + + if (!process.WaitForExit((int)CommandTimeout.TotalMilliseconds)) + { + TryTerminate(process); + ObserveRedirectedStreamFaults(standardOutputTask, standardErrorTask); + return new(true, false, EmptyOutput, EmptyOutput); + } + + return new( + true, + process.ExitCode == 0, + AwaitStreamRead(standardOutputTask), + AwaitStreamRead(standardErrorTask)); } + } - return new( - process.ExitCode == 0, - process.StandardOutput.ReadToEnd(), - process.StandardError.ReadToEnd()); + private static string AwaitStreamRead(Task readTask) + { + try + { + return readTask.GetAwaiter().GetResult(); + } + catch + { + return EmptyOutput; + } + } + + private static void ObserveRedirectedStreamFaults(Task standardOutputTask, Task standardErrorTask) + { + _ = standardOutputTask.Exception; + _ = standardErrorTask.Exception; } private static void TryTerminate(Process process) @@ -97,8 +138,13 @@ private static void TryTerminate(Process process) } } - private readonly record struct ToolchainCommandExecution(bool Succeeded, string StandardOutput, string StandardError) + public readonly record struct ToolchainVersionProbeResult(bool Launched, string Version) + { + public static ToolchainVersionProbeResult Missing => new(false, EmptyOutput); + } + + private readonly record struct ToolchainCommandExecution(bool Launched, bool Succeeded, string StandardOutput, string StandardError) { - public static ToolchainCommandExecution Failed => new(false, string.Empty, string.Empty); + public static ToolchainCommandExecution LaunchFailed => new(false, false, EmptyOutput, EmptyOutput); } } diff --git a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderProfile.cs b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderProfile.cs index 61e01cb..4b0171e 100644 --- a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderProfile.cs +++ b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderProfile.cs @@ -2,6 +2,7 @@ namespace DotPilot.Runtime.Features.ToolchainCenter; internal sealed record ToolchainProviderProfile( int IssueNumber, + string SectionLabel, string DisplayName, string CommandName, IReadOnlyList VersionArguments, diff --git a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderProfiles.cs b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderProfiles.cs index 1c95bc3..e22bf44 100644 --- a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderProfiles.cs +++ b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderProfiles.cs @@ -19,12 +19,16 @@ internal static class ToolchainProviderProfiles private const string GitHubTokenSummary = "GitHub token for Copilot and GitHub CLI authenticated flows."; private const string GitHubHostTokenSummary = "Alternative GitHub host token for CLI-authenticated Copilot flows."; private const string GitHubModelsApiKeySummary = "Optional BYOK key for GitHub Models-backed Copilot routing."; + private const string CodexSectionLabel = "CODEX"; + private const string ClaudeSectionLabel = "CLAUDE"; + private const string GitHubSectionLabel = "GITHUB"; private static readonly string[] VersionArguments = ["--version"]; public static IReadOnlyList All { get; } = [ new( ToolchainCenterIssues.CodexReadiness, + CodexSectionLabel, ProviderToolchainNames.CodexDisplayName, ProviderToolchainNames.CodexCommandName, VersionArguments, @@ -40,6 +44,7 @@ internal static class ToolchainProviderProfiles ]), new( ToolchainCenterIssues.ClaudeCodeReadiness, + ClaudeSectionLabel, ProviderToolchainNames.ClaudeCodeDisplayName, ProviderToolchainNames.ClaudeCodeCommandName, VersionArguments, @@ -55,6 +60,7 @@ internal static class ToolchainProviderProfiles ]), new( ToolchainCenterIssues.GitHubCopilotReadiness, + GitHubSectionLabel, ProviderToolchainNames.GitHubCopilotDisplayName, ProviderToolchainNames.GitHubCopilotCommandName, VersionArguments, diff --git a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderSnapshotFactory.cs b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderSnapshotFactory.cs index ad54d9b..6dd988a 100644 --- a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderSnapshotFactory.cs +++ b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainProviderSnapshotFactory.cs @@ -20,10 +20,12 @@ internal static class ToolchainProviderSnapshotFactory private const string AuthMissingSummary = "No non-interactive authentication signal was detected."; private const string AuthConnectedSummary = "A non-interactive authentication signal is configured."; private const string ReadinessMissingSummaryFormat = "{0} is not installed on PATH."; + private const string ReadinessLaunchFailedSummaryFormat = "{0} is on PATH, but dotPilot could not launch the CLI automatically."; private const string ReadinessAuthRequiredSummaryFormat = "{0} is installed, but authentication still needs operator attention."; private const string ReadinessLimitedSummaryFormat = "{0} is installed, but one or more readiness prerequisites still need attention."; private const string ReadinessReadySummaryFormat = "{0} is ready for pre-session operator checks."; private const string HealthBlockedMissingSummaryFormat = "{0} launch is blocked until the CLI is installed."; + private const string HealthBlockedLaunchSummaryFormat = "{0} launch is blocked until dotPilot can start the CLI successfully."; private const string HealthBlockedAuthSummaryFormat = "{0} launch is blocked until authentication is configured."; private const string HealthWarningSummaryFormat = "{0} is installed, but diagnostics still show warnings."; private const string HealthReadySummaryFormat = "{0} passed the available pre-session readiness checks."; @@ -34,6 +36,7 @@ internal static class ToolchainProviderSnapshotFactory private const string ResumeDiagnosticName = "Resume test"; private const string LaunchPassedSummary = "The executable is installed and launchable from PATH."; private const string LaunchFailedSummary = "The executable is not available on PATH."; + private const string LaunchUnavailableSummary = "The executable was detected, but dotPilot could not launch it automatically."; private const string VersionFailedSummary = "The version could not be resolved automatically."; private const string ConnectionReadySummary = "The provider is ready for a live connection test from the Toolchain Center."; private const string ConnectionBlockedSummary = "Fix installation and authentication before running a live connection test."; @@ -50,10 +53,12 @@ internal static class ToolchainProviderSnapshotFactory private static readonly System.Text.CompositeFormat DocsActionTitleCompositeFormat = System.Text.CompositeFormat.Parse(DocsActionTitleFormat); private static readonly System.Text.CompositeFormat VersionSummaryCompositeFormat = System.Text.CompositeFormat.Parse(VersionSummaryFormat); private static readonly System.Text.CompositeFormat ReadinessMissingSummaryCompositeFormat = System.Text.CompositeFormat.Parse(ReadinessMissingSummaryFormat); + private static readonly System.Text.CompositeFormat ReadinessLaunchFailedSummaryCompositeFormat = System.Text.CompositeFormat.Parse(ReadinessLaunchFailedSummaryFormat); private static readonly System.Text.CompositeFormat ReadinessAuthRequiredSummaryCompositeFormat = System.Text.CompositeFormat.Parse(ReadinessAuthRequiredSummaryFormat); private static readonly System.Text.CompositeFormat ReadinessLimitedSummaryCompositeFormat = System.Text.CompositeFormat.Parse(ReadinessLimitedSummaryFormat); private static readonly System.Text.CompositeFormat ReadinessReadySummaryCompositeFormat = System.Text.CompositeFormat.Parse(ReadinessReadySummaryFormat); private static readonly System.Text.CompositeFormat HealthBlockedMissingSummaryCompositeFormat = System.Text.CompositeFormat.Parse(HealthBlockedMissingSummaryFormat); + private static readonly System.Text.CompositeFormat HealthBlockedLaunchSummaryCompositeFormat = System.Text.CompositeFormat.Parse(HealthBlockedLaunchSummaryFormat); private static readonly System.Text.CompositeFormat HealthBlockedAuthSummaryCompositeFormat = System.Text.CompositeFormat.Parse(HealthBlockedAuthSummaryFormat); private static readonly System.Text.CompositeFormat HealthWarningSummaryCompositeFormat = System.Text.CompositeFormat.Parse(HealthWarningSummaryFormat); private static readonly System.Text.CompositeFormat HealthReadySummaryCompositeFormat = System.Text.CompositeFormat.Parse(HealthReadySummaryFormat); @@ -91,54 +96,56 @@ private static ToolchainProviderSnapshot Create(ToolchainProviderProfile profile { var executablePath = ToolchainCommandProbe.ResolveExecutablePath(profile.CommandName); var isInstalled = !string.IsNullOrWhiteSpace(executablePath); - var installedVersion = isInstalled - ? ToolchainCommandProbe.ReadVersion(executablePath!, profile.VersionArguments) - : string.Empty; + var versionProbe = isInstalled + ? ToolchainCommandProbe.ProbeVersion(executablePath!, profile.VersionArguments) + : ToolchainCommandProbe.ToolchainVersionProbeResult.Missing; + var launchAvailable = isInstalled && versionProbe.Launched; + var installedVersion = launchAvailable ? versionProbe.Version : string.Empty; var authConfigured = profile.AuthenticationEnvironmentVariables .Select(Environment.GetEnvironmentVariable) .Any(static value => !string.IsNullOrWhiteSpace(value)); - var toolAccessAvailable = isInstalled && ( + var toolAccessAvailable = launchAvailable && ( profile.ToolAccessArguments.Count == 0 || ToolchainCommandProbe.CanExecute(executablePath!, profile.ToolAccessArguments)); - var providerStatus = ResolveProviderStatus(isInstalled, authConfigured, toolAccessAvailable); - var readinessState = ResolveReadinessState(isInstalled, authConfigured, toolAccessAvailable, installedVersion); + var providerStatus = ResolveProviderStatus(isInstalled, launchAvailable, authConfigured, toolAccessAvailable); + var readinessState = ResolveReadinessState(isInstalled, launchAvailable, authConfigured, toolAccessAvailable, installedVersion); var versionStatus = ResolveVersionStatus(isInstalled, installedVersion); var authStatus = authConfigured ? ToolchainAuthStatus.Connected : ToolchainAuthStatus.Missing; - var healthStatus = ResolveHealthStatus(isInstalled, authConfigured, toolAccessAvailable, installedVersion); + var healthStatus = ResolveHealthStatus(isInstalled, launchAvailable, authConfigured, toolAccessAvailable, installedVersion); var polling = CreateProviderPolling(evaluatedAt, readinessState); return new( profile.IssueNumber, - ToolchainCenterIssues.FormatIssueLabel(profile.IssueNumber), + profile.SectionLabel, new ProviderDescriptor { Id = ToolchainDeterministicIdentity.CreateProviderId(profile.CommandName), DisplayName = profile.DisplayName, CommandName = profile.CommandName, Status = providerStatus, - StatusSummary = ResolveReadinessSummary(profile.DisplayName, readinessState), + StatusSummary = ResolveReadinessSummary(profile.DisplayName, isInstalled, launchAvailable, readinessState), RequiresExternalToolchain = true, }, executablePath ?? MissingExecutablePath, string.IsNullOrWhiteSpace(installedVersion) ? MissingVersion : installedVersion, readinessState, - ResolveReadinessSummary(profile.DisplayName, readinessState), + ResolveReadinessSummary(profile.DisplayName, isInstalled, launchAvailable, readinessState), versionStatus, ResolveVersionSummary(versionStatus, installedVersion), authStatus, authConfigured ? AuthConnectedSummary : AuthMissingSummary, healthStatus, - ResolveHealthSummary(profile.DisplayName, healthStatus, authConfigured), + ResolveHealthSummary(profile.DisplayName, healthStatus, isInstalled, launchAvailable, authConfigured), CreateActions(profile, readinessState), - CreateDiagnostics(profile, isInstalled, authConfigured, installedVersion, toolAccessAvailable), + CreateDiagnostics(profile, isInstalled, launchAvailable, authConfigured, installedVersion, toolAccessAvailable), CreateConfiguration(profile), polling); } - private static ProviderConnectionStatus ResolveProviderStatus(bool isInstalled, bool authConfigured, bool toolAccessAvailable) + private static ProviderConnectionStatus ResolveProviderStatus(bool isInstalled, bool launchAvailable, bool authConfigured, bool toolAccessAvailable) { - if (!isInstalled) + if (!isInstalled || !launchAvailable) { return ProviderConnectionStatus.Unavailable; } @@ -155,11 +162,12 @@ private static ProviderConnectionStatus ResolveProviderStatus(bool isInstalled, private static ToolchainReadinessState ResolveReadinessState( bool isInstalled, + bool launchAvailable, bool authConfigured, bool toolAccessAvailable, string installedVersion) { - if (!isInstalled) + if (!isInstalled || !launchAvailable) { return ToolchainReadinessState.Missing; } @@ -191,11 +199,12 @@ private static ToolchainVersionStatus ResolveVersionStatus(bool isInstalled, str private static ToolchainHealthStatus ResolveHealthStatus( bool isInstalled, + bool launchAvailable, bool authConfigured, bool toolAccessAvailable, string installedVersion) { - if (!isInstalled || !authConfigured) + if (!isInstalled || !launchAvailable || !authConfigured) { return ToolchainHealthStatus.Blocked; } @@ -205,9 +214,14 @@ private static ToolchainHealthStatus ResolveHealthStatus( : ToolchainHealthStatus.Warning; } - private static string ResolveReadinessSummary(string displayName, ToolchainReadinessState readinessState) => + private static string ResolveReadinessSummary( + string displayName, + bool isInstalled, + bool launchAvailable, + ToolchainReadinessState readinessState) => readinessState switch { + ToolchainReadinessState.Missing when isInstalled && !launchAvailable => string.Format(System.Globalization.CultureInfo.InvariantCulture, ReadinessLaunchFailedSummaryCompositeFormat, displayName), ToolchainReadinessState.Missing => string.Format(System.Globalization.CultureInfo.InvariantCulture, ReadinessMissingSummaryCompositeFormat, displayName), ToolchainReadinessState.ActionRequired => string.Format(System.Globalization.CultureInfo.InvariantCulture, ReadinessAuthRequiredSummaryCompositeFormat, displayName), ToolchainReadinessState.Limited => string.Format(System.Globalization.CultureInfo.InvariantCulture, ReadinessLimitedSummaryCompositeFormat, displayName), @@ -222,11 +236,17 @@ private static string ResolveVersionSummary(ToolchainVersionStatus versionStatus _ => string.Format(System.Globalization.CultureInfo.InvariantCulture, VersionSummaryCompositeFormat, installedVersion), }; - private static string ResolveHealthSummary(string displayName, ToolchainHealthStatus healthStatus, bool authConfigured) => + private static string ResolveHealthSummary( + string displayName, + ToolchainHealthStatus healthStatus, + bool isInstalled, + bool launchAvailable, + bool authConfigured) => healthStatus switch { - ToolchainHealthStatus.Blocked when authConfigured => string.Format(System.Globalization.CultureInfo.InvariantCulture, HealthBlockedMissingSummaryCompositeFormat, displayName), - ToolchainHealthStatus.Blocked => string.Format(System.Globalization.CultureInfo.InvariantCulture, HealthBlockedAuthSummaryCompositeFormat, displayName), + ToolchainHealthStatus.Blocked when !isInstalled => string.Format(System.Globalization.CultureInfo.InvariantCulture, HealthBlockedMissingSummaryCompositeFormat, displayName), + ToolchainHealthStatus.Blocked when !launchAvailable => string.Format(System.Globalization.CultureInfo.InvariantCulture, HealthBlockedLaunchSummaryCompositeFormat, displayName), + ToolchainHealthStatus.Blocked when !authConfigured => string.Format(System.Globalization.CultureInfo.InvariantCulture, HealthBlockedAuthSummaryCompositeFormat, displayName), ToolchainHealthStatus.Warning => string.Format(System.Globalization.CultureInfo.InvariantCulture, HealthWarningSummaryCompositeFormat, displayName), _ => string.Format(System.Globalization.CultureInfo.InvariantCulture, HealthReadySummaryCompositeFormat, displayName), }; @@ -283,18 +303,19 @@ private static ToolchainActionDescriptor[] CreateActions( private static ToolchainDiagnosticDescriptor[] CreateDiagnostics( ToolchainProviderProfile profile, bool isInstalled, + bool launchAvailable, bool authConfigured, string installedVersion, bool toolAccessAvailable) { - var launchPassed = isInstalled; + var launchPassed = launchAvailable; var versionPassed = !string.IsNullOrWhiteSpace(installedVersion); var connectionReady = launchPassed && authConfigured; var resumeReady = connectionReady; return [ - new(LaunchDiagnosticName, launchPassed ? ToolchainDiagnosticStatus.Passed : ToolchainDiagnosticStatus.Failed, launchPassed ? LaunchPassedSummary : LaunchFailedSummary), + new(LaunchDiagnosticName, launchPassed ? ToolchainDiagnosticStatus.Passed : ToolchainDiagnosticStatus.Failed, launchPassed ? LaunchPassedSummary : (isInstalled ? LaunchUnavailableSummary : LaunchFailedSummary)), new(VersionDiagnosticName, launchPassed ? (versionPassed ? ToolchainDiagnosticStatus.Passed : ToolchainDiagnosticStatus.Warning) : ToolchainDiagnosticStatus.Blocked, versionPassed ? ResolveVersionSummary(ToolchainVersionStatus.Detected, installedVersion) : VersionFailedSummary), new(AuthDiagnosticName, launchPassed ? (authConfigured ? ToolchainDiagnosticStatus.Passed : ToolchainDiagnosticStatus.Warning) : ToolchainDiagnosticStatus.Blocked, authConfigured ? AuthConnectedSummary : AuthMissingSummary), new(profile.ToolAccessDiagnosticName, launchPassed ? (toolAccessAvailable ? ToolchainDiagnosticStatus.Passed : ToolchainDiagnosticStatus.Warning) : ToolchainDiagnosticStatus.Blocked, toolAccessAvailable ? profile.ToolAccessReadySummary : profile.ToolAccessBlockedSummary), diff --git a/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs b/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs index be0177b..2df2742 100644 --- a/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs +++ b/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs @@ -4,10 +4,8 @@ public class RuntimeFoundationCatalogTests { private const string ApprovalPrompt = "Please continue, but stop for approval before changing files."; private const string BlankPrompt = " "; - private const string CodexCommandName = "codex"; - private const string ClaudeCommandName = "claude"; - private const string GitHubCommandName = "gh"; private const string DeterministicClientStatusSummary = "Always available for in-repo and CI validation."; + private const string RuntimeEpicLabel = "LOCAL RUNTIME READINESS"; private static readonly DateTimeOffset DeterministicArtifactCreatedAt = new(2026, 3, 13, 0, 0, 0, TimeSpan.Zero); [Test] @@ -17,8 +15,9 @@ public void CatalogGroupsEpicTwelveIntoFourSequencedSlices() var snapshot = catalog.GetSnapshot(); - snapshot.EpicLabel.Should().Be(RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.EmbeddedAgentRuntimeHostEpic)); + snapshot.EpicLabel.Should().Be(RuntimeEpicLabel); snapshot.Slices.Should().HaveCount(4); + snapshot.Slices.Select(slice => slice.IssueLabel).Should().ContainInOrder("DOMAIN", "CONTRACTS", "HOST", "ORCHESTRATION"); snapshot.Slices.Select(slice => slice.IssueNumber).Should().ContainInOrder( RuntimeFoundationIssues.DomainModel, RuntimeFoundationIssues.CommunicationContracts, @@ -180,23 +179,6 @@ public void DeterministicClientRejectsUnexpectedExecutionModes() action.Should().Throw(); } - [TestCase(CodexCommandName)] - [TestCase(ClaudeCommandName)] - [TestCase(GitHubCommandName)] - public void ExternalToolchainVerificationRunsOnlyWhenTheCommandIsAvailable(string commandName) - { - var catalog = CreateCatalog(); - var provider = catalog.GetSnapshot().Providers.Single(item => item.CommandName == commandName); - - Assume.That( - provider.Status, - Is.EqualTo(ProviderConnectionStatus.Available), - $"The '{commandName}' toolchain is not available in this environment."); - - provider.RequiresExternalToolchain.Should().BeTrue(); - provider.StatusSummary.Should().Contain("available"); - } - [Test] public void CatalogPreservesProviderIdentityAcrossSnapshotRefreshes() { @@ -231,15 +213,14 @@ public void TypedIdentifiersProduceStableNonEmptyRepresentations() } [Test] - [NonParallelizable] - public void ExternalProvidersBecomeUnavailableWhenPathIsCleared() + public void CatalogCachesProviderListAcrossSnapshotReads() { - using var scope = new EnvironmentVariableScope("PATH", string.Empty); var catalog = CreateCatalog(); - var externalProviders = catalog.GetSnapshot().Providers.Where(provider => provider.RequiresExternalToolchain); + var firstSnapshot = catalog.GetSnapshot(); + var secondSnapshot = catalog.GetSnapshot(); - externalProviders.Should().OnlyContain(provider => provider.Status == ProviderConnectionStatus.Unavailable); + ReferenceEquals(firstSnapshot.Providers, secondSnapshot.Providers).Should().BeTrue(); } private static RuntimeFoundationCatalog CreateCatalog() @@ -254,22 +235,4 @@ private static AgentTurnRequest CreateRequest( { return new AgentTurnRequest(SessionId.New(), AgentProfileId.New(), prompt, mode, providerStatus); } - - private sealed class EnvironmentVariableScope : IDisposable - { - private readonly string _variableName; - private readonly string? _originalValue; - - public EnvironmentVariableScope(string variableName, string? value) - { - _variableName = variableName; - _originalValue = Environment.GetEnvironmentVariable(variableName); - Environment.SetEnvironmentVariable(variableName, value); - } - - public void Dispose() - { - Environment.SetEnvironmentVariable(_variableName, _originalValue); - } - } } diff --git a/DotPilot.Tests/Features/ToolchainCenter/ToolchainCenterCatalogTests.cs b/DotPilot.Tests/Features/ToolchainCenter/ToolchainCenterCatalogTests.cs index b31831e..9b38c99 100644 --- a/DotPilot.Tests/Features/ToolchainCenter/ToolchainCenterCatalogTests.cs +++ b/DotPilot.Tests/Features/ToolchainCenter/ToolchainCenterCatalogTests.cs @@ -2,6 +2,8 @@ namespace DotPilot.Tests.Features.ToolchainCenter; public class ToolchainCenterCatalogTests { + private const string ToolchainEpicLabel = "PRESESSION READINESS"; + [Test] public void CatalogIncludesEpicIssueCoverageAndAllExternalProviders() { @@ -14,7 +16,9 @@ public void CatalogIncludesEpicIssueCoverageAndAllExternalProviders() .Order() .ToArray(); - snapshot.EpicLabel.Should().Be(ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ToolchainCenterEpic)); + snapshot.EpicLabel.Should().Be(ToolchainEpicLabel); + snapshot.Summary.Should().NotContain("Issue #"); + snapshot.Workstreams.Select(workstream => workstream.IssueLabel).Should().Equal("SURFACE", "DIAGNOSTICS", "CONFIGURATION", "POLLING"); coveredIssues.Should().Equal( ToolchainCenterIssues.ToolchainCenterUi, ToolchainCenterIssues.CodexReadiness, @@ -54,6 +58,16 @@ public void CatalogCanStartAndDisposeBackgroundPolling() snapshot.Providers.Should().NotBeEmpty(); } + [Test] + public void CatalogDisposeIsIdempotentAfterBackgroundPollingStarts() + { + var catalog = new ToolchainCenterCatalog(TimeProvider.System, startBackgroundPolling: true); + + catalog.Dispose(); + + catalog.Invoking(item => item.Dispose()).Should().NotThrow(); + } + [Test] [NonParallelizable] public void CatalogMarksProvidersMissingWhenPathAndAuthenticationSignalsAreCleared() diff --git a/DotPilot.Tests/Features/ToolchainCenter/ToolchainCommandProbeTests.cs b/DotPilot.Tests/Features/ToolchainCenter/ToolchainCommandProbeTests.cs index 105d6d8..86c0112 100644 --- a/DotPilot.Tests/Features/ToolchainCenter/ToolchainCommandProbeTests.cs +++ b/DotPilot.Tests/Features/ToolchainCenter/ToolchainCommandProbeTests.cs @@ -4,6 +4,8 @@ namespace DotPilot.Tests.Features.ToolchainCenter; public class ToolchainCommandProbeTests { + private const string NonExecutableContents = "not an executable"; + [Test] public void ReadVersionUsesStandardErrorWhenStandardOutputIsEmpty() { @@ -82,6 +84,37 @@ public void ReadVersionReturnsEmptyWhenTheCommandTimesOut() version.Should().BeEmpty(); } + [Test] + public void CanExecuteReturnsFalseWhenTheResolvedPathCannotBeLaunched() + { + var nonExecutablePath = Path.GetTempFileName(); + + try + { + File.WriteAllText(nonExecutablePath, NonExecutableContents); + + CanExecute(nonExecutablePath, []).Should().BeFalse(); + ReadVersion(nonExecutablePath, []).Should().BeEmpty(); + } + finally + { + File.Delete(nonExecutablePath); + } + } + + [Test] + public void CanExecuteReturnsTrueWhenTheCommandProducesLargeRedirectedOutput() + { + var (executablePath, arguments) = CreateShellCommand( + OperatingSystem.IsWindows() + ? "for /L %i in (1,1,3000) do @echo output-line-%i" + : "i=1; while [ $i -le 3000 ]; do printf 'output-line-%s\\n' \"$i\"; i=$((i+1)); done"); + + var canExecute = CanExecute(executablePath, arguments); + + canExecute.Should().BeTrue(); + } + private static string ReadVersion(string executablePath, IReadOnlyList arguments) { return (string)InvokeProbeMethod("ReadVersion", executablePath, arguments); diff --git a/DotPilot.Tests/Features/ToolchainCenter/ToolchainProviderSnapshotFactoryTests.cs b/DotPilot.Tests/Features/ToolchainCenter/ToolchainProviderSnapshotFactoryTests.cs index 7aad388..62b1a61 100644 --- a/DotPilot.Tests/Features/ToolchainCenter/ToolchainProviderSnapshotFactoryTests.cs +++ b/DotPilot.Tests/Features/ToolchainCenter/ToolchainProviderSnapshotFactoryTests.cs @@ -7,46 +7,72 @@ public class ToolchainProviderSnapshotFactoryTests [Test] public void ResolveProviderStatusCoversUnavailableAuthenticationAndMisconfiguredBranches() { - ResolveProviderStatus(isInstalled: false, authConfigured: false, toolAccessAvailable: false) + ResolveProviderStatus(isInstalled: false, launchAvailable: false, authConfigured: false, toolAccessAvailable: false) .Should().Be(ProviderConnectionStatus.Unavailable); - ResolveProviderStatus(isInstalled: true, authConfigured: false, toolAccessAvailable: false) + ResolveProviderStatus(isInstalled: true, launchAvailable: false, authConfigured: false, toolAccessAvailable: false) + .Should().Be(ProviderConnectionStatus.Unavailable); + ResolveProviderStatus(isInstalled: true, launchAvailable: true, authConfigured: false, toolAccessAvailable: false) .Should().Be(ProviderConnectionStatus.RequiresAuthentication); - ResolveProviderStatus(isInstalled: true, authConfigured: true, toolAccessAvailable: false) + ResolveProviderStatus(isInstalled: true, launchAvailable: true, authConfigured: true, toolAccessAvailable: false) .Should().Be(ProviderConnectionStatus.Misconfigured); - ResolveProviderStatus(isInstalled: true, authConfigured: true, toolAccessAvailable: true) + ResolveProviderStatus(isInstalled: true, launchAvailable: true, authConfigured: true, toolAccessAvailable: true) .Should().Be(ProviderConnectionStatus.Available); } [Test] public void ResolveReadinessStateCoversMissingActionRequiredLimitedAndReady() { - ResolveReadinessState(isInstalled: false, authConfigured: false, toolAccessAvailable: false, installedVersion: string.Empty) + ResolveReadinessState(isInstalled: false, launchAvailable: false, authConfigured: false, toolAccessAvailable: false, installedVersion: string.Empty) + .Should().Be(ToolchainReadinessState.Missing); + ResolveReadinessState(isInstalled: true, launchAvailable: false, authConfigured: false, toolAccessAvailable: true, installedVersion: "1.0.0") .Should().Be(ToolchainReadinessState.Missing); - ResolveReadinessState(isInstalled: true, authConfigured: false, toolAccessAvailable: true, installedVersion: "1.0.0") + ResolveReadinessState(isInstalled: true, launchAvailable: true, authConfigured: false, toolAccessAvailable: true, installedVersion: "1.0.0") .Should().Be(ToolchainReadinessState.ActionRequired); - ResolveReadinessState(isInstalled: true, authConfigured: true, toolAccessAvailable: false, installedVersion: "1.0.0") + ResolveReadinessState(isInstalled: true, launchAvailable: true, authConfigured: true, toolAccessAvailable: false, installedVersion: "1.0.0") .Should().Be(ToolchainReadinessState.Limited); - ResolveReadinessState(isInstalled: true, authConfigured: true, toolAccessAvailable: true, installedVersion: string.Empty) + ResolveReadinessState(isInstalled: true, launchAvailable: true, authConfigured: true, toolAccessAvailable: true, installedVersion: string.Empty) .Should().Be(ToolchainReadinessState.Limited); - ResolveReadinessState(isInstalled: true, authConfigured: true, toolAccessAvailable: true, installedVersion: "1.0.0") + ResolveReadinessState(isInstalled: true, launchAvailable: true, authConfigured: true, toolAccessAvailable: true, installedVersion: "1.0.0") .Should().Be(ToolchainReadinessState.Ready); } [Test] public void ResolveHealthStatusCoversBlockedWarningAndHealthy() { - ResolveHealthStatus(isInstalled: false, authConfigured: false, toolAccessAvailable: false, installedVersion: string.Empty) + ResolveHealthStatus(isInstalled: false, launchAvailable: false, authConfigured: false, toolAccessAvailable: false, installedVersion: string.Empty) + .Should().Be(ToolchainHealthStatus.Blocked); + ResolveHealthStatus(isInstalled: true, launchAvailable: false, authConfigured: false, toolAccessAvailable: true, installedVersion: "1.0.0") .Should().Be(ToolchainHealthStatus.Blocked); - ResolveHealthStatus(isInstalled: true, authConfigured: false, toolAccessAvailable: true, installedVersion: "1.0.0") + ResolveHealthStatus(isInstalled: true, launchAvailable: true, authConfigured: false, toolAccessAvailable: true, installedVersion: "1.0.0") .Should().Be(ToolchainHealthStatus.Blocked); - ResolveHealthStatus(isInstalled: true, authConfigured: true, toolAccessAvailable: false, installedVersion: "1.0.0") + ResolveHealthStatus(isInstalled: true, launchAvailable: true, authConfigured: true, toolAccessAvailable: false, installedVersion: "1.0.0") .Should().Be(ToolchainHealthStatus.Warning); - ResolveHealthStatus(isInstalled: true, authConfigured: true, toolAccessAvailable: true, installedVersion: string.Empty) + ResolveHealthStatus(isInstalled: true, launchAvailable: true, authConfigured: true, toolAccessAvailable: true, installedVersion: string.Empty) .Should().Be(ToolchainHealthStatus.Warning); - ResolveHealthStatus(isInstalled: true, authConfigured: true, toolAccessAvailable: true, installedVersion: "1.0.0") + ResolveHealthStatus(isInstalled: true, launchAvailable: true, authConfigured: true, toolAccessAvailable: true, installedVersion: "1.0.0") .Should().Be(ToolchainHealthStatus.Healthy); } + [Test] + public void ResolveReadinessSummaryDistinguishesMissingInstallFromBrokenLaunch() + { + ResolveReadinessSummary("Codex CLI", isInstalled: false, launchAvailable: false, ToolchainReadinessState.Missing) + .Should().Contain("not installed"); + ResolveReadinessSummary("Codex CLI", isInstalled: true, launchAvailable: false, ToolchainReadinessState.Missing) + .Should().Contain("could not launch"); + } + + [Test] + public void ResolveHealthSummaryPrefersInstallAndLaunchGuidanceBeforeAuth() + { + ResolveHealthSummary("Codex CLI", ToolchainHealthStatus.Blocked, isInstalled: false, launchAvailable: false, authConfigured: false) + .Should().Contain("installed"); + ResolveHealthSummary("Codex CLI", ToolchainHealthStatus.Blocked, isInstalled: true, launchAvailable: false, authConfigured: false) + .Should().Contain("start the CLI"); + ResolveHealthSummary("Codex CLI", ToolchainHealthStatus.Blocked, isInstalled: true, launchAvailable: true, authConfigured: false) + .Should().Contain("authentication"); + } + [Test] public void ResolveConfigurationStatusDistinguishesRequiredAndOptionalSignals() { @@ -61,17 +87,19 @@ public void ResolveConfigurationStatusDistinguishesRequiredAndOptionalSignals() .Should().Be(ToolchainConfigurationStatus.Configured); } - private static ProviderConnectionStatus ResolveProviderStatus(bool isInstalled, bool authConfigured, bool toolAccessAvailable) + private static ProviderConnectionStatus ResolveProviderStatus(bool isInstalled, bool launchAvailable, bool authConfigured, bool toolAccessAvailable) { return (ProviderConnectionStatus)InvokeFactoryMethod( "ResolveProviderStatus", isInstalled, + launchAvailable, authConfigured, toolAccessAvailable)!; } private static ToolchainReadinessState ResolveReadinessState( bool isInstalled, + bool launchAvailable, bool authConfigured, bool toolAccessAvailable, string installedVersion) @@ -79,6 +107,7 @@ private static ToolchainReadinessState ResolveReadinessState( return (ToolchainReadinessState)InvokeFactoryMethod( "ResolveReadinessState", isInstalled, + launchAvailable, authConfigured, toolAccessAvailable, installedVersion)!; @@ -86,6 +115,7 @@ private static ToolchainReadinessState ResolveReadinessState( private static ToolchainHealthStatus ResolveHealthStatus( bool isInstalled, + bool launchAvailable, bool authConfigured, bool toolAccessAvailable, string installedVersion) @@ -93,11 +123,42 @@ private static ToolchainHealthStatus ResolveHealthStatus( return (ToolchainHealthStatus)InvokeFactoryMethod( "ResolveHealthStatus", isInstalled, + launchAvailable, authConfigured, toolAccessAvailable, installedVersion)!; } + private static string ResolveReadinessSummary( + string displayName, + bool isInstalled, + bool launchAvailable, + ToolchainReadinessState readinessState) + { + return (string)InvokeFactoryMethod( + "ResolveReadinessSummary", + displayName, + isInstalled, + launchAvailable, + readinessState)!; + } + + private static string ResolveHealthSummary( + string displayName, + ToolchainHealthStatus healthStatus, + bool isInstalled, + bool launchAvailable, + bool authConfigured) + { + return (string)InvokeFactoryMethod( + "ResolveHealthSummary", + displayName, + healthStatus, + isInstalled, + launchAvailable, + authConfigured)!; + } + private static ToolchainConfigurationStatus ResolveConfigurationStatus(object signal, bool isConfigured) { return (ToolchainConfigurationStatus)InvokeFactoryMethod("ResolveConfigurationStatus", signal, isConfigured)!; diff --git a/DotPilot.Tests/Features/Workbench/PresentationViewModelTests.cs b/DotPilot.Tests/Features/Workbench/PresentationViewModelTests.cs index 84787e1..5252235 100644 --- a/DotPilot.Tests/Features/Workbench/PresentationViewModelTests.cs +++ b/DotPilot.Tests/Features/Workbench/PresentationViewModelTests.cs @@ -26,7 +26,7 @@ public void MainViewModelExposesWorkbenchShellState() viewModel.IsPreviewMode.Should().BeFalse(); viewModel.IsLogConsoleVisible = true; viewModel.IsArtifactsVisible.Should().BeFalse(); - viewModel.RuntimeFoundation.EpicLabel.Should().Be(RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.EmbeddedAgentRuntimeHostEpic)); + viewModel.RuntimeFoundation.EpicLabel.Should().Be("LOCAL RUNTIME READINESS"); viewModel.RuntimeFoundation.Providers.Should().Contain(provider => !provider.RequiresExternalToolchain); } @@ -69,7 +69,7 @@ public void SecondViewModelExposesAgentBuilderState() viewModel.Skills.Should().Contain(skill => skill.IsEnabled); viewModel.Skills.Should().Contain(skill => !skill.IsEnabled); viewModel.RuntimeFoundation.DeterministicClientName.Should().Be("In-Repo Test Client"); - viewModel.RuntimeFoundation.Providers.Should().HaveCountGreaterOrEqualTo(4); + viewModel.RuntimeFoundation.Providers.Should().ContainSingle(); } private static RuntimeFoundationCatalog CreateRuntimeFoundationCatalog() diff --git a/docs/Architecture.md b/docs/Architecture.md index 63a80de..2ba8201 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -162,7 +162,6 @@ flowchart LR Catalog["RuntimeFoundationCatalog"] Toolchains["ToolchainCenterCatalog"] TestClient["DeterministicAgentRuntimeClient"] - Probe["ProviderToolchainProbe"] ToolchainProbe["ToolchainCommandProbe + provider profiles"] Contracts["Typed IDs + contracts"] Future["Future Orleans + Agent Framework integrations"] @@ -172,7 +171,6 @@ flowchart LR ViewModels --> Catalog ViewModels --> Toolchains Catalog --> TestClient - Catalog --> Probe Catalog --> Contracts Toolchains --> ToolchainProbe Toolchains --> Contracts diff --git a/pr-76-review-followup.plan.md b/pr-76-review-followup.plan.md new file mode 100644 index 0000000..2f2e4f0 --- /dev/null +++ b/pr-76-review-followup.plan.md @@ -0,0 +1,87 @@ +# PR 76 Review Follow-up Plan + +## Goal + +Address the meaningful review comments on `PR #76`, remove backlog-specific text that leaked into production `ToolchainCenter` runtime metadata, and update the PR body so merge closes every relevant open issue included in the stacked change set. + +## Scope + +- In scope: + - `DotPilot.Runtime` fixes for `ToolchainCenterCatalog`, `ToolchainCommandProbe`, `ToolchainProviderSnapshotFactory`, and `RuntimeFoundationCatalog` + - regression and behavior tests in `DotPilot.Tests` + - PR `#76` body update with GitHub closing references for the open issue stack included in the branch history +- Out of scope: + - new product features outside existing `PR #76` + - dependency changes + - release workflow changes + +## Constraints And Risks + +- Build and test must run with `-warnaserror`. +- Do not run parallel `dotnet` or `MSBuild` work in the same checkout. +- `DotPilot.UITests` remains mandatory final verification. +- Review fixes must not keep GitHub backlog text inside production runtime snapshots or user-facing summaries. +- PR body should only close issues actually delivered by this stacked branch. + +## Testing Methodology + +- Runtime snapshot and probe behavior will be tested through `DotPilot.Tests` using real subprocess execution paths rather than mocks. +- Catalog lifecycle fixes will be covered with deterministic tests that validate disposal, snapshot stability, and provider caching behavior. +- Final validation must prove both the focused runtime slice and the broader repo verification path. + +## Ordered Plan + +- [x] Step 1. Establish the real baseline for this PR branch. + - Verification: + - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter Toolchain` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter RuntimeFoundationCatalog` +- [x] Step 2. Remove backlog-specific text from `ToolchainCenterCatalog` and make snapshot polling/disposal thread-safe. + - Verification: + - targeted `ToolchainCenterCatalogTests` +- [x] Step 3. Fix `ToolchainCommandProbe` launch-failure and redirected-stream handling. + - Verification: + - targeted `ToolchainCommandProbeTests` +- [x] Step 4. Fix provider-summary/status logic in `ToolchainProviderSnapshotFactory`. + - Verification: + - targeted `ToolchainProviderSnapshotFactoryTests` +- [x] Step 5. Fix `RuntimeFoundationCatalog` provider caching so UI-thread snapshot reads do not re-probe subprocesses. + - Verification: + - targeted `RuntimeFoundationCatalogTests` +- [x] Step 6. Update PR `#76` body with GitHub closing references for all relevant open issues merged through this stack. + - Verification: + - `gh pr view 76 --repo managedcode/dotPilot --json body` +- [x] Step 7. Run final verification and record outcomes. + - Verification: + - `dotnet format DotPilot.slnx --verify-no-changes` + - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj` + - `dotnet test DotPilot.UITests/DotPilot.UITests.csproj` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` + +## Baseline Results + +- [x] `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` +- [x] `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter Toolchain` +- [x] `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter RuntimeFoundationCatalog` + +## Known Failing Tests + +- None. The focused baseline and final repo validation passed. + +## Results + +- `dotnet format DotPilot.slnx --verify-no-changes` passed. +- `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` passed. +- `dotnet test DotPilot.slnx` passed with `57` unit tests and `22` UI tests green. +- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` passed with overall collector result `91.09%` line / `63.66%` branch. +- `PR #76` body now uses `Closes #13`, `Closes #14`, and `Closes #28-#39`, so those issues will auto-close on merge. + +## Final Validation Skills + +- `mcaf-dotnet` + - Run build and test verification with the repo-defined commands. +- `mcaf-testing` + - Confirm new regressions cover the review-comment failure modes. +- `gh-address-comments` + - Verify the review comments are resolved and the PR body closes the correct issues on merge. From fbe39c8bf4e57f27041fc3d37cf6951e565b2f1e Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 13 Mar 2026 23:35:36 +0100 Subject: [PATCH 5/8] fix consolidated branch validation baseline --- CodeMetricsConfig.txt | 3 ++ consolidate-codex-branches.plan.md | 80 ++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 CodeMetricsConfig.txt create mode 100644 consolidate-codex-branches.plan.md diff --git a/CodeMetricsConfig.txt b/CodeMetricsConfig.txt new file mode 100644 index 0000000..e3bf457 --- /dev/null +++ b/CodeMetricsConfig.txt @@ -0,0 +1,3 @@ +# Tighten maintainability checks enough to catch genuinely overloaded methods +# without turning the first pass into pure noise. +CA1502(Method): 15 diff --git a/consolidate-codex-branches.plan.md b/consolidate-codex-branches.plan.md new file mode 100644 index 0000000..7f5808f --- /dev/null +++ b/consolidate-codex-branches.plan.md @@ -0,0 +1,80 @@ +## Goal + +Consolidate the user-requested local branches into one new branch, keep the merged runtime and UI fixes, restore a green validation baseline, open one replacement PR, and leave only `main` plus the new consolidated branch in the local repo. + +## Scope + +In scope: +- Merge the requested branch content into `codex/consolidated-13-15-76` +- Fix any integration regressions introduced by the consolidation +- Re-run full repo validation, including `DotPilot.UITests` +- Push the consolidated branch and open a single PR +- Remove extra local branches and extra local worktrees so only `main` and the consolidated branch remain + +Out of scope: +- New backlog feature work outside the merged branches +- Any dependency additions +- Human merge/approval actions on GitHub + +## Constraints And Risks + +- The repo requires `-warnaserror` builds. +- UI tests must run through the real `DotPilot.UITests` harness; no manual app launch outside the harness. +- The consolidated branch must preserve the startup responsiveness fixes from the PR 76 review follow-up. +- The local branch cleanup must not delete `main` or the new consolidated branch. + +## Testing Methodology + +- Validate the compile baseline with the repo `build` command. +- Validate end-to-end UI behavior only through `dotnet test DotPilot.UITests/DotPilot.UITests.csproj`. +- Validate the full repo through the solution test command after focused fixes are green. +- Validate coverage with the repo collector command and confirm no regression versus the pre-consolidation baseline. + +## Ordered Plan + +- [x] Confirm the active branch/worktree state and identify the consolidated branch target. +- [x] Reproduce the consolidated-branch regression through the real `DotPilot.UITests` harness. +- [x] Capture the root cause of the harness failure instead of treating it as a generic host timeout. +- [x] Restore the missing shared build input and any other merge fallout required to make the browser host buildable again. +- [x] Run focused UI verification to prove the browser host starts and the failing settings/workbench flow passes again. +- [x] Run the full required validation sequence: + - `dotnet format DotPilot.slnx --verify-no-changes` + - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - `dotnet test DotPilot.slnx` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` +- [ ] Commit the consolidation fixes on `codex/consolidated-13-15-76`. +- [ ] Push the consolidated branch and open one replacement PR to `main`. +- [ ] Delete extra local branches and extra local worktrees so only `main` and `codex/consolidated-13-15-76` remain locally. + +## Full-Test Baseline + +- `dotnet test DotPilot.UITests/DotPilot.UITests.csproj --filter FullyQualifiedName~WhenNavigatingToSettingsThenCategoriesAndEntriesAreVisible -v minimal` + - Failed before test execution with `CSC` errors because `/Users/ksemenenko/Developer/dotPilot/CodeMetricsConfig.txt` was missing during the `net10.0-browserwasm` host build. + +## Tracked Failing Tests + +- [x] `WhenNavigatingToSettingsThenCategoriesAndEntriesAreVisible` + - Symptom: browser host exits before reachable + - Root cause: `CodeMetricsConfig.txt` missing from repo root, so the browserwasm compile inside the harness fails + - Intended fix: restore `CodeMetricsConfig.txt` with the shared analyzer config content and rerun the harness + +## Verification Results + +- `dotnet test DotPilot.UITests/DotPilot.UITests.csproj --filter FullyQualifiedName~WhenNavigatingToSettingsThenCategoriesAndEntriesAreVisible -v minimal` + - Passed: `1` +- `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - Passed with `0` warnings and `0` errors +- `dotnet test DotPilot.slnx` + - Passed: `60` unit tests and `22` UI tests +- `dotnet format DotPilot.slnx --verify-no-changes` + - Passed +- `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` + - Passed: `60` tests + - Coverage artifact: `DotPilot.Tests/TestResults/9a4b4ba7-ae2c-4a23-9eab-0af4d4e30730/coverage.cobertura.xml` + +## Done Criteria + +- The consolidated branch contains the requested merged work plus the follow-up fixes. +- Full repo validation is green. +- One PR exists for the consolidated branch. +- Only `main` and `codex/consolidated-13-15-76` remain as local branches. From 9c5481c691f0d177a9b97d2f8d7b02a188014d84 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 13 Mar 2026 23:37:21 +0100 Subject: [PATCH 6/8] document consolidated branch completion --- consolidate-codex-branches.plan.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/consolidate-codex-branches.plan.md b/consolidate-codex-branches.plan.md index 7f5808f..814ceba 100644 --- a/consolidate-codex-branches.plan.md +++ b/consolidate-codex-branches.plan.md @@ -42,9 +42,9 @@ Out of scope: - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` - `dotnet test DotPilot.slnx` - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` -- [ ] Commit the consolidation fixes on `codex/consolidated-13-15-76`. -- [ ] Push the consolidated branch and open one replacement PR to `main`. -- [ ] Delete extra local branches and extra local worktrees so only `main` and `codex/consolidated-13-15-76` remain locally. +- [x] Commit the consolidation fixes on `codex/consolidated-13-15-76`. +- [x] Push the consolidated branch and open one replacement PR to `main`. +- [x] Delete extra local branches and extra local worktrees so only `main` and `codex/consolidated-13-15-76` remain locally. ## Full-Test Baseline @@ -72,6 +72,14 @@ Out of scope: - Passed: `60` tests - Coverage artifact: `DotPilot.Tests/TestResults/9a4b4ba7-ae2c-4a23-9eab-0af4d4e30730/coverage.cobertura.xml` +## Git Results + +- Consolidated branch pushed: `origin/codex/consolidated-13-15-76` +- Replacement PR: `#79` +- Local branches remaining: `main`, `codex/consolidated-13-15-76` +- Local worktrees remaining: only `/Users/ksemenenko/Developer/dotPilot` +- Remote branches remaining for this repo clone: `origin/main`, `origin/codex/consolidated-13-15-76` + ## Done Criteria - The consolidated branch contains the requested merged work plus the follow-up fixes. From 1934b8856d754cb37abd0ad8c2bdb79507c6a789 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Sat, 14 Mar 2026 02:01:49 +0100 Subject: [PATCH 7/8] Fix PR #79 review follow-ups --- .../ToolchainCenterContracts.cs | 4 +- .../DeterministicAgentRuntimeClient.cs | 3 +- .../RuntimeFoundationCatalog.cs | 4 +- .../ToolchainCenter/ToolchainCenterCatalog.cs | 2 +- .../ToolchainCenter/ToolchainCommandProbe.cs | 49 ++++++++++--- .../RuntimeFoundationCatalogTests.cs | 7 +- .../ToolchainCenterCatalogTests.cs | 4 +- DotPilot.UITests/AGENTS.md | 2 +- .../Controls/ToolchainCenterPanel.xaml | 6 +- .../WorkbenchPresentationModels.cs | 2 +- docs/Architecture.md | 3 - pr-review-comment-sweep.plan.md | 73 +++++++++++++++++++ 12 files changed, 131 insertions(+), 28 deletions(-) create mode 100644 pr-review-comment-sweep.plan.md diff --git a/DotPilot.Core/Features/ToolchainCenter/ToolchainCenterContracts.cs b/DotPilot.Core/Features/ToolchainCenter/ToolchainCenterContracts.cs index 7fb1fa2..dae02b9 100644 --- a/DotPilot.Core/Features/ToolchainCenter/ToolchainCenterContracts.cs +++ b/DotPilot.Core/Features/ToolchainCenter/ToolchainCenterContracts.cs @@ -4,7 +4,7 @@ namespace DotPilot.Core.Features.ToolchainCenter; public sealed record ToolchainCenterWorkstreamDescriptor( int IssueNumber, - string IssueLabel, + string SectionLabel, string Name, string Summary); @@ -37,7 +37,7 @@ public sealed record ToolchainPollingDescriptor( public sealed record ToolchainProviderSnapshot( int IssueNumber, - string IssueLabel, + string SectionLabel, ProviderDescriptor Provider, string ExecutablePath, string InstalledVersion, diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs b/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs index c6b6bdd..2222f8d 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/DeterministicAgentRuntimeClient.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using DotPilot.Core.Features.ControlPlaneDomain; using DotPilot.Core.Features.RuntimeCommunication; using DotPilot.Core.Features.RuntimeFoundation; @@ -64,7 +63,7 @@ AgentExecutionMode.Execute when RequiresApproval(request.Prompt) => Result throw new UnreachableException(), + _ => Result.Fail(RuntimeCommunicationProblems.OrchestrationUnavailable()), }); } diff --git a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs index 944337b..a6118c6 100644 --- a/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs +++ b/DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs @@ -28,7 +28,7 @@ public sealed class RuntimeFoundationCatalog : IRuntimeFoundationCatalog "Agent Framework integration is prepared as a separate slice that can plug into the embedded host without reshaping the UI layer."; private readonly IReadOnlyList _providers; - public RuntimeFoundationCatalog() => _providers = CreateProviders(); + public RuntimeFoundationCatalog() => _providers = Array.AsReadOnly(CreateProviders()); public RuntimeFoundationSnapshot GetSnapshot() { @@ -72,7 +72,7 @@ private static IReadOnlyList CreateSlices() ]; } - private static IReadOnlyList CreateProviders() + private static ProviderDescriptor[] CreateProviders() { return [ diff --git a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCenterCatalog.cs b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCenterCatalog.cs index bf2d6fd..60f6b84 100644 --- a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCenterCatalog.cs +++ b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCenterCatalog.cs @@ -4,7 +4,7 @@ namespace DotPilot.Runtime.Features.ToolchainCenter; public sealed class ToolchainCenterCatalog : IToolchainCenterCatalog, IDisposable { - private const string EpicLabelValue = "PRESESSION READINESS"; + private const string EpicLabelValue = "PRE-SESSION READINESS"; private const string EpicSummary = "Provider installation, launch checks, authentication, configuration, and refresh state stay visible before the first live session."; private const string UiWorkstreamLabel = "SURFACE"; diff --git a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs index a9dd590..e2e7468 100644 --- a/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs +++ b/DotPilot.Runtime/Features/ToolchainCenter/ToolchainCommandProbe.cs @@ -5,6 +5,7 @@ namespace DotPilot.Runtime.Features.ToolchainCenter; internal static class ToolchainCommandProbe { private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(2); + private static readonly TimeSpan RedirectDrainTimeout = TimeSpan.FromSeconds(1); private const string VersionSeparator = "version"; private const string EmptyOutput = ""; @@ -87,14 +88,19 @@ private static ToolchainCommandExecution Execute(string executablePath, IReadOnl using (process) { - var standardOutputTask = process.StandardOutput.ReadToEndAsync(); - var standardErrorTask = process.StandardError.ReadToEndAsync(); + var standardOutputTask = ObserveRedirectedStream(process.StandardOutput.ReadToEndAsync()); + var standardErrorTask = ObserveRedirectedStream(process.StandardError.ReadToEndAsync()); if (!process.WaitForExit((int)CommandTimeout.TotalMilliseconds)) { TryTerminate(process); - ObserveRedirectedStreamFaults(standardOutputTask, standardErrorTask); - return new(true, false, EmptyOutput, EmptyOutput); + WaitForTermination(process); + + return new( + true, + false, + AwaitStreamRead(standardOutputTask), + AwaitStreamRead(standardErrorTask)); } return new( @@ -105,10 +111,26 @@ private static ToolchainCommandExecution Execute(string executablePath, IReadOnl } } + private static Task ObserveRedirectedStream(Task readTask) + { + _ = readTask.ContinueWith( + static task => _ = task.Exception, + CancellationToken.None, + TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + + return readTask; + } + private static string AwaitStreamRead(Task readTask) { try { + if (!readTask.Wait(RedirectDrainTimeout)) + { + return EmptyOutput; + } + return readTask.GetAwaiter().GetResult(); } catch @@ -117,19 +139,28 @@ private static string AwaitStreamRead(Task readTask) } } - private static void ObserveRedirectedStreamFaults(Task standardOutputTask, Task standardErrorTask) + private static void TryTerminate(Process process) { - _ = standardOutputTask.Exception; - _ = standardErrorTask.Exception; + try + { + if (!process.HasExited) + { + process.Kill(entireProcessTree: true); + } + } + catch + { + // Best-effort cleanup only. + } } - private static void TryTerminate(Process process) + private static void WaitForTermination(Process process) { try { if (!process.HasExited) { - process.Kill(entireProcessTree: true); + process.WaitForExit((int)RedirectDrainTimeout.TotalMilliseconds); } } catch diff --git a/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs b/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs index 2df2742..c2ff457 100644 --- a/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs +++ b/DotPilot.Tests/Features/RuntimeFoundation/RuntimeFoundationCatalogTests.cs @@ -174,9 +174,11 @@ public void DeterministicClientRejectsUnexpectedExecutionModes() var client = new DeterministicAgentRuntimeClient(); var invalidRequest = CreateRequest("Plan the runtime foundation rollout.", (AgentExecutionMode)int.MaxValue); - var action = () => client.ExecuteAsync(invalidRequest, CancellationToken.None); + var result = client.ExecuteAsync(invalidRequest, CancellationToken.None).AsTask().GetAwaiter().GetResult(); - action.Should().Throw(); + result.IsFailed.Should().BeTrue(); + result.HasProblem.Should().BeTrue(); + result.Problem!.HasErrorCode(RuntimeCommunicationProblemCode.OrchestrationUnavailable).Should().BeTrue(); } [Test] @@ -221,6 +223,7 @@ public void CatalogCachesProviderListAcrossSnapshotReads() var secondSnapshot = catalog.GetSnapshot(); ReferenceEquals(firstSnapshot.Providers, secondSnapshot.Providers).Should().BeTrue(); + firstSnapshot.Providers.Should().NotBeAssignableTo(); } private static RuntimeFoundationCatalog CreateCatalog() diff --git a/DotPilot.Tests/Features/ToolchainCenter/ToolchainCenterCatalogTests.cs b/DotPilot.Tests/Features/ToolchainCenter/ToolchainCenterCatalogTests.cs index 9b38c99..2acd56d 100644 --- a/DotPilot.Tests/Features/ToolchainCenter/ToolchainCenterCatalogTests.cs +++ b/DotPilot.Tests/Features/ToolchainCenter/ToolchainCenterCatalogTests.cs @@ -2,7 +2,7 @@ namespace DotPilot.Tests.Features.ToolchainCenter; public class ToolchainCenterCatalogTests { - private const string ToolchainEpicLabel = "PRESESSION READINESS"; + private const string ToolchainEpicLabel = "PRE-SESSION READINESS"; [Test] public void CatalogIncludesEpicIssueCoverageAndAllExternalProviders() @@ -18,7 +18,7 @@ public void CatalogIncludesEpicIssueCoverageAndAllExternalProviders() snapshot.EpicLabel.Should().Be(ToolchainEpicLabel); snapshot.Summary.Should().NotContain("Issue #"); - snapshot.Workstreams.Select(workstream => workstream.IssueLabel).Should().Equal("SURFACE", "DIAGNOSTICS", "CONFIGURATION", "POLLING"); + snapshot.Workstreams.Select(workstream => workstream.SectionLabel).Should().Equal("SURFACE", "DIAGNOSTICS", "CONFIGURATION", "POLLING"); coveredIssues.Should().Equal( ToolchainCenterIssues.ToolchainCenterUi, ToolchainCenterIssues.CodexReadiness, diff --git a/DotPilot.UITests/AGENTS.md b/DotPilot.UITests/AGENTS.md index ecc31e9..67b921f 100644 --- a/DotPilot.UITests/AGENTS.md +++ b/DotPilot.UITests/AGENTS.md @@ -13,7 +13,7 @@ Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven UI tests - `DotPilot.UITests.csproj` - `Harness/Constants.cs` - `Harness/TestBase.cs` -- `Features/ApplicationShell/Given_MainPage.cs` +- `Features/ApplicationShell/Given_MainPage.cs` (`GivenWorkbenchShell` in `DotPilot.UITests.Features.Workbench`) ## Boundaries diff --git a/DotPilot/Presentation/Controls/ToolchainCenterPanel.xaml b/DotPilot/Presentation/Controls/ToolchainCenterPanel.xaml index 0d8a89c..9e09cbd 100644 --- a/DotPilot/Presentation/Controls/ToolchainCenterPanel.xaml +++ b/DotPilot/Presentation/Controls/ToolchainCenterPanel.xaml @@ -19,7 +19,7 @@ FontSize="11" FontWeight="Medium" Foreground="{StaticResource AppMutedTextBrush}" - Text="{x:Bind Workstream.IssueLabel}" /> + Text="{x:Bind Workstream.SectionLabel}" /> @@ -44,7 +44,7 @@ FontSize="11" FontWeight="Medium" Foreground="{StaticResource AppMutedTextBrush}" - Text="{x:Bind IssueLabel}" /> + Text="{x:Bind SectionLabel}" /> + Text="{Binding SelectedToolchainProviderSnapshot.SectionLabel}" /> Snapshot.Provider.DisplayName; - public string IssueLabel => Snapshot.IssueLabel; + public string SectionLabel => Snapshot.SectionLabel; public string ReadinessLabel => Snapshot.ReadinessState.ToString(); diff --git a/docs/Architecture.md b/docs/Architecture.md index 2ba8201..2027d8a 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -35,7 +35,6 @@ flowchart LR Adr3["ADR-0003 vertical slices + UI-only app"] Feature["agent-control-plane-experience.md"] Toolchains["toolchain-center.md"] - Plan["vertical-slice-runtime-foundation.plan.md"] Ui["DotPilot Uno UI host"] Core["DotPilot.Core contracts"] Runtime["DotPilot.Runtime services"] @@ -48,7 +47,6 @@ flowchart LR Root --> Adr3 Root --> Feature Root --> Toolchains - Root --> Plan Root --> Ui Root --> Core Root --> Runtime @@ -183,7 +181,6 @@ flowchart LR ### Planning and decision docs - `Solution governance` — [../AGENTS.md](../AGENTS.md) -- `Task plan` — [../vertical-slice-runtime-foundation.plan.md](../vertical-slice-runtime-foundation.plan.md) - `Primary architecture decision` — [ADR-0001](./ADR/ADR-0001-agent-control-plane-architecture.md) - `Vertical-slice solution decision` — [ADR-0003](./ADR/ADR-0003-vertical-slices-and-ui-only-uno-app.md) - `Feature spec` — [Agent Control Plane Experience](./Features/agent-control-plane-experience.md) diff --git a/pr-review-comment-sweep.plan.md b/pr-review-comment-sweep.plan.md new file mode 100644 index 0000000..c726fa1 --- /dev/null +++ b/pr-review-comment-sweep.plan.md @@ -0,0 +1,73 @@ +## Goal + +Address the meaningful review comments across all currently open PRs created by this branch owner, starting from the oldest open PR and moving forward, then validate the affected slices and keep the repository push-ready. + +## Scope + +In scope: +- open PRs created by this account, processed oldest to newest +- code-review comments, review threads, and actionable issue comments that still make engineering sense +- code, tests, docs, and PR metadata changes needed to satisfy those comments +- verification for each touched slice plus the final required repo validation + +Out of scope: +- comments on already merged or closed PRs unless they reappear on an open PR +- comments that are stale, incorrect, or conflict with newer accepted decisions +- rebasing or rewriting unrelated branch history + +## Current PR Order + +1. PR `#79` — `codex/consolidated-13-15-76` +2. PR `#80` — `codex/issue-24-embedded-orleans-host` +3. PR `#81` — `codex/epic-12-embedded-runtime` +4. PR `#82` — `codex/epic-11-foundation-contracts` + +## Constraints And Risks + +- Start with the oldest open PR and move forward. +- Only fix comments that still make sense against the current repository state. +- Keep serial `dotnet` execution; do not run concurrent build/test commands in one checkout. +- Each production change needs corresponding automated coverage if behavior changes. +- The branch may need updates that touch multiple slices; keep validation layered and honest. + +## Testing Methodology + +- Gather all open review comments and unresolved threads for PRs `#79-#82`. +- For each PR, apply only the comments that remain valid. +- Run focused tests around the touched slice before moving to the next PR. +- After the sweep, run: + - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - `dotnet test DotPilot.slnx` + - `dotnet format DotPilot.slnx --verify-no-changes` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"` + +## Ordered Plan + +- [x] Confirm the open PR list and processing order. +- [x] Collect actionable review comments and threads for PRs `#79`, `#80`, `#81`, and `#82`. +- [x] Audit each comment for current validity and group them by PR and affected slice. +- [x] Apply the valid fixes for PR `#79` and run focused verification. +- [ ] Apply the valid fixes for PR `#80` and run focused verification. +- [ ] Apply the valid fixes for PR `#81` and run focused verification. +- [ ] Apply the valid fixes for PR `#82` and run focused verification. +- [ ] Run the full repo validation sequence. +- [ ] Commit the sweep and push the branch updates needed for the affected PR heads. + +## Full-Test Baseline + +- [x] Sweep baseline captured from open PR review threads and current branch verification. +- [x] PR `#79` focused verification passed: + - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter "FullyQualifiedName~ToolchainCenter|FullyQualifiedName~RuntimeFoundation"` + - `dotnet test DotPilot.UITests/DotPilot.UITests.csproj --filter "FullyQualifiedName~WhenNavigatingToSettingsThenCategoriesAndEntriesAreVisible|FullyQualifiedName~WhenNavigatingToSettingsThenToolchainCenterProviderDetailsAreVisible|FullyQualifiedName~WhenSwitchingToolchainProvidersThenProviderSpecificDetailsAreVisible"` + - `dotnet format DotPilot.slnx --verify-no-changes` + +## Tracked Failing Tests + +- [ ] No failing tests tracked yet. Add each failure here if comment-driven fixes expose regressions. + +## Done Criteria + +- Every meaningful open review comment across PRs `#79-#82` has been either fixed or explicitly rejected as stale/invalid. +- Relevant focused tests are green after each PR-specific fix set. +- The full repo validation sequence is green after the full sweep. From a266e3d56d0cc2e3af7e42905505a8a8c82f0e35 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Sat, 14 Mar 2026 02:27:05 +0100 Subject: [PATCH 8/8] Document PR review sweep completion --- pr-review-comment-sweep.plan.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/pr-review-comment-sweep.plan.md b/pr-review-comment-sweep.plan.md index c726fa1..03d402c 100644 --- a/pr-review-comment-sweep.plan.md +++ b/pr-review-comment-sweep.plan.md @@ -47,11 +47,11 @@ Out of scope: - [x] Collect actionable review comments and threads for PRs `#79`, `#80`, `#81`, and `#82`. - [x] Audit each comment for current validity and group them by PR and affected slice. - [x] Apply the valid fixes for PR `#79` and run focused verification. -- [ ] Apply the valid fixes for PR `#80` and run focused verification. -- [ ] Apply the valid fixes for PR `#81` and run focused verification. -- [ ] Apply the valid fixes for PR `#82` and run focused verification. -- [ ] Run the full repo validation sequence. -- [ ] Commit the sweep and push the branch updates needed for the affected PR heads. +- [x] Apply the valid fixes for PR `#80` and run focused verification. +- [x] Apply the valid fixes for PR `#81` and run focused verification. +- [x] Apply the valid fixes for PR `#82` and run focused verification. +- [x] Run the full repo validation sequence. +- [x] Commit the sweep and push the branch updates needed for the affected PR heads. ## Full-Test Baseline @@ -61,10 +61,27 @@ Out of scope: - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter "FullyQualifiedName~ToolchainCenter|FullyQualifiedName~RuntimeFoundation"` - `dotnet test DotPilot.UITests/DotPilot.UITests.csproj --filter "FullyQualifiedName~WhenNavigatingToSettingsThenCategoriesAndEntriesAreVisible|FullyQualifiedName~WhenNavigatingToSettingsThenToolchainCenterProviderDetailsAreVisible|FullyQualifiedName~WhenSwitchingToolchainProvidersThenProviderSpecificDetailsAreVisible"` - `dotnet format DotPilot.slnx --verify-no-changes` +- [x] PR `#80` focused verification passed: + - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter "FullyQualifiedName~EmbeddedRuntimeHost|FullyQualifiedName~ToolchainCommandProbe"` + - `dotnet format DotPilot.slnx --verify-no-changes` +- [x] PR `#81` focused verification passed: + - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter "FullyQualifiedName~AgentFrameworkRuntimeClient|FullyQualifiedName~EmbeddedRuntimeTrafficPolicy|FullyQualifiedName~RuntimeFoundationCatalog"` + - `dotnet format DotPilot.slnx --verify-no-changes` +- [x] PR `#82` focused verification passed: + - `dotnet build DotPilot.slnx -warnaserror -m:1 -p:BuildInParallel=false` + - `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --filter "FullyQualifiedName~ControlPlaneDomain"` + - `dotnet format DotPilot.slnx --verify-no-changes` +- [x] Full repo validation passed on every updated PR head: + - PR `#79` (`codex/consolidated-13-15-76`): `60` unit tests, `22` UI tests, coverage collector green. + - PR `#80` (`codex/issue-24-embedded-orleans-host`): `68` unit tests, `22` UI tests, coverage collector green. + - PR `#81` (`codex/epic-12-embedded-runtime`): `75` unit tests, `22` UI tests, coverage collector green. + - PR `#82` (`codex/epic-11-foundation-contracts`): `61` unit tests, `22` UI tests, coverage collector green. ## Tracked Failing Tests -- [ ] No failing tests tracked yet. Add each failure here if comment-driven fixes expose regressions. +- [x] No failing tests remained after the PR sweep. ## Done Criteria