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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Run tests
run: |
if [ "${{ matrix.python-version }}" = "3.11" ]; then
uv run pytest -q --no-testmon --cov --cov-report=xml --cov-report=term-missing -p no:cacheprovider
uv run pytest -q --no-testmon --cov=scripts --cov-report=xml --cov-report=term-missing -p no:cacheprovider
else
uv run pytest -q --no-testmon -p no:cacheprovider
fi
Expand Down
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ Thumbs.db
!SECURITY.md
!CODE_OF_CONDUCT.md
!CONTRIBUTING.md
!TESTING_STREAMLIT.md

*.backup
*.py.backup
Expand Down Expand Up @@ -233,7 +232,6 @@ docker-compose.override.yml

# Stray root files
1
/1

tasks/
task_channel/
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ repos:

- id: ruff-format
name: Ruff Format
entry: uv run ruff format --quiet
entry: uv run --active ruff format --quiet
language: system
types: [python]
pass_filenames: false
stages: [pre-commit]

- id: ruff-check
name: Ruff Lint (auto-fix)
entry: uv run ruff check --fix --exit-non-zero-on-fix --quiet
entry: uv run --active ruff check --fix --exit-non-zero-on-fix --quiet
language: system
types: [python]
pass_filenames: false
stages: [pre-commit]

- id: basedpyright
name: BasedPyright (type check)
entry: uv run basedpyright
entry: uv run --active basedpyright
language: system
types: [python]
pass_filenames: false
Expand Down
10 changes: 6 additions & 4 deletions copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ package_name:
default: "{{ project_name | lower | replace(' ', '_') | replace('-', '_') }}"
help: Python package name (must be valid Python identifier)
validator: >-
{% if not package_name.replace('_', '').isalnum()
or (package_name | length > 0 and package_name[0].isdigit()) %}
Package name must be a valid Python identifier (alphanumeric and underscores only; cannot start with a digit)
{% if not package_name.isidentifier()
or not package_name.replace('_', '').isalnum() %}
Package name must be a valid Python identifier (letters, digits, underscores; cannot start with a digit)
{% endif %}

project_description:
Expand Down Expand Up @@ -158,6 +158,8 @@ current_year:
when: false
default: "{% now 'utc', '%Y' %}"

# Renders as a JSON array string, e.g. '["3.11", "3.12", "3.13"]'. In workflow templates,
# use fromJson() when passing to a matrix (e.g. matrix.python: ${{ fromJson(...) }}).
github_actions_python_versions:
type: str
when: false
Expand Down Expand Up @@ -226,7 +228,7 @@ _tasks:
'
- command: uv lock
- command: >-
uv sync --frozen --extra dev --extra test
uv sync --extra dev --extra test
{%- if include_docs %} --extra docs{% endif -%}
{%- if include_git_cliff %} --group changelog{% endif %}
- command: uv run pre-commit install || true
Expand Down
34 changes: 27 additions & 7 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,14 @@ test-failed-verbose:
coverage:
@uv run pytest tests/ \
--no-testmon \
--cov \
--cov=scripts \
--cov-report=term-missing \
--cov-report=html \
--cov-report=xml

# Test command matching GitHub CI (3.11 matrix leg in .github/workflows/tests.yml)
test-ci:
@uv run pytest -q --no-testmon --cov --cov-report=xml --cov-report=term-missing -p no:cacheprovider
@uv run pytest -q --no-testmon --cov=scripts --cov-report=xml --cov-report=term-missing -p no:cacheprovider

# Full tests.yml matrix (3.11 with coverage; 3.12/3.13 with pytest -q only).
# 3.11 uses the default project .venv (same as `test-ci`). 3.12/3.13 use
Expand All @@ -144,7 +144,7 @@ test-ci-matrix:
echo "=== Python 3.11 + coverage (tests.yml matrix) ==="
unset UV_PROJECT_ENVIRONMENT
uv sync --frozen --extra dev --extra test --python 3.11
uv run pytest -q --no-testmon --cov --cov-report=xml --cov-report=term-missing
uv run pytest -q --no-testmon --cov=scripts --cov-report=xml --cov-report=term-missing
for py in 3.12 3.13; do
echo "=== Python ${py} (tests.yml matrix) ==="
suffix="${py//./}"
Expand Down Expand Up @@ -237,12 +237,32 @@ publish:
# -------------------------------------------------------------------------
# Install all package dependencies
# -------------------------------------------------------------------------
# Ensures `uv` is a user-level tool (not installed into the project venv). On Windows, install
# uv from https://docs.astral.sh/uv/ if bash/curl are unavailable.

