Skip to content

Commit e0cde0d

Browse files
author
Ramraj Bishnoie
committed
chore: converting claude code to agent skills
1 parent 3307342 commit e0cde0d

4 files changed

Lines changed: 116 additions & 91 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Get started in minutes:
5454
## How It Works
5555

5656
1. **Security rules** are written in unified markdown format (`sources/` directory)
57-
2. **Conversion tools** translate rules to IDE-specific formats (Cursor, Windsurf, Copilot, Claude Code, Antigravity)
57+
2. **Conversion tools** translate rules to IDE-specific formats (Cursor, Windsurf, Copilot, Agent Skills, Antigravity)
5858
3. **Release automation** packages rules into downloadable ZIP files
5959
4. **AI assistants** reference these rules when generating or reviewing code
6060
5. **Secure code** is produced automatically without developer intervention
@@ -63,7 +63,7 @@ Get started in minutes:
6363

6464
```
6565
sources/ # Source rules
66-
skills/ # Claude Code plugin (generated, committed)
66+
skills/ # Agent Skills format (generated, committed)
6767
src/ # Conversion and validation tools
6868
dist/ # Other IDE bundles (generated, not committed)
6969
```

src/convert_to_ide_formats.py

Lines changed: 76 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
Convert Unified Rules to IDE Formats
77
88
Transforms the unified markdown sources into IDE-specific bundles (Cursor,
9-
Windsurf, Copilot, Claude Code). This script is the main entry point for producing
10-
distributable rule packs from the sources/ directory.
9+
Windsurf, Copilot, Agent Skills, Antigravity). This script is the main entry point
10+
for producing distributable rule packs from the sources/ directory.
1111
"""
1212

1313
import re
@@ -16,7 +16,13 @@
1616
from collections import defaultdict
1717

1818
from converter import RuleConverter
19-
from formats import CursorFormat, WindsurfFormat, CopilotFormat, ClaudeCodeFormat, AntigravityFormat
19+
from formats import (
20+
CursorFormat,
21+
WindsurfFormat,
22+
CopilotFormat,
23+
AgentSkillsFormat,
24+
AntigravityFormat,
25+
)
2026
from utils import get_version_from_pyproject
2127
from validate_versions import set_plugin_version, set_marketplace_version
2228

@@ -26,7 +32,7 @@
2632

2733
def sync_plugin_metadata(version: str) -> None:
2834
"""
29-
Sync version from pyproject.toml to Claude Code plugin metadata files.
35+
Sync version from pyproject.toml to Agent Skills metadata files.
3036
3137
Args:
3238
version: Version string from pyproject.toml
@@ -39,17 +45,17 @@ def sync_plugin_metadata(version: str) -> None:
3945
def matches_tag_filter(rule_tags: list[str], filter_tags: list[str]) -> bool:
4046
"""
4147
Check if rule has all required tags (AND logic).
42-
48+
4349
Args:
4450
rule_tags: List of tags from the rule (already normalized to lowercase)
4551
filter_tags: List of tags to filter by (already normalized to lowercase)
46-
52+
4753
Returns:
4854
True if rule has all filter tags (or no filter), False otherwise
4955
"""
5056
if not filter_tags:
5157
return True # No filter means all pass
52-
58+
5359
return all(tag in rule_tags for tag in filter_tags)
5460

5561

@@ -98,14 +104,20 @@ def update_skill_md(language_to_rules: dict[str, list[str]], skill_path: str) ->
98104
print(f"Updated SKILL.md with language mappings")
99105

100106

