Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
<ProjectReference Include="..\..\Infrastructure\Backlog.Infrastructure.Capture\Backlog.Infrastructure.Capture.csproj" />
<ProjectReference Include="..\..\Infrastructure\Backlog.Infrastructure.Claude\Backlog.Infrastructure.Claude.csproj" />
<ProjectReference Include="..\..\Infrastructure\Backlog.Infrastructure.Copilot\Backlog.Infrastructure.Copilot.csproj" />
<!-- The Devbook database's refresher and its two retrieval adapters, which
AddDevbookAdapters composes behind the module's ports. -->
<ProjectReference Include="..\..\Infrastructure\Backlog.Infrastructure.Devbook\Backlog.Infrastructure.Devbook.csproj" />
<ProjectReference Include="..\..\Infrastructure\Backlog.Infrastructure.FileSystem\Backlog.Infrastructure.FileSystem.csproj" />
<ProjectReference Include="..\..\Infrastructure\Backlog.Infrastructure.GitHub\Backlog.Infrastructure.GitHub.csproj" />
<ProjectReference Include="..\..\Infrastructure\Backlog.Infrastructure.Sqlite\Backlog.Infrastructure.Sqlite.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using Backlog.Desktop.UI.Devbook;
using Backlog.Desktop.UI.Devbook.Extensions;
using Backlog.Desktop.UI.Inbox;
using Backlog.Desktop.UI.Shell;
using Backlog.Desktop.UI.Tasks;
using Backlog.Infrastructure.AzureFoundry;
using Backlog.Infrastructure.Capture.Extensions;
using Backlog.Infrastructure.Claude;
using Backlog.Infrastructure.Copilot;
using Backlog.Infrastructure.Devbook;
using Backlog.Infrastructure.FileSystem;
using Backlog.Infrastructure.FileSystem.Dashboard;
using Backlog.Infrastructure.FileSystem.Inbox;
Expand All @@ -18,6 +19,7 @@
using Backlog.Modules.Capture.Extensions;
using Backlog.Modules.Dashboard.Extensions;
using Backlog.Modules.Dashboard.UI.Extensions;
using Backlog.Modules.Devbook.Abstractions;
using Backlog.Modules.DevPc.UI;
using Backlog.Modules.Inbox.Abstractions.Services;
using Backlog.Modules.Inbox.Extensions;
Expand All @@ -30,6 +32,7 @@
using Backlog.SharedKernel;
using Backlog.UI.Components.Feedback;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Backlog.Desktop.Composition;