install:
@python -m pip install --upgrade pip
@python -m pip install --upgrade uv
@just sync
@just precommit-install
#!/usr/bin/env bash
set -euo pipefail
if ! command -v uv >/dev/null 2>&1; then
if command -v curl >/dev/null 2>&1; then
curl -LsSf https://astral.sh/uv/install.sh | sh
elif command -v wget >/dev/null 2>&1; then
wget -qO- https://astral.sh/uv/install.sh | sh
else
echo "Install uv from https://docs.astral.sh/uv/ (Windows: PowerShell installer)." >&2
exit 1
fi
if [ -f "${HOME}/.local/bin/env" ]; then
# shellcheck source=/dev/null
. "${HOME}/.local/bin/env"
fi
fi
command -v uv >/dev/null 2>&1 || {
echo "uv not found on PATH after install" >&2
exit 1
}
just sync
just precommit-install

# One-command developer onboarding: sync deps, register hooks, run diagnostics
bootstrap:
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ select = [
"T20", # flake8-print — discourage print() in non-test code
]

# E501 is intentionally ignored — line length is enforced by ruff format, not ruff check
ignore = [
"E501", # line too long (handled by formatter)
"E501",
]

[tool.ruff.lint.pydocstyle]
Expand Down
2 changes: 1 addition & 1 deletion template/.github/workflows/ci.yml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ jobs:
run: uv sync --frozen --extra dev --extra test{% if include_docs %} --extra docs{% endif %}

- name: Run tests
run: uv run pytest -q --no-testmon --cov --cov-report=xml --cov-report=term-missing -p no:cacheprovider
run: uv run pytest -q --no-testmon --cov={{ package_name }} --cov-report=xml --cov-report=term-missing -p no:cacheprovider

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
Expand Down
2 changes: 0 additions & 2 deletions template/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ Thumbs.db
!SECURITY.md
!CODE_OF_CONDUCT.md
!CONTRIBUTING.md
!TESTING_STREAMLIT.md

*.backup
*.py.backup
Expand Down Expand Up @@ -233,7 +232,6 @@ docker-compose.override.yml

# Stray root files
1
/1

tasks/
task_channel/
Expand Down
6 changes: 3 additions & 3 deletions template/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ repos:

- id: ruff-format
name: Ruff Format
entry: uv run ruff format --quiet
entry: uv run --active ruff format --quiet
language: system
types: [python]
pass_filenames: false
stages: [pre-commit]

- id: ruff-check
name: Ruff Lint (auto-fix)
entry: uv run ruff check --fix --exit-non-zero-on-fix --quiet
entry: uv run --active ruff check --fix --exit-non-zero-on-fix --quiet
language: system
types: [python]
pass_filenames: false
stages: [pre-commit]

- id: basedpyright
name: BasedPyright (type check)
entry: uv run basedpyright
entry: uv run --active basedpyright
language: system
types: [python]
pass_filenames: false
Expand Down
64 changes: 59 additions & 5 deletions template/justfile.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ coverage:

# Test command matching GitHub CI (.github/workflows/ci.yml test job)
test-ci:
@uv run pytest -q --no-testmon --cov --cov-report=xml --cov-report=term-missing -p no:cacheprovider
@uv run pytest -q --no-testmon --cov={{ package_name }} --cov-report=xml --cov-report=term-missing -p no:cacheprovider

# -------------------------------------------------------------------------
# Pre-commit
Expand Down Expand Up @@ -230,12 +230,32 @@ publish:
# -------------------------------------------------------------------------
# Install all package dependencies
# -------------------------------------------------------------------------
# Ensures `uv` is a user-level tool (not installed into the project venv). On Windows, install
# uv from https://docs.astral.sh/uv/ if bash/curl are unavailable.

install:
@python -m pip install --upgrade pip
@python -m pip install --upgrade uv
@just sync
@just precommit-install
#!/usr/bin/env bash
set -euo pipefail
if ! command -v uv >/dev/null 2>&1; then
if command -v curl >/dev/null 2>&1; then
curl -LsSf https://astral.sh/uv/install.sh | sh
elif command -v wget >/dev/null 2>&1; then
wget -qO- https://astral.sh/uv/install.sh | sh
else
echo "Install uv from https://docs.astral.sh/uv/ (Windows: PowerShell installer)." >&2
exit 1
fi
if [ -f "${HOME}/.local/bin/env" ]; then
# shellcheck source=/dev/null
. "${HOME}/.local/bin/env"
fi
fi
command -v uv >/dev/null 2>&1 || {
echo "uv not found on PATH after install" >&2
exit 1
}
just sync
just precommit-install