101-
def convert_rules(input_path: str, output_dir: str = "dist", include_claudecode: bool = True, version: str = None, filter_tags: list[str] = None) -> dict[str, list[str]]:
107+
def convert_rules(
108+
input_path: str,
109+
output_dir: str = "dist",
110+
include_agentskills: bool = True,
111+
version: str = None,
112+
filter_tags: list[str] = None,
113+
) -> dict[str, list[str]]:
102114
"""
103115
Convert rule file(s) to all supported IDE formats using RuleConverter.
104116
105117
Args:
106118
input_path: Path to a single .md file or folder containing .md files
107119
output_dir: Output directory (default: 'dist/')
108-
include_claudecode: Whether to generate Claude Code plugin (default: True, only for core rules)
120+
include_agentskills: Whether to generate Agent Skills format (default: True, only for core rules)
109121
version: Version string to use (default: read from pyproject.toml)
110122
filter_tags: Optional list of tags to filter by (AND logic, case-insensitive)
111123
@@ -117,7 +129,7 @@ def convert_rules(input_path: str, output_dir: str = "dist", include_claudecode:
117129
}
118130
119131
Example:
120-
results = convert_rules("sources/core", "dist", include_claudecode=True)
132+
results = convert_rules("sources/core", "dist", include_agentskills=True)
121133
print(f"Converted {len(results['success'])} rules")
122134
"""
123135
if version is None:
@@ -130,10 +142,10 @@ def convert_rules(input_path: str, output_dir: str = "dist", include_claudecode:
130142
CopilotFormat(version),
131143
AntigravityFormat(version),
132144
]
133-
134-
# Only include Claude Code for core rules (committed plugin)
135-
if include_claudecode:
136-
all_formats.append(ClaudeCodeFormat(version))
145+
146+
# Only include Agent Skills format for core rules (committed as skills)
147+
if include_agentskills:
148+
all_formats.append(AgentSkillsFormat(version))
137149

138150
converter = RuleConverter(formats=all_formats)
139151
path = Path(input_path)
@@ -151,7 +163,7 @@ def convert_rules(input_path: str, output_dir: str = "dist", include_claudecode:
151163
md_files = sorted(list(path.rglob("*.md")))
152164
if not md_files:
153165
raise ValueError(f"No .md files found in {input_path}")
154-
166+
155167
print(f"Converting {len(md_files)} files from: {path}")
156168

157169
# Setup output directory
@@ -165,7 +177,7 @@ def convert_rules(input_path: str, output_dir: str = "dist", include_claudecode:
165177
try:
166178
# Convert the file (raises exceptions on error)
167179
result = converter.convert(md_file)
168-
180+
169181
# Apply tag filter if specified
170182
if filter_tags and not matches_tag_filter(result.tags, filter_tags):
171183
results["skipped"].append(result.filename)
@@ -175,17 +187,15 @@ def convert_rules(input_path: str, output_dir: str = "dist", include_claudecode:
175187
output_files = []
176188
for format_name, output in result.outputs.items():
177189
# Construct output path
178-
# Claude Code goes to project root ./skills/
190+
# Agent Skills goes to project root ./skills/
179191
# Other formats go to dist/ (or specified output_dir)
180-
if format_name == "claudecode":
192+
if format_name == "agentskills":
181193
base_dir = PROJECT_ROOT
182194
else:
183195
base_dir = output_base
184-
196+
185197
output_file = (
186-
base_dir
187-
/ output.subpath
188-
/ f"{result.basename}{output.extension}"
198+
base_dir / output.subpath / f"{result.basename}{output.extension}"
189199
)
190200

191201
# Create directory if it doesn't exist and write file
@@ -225,30 +235,32 @@ def convert_rules(input_path: str, output_dir: str = "dist", include_claudecode:
225235
f"\nResults: {len(results['success'])} success, {len(results['errors'])} errors"
226236
)
227237

228-
# Generate SKILL.md with language mappings (only if Claude Code is included)
229-
if include_claudecode and language_to_rules:
230-
template_path = PROJECT_ROOT / "sources" / "core" / "codeguard-SKILLS.md.template"
231-
238+
# Generate SKILL.md with language mappings (only if Agent Skills is included)
239+
if include_agentskills and language_to_rules:
240+
template_path = (
241+
PROJECT_ROOT / "sources" / "core" / "codeguard-SKILLS.md.template"
242+
)
243+
232244
if not template_path.exists():
233245
raise FileNotFoundError(
234246
f"SKILL.md template not found at {template_path}. "
235-
"This file is required for Claude Code plugin generation."
247+
"This file is required for Agent Skills generation."
236248
)
237-
249+
238250
output_skill_dir = PROJECT_ROOT / "skills" / "software-security"
239251
output_skill_dir.mkdir(parents=True, exist_ok=True)
240252
output_skill_path = output_skill_dir / "SKILL.md"
241-
253+
242254
# Read template and inject current version from pyproject.toml
243255
template_content = template_path.read_text(encoding="utf-8")
244256
# Replace the hardcoded version with actual version
245257
template_content = re.sub(
246258
r'codeguard-version:\s*"[^"]*"',
247259
f'codeguard-version: "{version}"',
248-
template_content
260+
template_content,
249261
)
250262
output_skill_path.write_text(template_content, encoding="utf-8")
251-
263+
252264
update_skill_md(language_to_rules, str(output_skill_path))
253265

254266
return results
@@ -262,15 +274,15 @@ def _resolve_source_paths(args) -> list[Path]:
262274
# If --source flags provided, resolve under sources/
263275
if args.source:
264276
return [Path("sources") / src for src in args.source]
265-
277+
266278
# Default: core rules only
267279
return [Path("sources/core")]
268280

269281

270282
if __name__ == "__main__":
271283
import sys
272284
from argparse import ArgumentParser
273-
285+
274286
parser = ArgumentParser(
275287
description="Convert unified rule markdown into IDE-specific bundles."
276288
)
@@ -291,7 +303,7 @@ def _resolve_source_paths(args) -> list[Path]:
291303
dest="tags",
292304
help="Filter rules by tags (comma-separated, case-insensitive, AND logic). Example: --tag api,web-security",
293305
)
294-
306+
295307
cli_args = parser.parse_args()
296308
source_paths = _resolve_source_paths(cli_args)
297309

@@ -307,27 +319,31 @@ def _resolve_source_paths(args) -> list[Path]:
307319
for source_path in source_paths:
308320
for md_file in source_path.rglob("*.md"):
309321
filename_to_sources[md_file.name].append(source_path.name)
310-
311-
duplicates = {name: srcs for name, srcs in filename_to_sources.items() if len(srcs) > 1}
322+
323+
duplicates = {
324+
name: srcs for name, srcs in filename_to_sources.items() if len(srcs) > 1
325+
}
312326
if duplicates:
313327
print(f"❌ Found {len(duplicates)} duplicate filename(s) across sources:")
314328
for filename, sources in duplicates.items():
315329
print(f" - {filename} in: {', '.join(sources)}")
316330
print("\nPlease rename files to have unique names across all sources.")
317331
sys.exit(1)
318-
332+
319333
# Get version once and sync to metadata files
320334
version = get_version_from_pyproject()
321335
sync_plugin_metadata(version)
322336

323-
# Check if core is in the sources for Claude Code plugin generation
337+
# Check if core is in the sources for Agent Skills generation
324338
has_core = Path("sources/core") in source_paths
325339
if has_core:
326340
# Validate template exists early
327-
template_path = PROJECT_ROOT / "sources" / "core" / "codeguard-SKILLS.md.template"
341+
template_path = (
342+
PROJECT_ROOT / "sources" / "core" / "codeguard-SKILLS.md.template"
343+
)
328344
if not template_path.exists():
329345
print(f"❌ SKILL.md template not found at {template_path}")
330-
print("This file is required for Claude Code plugin generation.")
346+
print("This file is required for Agent Skills generation.")
331347
sys.exit(1)
332348

333349
# Clean output directories once before processing
@@ -341,46 +357,50 @@ def _resolve_source_paths(args) -> list[Path]:
341357
if skills_rules_dir.exists():
342358
shutil.rmtree(skills_rules_dir)
343359
print(f"✅ Cleaned skills/ directory")
344-
360+
345361
# Print processing summary
346362
if len(source_paths) > 1:
347-
sources_list = ', '.join(p.name for p in source_paths)
363+
sources_list = ", ".join(p.name for p in source_paths)
348364
print(f"\nConverting {len(source_paths)} sources: {sources_list}")
349365
if has_core:
350-
print("(Claude Code plugin will include only core rules)")
366+
print("(Agent Skills will include only core rules)")
351367
print()
352-
368+
353369
# Convert all sources
354370
aggregated = {"success": [], "errors": [], "skipped": []}
355371
# Parse comma-separated tags and normalize to lowercase
356372
filter_tags = None
357373
if cli_args.tags:
358-
filter_tags = [tag.strip().lower() for tag in cli_args.tags.split(",") if tag.strip()]
359-
374+
filter_tags = [
375+
tag.strip().lower() for tag in cli_args.tags.split(",") if tag.strip()
376+
]
377+
360378
# Print tag filter info if active
361379
if filter_tags:
362-
print(f"Tag filter active: {', '.join(filter_tags)} (AND logic - rules must have all tags)\n")
363-
380+
print(
381+
f"Tag filter active: {', '.join(filter_tags)} (AND logic - rules must have all tags)\n"
382+
)
383+
364384
for source_path in source_paths:
365385
is_core = source_path == Path("sources/core")
366-
386+
367387
print(f"Processing: {source_path}")
368388
results = convert_rules(
369-
str(source_path),
370-
cli_args.output_dir,
371-
include_claudecode=is_core,
389+
str(source_path),
390+
cli_args.output_dir,
391+
include_agentskills=is_core,
372392
version=version,
373-
filter_tags=filter_tags
393+
filter_tags=filter_tags,
374394
)
375-
395+
376396
aggregated["success"].extend(results["success"])
377397
aggregated["errors"].extend(results["errors"])
378398
if "skipped" in results:
379399
aggregated["skipped"].extend(results["skipped"])
380400
print("")
381-
401+
382402
if aggregated["errors"]:
383403
print("❌ Some conversions failed")
384404
sys.exit(1)
385-
405+
386406
print("✅ All conversions successful")

src/formats/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
- CursorFormat: Generates .mdc files for Cursor IDE
1212
- WindsurfFormat: Generates .md files for Windsurf IDE
1313
- CopilotFormat: Generates .instructions.md files for GitHub Copilot
14-
- ClaudeCodeFormat: Generates .md files for Claude Code plugins
14+
- AgentSkillsFormat: Generates .md files for Agent Skills (OpenAI Codex, Claude Code, other AI coding tools)
15+
- AntigravityFormat: Generates .md files for Google Antigravity
1516
1617
Usage:
1718
from formats import BaseFormat, ProcessedRule, CursorFormat, WindsurfFormat, CopilotFormat, ClaudeCodeFormat
@@ -21,15 +22,16 @@
2122
CursorFormat(version),
2223
WindsurfFormat(version),
2324
CopilotFormat(version),
24-
ClaudeCodeFormat(version),
25+
AgentSkillsFormat(version),
26+
AntigravityFormat(version),
2527
]
2628
"""
2729

2830
from formats.base import BaseFormat, ProcessedRule
2931
from formats.cursor import CursorFormat
3032
from formats.windsurf import WindsurfFormat
3133
from formats.copilot import CopilotFormat
32-
from formats.claudecode import ClaudeCodeFormat
34+
from formats.agentskills import AgentSkillsFormat
3335
from formats.antigravity import AntigravityFormat
3436

3537
__all__ = [
@@ -38,6 +40,6 @@
3840
"CursorFormat",
3941
"WindsurfFormat",
4042
"CopilotFormat",
41-
"ClaudeCodeFormat",
43+
"AgentSkillsFormat",
4244
"AntigravityFormat",
4345
]

0 commit comments

Comments
 (0)