[CmdletBinding()] param() $ErrorActionPreference = 'Stop' $backendRoot = Split-Path -Parent $PSScriptRoot $workspaceRoot = Split-Path -Parent $backendRoot $failures = [System.Collections.Generic.List[string]]::new() $checked = 0 function Add-Failure([string]$message) { $script:failures.Add($message) } function Read-RequiredText([string]$path) { $script:checked++ if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { Add-Failure "缺少索引文件: $path" return '' } return Get-Content -LiteralPath $path -Raw } function Assert-Contains([string]$text, [string]$needle, [string]$scope) { $script:checked++ if (-not $text.Contains($needle)) { Add-Failure "$scope 未索引: $needle" } } function Assert-Path([string]$relativePath) { $script:checked++ $absolutePath = Join-Path $workspaceRoot $relativePath if (-not (Test-Path -LiteralPath $absolutePath)) { Add-Failure "索引入口不存在: $relativePath" } } $rootAgentsPath = Join-Path $workspaceRoot 'AGENTS.md' $adminAgentsPath = Join-Path $workspaceRoot 'apps/admin-web/AGENTS.md' $clientAgentsPath = Join-Path $workspaceRoot 'apps/client-web/AGENTS.md' $pdaAgentsPath = Join-Path $workspaceRoot 'apps/pda-uniapp/AGENTS.md' $backendAgentsPath = Join-Path $backendRoot 'AGENTS.md' $rootAgents = Read-RequiredText $rootAgentsPath $adminAgents = Read-RequiredText $adminAgentsPath $clientAgents = Read-RequiredText $clientAgentsPath $pdaAgents = Read-RequiredText $pdaAgentsPath $backendAgents = Read-RequiredText $backendAgentsPath # 部署单元必须能从根索引进入。 Get-ChildItem -LiteralPath (Join-Path $workspaceRoot 'apps') -Directory | Sort-Object Name | ForEach-Object { Assert-Contains $rootAgents "apps/$($_.Name)/" '根 AGENTS.md' } Assert-Contains $rootAgents 'baiyue-firstleg/' '根 AGENTS.md' # 前端当前全部业务模块必须能从应用级索引命中。 $applications = @( @{ Root = 'apps/admin-web/src/modules'; Text = $adminAgents; Scope = 'Admin AGENTS.md' }, @{ Root = 'apps/client-web/src/modules'; Text = $clientAgents; Scope = 'Client AGENTS.md' } ) foreach ($application in $applications) { foreach ($moduleDirectory in (Get-ChildItem -LiteralPath (Join-Path $workspaceRoot $application.Root) -Directory | Sort-Object Name)) { Assert-Contains $application.Text "modules/$($moduleDirectory.Name)/" $application.Scope } } # PDA 只检查 pages.json 正式注册页面,不把空目录或未注册原型纳入索引。 $pagesJsonPath = Join-Path $workspaceRoot 'apps/pda-uniapp/pages.json' $pagesJson = Get-Content -LiteralPath $pagesJsonPath -Raw | ConvertFrom-Json $registeredPageRoots = $pagesJson.pages.path | ForEach-Object { $parts = $_ -split '/'; "$($parts[0])/$($parts[1])/" } | Sort-Object -Unique foreach ($pageRoot in $registeredPageRoots) { Assert-Contains $pdaAgents $pageRoot 'PDA AGENTS.md' } # Maven 聚合声明的全部历史/当前模块必须在后端根指引中可定位。 [xml]$pom = Get-Content -LiteralPath (Join-Path $backendRoot 'pom.xml') -Raw foreach ($module in $pom.project.modules.module) { Assert-Contains $backendAgents "$module/" '后端 AGENTS.md' } # 对本轮高变化链路做精确文件门禁,防止改名或删除后留下悬空索引。 $criticalPaths = @( 'apps/admin-web/src/modules/overseas-warehouse/api/inboundOrders.ts', 'apps/admin-web/src/modules/overseas-warehouse/api/inboundPlans.ts', 'apps/admin-web/src/modules/overseas-warehouse/api/inventory.ts', 'apps/admin-web/src/modules/overseas-warehouse/api/pallets.ts', 'apps/admin-web/src/modules/overseas-business/api/platformOverseasBusiness.ts', 'apps/admin-web/src/modules/overseas-master-data/api/overseasMasterData.ts', 'apps/pda-uniapp/pages/tasks/tasks.vue', 'apps/pda-uniapp/utils/evidence-upload-queue.js', 'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/service/FeePaymentSnapshotValidator.java', 'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/client/FeePaymentVoucherFileClient.java', 'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapper.java', 'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentService.java', 'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/UnloadPhotoFileGateway.java', 'baiyue-firstleg/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/FirstlegEventClient.java', 'baiyue-firstleg/common-event-contract/src/main/java/com/baiyue/common/event/FirstLegLoadingPlanCompletedV2.java' ) $criticalPaths | ForEach-Object { Assert-Path $_ } # 只检查明确维护的快速索引表,避免把排除规则正文中的目录名误判为业务入口。 $indexedAgentFiles = @( $rootAgentsPath, $adminAgentsPath, $clientAgentsPath, $pdaAgentsPath, $backendAgentsPath, (Join-Path $backendRoot 'firstleg/AGENTS.md'), (Join-Path $backendRoot 'finance/AGENTS.md'), (Join-Path $backendRoot 'lastleg-service/AGENTS.md'), (Join-Path $backendRoot 'event-bridge-service/AGENTS.md'), (Join-Path $backendRoot 'file/AGENTS.md'), (Join-Path $backendRoot 'common-event-contract/AGENTS.md') ) $excludedTargetPattern = '(?i)`(?:[^`]*/)?(node_modules|node_modules\.broken-by-path|dist|target|reports|output|outputs|uni_modules|\.git|\.codex[^/`]*)(?:/|`)' $excludedPatternSamples = @( '| 错误入口 | `node_modules/pkg/` |', '| 错误入口 | `src/target/classes/` |', '| 错误入口 | `.codex-temp/task/` |' ) foreach ($sample in $excludedPatternSamples) { $script:checked++ if ($sample -notmatch $excludedTargetPattern) { Add-Failure "排除目录检测器自检失败: $sample" } } foreach ($agentFile in $indexedAgentFiles) { $script:checked++ if (-not (Test-Path -LiteralPath $agentFile -PathType Leaf)) { Add-Failure "缺少受检索引文件: $agentFile" continue } $lineNumber = 0 foreach ($line in Get-Content -LiteralPath $agentFile) { $lineNumber++ if ($line.StartsWith('|') -and $line -match $excludedTargetPattern) { Add-Failure "快速索引引用了排除目录: $agentFile`:$lineNumber" } } } if ($failures.Count -gt 0) { Write-Host "AGENTS 索引合同失败,共 $($failures.Count) 项:" -ForegroundColor Red $failures | ForEach-Object { Write-Host "- $_" -ForegroundColor Red } exit 1 } Write-Host "AGENTS 索引合同通过:$checked 项检查,覆盖部署单元、前端模块、PDA 注册页面、Maven 模块、关键入口和排除目录。" -ForegroundColor Green exit 0