概述
系列定位:在项目中要搭建出完整的Harness Engineering四层文档体系,基本分为6步,接下来我会通过本系列共 6 篇文章,指导从零搭建 Harness Engineering 四层文档体系,最终实现从 BRD/PRD 到代码交付的一站式 AI 开发与自检闭环。
本篇定位:第 6 步(最终步)------编写统领全局的 AI Coding 指南,建立 Linter 自动化拦截和自动化治理脚本,实现从 BRD/PRD 到 PR 交付的完整自检闭环。
1. 为什么最后一步是"总指南 + 自动化层"
1.1 本步要解决的两个问题
| 问题 | 解决方式 | 产出 |
|---|---|---|
| 四层文档体系分散,缺乏全局视角 | 编写 AI Coding 总指南,编织四层文档的全景索引和协作流程 | docs/ai-coding-guide.md |
| AI Agent 的产出仅靠主观判断,缺乏客观验证 | 建立 Linter 自动拦截 + 自动化治理脚本,实现客观数据驱动的自检闭环 | build/linter/ + build/automation/scripts/ |
1.2 为什么必须最后做
Step 1~5 产出约 30 个文档/文件
│
▼
Step 6: 将这 30 个文件编织成一个全局索引
+ 建立自动化机制验证这些文件的执行效果
- 总指南需要引用全部已有文档:如果在其他文档未就位时编写,索引会不完整
- Linter 规则需要与 Rules 对齐:Rules 层(Step 2)定义了约束,Linter 配置是这些约束的自动化执行
- 自动化脚本需要引用全部四层:文档一致性检测需要扫描全部 .md/.yaml/.ps1/.xml 文件
2. 需要创建的文件清单
docs/
└── ai-coding-guide.md # AI Coding 规范化开发总指南(全局索引)
build/
├── linter/
│ ├── pmd-exchange-ruleset.xml # PMD 代码质量规则集
│ └── checkstyle-exchange.xml # Checkstyle 代码风格规则集
│
└── automation/
└── scripts/
├── pre-check.ps1 # 环境前置检查
├── worktree-manager.ps1 # Git Worktree 自动化管理
├── collect-sidecar.ps1 # 客观侧性数据采集
├── decay-detector.ps1 # 自动衰减检测
├── governance-report.ps1 # 持续治理报告生成
└── index-sync-check.ps1 # 文档全景索引一致性检测
共 8 个文件(1 个总指南 + 2 个 Linter 配置 + 5~6 个脚本)。
3. 第一部分:AI Coding 总指南
3.1 docs/ai-coding-guide.md -人类参考文档
定位
这是整个 Harness Engineering 体系的顶层文档 ,核心定位是"人类参考文档 ",不是"AI 执行文档"。它不重复四层文档的内容,而是编织四层的协作关系:
- §1 Harness 理念:核心思想、设计目标
- §2 四层文档体系:四层的关系图和职责划分
- §3 PRD 驱动开发全链路:7 步流程的详细说明和按需加载清单
- §4 按需加载策略:每步加载哪些文档、预估 token
- §5 文档全景索引:全部文件的目录树(5.1~5.5)
- §6 核心技术约束速查:always-on.md 的速查表
- §7 实战示例:以一个完整案例展示端到端流程
- §8 文档元信息规范:front matter 格式
- §9 协作机制总结:开发者与 AI Agent 的交互流程图
3.2 必须包含的章节
§1 Harness Engineering 理念
markdown
## 1. Harness Engineering 理念
### 1.1 核心思想
Harness Engineering(驾驭式工程化)是一种让 AI Agent 在严格约束下精准产出代码
的工程方法论。其核心原则是:**AI 生成,人类驾驭**------通过分层文档体系为 AI 提供
充分的上下文知识和硬性约束,同时通过人工确认门禁确保关键决策由人类把控。
### 1.2 设计目标
| 目标 | 实现方式 |
|------|---------|
| 精准性 | Rules 层硬性约束 + Skills 层操作手册 |
| 可控性 | PRD → 设计 → 评审 → 编码 → 自检 全链路门禁 |
| 高效性 | 分步按需加载策略,单步 token ≤ 3000 |
| 一致性 | docs/ 知识层统一管理,人机共读 |
§2 四层文档体系
用 ASCII 图展示四层关系:
markdown
## 2. 四层文档体系
┌─────────────────────────────────────────────────────┐
│ AGENTS.md │
│ 项目入口 · 技术栈基线 · 快速导航 │
├─────────────┬───────────────┬───────────────────────┤
│ Rules 层 │ Skills 层 │ Wiki 层 (docs/) │
│ 硬性约束 │ 操作手册 │ 知识库 │
│ .qoder/ │ .qoder/ │ docs/ │
│ rules/ │ skills/ │ │
├─────────────┴───────────────┴───────────────────────┤
│ Changes 层 (docs/changes/) │
│ 变更管理 · 影响分析 · PR 自检 │
└─────────────────────────────────────────────────────┘
每层用表格列出文件清单、路径和约束内容(引用 Step 1~5 已创建的文件)。
§3 PRD 驱动开发全链路
这是总指南的核心章节,描述从 PRD 输入到交付的完整 7 步流程:
markdown
## 3. PRD 驱动开发全链路
### 3.1 流程总览
Step 0: 环境检查与输入校验 → Step 1: PRD解析 → Step 2: 影响分析 → Step 3: 技术设计文档
→ Step 4: 设计评审(人工门禁)→ Step 5: 代码生成 → Step 5.5: 编译验证(含测试代码)→ Step 6: 配置与文档同步
→ Step 7: 交付前自检 → 交付
> **铁律**:禁止跳过环境检查与输入校验;禁止跳过技术设计文档直接写代码;禁止跳过设计评审直接生成代码;
> 禁止跳过自检直接交付。
### 3.2 分步详解与文档调用清单
#### Step 1: PRD 解析与上下文加载
| 文档 | 用途 |
|------|------|
| AGENTS.md | 获取项目概览与技术栈基线 |
| docs/reference/prd-template.md | 对照 PRD 模板解析需求结构 |
| docs/reference/brd-to-prd-guide.md | (条件)BRD→PRD 转换参考 |
产出:需求解析摘要
{...Step 2~7 类似}
§4 按需加载策略
markdown
## 4. 按需加载策略
### 4.1 设计原则
- Rules 层:始终在上下文中(IDE 自动注入)
- Skills 层:仅在对应 Step 触发时加载
- Wiki 层:按步骤加载该步骤所需文档
- 单步控制:单个 Step 的按需加载总量控制在 3000 token 以内
### 4.2 各步骤加载清单与预估 token
| 步骤 | 按需加载 | 预估 token |
|------|---------|-----------|
| Step 1 PRD解析 | AGENTS.md + prd-template.md | ~1200 |
| Step 2 影响分析 | boundaries.md + impact-analysis.md | ~2000 |
| Step 3 设计文档 | _template.md + data-flow.md + feature-*.md | ~3000 |
| Step 4 设计评审 | design-review.md | ~1000 |
| Step 5 代码生成 | 按需触发 add-*.md | ~800 |
| Step 6 文档同步 | error-codes.md + api.spec.yaml | ~1500 |
| Step 7 自检 | auto-governance.md + pr-checklist.md | ~1600 |
§5 文档全景索引
这是总指南最重要的维护章节,列出全部文件的目录树:
markdown
## 5. 文档全景索引
### 5.1 指令层(.qoder/)
{列出 .qoder/rules/ 和 .qoder/skills/ 下的全部文件}
### 5.2 知识层(docs/)
{列出 docs/ 下的全部 .md 和 .yaml 文件}
### 5.3 项目入口
{列出项目根目录下的 AGENTS.md、README.md、CHANGELOG.md}
### 5.4 自动化层脚本
{列出 build/automation/scripts/ 下的全部 .ps1 文件}
### 5.5 Linter 规则配置
{列出 build/linter/ 下的全部 .xml 文件}
关键规则:凡是新增或删除 .md/.yaml/.ps1/.xml 文件时,必须同步更新此章节的文件树。
§7 实战示例
以一个完整案例(如"新增供应商对接")展示端到端流程:
markdown
## 7. 实战示例:新增供应商对接
### 7.1 用户提供 PRD
开发者参照 prd-template.md 编写 PRD(业务人员填 Part A)。
### 7.2 AI Agent 执行流程
Step 1: 加载 AGENTS.md + prd-template.md → 解析 PRD
↓
Step 2: 加载 boundaries.md + impact-analysis.md → 输出影响分析
↓
Step 3: 加载 _template.md + data-flow.md → 生成设计文档 (draft)
↓
Step 4: 加载 design-review.md → 7 维度评审
→ ⚠️ 暂停,等待用户确认
→ 用户确认「通过」→ status 改为 active
↓
Step 5: 加载 add-vendor-adapter.md → 按序生成代码
↓
Step 5.5: 编译验证(含测试代码)
↓
Step 6: 加载 error-codes.md + api.spec.yaml → 同步文档
↓
Step 7: 加载 auto-governance.md + pr-checklist.md → 自检
→ 交付
§9 协作机制总结
用 ASCII 时序图展示开发者与 AI Agent 的交互流程:
markdown
## 9. 协作机制总结
开发者 AI Agent 文档体系
│ │ │
│ 1. 提交 PRD │ │
│ ──────────────────────▶ │ │
│ │ 2. 加载文档(按需) │
│ │ ◀────────────────────── │
│ │ 3. 生成设计文档 │
│ │ ──────────────────────▶ │
│ 4. 评审报告 + 设计摘要 │ │
│ ◀────────────────────── │ │
│ 5. 人工确认「通过」 │ │
│ ──────────────────────▶ │ │
│ │ 6. 生成代码 + 测试 │
│ │ 7. 同步文档 + 自检 │
│ 8. 自检结果 + 交付 │ │
│ ◀────────────────────── │ │
3.3 编写要点
- 总指南是"索引"不是"副本":引用四层文档,不重复其内容
- 全景索引必须完整:每新增/删除文件都要更新 §5
- 实战示例要端到端:从 PRD 输入到 PR 交付的完整流程
- 面向人而非AI:核心定位是"人类参考文档",不是"AI 执行文档"
实际示例
通过agent生成
markdown
---
last_updated: 2026-06-29
status: active
owner: @zhangsan
---
# AI Coding 规范化开发指南
> **定位**:本文档是 app-exchange-port 项目使用 AI Coding 进行一站式规范化开发的总体指南,
> 基于 Harness Engineering 理念,将 PRD 驱动开发全链路中涉及的所有 .md 文档统一编排,
> 为开发者提供从需求输入到代码交付的完整操作手册。
---
## 1. Harness Engineering 理念
### 1.1 核心思想
Harness Engineering(驾驭式工程化)是一种让 AI Agent 在严格约束下精准产出代码的工程方法论。
其核心原则是:**AI 生成,人类驾驭**------通过分层文档体系为 AI 提供充分的上下文知识和硬性约束,
同时通过人工确认门禁确保关键决策由人类把控。
### 1.2 设计目标
| 目标 | 实现方式 |
|------|---------|
| **精准性** --- AI 生成的代码符合项目技术栈和架构规范 | Rules 层硬性约束 + Skills 层操作手册 |
| **可控性** --- 开发流程可追溯、可审查、可回退 | PRD → 设计 → 评审 → 编码 → 自检 全链路门禁 |
| **高效性** --- 减少 AI 上下文膨胀,按需加载文档 | 分步按需加载策略,单步 token ≤ 3000 |
| **一致性** --- 人机共读同一套知识库 | docs/ 知识层统一管理架构、规范、设计文档 |
---
## 2. 四层文档体系
项目文档分为四层,各层职责明确、协同运作:
\```
┌─────────────────────────────────────────────────────┐
│ AGENTS.md │
│ 项目入口 · 技术栈基线 · 快速导航 │
├─────────────┬───────────────┬───────────────────────┤
│ Rules 层 │ Skills 层 │ Wiki 层 (docs/) │
│ 硬性约束 │ 操作手册 │ 知识库 │
│ .qoder/ │ .qoder/ │ docs/ │
│ rules/ │ skills/ │ │
├─────────────┴───────────────┴───────────────────────┤
│ Changes 层 (docs/changes/) │
│ 变更管理 · 影响分析 · PR 自检 │
└─────────────────────────────────────────────────────┘
\```
### 2.1 Rules 层 --- 硬性约束(始终生效)
Rules 层文件由 IDE 自动注入 AI Agent 上下文,无需手动加载,在所有任务中始终生效。
| 文件 | 路径 | 约束内容 |
|------|------|---------|
| 技术硬约束 | `.qoder/rules/always-on.md` | 技术栈锁定、模块依赖方向、依赖注入、日志、HTTP、数据库、线程池、限流、traceId、事务、配置、注释语言、代码行数限制(共 13 条) |
| Agent 工作流 | `.qoder/rules/agent-workflow.md` | 强制流程顺序(含 BRD→PRD)、设计文档要求、评审门禁、按需加载策略、代码生成约束、交付自检、文档同步、自动化层自我验证(共 8 节) |
| Java 代码风格 | `.qoder/rules/java-code-style.md` | 包结构、类命名(18 类型)、Lombok 使用、异常处理、import 规范、供应商扩展规范 |
| 测试规则 | `.qoder/rules/java-testing.md` | 测试分类(单元/集成/架构)、JUnit 5 + Mockito + ArchUnit 框架、命名规范、Mock 策略、集成测试规则、架构测试规则、覆盖要求、禁止项 |
### 2.2 Skills 层 --- 操作手册(按需触发)
Skills 层是 AI Agent 的操作手册,仅在对应开发步骤触发时加载。
| 技能 | 路径 | 触发场景 |
|------|------|---------|
| PRD 转代码(主技能) | `.qoder/skills/prd-to-code.md` | 用户提供 PRD 要求开发时 |
| 技术设计评审 | `.qoder/skills/design-review.md` | 设计文档生成后、代码生成前 |
| 新增供应商适配器 | `.qoder/skills/add-vendor-adapter.md` | PRD 要求对接新供应商时 |
| 新增 Entity/Mapper/Manager | `.qoder/skills/add-entity-mapper.md` | PRD 要求新增数据库表时 |
| 新增 API 接口 | `.qoder/skills/add-api-endpoint.md` | PRD 要求新增对外接口时 |
| 文档健康度扫描 | `.qoder/skills/doc-gardening.md` | 提交前检查文档健康度时 |
### 2.3 Wiki 层 --- 知识库(人机共读)
Wiki 层位于 `docs/` 目录,提供项目架构、编码规范、设计文档等上下文知识,供 AI Agent 按需加载。
| 子目录 | 内容 | 关键文件 |
|--------|------|---------|
| `docs/architecture/` | 系统架构 | `overview.md`(架构概览)、`boundaries.md`(模块边界)、`data-flow.md`(数据流转) |
| `docs/conventions/` | 编码规范 | `README.md`(总览)、`naming.md`(命名)、`error-handling.md`(错误处理)、`testing.md`(测试)、`logging.md`(日志) |
| `docs/design/` | 功能设计文档 | `_template.md`(设计模板)、`feature-*.md`(各功能设计文档) |
| `docs/reference/` | 参考资料库 | `prd-template.md`(PRD 模板,Part A/B 分层)、`brd-to-prd-guide.md`(BRD→PRD 转换指南)、`api.spec.yaml`(API 规范)、`error-codes.md`(错误码) |
| `docs/plans/` | 迭代计划 | `current-sprint.md`(当前迭代)、`backlog.md`(待办) |
### 2.4 Changes 层 --- 变更管理
Changes 层确保代码变更的可追溯性、一致性和安全性。
| 文件 | 路径 | 用途 |
|------|------|------|
| PR 自检清单 | `docs/changes/pr-checklist.md` | 代码生成后逐项自检(规则合规、代码质量、测试、文档同步、提交规范) |
| 影响分析指南 | `docs/changes/impact-analysis.md` | 变更前分析影响范围(模块影响矩阵、变更类型、文档关联检查) |
| 变更记录 | `CHANGELOG.md`(项目根) | 记录每次变更历史 |
---
## 3. PRD 驱动开发全链路
### 3.1 流程总览
\```
Step 0: 环境检查与输入校验 → Step 1: PRD解析 → Step 2: 影响分析 → Step 3: 技术设计文档
→ Step 4: 设计评审(人工门禁)→ Step 5: 代码生成 → Step 5.5: 编译验证(含测试代码)→ Step 6: 配置与文档同步
→ Step 7: 交付前自检 → 交付
\```
> **铁律**:禁止跳过环境检查与输入校验;禁止跳过技术设计文档直接写代码;禁止跳过设计评审直接生成代码;禁止跳过自检直接交付。
### 3.2 分步详解与文档调用清单
#### Step 0: 环境检查与输入校验
**目标**:检查环境就绪,创建隔离工作区,校验输入文档完整性(含 BRD→PRD 转换)。
**按需加载文档**:
| 文档 | 用途 |
|------|------|
| `build/automation/scripts/pre-check.ps1` | JDK/Maven/Git 环境校验 |
| `docs/reference/prd-template.md` 第 0 节 | PRD 最低完整性要求校验 |
| `docs/reference/brd-to-prd-guide.md` | (条件)如输入为 BRD,转换为 PRD 结构 |
**产出**:就绪环境 + 隔离 Git worktree + 校验通过的 PRD。PRD 必填项缺失则暂停等待用户补充。
---
#### Step 1: PRD 解析与上下文加载
**目标**:解析 PRD 需求,提取功能目标、输入输出、业务规则。
**按需加载文档**:
| 文档 | 用途 |
|------|------|
| `AGENTS.md` | 获取项目概览与技术栈基线 |
| `docs/reference/prd-template.md` | 对照 PRD 模板(Part A/B 分层)解析需求结构 |
| `docs/reference/brd-to-prd-guide.md` | (条件)如 PRD 中出现技术方案关键词,参照指南判断是否需移至设计阶段 |
**产出**:需求解析摘要,标记缺失信息为"待补充"。如 PRD Part B 留空,标记为"AI Agent 推导"。
---
#### Step 2: 影响分析
**目标**:确定功能归属模块,列出新增/修改文件清单。
**按需加载文档**:
| 文档 | 用途 |
|------|------|
| `docs/architecture/boundaries.md` | 模块边界与依赖方向约束 |
| `docs/changes/impact-analysis.md` | 影响分析模板与模块影响矩阵 |
| `docs/reference/api.spec.yaml`(条件) | 仅 PRD 涉及 API 变更时读取相关段 |
**产出**:影响分析报告(变更模块、新增文件、修改文件、数据库变更、配置变更、文档同步、回归风险)。
---
#### Step 3: 技术设计文档生成
**目标**:基于模板生成技术设计文档,保存到 `docs/design/feature-{功能名}.md`。
**按需加载文档**:
| 文档 | 用途 |
|------|------|
| `docs/design/_template.md` | 技术设计文档模板 |
| `docs/architecture/data-flow.md` | 数据流转路径参考 |
| `docs/design/feature-*.md`(1~2 个相关) | 相似功能的设计参考 |
**产出**:`docs/design/feature-{功能名}.md`,`status: draft`,包含:目标、范围、分层设计、数据库约束、API 约定、配置项、测试要求、风险与对策、自检清单。
---
#### Step 4: 设计评审(人工确认门禁)
**目标**:对技术设计文档进行 7 维度评审,通过后方可进入代码生成。
**按需加载文档**:
| 文档 | 用途 |
|------|------|
| `.qoder/skills/design-review.md` | 评审维度与检查清单 |
**评审 7 维度**:
1. **架构合规性** --- 模块归属、依赖方向、跨层引用
2. **类设计合理性** --- 基类复用、注解规范、命名规范
3. **数据库设计** --- 表名前缀、标准字段、索引、DDL 完整性
4. **API 设计** --- 路径风格、DTO 结构、错误码、文档同步标注
5. **测试方案** --- 单元测试/集成测试/架构测试覆盖、Mock 策略、命名规范
6. **配置与文档同步** --- Nacos 配置、文档更新标注
7. **风险评估** --- 技术风险、回归风险、缓解措施
> **人工确认门禁**:评审完成后必须暂停,将设计文档摘要 + 评审报告呈现给用户,等待明确确认:
> - 用户确认「通过」→ 设计文档 `status` 改为 `active`,继续 Step 5
> - 用户要求修改 → 修复后重新提交评审
> - 用户要求重新设计 → 返回 Step 3
>
> **禁止在未获得用户确认前自动进入代码生成阶段。**
---
#### Step 5: 代码生成
**目标**:按模块依赖方向自底向上生成代码,严格遵守 Rules 层全部规则。
**按需加载文档**(按需触发对应 Skill):
| 条件 | 加载 Skill |
|------|-----------|
| 新增数据库表 | `.qoder/skills/add-entity-mapper.md` |
| 新增供应商对接 | `.qoder/skills/add-vendor-adapter.md` |
| 新增 API 接口 | `.qoder/skills/add-api-endpoint.md` |
**代码生成顺序**(严格遵守依赖方向):
\```
1. dal 层 Entity → Mapper → Manager → Mapper XML
2. business 层 接口/抽象类 → 核心实现(@Component + @Slf4j)
3. ext 层 供应商适配器(InputConvertor / HttpResultParser / PortResultConvertor)
4. app 层 配置注册(BizConfiguration.preInitAppConfig)+ Trigger 编排
5. runner/soa 层 Controller / Service 暴露
6. 测试代码 JUnit 5 + Mockito 单元测试
\```
**代码生成约束**:
- 每个 Java 文件必须有中文类注释 + `@author` 标注
- 必须同步生成 JUnit 5 + Mockito 单元测试
- 测试覆盖:正常路径、参数校验失败、异常路径、边界条件
- 禁止使用 `@Transactional`,使用 `TransactionTemplate` 编程式事务
- 禁止直接注入 Mapper,使用 `DalManager.of(beanName)`
- JSON 统一使用 FastJSON,HTTP 统一使用 OkHttp3 + HttpClientFactory
- 代码生成后运行 Linter 自动检查:`mvn pmd:check checkstyle:check`,报错消息含三要素(问题/修复/文档),AI Agent 可自动修复
- 架构依赖校验运行 ArchUnit 测试:`mvn test -pl exchange-test -Dtest=ArchitectureRulesTest`
---
#### Step 6: 配置与文档同步
**目标**:同步更新受影响的配置和文档。
**按需加载文档**:
| 文档 | 用途 |
|------|------|
| `docs/reference/error-codes.md` | 登记新增错误码 |
| `docs/reference/api.spec.yaml` | 新增/修改 API 定义 |
**同步清单**:
| 变更类型 | 需更新的文档 |
|---------|-------------|
| 新增/修改错误码 | `docs/reference/error-codes.md` + `CodeEnum.java` |
| 新增/修改 API | `docs/reference/api.spec.yaml` |
| 新增功能 | `docs/design/feature-*.md`(status → active)+ `CHANGELOG.md` |
| 修改模块结构 | `docs/architecture/boundaries.md` |
| 修改数据流转 | `docs/architecture/data-flow.md` |
---
#### Step 7: 交付前自检
**目标**:逐项执行 PR 自检清单,确认通过后方可交付。
**按需加载文档**:
| 文档 | 用途 |
|------|------|
| `docs/changes/pr-checklist.md` | PR 提交前自检清单 |
**自检维度**:
1. **规则合规** --- 依赖方向、日志、注入、Entity 注解、JSON、HTTP、数据库、线程池、事务、限流、traceId、配置
2. **代码质量** --- 类注释、方法注释、空 catch、异常日志格式、import、单文件行数
3. **测试检查** --- JUnit 5 + Mockito、命名、覆盖场景、Mock 策略、无外部依赖
4. **文档同步** --- 错误码、API、设计文档、last_updated、CHANGELOG
5. **提交规范** --- commit message、PR 粒度、Git hooks
---
## 4. 按需加载策略
### 4.1 设计原则
为避免 AI Agent 上下文膨胀导致 token 浪费和生成质量下降,采用分步按需加载策略:
- **Rules 层**:始终在上下文中(IDE 自动注入),不计入按需加载
- **Skills 层**:仅在对应 Step 触发时加载
- **Wiki 层**:按步骤加载该步骤所需文档,禁止预读后续步骤文档
- **单步控制**:单个 Step 的按需加载总量控制在 3000 token 以内
### 4.2 各步骤加载清单与预估 token
| 步骤 | 按需加载 | 预估 token |
|------|---------|-----------|
| Step 1 PRD 解析 | `AGENTS.md` + `prd-template.md` +(条件)`brd-to-prd-guide.md` | ~800~1200 |
| Step 2 影响分析 | `boundaries.md` + `impact-analysis.md` +(条件)`api.spec.yaml` | ~1500~3500 |
| Step 3 设计文档 | `_template.md` + `data-flow.md` + 1~2 个 `feature-*.md` | ~3000 |
| Step 4 设计评审 | `design-review.md` | ~1000 |
| Step 5 代码生成 | 按需触发 `add-*.md`(如需) | ~800 |
| Step 6 文档同步 | `error-codes.md` + `api.spec.yaml`(相关段) | ~1500 |
| Step 7 自检 | `pr-checklist.md` | ~600 |
### 4.3 加载规则
1. 禁止在 Step 1 预读 Step 2~7 的文档
2. 每个 Step 完成后,可释放前序步骤的文档上下文
3. 如某 Step 发现需要回溯前序文档,按需补读即可
4. Rules 层始终在上下文中(IDE 自动注入),不计入按需加载
5. Skills 层仅在对应 Step 触发时加载
---
## 5. 文档全景索引
### 5.1 指令层(.qoder/)
\```
.qoder/
├── rules/ # Rules 层 --- 始终生效
│ ├── always-on.md # 技术硬约束(13 条)
│ ├── agent-workflow.md # AI Agent 工作流(8 节,含自我验证)
│ ├── java-code-style.md # Java 代码风格
│ └── java-testing.md # 测试规则
└── skills/ # Skills 层 --- 按需触发
├── prd-to-code.md # PRD 转代码(主技能,7 步流程 + Step 5.5 编译门禁)
├── flow-orchestration.md # 全流程编排与断点恢复(流程图 + 断点恢复 + 人工介入点 + 失败处理总表)
├── design-review.md # 设计评审(7 维度门禁)
├── add-vendor-adapter.md # 新增供应商适配器(8 步)
├── add-entity-mapper.md # 新增 Entity/Mapper/Manager(7 步)
├── add-api-endpoint.md # 新增 API 接口(2 场景)
├── doc-gardening.md # 文档健康度扫描(5 步)
└── auto-governance.md # 自动治理巡检(5 步:采集→检测→自动修复→建议→报告)
\```
### 5.2 知识层(docs/)
\```
docs/
├── ai-coding-guide.md # AI Coding 规范化开发指南(本文档)
├── architecture/ # 架构知识
│ ├── overview.md # 系统架构概览
│ ├── boundaries.md # 模块边界与依赖约束
│ ├── data-flow.md # 数据流转(同步/异步/文件)
│ └── code-assets.md # 代码资产清单(基类/接口/工厂 + Manager 双模式决策树)
├── conventions/ # 编码规范
│ ├── README.md # 规范总览
│ ├── naming.md # 命名规范
│ ├── error-handling.md # 错误处理规范
│ ├── testing.md # 测试规范
│ ├── logging.md # 日志规范
│ └── linter-rules.md # Linter 自动化规则集(PMD/Checkstyle/ArchUnit/Maven Enforcer)
├── design/ # 功能设计文档
│ ├── _template.md # 设计文档模板
│ ├── feature-sync-single-request.md # 同步单笔请求
│ ├── feature-async-batch-sharding.md # 异步批量分片
│ ├── feature-vendor-adapter.md # 供应商适配器
│ ├── feature-rate-limiting.md # 限流
│ ├── feature-scheduler-framework.md # 调度框架
│ ├── feature-trace-propagation.md # traceId 传播
│ ├── feature-high-concurrency-query.md # 高并发查询
│ └── feature-automation-layer.md # 自动化层设计文档
├── reference/ # 参考资料
│ ├── prd-template.md # PRD 输入模板(Part A 业务层 + Part B 技术层)
│ ├── brd-to-prd-guide.md # BRD→PRD 转换指南(What vs How)
│ ├── api.spec.yaml # API 规范(OpenAPI 3.0)
│ ├── error-codes.md # 错误码定义
│ └── prd-high-Single-request.md # 高并发查询 PRD 示例(正确填写示范)
├── changes/ # 变更管理
│ ├── README.md # 变更管理层说明
│ ├── pr-checklist.md # PR 自检清单
│ └── impact-analysis.md # 影响分析指南
├── plans/ # 迭代计划
│ ├── current-sprint.md # 当前迭代
│ └── backlog.md # 待办列表
└── guides/ # Harness Engineering 搭建指南
├── harness-overview.md # 搭建指南总览
├── harness-step1-project-entry-and-architecture.md
├── harness-step2-rules-layer.md
├── harness-step3-wiki-layer.md
├── harness-step4-skills-layer.md
├── harness-step5-changes-layer.md
└── harness-step6-guide-and-automation.md
\```
### 5.3 项目入口
\```
项目根/
├── AGENTS.md # AI Agent 项目入口(技术栈基线 + 快速导航)
├── README.md # 项目整体说明
└── CHANGELOG.md # 变更记录
\```
### 5.4 自动化层脚本(build/automation/scripts/)
\```
build/automation/scripts/
├── pre-check.ps1 # 环境前置检查(JDK/Maven/Git/项目结构 5 项检查)
├── worktree-manager.ps1 # Git Worktree 自动化管理(create/list/clean/status)
├── collect-sidecar.ps1 # 客观侧性数据采集(构建/测试/Linter 结果 → JSON)
├── decay-detector.ps1 # 自动衰减检测(文档过期/代码-文档漂移/规则命中率/技术栈对齐)
├── governance-report.ps1 # 持续治理报告生成(汇总侧性数据 + 衰减报告 → Markdown)
└── index-sync-check.ps1 # 文档全景索引一致性检测(磁盘文件 vs §5 索引比对)
\```
### 5.5 Linter 规则配置(build/linter/)
\```
build/linter/
├── pmd-exchange-ruleset.xml # PMD 代码质量规则集(16 条规则,对应 linter-rules.md)
└── checkstyle-exchange.xml # Checkstyle 代码风格规则集(11 条规则,对应 linter-rules.md)
\```
---
## 6. 核心技术约束速查
以下约束在 `always-on.md` 中完整定义,AI Agent 在代码生成时必须严格遵守:
| 约束项 | 规则 | 禁止 |
|--------|------|------|
| 技术栈 | JDK 1.8 / Spring Boot 2.3.12 / MyBatis-Plus 3.4.3 | Java 9+ 语法、Spring 3.x、MP 3.5+ |
| JSON | FastJSON 1.2.83 | Jackson / Gson |
| HTTP | OkHttp3 + HttpClientFactory | RestTemplate / Apache HttpClient |
| 数据库 | DalManager.of(beanName) | 直接注入 Mapper |
| 事务 | TransactionTemplate 编程式 | @Transactional 注解 |
| 日志 | @Slf4j + 占位符格式 | LoggerFactory / System.out |
| 依赖注入 | @Resource / 构造器注入 | 字段级 @Autowired |
| 线程池 | BatchExecutorTemplate / ThreadPoolExecutorBuilder | 裸 new ThreadPoolExecutor |
| 限流 | Resilience4j + RateLimitFactory | 自行实现限流 |
| traceId | @MDC + ContextHolder / MDCTemplate | 线程切换丢失 traceId |
| 配置 | Nacos | 硬编码环境地址 / 密钥 |
| 模块依赖 | common → dal → business → ext → app → runner | 反向依赖 / 跨层引用 |
---
## 7. 实战示例:新增供应商对接
以下以"新增一个供应商适配器"为例,展示完整的 AI Coding 开发流程:
### 7.1 用户提供 PRD
开发者参照 `docs/reference/prd-template.md` 编写 PRD(业务人员填 Part A,技术细节留空由 AI Agent 推导),可参考 `docs/reference/brd-to-prd-guide.md` 了解 BRD→PRD 转换要点。编写完成后提交给 AI Agent。
### 7.2 AI Agent 执行流程
\```
Step 1: 加载 AGENTS.md + prd-template.md → 解析 PRD
↓
Step 2: 加载 boundaries.md + impact-analysis.md → 输出影响分析
· 确定需新增 exchange-ext-{供应商} 子模块
· 确定需修改 BizConfiguration.preInitAppConfig()
↓
Step 3: 加载 _template.md + data-flow.md + feature-vendor-adapter.md
→ 生成 docs/design/feature-{供应商}.md (status: draft)
↓
Step 4: 加载 design-review.md → 7 维度评审
→ ⚠️ 暂停,输出评审报告,等待用户确认
→ 用户确认「通过」→ status 改为 active
↓
Step 5: 加载 add-vendor-adapter.md → 按序生成代码
· dal 层: Entity → Mapper → Manager(如需自有结果表)
· ext 层: InputConvertor → HttpResultParser → PortResultConvertor
· app 层: BizConfiguration 注册 PortConfig
· 测试: JUnit 5 + Mockito 单元测试
↓
Step 6: 加载 error-codes.md + api.spec.yaml → 同步文档
· 新增错误码登记
· 新增 API 定义(如需)
· 更新 CHANGELOG.md
↓
Step 7: 加载 pr-checklist.md → 逐项自检
· 规则合规 ✓ · 代码质量 ✓ · 测试覆盖 ✓ · 文档同步 ✓ · 提交规范 ✓
→ 交付
\```
### 7.3 提交后
执行 `/doc-gardening` 检查文档健康度,确保所有 .md 文件的 front matter 完整、日期更新。
---
## 8. 文档元信息规范
所有项目 .md 文件头部必须包含 YAML front matter:
```yaml
---
last_updated: YYYY-MM-DD
status: active # active | deprecated | draft
owner: @username
---
\```
| 字段 | 说明 |
|------|------|
| `last_updated` | 最后更新日期,格式 YYYY-MM-DD |
| `status` | 文档状态:`active`(生效中)/ `draft`(草稿)/ `deprecated`(已废弃) |
| `owner` | 文档负责人 |
**过期检测规则**(由 `/doc-gardening` 执行):
- `last_updated` 距今 > 90 天 → 标记为 **stale**(过期)
- `last_updated` 距今 > 180 天 → 标记为 **critical**(严重过期)
- `status: draft` → 标记为 **待发布**
- `status: deprecated` → 标记为 **已废弃**
- `owner` 缺失 → 标记为 **orphan**(无主文档)
---
## 9. 协作机制总结
\```
开发者 AI Agent 文档体系
│ │ │
│ 1. 提交 PRD │ │
│ ──────────────────────▶ │ │
│ │ 2. 加载 AGENTS.md │
│ │ + prd-template.md │
│ │ ◀────────────────────── │
│ │ 3. 加载 boundaries.md │
│ │ + impact-analysis.md │
│ │ ◀────────────────────── │
│ │ 4. 加载 _template.md │
│ │ + data-flow.md │
│ │ ◀────────────────────── │
│ │ 5. 生成设计文档 │
│ │ ──────────────────────▶ │
│ │ 6. 加载 design-review │
│ │ ◀────────────────────── │
│ 7. 评审报告 + 设计摘要 │ │
│ ◀────────────────────── │ │
│ │ │
│ 8. 人工确认「通过」 │ │
│ ──────────────────────▶ │ │
│ │ 9. 加载 add-*.md skill │
│ │ ◀────────────────────── │
│ │ 10. 生成代码 + 测试 │
│ │ 11. 同步文档 │
│ │ ──────────────────────▶ │
│ │ 12. 加载 pr-checklist │
│ │ ◀────────────────────── │
│ 13. 自检结果 + 交付 │ │
│ ◀────────────────────── │ │
\```
**核心设计**:AI Agent 负责加载文档、生成代码和同步文档;开发者负责提供 PRD、确认设计评审和验收交付。文档体系是双方共享的知识桥梁,Rules 层约束 AI 行为,Skills 层指导 AI 操作,Wiki 层提供上下文,Changes 层保障变更质量。
4. 第二部分:Linter 自动化规则配置
4.1 定位
Linter 配置是 Rules 层硬性约束的自动化执行器 。always-on.md 中定义的规则,通过 PMD/Checkstyle/ArchUnit/Maven Enforcer 自动拦截。
4.2 PMD 代码质量规则集(pmd-exchange-ruleset.xml)
将 always-on.md 中的规则映射为 PMD 规则:
| always-on.md 规则 | PMD 规则 | 阻塞级别 |
|---|---|---|
| 禁止 System.out.println | SystemPrintln | error |
| 禁止空 catch 块 | EmptyCatchBlock | error |
| 禁止字符串拼接日志 | GuardLogStatement | warning |
| 单文件 ≤ 300 行 | ExcessiveLength | warning |
| 单方法 ≤ 80 行 | ExcessiveMethodLength | warning |
| 禁止未使用 import | UnusedImports | error |
XML 模板
xml
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Exchange Ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>Exchange project PMD rules</description>
<!-- 禁止 System.out.println -->
<rule ref="category/java/bestpractices.xml/SystemPrintln">
<priority>1</priority>
</rule>
<!-- 禁止空 catch 块 -->
<rule ref="category/java/errorprone.xml/EmptyCatchBlock">
<priority>1</priority>
</rule>
<!-- 单文件 ≤ 300 行 -->
<rule ref="category/java/design.xml/ExcessiveLength">
<properties>
<property name="minimum" value="300"/>
</properties>
<priority>2</priority>
</rule>
<!-- 单方法 ≤ 80 行 -->
<rule ref="category/java/design.xml/ExcessiveMethodLength">
<properties>
<property name="minimum" value="80"/>
</properties>
<priority>2</priority>
</rule>
</ruleset>
实际示例
通过agent生成
xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
PMD 代码质量规则集 --- app-exchange-port 项目
基于 .qoder/rules/always-on.md + java-code-style.md + java-testing.md + docs/conventions/ 中的禁止规则
每条规则的 message 遵循三要素格式:问题是什么 | 怎么修 | 去哪看文档
当 AI Agent 看到 PMD 报错时,无需额外提示即可根据 message 自动修复
-->
<ruleset name="exchange-pmd-ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>
app-exchange-port 项目 PMD 代码质量规则集。
覆盖日志规范、依赖注入、HTTP 调用、数据库访问、线程池、事务、异常处理、Lombok、测试规范共 9 大类 16 条规则。
规则 ID 格式:EXCHANGE-{类别}-{序号},对应 .md 文档中的禁止规则。
</description>
<!-- ================================================================ -->
<!-- 一、日志规范(来源:always-on.md#4, docs/conventions/logging.md) -->
<!-- ================================================================ -->
<!-- EXCHANGE-LOG-001: 禁止 LoggerFactory.getLogger() -->
<rule name="ExchangeNoLoggerFactory"
language="java"
message="[EXCHANGE-LOG-001] 禁止使用 LoggerFactory.getLogger()。问题:违反日志规范,项目统一使用 Lombok @Slf4j,禁止手动创建 Logger 实例。修复:1.在类上添加 @Slf4j 注解(import lombok.extern.slf4j.Slf4j) 2.删除 private static final Logger xxx = LoggerFactory.getLogger(...) 声明 3.将类中所有 xxx.error/warn/info/debug(...) 调用替换为 log.error/warn/info/debug(...) 4.删除 org.slf4j.Logger 和 org.slf4j.LoggerFactory 的 import。文档:.qoder/rules/always-on.md 第4节日志规范, docs/conventions/logging.md"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止使用 LoggerFactory.getLogger() 创建 Logger,必须使用 Lombok @Slf4j 注解</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression/PrimaryPrefix/Name[
@Image='LoggerFactory.getLogger'
or ends-with(@Image, '.LoggerFactory.getLogger')
]
]]></value>
</property>
</properties>
</rule>
<!-- EXCHANGE-LOG-002: 禁止 System.out.println() / System.err.println() -->
<rule name="ExchangeNoSystemOut"
language="java"
message="[EXCHANGE-LOG-002] 禁止使用 System.out.println() 或 System.err.println()。问题:违反日志规范,必须使用 @Slf4j 的 log.info/warn/error 记录日志,System.out 不走日志框架、无法关联 traceId、生产环境无法控制级别。修复:1.确认类上有 @Slf4j 注解 2.将 System.out.println(xxx) 替换为 log.info("描述,param={}", xxx) 3.将 System.err.println(xxx) 替换为 log.error("描述,param={}", xxx)。文档:.qoder/rules/always-on.md 第4节, docs/conventions/logging.md"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止使用 System.out.println() 和 System.err.println()</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression/PrimaryPrefix/Name[
starts-with(@Image, 'System.out')
or starts-with(@Image, 'System.err')
]
]]></value>
</property>
</properties>
</rule>
<!-- EXCHANGE-LOG-003: 禁止 e.printStackTrace() -->
<rule name="ExchangeNoPrintStackTrace"
language="java"
message="[EXCHANGE-LOG-003] 禁止使用 e.printStackTrace()。问题:违反异常处理规范,printStackTrace 输出到 stderr 不走日志框架、无法关联 traceId、生产环境无法收集。修复:1.确认类上有 @Slf4j 注解 2.将 e.printStackTrace() 替换为 log.error("描述,param={}", param, e) --- 异常对象 e 必须作为最后一个参数。文档:.qoder/rules/always-on.md 第4节, docs/conventions/error-handling.md"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止使用 e.printStackTrace(),必须使用 log.error 记录异常</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression/PrimaryPrefix/Name[
@Image='printStackTrace'
or ends-with(@Image, '.printStackTrace')
]
]]></value>
</property>
</properties>
</rule>
<!-- EXCHANGE-LOG-004: 禁止日志字符串拼接 -->
<rule name="ExchangeNoLogStringConcat"
language="java"
message="[EXCHANGE-LOG-004] 禁止日志中使用字符串拼接(+)。问题:违反日志规范,必须使用占位符 {} 格式,字符串拼接在日志级别关闭时仍会执行拼接造成性能浪费。修复:将 log.xxx("planId:" + planId + " 失败") 替换为 log.xxx("planId={}, 失败", planId)。文档:.qoder/rules/always-on.md 第4节, docs/conventions/logging.md"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止在日志调用中使用字符串拼接(+),必须使用 {} 占位符</description>
<priority>2</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression[
PrimaryPrefix/Name[@Image='log.error' or @Image='log.warn' or @Image='log.info' or @Image='log.debug'
or ends-with(@Image, '.log.error') or ends-with(@Image, '.log.warn')
or ends-with(@Image, '.log.info') or ends-with(@Image, '.log.debug')]
and PrimarySuffix/Arguments//AdditiveExpression[@Operator='+']
]
]]></value>
</property>
</properties>
</rule>
<!-- ================================================================ -->
<!-- 二、依赖注入(来源:always-on.md#3) -->
<!-- ================================================================ -->
<!-- EXCHANGE-DI-001: 禁止字段级 @Autowired -->
<rule name="ExchangeNoFieldAutowired"
language="java"
message="[EXCHANGE-DI-001] 禁止在字段上使用 @Autowired。问题:违反依赖注入规范,字段注入导致无法声明 final、难以单元测试、隐藏依赖关系。修复:方案一(推荐):改为构造器注入,将字段声明为 private final,删除 @Autowired,在构造器上无需加 @Autowired(Spring 4.3+ 单构造器自动注入)。方案二:将 @Autowired 替换为 @Resource(javax.annotation.Resource)。文档:.qoder/rules/always-on.md 第3节依赖注入"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止在字段声明上使用 @Autowired 注解</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//FieldDeclaration//Annotation/Name[
@Image='Autowired'
or @Image='org.springframework.beans.factory.annotation.Autowired'
]
]]></value>
</property>
</properties>
</rule>
<!-- EXCHANGE-DI-002: 禁止构造器上的 @Autowired -->
<rule name="ExchangeNoConstructorAutowired"
language="java"
message="[EXCHANGE-DI-002] 禁止在构造器上使用 @Autowired。问题:Spring 4.3+ 单构造器自动注入,@Autowired 注解冗余。修复:直接删除构造器上的 @Autowired 注解即可。文档:.qoder/rules/always-on.md 第3节依赖注入"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止在构造器上使用 @Autowired 注解(Spring 4.3+ 自动注入)</description>
<priority>2</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//ConstructorDeclaration//Annotation/Name[
@Image='Autowired'
or @Image='org.springframework.beans.factory.annotation.Autowired'
]
]]></value>
</property>
</properties>
</rule>
<!-- ================================================================ -->
<!-- 三、HTTP 调用(来源:always-on.md#5) -->
<!-- ================================================================ -->
<!-- EXCHANGE-HTTP-001: 禁止 new OkHttpClient() -->
<rule name="ExchangeNoNewOkHttpClient"
language="java"
message="[EXCHANGE-HTTP-001] 禁止直接 new OkHttpClient()。问题:违反 HTTP 调用规范,必须通过 HttpClientFactory 获取 OkHttp 客户端,直接 new 会创建无法复用连接池的实例、缺少监控拦截器。修复:1.注入 HttpClientFactory(@Resource private HttpClientFactory httpClientFactory) 2.将 new OkHttpClient() 替换为 httpClientFactory.getClient() 或 httpClientFactory.getMonitorClient()。文档:.qoder/rules/always-on.md 第5节HTTP调用, docs/architecture/boundaries.md"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止直接 new OkHttpClient(),必须通过 HttpClientFactory 获取</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[
@Image='OkHttpClient'
]
]]></value>
</property>
</properties>
</rule>
<!-- ================================================================ -->
<!-- 四、数据库访问(来源:always-on.md#6, docs/conventions/error-handling.md) -->
<!-- ================================================================ -->
<!-- EXCHANGE-DB-001: 禁止 @Transactional -->
<rule name="ExchangeNoTransactional"
language="java"
message="[EXCHANGE-DB-001] 禁止使用 @Transactional 注解。问题:@Transactional 与动态数据源(Dynamic Datasource)不兼容,导致数据源切换失效。修复:1.删除 @Transactional 注解 2.注入 TransactionTemplate(@Resource private TransactionTemplate transactionTemplate) 3.使用编程式事务:transactionTemplate.execute(transaction -> { try { // 业务操作 } catch (Exception e) { transaction.setRollbackOnly(); log.error("描述", e); return false; } return true; })。文档:.qoder/rules/always-on.md 第10节事务, docs/conventions/error-handling.md#事务处理"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止使用 @Transactional 注解,必须使用 TransactionTemplate 编程式事务</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//Annotation/Name[
@Image='Transactional'
or @Image='org.springframework.transaction.annotation.Transactional'
]
]]></value>
</property>
</properties>
</rule>
<!-- ================================================================ -->
<!-- 五、线程池(来源:always-on.md#7) -->
<!-- ================================================================ -->
<!-- EXCHANGE-THREAD-001: 禁止裸 new ThreadPoolExecutor -->
<rule name="ExchangeNoNewThreadPoolExecutor"
language="java"
message="[EXCHANGE-THREAD-001] 禁止直接 new ThreadPoolExecutor()。问题:违反线程池规范,必须使用 BatchExecutorTemplate 或 ThreadPoolExecutorBuilder(exchange-common/juc),裸 new 无法统一监控、无法优雅停机。修复:方案一(批量任务):注入 BatchExecutorTemplate,使用 batchExecutorTemplate.execute(tasks, callback)。方案二(自定义线程池):使用 ThreadPoolExecutorBuilder.builder().name("xxx").coreSize(n).maxSize(n).build()。文档:.qoder/rules/always-on.md 第7节线程池, docs/architecture/boundaries.md#exchange-common"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止直接 new ThreadPoolExecutor(),必须使用 BatchExecutorTemplate 或 ThreadPoolExecutorBuilder</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[
@Image='ThreadPoolExecutor'
]
]]></value>
</property>
</properties>
</rule>
<!-- EXCHANGE-THREAD-002: 禁止 new ForkJoinPool -->
<rule name="ExchangeNoNewForkJoinPool"
language="java"
message="[EXCHANGE-THREAD-002] 禁止直接 new ForkJoinPool()。问题:违反线程池规范,ForkJoinPool 不受项目统一管理、无法关联 traceId、无法优雅停机。修复:使用 ThreadPoolExecutorBuilder(exchange-common/juc) 创建受管线程池,或使用 BatchExecutorTemplate。文档:.qoder/rules/always-on.md 第7节线程池"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止直接 new ForkJoinPool(),必须使用项目统一线程池</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[
@Image='ForkJoinPool'
]
]]></value>
</property>
</properties>
</rule>
<!-- ================================================================ -->
<!-- 六、异常处理(来源:java-code-style.md, docs/conventions/error-handling.md) -->
<!-- ================================================================ -->
<!-- EXCHANGE-CATCH-001: 禁止空 catch 块 --- 使用 PMD 内置规则 -->
<rule ref="category/java/errorprone.xml/EmptyCatchBlock">
<message>[EXCHANGE-CATCH-001] 禁止空 catch 块。问题:违反异常处理规范,空 catch 会吞掉异常导致问题难以排查。修复:方案一(记录日志):catch 块中添加 log.error("描述,param={}", param, e)。方案二(向上抛出):catch 块中 throw new RuntimeException(e) 或自定义业务异常。方案三(返回错误码):catch 块中 return ResponseDTO.errorInstance(CodeEnum.UNKNOWN, e.getMessage())。文档:.qoder/rules/java-code-style.md#异常处理, docs/conventions/error-handling.md#异常处理原则</message>
</rule>
<!-- ================================================================ -->
<!-- 七、Lombok 使用(来源:java-code-style.md) -->
<!-- ================================================================ -->
<!-- EXCHANGE-LOMBOK-001: 禁止 @AllArgsConstructor + @NoArgsConstructor 组合 -->
<rule name="ExchangeNoAllArgsNoArgsCombo"
language="java"
message="[EXCHANGE-LOMBOK-001] 检测到 @AllArgsConstructor,请确认是否同时使用了 @NoArgsConstructor。问题:禁止使用 @AllArgsConstructor + @NoArgsConstructor 组合替代 @Data,该组合无法生成 toString/equals/hashCode 且语义不清。修复:1.删除 @AllArgsConstructor 和 @NoArgsConstructor 2.在类上添加 @Data 注解 3.如需构建器,添加 @Builder + @Builder.Default。文档:.qoder/rules/java-code-style.md#Lombok-使用"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>检测 @AllArgsConstructor 使用,提示确认是否违反 @Data 替代规范</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//Annotation/Name[
@Image='AllArgsConstructor'
or @Image='lombok.AllArgsConstructor'
]
]]></value>
</property>
</properties>
</rule>
<!-- ================================================================ -->
<!-- 八、测试规范(来源:java-testing.md) -->
<!-- ================================================================ -->
<!-- EXCHANGE-TEST-001: 禁止 JUnit 4 @RunWith -->
<rule name="ExchangeNoRunWith"
language="java"
message="[EXCHANGE-TEST-001] 禁止使用 JUnit 4 的 @RunWith 注解。问题:违反测试规范,项目使用 JUnit 5 (Jupiter),@RunWith 是 JUnit 4 注解不兼容。修复:1.删除 @RunWith(Xxx.class) 2.添加 @ExtendWith(MockitoExtension.class)(import org.junit.jupiter.api.extension.ExtendWith) 3.将 import org.junit.runner.RunWith 替换为 import org.junit.jupiter.api.extension.ExtendWith 4.将 org.junit.Test 替换为 org.junit.jupiter.api.Test。文档:.qoder/rules/java-testing.md#框架"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止使用 JUnit 4 的 @RunWith 注解,必须使用 JUnit 5 的 @ExtendWith</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//Annotation/Name[
@Image='RunWith'
or @Image='org.junit.runner.RunWith'
]
]]></value>
</property>
</properties>
</rule>
<!-- EXCHANGE-TEST-002: 禁止 Thread.sleep() -->
<rule name="ExchangeNoThreadSleep"
language="java"
message="[EXCHANGE-TEST-002] 禁止在测试中使用 Thread.sleep()。问题:违反测试规范,Thread.sleep 会导致测试不稳定(时序依赖)、执行缓慢。修复:方案一(等待异步结果):使用 CountDownLatch + await() 替代 Thread.sleep。方案二(同步执行):将异步逻辑改为同步调用直接验证结果。文档:.qoder/rules/java-testing.md#禁止"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止在测试代码中使用 Thread.sleep(),必须使用 CountDownLatch 或同步执行</description>
<priority>1</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression/PrimaryPrefix/Name[
@Image='Thread.sleep'
or ends-with(@Image, '.Thread.sleep')
]
]]></value>
</property>
</properties>
</rule>
<!-- EXCHANGE-TEST-003: 禁止直接注入 Mapper(在非 dal 模块中) -->
<rule name="ExchangeNoDirectMapperInjection"
language="java"
message="[EXCHANGE-TEST-003] 检测到直接注入 Mapper。问题:违反数据库访问规范,必须通过 DalManager.of(beanName) 获取 Manager 实例,禁止直接注入 Mapper。修复:1.删除 @Resource/@Autowired private XxxMapper xxxMapper 2.使用 DalManager.of("xxxManagerImpl") 获取 Manager 3.将 xxxMapper.selectById(id) 替换为 manager.getById(id)。文档:.qoder/rules/always-on.md 第6节数据库访问, docs/architecture/boundaries.md#跨模块约束"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>检测直接注入 Mapper(应通过 DalManager.of() 获取 Manager)</description>
<priority>2</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//FieldDeclaration[
Type/ReferenceType/ClassOrInterfaceType[
ends-with(@Image, 'Mapper')
and not(starts-with(@Image, 'Base'))
]
]
]]></value>
</property>
</properties>
</rule>
<!-- ================================================================ -->
<!-- 九、代码质量(来源:conventions/README.md, pr-checklist.md) -->
<!-- ================================================================ -->
<!-- EXCHANGE-QUALITY-001: 禁止 System.exit() 在非启动类中调用 -->
<rule name="ExchangeNoSystemExit"
language="java"
message="[EXCHANGE-QUALITY-001] 禁止在业务代码中使用 System.exit()。问题:System.exit 会强制终止 JVM,影响其他正在执行的线程和请求。修复:1.删除 System.exit() 调用 2.使用异常或返回值通知调用方处理 3.如需优雅停机,使用 exchange-common/shutdown 模块。文档:.qoder/rules/always-on.md, docs/changes/pr-checklist.md"
class="net.sourceforge.pmd.lang.rule.XPathRule">
<description>禁止在业务代码中使用 System.exit()</description>
<priority>2</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//PrimaryExpression/PrimaryPrefix/Name[
@Image='System.exit'
or ends-with(@Image, '.System.exit')
]
]]></value>
</property>
</properties>
</rule>
</ruleset>
4.3 Checkstyle 代码风格规则集(checkstyle-exchange.xml)
将 java-code-style.md 中的规则映射为 Checkstyle 规则:
| 规则 | Checkstyle 模块 | 阻塞级别 |
|---|---|---|
| 类注释必须存在 | JavadocType | error |
| 方法注释必须存在 | JavadocMethod | error |
| 禁止未使用 import | UnusedImports | error |
| import 排序规范 | CustomImportOrder | warning |
实际示例
通过agent生成
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle 代码风格配置 --- app-exchange-port 项目
基于 .qoder/rules/always-on.md + java-code-style.md 中的 import 禁止规则和命名规范
每条规则的 message 遵循三要素格式:问题是什么 | 怎么修 | 去哪看文档
当 AI Agent 看到 Checkstyle 报错时,无需额外提示即可根据 message 自动修复
-->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="error"/>
<property name="fileExtensions" value="java"/>
<!-- ================================================================ -->
<!-- 文件级检查 -->
<!-- ================================================================ -->
<!-- EXCHANGE-CS-FILE-001: 文件行数不超过 300 行 -->
<module name="FileLength">
<property name="max" value="300"/>
<message key="maxLen.fileLength"
value="[EXCHANGE-CS-FILE-001] 文件行数超过 300 行限制(当前 {0} 行)。问题:违反代码质量规范,单文件超过 300 行难以维护。修复:1.按职责拆分为多个类 2.将策略逻辑提取为独立的策略类 3.将通用方法提取为工具类。文档:always-on.md#13, docs/conventions/README.md#通用原则"/>
</module>
<!-- ================================================================ -->
<!-- Import 检查(来源:always-on.md#1 FastJSON 唯一, always-on.md#5 HTTP) -->
<!-- ================================================================ -->
<module name="TreeWalker">
<!-- EXCHANGE-CS-001: 禁止 import Jackson -->
<module name="IllegalImport">
<property name="illegalPkgs" value="com.fasterxml.jackson, org.codehaus.jackson"/>
<message key="import.illegal"
value="[EXCHANGE-CS-001] 禁止 import Jackson({0})。问题:违反技术栈规范,项目统一使用 FastJSON(com.alibaba.fastjson.JSON) 作为唯一 JSON 序列化库,禁止引入 Jackson。修复:1.删除该 import 语句 2.将 ObjectMapper 替换为 com.alibaba.fastjson.JSON 3.将 @JsonProperty 替换为 @JSONField 4.将 @JsonIgnore 替换为 @JSONField(serialize=false)。文档:.qoder/rules/always-on.md 第1节技术栈锁定"/>
</module>
<!-- EXCHANGE-CS-002: 禁止 import Gson -->
<module name="IllegalImport">
<property name="illegalPkgs" value="com.google.gson"/>
<message key="import.illegal"
value="[EXCHANGE-CS-002] 禁止 import Gson({0})。问题:违反技术栈规范,项目统一使用 FastJSON(com.alibaba.fastjson.JSON) 作为唯一 JSON 序列化库,禁止引入 Gson。修复:1.删除该 import 语句 2.将 Gson() 替换为 com.alibaba.fastjson.JSON 3.将 fromJson(str, Cls.class) 替换为 JSON.parseObject(str, Cls.class) 4.将 toJson(obj) 替换为 JSON.toJSONString(obj)。文档:.qoder/rules/always-on.md 第1节技术栈锁定"/>
</module>
<!-- EXCHANGE-CS-003: 禁止 import RestTemplate -->
<module name="IllegalImport">
<property name="illegalPkgs" value="org.springframework.web.client.RestTemplate"/>
<message key="import.illegal"
value="[EXCHANGE-CS-003] 禁止 import RestTemplate({0})。问题:违反 HTTP 调用规范,项目统一使用 OkHttp3 + HttpClientFactory(exchange-common/http),禁止使用 RestTemplate。修复:1.删除该 import 语句 2.注入 HttpClientFactory(@Resource private HttpClientFactory httpClientFactory) 3.使用 httpClientFactory.getClient() 获取 OkHttpClient 4.构建 Request + 使用 client.newCall(request).execute() 发起请求。文档:.qoder/rules/always-on.md 第5节HTTP调用"/>
</module>
<!-- EXCHANGE-CS-004: 禁止 import Apache HttpClient -->
<module name="IllegalImport">
<property name="illegalPkgs" value="org.apache.http.client, org.apache.http.impl"/>
<message key="import.illegal"
value="[EXCHANGE-CS-004] 禁止 import Apache HttpClient({0})。问题:违反 HTTP 调用规范,项目统一使用 OkHttp3 + HttpClientFactory(exchange-common/http),禁止使用 Apache HttpClient。修复:1.删除该 import 语句 2.注入 HttpClientFactory 3.使用 OkHttp3 API 替换 Apache HttpClient API。文档:.qoder/rules/always-on.md 第5节HTTP调用"/>
</module>
<!-- EXCHANGE-CS-005: 禁止 import java.util.logging -->
<module name="IllegalImport">
<property name="illegalPkgs" value="java.util.logging"/>
<message key="import.illegal"
value="[EXCHANGE-CS-005] 禁止 import java.util.logging({0})。问题:违反日志规范,项目统一使用 Lombok @Slf4j(Logback 实现),禁止使用 JUL。修复:1.删除该 import 语句 2.在类上添加 @Slf4j 注解 3.使用 log.info/warn/error 替代 Logger.info/warning/severe。文档:docs/conventions/logging.md#日志框架"/>
</module>
<!-- EXCHANGE-CS-006: 禁止 import org.junit.runner(JUnit 4) -->
<module name="IllegalImport">
<property name="illegalPkgs" value="org.junit.runner, org.junit.runners"/>
<message key="import.illegal"
value="[EXCHANGE-CS-006] 禁止 import JUnit 4 包({0})。问题:违反测试规范,项目使用 JUnit 5 (Jupiter),禁止使用 JUnit 4。修复:1.删除该 import 语句 2.将 org.junit.runner.RunWith 替换为 org.junit.jupiter.api.extension.ExtendWith 3.将 org.junit.Test 替换为 org.junit.jupiter.api.Test 4.将 org.junit.Assert 替换为 org.junit.jupiter.api.Assertions。文档:.qoder/rules/java-testing.md#框架"/>
</module>
<!-- EXCHANGE-CS-007: 禁止 import org.junit.Assert(JUnit 4 断言) -->
<module name="IllegalImport">
<property name="illegalPkgs" value="org.junit.Assert"/>
<message key="import.illegal"
value="[EXCHANGE-CS-007] 禁止 import org.junit.Assert({0})。问题:违反测试规范,JUnit 5 使用 org.junit.jupiter.api.Assertions 进行断言。修复:1.删除 import org.junit.Assert 2.添加 import org.junit.jupiter.api.Assertions 3.将 Assert.assertEquals(a, b) 替换为 Assertions.assertEquals(a, b) 4.将 Assert.assertNotNull(x) 替换为 Assertions.assertNotNull(x)。文档:.qoder/rules/java-testing.md#框架"/>
</module>
<!-- EXCHANGE-CS-008: 检测未使用的 import -->
<module name="UnusedImports">
<message key="import.unused"
value="[EXCHANGE-CS-008] 存在未使用的 import({0})。问题:违反代码质量规范,未使用的 import 会增加代码噪音。修复:直接删除该 import 语句。文档:docs/changes/pr-checklist.md#代码质量检查"/>
</module>
<!-- EXCHANGE-CS-009: Import 排序规范 -->
<module name="CustomImportOrder">
<property name="customImportOrderRules"
value="STATIC###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE"/>
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
<message key="custom.import.order"
value="[EXCHANGE-CS-009] Import 顺序不符合规范。问题:违反代码风格规范,import 应按 静态导入 ### 标准Java包 ### 第三方包 分组排列,组内字母排序,组间空行分隔。修复:1.将 static import 放在最前 2.将 java.* / javax.* 放在第二组 3.将 com.* / org.* 等第三方包放在第三组 4.组内按字母排序 5.组间添加空行。文档:docs/conventions/README.md"/>
</module>
<!-- EXCHANGE-CS-010: 禁止通配符 import -->
<module name="AvoidStarImport">
<message key="import.avoidStar"
value="[EXCHANGE-CS-010] 禁止通配符 import({0})。问题:违反代码风格规范,通配符 import 会隐藏依赖关系并导致冲突。修复:将 import xxx.* 替换为逐个显式 import,只导入实际使用的类。文档:java-code-style.md#import 规范"/>
</module>
</module>
</module>
4.4 ArchUnit 架构测试
使用 ArchUnit 自动验证模块依赖方向:
java
@AnalyzeClasses(packages = "com.jzsk.exchange")
public class ArchitectureRulesTest {
@ArchTest
static final ArchRule 依赖方向不可逆 = layeredArchitecture()
.layer("common").definedBy("..common..")
.layer("dal").definedBy("..dal..")
.layer("business").definedBy("..business..")
.layer("ext").definedBy("..ext..")
.layer("app").definedBy("..app..")
.layer("runner").definedBy("..runner..")
.whereLayer("common").mayNotAccessAnyLayer()
.whereLayer("dal").mayOnlyAccessLayers("common")
.whereLayer("business").mayOnlyAccessLayers("dal", "common")
.whereLayer("ext").mayOnlyAccessLayers("business")
.whereLayer("app").mayOnlyAccessLayers("ext", "business", "dal", "common")
.whereLayer("runner").mayOnlyAccessLayers("app");
}
4.5 Maven Enforcer 依赖管理
使用 Maven Enforcer Plugin 强制依赖方向:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>enforce-dependency-direction</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<bannedDependencies>
<excludes>
<!-- common 不允许依赖任何项目内模块 -->
<exclude>com.jzsk.exchange:*</exclude>
</excludes>
</bannedDependencies>
</rules>
</configuration>
</execution>
</executions>
</plugin>
5. 第三部分:自动化治理脚本
5.1 pre-check.ps1 --- 环境前置检查
触发时机 :prd-to-code.md Step 0.1
检查项:
| 检查项 | 检查方式 | 不通过时 |
|---|---|---|
| JDK 版本 | java -version |
阻断,提示安装 JDK 1.8 |
| Maven 版本 | mvn -version |
阻断,提示安装 Maven 3.6+ |
| Git 已配置 | git config user.name |
阻断,提示配置 Git |
| 项目结构 | 检查 pom.xml 存在 | 阻断,提示目录错误 |
| .qoder/rules/ 存在 | 检查目录 | 警告,提示 Rules 层未建立 |
输出格式:JSON,包含每项检查的 pass/fail 结果。
实际示例
通过agent生成
bash
<#
.SYNOPSIS
环境前置检查脚本 --- 在 prd-to-code Step 0 中执行,确保环境就绪
.DESCRIPTION
Harness 自动化层组件之五:检查 JDK 1.8、Maven 3.6+、Git 是否可用,
检查项目是否可编译(快速编译检查),检查 build/automation/scripts/ 脚本是否存在。
全部通过返回 exit 0,任一项不通过返回 exit 1 并输出缺失项。
.EXAMPLE
.\pre-check.ps1
#>
$ErrorActionPreference = "Stop"
Write-Host "[PRECHECK] 开始环境前置检查..." -ForegroundColor Cyan
$allPass = $true
# ── 1. JDK 检查 ──
Write-Host "[PRECHECK] [1/5] JDK 检查..." -NoNewline
$javaVersion = & java -version 2>&1 | Out-String
if ($javaVersion -match 'version "1\.8') {
Write-Host " PASS (JDK 1.8)" -ForegroundColor Green
} else {
Write-Host " FAIL" -ForegroundColor Red
Write-Host " 期望: JDK 1.8, 实际: $($javaVersion.Split("`n")[0])" -ForegroundColor Red
Write-Host " 修复: 安装 JDK 1.8 并设置 JAVA_HOME" -ForegroundColor Yellow
$allPass = $false
}
# ── 2. Maven 检查 ──
Write-Host "[PRECHECK] [2/5] Maven 检查..." -NoNewline
$mvnVersion = & mvn --version 2>&1 | Out-String
if ($mvnVersion -match 'Apache Maven (\d+\.\d+)') {
$mvnVer = $Matches[1]
if ([double]$mvnVer -ge 3.6) {
Write-Host " PASS (Maven $mvnVer)" -ForegroundColor Green
} else {
Write-Host " FAIL (Maven $mvnVer < 3.6)" -ForegroundColor Red
Write-Host " 修复: 升级 Maven 到 3.6+" -ForegroundColor Yellow
$allPass = $false
}
} else {
Write-Host " FAIL (未找到 Maven)" -ForegroundColor Red
Write-Host " 修复: 安装 Maven 并添加到 PATH" -ForegroundColor Yellow
$allPass = $false
}
# ── 3. Git 检查 ──
Write-Host "[PRECHECK] [3/5] Git 检查..." -NoNewline
$gitVersion = & git --version 2>&1 | Out-String
if ($gitVersion -match 'git version') {
Write-Host " PASS ($($gitVersion.Trim()))" -ForegroundColor Green
} else {
Write-Host " FAIL" -ForegroundColor Red
Write-Host " 修复: 安装 Git 并添加到 PATH" -ForegroundColor Yellow
$allPass = $false
}
# ── 4. 项目根目录检查 ──
Write-Host "[PRECHECK] [4/5] 项目结构检查..." -NoNewline
$repoRoot = (git rev-parse --show-toplevel 2>$null | Out-String).Trim()
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "pom.xml"))) {
Write-Host " FAIL (未找到 pom.xml)" -ForegroundColor Red
Write-Host " 修复: 请在 Git 仓库根目录执行" -ForegroundColor Yellow
$allPass = $false
} else {
Write-Host " PASS ($repoRoot)" -ForegroundColor Green
}
# ── 5. 自动化脚本检查 ──
Write-Host "[PRECHECK] [5/5] 自动化脚本检查..." -NoNewline
$requiredScripts = @(
"build\automation\scripts\collect-sidecar.ps1",
"build\automation\scripts\decay-detector.ps1",
"build\automation\scripts\governance-report.ps1",
"build\automation\scripts\worktree-manager.ps1"
)
$missingScripts = @()
foreach ($script in $requiredScripts) {
$fullPath = Join-Path $repoRoot $script
if (-not (Test-Path $fullPath)) { $missingScripts += $script }
}
if ($missingScripts.Count -eq 0) {
Write-Host " PASS (4/4 脚本就绪)" -ForegroundColor Green
} else {
Write-Host " FAIL ($($missingScripts.Count) 个缺失)" -ForegroundColor Red
foreach ($s in $missingScripts) { Write-Host " 缺失: $s" -ForegroundColor Yellow }
$allPass = $false
}
# ── 汇总 ──
Write-Host ""
if ($allPass) {
Write-Host "[PRECHECK] 全部检查通过,环境就绪。" -ForegroundColor Green
exit 0
} else {
Write-Host "[PRECHECK] 检查未通过,请修复上述问题后重试。" -ForegroundColor Red
exit 1
}
5.2 worktree-manager.ps1 --- Git Worktree 自动化
触发时机 :prd-to-code.md Step 0.2
功能:
| 命令 | 功能 |
|---|---|
create "feature-{name}" |
创建隔离 worktree |
list |
列出所有 worktree |
clean |
清理已合并的 worktree |
status |
检查 worktree 合并就绪状态 |
实际示例
通过agent生成
bash
<#
.SYNOPSIS
Git Worktree 自动化管理脚本 --- 为 AI Agent 任务创建隔离工作区
.DESCRIPTION
Harness 自动化层组件之一:管理 Git Worktree 的完整生命周期。
- create: 基于 PRD 名称创建隔离 worktree + 分支
- list: 列出所有活跃 worktree
- clean: 清理过期 worktree(默认 >7 天)
- status: 检查 worktree 是否合并就绪(Linter + 测试通过)
.EXAMPLE
.\worktree-manager.ps1 create "feature-umeng-query"
.\worktree-manager.ps1 list
.\worktree-manager.ps1 clean 7
.\worktree-manager.ps1 status "feature-umeng-query"
#>
param(
[Parameter(Position = 0)]
[ValidateSet("create", "list", "clean", "status")]
[string]$Action = "list",
[Parameter(Position = 1)]
[string]$BranchName,
[Parameter(Position = 2)]
[int]$MaxDays = 7
)
$ErrorActionPreference = "Stop"
$repoRoot = (git rev-parse --show-toplevel 2>$null | Out-String).Trim()
if (-not $repoRoot) {
Write-Error "[WORKTREE] 无法确定 Git 仓库根目录。请在仓库内执行此脚本。"
exit 1
}
$worktreeBase = Join-Path $repoRoot "..\.worktrees"
if (-not (Test-Path $worktreeBase)) {
New-Item -ItemType Directory -Path $worktreeBase -Force | Out-Null
}
function New-Worktree {
param([string]$Name)
if (-not $Name) {
Write-Error "[WORKTREE] 用法: worktree-manager.ps1 create <branch-name>"
exit 1
}
$timestamp = Get-Date -Format "yyyyMMdd-HHmm"
$fullBranch = "ai-task/$Name-$timestamp"
$worktreePath = Join-Path $worktreeBase ($Name -replace '[^a-zA-Z0-9-]', '-')
if (Test-Path $worktreePath) {
$worktreePath = "${worktreePath}-$timestamp"
}
Write-Host "[WORKTREE] 创建隔离工作区:" -ForegroundColor Cyan
Write-Host " 分支: $fullBranch"
Write-Host " 路径: $worktreePath"
git worktree add -b $fullBranch $worktreePath 2>&1 | ForEach-Object { Write-Host " $_" }
if ($LASTEXITCODE -eq 0) {
$meta = @{
branch = $fullBranch
path = $worktreePath
createdAt = (Get-Date).ToString("o")
task = $Name
}
$meta | ConvertTo-Json | Set-Content (Join-Path $worktreePath ".worktree-meta.json")
Write-Host "[WORKTREE] 创建成功。AI Agent 可在 $worktreePath 中工作。" -ForegroundColor Green
Write-Host "[WORKTREE] 元信息已写入 .worktree-meta.json" -ForegroundColor DarkGray
} else {
Write-Error "[WORKTREE] 创建失败,请检查 Git 状态。"
}
}
function Show-Worktrees {
Write-Host "[WORKTREE] 活跃工作区列表:" -ForegroundColor Cyan
$output = git worktree list --porcelain 2>$null
if (-not $output) {
Write-Host " (无活跃 worktree)"
return
}
$current = @{}
foreach ($line in ($output -split "`n")) {
if ($line -match '^worktree\s+(.+)') { $current.path = $Matches[1] }
elseif ($line -match '^branch\s+(.+)') { $current.branch = $Matches[1] }
elseif ($line -match '^HEAD\s+(.+)') {
$current.head = $Matches[1].Substring(0, 8)
$metaFile = Join-Path $current.path ".worktree-meta.json"
$task = "-"
$age = "-"
if (Test-Path $metaFile) {
$meta = Get-Content $metaFile | ConvertFrom-Json
$task = $meta.task
$created = [DateTime]::Parse($meta.createdAt)
$age = [math]::Round(((Get-Date) - $created).TotalDays, 1)
}
Write-Host (" {0,-40} {1,-45} age={2}d task={3}" -f $current.branch, $current.path, $age, $task)
$current = @{}
}
}
}
function Remove-StaleWorktrees {
param([int]$Days)
Write-Host "[WORKTREE] 清理 ${Days} 天前的过期工作区:" -ForegroundColor Yellow
$output = git worktree list --porcelain 2>$null
if (-not $output) { Write-Host " (无活跃 worktree)"; return }
$worktrees = @()
$current = @{}
foreach ($line in ($output -split "`n")) {
if ($line -match '^worktree\s+(.+)') { $current.path = $Matches[1] }
elseif ($line -match '^branch\s+(.+)') { $current.branch = $Matches[1] }
elseif ($line -match '^HEAD\s+(.+)') {
$current.head = $Matches[1]
$worktrees += [PSCustomObject]$current
$current = @{}
}
}
$cleaned = 0
foreach ($wt in $worktrees) {
$metaFile = Join-Path $wt.path ".worktree-meta.json"
$shouldClean = $false
$task = "unknown"
if (Test-Path $metaFile) {
$meta = Get-Content $metaFile | ConvertFrom-Json
$task = $meta.task
$created = [DateTime]::Parse($meta.createdAt)
$age = ((Get-Date) - $created).TotalDays
if ($age -gt $Days) { $shouldClean = $true }
}
if ($shouldClean) {
Write-Host " 清理: $($wt.branch) (task=$task, age=$([math]::Round($age,1))d)" -ForegroundColor Yellow
git worktree remove --force $wt.path 2>&1 | Out-Null
git branch -D $wt.branch 2>&1 | Out-Null
$cleaned++
}
}
if ($cleaned -eq 0) { Write-Host " 无过期工作区需要清理。" -ForegroundColor Green }
else { Write-Host "[WORKTREE] 已清理 $cleaned 个过期工作区。" -ForegroundColor Green }
}
function Test-MergeReady {
param([string]$Name)
$output = git worktree list --porcelain 2>$null
$targetPath = $null
$current = @{}
foreach ($line in ($output -split "`n")) {
if ($line -match '^worktree\s+(.+)') { $current.path = $Matches[1] }
elseif ($line -match '^branch\s+(.+)') { $current.branch = $Matches[1] }
elseif ($line -match '^HEAD\s+(.+)') {
if ($current.branch -like "*$Name*") { $targetPath = $current.path; break }
$current = @{}
}
}
if (-not $targetPath) {
Write-Error "[WORKTREE] 未找到匹配 '$Name' 的工作区。"
exit 1
}
Write-Host "[WORKTREE] 检查合并就绪状态: $Name" -ForegroundColor Cyan
Write-Host " 路径: $targetPath"
$checks = @()
Push-Location $targetPath
try {
Write-Host " [1/3] 编译检查..." -NoNewline
$compileResult = mvn compile -q -f $repoRoot 2>&1
if ($LASTEXITCODE -eq 0) { Write-Host " PASS" -ForegroundColor Green; $checks += "compile:PASS" }
else { Write-Host " FAIL" -ForegroundColor Red; $checks += "compile:FAIL" }
Write-Host " [2/3] Linter 检查..." -NoNewline
$pmdResult = mvn pmd:check -q -f $repoRoot 2>&1
if ($LASTEXITCODE -eq 0) { Write-Host " PASS" -ForegroundColor Green; $checks += "pmd:PASS" }
else { Write-Host " FAIL" -ForegroundColor Red; $checks += "pmd:FAIL" }
Write-Host " [3/3] 测试检查..." -NoNewline
$testResult = mvn test -q -f $repoRoot -DskipTests=false 2>&1
if ($LASTEXITCODE -eq 0) { Write-Host " PASS" -ForegroundColor Green; $checks += "test:PASS" }
else { Write-Host " FAIL" -ForegroundColor Red; $checks += "test:FAIL" }
} finally { Pop-Location }
$allPass = ($checks | Where-Object { $_ -like "*:FAIL" }).Count -eq 0
if ($allPass) { Write-Host "[WORKTREE] 合并就绪: 全部检查通过" -ForegroundColor Green }
else { Write-Host "[WORKTREE] 合并就绪: 有未通过检查,请修复后再合并" -ForegroundColor Red }
return $allPass
}
switch ($Action) {
"create" { New-Worktree -Name $BranchName }
"list" { Show-Worktrees }
"clean" { Remove-StaleWorktrees -Days $MaxDays }
"status" { Test-MergeReady -Name $BranchName }
}
5.3 collect-sidecar.ps1 --- 客观侧性数据采集
触发时机 :prd-to-code.md Step 7 / auto-governance.md Step 1
采集内容:
| 数据项 | 采集方式 | 输出文件 |
|---|---|---|
| 编译结果 | mvn compile -q |
build-status.json |
| 测试结果 | mvn test |
test-results.json |
| Linter 结果 | mvn pmd:check checkstyle:check |
linter-results.json |
| 汇总 | 合并以上 | sidecar-summary.json |
关键设计:sidecar-summary.json 包含 timestamp 字段,超过 24 小时视为过期。
实际示例
通过agent生成
bash
<#
.SYNOPSIS
Git Worktree 自动化管理脚本 --- 为 AI Agent 任务创建隔离工作区
.DESCRIPTION
Harness 自动化层组件之一:管理 Git Worktree 的完整生命周期。
- create: 基于 PRD 名称创建隔离 worktree + 分支
- list: 列出所有活跃 worktree
- clean: 清理过期 worktree(默认 >7 天)
- status: 检查 worktree 是否合并就绪(Linter + 测试通过)
.EXAMPLE
.\worktree-manager.ps1 create "feature-umeng-query"
.\worktree-manager.ps1 list
.\worktree-manager.ps1 clean 7
.\worktree-manager.ps1 status "feature-umeng-query"
#>
param(
[Parameter(Position = 0)]
[ValidateSet("create", "list", "clean", "status")]
[string]$Action = "list",
[Parameter(Position = 1)]
[string]$BranchName,
[Parameter(Position = 2)]
[int]$MaxDays = 7
)
$ErrorActionPreference = "Stop"
$repoRoot = (git rev-parse --show-toplevel 2>$null | Out-String).Trim()
if (-not $repoRoot) {
Write-Error "[WORKTREE] 无法确定 Git 仓库根目录。请在仓库内执行此脚本。"
exit 1
}
$worktreeBase = Join-Path $repoRoot "..\.worktrees"
if (-not (Test-Path $worktreeBase)) {
New-Item -ItemType Directory -Path $worktreeBase -Force | Out-Null
}
function New-Worktree {
param([string]$Name)
if (-not $Name) {
Write-Error "[WORKTREE] 用法: worktree-manager.ps1 create <branch-name>"
exit 1
}
$timestamp = Get-Date -Format "yyyyMMdd-HHmm"
$fullBranch = "ai-task/$Name-$timestamp"
$worktreePath = Join-Path $worktreeBase ($Name -replace '[^a-zA-Z0-9-]', '-')
if (Test-Path $worktreePath) {
$worktreePath = "${worktreePath}-$timestamp"
}
Write-Host "[WORKTREE] 创建隔离工作区:" -ForegroundColor Cyan
Write-Host " 分支: $fullBranch"
Write-Host " 路径: $worktreePath"
git worktree add -b $fullBranch $worktreePath 2>&1 | ForEach-Object { Write-Host " $_" }
if ($LASTEXITCODE -eq 0) {
$meta = @{
branch = $fullBranch
path = $worktreePath
createdAt = (Get-Date).ToString("o")
task = $Name
}
$meta | ConvertTo-Json | Set-Content (Join-Path $worktreePath ".worktree-meta.json")
Write-Host "[WORKTREE] 创建成功。AI Agent 可在 $worktreePath 中工作。" -ForegroundColor Green
Write-Host "[WORKTREE] 元信息已写入 .worktree-meta.json" -ForegroundColor DarkGray
} else {
Write-Error "[WORKTREE] 创建失败,请检查 Git 状态。"
}
}
function Show-Worktrees {
Write-Host "[WORKTREE] 活跃工作区列表:" -ForegroundColor Cyan
$output = git worktree list --porcelain 2>$null
if (-not $output) {
Write-Host " (无活跃 worktree)"
return
}
$current = @{}
foreach ($line in ($output -split "`n")) {
if ($line -match '^worktree\s+(.+)') { $current.path = $Matches[1] }
elseif ($line -match '^branch\s+(.+)') { $current.branch = $Matches[1] }
elseif ($line -match '^HEAD\s+(.+)') {
$current.head = $Matches[1].Substring(0, 8)
$metaFile = Join-Path $current.path ".worktree-meta.json"
$task = "-"
$age = "-"
if (Test-Path $metaFile) {
$meta = Get-Content $metaFile | ConvertFrom-Json
$task = $meta.task
$created = [DateTime]::Parse($meta.createdAt)
$age = [math]::Round(((Get-Date) - $created).TotalDays, 1)
}
Write-Host (" {0,-40} {1,-45} age={2}d task={3}" -f $current.branch, $current.path, $age, $task)
$current = @{}
}
}
}
function Remove-StaleWorktrees {
param([int]$Days)
Write-Host "[WORKTREE] 清理 ${Days} 天前的过期工作区:" -ForegroundColor Yellow
$output = git worktree list --porcelain 2>$null
if (-not $output) { Write-Host " (无活跃 worktree)"; return }
$worktrees = @()
$current = @{}
foreach ($line in ($output -split "`n")) {
if ($line -match '^worktree\s+(.+)') { $current.path = $Matches[1] }
elseif ($line -match '^branch\s+(.+)') { $current.branch = $Matches[1] }
elseif ($line -match '^HEAD\s+(.+)') {
$current.head = $Matches[1]
$worktrees += [PSCustomObject]$current
$current = @{}
}
}
$cleaned = 0
foreach ($wt in $worktrees) {
$metaFile = Join-Path $wt.path ".worktree-meta.json"
$shouldClean = $false
$task = "unknown"
if (Test-Path $metaFile) {
$meta = Get-Content $metaFile | ConvertFrom-Json
$task = $meta.task
$created = [DateTime]::Parse($meta.createdAt)
$age = ((Get-Date) - $created).TotalDays
if ($age -gt $Days) { $shouldClean = $true }
}
if ($shouldClean) {
Write-Host " 清理: $($wt.branch) (task=$task, age=$([math]::Round($age,1))d)" -ForegroundColor Yellow
git worktree remove --force $wt.path 2>&1 | Out-Null
git branch -D $wt.branch 2>&1 | Out-Null
$cleaned++
}
}
if ($cleaned -eq 0) { Write-Host " 无过期工作区需要清理。" -ForegroundColor Green }
else { Write-Host "[WORKTREE] 已清理 $cleaned 个过期工作区。" -ForegroundColor Green }
}
function Test-MergeReady {
param([string]$Name)
$output = git worktree list --porcelain 2>$null
$targetPath = $null
$current = @{}
foreach ($line in ($output -split "`n")) {
if ($line -match '^worktree\s+(.+)') { $current.path = $Matches[1] }
elseif ($line -match '^branch\s+(.+)') { $current.branch = $Matches[1] }
elseif ($line -match '^HEAD\s+(.+)') {
if ($current.branch -like "*$Name*") { $targetPath = $current.path; break }
$current = @{}
}
}
if (-not $targetPath) {
Write-Error "[WORKTREE] 未找到匹配 '$Name' 的工作区。"
exit 1
}
Write-Host "[WORKTREE] 检查合并就绪状态: $Name" -ForegroundColor Cyan
Write-Host " 路径: $targetPath"
$checks = @()
Push-Location $targetPath
try {
Write-Host " [1/3] 编译检查..." -NoNewline
$compileResult = mvn compile -q -f $repoRoot 2>&1
if ($LASTEXITCODE -eq 0) { Write-Host " PASS" -ForegroundColor Green; $checks += "compile:PASS" }
else { Write-Host " FAIL" -ForegroundColor Red; $checks += "compile:FAIL" }
Write-Host " [2/3] Linter 检查..." -NoNewline
$pmdResult = mvn pmd:check -q -f $repoRoot 2>&1
if ($LASTEXITCODE -eq 0) { Write-Host " PASS" -ForegroundColor Green; $checks += "pmd:PASS" }
else { Write-Host " FAIL" -ForegroundColor Red; $checks += "pmd:FAIL" }
Write-Host " [3/3] 测试检查..." -NoNewline
$testResult = mvn test -q -f $repoRoot -DskipTests=false 2>&1
if ($LASTEXITCODE -eq 0) { Write-Host " PASS" -ForegroundColor Green; $checks += "test:PASS" }
else { Write-Host " FAIL" -ForegroundColor Red; $checks += "test:FAIL" }
} finally { Pop-Location }
$allPass = ($checks | Where-Object { $_ -like "*:FAIL" }).Count -eq 0
if ($allPass) { Write-Host "[WORKTREE] 合并就绪: 全部检查通过" -ForegroundColor Green }
else { Write-Host "[WORKTREE] 合并就绪: 有未通过检查,请修复后再合并" -ForegroundColor Red }
return $allPass
}
switch ($Action) {
"create" { New-Worktree -Name $BranchName }
"list" { Show-Worktrees }
"clean" { Remove-StaleWorktrees -Days $MaxDays }
"status" { Test-MergeReady -Name $BranchName }
}
5.4 decay-detector.ps1 --- 自动衰减检测
触发时机 :auto-governance.md Step 2
检测维度:
| 维度 | 检测方式 | 衰减信号 |
|---|---|---|
| 文档过期 | last_updated 距今 > 90 天 | stale / critical |
| 代码-文档漂移 | 文件存在但全景索引未注册 | orphan |
| 规则命中率 | Linter 规则近 30 天触发次数 = 0 | dead-rule |
| 技术栈对齐 | pom.xml 版本 vs always-on.md 声明 | mismatch |
输出:decay-report.json,包含 overallScore(0~100)。
实际示例
通过agent生成
bash
<#
.SYNOPSIS
自动衰减检测脚本 --- 检测文档/规则/约束随时间推移的可信度下降
.DESCRIPTION
Harness 自动化层组件之三:检测四个维度的衰减:
1. 文档过期:last_updated 超过 90 天的 .md 文件
2. 代码-文档漂移:源码变更但对应设计文档未同步更新
3. 规则零命中:Linter 规则从未被触发(可能已过时或覆盖不足)
4. 技术栈基线对齐:AGENTS.md 技术栈与 pom.xml 实际版本是否一致
输出 decay-report.json 供 AI Agent 读取决策。
.EXAMPLE
.\decay-detector.ps1
.\decay-detector.ps1 -Verbose
#>
param([switch]$Verbose = $false)
$ErrorActionPreference = "Continue"
$repoRoot = (git rev-parse --show-toplevel 2>$null | Out-String).Trim()
if (-not $repoRoot) { Write-Error "[DECAY] 请在 Git 仓库内执行"; exit 1 }
$sidecarDir = Join-Path $repoRoot "build\automation\sidecar"
if (-not (Test-Path $sidecarDir)) { New-Item -ItemType Directory -Path $sidecarDir -Force | Out-Null }
$timestamp = (Get-Date).ToString("o")
$now = Get-Date
$staleThreshold = 90
$driftThreshold = 30
# ── 1. 文档过期检测 ──
Write-Host "[DECAY] [1/4] 文档过期检测..." -ForegroundColor Cyan
$staleDocs = @()
$mdFiles = Get-ChildItem -Path $repoRoot -Filter "*.md" -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch '\\target\\' -and $_.FullName -notmatch '\\\.git\\' -and $_.FullName -notmatch '\\\.qoder\\cache\\' }
foreach ($md in $mdFiles) {
$content = Get-Content $md.FullName -Raw -ErrorAction SilentlyContinue
if (-not $content) { continue }
$lastUpdated = $null
if ($content -match 'last_updated:\s*(\d{4}-\d{2}-\d{2})') {
$lastUpdated = [DateTime]::ParseExact($Matches[1], "yyyy-MM-dd", $null)
$age = ($now - $lastUpdated).TotalDays
if ($age -gt $staleThreshold) {
$staleDocs += @{
file = $md.FullName.Replace($repoRoot + "\", "").Replace("\", "/")
lastUpdated = $Matches[1]
daysOld = [math]::Round($age, 0)
}
if ($Verbose) { Write-Host " STALE: $($md.Name) ($([math]::Round($age,0))d old)" -ForegroundColor Yellow }
}
}
}
Write-Host " 过期文档: $($staleDocs.Count) 个" -ForegroundColor $(if ($staleDocs.Count -eq 0) { "Green" } else { "Yellow" })
# ── 2. 代码-文档漂移检测 ──
Write-Host "[DECAY] [2/4] 代码-文档漂移检测..." -ForegroundColor Cyan
$drifts = @()
# 源码→设计文档映射规则
$docMappings = @(
@{ pattern = 'SyncSingle|sync.*single'; docs = @('docs/design/feature-sync-single-request.md') },
@{ pattern = 'async.*batch|sharding|partition'; docs = @('docs/design/feature-async-batch-sharding.md') },
@{ pattern = 'Vendor|vendor|ext.*tc|ext.*ym|ext.*bh|tianchuang|youmeng|baihang'; docs = @('docs/design/feature-vendor-adapter.md') },
@{ pattern = 'RateLimit|rateLimit|Sentinel|sentinel'; docs = @('docs/design/feature-rate-limiting.md') },
@{ pattern = 'TraceId|traceId|MDC|trace'; docs = @('docs/design/feature-trace-propagation.md') },
@{ pattern = 'Scheduler|scheduler|XXL|xxl|ScheduleTask'; docs = @('docs/design/feature-scheduler-framework.md') }
)
$javaFiles = Get-ChildItem -Path $repoRoot -Filter "*.java" -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch '\\target\\' -and $_.FullName -notmatch '\\test\\' }
$checkedFiles = 0
foreach ($java in $javaFiles) {
$relativePath = $java.FullName.Replace($repoRoot + "\", "").Replace("\", "/")
# 获取源码最后修改日期(git log)
$lastCommitDate = git log -1 --format="%ci" -- $relativePath 2>$null | Out-String
if (-not $lastCommitDate.Trim()) { continue }
try { $codeDate = [DateTime]::Parse($lastCommitDate.Trim()) } catch { continue }
$checkedFiles++
# 匹配对应文档
foreach ($mapping in $docMappings) {
if ($java.Name -match $mapping.pattern -or $relativePath -match $mapping.pattern) {
foreach ($docPath in $mapping.docs) {
$fullDocPath = Join-Path $repoRoot ($docPath -replace '/', '\')
if (Test-Path $fullDocPath) {
$docContent = Get-Content $fullDocPath -Raw -ErrorAction SilentlyContinue
if ($docContent -match 'last_updated:\s*(\d{4}-\d{2}-\d{2})') {
$docDate = [DateTime]::ParseExact($Matches[1], "yyyy-MM-dd", $null)
$driftDays = ($codeDate - $docDate).TotalDays
if ($driftDays -gt $driftThreshold) {
$drifts += @{
codeFile = $relativePath
codeLastCommit = $codeDate.ToString("yyyy-MM-dd")
linkedDoc = $docPath
docLastUpdated = $Matches[1]
driftDays = [math]::Round($driftDays, 0)
}
if ($Verbose) { Write-Host " DRIFT: $relativePath → $docPath (drift=$([math]::Round($driftDays,0))d)" -ForegroundColor Yellow }
}
}
}
}
break
}
}
}
# 去重(同一个 doc 可能被多个 java 文件触发)
$drifts = $drifts | Sort-Object linkedDoc, driftDays -Descending | Group-Object linkedDoc | ForEach-Object { $_.Group[0] }
Write-Host " 检查源码文件: $checkedFiles 个" -ForegroundColor DarkGray
Write-Host " 代码-文档漂移: $($drifts.Count) 处" -ForegroundColor $(if ($drifts.Count -eq 0) { "Green" } else { "Yellow" })
# ── 3. Linter 规则零命中检测 ──
Write-Host "[DECAY] [3/4] Linter 规则命中率检测..." -ForegroundColor Cyan
$ruleCoverage = @{ totalRules = 35; hitRules = 0; zeroHitRules = @(); coverage = 0 }
$pmdViolationsFile = Join-Path $sidecarDir "linter-violations.json"
$csViolationsFile = Join-Path $sidecarDir "checkstyle-violations.json"
$hitRules = @{}
if (Test-Path $pmdViolationsFile) {
$pmdData = Get-Content $pmdViolationsFile -Raw | ConvertFrom-Json
if ($pmdData.byRule) {
foreach ($key in $pmdData.byRule.PSObject.Properties.Name) { $hitRules[$key] = $true }
}
}
if (Test-Path $csViolationsFile) {
$csData = Get-Content $csViolationsFile -Raw | ConvertFrom-Json
if ($csData.byRule) {
foreach ($key in $csData.byRule.PSObject.Properties.Name) {
$ruleName = if ($key -match 'EXCHANGE') { $key } else { "EXCHANGE-CS-$key" }
$hitRules[$ruleName] = $true
}
}
}
$ruleCoverage.hitRules = $hitRules.Count
$ruleCoverage.coverage = [math]::Round($hitRules.Count / $ruleCoverage.totalRules, 4)
# 已知规则列表(与 pmd-exchange-ruleset.xml + checkstyle-exchange.xml 对齐)
$allPmdRules = @(
"ExchangeNoLoggerFactory","ExchangeNoSystemOut","ExchangeNoPrintStackTrace","ExchangeNoLogStringConcat",
"ExchangeNoFieldAutowired","ExchangeNoConstructorAutowired","ExchangeNoNewOkHttpClient",
"ExchangeNoTransactional","ExchangeNoThreadPoolExecutor","ExchangeNoForkJoinPool",
"EmptyCatchBlock","ExchangeNoAllArgsConstructor","ExchangeNoRunWith","ExchangeNoThreadSleep",
"ExchangeNoMapperFieldInjection","ExchangeNoSystemExit"
)
$allCsRules = @("IllegalImport","FileLength","UnusedImports","CustomImportOrder")
foreach ($r in $allPmdRules) { if (-not $hitRules.ContainsKey($r)) { $ruleCoverage.zeroHitRules += $r } }
foreach ($r in $allCsRules) { if (-not $hitRules.ContainsKey($r)) { $ruleCoverage.zeroHitRules += $r } }
Write-Host " 命中规则: $($ruleCoverage.hitRules)/$($ruleCoverage.totalRules) (覆盖率=$([math]::Round($ruleCoverage.coverage*100,1))%)" -ForegroundColor $(if ($ruleCoverage.coverage -ge 0.5) { "Green" } else { "Yellow" })
if ($ruleCoverage.zeroHitRules.Count -gt 0 -and $Verbose) {
Write-Host " 零命中规则:" -ForegroundColor DarkGray
foreach ($r in $ruleCoverage.zeroHitRules) { Write-Host " - $r" -ForegroundColor DarkGray }
}
# ── 4. 技术栈基线对齐检测 ──
Write-Host "[DECAY] [4/4] 技术栈基线对齐检测..." -ForegroundColor Cyan
$techStack = @{ aligned = $true; mismatches = @() }
$agentsContent = Get-Content (Join-Path $repoRoot "AGENTS.md") -Raw -ErrorAction SilentlyContinue
$pomContent = Get-Content (Join-Path $repoRoot "pom.xml") -Raw -ErrorAction SilentlyContinue
# 从 pom.xml 提取实际版本
$versionChecks = @(
@{ name = "JDK"; agentsValue = "1.8"; pomPattern = '<maven\.compiler\.source>([^<]+)</maven\.compiler\.source>' },
@{ name = "Spring Boot"; agentsValue = "2.3.12.RELEASE"; pomPattern = '<spring-boot\.version>([^<]+)</spring-boot\.version>' },
@{ name = "MyBatis-Plus"; agentsValue = "3.4.3"; pomPattern = '<mybatis-plus\.version>([^<]+)</mybatis-plus\.version>' },
@{ name = "FastJSON"; agentsValue = "1.2.83"; pomPattern = '<fastjson\.version>([^<]+)</fastjson\.version>' },
@{ name = "OkHttp3"; agentsValue = "3.14.9"; pomPattern = '<okhttp\.version>([^<]+)</okhttp\.version>' },
@{ name = "JUnit"; agentsValue = "5.6.3"; pomPattern = '<junit\.version>([^<]+)</junit\.version>' },
@{ name = "Mockito"; agentsValue = "3.12.4"; pomPattern = '<mockito\.version>([^<]+)</mockito\.version>' }
)
foreach ($check in $versionChecks) {
$pomVersion = $null
if ($pomContent -match $check.pomPattern) { $pomVersion = $Matches[1] }
if ($pomVersion -and $pomVersion -ne $check.agentsValue) {
$techStack.aligned = $false
$techStack.mismatches += @{
component = $check.name
agentsValue = $check.agentsValue
pomValue = $pomVersion
}
Write-Host " MISMATCH: $($check.name) --- AGENTS.md=$($check.agentsValue), pom.xml=$pomVersion" -ForegroundColor Yellow
}
}
if ($techStack.aligned) { Write-Host " 技术栈基线一致" -ForegroundColor Green }
# ── 衰减评分 ──
$docScore = 100 - [math]::Min($staleDocs.Count * 10, 100)
$driftScore = 100 - [math]::Min($drifts.Count * 15, 100)
$coverageScore = [math]::Round($ruleCoverage.coverage * 100, 0)
$stackScore = if ($techStack.aligned) { 100 } else { 100 - $techStack.mismatches.Count * 20 }
$overallScore = [math]::Round(($docScore + $driftScore + $coverageScore + $stackScore) / 4, 0)
$decayReport = @{
timestamp = $timestamp
overallScore = $overallScore
dimensions = @{
docFreshness = @{ score = $docScore; staleCount = $staleDocs.Count; threshold = "$staleThreshold days" }
codeDocAlignment = @{ score = $driftScore; driftCount = $drifts.Count; threshold = "$driftThreshold days drift" }
ruleCoverage = @{ score = $coverageScore; hitRules = $ruleCoverage.hitRules; totalRules = $ruleCoverage.totalRules; zeroHitRules = $ruleCoverage.zeroHitRules }
techStackAlignment = @{ score = $stackScore; aligned = $techStack.aligned; mismatches = $techStack.mismatches }
}
staleDocs = $staleDocs
codeDocDrift = $drifts
ruleCoverage = $ruleCoverage
techStackAlignment = $techStack
}
$decayReport | ConvertTo-Json -Depth 4 | Set-Content (Join-Path $sidecarDir "decay-report.json")
Write-Host ""
Write-Host "[DECAY] 衰减检测完成" -ForegroundColor Green
Write-Host " 整体评分: $overallScore / 100" -ForegroundColor $(if ($overallScore -ge 80) { "Green" } elseif ($overallScore -ge 60) { "Yellow" } else { "Red" })
Write-Host " 文档新鲜度: $docScore ($($staleDocs.Count) 个过期)"
Write-Host " 代码-文档对齐: $driftScore ($($drifts.Count) 处漂移)"
Write-Host " 规则覆盖率: $coverageScore ($($ruleCoverage.hitRules)/$($ruleCoverage.totalRules) 命中)"
Write-Host " 技术栈对齐: $stackScore ($($techStack.mismatches.Count) 个不一致)"
Write-Host ""
Write-Host "[DECAY] 报告已写入: $sidecarDir\decay-report.json"
5.5 governance-report.ps1 --- 持续治理报告
触发时机 :auto-governance.md Step 5
功能:汇总 sidecar 数据 + 衰减检测,生成 Markdown 治理报告。
报告内容:
- 构建状态(编译/测试/Linter 通过率)
- 文档健康度(stale/critical/orphan 统计)
- 衰减评分(overallScore + 各维度明细)
- 建议清单(需人工处理的问题)
实际示例
通过agent生成
bash
<#
.SYNOPSIS
持续治理报告生成脚本 --- 汇总侧性数据 + 衰减报告,生成 Markdown 治理周报
.DESCRIPTION
Harness 自动化层组件之四:读取 build/automation/sidecar/ 下的所有 JSON 数据,
结合衰减检测结果,生成结构化的治理报告(Markdown),保存到 docs/changes/ 目录。
AI Agent 可读取此报告了解项目整体健康状态并制定修复计划。
也可手动运行用于定期治理巡检。
.EXAMPLE
.\governance-report.ps1 # 生成报告
.\governance-report.ps1 -RunCollect # 先采集侧性数据再生成报告
#>
param([switch]$RunCollect = $false)
$ErrorActionPreference = "Continue"
$repoRoot = (git rev-parse --show-toplevel 2>$null | Out-String).Trim()
if (-not $repoRoot) { Write-Error "[REPORT] 请在 Git 仓库内执行"; exit 1 }
$sidecarDir = Join-Path $repoRoot "build\automation\sidecar"
$reportDir = Join-Path $repoRoot "docs\changes"
$reportDate = Get-Date -Format "yyyy-MM-dd"
$reportPath = Join-Path $reportDir "governance-report-$reportDate.md"
# 可选:先运行采集
if ($RunCollect) {
Write-Host "[REPORT] 先运行侧性数据采集..." -ForegroundColor Cyan
$collectScript = Join-Path $repoRoot "build\automation\scripts\collect-sidecar.ps1"
if (Test-Path $collectScript) { & powershell -File $collectScript }
$decayScript = Join-Path $repoRoot "build\automation\scripts\decay-detector.ps1"
if (Test-Path $decayScript) { & powershell -File $decayScript }
}
# 加载侧性数据
function Read-Json($path) {
if (Test-Path $path) { return Get-Content $path -Raw | ConvertFrom-Json }
return $null
}
$buildStatus = Read-Json (Join-Path $sidecarDir "build-status.json")
$testResults = Read-Json (Join-Path $sidecarDir "test-results.json")
$pmdData = Read-Json (Join-Path $sidecarDir "linter-violations.json")
$csData = Read-Json (Join-Path $sidecarDir "checkstyle-violations.json")
$decay = Read-Json (Join-Path $sidecarDir "decay-report.json")
# 历史趋势(读取上一个报告)
$prevReports = Get-ChildItem -Path $reportDir -Filter "governance-report-*.md" | Sort-Object Name -Descending
$prevScore = $null
if ($prevReports.Count -gt 1) {
$prevContent = Get-Content $prevReports[1].FullName -Raw
if ($prevContent -match '整体评分.*?(\d+)\s*/\s*100') { $prevScore = [int]$Matches[1] }
}
# ── 生成 Markdown 报告 ──
$sb = [System.Text.StringBuilder]::new()
[void]$sb.AppendLine("---")
[void]$sb.AppendLine("last_updated: $reportDate")
[void]$sb.AppendLine("status: active")
[void]$sb.AppendLine("owner: @auto-governance")
[void]$sb.AppendLine("---")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("# 治理报告 $reportDate")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("> 本报告由 `governance-report.ps1` 自动生成,基于客观数据(构建/测试/Linter/衰减检测)。")
[void]$sb.AppendLine("> AI Agent 可读取此报告制定修复计划。")
[void]$sb.AppendLine("")
# ── 1. 执行摘要 ──
[void]$sb.AppendLine("## 执行摘要")
[void]$sb.AppendLine("")
$score = if ($decay) { $decay.overallScore } else { "N/A" }
$scoreColor = if ($score -is [int]) { if ($score -ge 80) { "健康" } elseif ($score -ge 60) { "关注" } else { "风险" } } else { "未知" }
[void]$sb.AppendLine("| 指标 | 值 | 状态 |")
[void]$sb.AppendLine("|------|-----|------|")
if ($prevScore) {
$trend = $score - $prevScore
$trendStr = if ($trend -gt 0) { "↑ +$trend" } elseif ($trend -lt 0) { "↓ $trend" } else { "→ 持平" }
[void]$sb.AppendLine("| 整体评分 | $score / 100 | $scoreColor (上周: $prevScore, $trendStr) |")
} else {
[void]$sb.AppendLine("| 整体评分 | $score / 100 | $scoreColor |")
}
$buildStr = if ($buildStatus) { if ($buildStatus.compileSuccess) { "PASS" } else { "FAIL ($($buildStatus.errorCount) errors)" } } else { "N/A" }
[void]$sb.AppendLine("| 编译状态 | $buildStr | $(if ($buildStatus.compileSuccess) { '正常' } else { '异常' }) |")
$testStr = if ($testResults) { "$($testResults.passed)/$($testResults.totalTests) (通过率=$([math]::Round($testResults.passRate*100,1))%)" } else { "N/A" }
[void]$sb.AppendLine("| 测试通过率 | $testStr | $(if ($testResults -and $testResults.failed -eq 0) { '正常' } else { '异常' }) |")
$pmdStr = if ($pmdData) { $pmdData.total } else { "N/A" }
[void]$sb.AppendLine("| PMD 违规 | $pmdStr | $(if ($pmdData -and $pmdData.total -eq 0) { '正常' } else { '需修复' }) |")
$csStr = if ($csData) { $csData.total } else { "N/A" }
[void]$sb.AppendLine("| Checkstyle 违规 | $csStr | $(if ($csData -and $csData.total -eq 0) { '正常' } else { '需修复' }) |")
$staleStr = if ($decay) { $decay.dimensions.docFreshness.staleCount } else { "N/A" }
[void]$sb.AppendLine("| 过期文档 | $staleStr | $(if ($staleStr -eq 0) { '正常' } else { '需更新' }) |")
$driftStr = if ($decay) { $decay.dimensions.codeDocAlignment.driftCount } else { "N/A" }
[void]$sb.AppendLine("| 代码-文档漂移 | $driftStr | $(if ($driftStr -eq 0) { '正常' } else { '需同步' }) |")
[void]$sb.AppendLine("")
# ── 2. 衰减详情 ──
if ($decay) {
[void]$sb.AppendLine("## 衰减检测")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| 维度 | 评分 | 详情 |")
[void]$sb.AppendLine("|------|------|------|")
[void]$sb.AppendLine("| 文档新鲜度 | $($decay.dimensions.docFreshness.score) | $($decay.dimensions.docFreshness.staleCount) 个过期 |")
[void]$sb.AppendLine("| 代码-文档对齐 | $($decay.dimensions.codeDocAlignment.score) | $($decay.dimensions.codeDocAlignment.driftCount) 处漂移 |")
[void]$sb.AppendLine("| 规则覆盖率 | $($decay.dimensions.ruleCoverage.score) | $($decay.dimensions.ruleCoverage.hitRules)/$($decay.dimensions.ruleCoverage.totalRules) 命中 |")
[void]$sb.AppendLine("| 技术栈对齐 | $($decay.dimensions.techStackAlignment.score) | $(if ($decay.dimensions.techStackAlignment.aligned) { '一致' } else { '不一致' }) |")
[void]$sb.AppendLine("")
# 过期文档列表
if ($decay.staleDocs -and $decay.staleDocs.Count -gt 0) {
[void]$sb.AppendLine("### 过期文档(>90 天)")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| 文档 | 最后更新 | 过期天数 |")
[void]$sb.AppendLine("|------|---------|---------|")
foreach ($d in $decay.staleDocs) { [void]$sb.AppendLine("| $($d.file) | $($d.lastUpdated) | $($d.daysOld)d |") }
[void]$sb.AppendLine("")
}
# 代码-文档漂移列表
if ($decay.codeDocDrift -and $decay.codeDocDrift.Count -gt 0) {
[void]$sb.AppendLine("### 代码-文档漂移")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| 源码文件 | 代码最后提交 | 关联文档 | 文档最后更新 | 漂移天数 |")
[void]$sb.AppendLine("|---------|------------|---------|------------|---------|")
foreach ($dr in $decay.codeDocDrift) { [void]$sb.AppendLine("| $($dr.codeFile) | $($dr.codeLastCommit) | $($dr.linkedDoc) | $($dr.docLastUpdated) | $($dr.driftDays)d |") }
[void]$sb.AppendLine("")
}
# 零命中规则
if ($decay.ruleCoverage.zeroHitRules -and $decay.ruleCoverage.zeroHitRules.Count -gt 0) {
[void]$sb.AppendLine("### 零命中 Linter 规则(需观察是否过时)")
[void]$sb.AppendLine("")
foreach ($r in $decay.ruleCoverage.zeroHitRules) { [void]$sb.AppendLine("- `$r`") }
[void]$sb.AppendLine("")
}
# 技术栈不一致
if (-not $decay.techStackAlignment.aligned) {
[void]$sb.AppendLine("### 技术栈不一致")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| 组件 | AGENTS.md | pom.xml |")
[void]$sb.AppendLine("|------|-----------|---------|")
foreach ($m in $decay.techStackAlignment.mismatches) { [void]$sb.AppendLine("| $($m.component) | $($m.agentsValue) | $($m.pomValue) |") }
[void]$sb.AppendLine("")
}
}
# ── 3. Linter 违规详情 ──
if ($pmdData -and $pmdData.total -gt 0) {
[void]$sb.AppendLine("## PMD 违规详情")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| 规则 | 数量 |")
[void]$sb.AppendLine("|------|------|")
foreach ($prop in $pmdData.byRule.PSObject.Properties) { [void]$sb.AppendLine("| $($prop.Name) | $($prop.Value) |") }
[void]$sb.AppendLine("")
if ($pmdData.violations -and $pmdData.violations.Count -gt 0) {
[void]$sb.AppendLine("### 违规明细(前 10 条)")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| 文件 | 行 | 规则 | 消息 |")
[void]$sb.AppendLine("|------|-----|------|------|")
foreach ($v in ($pmdData.violations | Select-Object -First 10)) {
$msg = if ($v.message) { ($v.message -replace '\|', '/' -replace "`n", " ").Substring(0, [math]::Min($v.message.Length, 80)) } else { "" }
[void]$sb.AppendLine("| $($v.file) | $($v.line) | $($v.rule) | $msg |")
}
[void]$sb.AppendLine("")
}
}
# ── 4. 测试失败详情 ──
if ($testResults -and $testResults.failed -gt 0) {
[void]$sb.AppendLine("## 测试失败详情")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("| 测试类 | 方法 | 消息 |")
[void]$sb.AppendLine("|--------|------|------|")
foreach ($f in $testResults.failures) {
$msg = if ($f.message) { ($f.message -replace '\|', '/' -replace "`n", " ").Substring(0, [math]::Min($f.message.Length, 100)) } else { "" }
[void]$sb.AppendLine("| $($f.className) | $($f.methodName) | $msg |")
}
[void]$sb.AppendLine("")
}
# ── 5. 行动项 ──
[void]$sb.AppendLine("## 行动项")
[void]$sb.AppendLine("")
$actionIdx = 1
if ($buildStatus -and -not $buildStatus.compileSuccess) { [void]$sb.AppendLine("$actionIdx. **[紧急]** 修复编译错误($($buildStatus.errorCount) 个)"); $actionIdx++ }
if ($testResults -and $testResults.failed -gt 0) { [void]$sb.AppendLine("$actionIdx. **[紧急]** 修复失败测试($($testResults.failed) 个)"); $actionIdx++ }
if ($pmdData -and $pmdData.total -gt 0) { [void]$sb.AppendLine("$actionIdx. **[高]** 修复 PMD 违规($($pmdData.total) 个)--- 参见 docs/conventions/linter-rules.md"); $actionIdx++ }
if ($csData -and $csData.total -gt 0) { [void]$sb.AppendLine("$actionIdx. **[高]** 修复 Checkstyle 违规($($csData.total) 个)--- 参见 docs/conventions/linter-rules.md"); $actionIdx++ }
if ($decay -and $decay.staleDocs.Count -gt 0) { [void]$sb.AppendLine("$actionIdx. **[中]** 更新过期文档($($decay.staleDocs.Count) 个)--- 执行 `/doc-gardening`"); $actionIdx++ }
if ($decay -and $decay.codeDocDrift.Count -gt 0) { [void]$sb.AppendLine("$actionIdx. **[中]** 同步代码-文档漂移($($decay.codeDocDrift.Count) 处)--- 源码已变更但文档未更新"); $actionIdx++ }
if ($decay -and -not $decay.techStackAlignment.aligned) { [void]$sb.AppendLine("$actionIdx. **[中]** 对齐技术栈基线($($decay.techStackAlignment.mismatches.Count) 个不一致)--- 更新 AGENTS.md 或 pom.xml"); $actionIdx++ }
if ($decay -and $decay.ruleCoverage.zeroHitRules.Count -gt 5) { [void]$sb.AppendLine("$actionIdx. **[低]** 评估零命中 Linter 规则($($decay.ruleCoverage.zeroHitRules.Count) 条)--- 确认是否需要保留"); $actionIdx++ }
if ($actionIdx -eq 1) { [void]$sb.AppendLine("无待办行动项,项目健康状态良好。") }
[void]$sb.AppendLine("")
[void]$sb.AppendLine("---")
[void]$sb.AppendLine("*生成时间: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | 数据来源: build/automation/sidecar/*")
# 写入文件
$sb.ToString() | Set-Content $reportPath -Encoding UTF8
Write-Host "[REPORT] 治理报告已生成: $reportPath" -ForegroundColor Green
Write-Host "[REPORT] 整体评分: $score / 100" -ForegroundColor $(if ($score -ge 80) { "Green" } elseif ($score -ge 60) { "Yellow" } else { "Red" })
5.6 index-sync-check.ps1 --- 文档全景索引一致性检测
触发时机:PR 自检清单中的"文档同步检查"项
检测逻辑:
- 扫描磁盘上的 .md/.yaml/.ps1/.xml 文件
- 解析
ai-coding-guide.md§5 全景索引 - 对比磁盘文件 vs 索引文件
- 输出差异:磁盘有但索引无 / 索引有但磁盘无
输出格式:
json
{
"diskFiles": 35,
"indexFiles": 33,
"missingInIndex": ["docs/new-file.md"],
"missingInDisk": [],
"consistent": false
}
实际示例
通过agent生成
bash
<#
.SYNOPSIS
文档全景索引一致性检测脚本 --- 确保 ai-coding-guide.md §5 索引与磁盘实际文件一致
.DESCRIPTION
Harness 自动化层组件:扫描 .qoder/ 和 docs/ 目录下的实际 .md/.yaml 文件,
与 docs/ai-coding-guide.md §5「文档全景索引」中的文件条目进行比对,
输出缺失项(磁盘有但索引无)和幽灵项(索引有但磁盘无)。
输出 index-sync-report.json 供 AI Agent 和 doc-gardening 读取决策。
.EXAMPLE
.\index-sync-check.ps1
.\index-sync-check.ps1 -Verbose
#>
param([switch]$Verbose = $false)
$ErrorActionPreference = "Continue"
$repoRoot = (git rev-parse --show-toplevel 2>$null | Out-String).Trim()
if (-not $repoRoot) { Write-Error "[INDEX-SYNC] 请在 Git 仓库内执行"; exit 1 }
$sidecarDir = Join-Path $repoRoot "build\automation\sidecar"
if (-not (Test-Path $sidecarDir)) { New-Item -ItemType Directory -Path $sidecarDir -Force | Out-Null }
$timestamp = (Get-Date).ToString("o")
# ── 1. 收集磁盘实际文件 ──
Write-Host "[INDEX-SYNC] [1/3] 收集磁盘实际文件..." -ForegroundColor Cyan
$actualFiles = @()
# .qoder/rules/*.md
$qoderRulesDir = Join-Path $repoRoot ".qoder\rules"
if (Test-Path $qoderRulesDir) {
Get-ChildItem -Path $qoderRulesDir -Filter "*.md" | ForEach-Object {
$actualFiles += $_.Name
}
}
# .qoder/skills/*.md
$qoderSkillsDir = Join-Path $repoRoot ".qoder\skills"
if (Test-Path $qoderSkillsDir) {
Get-ChildItem -Path $qoderSkillsDir -Filter "*.md" | ForEach-Object {
$actualFiles += $_.Name
}
}
# docs/**/*.md and docs/**/*.yaml
$docsDir = Join-Path $repoRoot "docs"
if (Test-Path $docsDir) {
Get-ChildItem -Path $docsDir -Recurse -Include "*.md","*.yaml" -ErrorAction SilentlyContinue | ForEach-Object {
$actualFiles += $_.Name
}
}
# Project root files (§5.3)
foreach ($rootFile in @("AGENTS.md", "README.md", "CHANGELOG.md")) {
$fullPath = Join-Path $repoRoot $rootFile
if (Test-Path $fullPath) {
$actualFiles += $rootFile
}
}
# build/automation/scripts/*.ps1 (§5.4)
$scriptsDir = Join-Path $repoRoot "build\automation\scripts"
if (Test-Path $scriptsDir) {
Get-ChildItem -Path $scriptsDir -Filter "*.ps1" | ForEach-Object {
$actualFiles += $_.Name
}
}
# build/linter/*.xml (§5.5)
$linterDir = Join-Path $repoRoot "build\linter"
if (Test-Path $linterDir) {
Get-ChildItem -Path $linterDir -Filter "*.xml" | ForEach-Object {
$actualFiles += $_.Name
}
}
# 去重并排序
$actualFiles = $actualFiles | Sort-Object -Unique
Write-Host " 磁盘文件数: $($actualFiles.Count) 个" -ForegroundColor DarkGray
# ── 2. 解析 ai-coding-guide.md §5 索引 ──
Write-Host "[INDEX-SYNC] [2/3] 解析 ai-coding-guide.md §5 文档全景索引..." -ForegroundColor Cyan
$guidePath = Join-Path $repoRoot "docs\ai-coding-guide.md"
if (-not (Test-Path $guidePath)) {
Write-Host " ERROR: docs/ai-coding-guide.md 不存在" -ForegroundColor Red
exit 1
}
$guideContent = Get-Content $guidePath -Raw -Encoding UTF8
# 提取 §5 章节(从 "## 5. 文档全景索引" 到下一个 "## " 或文件末尾)
$sectionPattern = '(?s)## 5\. 文档全景索引(.*?)(?=\n## |\Z)'
$sectionMatch = [regex]::Match($guideContent, $sectionPattern)
if (-not $sectionMatch.Success) {
Write-Host " ERROR: 未找到 §5 文档全景索引章节" -ForegroundColor Red
exit 1
}
$sectionContent = $sectionMatch.Groups[1].Value
# 从代码块中提取文件名(匹配 .md 和 .yaml 文件)
# 匹配模式:行中包含 ├── 或 └── 后跟文件名
$indexedFiles = @()
$lines = $sectionContent -split "`n"
foreach ($line in $lines) {
# 匹配代码块中的文件条目:├── filename.md / .yaml / .ps1 / .xml
if ($line -match '[├└]──\s+(\S+\.(?:md|yaml|ps1|xml))') {
$fileName = $Matches[1]
# 排除目录名(以 / 结尾的被前面的正则排除,因为不以 .md/.yaml 结尾)
$indexedFiles += $fileName
}
}
# 去重并排序
$indexedFiles = $indexedFiles | Sort-Object -Unique
Write-Host " 索引文件数: $($indexedFiles.Count) 个" -ForegroundColor DarkGray
# ── 3. 比对差异 ──
Write-Host "[INDEX-SYNC] [3/3] 比对差异..." -ForegroundColor Cyan
$actualSet = [System.Collections.Generic.HashSet[string]]::new([string[]]$actualFiles)
$indexedSet = [System.Collections.Generic.HashSet[string]]::new([string[]]$indexedFiles)
# 磁盘有但索引无 → 缺失项
$missingFromIndex = @()
foreach ($f in $actualFiles) {
if (-not $indexedSet.Contains($f)) {
$missingFromIndex += $f
}
}
# 索引有但磁盘无 → 幽灵项
$ghostEntries = @()
foreach ($f in $indexedFiles) {
if (-not $actualSet.Contains($f)) {
$ghostEntries += $f
}
}
# 输出结果
$synced = ($missingFromIndex.Count -eq 0 -and $ghostEntries.Count -eq 0)
if ($synced) {
Write-Host " ✅ 索引与磁盘文件完全一致" -ForegroundColor Green
} else {
if ($missingFromIndex.Count -gt 0) {
Write-Host " ⚠ 缺失项(磁盘有但索引无): $($missingFromIndex.Count) 个" -ForegroundColor Yellow
foreach ($f in $missingFromIndex) {
Write-Host " - $f" -ForegroundColor Yellow
}
}
if ($ghostEntries.Count -gt 0) {
Write-Host " ⚠ 幽灵项(索引有但磁盘无): $($ghostEntries.Count) 个" -ForegroundColor Yellow
foreach ($f in $ghostEntries) {
Write-Host " - $f" -ForegroundColor Yellow
}
}
}
# ── 生成 JSON 报告 ──
$report = @{
timestamp = $timestamp
synced = $synced
actualFileCount = $actualFiles.Count
indexedFileCount = $indexedFiles.Count
missingFromIndex = $missingFromIndex
ghostEntries = $ghostEntries
actualFiles = $actualFiles
indexedFiles = $indexedFiles
}
$reportPath = Join-Path $sidecarDir "index-sync-report.json"
$report | ConvertTo-Json -Depth 3 | Set-Content $reportPath
Write-Host ""
Write-Host "[INDEX-SYNC] 检测完成" -ForegroundColor $(if ($synced) { "Green" } else { "Yellow" })
Write-Host " 实际文件: $($actualFiles.Count) | 索引文件: $($indexedFiles.Count) | 缺失: $($missingFromIndex.Count) | 幽灵: $($ghostEntries.Count)"
Write-Host " 报告路径: $reportPath"
Write-Host ""
if (-not $synced) { exit 2 } else { exit 0 }
6. 自动化层的门禁闭环
6.1 完整的门禁流程
代码生成完成
│
▼
Step 5.5: 编译验证(mvn test-compile)
│ 失败 → 返回修复
│ 通过 ↓
▼
Step 7.1: 自动治理巡检
│
├─ collect-sidecar.ps1(采集客观数据)
│ └─ build-status.json(编译结果)
│ └─ test-results.json(测试结果)
│ └─ linter-results.json(Linter 结果)
│
├─ decay-detector.ps1(衰减检测)
│ └─ decay-report.json(overallScore)
│
├─ 门禁判断
│ ├─ 编译失败 → 阻断交付
│ ├─ 测试失败 → 阻断交付
│ ├─ 衰减评分 < 60 → 人工确认
│ └─ sidecar 过期 → 重新采集
│
▼
Step 7.2: PR 自检清单
│
├─ 规则合规检查(对照 always-on.md)
├─ 代码质量检查
├─ 测试检查
├─ 文档同步检查(含 index-sync-check.ps1)
└─ 提交规范检查
│
▼
交付
6.2 客观侧性优先原则
核心原则:当 AI Agent 的主观判断与 sidecar JSON 客观数据冲突时,以客观数据为准。
| AI 主观判断 | sidecar 客观数据 | 决策 |
|---|---|---|
| "代码没问题" | compileSuccess = false | 以客观数据为准,修复编译错误 |
| "测试应该通过" | test-results failed > 0 | 以客观数据为准,修复失败测试 |
| "文档是完整的" | index-sync-check inconsistent | 以客观数据为准,更新索引 |
7. 完整的端到端流程
从 BRD 到 PR 的完整路径:
1. 业务方编写 BRD
│
▼
2. 产品经理参照 brd-to-prd-guide.md 将 BRD 转为 PRD(参照 prd-template.md)
│
▼
3. 提交 PRD 给 AI Agent
│
▼
4. AI Agent 执行 prd-to-code 技能(7 步流程)
│
├─ Step 0: 环境检查 + Worktree + PRD 校验
├─ Step 1: PRD 解析(加载 AGENTS.md + prd-template.md)
├─ Step 2: 影响分析(加载 boundaries.md + impact-analysis.md)
├─ Step 3: 设计文档生成(加载 _template.md + data-flow.md)
├─ Step 4: 设计评审(加载 design-review.md)
│ ⚠️ 暂停 → 人工确认「通过」
├─ Step 5: 代码生成(加载 add-*.md)
│ → 遵守 always-on.md 全部规则
│ → 遵守 java-code-style.md 代码风格
│ → 同步生成 JUnit 5 + Mockito 测试
├─ Step 5.5: 编译验证(mvn test-compile)
├─ Step 6: 文档同步(更新 error-codes.md + api.spec.yaml + CHANGELOG.md)
└─ Step 7: 交付前自检
├─ 自动治理巡检(collect-sidecar + decay-detector)
├─ 门禁检查(编译/测试/衰减评分)
└─ PR 自检清单(6 维度逐项检查)
│
▼
5. 交付(生成 PR)
│
▼
6. 提交后
├─ 执行 /doc-gardening(文档健康度扫描)
└─ 更新 current-sprint.md 任务状态
8. 完成验证清单
完成本步后,逐项检查:
总指南
-
docs/ai-coding-guide.md--- 包含 §1~§9 全部章节 - §2 四层文档体系图与实际文件一致
- §3 八步流程的按需加载清单与 agent-workflow.md §4 一致
- §5 文档全景索引包含全部已创建的文件(磁盘文件数 = 索引文件数)
- §7 实战示例覆盖从 PRD 到交付的完整流程
- §9 协作机制时序图包含开发者与 AI Agent 的完整交互
Linter 配置
-
build/linter/pmd-exchange-ruleset.xml--- 包含 always-on.md 对应的 PMD 代码质量规则 -
build/linter/checkstyle-exchange.xml--- 包含 java-code-style.md 对应的 Checkstyle 代码风格规则 - ArchUnit 测试覆盖模块依赖方向(与 boundaries.md 一致)
- Maven Enforcer 强制依赖方向约束
自动化脚本
-
pre-check.ps1--- 检查 JDK / Maven / Git / 项目结构 -
worktree-manager.ps1--- 支持 create / list / clean / status 命令 -
collect-sidecar.ps1--- 采集编译/测试/Linter 结果到 JSON -
decay-detector.ps1--- 检测文档过期/漂移/规则命中率/技术栈对齐 -
governance-report.ps1--- 生成 Markdown 治理报告 -
index-sync-check.ps1--- 对比磁盘文件 vs 全景索引,输出差异
全局一致性
-
AGENTS.md快速导航表包含全部文件(Rules 4 + Skills 8 + Wiki 11 + Changes 3 + 项目入口 3 + 脚本 6 + Linter 2) - 运行
index-sync-check.ps1通过(磁盘文件数 = 索引文件数) -
ai-coding-guide.md§5 全景索引与磁盘实际文件一致 -
pr-checklist.md的自动化门禁检查项与脚本产出对应
9. 四层体系全景总结
完成全部 6 步后,你的项目应具备以下完整的 Harness Engineering 文档体系:
项目根/
├── AGENTS.md ← Step 1: 项目入口
├── README.md ← Step 1: 项目说明
├── CHANGELOG.md ← Step 5: 变更记录
│
├── .qoder/
│ ├── rules/ ← Step 2: Rules 层(4 文件)
│ │ ├── always-on.md
│ │ ├── agent-workflow.md
│ │ ├── java-code-style.md
│ │ └── java-testing.md
│ │
│ └── skills/ ← Step 4: Skills 层(8 文件)
│ ├── prd-to-code.md
│ ├── flow-orchestration.md
│ ├── design-review.md
│ ├── add-vendor-adapter.md
│ ├── add-entity-mapper.md
│ ├── add-api-endpoint.md
│ ├── doc-gardening.md
│ └── auto-governance.md
│
├── docs/
│ ├── ai-coding-guide.md ← Step 6: 总指南
│ │
│ ├── architecture/ ← Step 1: 架构基线(4 文件)
│ │ ├── overview.md
│ │ ├── boundaries.md
│ │ ├── data-flow.md
│ │ └── code-assets.md
│ │
│ ├── conventions/ ← Step 3: 编码规范(6 文件)
│ │ ├── README.md
│ │ ├── naming.md
│ │ ├── error-handling.md
│ │ ├── testing.md
│ │ ├── logging.md
│ │ └── linter-rules.md
│ │
│ ├── design/ ← Step 3: 设计模板(1 文件 + 按需)
│ │ └── _template.md
│ │
│ ├── reference/ ← Step 3: 参考资料(4 文件)
│ │ ├── prd-template.md
│ │ ├── brd-to-prd-guide.md
│ │ ├── api.spec.yaml
│ │ └── error-codes.md
│ │
│ ├── changes/ ← Step 5: 变更管理(3 文件)
│ │ ├── README.md
│ │ ├── pr-checklist.md
│ │ └── impact-analysis.md
│ │
│ ├── plans/ ← Step 5: 迭代计划(2 文件)
│ │ ├── current-sprint.md
│ │ └── backlog.md
│ │
│ └── guides/ ← 本系列 6 篇指南
│ ├── harness-step1-project-entry-and-architecture.md
│ ├── harness-step2-rules-layer.md
│ ├── harness-step3-wiki-layer.md
│ ├── harness-step4-skills-layer.md
│ ├── harness-step5-changes-layer.md
│ └── harness-step6-guide-and-automation.md
│
└── build/
├── linter/ ← Step 6: Linter 配置(2 文件)
│ ├── pmd-exchange-ruleset.xml
│ └── checkstyle-exchange.xml
│
└── automation/scripts/ ← Step 6: 自动化脚本(6 文件)
├── pre-check.ps1
├── worktree-manager.ps1
├── collect-sidecar.ps1
├── decay-detector.ps1
├── governance-report.ps1
└── index-sync-check.ps1
总计约 40 个文件(含本系列 6 篇指南),覆盖:
- 4 个 Rules 文件:始终生效的硬性约束
- 8 个 Skills 文件:按需触发的操作手册
- ~17 个 Wiki 文件:人机共读的知识库
- 3 个 Changes 文件:变更管理机制
- 3 个项目入口文件:AGENTS.md / README.md / CHANGELOG.md
- 1 个总指南:全局编织
- 2 个 Linter 配置:自动化规则拦截
- 6 个自动化脚本:客观数据采集与治理
- 6 篇搭建指南:本系列文档
10. 体系运转的核心机制
完成搭建后,体系通过以下机制持续运转:
10.1 PRD 驱动闭环
(BRD) → PRD → 设计 → 评审(人工门禁)→ 编码 → 编译验证 → 文档同步 → 自检(自动门禁)→ PR
10.2 人工确认门禁
在设计评审环节,AI Agent 必须暂停等待人类确认。这是"AI 生成,人类驾驭"的核心体现。
10.3 客观数据门禁
在交付前自检环节,AI Agent 必须读取 sidecar JSON 做客观决策。编译失败/测试失败时禁止交付。
10.4 文档一致性保障
新增/删除文件 → 更新 ai-coding-guide.md §5 全景索引 → index-sync-check.ps1 验证一致性
10.5 文档健康度保障
doc-gardening.md → 检测过期/orphan/索引不一致 → 标记并修复
decay-detector.ps1 → 检测代码-文档漂移/规则命中率 → 生成衰减报告
10.6 持续治理
collect-sidecar.ps1 → 采集客观数据 → governance-report.ps1 → 生成治理报告 → 团队 review
至此,Harness Engineering 四层文档体系搭建完成。团队可以通过提交 PRD,由 AI Agent 在严格约束下完成从设计到交付的全流程,同时通过自动化门禁确保产出质量。