diff --git a/.github/instructions/index.instructions.md b/.github/instructions/index.instructions.md index 38e8470..dc9dc2e 100644 --- a/.github/instructions/index.instructions.md +++ b/.github/instructions/index.instructions.md @@ -14,7 +14,7 @@ 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) modules/ - common/ # cli.py, properties.py, utils.py — shared helpers + common/ # cli.py, properties.py, route_utils.py, utils.py — shared helpers repo/ # pull.py, push.py, log.py, squash.py, rebase.py, pr_*.py — git/PR workflow modules setup/ # properties.py — creates/stamps properties.yml, called by setup.sh/setup.ps1 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 @@ -35,7 +35,7 @@ tasks/ .github/ instructions/ # Copilot instruction files prompts/ # Copilot prompt files (/push, /pull, /squash, /rebase, /fix, /test, /docs, /pr-notes, /pr, /pr-cleanup, /punch-it-chewy, /template, /update, /upgrade, /repo, /setup) — source of truth for slash commands - workflows/ # tests.yml (reusable), feature_branches.yml, protected_branches.yml + workflows/ # tests.yml — ruff + pylint + yamllint + actionlint + check_agents, on PRs to development/main .claude/ commands/ # Claude Code slash commands, hand-maintained mirror of .github/prompts/ (see prompts.instructions.md) skills/ # Claude Code skills (auto-discovered), hand-maintained mirror of .github/prompts/ (see prompts.instructions.md) diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md index ec4d455..f55e532 100644 --- a/.github/instructions/modules.instructions.md +++ b/.github/instructions/modules.instructions.md @@ -52,6 +52,7 @@ def main() -> None: |--------|----------| | `cli.py` | Click-like `echo`, `prompt`, `confirm`, `is_tty`, `command`/`option` decorators | | `properties.py` | Read `properties.yml` — `get_repo_local()`, `get_repo_remote()`, `get_template_local()`, `get_template_remote()` | +| `route_utils.py` | AI-tool command routers (`modules/*/route.py`) — `find_repo_root()` (`properties.yml`-anchored, falls back to `cwd()`, no exception), `build_env()` | | `utils.py` | `success()`, `error()`, `warning()`, `info()`, `create_slug()` | ## Guidelines diff --git a/.github/instructions/tasks.instructions.md b/.github/instructions/tasks.instructions.md index ea87a7b..5d845cd 100644 --- a/.github/instructions/tasks.instructions.md +++ b/.github/instructions/tasks.instructions.md @@ -94,7 +94,7 @@ namespace.add_collection(my_new_module, name="my_new_module") | libs | `uv run --no-sync invoke upgrade.libs` | Upgrade libraries only | | python | `uv run --no-sync invoke upgrade.python` | Upgrade Python only | | sync | `uv run --no-sync invoke upgrade.sync` | Sync dependencies (no version check) | -| upgrade | `uv run --no-sync invoke upgrade.upgrade` | Upgrade Python + all dependencies (default) | +| 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 @@ -115,7 +115,7 @@ module behavior behind these. | Fix code style | `uv run --no-sync invoke fix` | | Run all tests | `uv run --no-sync invoke test` | | Run one linter | `uv run --no-sync invoke tests.pylint` | -| Upgrade everything | `uv run --no-sync invoke upgrade.upgrade` | +| Upgrade everything | `uv run --no-sync invoke upgrade` | | Run a module | `uv run --no-sync python -m modules.chat.start --title="..."` | | Test a route | `uv run --no-sync python -m modules.chat.route "start my chat"` | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d83bf8c..25530a2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,9 +3,10 @@ name: Tests run-name: "Tests (${{ github.head_ref || github.ref_name }})" on: - push: + pull_request: branches: - - main # Trigger on pushes to the main branch + - development # Trigger on PRs targeting development + - main # Trigger on PRs targeting main jobs: # https://github.com/rhysd/actionlint diff --git a/README.md b/README.md index 4b07619..915e0f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Repo Setup (Python) +# Template Python [![Tests](https://github.com/LevonBecker/template_python/actions/workflows/tests.yml/badge.svg)](https://github.com/LevonBecker/template_python/actions/workflows/tests.yml) Skeleton setup for a repository that uses Python invoke tasks, reusable modules, uv for @@ -7,7 +7,8 @@ project to get a consistent git workflow, linting, and session logging out of th ## Setup ```bash -./setup.sh +./setup.sh # macOS/Linux +.\setup.ps1 # Windows (PowerShell) ``` Creates a `.venv` with `uv` and installs dependencies. Update `properties.yml` with the local repo @@ -21,7 +22,7 @@ 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) modules/ - common/ # cli.py, properties.py, utils.py — shared helpers + common/ # cli.py, properties.py, route_utils.py, utils.py — shared helpers repo/ # pull.py, push.py, log.py, squash.py, rebase.py, pr_*.py — git/PR workflow modules setup/ # properties.py — creates/stamps properties.yml, called by setup.sh/setup.ps1 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 @@ -43,9 +44,7 @@ tasks/ instructions/ # Copilot instruction files prompts/ # /push, /pull, /squash, /rebase, /fix, /test, /docs, /pr-notes, /pr, /pr-cleanup, /punch-it-chewy, /template, /update, /upgrade, /repo, /setup — source of truth workflows/ - tests.yml # Reusable CI: ruff + pylint + yamllint + actionlint + check_agents - feature_branches.yml # Runs tests.yml on pull_request - protected_branches.yml # Runs tests.yml on push to main + tests.yml # CI: ruff + pylint + yamllint + actionlint + check_agents, on PRs to development/main .claude/ commands/ # Claude Code slash commands, hand-maintained mirror of .github/prompts/ skills/ # Claude Code skills (auto-discovered), hand-maintained mirror of .github/prompts/ @@ -144,6 +143,6 @@ they aren't meant to lint clean from this repo's root. | [`addons/shopfiy_dawn_theme/`](addons/shopfiy_dawn_theme/) | `dawn.list` / `dawn.upgrade` invoke tasks for tracking and staging upstream Shopify/dawn theme upgrades | `modules/dawn/` (`__init__.py`, `list.py`, `upgrade.py`, `README.md`), `tasks/dawn.py`, `.github/workflows/{deploy,promote,release,tests,upgrade}.yml`, `.github/instructions/dawn.instructions.md`, `.github/prompts/dawn.prompt.md`, `.claude/commands/dawn.md` | ## CI -GitHub Actions runs Ruff, Pylint, yamllint, actionlint, and check_agents on every push and pull request via -`.github/workflows/tests.yml` (invoked by `feature_branches.yml` and `protected_branches.yml`). +GitHub Actions runs Ruff, Pylint, yamllint, actionlint, and check_agents on every pull request +targeting `development` or `main` via `.github/workflows/tests.yml`. diff --git a/modules/common/README.md b/modules/common/README.md index 39fa584..b72d490 100644 --- a/modules/common/README.md +++ b/modules/common/README.md @@ -6,24 +6,23 @@ Shared utilities and helper functions used across all modules in the template_py This module provides common functionality that is used by other modules throughout the repository, including configuration parsing and utility functions. -## Modules - -### `utils.py` - -Common utility functions for console output, error handling, and shared operations. - -**Functions:** -- `success(message)` - Print success messages with ✅ emoji -- `error(message)` - Print error messages with ❌ emoji -- `warning(message)` - Print warning messages with ⚠️ emoji -- `info(message)` - Print info messages with ℹ️ emoji +## Files + +- `utils.py` — console output helpers: `success()`, `error()`, `warning()`, `info()` (✅/❌/⚠️/ℹ️ + prefixed), plus `create_slug()` +- `properties.py` — reads `properties.yml`: `get_repo_root()`, `get_repo_local()`, + `get_repo_remote()`, `get_template_local()`, `get_template_remote()` +- `cli.py` — Click-like CLI compatibility helpers backed by `argparse` (`echo`, `secho`, `prompt`, + `confirm`, `is_tty`, `Choice`, `command`/`option` decorators) — TUI-safe, no external dependency +- `route_utils.py` — shared helpers for the `/repo`- and `/template`-style AI-tool command + routers: `find_repo_root()` (walks upward for `properties.yml`), `build_env()` +- `README.md` — this file ## Dependencies This module depends on: -- `common.properties` - For reading configuration from `properties.yml` -- Standard library: `pathlib`, `shutil` -- Internal CLI helper: `modules/common/cli.py` (TUI-safe prompt/confirm/option handling) +- Standard library: `pathlib`, `argparse`, `subprocess` +- `pyyaml` — for reading `properties.yml` ## Architecture diff --git a/modules/repo/README.md b/modules/repo/README.md index 5b9f5cc..4034e4b 100644 --- a/modules/repo/README.md +++ b/modules/repo/README.md @@ -1,169 +1,44 @@ -# Repo Manager Agent +# Repo Module -The repo module handles repository-level operations including git synchronization. - -## Features - -- **Repository Push/Pull**: Full git operations -- **Automated Commits**: Timestamp-based commit messages -- **Error Handling**: Graceful handling of push/pull failures - -## Commands - -### `/repo push` (alias: `/push`) -Push changes to git remote from any location. - -**Workflow:** -1. **Navigate to repository root** - Automatically changes to repo directory -2. **Git pull** - Fetch and merge latest changes from remote -3. **Check for local changes** - Identify uncommitted work with `git status` -4. **Stage all changes** - Run `git add .` if changes detected -5. **Commit with timestamp** - Create commit: `Push repository: Automated commit YYYY-MM-DD HH:MM:SS` -6. **Push to remote** - Upload changes to GitHub -7. **Confirm completion** - Show status summary - -**Features:** -- ✅ Works from any directory in the repository -- ✅ Automatic git operations (pull, commit, push) -- ✅ Timestamp-based commit messages -- ✅ Error handling for each push step - -**When to Use:** -- **End of session** - Push all work before closing -- **After making changes** - Save work to remote -- **Regular backups** - Keep work synchronized and safe -- **After major changes** - Backup important work immediately - -**Error Handling:** -- Git pull failure: Stops execution -- Git push failure: Stops execution with error - -**Module:** `modules.repo.push` (via `/repo push`) - -### `/repo pull` (alias: `/pull`) -Pull updates from git remote from any location. - -**Workflow:** -1. **Check working directory** - Stash local changes if the working tree is dirty -2. **Git pull** - Fetch and merge latest changes from remote (rebase, to handle diverged history) -3. **Restore stash** - Restore stashed changes, if any were made - -**Features:** -- ✅ Works from any directory in the repository -- ✅ Stashes and restores local changes automatically around the pull -- ✅ Rebases onto latest remote history instead of merge-committing - -**When to Use:** -- **Start of session** - Get latest from git remote -- **After making changes on another device** - Pull updates to local -- **Before starting work** - Ensure you have latest version - -**Error Handling:** -- Stash failure: Stops execution -- Git pull failure: Stops execution; restores the stash first if one was made - -**Module:** `modules.repo.pull` (via `/repo pull`) - -## Branch Maintenance - -### `/rebase` -Rebase the current branch onto the remote default branch (`origin/main` or `origin/master`). -Optionally offers to run `/squash` first. Handles stashing local changes and interactive conflict -resolution (ours/theirs/manual) if restoring the stash conflicts. - -**Module:** `modules.repo.rebase` (via `/rebase`) - -### `/squash` -Anchored squash of every commit down to the repository's root commit into one commit, with an -auto-generated bulleted message. Prompts to review the message, confirm the (irreversible) local -squash, and optionally force-push (`--force-with-lease`). - -**Module:** `modules.repo.squash` (via `/squash`) - -### `/pr-cleanup` -Cleans up after a PR has been merged on GitHub: confirms the current branch's PR is merged (`gh pr -view`), switches to the detected default branch, pulls latest, then force-deletes the local -feature branch (`git branch -D` — safe here since the merge was already confirmed via GitHub, -which handles squash merges that `git branch -d` would otherwise reject as "not fully merged"). - -**Module:** `modules.repo.pr_cleanup` (via `/pr-cleanup`) - -## Git Integration - -### Push/Pull Operations - -**Pull Sequence:** -```bash -git status --porcelain # Check for uncommitted changes, stash if dirty -git pull --rebase # Fetch and rebase onto remote -git stash pop # Restore stashed changes, if any -``` - -**Push Sequence:** -```bash -git pull # Fetch and merge from remote -git status --porcelain # Check for changes -git add . # Stage all changes -git commit -m "Push repository: Automated commit 2025-12-11 01:30:45" -git push # Upload to remote -``` - -**Commit Message Format:** -``` -Push repository: Automated commit YYYY-MM-DD HH:MM:SS -``` - -## Configuration - -Default settings in `config.yml`: -- `repo_path`: "${repo_local}" - -## Permissions Required - -- `git_pull` - Pull from remote repository -- `git_push` - Push to remote repository -- `git_commit` - Create commits -- `git_status` - Check repository status -- `directory_access` - Navigate repository directories -- `bash_execute` - Run shell scripts - -## Dependencies - -- `git` - Version control operations - -## Best Practices - -1. **Pull/Push Regularly**: Run `/repo pull` at start, `/repo push` at end of sessions -2. **Check Status**: Review git status before push if concerned about changes -3. **Network Aware**: Git operations require network connectivity -4. **Backup Important**: Always push before major changes or deletions - -## Workflow Examples - -**Cross-Device Workflow:** -``` -Device A: -1. /repo pull # Pull latest before starting - [work on content] -2. /repo push # Push changes to remote - -Device B (later): -1. /repo pull # Pull changes from Device A -``` - -## Related Commands - -- `/repo push` - Push all commits to remote (alias: `/push`) -- `/repo pull` - Pull latest changes from remote (alias: `/pull`) +Git workflow and session-logging logic behind the `repo.*` invoke tasks and their `/push`, +`/pull`, `/squash`, `/rebase`, `/pr-notes`, `/pr`, `/pr-cleanup`, and `/repo` slash-command +counterparts. Every file exposes a `main()` entry point, runnable standalone via +`python -m modules.repo.`. ## Files -- `push.py` - Push to git (used by `/repo push`, alias `/push`) -- `pull.py` - Pull from git (used by `/repo pull`, alias `/pull`) -- `config.yml` - Agent configuration -- `README.md` - This file - -## Command Aliases - -- `/push` → Push to git -- `/pull` → Pull from git +- `pull.py` — stash local changes if dirty, `git pull --rebase`, restore the stash + (`repo.pull` / `/pull`) +- `push.py` — pull, stage, commit with a timestamped message, and push the primary tracked branch + (`repo.push` / `/push`) +- `pr_push.py` — push the current **feature** branch (fix → test → commit → push), for branches + that may not have upstream tracking yet; used by `/punch-it-chewy` ahead of opening a PR +- `log.py` — save a session log markdown file to `logs/` (`repo.log`) +- `squash.py` — anchored squash of every commit down to the root commit, with an auto-generated + message and optional force-push (`repo.squash` / `/squash`) +- `rebase.py` — rebase onto the remote default branch, optionally squashing first, with stash and + conflict handling (`repo.rebase` / `/rebase`) +- `pr_diff.py` — detect a PR's base branch and print commit log/diff context vs. that base; also + exposes `PROTECTED_BRANCHES`, `current_branch()`, and `detect_base_branch()` shared by the other + `pr_*`/`push.py`/`pr_cleanup.py` modules (`repo.pr_diff`) +- `pr_notes.py` — save PR notes markdown to `tmp/pull_requests/` (`repo.pr_notes_save`) +- `pr_create.py` — open a GitHub Pull Request for the current branch via `gh` (`repo.pr_create` + / `/pr`) +- `pr_cleanup.py` — confirm the current branch's PR is merged (`gh pr view`), switch to the + detected default branch, pull latest, then force-delete the local feature branch + (`git branch -D` — safe here since the merge was already confirmed via GitHub, which handles + squash merges that `git branch -d` would otherwise reject as "not fully merged") + (`repo.pr_cleanup` / `/pr-cleanup`) +- `route.py` — routes `/repo ...` arguments to the module above matching that + subcommand (`push`, `pull`, `pr_diff`, `pr_notes`, `pr_create`, `pr_push`, `pr_cleanup`, + `rebase`, `squash`) +- `README.md` — this file + +## Conventions + +- Shell out via `subprocess.run(..., cwd=repo_path)` — never `shell=True` +- Resolve the repo path via `modules.common.properties.get_repo_local()` +- Report outcomes via `modules.common.utils` (`success`/`error`/`warning`) +- `PROTECTED_BRANCHES` (`development`, `develop`, `main`, `master`) is defined once in + `pr_diff.py` and imported wherever a module needs to distinguish a feature branch from the + primary tracked branch diff --git a/modules/tests/check_agents.py b/modules/tests/check_agents.py index 53dbc35..c548463 100644 --- a/modules/tests/check_agents.py +++ b/modules/tests/check_agents.py @@ -2,12 +2,28 @@ from __future__ import annotations +import os from pathlib import Path from ..common import cli as click -from ..common.properties import get_repo_local +from ..common.route_utils import find_repo_root from ..common.utils import error, success +# This file's fixed depth below the repo root (modules/tests/check_agents.py -> repo root). +_REPO_ROOT_DEPTH = 2 + + +def _resolve_repo_root() -> Path: + """Return the repo root. + + In CI, properties.yml (the anchor find_repo_root() searches for) is gitignored and never + created, so resolve via this file's fixed position relative to the repo root instead. + Locally, prefer find_repo_root() so the check still works when invoked from a subdirectory. + """ + if os.environ.get("CI"): + return Path(__file__).resolve().parents[_REPO_ROOT_DEPTH] + return find_repo_root() + def _prompt_names(repo_path: Path) -> set[str]: """Return the command names sourced from .github/prompts/*.prompt.md.""" @@ -49,7 +65,7 @@ def _report_diff(label: str, expected: set[str], actual: set[str]) -> bool: @click.command() def main() -> None: """Verify every .github/prompts/*.prompt.md has a matching mirror in the other three command dirs.""" - repo_path = get_repo_local() + repo_path = _resolve_repo_root() prompts = _prompt_names(repo_path) click.echo(f"Checking {len(prompts)} prompt(s) are mirrored into commands, skills, and clinerules...")