first commit

This commit is contained in:
xiang
2026-07-25 23:45:09 +08:00
commit c7bd957e6f
47 changed files with 3870 additions and 0 deletions
@@ -0,0 +1,221 @@
#!/usr/bin/env python3
"""安全初始化 Vibe Coding 的 L1 或 L2 文档目录。"""
from __future__ import annotations
import argparse
import datetime as dt
import re
import shutil
import sys
import tempfile
from pathlib import Path
SKILL_ROOT = Path(__file__).resolve().parent.parent
TEMPLATES = SKILL_ROOT / "assets" / "templates"
INVALID_NAME = re.compile(r'[<>:"/\\|?*\x00-\x1f]')
def safe_name(value: str) -> str:
cleaned = INVALID_NAME.sub("_", value).strip().rstrip(".")
if not cleaned:
raise ValueError("名称不能为空或只包含非法字符")
return cleaned
def render(template_name: str, replacements: dict[str, str]) -> str:
text = (TEMPLATES / template_name).read_text(encoding="utf-8")
for key, value in replacements.items():
text = text.replace("{{" + key + "}}", value)
return text
def write_new(path: Path, content: str) -> None:
if path.exists():
raise FileExistsError(f"拒绝覆盖已有文件:{path}")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
def parse_subtask(raw: str) -> tuple[str, str]:
if ":" not in raw:
raise ValueError(f"子任务格式应为 ST-001:名称,实际为:{raw}")
number, name = raw.split(":", 1)
number = number.strip().upper()
if not re.fullmatch(r"ST-\d{3}", number):
raise ValueError(f"子任务编号格式错误:{number}")
return number, safe_name(name)
def build_l1(args: argparse.Namespace) -> Path:
now = dt.datetime.now().astimezone().isoformat(timespec="seconds")
replacements = {
"需求名称": args.name,
"编号": args.requirement_id or "待确认",
"状态": "待确认",
"时间": now,
}
if args.subtask_dir:
target_dir = Path(args.subtask_dir).resolve()
if not target_dir.is_dir():
raise FileNotFoundError(f"子任务目录不存在:{target_dir}")
write_new(
target_dir / "01.精简变更日志.md",
render("L1-精简变更日志模板.md", replacements),
)
return target_dir
management_root = Path(args.management_root).resolve()
management_root.mkdir(parents=True, exist_ok=True)
pm_id = safe_name(args.requirement_id or "待确认")
target = management_root / f"PM-{pm_id}-{safe_name(args.name)}"
if target.exists():
raise FileExistsError(f"目标目录已存在:{target}")
temporary = Path(tempfile.mkdtemp(prefix=".vibe-init-", dir=management_root))
try:
write_new(
temporary / "01.精简变更日志.md",
render("L1-精简变更日志模板.md", replacements),
)
temporary.replace(target)
except Exception:
shutil.rmtree(temporary, ignore_errors=True)
raise
return target
def build_l2(args: argparse.Namespace) -> Path:
management_root = Path(args.management_root).resolve()
management_root.mkdir(parents=True, exist_ok=True)
pm_id = safe_name(args.pm_id or "待确认")
demand_name = safe_name(args.name)
target = management_root / f"PM-{pm_id}-{demand_name}"
if target.exists():
raise FileExistsError(f"目标目录已存在:{target}")
subtasks = [parse_subtask(item) for item in args.subtask]
if not subtasks:
raise ValueError("L2 至少需要一个 --subtask ST-001:名称")
numbers = [number for number, _ in subtasks]
if len(numbers) != len(set(numbers)):
raise ValueError("子任务编号不能重复")
now = dt.datetime.now().astimezone().isoformat(timespec="seconds")
common = {
"PM编号": pm_id,
"需求名称": demand_name,
"名称": demand_name,
"状态": "需求分析中",
"阶段": "需求分析",
"时间": now,
}
temporary = Path(tempfile.mkdtemp(prefix=".vibe-init-", dir=management_root))
try:
source_version = safe_name(args.source_version)
(temporary / "source" / source_version).mkdir(parents=True)
(temporary / "code").mkdir(parents=True)
(temporary / "tasks").mkdir(parents=True)
overview = render("大需求总览模板.md", common)
default_row = (
"| ST-001 | {{模块}} | 需求分析 | 需求分析中 | {{依赖}} | "
"[查看](./tasks/ST-001-模块/01.需求分析.md) |"
)
rows = "\n".join(
f"| {number} | {name} | 需求分析 | 未开始 | 待梳理 | "
f"[查看](./tasks/{number}-{name}/01.需求分析.md) |"
for number, name in subtasks
)
overview = overview.replace(default_row, rows)
write_new(temporary / "README.md", overview)
write_new(temporary / "STATUS.md", render("任务状态模板.md", common))
write_new(
temporary / "技术侧需求分析.md",
render("技术侧需求分析模板.md", common),
)
source_index = render("原始需求版本索引模板.md", common)
source_index = source_index.replace("| v1 |", f"| {source_version} |")
source_index = source_index.replace("./v1/", f"./{source_version}/")
write_new(temporary / "source" / "README.md", source_index)
write_new(
temporary / "code" / "MANIFEST.md",
render("代码副本清单模板.md", common),
)
phase_templates = [
("01-需求分析模板.md", "01.需求分析.md"),
("02-技术实现方案模板.md", "02.技术实现方案.md"),
("03-冒烟与逻辑验证模板.md", "03.冒烟与逻辑验证.md"),
("04-技术实现记录模板.md", "04.技术实现记录.md"),
("05-代码Review报告模板.md", "05.代码Review报告.md"),
("06-验收与交付报告模板.md", "06.验收与交付报告.md"),
]
for number, name in subtasks:
task_dir = temporary / "tasks" / f"{number}-{name}"
replacements = dict(common)
replacements.update(
{
"ST编号": number,
"子任务名称": name,
"PM编号及链接": f"[{pm_id}](../../README.md)",
"链接": "../../README.md",
}
)
for template_name, output_name in phase_templates:
write_new(
task_dir / output_name,
render(template_name, replacements),
)
temporary.replace(target)
except Exception:
shutil.rmtree(temporary, ignore_errors=True)
raise
return target
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="初始化 Vibe Coding L1/L2 文档,拒绝覆盖已有目标。",
)
subparsers = parser.add_subparsers(dest="level", required=True)
l1 = subparsers.add_parser("l1", help="初始化一份 L1 精简变更日志")
l1.add_argument("--name", required=True, help="需求名称")
l1.add_argument("--requirement-id", help="需求编号;未提供时使用“待确认”")
l1_target = l1.add_mutually_exclusive_group(required=True)
l1_target.add_argument("--management-root", help="独立需求的 management 目录")
l1_target.add_argument("--subtask-dir", help="既有大需求下的子任务目录")
l2 = subparsers.add_parser("l2", help="初始化完整 L2 PM 目录")
l2.add_argument("--management-root", required=True, help="management 目录")
l2.add_argument("--name", required=True, help="大需求名称")
l2.add_argument("--pm-id", help="PM 编号;未提供时使用“待确认”")
l2.add_argument("--source-version", default="v1", help="初始原始需求版本")
l2.add_argument(
"--subtask",
action="append",
default=[],
help="子任务,格式 ST-001:名称;可重复",
)
return parser
def main() -> int:
parser = build_parser()
args = parser.parse_args()
try:
target = build_l1(args) if args.level == "l1" else build_l2(args)
except (OSError, ValueError) as exc:
print(f"初始化失败:{exc}", file=sys.stderr)
return 1
print(f"初始化完成:{target}")
return 0
if __name__ == "__main__":
raise SystemExit(main())