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
30 changes: 17 additions & 13 deletions .github/instructions/index.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,30 @@ 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=<marker> 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
setup/ # properties.py — creates properties.yml (no-op if it exists), called by setup.sh/setup.ps1; templates/properties/*.yml — tier fragments
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
Expand Down
70 changes: 50 additions & 20 deletions .github/instructions/tasks.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<task>`
- 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.<task>`
- 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
Expand All @@ -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
Expand All @@ -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=<marker>` to `tests.pytest` to run a subset (e.g. `scope=agents`, `scope=setup`,
`scope="not style"`) — matches the pytest marker each `tests/<folder>/` 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 |
Expand All @@ -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 |
|------|---------|-------------|
Expand Down
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,30 @@ 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=<marker> 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
setup/ # properties.py — creates properties.yml (no-op if it exists), called by setup.sh/setup.ps1; templates/properties/*.yml — tier fragments
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
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
39 changes: 19 additions & 20 deletions tasks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Empty file added tasks/ai/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
Empty file added tasks/common/__init__.py
Empty file.
File renamed without changes.
15 changes: 8 additions & 7 deletions tasks/combos.py → tasks/common/main.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading