GOV8 建立连续型 Issue 治理基线
这个提交包含在:
+105
@@ -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 不只看代码格式,优先检查行为风险:
|
||||
|
||||
@@ -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 历史恢复 |
|
||||
|
||||
## 当前事实源
|
||||
|
||||
| 路径 | 分类 | 当前用途 |
|
||||
|
||||
+33
@@ -1,7 +1,11 @@
|
||||
# hyb-oa 文档索引
|
||||
|
||||
**文档状态**:当前唯一文档入口。
|
||||
**分类**:入口索引。
|
||||
**适用时间**:DCL7 之后。
|
||||
**关联 Issue**:`GOV8`。
|
||||
**最近审计**:2026-05-13。
|
||||
**不得误读**:本文只指示当前事实源和读取顺序;历史 Issue CSV 只能用于追溯,不直接生成新开发任务。
|
||||
**治理口径**:中文优先,物理精简。已确认不用的历史文档不放 `docs/archive/`,直接从当前工作树移除;需要时从 git 历史恢复。
|
||||
|
||||
## 读取规则
|
||||
@@ -10,6 +14,8 @@
|
||||
- 判断历史证据:读轻量历史索引,必要时用 `git show <commit>:<old-path>` 恢复旧文档。
|
||||
- 判断冲突:最新 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 历史 | 只追溯,不直接派工 |
|
||||
|
||||
## 当前事实源
|
||||
|
||||
| 文档 | 用途 |
|
||||
|
||||
@@ -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""]",本轮面向用户的收口报告;不要求部署。
|
||||
|
+22
-1
@@ -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) 阅读。
|
||||
|
||||
@@ -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 || '<empty>'} 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);
|
||||
在新工单中引用
屏蔽一个用户