Skip to content

Commit 37eb840

Browse files
committed
feat: 完成 5.0 Phase 1-2 核心闭环
Phase 1 可视化架构画布(coderef_arch_canvas): - 新增 canvas_generator:自包含 HTML 三层画布(业务/技术/代码层) - 拖拽定义归属、业务→技术连线、差距高亮、导出/复制目标架构 JSON - 零外部依赖(无 unpkg/cdn),数据层复用 arch_gap_analyzer + 知识图谱 Phase 2 重构任务卡 + 对齐验证闭环: - 新增 refactor_task_generator(coderef_refactor_plan):差距 → 编程 AI 可执行任务卡 (create_module/fix_dependency/break_cycle/implement_flow/move_module/split_module, 含图谱确定性影响范围 + 架构/功能验证标准),按执行顺序排序 - 新增 arch_alignment_verifier(coderef_arch_verify):四维对齐度评分 (职责40%+依赖30%+业务20%+健康10%)+ 差距复检,支持 changed_files 增量模式 配套: - mcp_server 注册 3 个新工具(工具数 40 → 43),全轻量同步 - README 工具清单/结构/更新日志补充 5.0 条目 - docs/5.0-plan.md 补充 Phase 1/Phase 2 详细设计与验证结果 - 已在 requests 项目全链路验证:任务卡 24 张、对齐度 0.63、画布 23.7KB 自包含
1 parent ceece72 commit 37eb840

6 files changed

Lines changed: 1215 additions & 22 deletions

File tree

‎README.md‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
## 它是什么
1717

18-
CodeRef-AI 通过 MCP 协议暴露 **40 个工具**,同时服务两类人:
18+
CodeRef-AI 通过 MCP 协议暴露 **43 个工具**,同时服务两类人:
1919

2020
- **编程 AI 的治理外脑**:让 AI 不再逐文件读代码,而是像查数据库一样查询项目的结构、调用链与风险;持有一条 LLM/CodeRabbit 论断时,还能用静态图谱做确定性核验,再决定采不采信。
2121
- **非编程人员的技术助理**:把看不懂的代码变成通俗的健康仪表盘、Wiki 文档和流程确证,让你不用读代码,也能确认项目有没有按你的设想运转。
@@ -60,7 +60,7 @@ CodeRef 4.0 由四个引擎驱动,覆盖「审计 → 记忆 → 创新 →
6060
| **变更守护引擎** | 拦截 AI 把代码改坏,输出人能看懂的变更报告 | `coderef_change_guard` `coderef_change_report` |
6161
| **人话解读平台** | 把确定性格子结论翻译成非编程人员听得懂的"人话" | `coderef_interpret` |
6262

63-
## 40 个 MCP 工具
63+
## 43 个 MCP 工具
6464

6565
### 审计引擎
6666

@@ -76,6 +76,9 @@ CodeRef 4.0 由四个引擎驱动,覆盖「审计 → 记忆 → 创新 →
7676
| `coderef_target_arch_set` | 设置/更新目标架构 JSON(5.0 架构推回正轨的参照系),校验后落盘 `<project>/.coderef/target_arch.json`。纯确定性校验,不依赖 LLM | 否 |
7777
| `coderef_target_arch_get` | 获取当前目标架构 JSON | 否 |
7878
| `coderef_arch_gap` | 架构差距分析(5.0 核心):对比现状知识图谱与目标架构,输出 7 类确定性差距(职责缺失/依赖违例/循环依赖/业务断链/游离模块/上帝模块/异常规模)。纯静态、复用 arch_audit,不依赖 LLM | 否 |
79+
| `coderef_arch_canvas` | 可视化架构画布(5.0 Phase 1):自包含 HTML 三层画布(业务/技术/代码层),拖拽定义归属、业务→技术连线、差距高亮、导出目标架构 JSON | 否 |
80+
| `coderef_refactor_plan` | 重构任务卡(5.0 Phase 2):把差距清单转为编程 AI 可执行的任务卡(create_module/fix_dependency/break_cycle/implement_flow/move_module/split_module + 影响范围 + 验证标准) | 否 |
81+
| `coderef_arch_verify` | 架构对齐验证(5.0 Phase 2):四维对齐度评分(职责40%+依赖30%+业务20%+健康10%)+ 差距复检;支持 changed_files 增量模式 | 否 |
7982
| `coderef_architecture` | 架构分析图谱 + 交互式 HTML 模块画布 | 否 |
8083
| `coderef_docs` | 项目 Wiki 文档生成 + 子项目探测 | 是 |
8184
| `coderef_docs_read` | 按需读取已生成 Wiki 文档正文(返回内容而非路径,解决 AI 无法 fs 访问外部文件夹) | 否 |
@@ -325,6 +328,9 @@ coderef-ai/
325328
│ ├── arch_audit.py # 架构腐化诊断(循环依赖/上帝模块/分层违例)
326329
│ ├── target_arch_schema.py # 目标架构 JSON Schema(5.0:人定义的正轨)
327330
│ ├── arch_gap_analyzer.py # 架构差距分析器(5.0:现状 vs 目标架构)
331+
│ ├── canvas_generator.py # 可视化架构画布(5.0 Phase 1:三层拖拽画布)
332+
│ ├── refactor_task_generator.py # 重构任务卡生成器(5.0 Phase 2)
333+
│ ├── arch_alignment_verifier.py # 架构对齐验证器(5.0 Phase 2:四维评分)
328334
│ ├── graph_closure.py # 调用闭包计算(flow_verify 与 wiki_cross_verify 共用)
329335
│ ├── workflow_graph.py # 架构图生成器(vis-network)
330336
│ ├── diagram_generator.py # 图表/画布生成
@@ -439,13 +445,16 @@ CodeRef-AI 从"一份看得懂的项目简报"出发,一步步长出静态审
439445

440446
> 4.X 系列已定版,完整更新日志(v3.0 – v4.9.12)已归档至 [docs/changelog/CHANGELOG.md](docs/changelog/CHANGELOG.md)。
441447
442-
### v5.0.0 — 5.0 启动:架构推回正轨(Phase 0 骨架)
448+
### v5.0.0 — 5.0 启动:架构推回正轨(Phase 0-2 核心闭环)
443449

444450
- **目标架构 JSON Schema**(新增 `target_arch_schema`):定义"人定义的正轨"标准结构(业务层 business_flows / 技术层 tech_roles / 约束 constraints),零依赖手写校验,结构化错误返回
445451
- **架构差距分析器**(新增 `arch_gap_analyzer`):对比现状知识图谱与目标架构,输出 7 类确定性差距(missing 职责缺失 / dependency_violation 依赖违例 / cycle 循环依赖 / business_gap 业务断链 / unassigned 游离模块 / god_module 上帝模块 / large_module 异常规模),复用 arch_audit 不重写
446-
- **新增 MCP 工具**:`coderef_target_arch_set`(设置目标架构,落盘 `<project>/.coderef/target_arch.json`)/ `coderef_target_arch_get`(获取)/ `coderef_arch_gap`(差距分析),全部纯静态、确定性、轻量同步
447-
- **开发计划**:`docs/5.0-plan.md`(Phase 0 详细设计 + 8 个设计疑点决策 + 验证方案)
448-
- 版本号:4.9.12 → 5.0.0
452+
- **可视化架构画布**(新增 `canvas_generator`,Phase 1):自包含 HTML 三层画布(业务/技术/代码层),拖拽定义归属、业务→技术连线、差距高亮、导出目标架构 JSON,零外部依赖离线可用
453+
- **重构任务卡生成器**(新增 `refactor_task_generator`,Phase 2):差距清单 → 编程 AI 可执行任务卡(create_module/fix_dependency/break_cycle/implement_flow/move_module/split_module + 图谱影响范围 + 验证标准)
454+
- **架构对齐验证器**(新增 `arch_alignment_verifier`,Phase 2):四维对齐度评分(职责40%+依赖30%+业务20%+健康10%)+ 差距复检,支持 changed_files 增量模式
455+
- **新增 MCP 工具**(6 个):`coderef_target_arch_set` / `coderef_target_arch_get` / `coderef_arch_gap` / `coderef_arch_canvas` / `coderef_refactor_plan` / `coderef_arch_verify`,全部纯静态、确定性、轻量同步
456+
- **开发计划**:`docs/5.0-plan.md`(Phase 0-2 详细设计 + 设计疑点决策 + 验证方案)
457+
- **版本号**:4.9.12 → 5.0.0(工具数 37 → 43)
449458

450459
### v4.9.12 — 修复 Coderef-Test 测试报告(20260823-v4.9.11-r5)遗留项
451460

