Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,14 @@ print(result["DisplayText"])

```python
# WAV PCM 16kHz
"Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000"
wav_headers = {
"Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000"
}
Comment on lines +102 to +104

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description has been updated to reflect the actual scope: 145 changed files spanning tests/ scenarios, plugin skill content under .github/plugins/, repository validators, and a CI workflow update under .github/. The original "178 files under tests/" count was from an earlier draft before files were reorganized.


# OGG OPUS
"Content-Type": "audio/ogg; codecs=opus"
ogg_headers = {
"Content-Type": "audio/ogg; codecs=opus"
}
```

## Response Formats
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ async def list_items() -> list[Item]:

```python
@router.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(item: ItemCreate) -> Item:
...

@router.delete("/items/{id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(id: str) -> None:
...
```

## Integration Steps
Expand Down
Original file line number Diff line number Diff line change
@@ -1,142 +1,147 @@
"""
{{ResourceName}} Router
ResourceName Router

Handles CRUD operations for {{resource_name}} resources.
Handles CRUD operations for resource_name resources.

Template placeholders to replace:
- ResourceName (PascalCase)
- resource_name (snake_case)
- resource_plural (plural snake_case)
"""

from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status

from app.auth.jwt import get_current_user, get_current_user_required
from app.models.user import User
from app.models.{{resource_name}} import (
{{ResourceName}},
{{ResourceName}}Create,
{{ResourceName}}Update,
from app.models.resource_name import (
ResourceName,
ResourceNameCreate,
ResourceNameUpdate,
)
from app.services.{{resource_name}}_service import {{ResourceName}}Service
from app.services.resource_name_service import ResourceNameService

router = APIRouter(prefix="/api", tags=["{{resource_plural}}"])
router = APIRouter(prefix="/api", tags=["resource_plural"])


# ============================================================================
# Dependencies
# ============================================================================


def get_service() -> {{ResourceName}}Service:
def get_service() -> ResourceNameService:
"""Dependency to get service instance."""
return {{ResourceName}}Service()
return ResourceNameService()


# ============================================================================
# Endpoints
# ============================================================================


@router.get("/{{resource_plural}}", response_model=list[{{ResourceName}}])
async def list_{{resource_plural}}(
@router.get("/resource_plural", response_model=list[ResourceName])
async def list_resource_plural(
limit: int = Query(default=50, ge=1, le=100),
offset: int = Query(default=0, ge=0),
current_user: Optional[User] = Depends(get_current_user),
service: {{ResourceName}}Service = Depends(get_service),
) -> list[{{ResourceName}}]:
service: ResourceNameService = Depends(get_service),
) -> list[ResourceName]:
"""
List all {{resource_plural}}.
List all resource_plural.

- **limit**: Maximum number of items to return (1-100)
- **offset**: Number of items to skip
"""
return await service.list_{{resource_plural}}(limit=limit, offset=offset)
return await service.list_resource_plural(limit=limit, offset=offset)


@router.get("/{{resource_plural}}/{{{resource_name}}_id}", response_model={{ResourceName}})
async def get_{{resource_name}}(
{{resource_name}}_id: str,
@router.get("/resource_plural/{resource_name_id}", response_model=ResourceName)
async def get_resource_name(
resource_name_id: str,
current_user: Optional[User] = Depends(get_current_user),
service: {{ResourceName}}Service = Depends(get_service),
) -> {{ResourceName}}:
service: ResourceNameService = Depends(get_service),
) -> ResourceName:
"""
Get a specific {{resource_name}} by ID.
Get a specific resource_name by ID.

Raises 404 if not found.
"""
result = await service.get_{{resource_name}}_by_id({{resource_name}}_id)
result = await service.get_resource_name_by_id(resource_name_id)
if result is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="{{ResourceName}} not found",
detail="ResourceName not found",
)
return result


@router.post(
"/{{resource_plural}}",
response_model={{ResourceName}},
"/resource_plural",
response_model=ResourceName,
status_code=status.HTTP_201_CREATED,
)
async def create_{{resource_name}}(
data: {{ResourceName}}Create,
async def create_resource_name(
data: ResourceNameCreate,
current_user: User = Depends(get_current_user_required),
service: {{ResourceName}}Service = Depends(get_service),
) -> {{ResourceName}}:
service: ResourceNameService = Depends(get_service),
) -> ResourceName:
"""
Create a new {{resource_name}}.
Create a new resource_name.

Requires authentication.
"""
return await service.create_{{resource_name}}(data, current_user.id)
return await service.create_resource_name(data, current_user.id)


