feat(impact): Impact Agent MVP —— 变更点定位 + 影响调查书(追加改修场景)

- 门控:用户提供 existing_system 路径 → 进入影响调查;未提供 → 原流程不变
- CodeParser 解析 Java(@RestController/@Service/@Entity/@TableName)+ ExistingSystemExplorer 组装
- ImpactAgent 变更点定位(变更区分×既存対応 确定性比对,无 LLM)→ ImpactReport(JSON 可下载)
- 影响调查结果作为 Writer 生成概要设计书的主上下文({{impact}},无专用影响章)
- source_aggregator 解除 existing_system=None 硬编码
- 既有系统样本 sunOnly/stock-trade-system(无 LICENSE,仅测试输入,保留来源标注)
- 新造股票交易域追加改修样本 要件定義_追加改修_股票.xlsx(对齐 sunOnly 真实类名)
- 全量 351 passed / 99.27% 覆盖;门禁 PASS(16 要素:新规5/変更8/削除3/未受影响50)
This commit is contained in:
lhl
2026-08-23 17:59:08 +08:00
parent da33df92e1
commit 2e11720a2d
383 changed files with 18411 additions and 10 deletions
+126
View File
@@ -0,0 +1,126 @@
"""CodeParser 测试(Impact Agent MVP):Java 项目解析 → CodeStructure。"""
from pathlib import Path
import pytest
from genesis.impact.code_parser import CodeParseError, CodeParser
FIXTURE_JAVA = {
"trade-order/src/main/java/com/trade/order/controller/OrderController.java": (
"package com.trade.order.controller;\n"
"import org.springframework.web.bind.annotation.*;\n"
"@RestController\n"
"@RequestMapping(\"/api/order\")\n"
"public class OrderController {\n"
" @GetMapping(\"/{id}\")\n"
" public String getOrder(@PathVariable Long id) { return \"ok\"; }\n"
" @PostMapping(\"/create\")\n"
" public String createOrder() { return \"ok\"; }\n"
"}\n"
),
"trade-order/src/main/java/com/trade/order/service/OrderQueryService.java": (
"package com.trade.order.service;\n"
"import org.springframework.stereotype.Service;\n"
"@Service\n"
"public class OrderQueryService {\n"
" public void query() {}\n"
" public void update() {}\n"
"}\n"
),
"trade-order/src/main/java/com/trade/order/entity/OrderEntity.java": (
"package com.trade.order.entity;\n"
"import javax.persistence.Entity;\n"
"import javax.persistence.Table;\n"
"@Entity\n"
"@Table(name = \"t_order\")\n"
"public class OrderEntity {\n"
" private Long id;\n"
" private String orderNo;\n"
"}\n"
),
}
def _make_project(tmp_path, files: dict[str, str]) -> Path:
for rel, content in files.items():
p = tmp_path / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content, encoding="utf-8")
return tmp_path
def test_parse_controller_detected(tmp_path):
root = _make_project(tmp_path, FIXTURE_JAVA)
result = CodeParser().parse(root)
ctrls = {c.class_name: c for c in result.controllers}
oc = ctrls["OrderController"]
assert oc.base_path == "/api/order"
assert "/api/order/{id}" in oc.endpoints
assert "/api/order/create" in oc.endpoints
assert oc.source_uri.endswith("OrderController.java")
def test_parse_service_detected(tmp_path):
root = _make_project(tmp_path, FIXTURE_JAVA)
result = CodeParser().parse(root)
svcs = {s.class_name: s for s in result.services}
oq = svcs["OrderQueryService"]
assert "query" in oq.methods
assert "update" in oq.methods
def test_parse_entity_detected(tmp_path):
root = _make_project(tmp_path, FIXTURE_JAVA)
result = CodeParser().parse(root)
ents = {e.class_name: e for e in result.entities}
oe = ents["OrderEntity"]
assert oe.table_name == "t_order"
assert "id" in oe.fields
assert "orderNo" in oe.fields
def test_parse_endpoints_detected(tmp_path):
root = _make_project(tmp_path, FIXTURE_JAVA)
result = CodeParser().parse(root)
paths = {(e.method, e.path) for e in result.endpoints}
assert ("GET", "/api/order/{id}") in paths
assert ("POST", "/api/order/create") in paths
assert all(e.controller == "OrderController" for e in result.endpoints)
def test_parse_modules_top_level_dirs(tmp_path):
root = _make_project(tmp_path, FIXTURE_JAVA)
result = CodeParser().parse(root)
assert "trade-order" in result.modules
assert result.language == "java"
def test_parse_no_java_raises(tmp_path):
(tmp_path / "readme.txt").write_text("hello", encoding="utf-8")
with pytest.raises(CodeParseError):
CodeParser().parse(tmp_path)
def test_parse_missing_root_raises(tmp_path):
with pytest.raises(CodeParseError):
CodeParser().parse(tmp_path / "nope")
def test_parse_real_sample_has_order_controller():
root = Path(__file__).resolve().parents[1] / "samples" / "existing-system"
if not root.exists():
pytest.skip("样本缺失")
result = CodeParser().parse(root)
names = {c.class_name for c in result.controllers}
assert "OrderController" in names
assert any(c.base_path for c in result.controllers)
def test_parse_real_sample_has_entities_and_endpoints():
root = Path(__file__).resolve().parents[1] / "samples" / "existing-system"
if not root.exists():
pytest.skip("样本缺失")
result = CodeParser().parse(root)
assert result.entities, "既有系统样本应至少有一个实体类"
assert result.endpoints, "既有系统样本应至少有一个端点"
assert result.services, "既有系统样本应至少有一个 @Service 类"