feat: package vscode skill target

This commit is contained in:
yaojingang
2026-06-13 20:15:50 +08:00
parent f42bed2c98
commit 36dc46ab9b
55 changed files with 2586 additions and 284 deletions
+11 -3
View File
@@ -87,12 +87,14 @@ TARGET_TRANSFORMS: dict[str, dict[str, Any]] = {
"unsupported_features": [],
},
"vscode": {
"adapter_mode": "agent-skills-compatible-project-or-user-scope",
"generated_files": ["SKILL.md", "agents/interface.yaml"],
"adapter_mode": "vscode-agent-skills-adapter",
"generated_files": ["targets/vscode/adapter.json", "targets/vscode/README.md"],
"metadata_mapping": {
"name": "folder-name-and-SKILL.md::frontmatter.name",
"description": "SKILL.md::frontmatter.description",
"interface": "agents/interface.yaml",
"permissions": "targets/vscode/adapter.json::target_permission_contract",
"install_scope": "targets/vscode/README.md",
},
"preserved_semantics": ["trigger", "workflow", "resources", "eval-plan", "risk", "governance", "runtime", "trust", "permissions"],
"unsupported_features": ["VS Code installation scope is documented but not installed by this compiler"],
@@ -265,6 +267,12 @@ TARGET_PERMISSION_MODELS = {
"representation": "targets/generic/adapter.json::target_permission_contract",
"operator_note": "Generic target exposes permission metadata for downstream clients to enforce or review.",
},
"vscode": {
"model": "vscode-workspace-trust-plus-metadata",
"native_enforcement": False,
"representation": "targets/vscode/adapter.json::target_permission_contract and targets/vscode/README.md install notes",
"operator_note": "VS Code target relies on project or user skill installation plus VS Code workspace trust; Yao preserves permission metadata for reviewer and installer checks.",
},
}
@@ -545,7 +553,7 @@ def compile_target_contract(skill_dir: Path, target: str) -> dict[str, Any]:
"preserved_semantics": [],
"unsupported_features": [],
}
if target not in declared and not (target == "agent-skills" and "agent-skills-compatible" in declared):
if target not in declared and not (target in {"agent-skills", "vscode"} and "agent-skills-compatible" in declared):
warnings.append(f"Target is not declared in Skill IR or interface metadata: {target}")
if not sources["ir"]:
warnings.append("Skill IR is missing; compiler used frontmatter fallback")
+65 -5
View File
@@ -147,13 +147,17 @@ def build_semantic_contract(
"targets": target_values,
"source_files_count": count_list(ir, "source_files") if ir else 0,
}
alias_declared = (
platform in {"agent-skills", "vscode"} and "agent-skills-compatible" in target_values
)
semantic_parity = {
"source": "skill-ir" if ir else "frontmatter-fallback",
"ir_source": ir_source,
"name_matches_ir": bool(ir) and frontmatter_name == name,
"description_matches_ir": bool(ir) and frontmatter_description == description,
"platform_declared_in_ir": platform in target_values
or (platform == "generic" and "agent-skills-compatible" in target_values),
or (platform == "generic" and "agent-skills-compatible" in target_values)
or alias_declared,
"platform_declared_in_interface": platform in adapter_targets,
"display_name_present": bool(interface.get("display_name")),
"default_prompt_present": bool(interface.get("default_prompt")),
@@ -356,6 +360,44 @@ PLATFORM_CONTRACTS = {
"shell": "compatibility.execution.shell",
},
},
"vscode": {
"required_fields": [
"name",
"description",
"version",
"display_name",
"short_description",
"default_prompt",
"job_to_be_done",
"ir_source",
"ir_schema_version",
"semantic_contract",
"semantic_parity",
"compiler",
"compiled_contract",
"permission_contract",
"target_permission_contract",
"target_native_contract",
"target_transform",
"canonical_metadata",
"canonical_format",
"activation_mode",
"execution_context",
"shell",
"trust_level",
"remote_inline_execution",
"degradation_strategy",
"portability_profile",
],
"required_files": ["targets/vscode/adapter.json", "targets/vscode/README.md"],
"field_mapping": {
"name": "SKILL.md::frontmatter.name and folder name",
"description": "SKILL.md::frontmatter.description",
"display_name": "agents/interface.yaml::interface.display_name",
"execution_context": "compatibility.execution.context",
"permissions": "adapter.target_permission_contract",
},
},
}
EXCLUDED_ARCHIVE_PARTS = {".git", "__pycache__", ".venv", "venv", "node_modules", "dist"}
@@ -463,6 +505,24 @@ def write_adapter(skill_dir: Path, out_dir: Path, platform: str) -> Path:
encoding="utf-8",
)
payload["install_hint"] = f"Use the packaged skill directly; this target relies on SKILL.md and optional neutral metadata."
elif platform == "vscode":
notes = target_dir / "README.md"
native = payload["target_native_contract"]
notes.write_text(
f"# VS Code / Copilot Agent Skills Package\n\n"
f"Install `{skill_dir.name}` as a VS Code user or project scoped Agent Skill. Keep the folder name aligned with `SKILL.md` frontmatter name.\n\n"
f"Native surface: {native['native_surface']}.\n\n"
f"Activation: {native['activation']['policy']}\n\n"
f"Resources: {native['resources']['strategy']}\n\n"
f"Scripts: {native['scripts']['strategy']}\n\n"
f"Permission model: {payload['target_permission_contract']['permission_model']}. "
"Review `target_permission_contract`, workspace trust, and `reports/security_trust_report.md` before running scripts.\n\n"
"This adapter does not perform automatic VS Code installation; it preserves the reviewed source package plus install notes.\n",
encoding="utf-8",
)
payload["install_hint"] = (
"Install the package as a VS Code user or project scoped Agent Skill; use targets/vscode/README.md for scope and trust notes."
)
else:
payload["install_hint"] = f"Use {skill_dir.name} as an Agent Skills compatible package."
path = target_dir / "adapter.json"
@@ -511,9 +571,9 @@ def validate_exports(out_dir: Path, expectations: dict) -> dict:
required_targets = expectations.get("required_targets", [])
required_fields = expectations.get("required_fields", [])
required_by_target = {
"openai": expectations.get("openai_required_files", []),
"claude": expectations.get("claude_required_files", []),
"generic": expectations.get("generic_required_files", []),
key[: -len("_required_files")]: value
for key, value in expectations.items()
if key.endswith("_required_files")
}
for target in required_targets:
@@ -534,7 +594,7 @@ def validate_exports(out_dir: Path, expectations: dict) -> dict:
def main() -> None:
parser = argparse.ArgumentParser(description="Generate lightweight cross-platform packaging artifacts.")
parser.add_argument("skill_dir", help="Path to the skill directory")
parser.add_argument("--platform", action="append", default=[], help="Target platform: openai, claude, generic")
parser.add_argument("--platform", action="append", default=[], help="Target platform: openai, claude, generic, vscode")
parser.add_argument("--output-dir", default="dist", help="Output directory")
parser.add_argument("--expectations", help="JSON file describing packaging expectations")
parser.add_argument("--zip", action="store_true", help="Create a zip package")
+3
View File
@@ -129,6 +129,7 @@ compatibility:
- "openai"
- "claude"
- "generic"
- "vscode"
activation:
mode: "manual"
paths: []
@@ -143,6 +144,7 @@ compatibility:
openai: "metadata-adapter"
claude: "neutral-source-plus-adapter"
generic: "neutral-source"
vscode: "agent-skills-source-with-vscode-notes"
"""
@@ -213,6 +215,7 @@ def build_manifest(name: str, mode: str, archetype: str) -> dict:
"claude",
"generic",
"agent-skills-compatible",
"vscode",
],
"factory_components": [
"references",
+1 -1
View File
@@ -10,7 +10,7 @@ except ImportError: # pragma: no cover
yaml = None
DEFAULT_TARGETS = ["openai", "claude", "generic"]
DEFAULT_TARGETS = ["openai", "claude", "generic", "vscode"]
def display_path(path: Path, root: Path) -> str:
+7 -6
View File
@@ -108,12 +108,13 @@ def verify_package(
field in adapter,
f"{target} adapter includes field: {field}",
)
for target, key in (
("openai", "openai_required_files"),
("claude", "claude_required_files"),
("generic", "generic_required_files"),
):
for rel in expectations.get(key, []):
required_files_by_target = {
key[: -len("_required_files")]: value
for key, value in expectations.items()
if key.endswith("_required_files")
}
for target, required_files in required_files_by_target.items():
for rel in required_files:
add_check(checks, failures, f"{target}-file-{rel}", (package_dir / rel).exists(), f"Package contains {rel}")
archive_path = package_dir / f"{skill_dir.name}.zip"