Files
Codewalkers/apps/server/container.ts
Lukas May 50aea7e6f1 fix: Ensure phase branches exist before task dispatch
Task dispatch computed baseBranch as the phase branch name but never
ensured it existed in the git clone. When phases weren't dispatched
through the PhaseDispatchManager (which creates branches), the
git worktree add failed with "fatal: invalid reference".

Now DefaultDispatchManager calls ensureBranch for both the initiative
and phase branches before spawning, matching what PhaseDispatchManager
already does.
2026-03-05 20:49:21 +01:00

298 lines
9.7 KiB
TypeScript

/**
* Dependency Container
*
* Factory functions for creating the full dependency graph.
* Keeps startServer() thin and makes repo wiring reusable by the test harness.
*/
import type { DrizzleDatabase } from './db/index.js';
import {
createDatabase,
ensureSchema,
DrizzleInitiativeRepository,
DrizzlePhaseRepository,
DrizzleTaskRepository,
DrizzleMessageRepository,
DrizzleAgentRepository,
DrizzlePageRepository,
DrizzleProjectRepository,
DrizzleAccountRepository,
DrizzleChangeSetRepository,
DrizzleLogChunkRepository,
DrizzleConversationRepository,
DrizzleChatSessionRepository,
DrizzleReviewCommentRepository,
} from './db/index.js';
import type { InitiativeRepository } from './db/repositories/initiative-repository.js';
import type { PhaseRepository } from './db/repositories/phase-repository.js';
import type { TaskRepository } from './db/repositories/task-repository.js';
import type { MessageRepository } from './db/repositories/message-repository.js';
import type { AgentRepository } from './db/repositories/agent-repository.js';
import type { PageRepository } from './db/repositories/page-repository.js';
import type { ProjectRepository } from './db/repositories/project-repository.js';
import type { AccountRepository } from './db/repositories/account-repository.js';
import type { ChangeSetRepository } from './db/repositories/change-set-repository.js';
import type { LogChunkRepository } from './db/repositories/log-chunk-repository.js';
import type { ConversationRepository } from './db/repositories/conversation-repository.js';
import type { ChatSessionRepository } from './db/repositories/chat-session-repository.js';
import type { ReviewCommentRepository } from './db/repositories/review-comment-repository.js';
import type { EventBus } from './events/index.js';
import { createEventBus } from './events/index.js';
import { ProcessManager, ProcessRegistry } from './process/index.js';
import { LogManager } from './logging/index.js';
import { MultiProviderAgentManager } from './agent/index.js';
import { DefaultAccountCredentialManager } from './agent/credentials/index.js';
import type { AccountCredentialManager } from './agent/credentials/types.js';
import { DefaultDispatchManager } from './dispatch/manager.js';
import { DefaultPhaseDispatchManager } from './dispatch/phase-manager.js';
import type { DispatchManager, PhaseDispatchManager } from './dispatch/types.js';
import { SimpleGitBranchManager } from './git/simple-git-branch-manager.js';
import type { BranchManager } from './git/branch-manager.js';
import { ProjectSyncManager } from './git/remote-sync.js';
import { ExecutionOrchestrator } from './execution/orchestrator.js';
import { DefaultConflictResolutionService } from './coordination/conflict-resolution-service.js';
import { PreviewManager } from './preview/index.js';
import { findWorkspaceRoot } from './config/index.js';
import { createModuleLogger } from './logger/index.js';
import type { ServerContextDeps } from './server/index.js';
// =============================================================================
// Repositories
// =============================================================================
/**
* All 13 repository ports.
*/
export interface Repositories {
initiativeRepository: InitiativeRepository;
phaseRepository: PhaseRepository;
taskRepository: TaskRepository;
messageRepository: MessageRepository;
agentRepository: AgentRepository;
pageRepository: PageRepository;
projectRepository: ProjectRepository;
accountRepository: AccountRepository;
changeSetRepository: ChangeSetRepository;
logChunkRepository: LogChunkRepository;
conversationRepository: ConversationRepository;
chatSessionRepository: ChatSessionRepository;
reviewCommentRepository: ReviewCommentRepository;
}
/**
* Create all 13 Drizzle repository adapters from a database instance.
* Reusable by both the production server and the test harness.
*/
export function createRepositories(db: DrizzleDatabase): Repositories {
return {
initiativeRepository: new DrizzleInitiativeRepository(db),
phaseRepository: new DrizzlePhaseRepository(db),
taskRepository: new DrizzleTaskRepository(db),
messageRepository: new DrizzleMessageRepository(db),
agentRepository: new DrizzleAgentRepository(db),
pageRepository: new DrizzlePageRepository(db),
projectRepository: new DrizzleProjectRepository(db),
accountRepository: new DrizzleAccountRepository(db),
changeSetRepository: new DrizzleChangeSetRepository(db),
logChunkRepository: new DrizzleLogChunkRepository(db),
conversationRepository: new DrizzleConversationRepository(db),
chatSessionRepository: new DrizzleChatSessionRepository(db),
reviewCommentRepository: new DrizzleReviewCommentRepository(db),
};
}
// =============================================================================
// Container
// =============================================================================
/**
* Full dependency graph for the coordination server.
*/
export interface Container extends Repositories {
db: DrizzleDatabase;
eventBus: EventBus;
processManager: ProcessManager;
logManager: LogManager;
workspaceRoot: string;
credentialManager: AccountCredentialManager;
agentManager: MultiProviderAgentManager;
dispatchManager: DispatchManager;
phaseDispatchManager: PhaseDispatchManager;
branchManager: BranchManager;
projectSyncManager: ProjectSyncManager;
executionOrchestrator: ExecutionOrchestrator;
previewManager: PreviewManager;
/** Extract the subset of deps that CoordinationServer needs. */
toContextDeps(): ServerContextDeps;
}
/**
* Options for container creation.
*/
export interface ContainerOptions {
debug?: boolean;
}
/**
* Create the full dependency container.
*
* Wires: ProcessRegistry → EventBus → ProcessManager → LogManager →
* Database → Repositories → CredentialManager → AgentManager.
* Runs ensureSchema() and reconcileAfterRestart() before returning.
*/
export async function createContainer(options?: ContainerOptions): Promise<Container> {
const log = createModuleLogger('container');
// Infrastructure
const registry = new ProcessRegistry();
const eventBus = createEventBus();
const processManager = new ProcessManager(registry, eventBus);
const logManager = new LogManager();
// Database
const db = createDatabase();
ensureSchema(db);
log.info('database initialized');
// Repositories
const repos = createRepositories(db);
log.info('repositories created');
// Workspace root
const workspaceRoot = findWorkspaceRoot(process.cwd()) ?? process.cwd();
log.info({ workspaceRoot }, 'workspace root resolved');
// Credential manager
const credentialManager = new DefaultAccountCredentialManager(eventBus);
log.info('credential manager created');
// Agent manager
const agentManager = new MultiProviderAgentManager(
repos.agentRepository,
workspaceRoot,
repos.projectRepository,
repos.accountRepository,
eventBus,
credentialManager,
repos.changeSetRepository,
repos.phaseRepository,
repos.taskRepository,
repos.pageRepository,
repos.logChunkRepository,
options?.debug ?? false,
undefined, // processManagerOverride
repos.chatSessionRepository,
);
log.info('agent manager created');
// Reconcile agent state from any previous server session
await agentManager.reconcileAfterRestart();
log.info('agent reconciliation complete');
// Branch manager
const branchManager = new SimpleGitBranchManager();
log.info('branch manager created');
// Project sync manager
const projectSyncManager = new ProjectSyncManager(
repos.projectRepository,
workspaceRoot,
eventBus,
);
log.info('project sync manager created');
// Dispatch managers
const dispatchManager = new DefaultDispatchManager(
repos.taskRepository,
repos.messageRepository,
agentManager,
eventBus,
repos.initiativeRepository,
repos.phaseRepository,
repos.agentRepository,
repos.pageRepository,
repos.projectRepository,
branchManager,
workspaceRoot,
);
const phaseDispatchManager = new DefaultPhaseDispatchManager(
repos.phaseRepository,
repos.taskRepository,
dispatchManager,
eventBus,
repos.initiativeRepository,
repos.projectRepository,
branchManager,
workspaceRoot,
projectSyncManager,
);
log.info('dispatch managers created');
// Conflict resolution service (for orchestrator)
const conflictResolutionService = new DefaultConflictResolutionService(
repos.taskRepository,
repos.agentRepository,
repos.messageRepository,
eventBus,
);
// Execution orchestrator
const executionOrchestrator = new ExecutionOrchestrator(
branchManager,
repos.phaseRepository,
repos.taskRepository,
repos.initiativeRepository,
repos.projectRepository,
phaseDispatchManager,
dispatchManager,
conflictResolutionService,
eventBus,
workspaceRoot,
);
executionOrchestrator.start();
log.info('execution orchestrator started');
// Preview manager
const previewManager = new PreviewManager(
repos.projectRepository,
eventBus,
workspaceRoot,
repos.phaseRepository,
repos.initiativeRepository,
);
log.info('preview manager created');
return {
db,
eventBus,
processManager,
logManager,
workspaceRoot,
credentialManager,
agentManager,
dispatchManager,
phaseDispatchManager,
branchManager,
projectSyncManager,
executionOrchestrator,
previewManager,
...repos,
toContextDeps(): ServerContextDeps {
return {
agentManager,
credentialManager,
dispatchManager,
phaseDispatchManager,
branchManager,
projectSyncManager,
executionOrchestrator,
previewManager,
workspaceRoot,
...repos,
};
},
};
}