fix: harden skill template contracts and scaffolding
All checks were successful
技能自动化发布 / release (push) Successful in 4s
All checks were successful
技能自动化发布 / release (push) Successful in 4s
Align scaffold slug replacement, Action manifest strict boundaries, metadata prune API, field permission validation, and task_logs readonly semantics with documented template contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,6 +15,9 @@ from util.constants import SKILL_SLUG
|
||||
_TEMPLATE_PLACEHOLDER_SLUG = "your-skill-slug"
|
||||
_ALLOWED_PLACEMENTS = frozenset({"toolbar", "row", "batch", "cron", "agent", "skill-detail"})
|
||||
_RESERVED_PLACEMENTS = frozenset({"row", "batch"})
|
||||
_RESERVED_ACTION_FIELDS = frozenset({"bind", "concurrency", "locks"})
|
||||
_REQUIRED_MANIFEST_KEYS = frozenset({"schemaVersion", "skill", "actions"})
|
||||
_REQUIRED_ACTION_KEYS = frozenset({"id", "label", "description", "placements", "entrypoint"})
|
||||
_FORBIDDEN_BUSINESS_TERMS = (
|
||||
"wechat",
|
||||
"微信",
|
||||
@@ -57,6 +60,12 @@ def _collect_template_vars(args: list) -> set[str]:
|
||||
return found
|
||||
|
||||
|
||||
def _load_actions_schema() -> dict:
|
||||
path = os.path.join(get_skill_root(), "assets", "schemas", "skill-actions.schema.json")
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
class TestActionsManifest(unittest.TestCase):
|
||||
def test_manifest_exists_and_schema(self) -> None:
|
||||
manifest = _load_actions_manifest()
|
||||
@@ -181,6 +190,48 @@ class TestActionsManifest(unittest.TestCase):
|
||||
self.assertEqual(schema.get("title"), "SkillActionManifest")
|
||||
self.assertIn("actions", schema.get("properties", {}))
|
||||
|
||||
def test_skill_actions_schema_json_parses(self) -> None:
|
||||
schema = _load_actions_schema()
|
||||
self.assertEqual(schema["properties"]["schemaVersion"]["const"], 1)
|
||||
self.assertTrue(schema.get("additionalProperties") is False)
|
||||
self.assertIn("action", schema.get("$defs", {}))
|
||||
|
||||
def test_template_manifest_matches_schema_required_keys(self) -> None:
|
||||
manifest = _load_actions_manifest()
|
||||
schema = _load_actions_schema()
|
||||
for key in _REQUIRED_MANIFEST_KEYS:
|
||||
self.assertIn(key, manifest)
|
||||
self.assertIn(key, schema.get("properties", {}))
|
||||
for action in manifest["actions"]:
|
||||
for key in _REQUIRED_ACTION_KEYS:
|
||||
self.assertIn(key, action)
|
||||
|
||||
def test_template_example_does_not_use_reserved_action_fields(self) -> None:
|
||||
manifest = _load_actions_manifest()
|
||||
for action in manifest["actions"]:
|
||||
overlap = set(action.keys()) & _RESERVED_ACTION_FIELDS
|
||||
self.assertFalse(
|
||||
overlap,
|
||||
msg=f"{action['id']} uses reserved action fields not supported in template Phase 1: {overlap}",
|
||||
)
|
||||
|
||||
def test_actions_md_documents_strict_vs_host_boundary(self) -> None:
|
||||
path = os.path.join(get_skill_root(), "references", "ACTIONS.md")
|
||||
with open(path, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
for marker in (
|
||||
"严格规范",
|
||||
"向后兼容",
|
||||
"Phase 1",
|
||||
"bind",
|
||||
"concurrency",
|
||||
"locks",
|
||||
"row",
|
||||
"batch",
|
||||
"additionalProperties",
|
||||
):
|
||||
self.assertIn(marker, text, msg=f"ACTIONS.md missing {marker!r}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user