diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..cf24cccd --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,58 @@ +> 开工时标题使用 `WIP: <任务名称>` 并先填写“任务占用与边界”。Codex 完成独立合并前自审后补齐其余内容并移除 `WIP:`。 + +## 任务占用与边界 + +- 任务名称: +- 实际开发者: +- 任务来源/团队分工: +- 目标结果: +- 预计修改路径: +- 预计页面或路由: +- 预计 Controller / Repository: +- 数据源、表/视图和字段: +- 上游 `source_key` / `scope`: +- 权限名、菜单键或共享组件: +- 明确不修改的范围: + +## 开工碰撞检查 + +- [ ] Codex 已执行 `scripts/codex-task-preflight.ps1` +- [ ] 已检查最新 `main` 和所有远程任务分支 +- [ ] 已搜索主线既有页面、接口、Repository 和数据查询 +- [ ] 未发现重叠;或已在下方写明相关分支和协调结论 + +相关分支、重叠项与协调结论: + +## 实际改动 + +- 完成内容: +- Codex 实际修改的文件: +- 新增或变更的公共契约: +- 与原计划的差异: + +## 验证证据 + +- 正常流程: +- 空数据/错误输入/无权限场景: +- 受影响旧功能: +- 自动测试及结果: +- 实际页面、接口或只读业务查询结果: + +## 风险检查 + +- [ ] 不涉及金额、权限、审批、数据库迁移、生产配置或数据修复 +- [ ] 如涉及上述高风险内容,已在下方写明影响、回滚和所需授权 +- [ ] 未提交 `.env`、密码、Token、私钥、真实财务数据、生产日志或构建产物 +- [ ] 前端改动符合 `DESIGN.md`;无前端改动则写“不适用” + +高风险影响、回滚和授权状态: + +## 合并前复查 + +- [ ] 已重新拉取远程引用并用实际修改范围再次运行碰撞检查 +- [ ] PR 不再是 `WIP:`,所有测试结果已经补齐 +- [ ] Codex 已重新读取原始需求和验收标准,并逐文件审核 `origin/main...任务分支` 的完整 diff +- [ ] Codex 自审确认无越界修改、重复实现、敏感内容、未处理冲突和未验证结果 +- [ ] 合并顺序不会覆盖其他未合并任务 +- [ ] 已授权当前任务的 Codex 执行 Squash merge、验证 `main` 并清理任务分支 +- [ ] 如涉及生产迁移、数据修复、删除、覆盖配置或部署,已另行取得对应高风险授权 diff --git a/AGENTS.md b/AGENTS.md index e0ca7b3b..0352c609 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ ## 2. 分支与提交 -- `main` 是稳定主线,默认禁止直接推送、禁止强推、禁止直接修改。项目负责人 `WuYongJie`,以及得到其明确授权的 Codex,可以直接合并 PR 或直接推送 `main`;任何身份仍禁止强推。 +- `main` 是稳定主线,禁止直接在其上开发、禁止强推。团队成员的任务必须先经过独立分支和 PR;任务 Codex 完成独立合并前自审及实际验证后,即被授权执行 Squash merge、推送 `main`、验证合并结果和清理任务分支,不需要等待其他开发者或项目负责人操作。 - 每次开工必须先核对当前目录、远程仓库、Git 身份和当前分支:项目目录必须是 `CaiWuXiTong`,`origin` 必须指向 `WuYongJie/CaiWuXiTong`,分支用户名必须属于当前任务的实际开发者。发现其他项目名、其他项目开发者名或无法解释的旧分支时立即停止,不得继续提交。 - 每项任务都必须从最新 `main` 新建分支;不得长期共用或复用旧任务分支。 - 分支命名:新功能用 `feature/用户名-任务名`,修复用 `fix/用户名-问题名`,紧急修复用 `hotfix/用户名-问题名`。 @@ -30,17 +30,28 @@ - 提交前必须查看 `git status` 和 `git diff`,确认没有把别人的改动、日志、构建产物、`.env` 或敏感文件带进去。 - 禁止使用 `git push --force`、`git reset --hard`、`git clean -fd`,除非负责人明确给出目标和操作步骤。 +### 2.1 多人任务可见性与碰撞检查 + +- 团队按既有分工自行认领任务。每位开发者把已确定的任务交给自己的 Codex 后,Codex 必须先划定预计修改路径,以及路由、Controller、Repository、数据表/视图、上游 `source_key`、`scope`、权限名、菜单键等公共契约;范围不明确时不得开始修改。 +- 开工前由 Codex 执行 `scripts/codex-task-preflight.ps1`,检查最新 `main`、所有远程任务分支和主线既有实现。检查参数必须包含任务名、实际开发者、预计修改路径和精确公共契约关键词;不得用“页面”“接口”“数据库”等宽泛词代替。 +- 预检通过后立即创建并推送独立任务分支,并在 Gitea 创建标题以 `WIP:` 开头的 PR。WIP PR 必须在写大量代码前公开任务目标、模块边界和公共契约,不能等功能完成后才首次推送。 +- 任何 Codex 发现相同页面、路由、Controller、Repository、表/视图、`source_key`、`scope`、权限名、菜单键或公共组件正在另一远程分支开发时,必须停止写入并把重叠项报告给相关开发者;不得自行再造另一套实现,也不得通过覆盖或回退对方代码解决。 +- 远程不可见的本机改动无法被其他 Codex 检查。任务分支和 WIP PR 不及时推送造成的重复开发,不能以“Codex 没发现”为理由继续合并。 +- 并行 Codex/Subagent 优先用于只读探索、测试、日志分析和代码审查;多个代理不得同时写同一批文件。需要并行写入时必须使用互不重叠的 worktree 和文件边界,由主任务代理统一复核。 +- 普通代码重叠和同步方案由相关分支提交人及其 Codex 处理。只有产品方向、重大业务规则或生产高风险操作需要升级确认。 + ## 3. 新人使用 AI 的固定流程 新人本地接入的唯一手工动作是启动 Docker Desktop、在网页中登录,以及输入一次负责人提供的系统数据库密码。拉取代码、构建、初始化、本机引导库迁移、缓存清理、服务启动、页面打开、系统数据库切换后的两套上游验证和数据字典同步均由 Codex 完成;不得把命令、迁移、连接参数、TLS、证书、`APP_KEY` 或上游配置交给新人处理。 -1. 先确认任务目标、验收方式、影响范围和不能触碰的模块。 +1. 先确认任务目标和验收方式,由 Codex 根据现有代码、远程分支和团队既有分工划定影响范围与不能触碰的模块。 2. 进入正确项目和分支,先执行 `git status`;看到陌生改动,先停止并说明情况。 -3. 向 AI 提供:任务目标、当前分支、报错或现象、相关文件、不可修改的范围。 -4. 要求 AI 先说明改动计划、涉及文件、风险和验证步骤;确认后再修改。 -5. 修改后查看 diff,并要求 AI 用普通语言解释“改了什么、为什么、怎样测试”。 -6. 在本地或测试环境完成最小有效验证,记录实际结果。 -7. 推送任务分支并发起 PR;新人不得自行绕过 PR 合并到 `main`。项目负责人和其明确授权的 Codex 按上面的例外执行。 +3. 向 AI 提供:任务目标、当前分支、报错或现象和验收结果;技术范围由 AI 检查后提出,不能反向要求新人猜文件、命令或数据链路。 +4. 要求 AI 运行任务预检,说明改动计划、涉及文件、公共契约、冲突风险和验证步骤;预检阻断时先由相关开发者协调,不能继续修改。 +5. 预检通过后立即推送任务分支并创建 WIP PR,再进入正式开发。 +6. 修改后查看 diff,并要求 AI 用普通语言解释“改了什么、为什么、怎样测试”。 +7. 在本地或测试环境完成最小有效验证,记录实际结果。 +8. 更新 WIP PR 后,由当前任务的 Codex 切换到“合并前自审”阶段,重新从最新 `main` 审核完整 diff、公共契约、冲突、测试和风险;全部通过后安全合并到 `main`、验证主线并清理分支。新人不手工执行合并命令,也不得绕过 PR。 推荐向 AI 提问的模板: @@ -49,23 +60,24 @@ 任务:<要实现的功能或要修复的问题>。 当前分支:<分支名>。 现场:<报错、复现步骤、相关文件>。 -边界:不要修改 <模块/表/配置>;不要执行删除、部署、生产数据库操作。 -要求:先列出改动计划、风险和验证步骤,等我确认后再改。 +验收:<页面、接口或业务结果>。 +要求:先检查最新 main、全部远程任务分支和现有实现,运行 scripts/codex-task-preflight.ps1,自行划定技术边界;有重叠就停止并报告。预检通过后立即推送任务分支并创建 WIP PR,再开始修改。不要执行删除、部署或生产数据库操作。 ``` ## 4. PR 与合并 -- 新人和普通协作者的任务分支必须通过 PR 合并到 `main`;项目负责人和其明确授权的 Codex 可以作为例外直接合并或推送。 +- 所有成员的任务分支必须通过 PR 合并到 `main`。PR 满足检查和验证条件后,任务开发者的 Codex 可以直接执行 Squash merge,不需要项目负责人代为合并;禁止绕过 PR 把开发提交直接推入 `main`。 - PR 作者必须填写:做了什么、Codex 改了哪些文件、怎样测试、是否涉及金额/权限/数据库/配置。 -- 新人提交的 PR 至少由另一位成员检查后才能批准;作者不得把“AI 已完成”当作测试结果。项目负责人和其明确授权的 Codex 不受这条审核人数限制,但仍必须完成实际验证并如实记录结果。 -- 涉及金额计算、权限、导入、数据库迁移、账户或系统配置时,必须在 PR 中明确标注,并由负责人最终确认。 -- 使用仓库默认的 Squash merge(压缩合并)。合并由负责人或指定合并人执行。 -- PR 合并后分支自动删除;不要把已合并分支继续用于新任务。 +- WIP PR 是任务占用和公共契约的远程可见记录。标题使用 `WIP: <任务>`;描述必须使用仓库的 `.gitea/PULL_REQUEST_TEMPLATE.md`,其中预计范围在开工时填写,实际范围和测试结果在 Codex 合并前自审时补齐。 +- PR 不要求另一位开发者审核。任务 Codex 必须在实现结束后另做一次合并前自审:重新读取需求和验收标准,以 `origin/main...任务分支` 的完整 diff 为准逐文件检查,不得直接沿用开发阶段的“已经完成”结论。 +- 涉及金额计算、权限、导入、数据库迁移、账户或系统配置时,必须在 PR 中明确标注影响与回滚;代码仍可在检查和测试通过后由 Codex 合并,但生产迁移、数据修复、删除、覆盖配置和部署继续执行独立高风险授权。 +- 使用仓库默认的 Squash merge(压缩合并)。执行合并的 Codex 必须先重新拉取远程引用,确认工作区干净、PR 不再是 WIP、需求和验收逐项满足、完整 diff 无越界或敏感内容、测试通过、目标为最新 `main` 且没有未协调重叠,再执行合并。 +- 合并后 Codex 必须核对远程 `main` 已包含目标改动,报告合并提交和验证结果,并删除已合并的本地、远程任务分支;下一项任务必须重新从最新 `main` 创建分支。 ## 5. 测试与问题处理 - 修改完成不等于可用。至少验证正常流程、错误输入/无权限场景和本次改动影响的旧功能。 -- 出现冲突、测试失败或 PR 显示落后 `main` 时,不要自行猜命令。先让 AI 只分析同步方案,禁止强推和 rebase;必要时找负责人处理。 +- 出现冲突、测试失败或 PR 显示落后 `main` 时,不要自行猜命令。先让 AI 只分析同步方案,禁止强推和 rebase;普通代码冲突交由相关分支提交人及其 Codex 处理,未解决前不得合并。 - 提交问题时必须写清:环境、账号角色、复现步骤、实际结果、期望结果和报错截图/日志(脱敏后)。 ## 6. 数据与安全红线 diff --git a/docs/team-collaboration.md b/docs/team-collaboration.md index f31c10a1..b44fde42 100644 --- a/docs/team-collaboration.md +++ b/docs/team-collaboration.md @@ -1,92 +1,137 @@ # 新人团队协作 SOP -这份说明适用于 DoDo、sumu、shanshi、wangshu、chenxi、renshen 等使用 Codex 或其他 AI 编程工具参与本项目的成员。 +这份说明适用于 DoDo、sumu、shanshi、wangshu、chenxi、renshen 等使用 Codex 参与本项目的成员。团队按照已经确定的业务分工自行协作;开发者只需要把自己的任务目标、现场和验收结果告诉 Codex,仓库检查、技术范围、分支、命令、测试和 PR 由 Codex 完成。 + +## 一条必须遵守的规则 + +任务不能只留在个人电脑里。Codex 完成开工检查后,必须立即推送任务分支并创建 `WIP:` PR,先让其他开发者和 Codex 看见任务范围,再开始大量写代码。 + +没有推送的本机代码,其他人的 Codex 无法发现。等功能写完才推送,是重复页面、重复接口和重复数据库逻辑的主要来源。 ## 开始一项任务 -先确认自己正在正确的项目、仓库和身份下工作: +开发者把下面这段交给自己的 Codex: + +```text +项目:CaiWuXiTong。 +任务:<已经分工给我的功能或修复>。 +现场:<报错、截图、现有页面或业务说明>。 +验收:<最终页面、接口或业务结果>。 + +请先核对仓库、Git 身份、最新 main 和全部远程任务分支,搜索现有页面、路由、Controller、Repository、表/视图、source_key、scope、权限名、菜单键和共享组件。自行列出预计修改路径与精确公共契约关键词,并运行 scripts/codex-task-preflight.ps1。 + +发现其他远程分支正在处理相同功能或公共契约时立即停止,只报告重叠项,不能继续写代码或另造一套实现。预检通过后,从最新 main 创建本人独立任务分支,立即推送并创建 WIP PR,填写任务范围,再开始修改。 +``` + +Codex 必须先核对: ```bash git rev-parse --show-toplevel git status --short --branch git remote get-url origin git config user.name +git fetch --prune origin ``` 检查结果必须满足: -- 当前项目是 `CaiWuXiTong`。 -- `origin` 指向 `WuYongJie/CaiWuXiTong`。 -- 当前分支没有使用其他项目或其他开发者的名字。 -- 现有分支确实属于本人尚未完成的当前任务;已合并分支不得复用。 +- 当前项目是 `CaiWuXiTong`; +- `origin` 指向 `WuYongJie/CaiWuXiTong`; +- Git 身份和分支用户名属于当前实际开发者; +- 没有无法解释的受控文件改动; +- 当前任务没有复用已合并旧分支; +- 已列出具体预计路径和公共契约,不能只写“财务页面”“数据库接口”等宽泛范围。 -任何一项不符合时先停止,把上述输出发给负责人或 Codex 核对。确认无误后,每次都从最新主线创建新任务分支: +## Codex 开工碰撞检查 + +由 Codex 执行,开发者不手工输入命令: + +```powershell +.\scripts\codex-task-preflight.ps1 ` + -Task '<任务名称>' ` + -Developer '<实际开发者用户名>' ` + -PlannedPath @('resources/views/目标模块', 'modules/EcommerceFinance/Repositories/目标文件.php') ` + -Contract @('具体路由片段', 'Repository类名', 'source.scope', '真实表或视图名') +``` + +脚本会: + +1. 拉取最新远程引用; +2. 检查当前仓库、远程地址、Git 身份和工作区; +3. 检查所有尚未包含于 `main` 的远程任务分支; +4. 比较预计路径是否重叠; +5. 在远程 diff 中检查路由、Repository、表/视图、`scope` 等公共契约; +6. 搜索 `main` 是否已经存在同名功能; +7. 给出“可以开始”或“停止并协调”的明确结果。 + +脚本返回阻断时,Codex 不得修改代码。相关开发者根据报告中的分支、文件和公共契约自行确认保留哪套实现,确认后重新运行预检。 + +## 立即建立远程可见任务 + +预检通过后,Codex 从最新 `main` 创建新分支: ```bash git switch main -git pull origin main +git pull --ff-only origin main git switch -c feature/用户名-任务名 -``` - -示例: - -```bash -git switch -c feature/dodo-login -``` - -不要直接在 `main` 修改,也不要在上一个已完成任务的分支继续做新功能。分支名中的用户名必须是当前任务实际开发者,不能复制其他项目的分支名。 - -## 开发时 - -1. 先把任务和边界告诉 Codex,要求先分析后修改。 -2. 每次修改后检查变更: - -```bash -git status -git diff -``` - -3. 做完最小测试:目标页面/接口能否使用,错误输入是否有合理提示。 -4. 不要提交 `.env`、密码、Token、真实财务数据、日志或构建产物。 - -## 提交与推送 - -```bash -git add <本次任务相关文件> -git commit -m "feat: 简短说明本次功能" git push -u origin feature/用户名-任务名 ``` -提交信息只描述本次任务,例如: +修复使用 `fix/用户名-问题名`,紧急修复使用 `hotfix/用户名-问题名`。一个分支只做一个任务,已合并分支不得复用。 + +随后在 Gitea 立即创建 PR: + +- 标题:`WIP: <任务名称>`; +- 目标分支:`main`; +- 描述:完整填写 `.gitea/PULL_REQUEST_TEMPLATE.md` 中的“开工范围”; +- 尚未完成的测试明确写“待完成”,不能伪造结果。 + +WIP PR 用于公开任务占用,不代表功能已经完成,也不能被合并。 + +## 开发过程 + +1. 页面先复用现有共享组件和业务 Repository; +2. Controller 和页面不得直接连接数据库; +3. 新上游查询必须同时具备 Repository、稳定 `scope`、缓存调用、刷新器映射和测试; +4. 公共路由、权限、侧边栏、数据库配置、共享组件和部署脚本有改动时,必须在 WIP PR 中单独列出; +5. 多个 Codex/Subagent 可以并行做只读排查、测试和审查,但不能同时写同一批文件; +6. 每次准备扩大修改范围时,先更新 WIP PR,再用新增路径和契约重新运行预检; +7. 修改后由 Codex 检查 `git status`、`git diff`,不得提交 `.env`、密码、Token、真实财务数据、日志或构建产物。 + +## 申请合并 + +Codex 在合并前必须进入一次独立自审,不得直接沿用开发阶段的完成结论: + +1. 再次 `git fetch --prune origin`; +2. 使用实际修改路径和公共契约重新运行预检; +3. 对比最新 `main`,确认没有新的重叠任务; +4. 完成目标页面/接口、错误输入和受影响旧功能的最小验证; +5. 更新 PR 中的实际文件、公共契约、测试证据和风险; +6. 重新读取原始需求和验收标准,逐文件审核 `origin/main...任务分支` 的完整 diff; +7. 确认没有越界修改、重复实现、敏感内容、未处理冲突和未验证结果; +8. 自审通过后将标题从 `WIP:` 改成正式标题。 + +开发者把下面这段交给自己的 Codex: ```text -feat: 增加店铺列表筛选 -fix: 修正订单金额汇总 +请进入独立的合并前自审,不要直接沿用开发阶段的完成结论。重新 fetch、读取原始需求和验收标准,并以 origin/main...当前任务分支的完整 diff 为准逐文件审核。检查工作区、WIP 状态、修改边界、公共契约、远程支线重叠、敏感内容、测试证据和目标 main。条件全部满足后,使用 Squash merge 合并到 main,核对远程 main 已包含目标改动,再验证实际页面或接口并删除已合并任务分支。禁止强推;如有冲突、测试失败、自审不通过或涉及尚未授权的生产迁移/数据操作,立即停止并报告。 ``` -不要使用 `update`、`test`、`修改一下` 这类无法理解的提交说明。 +日常 PR 自审、合并、主线验证和分支清理由任务开发者的 Codex 完成,不需要另一位开发者或项目负责人参与。开发者不手工执行 Git 合并命令,也不能绕过 PR。涉及生产迁移、数据修复、删除、覆盖配置或部署时,继续执行仓库现有高风险授权规则。 -## 发起 PR +## PR 落后或发生冲突 -在 Gitea 中发起从任务分支到 `main` 的 PR,并填写: +开发者把下面这段交给 Codex: ```text -做了什么: -Codex 改了哪些文件: -我怎么测试的: -是否涉及金额、权限、数据库或配置: +当前任务分支的 PR 落后 main 或发生冲突。请先 fetch,并只读分析最新 main、当前分支和其他活跃远程分支的差异。禁止 force push 和 rebase。请列出冲突文件、双方业务逻辑、推荐保留方案和测试范围。普通代码冲突由相关分支提交人及其 Codex 处理;没有完成协调前不得修改、推送或合并。 ``` -另一位成员检查后批准。负责人使用 Squash merge 合并;合并后删除本地和远程任务分支,下一项任务重新从最新 `main` 创建新分支。 +不能用删除对方代码、恢复旧版本、复制另一项目实现或扩大公共权限的方式消除冲突。 -## PR 显示“落后 main”或发生冲突 +## 任务完成 -不要使用强推,也不要自己猜 rebase 命令。把下面这段交给 Codex: - -```text -我在 <当前任务分支>,PR 显示已落后 main/发生冲突。 -请先只分析如何安全同步,禁止 force push 和 rebase。 -请给出最小操作步骤,并说明每一步的作用和可能风险。 -``` - -若仍不确定,停止操作并联系负责人。 +- PR 合并后删除本地和远程任务分支; +- 下一项任务重新从最新 `main` 开始; +- WIP PR、远程分支和实际代码是团队 Codex 判断任务占用的依据; +- 产品方向或重大业务规则无法由现有需求判断时,再提交清晰的选项、影响和证据进行确认。 diff --git a/docs/开发与部署SOP.md b/docs/开发与部署SOP.md index 26b37dda..4e659fe0 100644 --- a/docs/开发与部署SOP.md +++ b/docs/开发与部署SOP.md @@ -2,12 +2,24 @@ ## 1. 这套流程解决什么问题 -本项目由 6 位新人使用 Codex 协作开发。项目负责人只确认方向、验收结果和高风险动作,不负责日常命令操作。 +本项目由 6 位新人使用 Codex 协作开发。团队按照已经确定的业务分工自行认领和协调任务;Codex 负责仓库检查、技术范围、分支、实现、测试和 PR,日常命令不能转交给新人。产品方向、验收结果和高风险操作仍按仓库规则确认。 + +开发协作固定链路如下,详细要求见 `docs/team-collaboration.md`: + +```text +团队既有分工 -> Codex 检查 main、远程支线和既有实现 + -> 运行 scripts/codex-task-preflight.ps1 + -> 立即推送独立任务分支并创建 WIP PR + -> 开发、测试、再次碰撞检查 + -> 任务 Codex 独立自审 -> 合并、验证 main 并清理分支 +``` + +任务没有远程分支和 WIP PR 时,其他人的 Codex 无法看见,不得开始大量开发。发现相同页面、路由、Repository、表/视图、上游 `scope`、权限名、菜单键或公共组件时,相关 Codex 必须停止写入并报告重叠项,由相关开发者先完成协调。 固定链路如下: ```text -任务分支开发 -> 本机验证 -> PR -> 合并 main +任务分支开发 -> 本机验证 -> PR -> 任务 Codex 独立自审 -> 合并并验证 main -> 开发机复用缓存构建镜像 -> 经 SSH 隧道只上传变化的镜像层 -> 2GB 服务器只拉取本机 Registry 并切换镜像 diff --git a/scripts/codex-task-preflight.ps1 b/scripts/codex-task-preflight.ps1 new file mode 100644 index 00000000..ae4fcd9c --- /dev/null +++ b/scripts/codex-task-preflight.ps1 @@ -0,0 +1,205 @@ +[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