# One-command developer onboarding: sync deps, register hooks, run diagnostics
bootstrap:
Expand Down Expand Up @@ -365,3 +385,37 @@ release BUMP_TYPE="patch":

@echo "✅ Release complete!"
@git describe --tags --abbrev=0

# -------------------------------------------------------------------------
# Repo automation
# -------------------------------------------------------------------------

# Generate repo freshness dashboard + JSON artifacts
freshness:
@uv run python scripts/repo_file_freshness.py

# Validate root/template sync map and parity checks
sync-check:
@uv run python scripts/check_root_template_sync.py

# Print a conventional PR title + PR body (template + git log) for pr-policy compliance
pr-draft:
@uv run python scripts/pr_commit_policy.py draft

# -------------------------------------------------------------------------
# SDLC: Task management
# -------------------------------------------------------------------------

# Validate a task YAML against Definition of Ready
dor-check TASK_ID:
python3 .claude/skills/sdlc-workflow/scripts/validate_dor.py tasks/{{TASK_ID}}.yaml

# List all tasks and their statuses
tasks:
@echo "Task ID Status Title"
@echo "---------- ---------- -----"
@python3 -c "import yaml; from pathlib import Path; [print(f\"{d['task_id']:<14}{d['status']:<14}{d['title']}\") for p in sorted(Path('tasks').glob('TASK_*.yaml')) if (d := yaml.safe_load(p.read_text()))]"

# Run pre-flight checks before starting SDLC pipeline
preflight TASK_ID:
bash .claude/skills/sdlc-workflow/scripts/preflight.sh {{TASK_ID}}
3 changes: 2 additions & 1 deletion template/pyproject.toml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,9 @@ select = [
"T20", # flake8-print — discourage print() in non-test code
]

# E501 is intentionally ignored — line length is enforced by ruff format, not ruff check
ignore = [
"E501", # line too long (handled by formatter)
"E501",
]

[tool.ruff.lint.pydocstyle]
Expand Down
26 changes: 24 additions & 2 deletions tests/integration/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,28 @@ def test_package_name_validator_rejects_leading_digit(tmp_path: Path) -> None:
assert proc.returncode != 0, "copier should reject package_name starting with a digit"


def test_package_name_validator_rejects_non_identifier(tmp_path: Path) -> None:
"""Hyphenated ``package_name`` values must fail Copier validation (not a Python identifier)."""
test_dir = tmp_path / "bad_hyphen_pkg"
proc = run_command(
[
"copier",
"copy",
"--vcs-ref",
"HEAD",
TEMPLATE_GIT_SRC,
str(test_dir),
"--trust",
"--defaults",
"--skip-tasks",
"--data",
"package_name=my-bad-pkg",
],
check=False,
)
assert proc.returncode != 0, "copier should reject package_name that is not a valid identifier"


def test_computed_values_not_recorded_in_answers_file(tmp_path: Path) -> None:
"""Questions with ``when: false`` must not be stored in the answers file."""
test_dir = tmp_path / "computed_answers"
Expand Down Expand Up @@ -1572,14 +1594,14 @@ def test_ci_workflow_aligns_with_just_ci(tmp_path: Path) -> None:
assert "uv run ruff format --check src tests" in workflow
assert "uv run ruff check src tests" in workflow
assert (
"uv run pytest -q --no-testmon --cov --cov-report=xml --cov-report=term-missing -p no:cacheprovider"
"uv run pytest -q --no-testmon --cov=ci_just_align --cov-report=xml --cov-report=term-missing -p no:cacheprovider"
in workflow
)

justfile = (test_dir / "justfile").read_text(encoding="utf-8")
assert "test-ci:" in justfile
assert (
"pytest -q --no-testmon --cov --cov-report=xml --cov-report=term-missing -p no:cacheprovider"
"pytest -q --no-testmon --cov=ci_just_align --cov-report=xml --cov-report=term-missing -p no:cacheprovider"
in justfile
)
assert "@just test-ci" in justfile
Expand Down
Loading