Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gen.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"source_commit": "cbacac03c4f1a2a98f74db669a6fb07c0c56598c"
"source_commit": "1a682947cba32cf352d0afab77f5f9bf49e0689a"
}
63 changes: 63 additions & 0 deletions scripts/build_claude_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Build a complete Claude plugin source snapshot."""

import argparse
import os
import shutil
import stat
import tempfile
from pathlib import Path

from skillsgen.generate import generate_all


# Universe excludes public release state from its source mirror. This SemVer
# marks local artifacts as unreleased without constraining marketplace releases.
UNRELEASED_PLUGIN_VERSION = "0.0.0-unreleased"


def _make_writable(root: Path) -> None:
"""Make the copied source tree writable for generation."""
root.chmod(root.stat().st_mode | stat.S_IWUSR)
for directory, directory_names, file_names in os.walk(root):
for name in directory_names + file_names:
path = Path(directory) / name
path.chmod(path.stat().st_mode | stat.S_IWUSR)


def build_claude_plugin(
source_root: Path,
output_directory: Path,
) -> None:
"""Generate and copy the complete Claude provider artifact."""
with tempfile.TemporaryDirectory(prefix="databricks-agent-skills-") as temporary:
working_root = Path(temporary) / "databricks-agent-skills"
shutil.copytree(source_root, working_root)
_make_writable(working_root)

generate_all(
working_root,
version_override=UNRELEASED_PLUGIN_VERSION,
)
generated_plugin = working_root / "plugins" / "databricks" / "claude"
if not generated_plugin.is_dir():
message = f"The generator did not produce {generated_plugin}."
raise RuntimeError(message)
if output_directory.exists():
shutil.rmtree(output_directory)
shutil.copytree(generated_plugin, output_directory)


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--source-marker", required=True)
parser.add_argument("--output", required=True)
args = parser.parse_args()

source_marker = Path(args.source_marker)
source_root = source_marker.parent.parent
build_claude_plugin(source_root, Path(args.output))


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions scripts/skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
SHARED_ASSETS,
STABLE_REPO_DIR,
EXPERIMENTAL_REPO_DIR,
UNPUBLISHED_FILENAMES,
is_publishable_path,
iter_skill_dirs,
iter_experimental_skill_dirs,
extract_version_from_skill,
Expand Down
9 changes: 3 additions & 6 deletions scripts/skillsgen/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

from skillsgen.commands import render_command_files
from skillsgen.common import BUNDLE_DIR, _serialize_plugin_json
from skillsgen.discovery import is_publishable_path
from skillsgen.plugins import (
_GENERATED_README,
build_claude_plugin,
Expand Down Expand Up @@ -56,12 +57,8 @@ def _provider_specs() -> dict:


def _is_noise(rel_parts: tuple) -> bool:
"""Files that must never ship (mirrors discovery.iter_skill_files)."""
if any(part.startswith(".") for part in rel_parts):
return True
if "__pycache__" in rel_parts:
return True
return any(part.endswith(".pyc") for part in rel_parts)
"""Return whether a source path must not ship."""
return not is_publishable_path(rel_parts)


def _iter_copy(repo_root: Path, src_dir: str):
Expand Down
33 changes: 24 additions & 9 deletions scripts/skillsgen/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@
STABLE_REPO_DIR = "skills"
EXPERIMENTAL_REPO_DIR = "experimental"

UNPUBLISHED_FILENAMES = frozenset(
{
"BUILD",
"BUILD.bazel",
"METADATA",
"OWNERS",
}
)


def is_publishable_path(relative_parts: tuple[str, ...]) -> bool:
"""Return whether a source path may appear in a published artifact."""
if any(part.startswith(".") for part in relative_parts):
return False
if "__pycache__" in relative_parts:
return False
if any(part.endswith(".pyc") for part in relative_parts):
return False
return not any(part in UNPUBLISHED_FILENAMES for part in relative_parts)


def iter_skill_dirs(repo_root: Path, parent: str = STABLE_REPO_DIR):
"""Yield skill directories under `parent` that contain SKILL.md."""
Expand Down Expand Up @@ -68,21 +88,16 @@ def extract_version_from_skill(skill_path: Path) -> str:


def iter_skill_files(skill_path: Path):
"""Yield tracked files in a skill directory, skipping VCS-ignored noise.
"""Yield files in a skill directory that may be published.

Filters out dot-prefixed paths (.DS_Store, .git, etc.), __pycache__
directories, and *.pyc files so manifest output stays reproducible
across machines.
This applies the same governance and generated-file exclusions as bundle
generation so the manifest and provider artifacts cannot diverge.
"""
for file_path in skill_path.rglob("*"):
if not file_path.is_file():
continue
rel_parts = file_path.relative_to(skill_path).parts
if any(part.startswith(".") for part in rel_parts):
continue
if "__pycache__" in rel_parts:
continue
if file_path.suffix == ".pyc":
if not is_publishable_path(rel_parts):
continue
yield file_path

Expand Down
22 changes: 21 additions & 1 deletion tests/skills_generator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ def test_copilot_bundle_uses_root_hooks_file(self):
self.assertTrue((cph / "hooks.json").exists())
self.assertFalse((cph / "hooks" / "hooks.json").exists())

def test_bundle_skips_vcs_noise(self):
def test_bundle_skips_unpublished_files(self):
with tempfile.TemporaryDirectory() as d:
root = Path(d)
for dd in self._SRC_DIRS:
Expand All @@ -405,10 +405,14 @@ def test_bundle_skips_vcs_noise(self):
noise.mkdir(parents=True, exist_ok=True)
(noise / "x.pyc").write_text("x")
(root / "skills" / "databricks-core" / ".DS_Store").write_text("x")
for filename in skills.UNPUBLISHED_FILENAMES:
(root / "skills" / "databricks-core" / filename).write_text("x")
skills.generate_bundle(root, self.meta)
seeded = root / "plugins/databricks/claude/skills/databricks-core"
self.assertFalse((seeded / ".DS_Store").exists())
self.assertFalse((seeded / "__pycache__").exists())
for filename in skills.UNPUBLISHED_FILENAMES:
self.assertFalse((seeded / filename).exists())


class ScopedSourcesTest(unittest.TestCase):
Expand Down Expand Up @@ -513,6 +517,22 @@ def test_generate_all_resyncs_stale_skill_asset(self):
skills.generate_all(root, version_override="9.9.9")
self.assertEqual(skills.check_codex_metadata(root), [])

def test_generate_all_excludes_unpublished_files(self):
with tempfile.TemporaryDirectory() as d:
root = self._seed(Path(d))
core = root / "skills/databricks-core"
for filename in skills.UNPUBLISHED_FILENAMES:
(core / filename).write_text("universe-only")

result = skills.generate_all(root, version_override="9.9.9")

core_manifest = result["manifest"]["skills"]["databricks-core"]
manifest_files = set(core_manifest["files"])
self.assertTrue(manifest_files.isdisjoint(skills.UNPUBLISHED_FILENAMES))
bundled_core = root / "plugins/databricks/claude/skills/databricks-core"
for filename in skills.UNPUBLISHED_FILENAMES:
self.assertFalse((bundled_core / filename).exists())


if __name__ == "__main__":
unittest.main()
Loading