[CmdletBinding()] param( [string]$RootPath = "C:\Users\stefan\git", [string]$StandardsRepoPath = "C:\Users\stefan\git\project-standards", [string[]]$Exclude = @('project-standards'), [switch]$CheckOnly, [switch]$Watch, [int]$IntervalSeconds = 60 ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $agentsTemplate = Join-Path $StandardsRepoPath 'templates/AGENTS.base.md' $dodTemplate = Join-Path $StandardsRepoPath 'templates/DEFINITION_OF_DONE.base.md' if (-not (Test-Path -Path $agentsTemplate -PathType Leaf)) { throw "AGENTS template not found: $agentsTemplate" } if (-not (Test-Path -Path $dodTemplate -PathType Leaf)) { throw "DoD template not found: $dodTemplate" } if ($IntervalSeconds -lt 5) { throw 'IntervalSeconds must be >= 5.' } function Get-ContentHashOrMissing { param([string]$Path) if (-not (Test-Path -Path $Path -PathType Leaf)) { return '__MISSING__' } return (Get-FileHash -Path $Path -Algorithm SHA256).Hash } function Ensure-FileFromTemplate { param( [string]$Template, [string]$Target, [switch]$OnlyCheck ) $templateHash = Get-ContentHashOrMissing -Path $Template $targetHash = Get-ContentHashOrMissing -Path $Target if ($templateHash -eq $targetHash) { return 'ok' } if ($OnlyCheck) { return 'drift' } $parent = Split-Path -Parent $Target if (-not (Test-Path -Path $parent -PathType Container)) { New-Item -ItemType Directory -Path $parent | Out-Null } Copy-Item -Path $Template -Destination $Target -Force return 'updated' } function Invoke-ReconcileOnce { param([switch]$OnlyCheck) $resolvedRoot = (Resolve-Path -Path $RootPath -ErrorAction Stop).Path $repos = Get-ChildItem -Path $resolvedRoot -Directory | Where-Object { $Exclude -notcontains $_.Name } $summary = [ordered]@{ scanned = 0 updated = 0 drift = 0 } foreach ($repo in $repos) { $repoPath = $repo.FullName $agentsTarget = Join-Path $repoPath 'AGENTS.md' $dodTarget = Join-Path (Join-Path $repoPath 'docs') 'DEFINITION_OF_DONE.md' $summary.scanned++ $agentsState = Ensure-FileFromTemplate -Template $agentsTemplate -Target $agentsTarget -OnlyCheck:$OnlyCheck $dodState = Ensure-FileFromTemplate -Template $dodTemplate -Target $dodTarget -OnlyCheck:$OnlyCheck if ($agentsState -eq 'updated' -or $dodState -eq 'updated') { $summary.updated++ Write-Host "UPDATED: $repoPath" continue } if ($agentsState -eq 'drift' -or $dodState -eq 'drift') { $summary.drift++ Write-Host "DRIFT: $repoPath" continue } Write-Host "OK: $repoPath" } Write-Host "Summary -> scanned=$($summary.scanned), updated=$($summary.updated), drift=$($summary.drift)" return $summary } if ($Watch) { while ($true) { $now = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' Write-Host "[$now] Reconciling project standards..." [void](Invoke-ReconcileOnce -OnlyCheck:$CheckOnly) Start-Sleep -Seconds $IntervalSeconds } } $result = Invoke-ReconcileOnce -OnlyCheck:$CheckOnly if ($CheckOnly -and $result.drift -gt 0) { exit 1 }