CaiWuXiTong/scripts/codex-task-preflight.ps1

206 lines
6.5 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Task,
[Parameter(Mandatory = $true)]
[ValidatePattern('^[A-Za-z0-9_-]+$')]
[string]$Developer,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string[]]$PlannedPath,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string[]]$Contract,
[string]$Remote = 'origin',
[string]$BaseRef = 'origin/main'
)
$ErrorActionPreference = 'Stop'
function Invoke-Git {
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$Arguments)
$output = & git @Arguments 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Git 命令执行失败:git $($Arguments -join ' ')`n$($output -join "`n")"
}
return @($output)
}
function Normalize-RepoPath {
param([string]$Path)
return ($Path.Trim() -replace '\\', '/').TrimStart('.', '/').TrimEnd('/')
}
function Test-PathOverlap {
param(
[string]$Planned,
[string]$Changed
)
return $Planned -eq $Changed `
-or $Changed.StartsWith($Planned + '/', [StringComparison]::OrdinalIgnoreCase) `
-or $Planned.StartsWith($Changed + '/', [StringComparison]::OrdinalIgnoreCase)
}
$repoRoot = (Invoke-Git rev-parse --show-toplevel | Select-Object -First 1).Trim()
$repoName = Split-Path -Leaf $repoRoot
$originUrl = (Invoke-Git remote get-url $Remote | Select-Object -First 1).Trim()
$gitUser = (Invoke-Git config user.name | Select-Object -First 1).Trim()
if ($repoName -ne 'CaiWuXiTong') {
throw "当前目录不是 CaiWuXiTong:$repoRoot"
}
if ($originUrl -notmatch 'WuYongJie/CaiWuXiTong(?:\.git)?$') {
throw "远程仓库不是 WuYongJie/CaiWuXiTong:$originUrl"
}
if ([string]::IsNullOrWhiteSpace($gitUser)) {
throw 'Git user.name 未配置。'
}
$plannedPaths = @($PlannedPath | ForEach-Object { Normalize-RepoPath $_ } | Where-Object { $_ } | Select-Object -Unique)
$contracts = @($Contract | ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique)
if ($plannedPaths.Count -eq 0 -or $contracts.Count -eq 0) {
throw '必须同时提供预计修改路径和公共契约关键词,不能在范围不明确时开始开发。'
}
Invoke-Git fetch --prune $Remote | Out-Null
Invoke-Git rev-parse --verify $BaseRef | Out-Null
$status = @(Invoke-Git status --porcelain=v1)
$trackedChanges = @($status | Where-Object { $_ -and -not $_.StartsWith('?? ') })
$untrackedChanges = @($status | Where-Object { $_ -and $_.StartsWith('?? ') })
if ($trackedChanges.Count -gt 0) {
Write-Host '阻断:工作区已有受控文件改动,不能开始新任务。' -ForegroundColor Red
$trackedChanges | ForEach-Object { Write-Host " $_" }
exit 2
}
$currentBranch = (Invoke-Git branch --show-current | Select-Object -First 1).Trim()
$remoteBranches = @(
Invoke-Git for-each-ref '--format=%(refname:short)' "refs/remotes/$Remote" |
Where-Object {
$_ -and
$_ -ne $Remote -and
$_ -ne "$Remote/HEAD" -and
$_ -ne $BaseRef -and
$_ -ne "$Remote/$currentBranch"
}
)
$branchFindings = @()
foreach ($branch in $remoteBranches) {
$counts = ((Invoke-Git rev-list --left-right --count "$BaseRef...$branch") | Select-Object -First 1).Trim() -split '\s+'
$branchOnly = [int]$counts[1]
if ($branchOnly -eq 0) {
continue
}
& git merge-base --is-ancestor $branch $BaseRef
if ($LASTEXITCODE -eq 0) {
continue
}
$changedFiles = @(Invoke-Git diff --name-only "$BaseRef...$branch" | ForEach-Object { Normalize-RepoPath $_ })
$pathHits = @()
foreach ($planned in $plannedPaths) {
foreach ($changed in $changedFiles) {
if (Test-PathOverlap -Planned $planned -Changed $changed) {
$pathHits += $changed
}
}
}
$subject = (Invoke-Git log -1 '--format=%s' $branch | Select-Object -First 1).Trim()
$diffText = (Invoke-Git diff --unified=0 "$BaseRef...$branch") -join "`n"
$contractHits = @(
$contracts | Where-Object {
$branch -match [Regex]::Escape($_) `
-or $subject -match [Regex]::Escape($_) `
-or $diffText -match [Regex]::Escape($_)
}
)
if ($pathHits.Count -gt 0 -or $contractHits.Count -gt 0) {
$branchFindings += [pscustomobject]@{
Branch = $branch
Subject = $subject
PathHits = @($pathHits | Select-Object -Unique)
ContractHits = @($contractHits | Select-Object -Unique)
}
}
}
$existingFindings = @()
$searchRoots = @('app', 'config', 'database', 'modules', 'resources', 'routes', 'tests')
foreach ($keyword in $contracts) {
$matches = @(& git grep -n -I -F -e $keyword $BaseRef -- @searchRoots 2>$null)
if ($LASTEXITCODE -notin @(0, 1)) {
throw "检查主线公共契约失败:$keyword"
}
if ($matches.Count -gt 0) {
$existingFindings += [pscustomobject]@{
Keyword = $keyword
Matches = @($matches | Select-Object -First 8)
}
}
}
Write-Host "Codex 任务开工检查:$Task" -ForegroundColor Cyan
Write-Host "开发者:$Developer;Git 身份:$gitUser;基线:$BaseRef"
Write-Host "预计路径:$($plannedPaths -join ', ')"
Write-Host "公共契约:$($contracts -join ', ')"
if ($untrackedChanges.Count -gt 0) {
Write-Host '提醒:存在未跟踪文件,Codex 必须确认归属且不得顺带提交:' -ForegroundColor Yellow
$untrackedChanges | ForEach-Object { Write-Host " $_" }
}
if ($existingFindings.Count -gt 0) {
Write-Host '主线已存在相同关键词,请先复用或说明差异:' -ForegroundColor Yellow
foreach ($finding in $existingFindings) {
Write-Host " [$($finding.Keyword)]"
$finding.Matches | ForEach-Object { Write-Host " $_" }
}
}
if ($branchFindings.Count -gt 0) {
Write-Host '阻断:以下远程任务分支与当前计划重叠。停止修改,由相关开发者先完成协调:' -ForegroundColor Red
foreach ($finding in $branchFindings) {
Write-Host " $($finding.Branch) — $($finding.Subject)"
if ($finding.PathHits.Count -gt 0) {
Write-Host " 重叠路径:$($finding.PathHits -join ', ')"
}
if ($finding.ContractHits.Count -gt 0) {
Write-Host " 重叠契约:$($finding.ContractHits -join ', ')"
}
}
exit 2
}
Write-Host '通过:未发现远程任务分支与当前计划重叠。可以创建或继续独立任务分支。' -ForegroundColor Green
exit 0