feat(web): 项目级配置 + 会话命名/历史 + 设计文档纳入影响调查

- 会话支持 name/project 字段,上传要件定义后自动命名;前端侧边栏会话历史 + localStorage 恢复,顶部只显示会话名
- 新增 ProjectsStore(SQLite)与 /api/projects CRUD;绑定项目后 _rebuild_source 合并模板/规则/代码库/设计文档,上传区仅要件定义
- StructuredSource.design_docs 与 ImpactReport.design_references;影响调查新增既有设计文档确定性交叉引用(无 LLM)
- 同步更新 docs/design.md §12.7、README、_AI_USAGE_LOG.md;全量测试 558 通过,覆盖率 99.10%
This commit is contained in:
lhl
2026-08-27 12:13:28 +08:00
parent 838a93720e
commit 916c5beed7
18 changed files with 1174 additions and 65 deletions
+39
View File
@@ -7,11 +7,13 @@ from __future__ import annotations
import logging
from datetime import date
from pathlib import Path
from genesis.data_models import (
ChangeAnalysis,
ChangeElement,
ChangeType,
DesignReference,
ExcelTable,
ImpactReport,
ImpactWarning,
@@ -66,6 +68,10 @@ def impact_report_to_dict(report: ImpactReport) -> dict:
"warnings": [{"element_id": w.element_id, "issue": w.issue} for w in ca.warnings],
},
"summary": dict(report.summary),
"design_references": [
{"doc_name": r.doc_name, "identifier": r.identifier, "snippet": r.snippet}
for r in report.design_references
],
}
@@ -124,6 +130,7 @@ class ImpactAgent:
"unchanged": unchanged,
"warnings": len(warnings),
}
design_references = self._cross_ref_design_docs(structured_source, lookup)
return ImpactReport(
metadata={
"version": "v1",
@@ -134,8 +141,40 @@ class ImpactAgent:
},
change_analysis=change_analysis,
summary=summary,
design_references=design_references,
)
def _cross_ref_design_docs(
self, structured_source: StructuredSource, lookup: dict[str, list[dict]]
) -> list[DesignReference]:
"""既有设计文档确定性交叉引用(Type A 辅助证据)。
以既有系统(existing_system)解析出的标识符(类/方法/模块名)为锚,
在 design_docs 的 markdown 文本中检索命中,输出 DesignReference 列表。
无 LLM 参与,纯字符串匹配。
"""
design_docs = getattr(structured_source, "design_docs", []) or []
if not design_docs:
return []
identifiers = [k for k in lookup.keys() if len(k) >= 3]
refs: list[DesignReference] = []
for doc in design_docs:
text = getattr(doc, "markdown_content", "") or ""
if not text:
continue
doc_name = Path(getattr(doc, "source_path", "design.docx")).name
text_lower = text.lower()
for ident in identifiers:
il = ident.lower()
idx = text_lower.find(il)
if idx >= 0:
token = text[idx:idx + len(il)] # 设计文档中的原始大小写
start = max(0, idx - 30)
end = min(len(text), idx + len(il) + 30)
snippet = text[start:end].replace("\n", " ").strip()
refs.append(DesignReference(doc_name=doc_name, identifier=token, snippet=snippet))
return refs
# ---------- 内部 ----------
def _classify_table(