feat: rename bugfix-ship/requirement-flow to short-ship/long-ship with shared ship records
short-ship and long-ship share one ship-id record under .pouch/ship/<id>/. specifier writes PRDs into spec/; delivery-loop appends evidence pointers. Old names stay as deprecation stubs so existing installs keep working.
This commit is contained in:
@@ -32,8 +32,14 @@ class CatalogTests(unittest.TestCase):
|
||||
"releaser",
|
||||
"note-style",
|
||||
"pouch",
|
||||
"short-ship",
|
||||
"long-ship",
|
||||
"ship",
|
||||
"pr-ship",
|
||||
# 旧名兼容占位,仍须可列出(见 test_manage_release_is_deprecation_stub)
|
||||
"manage-release",
|
||||
"bugfix-ship",
|
||||
"requirement-flow",
|
||||
):
|
||||
self.assertIn(f"| {name} |", result.stdout)
|
||||
self.assertIn("/ack 初始化", result.stdout)
|
||||
@@ -51,6 +57,28 @@ class CatalogTests(unittest.TestCase):
|
||||
self.assertFalse((stub / "references").exists())
|
||||
self.assertFalse((stub / "templates").exists())
|
||||
|
||||
def test_bugfix_ship_is_deprecation_stub(self) -> None:
|
||||
stub = REPO_ROOT / "skills" / "bugfix-ship"
|
||||
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
|
||||
self.assertIn("name: bugfix-ship", skill)
|
||||
self.assertIn("已弃用", skill)
|
||||
self.assertIn("../short-ship/SKILL.md", skill)
|
||||
self.assertTrue((REPO_ROOT / "skills" / "short-ship" / "SKILL.md").is_file())
|
||||
self.assertFalse((stub / "references").exists())
|
||||
self.assertFalse((stub / "templates").exists())
|
||||
self.assertFalse((stub / "agents").exists())
|
||||
|
||||
def test_requirement_flow_is_deprecation_stub(self) -> None:
|
||||
stub = REPO_ROOT / "skills" / "requirement-flow"
|
||||
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
|
||||
self.assertIn("name: requirement-flow", skill)
|
||||
self.assertIn("已弃用", skill)
|
||||
self.assertIn("../long-ship/SKILL.md", skill)
|
||||
self.assertTrue((REPO_ROOT / "skills" / "long-ship" / "SKILL.md").is_file())
|
||||
self.assertFalse((stub / "references").exists())
|
||||
self.assertFalse((stub / "templates").exists())
|
||||
self.assertFalse((stub / "agents").exists())
|
||||
|
||||
def test_missing_readme_fails(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
skill = Path(tmp) / "demo"
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
CREATE = REPO_ROOT / "skills" / "ship" / "scripts" / "create_ship.py"
|
||||
VALIDATE = REPO_ROOT / "skills" / "ship" / "scripts" / "validate_ship.py"
|
||||
APPEND = REPO_ROOT / "skills" / "ship" / "scripts" / "append_evidence.py"
|
||||
|
||||
|
||||
def _run(script: Path, *args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[sys.executable, str(script), *args],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=cwd,
|
||||
)
|
||||
|
||||
|
||||
class YamlEmptyCollectionsTests(unittest.TestCase):
|
||||
def test_roundtrip_empty_list_and_dict(self) -> None:
|
||||
from pouch.yaml_io import safe_dump, safe_load
|
||||
|
||||
data = {"items": [], "meta": {}}
|
||||
loaded = safe_load(safe_dump(data))
|
||||
self.assertEqual(loaded, data)
|
||||
|
||||
|
||||
class CreateAndValidateTests(unittest.TestCase):
|
||||
def test_create_short_ship_validates(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
result = _run(
|
||||
CREATE,
|
||||
"--project-root",
|
||||
str(root),
|
||||
"--ship-id",
|
||||
"add-export-button",
|
||||
"--kind",
|
||||
"short",
|
||||
"--title",
|
||||
"列表页增加导出按钮",
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
ship_dir = root / ".pouch" / "ship" / "add-export-button"
|
||||
self.assertTrue((ship_dir / "current.yaml").is_file())
|
||||
self.assertTrue((ship_dir / "spec.md").is_file())
|
||||
self.assertFalse((ship_dir / "spec").is_dir())
|
||||
check = _run(VALIDATE, str(ship_dir))
|
||||
self.assertEqual(check.returncode, 0, check.stderr)
|
||||
|
||||
def test_create_long_ship_has_spec_dir(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
result = _run(
|
||||
CREATE,
|
||||
"--project-root",
|
||||
str(root),
|
||||
"--ship-id",
|
||||
"tenant-permissions",
|
||||
"--kind",
|
||||
"long",
|
||||
"--title",
|
||||
"多租户权限",
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
ship_dir = root / ".pouch" / "ship" / "tenant-permissions"
|
||||
self.assertTrue((ship_dir / "spec").is_dir())
|
||||
check = _run(VALIDATE, str(ship_dir))
|
||||
self.assertEqual(check.returncode, 0, check.stderr)
|
||||
|
||||
def test_rejects_date_id(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
result = _run(
|
||||
CREATE,
|
||||
"--project-root",
|
||||
tmp,
|
||||
"--ship-id",
|
||||
"20261013",
|
||||
"--kind",
|
||||
"short",
|
||||
"--title",
|
||||
"nope",
|
||||
)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("日期", result.stderr)
|
||||
|
||||
def test_append_test_run_pointer(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
created = _run(
|
||||
CREATE,
|
||||
"--project-root",
|
||||
str(root),
|
||||
"--ship-id",
|
||||
"add-export-button",
|
||||
"--kind",
|
||||
"short",
|
||||
"--title",
|
||||
"列表页增加导出按钮",
|
||||
)
|
||||
self.assertEqual(created.returncode, 0, created.stderr)
|
||||
run_dir = root / ".pouch" / "tester" / "runs"
|
||||
run_dir.mkdir(parents=True)
|
||||
run_path = run_dir / "20261013T010000Z-smoke-ab12.yaml"
|
||||
run_path.write_text("runId: demo\n", encoding="utf-8")
|
||||
ship_dir = root / ".pouch" / "ship" / "add-export-button"
|
||||
appended = _run(
|
||||
APPEND,
|
||||
str(ship_dir),
|
||||
"--kind",
|
||||
"testRun",
|
||||
"--path",
|
||||
".pouch/tester/runs/20261013T010000Z-smoke-ab12.yaml",
|
||||
"--result",
|
||||
"pass",
|
||||
"--suite",
|
||||
"smoke",
|
||||
)
|
||||
self.assertEqual(appended.returncode, 0, appended.stderr)
|
||||
text = (ship_dir / "current.yaml").read_text(encoding="utf-8")
|
||||
self.assertIn("20261013T010000Z-smoke-ab12.yaml", text)
|
||||
|
||||
def test_missing_pointer_file_fails(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
_run(
|
||||
CREATE,
|
||||
"--project-root",
|
||||
str(root),
|
||||
"--ship-id",
|
||||
"add-export-button",
|
||||
"--kind",
|
||||
"short",
|
||||
"--title",
|
||||
"列表页增加导出按钮",
|
||||
)
|
||||
ship_dir = root / ".pouch" / "ship" / "add-export-button"
|
||||
appended = _run(
|
||||
APPEND,
|
||||
str(ship_dir),
|
||||
"--kind",
|
||||
"testRun",
|
||||
"--path",
|
||||
".pouch/tester/runs/missing.yaml",
|
||||
"--result",
|
||||
"pass",
|
||||
)
|
||||
self.assertNotEqual(appended.returncode, 0)
|
||||
self.assertIn("不存在", appended.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user