From 6b445a13e14b413b4cd9573106840badece9e3f4 Mon Sep 17 00:00:00 2001 From: shunli Date: Wed, 13 May 2026 16:57:58 +0800 Subject: [PATCH] =?UTF-8?q?GOV8=20=E5=BB=BA=E7=AB=8B=E8=BF=9E=E7=BB=AD?= =?UTF-8?q?=E5=9E=8B=20Issue=20=E6=B2=BB=E7=90=86=E5=9F=BA=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/开发交付与变更管理规约.md | 105 +++++++ docs/文档事实源状态矩阵.md | 55 ++++ docs/文档索引.md | 33 +++ ...-05-13_16-31-09-gov8-system-governance.csv | 21 ++ issues/README.md | 23 +- scripts/qa/audit-docs.mjs | 259 ++++++++++++++++++ 6 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 issues/2026-05-13_16-31-09-gov8-system-governance.csv create mode 100644 scripts/qa/audit-docs.mjs diff --git a/docs/开发交付与变更管理规约.md b/docs/开发交付与变更管理规约.md index 4eb30a2a..e76335c3 100644 --- a/docs/开发交付与变更管理规约.md +++ b/docs/开发交付与变更管理规约.md @@ -1,7 +1,11 @@ # 开发交付与变更管理规约 **文档状态**:当前开发交付事实源。 +**分类**:开发治理。 **适用范围**:hyb-oa 所有非微小代码、文档、测试、脚本和部署准备变更。 +**关联 Issue**:`GOV8`。 +**最近审计**:2026-05-13。 +**不得误读**:本文定义开发和变更流程,不替代具体业务 PRD、模块规约或测试规约。 **路线口径**:开发必须围绕最新 Issue CSV 执行;hyb-oa 是唯一业务主系统,企业微信只做基础设施,飞书路线已废弃。 本文回答“一个变更怎么开始、怎么审查、什么时候提交、怎么交付”。测试细节以 [测试规约与回归门禁](./测试规约与回归门禁.md) 为准。 @@ -32,6 +36,48 @@ 如果回归失败,不能把 `Regression_Status` 标为 `DONE`。如果发现范围外缺口,记录到 Notes 或新 Issue。 +### 2.1 连续型 Issue 策略 + +后续默认使用“少量长期连续合同 + 多行任务 + 阶段收口”,不再为每个小反馈、小链接修正或单点讨论新建 CSV。 + +| 治理流 | 覆盖范围 | 进入条件 | +| --- | --- | --- | +| `GOV8` | 文档、流程、审计、Issue 管理 | 材料散、权威不清、流程模板和审计问题 | +| `R7E` | 真实扩大试运行 | 用户确认参与人、窗口、回滚和每日收口后 | +| `ORG8` | 组织账号、角色、权限、企业微信绑定 | 组织主数据或权限边界问题 | +| `WF8` | 审批、通知、催办、转审 | 审批通知能力增强,但不涉及复杂 BPM | +| `BIZ8` | OCR、票据、财务、费用、项目轻量增强 | 业务主链路增强,但不涉及 ERP 或重预算 | +| `QAOPS8` | 测试、发布、运维、上线准备 | 回归、门禁、备份、SLO、发布问题 | + +连续型 Issue 规则: + +- 同类型、同目标、同风险域的问题进入同一个连续 CSV。 +- 只有跨业务域、跨风险等级、跨里程碑,或当前治理流阶段性收口后,才新建下一版 CSV。 +- 一个连续 CSV 内用领域前缀表达子类,例如 `GOV8-DOC-*`、`ORG8-PERM-*`、`WF8-NOTIFY-*`。 +- 旧 Issue CSV 保留为历史审计证据,不删除、不批量回写、不移动到归档目录。 +- 如果新治理流替代旧合同,只在新 CSV 的 `Notes` 或文档索引中说明 `supersedes`。 +- 当前可执行合同只在 `docs/文档索引.md` 和 `docs/文档事实源状态矩阵.md` 中暴露,避免主入口被历史 Issue 淹没。 + +### 2.2 需求冻结和变更请求 + +非微小需求必须先进入需求事实源和当前同类型连续 CSV。需求冻结后,新增想法默认进入后续池;只有同时满足以下条件,才允许并入当前轮: + +- 不扩大当前治理流范围。 +- 不改变已批准验收口径。 +- 不引入新的权限、状态机、数据迁移或生产风险。 +- 不增加未覆盖测试面。 +- 不导致当前 `Test_Method` 失效。 + +变更请求至少写清: + +| 字段 | 说明 | +| --- | --- | +| 变更原因 | 为什么现在提出 | +| 影响范围 | 影响哪些角色、流程、接口、页面、数据或文档 | +| 处理方式 | 替代原需求、追加当前行、进入同治理流后续行或进入下一阶段 | +| 测试影响 | 原测试是否仍有效,是否新增权限/视觉/公网/真机验证 | +| 回滚路径 | 如何撤销或延后 | + ## 3. 开发边界 - 每次只做当前 Issue 范围内的事,不顺手重构无关模块。 @@ -40,6 +86,65 @@ - 不在当前 MVP 首批范围内扩展预算、ERP、AI、飞书、企业微信原生审批、复杂 BPM 或重型项目管理。 - 未经确认不得重置数据库、编辑迁移历史、修改生产配置、暴露真实密钥或处理真实敏感数据。 +## 3.1 方案和计划模板 + +后续非微小变更在实施前必须至少给出以下轻量模板内容。模板可以写在回复、Issue CSV `Notes` 或对应规约中,但必须可审计。 + +### 需求整理模板 + +| 字段 | 要求 | +| --- | --- | +| 背景 | 真实反馈、试运行问题、业务制度或技术风险 | +| 目标角色 | 员工、审批人、财务、管理员、老板/负责人等 | +| 主流程 | 从发起到验收的业务路径 | +| 必须做 | 当前轮必须完成的行为 | +| 暂不做 | 明确冻结或后续池能力 | +| 验收口径 | 用户或自动化如何判断完成 | + +### 设计方案模板 + +| 字段 | 要求 | +| --- | --- | +| 数据/API | 新增或变更的数据、接口、字段和兼容性 | +| 权限 | 服务端授权、角色边界、敏感字段和审计 | +| 状态流 | 状态转换、非法跳转、并发和重复提交 | +| 页面 | PC、移动、企业微信入口和视觉规则 | +| 异常 | 缺配置、失败、重试、SKIP 和用户提示 | +| 回滚 | 代码、配置、数据、迁移和发布回滚路径 | +| 测试影响 | 需要新增或复跑的验证 | + +### 开发计划模板 + +| 字段 | 要求 | +| --- | --- | +| 治理流 | GOV8/R7E/ORG8/WF8/BIZ8/QAOPS8 | +| Issue 行 | 对应 CSV 行 ID | +| 文件范围 | `docs/`、`server/`、`client/`、`scripts/qa/` 等 | +| 依赖 | 前置 Issue 行和外部条件 | +| 交接物 | 代码、文档、脚本、API 契约、测试证据 | + +### 测试方案模板 + +| 字段 | 要求 | +| --- | --- | +| 主流程 | 至少覆盖一个业务流程级验证 | +| 异常流程 | 权限不足、缺配置、失败、重复提交、撤回/驳回等 | +| 权限 | 普通员工、审批人、财务、管理员边界 | +| 视觉 | PC、移动、企业微信 UA 和关键视口 | +| 公网/真机 | `/ready`、public bundle、live readiness、真实 OCR、真机反馈 | +| SKIP 条件 | 只允许真实外部条件缺失时 SKIP,且必须记录补测条件 | + +### 测试计划模板 + +| 字段 | 要求 | +| --- | --- | +| 执行时机 | 开发前、后端后、前端后、review 前、回填前、commit 前 | +| 命令 | 可复现命令或人工步骤 | +| 环境 | 本地、公网匿名、带凭据、企业微信真机 | +| 证据路径 | artifacts、output、报告、Issue CSV Notes | +| 负责人 | backend/frontend/qa/ops 或人工验收角色 | +| 阻断条件 | 哪些失败必须停止推进 | + ## 4. Review 规则 Review 不只看代码格式,优先检查行为风险: diff --git a/docs/文档事实源状态矩阵.md b/docs/文档事实源状态矩阵.md index 254e9666..66426be6 100644 --- a/docs/文档事实源状态矩阵.md +++ b/docs/文档事实源状态矩阵.md @@ -1,13 +1,68 @@ # 文档事实源状态矩阵 **文档状态**:当前文档治理事实源。 +**分类**:文档台账。 **适用时间**:DCL7 之后。 +**关联 Issue**:`GOV8`。 +**最近审计**:2026-05-13。 +**不得误读**:本文登记文档权威性和状态,不替代具体 PRD、模块规约、测试规约或 Issue CSV。 **治理口径**:中文优先,物理精简。历史资料不建 `docs/archive/`,已确认不用的文档从当前树移除,通过 git 历史恢复。 ## 冲突优先级 最新 Issue CSV > 当前 PRD/模块规约 > 产品和设计规则 > 当前操作手册 > 技术参考 > 历史证据 > git 历史。 +## 台账状态值 + +| Status | 含义 | 是否可直接指导开发 | +| --- | --- | --- | +| `CURRENT` | 当前事实源或当前执行合同 | 可以 | +| `DRAFT` | 草案或准备材料,未完成启动确认 | 不可以 | +| `EVIDENCE` | 试运行、测试、审计或验收证据 | 不可以 | +| `HISTORICAL` | 历史快照或旧合同 | 不可以 | +| `SUPERSEDED` | 已被新文档或新合同替代 | 不可以 | +| `REMOVED` | 已从当前树物理移除,只能从 git 历史恢复 | 不可以 | + +## 当前活跃治理流 + +| Stream | Contract | Status | 用途 | +| --- | --- | --- | --- | +| `GOV8` | `issues/2026-05-13_16-31-09-gov8-system-governance.csv` | `CURRENT` | 连续型 Issue 治理、文档台账、流程模板和审计脚本 | +| `R7E` | 待新建连续合同 | `DRAFT` | 真实扩大试运行人工确认和执行 | +| `ORG8` | 待新建连续合同 | `DRAFT` | 组织账号和权限主数据治理 | +| `WF8` | 待新建连续合同 | `DRAFT` | 审批与通知增强 | +| `BIZ8` | 待新建连续合同 | `DRAFT` | 报销/OCR/财务/项目轻量增强 | +| `QAOPS8` | 待新建连续合同 | `DRAFT` | 测试、发布、运维和上线准备 | + +旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。 + +## 文档台账 + +| Doc_ID | Path | Category | Authority | Status | Owner | Related_Issues | Evidence | Supersedes | Last_Audited | Audit_Method | Risk | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `IDX` | `docs/文档索引.md` | 入口索引 | 最高文档入口 | `CURRENT` | product/dev | `GOV8` | `rg` | none | 2026-05-13 | `node scripts/qa/audit-docs.mjs --check-registry` | medium | 判断当前该读什么 | +| `REG` | `docs/文档事实源状态矩阵.md` | 文档台账 | 文档分类和状态事实源 | `CURRENT` | product/dev | `GOV8` | `audit-docs` | none | 2026-05-13 | `audit-docs --check-registry` | medium | 判断文档权威性 | +| `PRD-MVP` | `docs/海音达OA-MVP需求规格说明书.md` | 需求事实源 | PRD 主事实源 | `CURRENT` | product | `REQ8,P2T,GOV8` | Issue CSV | `docs/requirements-specification.md` | 2026-05-13 | `rg` | high | 定义做什么、不做什么和验收口径 | +| `PRODUCT-SCOPE` | `docs/产品口径.md` | 需求事实源 | 产品边界事实源 | `CURRENT` | product | `REQ8,P2T` | `rg` | none | 2026-05-13 | `rg` | high | 记录已拍板产品边界 | +| `DESIGN-UI` | `docs/产品设计规则.md` | 设计事实源 | PC/移动/企业微信设计事实源 | `CURRENT` | design/frontend | `MIA7,MIF7,GOV8` | Playwright/视觉审查 | none | 2026-05-13 | `rg` | high | 页面改动必须读取 | +| `DELIVERY` | `docs/开发交付与变更管理规约.md` | 开发治理 | 开发交付事实源 | `CURRENT` | dev/qa | `GOV8` | `rg` | none | 2026-05-13 | `rg` | high | Issue、review、commit 和交付规则 | +| `QA-GATE` | `docs/测试规约与回归门禁.md` | 测试验收 | 测试和回归门禁事实源 | `CURRENT` | qa | `QAF8,GOV8` | 回归脚本/Playwright | none | 2026-05-13 | `rg` | high | 测试计划和 DONE 门禁 | +| `AGENT-PRACTICE` | `docs/Agent开发交付实践.md` | Agent 治理 | Agent 协作事实源 | `CURRENT` | dev | `GOV8` | Skill 校验 | none | 2026-05-13 | `rg` | medium | Agent 协作开发实践 | +| `AGENTS` | `AGENTS.md` | 执行硬约束 | 项目硬规则 | `CURRENT` | lead | `GOV8` | 人工核对 | none | 2026-05-13 | `rg` | high | 不可违反约束 | +| `SKILL-HYB` | `.agents/skills/hyb-oa-delivery/SKILL.md` | Skill | 项目交付 Skill | `CURRENT` | dev | `GOV8` | quick_validate | none | 2026-05-13 | `quick_validate.py` | medium | 每轮交付操作入口 | +| `REQ-WF` | `docs/需求-统一审批中心规约.md` | 需求事实源 | 审批模块规约 | `CURRENT` | product/backend | `AP6,WF8` | workflow 回归 | none | 2026-05-13 | `rg` | high | 审批、催办、通知和状态流 | +| `REQ-OCR` | `docs/需求-报销发票OCR规约.md` | 需求事实源 | OCR 模块规约 | `CURRENT` | product/backend | `RI6,OCR8,BIZ8` | OCR 回归 | none | 2026-05-13 | `rg` | high | OCR 和发票边界 | +| `RULE-REIMB` | `docs/报销发票附件规则.md` | 需求事实源 | 报销发票附件规则 | `CURRENT` | product/finance | `RI6,BIZ8` | 报销回归 | none | 2026-05-13 | `rg` | high | 报销、发票、附件细则 | +| `REQ-WECOM` | `docs/需求-企业微信基础设施规约.md` | 需求事实源 | 企业微信边界规约 | `CURRENT` | product/backend | `R7E,ORG8,WF8` | live smoke/真机反馈 | none | 2026-05-13 | `rg` | high | 企业微信只做入口、身份和通知 | +| `RUNBOOK-TRIAL` | `docs/试运行与自动化验证手册.md` | 操作手册 | 试运行验证手册 | `CURRENT` | qa/ops | `R7E,QAOPS8` | live readiness | none | 2026-05-13 | `rg` | medium | 本地、公网、企业微信和真实 OCR 验证 | +| `MANUAL-ADMIN` | `docs/管理员手册.md` | 操作手册 | 管理员手册 | `CURRENT` | ops/admin | `ORG8,R7E` | 人工验收 | none | 2026-05-13 | `rg` | medium | 管理员上线和配置 | +| `MANUAL-EMPLOYEE` | `docs/员工快速上手.md` | 操作手册 | 员工手册 | `CURRENT` | product | `R7E` | 人工验收 | none | 2026-05-13 | `rg` | low | 员工侧使用说明 | +| `R7-DRAFT` | `docs/trialrun/R7扩大试运行草案.md` | 草案 | R7 准备材料 | `DRAFT` | product/qa | `R7E` | R7EP 报告 | none | 2026-05-13 | `rg` | medium | 不代表真实 R7 已启动 | +| `OPS-RUNBOOK` | `docs/trialrun/OPS7运维交接Runbook.md` | 运维草案 | OPS 准备材料 | `DRAFT` | ops | `OPS8,QAOPS8` | 只读检查 | none | 2026-05-13 | `rg` | medium | 不代表恢复演练已执行 | +| `TRIAL-REPORT` | `docs/trialrun/人工试运行报告.md` | 证据材料 | 试运行证据入口 | `EVIDENCE` | qa | `R7EP,OPS8,QAF8` | artifacts/qa-flow | none | 2026-05-13 | `rg` | medium | 只证明发生过,不直接派工 | +| `HISTORY-MATRIX` | `docs/历史功能状态矩阵.md` | 历史材料 | 历史功能索引 | `HISTORICAL` | product | `DCL7` | git 历史 | none | 2026-05-13 | `rg` | low | 只追溯历史 | +| `OLD-IMPL-PLAN` | `docs/implementation_plan.md` | 已移除历史 | 早期实施计划 | `REMOVED` | product | `DCL7` | git history | none | 2026-05-13 | manual | low | 从 git 历史恢复 | + ## 当前事实源 | 路径 | 分类 | 当前用途 | diff --git a/docs/文档索引.md b/docs/文档索引.md index 26cfe29c..567058e3 100644 --- a/docs/文档索引.md +++ b/docs/文档索引.md @@ -1,7 +1,11 @@ # hyb-oa 文档索引 **文档状态**:当前唯一文档入口。 +**分类**:入口索引。 **适用时间**:DCL7 之后。 +**关联 Issue**:`GOV8`。 +**最近审计**:2026-05-13。 +**不得误读**:本文只指示当前事实源和读取顺序;历史 Issue CSV 只能用于追溯,不直接生成新开发任务。 **治理口径**:中文优先,物理精简。已确认不用的历史文档不放 `docs/archive/`,直接从当前工作树移除;需要时从 git 历史恢复。 ## 读取规则 @@ -10,6 +14,8 @@ - 判断历史证据:读轻量历史索引,必要时用 `git show :` 恢复旧文档。 - 判断冲突:最新 Issue CSV > 当前 PRD/模块规约 > 设计和产品规则 > 当前操作手册 > 技术参考 > 历史证据。 - 英文文件名只在技术生态或脚本强引用时保留,例如 `docs/mcp-tools.md`。 +- 判断当前可执行任务:只读当前活跃治理流和对应连续型 Issue CSV;旧 Issue CSV 默认是历史审计证据。 +- 新需求或试运行反馈:优先进入当前同类型连续治理流;只有跨业务域、跨风险等级或阶段收口后,才新建下一版 CSV。 ## 最近事实源链 @@ -21,6 +27,33 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M - MIA7 已完成移动信息架构、视觉和流程审查;MIF7-1..7 与 MIF7T 已完成本地整改、公网部署、公网匿名复检和带凭据 live smoke/OCR 复检。 - R7/OPS7/DOC7 是准备材料,不代表已上线或已启动真实 R7。 +## 当前活跃治理流 + +后续不再为每次聊天、小反馈或单点修正单独创建 Issue CSV。同类型、同目标、同风险域的任务优先进入同一个连续型 Issue CSV,直到该治理流完成明确阶段。 + +| 治理流 | 当前合同 | 当前状态 | 范围 | 不做 | +| --- | --- | --- | --- | --- | +| `GOV8` | `issues/2026-05-13_16-31-09-gov8-system-governance.csv` | 当前执行 | 文档分类、台账、流程模板、审计脚本、需求冻结、变更控制 | 不改业务代码、不部署 | +| `R7E` | 待 GOV8 收口后新建连续合同 | 规划 | 人工确认、真实试运行、每日问题收口、验收结论 | 不新增 P2 能力 | +| `ORG8` | 待 R7E 或用户确认后新建连续合同 | 规划 | 组织、账号、直属主管、停用、角色、企业微信绑定、权限边界 | 不做审批流程增强 | +| `WF8` | 待 ORG8 或用户确认后新建连续合同 | 规划 | 转审、通知、催办、邮件通道决策、三渠道验收 | 不做复杂 BPM 或审批设计器 | +| `BIZ8` | 待 R7E/P2 排序后新建连续合同 | 规划 | OCR、票据、财务、费用提示、项目关联 | 不做 ERP 实时集成或重预算 | +| `QAOPS8` | 贯穿执行,必要时新建连续合同 | 规划 | 回归稳定、测试覆盖、上线门禁、备份恢复、SLO | 不改业务需求 | + +旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。 + +## 按开发阶段阅读 + +| 阶段 | 必读入口 | 目的 | +| --- | --- | --- | +| 需求阶段 | 最新同类型连续 Issue CSV、[海音达OA-MVP需求规格说明书](./海音达OA-MVP需求规格说明书.md)、对应 `需求-*.md`、[产品口径](./产品口径.md) | 判断做什么、不做什么、验收什么 | +| 设计阶段 | [产品设计规则](./产品设计规则.md)、[架构概览](./架构概览.md)、[集成边界](./集成边界.md)、`docs/ops/*.md`、必要 ADR | 判断页面、权限、状态流、集成和非功能边界 | +| 开发阶段 | 当前连续 Issue CSV、[开发交付与变更管理规约](./开发交付与变更管理规约.md)、`AGENTS.md` | 判断文件范围、依赖、方案批准和交接物 | +| 测试阶段 | [测试规约与回归门禁](./测试规约与回归门禁.md)、[试运行与自动化验证手册](./试运行与自动化验证手册.md)、当前 Issue 的 `Test_Method` | 判断怎么测、何时可回填 DONE | +| 发布阶段 | [发布试运行准备清单](./trialrun/发布试运行准备清单.md)、[公网 Live Smoke 准备清单](./trialrun/公网LiveSmoke准备清单.md)、[OPS7 运维交接 Runbook](./trialrun/OPS7运维交接Runbook.md) | 判断公网、企业微信、真实 OCR 和回滚条件 | +| 审计阶段 | [文档事实源状态矩阵](./文档事实源状态矩阵.md)、[人工试运行报告](./trialrun/人工试运行报告.md)、当前连续 Issue CSV、git 历史 | 判断当前事实、历史证据和冲突处理 | +| 历史追溯 | [历史功能状态矩阵](./历史功能状态矩阵.md)、旧 Issue CSV、git 历史 | 只追溯,不直接派工 | + ## 当前事实源 | 文档 | 用途 | diff --git a/issues/2026-05-13_16-31-09-gov8-system-governance.csv b/issues/2026-05-13_16-31-09-gov8-system-governance.csv new file mode 100644 index 00000000..4015111f --- /dev/null +++ b/issues/2026-05-13_16-31-09-gov8-system-governance.csv @@ -0,0 +1,21 @@ +ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes +GOV8-0,连续型治理合同建档,"建立 GOV8 连续型系统治理合同,锁定本轮只做文档、流程、Issue 管理和审计脚本治理,不改 server/client 业务代码。","1) 本 CSV 校验通过;2) 范围限定 docs/issues/scripts/qa;3) 不删除旧 Issue CSV;4) 不部署;5) 后续同类治理任务优先进入本 CSV。","流程级验证:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-13_16-31-09-gov8-system-governance.csv;人工核对 Files 范围不含 server/client 业务代码。",python3|manual,DONE,DONE,DONE,issues/2026-05-13_16-31-09-gov8-system-governance.csv,[],回滚路径:删除本 CSV 与本轮 docs/scripts/qa 变更;不影响运行环境。 +GOV8-1,旧 Issue 现状盘点,"盘点当前 Issue CSV 过多且不可轻易删除的问题,明确旧 Issue 作为历史审计证据保留,不批量回写、不挪 archive。","1) 文档写明旧 Issue 不删除;2) 当前活跃合同和历史合同分层;3) 旧合同只通过索引/状态矩阵降级;4) 不修改旧 CSV。","流程级验证:rg -n '旧 Issue|历史合同|不删除|不批量回写|当前活跃治理流' docs/文档索引.md docs/文档事实源状态矩阵.md issues/README.md。",rg,DONE,DONE,DONE,docs/文档索引.md|docs/文档事实源状态矩阵.md|issues/README.md,"[""GOV8-0""]",不回写历史快照;只增加治理口径。 +GOV8-2,文档台账字段设计,"把文档事实源状态矩阵升级为轻量文档台账,固定 Doc_ID、Path、Category、Authority、Status、Owner、Related_Issues、Evidence、Supersedes、Last_Audited、Audit_Method、Risk、Notes 字段。","1) 状态矩阵包含文档台账字段说明;2) 状态值包含 CURRENT/DRAFT/EVIDENCE/HISTORICAL/SUPERSEDED/REMOVED;3) 核心文档至少登记一轮;4) 审计脚本能检查字段存在。","流程级验证:node scripts/qa/audit-docs.mjs --check-registry;rg -n 'Doc_ID|CURRENT|DRAFT|EVIDENCE|HISTORICAL|SUPERSEDED|REMOVED' docs/文档事实源状态矩阵.md。",node|rg,DONE,DONE,DONE,docs/文档事实源状态矩阵.md|scripts/qa/audit-docs.mjs,"[""GOV8-0""]",台账先覆盖核心事实源、操作手册、草案/证据和技术参考;后续可在 GOV8 继续补全。 +GOV8-3,核心文档分类登记,"在文档台账中登记核心当前事实源、操作手册、草案证据、技术参考和已移除历史材料。","1) 当前事实源、操作手册、草案/证据、技术参考均有分类;2) 每行有 Authority 和 Status;3) 当前事实源可指导开发;4) 草案/历史不能直接派工。","流程级验证:node scripts/qa/audit-docs.mjs --check-registry;人工核对状态矩阵中核心文档分类。",node|manual,DONE,DONE,DONE,docs/文档事实源状态矩阵.md|scripts/qa/audit-docs.mjs,"[""GOV8-2""]",不要求一次登记所有历史 Issue;先固定核心文档台账。 +GOV8-4,文档索引按开发阶段重写,"把文档索引从文档清单升级为按开发阶段阅读入口,覆盖需求、设计、开发、测试、发布、审计、历史追溯。","1) 索引有按开发阶段阅读规则;2) 当前活跃治理流可见;3) 历史合同入口被降级;4) 草案和历史证据不得直接开发。","流程级验证:rg -n '按开发阶段阅读|需求阶段|设计阶段|开发阶段|测试阶段|发布阶段|审计阶段|当前活跃治理流|不得直接开发' docs/文档索引.md。",rg,DONE,DONE,DONE,docs/文档索引.md,"[""GOV8-1"",""GOV8-3""]",保留 DCL7 中文优先和物理精简策略。 +GOV8-5,核心文档统一头部规则,"固定核心文档顶部元数据规则,明确文档状态、分类、适用范围、上级事实源、关联 Issue、最近审计和不得误读。","1) 开发交付规约写明统一头部规则;2) 文档索引/状态矩阵已有当前头部;3) 后续新增核心文档必须套用;4) 不批量强改所有历史文档。","流程级验证:rg -n '文档状态|分类|适用范围|上级事实源|关联 Issue|最近审计|不得误读' docs/开发交付与变更管理规约.md docs/文档索引.md docs/文档事实源状态矩阵.md。",rg,DONE,DONE,DONE,docs/开发交付与变更管理规约.md|docs/文档索引.md|docs/文档事实源状态矩阵.md,"[""GOV8-4""]",只要求当前事实源和后续新增文档遵守;历史证据不大规模重写。 +GOV8-6,需求冻结规则,"补强需求冻结规则:非微小需求必须先进入事实源和当前连续 CSV,冻结后新增想法默认进入后续池。","1) 需求冻结规则写入开发交付规约;2) 当前轮变更准入条件明确;3) 不再为小反馈单独开 CSV;4) 后续池和冻结项分流清楚。","流程级验证:rg -n '需求冻结|后续池|当前轮变更|小反馈|连续 CSV' docs/开发交付与变更管理规约.md issues/README.md。",rg,DONE,DONE,DONE,docs/开发交付与变更管理规约.md|issues/README.md,"[""GOV8-5""]",开发中临时变更必须先做影响评估。 +GOV8-7,变更请求规则,"补强变更请求规则:开发中新增范围必须说明原因、影响、测试变化和是否替代原需求。","1) 变更请求字段明确;2) 不满足准入条件时进入下一阶段或同治理流后续行;3) 测试影响必须同步;4) 旧 Issue 不回写。","流程级验证:rg -n '变更请求|变更原因|影响范围|测试影响|替代原需求|下一阶段' docs/开发交付与变更管理规约.md。",rg,DONE,DONE,DONE,docs/开发交付与变更管理规约.md,"[""GOV8-6""]",只做流程规则,不改业务逻辑。 +GOV8-8,设计方案模板,"在开发交付规约中加入设计方案模板,覆盖数据/API、权限、状态流、页面、异常、回滚和测试影响。","1) 模板字段完整;2) 涉及权限/状态机/迁移时必须写回滚;3) 可映射到 Issue CSV;4) 不引入外部平台。","流程级验证:rg -n '设计方案模板|数据/API|权限|状态流|回滚|测试影响' docs/开发交付与变更管理规约.md。",rg,DONE,DONE,DONE,docs/开发交付与变更管理规约.md,"[""GOV8-7""]",复用现有 Issue CSV,不新增外部工具。 +GOV8-9,开发计划模板,"在开发交付规约中加入开发计划模板,明确治理流、Issue 行、文件范围、依赖、验证和交接物。","1) 模板能指导 backend/frontend/qa 分工;2) 文件范围和依赖清晰;3) 交接物明确;4) 与连续型 CSV 规则一致。","流程级验证:rg -n '开发计划模板|治理流|Issue 行|文件范围|依赖|交接物' docs/开发交付与变更管理规约.md。",rg,DONE,DONE,DONE,docs/开发交付与变更管理规约.md,"[""GOV8-8""]",后续实现 worker 仍按 AGENTS 作用域约束。 +GOV8-10,测试方案模板,"在开发交付规约中加入测试方案模板,覆盖主流程、异常、权限、视觉、企业微信、公网和 SKIP 条件。","1) 测试方案字段完整;2) 明确流程级 Test_Method;3) 区分本地/公网匿名/带凭据;4) SKIP 不是通过。","流程级验证:rg -n '测试方案模板|主流程|异常流程|权限|视觉|企业微信|公网|SKIP' docs/开发交付与变更管理规约.md。",rg,DONE,DONE,DONE,docs/开发交付与变更管理规约.md,"[""GOV8-9""]",测试细节仍以测试规约为准。 +GOV8-11,测试计划模板,"在开发交付规约中加入测试计划模板,明确执行时机、命令、环境、证据路径、负责人和阻断条件。","1) 测试计划字段完整;2) 可形成执行清单;3) 证据路径明确;4) 阻断条件清晰。","流程级验证:rg -n '测试计划模板|执行时机|命令|环境|证据路径|负责人|阻断条件' docs/开发交付与变更管理规约.md。",rg,DONE,DONE,DONE,docs/开发交付与变更管理规约.md,"[""GOV8-10""]",不替代 docs/测试规约与回归门禁.md。 +GOV8-12,文档审计脚本,"新增只读文档审计脚本,检查文档台账字段、核心路径、状态值、链接引用、敏感词和旧口径风险。","1) 脚本只读;2) --check-registry 可校验核心台账;3) --help 可用;4) 不打印真实 secret;5) 默认输出摘要。","流程级验证:node --check scripts/qa/audit-docs.mjs;node scripts/qa/audit-docs.mjs --help;node scripts/qa/audit-docs.mjs --check-registry。",node,DONE,DONE,DONE,scripts/qa/audit-docs.mjs,"[""GOV8-2""]",脚本用于治理门禁;后续可在 QAOPS8 中扩展。 +GOV8-13,旧口径和敏感词审计,"用文档审计脚本和 rg 扫描旧口径、TODO、未部署、尚未完成、密钥和敏感词风险。","1) 风险输出可解释;2) 不把历史证据误判为当前缺口;3) 不泄露密钥;4) 发现阻断风险时记录到 GOV8 后续行。","流程级验证:node scripts/qa/audit-docs.mjs;rg -n 'TODO|未部署|尚未完成|secret|token|密码|密钥' docs issues scripts/qa。",node|rg,DONE,DONE,DONE,docs|issues|scripts/qa,"[""GOV8-12""]",rg 命中可能包含规则说明,需人工解释。 +GOV8-14,链接和路径审计,"用文档审计脚本检查 Markdown 链接指向的本地 .md/.csv 路径是否存在。","1) 相对链接可解析;2) 绝对仓库链接可解释;3) 外部链接不误报;4) 断链进入后续修复行。","流程级验证:node scripts/qa/audit-docs.mjs --check-links。",node,DONE,DONE,DONE,scripts/qa/audit-docs.mjs|docs,"[""GOV8-12""]",脚本先覆盖 docs 下 Markdown 文档链接。 +GOV8-15,当前执行合同索引规则,"固定当前活跃治理流入口,只在文档索引和状态矩阵列当前活跃连续合同,历史合同通过 README 和 git 历史追溯。","1) 当前活跃治理流表存在;2) GOV8 为当前活跃治理流;3) 未来 R7E/ORG8/WF8/BIZ8/QAOPS8 有规划入口;4) 旧 Issue 不在主入口铺开。","流程级验证:rg -n '当前活跃治理流|GOV8|R7E|ORG8|WF8|BIZ8|QAOPS8|历史合同' docs/文档索引.md docs/文档事实源状态矩阵.md issues/README.md。",rg,DONE,DONE,DONE,docs/文档索引.md|docs/文档事实源状态矩阵.md|issues/README.md,"[""GOV8-4""]",减少 Issue 文件膨胀后的入口噪声。 +GOV8-16,历史 Issue 降级规则,"固定旧 Issue CSV 的处理规则:不删除、不回写、不归档目录,只通过新合同 Notes 或索引说明 supersedes。","1) 规则写入索引、状态矩阵或 README;2) 明确旧 Issue 是审计历史;3) supersedes 只写在新合同或索引;4) 不移动旧文件。","流程级验证:rg -n 'supersedes|不删除|不回写|历史审计|旧 Issue' docs/文档索引.md docs/文档事实源状态矩阵.md issues/README.md。",rg,DONE,DONE,DONE,docs/文档索引.md|docs/文档事实源状态矩阵.md|issues/README.md,"[""GOV8-15""]",保持 git 历史和审计语义。 +GOV8-17,治理试运行,"用 GOV8 本身试运行连续型 Issue 流程,验证一个治理流承载多行任务、状态回填和阶段收口是否可行。","1) GOV8 CSV 承载 18 行以上任务;2) 文档/脚本变更归入同一合同;3) 校验命令可执行;4) 不额外开小 CSV。","流程级验证:读取 GOV8 CSV 行数;validate_issues_csv.py 本 CSV;人工确认本轮无额外治理 CSV。",python3|manual,DONE,DONE,DONE,issues/2026-05-13_16-31-09-gov8-system-governance.csv,"[""GOV8-0""]",本轮即为治理试运行。 +GOV8-18,GOV8 回归与状态回填,"执行 GOV8 所有文档、CSV 和脚本校验,根据实测结果回填状态。","1) CSV 校验通过;2) audit-docs 关键模式通过;3) rg 关键规则命中;4) git diff --check 通过;5) 状态按证据回填。","流程级验证:validate_issues_csv.py 本 CSV;node --check scripts/qa/audit-docs.mjs;node scripts/qa/audit-docs.mjs --check-registry;node scripts/qa/audit-docs.mjs --check-links;git diff --check。",python3|node|git,DONE,DONE,DONE,issues/2026-05-13_16-31-09-gov8-system-governance.csv|docs|scripts/qa,"[""GOV8-13"",""GOV8-14"",""GOV8-17""]",回归失败不得标 DONE。 +GOV8-19,GOV8 收口报告,"输出 GOV8 变更摘要、验证结果、残留风险和后续连续治理流建议。","1) 最终回复说明改了什么;2) 说明验证命令和结果;3) 说明未部署;4) 指出下一步 R7E/ORG8/QAOPS8 入口;5) 如提交则引用 GOV8。","流程级验证:最终交付说明包含变更摘要、验证结果、部署状态和后续建议。",manual,DONE,DONE,DONE,N/A,"[""GOV8-18""]",本轮面向用户的收口报告;不要求部署。 diff --git a/issues/README.md b/issues/README.md index 06a7656d..0190ed48 100644 --- a/issues/README.md +++ b/issues/README.md @@ -2,6 +2,27 @@ 本项目使用 Issue CSV 作为需求驱动与执行合同。 +## 连续型 Issue 原则 + +GOV8 后默认采用“少量长期连续合同 + 多行任务 + 阶段收口”,避免每次聊天、小反馈或单点修正都创建新 CSV。 + +- 同类型、同目标、同风险域的问题进入同一个连续 CSV。 +- 一个连续 CSV 最多承载一个治理流,不混入无关业务域。 +- 只有跨业务域、跨风险等级、跨里程碑,或当前治理流阶段性收口后,才新建下一版 CSV。 +- 旧 Issue CSV 不删除、不批量回写、不移动到归档目录;它们是历史审计证据。 +- 当前可执行合同以 `docs/文档索引.md` 和 `docs/文档事实源状态矩阵.md` 标注的“当前活跃治理流”为准。 + +当前优先治理流: + +| 治理流 | 建议/当前文件 | 用途 | +| --- | --- | --- | +| `GOV8` | `2026-05-13_16-31-09-gov8-system-governance.csv` | 文档、流程、Issue 管理和审计治理 | +| `R7E` | 待新建连续 CSV | 真实扩大试运行 | +| `ORG8` | 待新建连续 CSV | 组织账号和权限主数据 | +| `WF8` | 待新建连续 CSV | 审批与通知增强 | +| `BIZ8` | 待新建连续 CSV | 报销/OCR/财务/项目轻量增强 | +| `QAOPS8` | 待新建连续 CSV | 测试、发布、运维和上线准备 | + ## 必填列(全部必填) - `ID` - `Title` @@ -45,7 +66,7 @@ - UTF-8 编码。 ## 权威读取顺序 -- **当前执行合同**:优先查看时间最新且已批准的 `issues/*.csv`;当前治理收口入口为 [2026-05-05_20-01-18-r90-r91-trialrun-closeout.csv](/home/shunli/Projects/hyb-oa/issues/2026-05-05_20-01-18-r90-r91-trialrun-closeout.csv:1)。 +- **当前执行合同**:优先查看 `docs/文档索引.md` 和 `docs/文档事实源状态矩阵.md` 标注的当前活跃治理流;不要只按文件时间最新判断。 - **历史蓝图链**:P5 相关只把 [2026-02-09_13-39-02-p5-blueprint-v3.csv](/home/shunli/Projects/hyb-oa/issues/2026-02-09_13-39-02-p5-blueprint-v3.csv:1) 视为权威完成态;[2026-02-09_11-17-07-p5-blueprint.csv](/home/shunli/Projects/hyb-oa/issues/2026-02-09_11-17-07-p5-blueprint.csv:1) 和 [2026-02-09_12-34-42-p5-blueprint-v2.csv](/home/shunli/Projects/hyb-oa/issues/2026-02-09_12-34-42-p5-blueprint-v2.csv:1) 保留为历史快照,不再单独作为当前待办来源。 - **试运行链**:本地/外部试运行相关优先串读 `R70 -> R71 -> R73 -> R74 -> R75 -> R76 -> R77 -> R78 -> R79 -> R80 -> R82 -> R83-R86 -> R87 -> R88 -> R89 -> R90/R91`,不要只看单一 CSV 判断系统状态。 - **跨文档事实源**:业务能力现状优先结合 [历史功能状态矩阵.md](/home/shunli/Projects/hyb-oa/docs/历史功能状态矩阵.md:1)、[试运行覆盖矩阵.csv](/home/shunli/Projects/hyb-oa/docs/trialrun/试运行覆盖矩阵.csv:1) 和 [人工试运行报告.md](/home/shunli/Projects/hyb-oa/docs/trialrun/人工试运行报告.md:1) 阅读。 diff --git a/scripts/qa/audit-docs.mjs b/scripts/qa/audit-docs.mjs new file mode 100644 index 00000000..fcdd6a49 --- /dev/null +++ b/scripts/qa/audit-docs.mjs @@ -0,0 +1,259 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; + +const repoRoot = process.cwd(); +const args = new Set(process.argv.slice(2)); + +const registryPath = 'docs/文档事实源状态矩阵.md'; +const requiredFields = [ + 'Doc_ID', + 'Path', + 'Category', + 'Authority', + 'Status', + 'Owner', + 'Related_Issues', + 'Evidence', + 'Supersedes', + 'Last_Audited', + 'Audit_Method', + 'Risk', + 'Notes', +]; +const validStatuses = new Set([ + 'CURRENT', + 'DRAFT', + 'EVIDENCE', + 'HISTORICAL', + 'SUPERSEDED', + 'REMOVED', +]); +const requiredCorePaths = [ + 'docs/文档索引.md', + 'docs/文档事实源状态矩阵.md', + 'docs/海音达OA-MVP需求规格说明书.md', + 'docs/开发交付与变更管理规约.md', + 'docs/测试规约与回归门禁.md', + 'docs/产品设计规则.md', + 'docs/产品口径.md', + 'AGENTS.md', + '.agents/skills/hyb-oa-delivery/SKILL.md', +]; + +function printHelp() { + console.log(`Usage: node scripts/qa/audit-docs.mjs [options] + +Options: + --check-registry Validate docs registry fields, status values, and core paths. + --check-links Scan docs markdown links to local .md/.csv targets. + --help Show this help. + +Default without options runs registry, link, stale-term, and sensitive-risk checks. +This script is read-only and never prints secret values.`); +} + +function readText(relativePath) { + return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8'); +} + +function pathExists(relativePath) { + return fs.existsSync(path.join(repoRoot, relativePath)); +} + +function listFiles(dir, predicate = () => true) { + const absDir = path.join(repoRoot, dir); + if (!fs.existsSync(absDir)) return []; + const result = []; + for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) { + const relative = path.join(dir, entry.name); + if (entry.isDirectory()) { + result.push(...listFiles(relative, predicate)); + } else if (predicate(relative)) { + result.push(relative); + } + } + return result; +} + +function extractRegistryTable() { + const text = readText(registryPath); + const marker = '## 文档台账'; + const start = text.indexOf(marker); + if (start < 0) { + return { header: [], rows: [], errors: ['missing registry section: ## 文档台账'] }; + } + const after = text.slice(start + marker.length); + const nextSection = after.search(/\n## /); + const section = nextSection >= 0 ? after.slice(0, nextSection) : after; + const tableLines = section + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.startsWith('|') && line.endsWith('|')); + if (tableLines.length < 2) { + return { header: [], rows: [], errors: ['registry table is missing or too short'] }; + } + const cells = (line) => line.slice(1, -1).split('|').map((cell) => cell.trim()); + const header = cells(tableLines[0]); + const rows = tableLines.slice(2).map((line) => cells(line)); + return { header, rows, errors: [] }; +} + +function stripCode(value) { + return value.replace(/^`|`$/g, '').trim(); +} + +function checkRegistry() { + const errors = []; + const warnings = []; + if (!pathExists(registryPath)) { + return { errors: [`missing ${registryPath}`], warnings }; + } + const { header, rows, errors: parseErrors } = extractRegistryTable(); + errors.push(...parseErrors); + for (const field of requiredFields) { + if (!header.includes(field)) { + errors.push(`registry missing field: ${field}`); + } + } + const pathIndex = header.indexOf('Path'); + const statusIndex = header.indexOf('Status'); + const registeredPaths = new Set(); + for (const row of rows) { + if (row.length !== header.length) { + errors.push(`registry row has ${row.length} cells, expected ${header.length}: ${row.join(' | ')}`); + continue; + } + const docPath = stripCode(row[pathIndex] ?? ''); + const status = stripCode(row[statusIndex] ?? ''); + registeredPaths.add(docPath); + if (!validStatuses.has(status)) { + errors.push(`invalid status ${status || ''} for ${docPath}`); + } + if (status !== 'REMOVED' && docPath !== 'none' && !pathExists(docPath)) { + errors.push(`registered path missing: ${docPath}`); + } + } + for (const corePath of requiredCorePaths) { + if (!registeredPaths.has(corePath)) { + errors.push(`core path not registered: ${corePath}`); + } + } + const docsFiles = listFiles('docs', (file) => file.endsWith('.md') || file.endsWith('.csv')); + const unregistered = docsFiles.filter((file) => !registeredPaths.has(file)); + if (unregistered.length > 0) { + warnings.push(`unregistered docs files: ${unregistered.length}`); + } + return { errors, warnings }; +} + +function normalizeLinkTarget(sourceFile, target) { + let clean = target.trim(); + if (clean.startsWith('<') && clean.endsWith('>')) clean = clean.slice(1, -1); + if (/^(https?:|mailto:|#)/i.test(clean)) return null; + clean = clean.split('#')[0].trim(); + if (!clean || !/\.(md|csv)$/i.test(clean)) return null; + if (clean.startsWith('/home/shunli/Projects/hyb-oa/')) { + return path.relative(repoRoot, clean); + } + if (clean.startsWith('/')) return null; + return path.normalize(path.join(path.dirname(sourceFile), clean)); +} + +function checkLinks() { + const errors = []; + const warnings = []; + const markdownFiles = listFiles('docs', (file) => file.endsWith('.md')); + const linkPattern = /\[[^\]]+\]\(([^)]+)\)/g; + for (const file of markdownFiles) { + const text = readText(file); + for (const match of text.matchAll(linkPattern)) { + const normalized = normalizeLinkTarget(file, match[1]); + if (!normalized) continue; + if (!pathExists(normalized)) { + warnings.push(`broken local link in ${file}: ${match[1]}`); + } + } + } + return { errors, warnings }; +} + +function checkSensitiveRisk() { + const warnings = []; + const files = [ + ...listFiles('docs', (file) => /\.(md|csv)$/i.test(file)), + ...listFiles('issues', (file) => file.endsWith('.csv') || file.endsWith('.md')), + ...listFiles('scripts/qa', (file) => /\.(mjs|js|sh)$/i.test(file)), + ]; + const patterns = [ + /BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY/, + /AKIA[0-9A-Z]{16}/, + /(secret|token|password|api[_-]?key)\s*[:=]\s*['"]?[A-Za-z0-9_./+=-]{16,}/i, + ]; + for (const file of files) { + const text = readText(file); + if (patterns.some((pattern) => pattern.test(text))) { + warnings.push(`possible sensitive literal risk in ${file}`); + } + } + return { errors: [], warnings }; +} + +function checkStaleTerms() { + const warnings = []; + const currentDocs = [ + 'docs/文档索引.md', + 'docs/文档事实源状态矩阵.md', + 'docs/海音达OA-MVP需求规格说明书.md', + 'docs/开发交付与变更管理规约.md', + 'docs/测试规约与回归门禁.md', + ]; + const staleTerms = ['TODO', '未部署', '尚未完成', '下一批', '旧口径']; + for (const file of currentDocs) { + if (!pathExists(file)) continue; + const text = readText(file); + const hits = staleTerms.filter((term) => text.includes(term)); + if (hits.length > 0) { + warnings.push(`stale-term review needed in ${file}: ${hits.join(', ')}`); + } + } + return { errors: [], warnings }; +} + +function mergeResults(results) { + return results.reduce( + (acc, item) => ({ + errors: [...acc.errors, ...item.errors], + warnings: [...acc.warnings, ...item.warnings], + }), + { errors: [], warnings: [] }, + ); +} + +if (args.has('--help')) { + printHelp(); + process.exit(0); +} + +const checks = []; +if (args.has('--check-registry')) checks.push(checkRegistry); +if (args.has('--check-links')) checks.push(checkLinks); +if (checks.length === 0) { + checks.push(checkRegistry, checkLinks, checkStaleTerms, checkSensitiveRisk); +} + +const result = mergeResults(checks.map((check) => check())); + +console.log(`audit-docs: errors=${result.errors.length} warnings=${result.warnings.length}`); +for (const warning of result.warnings.slice(0, 40)) { + console.log(`warning: ${warning}`); +} +if (result.warnings.length > 40) { + console.log(`warning: ${result.warnings.length - 40} more warnings omitted`); +} +for (const error of result.errors) { + console.error(`error: ${error}`); +} + +process.exit(result.errors.length === 0 ? 0 : 1);