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:
@@ -1080,6 +1080,76 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
|
||||
}
|
||||
});
|
||||
|
||||
// cw project sync [name] --all
|
||||
projectCommand
|
||||
.command('sync [name]')
|
||||
.description('Sync project clone(s) from remote')
|
||||
.option('--all', 'Sync all projects')
|
||||
.action(async (name: string | undefined, options: { all?: boolean }) => {
|
||||
try {
|
||||
const client = createDefaultTrpcClient();
|
||||
if (options.all) {
|
||||
const results = await client.syncAllProjects.mutate();
|
||||
for (const r of results) {
|
||||
const status = r.success ? 'ok' : `FAILED: ${r.error}`;
|
||||
console.log(`${r.projectName}: ${status}`);
|
||||
}
|
||||
} else if (name) {
|
||||
const projects = await client.listProjects.query();
|
||||
const project = projects.find((p) => p.name === name || p.id === name);
|
||||
if (!project) {
|
||||
console.error(`Project not found: ${name}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const result = await client.syncProject.mutate({ id: project.id });
|
||||
if (result.success) {
|
||||
console.log(`Synced ${result.projectName}: fetched=${result.fetched}, fast-forwarded=${result.fastForwarded}`);
|
||||
} else {
|
||||
console.error(`Sync failed: ${result.error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.error('Specify a project name or use --all');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to sync:', (error as Error).message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// cw project status [name]
|
||||
projectCommand
|
||||
.command('status [name]')
|
||||
.description('Show sync status for a project')
|
||||
.action(async (name: string | undefined) => {
|
||||
try {
|
||||
const client = createDefaultTrpcClient();
|
||||
const projects = await client.listProjects.query();
|
||||
const targets = name
|
||||
? projects.filter((p) => p.name === name || p.id === name)
|
||||
: projects;
|
||||
|
||||
if (targets.length === 0) {
|
||||
console.log(name ? `Project not found: ${name}` : 'No projects registered');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const project of targets) {
|
||||
const status = await client.getProjectSyncStatus.query({ id: project.id });
|
||||
const fetchedStr = status.lastFetchedAt
|
||||
? new Date(status.lastFetchedAt).toLocaleString()
|
||||
: 'never';
|
||||
console.log(`${project.name}:`);
|
||||
console.log(` Last fetched: ${fetchedStr}`);
|
||||
console.log(` Ahead: ${status.ahead} Behind: ${status.behind}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get status:', (error as Error).message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Account command group
|
||||
const accountCommand = program
|
||||
.command('account')
|
||||
|
||||
Reference in New Issue
Block a user