Files
Neo-ZQYY/.kiro/specs/repo-audit/design.md

425 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设计文档:仓库治理只读审计
## 概述
本设计描述三个 Python 审计脚本的实现方案,用于对 etl-billiards 仓库进行只读分析并生成三份 Markdown 报告。脚本仅读取文件系统和源代码,不连接数据库、不修改任何现有文件,仅在 `docs/audit/repo/` 目录下输出报告。
审计脚本采用模块化设计:一个共享的仓库扫描器负责遍历文件系统,三个独立的分析器分别生成文件清单、流程树和文档对齐报告。
## 架构
```mermaid
graph TD
A[scripts/audit/run_audit.py<br/>审计主入口] --> B[scripts/audit/scanner.py<br/>仓库扫描器]
A --> C[scripts/audit/inventory_analyzer.py<br/>文件清单分析器]
A --> D[scripts/audit/flow_analyzer.py<br/>流程树分析器]
A --> E[scripts/audit/doc_alignment_analyzer.py<br/>文档对齐分析器]
B --> F[文件系统<br/>只读遍历]
C --> G[docs/audit/repo/file_inventory.md]
D --> H[docs/audit/repo/flow_tree.md]
E --> I[docs/audit/repo/doc_alignment.md]
C --> B
D --> B
E --> B
```
### 执行流程
1. `run_audit.py` 作为主入口,初始化扫描器并依次调用三个分析器
2. `scanner.py` 递归遍历仓库,构建文件元信息列表(路径、大小、类型)
3. 各分析器接收扫描结果,执行各自的分析逻辑,输出 Markdown 报告
4. 所有报告写入 `docs/audit/repo/` 目录
## 组件与接口
### 1. 仓库扫描器 (`scripts/audit/scanner.py`)
负责递归遍历仓库文件系统,返回结构化的文件元信息。
```python
@dataclass
class FileEntry:
"""单个文件/目录的元信息"""
rel_path: str # 相对于仓库根目录的路径
is_dir: bool # 是否为目录
size_bytes: int # 文件大小(目录为 0
extension: str # 文件扩展名(小写,含点号)
is_empty_dir: bool # 是否为空目录
EXCLUDED_PATTERNS: list[str] = [
".git", "__pycache__", ".pytest_cache",
"*.pyc", ".kiro",
]
def scan_repo(root: Path, exclude: list[str] = EXCLUDED_PATTERNS) -> list[FileEntry]:
"""递归扫描仓库,返回所有文件和目录的元信息列表"""
...
```
### 2. 文件清单分析器 (`scripts/audit/inventory_analyzer.py`)
对扫描结果进行用途分类和处置标签分配。
```python
# 用途分类枚举
class Category(str, Enum):
CORE_CODE = "核心代码"
CONFIG = "配置"
DATABASE_DEF = "数据库定义"
TEST = "测试"
DOCS = "文档"
SCRIPTS = "脚本工具"
GUI = "GUI"
BUILD_DEPLOY = "构建与部署"
LOG_OUTPUT = "日志与输出"
TEMP_DEBUG = "临时与调试"
OTHER = "其他"
# 处置标签枚举
class Disposition(str, Enum):
KEEP = "保留"
CANDIDATE_DELETE = "候选删除"
CANDIDATE_ARCHIVE = "候选归档"
NEEDS_REVIEW = "待确认"
@dataclass
class InventoryItem:
"""清单条目"""
rel_path: str
category: Category
disposition: Disposition
description: str
def classify(entry: FileEntry) -> InventoryItem:
"""根据路径、扩展名等规则对单个文件/目录进行分类和标签分配"""
...
def build_inventory(entries: list[FileEntry]) -> list[InventoryItem]:
"""批量分类所有文件条目"""
...
def render_inventory_report(items: list[InventoryItem], repo_root: str) -> str:
"""生成 Markdown 格式的文件清单报告"""
...
```
**分类规则(按优先级从高到低)**
| 路径模式 | 用途分类 | 默认处置 |
|---------|---------|---------|
| `tmp/` 下所有文件 | 临时与调试 | 候选删除/候选归档 |
| `logs/``export/` 下的运行时产出 | 日志与输出 | 候选归档 |
| `*.lnk``*.rar` 文件 | 其他 | 候选删除 |
| 空目录(如 `Deleded & backup/` | 其他 | 候选删除 |
| `tasks/``loaders/``scd/``orchestration/``quality/``models/``utils/``api/` | 核心代码 | 保留 |
| `config/` | 配置 | 保留 |
| `database/*.sql``database/migrations/` | 数据库定义 | 保留 |
| `database/*.py` | 核心代码 | 保留 |
| `tests/` | 测试 | 保留 |
| `docs/` | 文档 | 保留 |
| `scripts/` 下的 `.py` 文件 | 脚本工具 | 保留/待确认 |
| `gui/` | GUI | 保留 |
| `setup.py``build_exe.py``*.bat``*.sh``*.ps1` | 构建与部署 | 保留 |
| 根目录散落文件(`Prompt用.md``Untitled``fix_symbols.py` 等) | 其他 | 待确认 |
### 3. 流程树分析器 (`scripts/audit/flow_analyzer.py`)
通过静态分析 Python 源码的 `import` 语句和类继承关系,构建从入口到末端模块的调用树。
```python
@dataclass
class FlowNode:
"""流程树节点"""
name: str # 节点名称(模块名/类名/函数名)
source_file: str # 所在源文件路径
node_type: str # 类型entry/module/class/function
children: list["FlowNode"]
def parse_imports(filepath: Path) -> list[str]:
"""使用 ast 模块解析 Python 文件的 import 语句,返回被导入的本地模块列表"""
...
def build_flow_tree(repo_root: Path, entry_file: str) -> FlowNode:
"""从指定入口文件出发,递归追踪 import 链,构建流程树"""
...
def find_orphan_modules(repo_root: Path, all_entries: list[FileEntry], reachable: set[str]) -> list[str]:
"""找出未被任何入口直接或间接引用的 Python 模块"""
...
def render_flow_report(trees: list[FlowNode], orphans: list[str], repo_root: str) -> str:
"""生成 Markdown 格式的流程树报告(含 Mermaid 图和缩进文本)"""
...
```
**入口点识别**
- CLI 入口:`cli/main.py``main()` 函数
- GUI 入口:`gui/main.py``main()` 函数
- 批处理入口:`run_etl.bat``run_gui.bat``run_ods.bat` → 解析其中的 `python` 命令
- 运维脚本:`scripts/*.py` → 各自的 `if __name__ == "__main__"`
**静态分析策略**
- 使用 Python `ast` 模块解析源文件,提取 `import``from ... import` 语句
- 仅追踪项目内部模块(排除标准库和第三方包)
- 通过 `orchestration/task_registry.py` 的注册语句识别所有任务类及其源文件
- 通过类继承关系(`BaseTask``BaseLoader``BaseDwsTask` 等)识别任务和加载器层级
### 4. 文档对齐分析器 (`scripts/audit/doc_alignment_analyzer.py`)
检查文档与代码之间的映射关系、过期点、冲突点和缺失点。
```python
@dataclass
class DocMapping:
"""文档与代码的映射关系"""
doc_path: str # 文档文件路径
doc_topic: str # 文档主题
related_code: list[str] # 关联的代码文件/模块
status: str # 状态aligned/stale/conflict/orphan
@dataclass
class AlignmentIssue:
"""对齐问题"""
doc_path: str
issue_type: str # stale/conflict/missing
description: str
related_code: str
def scan_docs(repo_root: Path) -> list[str]:
"""扫描所有文档文件路径"""
...
def extract_code_references(doc_path: Path) -> list[str]:
"""从文档中提取代码引用(文件路径、类名、函数名、表名等)"""
...
def check_reference_validity(ref: str, repo_root: Path) -> bool:
"""检查文档中的代码引用是否仍然有效"""
...
def find_undocumented_modules(repo_root: Path, documented: set[str]) -> list[str]:
"""找出缺少文档的核心代码模块"""
...
def check_ddl_vs_dictionary(repo_root: Path) -> list[AlignmentIssue]:
"""比对 DDL 文件与数据字典文档的覆盖度"""
...
def check_api_samples_vs_parsers(repo_root: Path) -> list[AlignmentIssue]:
"""比对 API 响应样本与 ODS 表结构/解析器的一致性"""
...
def render_alignment_report(mappings: list[DocMapping], issues: list[AlignmentIssue], repo_root: str) -> str:
"""生成 Markdown 格式的文档对齐报告"""
...
```
**文档来源识别**
- `docs/` 目录下的 `.md``.txt``.csv` 文件
- 根目录的 `README.md`
- `开发笔记/` 目录
- 各模块内的 `README.md``gui/README.md``fetch-test/README.md`
- `.kiro/steering/` 下的引导文件
- `docs/test-json-doc/` 下的 API 响应样本及分析文档
**对齐检查策略**
- 过期点检测:文档中引用的文件路径、类名、函数名在代码中已不存在
- 冲突点检测DDL 中的表/字段定义与数据字典文档不一致API 样本字段与解析器不匹配
- 缺失点检测:核心代码模块(`tasks/``loaders/``orchestration/` 等)缺少对应文档
### 5. 审计主入口 (`scripts/audit/run_audit.py`)
```python
def run_audit(repo_root: Path | None = None) -> None:
"""执行完整审计流程,生成三份报告到 docs/audit/"""
...
if __name__ == "__main__":
run_audit()
```
## 数据模型
### FileEntry文件元信息
| 字段 | 类型 | 说明 |
|------|------|------|
| `rel_path` | `str` | 相对路径 |
| `is_dir` | `bool` | 是否为目录 |
| `size_bytes` | `int` | 文件大小 |
| `extension` | `str` | 扩展名 |
| `is_empty_dir` | `bool` | 是否为空目录 |
### InventoryItem清单条目
| 字段 | 类型 | 说明 |
|------|------|------|
| `rel_path` | `str` | 相对路径 |
| `category` | `Category` | 用途分类 |
| `disposition` | `Disposition` | 处置标签 |
| `description` | `str` | 简要说明 |
### FlowNode流程树节点
| 字段 | 类型 | 说明 |
|------|------|------|
| `name` | `str` | 节点名称 |
| `source_file` | `str` | 源文件路径 |
| `node_type` | `str` | 节点类型 |
| `children` | `list[FlowNode]` | 子节点列表 |
### DocMapping / AlignmentIssue文档对齐
| 字段 | 类型 | 说明 |
|------|------|------|
| `doc_path` | `str` | 文档路径 |
| `doc_topic` / `issue_type` | `str` | 主题/问题类型 |
| `related_code` | `list[str]` / `str` | 关联代码 |
| `status` / `description` | `str` | 状态/描述 |
## 正确性属性
*属性Property是指在系统所有合法执行路径上都应成立的特征或行为——本质上是对"系统应该做什么"的形式化陈述。属性是连接人类可读规格说明与机器可验证正确性保证之间的桥梁。*
### Property 1: classify 完整性
*对于任意* `FileEntry``classify` 函数返回的 `InventoryItem``category` 字段应属于 `Category` 枚举,`disposition` 字段应属于 `Disposition` 枚举,且 `description` 字段为非空字符串。
**Validates: Requirements 1.2, 1.3**
### Property 2: 清单渲染完整性
*对于任意* `InventoryItem` 列表,`render_inventory_report` 生成的 Markdown 文本中,每个条目对应的行应包含该条目的 `rel_path``category.value``disposition.value``description` 四个字段。
**Validates: Requirements 1.4**
### Property 3: 空目录标记为候选删除
*对于任意* `is_empty_dir=True``FileEntry``classify` 返回的 `disposition` 应为 `Disposition.CANDIDATE_DELETE`
**Validates: Requirements 1.5**
### Property 4: .lnk/.rar 文件标记为候选删除
*对于任意* 扩展名为 `.lnk``.rar``FileEntry``classify` 返回的 `disposition` 应为 `Disposition.CANDIDATE_DELETE`
**Validates: Requirements 1.6**
### Property 5: tmp/ 下文件处置范围
*对于任意* `rel_path``tmp/` 开头的 `FileEntry``classify` 返回的 `disposition` 应为 `Disposition.CANDIDATE_DELETE``Disposition.CANDIDATE_ARCHIVE` 之一。
**Validates: Requirements 1.7**
### Property 6: 运行时产出目录标记为候选归档
*对于任意* `rel_path``logs/``export/` 开头且非 `__init__.py``FileEntry``classify` 返回的 `disposition` 应为 `Disposition.CANDIDATE_ARCHIVE`
**Validates: Requirements 1.8**
### Property 7: 扫描器排除规则
*对于任意* 文件树,`scan_repo` 返回的 `FileEntry` 列表中不应包含 `rel_path` 匹配排除模式(`.git``__pycache__``.pytest_cache`)的条目。
**Validates: Requirements 1.1**
### Property 8: 清单按分类分组
*对于任意* `InventoryItem` 列表,`render_inventory_report` 生成的 Markdown 中,同一 `Category` 的条目应连续出现(即按分类分组排列)。
**Validates: Requirements 1.10**
### Property 9: 流程树节点 source_file 有效性
*对于任意* `FlowNode` 树中的节点,`source_file` 字段应为非空字符串,且对应的文件在仓库中实际存在。
**Validates: Requirements 2.7**
### Property 10: 孤立模块检测正确性
*对于任意* 文件集合和可达模块集合,`find_orphan_modules` 返回的孤立模块列表中的每个模块都不应出现在可达集合中,且可达集合中的每个模块都不应出现在孤立列表中。
**Validates: Requirements 2.8**
### Property 11: 过期引用检测
*对于任意* 文档中提取的代码引用,若该引用指向的文件路径在仓库中不存在,则 `check_reference_validity` 应返回 `False`
**Validates: Requirements 3.3**
### Property 12: 缺失文档检测
*对于任意* 核心代码模块集合和已文档化模块集合,`find_undocumented_modules` 返回的缺失列表应恰好等于核心模块集合与已文档化集合的差集。
**Validates: Requirements 3.5**
### Property 13: 统计摘要一致性
*对于任意* 报告的统计摘要,各分类/标签的计数之和应等于对应条目列表的总长度。
**Validates: Requirements 4.5, 4.6, 4.7**
### Property 14: 报告头部元信息
*对于任意* 报告输出,头部应包含一个符合 ISO 格式的时间戳字符串和仓库根目录路径字符串。
**Validates: Requirements 4.2**
### Property 15: 写操作仅限 docs/audit/
*对于任意* 审计执行过程,所有文件写操作的目标路径应以 `docs/audit/repo/` 为前缀。
**Validates: Requirements 5.2**
### Property 16: 文档对齐报告分区完整性
*对于任意* `render_alignment_report` 的输出Markdown 文本应包含"映射关系"、"过期点"、"冲突点"、"缺失点"四个分区标题。
**Validates: Requirements 3.8**
## 错误处理
| 场景 | 处理方式 |
|------|---------|
| 文件读取权限不足 | 记录警告到报告的"错误"分区,跳过该文件,继续处理 |
| Python 源文件语法错误(`ast.parse` 失败) | 记录警告,将该文件标记为"待确认",不中断流程树构建 |
| 文档中的代码引用格式无法解析 | 跳过该引用,不产生误报 |
| DDL 文件 SQL 语法不规范 | 使用正则提取 `CREATE TABLE` 和列定义,容忍非标准语法 |
| `docs/audit/repo/` 目录创建失败 | 抛出异常并终止,因为无法输出报告 |
| 编码问题(非 UTF-8 文件) | 尝试 `utf-8``gbk``latin-1` 回退读取,记录编码警告 |
## 测试策略
### 测试框架
- 单元测试与属性测试均使用 `pytest`
- 属性测试库:`hypothesis`Python 生态最成熟的属性测试框架)
- 测试文件位于 `tests/unit/test_audit_*.py`
### 单元测试
针对具体示例和边界情况:
- 扫描器对实际仓库子集的遍历结果
- classify 对已知文件路径的分类正确性(如 `tmp/hebing.py` → 临时与调试/候选删除)
- 入口点识别对实际仓库的结果
- DDL 与数据字典的比对结果
- 文件读取失败时的容错行为
- `docs/audit/repo/` 目录不存在时的自动创建
### 属性测试
每个正确性属性对应一个属性测试,使用 `hypothesis` 生成随机输入:
- 每个属性测试至少运行 100 次迭代
- 每个测试用注释标注对应的设计属性编号
- 标注格式:**Feature: repo-audit, Property {N}: {属性标题}**
**生成器策略**
- `FileEntry` 生成器:随机路径(含各种扩展名、目录层级)、随机大小、随机 is_dir/is_empty_dir
- `InventoryItem` 生成器:随机 Category/Disposition 组合、随机描述文本
- `FlowNode` 生成器:随机树结构(限制深度和宽度)
- 文件树生成器:构造临时目录结构用于扫描器测试