feat(skill-learning): merge plugin skill updates

This commit is contained in:
2026-06-16 11:55:55 +08:00
parent c9e6c37b5c
commit a34b1219bc
15 changed files with 860 additions and 5 deletions

View File

@ -8,6 +8,7 @@ from pathlib import Path
from beaver.skills.catalog.utils import strip_frontmatter
from beaver.skills.specs import SkillDraft, SkillReviewState, SkillSpec, SkillSpecStore, SkillStatus, SkillVersion
from beaver.skills.specs.serialization import canonical_hash, normalize_frontmatter, summarize_skill_content
from beaver.plugins.hashing import hash_plugin_skill_tree
class SkillPublisher:
@ -40,6 +41,7 @@ class SkillPublisher:
summary=summarize_skill_content(body),
tool_hints=self.store._extract_tool_hints(normalize_frontmatter(draft.proposed_frontmatter)),
provenance={
**dict(draft.provenance),
"draft_id": draft_id,
"proposal_kind": draft.proposal_kind,
"trigger_run_id": draft.trigger_run_id,
@ -47,7 +49,13 @@ class SkillPublisher:
},
)
self.store.write_skill_version(version, content)
self._copy_uploaded_supporting_files(draft, next_version)
if draft.proposal_kind == "plugin_skill_update":
self._copy_plugin_update_supporting_files(draft, next_version)
version_dir = self.store.root / draft.skill_name / "versions" / next_version
version.tree_hash = hash_plugin_skill_tree(version_dir).skill_tree_hash
self.store._write_json(version_dir / "version.json", version.to_dict())
else:
self._copy_uploaded_supporting_files(draft, next_version)
self.store.set_current_version(skill_name, next_version)
spec = self.store.get_skill_spec(skill_name)
@ -194,6 +202,25 @@ class SkillPublisher:
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, target)
def _copy_plugin_update_supporting_files(self, draft: SkillDraft, version: str) -> None:
plugin_id = str(draft.provenance.get("plugin_id") or "")
tree_hash = str(draft.provenance.get("new_upstream_tree_hash") or "")
if not plugin_id or not tree_hash:
raise ValueError("Plugin update draft is missing upstream provenance")
upstream = self.store.read_upstream_snapshot(draft.skill_name, plugin_id, tree_hash)
if upstream is None:
raise ValueError("Plugin update upstream snapshot is missing")
target_root = self.store.root / draft.skill_name / "versions" / version
for source in sorted(upstream.root.rglob("*"), key=lambda item: item.relative_to(upstream.root).as_posix()):
if not source.is_file() or source.is_symlink():
continue
relative = source.relative_to(upstream.root)
if relative.as_posix() in {"SKILL.md", "upstream.json", "version.json"}:
continue
target = target_root / relative
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, target)
def _require_draft(self, skill_name: str, draft_id: str) -> SkillDraft:
draft = self.store.read_draft(skill_name, draft_id)
if draft is None: