feat: Add remote sync for project clones

Fetch remote changes before agents start working so they build on
up-to-date code. Adds ProjectSyncManager with git fetch + ff-only
merge of defaultBranch, integrated into phase dispatch to sync
before branch creation.

- Schema: lastFetchedAt column on projects table (migration 0029)
- Events: project:synced, project:sync_failed
- Phase dispatch: sync all linked projects before creating branches
- tRPC: syncProject, syncAllProjects, getProjectSyncStatus
- CLI: cw project sync [name] --all, cw project status [name]
- Frontend: sync button + ahead/behind badge on projects settings
This commit is contained in:
Lukas May
2026-03-05 11:45:09 +01:00
parent 79966cdf20
commit 5e77bf104c
20 changed files with 496 additions and 6 deletions

View File

@@ -24,6 +24,7 @@ import type { CoordinationManager } from '../../coordination/types.js';
import type { BranchManager } from '../../git/branch-manager.js';
import type { ExecutionOrchestrator } from '../../execution/orchestrator.js';
import type { PreviewManager } from '../../preview/index.js';
import type { ProjectSyncManager } from '../../git/remote-sync.js';
export function requireAgentManager(ctx: TRPCContext) {
if (!ctx.agentManager) {
@@ -214,3 +215,13 @@ export function requireReviewCommentRepository(ctx: TRPCContext): ReviewCommentR
}
return ctx.reviewCommentRepository;
}
export function requireProjectSyncManager(ctx: TRPCContext): ProjectSyncManager {
if (!ctx.projectSyncManager) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Project sync manager not available',
});
}
return ctx.projectSyncManager;
}

View File

@@ -7,7 +7,7 @@ import { z } from 'zod';
import { join } from 'node:path';
import { rm } from 'node:fs/promises';
import type { ProcedureBuilder } from '../trpc.js';
import { requireProjectRepository } from './_helpers.js';
import { requireProjectRepository, requireProjectSyncManager } from './_helpers.js';
import { cloneProject } from '../../git/clone.js';
import { getProjectCloneDir } from '../../git/project-clones.js';
@@ -153,5 +153,25 @@ export function projectProcedures(publicProcedure: ProcedureBuilder) {
await repo.setInitiativeProjects(input.initiativeId, input.projectIds);
return { success: true };
}),
syncProject: publicProcedure
.input(z.object({ id: z.string().min(1) }))
.mutation(async ({ ctx, input }) => {
const syncManager = requireProjectSyncManager(ctx);
return syncManager.syncProject(input.id);
}),
syncAllProjects: publicProcedure
.mutation(async ({ ctx }) => {
const syncManager = requireProjectSyncManager(ctx);
return syncManager.syncAllProjects();
}),
getProjectSyncStatus: publicProcedure
.input(z.object({ id: z.string().min(1) }))
.query(async ({ ctx, input }) => {
const syncManager = requireProjectSyncManager(ctx);
return syncManager.getSyncStatus(input.id);
}),
};
}