@router.patch("/{{resource_plural}}/{{{resource_name}}_id}", response_model={{ResourceName}})
async def update_{{resource_name}}(
{{resource_name}}_id: str,
data: {{ResourceName}}Update,
@router.patch("/resource_plural/{resource_name_id}", response_model=ResourceName)
async def update_resource_name(
resource_name_id: str,
data: ResourceNameUpdate,
current_user: User = Depends(get_current_user_required),
service: {{ResourceName}}Service = Depends(get_service),
) -> {{ResourceName}}:
service: ResourceNameService = Depends(get_service),
) -> ResourceName:
"""
Update an existing {{resource_name}}.
Update an existing resource_name.

Requires authentication and ownership.
"""
# Verify ownership
existing = await service.get_{{resource_name}}_by_id({{resource_name}}_id)
existing = await service.get_resource_name_by_id(resource_name_id)
if existing is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="{{ResourceName}} not found",
detail="ResourceName not found",
)

# Optional: Check ownership
# if existing.author_id != current_user.id:
# raise HTTPException(
# status_code=status.HTTP_403_FORBIDDEN,
# detail="Not authorized to update this {{resource_name}}",
# detail="Not authorized to update this resource_name",
# )

return await service.update_{{resource_name}}({{resource_name}}_id, data)
return await service.update_resource_name(resource_name_id, data)


@router.delete(
"/{{resource_plural}}/{{{resource_name}}_id}",
"/resource_plural/{resource_name_id}",
status_code=status.HTTP_204_NO_CONTENT,
)
async def delete_{{resource_name}}(
{{resource_name}}_id: str,
async def delete_resource_name(
resource_name_id: str,
current_user: User = Depends(get_current_user_required),
service: {{ResourceName}}Service = Depends(get_service),
service: ResourceNameService = Depends(get_service),
) -> None:
"""
Delete a {{resource_name}}.
Delete a resource_name.

Requires authentication and ownership.
"""
existing = await service.get_{{resource_name}}_by_id({{resource_name}}_id)
existing = await service.get_resource_name_by_id(resource_name_id)
if existing is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="{{ResourceName}} not found",
detail="ResourceName not found",
)

await service.delete_{{resource_name}}({{resource_name}}_id)
await service.delete_resource_name(resource_name_id)
94 changes: 94 additions & 0 deletions .github/scripts/check_skill_python_syntax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Validate Python syntax across skill files and Python fenced snippets.

This script checks:
1) Python files under .github/plugins/*/skills and .github/skills
2) Python code fences in SKILL.md files under those trees

It exits non-zero if any syntax error is found.
"""

from __future__ import annotations

import ast
import re
import sys
from pathlib import Path


PY_FENCE_RE = re.compile(r"```(?:python|py)\n(.*?)\n```", re.S)


def check_python_file(path: Path, repo_root: Path, errors: list[str]) -> int:
try:
ast.parse(path.read_text(encoding="utf-8"))
return 1
except SyntaxError as exc:
rel = path.relative_to(repo_root).as_posix()
line = exc.lineno or 0
text = (exc.text or "").strip()
errors.append(f"{rel}:{line}: {exc.msg} :: {text}")
return 0


def check_markdown_fences(path: Path, repo_root: Path, errors: list[str]) -> int:
text = path.read_text(encoding="utf-8")
checked = 0

for block_idx, match in enumerate(PY_FENCE_RE.finditer(text), 1):
code = match.group(1)
checked += 1
try:
ast.parse(code)
except SyntaxError as exc:
start_line = text[: match.start()].count("\n") + 1
line_in_block = exc.lineno or 0
approx_file_line = start_line + line_in_block
rel = path.relative_to(repo_root).as_posix()
snippet = (exc.text or "").strip()
errors.append(
f"{rel}:block {block_idx} (~L{approx_file_line}): {exc.msg} :: {snippet}"
)

return checked


def main() -> int:
repo_root = Path(__file__).resolve().parents[2]
roots = [repo_root / ".github" / "plugins", repo_root / ".github" / "skills"]

errors: list[str] = []
py_checked = 0
fence_checked = 0

for root in roots:
if not root.exists():
continue

if root.name == "plugins":
py_files = root.rglob("skills/**/*.py")
md_files = root.rglob("skills/**/SKILL.md")
else:
py_files = root.rglob("**/*.py")
md_files = root.rglob("**/SKILL.md")

for py_path in py_files:
py_checked += check_python_file(py_path, repo_root, errors)

for md_path in md_files:
fence_checked += check_markdown_fences(md_path, repo_root, errors)

print(f"Checked Python files: {py_checked}")
print(f"Checked Python code fences: {fence_checked}")

if errors:
print("\nPython syntax errors detected:")
for err in errors:
print(f"- {err}")
return 1

print("All checked Python files and snippets are syntactically valid.")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading