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

@@ -53,8 +53,27 @@ Worktrees stored in `.cw-worktrees/` subdirectory of the repo. Each agent gets a
- `ensureProjectClone(project, workspaceRoot)` — Idempotent: checks if clone exists, clones if not
- `getProjectCloneDir(name, id)` — Canonical path: `repos/<sanitized-name>-<id>/`
### ProjectSyncManager (`apps/server/git/remote-sync.ts`)
Fetches remote changes for project clones and fast-forwards the local `defaultBranch`. Safe to run with active worktrees — only updates remote-tracking refs and the base branch (never checked out directly by any worktree).
**Constructor**: `ProjectSyncManager(projectRepository, workspaceRoot, eventBus?)`
| Method | Purpose |
|--------|---------|
| `syncProject(projectId)` | `git fetch origin` + `git merge --ff-only origin/<defaultBranch>`, updates `lastFetchedAt` |
| `syncAllProjects()` | Sync all registered projects sequentially |
| `getSyncStatus(projectId)` | Returns `{ ahead, behind, lastFetchedAt }` via `rev-list --left-right --count` |
| `getInitiativeDivergence(projectId, branch)` | Ahead/behind between `defaultBranch` and an initiative branch |
**Sync flow during phase dispatch**: `dispatchNextPhase()` syncs all linked project clones *before* creating initiative/phase branches, so branches fork from up-to-date remote state. Sync is best-effort — failures are logged but don't block dispatch.
**CLI**: `cw project sync [name] --all`, `cw project status [name]`
**tRPC**: `syncProject`, `syncAllProjects`, `getProjectSyncStatus`
### Events Emitted
`worktree:created`, `worktree:removed`, `worktree:merged`, `worktree:conflict`
`worktree:created`, `worktree:removed`, `worktree:merged`, `worktree:conflict`, `project:synced`, `project:sync_failed`
---