From d48ec8602621f3b16e94ebc6ab268c69cfdef45f Mon Sep 17 00:00:00 2001 From: "sonto.lau" Date: Sat, 7 Mar 2026 00:47:54 +0800 Subject: [PATCH] bug fix --- .../handlers/module_handler.py | 502 +++++++++++++++++- 1 file changed, 501 insertions(+), 1 deletion(-) diff --git a/requirements_generator/handlers/module_handler.py b/requirements_generator/handlers/module_handler.py index b5caef0..5faf77f 100644 --- a/requirements_generator/handlers/module_handler.py +++ b/requirements_generator/handlers/module_handler.py @@ -226,14 +226,514 @@ def module_redescribe( with console.status( f"[cyan]LLM 正在重新分解模块 '{target_module}' 需求...[/cyan]" ): + # ✅ 修复:移除不存在的 module_hint 参数 + # 分解后统一通过 req.module = target_module 强制设置模块归属 new_reqs = analyzer.decompose( raw_requirement = new_desc_text, project_id = project.id, raw_req_id = raw_req_id, knowledge = knowledge_text, - module_hint = target_module, ) + # 强制将所有新需求归属到当前模块 + for req in new_reqs: + req.module = target_module + + console.print(f"[green]✓ 重新分解完成,共 {len(new_reqs)} 条新功能需求[/green]") + + print_req_diff(old_reqs=module_reqs, new_reqs=new_reqs) + + if not Confirm.ask( + f"确认用以上 {len(new_reqs)} 条新需求替换模块 '{target_module}' 的旧需求?", + default=True, + ): + console.print("[yellow]已取消重新描述。[/yellow]") + return + + # ── 软删除旧需求 ────────────────────────────────── + deleted_ids = [] + for req in module_reqs: + req.status = "deleted" + db.update_functional_requirement(req) + deleted_ids.append(req.id) + console.print( + f"[dim]✓ 已软删除旧需求 {len(deleted_ids)} 条(ID: {deleted_ids})[/dim]" + ) + + # ── 持久化新需求 ────────────────────────────────── + all_reqs = db.list_functional_requirements(project.id) + max_idx = max((r.index_no for r in all_reqs), default=0) + for i, req in enumerate(new_reqs): + req.index_no = max_idx + i + 1 + req.id = db.create_functional_requirement(req) + console.print(f"[green]✓ 新功能需求已持久化,共 {len(new_reqs)} 条[/green]") + + # ── 记录变更历史 ────────────────────────────────── + change_summary = ( + f"模块 '{target_module}' 需求重新描述:\n" + f" 旧需求 {len(module_reqs)} 条(已软删除):\n" + + "\n".join(f" - [{r.id}] {r.title}" for r in module_reqs) + + f"\n 新需求 {len(new_reqs)} 条:\n" + + "\n".join(f" + [{r.id}] {r.title}" for r in new_reqs) + ) + log_change( + project_id = project.id, + change_type = CHG_REDESCRIBE, + summary = change_summary, + module = target_module, + ) + + # ── 重新生成签名与代码 ──────────────────────────── + console.print( + f"\n[cyan]正在为模块 '{target_module}' 重新生成签名与代码...[/cyan]" + ) + generator = CodeGenerator(llm) + signatures = generate_signatures(analyzer, new_reqs, knowledge_text) + mod_sig_file = f"module_{target_module}_redescribe_signatures.json" + write_function_signatures_json( + output_dir=output_dir, signatures=signatures, + project_name=project.name, project_description=project.description or "", + file_name=mod_sig_file, + ) + print_signatures_preview(signatures) + + code_files = generate_code( + generator=generator, project=project, func_reqs=new_reqs, + output_dir=output_dir, knowledge_text=knowledge_text, signatures=signatures, + ) + for cf in code_files: + db.upsert_code_file(cf) + for req in new_reqs: + req.status = "generated" + db.update_functional_requirement(req) + + patch_and_save_signatures( + project=project, signatures=signatures, code_files=code_files, + output_dir=output_dir, file_name=mod_sig_file, + ) + merge_signatures_to_main(project, signatures, output_dir) + + all_active = active_reqs(db.list_functional_requirements(project.id)) + write_readme(project, all_active, output_dir) + + console.print(Panel.fit( + f"[bold green]✅ 模块 '{target_module}' 需求重新描述完成![/bold green]\n" + f"新需求 {len(new_reqs)} 条,生成代码 {len(code_files)} 个文件", + border_style="green", + )) + + +# ══════════════════════════════════════════════════════ +# B3:逐条修改模块内需求 +# ══════════════════════════════════════════════════════ + +def module_edit_each( + project: Project, + module_reqs: list, + output_dir: str, + knowledge_text: str = "", +) -> None: + """对模块内需求逐条展示并允许修改,每条确认后立即重新生成签名与代码,记录变更历史。""" + llm = LLMClient() + analyzer = RequirementAnalyzer(llm) + generator = CodeGenerator(llm) + + for req in module_reqs: + console.print(Rule(f"[cyan]需求 ID={req.id} · {req.title}[/cyan]")) + console.print( + f" 描述: {req.description}\n" + f" 优先级: {req.priority}\n" + f" 模块: {req.module}" + ) + + if not Confirm.ask("是否修改此需求?", default=False): + continue + + new_description = Prompt.ask(" 新描述", default=req.description) + new_priority = Prompt.ask( + " 新优先级", default=req.priority, + choices=["high", "medium", "low"], + ) + new_module = Prompt.ask(" 新模块名", default=req.module) + + changed = ( + new_description != req.description + or new_priority != req.priority + or new_module != req.module + ) + if not changed: + console.print("[dim] 未检测到变更,跳过。[/dim]") + continue + + change_summary = ( + f"需求 '{req.title}'(ID={req.id})变更:\n" + f" 描述: '{req.description}' → '{new_description}'\n" + f" 优先级: '{req.priority}' → '{new_priority}'\n" + f" 模块: '{req.module}' → '{new_module}'" + ) + console.print(Panel(change_summary, title="📝 变更预览", border_style="yellow")) + + if not Confirm.ask("确认此条变更?", default=True): + console.print("[yellow] 已跳过。[/yellow]") + continue + + req.description = new_description + req.priority = new_priority + req.module = new_module + req.status = "pending" + db.update_functional_requirement(req) + + log_change( + project_id = project.id, + change_type = CHG_EDIT, + summary = change_summary, + module = new_module, + req_id = req.id, + ) + + console.print(f" [cyan]正在重新生成 {req.function_name} 的签名与代码...[/cyan]") + change_sig_file = f"change_{req.id}_signatures.json" + signatures = generate_signatures(analyzer, [req], knowledge_text) + write_function_signatures_json( + output_dir=output_dir, signatures=signatures, + project_name=project.name, project_description=project.description or "", + file_name=change_sig_file, + ) + code_files = generate_code( + generator=generator, project=project, func_reqs=[req], + output_dir=output_dir, knowledge_text=knowledge_text, + signatures=signatures, + ) + for cf in code_files: + db.upsert_code_file(cf) + req.status = "generated" + db.update_functional_requirement(req) + patch_and_save_signatures( + project=project, signatures=signatures, code_files=code_files, + output_dir=output_dir, file_name=change_sig_file, + ) + merge_signatures_to_main(project, signatures, output_dir) + console.print(f" [green]✓ 需求 ID={req.id} 变更完成[/green]") + + console.print("[green]✓ 模块内需求逐条修改完成[/green]") + + +# ══════════════════════════════════════════════════════ +# B4:批量重新生成模块代码 +# ══════════════════════════════════════════════════════ + +def module_regen_code( + project: Project, + module_reqs: list, + output_dir: str, + knowledge_text: str = "", +) -> None: + """保持需求内容不变,对整个模块批量重新生成签名与代码,记录变更历史。""" + module_name = module_reqs[0].module if module_reqs else "未知模块" + console.print(Panel( + f"模块:[bold magenta]{module_name}[/bold magenta]\n" + f"需求数量:{len(module_reqs)} 条\n" + + "\n".join( + f" · [{r.id}] {r.title} ({r.function_name})" + for r in module_reqs + ), + title="⚡ 批量重新生成预览", + border_style="cyan", + )) + + if not Confirm.ask( + f"确认对模块 '{module_name}' 的 {len(module_reqs)} 条需求批量重新生成代码?", + default=True, + ): + console.print("[yellow]已取消。[/yellow]") + return + + llm = LLMClient() + analyzer = RequirementAnalyzer(llm) + generator = CodeGenerator(llm) + + for req in module_reqs: + req.status = "pending" + db.update_functional_requirement(req) + + console.print(f"\n[cyan]正在为模块 '{module_name}' 生成函数签名...[/cyan]") + signatures = generate_signatures(analyzer, module_reqs, knowledge_text) + mod_sig_file = f"module_{module_name}_signatures.json" + write_function_signatures_json( + output_dir=output_dir, signatures=signatures, + project_name=project.name, project_description=project.description or "", + file_name=mod_sig_file, + ) + print_signatures_preview(signatures) + + console.print(f"\n[cyan]正在为模块 '{module_name}' 生成代码文件...[/cyan]") + code_files = generate_code( + generator=generator, project=project, func_reqs=module_reqs, + output_dir=output_dir, knowledge_text=knowledge_text, signatures=signatures, + ) + for cf in code_files: + db.upsert_code_file(cf) + for req in module_reqs: + req.status = "generated" + db.update_functional_requirement(req) + + patch_and_save_signatures( + project=project, signatures=signatures, code_files=code_files, + output_dir=output_dir, file_name=mod_sig_file, + ) + merge_signatures_to_main(project, signatures, output_dir) + + change_summary = ( + f"模块 '{module_name}' 批量重新生成代码,共 {len(module_reqs)} 条需求:\n" + + "\n".join(f" · [{r.id}] {r.title}" for r in module_reqs) + ) + log_change( + project_id = project.id, + change_type = CHG_REGEN, + summary = change_summary, + module = module_name, + ) + console.print( + f"[bold green]✅ 模块 '{module_name}' 批量重新生成完成!" + f"共 {len(code_files)} 个文件[/bold green]" + )# handlers/module_handler.py - 模块级变更处理 +# B1 重命名 / B2 重新描述 / B3 逐条修改 / B4 批量重新生成 + +from rich.console import Console +from rich.panel import Panel +from rich.prompt import Confirm, Prompt +from rich.rule import Rule +from rich.table import Table + +import config +from constants import CHG_EDIT, CHG_REGEN, CHG_RENAME, CHG_REDESCRIBE +from core_utils import ( + active_reqs, generate_code, generate_signatures, + log_change, merge_signatures_to_main, + patch_and_save_signatures, write_readme, +) +from core.code_generator import CodeGenerator +from core.llm_client import LLMClient +from core.requirement_analyzer import RequirementAnalyzer +from database.db_manager import DBManager +from database.models import FunctionalRequirement, Project +from ui.display import ( + print_functional_requirements, print_module_list, + print_req_diff, print_signatures_preview, +) +from utils.output_writer import write_function_signatures_json + +console = Console() +db = DBManager() + + +# ══════════════════════════════════════════════════════ +# 模块变更入口(子菜单) +# ══════════════════════════════════════════════════════ + +def change_module_requirements( + project: Project, + func_reqs: list, + output_dir: str, + knowledge_text: str = "", +) -> None: + """ + 选择目标模块,进入模块级变更子菜单: + B1 · 重命名模块 + B2 · 重新描述模块需求 + B3 · 逐条修改模块内需求 + B4 · 批量重新生成模块代码 + """ + console.print(Rule("[bold magenta]变更指定模块需求[/bold magenta]")) + + modules = print_module_list(func_reqs) + if not modules: + console.print("[yellow]当前项目暂无模块信息。[/yellow]") + return + + mod_idx_str = Prompt.ask("请输入模块序号(输入 0 返回)", default="0") + if mod_idx_str == "0": + return + try: + mod_idx = int(mod_idx_str) - 1 + if mod_idx < 0 or mod_idx >= len(modules): + raise ValueError + target_module = modules[mod_idx] + except ValueError: + console.print("[red]序号无效[/red]") + return + + module_reqs = [ + r for r in func_reqs + if (r.module or config.DEFAULT_MODULE) == target_module + and r.status != "deleted" + ] + console.print( + f"\n[bold magenta]模块:{target_module}[/bold magenta]" + f" 共 {len(module_reqs)} 条需求" + ) + print_functional_requirements(module_reqs) + + sub_menu = [ + ("B1", "✏️ 重命名模块", "修改模块名,批量更新该模块所有需求"), + ("B2", "📝 重新描述模块需求", "重新输入模块需求描述,LLM 重新分解并生成代码"), + ("B3", "🔧 逐条修改模块内需求", "对模块内每条需求单独修改并重新生成"), + ("B4", "⚡ 批量重新生成模块代码", "保持需求内容不变,整体重新生成签名+代码"), + ] + + while True: + console.print(Rule()) + table = Table(show_header=False, box=None, padding=(0, 2)) + table.add_column("选项", style="bold yellow", width=5) + table.add_column("功能", style="bold", width=26) + table.add_column("说明", style="dim", width=42) + for key, name, desc in sub_menu: + table.add_row(f"[{key}]", name, desc) + table.add_row("[back]", "↩ 返回", "") + console.print(table) + + sub_action = Prompt.ask( + "请选择操作", + choices=["B1", "B2", "B3", "B4", "back"], + default="back", + ) + + if sub_action == "B1": + module_rename( + project=project, module_reqs=module_reqs, + old_module=target_module, + ) + break + + elif sub_action == "B2": + module_redescribe( + project=project, module_reqs=module_reqs, + target_module=target_module, output_dir=output_dir, + knowledge_text=knowledge_text, + ) + break + + elif sub_action == "B3": + module_edit_each( + project=project, module_reqs=module_reqs, + output_dir=output_dir, knowledge_text=knowledge_text, + ) + module_reqs = [ + db.get_functional_requirement(r.id) + for r in module_reqs + if db.get_functional_requirement(r.id) + ] + + elif sub_action == "B4": + module_regen_code( + project=project, module_reqs=module_reqs, + output_dir=output_dir, knowledge_text=knowledge_text, + ) + + else: + break + + +# ══════════════════════════════════════════════════════ +# B1:重命名模块 +# ══════════════════════════════════════════════════════ + +def module_rename( + project: Project, + module_reqs: list, + old_module: str, +) -> None: + """将指定模块重命名,批量更新该模块下所有需求的 module 字段,记录变更历史。""" + new_module = Prompt.ask(f"请输入新模块名(当前:{old_module})") + if not new_module.strip(): + console.print("[red]模块名不能为空[/red]") + return + if new_module == old_module: + console.print("[dim]模块名未变更,跳过。[/dim]") + return + + change_summary = ( + f"模块重命名:'{old_module}' → '{new_module}'\n" + f" 影响需求数:{len(module_reqs)} 条\n" + + "\n".join(f" · [{r.id}] {r.title}" for r in module_reqs) + ) + console.print(Panel(change_summary, title="📝 模块重命名预览", border_style="yellow")) + + if not Confirm.ask("确认执行模块重命名?", default=True): + console.print("[yellow]已取消。[/yellow]") + return + + for req in module_reqs: + req.module = new_module + db.update_functional_requirement(req) + + log_change( + project_id = project.id, + change_type = CHG_RENAME, + summary = change_summary, + module = old_module, + ) + console.print( + f"[green]✓ 模块已重命名:'{old_module}' → '{new_module}'," + f"共更新 {len(module_reqs)} 条需求[/green]" + ) + + +# ══════════════════════════════════════════════════════ +# B2:重新描述模块需求 +# ══════════════════════════════════════════════════════ + +def module_redescribe( + project: Project, + module_reqs: list, + target_module: str, + output_dir: str, + knowledge_text: str = "", +) -> None: + """ + 用户重新输入该模块的需求描述文本, + LLM 重新分解为功能需求,展示新旧对比后确认, + 软删除旧需求,持久化新需求,重新生成签名+代码,记录变更历史。 + """ + console.print(Rule( + f"[bold magenta]重新描述模块 '{target_module}' 需求[/bold magenta]" + )) + + console.print("[bold]当前模块需求:[/bold]") + print_functional_requirements(module_reqs) + + console.print( + f"\n[bold]请重新输入模块 '[magenta]{target_module}[/magenta]'" + " 的需求描述[/bold](输入空行结束):" + ) + lines = [] + while True: + line = input() + if line == "": + break + lines.append(line) + new_desc_text = "\n".join(lines).strip() + if not new_desc_text: + console.print("[yellow]输入为空,已取消。[/yellow]") + return + + llm = LLMClient() + analyzer = RequirementAnalyzer(llm) + raw_req_id = module_reqs[0].raw_req_id if module_reqs else None + + with console.status( + f"[cyan]LLM 正在重新分解模块 '{target_module}' 需求...[/cyan]" + ): + # ✅ 修复:移除不存在的 module_hint 参数 + # 分解后统一通过 req.module = target_module 强制设置模块归属 + new_reqs = analyzer.decompose( + raw_requirement = new_desc_text, + project_id = project.id, + raw_req_id = raw_req_id, + knowledge = knowledge_text, + ) + + # 强制将所有新需求归属到当前模块 for req in new_reqs: req.module = target_module