From 10c5a295a295348abce80ae5379f93cf8ae6235a Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Tue, 22 Sep 2026 21:51:18 +0800 Subject: [PATCH] feat(rdev): nightly four-workflow expansion and proposal title alignment --- .doc/ops/2026-09-21-validation-contract.md | 11 ++-- .../short-term/2026-09-22-rdev-closeout.md | 65 +++++++++++++++++++ .../2026-09-22-rdev-closeout-test-report.md | 52 +++++++++++++++ .github/workflows/nightly-real-pi.yml | 2 +- configs/overlays/rdev_ph01.md | 4 ++ internal/squad/quality_gate.go | 22 +++---- internal/squad/quality_gate_rdev.go | 30 +++++++++ internal/squad/rdev_overlay_test.go | 16 +++++ internal/squad/rdev_stage_meta.go | 4 +- scripts/run-nightly-real-pi.sh | 13 ++-- 10 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 .doc/ops/short-term/2026-09-22-rdev-closeout.md create mode 100644 .doc/test-reports/2026-09-22-rdev-closeout-test-report.md diff --git a/.doc/ops/2026-09-21-validation-contract.md b/.doc/ops/2026-09-21-validation-contract.md index 0fa5eaf9..b0b4ba92 100644 --- a/.doc/ops/2026-09-21-validation-contract.md +++ b/.doc/ops/2026-09-21-validation-contract.md @@ -25,14 +25,17 @@ - 目标环境批准的实际模型、协议及制品;通过 `PI_AGENT_MODEL` 传入批准值并比对 Runner 实际配置。脚本默认值不构成批准,也不证明实际模型。 - 脚本可能构建候选镜像、写 promotion receipt;须满足构建身份/授权条件。`NIGHTLY_SKIP_IMAGE_BUILD=1` 不能补足缺失的制品摘要证据。 -脚本当前筛选 **仅两项**: +脚本当前筛选 **五项**(U8 + 四条工作流治理链): ```text TestMultiIntentU8_RealPI_ConfirmGenerate_PG +TestWorkflowGovernance_PiAgent_PG_FullChain +TestWorkflowGovernance_PiAgent_PG_ProductManager_FullChain +TestWorkflowGovernance_PiAgent_PG_TestManager_FullChain TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain ``` -它不是“全部工作流通过”。按变更范围追加精确名称,例如(前提条件已核验,且已处于相应环境执行租约/独立环境): +它覆盖 main / product_manager / test_manager / FullNone 四条工作流治理链,但**不是**「全部工作流通过」,也不含 rdev P3 Go 顶层(rdev 仍用 Python 三样本 harness)。按变更范围追加精确名称,例如(前提条件已核验,且已处于相应环境执行租约/独立环境): ```bash go test -count=1 -timeout 2h -v ./internal/server/ -run '^TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E$' @@ -43,7 +46,7 @@ go test -count=1 -timeout 90m -v ./internal/server/ -run '^TestWorkflowGovernanc 测试筛选区分大小写;`-run 'PI'` 不能选择名称中的 `PiAgent`。追加用例先读其环境前提;FullLifecycle 中 `RUN_FULL_ADHOC_WBS` 与 `RUN_HEAVY_STAGES` 控制额外段,不能把默认未跑段报为已覆盖。普通 PG 用例同样以源码中的精确名称/前提为准。 -运行前列 `expected_tests`(含必须子场景),运行后逐项对照执行、PASS、FAIL、SKIP、未匹配及重试。退出码 0 或 nightly receipt `pass` 不能替代该对照:基线脚本逐项核对两个预期顶层测试;额外子场景/用例仍须逐项核验。缺项不记 PASS,基础设施问题记录 blocker/infra_fail。保留首轮与重试关系,不隐藏失败。 +运行前列 `expected_tests`(含必须子场景),运行后逐项对照执行、PASS、FAIL、SKIP、未匹配及重试。退出码 0 或 nightly receipt `pass` 不能替代该对照:基线脚本逐项核对五个预期顶层测试;额外子场景/用例仍须逐项核验。缺项不记 PASS,基础设施问题记录 blocker/infra_fail。保留首轮与重试关系,不隐藏失败。 ## 模型与环境证据 @@ -59,4 +62,4 @@ go test -count=1 -timeout 90m -v ./internal/server/ -run '^TestWorkflowGovernanc ## 工程工具验收补充 -本地协调与运维守卫使用 `python3 -B -m unittest discover -s scripts/tests -p 'test_*.py' -v`,在临时真实 Git/SQLite/进程环境执行;Docker 守卫测试使用受控目标记录调用,不重建共享服务。nightly 入口现在核对最终尝试日志中两个预期顶层测试均恰好执行且 PASS;零用例、部分 SKIP 或重复拼接证据均不能生成 PASS 收据。该日志核验不是本轮执行了产品 PI 的声明。 +本地协调与运维守卫使用 `python3 -B -m unittest discover -s scripts/tests -p 'test_*.py' -v`,在临时真实 Git/SQLite/进程环境执行;Docker 守卫测试使用受控目标记录调用,不重建共享服务。nightly 入口现在核对最终尝试日志中五个预期顶层测试均恰好执行且 PASS;零用例、部分 SKIP 或重复拼接证据均不能生成 PASS 收据。该日志核验不是本轮执行了产品 PI 的声明。 diff --git a/.doc/ops/short-term/2026-09-22-rdev-closeout.md b/.doc/ops/short-term/2026-09-22-rdev-closeout.md new file mode 100644 index 00000000..050377b6 --- /dev/null +++ b/.doc/ops/short-term/2026-09-22-rdev-closeout.md @@ -0,0 +1,65 @@ +--- +title: "W4 rdev 收口" +task_id: rdev-closeout-2026-09-22 +status: done +created: 2026-09-22 +updated: 2026-09-22 +owner: Cursor +task_type: implementation +task_tier: small +policy_revision: "AGENTS+ops@364ed7f1" +branch: feature/rdev-closeout-2026-09-22 +worktree: .worktrees/rdev-closeout-2026-09-22 +base_dev_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87" +write_scope: [".doc", "configs/overlays", "internal/squad", "scripts", ".github/workflows"] +allowed_environments: [] +head_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87" +mutex: rdev-closeout +review_state: pending +integration_state: pending +deployment: not_required +cleanup: pending +cleanup_owner: 独立维护者 +cleanup_trigger: tip 为 origin/dev 祖先且无进程后复查 +remote_sync: not_requested +--- + +# W4 rdev 收口 + +## 用户目标与授权 + +- 用户目标:rd_proposal 标题对齐模板;nightly 扩成四工作流;P3 三样本在当前 HEAD 重跑并复验法规表误伤修复;short-term + commit。 +- 交付终点:实现与证据就绪(本地提交);不合入、不 push。 +- 禁止项:不 push;不动共享 KB / 项目 143;不启用红线 flag;无 `env:dev` 能力时不写共享服务。 + +## 范围与非目标 + +- 允许:质量门禁/overlay/hint、nightly 脚本与契约文案、dry fixture、本任务 short-term/报告。 +- 非目标:从本 worktree 对共享 dev 跑长 E2E;rdev 专用 Go PG 顶层测试;升 FEATURE-MATRIX / gap-matrix。 + +## 步骤与验收 + +1. [x] `rd_proposal` 精确标题门禁 + ph01 overlay/hint — `rdevProposalHeadingIssues`;`TestRDevQualityGates` dry fixture。 +2. [x] nightly 扩成四工作流治理链(FullChain ×3 + FullNone)并保留 U8 — `scripts/run-nightly-real-pi.sh` + workflow timeout + 验证契约。 +3. [x] 法规表误伤 — 既有 `markdownTable` 首表截止 + P3 终稿 dry 复验仍无「问题行缺少」。 +4. [x] P3 真 PI 重跑 — **blocked**(见下);不以 stub 冒充 PASS。 + +## 验证与证据 + +| 范围 / 被测 SHA | 命令与环境 | 预期 | 实际 / 判定 | 证据 | +|---|---|---|---|---| +| 标题门禁 + 法规表 dry | `go test ./internal/squad/ -run 'TestRDevQualityGates\|TestRDevOverlay'` | PASS | PASS | 本机 2026-09-22 | +| P3 三样本真 PI | `run_p3_real_pi.py` + 共享/独立 PI | 需 env:dev 租约 + 非 143 项目 | **blocked** | 任务无 `env:dev`;无 `TEST_DATABASE_URL`;规范禁止从 task worktree 写共享服务 | +| nightly 四工作流 | 脚本 `--expected` 五项 | 脚本已扩 | 未在本机实跑四链(耗时/租约) | 脚本与契约 diff | + +## 收口与交接 + +- 剩余:获 `env:dev`(或独立固定环境)后在新 HEAD 重跑 P3;可选补 rdev Go PG 顶层进 nightly。 +- 本地提交:本分支 tip;dev 集成 / 远端:未请求。 +- 红线未动。 + +## 相关链接 + +- [报告](../../test-reports/2026-09-22-rdev-closeout-test-report.md) +- [P3 历史报告](../../test-reports/2026-09-19-rdev-p3-real-pi-test-report.md) +- [验证契约](../2026-09-21-validation-contract.md) diff --git a/.doc/test-reports/2026-09-22-rdev-closeout-test-report.md b/.doc/test-reports/2026-09-22-rdev-closeout-test-report.md new file mode 100644 index 00000000..d6ae2d94 --- /dev/null +++ b/.doc/test-reports/2026-09-22-rdev-closeout-test-report.md @@ -0,0 +1,52 @@ +# W4 rdev 收口测试报告 + +## 一、背景 + +P3(2026-09-19)在项目 6533 真 PI 上跑过三样本:`rd_proposal` 生成完成但章节标题未对齐模板;`rd_planning_review` 因同节法规对照表被算进「问题行」而 quality_failed(score 20)。同日已修 `markdownTable` 首表截止,但标题对齐与 nightly 四工作流挂钩仍开放。 + +## 二、目标 + +1. 建议书标题必须对齐 `templates/docs/rd_proposal.md` 四个专业 `##`。 +2. nightly 从「U8 + FullNone」扩成「U8 + 四条工作流治理链」。 +3. 用 dry fixture 复验法规表误伤修复;真 PI 三样本仅在授权环境可跑时重跑。 + +## 三、环境 + +- OS:darwin;worktree `feature/rdev-closeout-2026-09-22`;基线 `364ed7f1`。 +- 共享 dev health:schema 61、`kb_ready`、Runner 可达;**本任务 capabilities 无 `env:dev`**,且无 `TEST_DATABASE_URL`,故不写共享服务、不重跑真 PI。 + +## 四、结果 + +### 4.1 标题对齐门禁 + +- **输入**:`rdevProposalHeadingIssues`;P3 终稿 dry;对齐样例;「风险清单」负例。 +- **实际**:P3 终稿检出缺失「项目背景与立项依据」「目标、里程碑与资源假设」「主要风险与对策概览」等;对齐样例 0 issue;改名为「风险清单」失败。 +- **判定**:PASS(门禁+fixture)。真 PI 重跑 **blocked**,不记生成 PASS。 +- **附**:`configs/overlays/rdev_ph01.md` 与 `rdev_stage_meta` hint 写明禁止自拟标题。 + +### 4.2 法规表误伤 + +- **输入**:既有 later-table 用例 + `rd_planning_review-final.md`。 +- **实际**:无「问题行缺少」误报。 +- **判定**:PASS(dry 复验)。 + +### 4.3 nightly 四工作流 + +- **输入**:`scripts/run-nightly-real-pi.sh`、`nightly-real-pi.yml`、验证契约。 +- **实际**:`RUN_FILTER` 含 FullChain / ProductManager_FullChain / TestManager_FullChain / FullNoneMixedChain + U8;suite 默认超时 180m;Actions job 240m;契约列出五项 expected。 +- **判定**:脚本与契约 PASS。本机未实跑四链(租约/时长);rdev Python P3 **未**挂进 nightly(无 Go 顶层脚本)。 + +### 4.4 P3 真 PI 重跑 + +- **判定**:blocked。原因:任务无 `env:dev`;操作规范禁止从 task worktree 写共享服务;`.env` 无 `TEST_DATABASE_URL`;用户禁止动项目 143 / 共享 KB。接管条件:独立固定 PG + env 租约后执行 `run_p3_real_pi.py`。 + +## 五、套件 + +```text +go test -count=1 ./internal/squad/ -run 'TestRDevQualityGates|TestRDevOverlay' +# ok +``` + +## 六、结论 + +门禁、overlay、nightly 四工作流脚本与 dry 复验已就绪。真 PI 三样本与 nightly 实跑收据待授权环境。红线未动。 diff --git a/.github/workflows/nightly-real-pi.yml b/.github/workflows/nightly-real-pi.yml index 82a1112a..0210e8e7 100644 --- a/.github/workflows/nightly-real-pi.yml +++ b/.github/workflows/nightly-real-pi.yml @@ -17,7 +17,7 @@ jobs: # Do not use ubuntu-latest docker isolation for this job on this machine. nightly-real-pi: runs-on: nightly - timeout-minutes: 120 + timeout-minutes: 240 permissions: contents: read env: diff --git a/configs/overlays/rdev_ph01.md b/configs/overlays/rdev_ph01.md index bdd71d23..1636e281 100644 --- a/configs/overlays/rdev_ph01.md +++ b/configs/overlays/rdev_ph01.md @@ -5,7 +5,11 @@ ### role:writer 写满策划类章节;结论与范围表一致。 +项目建议书(rd_proposal)的 `##` 标题必须原样使用模板: +「项目背景与立项依据」「产品定义与范围」「目标、里程碑与资源假设」「主要风险与对策概览」。 +不得改成「风险清单」「产品定义」等自拟标题,也不得加「工程师视角」等后缀。 ### role:leader 查范围是否越权到未批准内容;结论是否可追溯到前序 bizdev 批准件。 +核对建议书章节标题是否与 template.md 完全一致;标题不对齐则判门禁失败并要求修复。 diff --git a/internal/squad/quality_gate.go b/internal/squad/quality_gate.go index 2f3b8fa4..0a42c081 100644 --- a/internal/squad/quality_gate.go +++ b/internal/squad/quality_gate.go @@ -54,25 +54,23 @@ func (q *QualityGate) Validate(wsDir string, taskKey string, content string) (*Q CheckedAt: now, } - // 1. 检查标题完整性 (对比 template.md 的 ## 章节) + // 1. 检查标题完整性 (对比 template.md 的 ## 章节;清洗后须精确相等,禁止自拟近义标题) tmplPath := filepath.Join(wsDir, "template.md") tmplData, err := os.ReadFile(tmplPath) if err == nil { expectedHeadings := extractHeadings(string(tmplData)) actualHeadings := extractHeadings(content) + actualSet := make(map[string]struct{}, len(actualHeadings)) + for _, act := range actualHeadings { + actualSet[act] = struct{}{} + } for _, h := range expectedHeadings { - found := false - for _, act := range actualHeadings { - if strings.Contains(act, h) || strings.Contains(h, act) { - found = true - break - } - } - if !found && !strings.Contains(content, h) { - report.MissingSections = append(report.MissingSections, h) - report.Violations = append(report.Violations, fmt.Sprintf("缺少必填章节: %s", h)) - report.Score -= 15.0 + if _, ok := actualSet[h]; ok { + continue } + report.MissingSections = append(report.MissingSections, h) + report.Violations = append(report.Violations, fmt.Sprintf("缺少必填章节: %s", h)) + report.Score -= 15.0 } if len(report.MissingSections) > 0 { report.TitleCheck = false diff --git a/internal/squad/quality_gate_rdev.go b/internal/squad/quality_gate_rdev.go index 5637c9b8..cbf954a3 100644 --- a/internal/squad/quality_gate_rdev.go +++ b/internal/squad/quality_gate_rdev.go @@ -12,12 +12,23 @@ import ( var rdevReqIDPattern = regexp.MustCompile(`\b(UR|RS)-(\d+)\b`) +// rdProposalRequiredHeadings are the professional ## titles from templates/docs/rd_proposal.md. +// Exact cleaned match is required; fuzzy Contains is not enough for title alignment. +var rdProposalRequiredHeadings = []string{ + "项目背景与立项依据", + "产品定义与范围", + "目标、里程碑与资源假设", + "主要风险与对策概览", +} + func rdevDeterministicViolations(wsDir, taskKey, content string) []string { meta, ok := LookupRDevTask(taskKey) if !ok { return nil } switch taskKey { + case "rd_proposal": + return rdevProposalHeadingIssues(content) case "rd_user_needs": return rdevRequirementIssues(wsDir, content, "UR") case "rd_system_reqs": @@ -33,6 +44,25 @@ func rdevDeterministicViolations(wsDir, taskKey, content string) []string { } } +func rdevProposalHeadingIssues(content string) []string { + // Require literal "## 标题" lines (no numbering / engineer-suffix variants). + var issues []string + for _, want := range rdProposalRequiredHeadings { + exact := "## " + want + found := false + for _, line := range strings.Split(content, "\n") { + if strings.TrimSpace(line) == exact { + found = true + break + } + } + if !found { + issues = append(issues, "建议书章节标题未对齐模板: "+want) + } + } + return issues +} + func rdevRequirementIssues(wsDir, content, prefix string) []string { var issues []string if strings.Contains(content, "随机抽取") { diff --git a/internal/squad/rdev_overlay_test.go b/internal/squad/rdev_overlay_test.go index 0db54380..45cb8009 100644 --- a/internal/squad/rdev_overlay_test.go +++ b/internal/squad/rdev_overlay_test.go @@ -133,6 +133,22 @@ func TestRDevQualityGates(t *testing.T) { } } + proposalSample, err := os.ReadFile("../../.doc/test-reports/assets/2026-09-19-rdev-p3-real-pi/rd_proposal-final.md") + if err != nil { + t.Fatal(err) + } + if issues := rdevProposalHeadingIssues(string(proposalSample)); len(issues) == 0 { + t.Fatal("P3 rd_proposal dry fixture must fail title alignment (self-authored headings)") + } + aligned := "## 结论\n\nok\n\n## 适用性\n\nok\n\n## 项目背景与立项依据\n\nok\n\n## 产品定义与范围\n\nok\n\n## 目标、里程碑与资源假设\n\nok\n\n## 主要风险与对策概览\n\nok\n\n## 追溯\n\n| RD- | |\n\n## 已有材料摘要\n\nok\n\n## 待确认清单\n\n- 无\n\n## 签署\n\nok\n\n## 说明\n\nok\n" + if issues := rdevProposalHeadingIssues(aligned); len(issues) != 0 { + t.Fatalf("template-aligned proposal must pass heading gate: %v", issues) + } + fuzzy := strings.ReplaceAll(aligned, "## 主要风险与对策概览", "## 风险清单") + if got := rdevProposalHeadingIssues(fuzzy); len(got) == 0 { + t.Fatal("renamed risk heading must fail exact title alignment") + } + thin := "已收照片 1 张。" rep, err = gate.Validate(ws, "rd_prototype_photos", thin) if err != nil { diff --git a/internal/squad/rdev_stage_meta.go b/internal/squad/rdev_stage_meta.go index 9579a04b..0d30b18a 100644 --- a/internal/squad/rdev_stage_meta.go +++ b/internal/squad/rdev_stage_meta.go @@ -18,7 +18,9 @@ type rdevOverrideStep struct { var rdevStageHints = map[string]rdevStageHint{ "rd_proposal": { - Hint: "按模板编制《项目建议书》(策划)。写满章节并给出本阶段结论与追溯;缺项写「待确认」,不做疗效宣称。", + Hint: "按模板编制《项目建议书》(策划)。## 标题必须与 template.md 完全一致:" + + "「项目背景与立项依据」「产品定义与范围」「目标、里程碑与资源假设」「主要风险与对策概览」;" + + "禁止改写为「风险清单」等自拟标题。写满章节并给出本阶段结论与追溯;缺项写「待确认」,不做疗效宣称。", Aliases: []string{"项目建议书", "立项建议"}, }, "rd_design_plan": { diff --git a/scripts/run-nightly-real-pi.sh b/scripts/run-nightly-real-pi.sh index 011ffe86..64e1476f 100755 --- a/scripts/run-nightly-real-pi.sh +++ b/scripts/run-nightly-real-pi.sh @@ -26,9 +26,9 @@ RUNNER_URL="${PI_AGENT_RUNNER_URL:-http://127.0.0.1:18865}" TOKEN="${PI_AGENT_RUNNER_AUTH_TOKEN:-${RUNNER_AUTH_TOKEN:-}}" MODEL_ID="${PI_AGENT_MODEL:-MiniMax-M2.7-highspeed}" PROTOCOL="${PI_AGENT_PROTOCOL:-typed-result-v1}" -TIMEOUT="${NIGHTLY_SUITE_TIMEOUT:-90m}" +TIMEOUT="${NIGHTLY_SUITE_TIMEOUT:-180m}" COMMIT="$(git rev-parse HEAD)" -SUITE="nightly-real-pi-u8-fullnone" +SUITE="nightly-real-pi-u8-four-workflows" STAMP="$(date -u +%Y%m%dT%H%M%SZ)" EVIDENCE_DIR="${NIGHTLY_EVIDENCE_DIR:-${ROOT}/.doc/test-reports/assets/promotion-receipts/evidence-${COMMIT:0:12}-${STAMP}}" mkdir -p "${EVIDENCE_DIR}" @@ -172,7 +172,9 @@ preflight_shared_kb # Clear env pollution that flakes short/config tests elsewhere. unset AGENT_TIMEOUT KB_DATABASE_URL KB_EMBEDDING_DIM 2>/dev/null || true -RUN_FILTER='TestMultiIntentU8_RealPI_ConfirmGenerate_PG|TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain' +# Four workflow governance chains (main / product_manager / test_manager / FullNone) +# plus U8 confirm-generate. Rdev P3 remains the Python harness (not yet a Go PG top-level). +RUN_FILTER='TestMultiIntentU8_RealPI_ConfirmGenerate_PG|TestWorkflowGovernance_PiAgent_PG_(FullChain|ProductManager_FullChain|TestManager_FullChain|FullNoneMixedChain)$' LOG_FILE="${EVIDENCE_DIR}/go-test.txt" set +e go test -count=1 -timeout "${TIMEOUT}" -v ./internal/server/ -run "${RUN_FILTER}" 2>&1 | tee "${LOG_FILE}" @@ -190,9 +192,12 @@ fi set -e if [[ "${TEST_RC}" -eq 0 ]]; then - # Exit zero alone also covers no tests and partial skips; require both named tests. + # Exit zero alone also covers no tests and partial skips; require all named top-level tests. if ! python3 "${ROOT}/scripts/verify_go_test_evidence.py" --log "${RESULT_LOG}" \ --expected TestMultiIntentU8_RealPI_ConfirmGenerate_PG \ + --expected TestWorkflowGovernance_PiAgent_PG_FullChain \ + --expected TestWorkflowGovernance_PiAgent_PG_ProductManager_FullChain \ + --expected TestWorkflowGovernance_PiAgent_PG_TestManager_FullChain \ --expected TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain \ > "${EVIDENCE_DIR}/expected-tests.json"; then write_receipt infra_fail infra "${LOG_FILE}" "${EVIDENCE_DIR}/expected-tests.json"