From 6372bb17b95bec4a69785bf99ee52ced62dcc685 Mon Sep 17 00:00:00 2001 From: lhl Date: Mon, 24 Aug 2026 11:05:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(run):=20=E8=AF=95=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=20+=20=E8=BF=90=E8=A1=8C=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=EF=BC=88=E7=9C=9F=E5=AE=9E=20LLM=20=E9=BB=98=E8=AE=A4=20/=20--?= =?UTF-8?q?fake=20=E7=A6=BB=E7=BA=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/run_trial.py:库级端到端驱动(argparse 默认指向 samples/ 股票追加改修+sunOnly+真实模板) - --fake 离线 FakeEngine(无 key)/ 默认 build_inference_engine 真实 LLM(读 .env) - SourceParser → WriteOrchestrator 门控自动影响调查 → output/output.docx + impact-report.json - 暴露 main(argv) 供测试 - tests/test_run_trial.py(fake 模式断言 13 章 + summary 16/5/8/3/50/0 + 文件产出 + docx 注入) - .gitignore 新增 output/(试运行产物不入库) - 新建 README.md(安装 / .env 配置 / 离线与真实试运行 / 输出 / 测试) - 全量 364 passed / 99.28%;fake 试运行 PASS(13 章、summary 与 MVP 基线一致) --- .gitignore | 1 + README.md | 62 ++++++++++++++++++++++ _AI_USAGE_LOG.md | 1 + scripts/run_trial.py | 115 ++++++++++++++++++++++++++++++++++++++++ tests/test_run_trial.py | 39 ++++++++++++++ 5 files changed, 218 insertions(+) create mode 100644 README.md create mode 100644 scripts/run_trial.py create mode 100644 tests/test_run_trial.py diff --git a/.gitignore b/.gitignore index 94cb8dd..43dba86 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # ---- 运行时数据 ---- data/ +output/ # ---- Python 构建产物 ---- __pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1cb3be --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Genesis — 概要设计书自动生成 Agent + +读取 Excel 版要件定义、概要设计做成说明书、概要设计模板、概要设计书记入规则和图表规则等输入资料,自动生成符合规范的 Word 版概要设计书(追加/改修场景会结合既有系统源码做影响调查)。 + +## 安装 + +```powershell +# 1. 创建虚拟环境并安装依赖(Python >= 3.11) +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +pip install -e ".[dev]" + +# 2. 配置 LLM API Key(真实模式必需;离线 --fake 模式可跳过) +# 编辑 .env(已被 .gitignore 忽略,密钥不入库): +# GENESIS_INFERENCE__API_KEY=sk-xxx +# 可选: +# GENESIS_INFERENCE__BASE_URL=https://api.deepseek.com +# GENESIS_INFERENCE__MODEL=deepseek-chat +``` + +## 试运行 + +默认输入为 `samples/` 下既有样本(追加改修·股票场景 + sunOnly 既有系统 + 真实概要设计书模板)。 + +### 离线 Fake 模式(无需 API key,验证整条管线) + +```powershell +python scripts/run_trial.py --fake +``` + +### 真实 LLM 模式(需先在 .env 配置 API Key) + +```powershell +python scripts/run_trial.py +``` + +### 输出 + +- `output/output.docx` — 概要设计书 +- `output/impact-report.json` — 影响调查书(JSON,独立可下载) + +`output/` 已加入 `.gitignore`,试运行产物不入库。 + +### 自定义输入 + +```powershell +python scripts/run_trial.py ` + --requirement samples\要件定義_追加改修_股票.xlsx ` + --template samples\概要設計書テンプレート.docx ` + --rules samples\概要設計做成説明書.docx samples\記入規則.docx ` + --existing-system samples\existing-system ` + --language java ` + --output output\output.docx +``` + +## 测试 + +```powershell +python -m pytest +``` + +全量单测 + 覆盖率检查(红线 `fail_under = 99`)。 diff --git a/_AI_USAGE_LOG.md b/_AI_USAGE_LOG.md index 35c28b5..c0e14cb 100644 --- a/_AI_USAGE_LOG.md +++ b/_AI_USAGE_LOG.md @@ -113,3 +113,4 @@ | 2026-08-23 | 架构设计 | Impact Agent MVP 设计(brainstorming 逐问确认):门控=用户是否提供 existing_system 路径(流水线级,非章节级);范围确认=与用户确认调查范围(模块/深度);消费模型=影响调查结果是生成概要设计书的主上下文(整本基于影响结果生成,无专用影响章/无 {{section:impact}} 锚点门控);MVP=变更点定位(确定性规则,无 LLM);spec 落盘 docs/superpowers/specs/2026-08-23-impact-mvp.md | docs/superpowers/specs/2026-08-23-impact-mvp.md | deepseek-v4-flash | | 2026-08-23 | Agent 实现 | Impact Agent MVP 实现(TDD RED→GREEN→全量 351 passed / 99.27%):data_models 新增 ChangeType/ChangeElement/ImpactWarning/ChangeAnalysis/ImpactReport + StructuredSource.impact_report;新增 src/genesis/impact/(code_parser.py Java 解析 @RestController/@Service/@Entity/@TableName、existing_system_explorer.py 组装、impact_agent.py 变更点定位+impact_report_to_dict 序列化);source_aggregator 解除 existing_system=None 硬编码(新增 existing_system_path 门控);writer 集成——GenerationContext.impact_report + to_vars()[impact]、WRITER_PROMPT_TEMPLATE 加「影响调查上下文」、context_builder 透传、WriteOrchestrator.generate 门控自动跑 ImpactAgent + 回填 + 默认 meta(doc_title/version/created_at);新造样本 要件定義_追加改修_股票.xlsx(对齐 sunOnly 真实类名);门禁(fake 模式)PASS:影响调查 total=16 new=5 modified=8 deleted=3 unchanged=50 warnings=0,概要设计书 13 章产出,影响调查书 JSON 可下载 | src/genesis/data_models.py; src/genesis/impact/code_parser.py; src/genesis/impact/existing_system_explorer.py; src/genesis/impact/impact_agent.py; src/genesis/parsers/source_aggregator.py; src/genesis/writer/models.py; src/genesis/writer/writer_agent.py; src/genesis/writer/context_builder.py; src/genesis/writer/orchestrator.py; tests/test_data_models.py; tests/test_code_parser.py; tests/test_existing_system_explorer.py; tests/test_impact_agent.py; tests/test_source_aggregator.py; tests/test_phase5_models.py; tests/test_phase5_writer_agent.py; tests/test_phase5_context_builder.py; tests/test_phase5_writer_orchestrator.py; samples/要件定義_追加改修_股票.xlsx; samples/existing-system/; docs/superpowers/specs/2026-08-23-impact-mvp.md; _AI_USAGE_LOG.md | deepseek-v4-flash | | 2026-08-23 | Agent 实现 | CodeParser 多语言就绪预备性重构(行为不变):code_parser.py 重构为语言适配器注册表——BaseLanguageParser 抽象基类(extensions/source_files/parse_file 契约)+ JavaLanguageParser(迁移全部 Java 正则,输出与重构前逐字节一致:20Ctrl/25Svc/22Ent/113EP/13模块/219类/278imports)+ LANGUAGE_PARSERS 注册表 + register_language_parser 扩展点 + CodeParser 分发器(parse(root, language=None) 自动按扩展名探测/显式语言覆盖/多语言合并 language 逗号连接/无源码与不支持语言抛 CodeParseError);source_aggregator 新增 existing_system_language 透传参数;config.py allowed_extensions 补 .py/.ts/.go/.cs;删除自动探测下不可达 continue 死分支;TDD RED(ImportError)→ GREEN(test_code_parser 17 passed + test_source_aggregator 19 passed)→ 全量 362 passed / 99.28%(基线 351/99.27%,+11 新测试);门禁(fake 模式)复跑 PASS:summary 与 MVP 基线一致(total=16 new=5 modified=8 deleted=3 unchanged=50 warnings=0),概要设计书 13 章;未实现任何新语言(仅 Java,后续加语言=新增适配器类+register_language_parser,下游零改动) | src/genesis/impact/code_parser.py; src/genesis/parsers/source_aggregator.py; src/genesis/config.py; tests/test_code_parser.py; tests/test_source_aggregator.py; _AI_USAGE_LOG.md | deepseek-v4-flash | +| 2026-08-24 | Agent 实现 | 试运行脚本与运行说明:新增 scripts/run_trial.py(库级端到端驱动——argparse 默认指向 samples/ 股票追加改修+sunOnly+真实模板;--fake 离线 FakeEngine / 默认 build_inference_engine 真实 LLM 读 .env key;SourceParser→WriteOrchestrator 门控自动影响调查→output/output.docx + impact-report.json;暴露 main(argv) 供测试);新增 tests/test_run_trial.py(fake 模式断言 13 章 + summary 16/5/8/3/50/0 + 文件产出 + docx 注入);.gitignore 新增 output/(试运行产物不入库);新建 README.md(安装/配置 .env/离线与真实试运行命令/输出/测试);TDD RED(ModuleNotFoundError)→ GREEN(2 passed)→ 全量 364 passed / 99.28%;fake 试运行 PASS(13 章、summary 与 MVP 基线一致、output.docx 含注入文本);真实 LLM 试运行待用户配置 .env API Key | scripts/run_trial.py; tests/test_run_trial.py; .gitignore; README.md; _AI_USAGE_LOG.md | deepseek-v4-flash | diff --git a/scripts/run_trial.py b/scripts/run_trial.py new file mode 100644 index 0000000..6cc60e0 --- /dev/null +++ b/scripts/run_trial.py @@ -0,0 +1,115 @@ +"""库级端到端试运行驱动(真实 LLM / 离线 Fake)。 + +用法: + python scripts/run_trial.py [--fake] [--output out.docx] ... + +默认输入为 samples/ 下既有样本(追加改修·股票场景 + sunOnly 既有系统 + 真实概要设计书模板)。 +真实模式需先在 .env 配置 GENESIS_INFERENCE__API_KEY(见 README「运行说明」), +缺失时抛 LLMNotConfiguredError 并给出配置提示;--fake 为离线确定性引擎,无需 key。 + +输出: + 概要设计书 docx + 影响调查书 impact-report.json(默认写至 output/,不入库)。 +""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from types import SimpleNamespace + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT / "src") not in sys.path: + sys.path.insert(0, str(ROOT / "src")) + +from genesis.impact.impact_agent import impact_report_to_dict # noqa: E402 +from genesis.parsers.source_aggregator import SourceParser # noqa: E402 +from genesis.writer.orchestrator import WriteOrchestrator # noqa: E402 + + +class FakeEngine: + """离线确定性引擎:章节正文固定为占位文本(用于无 key 验证整条管线)。""" + + def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2): + return SimpleNamespace( + data={ + "title": variables["title"], + "blocks": [{"type": "paragraph", "text": "自动生成内容"}], + }, + status="ok", + ) + + +def _parse_args(argv: list[str] | None) -> argparse.Namespace: + p = argparse.ArgumentParser(description="概要设计书自动生成试运行") + p.add_argument("--requirement", default=str(ROOT / "samples" / "要件定義_追加改修_股票.xlsx"), + help="要件定义 Excel(可多个,逗号分隔)") + p.add_argument("--template", default=str(ROOT / "samples" / "概要設計書テンプレート.docx"), + help="概要设计书 Word 模板") + p.add_argument("--rules", nargs="*", default=[ + str(ROOT / "samples" / "概要設計做成説明書.docx"), + str(ROOT / "samples" / "記入規則.docx"), + ], help="写入规则/记入规则 docx(可多个)") + p.add_argument("--existing-system", default=str(ROOT / "samples" / "existing-system"), + help="既有系统源码目录(追加/改修场景)") + p.add_argument("--language", default="java", help="既有系统开发语言(默认 java,None=自动探测)") + p.add_argument("--samples-dir", default=str(ROOT / "samples"), help="样本资产根目录") + p.add_argument("--output", default=str(ROOT / "output" / "output.docx"), help="概要设计书输出路径") + p.add_argument("--impact-report", default=str(ROOT / "output" / "impact-report.json"), + help="影响调查书 JSON 输出路径") + p.add_argument("--fake", action="store_true", help="离线 Fake 引擎(无需 API key)") + return p.parse_args(argv) + + +def main(argv: list[str] | None = None) -> dict: + args = _parse_args(argv if argv is not None else sys.argv[1:]) + + if args.fake: + engine = FakeEngine() + else: + from genesis.inference.factory import build_inference_engine # noqa: E402 + engine = build_inference_engine() + + ss = SourceParser().parse( + requirement_paths=[args.requirement], + template_path=args.template, + rule_paths=list(args.rules), + existing_system_path=args.existing_system, + existing_system_language=args.language, + ) + + out_path = Path(args.output) + out_path.parent.mkdir(parents=True, exist_ok=True) + orch = WriteOrchestrator() + contents = orch.generate( + ss, str(out_path), + samples_dir=args.samples_dir, + engine=engine, + template_path=str(Path(args.template)), + ) + + report = ss.impact_report + if report is None: + raise RuntimeError("影响调查未执行(未提供既有系统或门控未触发)") + report_path = Path(args.impact_report) + report_path.parent.mkdir(parents=True, exist_ok=True) + report_path.write_text( + json.dumps(impact_report_to_dict(report), ensure_ascii=False, indent=2), + encoding="utf-8", + ) + + summary = dict(report.summary) + print(f"章节数: {len(contents)}") + print(f"影响调查: {json.dumps(summary, ensure_ascii=False)}") + print(f"概要设计书: {out_path}") + print(f"影响调查书: {report_path}") + return { + "chapters": len(contents), + "summary": summary, + "output": str(out_path), + "impact_report": str(report_path), + } + + +if __name__ == "__main__": + main() diff --git a/tests/test_run_trial.py b/tests/test_run_trial.py new file mode 100644 index 0000000..9dccf78 --- /dev/null +++ b/tests/test_run_trial.py @@ -0,0 +1,39 @@ +"""scripts/run_trial.py 驱动测试(fake 模式,离线确定性,无需 API key)。""" +import json +from pathlib import Path + +from scripts.run_trial import main + + +def test_run_trial_fake_produces_docx_and_report(tmp_path): + out = tmp_path / "output.docx" + report = tmp_path / "impact-report.json" + + result = main([ + "--fake", + "--output", str(out), + "--impact-report", str(report), + ]) + + # 章数:真实概要设计书模板 13 章 + assert result["chapters"] == 13 + # 影响调查 summary 与 MVP 基线一致 + assert result["summary"] == { + "total": 16, "new": 5, "modified": 8, "deleted": 3, + "unchanged": 50, "warnings": 0, + } + assert out.is_file() + assert report.is_file() + data = json.loads(report.read_text(encoding="utf-8")) + assert data["summary"] == result["summary"] + + +def test_run_trial_fake_docx_contains_injected_text(tmp_path): + out = tmp_path / "out.docx" + report = tmp_path / "ir.json" + + main(["--fake", "--output", str(out), "--impact-report", str(report)]) + + from docx import Document + joined = "\n".join(p.text for p in Document(str(out)).paragraphs) + assert "自动生成内容" in joined