feat: rename skills/skiff to pouch and move ACK state under .pouch

Use ~/.pouch, the pouch CLI, and .pouch.yaml as the SSOT container.
Keep the inner skills/ packages, and store ACK project state in
.pouch/ack instead of docs/ack.
This commit is contained in:
2026-08-25 15:20:02 +08:00
parent ee31278947
commit f3cd56b78e
83 changed files with 1265 additions and 1192 deletions
+1 -1
View File
@@ -291,7 +291,7 @@ class AckDeliveryValidationTests(unittest.TestCase):
errors = validate_delivery.validate_tasks_link(contract, tasks)
self.assertIn(
"tasks.project.deliveryFile 必须固定为 docs/ack/delivery.yaml",
"tasks.project.deliveryFile 必须固定为 .pouch/ack/delivery.yaml",
errors,
)
self.assertIn("delivery.project.name 必须与 tasks.project.name 一致", errors)
+14 -14
View File
@@ -445,7 +445,7 @@ class AckKnowledgeTests(unittest.TestCase):
tasks_data = {
"project": {
"name": "notes-web",
"knowledgeFile": "docs/ack/knowledge.yaml",
"knowledgeFile": ".pouch/ack/knowledge.yaml",
},
"tasks": [task],
}
@@ -470,7 +470,7 @@ class AckKnowledgeTests(unittest.TestCase):
tasks = {
"project": {
"name": "another-project",
"knowledgeFile": "docs/ack/knowledge.yaml",
"knowledgeFile": ".pouch/ack/knowledge.yaml",
},
"tasks": [],
}
@@ -502,7 +502,7 @@ class AckKnowledgeTests(unittest.TestCase):
tasks_data = {
"project": {
"name": "notes-web",
"knowledgeFile": "docs/ack/knowledge.yaml",
"knowledgeFile": ".pouch/ack/knowledge.yaml",
},
"tasks": [task],
}
@@ -522,7 +522,7 @@ class AckKnowledgeTests(unittest.TestCase):
tasks_data = {
"project": {
"name": "notes-web",
"knowledgeFile": "docs/ack/knowledge.yaml",
"knowledgeFile": ".pouch/ack/knowledge.yaml",
},
"tasks": [
{
@@ -567,7 +567,7 @@ class AckKnowledgeTests(unittest.TestCase):
}
with tempfile.TemporaryDirectory() as temp_dir:
project = Path(temp_dir) / "project"
ack_dir = project / "docs" / "ack"
ack_dir = project / ".pouch" / "ack"
ack_dir.mkdir(parents=True)
knowledge_path = ack_dir / "knowledge.yaml"
tasks_path = ack_dir / "tasks.yaml"
@@ -578,7 +578,7 @@ class AckKnowledgeTests(unittest.TestCase):
"project": {
"name": "notes-web",
"repoPath": str(project),
"knowledgeFile": "docs/ack/other.yaml",
"knowledgeFile": ".pouch/ack/other.yaml",
},
"tasks": [task],
}
@@ -595,9 +595,9 @@ class AckKnowledgeTests(unittest.TestCase):
errors = validate_knowledge.validate_task_references(data, tasks_data)
self.assertTrue(any("project.knowledgeFile 必填" in e for e in errors))
tasks_data["project"]["knowledgeFile"] = "docs/ack/knowledge.yaml"
tasks_data["project"]["knowledgeFile"] = ".pouch/ack/knowledge.yaml"
staging_root = Path(temp_dir) / "staging"
staged = staging_root / "docs" / "ack" / "knowledge.yaml"
staged = staging_root / ".pouch" / "ack" / "knowledge.yaml"
staged.parent.mkdir(parents=True)
staged.write_text(
yaml.safe_dump(data, allow_unicode=True), encoding="utf-8"
@@ -636,7 +636,7 @@ class AckKnowledgeTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as temp_dir:
base = Path(temp_dir)
project = base / "project"
ack_dir = project / "docs" / "ack"
ack_dir = project / ".pouch" / "ack"
outside = base / "outside"
ack_dir.mkdir(parents=True)
outside.mkdir()
@@ -653,7 +653,7 @@ class AckKnowledgeTests(unittest.TestCase):
"project": {
"name": "notes-web",
"repoPath": str(project / "missing"),
"knowledgeFile": "docs/ack/not-the-current-file.yaml",
"knowledgeFile": ".pouch/ack/not-the-current-file.yaml",
},
"tasks": [task],
}
@@ -746,7 +746,7 @@ class AckKnowledgeTests(unittest.TestCase):
tasks_data = {
"project": {
"name": "notes-web",
"knowledgeFile": "docs/ack/knowledge.yaml",
"knowledgeFile": ".pouch/ack/knowledge.yaml",
},
"tasks": [task],
}
@@ -765,7 +765,7 @@ class AckKnowledgeTests(unittest.TestCase):
def test_cli_validates_tasks_and_selector_outputs_stable_refs(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
project = Path(temp_dir) / "project"
ack_dir = project / "docs" / "ack"
ack_dir = project / ".pouch" / "ack"
ack_dir.mkdir(parents=True)
knowledge_path = ack_dir / "knowledge.yaml"
knowledge_path.write_text(
@@ -779,7 +779,7 @@ class AckKnowledgeTests(unittest.TestCase):
"project": {
"name": "notes-web",
"repoPath": str(project),
"knowledgeFile": "docs/ack/knowledge.yaml",
"knowledgeFile": ".pouch/ack/knowledge.yaml",
},
"tasks": [
{
@@ -878,7 +878,7 @@ class AckKnowledgeTests(unittest.TestCase):
def test_paired_examples_validate_when_installed_in_project_layout(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
project = Path(temp_dir) / "notes-web"
ack_dir = project / "docs" / "ack"
ack_dir = project / ".pouch" / "ack"
ack_dir.mkdir(parents=True)
knowledge_path = ack_dir / "knowledge.yaml"
tasks_path = ack_dir / "tasks.yaml"
+2 -2
View File
@@ -435,7 +435,7 @@ class PlanTests(unittest.TestCase):
def test_authoritative_board_is_derived_from_project_root_without_repo_path(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
project = Path(temporary).resolve()
ack_dir = project / "docs" / "ack"
ack_dir = project / ".pouch" / "ack"
ack_dir.mkdir(parents=True)
task_board = board(project)
del task_board["project"]["repoPath"]
@@ -454,7 +454,7 @@ class PlanTests(unittest.TestCase):
def test_authoritative_board_ignores_legacy_repo_path(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
project = Path(temporary).resolve()
ack_dir = project / "docs" / "ack"
ack_dir = project / ".pouch" / "ack"
ack_dir.mkdir(parents=True)
task_board = board(project)
task_board["project"]["repoPath"] = "/legacy/other-worktree"
+7 -7
View File
@@ -13,11 +13,11 @@ class AckSkillContentTests(unittest.TestCase):
content = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(encoding="utf-8")
for expected in (
"skiff init ack --project <project-root>",
"docs/ack/project.md",
"docs/ack/tasks.yaml",
"docs/ack/knowledge.yaml",
"docs/ack/delivery.yaml",
"pouch init ack --project <project-root>",
".pouch/ack/project.md",
".pouch/ack/tasks.yaml",
".pouch/ack/knowledge.yaml",
".pouch/ack/delivery.yaml",
"tasks: []",
"validate_tasks.py",
"validate_knowledge.py",
@@ -131,7 +131,7 @@ class AckSkillContentTests(unittest.TestCase):
):
self.assertTrue((ack_dir / relative_path).is_file(), relative_path)
version = (ack_dir / "VERSION").read_text(encoding="utf-8").strip()
self.assertEqual(version, "0.18.0")
self.assertEqual(version, "0.19.0")
self.assertIn(
f'ackVersion: "{version}"',
(ack_dir / "examples" / "tasks.example.yaml").read_text(encoding="utf-8"),
@@ -156,7 +156,7 @@ class AckSkillContentTests(unittest.TestCase):
# v0.19 起 allowedWorktrees 白名单废弃:模板只保留废弃说明注释,不再生成该字段
self.assertNotIn("allowedWorktrees:", template)
self.assertIn("allowedWorktrees", template)
self.assertIn('knowledgeFile: "docs/ack/knowledge.yaml"', template)
self.assertIn('knowledgeFile: ".pouch/ack/knowledge.yaml"', template)
if __name__ == "__main__":
+9 -9
View File
@@ -364,7 +364,7 @@ class AckTaskValidationTests(unittest.TestCase):
def test_delivery_run_is_separate_and_requires_verified_tasks(self) -> None:
board = valid_manual_routing_board()
board["project"]["deliveryFile"] = "docs/ack/delivery.yaml"
board["project"]["deliveryFile"] = ".pouch/ack/delivery.yaml"
board["tasks"][0]["status"] = "verified"
board["deliveryRuns"] = [
{
@@ -405,7 +405,7 @@ class AckTaskValidationTests(unittest.TestCase):
def test_validation_ready_delivery_run_does_not_require_pull_request(self) -> None:
board = valid_manual_routing_board()
board["project"]["deliveryFile"] = "docs/ack/delivery.yaml"
board["project"]["deliveryFile"] = ".pouch/ack/delivery.yaml"
board["tasks"][0]["status"] = "verified"
board["deliveryRuns"] = [
{
@@ -440,7 +440,7 @@ class AckTaskValidationTests(unittest.TestCase):
def test_intent_delivery_run_allows_empty_task_ids(self) -> None:
board = valid_manual_routing_board()
board["project"]["deliveryFile"] = "docs/ack/delivery.yaml"
board["project"]["deliveryFile"] = ".pouch/ack/delivery.yaml"
board["deliveryRuns"] = [
{
"id": "DR-test-env-1",
@@ -488,7 +488,7 @@ class AckTaskValidationTests(unittest.TestCase):
)
board = valid_manual_routing_board()
board["project"]["deliveryFile"] = "docs/ack/delivery.yaml"
board["project"]["deliveryFile"] = ".pouch/ack/delivery.yaml"
self.assert_board_rejected_in_all_modes(
board,
"引用 deliveryFile 的任务板必须包含 deliveryRuns 列表",
@@ -496,7 +496,7 @@ class AckTaskValidationTests(unittest.TestCase):
def test_delivery_run_binds_revisions_and_final_artifact_digest(self) -> None:
board = valid_manual_routing_board()
board["project"]["deliveryFile"] = "docs/ack/delivery.yaml"
board["project"]["deliveryFile"] = ".pouch/ack/delivery.yaml"
board["deliveryRuns"] = [
{
"id": "DR-demo-2",
@@ -912,11 +912,11 @@ class AckTaskValidationTests(unittest.TestCase):
def test_knowledge_file_is_fixed_in_all_modes(self) -> None:
board = valid_knowledge_board()
board["project"]["knowledgeFile"] = "docs/ack/alternate.yaml"
board["project"]["knowledgeFile"] = ".pouch/ack/alternate.yaml"
self.assert_board_rejected_in_all_modes(
board,
"project.knowledgeFile 必须固定为 docs/ack/knowledge.yaml",
"project.knowledgeFile 必须固定为 .pouch/ack/knowledge.yaml",
)
def test_basic_identifiers_must_be_nonempty_strings_in_all_modes(self) -> None:
@@ -1116,8 +1116,8 @@ class AckTaskValidationTests(unittest.TestCase):
"repoPath": "/repo",
"baseUrl": "http://127.0.0.1:3000",
"devWorktree": "/repo-dev",
"overlayFile": "docs/ack/project.md",
"knowledgeFile": "docs/ack/knowledge.yaml",
"overlayFile": ".pouch/ack/project.md",
"knowledgeFile": ".pouch/ack/knowledge.yaml",
}
)
board["tasks"][0].update(
+8 -8
View File
@@ -42,7 +42,7 @@ class AckVerificationRunnerTests(unittest.TestCase):
project: Path,
knowledge: dict,
) -> subprocess.CompletedProcess[str]:
knowledge_path = project / "docs" / "ack" / "knowledge.yaml"
knowledge_path = project / ".pouch" / "ack" / "knowledge.yaml"
knowledge_path.parent.mkdir(parents=True, exist_ok=True)
knowledge_path.write_text(
yaml.safe_dump(knowledge, allow_unicode=True),
@@ -153,7 +153,7 @@ class AckVerificationRunnerTests(unittest.TestCase):
target = outside / "unsafe"
target.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
target.chmod(target.stat().st_mode | 0o111)
knowledge_path = project / "docs" / "ack" / "knowledge.yaml"
knowledge_path = project / ".pouch" / "ack" / "knowledge.yaml"
knowledge_path.parent.mkdir(parents=True)
knowledge_path.write_text(
yaml.safe_dump(
@@ -194,7 +194,7 @@ class AckVerificationRunnerTests(unittest.TestCase):
encoding="utf-8",
)
target.chmod(target.stat().st_mode | 0o111)
knowledge_path = project / "docs" / "ack" / "knowledge.yaml"
knowledge_path = project / ".pouch" / "ack" / "knowledge.yaml"
knowledge_path.parent.mkdir(parents=True)
knowledge_path.write_text(
"""\
@@ -238,7 +238,7 @@ entries: []
with tempfile.TemporaryDirectory() as temp_dir:
base = Path(temp_dir)
project = base / "project"
ack_dir = project / "docs" / "ack"
ack_dir = project / ".pouch" / "ack"
ack_dir.mkdir(parents=True)
document = yaml.safe_dump(
knowledge_with_target("checks/reviewed", []),
@@ -293,7 +293,7 @@ entries: []
with tempfile.TemporaryDirectory() as temp_dir:
base = Path(temp_dir)
project = base / "project"
canonical = project / "docs" / "ack" / "knowledge.yaml"
canonical = project / ".pouch" / "ack" / "knowledge.yaml"
canonical.parent.mkdir(parents=True)
safe_document = knowledge_with_target("checks/safe", [])
forged_document = knowledge_with_target("checks/danger", [])
@@ -335,7 +335,7 @@ entries: []
) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
project = Path(temp_dir) / "project"
canonical = project / "docs" / "ack" / "knowledge.yaml"
canonical = project / ".pouch" / "ack" / "knowledge.yaml"
canonical.parent.mkdir(parents=True)
canonical.write_text(
yaml.safe_dump(
@@ -346,7 +346,7 @@ entries: []
)
source_fd, error = RUNNER_MODULE._open_regular_beneath(
project,
"docs/ack/knowledge.yaml",
".pouch/ack/knowledge.yaml",
require_executable=False,
)
self.assertIsNone(error)
@@ -521,7 +521,7 @@ entries: []
encoding="utf-8",
)
target.chmod(target.stat().st_mode | 0o111)
knowledge_path = project / "docs" / "ack" / "knowledge.yaml"
knowledge_path = project / ".pouch" / "ack" / "knowledge.yaml"
knowledge_path.parent.mkdir(parents=True)
knowledge_path.write_text(
yaml.safe_dump(
+11 -11
View File
@@ -25,7 +25,7 @@ class AgentsTargetTests(unittest.TestCase):
def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory()
self.home = Path(self.temp_dir.name)
self.skills_home = self.home / ".skills"
self.skills_home = self.home / ".pouch"
self.skills_home.mkdir(parents=True)
(self.skills_home / "catalog.yaml").write_text("", encoding="utf-8")
write_skill(self.skills_home, "demo-skill")
@@ -33,12 +33,12 @@ class AgentsTargetTests(unittest.TestCase):
def tearDown(self) -> None:
self.temp_dir.cleanup()
def run_skiff(self, *args: str) -> subprocess.CompletedProcess[str]:
def run_pouch(self, *args: str) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["HOME"] = str(self.home)
env["PYTHONPATH"] = str(REPO_ROOT)
return subprocess.run(
[sys.executable, "-m", "skiff", *args],
[sys.executable, "-m", "pouch", *args],
cwd=REPO_ROOT,
env=env,
text=True,
@@ -47,7 +47,7 @@ class AgentsTargetTests(unittest.TestCase):
)
def test_global_add_creates_symlink_in_agents_skills(self) -> None:
result = self.run_skiff("add", "builtin/demo-skill", "-g", "-a", "agents")
result = self.run_pouch("add", "builtin/demo-skill", "-g", "-a", "agents")
self.assertEqual(result.returncode, 0, result.stderr)
link = self.home / ".agents" / "skills" / "demo-skill"
@@ -56,7 +56,7 @@ class AgentsTargetTests(unittest.TestCase):
link.resolve(), (self.skills_home / "skills" / "demo-skill").resolve()
)
removed = self.run_skiff("rm", "builtin/demo-skill", "-g", "-a", "agents")
removed = self.run_pouch("rm", "builtin/demo-skill", "-g", "-a", "agents")
self.assertEqual(removed.returncode, 0, removed.stderr)
self.assertFalse(link.exists())
@@ -64,7 +64,7 @@ class AgentsTargetTests(unittest.TestCase):
project = self.home / "app"
project.mkdir()
added = self.run_skiff(
added = self.run_pouch(
"add",
"builtin/demo-skill",
"--project",
@@ -80,25 +80,25 @@ class AgentsTargetTests(unittest.TestCase):
links = list((project / ".agents" / "skills").glob("demo-skill"))
self.assertEqual(len(links), 1)
self.assertTrue(links[0].is_symlink())
manifest = (project / ".skills.yaml").read_text(encoding="utf-8")
manifest = (project / ".pouch.yaml").read_text(encoding="utf-8")
self.assertIn("demo-skill", manifest)
removed = self.run_skiff(
removed = self.run_pouch(
"rm", "builtin/demo-skill", "--project", str(project), "-y"
)
self.assertEqual(removed.returncode, 0, removed.stderr)
self.assertFalse((project / ".agents" / "skills" / "demo-skill").exists())
def test_status_lists_installed_skill_for_agents_target(self) -> None:
installed = self.run_skiff("add", "builtin/demo-skill", "-g", "-a", "agents")
status = self.run_skiff("status", "-a", "agents")
installed = self.run_pouch("add", "builtin/demo-skill", "-g", "-a", "agents")
status = self.run_pouch("status", "-a", "agents")
self.assertEqual(installed.returncode, 0, installed.stderr)
self.assertEqual(status.returncode, 0, status.stderr)
self.assertIn("demo-skill", status.stdout)
def test_opencode_target_is_no_longer_supported(self) -> None:
result = self.run_skiff("add", "builtin/demo-skill", "-g", "-a", "opencode")
result = self.run_pouch("add", "builtin/demo-skill", "-g", "-a", "opencode")
self.assertNotEqual(result.returncode, 0)
self.assertIn("未知 agent", result.stderr)
+20 -20
View File
@@ -9,8 +9,8 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from skiff import catalog
from skiff.catalog import catalog_checkout_path, catalog_repo
from pouch import catalog
from pouch.catalog import catalog_checkout_path, catalog_repo
REPO_ROOT = Path(__file__).resolve().parents[1]
@@ -40,7 +40,7 @@ class CatalogCollectionTests(unittest.TestCase):
def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory()
self.home = Path(self.temp_dir.name)
self.skills_home = self.home / ".skills"
self.skills_home = self.home / ".pouch"
(self.skills_home / "skills").mkdir(parents=True)
self.upstream = self.home / "upstream"
@@ -79,12 +79,12 @@ class CatalogCollectionTests(unittest.TestCase):
def tearDown(self) -> None:
self.temp_dir.cleanup()
def run_skiff(self, *args: str) -> subprocess.CompletedProcess[str]:
def run_pouch(self, *args: str) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["HOME"] = str(self.home)
env["PYTHONPATH"] = str(REPO_ROOT)
return subprocess.run(
[sys.executable, "-m", "skiff", *args],
[sys.executable, "-m", "pouch", *args],
cwd=REPO_ROOT,
env=env,
text=True,
@@ -95,16 +95,16 @@ class CatalogCollectionTests(unittest.TestCase):
def test_catalog_repo_expands_home_relative_local_path(self) -> None:
with patch.dict(os.environ, {"HOME": str(self.home)}):
self.assertEqual(
catalog_repo({"repo": "~/.skills"}),
catalog_repo({"repo": "~/.pouch"}),
str(self.skills_home),
)
self.assertEqual(
catalog_checkout_path("skills", {"repo": "~/.skills"}),
catalog_checkout_path("skills", {"repo": "~/.pouch"}),
self.skills_home.resolve(),
)
def test_add_collection_installs_every_discovered_skill(self) -> None:
result = self.run_skiff("add", "test-pack", "-g", "-a", "codex")
result = self.run_pouch("add", "test-pack", "-g", "-a", "codex")
self.assertEqual(result.returncode, 0, result.stderr)
skill_dir = self.home / ".codex" / "skills"
@@ -115,7 +115,7 @@ class CatalogCollectionTests(unittest.TestCase):
self.assertFalse((skill_dir / "test-pack").exists())
def test_add_qualified_collection_skill_installs_only_that_skill(self) -> None:
result = self.run_skiff("add", "test-pack/second-skill", "-g", "-a", "codex")
result = self.run_pouch("add", "test-pack/second-skill", "-g", "-a", "codex")
self.assertEqual(result.returncode, 0, result.stderr)
skill_dir = self.home / ".codex" / "skills"
@@ -124,7 +124,7 @@ class CatalogCollectionTests(unittest.TestCase):
self.assertTrue((skill_dir / "second-skill" / "SKILL.md").is_file())
def test_add_collection_with_catalog_source_installs_all(self) -> None:
result = self.run_skiff(
result = self.run_pouch(
"add",
"test-pack",
"--source",
@@ -140,7 +140,7 @@ class CatalogCollectionTests(unittest.TestCase):
self.assertTrue((skill_dir / "second-skill").is_symlink())
def test_legacy_registry_source_alias_still_installs(self) -> None:
result = self.run_skiff(
result = self.run_pouch(
"add",
"test-pack",
"--source",
@@ -159,7 +159,7 @@ class CatalogCollectionTests(unittest.TestCase):
project = self.home / "project"
project.mkdir()
added = self.run_skiff(
added = self.run_pouch(
"add",
"test-pack/first-skill",
"--project",
@@ -169,8 +169,8 @@ class CatalogCollectionTests(unittest.TestCase):
)
link = project / ".agents" / "skills" / "first-skill"
link.unlink()
shutil.rmtree(self.home / ".local" / "share" / "skills" / "externals")
synced = self.run_skiff(
shutil.rmtree(self.home / ".local" / "share" / "pouch" / "externals")
synced = self.run_pouch(
"sync",
"--project",
str(project),
@@ -179,7 +179,7 @@ class CatalogCollectionTests(unittest.TestCase):
)
self.assertEqual(added.returncode, 0, added.stderr)
self.assertIn('source: "catalog:test-pack"', project.joinpath(".skills.yaml").read_text())
self.assertIn('source: "catalog:test-pack"', project.joinpath(".pouch.yaml").read_text())
self.assertEqual(synced.returncode, 0, synced.stderr)
self.assertTrue(link.is_symlink())
self.assertTrue((link / "SKILL.md").is_file())
@@ -188,15 +188,15 @@ class CatalogCollectionTests(unittest.TestCase):
blocked = self.home / ".codex" / "skills" / "second-skill"
blocked.mkdir(parents=True)
result = self.run_skiff("add", "test-pack", "-g", "-a", "codex")
result = self.run_pouch("add", "test-pack", "-g", "-a", "codex")
self.assertNotEqual(result.returncode, 0)
self.assertFalse((self.home / ".codex" / "skills" / "first-skill").exists())
self.assertTrue(blocked.is_dir())
def test_remove_collection_removes_all_child_links(self) -> None:
installed = self.run_skiff("add", "test-pack", "-g", "-a", "codex")
removed = self.run_skiff("remove", "test-pack", "-g", "-a", "codex")
installed = self.run_pouch("add", "test-pack", "-g", "-a", "codex")
removed = self.run_pouch("remove", "test-pack", "-g", "-a", "codex")
self.assertEqual(installed.returncode, 0, installed.stderr)
self.assertEqual(removed.returncode, 0, removed.stderr)
@@ -205,8 +205,8 @@ class CatalogCollectionTests(unittest.TestCase):
self.assertFalse((skill_dir / "second-skill").exists())
def test_status_expands_collection_children(self) -> None:
installed = self.run_skiff("add", "test-pack", "-g", "-a", "codex")
status = self.run_skiff("status", "-a", "codex")
installed = self.run_pouch("add", "test-pack", "-g", "-a", "codex")
status = self.run_pouch("status", "-a", "codex")
self.assertEqual(installed.returncode, 0, installed.stderr)
self.assertEqual(status.returncode, 0, status.stderr)
+29 -29
View File
@@ -9,7 +9,7 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from skiff import cli
from pouch import cli
REPO_ROOT = Path(__file__).resolve().parents[1]
@@ -19,7 +19,7 @@ class CreateWorkflowTests(unittest.TestCase):
def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory()
self.home = Path(self.temp_dir.name)
self.skills_home = self.home / ".skills"
self.skills_home = self.home / ".pouch"
template = self.skills_home / "skills" / "_template"
template.mkdir(parents=True)
template.joinpath("SKILL.md").write_text(
@@ -35,28 +35,28 @@ class CreateWorkflowTests(unittest.TestCase):
"给出一条可以直接交给 Agent 的示例请求。\n",
encoding="utf-8",
)
project_skill = self.skills_home / "skills" / "skiff"
project_skill = self.skills_home / "skills" / "pouch"
project_skill.mkdir()
project_skill.joinpath("SKILL.md").write_text(
"---\nname: skiff\ndescription: >-\n"
"---\nname: pouch\ndescription: >-\n"
" 创建和维护自研 skill。用于创建、安装、反馈或更新 skill 时。\n"
"---\n\n# skiff\n\n## 步骤\n\n1. 维护 skill。\n",
"---\n\n# pouch\n\n## 步骤\n\n1. 维护 skill。\n",
encoding="utf-8",
)
project_skill.joinpath("README.md").write_text(
"# skiff\n\n供人类使用的 skiff 说明。\n",
"# pouch\n\n供人类使用的 pouch 说明。\n",
encoding="utf-8",
)
def tearDown(self) -> None:
self.temp_dir.cleanup()
def run_skiff(self, *args: str) -> subprocess.CompletedProcess[str]:
def run_pouch(self, *args: str) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["HOME"] = str(self.home)
env["PYTHONPATH"] = str(REPO_ROOT)
return subprocess.run(
[sys.executable, "-m", "skiff", *args],
[sys.executable, "-m", "pouch", *args],
cwd=REPO_ROOT,
env=env,
text=True,
@@ -65,25 +65,25 @@ class CreateWorkflowTests(unittest.TestCase):
)
def test_setup_command_is_not_exposed(self) -> None:
help_result = self.run_skiff("--help")
setup_result = self.run_skiff("setup", str(self.skills_home))
help_result = self.run_pouch("--help")
setup_result = self.run_pouch("setup", str(self.skills_home))
self.assertEqual(help_result.returncode, 0, help_result.stderr)
self.assertNotIn("setup", help_result.stdout)
self.assertNotEqual(setup_result.returncode, 0)
def test_bootstrap_installs_project_skill_globally_for_all_agents(self) -> None:
first = self.run_skiff("bootstrap")
second = self.run_skiff("bootstrap")
first = self.run_pouch("bootstrap")
second = self.run_pouch("bootstrap")
self.assertEqual(first.returncode, 0, first.stderr)
self.assertEqual(second.returncode, 0, second.stderr)
expected = (self.skills_home / "skills" / "skiff").resolve()
expected = (self.skills_home / "skills" / "pouch").resolve()
for relative in (
".cursor/skills/skiff",
".claude/skills/skiff",
".codex/skills/skiff",
".agents/skills/skiff",
".cursor/skills/pouch",
".claude/skills/pouch",
".codex/skills/pouch",
".agents/skills/pouch",
):
link = self.home / relative
self.assertTrue(link.is_symlink(), relative)
@@ -93,8 +93,8 @@ class CreateWorkflowTests(unittest.TestCase):
def test_update_pulls_skills_home(self) -> None:
with (
patch.object(cli, "SKILLS_HOME", self.skills_home),
patch.object(cli, "ensure_skills_home"),
patch.object(cli, "POUCH_HOME", self.skills_home),
patch.object(cli, "ensure_pouch_home"),
patch.object(cli.subprocess, "run") as run,
):
cli.cmd_update(argparse.Namespace())
@@ -108,7 +108,7 @@ class CreateWorkflowTests(unittest.TestCase):
project = self.home / "project"
project.mkdir()
result = self.run_skiff(
result = self.run_pouch(
"create",
"migration-review",
"--idea",
@@ -128,19 +128,19 @@ class CreateWorkflowTests(unittest.TestCase):
self.assertIn("idea: 检查数据库迁移", brief)
self.assertIn(f"source_project: {project.resolve()}", brief)
self.assertFalse((self.skills_home / "skills" / "migration-review").exists())
self.assertIn("请完善 skiff 草稿 migration-review", result.stdout)
self.assertIn("请完善 pouch 草稿 migration-review", result.stdout)
def test_check_rejects_template_placeholders(self) -> None:
created = self.run_skiff("create", "migration-review", "--idea", "检查数据库迁移")
created = self.run_pouch("create", "migration-review", "--idea", "检查数据库迁移")
self.assertEqual(created.returncode, 0, created.stderr)
result = self.run_skiff("check", "migration-review")
result = self.run_pouch("check", "migration-review")
self.assertNotEqual(result.returncode, 0)
self.assertIn("模板占位", result.stderr)
def test_finalize_moves_valid_draft_and_removes_brief(self) -> None:
created = self.run_skiff("create", "migration-review", "--idea", "检查数据库迁移")
created = self.run_pouch("create", "migration-review", "--idea", "检查数据库迁移")
self.assertEqual(created.returncode, 0, created.stderr)
draft = self.skills_home / ".drafts" / "migration-review"
draft.joinpath("SKILL.md").write_text(
@@ -156,7 +156,7 @@ class CreateWorkflowTests(unittest.TestCase):
encoding="utf-8",
)
result = self.run_skiff("finalize", "migration-review")
result = self.run_pouch("finalize", "migration-review")
self.assertEqual(result.returncode, 0, result.stderr)
final = self.skills_home / "skills" / "migration-review"
@@ -179,7 +179,7 @@ class CreateWorkflowTests(unittest.TestCase):
encoding="utf-8",
)
result = self.run_skiff("check", "reference-check")
result = self.run_pouch("check", "reference-check")
self.assertNotEqual(result.returncode, 0)
self.assertIn("引用文件不存在", result.stderr)
@@ -194,19 +194,19 @@ class CreateWorkflowTests(unittest.TestCase):
encoding="utf-8",
)
result = self.run_skiff("check", "missing-readme")
result = self.run_pouch("check", "missing-readme")
self.assertNotEqual(result.returncode, 0)
self.assertIn("缺少 README.md", result.stderr)
def test_finalize_does_not_overwrite_existing_owned_skill(self) -> None:
created = self.run_skiff("create", "collision-check", "--idea", "检查名称冲突")
created = self.run_pouch("create", "collision-check", "--idea", "检查名称冲突")
self.assertEqual(created.returncode, 0, created.stderr)
final = self.skills_home / "skills" / "collision-check"
final.mkdir()
final.joinpath("marker").write_text("keep", encoding="utf-8")
result = self.run_skiff("finalize", "collision-check")
result = self.run_pouch("finalize", "collision-check")
self.assertNotEqual(result.returncode, 0)
self.assertEqual(final.joinpath("marker").read_text(encoding="utf-8"), "keep")
+31 -31
View File
@@ -8,8 +8,8 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from skiff.project import normalize_skill_entry
from skiff.sources import fetch_source
from pouch.project import normalize_skill_entry
from pouch.sources import fetch_source
REPO_ROOT = Path(__file__).resolve().parents[1]
@@ -41,19 +41,19 @@ class CustomSourceTests(unittest.TestCase):
def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory()
self.home = Path(self.temp_dir.name)
self.skills_home = self.home / ".skills"
self.skills_home = self.home / ".pouch"
(self.skills_home / "skills").mkdir(parents=True)
(self.skills_home / "catalog.yaml").write_text("", encoding="utf-8")
def tearDown(self) -> None:
self.temp_dir.cleanup()
def run_skiff(self, *args: str) -> subprocess.CompletedProcess[str]:
def run_pouch(self, *args: str) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["HOME"] = str(self.home)
env["PYTHONPATH"] = str(REPO_ROOT)
return subprocess.run(
[sys.executable, "-m", "skiff", *args],
[sys.executable, "-m", "pouch", *args],
cwd=REPO_ROOT,
env=env,
text=True,
@@ -65,7 +65,7 @@ class CustomSourceTests(unittest.TestCase):
company = self.home / "company"
expected = write_skill(company / "internal" / "skills", "code-review")
added = self.run_skiff(
added = self.run_pouch(
"source",
"add",
"company",
@@ -74,8 +74,8 @@ class CustomSourceTests(unittest.TestCase):
"--skills-path",
"internal/skills",
)
listed = self.run_skiff("list", "--source", "company")
installed = self.run_skiff("add", "company/code-review", "-g", "-a", "codex")
listed = self.run_pouch("list", "--source", "company")
installed = self.run_pouch("add", "company/code-review", "-g", "-a", "codex")
self.assertEqual(added.returncode, 0, added.stderr)
self.assertEqual(listed.returncode, 0, listed.stderr)
@@ -84,7 +84,7 @@ class CustomSourceTests(unittest.TestCase):
link = self.home / ".codex" / "skills" / "code-review"
self.assertTrue(link.is_symlink())
self.assertEqual(link.resolve(), expected.resolve())
config = self.home / ".config" / "skiff" / "config.yaml"
config = self.home / ".config" / "pouch" / "config.yaml"
self.assertIn("company:", config.read_text(encoding="utf-8"))
def test_local_source_can_expose_a_single_skill(self) -> None:
@@ -94,7 +94,7 @@ class CustomSourceTests(unittest.TestCase):
"---\nname: solo\ndescription: Test single source.\n---\n",
encoding="utf-8",
)
added = self.run_skiff(
added = self.run_pouch(
"source",
"add",
"solo",
@@ -103,7 +103,7 @@ class CustomSourceTests(unittest.TestCase):
"--skills-path",
".",
)
installed = self.run_skiff(
installed = self.run_pouch(
"add",
"solo",
"-g",
@@ -122,10 +122,10 @@ class CustomSourceTests(unittest.TestCase):
expected = write_skill(company / "skills", "code-review")
project = self.home / "project"
project.mkdir()
added = self.run_skiff("source", "add", "company", "--local", str(company))
added = self.run_pouch("source", "add", "company", "--local", str(company))
self.assertEqual(added.returncode, 0, added.stderr)
result = self.run_skiff(
result = self.run_pouch(
"add",
"company/code-review",
"--project",
@@ -135,7 +135,7 @@ class CustomSourceTests(unittest.TestCase):
)
self.assertEqual(result.returncode, 0, result.stderr)
manifest = project.joinpath(".skills.yaml").read_text(encoding="utf-8")
manifest = project.joinpath(".pouch.yaml").read_text(encoding="utf-8")
self.assertIn('name: "code-review"', manifest)
self.assertIn("source: company", manifest)
self.assertEqual(
@@ -149,14 +149,14 @@ class CustomSourceTests(unittest.TestCase):
write_skill(company / "skills", "code-review")
project = self.home / "project"
project.mkdir()
project.joinpath(".skills.yaml").write_text(
project.joinpath(".pouch.yaml").write_text(
"skills:\n - code-review\n",
encoding="utf-8",
)
added = self.run_skiff("source", "add", "company", "--local", str(company))
added = self.run_pouch("source", "add", "company", "--local", str(company))
self.assertEqual(added.returncode, 0, added.stderr)
result = self.run_skiff(
result = self.run_pouch(
"add",
"company/code-review",
"--project",
@@ -166,17 +166,17 @@ class CustomSourceTests(unittest.TestCase):
)
self.assertEqual(result.returncode, 0, result.stderr)
manifest = project.joinpath(".skills.yaml").read_text(encoding="utf-8")
manifest = project.joinpath(".pouch.yaml").read_text(encoding="utf-8")
self.assertIn("source: company", manifest)
def test_unqualified_duplicate_requires_explicit_source(self) -> None:
write_skill(self.skills_home / "skills", "code-review")
company = self.home / "company"
write_skill(company / "skills", "code-review")
added = self.run_skiff("source", "add", "company", "--local", str(company))
added = self.run_pouch("source", "add", "company", "--local", str(company))
self.assertEqual(added.returncode, 0, added.stderr)
result = self.run_skiff("add", "code-review", "-g", "-a", "codex")
result = self.run_pouch("add", "code-review", "-g", "-a", "codex")
self.assertNotEqual(result.returncode, 0)
self.assertIn("builtin/code-review", result.stderr)
@@ -194,9 +194,9 @@ class CustomSourceTests(unittest.TestCase):
" path: skills\n",
encoding="utf-8",
)
added = self.run_skiff("source", "add", "company", "--local", str(company))
added = self.run_pouch("source", "add", "company", "--local", str(company))
result = self.run_skiff(
result = self.run_pouch(
"add",
"company/code-review",
"-g",
@@ -216,15 +216,15 @@ class CustomSourceTests(unittest.TestCase):
expected = write_skill(company / "skills", "code-review")
project = self.home / "project"
project.mkdir()
project.joinpath(".skills.yaml").write_text(
project.joinpath(".pouch.yaml").write_text(
"skills:\n - name: code-review\n source: company\n"
"targets:\n - codex\n",
encoding="utf-8",
)
added = self.run_skiff("source", "add", "company", "--local", str(company))
added = self.run_pouch("source", "add", "company", "--local", str(company))
self.assertEqual(added.returncode, 0, added.stderr)
result = self.run_skiff("sync", "--project", str(project), "-a", "codex")
result = self.run_pouch("sync", "--project", str(project), "-a", "codex")
self.assertEqual(result.returncode, 0, result.stderr)
link = project / ".agents" / "skills" / "code-review"
@@ -233,16 +233,16 @@ class CustomSourceTests(unittest.TestCase):
def test_sync_reports_missing_machine_source(self) -> None:
project = self.home / "project"
project.mkdir()
project.joinpath(".skills.yaml").write_text(
project.joinpath(".pouch.yaml").write_text(
"skills:\n - name: code-review\n source: company\n",
encoding="utf-8",
)
result = self.run_skiff("sync", "--project", str(project), "-a", "codex")
result = self.run_pouch("sync", "--project", str(project), "-a", "codex")
self.assertNotEqual(result.returncode, 0)
self.assertIn("本机尚未配置", result.stderr)
self.assertIn("skiff source add company", result.stderr)
self.assertIn("pouch source add company", result.stderr)
def test_git_source_is_cloned_to_default_checkout(self) -> None:
upstream = self.home / "upstream"
@@ -266,10 +266,10 @@ class CustomSourceTests(unittest.TestCase):
capture_output=True,
)
result = self.run_skiff("source", "add", "company", str(upstream))
result = self.run_pouch("source", "add", "company", str(upstream))
self.assertEqual(result.returncode, 0, result.stderr)
checkout = self.home / ".local" / "share" / "skiff" / "sources" / "company"
checkout = self.home / ".local" / "share" / "pouch" / "sources" / "company"
self.assertTrue((checkout / ".git").is_dir())
self.assertTrue((checkout / "skills" / "release-check" / "SKILL.md").is_file())
@@ -281,7 +281,7 @@ class CustomSourceTests(unittest.TestCase):
"checkout": str(checkout),
}
with patch("skiff.sources.subprocess.run") as run:
with patch("pouch.sources.subprocess.run") as run:
fetch_source("company", entry)
run.assert_called_once_with(
+8 -8
View File
@@ -11,10 +11,10 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
class InstallScriptTests(unittest.TestCase):
def test_install_bootstraps_skiff_skill_for_all_agents(self) -> None:
def test_install_bootstraps_pouch_skill_for_all_agents(self) -> None:
with tempfile.TemporaryDirectory() as temp:
home = Path(temp)
(home / ".skills").symlink_to(REPO_ROOT, target_is_directory=True)
(home / ".pouch").symlink_to(REPO_ROOT, target_is_directory=True)
env = os.environ.copy()
env["HOME"] = str(home)
@@ -29,15 +29,15 @@ class InstallScriptTests(unittest.TestCase):
self.assertEqual(result.returncode, 0, result.stderr)
for relative in (
".cursor/skills/skiff",
".claude/skills/skiff",
".codex/skills/skiff",
".agents/skills/skiff",
".cursor/skills/pouch",
".claude/skills/pouch",
".codex/skills/pouch",
".agents/skills/pouch",
):
link = home / relative
self.assertTrue(link.is_symlink(), relative)
self.assertEqual(link.resolve(), (REPO_ROOT / "skills" / "skiff").resolve())
self.assertIn("已全局安装 builtin skiff skill", result.stdout)
self.assertEqual(link.resolve(), (REPO_ROOT / "skills" / "pouch").resolve())
self.assertIn("已全局安装 builtin pouch skill", result.stdout)
if __name__ == "__main__":
+6 -6
View File
@@ -8,18 +8,18 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
class ProjectSkillContentTests(unittest.TestCase):
def test_skiff_skill_documents_project_usage_and_feedback_loop(self) -> None:
content = (REPO_ROOT / "skills" / "skiff" / "SKILL.md").read_text(encoding="utf-8")
def test_pouch_skill_documents_project_usage_and_feedback_loop(self) -> None:
content = (REPO_ROOT / "skills" / "pouch" / "SKILL.md").read_text(encoding="utf-8")
for expected in (
"skiff add <name>",
"pouch add <name>",
"问题或优化回流",
"实际结果",
"期望结果",
"skiff check <name>",
"~/.skills/skills/<name>/",
"pouch check <name>",
"~/.pouch/skills/<name>/",
"第三方 skill",
"skiff init ack",
"pouch init ack",
"显式调用全局 `/ack` skill",
):
self.assertIn(expected, content)
+16 -16
View File
@@ -10,10 +10,10 @@ import unittest
from pathlib import Path
from unittest.mock import Mock, patch
from skiff import cli
from skiff import yaml_io
from skiff.catalog import catalog_repo_path, catalog_skill_path
from skiff.selector import SkillChoice, filter_choices, fit_to_width, select_skills
from pouch import cli
from pouch import yaml_io
from pouch.catalog import catalog_repo_path, catalog_skill_path
from pouch.selector import SkillChoice, filter_choices, fit_to_width, select_skills
REPO_ROOT = Path(__file__).resolve().parents[1]
@@ -210,8 +210,8 @@ class SelectorTests(unittest.TestCase):
"path": "skills",
}
with patch("skiff.catalog.catalog_checkout_path", return_value=checkout):
from skiff.catalog import discover_catalog_skills
with patch("pouch.catalog.catalog_checkout_path", return_value=checkout):
from pouch.catalog import discover_catalog_skills
self.assertEqual(discover_catalog_skills("unsafe", entry), {})
@@ -257,13 +257,13 @@ class SelectCommandTests(unittest.TestCase):
def test_non_tty_exits_with_add_guidance(self) -> None:
with tempfile.TemporaryDirectory() as temp:
home = Path(temp)
(home / ".skills" / "skills").mkdir(parents=True)
(home / ".pouch" / "skills").mkdir(parents=True)
env = os.environ.copy()
env["HOME"] = str(home)
env["PYTHONPATH"] = str(REPO_ROOT)
result = subprocess.run(
[sys.executable, "-m", "skiff", "select"],
[sys.executable, "-m", "pouch", "select"],
cwd=REPO_ROOT,
env=env,
text=True,
@@ -272,7 +272,7 @@ class SelectCommandTests(unittest.TestCase):
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("skiff add <name>", result.stderr)
self.assertIn("pouch add <name>", result.stderr)
def test_project_selection_installs_new_and_records_all_selected(self) -> None:
with tempfile.TemporaryDirectory() as temp:
@@ -306,7 +306,7 @@ class SelectCommandTests(unittest.TestCase):
with (
patch.object(cli.sys, "stdin", stdin),
patch.object(cli.sys, "stdout", stdout),
patch.object(cli, "ensure_skills_home"),
patch.object(cli, "ensure_pouch_home"),
patch.object(cli, "list_builtin_skills", return_value=["builtin-one"]),
patch.object(cli, "skill_description", return_value="builtin"),
patch.object(
@@ -346,7 +346,7 @@ class SelectCommandTests(unittest.TestCase):
):
cli.cmd_select(args)
manifest = (project / ".skills.yaml").read_text(encoding="utf-8")
manifest = (project / ".pouch.yaml").read_text(encoding="utf-8")
self.assertEqual(installed, ["catalog-one"])
self.assertIn("builtin-one", manifest)
@@ -405,7 +405,7 @@ class SelectCommandTests(unittest.TestCase):
with (
patch.object(cli.sys, "stdin", stdin),
patch.object(cli.sys, "stdout", stdout),
patch.object(cli, "ensure_skills_home"),
patch.object(cli, "ensure_pouch_home"),
patch.object(cli, "list_builtin_skills", return_value=[]),
patch.object(
cli,
@@ -440,7 +440,7 @@ class SelectCommandTests(unittest.TestCase):
with (
patch.object(cli.sys, "stdin", stdin),
patch.object(cli.sys, "stdout", stdout),
patch.object(cli, "ensure_skills_home"),
patch.object(cli, "ensure_pouch_home"),
patch.object(cli, "list_builtin_skills", return_value=[]),
patch.object(
cli,
@@ -506,7 +506,7 @@ class SelectCommandTests(unittest.TestCase):
with (
patch.object(cli.sys, "stdin", stdin),
patch.object(cli.sys, "stdout", stdout),
patch.object(cli, "ensure_skills_home"),
patch.object(cli, "ensure_pouch_home"),
patch.object(cli, "list_builtin_skills", return_value=[]),
patch.object(cli, "load_catalog", return_value={}),
patch.object(
@@ -567,7 +567,7 @@ class SelectCommandTests(unittest.TestCase):
patch.object(cli.sys, "stdin", stdin),
patch.object(cli.sys, "stdout", stdout),
patch.object(cli, "SKILLS_DIR", skills_root),
patch.object(cli, "ensure_skills_home"),
patch.object(cli, "ensure_pouch_home"),
patch.object(cli, "list_builtin_skills", return_value=["ack"]),
patch.object(cli, "skill_description", return_value="builtin ack"),
patch.object(
@@ -575,7 +575,7 @@ class SelectCommandTests(unittest.TestCase):
"load_catalog",
return_value={
"skills": {
"repo": "~/.skills",
"repo": "~/.pouch",
"ref": "main",
"path": "skills",
}
+127 -127
View File
@@ -9,7 +9,7 @@ from pathlib import Path
from types import SimpleNamespace
from unittest import mock
import skiff.cli
import pouch.cli
REPO_ROOT = Path(__file__).resolve().parents[1]
@@ -18,7 +18,7 @@ class SkillInitTests(unittest.TestCase):
def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory()
self.home = Path(self.temp_dir.name)
self.skills_home = self.home / ".skills"
self.skills_home = self.home / ".pouch"
skill = self.skills_home / "skills" / "ack"
(skill / "templates").mkdir(parents=True)
(skill / "scripts").mkdir()
@@ -40,7 +40,7 @@ class SkillInitTests(unittest.TestCase):
tasks_template = skill / "templates" / "tasks.template.yaml"
tasks_template.write_text(
tasks_template.read_text(encoding="utf-8")
+ ' deliveryFile: "docs/ack/delivery.yaml"\n'
+ ' deliveryFile: ".pouch/ack/delivery.yaml"\n'
+ 'deliveryRuns: []\n',
encoding="utf-8",
)
@@ -78,12 +78,12 @@ class SkillInitTests(unittest.TestCase):
def tearDown(self) -> None:
self.temp_dir.cleanup()
def run_skiff(self, *args: str) -> subprocess.CompletedProcess[str]:
def run_pouch(self, *args: str) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["HOME"] = str(self.home)
env["PYTHONPATH"] = str(REPO_ROOT)
return subprocess.run(
[sys.executable, "-m", "skiff", *args],
[sys.executable, "-m", "pouch", *args],
cwd=REPO_ROOT,
env=env,
text=True,
@@ -95,10 +95,10 @@ class SkillInitTests(unittest.TestCase):
project = self.home / "sample-app"
project.mkdir()
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertEqual(result.returncode, 0, result.stderr)
target = project / "docs" / "ack"
target = project / ".pouch" / "ack"
self.assertFalse((target / "kit").exists())
self.assertFalse((target / "framework").exists())
project_content = (target / "project.md").read_text(encoding="utf-8")
@@ -117,12 +117,12 @@ class SkillInitTests(unittest.TestCase):
def test_init_refuses_to_overwrite_existing_files(self) -> None:
project = self.home / "existing-app"
target = project / "docs" / "ack"
target = project / ".pouch" / "ack"
target.mkdir(parents=True)
existing = target / "project.md"
existing.write_text("keep me", encoding="utf-8")
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertIn("拒绝覆盖已有路径", result.stderr)
@@ -133,12 +133,12 @@ class SkillInitTests(unittest.TestCase):
def test_init_refuses_to_overwrite_existing_knowledge_file(self) -> None:
project = self.home / "existing-knowledge-app"
target = project / "docs" / "ack"
target = project / ".pouch" / "ack"
target.mkdir(parents=True)
existing = target / "knowledge.yaml"
existing.write_text("keep me", encoding="utf-8")
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertIn("拒绝覆盖已有路径", result.stderr)
@@ -148,12 +148,12 @@ class SkillInitTests(unittest.TestCase):
def test_init_refuses_to_overwrite_existing_delivery_file(self) -> None:
project = self.home / "existing-delivery-app"
target = project / "docs" / "ack"
target = project / ".pouch" / "ack"
target.mkdir(parents=True)
existing = target / "delivery.yaml"
existing.write_text("keep me", encoding="utf-8")
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertIn("拒绝覆盖已有路径", result.stderr)
@@ -163,25 +163,25 @@ class SkillInitTests(unittest.TestCase):
self.assertFalse((target / "knowledge.yaml").exists())
def test_init_rejects_symlinked_destination_directories(self) -> None:
for symlink_level in ("docs", "ack"):
for symlink_level in (".pouch", "ack"):
with self.subTest(symlink_level=symlink_level):
project = self.home / f"symlink-{symlink_level}-app"
outside = self.home / f"symlink-{symlink_level}-outside"
project.mkdir()
outside.mkdir()
if symlink_level == "docs":
(project / "docs").symlink_to(
if symlink_level == ".pouch":
(project / ".pouch").symlink_to(
outside,
target_is_directory=True,
)
else:
(project / "docs").mkdir()
(project / "docs" / "ack").symlink_to(
(project / ".pouch").mkdir()
(project / ".pouch" / "ack").symlink_to(
outside,
target_is_directory=True,
)
result = self.run_skiff(
result = self.run_pouch(
"init",
"ack",
"--project",
@@ -202,7 +202,7 @@ class SkillInitTests(unittest.TestCase):
outside.mkdir()
(project / "skills").symlink_to(outside, target_is_directory=True)
result = self.run_skiff(
result = self.run_pouch(
"init",
"../skills/ack",
"--project",
@@ -218,34 +218,34 @@ class SkillInitTests(unittest.TestCase):
project.mkdir()
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli,
pouch.cli,
"_rename_directory_noreplace",
side_effect=RuntimeError("publish interrupted"),
),
):
with self.assertRaisesRegex(RuntimeError, "publish interrupted"):
skiff.cli.cmd_init(
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
self.assertFalse((project / "docs" / "ack").exists())
docs = project / "docs"
if docs.exists():
self.assertEqual(list(docs.iterdir()), [])
self.assertFalse((project / ".pouch" / "ack").exists())
pouch_dir = project / ".pouch"
if pouch_dir.exists():
self.assertEqual(list(pouch_dir.iterdir()), [])
def test_atomic_publish_never_replaces_a_raced_destination(self) -> None:
project = self.home / "atomic-no-replace-app"
project.mkdir()
raced_inode: int | None = None
real_publish = skiff.cli._rename_directory_noreplace
real_publish = pouch.cli._rename_directory_noreplace
def create_destination_then_publish(
source_parent_fd: int,
@@ -268,25 +268,25 @@ class SkillInitTests(unittest.TestCase):
)
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli,
pouch.cli,
"_rename_directory_noreplace",
side_effect=create_destination_then_publish,
),
):
with self.assertRaisesRegex(SystemExit, "拒绝覆盖已有路径"):
skiff.cli.cmd_init(
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
target = project / "docs" / "ack"
target = project / ".pouch" / "ack"
self.assertTrue(target.is_dir())
self.assertEqual(target.stat().st_ino, raced_inode)
self.assertEqual(list(target.iterdir()), [])
@@ -294,9 +294,9 @@ class SkillInitTests(unittest.TestCase):
def test_published_destination_replacement_never_reports_success(self) -> None:
project = self.home / "published-destination-app"
moved_target = project / "docs" / "ack-moved"
moved_target = project / ".pouch" / "ack-moved"
project.mkdir()
real_publish = skiff.cli._rename_directory_noreplace
real_publish = pouch.cli._rename_directory_noreplace
def replace_destination_after_publish(
source_parent_fd: int,
@@ -310,30 +310,30 @@ class SkillInitTests(unittest.TestCase):
destination_parent_fd,
destination_name,
)
target = project / "docs" / destination_name
target = project / ".pouch" / destination_name
target.rename(moved_target)
target.mkdir()
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli,
pouch.cli,
"_rename_directory_noreplace",
side_effect=replace_destination_after_publish,
),
):
with self.assertRaisesRegex(SystemExit, "ACK 目录已被替换"):
skiff.cli.cmd_init(
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
self.assertEqual(list((project / "docs" / "ack").iterdir()), [])
self.assertEqual(list((project / ".pouch" / "ack").iterdir()), [])
self.assertEqual(
sorted(path.name for path in moved_target.iterdir()),
["delivery.yaml", "knowledge.yaml", "project.md", "tasks.yaml"],
@@ -345,7 +345,7 @@ class SkillInitTests(unittest.TestCase):
project.mkdir()
attacker.mkdir()
(attacker / "marker").write_text("forged", encoding="utf-8")
real_publish = skiff.cli._rename_directory_noreplace
real_publish = pouch.cli._rename_directory_noreplace
def replace_outer_transaction_then_publish(
source_parent_fd: int,
@@ -353,15 +353,15 @@ class SkillInitTests(unittest.TestCase):
destination_parent_fd: int,
destination_name: str,
) -> None:
docs = project / "docs"
pouch_dir = project / ".pouch"
transactions = [
path
for path in docs.iterdir()
for path in pouch_dir.iterdir()
if path.name.startswith(".ack-init-")
]
self.assertEqual(len(transactions), 1)
transaction = transactions[0]
saved = docs / f"{transaction.name}.saved"
saved = pouch_dir / f"{transaction.name}.saved"
transaction.rename(saved)
transaction.symlink_to(attacker, target_is_directory=True)
real_publish(
@@ -372,24 +372,24 @@ class SkillInitTests(unittest.TestCase):
)
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli,
pouch.cli,
"_rename_directory_noreplace",
side_effect=replace_outer_transaction_then_publish,
),
):
skiff.cli.cmd_init(
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
target = project / "docs" / "ack"
target = project / ".pouch" / "ack"
self.assertTrue(target.is_dir())
self.assertFalse(target.is_symlink())
self.assertFalse((target / "marker").exists())
@@ -412,25 +412,25 @@ class SkillInitTests(unittest.TestCase):
real_fsync(file_descriptor)
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli.os,
pouch.cli.os,
"fsync",
side_effect=fail_directory_fsync_after_publish,
),
):
with self.assertRaisesRegex(SystemExit, "已完整发布"):
skiff.cli.cmd_init(
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
target = project / "docs" / "ack"
target = project / ".pouch" / "ack"
self.assertEqual(
sorted(path.name for path in target.iterdir()),
["delivery.yaml", "knowledge.yaml", "project.md", "tasks.yaml"],
@@ -440,7 +440,7 @@ class SkillInitTests(unittest.TestCase):
project = self.home / "root-replacement-app"
moved_project = self.home / "root-replacement-moved"
project.mkdir()
real_open_docs = skiff.cli._open_or_create_directory_at
real_open_docs = pouch.cli._open_or_create_directory_at
replaced = False
def replace_root_then_open_docs(
@@ -455,32 +455,32 @@ class SkillInitTests(unittest.TestCase):
return real_open_docs(parent_fd, name)
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli,
pouch.cli,
"_open_or_create_directory_at",
side_effect=replace_root_then_open_docs,
),
):
with self.assertRaisesRegex(SystemExit, "项目目录已被替换"):
skiff.cli.cmd_init(
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
self.assertFalse((project / "docs" / "ack").exists())
self.assertFalse((moved_project / "docs" / "ack").exists())
self.assertFalse((project / ".pouch" / "ack").exists())
self.assertFalse((moved_project / ".pouch" / "ack").exists())
def test_project_root_replacement_at_publish_never_reports_success(self) -> None:
project = self.home / "publish-root-replacement-app"
moved_project = self.home / "publish-root-replacement-moved"
project.mkdir()
real_publish = skiff.cli._rename_directory_noreplace
real_publish = pouch.cli._rename_directory_noreplace
def replace_root_then_publish(
source_parent_fd: int,
@@ -498,36 +498,36 @@ class SkillInitTests(unittest.TestCase):
)
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli,
pouch.cli,
"_rename_directory_noreplace",
side_effect=replace_root_then_publish,
),
):
with self.assertRaisesRegex(SystemExit, "ACK 目录已移动或不可访问"):
skiff.cli.cmd_init(
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
self.assertFalse((project / "docs" / "ack").exists())
target = moved_project / "docs" / "ack"
self.assertFalse((project / ".pouch" / "ack").exists())
target = moved_project / ".pouch" / "ack"
self.assertEqual(
sorted(path.name for path in target.iterdir()),
["delivery.yaml", "knowledge.yaml", "project.md", "tasks.yaml"],
)
def test_docs_replacement_aborts_before_publish(self) -> None:
project = self.home / "docs-replacement-app"
moved_docs = project / "docs-moved"
def test_pouch_dir_replacement_aborts_before_publish(self) -> None:
project = self.home / "pouch-dir-replacement-app"
moved_pouch = project / "pouch-dir-moved"
project.mkdir()
real_assert_binding = skiff.cli._assert_open_directory_path
real_assert_binding = pouch.cli._assert_open_directory_path
replaced = False
def replace_docs_at_publish_check(
@@ -538,9 +538,9 @@ class SkillInitTests(unittest.TestCase):
label: str = "项目目录",
) -> None:
nonlocal replaced
if label == "docs 目录" and phase == "发布" and not replaced:
(project / "docs").rename(moved_docs)
(project / "docs").mkdir()
if label == ".pouch 目录" and phase == "发布" and not replaced:
(project / ".pouch").rename(moved_pouch)
(project / ".pouch").mkdir()
replaced = True
real_assert_binding(
directory_fd,
@@ -550,32 +550,32 @@ class SkillInitTests(unittest.TestCase):
)
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli,
pouch.cli,
"_assert_open_directory_path",
side_effect=replace_docs_at_publish_check,
),
):
with self.assertRaisesRegex(SystemExit, "docs 目录已被替换"):
skiff.cli.cmd_init(
with self.assertRaisesRegex(SystemExit, "\\.pouch 目录已被替换"):
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
self.assertFalse((project / "docs" / "ack").exists())
self.assertFalse((moved_docs / "ack").exists())
self.assertFalse((project / ".pouch" / "ack").exists())
self.assertFalse((moved_pouch / "ack").exists())
def test_docs_replacement_at_publish_never_reports_success(self) -> None:
project = self.home / "publish-docs-replacement-app"
moved_docs = project / "docs-moved"
def test_pouch_dir_replacement_at_publish_never_reports_success(self) -> None:
project = self.home / "publish-pouch-dir-replacement-app"
moved_pouch = project / "pouch-dir-moved"
project.mkdir()
real_publish = skiff.cli._rename_directory_noreplace
real_publish = pouch.cli._rename_directory_noreplace
def replace_docs_then_publish(
source_parent_fd: int,
@@ -583,8 +583,8 @@ class SkillInitTests(unittest.TestCase):
destination_parent_fd: int,
destination_name: str,
) -> None:
(project / "docs").rename(moved_docs)
(project / "docs").mkdir()
(project / ".pouch").rename(moved_pouch)
(project / ".pouch").mkdir()
real_publish(
source_parent_fd,
source_name,
@@ -593,26 +593,26 @@ class SkillInitTests(unittest.TestCase):
)
with (
mock.patch.object(skiff.cli, "SKILLS_HOME", self.skills_home),
mock.patch.object(pouch.cli, "POUCH_HOME", self.skills_home),
mock.patch.object(
skiff.cli,
pouch.cli,
"SKILLS_DIR",
self.skills_home / "skills",
),
mock.patch.object(skiff.cli, "ensure_skills_home"),
mock.patch.object(pouch.cli, "ensure_pouch_home"),
mock.patch.object(
skiff.cli,
pouch.cli,
"_rename_directory_noreplace",
side_effect=replace_docs_then_publish,
),
):
with self.assertRaisesRegex(SystemExit, "ACK 目录已移动或不可访问"):
skiff.cli.cmd_init(
pouch.cli.cmd_init(
SimpleNamespace(name="ack", project=str(project))
)
self.assertFalse((project / "docs" / "ack").exists())
target = moved_docs / "ack"
self.assertFalse((project / ".pouch" / "ack").exists())
target = moved_pouch / "ack"
self.assertEqual(
sorted(path.name for path in target.iterdir()),
["delivery.yaml", "knowledge.yaml", "project.md", "tasks.yaml"],
@@ -629,11 +629,11 @@ class SkillInitTests(unittest.TestCase):
/ "knowledge.template.yaml"
).unlink()
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertIn("knowledge.template.yaml", result.stderr)
self.assertFalse((project / "docs" / "ack").exists())
self.assertFalse((project / ".pouch" / "ack").exists())
def test_ack_init_requires_delivery_template(self) -> None:
project = self.home / "missing-delivery-template-app"
@@ -646,11 +646,11 @@ class SkillInitTests(unittest.TestCase):
/ "delivery.template.yaml"
).unlink()
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertIn("delivery.template.yaml", result.stderr)
self.assertFalse((project / "docs" / "ack").exists())
self.assertFalse((project / ".pouch" / "ack").exists())
def test_ack_init_requires_all_validators(self) -> None:
for validator_name in (
@@ -671,7 +671,7 @@ class SkillInitTests(unittest.TestCase):
original = validator.read_text(encoding="utf-8")
validator.unlink()
try:
result = self.run_skiff(
result = self.run_pouch(
"init",
"ack",
"--project",
@@ -683,7 +683,7 @@ class SkillInitTests(unittest.TestCase):
self.assertNotEqual(result.returncode, 0)
self.assertIn("缺少初始化校验器", result.stderr)
self.assertIn(validator_name, result.stderr)
self.assertFalse((project / "docs" / "ack").exists())
self.assertFalse((project / ".pouch" / "ack").exists())
def test_validator_failure_leaves_no_partial_initialization(self) -> None:
project = self.home / "invalid-knowledge-app"
@@ -693,12 +693,12 @@ class SkillInitTests(unittest.TestCase):
)
validator.write_text("raise SystemExit(1)\n", encoding="utf-8")
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertNotIn("Traceback", result.stderr)
self.assertIn("初始化知识库校验失败", result.stderr)
self.assertFalse((project / "docs" / "ack").exists())
self.assertFalse((project / ".pouch" / "ack").exists())
def test_delivery_validator_failure_leaves_no_partial_initialization(self) -> None:
project = self.home / "invalid-delivery-app"
@@ -708,12 +708,12 @@ class SkillInitTests(unittest.TestCase):
)
validator.write_text("raise SystemExit(1)\n", encoding="utf-8")
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertNotIn("Traceback", result.stderr)
self.assertIn("初始化交付契约校验失败", result.stderr)
self.assertFalse((project / "docs" / "ack").exists())
self.assertFalse((project / ".pouch" / "ack").exists())
def test_validator_cannot_replace_staged_bytes_before_install(self) -> None:
project = self.home / "mutated-staging-app"
@@ -728,11 +728,11 @@ class SkillInitTests(unittest.TestCase):
encoding="utf-8",
)
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertIn("临时文件在校验期间发生变化", result.stderr)
self.assertFalse((project / "docs" / "ack").exists())
self.assertFalse((project / ".pouch" / "ack").exists())
def test_ack_init_validates_mirrored_staging_root(self) -> None:
project = self.home / "staged-knowledge-app"
@@ -749,16 +749,16 @@ class SkillInitTests(unittest.TestCase):
"root = Path(sys.argv[sys.argv.index('--project-root') + 1])\n"
"knowledge = Path(sys.argv[1])\n"
"tasks = Path(sys.argv[sys.argv.index('--tasks') + 1])\n"
"expected = root / 'docs' / 'ack'\n"
"expected = root / '.pouch' / 'ack'\n"
"raise SystemExit(0 if knowledge.parent == expected and "
"tasks.parent == expected else 4)\n",
encoding="utf-8",
)
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue((project / "docs" / "ack" / "knowledge.yaml").is_file())
self.assertTrue((project / ".pouch" / "ack" / "knowledge.yaml").is_file())
def test_ack_delivery_init_validates_mirrored_staging_root(self) -> None:
project = self.home / "staged-delivery-app"
@@ -775,16 +775,16 @@ class SkillInitTests(unittest.TestCase):
"root = Path(sys.argv[sys.argv.index('--project-root') + 1])\n"
"delivery = Path(sys.argv[1])\n"
"tasks = Path(sys.argv[sys.argv.index('--tasks') + 1])\n"
"expected = root / 'docs' / 'ack'\n"
"expected = root / '.pouch' / 'ack'\n"
"raise SystemExit(0 if delivery.parent == expected and "
"tasks.parent == expected else 4)\n",
encoding="utf-8",
)
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue((project / "docs" / "ack" / "delivery.yaml").is_file())
self.assertTrue((project / ".pouch" / "ack" / "delivery.yaml").is_file())
def test_non_ack_init_still_requires_only_project_and_tasks_templates(self) -> None:
skill = self.skills_home / "skills" / "plain"
@@ -799,10 +799,10 @@ class SkillInitTests(unittest.TestCase):
project = self.home / "plain-app"
project.mkdir()
result = self.run_skiff("init", "plain", "--project", str(project))
result = self.run_pouch("init", "plain", "--project", str(project))
self.assertEqual(result.returncode, 0, result.stderr)
target = project / "docs" / "plain"
target = project / ".pouch" / "plain"
self.assertTrue((target / "project.md").is_file())
self.assertTrue((target / "tasks.yaml").is_file())
self.assertFalse((target / "knowledge.yaml").exists())
@@ -811,14 +811,14 @@ class SkillInitTests(unittest.TestCase):
def test_init_rejects_missing_project_directory(self) -> None:
project = self.home / "missing-app"
result = self.run_skiff("init", "ack", "--project", str(project))
result = self.run_pouch("init", "ack", "--project", str(project))
self.assertNotEqual(result.returncode, 0)
self.assertIn("项目目录不存在", result.stderr)
self.assertFalse(project.exists())
def test_legacy_kit_command_is_not_exposed(self) -> None:
result = self.run_skiff("kit", "init", "ack")
result = self.run_pouch("kit", "init", "ack")
self.assertNotEqual(result.returncode, 0)
self.assertIn("invalid choice", result.stderr)