135 lines
3.1 KiB
PowerShell
135 lines
3.1 KiB
PowerShell
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)
|