feat(ship): merge short-ship and long-ship into one entry

Promote ship to the user-facing delivery skill. Kind is short or long
from an explicit mode, then self-judgment, then a question, defaulting
to short. Old names remain deprecation stubs.
This commit is contained in:
2026-10-24 14:27:19 +08:00
parent 7d6f3fe8e5
commit a15a046e4e
33 changed files with 331 additions and 315 deletions
+28 -4
View File
@@ -62,8 +62,8 @@ class CatalogTests(unittest.TestCase):
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.assertIn("../ship/SKILL.md", skill)
self.assertTrue((REPO_ROOT / "skills" / "ship" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
self.assertFalse((stub / "agents").exists())
@@ -73,8 +73,32 @@ class CatalogTests(unittest.TestCase):
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.assertIn("../ship/SKILL.md", skill)
self.assertTrue((REPO_ROOT / "skills" / "ship" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
self.assertFalse((stub / "agents").exists())
def test_short_ship_is_deprecation_stub(self) -> None:
stub = REPO_ROOT / "skills" / "short-ship"
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("name: short-ship", skill)
self.assertIn("已弃用", skill)
self.assertIn("../ship/SKILL.md", skill)
self.assertIn("short 模式", skill)
self.assertTrue((REPO_ROOT / "skills" / "ship" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
self.assertFalse((stub / "agents").exists())
def test_long_ship_is_deprecation_stub(self) -> None:
stub = REPO_ROOT / "skills" / "long-ship"
skill = (stub / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("name: long-ship", skill)
self.assertIn("已弃用", skill)
self.assertIn("../ship/SKILL.md", skill)
self.assertIn("long 模式", skill)
self.assertTrue((REPO_ROOT / "skills" / "ship" / "SKILL.md").is_file())
self.assertFalse((stub / "references").exists())
self.assertFalse((stub / "templates").exists())
self.assertFalse((stub / "agents").exists())
+32 -7
View File
@@ -203,16 +203,16 @@ def _create(root: Path, ship_id: str, kind: str, *, stop_at: str | None = None)
return root / ".pouch" / "ship" / ship_id
def _next(root: Path, ship_id: str, entry: str) -> dict:
result = _run(
NEXT,
def _next(root: Path, ship_id: str, entry: str | None = None) -> dict:
args = [
"--project-root",
str(root),
"--ship-id",
ship_id,
"--entry",
entry,
)
]
if entry is not None:
args.extend(["--entry", entry])
result = _run(NEXT, *args)
if result.returncode != 0:
raise AssertionError(result.stderr or result.stdout)
return json.loads(result.stdout)
@@ -404,11 +404,36 @@ class NextStepTests(unittest.TestCase):
self.assertIn("不要自己做", doc["summary"])
def test_entry_skills_are_managers(self) -> None:
for name in ("short-ship", "long-ship", "pr-ship"):
for name in ("ship", "pr-ship"):
text = (REPO_ROOT / "skills" / name / "SKILL.md").read_text(encoding="utf-8")
self.assertIn("开新 agent", text)
self.assertNotIn("读取并执行", text)
def test_next_step_infers_entry_from_kind(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
_create(root, "add-export-button", "short", stop_at="verified")
doc = _next(root, "add-export-button")
self.assertEqual(doc["entry"], "short")
self.assertEqual(doc["step"], "write_spec")
_create(root, "tenant-permissions", "long")
doc = _next(root, "tenant-permissions")
self.assertEqual(doc["entry"], "long")
self.assertEqual(doc["step"], "specifier_new")
missing = _run(
NEXT,
"--project-root",
str(root),
"--ship-id",
"does-not-exist",
)
self.assertEqual(missing.returncode, 0, missing.stderr)
blocked = json.loads(missing.stdout)
self.assertEqual(blocked["entry"], "short")
self.assertEqual(blocked["step"], "ensure_record")
if __name__ == "__main__":
unittest.main()