#!/usr/bin/env python3 """Create a .pouch/ship// record from templates. Refuse to overwrite.""" from __future__ import annotations import argparse import shutil import sys from pathlib import Path from ship_lib import check_ship_id, skill_dir from validate_ship import validate_ship_dir def _replace(text: str, mapping: dict[str, str]) -> str: for key, value in mapping.items(): text = text.replace(f"<{key}>", value) text = text.replace(f"{{{{ {key} }}}}", value) return text def create_ship( project_root: Path, ship_id: str, kind: str, title: str, *, stop_at: str | None = None, source_kind: str = "user", source_ref: str | None = None, ) -> Path: err = check_ship_id(ship_id) if err: raise SystemExit(err) if kind not in {"short", "long"}: raise SystemExit(f"kind 无效: {kind}") title = title.strip() if not title: raise SystemExit("title 不能为空") dest = project_root / ".pouch" / "ship" / ship_id if dest.exists(): raise SystemExit(f"拒绝覆盖已有路径: {dest}") templates = skill_dir() / "templates" dest.mkdir(parents=True) mapping = { "ship-id": ship_id, "title": title, "kind": kind, "short title": title, } current = _replace( (templates / "current.template.yaml").read_text(encoding="utf-8"), mapping, ) current = current.replace("kind: short", f"kind: {kind}", 1) current = current.replace("status: draft", "status: draft", 1) if stop_at: current = current.replace("stopAt: null", f"stopAt: {stop_at}") if source_kind != "user": current = current.replace("kind: user", f"kind: {source_kind}", 1) if source_ref: current = current.replace("ref: null", f"ref: {source_ref}", 1) if kind == "long": current = current.replace("status: draft", "status: spec_review", 1) (dest / "current.yaml").write_text(current, encoding="utf-8") (dest / "spec.md").write_text( _replace((templates / "spec.template.md").read_text(encoding="utf-8"), mapping), encoding="utf-8", ) (dest / "tasks.yaml").write_text( _replace( (templates / "tasks.template.yaml").read_text(encoding="utf-8"), mapping ), encoding="utf-8", ) (dest / "status.md").write_text( _replace( (templates / "status.template.md").read_text(encoding="utf-8"), {**mapping, "status": "spec_review" if kind == "long" else "draft", "stopAt": stop_at or "null", "delivery.state": "change_ready"}, ), encoding="utf-8", ) if kind == "long": spec_dir = dest / "spec" spec_dir.mkdir() (spec_dir / "brief.md").write_text(f"# {title}\n\n", encoding="utf-8") (spec_dir / "review.yaml").write_text("[]\n", encoding="utf-8") (spec_dir / "decisions.yaml").write_text("[]\n", encoding="utf-8") (spec_dir / "risks.yaml").write_text("[]\n", encoding="utf-8") issues = validate_ship_dir(dest) if issues: shutil.rmtree(dest) for item in issues: print(f"✗ {item}", file=sys.stderr) raise SystemExit("新建后校验失败,已删除不完整目录") return dest def main() -> int: parser = argparse.ArgumentParser(description="新建 ship 记录") parser.add_argument("--project-root", required=True) parser.add_argument("--ship-id", required=True) parser.add_argument("--kind", choices=("short", "long"), required=True) parser.add_argument("--title", required=True) parser.add_argument("--stop-at", default=None) parser.add_argument("--source-kind", default="user") parser.add_argument("--source-ref", default=None) args = parser.parse_args() dest = create_ship( Path(args.project_root).resolve(), args.ship_id, args.kind, args.title, stop_at=args.stop_at, source_kind=args.source_kind, source_ref=args.source_ref, ) print(dest) return 0 if __name__ == "__main__": raise SystemExit(main())