‎core/arch_alignment_verifier.py‎

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
ArchAlignmentVerifier — 架构对齐验证器(5.0 Phase 2)
4+
5+
重构后确定性验证现状与目标架构的对齐度,输出四维评分 + 差距复检。
6+
7+
对齐度评分(0–100):
8+
职责对齐 40% 已归属模块数 / 总模块数(游离模块惩罚)
9+
依赖健康 30% 1 - 违例依赖边 / 总模块依赖边
10+
业务覆盖 20% 有实现角色数 / 总角色数
11+
代码健康 10% 复用 arch_audit 健康度归一化(0–10 → 0–1)
12+
13+
增量验证:传入本次改动文件集合时,只对受影响子图(含改动文件的调用闭包)
14+
复检,快速反馈单张任务卡是否达标。
15+
"""
16+
17+
import os
18+
from typing import Any, Dict, List, Optional
19+
from core.arch_gap_analyzer import analyze_gap, _is_test_module, _match_module_ids
20+
from core.arch_audit import locate_kg_db, module_of
21+
from core.graph_closure import load_graph, file_base
22+
23+
WEIGHTS = {
24+
"responsibility": 0.40,
25+
"dependency": 0.30,
26+
"business": 0.20,
27+
"health": 0.10,
28+
}
29+
30+
31+
class ArchAlignmentVerifier:
32+
"""架构对齐验证器(5.0 Phase 2)"""
33+
34+
def verify(
35+
self,
36+
project_path: str,
37+
target_arch: Optional[dict] = None,
38+
changed_files: Optional[List[str]] = None,
39+
db_path: Optional[str] = None,
40+
) -> dict:
41+
"""架构对齐验证主入口。
42+
43+
Args:
44+
project_path: 目标项目路径。
45+
target_arch: 目标架构(缺省读取已存储)。
46+
changed_files: 本次改动文件集合(增量验证时传入;None = 全量)。
47+
db_path: 知识图谱 db(缺省自动定位)。
48+
49+
Returns:
50+
score 各维度 + 总对齐度 + 差距复检。
51+
"""
52+
db = db_path or locate_kg_db(project_path)
53+
if not db or not os.path.exists(db):
54+
return {"ok": False, "project_path": project_path,
55+
"message": "知识图谱不存在,需先构建", "score": 0.0,
56+
"dimensions": {}, "gaps": []}
57+
58+
if target_arch is None:
59+
target_arch = self._load_stored_arch(project_path)
60+
if target_arch is None:
61+
return {"ok": False, "project_path": project_path,
62+
"message": "缺少目标架构,请先设置或传入", "score": 0.0,
63+
"dimensions": {}, "gaps": []}
64+
65+
# 1. 全量差距(供评分)
66+
gap_result = analyze_gap(project_path, target_arch, db_path=db)
67+
68+
nodes, adj = load_graph(db)
69+
70+
# 2. 子图裁剪(增量验证):只对改动文件影响的差距复检
71+
if changed_files:
72+
focus = self._focus_set(changed_files, nodes)
73+
gaps = self._filter_gaps_in_focus(gap_result.get("gaps", []), focus)
74+
else:
75+
gaps = gap_result.get("gaps", [])
76+
77+
# 3. 四维评分
78+
dimensions = self._score_dimensions(project_path, target_arch,
79+
nodes, adj, gap_result, db)
80+
total = round(
81+
dimensions["responsibility"]["score"] * WEIGHTS["responsibility"]
82+
+ dimensions["dependency"]["score"] * WEIGHTS["dependency"]
83+
+ dimensions["business"]["score"] * WEIGHTS["business"]
84+
+ dimensions["health"]["score"] * WEIGHTS["health"],
85+
2,
86+
)
87+
88+
return {
89+
"ok": True,
90+
"project_path": project_path,
91+
"tool": "coderef_arch_verify",
92+
"mode": "incremental" if changed_files else "full",
93+
"changed_files": changed_files or [],
94+
"score": total,
95+
"dimensions": dimensions,
96+
"gaps": gaps,
97+
"remaining": {
98+
"total": len(gaps),
99+
"high": sum(1 for g in gaps if g["severity"] == "high"),
100+
"medium": sum(1 for g in gaps if g["severity"] == "medium"),
101+
"low": sum(1 for g in gaps if g["severity"] == "low"),
102+
},
103+
}
104+
105+
# ────────────────────────────────────────────────
106+
# 内部方法
107+
# ────────────────────────────────────────────────
108+
109+
def _load_stored_arch(self, project_path: str) -> Optional[dict]:
110+
import json
111+
path = os.path.join(project_path, ".coderef", "target_arch.json")
112+
if os.path.exists(path):
113+
with open(path, "r", encoding="utf-8") as f:
114+
return json.load(f)
115+
return None
116+
117+
def _score_dimensions(self, project_path, target_arch, nodes, adj,
118+
gap_result, db) -> dict:
119+
roles = target_arch.get("tech_roles") or []
120+
total_mods = sum(1 for n in nodes.values()
121+
if n.get("type") == "module"
122+
and not _is_test_module(module_of(n, project_path) or n.get("name", "")))
123+
assigned_ids = set()
124+
for role in roles:
125+
assigned_ids |= _match_module_ids(nodes, project_path,
126+
role.get("target_modules", []))
127+
responsibility = len(assigned_ids) / total_mods if total_mods else 1.0
128+
129+
# 依赖健康:违例边 / 文件级模块依赖边
130+
module_edges = self._count_module_edges(nodes, adj, project_path)
131+
violation_edges = 0
132+
for g in gap_result.get("gaps", []):
133+
if g.get("type") == "dependency_violation":
134+
violation_edges += 1
135+
elif g.get("type") == "cycle":
136+
# 环中边近似 = 环长-1(保守)
137+
violation_edges += max(1, len(g.get("modules", [])) - 1)
138+
dependency = max(0.0, 1.0 - violation_edges / max(1, module_edges))
139+
140+
business = self._score_business(target_arch, nodes, project_path)
141+
142+
health = self._score_health(project_path, db)
143+
144+
return {
145+
"responsibility": {
146+
"score": round(responsibility, 2),
147+
"weight": WEIGHTS["responsibility"],
148+
"assigned": len(assigned_ids),
149+
"total_modules": total_mods,
150+
},
151+
"dependency": {
152+
"score": round(dependency, 2),
153+
"weight": WEIGHTS["dependency"],
154+
"violations": violation_edges,
155+
"module_edges": module_edges,
156+
},
157+
"business": {
158+
"score": round(business["ratio"], 2),
159+
"weight": WEIGHTS["business"],
160+
"implemented_roles": business["implemented"],
161+
"total_roles": business["total"],
162+
},
163+
"health": {
164+
"score": round(health, 2),
165+
"weight": WEIGHTS["health"],
166+
"arch_health_raw": None,
167+
},
168+
}
169+
170+
def _count_module_edges(self, nodes: dict, adj: Dict[str, list],
171+
project_path: str) -> int:
172+
"""统计文件级模块依赖边(去重 pair,排除测试模块)。"""
173+
pairs = set()
174+
for nid, targets in adj.items():
175+
src_mod = module_of(nodes.get(nid, {}), project_path) or ""
176+
if not src_mod or _is_test_module(src_mod):
177+
continue
178+
for t in targets:
179+
dst_mod = module_of(nodes.get(t, {}), project_path) or ""
180+
if not dst_mod or _is_test_module(dst_mod) or dst_mod == src_mod:
181+
continue
182+
pairs.add((src_mod, dst_mod))
183+
return len(pairs)
184+
185+
def _score_business(self, target_arch, nodes, project_path) -> dict:
186+
roles = target_arch.get("tech_roles") or []
187+
implemented = 0
188+
for role in roles:
189+
specs = role.get("target_modules", [])
190+
if any(self._module_exists(project_path, s, nodes) for s in specs):
191+
implemented += 1
192+
total = len(roles)
193+
return {"implemented": implemented, "total": total,
194+
"ratio": implemented / total if total else 1.0}
195+
196+
def _module_exists(self, project_path: str, spec: str, nodes: dict) -> bool:
197+
from core.arch_gap_analyzer import _module_exists
198+
return _module_exists(project_path, spec, nodes)
199+
200+
def _score_health(self, project_path: str, db: str) -> float:
201+
try:
202+
from core.arch_audit import audit as arch_audit
203+
r = arch_audit(project_path, db_path=db)
204+
health_score = r.get("health_score")
205+
if isinstance(health_score, (int, float)):
206+
return min(1.0, health_score / 10.0)
207+
except Exception:
208+
pass
209+
return 0.5 # 保守默认(未计入健康维度)
210+
211+
def _focus_set(self, changed_files: List[str], nodes: dict) -> set:
212+
"""扩展本次改动文件为"受影响文件闭包"(文件级)。"""
213+
bases = {os.path.basename(f).replace(".py", "") for f in changed_files if f}
214+
focus = set(bases)
215+
for nid, n in nodes.items():
216+
base = file_base(n)
217+
b = (os.path.splitext(base)[0] if base else "")
218+
if b in bases:
219+
focus.add(base)
220+
return focus
221+
222+
def _filter_gaps_in_focus(self, gaps: List[dict], focus: set) -> List[dict]:
223+
"""增量:只保留差距涉及的目标模块在受影响闭包内的差距。"""
224+
if not focus:
225+
return gaps
226+
out = []
227+
for g in gaps:
228+
mod = g.get("module") or ""
229+
base = mod.split("/")[-1] if mod else ""
230+
match = (base in focus) or (
231+
g.get("type") == "cycle"
232+
and any((m.split("/")[-1]) in focus for m in g.get("modules", []))
233+
)
234+
if match:
235+
out.append(g)
236+
return out

0 commit comments

Comments
 (0)