From 74529148861006cb47e7cd9a76b37ab4bcbdce97 Mon Sep 17 00:00:00 2001 From: WuYongJie <549727955@qq.com> Date: Thu, 6 Aug 2026 19:15:56 +0800 Subject: [PATCH] fix: distinguish reused contracts in task preflight --- .gitea/PULL_REQUEST_TEMPLATE.md | 7 ++- AGENTS.md | 12 ++-- docs/team-collaboration.md | 17 +++--- scripts/codex-task-preflight.ps1 | 101 ++++++++++++++++++++++++++----- 4 files changed, 104 insertions(+), 33 deletions(-) diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md index cf24cccd..9a97ea70 100644 --- a/.gitea/PULL_REQUEST_TEMPLATE.md +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -7,8 +7,8 @@ - 任务来源/团队分工: - 目标结果: - 预计修改路径: -- 预计页面或路由: -- 预计 Controller / Repository: +- 将写入的公共契约(路由、Controller 方法、Repository、表/视图、`source_key`、`scope`、权限名、菜单键或共享组件): +- 仅复用、明确不修改的公共契约: - 数据源、表/视图和字段: - 上游 `source_key` / `scope`: - 权限名、菜单键或共享组件: @@ -19,7 +19,8 @@ - [ ] Codex 已执行 `scripts/codex-task-preflight.ps1` - [ ] 已检查最新 `main` 和所有远程任务分支 - [ ] 已搜索主线既有页面、接口、Repository 和数据查询 -- [ ] 未发现重叠;或已在下方写明相关分支和协调结论 +- [ ] 已把“将写入”和“仅复用、不修改”的公共契约分开;仅复用项不作为单独阻断条件 +- [ ] 未发现实际写入重叠;或已在下方写明相关分支、具体重叠项和协调结论 相关分支、重叠项与协调结论: diff --git a/AGENTS.md b/AGENTS.md index 0352c609..b6cae44f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,10 +32,10 @@ ### 2.1 多人任务可见性与碰撞检查 -- 团队按既有分工自行认领任务。每位开发者把已确定的任务交给自己的 Codex 后,Codex 必须先划定预计修改路径,以及路由、Controller、Repository、数据表/视图、上游 `source_key`、`scope`、权限名、菜单键等公共契约;范围不明确时不得开始修改。 -- 开工前由 Codex 执行 `scripts/codex-task-preflight.ps1`,检查最新 `main`、所有远程任务分支和主线既有实现。检查参数必须包含任务名、实际开发者、预计修改路径和精确公共契约关键词;不得用“页面”“接口”“数据库”等宽泛词代替。 +- 团队按既有分工自行认领任务。每位开发者把已确定的任务交给自己的 Codex 后,Codex 必须先划定精确预计修改路径,并把路由、Controller、Repository、数据表/视图、上游 `source_key`、`scope`、权限名、菜单键等公共契约分为“将写入”和“仅复用、不修改”两类;范围不明确时不得开始修改。 +- 开工前由 Codex 执行 `scripts/codex-task-preflight.ps1`,检查最新 `main`、所有远程任务分支和主线既有实现。`-Contract`(或 `-WriteContract`)只能填写将写入的精确公共契约,`-ReuseContract` 填写只调用或依赖、明确不修改的公共契约;预计路径不得用 `app`、`tests`、`页面`、`接口`、`数据库`等宽泛根目录或词语代替。 - 预检通过后立即创建并推送独立任务分支,并在 Gitea 创建标题以 `WIP:` 开头的 PR。WIP PR 必须在写大量代码前公开任务目标、模块边界和公共契约,不能等功能完成后才首次推送。 -- 任何 Codex 发现相同页面、路由、Controller、Repository、表/视图、`source_key`、`scope`、权限名、菜单键或公共组件正在另一远程分支开发时,必须停止写入并把重叠项报告给相关开发者;不得自行再造另一套实现,也不得通过覆盖或回退对方代码解决。 +- 只有预计修改路径重叠,或两个任务都会写入同一页面、路由、Controller、Repository、表/视图、`source_key`、`scope`、权限名、菜单键或公共组件时,Codex 才必须停止写入并报告具体重叠项。仅调用未修改的共享路由、`Pages::show()`、通用组件或既有 Repository 不属于冲突,但必须在 WIP PR 列为“仅复用”;如果实施中需要改该契约实现,必须先停止、更新 WIP PR 并重新预检。 - 远程不可见的本机改动无法被其他 Codex 检查。任务分支和 WIP PR 不及时推送造成的重复开发,不能以“Codex 没发现”为理由继续合并。 - 并行 Codex/Subagent 优先用于只读探索、测试、日志分析和代码审查;多个代理不得同时写同一批文件。需要并行写入时必须使用互不重叠的 worktree 和文件边界,由主任务代理统一复核。 - 普通代码重叠和同步方案由相关分支提交人及其 Codex 处理。只有产品方向、重大业务规则或生产高风险操作需要升级确认。 @@ -47,7 +47,7 @@ 1. 先确认任务目标和验收方式,由 Codex 根据现有代码、远程分支和团队既有分工划定影响范围与不能触碰的模块。 2. 进入正确项目和分支,先执行 `git status`;看到陌生改动,先停止并说明情况。 3. 向 AI 提供:任务目标、当前分支、报错或现象和验收结果;技术范围由 AI 检查后提出,不能反向要求新人猜文件、命令或数据链路。 -4. 要求 AI 运行任务预检,说明改动计划、涉及文件、公共契约、冲突风险和验证步骤;预检阻断时先由相关开发者协调,不能继续修改。 +4. 要求 AI 运行任务预检,分别说明将写入与仅复用的公共契约、涉及文件、冲突风险和验证步骤;只有实际写入重叠时才阻断,预检阻断时先由相关开发者协调,不能继续修改。 5. 预检通过后立即推送任务分支并创建 WIP PR,再进入正式开发。 6. 修改后查看 diff,并要求 AI 用普通语言解释“改了什么、为什么、怎样测试”。 7. 在本地或测试环境完成最小有效验证,记录实际结果。 @@ -61,14 +61,14 @@ 当前分支:<分支名>。 现场:<报错、复现步骤、相关文件>。 验收:<页面、接口或业务结果>。 -要求:先检查最新 main、全部远程任务分支和现有实现,运行 scripts/codex-task-preflight.ps1,自行划定技术边界;有重叠就停止并报告。预检通过后立即推送任务分支并创建 WIP PR,再开始修改。不要执行删除、部署或生产数据库操作。 +要求:先检查最新 main、全部远程任务分支和现有实现,运行 scripts/codex-task-preflight.ps1;明确列出精确修改路径、将写入的公共契约和仅复用但不修改的公共契约。只有实际写入重叠才停止并报告;仅复用共享路由或组件时记录在 WIP PR 后可继续。预检通过后立即推送任务分支并创建 WIP PR,再开始修改。不要执行删除、部署或生产数据库操作。 ``` ## 4. PR 与合并 - 所有成员的任务分支必须通过 PR 合并到 `main`。PR 满足检查和验证条件后,任务开发者的 Codex 可以直接执行 Squash merge,不需要项目负责人代为合并;禁止绕过 PR 把开发提交直接推入 `main`。 - PR 作者必须填写:做了什么、Codex 改了哪些文件、怎样测试、是否涉及金额/权限/数据库/配置。 -- WIP PR 是任务占用和公共契约的远程可见记录。标题使用 `WIP: <任务>`;描述必须使用仓库的 `.gitea/PULL_REQUEST_TEMPLATE.md`,其中预计范围在开工时填写,实际范围和测试结果在 Codex 合并前自审时补齐。 +- WIP PR 是任务占用和公共契约的远程可见记录。标题使用 `WIP: <任务>`;描述必须使用仓库的 `.gitea/PULL_REQUEST_TEMPLATE.md`,其中预计范围、将写入契约和仅复用契约在开工时填写,实际范围和测试结果在 Codex 合并前自审时补齐。 - PR 不要求另一位开发者审核。任务 Codex 必须在实现结束后另做一次合并前自审:重新读取需求和验收标准,以 `origin/main...任务分支` 的完整 diff 为准逐文件检查,不得直接沿用开发阶段的“已经完成”结论。 - 涉及金额计算、权限、导入、数据库迁移、账户或系统配置时,必须在 PR 中明确标注影响与回滚;代码仍可在检查和测试通过后由 Codex 合并,但生产迁移、数据修复、删除、覆盖配置和部署继续执行独立高风险授权。 - 使用仓库默认的 Squash merge(压缩合并)。执行合并的 Codex 必须先重新拉取远程引用,确认工作区干净、PR 不再是 WIP、需求和验收逐项满足、完整 diff 无越界或敏感内容、测试通过、目标为最新 `main` 且没有未协调重叠,再执行合并。 diff --git a/docs/team-collaboration.md b/docs/team-collaboration.md index b44fde42..3f97d2b3 100644 --- a/docs/team-collaboration.md +++ b/docs/team-collaboration.md @@ -18,9 +18,9 @@ 现场:<报错、截图、现有页面或业务说明>。 验收:<最终页面、接口或业务结果>。 -请先核对仓库、Git 身份、最新 main 和全部远程任务分支,搜索现有页面、路由、Controller、Repository、表/视图、source_key、scope、权限名、菜单键和共享组件。自行列出预计修改路径与精确公共契约关键词,并运行 scripts/codex-task-preflight.ps1。 +请先核对仓库、Git 身份、最新 main 和全部远程任务分支,搜索现有页面、路由、Controller、Repository、表/视图、source_key、scope、权限名、菜单键和共享组件。自行列出精确预计修改路径,并把公共契约分为“将写入”和“仅复用、不修改”两类,然后运行 scripts/codex-task-preflight.ps1。 -发现其他远程分支正在处理相同功能或公共契约时立即停止,只报告重叠项,不能继续写代码或另造一套实现。预检通过后,从最新 main 创建本人独立任务分支,立即推送并创建 WIP PR,填写任务范围,再开始修改。 +发现预计修改路径重叠,或双方都会写入同一公共契约时立即停止,只报告具体重叠项,不能继续写代码或另造一套实现。只调用既有共享路由、Controller、组件或 Repository 而不修改它们,不是冲突;在 WIP PR 中列为“仅复用”并通过预检后可以继续。预检通过后,从最新 main 创建本人独立任务分支,立即推送并创建 WIP PR,填写任务范围,再开始修改。 ``` Codex 必须先核对: @@ -40,7 +40,7 @@ git fetch --prune origin - Git 身份和分支用户名属于当前实际开发者; - 没有无法解释的受控文件改动; - 当前任务没有复用已合并旧分支; -- 已列出具体预计路径和公共契约,不能只写“财务页面”“数据库接口”等宽泛范围。 +- 已列出具体预计路径、将写入的公共契约和仅复用的公共契约,不能只写“财务页面”“数据库接口”等宽泛范围;预计路径也不能只写 `app`、`tests` 等根目录。 ## Codex 开工碰撞检查 @@ -50,8 +50,9 @@ git fetch --prune origin .\scripts\codex-task-preflight.ps1 ` -Task '<任务名称>' ` -Developer '<实际开发者用户名>' ` - -PlannedPath @('resources/views/目标模块', 'modules/EcommerceFinance/Repositories/目标文件.php') ` - -Contract @('具体路由片段', 'Repository类名', 'source.scope', '真实表或视图名') + -PlannedPath @('config/business_navigation.php', 'tests/Feature/Business/OrderDataNavigationTest.php') ` + -Contract @('order-data/after-sales-expense-details', 'order-data/product-promotion-expense-details') ` + -ReuseContract @('business.pages.show') ``` 脚本会: @@ -60,11 +61,11 @@ git fetch --prune origin 2. 检查当前仓库、远程地址、Git 身份和工作区; 3. 检查所有尚未包含于 `main` 的远程任务分支; 4. 比较预计路径是否重叠; -5. 在远程 diff 中检查路由、Repository、表/视图、`scope` 等公共契约; +5. 在远程 diff 中检查将写入的路由、Repository、表/视图、`scope` 等公共契约;仅复用契约只输出提示,不单独阻断; 6. 搜索 `main` 是否已经存在同名功能; 7. 给出“可以开始”或“停止并协调”的明确结果。 -脚本返回阻断时,Codex 不得修改代码。相关开发者根据报告中的分支、文件和公共契约自行确认保留哪套实现,确认后重新运行预检。 +脚本返回阻断时,Codex 不得修改代码。阻断只表示存在实际写入重叠:相同预计修改路径,或双方都会修改同一公共契约。相关开发者根据报告中的分支、文件和公共契约自行确认保留哪套实现,确认后重新运行预检。若只复用既有共享能力,则在 WIP PR 写明“不修改”即可继续。 ## 立即建立远程可见任务 @@ -93,7 +94,7 @@ WIP PR 用于公开任务占用,不代表功能已经完成,也不能被合 1. 页面先复用现有共享组件和业务 Repository; 2. Controller 和页面不得直接连接数据库; 3. 新上游查询必须同时具备 Repository、稳定 `scope`、缓存调用、刷新器映射和测试; -4. 公共路由、权限、侧边栏、数据库配置、共享组件和部署脚本有改动时,必须在 WIP PR 中单独列出; +4. 公共路由、权限、侧边栏、数据库配置、共享组件和部署脚本有改动时,必须在 WIP PR 中列为“将写入”;只调用、不修改的共享能力列为“仅复用”。实施中若要从“仅复用”改为“将写入”,必须先停止、更新 WIP PR 并重新运行预检; 5. 多个 Codex/Subagent 可以并行做只读排查、测试和审查,但不能同时写同一批文件; 6. 每次准备扩大修改范围时,先更新 WIP PR,再用新增路径和契约重新运行预检; 7. 修改后由 Codex 检查 `git status`、`git diff`,不得提交 `.env`、密码、Token、真实财务数据、日志或构建产物。 diff --git a/scripts/codex-task-preflight.ps1 b/scripts/codex-task-preflight.ps1 index ae4fcd9c..e5628779 100644 --- a/scripts/codex-task-preflight.ps1 +++ b/scripts/codex-task-preflight.ps1 @@ -14,8 +14,11 @@ param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] + [Alias('WriteContract')] [string[]]$Contract, + [string[]]$ReuseContract = @(), + [string]$Remote = 'origin', [string]$BaseRef = 'origin/main' ) @@ -69,10 +72,18 @@ if ([string]::IsNullOrWhiteSpace($gitUser)) { } $plannedPaths = @($PlannedPath | ForEach-Object { Normalize-RepoPath $_ } | Where-Object { $_ } | Select-Object -Unique) -$contracts = @($Contract | ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique) +$writeContracts = @($Contract | ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique) +$reuseContracts = @($ReuseContract | ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique) -if ($plannedPaths.Count -eq 0 -or $contracts.Count -eq 0) { - throw '必须同时提供预计修改路径和公共契约关键词,不能在范围不明确时开始开发。' +if ($plannedPaths.Count -eq 0 -or $writeContracts.Count -eq 0) { + throw '必须同时提供预计修改路径和将写入的公共契约关键词,不能在范围不明确时开始开发。' +} + +$broadPathRoots = @('app', 'bootstrap', 'config', 'database', 'deploy', 'docs', 'modules', 'public', 'resources', 'routes', 'scripts', 'tests') +$broadPlannedPaths = @($plannedPaths | Where-Object { $broadPathRoots -contains $_ }) + +if ($broadPlannedPaths.Count -gt 0) { + throw "预计修改路径不能使用宽泛根目录:$($broadPlannedPaths -join ', ')。请填写精确文件或最小子目录,例如 tests/Feature/Business/OrderDataNavigationTest.php。" } Invoke-Git fetch --prune $Remote | Out-Null @@ -101,6 +112,7 @@ $remoteBranches = @( ) $branchFindings = @() +$reuseFindings = @() foreach ($branch in $remoteBranches) { $counts = ((Invoke-Git rev-list --left-right --count "$BaseRef...$branch") | Select-Object -First 1).Trim() -split '\s+' @@ -128,28 +140,44 @@ foreach ($branch in $remoteBranches) { $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 { + $writeContractHits = @( + $writeContracts | 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) { + $reuseContractHits = @( + $reuseContracts | Where-Object { + $branch -match [Regex]::Escape($_) ` + -or $subject -match [Regex]::Escape($_) ` + -or $diffText -match [Regex]::Escape($_) + } + ) + + if ($pathHits.Count -gt 0 -or $writeContractHits.Count -gt 0) { $branchFindings += [pscustomobject]@{ - Branch = $branch - Subject = $subject - PathHits = @($pathHits | Select-Object -Unique) - ContractHits = @($contractHits | Select-Object -Unique) + Branch = $branch + Subject = $subject + PathHits = @($pathHits | Select-Object -Unique) + WriteContractHits = @($writeContractHits | Select-Object -Unique) + ReuseContractHits = @($reuseContractHits | Select-Object -Unique) + } + } elseif ($reuseContractHits.Count -gt 0) { + $reuseFindings += [pscustomobject]@{ + Branch = $branch + Subject = $subject + ReuseContractHits = @($reuseContractHits | Select-Object -Unique) } } } $existingFindings = @() +$existingReuseFindings = @() $searchRoots = @('app', 'config', 'database', 'modules', 'resources', 'routes', 'tests') -foreach ($keyword in $contracts) { +foreach ($keyword in $writeContracts) { $matches = @(& git grep -n -I -F -e $keyword $BaseRef -- @searchRoots 2>$null) if ($LASTEXITCODE -notin @(0, 1)) { @@ -164,10 +192,29 @@ foreach ($keyword in $contracts) { } } +foreach ($keyword in $reuseContracts) { + $matches = @(& git grep -n -I -F -e $keyword $BaseRef -- @searchRoots 2>$null) + + if ($LASTEXITCODE -notin @(0, 1)) { + throw "检查主线仅复用公共契约失败:$keyword" + } + + if ($matches.Count -gt 0) { + $existingReuseFindings += [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 ', ')" +Write-Host "将写入的公共契约:$($writeContracts -join ', ')" + +if ($reuseContracts.Count -gt 0) { + Write-Host "仅复用、不修改的公共契约:$($reuseContracts -join ', ')" +} if ($untrackedChanges.Count -gt 0) { Write-Host '提醒:存在未跟踪文件,Codex 必须确认归属且不得顺带提交:' -ForegroundColor Yellow @@ -183,8 +230,26 @@ if ($existingFindings.Count -gt 0) { } } +if ($existingReuseFindings.Count -gt 0) { + Write-Host '主线已存在下列仅复用契约;这属于预期复用,不单独阻断:' -ForegroundColor Cyan + + foreach ($finding in $existingReuseFindings) { + Write-Host " [$($finding.Keyword)]" + $finding.Matches | ForEach-Object { Write-Host " $_" } + } +} + +if ($reuseFindings.Count -gt 0) { + Write-Host '提示:以下远程任务分支也在使用仅复用契约;当前任务已声明不修改它们,因此不单独阻断。若实施中需要改该契约实现,必须停止、更新 WIP PR 并重新预检:' -ForegroundColor Yellow + + foreach ($finding in $reuseFindings) { + Write-Host " $($finding.Branch) — $($finding.Subject)" + Write-Host " 仅复用契约:$($finding.ReuseContractHits -join ', ')" + } +} + if ($branchFindings.Count -gt 0) { - Write-Host '阻断:以下远程任务分支与当前计划重叠。停止修改,由相关开发者先完成协调:' -ForegroundColor Red + Write-Host '阻断:以下远程任务分支与当前计划存在实际写入重叠。停止修改,由相关开发者先完成协调:' -ForegroundColor Red foreach ($finding in $branchFindings) { Write-Host " $($finding.Branch) — $($finding.Subject)" @@ -193,13 +258,17 @@ if ($branchFindings.Count -gt 0) { Write-Host " 重叠路径:$($finding.PathHits -join ', ')" } - if ($finding.ContractHits.Count -gt 0) { - Write-Host " 重叠契约:$($finding.ContractHits -join ', ')" + if ($finding.WriteContractHits.Count -gt 0) { + Write-Host " 重叠写入契约:$($finding.WriteContractHits -join ', ')" + } + + if ($finding.ReuseContractHits.Count -gt 0) { + Write-Host " 同时仅复用:$($finding.ReuseContractHits -join ', ')" } } exit 2 } -Write-Host '通过:未发现远程任务分支与当前计划重叠。可以创建或继续独立任务分支。' -ForegroundColor Green +Write-Host '通过:未发现远程任务分支与当前计划的实际写入重叠。可以创建或继续独立任务分支。' -ForegroundColor Green exit 0 -- 2.47.2