AIDeveloper-PC/requirements_generator/main.py

1384 lines
50 KiB
Python
Raw Normal View History

2026-03-04 18:09:45 +00:00
#!/usr/bin/env python3
2026-03-06 14:25:18 +00:00
# encoding: utf-8
2026-03-06 15:41:59 +00:00
# main.py - 主入口:完全交互式菜单驱动
2026-03-04 18:09:45 +00:00
import os
import sys
2026-03-06 15:41:59 +00:00
import json
from typing import Dict, List, Optional, Tuple
2026-03-04 18:09:45 +00:00
from rich.console import Console
2026-03-05 09:24:49 +00:00
from rich.table import Table
from rich.panel import Panel
from rich.prompt import Prompt, Confirm
2026-03-06 15:41:59 +00:00
from rich.rule import Rule
2026-03-04 18:09:45 +00:00
import config
from database.db_manager import DBManager
2026-03-06 15:41:59 +00:00
from database.models import (
Project, RawRequirement, FunctionalRequirement, ChangeHistory,
)
2026-03-05 09:24:49 +00:00
from core.llm_client import LLMClient
2026-03-04 18:09:45 +00:00
from core.requirement_analyzer import RequirementAnalyzer
2026-03-05 09:24:49 +00:00
from core.code_generator import CodeGenerator
from utils.file_handler import read_file_auto, merge_knowledge_files
2026-03-04 18:09:45 +00:00
from utils.output_writer import (
2026-03-05 05:38:26 +00:00
ensure_project_dir, build_project_output_dir,
write_project_readme, write_function_signatures_json,
validate_all_signatures, patch_signatures_with_url,
2026-03-04 18:09:45 +00:00
)
console = Console()
2026-03-06 16:10:39 +00:00
db = DBManager()
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
# 通用显示工具
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
def print_banner():
2026-03-06 15:41:59 +00:00
console.print()
2026-03-04 18:09:45 +00:00
console.print(Panel.fit(
"[bold cyan]🚀 需求分析 & 代码生成工具[/bold cyan]\n"
"[dim]Powered by LLM · SQLite · Python[/dim]",
2026-03-05 05:38:26 +00:00
border_style="cyan",
2026-03-04 18:09:45 +00:00
))
2026-03-06 15:41:59 +00:00
console.print()
def print_main_menu():
console.print(Rule("[bold cyan]主菜单[/bold cyan]"))
menu_items = [
2026-03-06 16:10:39 +00:00
("1", "📁 新建项目", "输入需求 → 分解 → 生成代码"),
("2", "🔄 变更项目需求", "变更已有需求 / 变更模块需求 / 新增需求"),
("3", "📋 查看所有项目", "列表展示全部项目"),
("4", "🔍 查看项目详情", "需求列表 / 模块分组 / 变更历史"),
("5", "🗑 删除指定项目", "删除项目及其所有数据"),
("0", "🚪 退出", ""),
2026-03-06 15:41:59 +00:00
]
table = Table(show_header=False, box=None, padding=(0, 2))
table.add_column("选项", style="bold yellow", width=4)
2026-03-06 16:10:39 +00:00
table.add_column("功能", style="bold", width=22)
table.add_column("说明", style="dim", width=38)
2026-03-06 15:41:59 +00:00
for key, name, desc in menu_items:
table.add_row(f"[{key}]", name, desc)
console.print(table)
console.print()
def print_projects_table(projects: List[Project]):
if not projects:
console.print("[yellow] 暂无项目记录。[/yellow]")
return
table = Table(title="📋 项目列表", show_lines=True)
2026-03-06 16:10:39 +00:00
table.add_column("ID", style="cyan", width=6)
table.add_column("项目名", style="bold", width=22)
table.add_column("语言", style="magenta", width=12)
table.add_column("描述", width=35)
table.add_column("输出目录", style="dim", width=30)
2026-03-06 15:41:59 +00:00
for p in projects:
2026-03-06 16:10:39 +00:00
desc = p.description or ""
2026-03-06 15:41:59 +00:00
table.add_row(
str(p.id),
p.name,
p.language or "-",
2026-03-06 16:10:39 +00:00
desc[:40] + ("..." if len(desc) > 40 else ""),
2026-03-06 15:41:59 +00:00
p.output_dir or "-",
)
console.print(table)
2026-03-04 18:09:45 +00:00
2026-03-05 05:38:26 +00:00
def print_functional_requirements(reqs: List[FunctionalRequirement]):
2026-03-06 15:41:59 +00:00
if not reqs:
console.print("[yellow] 暂无功能需求。[/yellow]")
return
2026-03-04 18:09:45 +00:00
table = Table(title="📋 功能需求列表", show_lines=True)
2026-03-06 16:10:39 +00:00
table.add_column("序号", style="cyan", width=6)
table.add_column("ID", style="dim", width=6)
table.add_column("模块", style="magenta", width=15)
table.add_column("标题", style="bold", width=18)
table.add_column("函数名", width=25)
table.add_column("优先级", width=8)
table.add_column("状态", width=10)
table.add_column("描述", width=35)
2026-03-04 18:09:45 +00:00
priority_color = {"high": "red", "medium": "yellow", "low": "green"}
2026-03-06 16:10:39 +00:00
status_color = {"pending": "yellow", "generated": "green", "failed": "red"}
2026-03-04 18:09:45 +00:00
for req in reqs:
2026-03-06 15:41:59 +00:00
pc = priority_color.get(req.priority, "white")
sc = status_color.get(req.status, "white")
2026-03-06 16:10:39 +00:00
desc = req.description
2026-03-04 18:09:45 +00:00
table.add_row(
str(req.index_no),
str(req.id) if req.id else "-",
2026-03-05 05:38:26 +00:00
req.module or config.DEFAULT_MODULE,
2026-03-04 18:09:45 +00:00
req.title,
2026-03-05 09:24:49 +00:00
f"[code]{req.function_name}[/code]",
2026-03-06 15:41:59 +00:00
f"[{pc}]{req.priority}[/{pc}]",
f"[{sc}]{req.status}[/{sc}]",
2026-03-06 16:10:39 +00:00
desc[:40] + "..." if len(desc) > 40 else desc,
2026-03-04 18:09:45 +00:00
)
console.print(table)
2026-03-05 05:38:26 +00:00
def print_module_summary(reqs: List[FunctionalRequirement]):
module_map: Dict[str, List[str]] = {}
for req in reqs:
m = req.module or config.DEFAULT_MODULE
module_map.setdefault(m, []).append(req.function_name)
table = Table(title="📦 功能模块分组", show_lines=True)
2026-03-06 16:10:39 +00:00
table.add_column("模块", style="magenta bold", width=20)
table.add_column("函数数量", style="cyan", width=8)
table.add_column("函数列表", width=50)
2026-03-05 05:38:26 +00:00
for module, funcs in sorted(module_map.items()):
table.add_row(module, str(len(funcs)), ", ".join(funcs))
console.print(table)
2026-03-04 18:09:45 +00:00
2026-03-05 05:38:26 +00:00
2026-03-06 16:10:39 +00:00
def print_module_list(reqs: List[FunctionalRequirement]) -> List[str]:
"""打印带序号的模块列表,返回有序模块名列表。"""
module_map: Dict[str, List[FunctionalRequirement]] = {}
for req in reqs:
m = req.module or config.DEFAULT_MODULE
module_map.setdefault(m, []).append(req)
table = Table(title="📦 模块列表", show_lines=True)
table.add_column("序号", style="cyan", width=6)
table.add_column("模块名", style="magenta bold", width=22)
table.add_column("需求数量", style="yellow", width=8)
table.add_column("包含函数", width=45)
modules = sorted(module_map.keys())
for i, module in enumerate(modules, 1):
funcs = [r.function_name for r in module_map[module]]
table.add_row(str(i), module, str(len(funcs)), ", ".join(funcs))
console.print(table)
return modules
2026-03-05 05:38:26 +00:00
def print_signatures_preview(signatures: List[dict]):
2026-03-04 18:09:45 +00:00
table = Table(title="📄 函数签名预览", show_lines=True)
2026-03-06 16:10:39 +00:00
table.add_column("需求编号", style="cyan", width=8)
table.add_column("模块", style="magenta", width=15)
table.add_column("函数名", style="bold", width=22)
table.add_column("参数数", width=6)
table.add_column("返回类型", width=10)
table.add_column("URL", style="dim", width=28)
2026-03-04 18:09:45 +00:00
for sig in signatures:
2026-03-06 16:10:39 +00:00
ret = sig.get("return") or {}
url = sig.get("url", "")
2026-03-04 18:09:45 +00:00
url_display = os.path.basename(url) if url else "[dim]待生成[/dim]"
table.add_row(
sig.get("requirement_id", "-"),
2026-03-05 05:38:26 +00:00
sig.get("module", "-"),
2026-03-04 18:09:45 +00:00
sig.get("name", "-"),
str(len(sig.get("parameters", {}))),
ret.get("type", "void"),
url_display,
)
console.print(table)
2026-03-06 15:41:59 +00:00
def print_change_history(histories: List[ChangeHistory]):
if not histories:
console.print("[dim] 暂无变更历史。[/dim]")
return
table = Table(title="🕒 变更历史", show_lines=True)
2026-03-06 16:10:39 +00:00
table.add_column("ID", style="cyan", width=6)
table.add_column("时间", style="dim", width=20)
table.add_column("变更内容", width=60)
2026-03-06 15:41:59 +00:00
for h in histories:
table.add_row(
str(h.id),
str(h.created_at) if hasattr(h, "created_at") else "-",
h.changes[:80] + "..." if len(h.changes) > 80 else h.changes,
2026-03-04 18:09:45 +00:00
)
2026-03-06 15:41:59 +00:00
console.print(table)
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
def select_project(prompt_text: str = "请选择项目 ID") -> Optional[Project]:
projects = db.list_projects()
if not projects:
console.print("[yellow]当前暂无项目,请先新建项目。[/yellow]")
return None
print_projects_table(projects)
pid_str = Prompt.ask(f"{prompt_text}(输入 0 返回)", default="0")
if pid_str == "0":
return None
try:
pid = int(pid_str)
except ValueError:
console.print("[red]ID 必须为整数[/red]")
return None
project = db.get_project_by_id(pid)
2026-03-06 15:41:59 +00:00
if not project:
console.print(f"[red]项目 ID={pid} 不存在[/red]")
return None
2026-03-04 18:09:45 +00:00
return project
2026-03-06 15:41:59 +00:00
def _get_ext(language: str) -> str:
ext_map = {
"python": ".py", "javascript": ".js", "typescript": ".ts",
"java": ".java", "go": ".go", "rust": ".rs",
}
return ext_map.get((language or "python").lower(), ".txt")
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
# 菜单功能 1新建项目完整工作流
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
def menu_create_project():
console.print(Rule("[bold blue]新建项目[/bold blue]"))
# ── 1. 项目基本信息 ──────────────────────────────
project_name = Prompt.ask("📁 项目名称")
if not project_name.strip():
console.print("[red]项目名称不能为空[/red]")
return
existing = db.get_project_by_name(project_name)
if existing:
if not Confirm.ask(f"⚠️ 项目 '{project_name}' 已存在,继续使用该项目?"):
return
project = existing
console.print(f"[green]✓ 已加载项目: {project_name} (ID={project.id})[/green]")
2026-03-04 18:09:45 +00:00
else:
2026-03-06 15:41:59 +00:00
language = Prompt.ask(
"💻 目标语言",
default=config.DEFAULT_LANGUAGE,
choices=["python", "javascript", "typescript", "java", "go", "rust"],
)
description = Prompt.ask("📝 项目描述(可选)", default="")
project = Project(
2026-03-06 16:10:39 +00:00
name=project_name,
language=language,
output_dir=build_project_output_dir(project_name),
description=description,
2026-03-06 15:41:59 +00:00
)
project.id = db.create_project(project)
console.print(f"[green]✓ 项目已创建: {project_name} (ID={project.id})[/green]")
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
# ── 2. 知识库(可选)────────────────────────────
2026-03-06 16:10:39 +00:00
knowledge_text = _load_knowledge_optional()
2026-03-06 15:41:59 +00:00
# ── 3. 原始需求输入 ──────────────────────────────
console.print("\n[bold]请选择需求来源:[/bold]")
source = Prompt.ask("来源", choices=["text", "file"], default="text")
if source == "file":
file_path = Prompt.ask("需求文件路径")
2026-03-06 16:10:39 +00:00
raw_text = read_file_auto(file_path)
2026-03-06 15:41:59 +00:00
source_name = file_path
source_type = "file"
2026-03-04 18:09:45 +00:00
else:
2026-03-06 15:41:59 +00:00
console.print("[dim]请输入原始需求(输入空行结束):[/dim]")
lines = []
while True:
line = input()
if line == "":
break
lines.append(line)
2026-03-06 16:10:39 +00:00
raw_text = "\n".join(lines).strip()
2026-03-06 15:41:59 +00:00
source_name = ""
source_type = "text"
if not raw_text:
console.print("[red]原始需求不能为空[/red]")
return
2026-03-04 18:09:45 +00:00
raw_req = RawRequirement(
2026-03-06 16:10:39 +00:00
project_id=project.id,
content=raw_text,
source_type=source_type,
source_name=source_name,
knowledge=knowledge_text,
2026-03-04 18:09:45 +00:00
)
raw_req_id = db.create_raw_requirement(raw_req)
2026-03-06 15:41:59 +00:00
console.print(f"[green]✓ 原始需求已保存 (ID={raw_req_id})[/green]")
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
# ── 4. LLM 分解功能需求 ──────────────────────────
2026-03-06 16:10:39 +00:00
llm = LLMClient()
2026-03-06 15:41:59 +00:00
analyzer = RequirementAnalyzer(llm)
with console.status("[cyan]LLM 正在分解需求...[/cyan]"):
2026-03-04 18:09:45 +00:00
func_reqs = analyzer.decompose(
2026-03-06 16:10:39 +00:00
raw_requirement=raw_text,
project_id=project.id,
raw_req_id=raw_req_id,
knowledge=knowledge_text,
2026-03-04 18:09:45 +00:00
)
2026-03-06 15:41:59 +00:00
console.print(f"[green]✓ 分解完成,共 {len(func_reqs)} 条功能需求[/green]")
print_functional_requirements(func_reqs)
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
while True:
action = Prompt.ask(
"操作",
choices=["continue", "edit", "delete", "add"],
default="continue",
)
if action == "continue":
break
elif action == "edit":
func_reqs = _interactive_edit_req(func_reqs)
elif action == "delete":
func_reqs = _interactive_delete_req(func_reqs)
elif action == "add":
func_reqs = _interactive_add_req(func_reqs, project, raw_req_id)
2026-03-04 18:09:45 +00:00
for req in func_reqs:
req.id = db.create_functional_requirement(req)
2026-03-06 15:41:59 +00:00
console.print(f"[green]✓ 功能需求已持久化,共 {len(func_reqs)} 条[/green]")
# ── 5. 模块分类 ──────────────────────────────────
with console.status("[cyan]LLM 正在进行模块分类...[/cyan]"):
module_map = analyzer.classify_modules(func_reqs, knowledge_text)
name_to_module = {item["function_name"]: item["module"] for item in module_map}
for req in func_reqs:
if req.function_name in name_to_module:
req.module = name_to_module[req.function_name]
db.update_functional_requirement(req)
print_module_summary(func_reqs)
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
if Confirm.ask("是否手动调整模块归属?", default=False):
func_reqs = _interactive_adjust_modules(func_reqs)
2026-03-06 16:10:39 +00:00
# ── 6. 输出目录 ──────────────────────────────────
2026-03-06 15:41:59 +00:00
output_dir = ensure_project_dir(project.name)
# ── 7. 生成函数签名 ──────────────────────────────
console.print("\n[bold]Step · 生成函数签名[/bold]", style="blue")
2026-03-06 16:10:39 +00:00
signatures = _generate_signatures(analyzer, func_reqs, knowledge_text)
2026-03-06 15:41:59 +00:00
json_path = write_function_signatures_json(
2026-03-06 16:10:39 +00:00
output_dir=output_dir,
signatures=signatures,
project_name=project.name,
project_description=project.description or "",
file_name="function_signatures.json",
2026-03-06 15:41:59 +00:00
)
console.print(f"[green]✓ 签名 JSON 已写入: {json_path}[/green]")
print_signatures_preview(signatures)
errors = validate_all_signatures(signatures)
if errors:
console.print(f"[yellow]⚠️ {len(errors)} 个签名存在结构问题[/yellow]")
# ── 8. 生成代码文件 ──────────────────────────────
console.print("\n[bold]Step · 生成代码文件[/bold]", style="blue")
2026-03-06 16:10:39 +00:00
generator = CodeGenerator(llm)
2026-03-06 15:41:59 +00:00
code_files = _generate_code(
2026-03-06 16:10:39 +00:00
generator=generator,
project=project,
func_reqs=func_reqs,
output_dir=output_dir,
knowledge_text=knowledge_text,
signatures=signatures,
2026-03-06 15:41:59 +00:00
)
for cf in code_files:
db.upsert_code_file(cf)
for req in func_reqs:
req.status = "generated"
db.update_functional_requirement(req)
console.print(f"[green]✓ 代码生成完成,共 {len(code_files)} 个文件[/green]")
# ── 9. 生成 README ───────────────────────────────
_write_readme(project, func_reqs, output_dir)
# ── 10. 回填签名 URL ─────────────────────────────
_patch_and_save_signatures(
2026-03-06 16:10:39 +00:00
project=project,
signatures=signatures,
code_files=code_files,
output_dir=output_dir,
file_name="function_signatures.json",
2026-03-06 15:41:59 +00:00
)
console.print(Panel.fit(
f"[bold green]🎉 项目创建完成![/bold green]\n"
f"输出目录: [cyan]{output_dir}[/cyan]",
border_style="green",
))
_pause()
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
# 菜单功能 2变更项目需求
2026-03-05 05:38:26 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
def menu_change_requirements():
console.print(Rule("[bold blue]变更项目需求[/bold blue]"))
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
project = select_project("请选择要变更需求的项目 ID")
if not project:
return
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
func_reqs = db.list_functional_requirements(project.id)
if not func_reqs:
console.print("[yellow]当前项目暂无功能需求。[/yellow]")
_pause()
return
2026-03-05 05:38:26 +00:00
2026-03-06 15:41:59 +00:00
output_dir = ensure_project_dir(project.name)
knowledge_text = _load_knowledge_optional()
2026-03-05 05:38:26 +00:00
2026-03-06 16:10:39 +00:00
change_menu = [
("A", "🔧 变更指定功能需求", "按需求 ID 修改单条需求"),
("B", "📦 变更指定模块需求", "按模块批量操作模块内所有需求"),
("C", " 新增需求", "输入新需求文本LLM 分解后生成代码"),
]
2026-03-05 05:38:26 +00:00
while True:
2026-03-06 15:41:59 +00:00
console.print()
print_functional_requirements(func_reqs)
2026-03-06 16:10:39 +00:00
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)
2026-03-06 15:41:59 +00:00
action = Prompt.ask(
"请选择操作",
2026-03-06 16:10:39 +00:00
choices=["A", "B", "C", "back"],
2026-03-06 15:41:59 +00:00
default="back",
2026-03-05 05:38:26 +00:00
)
2026-03-06 16:10:39 +00:00
if action == "A":
2026-03-06 15:41:59 +00:00
_change_existing_requirement(
2026-03-06 16:10:39 +00:00
project=project,
func_reqs=func_reqs,
output_dir=output_dir,
knowledge_text=knowledge_text,
)
func_reqs = db.list_functional_requirements(project.id)
elif action == "B":
_change_module_requirements(
project=project,
func_reqs=func_reqs,
output_dir=output_dir,
knowledge_text=knowledge_text,
2026-03-06 15:41:59 +00:00
)
func_reqs = db.list_functional_requirements(project.id)
2026-03-06 16:10:39 +00:00
elif action == "C":
2026-03-06 15:41:59 +00:00
_add_new_requirements(
2026-03-06 16:10:39 +00:00
project=project,
func_reqs=func_reqs,
output_dir=output_dir,
knowledge_text=knowledge_text,
2026-03-06 15:41:59 +00:00
)
func_reqs = db.list_functional_requirements(project.id)
else:
2026-03-05 05:38:26 +00:00
break
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
_pause()
# ══════════════════════════════════════════════════════
# 菜单功能 3查看所有项目
# ══════════════════════════════════════════════════════
2026-03-05 05:38:26 +00:00
2026-03-06 15:41:59 +00:00
def menu_list_projects():
console.print(Rule("[bold blue]所有项目[/bold blue]"))
projects = db.list_projects()
print_projects_table(projects)
_pause()
2026-03-05 05:38:26 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
# 菜单功能 4查看项目详情
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
def menu_project_detail():
console.print(Rule("[bold blue]查看项目详情[/bold blue]"))
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
project = select_project("请选择要查看的项目 ID")
if not project:
return
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
console.print(Panel(
f"[bold]项目名称:[/bold]{project.name}\n"
f"[bold]语言: [/bold]{project.language}\n"
f"[bold]描述: [/bold]{project.description or '(无)'}\n"
f"[bold]输出目录:[/bold]{project.output_dir or '(未生成)'}",
title=f"📁 项目 ID={project.id}",
border_style="cyan",
))
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
func_reqs = db.list_functional_requirements(project.id)
print_functional_requirements(func_reqs)
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
if func_reqs:
print_module_summary(func_reqs)
2026-03-06 16:10:39 +00:00
llm = LLMClient()
2026-03-06 15:41:59 +00:00
analyzer = RequirementAnalyzer(llm)
histories = analyzer.get_change_history(project.id)
print_change_history(histories)
if project.output_dir:
sig_path = os.path.join(project.output_dir, "function_signatures.json")
if os.path.exists(sig_path):
with open(sig_path, "r", encoding="utf-8") as f:
doc = json.load(f)
sigs = doc.get("functions", [])
if sigs:
console.print(f"\n[dim]签名 JSON 共 {len(sigs)} 条:{sig_path}[/dim]")
print_signatures_preview(sigs)
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
_pause()
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
# 菜单功能 5删除指定项目
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
def menu_delete_project():
console.print(Rule("[bold red]删除指定项目[/bold red]"))
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
project = select_project("请选择要删除的项目 ID")
if not project:
return
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
console.print(Panel(
f"[bold]项目名称:[/bold]{project.name}\n"
f"[bold]语言: [/bold]{project.language}\n"
f"[bold]描述: [/bold]{project.description or '(无)'}",
title="⚠️ 即将删除以下项目",
border_style="red",
))
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
if not Confirm.ask(
2026-03-06 16:10:39 +00:00
f"[bold red]确认删除项目 '{project.name}'ID={project.id})?此操作不可恢复![/bold red]",
default=False,
2026-03-06 15:41:59 +00:00
):
console.print("[yellow]已取消删除。[/yellow]")
_pause()
return
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
confirm_name = Prompt.ask("请再次输入项目名称以确认")
if confirm_name != project.name:
console.print("[red]项目名称不匹配,删除已取消。[/red]")
_pause()
return
db.delete_project(project.id)
console.print(f"[green]✓ 项目 '{project.name}'ID={project.id})已删除。[/green]")
_pause()
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 16:10:39 +00:00
# 变更操作 A变更指定功能需求
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
def _change_existing_requirement(
2026-03-06 16:10:39 +00:00
project: Project,
func_reqs: List[FunctionalRequirement],
output_dir: str,
knowledge_text: str = "",
2026-03-06 15:41:59 +00:00
) -> None:
2026-03-06 16:10:39 +00:00
"""按需求 ID 修改单条功能需求,确认后重新生成签名与代码。"""
2026-03-06 15:41:59 +00:00
req_id_str = Prompt.ask("请输入要变更的功能需求 ID")
try:
req_id = int(req_id_str)
except ValueError:
console.print("[red]ID 必须为整数[/red]")
return
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
req = db.get_functional_requirement(req_id)
if not req or req.project_id != project.id:
console.print("[red]需求 ID 不存在或不属于当前项目[/red]")
return
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
console.print(f"\n当前需求: [bold]{req.title}[/bold]ID={req.id}")
2026-03-06 16:10:39 +00:00
new_description = Prompt.ask(" 新描述", default=req.description)
new_priority = Prompt.ask(
2026-03-06 15:41:59 +00:00
" 新优先级", default=req.priority,
choices=["high", "medium", "low"],
2026-03-05 05:38:26 +00:00
)
2026-03-06 15:41:59 +00:00
new_module = Prompt.ask(" 新模块名", default=req.module)
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
changed = (
2026-03-06 16:10:39 +00:00
new_description != req.description
or new_priority != req.priority
or new_module != req.module
2026-03-06 15:41:59 +00:00
)
if not changed:
console.print("[dim]未检测到任何变更,跳过。[/dim]")
return
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
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"))
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
if not Confirm.ask("确认执行以上变更?", default=True):
console.print("[yellow]已取消变更。[/yellow]")
return
2026-03-04 18:09:45 +00:00
2026-03-06 16:10:39 +00:00
llm = LLMClient()
2026-03-06 15:41:59 +00:00
analyzer = RequirementAnalyzer(llm)
analyzer.log_change(project.id, change_summary)
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
req.description = new_description
2026-03-06 16:10:39 +00:00
req.priority = new_priority
req.module = new_module
req.status = "pending"
2026-03-06 15:41:59 +00:00
db.update_functional_requirement(req)
console.print(f"[green]✓ 需求 ID={req.id} 已更新[/green]")
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
console.print("\n[cyan]正在重新生成签名与代码...[/cyan]")
change_sig_file = f"change_{req.id}_signatures.json"
signatures = _generate_signatures(analyzer, [req], knowledge_text)
2026-03-06 16:10:39 +00:00
write_function_signatures_json(
output_dir=output_dir,
signatures=signatures,
project_name=project.name,
project_description=project.description or "",
file_name=change_sig_file,
2026-03-04 18:09:45 +00:00
)
2026-03-06 15:41:59 +00:00
print_signatures_preview(signatures)
2026-03-06 16:10:39 +00:00
generator = CodeGenerator(llm)
2026-03-06 15:41:59 +00:00
code_files = _generate_code(
2026-03-06 16:10:39 +00:00
generator=generator,
project=project,
func_reqs=[req],
output_dir=output_dir,
knowledge_text=knowledge_text,
signatures=signatures,
2026-03-06 15:41:59 +00:00
)
for cf in code_files:
db.upsert_code_file(cf)
req.status = "generated"
db.update_functional_requirement(req)
_patch_and_save_signatures(
2026-03-06 16:10:39 +00:00
project=project,
signatures=signatures,
code_files=code_files,
output_dir=output_dir,
file_name=change_sig_file,
2026-03-04 18:09:45 +00:00
)
2026-03-06 15:41:59 +00:00
_merge_signatures_to_main(project, signatures, output_dir)
console.print(f"[green]✓ 变更完成,代码已重新生成[/green]")
2026-03-04 18:09:45 +00:00
2026-03-05 09:24:49 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 16:10:39 +00:00
# 变更操作 B变更指定模块需求
# ══════════════════════════════════════════════════════
def _change_module_requirements(
project: Project,
func_reqs: List[FunctionalRequirement],
output_dir: str,
knowledge_text: str = "",
) -> None:
"""
选择目标模块进入模块级变更子菜单
B1 · 重命名模块
B2 · 逐条修改模块内需求
B3 · 批量重新生成模块代码
"""
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
]
console.print(
f"\n[bold magenta]模块:{target_module}[/bold magenta]"
f"{len(module_reqs)} 条需求"
)
print_functional_requirements(module_reqs)
# ── 模块级操作子菜单 ─────────────────────────────
sub_menu = [
("B1", "✏️ 重命名模块", "修改模块名,批量更新该模块所有需求"),
("B2", "🔧 逐条修改模块内需求", "对模块内每条需求单独修改并重新生成"),
("B3", "⚡ 批量重新生成模块代码", "保持需求内容不变,整体重新生成签名+代码"),
]
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=24)
table.add_column("说明", style="dim", width=38)
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", "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_edit_each(
project=project,
module_reqs=module_reqs,
output_dir=output_dir,
knowledge_text=knowledge_text,
)
# 刷新 module_reqs
module_reqs = [
db.get_functional_requirement(r.id)
for r in module_reqs
]
elif sub_action == "B3":
_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[FunctionalRequirement],
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
llm = LLMClient()
analyzer = RequirementAnalyzer(llm)
analyzer.log_change(project.id, change_summary)
for req in module_reqs:
req.module = new_module
db.update_functional_requirement(req)
console.print(
f"[green]✓ 模块已重命名:'{old_module}''{new_module}'"
f"共更新 {len(module_reqs)} 条需求[/green]"
)
# ── B2逐条修改模块内需求 ────────────────────────────
def _module_edit_each(
project: Project,
module_reqs: List[FunctionalRequirement],
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
analyzer.log_change(project.id, change_summary)
req.description = new_description
req.priority = new_priority
req.module = new_module
req.status = "pending"
db.update_functional_requirement(req)
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]")
# ── B3批量重新生成模块代码 ──────────────────────────
def _module_regen_code(
project: Project,
module_reqs: List[FunctionalRequirement],
output_dir: str,
knowledge_text: str = "",
) -> None:
"""
保持需求内容不变对整个模块的所有需求
批量重新生成函数签名与代码文件并更新主签名 JSON
"""
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)} 条需求"
)
analyzer.log_change(project.id, change_summary)
console.print(
f"[bold green]✅ 模块 '{module_name}' 批量重新生成完成!"
f"{len(code_files)} 个文件[/bold green]"
)
# ══════════════════════════════════════════════════════
# 变更操作 C新增需求
2026-03-05 09:24:49 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
def _add_new_requirements(
2026-03-06 16:10:39 +00:00
project: Project,
func_reqs: List[FunctionalRequirement],
output_dir: str,
knowledge_text: str = "",
2026-03-06 15:41:59 +00:00
) -> None:
2026-03-06 15:51:26 +00:00
raw_reqs = db.get_raw_requirement(project.id)
2026-03-06 15:41:59 +00:00
if not raw_reqs:
console.print("[red]当前项目无原始需求记录,请先完成初始需求录入流程。[/red]")
return
2026-03-06 15:54:46 +00:00
raw_req_id = raw_reqs.id
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
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
2026-03-05 09:24:49 +00:00
2026-03-06 16:10:39 +00:00
llm = LLMClient()
2026-03-06 15:41:59 +00:00
analyzer = RequirementAnalyzer(llm)
with console.status("[cyan]LLM 正在分解新增需求...[/cyan]"):
new_reqs = analyzer.add_new_requirements(
2026-03-06 16:10:39 +00:00
new_requirement_text=new_req_text,
project_id=project.id,
raw_req_id=raw_req_id,
existing_reqs=func_reqs,
knowledge=knowledge_text,
2026-03-06 15:41:59 +00:00
)
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
console.print(f"[green]✓ 分解完成,新增 {len(new_reqs)} 条功能需求[/green]")
print_functional_requirements(new_reqs)
2026-03-06 06:06:29 +00:00
2026-03-06 15:41:59 +00:00
change_summary = (
2026-03-06 16:10:39 +00:00
f"新增 {len(new_reqs)} 条功能需求:\n"
+ "\n".join(
f" + [{r.index_no}] {r.title} ({r.function_name})"
for r in new_reqs
)
2026-03-06 15:41:59 +00:00
)
console.print(Panel(change_summary, title="📝 新增需求预览", border_style="green"))
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
if not Confirm.ask("确认新增以上需求并生成代码?", default=True):
console.print("[yellow]已取消新增。[/yellow]")
return
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
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)
for req in new_reqs:
req.id = db.create_functional_requirement(req)
console.print(f"[green]✓ 新增功能需求已持久化[/green]")
analyzer.log_change(project.id, change_summary)
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
signatures = _generate_signatures(analyzer, new_reqs, knowledge_text)
write_function_signatures_json(
2026-03-06 16:10:39 +00:00
output_dir=output_dir,
signatures=signatures,
project_name=project.name,
project_description=project.description or "",
file_name="function_signatures_new.json",
2026-03-06 15:41:59 +00:00
)
print_signatures_preview(signatures)
2026-03-05 09:24:49 +00:00
2026-03-06 16:10:39 +00:00
generator = CodeGenerator(llm)
2026-03-06 15:41:59 +00:00
code_files = _generate_code(
2026-03-06 16:10:39 +00:00
generator=generator,
project=project,
func_reqs=new_reqs,
output_dir=output_dir,
knowledge_text=knowledge_text,
signatures=signatures,
2026-03-06 15:41:59 +00:00
)
for cf in code_files:
db.upsert_code_file(cf)
for req in new_reqs:
req.status = "generated"
db.update_functional_requirement(req)
all_reqs = db.list_functional_requirements(project.id)
_write_readme(project, all_reqs, output_dir)
_patch_and_save_signatures(
2026-03-06 16:10:39 +00:00
project=project,
signatures=signatures,
code_files=code_files,
output_dir=output_dir,
file_name="function_signatures_new.json",
2026-03-06 15:41:59 +00:00
)
_merge_signatures_to_main(project, signatures, output_dir)
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
console.print(
f"[bold green]✅ 新增需求处理完成!共生成 {len(code_files)} 个代码文件[/bold green]"
)
2026-03-05 09:24:49 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
# 公共内部工具函数
2026-03-05 09:24:49 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
def _load_knowledge_optional() -> str:
if Confirm.ask("是否加载知识库文件?", default=False):
kb_path = Prompt.ask("知识库文件路径(多个用逗号分隔)")
2026-03-06 16:10:39 +00:00
paths = [p.strip() for p in kb_path.split(",") if p.strip()]
text = merge_knowledge_files(paths)
2026-03-06 15:41:59 +00:00
console.print(f"[dim]已加载 {len(paths)} 个知识库文件[/dim]")
return text
return ""
def _generate_signatures(
2026-03-06 16:10:39 +00:00
analyzer: RequirementAnalyzer,
func_reqs: List[FunctionalRequirement],
knowledge_text: str = "",
2026-03-06 15:41:59 +00:00
) -> List[dict]:
def on_progress(i, t, req, sig, err):
status = "[red]✗ 降级[/red]" if err else "[green]✓[/green]"
console.print(
f" {status} [{i}/{t}] REQ.{req.index_no:02d} "
f"[cyan]{req.function_name}[/cyan]"
+ (f" | [red]{err}[/red]" if err else "")
)
2026-03-06 06:06:29 +00:00
2026-03-06 15:41:59 +00:00
return analyzer.build_function_signatures_batch(
2026-03-06 16:10:39 +00:00
func_reqs=func_reqs,
knowledge=knowledge_text,
on_progress=on_progress,
2026-03-06 15:41:59 +00:00
)
2026-03-06 06:06:29 +00:00
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
def _generate_code(
2026-03-06 16:10:39 +00:00
generator: CodeGenerator,
project: Project,
func_reqs: List[FunctionalRequirement],
output_dir: str,
knowledge_text: str = "",
signatures: List[dict] = None,
2026-03-06 15:41:59 +00:00
) -> list:
def on_progress(i, t, req, code_file, err):
if err:
console.print(
f" [red]✗[/red] [{i}/{t}] {req.function_name} | [red]{err}[/red]"
)
else:
console.print(
f" [green]✓[/green] [{i}/{t}] {req.function_name} "
f"→ [dim]{code_file.file_path}[/dim]"
)
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
return generator.generate_batch(
2026-03-06 16:10:39 +00:00
func_reqs=func_reqs,
output_dir=output_dir,
language=project.language,
knowledge=knowledge_text,
signatures=signatures,
on_progress=on_progress,
2026-03-06 15:41:59 +00:00
)
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
def _write_readme(
2026-03-06 16:10:39 +00:00
project: Project,
func_reqs: List[FunctionalRequirement],
output_dir: str,
2026-03-06 15:41:59 +00:00
) -> str:
req_lines = "\n".join(
2026-03-06 16:10:39 +00:00
f"{i + 1}. **{r.title}** (`{r.function_name}`) — {r.description}"
2026-03-06 15:41:59 +00:00
for i, r in enumerate(func_reqs)
)
2026-03-06 16:10:39 +00:00
modules = list({r.module for r in func_reqs if r.module})
path = write_project_readme(
output_dir=output_dir,
project_name=project.name,
project_description=project.description or "",
requirements_summary=req_lines,
modules=modules,
2026-03-06 15:41:59 +00:00
)
console.print(f"[green]✓ README 已生成: {path}[/green]")
return path
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
def _patch_and_save_signatures(
2026-03-06 16:10:39 +00:00
project: Project,
signatures: List[dict],
code_files: list,
output_dir: str,
file_name: str = "function_signatures.json",
2026-03-06 15:41:59 +00:00
) -> str:
func_name_to_url = {
cf.file_name.replace(_get_ext(project.language), ""): cf.file_path
for cf in code_files
}
patch_signatures_with_url(signatures, func_name_to_url)
path = write_function_signatures_json(
2026-03-06 16:10:39 +00:00
output_dir=output_dir,
signatures=signatures,
project_name=project.name,
project_description=project.description or "",
file_name=file_name,
2026-03-06 15:41:59 +00:00
)
console.print(f"[green]✓ 签名 URL 已回填: {path}[/green]")
return path
def _merge_signatures_to_main(
2026-03-06 16:10:39 +00:00
project: Project,
new_sigs: List[dict],
output_dir: str,
main_file: str = "function_signatures.json",
2026-03-06 15:41:59 +00:00
) -> None:
main_path = os.path.join(output_dir, main_file)
if os.path.exists(main_path):
with open(main_path, "r", encoding="utf-8") as f:
main_doc = json.load(f)
existing_sigs = main_doc.get("functions", [])
2026-03-05 09:24:49 +00:00
else:
2026-03-06 15:41:59 +00:00
existing_sigs = []
main_doc = {
2026-03-06 16:10:39 +00:00
"project": project.name,
2026-03-06 15:41:59 +00:00
"description": project.description or "",
2026-03-06 16:10:39 +00:00
"functions": [],
2026-03-06 15:41:59 +00:00
}
sig_map = {s["name"]: s for s in existing_sigs}
for sig in new_sigs:
sig_map[sig["name"]] = sig
main_doc["functions"] = list(sig_map.values())
with open(main_path, "w", encoding="utf-8") as f:
json.dump(main_doc, f, ensure_ascii=False, indent=2)
console.print(
f"[green]✓ 主签名 JSON 已更新: {main_path}"
f"(共 {len(main_doc['functions'])} 条)[/green]"
)
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
def _interactive_edit_req(
2026-03-06 16:10:39 +00:00
reqs: List[FunctionalRequirement],
2026-03-06 15:41:59 +00:00
) -> List[FunctionalRequirement]:
idx = int(Prompt.ask("输入要编辑的序号"))
for req in reqs:
if req.index_no == idx:
2026-03-06 16:10:39 +00:00
req.title = Prompt.ask("新标题", default=req.title)
req.description = Prompt.ask("新描述", default=req.description)
2026-03-06 15:41:59 +00:00
req.function_name = Prompt.ask("新函数名", default=req.function_name)
2026-03-06 16:10:39 +00:00
req.priority = Prompt.ask(
2026-03-06 15:41:59 +00:00
"新优先级", default=req.priority,
choices=["high", "medium", "low"],
)
req.module = Prompt.ask("新模块", default=req.module)
console.print(f"[green]✓ 序号 {idx} 已更新[/green]")
return reqs
console.print(f"[red]序号 {idx} 不存在[/red]")
return reqs
2026-03-04 18:09:45 +00:00
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
def _interactive_delete_req(
2026-03-06 16:10:39 +00:00
reqs: List[FunctionalRequirement],
2026-03-06 15:41:59 +00:00
) -> List[FunctionalRequirement]:
2026-03-06 16:10:39 +00:00
idx = int(Prompt.ask("输入要删除的序号"))
2026-03-06 15:41:59 +00:00
new_reqs = [r for r in reqs if r.index_no != idx]
if len(new_reqs) < len(reqs):
console.print(f"[green]✓ 序号 {idx} 已删除[/green]")
else:
console.print(f"[red]序号 {idx} 不存在[/red]")
return new_reqs
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
def _interactive_add_req(
2026-03-06 16:10:39 +00:00
reqs: List[FunctionalRequirement],
project: Project,
raw_req_id: int,
2026-03-06 15:41:59 +00:00
) -> List[FunctionalRequirement]:
2026-03-06 16:10:39 +00:00
next_idx = max((r.index_no for r in reqs), default=0) + 1
title = Prompt.ask("新需求标题")
description = Prompt.ask("新需求描述")
2026-03-06 15:41:59 +00:00
function_name = Prompt.ask("函数名snake_case")
2026-03-06 16:10:39 +00:00
priority = Prompt.ask(
2026-03-06 15:41:59 +00:00
"优先级", choices=["high", "medium", "low"], default="medium",
2026-03-05 05:38:26 +00:00
)
2026-03-06 16:10:39 +00:00
module = Prompt.ask("所属模块", default=config.DEFAULT_MODULE)
2026-03-06 15:41:59 +00:00
new_req = FunctionalRequirement(
2026-03-06 16:10:39 +00:00
project_id=project.id,
raw_req_id=raw_req_id,
index_no=next_idx,
title=title,
description=description,
function_name=function_name,
priority=priority,
module=module,
status="pending",
is_custom=True,
2026-03-04 18:09:45 +00:00
)
2026-03-06 15:41:59 +00:00
reqs.append(new_req)
console.print(f"[green]✓ 已添加新需求: {title}(序号 {next_idx}[/green]")
return reqs
2026-03-05 09:24:49 +00:00
2026-03-06 15:41:59 +00:00
def _interactive_adjust_modules(
2026-03-06 16:10:39 +00:00
reqs: List[FunctionalRequirement],
2026-03-06 15:41:59 +00:00
) -> List[FunctionalRequirement]:
while True:
idx = Prompt.ask("输入要调整的需求序号(输入 0 结束)", default="0")
if idx == "0":
break
for req in reqs:
if str(req.index_no) == idx:
new_module = Prompt.ask(
f"'{req.function_name}' 新模块名", default=req.module,
)
req.module = new_module
db.update_functional_requirement(req)
console.print(
f"[green]✓ 已更新模块: {req.function_name}{new_module}[/green]"
)
break
else:
console.print(f"[red]序号 {idx} 不存在[/red]")
return reqs
2026-03-05 09:24:49 +00:00
2026-03-04 18:09:45 +00:00
2026-03-06 15:41:59 +00:00
def _pause():
console.print()
input(" 按 Enter 键返回主菜单...")
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
# 主循环
2026-03-04 18:09:45 +00:00
# ══════════════════════════════════════════════════════
2026-03-06 15:41:59 +00:00
def main():
print_banner()
2026-03-06 06:06:29 +00:00
2026-03-06 15:41:59 +00:00
menu_handlers = {
"1": menu_create_project,
"2": menu_change_requirements,
"3": menu_list_projects,
"4": menu_project_detail,
"5": menu_delete_project,
}
2026-03-06 06:06:29 +00:00
while True:
2026-03-06 15:41:59 +00:00
print_main_menu()
choice = Prompt.ask(
"请输入选项",
choices=["0", "1", "2", "3", "4", "5"],
default="0",
)
if choice == "0":
console.print("\n[bold cyan]👋 再见![/bold cyan]\n")
2026-03-06 06:06:29 +00:00
break
2026-03-06 15:41:59 +00:00
handler = menu_handlers.get(choice)
if handler:
try:
handler()
except KeyboardInterrupt:
console.print("\n[yellow]操作已中断,返回主菜单。[/yellow]")
except Exception as e:
console.print(f"\n[bold red]❌ 操作出错: {e}[/bold red]")
import traceback
traceback.print_exc()
_pause()
2026-03-05 06:01:40 +00:00
2026-03-04 18:09:45 +00:00
if __name__ == "__main__":
2026-03-06 16:10:39 +00:00
main()