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
26 changes: 26 additions & 0 deletions skills/compose-python-stack/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: compose-python-stack
description: Design and generate a compatible project from the Python Template component layers or presets. Use when choosing a workload, framework, AI providers, data engines, interfaces, training and serving tools, deployment target, or IaC option; also use when a user wants a simple library, CLI, or API without AI.
---

# Compose Python Stack

Turn a product goal into a supported stack, explain the consequential choices, and generate it with Copier.

## Workflow

1. Read `docs/reference/presets.md` and `docs/reference/components.md`. When exact compatibility matters, also read `catalog/generated/catalog.json`.
2. Ask only about choices that change the architecture. Prefer one of the documented presets when it is close; otherwise select `custom` and compose the layers.
3. Keep model and embedding providers independent. Select at most one provider for each data role. Do not add AI to a simple library, Typer CLI, or FastAPI/Flask API unless requested.
4. Select one deployment target. Docker is the portable base for deployed projects; select either no IaC, Pulumi, or Terraform where the chosen target supports it.
5. Generate into a new directory with `copier copy gh:leynier/python-template <destination>`, answering interactively. For automation, pass explicit values with repeated `-d key=value` arguments.
6. Enter the generated directory and run `uv sync --all-groups`, `uv run pytest`, `uv run ruff check .`, and `uv run ruff format --check .`.
7. Report the selected layers, generated entry points, required environment variables, and any cloud resources the user must configure.

## Guardrails

- Treat catalog compatibility rules as authoritative; do not force an invalid combination.
- Preserve the first-class `python-library`, `typer-cli`, and `fastapi-api` paths.
- Do not propose ODMantic, AutoGOAL, FastUI, or Reflex; they are intentionally outside the supported catalog.
- Prefer Python 3.12 or newer and `uv` for environment and command execution.
- Never deploy, create paid resources, or write credentials unless the user explicitly authorizes that action.
4 changes: 4 additions & 0 deletions skills/compose-python-stack/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Compose Python Stack"
short_description: "Design and generate a compatible Python or AI stack"
default_prompt: "Use $compose-python-stack to choose compatible layers and generate a project from this repository."
27 changes: 27 additions & 0 deletions skills/maintain-python-template/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: maintain-python-template
description: Extend or repair the Python Template repository while keeping its catalog, Copier questions, templates, generated references, and tests synchronized. Use for adding a framework, provider, database, interface, ML tool, deployment target, preset, compatibility rule, or generated-project capability.
---

# Maintain Python Template

Make catalog-driven changes without creating a choice that renders but cannot run.

## Change Workflow

1. Inspect `catalog/components.yml`, `catalog/presets.yml`, `copier.yml`, the relevant files under `template/`, and existing tests before editing.
2. Define the component metadata and compatibility constraints in the catalog first. Keep each choice in exactly one architectural role unless it genuinely serves several roles.
3. Wire Copier defaults and choices to the catalog. Preserve the three simple presets and reject incompatible combinations with an actionable validator message.
4. Add functional source, dependency, configuration, test, README, and environment-variable templates for the component. Avoid placeholder-only integrations.
5. Run `uv run python scripts/compile_catalog.py --check`. If generated files are stale, run the command without `--check`, review all generated changes, then check again.
6. Add focused repository tests and generate at least one representative project. Validate its imports or endpoints, not only file presence.
7. Run the gates in `validate-python-stack` before proposing delivery.

## Invariants

- `catalog/generated/catalog.json`, `catalog/generated/choices.yml`, and `docs/reference/*.md` are compiler outputs; edit their sources instead.
- Generated projects use a `src` layout, `uv`, Ruff, pytest, typing, and reproducible dependency bounds.
- Optional layers must disappear cleanly when disabled.
- Conditional template path components must remain short enough to render on Windows.
- A deploy target keeps Docker portability; cloud IaC is limited to supported target/provider pairs.
- Do not reintroduce ODMantic, AutoGOAL, FastUI, or Reflex.
4 changes: 4 additions & 0 deletions skills/maintain-python-template/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Maintain Python Template"
short_description: "Extend the component catalog and Copier templates safely"
default_prompt: "Use $maintain-python-template to add or change a component while preserving catalog and generation invariants."
30 changes: 30 additions & 0 deletions skills/validate-python-stack/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: validate-python-stack
description: Validate the Python Template repository or a project generated from it. Use before committing, publishing, deploying, or reviewing a stack to check catalog compilation, rendering, dependencies, formatting, tests, security workflows, skills, and representative runtime behavior.
---

# Validate Python Stack

Choose the validation path that matches the current directory and report exact failures without hiding skipped checks.

## Template Repository

1. Run `uv sync --all-groups`.
2. Run `uv run python scripts/compile_catalog.py --check`.
3. Run `uv run ruff check .` and `uv run ruff format --check .`.
4. Run `uv run pytest -n auto`. Use focused tests first while iterating, but finish with the full suite.
5. For every directory under `skills/`, run the skill-creator validator when available and run `npx --yes skills add . --list` to verify discovery.
6. Generate the changed presets or custom combinations and exercise their real CLI, API, MCP, training, serving, or deployment entry point as applicable.
7. If GitHub workflows changed and Docker is available, run the repository's local Actions validation before pushing.

## Generated Project

1. Read `.copier-answers.yml`, `readme.md`, and `pyproject.toml` to identify enabled layers.
2. Run `uv sync --all-groups`, `uv run ruff check .`, `uv run ruff format --check .`, and `uv run pytest`.
3. Run `npx --yes skills add . --list`; expect `project-workflow` and only the conditional skills appropriate to the selected stack.
4. Exercise the primary entry point. For a containerized target, build the Docker image and verify its health route locally when Docker is available.
5. Validate generated JSON, TOML, YAML, Python, and HCL as structured data rather than with string-only assertions.

## Reporting

Separate passed, failed, and unavailable checks. Include the command, relevant error, and whether the failure belongs to the template, the generated project, local infrastructure, or an external service.
4 changes: 4 additions & 0 deletions skills/validate-python-stack/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Validate Python Stack"
short_description: "Validate catalog rules and generated project toolchains"
default_prompt: "Use $validate-python-stack to verify this template or a generated project before delivery."
25 changes: 25 additions & 0 deletions template/skills/project-workflow/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: project-workflow
description: Develop, test, and maintain this generated Python project using its recorded Copier choices and uv toolchain. Use for any code, dependency, configuration, documentation, or test change in this project, including simple libraries, CLIs, APIs, and layered AI or ML applications.
---

# Project Workflow

Use the generated project as the source of truth for its enabled layers.

## Workflow

1. Read `.copier-answers.yml`, `pyproject.toml`, and `readme.md` before changing architecture or commands.
2. Preserve the `src` layout, public entry points, Python version, dependency groups, and existing user changes.
3. Add dependencies with `uv add` or `uv add --group dev`; do not hand-edit the lockfile.
4. Keep secrets out of source control. Document required values in `.env.example` and load them through the generated settings layer when present.
5. Add or update tests alongside behavior changes.
6. Run `uv sync --all-groups`, `uv run ruff check .`, `uv run ruff format --check .`, and `uv run pytest` before delivery.
7. If the project was created from a Git reference and needs upstream fixes, use `copier update` only from a clean worktree and review the resulting diff carefully.

## Entry Points

- For a library, verify imports and its public API.
- For a CLI, run its generated console command and a representative option.
- For an API or interface, start it locally and exercise the health or primary route.
- For MCP, agents, RAG, inference, training, or deployment, use the additional generated skill when present.
4 changes: 4 additions & 0 deletions template/skills/project-workflow/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Project Workflow"
short_description: "Develop and verify this generated Python project"
default_prompt: "Use $project-workflow to make a change in this generated project and run its relevant quality gates."
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: operate-ai-stack
description: Develop, test, and troubleshoot this generated AI or ML workload across its framework, model and embedding providers, retrieval stores, interfaces, training, serving, and observability layers. Use when changing prompts, tools, agents, RAG, MCP, inference, fine-tuning, evaluations, or provider integration.
---

# Operate AI Stack

Treat `.copier-answers.yml` as the layer map and inspect the generated modules before assuming a framework or provider API.

## Workflow

1. Identify the workload, framework, model provider, embedding provider, data roles, interfaces, training extensions, serving engine, and quality tools that are actually enabled.
2. Preserve the boundary between model and embedding providers. Keep provider-specific construction in the generated provider module and inject it into framework code.
3. Use deterministic fakes for unit tests. Put real-provider checks behind explicit environment variables and never make the normal test suite spend tokens or require cloud credentials.
4. For agents and MCP, test tool schemas and error paths. For RAG, test ingestion, retrieval, empty results, and citation metadata. For training, test a tiny local batch and artifact creation. For inference, test health plus one prediction.
5. Record required secrets in `.env.example`, use the settings layer, and redact prompt, credential, and personal data from logs.
6. Run the project quality gates from `project-workflow`, followed by the smallest representative end-to-end check for the enabled stack.

## Operational Checks

- Pin or bound model and API dependencies; review upstream breaking changes before updating.
- Track latency, token or compute usage, provider errors, and evaluation quality separately.
- Make external calls timeout and fail clearly; do not silently switch providers or models.
- Require an explicit review before changing production prompts, tools with side effects, or model artifacts.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Operate AI Stack"
short_description: "Develop and operate this generated AI workload"
default_prompt: "Use $operate-ai-stack to change or troubleshoot this generated AI stack while preserving provider boundaries."
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: deploy-python-project
description: Validate and deploy this generated Python project using its selected target, portable Docker base, and optional Pulumi or Terraform configuration. Use for deployment preparation, local container checks, cloud previews, release rollout, health verification, or deployment troubleshooting.
---

# Deploy Python Project

Read `.copier-answers.yml` and `deploy/README.md` first; they identify the target, commands, environment variables, and any generated IaC.

## Workflow

1. Run the full project quality gates from `project-workflow`.
2. Build the generated Dockerfile locally. Start the image with explicit environment variables and verify its documented health route.
3. Validate target configuration as structured JSON, TOML, YAML, Python, or HCL. Never infer that a successful parse proves cloud readiness.
4. For Pulumi, run a preview before update. For Terraform, run format, init without changing remote state where possible, validate, and plan before apply.
5. Confirm account, region, project, service name, expected cost boundary, secrets source, and rollback strategy before creating or changing remote resources.
6. Deploy with the command documented in `deploy/README.md`, then verify the remote health route and one representative request.
7. Report the deployed revision, endpoint, verification evidence, and any manual DNS or secret-management step.

## Safety

- Do not deploy, apply IaC, delete resources, or expose a public endpoint without explicit authorization.
- Do not embed provider credentials in images, configuration, logs, or Git.
- Keep the Docker path usable even when the selected managed platform provides a native adapter.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Deploy Python Project"
short_description: "Deploy this project using its generated target configuration"
default_prompt: "Use $deploy-python-project to validate and deploy this generated project with its selected deployment target."
13 changes: 13 additions & 0 deletions tests/test_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@
SIMPLE_WORKLOADS = ["library", "cli", "api"]


def test_template_source_paths_fit_windows_git() -> None:
"""Leave room for the deep temporary roots used by Windows CI."""
template_root = pathlib.Path(__file__).parents[1] / "template"
relative_paths = [
str(path.relative_to(template_root.parent))
for path in template_root.rglob("*")
if path.is_file()
]
longest = max(relative_paths, key=len)

assert len(longest) <= 185, f"Template path is too long ({len(longest)}): {longest}"


@pytest.mark.parametrize("workload", SIMPLE_WORKLOADS)
def test_generates_cleanly(copie, workload: str) -> None:
"""Each workload renders without error."""
Expand Down
70 changes: 70 additions & 0 deletions tests/test_skills.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Agent skill discovery and conditional generation tests."""

from pathlib import Path

import yaml
from conftest import answers

REPOSITORY_SKILLS = {
"compose-python-stack",
"maintain-python-template",
"validate-python-stack",
}


def _assert_skill(skill_dir: Path, expected_name: str) -> None:
skill_md = skill_dir / "SKILL.md"
metadata = skill_dir / "agents" / "openai.yaml"
assert skill_md.is_file()
assert metadata.is_file()

contents = skill_md.read_text()
_, frontmatter, _ = contents.split("---", maxsplit=2)
parsed = yaml.safe_load(frontmatter)
assert parsed["name"] == expected_name
assert parsed["description"]

agent = yaml.safe_load(metadata.read_text())
assert agent["interface"]["display_name"]
assert f"${expected_name}" in agent["interface"]["default_prompt"]


def test_repository_skills_follow_the_agent_skills_layout() -> None:
skills_root = Path(__file__).parents[1] / "skills"
assert {path.name for path in skills_root.iterdir()} == REPOSITORY_SKILLS
for name in REPOSITORY_SKILLS:
_assert_skill(skills_root / name, name)


def test_simple_project_only_gets_the_common_workflow_skill(copie) -> None:
project = copie.copy(extra_answers=answers(preset="typer-cli")).project_dir
skills_root = project / "skills"

assert {path.name for path in skills_root.iterdir()} == {"project-workflow"}
_assert_skill(skills_root / "project-workflow", "project-workflow")


def test_ai_project_gets_the_ai_skill_without_deploy_skill(copie) -> None:
project = copie.copy(
extra_answers=answers(preset="pydantic-ai-openai", deploy_target="none")
).project_dir
skills_root = project / "skills"

assert {path.name for path in skills_root.iterdir()} == {
"operate-ai-stack",
"project-workflow",
}
_assert_skill(skills_root / "operate-ai-stack", "operate-ai-stack")


def test_deployed_simple_api_gets_deploy_skill_without_ai_skill(copie) -> None:
project = copie.copy(
extra_answers=answers(preset="fastapi-api", deploy_target="render")
).project_dir
skills_root = project / "skills"

assert {path.name for path in skills_root.iterdir()} == {
"deploy-python-project",
"project-workflow",
}
_assert_skill(skills_root / "deploy-python-project", "deploy-python-project")