fix: distinguish reused contracts in task preflight (#19)

This commit is contained in:
WuYongJie 2026-08-06 19:21:01 +08:00
parent ba6be3f3fd
commit 481b5d2e49
4 changed files with 104 additions and 33 deletions

View File

@ -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 和数据查询
- [ ] 未发现重叠;或已在下方写明相关分支和协调结论
- [ ] 已把“将写入”和“仅复用、不修改”的公共契约分开;仅复用项不作为单独阻断条件
- [ ] 未发现实际写入重叠;或已在下方写明相关分支、具体重叠项和协调结论
相关分支、重叠项与协调结论:

View File

@ -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` 且没有未协调重叠,再执行合并。

View File

@ -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、真实财务数据、日志或构建产物。

View File

@ -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