321 lines
12 KiB
Python
321 lines
12 KiB
Python
|
|
# handlers/change_handler.py - 需求变更菜单处理
|
|||
|
|
# 菜单 2 入口 / 操作 A 单条变更 / 操作 C 新增需求
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
|
|||
|
|
from constants import CHG_ADD, CHG_EDIT
|
|||
|
|
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 Project
|
|||
|
|
from handlers.module_handler import change_module_requirements
|
|||
|
|
from ui.display import (
|
|||
|
|
print_functional_requirements, print_module_summary,
|
|||
|
|
print_signatures_preview,
|
|||
|
|
)
|
|||
|
|
from ui.prompts import (
|
|||
|
|
load_knowledge_optional, pause, select_project,
|
|||
|
|
)
|
|||
|
|
from utils.output_writer import (
|
|||
|
|
ensure_project_dir, write_function_signatures_json,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
console = Console()
|
|||
|
|
db = DBManager()
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ══════════════════════════════════════════════════════
|
|||
|
|
# 菜单 2:变更项目需求(入口)
|
|||
|
|
# ══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
def menu_change_requirements() -> None:
|
|||
|
|
console.print(Rule("[bold blue]变更项目需求[/bold blue]"))
|
|||
|
|
|
|||
|
|
project = select_project("请选择要变更需求的项目 ID")
|
|||
|
|
if not project:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
func_reqs = db.list_functional_requirements(project.id)
|
|||
|
|
if not active_reqs(func_reqs):
|
|||
|
|
console.print("[yellow]当前项目暂无功能需求。[/yellow]")
|
|||
|
|
pause()
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
output_dir = ensure_project_dir(project.name)
|
|||
|
|
knowledge_text = load_knowledge_optional()
|
|||
|
|
|
|||
|
|
change_menu = [
|
|||
|
|
("A", "🔧 变更指定功能需求", "按需求 ID 修改单条需求"),
|
|||
|
|
("B", "📦 变更指定模块需求", "按模块批量操作模块内所有需求"),
|
|||
|
|
("C", "➕ 新增需求", "输入新需求文本,LLM 分解后生成代码"),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
while True:
|
|||
|
|
console.print()
|
|||
|
|
print_functional_requirements(active_reqs(func_reqs))
|
|||
|
|
console.print(Rule("[bold]变更操作[/bold]"))
|
|||
|
|
|
|||
|
|
table = Table(show_header=False, box=None, padding=(0, 2))
|
|||
|
|
table.add_column("选项", style="bold yellow", width=5)
|
|||
|
|
table.add_column("功能", style="bold", width=22)
|
|||
|
|
table.add_column("说明", style="dim", width=38)
|
|||
|
|
for key, name, desc in change_menu:
|
|||
|
|
table.add_row(f"[{key}]", name, desc)
|
|||
|
|
table.add_row("[back]", "↩ 返回主菜单", "")
|
|||
|
|
console.print(table)
|
|||
|
|
|
|||
|
|
action = Prompt.ask(
|
|||
|
|
"请选择操作", choices=["A", "B", "C", "back"], default="back",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if action == "A":
|
|||
|
|
change_existing_requirement(
|
|||
|
|
project=project, func_reqs=func_reqs,
|
|||
|
|
output_dir=output_dir, knowledge_text=knowledge_text,
|
|||
|
|
)
|
|||
|
|
elif action == "B":
|
|||
|
|
change_module_requirements(
|
|||
|
|
project=project, func_reqs=func_reqs,
|
|||
|
|
output_dir=output_dir, knowledge_text=knowledge_text,
|
|||
|
|
)
|
|||
|
|
elif action == "C":
|
|||
|
|
add_new_requirements(
|
|||
|
|
project=project, func_reqs=func_reqs,
|
|||
|
|
output_dir=output_dir, knowledge_text=knowledge_text,
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
# 每次操作后刷新需求列表
|
|||
|
|
func_reqs = db.list_functional_requirements(project.id)
|
|||
|
|
|
|||
|
|
pause()
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ══════════════════════════════════════════════════════
|
|||
|
|
# 操作 A:变更指定功能需求(单条)
|
|||
|
|
# ══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
def change_existing_requirement(
|
|||
|
|
project: Project,
|
|||
|
|
func_reqs: list,
|
|||
|
|
output_dir: str,
|
|||
|
|
knowledge_text: str = "",
|
|||
|
|
) -> None:
|
|||
|
|
"""按需求 ID 修改单条功能需求,确认后重新生成签名与代码,记录变更历史。"""
|
|||
|
|
req_id_str = Prompt.ask("请输入要变更的功能需求 ID")
|
|||
|
|
try:
|
|||
|
|
req_id = int(req_id_str)
|
|||
|
|
except ValueError:
|
|||
|
|
console.print("[red]ID 必须为整数[/red]")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
req = db.get_functional_requirement(req_id)
|
|||
|
|
if not req or req.project_id != project.id:
|
|||
|
|
console.print("[red]需求 ID 不存在或不属于当前项目[/red]")
|
|||
|
|
return
|
|||
|
|
if req.status == "deleted":
|
|||
|
|
console.print("[red]该需求已被删除,无法变更[/red]")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
console.print(f"\n当前需求: [bold]{req.title}[/bold](ID={req.id})")
|
|||
|
|
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]")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
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]")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
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"[green]✓ 需求 ID={req.id} 已更新[/green]")
|
|||
|
|
|
|||
|
|
console.print("\n[cyan]正在重新生成签名与代码...[/cyan]")
|
|||
|
|
llm = LLMClient()
|
|||
|
|
analyzer = RequirementAnalyzer(llm)
|
|||
|
|
generator = CodeGenerator(llm)
|
|||
|
|
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,
|
|||
|
|
)
|
|||
|
|
print_signatures_preview(signatures)
|
|||
|
|
|
|||
|
|
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]✓ 变更完成,代码已重新生成[/green]")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ══════════════════════════════════════════════════════
|
|||
|
|
# 操作 C:新增需求
|
|||
|
|
# ══════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
def add_new_requirements(
|
|||
|
|
project: Project,
|
|||
|
|
func_reqs: list,
|
|||
|
|
output_dir: str,
|
|||
|
|
knowledge_text: str = "",
|
|||
|
|
) -> None:
|
|||
|
|
"""输入新需求描述,LLM 分解后持久化并生成代码,记录变更历史。"""
|
|||
|
|
raw_reqs = db.get_raw_requirements_by_project(project.id)
|
|||
|
|
if not raw_reqs:
|
|||
|
|
console.print("[red]当前项目无原始需求记录,请先完成初始需求录入流程。[/red]")
|
|||
|
|
return
|
|||
|
|
raw_req_id = raw_reqs[0].id
|
|||
|
|
|
|||
|
|
console.print("\n[bold]请输入新增需求描述[/bold](输入空行结束):")
|
|||
|
|
lines = []
|
|||
|
|
while True:
|
|||
|
|
line = input()
|
|||
|
|
if line == "":
|
|||
|
|
break
|
|||
|
|
lines.append(line)
|
|||
|
|
new_req_text = "\n".join(lines).strip()
|
|||
|
|
if not new_req_text:
|
|||
|
|
console.print("[yellow]新增需求内容为空,已取消。[/yellow]")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
llm = LLMClient()
|
|||
|
|
analyzer = RequirementAnalyzer(llm)
|
|||
|
|
|
|||
|
|
with console.status("[cyan]LLM 正在分解新增需求...[/cyan]"):
|
|||
|
|
new_reqs = analyzer.add_new_requirements(
|
|||
|
|
new_requirement_text = new_req_text,
|
|||
|
|
project_id = project.id,
|
|||
|
|
raw_req_id = raw_req_id,
|
|||
|
|
existing_reqs = active_reqs(func_reqs),
|
|||
|
|
knowledge = knowledge_text,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
console.print(f"[green]✓ 分解完成,新增 {len(new_reqs)} 条功能需求[/green]")
|
|||
|
|
print_functional_requirements(new_reqs)
|
|||
|
|
|
|||
|
|
change_summary = (
|
|||
|
|
f"新增 {len(new_reqs)} 条功能需求:\n"
|
|||
|
|
+ "\n".join(
|
|||
|
|
f" + [{r.index_no}] {r.title} ({r.function_name})"
|
|||
|
|
for r in new_reqs
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
console.print(Panel(change_summary, title="📝 新增需求预览", border_style="green"))
|
|||
|
|
|
|||
|
|
if not Confirm.ask("确认新增以上需求并生成代码?", default=True):
|
|||
|
|
console.print("[yellow]已取消新增。[/yellow]")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# ── 模块分类 ──────────────────────────────────────
|
|||
|
|
with console.status("[cyan]LLM 正在进行模块分类...[/cyan]"):
|
|||
|
|
module_map = analyzer.classify_modules(new_reqs, knowledge_text)
|
|||
|
|
name_to_module = {item["function_name"]: item["module"] for item in module_map}
|
|||
|
|
for req in new_reqs:
|
|||
|
|
if req.function_name in name_to_module:
|
|||
|
|
req.module = name_to_module[req.function_name]
|
|||
|
|
print_module_summary(new_reqs)
|
|||
|
|
|
|||
|
|
# ── 持久化新需求 ──────────────────────────────────
|
|||
|
|
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]✓ 新增功能需求已持久化[/green]")
|
|||
|
|
|
|||
|
|
# ── 记录变更历史 ──────────────────────────────────
|
|||
|
|
log_change(
|
|||
|
|
project_id = project.id,
|
|||
|
|
change_type = CHG_ADD,
|
|||
|
|
summary = change_summary,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ── 生成签名与代码 ────────────────────────────────
|
|||
|
|
signatures = generate_signatures(analyzer, new_reqs, knowledge_text)
|
|||
|
|
write_function_signatures_json(
|
|||
|
|
output_dir=output_dir, signatures=signatures,
|
|||
|
|
project_name=project.name, project_description=project.description or "",
|
|||
|
|
file_name="function_signatures_new.json",
|
|||
|
|
)
|
|||
|
|
print_signatures_preview(signatures)
|
|||
|
|
|
|||
|
|
generator = CodeGenerator(llm)
|
|||
|
|
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="function_signatures_new.json",
|
|||
|
|
)
|
|||
|
|
merge_signatures_to_main(project, signatures, output_dir)
|
|||
|
|
|
|||
|
|
# ── 更新 README ───────────────────────────────────
|
|||
|
|
all_active = active_reqs(db.list_functional_requirements(project.id))
|
|||
|
|
write_readme(project, all_active, output_dir)
|
|||
|
|
|
|||
|
|
console.print(Panel.fit(
|
|||
|
|
f"[bold green]✅ 新增需求处理完成![/bold green]\n"
|
|||
|
|
f"共生成 {len(code_files)} 个代码文件",
|
|||
|
|
border_style="green",
|
|||
|
|
))
|