Repository navigation
chore: sync shared skills d0d88cb31f35 #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,8 +6,8 @@ | |||||||||||||||
| whose generated SKILL.md is the canonical core plus that distribution's | ||||||||||||||||
| host-connection adapter. The ``plugins`` host is the multi-host monorepo | ||||||||||||||||
| (ditto-assistant/ditto-plugins): one shared skills folder per plugin serves | ||||||||||||||||
| Claude, Cursor, Grok, Codex, Copilot, Antigravity and Kimi manifests, so every | ||||||||||||||||
| versioned manifest in the plugin directory is bumped together. | ||||||||||||||||
| Claude, Cursor, Grok, Codex, Copilot, Antigravity, Kimi and Gemini CLI manifests, | ||||||||||||||||
| so every versioned manifest in the plugin directory is bumped together. | ||||||||||||||||
| """ | ||||||||||||||||
| import argparse | ||||||||||||||||
| import hashlib | ||||||||||||||||
|
|
@@ -40,9 +40,15 @@ | |||||||||||||||
| ".github/plugin/plugin.json", | ||||||||||||||||
| ".antigravity-plugin/plugin.json", | ||||||||||||||||
| ".kimi-plugin/plugin.json", | ||||||||||||||||
| "gemini-extension.json", | ||||||||||||||||
| ], | ||||||||||||||||
| } | ||||||||||||||||
| MANIFESTS = {host: f"plugins/{DEFAULT_PLUGIN}/{paths[0]}" for host, paths in PLUGIN_MANIFESTS.items()} | ||||||||||||||||
| # Repository-root host manifests that point into one plugin, for hosts that only | ||||||||||||||||
| # install from a repository root (Kimi Code reads <repo>/.kimi-plugin/plugin.json | ||||||||||||||||
| # when given a GitHub URL). A plugin opts in with "root_manifests" in plugins.json; | ||||||||||||||||
| # they are versioned and bumped with that plugin's own manifests. | ||||||||||||||||
| ROOT_MANIFESTS = {"plugins": {".kimi-plugin/plugin.json"}} | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · bug — Root Kimi manifest version drift passes release validation · 🔎 Verified by trace When a configured root manifest such as Why Ditto flagged this (confidence 86%)The new root-manifest support declares that root manifests are versioned with their plugin's manifests (lines 47–51), and Git file evidence: scripts/validate_package.py unchanged (e9fe12c4b9bc2f9055225f6116b60b7fdbb97e5b) from 752ea6d to d016195. This establishes repository state, not runtime resolution or generated-content correctness. 🤖 Prompt for AI agents
|
||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def digest(value): | ||||||||||||||||
|
|
@@ -109,6 +115,13 @@ def plugin_registry(source): | |||||||||||||||
| names = catalog_names(source, entry["catalog"]) | ||||||||||||||||
| if adapted is not None and names is not None and adapted not in names: | ||||||||||||||||
| raise ValueError("Adapted skill must belong to the plugin catalog") | ||||||||||||||||
| roots = entry.get("root_manifests", []) | ||||||||||||||||
| allowed = set().union(*ROOT_MANIFESTS.values()) | ||||||||||||||||
| if not isinstance(roots, list) or any(not isinstance(r, str) or r not in allowed for r in roots) or len(set(roots)) != len(roots): | ||||||||||||||||
| raise ValueError("Invalid plugin registry: root_manifests must list known repository-root host manifests") | ||||||||||||||||
| claimed = [r for entry in data.values() for r in entry.get("root_manifests", [])] | ||||||||||||||||
| if len(set(claimed)) != len(claimed): | ||||||||||||||||
| raise ValueError("Invalid plugin registry: a root manifest can point into only one plugin") | ||||||||||||||||
| return data | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -161,11 +174,19 @@ def additional_files(source, plugin=DEFAULT_PLUGIN, catalog="memory", adapted=DE | |||||||||||||||
| return result | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def bump_manifests(repo, host, plugin): | ||||||||||||||||
| files = [file_at(repo, f"plugins/{plugin}/{rel}") for rel in PLUGIN_MANIFESTS[host]] | ||||||||||||||||
| if not files[0].exists(): | ||||||||||||||||
| def host_manifests(repo, host, plugin, root_manifests=()): | ||||||||||||||||
| """Repository-relative versioned manifests of one plugin that exist in repo.""" | ||||||||||||||||
| if any(r not in ROOT_MANIFESTS.get(host, set()) for r in root_manifests): | ||||||||||||||||
| raise ValueError("Root manifests are not supported for this host") | ||||||||||||||||
| paths = [f"plugins/{plugin}/{rel}" for rel in PLUGIN_MANIFESTS[host]] + list(root_manifests) | ||||||||||||||||
| return [p for p in paths if file_at(repo, p).exists()] | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def bump_manifests(repo, host, plugin, root_manifests=()): | ||||||||||||||||
| if not file_at(repo, f"plugins/{plugin}/{PLUGIN_MANIFESTS[host][0]}").exists(): | ||||||||||||||||
| raise ValueError("Missing primary host manifest") | ||||||||||||||||
| present = [(f, json.loads(f.read_text())) for f in files if f.exists()] | ||||||||||||||||
| files = [file_at(repo, p) for p in host_manifests(repo, host, plugin, root_manifests)] | ||||||||||||||||
| present = [(f, json.loads(f.read_text())) for f in files] | ||||||||||||||||
| if any("version" not in m for _, m in present): | ||||||||||||||||
| raise ValueError("Every host manifest in one plugin must carry a version before bumping") | ||||||||||||||||
| versions = {m["version"] for _, m in present} | ||||||||||||||||
|
|
@@ -181,7 +202,7 @@ def bump_manifests(repo, host, plugin): | |||||||||||||||
| return [f.relative_to(Path(repo).resolve()).as_posix() for f, _ in present] | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def build(source, repo, host, bump_version=False, plugin=DEFAULT_PLUGIN, catalog="memory", adapted=DEFAULT_ADAPTED): | ||||||||||||||||
| def build(source, repo, host, bump_version=False, plugin=DEFAULT_PLUGIN, catalog="memory", adapted=DEFAULT_ADAPTED, root_manifests=()): | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · bug — Reject builds when a claimed root manifest is missing · 🔎 Verified by trace When
Suggested change
Why Ditto flagged this (confidence 84%)At line 182, 🤖 Prompt for AI agents
|
||||||||||||||||
| paths = layout(plugin, adapted) | ||||||||||||||||
| sha = source_identity(source) | ||||||||||||||||
| generator = file_at(source, GENERATOR).read_bytes() | ||||||||||||||||
|
|
@@ -213,7 +234,7 @@ def build(source, repo, host, bump_version=False, plugin=DEFAULT_PLUGIN, catalog | |||||||||||||||
| return False | ||||||||||||||||
| output_changed = skill is not None and (not file_at(repo, paths["output"]).exists() or file_at(repo, paths["output"]).read_bytes() != skill) | ||||||||||||||||
| if bump_version and (output_changed or skill is None and not old or old.get("files_sha256", {}) != extra_hashes): | ||||||||||||||||
| bump_manifests(repo, host, plugin) | ||||||||||||||||
| bump_manifests(repo, host, plugin, root_manifests) | ||||||||||||||||
| # A renamed or dropped adapted skill leaves its previous output behind; | ||||||||||||||||
| # old locks without the key used the default adapted skill. | ||||||||||||||||
| if old: | ||||||||||||||||
|
|
@@ -236,12 +257,20 @@ def build(source, repo, host, bump_version=False, plugin=DEFAULT_PLUGIN, catalog | |||||||||||||||
| def build_all(source, repo, host, bump_version=False): | ||||||||||||||||
| """Build every plugin in the source registry that the distribution hosts.""" | ||||||||||||||||
| changed = {} | ||||||||||||||||
| for plugin, entry in plugin_registry(source).items(): | ||||||||||||||||
| registry = plugin_registry(source) | ||||||||||||||||
| if host == "plugins": | ||||||||||||||||
| # A root manifest no plugin claims would silently keep its old version. | ||||||||||||||||
| claimed = {r for entry in registry.values() for r in entry.get("root_manifests", [])} | ||||||||||||||||
| for root in sorted(ROOT_MANIFESTS[host] - claimed): | ||||||||||||||||
| if file_at(repo, root).exists(): | ||||||||||||||||
| raise ValueError(f"{root} exists but no plugin in scripts/plugins.json lists it under root_manifests") | ||||||||||||||||
| for plugin, entry in registry.items(): | ||||||||||||||||
| if host != "plugins" and plugin != DEFAULT_PLUGIN: | ||||||||||||||||
| continue # single-host repositories carry only the original plugin | ||||||||||||||||
| if not file_at(repo, f"plugins/{plugin}/{PLUGIN_MANIFESTS[host][0]}").exists(): | ||||||||||||||||
| raise ValueError(f"Distribution lacks the {plugin} plugin manifest; scaffold it before generating") | ||||||||||||||||
| changed[plugin] = build(source, repo, host, bump_version, plugin, entry["catalog"], entry.get("adapted_skill")) | ||||||||||||||||
| roots = entry.get("root_manifests", []) if host == "plugins" else [] | ||||||||||||||||
| changed[plugin] = build(source, repo, host, bump_version, plugin, entry["catalog"], entry.get("adapted_skill"), roots) | ||||||||||||||||
| return changed | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium · bug — Gemini manifests are not accepted by the release validator · 🔎 Verified by trace
When a plugin adds the Gemini manifest registered here,
scripts/validate_package.pyrejects it as an unexpected release file. The validator also omits its version from the host-manifest consistency check, so the new manifest is not covered by the release validation contract.Why Ditto flagged this (confidence 91%)
Line 43 makes
host_manifestsandbump_manifeststreatgemini-extension.jsonas a versioned plugin manifest, but the unchanged release validator does not allow that file or include its version in consistency checks. Thus a release containing the newly supported manifest is rejected, while a divergent Gemini version is not checked.Git file evidence: scripts/validate_package.py unchanged (e9fe12c4b9bc2f9055225f6116b60b7fdbb97e5b) from 752ea6d to d016195. This establishes repository state, not runtime resolution or generated-content correctness.
🤖 Prompt for AI agents