feat(039): team board settings — export, import, architecture fixes#128
Conversation
…ions - Introduced tasks for exporting and importing team board configurations. - Defined phases for setup, foundational work, and user stories for exporting board columns, swimlanes, card rules, backlogs, and taskboard columns. - Established a test-first approach with detailed test cases for each user story. - Implemented connector capability mechanism and ensured compatibility with existing systems. - Added observability metrics and logging for export/import processes.
# Conflicts: # CLAUDE.md
- Updated the constitution to align testing principles with the canonical taxonomy, introducing dual parent and specific tags for tests. - Revised the technology stack section to reflect changes in the test framework and taxonomy references. - Enhanced the BoardSwimLane record to include Id as source-only metadata, aligning with the Azure DevOps API. - Introduced BacklogLevelType enum to capture backlog level types, reflecting changes in the data model. - Updated ConnectorCapability enum to include granular capabilities for board configuration, ensuring compliance with functional requirements. - Modified test approach to enforce tagging of tests with both parent family and specific category tags, ensuring clarity in test classification. - Added detailed acceptance scenarios and functional requirements for board configuration export/import, ensuring comprehensive coverage. - Established a documentation sync phase to ensure all discrepancies are resolved before merging the spec branch.
…msModule refactor
- Introduced a new spec addendum for the WorkItems module refactor, detailing the alignment with the extension model. - Updated tasks for the 039-team-board-settings to reflect changes in extension contracts and implementation plans. - Created IExtensionContext and IModuleExtension interfaces to standardize extension behavior across modules. - Established a clear separation of concerns between core functionality and extensions within the Teams module. - Documented the implementation plan for converting existing capabilities to the new extension model.
…rator (ADR 0019 Stage 1) Stage 1 of the WorkItems extension-seam refactor, delivered test-first (RED -> GREEN -> REFACTOR), full suite green (1064/1064), both TFMs build. - Move the inventory/capture loop from WorkItemsModule.CaptureAsync into WorkItemsOrchestrator.CaptureAsync (IWorkItemsOrchestrator gains CaptureAsync). Driven by new WorkItemsOrchestratorInventoryTests. - Remove the unnecessary IWorkItemsOrchestratorFactory/WorkItemsOrchestratorFactory (manual DI); the container now composes the orchestrator graph directly. - WorkItemsModule is now a thin facade taking only IWorkItemsOrchestrator; ctor collapsed ~30 params -> 1, all phases delegate. - Close a DI-vs-factory duplicate-construction regression (DI now passes the inventory deps to the orchestrator). - Remove dead ApplyImportReplayLevers + unused activity sources (Rule 30). - Relocate behavioural test construction to WorkItemsModuleTestFactory; remove obsolete fat-ctor guard-clause tests and redundant module-inventory tests; reframe the module-isolation test to the thin module + orchestrator. - Add ADR 0019 (accepted) and the WorkItems refactor spec addendum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduces the work-item extension port and the first facet, test-first (RED -> GREEN), full suite green (1067/1067), both TFMs build. - WorkItemExtensionContext : IExtensionContext — the per-revision domain port context (revision + resolved target id), decoupled from the checkpoint engine. - LinksExtensionOptions — the extension's own IOptions<T> (no shared god-object). - LinksWorkItemExtension : IModuleExtension — encapsulates link-application (related/external/hyperlinks) as a domain capability; import-only (links are captured in revision.json on export). Driven by LinksWorkItemExtensionTests. Additive only: the checkpoint/resume engine is untouched. Wiring the processor to drive this port (replacing the inline link stage) is the next increment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… inline duplication) Wires the per-revision import to delegate the AppliedLinks stage to LinksWorkItemExtension, removing the inline _target.AddLinksAsync call. The checkpoint/resume logic, cursor markers, and enablement gate are unchanged — only the link-application call is rerouted through the capability port. - WorkItemExtensionContext carries the per-job IWorkItemTarget (the port reads its driven adapter from the per-entity context; the extension no longer ctor-injects a per-job dependency). - RevisionFolderProcessor: AppliedLinks stage now calls _linksExtension.ImportAsync; the extension is an optional ctor dependency defaulting to a new instance (same default-dependency pattern already used for AttachmentReplayService). - RevisionFolderProcessorFactory threads the DI-resolved extension through. - Register LinksWorkItemExtension + IOptions<LinksExtensionOptions> (IConfigSection, AddSchemaEntry) in AddWorkItemsModule. Full Infrastructure.Agent suite green (1067/1067), both TFMs build. No more duplicate link-application logic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…factor addendum Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…port (remove inline duplication) Extracts the attachment-replay capability into AttachmentsWorkItemExtension (IModuleExtension, Module=WorkItems, Name=Attachments, Order=400, import-only), mirroring the already-merged Links facet. The per-revision UploadedAttachments stage now delegates to the port instead of calling AttachmentReplayService inline. - AttachmentsExtensionOptions: own IOptions<T> (IConfigSection, AddSchemaEntry), Enabled default true. - WorkItemExtensionContext gains optional IdMapStore, ReadBinaryAsync, and AvailableBinaryPaths members so the port can build its per-job replay service from the per-entity context (Links is unaffected — additive only). - RevisionFolderProcessor: UploadedAttachments stage calls _attachmentsExtension.ImportAsync; extension is an optional ctor dependency defaulting to a new instance (same pattern as the Links extension). The checkpoint/resume cursor markers and the AttachmentsEnabled enablement gate are unchanged — only the replay call is rerouted. - RevisionFolderProcessorFactory threads the DI-resolved extension through. - Register AttachmentsWorkItemExtension + IOptions<AttachmentsExtensionOptions> in AddWorkItemsModule. Test-first: contract assertions + an ImportAsync behavioural test proving the attachment replays to the resolved target (RED via NotImplementedException, then GREEN). Full Infrastructure.Agent suite green (1070/1070), both TFMs build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…remove inline duplication) Extracts the inline work-item comment replay into CommentsWorkItemExtension (IModuleExtension, Module=WorkItems, Name=Comments, Order=500, import-only), mirroring the already-merged Links and Attachments facets. The per-revision import now delegates to the port instead of calling ProcessInlineCommentsAsync inline. - CommentsExtensionOptions: own IOptions<T> (IConfigSection, AddSchemaEntry), Enabled default true. - WorkItemExtensionContext gains an optional ReadTextAsync member so the port can read the comment.json sidecar (with the driver's legacy-path fallbacks) without owning package-access plumbing (Links/Attachments unaffected — additive only). - RevisionFolderProcessor: the inline-comments step calls _commentsExtension.ImportAsync; the extension is an optional ctor dependency defaulting to a new instance (same pattern as Links/Attachments). The ext.Comments.Enabled gate is unchanged and Comments still has no cursor stage — only the replay call is rerouted. The now-unused ProcessInlineCommentsAsync is removed. - RevisionFolderProcessorFactory threads the DI-resolved extension through. - Register CommentsWorkItemExtension + IOptions<CommentsExtensionOptions> in AddWorkItemsModule. Test-first: contract assertions + an ImportAsync behavioural test proving non-deleted comments replay (rendered text preferred) to the resolved target (RED via NotImplementedException, then GREEN). Full Infrastructure.Agent suite green (1073/1073), both TFMs build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…acet completion Removes the now-unused _attachmentReplayService field/ctor param from WorkItemResolutionProcessor (Rule 30; attachment replay now lives in the Attachments port). Updates the 039 addendum: Attachments + Comments facets done, EmbeddedImages confirmed as the decided field-rewrite contributor (no extraction), and the remaining work (Stage 5 god-object retirement; Stage 2 deferred). Full Infrastructure.Agent suite green (1073/1073), both TFMs build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Tool is defined by its single-instance / single run-wide config shape, not by purity. Tools may perform I/O (e.g. identity/cache lookups) and may hold run-wide derived state; they must not carry per-consumer/per-call mutable state. Fixes the execution-contract tool rules + violation list and the terminology/domain-model primers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ent into Stage 2 The facet enablement flags are read in multiple places (processor stage gates + stream-orchestrator skip-visibility) and the processor still maps each extension to a fixed cursor stage, so retiring the WorkItemsModuleExtensions enablement is entangled with the Stage 2 cursor generalisation and is folded into that dedicated, integrity-critical effort. Capabilities already own their logic as ports; only the enablement source still flows through the god-object gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(Stage 5)
Moves Links/Attachments/Comments enablement off the WorkItemsModuleExtensions
god-object flags onto each extension's own IsEnabled, bound from the same operator
config (Extensions.{Links,Attachments,Comments}.Enabled) for exact parity. The
processor's three stage gates now read _linksExtension/_attachmentsExtension/
_commentsExtension.IsEnabled; the cursor/resume logic and markers are byte-identical.
- Links/Attachments/CommentsExtensionOptions.Enabled made settable; bound via
Configure<IOptions<WorkItemsModuleOptions>> in AddWorkItemsModule.
- One test (ProcessAsync_WhenLinksDisabled_SkipsStageC) updated to disable Links via
an injected disabled port instead of the god-object flag.
Full Infrastructure.Agent suite green (1073/1073), both TFMs build. Cursor engine untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…led into Stage 2 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…jj95-wg3x NuGet audit (NU1903, warning-as-error) was failing the full-solution build on the transitive MessagePack 2.5.192 (pulled via StreamJsonRpc -> KubernetesClient in the Aspire AppHost). Enables central package transitive pinning and pins MessagePack to 2.5.301 — the patched release in the same 2.5.x line (binary-compatible, no major bump). Verified: full solution builds (exit 0) and the entire test suite passes with a real captured exit code (FULL_TEST_EXIT=0) — 1564 tests, 0 failures across all 10 test projects incl. net481 TfsMigrationAgent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replaced instances of WorkItemsImportRuntime with WorkItemRevisionLoopDriver across multiple test contexts. - Updated the BuildOrchestrator methods to return WorkItemRevisionLoopDriver instead of WorkItemsImportRuntime. - Introduced WorkItemRevisionLoopDriver as a test-only driver to facilitate testing without full ImportContext setup. - Added new tests for ImportResumeDecisionResolver and WorkItemRevisionStagePipeline to ensure correct behavior. - Removed obsolete WorkItemRevisionImporterTests and adjusted related test utilities to align with the new structure. - Updated PackageRuntimeBoundaryEnforcementTests to reflect changes in the WorkItemResolution structure.
Move CommentsWorkItemExtension from an ad-hoc inline call into the ordered extensionStages array so it gets a cursor write and is subject to resume/skip logic like Links and Attachments. Adds CursorStage.AppliedComments and registers it in WorkItemRevisionStagePipeline.StageNames as the fifth pipeline stage (after UploadedAttachments, before Completed). Updates all stage-ordering tests that expected UploadedAttachments → Completed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add an optional `extensionStages` constructor parameter to `WorkItemResolutionProcessor` so callers can supply a custom ordered stage list, enabling tests to vary extension presence without touching individual extension options. Make `WorkItemRevisionStage` public (was internal) so it can appear as a parameter type on the public ctor without an accessibility error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ferred to Stage 5 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t gating (Stage 3/4) Add AttachmentsWorkItemExtension and CommentsWorkItemExtension as optional ctor parameters on WorkItemsOrchestrator. ExportAsync now reads IsEnabled from these extension objects rather than from the WorkItemsModuleExtensions god-object, removing two god-object reads from the export path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t gating Replace ext.AttachmentsEnabled and ext.Comments.Enabled reads in the import log line with _attachmentsExtension.IsEnabled and _commentsExtension.IsEnabled. Loop-level ext.Comments.Enabled for comment-folder gating retained (levered value, different concern from the per-revision AppliedComments stage). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ge 4/5 prereq) Replace ext.RevisionsEnabled reads in the import path with _options.Value.Extensions.Revisions.Enabled. RevisionsEnabled is a core kill-switch (not extension-owned); reading from options directly removes the god-object dependency for this flag without behaviour change. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… (Stage 4/5 prereq) Replace ext.EmbeddedImages.Enabled god-object read in RevisionFolderProcessor with an injected EmbeddedImagesExtensionOptionsConfig parameter so the per-revision embedded-image gate no longer depends on the god-object at all. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…alue Remove the last non-deferred god-object read from the import startup log. ext.LinksEnabled → _options.Value.Extensions.Links.Enabled matches the pattern already used for Revisions, Attachments, and Comments in the same log line. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ate pending list Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The orchestrator's early-return guard and log already read _options.Value.Extensions.Revisions.Enabled directly. RevisionsEnabled on WorkItemsModuleExtensions was set in FromModule, FromOptions, and copied in ApplyReplayLevers but never read — removing it eliminates the last Revisions- specific field from the god-object. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…elpers WorkItemsModuleExtensions.FromModule(JobModule) was never called anywhere in production code or tests; all 8 private helpers served only that method. Removing them eliminates ~180 lines of dead code and the unused imports (System.Text.Json, System.Linq, DevOpsMigrationPlatform.Abstractions.Jobs). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace ext.Comments.Enabled read in RunRevisionFolderLoopAsync with _commentsExtension.IsEnabled, which is the injected CommentsWorkItemExtension. Since ApplyReplayLevers does not modify Comments, the levered value is identical to the pre-lever extension value — no behaviour change. Update WorkItemRevisionLoopDriver and ImportCommentsContext to accept CommentsWorkItemExtension? so tests can inject a disabled extension instead of setting the god-object field. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comments is no longer read from WorkItemsModuleExtensions anywhere in production code; the loop-level gate migrated to _commentsExtension.IsEnabled and ApplyReplayLevers no longer copies it. Remove the property, the FromOptions assignment, and the dead test initialiser in SkipUnresolvableContext. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tired Record that FromModule dead code, loop-level commentsEnabled gate, and Comments god-object property have all been migrated. Only Stage 5 blocked items (AttachmentsEnabled, LinksEnabled, EmbeddedImages in ApplyReplayLevers and EmitReplaySkipVisibilityEvents) remain. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ImageReplayService→RewriteTool
`Service` is not a permitted taxonomy role; stateless concern engines use the `Tool` role.
Renamed AttachmentReplayService → AttachmentReplayTool and EmbeddedImageReplayService →
EmbeddedImageRewriteTool ('Rewrite' reflects the field-URL-rewriting concern). Updated all
call sites, extension wiring, and test files. 1100 tests green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mExtension into DI factory The WorkItemsOrchestrator factory lambda was not passing the registered singletons; the ?? fallback always fired, creating default-options instances that ignored config. Now both extensions are resolved via GetRequiredService so the configured Enabled flag is honoured. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ter from ProcessAsync IWorkItemResolutionProcessor.ProcessAsync was declared with a WorkItemsModuleExtensions ext parameter that was never read in the implementation — the processor gates stages via injected extension objects. Removed the dead parameter from the interface, implementation, and all 17 call sites. Added attachmentsExtension param to RevisionFolderProcessorTests.CreateSut so the attachments-disabled gate can still be tested via the injected extension. 1100 green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…olutionProcessor.cs File name must match class name (WorkItemResolutionProcessor). No code changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…1 file rename as done Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…WorkItemExtension (HX-C2) AttachmentsWorkItemExtension was silently discarding attachment logs by using NullLogger. Now accepts ILogger<AttachmentReplayTool> via constructor and forwards it to AttachmentReplayTool. Fallback new() uses NullLogger where DI is not available (test-only code paths). Updated all test call sites and guarded-path list (RevisionFolderProcessor→WorkItemResolutionProcessor). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tances Previously ApplyReplayLevers computed levered booleans but they only flowed to telemetry; the processor used singleton extension IsEnabled (config-level) and completely ignored runtime levers. Now the processor factory Create() overload accepts per-job lever flags and synthesises disabled extension instances when a lever suppresses an extension — so stages are actually skipped, not just logged. The lever test now asserts specific false/false/false values rather than It.IsAny<bool>(), proving the orchestrator passes computed flags through. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eads ApplyReplayLevers now reads _options.Value.Extensions.* directly instead of ext.AttachmentsEnabled/LinksEnabled/EmbeddedImages.*; the god-object is no longer the source-of-truth going into the lever computation. EmitReplaySkipVisibilityEvents signature changed from WorkItemsModuleExtensions to two bool parameters (attachmentsEnabled, embeddedImagesEnabled). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mages from god-object These three extension-owned properties are removed from WorkItemsModuleExtensions. ComputeLeveredExtensionFlags() replaces ApplyReplayLevers() — reads _options.Value directly and returns a (bool, bool, bool) tuple. The god-object now carries only non-extension config: Query, ResolutionStrategy, IncludeFilters, ExcludeFilters. Tests that previously set AttachmentsEnabled/EmbeddedImages on the extensions object now configure WorkItemsModuleOptions with the extension disabled instead, which is the correct seam for controlling extension enablement. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ments extensions AttachmentsWorkItemExtension and CommentsWorkItemExtension now implement SupportsExport=true and ExportAsync, moving attachment binary download and inline comment export out of WorkItemExportOrchestrator inline code. WorkItemRevisionExportContext (new) carries per-revision export context (workItemId, revisionIndex, revision, folderPath, sourceEndpoint) for extensions to consume. WorkItemExportOrchestrator routes to extensions when exportExtensions is provided, falling back to inline paths for backward compatibility with tests that don't inject extensions. DI registration now injects IAttachmentBinarySource? and IWorkItemCommentSourceFactory? into the extension singletons. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l items done Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…2 doc fix - T091/T092: Update execution-model.md to document Teams simplified model (no mandatory tier yet; orchestrator does filtering — deliberate, not a deviation) - T099: Add TeamExtensionParityTests.cs — DomainTests for all four extensions (Settings/Iterations/Members/Capacity) proving artifact written to package - T013: Add ConnectorCapabilityTests.cs — UnitTests for StaticConnectorCapabilityProvider (flag included → true, excluded → false, composite BoardConfig → granular flags true, None → all false) - T014: ConnectorCapability [Flags] enum (None/BoardColumns/BoardRows/CardRules/Backlogs/ TaskboardColumns/BoardConfig composite) - T015: IConnectorCapabilityProvider interface - T016: StaticConnectorCapabilityProvider (bitwise Has() test) - T017: TfsConnectorCapabilityProvider (always returns false — explicit None declaration) - T017b: TfsNullBoardAdapter (throws NotSupportedException; satisfies DI contract) - T018: Register TfsConnectorCapabilityProvider + TfsNullBoardAdapter in TFS agent DI - T019: Register StaticConnectorCapabilityProvider(BoardConfig|Backlogs|Taskboard) in ADO DI - T020: Register StaticConnectorCapabilityProvider(BoardConfig|Backlogs|Taskboard) in Simulated DI - T021: All ConnectorCapabilityTests + full suite (723 tests) pass - Mark T013–T021, T091, T092, T099 complete in tasks.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pter + AzureDevOpsBoardAdapter stub - T022: BoardConfigTeamExtensionTests.cs — 5 DomainTests covering all US1 acceptance scenarios (multi-board, null WIP limit, capability-absent skip, Columns disabled, process defaults) - T023/T026: BoardConfigTeamExtension.cs — IModuleExtension with ExportAsync; gates on ConnectorCapability.BoardConfig; respects Columns/SwimLanes options; writes board-config.json - T024: SimulatedBoardAdapter.cs — deterministic 2-board adapter (Stories/Epics, 3 columns each) - T025: AzureDevOpsBoardAdapter.cs — Phase 4 stub (all methods throw NotImplementedException) - T027: Register BoardConfigTeamExtension + IOptions<BoardConfigExtensionOptions> in AddTeamsModule - T029: Structured ILogger observability (started/exported/skipped) in ExportAsync - T031: All 5 US1 tests pass; full suite 1105/1105 green - T074/T074b/T075: Columns-disabled and process-defaults tests pass - Also carry forward Phase 1-3 files (TeamExtensionContext, board abstractions, ConnectorCapability, Teams extension conversions, WorkItems god-object retirements) merged from 039-team-board-settings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tions, ConnectorCapability Phase 1: IModuleExtension seam for Teams — TeamExtensionContext, all four per-team capabilities converted to IModuleExtension, boolean-flag dispatch eliminated. Phase 2: Board-config abstraction types — BoardColumn, BoardSwimLane, CardRule, BacklogMetadata, TaskboardColumn, BoardConfig, TeamBoardConfig, ITeamBoardAdapter, BoardConfigExtensionOptions, BoardConfigImportMode. Phase 3: ConnectorCapability mechanism — [Flags] enum, IConnectorCapabilityProvider, StaticConnectorCapabilityProvider, TfsConnectorCapabilityProvider (None), TfsNullBoardAdapter, DI registrations for all three connectors. WorkItems: Links/Attachments deleted as extensions (fail Extension Seam Ethos); single PATCH consolidation via ApplyRevisionAsync; CommentsWorkItemExtension sole valid extension; ADR 0019 and guardrails updated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ardAdapter, AzureDevOpsBoardAdapter stub - BoardConfigTeamExtension: IModuleExtension with ExportAsync checking ConnectorCapability.BoardConfig - SimulatedBoardAdapter: deterministic 2-board, 3-column adapter for tests - AzureDevOpsBoardAdapter: Phase 4 stub (methods throw NotImplementedException) - DI: registered in Teams, Simulated, and AzureDevOps connectors - Tests: 5 DomainTest methods covering US1 acceptance scenarios (T022, T074, T075) - BoardConfigExtensionOptions.SectionName added Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…liance - Add SimulatedBoardAdapterExportTests (8 tests) and SimulatedBoardAdapterImportTests (7 tests) - Add TeamBoardConfigPerformanceTests (SC-001: 10 teams × 2 boards < 5 min) - Add AzureDevOpsBoardAdapterTests (11 contract tests) in new Infrastructure.AzureDevOps.Tests project - Add InternalsVisibleTo for AzureDevOps.Tests; add Infrastructure.Agent ref to Simulated.Tests - Implement BoardConfigExtensionOptions as IConfigSection; register AddSchemaEntry in TeamsServiceCollectionExtensions - Fix net481 ReadToEndAsync(CancellationToken) guard in BoardConfigTeamExtension - Add 2 missing tests: US6 scenario 6e (Skip + empty target → Replace) and SC-004 (idempotency) - Create discrepancies.md (D-001–D-009, all Resolved/N/A) - Update docs/capabilities-guide.md with Teams Board Configuration capability section - Update connector-model.md with ConnectorCapability mechanism - Update execution-model.md with BoardConfigTeamExtension worked example - Update analysis/pending-actions.md with spec 039 Complete entry - Mark all Phase 11 tasks complete (T069, T084–T089) - Fix build.ps1 elapsed format to uniform m:ss.sss across all summary rows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… against real TeamsOrchestrator - Add missing class-level [TestCategory] to 7 test classes (compliance gate) - Add missing method-level [TestCategory] to TeamExtensionDispatchTests methods - Rewrite TeamExtensionDispatchTests to exercise TeamsOrchestrator directly instead of reimplementing dispatch logic inline; all 4 tests now construct a real orchestrator with SpyExtension instances and assert on actual call counts - Test (c) strengthened: asserts both SupportsImport=true (called) and SupportsImport=false (skipped) in the same run Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cy inversion, screaming architecture Fix 1 (Critical): Move 5 interfaces to Abstractions.Agent — IProjectLifecycleProvider, IProjectLifecycleNameGenerator, IWorkItemExportOrchestrator, IWorkItemExportOrchestratorFactory, IImportCreatedNodeStateStore. Fix 2 (Critical): Remove direct Infrastructure.Storage.FileSystem reference from Infrastructure.Agent.csproj; move AddPackageManagementServices/AddPackageMigrationConfigLoader into PackageServiceCollectionExtensions in the FileSystem project; wire up shell layers (MigrationAgent, TfsMigrationAgent, TfsObjectModel) with explicit references. Fix 3 (High): Screaming Architecture renames — ExportImagesFromHtmlAsync, ExportImagesFromMarkdownAsync, DispatchTasksAsync, ExportAsync, ImportAsync, ImportRevisionAsync; propagated through implementations and tests. Fix 4 (High/VS-H1): Move PassThroughIdentityMappingService and IdMapStoreFactory from WorkItems.Identity cross-slice namespace to Infrastructure.Agent.Identity. Fix 5 (High): Add GetBoardConfigSnapshotAsync to ITeamBoardAdapter + TargetBoardSnapshot value object; refactor ImportCoreAsync to batch all target reads upfront; extract BoardConfigExportPlan private record to consolidate export capability gate checks. Fix 6 (High): Define RawWorkItemRevisionData + RawWorkItemRelation SDK-free records in Abstractions.Agent; update IAzureDevOpsWorkItemRevisionMapper.Map to take these instead of WorkItem; AzureDevOpsWorkItemRevisionSource converts via ToRaw() adapter. All 1136 tests green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PassThroughIdentityMappingService was moved from WorkItems.Identity to Identity in Fix 4 (VS-H1), but the net481 guarded-types contract test still referenced the old namespace. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eal board state The Skip mode check in BoardConfigTeamExtension calls snapshot.BoardNames.Contains(board.BoardName). SimulatedBoardAdapter was returning TargetBoardSnapshot.Empty (empty BoardNames), so Skip mode never fired and UpdateBoardColumnsAsync was called unconditionally. Populate BoardNames, BoardColumns, and TaskboardColumns from the existing simulated seed data so the snapshot reflects the adapter''s true state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 221 files, which is 71 over the limit of 150. To get a review, narrow the scope: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (221)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c039ce4340
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var contentCtx = new PackageContentContext( | ||
| PackageContentKind.Artefact, | ||
| Organisation: ctx.Organisation, | ||
| Project: ctx.ProjectName, |
There was a problem hiding this comment.
Read board-config artifacts from the source project
When importing between differently named source and target projects, ctx.ProjectName is the target project (set by TeamsOrchestrator before dispatch), but the exported board-config.json lives under the source project scope. This lookup therefore misses the artifact and the board-config import silently skips even though the package contains it; use ctx.SourceProjectName for package reads while keeping ctx.ProjectName for target API writes.
Useful? React with 👍 / 👎.
| string project, string teamId, CancellationToken ct) | ||
| { | ||
| ISet<string> boardNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||
| await foreach (var b in GetBoardsAsync(project, teamId, ct).ConfigureAwait(false)) |
There was a problem hiding this comment.
Enumerate target boards from the target endpoint
In Azure DevOps imports, this snapshot is used to decide whether Skip should leave an existing board alone and whether Merge should preserve target-only columns/lanes, but GetBoardsAsync is the export-side reader and constructs its client from _source. With a target team ID this reads the wrong organization/project (often yielding no boards), so Skip/Merge planning can overwrite target configuration instead of preserving it; the snapshot needs a target-side board enumeration.
Useful? React with 👍 / 👎.
| if (_options.CardRules && teamBoardConfig.CardRules is not null) | ||
| await _adapter.UpdateCardRuleSettingsAsync( | ||
| ctx.ProjectName, targetId, board.BoardName, teamBoardConfig.CardRules, ct).ConfigureAwait(false); |
There was a problem hiding this comment.
When a team has multiple boards with different card rules, this loop applies the single teamBoardConfig.CardRules value to every board. Since export already flattens all per-board rules into that one property, importing a package where only one board had a rule will duplicate that rule onto the other boards and cannot restore distinct rule sets; card rules need to remain keyed by board name through export and import.
Useful? React with 👍 / 👎.
Summary
Implements US1 of issue 039: export and import of team board configuration (columns, swimlanes, card rules, backlogs, taskboard columns) as part of the Teams module extension system.
What changed
Core feature (phases 1–11)
ITeamBoardAdapter— new port for all board read/write operations; implementations for Azure DevOps, Simulated, and TFS null adapterBoardConfigTeamExtension—IModuleExtensionthat exportsboard-config.jsonper team and imports it back with Replace / Merge / Skip modesConnectorCapabilityenum —BoardConfig,Backlogs,TaskboardColumnsflags;IConnectorCapabilityProviderportTargetBoardSnapshot— value object batching all target-side reads before any write (FR-013 state-mapping validation, Merge/Skip logic)BoardConfigExportPlan— private record consolidating capability-gate checks inExportAsyncRawWorkItemRevisionData/RawWorkItemRelation— SDK-free records inAbstractions.Agentreplacing ADO SDK types in the mapper interfaceArchitecture fixes (combined review)
IEmbeddedImageExportService,IRevisionFolderProcessormoved fromInfrastructure.Agent→Abstractions.Agent(CA/HX interfaces in wrong layer)PassThroughIdentityMappingService,IdMapStoreFactorymoved toInfrastructure.Agent.Identity(VS-H1 cross-slice namespace)ExportImagesFromHtmlAsync,DispatchTasksAsync,ImportRevisionAsync— public method renames to business verbs (SA screaming architecture)IWorkItemExportOrchestrator/IWorkItemExportOrchestratorFactorydeleted; dependency inversion correctedWorkItems module refactor (prerequisite work)
AppliedCommentscursor stage addedWorkItemsModuleExtensions) fields retired:RevisionsEnabled,Comments.Enabled,LinksEnabled,AttachmentsEnabled,EmbeddedImagesIModuleExtensionports (ADR 0019)WorkItemRevisionLoopDriverrenamed fromWorkItemsImportRuntimeBug fixes (this session)
Net481GuardedTypesContractTests— namespace updated fromWorkItems.IdentitytoIdentityafter VS-H1 moveSimulatedBoardAdapter.GetBoardConfigSnapshotAsync— was returningTargetBoardSnapshot.Empty; now returns real board state so Skip mode and state-mapping validation work correctly in system testsTest results (all fresh after last change)
Reviewer notes
TargetBoardSnapshot.BoardNamesusesISet<string>notIReadOnlySet<string>—IReadOnlySet<T>is unavailable on net481AzureDevOpsBoardAdapter.GetBoardConfigSnapshotAsyncis a real implementation stub; the live ADO adapter makes the actual REST callsBoardConfigExtensionOptions.ImportModedefault isReplace; Skip and Merge modes tested in both unit and simulated suitesTest plan
pwsh ./build.ps1 Test— 950/950 unit tests greenpwsh ./build.ps1 SystemTest_Simulated— 38/38 simulated system tests greenpwsh ./build.ps1 SystemTest_Live— 13/13 live system tests greenAssert.Inconclusiveintroduced🤖 Generated with Claude Code