From e507e0aa711179de502e614b068bd0d4dc558997 Mon Sep 17 00:00:00 2001 From: Levon Becker Date: Tue, 11 Aug 2026 13:04:57 -0700 Subject: [PATCH] Restructure tasks/ into ai/common/tests/ subpackages, organize tests/ Mirrors the same restructuring just done in fireball_orchestrator (a downstream consumer of this template), scoped to what actually exists here: - tasks/ai/{repo,template}.py -- tooling downstream repos use to operate on themselves (git/PR workflow, parent-template sync). Kept at their original top-level repo.*/template.* names. - tasks/common/{main,debug,ruff,setup,upgrade,uv,versioning}.py -- template_python-inherited boilerplate. main.py replaces combos.py (same fix/test/update aliases). Kept at their original top-level names too. - tasks/tests/ -- tasks/tests.py split into one file per check (actionlint, check_agents, pylint, pytest, rufflint, yamllint), still one flat tests.* namespace. check_agents.py now runs `pytest -m "agents"` instead of a hardcoded file path, so it doesn't go stale if that test file ever moves again. No CLI surface change from any of the above -- every task in this repo invokes identically to before. Also organized the actual pytest suite (tests/) into marker-named subfolders (agents/, setup/, style/), added a markers list to pyproject.toml, and gave tasks/tests/pytest.py a scope= param for marker-filtered subset runs (invoke tests.pytest scope=agents). Fixed each moved test file's REPO_ROOT parents[] index for the new nesting depth. README.md and .github/instructions/{index,tasks}.instructions.md updated to match, including documenting a real bug found while restructuring: Collection(auto_dash_names=...) must be a constructor kwarg, not a .configure({"auto_dash_names": False}) call (which silently does nothing). Companion to the template_python PR this repo forks from. Generated with Claude Code Co-Authored-By: Claude Sonnet 5 --- .github/instructions/index.instructions.md | 30 ++++++---- .github/instructions/tasks.instructions.md | 70 +++++++++++++++------- README.md | 34 ++++++----- pyproject.toml | 5 ++ tasks/__init__.py | 39 ++++++------ tasks/ai/__init__.py | 0 tasks/{ => ai}/repo.py | 0 tasks/{ => ai}/template.py | 0 tasks/common/__init__.py | 0 tasks/{ => common}/debug.py | 0 tasks/{combos.py => common/main.py} | 15 ++--- tasks/{ => common}/ruff.py | 0 tasks/{ => common}/setup.py | 0 tasks/{ => common}/upgrade.py | 0 tasks/{ => common}/uv.py | 0 tasks/{ => common}/versioning.py | 0 tasks/tests.py | 62 ------------------- tasks/tests/__init__.py | 23 +++++++ tasks/tests/actionlint.py | 10 ++++ tasks/tests/check_agents.py | 10 ++++ tasks/tests/pylint.py | 10 ++++ tasks/tests/pytest.py | 10 ++++ tasks/tests/rufflint.py | 10 ++++ tasks/tests/yamllint.py | 17 ++++++ tests/agents/__init__.py | 0 tests/{ => agents}/test_check_agents.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 +- 30 files changed, 220 insertions(+), 139 deletions(-) create mode 100644 tasks/ai/__init__.py rename tasks/{ => ai}/repo.py (100%) rename tasks/{ => ai}/template.py (100%) create mode 100644 tasks/common/__init__.py rename tasks/{ => common}/debug.py (100%) rename tasks/{combos.py => common/main.py} (61%) rename tasks/{ => common}/ruff.py (100%) rename tasks/{ => common}/setup.py (100%) rename tasks/{ => common}/upgrade.py (100%) rename tasks/{ => common}/uv.py (100%) rename tasks/{ => common}/versioning.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/check_agents.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 create mode 100644 tests/agents/__init__.py rename tests/{ => agents}/test_check_agents.py (93%) 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/.github/instructions/index.instructions.md b/.github/instructions/index.instructions.md index 5c5b8cf..ba4b3de 100644 --- a/.github/instructions/index.instructions.md +++ b/.github/instructions/index.instructions.md @@ -31,8 +31,10 @@ invoke.yml # Invoke config (auto_dash_names: false) setup.sh # Shell-based setup script (uv venv + uv sync) properties.yml # Project configuration (repo path/remote, template path/remote) template.ignore.yml # Paths /template pull/push must never touch (.gitignore handles the rest via git ls-files) -tests/ - test_check_agents.py # Verify .github/prompts/ mirrors are in sync, called by tests.check_agents +tests/ # One marker-named subfolder per concern (pass scope= to tests.pytest for a subset) + agents/ # test_check_agents.py — verify .github/prompts/ mirrors are in sync, called by tests.check_agents + setup/ # test_setup_properties.py — modules/setup/ (properties.yml bootstrap) + style/ # test_markdown_style.py — Markdown/doc style checks modules/ common/ # cli.py, properties.py, route_utils.py, utils.py — shared helpers repo/ # pull.py, push.py, squash.py, rebase.py, pr_*.py — git/PR workflow modules @@ -40,17 +42,19 @@ modules/ template/ # ignore.py, naming.py, pull.py, push.py, resolve.py, route.py, scope.py — sync shared tooling with the parent template repo for /template versioning/ # libs.py, python.py, workflows.py, upgrade.py, project.py — check pyproject.toml deps & workflow action refs vs. latest releases, bump the repo's VERSION file tasks/ - __init__.py # Wires the invoke Collection (debug, repo, ruff, setup, template, tests, upgrade, uv, versioning) plus top-level aliases (fix, test, update) - combos.py # Top-level aliases: fix, test, update - debug.py # debug.env — print cwd + sorted env vars - repo.py # repo.pull, repo.push, repo.squash, repo.rebase, repo.pr_diff, repo.pr_notes_save, repo.pr_create, repo.pr_cleanup - ruff.py # ruff.fix, ruff.format - setup.py # setup.properties — creates/stamps properties.yml - template.py # template.pull, template.pull_copy, template.push_diff, template.push_apply, template.push_create_pr - tests.py # tests.actionlint, tests.check_agents, tests.pylint, tests.pytest, tests.rufflint, tests.yamllint - upgrade.py # upgrade (default), upgrade.python, upgrade.libs, upgrade.sync — installs; run ver.update first - uv.py # uv.upgrade_bin, uv.upgrade_libs - versioning.py # ver.libs, ver.python, ver.workflows, ver.all, ver.update, ver.upgrade, ver.project_bump_build, ver.project_bump_release + __init__.py # Wires the invoke Collection: common/ and tests/ (registered at their original top-level names — debug, ruff, setup, tests, plus bare fix/test/update), ai/ (registered at repo, template — tooling downstream repos use to operate on themselves) + ai/ + repo.py # repo.pull, repo.push, repo.squash, repo.rebase, repo.pr_diff, repo.pr_notes_save, repo.pr_create, repo.pr_cleanup + template.py # template.pull, template.pull_copy, template.push_diff, template.push_apply, template.push_create_pr + common/ + main.py # Top-level aliases: fix, test, update (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 + upgrade.py # upgrade (default), upgrade.python, upgrade.libs, upgrade.sync — installs; run ver.update first + uv.py # uv.upgrade_bin, uv.upgrade_libs + versioning.py # ver.libs, ver.python, ver.workflows, ver.all, ver.update, ver.upgrade, ver.project_bump_build, ver.project_bump_release + tests/ # One file per check (actionlint.py, check_agents.py, pylint.py, pytest.py, rufflint.py, yamllint.py) — still one flat tests.* namespace .github/ instructions/ # Copilot instruction files prompts/ # Copilot prompt files (/push, /pull, /squash, /rebase, /fix, /test, /docs, /pr-notes, /pr, /pr-cleanup, /ship-it, /template, /update, /upgrade, /repo, /setup) — source of truth for slash commands diff --git a/.github/instructions/tasks.instructions.md b/.github/instructions/tasks.instructions.md index ed6e3f2..582fa58 100644 --- a/.github/instructions/tasks.instructions.md +++ b/.github/instructions/tasks.instructions.md @@ -14,12 +14,25 @@ here. ## File Location - All invoke task modules live under `tasks/` - `tasks/__init__.py` builds the root `Collection` — every new task module must be imported and wired there explicitly (no auto-glob loading) -- Group related tasks by concern: `tasks/repo.py`, `tasks/tests.py`, `tasks/ruff.py` +- Group related tasks by subpackage: `tasks/common/` (template_python-inherited boilerplate — + `main.py` for the bare `fix`/`test`/`update` aliases, `debug.py`, `ruff.py`, `setup.py`, + `upgrade.py`, `uv.py`, `versioning.py`), `tasks/ai/` (tooling downstream repos use to operate on + themselves — `repo.py`, `template.py`), `tasks/tests/` (one file per check). Every subpackage is + registered at its *original* top-level names (`debug.*`, `ruff.*`, `repo.*`, `template.*`, + `tests.*`, ...), not nested under a new `common.*`/`ai.*` prefix — the whole point of these + three folders is to organize files, not to change what any repo cloned from this template + actually types. ## Collection Conventions -- Sub-collections mirror file names: `tasks/repo.py` → `invoke repo.` -- Top-level alias tasks (no namespace) live in `tasks/combos.py` — short names (`test`, `fix`) -- Set `namespace.configure({"auto_dash_names": False})` so task names keep underscores +- Sub-collections mirror file/folder names: `tasks/ai/repo.py` → `invoke repo.` +- Top-level alias tasks (no namespace) live in `tasks/common/main.py` — short names (`test`, `fix`) +- **`auto_dash_names=False` is a `Collection()` *constructor* kwarg, not something + `.configure({"auto_dash_names": False})` actually controls** — the latter silently does nothing + for this. Every `Collection()` this repo constructs must pass `auto_dash_names=False` at + construction: `Collection(auto_dash_names=False)`. Harmless either way at the CLI (invoke + matches a typed `foo_bar` against a dash-registered `foo-bar` leniently), but `invoke -l`'s + displayed names should stay underscored to match how every task is actually documented and + typed here. ## Task Structure Pattern ```python @@ -36,34 +49,40 @@ def task_name(context): ``` ## Wiring a New Task Module +A single new file, registered directly on the root: ```python # tasks/__init__.py from invoke import Collection -from . import combos, my_new_module, repo, ruff, tests +from . import my_new_module -namespace = Collection() -namespace.configure({"auto_dash_names": False}) +namespace = Collection(auto_dash_names=False) namespace.add_collection(my_new_module, name="my_new_module") ``` +A new task joining one of the existing subpackages — import it from that subpackage's own +location (`tasks/ai/*.py`, `tasks/common/*.py`) and register it the same way; the subpackage +itself has no `main.py` of its own here (unlike a genuinely subject-named namespace such as an +`aws/` or `shopify/` folder might have in a downstream repo) since `ai`/`common` are just file +groupings, not new namespaces. + ## Alias Tasks -- Define combo/alias tasks in `tasks/combos.py`, calling sub-tasks directly: +- Define combo/alias tasks in `tasks/common/main.py`, calling sub-tasks directly: ```python @task def test(context): """Run All Tests""" - tests.actionlint(context) - tests.check_agents(context) - tests.pylint(context) - tests.pytest(context) - tests.rufflint(context) - tests.yamllint(context) + actionlint(context) + check_agents(context) + pylint(context) + pytest(context) + rufflint(context) + yamllint(context) ``` ## Calling Into `modules/` - Tasks that wrap git workflow logic (`repo.pull`, `repo.push`, etc.) should be thin wrappers that - import the module and call its `main()` — keep git/business logic in `modules/repo/*.py`, not in `tasks/repo.py` + import the module and call its `main()` — keep git/business logic in `modules/repo/*.py`, not in `tasks/ai/repo.py` - Unused `context` parameters (required by Invoke's `@task` signature) should be prefixed `_context` ## Task Reference @@ -75,22 +94,32 @@ namespace.add_collection(my_new_module, name="my_new_module") | Test | `uv run --no-sync invoke test` | Run all tests (actionlint + check_agents + pylint + pytest + ruff + yamllint) | ### Test Tasks +Lives in `tasks/tests/` — one file per check (`actionlint.py`, `check_agents.py`, `pylint.py`, +`pytest.py`, `rufflint.py`, `yamllint.py`), still registered as one flat `tests.*` namespace. +Pass `scope=` to `tests.pytest` to run a subset (e.g. `scope=agents`, `scope=setup`, +`scope="not style"`) — matches the pytest marker each `tests//` corresponds to. + | Task | Command | Description | |------|---------|-------------| | actionlint | `uv run --no-sync invoke tests.actionlint` | GitHub Actions workflow validation | -| check_agents | `uv run --no-sync invoke tests.check_agents` | Verify `.github/prompts/` is mirrored into `.claude/commands/`, `.claude/skills/`, and `.clinerules/workflows/` (`pytest tests/test_check_agents.py`) | +| check_agents | `uv run --no-sync invoke tests.check_agents` | Verify `.github/prompts/` is mirrored into `.claude/commands/`, `.claude/skills/`, and `.clinerules/workflows/` (`pytest -m "agents"`, i.e. `tests/agents/`) | | pylint | `uv run --no-sync invoke tests.pylint` | Python code quality | | pytest | `uv run --no-sync invoke tests.pytest` | Python unit test suite (`tests/`) | | rufflint | `uv run --no-sync invoke tests.rufflint` | Python linting and formatting | | yamllint | `uv run --no-sync invoke tests.yamllint` | YAML file validation | ### Ruff Tasks +Lives in `tasks/common/ruff.py` (template_python-inherited, kept at its original `ruff.*` name). + | Task | Command | Description | |------|---------|-------------| | fix | `uv run --no-sync invoke ruff.fix` | Auto-fix ruff lint issues | | format | `uv run --no-sync invoke ruff.format` | Auto-format Python code | ### Upgrade Tasks +Lives in `tasks/common/upgrade.py` (template_python-inherited, kept at its original `upgrade.*` +name). + | Task | Command | Description | |------|---------|-------------| | libs | `uv run --no-sync invoke upgrade.libs` | Upgrade libraries only | @@ -99,10 +128,11 @@ namespace.add_collection(my_new_module, name="my_new_module") | upgrade | `uv run --no-sync invoke upgrade` | Upgrade Python + all dependencies (default; explicit form: `upgrade.all`) | ### Versioning Tasks -Read-only version-lock *checks* — compare `pyproject.toml` deps and `.github/workflows/` action -refs against latest releases and update the version locks in place (does not install anything; -see Upgrade Tasks above for that). See `.github/instructions/versioning.instructions.md` for the -module behavior behind these. +Lives in `tasks/common/versioning.py` (template_python-inherited, kept at its original `ver.*` +name — registered as `ver`, not `versioning`). Read-only version-lock *checks* — compare +`pyproject.toml` deps and `.github/workflows/` action refs against latest releases and update the +version locks in place (does not install anything; see Upgrade Tasks above for that). See +`.github/instructions/versioning.instructions.md` for the module behavior behind these. | Task | Command | Description | |------|---------|-------------| diff --git a/README.md b/README.md index 051a24f..6e512c4 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,10 @@ invoke.yml # Invoke config (auto_dash_names: false) setup.sh # Shell-based setup script (uv venv + uv sync) properties.yml # Project configuration (repo path/remote, template path/remote) template.ignore.yml # Paths /template pull/push must never touch (.gitignore handles the rest via git ls-files) -tests/ - test_check_agents.py # Verify .github/prompts/ mirrors are in sync, called by tests.check_agents +tests/ # One marker-named subfolder per concern (pass scope= to tests.pytest for a subset) + agents/ # test_check_agents.py — verify .github/prompts/ mirrors are in sync, called by tests.check_agents + setup/ # test_setup_properties.py — modules/setup/ (properties.yml bootstrap) + style/ # test_markdown_style.py — Markdown/doc style checks modules/ common/ # cli.py, properties.py, route_utils.py, utils.py — shared helpers repo/ # pull.py, push.py, squash.py, rebase.py, pr_*.py — git/PR workflow modules @@ -31,17 +33,19 @@ modules/ template/ # ignore.py, naming.py, pull.py, push.py, resolve.py, route.py, scope.py — sync shared tooling with the parent template repo for /template versioning/ # libs.py, python.py, workflows.py, upgrade.py, project.py — check pyproject.toml deps & workflow action refs vs. latest releases, bump the repo's VERSION file tasks/ - __init__.py # Wires the invoke Collection (debug, repo, ruff, setup, template, tests, upgrade, uv, versioning) plus top-level aliases (fix, test, update) - combos.py # Top-level aliases: fix, test, update - debug.py # debug.env — print cwd + sorted env vars - repo.py # repo.pull, repo.push, repo.squash, repo.rebase, repo.pr_diff, repo.pr_notes_save, repo.pr_create, repo.pr_cleanup - ruff.py # ruff.fix, ruff.format - setup.py # setup.properties — creates/stamps properties.yml - template.py # template.pull, template.pull_copy, template.push_diff, template.push_apply, template.push_create_pr - tests.py # tests.actionlint, tests.check_agents, tests.pylint, tests.pytest, tests.rufflint, tests.yamllint - upgrade.py # upgrade (default), upgrade.python, upgrade.libs, upgrade.sync — installs; run ver.update first - uv.py # uv.upgrade_bin, uv.upgrade_libs - versioning.py # ver.libs, ver.python, ver.workflows, ver.all, ver.update, ver.upgrade, ver.project_bump_build, ver.project_bump_release + __init__.py # Wires the invoke Collection: common/ and tests/ (registered at their original top-level names), ai/ (registered at repo, template — tooling downstream repos use to operate on themselves) + ai/ + repo.py # repo.pull, repo.push, repo.squash, repo.rebase, repo.pr_diff, repo.pr_notes_save, repo.pr_create, repo.pr_cleanup + template.py # template.pull, template.pull_copy, template.push_diff, template.push_apply, template.push_create_pr + common/ + main.py # Top-level aliases: fix, test, update (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 + upgrade.py # upgrade (default), upgrade.python, upgrade.libs, upgrade.sync — installs; run ver.update first + uv.py # uv.upgrade_bin, uv.upgrade_libs + versioning.py # ver.libs, ver.python, ver.workflows, ver.all, ver.update, ver.upgrade, ver.project_bump_build, ver.project_bump_release + tests/ # One file per check (actionlint.py, check_agents.py, pylint.py, pytest.py, rufflint.py, yamllint.py) — still one flat tests.* namespace .github/ instructions/ # Copilot instruction files prompts/ # /push, /pull, /squash, /rebase, /fix, /test, /docs, /pr-notes, /pr, /pr-cleanup, /ship-it, /template, /update, /upgrade, /repo, /setup — source of truth @@ -124,8 +128,8 @@ uv run --no-sync invoke setup.properties # Create/stamp properties.yml | [`modules/template/`](modules/template/README.md) | Sync shared, generic tooling with the parent template repo for `/template` | | [`modules/versioning/`](modules/versioning/README.md) | Check `pyproject.toml` deps and workflow action refs vs. latest releases, update locks; bump the repo's `VERSION` file for deploys/releases | -`tests/test_check_agents.py` (not a `modules/` package) verifies `.github/prompts/` is mirrored -into `.claude/commands/`, `.claude/skills/`, and `.clinerules/workflows/` — run via +`tests/agents/test_check_agents.py` (not a `modules/` package) verifies `.github/prompts/` is +mirrored into `.claude/commands/`, `.claude/skills/`, and `.clinerules/workflows/` — run via `uv run --no-sync invoke tests.check_agents`. See [modules/README.md](modules/README.md) for full details. diff --git a/pyproject.toml b/pyproject.toml index 8b6c589..e242a42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,11 @@ docstring-code-line-length = "dynamic" [tool.pytest.ini_options] testpaths = ["tests"] norecursedirs = ["tmp", ".venv", ".ruff_cache", "__pycache__", "addons"] +markers = [ + "agents: .github/prompts/ mirror consistency — tests/agents/", + "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/__init__.py b/tasks/__init__.py index b382700..81a3f9e 100644 --- a/tasks/__init__.py +++ b/tasks/__init__.py @@ -9,32 +9,31 @@ 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, - repo, - ruff, - setup, - template, - tests, - upgrade, - uv, - versioning, -) +from .ai import repo, template # noqa: E402 # pylint: disable=wrong-import-position +from .common import debug, ruff, setup, upgrade, uv, versioning # 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/` groups files inherited from template_python (shared boilerplate every repo cloned +# from that template carries) — registered at the exact same top-level names every such repo +# already uses (`debug.*`, `ruff.*`, `setup.*`, `tests.*`, plus bare `fix`/`test`), not nested +# under `common.*`. namespace.add_collection(debug, name="debug") -namespace.add_collection(repo, name="repo") namespace.add_collection(ruff, name="ruff") namespace.add_collection(setup, name="setup") -namespace.add_collection(template, name="template") -namespace.add_collection(tests, name="tests") namespace.add_collection(upgrade, name="upgrade") namespace.add_collection(uv, name="uv") namespace.add_collection(versioning, name="ver") +namespace.add_collection(tests_namespace, name="tests") + +namespace.add_task(common_main.fix, name="fix") +namespace.add_task(common_main.test, name="test") +namespace.add_task(common_main.update, name="update") -namespace.add_task(combos.fix, name="fix") -namespace.add_task(combos.test, name="test") -namespace.add_task(combos.update, name="update") +# `ai/` groups tooling downstream repos (this template's own children) use to operate on +# themselves — git/PR workflow (`repo`) and parent-template sync (`template`, this repo's own +# link back to template_python). Same "keep the original top-level name" treatment as `common/`. +namespace.add_collection(repo, name="repo") +namespace.add_collection(template, name="template") diff --git a/tasks/ai/__init__.py b/tasks/ai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tasks/repo.py b/tasks/ai/repo.py similarity index 100% rename from tasks/repo.py rename to tasks/ai/repo.py diff --git a/tasks/template.py b/tasks/ai/template.py similarity index 100% rename from tasks/template.py rename to tasks/ai/template.py 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/combos.py b/tasks/common/main.py similarity index 61% rename from tasks/combos.py rename to tasks/common/main.py index 2498a53..ecfdd87 100644 --- a/tasks/combos.py +++ b/tasks/common/main.py @@ -1,6 +1,7 @@ from invoke import task -from . import ruff, tests, versioning +from ..tests import actionlint, check_agents, pylint, pytest, rufflint, yamllint +from . import ruff, versioning @task @@ -13,12 +14,12 @@ def fix(context): @task def test(context): """Run All Tests""" - tests.actionlint(context) - tests.check_agents(context) - tests.pylint(context) - tests.pytest(context) - tests.rufflint(context) - tests.yamllint(context) + actionlint(context) + check_agents(context) + pylint(context) + pytest(context) + rufflint(context) + yamllint(context) @task 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/upgrade.py b/tasks/common/upgrade.py similarity index 100% rename from tasks/upgrade.py rename to tasks/common/upgrade.py diff --git a/tasks/uv.py b/tasks/common/uv.py similarity index 100% rename from tasks/uv.py rename to tasks/common/uv.py diff --git a/tasks/versioning.py b/tasks/common/versioning.py similarity index 100% rename from tasks/versioning.py rename to tasks/common/versioning.py diff --git a/tasks/tests.py b/tasks/tests.py deleted file mode 100644 index 4e3733c..0000000 --- a/tasks/tests.py +++ /dev/null @@ -1,62 +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 check_agents(context): - """Verify .github/prompts/ is mirrored into .claude/commands/, .claude/skills/, and .clinerules/workflows/""" - print("\n------------") - print("Check Agents") - print("------------\n") - context.run("pytest tests/test_check_agents.py") - - -@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..971f46e --- /dev/null +++ b/tasks/tests/__init__.py @@ -0,0 +1,23 @@ +"""`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 .check_agents import check_agents +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(check_agents, name="check_agents") +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/check_agents.py b/tasks/tests/check_agents.py new file mode 100644 index 0000000..64caaed --- /dev/null +++ b/tasks/tests/check_agents.py @@ -0,0 +1,10 @@ +from invoke import task + + +@task +def check_agents(context): + """Verify .github/prompts/ is mirrored into .claude/commands/, .claude/skills/, and .clinerules/workflows/""" + print("\n------------") + print("Check Agents") + print("------------\n") + context.run('pytest -m "agents"') 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..b061c78 --- /dev/null +++ b/tasks/tests/pytest.py @@ -0,0 +1,10 @@ +from invoke import task + + +@task(name="pytest") +def run_pytest(context, scope=None): + """Run Pytest Unit Test Suite (pass scope= to run a subset, e.g. scope=agents or scope="not style")""" + print("\n------------") + print("Pytest") + print("------------\n") + context.run(f'pytest -m "{scope}"' if scope else "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 . + """ + ) diff --git a/tests/agents/__init__.py b/tests/agents/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_check_agents.py b/tests/agents/test_check_agents.py similarity index 93% rename from tests/test_check_agents.py rename to tests/agents/test_check_agents.py index 89a12db..8826bcf 100644 --- a/tests/test_check_agents.py +++ b/tests/agents/test_check_agents.py @@ -4,7 +4,11 @@ from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parents[1] +import pytest + +pytestmark = pytest.mark.agents + +REPO_ROOT = Path(__file__).resolve().parents[2] def _prompt_slugs() -> set[str]: 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"