Add polling sync communication layer
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
134
sync/sync-agent.ps1
Normal file
134
sync/sync-agent.ps1
Normal file
@@ -0,0 +1,134 @@
|
||||
param(
|
||||
[int]$IntervalSeconds = 30,
|
||||
[switch]$Once
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
$StateDir = Join-Path $RepoRoot ".sync-state"
|
||||
$ErrorFile = Join-Path $StateDir "sync-error-pc.md"
|
||||
$AllowedPaths = @(
|
||||
"AGENT_BOARD.md",
|
||||
"README.md",
|
||||
".cursor/rules",
|
||||
"handoff",
|
||||
"evidence",
|
||||
"rollback",
|
||||
"tasks",
|
||||
"sync",
|
||||
".gitignore"
|
||||
)
|
||||
|
||||
function Ensure-StateDir {
|
||||
if (-not (Test-Path $StateDir)) {
|
||||
New-Item -ItemType Directory -Path $StateDir | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Write-SyncError {
|
||||
param([string]$Message)
|
||||
|
||||
Ensure-StateDir
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss zzz"
|
||||
@"
|
||||
# PC Sync Error
|
||||
|
||||
- Time: $timestamp
|
||||
- Repository: $RepoRoot
|
||||
- Message: $Message
|
||||
|
||||
Manual action required. Do not continue automatic sync until the repository is clean.
|
||||
"@ | Set-Content -Path $ErrorFile -Encoding UTF8
|
||||
}
|
||||
|
||||
function Invoke-Git {
|
||||
param([string[]]$GitArgs)
|
||||
& git @GitArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "git $($GitArgs -join ' ') failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AllowedChanges {
|
||||
$changes = & git -C $RepoRoot status --porcelain
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "git status failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
return $changes | Where-Object {
|
||||
$line = $_
|
||||
if ([string]::IsNullOrWhiteSpace($line)) {
|
||||
return $false
|
||||
}
|
||||
$path = $line.Substring(3).Trim()
|
||||
foreach ($allowed in $AllowedPaths) {
|
||||
if ($path -eq $allowed -or $path.StartsWith("$allowed/") -or $path.StartsWith("$allowed\")) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Test-ConflictMarkers {
|
||||
$conflicts = & git -C $RepoRoot diff --name-only --diff-filter=U
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "git diff conflict check failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
return -not [string]::IsNullOrWhiteSpace(($conflicts -join ""))
|
||||
}
|
||||
|
||||
function Sync-Once {
|
||||
Set-Location $RepoRoot
|
||||
Ensure-StateDir
|
||||
|
||||
Invoke-Git @("pull", "--rebase", "--autostash")
|
||||
|
||||
if (Test-ConflictMarkers) {
|
||||
throw "merge or rebase conflicts detected"
|
||||
}
|
||||
|
||||
$allowedChanges = @(Get-AllowedChanges)
|
||||
if ($allowedChanges.Count -eq 0) {
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($path in $AllowedPaths) {
|
||||
if (Test-Path (Join-Path $RepoRoot $path)) {
|
||||
Invoke-Git @("add", "--", $path)
|
||||
}
|
||||
}
|
||||
|
||||
$staged = & git -C $RepoRoot diff --cached --name-only
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "git diff --cached failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace(($staged -join ""))) {
|
||||
return
|
||||
}
|
||||
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
Invoke-Git @("commit", "-m", "Sync agent state from PC at $timestamp")
|
||||
Invoke-Git @("push")
|
||||
}
|
||||
|
||||
do {
|
||||
try {
|
||||
Sync-Once
|
||||
if (Test-Path $ErrorFile) {
|
||||
Remove-Item $ErrorFile -Force
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-SyncError $_.Exception.Message
|
||||
Write-Error $_
|
||||
break
|
||||
}
|
||||
|
||||
if ($Once) {
|
||||
break
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $IntervalSeconds
|
||||
} while ($true)
|
||||
Reference in New Issue
Block a user