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
+99 -1
View File
@@ -9,7 +9,7 @@ from types import SimpleNamespace
import pytest
from genesis.server.store import SessionStore
from genesis.server.store import SessionStore, ProjectsStore
from genesis.server.service import GenesisService, ServiceStepError, FileTypeError
_SAMPLE = Path(__file__).resolve().parents[1] / "sample"
@@ -59,6 +59,104 @@ def test_create_and_upload_files(svc):
assert got.files["requirements"]["name"] == "requirements_newdev.xlsx"
def test_create_session_name_and_project(tmp_path):
svc = GenesisService(
store=SessionStore(db_path=str(tmp_path / "s.db")),
data_root=str(tmp_path / "data"),
engine=FakeEngine(),
)
s = svc.create_session("u1", name="我的会话", project="projA")
got = svc.get_session(s.session_id)
assert got.name == "我的会话"
assert got.project == "projA"
def test_upload_requirements_auto_names_session(tmp_path):
svc = GenesisService(
store=SessionStore(db_path=str(tmp_path / "s.db")),
data_root=str(tmp_path / "data"),
engine=FakeEngine(),
)
s = svc.create_session("u1") # 默认「新会话」
svc.upload_file(s.session_id, "requirements", "要件定义_v2.xlsx", b"PK\x03\x04")
assert svc.get_session(s.session_id).name == "要件定义_v2"
def test_has_file_falls_back_to_project_config(tmp_path):
store = SessionStore(db_path=str(tmp_path / "s.db"))
projects = ProjectsStore(db_path=str(tmp_path / "p.db"))
projects.upsert(name="projA", display_name="P", template=str(_SAMPLE / "template_design_ja.docx"),
write_instruction="", rules=[], existing_system_code_dir="", design_docs_dir="")
svc = GenesisService(
store=store, data_root=str(tmp_path / "data"), engine=FakeEngine(), projects=projects,
)
s = svc.create_session("u1", project="projA")
svc.upload_file(s.session_id, "requirements", "requirements_newdev.xlsx",
(_SAMPLE / "requirements_newdev.xlsx").read_bytes())
rec = svc.get_session(s.session_id)
# requirements 仅用户上传;template 来自项目配置
assert svc.has_file(rec, "requirements") is True
assert svc.has_file(rec, "template") is True
assert svc.has_file(rec, "write_instruction") is False
assert svc._eff_path(rec, "template") == str(_SAMPLE / "template_design_ja.docx")
def test_rebuild_source_merges_project_config(tmp_path):
store = SessionStore(db_path=str(tmp_path / "s.db"))
projects = ProjectsStore(db_path=str(tmp_path / "p.db"))
projects.upsert(name="projA", display_name="P", template=str(_SAMPLE / "template_design_ja.docx"),
write_instruction=str(_SAMPLE / "rules_design_ja.docx"),
rules=[str(_SAMPLE / "rules_entry_ja.docx")],
existing_system_code_dir="", design_docs_dir="")
svc = GenesisService(
store=store, data_root=str(tmp_path / "data"), engine=FakeEngine(), projects=projects,
)
s = svc.create_session("u1", project="projA")
svc.upload_file(s.session_id, "requirements", "requirements_newdev.xlsx",
(_SAMPLE / "requirements_newdev.xlsx").read_bytes())
ss = svc._rebuild_source(svc.get_session(s.session_id))
assert ss.template is not None
assert len(ss.rule_docs) >= 2 # write_instruction + rules 合并
def test_rebuild_source_enumerates_design_docs_dir(tmp_path):
from docx import Document
store = SessionStore(db_path=str(tmp_path / "s.db"))
projects = ProjectsStore(db_path=str(tmp_path / "p.db"))
dd = tmp_path / "design"
dd.mkdir()
d = Document()
d.add_paragraph("设计文档:订单管理整体方案")
d.save(dd / "d1.docx")
projects.upsert(name="projA", display_name="P", template="", write_instruction="",
rules=[], existing_system_code_dir="", design_docs_dir=str(dd))
svc = GenesisService(
store=store, data_root=str(tmp_path / "data"), engine=FakeEngine(), projects=projects,
)
s = svc.create_session("u1", project="projA")
svc.upload_file(s.session_id, "requirements", "requirements_newdev.xlsx",
(_SAMPLE / "requirements_newdev.xlsx").read_bytes())
ss = svc._rebuild_source(svc.get_session(s.session_id))
assert len(ss.design_docs) == 1
def test_has_file_without_projects_returns_false(svc):
s = svc.create_session("u1")
rec = svc.get_session(s.session_id)
assert svc.has_file(rec, "template") is False
assert svc._eff_path(rec, "template") is None
def test_upload_requirements_does_not_override_named_session(tmp_path):
svc = GenesisService(
store=SessionStore(db_path=str(tmp_path / "s.db")),
data_root=str(tmp_path / "data"), engine=FakeEngine(),
)
s = svc.create_session("u1", name="已命名会话")
svc.upload_file(s.session_id, "requirements", "other_name.xlsx", b"PK\x03\x04")
assert svc.get_session(s.session_id).name == "已命名会话"
def test_upload_invalid_type_rejected(svc):
s = svc.create_session("u1")
with pytest.raises(FileTypeError):