Skip to content

Commit d8650b5

Browse files
committed
Merge remote-tracking branch 'origin/main' into fabian-integ
2 parents 493c0e4 + 7b398b3 commit d8650b5

4 files changed

Lines changed: 128 additions & 10 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Validate Manifest
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'skills/**'
7+
- 'scripts/generate_manifest.py'
8+
- 'manifest.json'
9+
push:
10+
branches:
11+
- main
12+
13+
jobs:
14+
validate:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: '3.11'
23+
24+
- name: Validate manifest is up to date
25+
run: python3 scripts/generate_manifest.py validate

‎README.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,42 @@ skill-name/
2626

2727
## Development
2828

29+
### Adding New Skills
30+
31+
When experimenting with new skill variations, create a "subskill" that references the main skill and adds specific guidance:
32+
33+
```markdown
34+
---
35+
name: "ai-databricks-apps"
36+
description: "Databricks apps with AI features"
37+
---
38+
39+
# AI powered Databricks Apps
40+
41+
First, load the base databricks-apps skill for foundational guidance.
42+
43+
Then apply these additional patterns:
44+
- Custom pattern 1
45+
- Custom pattern 2
46+
```
47+
48+
This approach:
49+
- Keeps the main skill stable and focused
50+
- Allows experimentation without modifying core skills
51+
- Makes it easy to follow the changes in the main skill
52+
53+
### Manifest Management
54+
2955
Generate manifest after adding/updating skills:
3056

3157
```bash
3258
python3 scripts/generate_manifest.py
3359
```
3460

61+
Validate that manifest is up to date (for CI):
62+
63+
```bash
64+
python3 scripts/generate_manifest.py validate
65+
```
66+
3567
The manifest is used by the CLI to discover available skills.

‎manifest.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"version": "1",
3-
"updated_at": "2026-01-22T14:04:56Z",
3+
"updated_at": "2026-01-23T14:54:09Z",
44
"skills": {
55
"databricks-apps": {
66
"version": "0.1.0",
7-
"updated_at": "2026-01-22T14:01:39Z"
7+
"updated_at": "2026-01-23T14:50:28Z"
88
}
99
}
1010
}

‎scripts/generate_manifest.py‎

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#!/usr/bin/env python3
2-
"""Generate manifest.json from skill directories."""
2+
"""Generate or validate manifest.json from skill directories."""
33

4+
import argparse
45
import json
56
import re
7+
import sys
68
from datetime import datetime, timezone
79
from pathlib import Path
810

@@ -54,7 +56,9 @@ def generate_manifest(repo_root: Path) -> dict:
5456
"""Generate manifest from skill directories."""
5557
skills = {}
5658

57-
for item in sorted(repo_root.iterdir()):
59+
skills_dir = repo_root / "skills"
60+
61+
for item in sorted(skills_dir.iterdir()):
5862
if not item.is_dir():
5963
continue
6064
if item.name.startswith(".") or item.name == "scripts":
@@ -76,15 +80,72 @@ def generate_manifest(repo_root: Path) -> dict:
7680
}
7781

7882

79-
def main() -> None:
80-
repo_root = Path(__file__).parent.parent
81-
manifest = generate_manifest(repo_root)
83+
def normalize_manifest(manifest: dict) -> dict:
84+
"""Normalize manifest for comparison by excluding updated_at timestamps."""
85+
normalized = manifest.copy()
86+
normalized.pop("updated_at", None)
87+
88+
skills = {}
89+
for name, skill in manifest.get("skills", {}).items():
90+
skill_copy = skill.copy()
91+
skill_copy.pop("updated_at", None)
92+
skills[name] = skill_copy
8293

94+
normalized["skills"] = skills
95+
return normalized
96+
97+
98+
def validate_manifest(repo_root: Path) -> bool:
99+
"""Validate that manifest.json is up to date. Returns True if valid."""
83100
manifest_path = repo_root / "manifest.json"
84-
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n")
85101

86-
print(f"Generated {manifest_path}")
87-
print(f"Found {len(manifest['skills'])} skill(s): {', '.join(manifest['skills'].keys())}")
102+
if not manifest_path.exists():
103+
print("ERROR: manifest.json does not exist", file=sys.stderr)
104+
return False
105+
106+
current_manifest = json.loads(manifest_path.read_text())
107+
expected_manifest = generate_manifest(repo_root)
108+
109+
# compare without timestamps
110+
current_normalized = normalize_manifest(current_manifest)
111+
expected_normalized = normalize_manifest(expected_manifest)
112+
113+
if current_normalized != expected_normalized:
114+
print("ERROR: manifest.json is out of date", file=sys.stderr)
115+
print("\nExpected:", file=sys.stderr)
116+
print(json.dumps(expected_normalized, indent=2), file=sys.stderr)
117+
print("\nActual:", file=sys.stderr)
118+
print(json.dumps(current_normalized, indent=2), file=sys.stderr)
119+
return False
120+
121+
print("manifest.json is up to date")
122+
return True
123+
124+
125+
def main() -> None:
126+
parser = argparse.ArgumentParser(description="Generate or validate manifest.json")
127+
parser.add_argument(
128+
"mode",
129+
nargs="?",
130+
default="generate",
131+
choices=["generate", "validate"],
132+
help="Mode: generate (creates manifest.json, default) or validate (checks if up to date)",
133+
)
134+
135+
args = parser.parse_args()
136+
repo_root = Path(__file__).parent.parent
137+
138+
match args.mode:
139+
case "generate":
140+
manifest = generate_manifest(repo_root)
141+
manifest_path = repo_root / "manifest.json"
142+
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n")
143+
print(f"Generated {manifest_path}")
144+
print(f"Found {len(manifest['skills'])} skill(s): {', '.join(manifest['skills'].keys())}")
145+
146+
case "validate":
147+
is_valid = validate_manifest(repo_root)
148+
sys.exit(0 if is_valid else 1)
88149

89150

90151
if __name__ == "__main__":

0 commit comments

Comments
 (0)