Expand All @@ -46,11 +49,13 @@ namespace Backlog.Desktop.Composition;
/// head's registrations in front of provider validation at all.
/// </para>
/// <para>
/// The Devbook block — the snapshot cache, the database refresher, the folder
/// source and the Devbook stores and screens — is not here yet. Issue #738 gives it
/// an extension of its own; until then each head registers it beside this call. The
/// Devbook's Ask AI source is here, in its place among the others, because the
/// order the areas are offered in is the order they are registered.
/// The Devbook is here too since issue #738: the adapters behind its ports — the
/// snapshot cache, the database refresher, the folder source, both retrieval tiers
/// and the annotation store — and then <c>AddDevbookModule</c> for the module's own
/// stores and screens, with the Copilot launcher and the window's lifetime the heads
/// already hand in. The Devbook's Ask AI source stays in its place among the
/// others, because the order the areas are offered in is the order they are
/// registered.
/// </para>
/// <para>
/// Nothing in here asks which head it is running in. Whatever differs arrives
Expand Down Expand Up @@ -212,9 +217,14 @@ public static IServiceCollection AddDesktopComposition(this IServiceCollection s
services.Add(new ServiceDescriptor(typeof(FeedbackReportChannel), typeof(FeedbackReportChannel), options.WindowStateLifetime));
services.AddCopilot(options.CopilotCliLauncher);
services.AddSingleton<TasksCopilotCli>();
// The Devbook: its adapters, then the module over them. The launcher is the
// one AddCopilot registers above, handed in rather than resolved so the
// module says which port it starts through; the domain store lives as long
// as one window's state does.
AddDevbookAdapters(services);
services.AddDevbookModule(options.CopilotCliLauncher, options.WindowStateLifetime);
// The open-chapter mirror the Devbook pane writes and the Ask AI source that
// pins from it, over the search and folder ports the head's Devbook block
// registers.
// pins from it, over the search and folder ports registered just above.
services.AddDevbookAiContentSource();

// One window's state, and what reads it. The panes' state captures the
Expand Down Expand Up @@ -277,4 +287,62 @@ public static IServiceCollection AddDesktopComposition(this IServiceCollection s
/// they cannot disagree about which service a head is talking to.</summary>
private static Uri SyncServiceAddress(IServiceProvider services) =>
services.GetRequiredService<SyncServiceEndpoint>().Resolve().Address;

/// <summary>
/// The adapters behind the Devbook module's ports, which both heads composed
/// alike when each registered its own Devbook block. The module's own services
/// are <c>AddDevbookModule</c>'s; these are the composition's because they are
/// file-system, GitHub and database adapters the Devbook UI project cannot see.
/// </summary>
private static void AddDevbookAdapters(IServiceCollection services)
{
// Devbook read from a repository branch, for a repository nobody has
// cloned. The network half — one listing per commit, one blob per file
// somebody opens — lives in the GitHub adapter and the disk half in the
// file-system one; the cache root arrives as a delegate rather than as the
// workspace store, because the GitHub adapter may not see that one.
services.AddSingleton<IDevbookSnapshotCache>(sp => new DevbookSnapshotCache(
() => sp.GetRequiredService<WorkspaceSettingsStore>().DevbookCacheDirectory,
sp.GetRequiredService<IGitHubTreeClient>(),
sp.GetRequiredService<IGitHubBranchCatalog>()));

// The devbook database: built by the app into the same cache folder as the
// snapshots, one per repository path, in the background when a repository
// is first read (local ADR 0015). Each head resolves it after Build(), which
// is what points every database reader at that folder.
services.AddSingleton(sp => DevbookDatabaseRefresher.StartForApp(
() => sp.GetRequiredService<WorkspaceSettingsStore>().DevbookCacheDirectory,
sp.GetService<ILogger<DevbookDatabaseRefresher>>()));

services.AddSingleton<IDevbookFolderSource>(sp => new DevbookFolderSource(
sp.GetRequiredService<GitHubSettingsStore>(),
sp.GetRequiredService<WorkspaceSettingsStore>(),
sp.GetRequiredService<IDevbookSnapshotCache>()));

// Retrieval, both tiers. Adapters over the generated database rather than
// over the Markdown: search is the one capability ADR 0004's ladder does
// not let degrade to a corpus scan, so where there is no database these
// report that in words instead of answering slowly or answering nothing.
services.AddSingleton<IDevbookSearch>(sp =>
new DevbookFullTextSearch(sp.GetRequiredService<IDevbookFolderSource>()));
services.AddSingleton<IDevbookVectorSearch>(sp =>
new DevbookSemanticSearch(sp.GetRequiredService<IDevbookFolderSource>(), DevbookEmbeddingModel.Default));
// What a task's Devbook references point at, for the Tasks detail panel and
// the MCP reference tools: Tasks' port, answered over the same database and
// folders, falling back to the Markdown where there is no database.
services.AddSingleton<IDevbookReferenceResolver>(sp =>
new DevbookReferenceResolver(sp.GetRequiredService<IDevbookFolderSource>()));

// A person's remarks on Devbook chapters: one JSON file per repository
// under the storage folder, following the root the way the inbox store
// does so a moved backlog takes its remarks along. The panels resolve this
// by interface and fall back to a session-scoped store when it is absent,
// which is why leaving this line out would not fail — it would only forget.
// The folder source is what lets it name a chapter the way every other
// device names it, whichever folder this machine has an area pointed at.
services.AddSingleton<IDevbookAnnotationStore>(sp =>
new DevbookAnnotationStore(
() => sp.GetRequiredService<WorkspaceSettingsStore>().RootDirectory,
folders: sp.GetRequiredService<IDevbookFolderSource>()));
}
}
83 changes: 5 additions & 78 deletions src/App/Backlog.Desktop/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
using Backlog.Desktop.Composition;
using Backlog.Desktop.Mcp;
using Backlog.Desktop.Services;
using Backlog.Desktop.UI.Devbook;
using Backlog.Desktop.UI.Shell;
using Backlog.Aspire.ServiceDefaults;
using Backlog.SharedKernel;
using Backlog.Modules.DevPc.Abstractions;
using Backlog.Modules.Devbook.Abstractions;
using Backlog.Modules.Tasks.Abstractions.Services;
using Backlog.Infrastructure.AzureFoundry;
using Backlog.Infrastructure.Claude;
Expand All @@ -19,7 +17,6 @@
using Backlog.Infrastructure.Sync;
using Backlog.Infrastructure.Sync.Annotations;
using Backlog.Infrastructure.Sync.Sessions;
using Backlog.UI.Components.Diagrams;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -100,8 +97,7 @@ public static MauiApp CreateMauiApp()
FeatureSettings = _ => new AppFeatureSettingsStore(AppFeatures.All),
WorkingHoursSettings = _ => new WorkingHoursSettingsStore(),
UsageResetSettings = _ => new UsageResetSettingsStore(),
// It carries the working week too, kept equal to the device's working-hours.json.
PlanningVelocitySettings = sp => new PlanningVelocitySettingsStore(sp.GetRequiredService<WorkingHoursSettingsStore>()),
PlanningVelocitySettings = _ => new PlanningVelocitySettingsStore(),
ShellNavigation = _ => new ShellNavigationStore(),
CaptureSourceSettings = _ => new CaptureSourcesSettingsStore(),
CaptureRunLog = _ => new CaptureRunLogStore(),
Expand Down Expand Up @@ -132,79 +128,10 @@ public static MauiApp CreateMauiApp()
WindowStateLifetime = ServiceLifetime.Singleton
});

