From 00341d9c53749ea8db60796ee91a4a0bddf32fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leynier=20Guti=C3=A9rrez=20Gonz=C3=A1lez?= Date: Sat, 8 Aug 2026 18:13:44 -0600 Subject: [PATCH 1/2] feat: add installable agent skills --- skills/compose-python-stack/SKILL.md | 26 +++++++ .../compose-python-stack/agents/openai.yaml | 4 ++ skills/maintain-python-template/SKILL.md | 27 +++++++ .../agents/openai.yaml | 4 ++ skills/validate-python-stack/SKILL.md | 30 ++++++++ .../validate-python-stack/agents/openai.yaml | 4 ++ template/skills/project-workflow/SKILL.md | 25 +++++++ .../project-workflow/agents/openai.yaml | 4 ++ ...ties != 'none' %}SKILL.md{% endif %}.jinja | 24 +++++++ ...s != 'none' %}openai.yaml{% endif %}.jinja | 4 ++ ...rget != 'none' %}SKILL.md{% endif %}.jinja | 24 +++++++ ...t != 'none' %}openai.yaml{% endif %}.jinja | 4 ++ tests/test_skills.py | 70 +++++++++++++++++++ 13 files changed, 250 insertions(+) create mode 100644 skills/compose-python-stack/SKILL.md create mode 100644 skills/compose-python-stack/agents/openai.yaml create mode 100644 skills/maintain-python-template/SKILL.md create mode 100644 skills/maintain-python-template/agents/openai.yaml create mode 100644 skills/validate-python-stack/SKILL.md create mode 100644 skills/validate-python-stack/agents/openai.yaml create mode 100644 template/skills/project-workflow/SKILL.md create mode 100644 template/skills/project-workflow/agents/openai.yaml create mode 100644 template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}SKILL.md{% endif %}.jinja create mode 100644 template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}agents{% endif %}/{% if ai_capabilities != 'none' %}openai.yaml{% endif %}.jinja create mode 100644 template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}SKILL.md{% endif %}.jinja create mode 100644 template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}agents{% endif %}/{% if deploy_target != 'none' %}openai.yaml{% endif %}.jinja create mode 100644 tests/test_skills.py diff --git a/skills/compose-python-stack/SKILL.md b/skills/compose-python-stack/SKILL.md new file mode 100644 index 0000000..2bf76b3 --- /dev/null +++ b/skills/compose-python-stack/SKILL.md @@ -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 `, 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. diff --git a/skills/compose-python-stack/agents/openai.yaml b/skills/compose-python-stack/agents/openai.yaml new file mode 100644 index 0000000..b058d96 --- /dev/null +++ b/skills/compose-python-stack/agents/openai.yaml @@ -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." diff --git a/skills/maintain-python-template/SKILL.md b/skills/maintain-python-template/SKILL.md new file mode 100644 index 0000000..97245e6 --- /dev/null +++ b/skills/maintain-python-template/SKILL.md @@ -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. diff --git a/skills/maintain-python-template/agents/openai.yaml b/skills/maintain-python-template/agents/openai.yaml new file mode 100644 index 0000000..c759672 --- /dev/null +++ b/skills/maintain-python-template/agents/openai.yaml @@ -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." diff --git a/skills/validate-python-stack/SKILL.md b/skills/validate-python-stack/SKILL.md new file mode 100644 index 0000000..4a9879a --- /dev/null +++ b/skills/validate-python-stack/SKILL.md @@ -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. diff --git a/skills/validate-python-stack/agents/openai.yaml b/skills/validate-python-stack/agents/openai.yaml new file mode 100644 index 0000000..9442f17 --- /dev/null +++ b/skills/validate-python-stack/agents/openai.yaml @@ -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." diff --git a/template/skills/project-workflow/SKILL.md b/template/skills/project-workflow/SKILL.md new file mode 100644 index 0000000..f2b7c3a --- /dev/null +++ b/template/skills/project-workflow/SKILL.md @@ -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. diff --git a/template/skills/project-workflow/agents/openai.yaml b/template/skills/project-workflow/agents/openai.yaml new file mode 100644 index 0000000..95a9b98 --- /dev/null +++ b/template/skills/project-workflow/agents/openai.yaml @@ -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." diff --git a/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}SKILL.md{% endif %}.jinja b/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}SKILL.md{% endif %}.jinja new file mode 100644 index 0000000..128c469 --- /dev/null +++ b/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}SKILL.md{% endif %}.jinja @@ -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. diff --git a/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}agents{% endif %}/{% if ai_capabilities != 'none' %}openai.yaml{% endif %}.jinja b/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}agents{% endif %}/{% if ai_capabilities != 'none' %}openai.yaml{% endif %}.jinja new file mode 100644 index 0000000..8978830 --- /dev/null +++ b/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}agents{% endif %}/{% if ai_capabilities != 'none' %}openai.yaml{% endif %}.jinja @@ -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." diff --git a/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}SKILL.md{% endif %}.jinja b/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}SKILL.md{% endif %}.jinja new file mode 100644 index 0000000..0f43f9a --- /dev/null +++ b/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}SKILL.md{% endif %}.jinja @@ -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. diff --git a/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}agents{% endif %}/{% if deploy_target != 'none' %}openai.yaml{% endif %}.jinja b/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}agents{% endif %}/{% if deploy_target != 'none' %}openai.yaml{% endif %}.jinja new file mode 100644 index 0000000..9a91508 --- /dev/null +++ b/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}agents{% endif %}/{% if deploy_target != 'none' %}openai.yaml{% endif %}.jinja @@ -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." diff --git a/tests/test_skills.py b/tests/test_skills.py new file mode 100644 index 0000000..0956e64 --- /dev/null +++ b/tests/test_skills.py @@ -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") From f077a4680e5ca56a84706769a730b2c44a0d73b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leynier=20Guti=C3=A9rrez=20Gonz=C3=A1lez?= Date: Sat, 8 Aug 2026 18:18:03 -0600 Subject: [PATCH 2/2] fix: keep conditional skill paths Windows-safe --- ... 'none' %}SKILL.md{% endif %}.jinja => SKILL.md} | 0 .../openai.yaml} | 0 ... 'none' %}SKILL.md{% endif %}.jinja => SKILL.md} | 0 .../openai.yaml} | 0 tests/test_generation.py | 13 +++++++++++++ 5 files changed, 13 insertions(+) rename template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{{% if ai_capabilities != 'none' %}SKILL.md{% endif %}.jinja => SKILL.md} (100%) rename template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{{% if ai_capabilities != 'none' %}agents{% endif %}/{% if ai_capabilities != 'none' %}openai.yaml{% endif %}.jinja => agents/openai.yaml} (100%) rename template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{{% if deploy_target != 'none' %}SKILL.md{% endif %}.jinja => SKILL.md} (100%) rename template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{{% if deploy_target != 'none' %}agents{% endif %}/{% if deploy_target != 'none' %}openai.yaml{% endif %}.jinja => agents/openai.yaml} (100%) diff --git a/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}SKILL.md{% endif %}.jinja b/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/SKILL.md similarity index 100% rename from template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}SKILL.md{% endif %}.jinja rename to template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/SKILL.md diff --git a/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}agents{% endif %}/{% if ai_capabilities != 'none' %}openai.yaml{% endif %}.jinja b/template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/agents/openai.yaml similarity index 100% rename from template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/{% if ai_capabilities != 'none' %}agents{% endif %}/{% if ai_capabilities != 'none' %}openai.yaml{% endif %}.jinja rename to template/skills/{% if ai_capabilities != 'none' %}operate-ai-stack{% endif %}/agents/openai.yaml diff --git a/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}SKILL.md{% endif %}.jinja b/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/SKILL.md similarity index 100% rename from template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}SKILL.md{% endif %}.jinja rename to template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/SKILL.md diff --git a/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}agents{% endif %}/{% if deploy_target != 'none' %}openai.yaml{% endif %}.jinja b/template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/agents/openai.yaml similarity index 100% rename from template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/{% if deploy_target != 'none' %}agents{% endif %}/{% if deploy_target != 'none' %}openai.yaml{% endif %}.jinja rename to template/skills/{% if deploy_target != 'none' %}deploy-python-project{% endif %}/agents/openai.yaml diff --git a/tests/test_generation.py b/tests/test_generation.py index c815e0c..9025d86 100644 --- a/tests/test_generation.py +++ b/tests/test_generation.py @@ -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."""