From d30c974d144c97ec26ed0dc07c4b388b83c95ad2 Mon Sep 17 00:00:00 2001 From: Levon Becker Date: Tue, 11 Aug 2026 12:56:06 -0700 Subject: [PATCH 1/2] Restructure tasks/ into common/ and tests/ subpackages - tasks/common/{main,debug,ruff,setup}.py -- main.py replaces combos.py (same fix/test top-level aliases), debug/ruff/setup grouped alongside it. - tasks/tests/ -- tasks/tests.py split into one file per check (actionlint.py, pylint.py, pytest.py, rufflint.py, yamllint.py). Both subpackages keep their tasks registered at the exact same top-level invoke names they had before (debug.*, ruff.*, setup.*, tests.*, plus bare fix/test) -- this is a file-layout change only, not a CLI surface change, so every repo cloned from this template keeps working identically. Matches the same restructuring just done in fireball_orchestrator (a downstream consumer of this template), minus everything project-specific that lives in template_ai_python or further downstream instead. README.md's Project Structure section updated to match. Generated with Claude Code Co-Authored-By: Claude Sonnet 5 --- README.md | 13 ++++----- tasks/__init__.py | 26 +++++++++--------- tasks/combos.py | 20 -------------- tasks/common/__init__.py | 0 tasks/{ => common}/debug.py | 0 tasks/common/main.py | 21 +++++++++++++++ tasks/{ => common}/ruff.py | 0 tasks/{ => common}/setup.py | 0 tasks/tests.py | 53 ------------------------------------- tasks/tests/__init__.py | 21 +++++++++++++++ tasks/tests/actionlint.py | 10 +++++++ tasks/tests/pylint.py | 10 +++++++ tasks/tests/pytest.py | 10 +++++++ tasks/tests/rufflint.py | 10 +++++++ tasks/tests/yamllint.py | 17 ++++++++++++ 15 files changed, 120 insertions(+), 91 deletions(-) delete mode 100644 tasks/combos.py create mode 100644 tasks/common/__init__.py rename tasks/{ => common}/debug.py (100%) create mode 100644 tasks/common/main.py rename tasks/{ => common}/ruff.py (100%) rename tasks/{ => common}/setup.py (100%) delete mode 100644 tasks/tests.py create mode 100644 tasks/tests/__init__.py create mode 100644 tasks/tests/actionlint.py create mode 100644 tasks/tests/pylint.py create mode 100644 tasks/tests/pytest.py create mode 100644 tasks/tests/rufflint.py create mode 100644 tasks/tests/yamllint.py diff --git a/README.md b/README.md index 6faed53..03e2592 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,13 @@ modules/ common/ # cli.py, properties.py, utils.py — shared helpers setup/ # properties.py — creates properties.yml (no-op if it exists), called by setup.sh/setup.ps1; templates/properties/*.yml — tier fragments tasks/ - __init__.py # Wires the invoke Collection (debug, ruff, setup, tests) plus top-level aliases (fix, test) - combos.py # Top-level aliases: fix, test - debug.py # debug.env — print cwd + sorted env vars - ruff.py # ruff.fix, ruff.format - setup.py # setup.properties — creates/stamps properties.yml - tests.py # tests.actionlint, tests.pylint, tests.pytest, tests.rufflint, tests.yamllint + __init__.py # Wires the invoke Collection: common/ and tests/ (registered at their original top-level names — debug, ruff, setup, tests, plus bare fix/test — not nested under common.*/tests.* prefixes) + common/ + main.py # Top-level aliases: fix, test (was combos.py) + debug.py # debug.env — print cwd + sorted env vars + ruff.py # ruff.fix, ruff.format + setup.py # setup.properties — creates/stamps properties.yml + tests/ # One file per check (actionlint.py, pylint.py, pytest.py, rufflint.py, yamllint.py) — still one flat tests.* namespace: tests.actionlint, tests.pylint, tests.pytest, tests.rufflint, tests.yamllint .github/ copilot-instructions.md # What this repo is, for GitHub Copilot workflows/ diff --git a/tasks/__init__.py b/tasks/__init__.py index 29d3b1f..2092b68 100644 --- a/tasks/__init__.py +++ b/tasks/__init__.py @@ -9,21 +9,23 @@ if str(_REPO_ROOT) not in sys.path: sys.path.insert(0, str(_REPO_ROOT)) -from . import ( # noqa: E402 # pylint: disable=wrong-import-position - combos, - debug, - ruff, - setup, - tests, -) +from .common import debug, ruff, setup # noqa: E402 # pylint: disable=wrong-import-position +from .common import main as common_main # noqa: E402 # pylint: disable=wrong-import-position +from .tests import namespace as tests_namespace # noqa: E402 # pylint: disable=wrong-import-position -namespace = Collection() -namespace.configure({"auto_dash_names": False}) +namespace = Collection(auto_dash_names=False) +# `common/` and `tests/` are the only two subpackages here — `tasks/` in this base template is +# deliberately as small as possible (debug/ruff/setup + the fix/test aliases, plus the tests +# themselves), everything project-specific (repo/template sync, AI-agent tooling, versioning) +# lives downstream in template_ai_python instead. Both stay registered at their original +# top-level names (`debug.*`, `ruff.*`, `setup.*`, `tests.*`, plus bare `fix`/`test`) rather than +# nested under `common.*`/`tests.*` prefixes that don't exist — every repo that clones this +# template inherits that exact CLI surface. namespace.add_collection(debug, name="debug") namespace.add_collection(ruff, name="ruff") namespace.add_collection(setup, name="setup") -namespace.add_collection(tests, name="tests") +namespace.add_collection(tests_namespace, name="tests") -namespace.add_task(combos.fix, name="fix") -namespace.add_task(combos.test, name="test") +namespace.add_task(common_main.fix, name="fix") +namespace.add_task(common_main.test, name="test") diff --git a/tasks/combos.py b/tasks/combos.py deleted file mode 100644 index 77e2140..0000000 --- a/tasks/combos.py +++ /dev/null @@ -1,20 +0,0 @@ -from invoke import task - -from . import ruff, tests - - -@task -def fix(context): - """Run All Automated Fixes""" - ruff.fix(context) - ruff.format(context) - - -@task -def test(context): - """Run All Tests""" - tests.actionlint(context) - tests.pylint(context) - tests.pytest(context) - tests.rufflint(context) - tests.yamllint(context) diff --git a/tasks/common/__init__.py b/tasks/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tasks/debug.py b/tasks/common/debug.py similarity index 100% rename from tasks/debug.py rename to tasks/common/debug.py diff --git a/tasks/common/main.py b/tasks/common/main.py new file mode 100644 index 0000000..39b557c --- /dev/null +++ b/tasks/common/main.py @@ -0,0 +1,21 @@ +from invoke import task + +from ..tests import actionlint, pylint, pytest, rufflint, yamllint +from . import ruff + + +@task +def fix(context): + """Run All Automated Fixes""" + ruff.fix(context) + ruff.format(context) + + +@task +def test(context): + """Run All Tests""" + actionlint(context) + pylint(context) + pytest(context) + rufflint(context) + yamllint(context) diff --git a/tasks/ruff.py b/tasks/common/ruff.py similarity index 100% rename from tasks/ruff.py rename to tasks/common/ruff.py diff --git a/tasks/setup.py b/tasks/common/setup.py similarity index 100% rename from tasks/setup.py rename to tasks/common/setup.py diff --git a/tasks/tests.py b/tasks/tests.py deleted file mode 100644 index c4f41e2..0000000 --- a/tasks/tests.py +++ /dev/null @@ -1,53 +0,0 @@ -from invoke import task - - -@task -def actionlint(context): - """Run Action Lint""" - print("\n------------") - print("Action Lint") - print("------------\n") - context.run("actionlint") - - -@task -def pylint(context): - """Run PyLint on Entire Repo""" - print("\n------------") - print("Pylint Lint") - print("------------\n") - context.run("pylint --verbose --rcfile=pyproject.toml .") - - -@task -def pytest(context): - """Run Pytest Unit Test Suite""" - print("\n------------") - print("Pytest") - print("------------\n") - context.run("pytest") - - -@task -def rufflint(context): - """Run Ruff Linter on Entire Repo""" - print("\n------------") - print("Ruff Lint") - print("------------\n") - context.run("ruff check .") - - -@task -def yamllint(context): - """Run Yaml Linter on Entire Repo""" - print("\n------------") - print("Yaml Lint") - print("------------\n") - context.run( - """ - yamllint --list-files -c .yamllint . && - echo '------------' && - echo -e && - yamllint -f parsable -c .yamllint . - """ - ) diff --git a/tasks/tests/__init__.py b/tasks/tests/__init__.py new file mode 100644 index 0000000..dd66427 --- /dev/null +++ b/tasks/tests/__init__.py @@ -0,0 +1,21 @@ +"""`tests` collection — one file per check (was a single flat `tasks/tests.py`), still registered +as one flat `tests.*` namespace (`tests.actionlint`, `tests.pytest`, etc.) so nothing that calls +these tasks needs to change. Re-exports each task function at package level too, so +`tasks/common/main.py`'s `test` task can keep calling `actionlint(context)` etc. exactly like it +did when this was one file. +""" + +from invoke import Collection + +from .actionlint import actionlint +from .pylint import pylint +from .pytest import run_pytest as pytest +from .rufflint import rufflint +from .yamllint import yamllint + +namespace = Collection(auto_dash_names=False) +namespace.add_task(actionlint, name="actionlint") +namespace.add_task(pylint, name="pylint") +namespace.add_task(pytest, name="pytest") +namespace.add_task(rufflint, name="rufflint") +namespace.add_task(yamllint, name="yamllint") diff --git a/tasks/tests/actionlint.py b/tasks/tests/actionlint.py new file mode 100644 index 0000000..c42bdf8 --- /dev/null +++ b/tasks/tests/actionlint.py @@ -0,0 +1,10 @@ +from invoke import task + + +@task +def actionlint(context): + """Run Action Lint""" + print("\n------------") + print("Action Lint") + print("------------\n") + context.run("actionlint") diff --git a/tasks/tests/pylint.py b/tasks/tests/pylint.py new file mode 100644 index 0000000..8f90ef3 --- /dev/null +++ b/tasks/tests/pylint.py @@ -0,0 +1,10 @@ +from invoke import task + + +@task +def pylint(context): + """Run PyLint on Entire Repo""" + print("\n------------") + print("Pylint Lint") + print("------------\n") + context.run("pylint --verbose --rcfile=pyproject.toml .") diff --git a/tasks/tests/pytest.py b/tasks/tests/pytest.py new file mode 100644 index 0000000..76853c5 --- /dev/null +++ b/tasks/tests/pytest.py @@ -0,0 +1,10 @@ +from invoke import task + + +@task(name="pytest") +def run_pytest(context): + """Run Pytest Unit Test Suite""" + print("\n------------") + print("Pytest") + print("------------\n") + context.run("pytest") diff --git a/tasks/tests/rufflint.py b/tasks/tests/rufflint.py new file mode 100644 index 0000000..7d8a51c --- /dev/null +++ b/tasks/tests/rufflint.py @@ -0,0 +1,10 @@ +from invoke import task + + +@task +def rufflint(context): + """Run Ruff Linter on Entire Repo""" + print("\n------------") + print("Ruff Lint") + print("------------\n") + context.run("ruff check .") diff --git a/tasks/tests/yamllint.py b/tasks/tests/yamllint.py new file mode 100644 index 0000000..2e31ebb --- /dev/null +++ b/tasks/tests/yamllint.py @@ -0,0 +1,17 @@ +from invoke import task + + +@task +def yamllint(context): + """Run Yaml Linter on Entire Repo""" + print("\n------------") + print("Yaml Lint") + print("------------\n") + context.run( + """ + yamllint --list-files -c .yamllint . && + echo '------------' && + echo -e && + yamllint -f parsable -c .yamllint . + """ + ) From 29de37db8e7acdcd87ddad5ff8528c009b84f7be Mon Sep 17 00:00:00 2001 From: Levon Becker Date: Tue, 11 Aug 2026 13:04:36 -0700 Subject: [PATCH 2/2] Organize tests/ into marker-named subfolders, add --scope to tests.pytest - tests/setup/test_setup_properties.py, tests/style/test_markdown_style.py -- moved from a flat tests/ directory into one subfolder per concern, each with pytestmark = pytest.mark. and an __init__.py. - pyproject.toml gained a markers list (setup, style) matching the folders. - tasks/tests/pytest.py gained a scope= param -- `invoke tests.pytest scope=setup` (or scope="not style") runs a marker-filtered subset instead of the whole suite. Same shape fireball_orchestrator's own tests/ already uses (one marker-named subfolder per concern, scope= for filtering). Generated with Claude Code Co-Authored-By: Claude Sonnet 5 --- pyproject.toml | 4 ++++ tasks/tests/pytest.py | 6 +++--- tests/setup/__init__.py | 0 tests/{ => setup}/test_setup_properties.py | 2 ++ tests/style/__init__.py | 0 tests/{ => style}/test_markdown_style.py | 6 +++++- 6 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 tests/setup/__init__.py rename tests/{ => setup}/test_setup_properties.py (99%) create mode 100644 tests/style/__init__.py rename tests/{ => style}/test_markdown_style.py (94%) diff --git a/pyproject.toml b/pyproject.toml index 5ce64d8..617d05a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,10 @@ docstring-code-line-length = "dynamic" [tool.pytest.ini_options] testpaths = ["tests"] norecursedirs = ["tmp", ".venv", ".ruff_cache", "__pycache__"] +markers = [ + "setup: modules/setup/ (properties.yml bootstrap) — tests/setup/", + "style: Markdown/doc style checks — tests/style/", +] [tool.pylint.main] py-version = "3.14" diff --git a/tasks/tests/pytest.py b/tasks/tests/pytest.py index 76853c5..5281f35 100644 --- a/tasks/tests/pytest.py +++ b/tasks/tests/pytest.py @@ -2,9 +2,9 @@ @task(name="pytest") -def run_pytest(context): - """Run Pytest Unit Test Suite""" +def run_pytest(context, scope=None): + """Run Pytest Unit Test Suite (pass scope= to run a subset, e.g. scope=setup or scope="not style")""" print("\n------------") print("Pytest") print("------------\n") - context.run("pytest") + context.run(f'pytest -m "{scope}"' if scope else "pytest") diff --git a/tests/setup/__init__.py b/tests/setup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_setup_properties.py b/tests/setup/test_setup_properties.py similarity index 99% rename from tests/test_setup_properties.py rename to tests/setup/test_setup_properties.py index ca8496e..6382890 100644 --- a/tests/test_setup_properties.py +++ b/tests/setup/test_setup_properties.py @@ -4,6 +4,8 @@ from modules.setup import properties as setup_props +pytestmark = pytest.mark.setup + def _template_lines() -> list[str]: return setup_props._build_initial_content().splitlines(keepends=True) # pylint: disable=protected-access diff --git a/tests/style/__init__.py b/tests/style/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_markdown_style.py b/tests/style/test_markdown_style.py similarity index 94% rename from tests/test_markdown_style.py rename to tests/style/test_markdown_style.py index b1a9b85..fc61ff3 100644 --- a/tests/test_markdown_style.py +++ b/tests/style/test_markdown_style.py @@ -8,7 +8,11 @@ from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parents[1] +import pytest + +pytestmark = pytest.mark.style + +REPO_ROOT = Path(__file__).resolve().parents[2] INSTRUCTIONS_DIR = REPO_ROOT / ".github" / "instructions"