// Devbook read from a repository branch, for a repository nobody has
// cloned. The network half — one listing per commit, one blob per file
// somebody opens — lives in the GitHub adapter and the disk half here;
// the cache root arrives as a delegate rather than as the workspace
// store, because the GitHub adapter may not see this one.
builder.Services.AddSingleton<IDevbookSnapshotCache>(sp => new DevbookSnapshotCache(
() => sp.GetRequiredService<WorkspaceSettingsStore>().DevbookCacheDirectory,
sp.GetRequiredService<IGitHubTreeClient>(),
sp.GetRequiredService<IGitHubBranchCatalog>()));

// The devbook database: built by the app into the same cache folder as
// the snapshots, one per repository path, in the background when a
// repository is first read (local ADR 0015). Resolving it is what points
// every database reader at that folder - see below.
builder.Services.AddSingleton(sp => DevbookDatabaseRefresher.StartForApp(
() => sp.GetRequiredService<WorkspaceSettingsStore>().DevbookCacheDirectory,
sp.GetService<ILogger<DevbookDatabaseRefresher>>()));

builder.Services.AddSingleton<IDevbookFolderSource>(sp => new DevbookFolderSource(
sp.GetRequiredService<GitHubSettingsStore>(),
sp.GetRequiredService<WorkspaceSettingsStore>(),
sp.GetRequiredService<IDevbookSnapshotCache>()));
builder.Services.AddSingleton<DesignDevbookProvider>();
builder.Services.AddSingleton<AiDevbookProvider>();
builder.Services.AddSingleton<TechnologyDevbookService>();
builder.Services.AddSingleton<DevbookAtlasService>();
// Retrieval, both tiers. Adapters over the generated database rather than
// over the Markdown: search is the one capability ADR 0004's ladder does
// not let degrade to a corpus scan, so where there is no database these
// report that in words instead of answering slowly or answering nothing.
builder.Services.AddSingleton<IDevbookSearch>(sp =>
new DevbookFullTextSearch(sp.GetRequiredService<IDevbookFolderSource>()));
builder.Services.AddSingleton<IDevbookVectorSearch>(sp =>
new DevbookSemanticSearch(sp.GetRequiredService<IDevbookFolderSource>(), DevbookEmbeddingModel.Default));
// What a task's Devbook references point at, for the Tasks detail panel and
// the MCP reference tools: Tasks' port, answered over the same database and
// folders, falling back to the Markdown where there is no database.
builder.Services.AddSingleton<IDevbookReferenceResolver>(sp =>
new DevbookReferenceResolver(sp.GetRequiredService<IDevbookFolderSource>()));
builder.Services.AddSingleton<InstructionSourceDiscovery>();
builder.Services.AddSingleton<DevbookMenu>();
builder.Services.AddSingleton<DevbookCopilotCli>();
// The shared diagram component asks for this optionally, so registering it
// is what switches Archify artifacts on for the app at all. Everything it
// answers — the flag, which clone the chapters came from, whether a CLI is
// installed — is the host's to know, which is why the library only asks.
builder.Services.AddSingleton<IDiagramArtifactSource, ArchifyDiagramArtifacts>();
builder.Services.AddSingleton<DevbookScope>();
builder.Services.AddSingleton<DevbookUpdateService>();

// Shared by the Devbook pane and the settings screen, and a singleton so
// the branch list somebody fetched in one is already there in the other.
builder.Services.AddSingleton<DevbookSourceSelection>();
builder.Services.AddSingleton<DevbookFolderOpenService>();
builder.Services.AddSingleton<Arc42DevbookStore>();
// The C4 model beside the architecture chapters. Registered next to the
// arc42 store because it answers the same scope question against the same
// clone; it reads its own feature key and hands back nothing when that key
// is off, so registering it does not turn it on.
builder.Services.AddSingleton<C4DevbookStore>();
builder.Services.AddSingleton<DevbookChapterWriter>();
builder.Services.AddSingleton(sp => new DomainDevbookStore(sp.GetRequiredService<IDevbookFolderSource>()));
// A person's remarks on Devbook chapters: one JSON file per repository
// under the storage folder, following the root the way the inbox store
// does so a moved backlog takes its remarks along. The panels resolve this
// by interface and fall back to a session-scoped store when it is absent,
// which is why leaving this line out would not fail — it would only forget.
// The folder source is what lets it name a chapter the way every other
// device names it, whichever folder this machine has an area pointed at.
builder.Services.AddSingleton<IDevbookAnnotationStore>(sp =>
new DevbookAnnotationStore(
() => sp.GetRequiredService<WorkspaceSettingsStore>().RootDirectory,
folders: sp.GetRequiredService<IDevbookFolderSource>()));
// The Devbook - its adapters and AddDevbookModule - is in the composition
// above since issue #738: one window, so its domain store is a singleton
// like the rest of the window's state, and its Copilot offers start the CLI
// through the launcher handed in there.

// The loopback MCP listener local ADR 0012 decided. TryAdd rather than
// Add, the way AddTaskSyncClient registers its own worker: this head
Expand Down
Loading
Loading