From 7bea61111b42a350c1fbdb051495496a9f9a205d Mon Sep 17 00:00:00 2001 From: Lila Brooks Date: Sat, 18 Jul 2026 21:55:33 -0400 Subject: [PATCH] fix: keep generated projects' CI green Generated projects inherited template-only CI that referenced the single-use generator scripts, so their GitHub Actions were red until the owner pruned them by hand. Move the chassis self-tests (template-smoke, package-smoke) out of tests.yml into a dedicated .github/workflows/template-chassis.yml that create-project deletes from the target and the manual setup path removes by hand. Point the code-quality "Check shell syntax" step at `make shell`, whose target already no-ops when the generator scripts are absent, instead of a hardcoded `bash -n` that exited 127 downstream. Guard the invariant in test_create_project.py and record the change in CHANGELOG.md and docs/log.md. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/code-quality.yml | 5 +- .github/workflows/template-chassis.yml | 83 ++++++++++++++++++++++++++ .github/workflows/tests.yml | 60 ------------------- CHANGELOG.md | 14 +++++ README.md | 13 ++-- docs/log.md | 13 +++- scripts/create-project | 11 ++-- scripts/rename-project | 5 +- tests/test_create_project.py | 31 +++++++++- 9 files changed, 159 insertions(+), 76 deletions(-) create mode 100644 .github/workflows/template-chassis.yml diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 2ae4f78..0609f78 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -41,8 +41,11 @@ jobs: - name: Run environment check run: python scripts/check-env.py + # `make shell` only syntax-checks the template's single-use generator + # scripts when they are present, so this step becomes a no-op once they + # have been removed from a project generated from the template. - name: Check shell syntax - run: bash -n scripts/create-project scripts/rename-project + run: make shell - name: Run Ruff lint run: ruff check . diff --git a/.github/workflows/template-chassis.yml b/.github/workflows/template-chassis.yml new file mode 100644 index 0000000..e255b23 --- /dev/null +++ b/.github/workflows/template-chassis.yml @@ -0,0 +1,83 @@ +name: Template chassis + +# Self-tests for the python-cli-template chassis itself: generating a project +# and building the template's own distributions. They exercise the single-use +# generator scripts and assert the template's own version, so they are +# meaningless in a project created from the template. `scripts/create-project` +# deletes this workflow from the target, and the manual setup path removes it +# by hand — see README.md. If you are looking at this file in a generated +# project, delete it. + +on: + push: + branches: [main] + tags: ["v*"] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + template-smoke: + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: "3.14" + cache: "pip" + + - name: Install uv + run: python -m pip install uv + + - name: Create and verify a generated project with uv + run: | + bash scripts/create-project "$RUNNER_TEMP/uv-tool" uv-tool --no-kit + test -f "$RUNNER_TEMP/uv-tool/uv.lock" + "$RUNNER_TEMP/uv-tool/.venv/bin/uv-tool" --version + + - name: Create and verify the pip fallback + run: | + pip_target="$RUNNER_TEMP/pip[tool]qa" + bash scripts/create-project "$pip_target" pip-tool --no-kit --pip + test ! -e "$pip_target/uv.lock" + "$pip_target/.venv/bin/pip-tool" --version + + package-smoke: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: "3.14" + cache: "pip" + + - name: Install package tools + run: python -m pip install -e ".[dev]" + + - name: Build and verify distributions + run: | + python -m build + wheel="$(find dist -name '*.whl' -print -quit)" + sdist="$(find dist -name '*.tar.gz' -print -quit)" + python -m venv "$RUNNER_TEMP/wheel-venv" + "$RUNNER_TEMP/wheel-venv/bin/pip" install --no-deps "$wheel" + test "$("$RUNNER_TEMP/wheel-venv/bin/skeleton-cli" --version)" = "skeleton-cli 0.2.0" + mkdir "$RUNNER_TEMP/sdist" + tar -xzf "$sdist" -C "$RUNNER_TEMP/sdist" + source_dir="$(find "$RUNNER_TEMP/sdist" -mindepth 1 -maxdepth 1 -type d -print -quit)" + (cd "$source_dir" && python -m pytest) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f5592cb..660f124 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,63 +37,3 @@ jobs: - name: Run tests run: pytest - - template-smoke: - runs-on: ubuntu-latest - timeout-minutes: 20 - - steps: - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.14" - cache: "pip" - - - name: Install uv - run: python -m pip install uv - - - name: Create and verify a generated project with uv - run: | - bash scripts/create-project "$RUNNER_TEMP/uv-tool" uv-tool --no-kit - test -f "$RUNNER_TEMP/uv-tool/uv.lock" - "$RUNNER_TEMP/uv-tool/.venv/bin/uv-tool" --version - - - name: Create and verify the pip fallback - run: | - pip_target="$RUNNER_TEMP/pip[tool]qa" - bash scripts/create-project "$pip_target" pip-tool --no-kit --pip - test ! -e "$pip_target/uv.lock" - "$pip_target/.venv/bin/pip-tool" --version - - package-smoke: - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 - with: - python-version: "3.14" - cache: "pip" - - - name: Install package tools - run: python -m pip install -e ".[dev]" - - - name: Build and verify distributions - run: | - python -m build - wheel="$(find dist -name '*.whl' -print -quit)" - sdist="$(find dist -name '*.tar.gz' -print -quit)" - python -m venv "$RUNNER_TEMP/wheel-venv" - "$RUNNER_TEMP/wheel-venv/bin/pip" install --no-deps "$wheel" - test "$("$RUNNER_TEMP/wheel-venv/bin/skeleton-cli" --version)" = "skeleton-cli 0.2.0" - mkdir "$RUNNER_TEMP/sdist" - tar -xzf "$sdist" -C "$RUNNER_TEMP/sdist" - source_dir="$(find "$RUNNER_TEMP/sdist" -mindepth 1 -maxdepth 1 -type d -print -quit)" - (cd "$source_dir" && python -m pytest) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb9d9aa..15ed149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,20 @@ All notable changes to this project are documented here. The format is based on - Root `CLAUDE.md` that imports `AGENTS.md`, so Claude Code and Codex read one set of repository instructions from a single source. +### Fixed + +- Generated projects no longer ship with red CI. The template's chassis + self-tests — generating a project and building the template's own + distributions — moved from `tests.yml` into a dedicated + `.github/workflows/template-chassis.yml`, which `create-project` deletes from + the target (and the manual setup path removes by hand). Those jobs run the + single-use generator scripts and assert the template's own version, so they + only failed once a generated project deleted the scripts. The code-quality + "Check shell syntax" step now runs `make shell`, whose `shell` target already + no-ops when the generator scripts are absent, instead of a hardcoded + `bash -n scripts/create-project scripts/rename-project` that exited 127 in a + generated project. + ### Changed - README now leads the "Using the template" section with the rename step the diff --git a/README.md b/README.md index 44d9e3d..7bd8d2b 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,8 @@ cannot portably use them. ```bash bash scripts/rename-project your-tool-name - rm scripts/rename-project scripts/create-project tests/test_create_project.py + rm scripts/rename-project scripts/create-project \ + tests/test_create_project.py .github/workflows/template-chassis.yml ``` `rename-project` rewrites the package (`skeleton_cli`), distribution @@ -101,9 +102,11 @@ cannot portably use them. source directory, substitutes your `git config user.name` and `user.email` as owner and contact in the docs and community files, and resets the project to its own `0.1.0` with a fresh `CHANGELOG.md` and `docs/log.md`. The `rm` - drops the two generator scripts and their smoke test — template tooling that - has no role in a downstream project. (The preferred path removes these same - three files for you.) + drops the two generator scripts, their smoke test, and the `template-chassis` + workflow — template tooling that has no role in a downstream project, and + whose CI jobs run those scripts and assert the template's own version, so + they only fail once the scripts are gone. (The preferred path removes these + same four files for you.) The rename step prunes Git internals, virtual environments, and tool caches by directory name, so checkout paths containing spaces or square brackets do @@ -236,7 +239,7 @@ you what you missed. ``` ├── AGENTS.md # Codex setup, verification, and safety instructions -├── .github/workflows/ # quality, tests, coverage — Python 3.12–3.14 matrix +├── .github/workflows/ # quality, tests, coverage (3.12–3.14); template-chassis self-test, dropped on generate ├── docs/ # knowledge bundle indexes and documentation log ├── scripts/ │ ├── check-env.py # file-sync damage preflight, first step of make check diff --git a/docs/log.md b/docs/log.md index 3575b6b..b7d3566 100644 --- a/docs/log.md +++ b/docs/log.md @@ -2,7 +2,7 @@ title: Documentation log type: log status: current -date: 2026-07-17 +date: 2026-07-18 owner: Lila Brooks deciders: [Lila Brooks] tags: [documentation, log] @@ -12,6 +12,17 @@ tags: [documentation, log] Dated changes to the docs bundle, newest first. +## 2026-07-18 + +- Documented that generated projects no longer inherit red CI. The README + manual path, `create-project`, and `rename-project`'s printed next steps now + remove `.github/workflows/template-chassis.yml` alongside the single-use + generator scripts, and the README describes the template-chassis self-test + workflow that generated projects drop. The template's chassis self-tests + (project generation and building the template's own distributions) moved out + of `tests.yml` into that dedicated workflow, and the code-quality shell check + now runs `make shell` so it self-heals once the generator scripts are gone. + ## 2026-07-17 - Fixed `rename-project`'s excluded-directory traversal for checkout paths diff --git a/scripts/create-project b/scripts/create-project index 16dec8d..c58a5af 100755 --- a/scripts/create-project +++ b/scripts/create-project @@ -7,7 +7,7 @@ # environment with uv (or the venv/pip fallback) and run `make check`, install # the claude-okf-repo-kit operating contract when a kit clone is available, # rerun the gate after installation, and delete the single-use template scripts -# from the target. What it cannot +# and the template-only CI workflow from the target. What it cannot # do — the project README, CHANGELOG, goal, and playbook content — it prints as # next steps. # @@ -146,11 +146,14 @@ else fi # Rename the skeleton with the target's own copy of the script, then remove -# the template-side tooling from the target: both single-use scripts and the -# smoke test that exercises them (it has nothing to test once they are gone). +# the template-side tooling from the target: both single-use scripts, the +# smoke test that exercises them (it has nothing to test once they are gone), +# and the template-chassis workflow (its jobs run those scripts and assert the +# template's own version, so they only fail in a generated project). bash "$TARGET/scripts/rename-project" "$DIST" | sed -n '1,2p' rm -f "$TARGET/scripts/rename-project" "$TARGET/scripts/create-project" \ - "$TARGET/tests/test_create_project.py" + "$TARGET/tests/test_create_project.py" \ + "$TARGET/.github/workflows/template-chassis.yml" venv_note="skipped (--skip-venv); preferred: uv sync --all-extras && uv run make check; fallback: python3 -m venv .venv && .venv/bin/pip install -e \".[dev,anthropic,openai]\" && make check" if [ "$SKIP_VENV" -eq 0 ]; then diff --git a/scripts/rename-project b/scripts/rename-project index 3492da6..8a6784d 100755 --- a/scripts/rename-project +++ b/scripts/rename-project @@ -195,7 +195,8 @@ Next steps: and .env.example. 5. Install the operating contract on top (goal, mapping, hooks, loop): bash /path/to/claude-okf-repo-kit/scripts/update-existing-repo . - 6. Remove the single-use generator files; they have no role downstream: + 6. Remove the single-use generator files and the template-only CI workflow; + they have no role downstream and its jobs fail once the scripts are gone: rm scripts/rename-project scripts/create-project \ - tests/test_create_project.py + tests/test_create_project.py .github/workflows/template-chassis.yml EOF diff --git a/tests/test_create_project.py b/tests/test_create_project.py index 8b75f41..655769a 100644 --- a/tests/test_create_project.py +++ b/tests/test_create_project.py @@ -3,9 +3,10 @@ The script is the deterministic path agents and humans run to stamp this template into a new project, so its contract is guarded here: extraction from the committed tree, rename, .gitignore merge, collision refusal, uv setup, -path-safe internal-directory pruning, kit-candidate reporting, and removal of -the single-use scripts. CI exercises the complete uv and pip paths against a -generated project. +path-safe internal-directory pruning, kit-candidate reporting, removal of the +single-use scripts, and removal of the template-only CI so a generated project +ships green. CI exercises the complete uv and pip paths against a generated +project. """ import os @@ -180,6 +181,30 @@ def test_removes_single_use_scripts_from_target(tmp_path: Path) -> None: assert result.returncode == 0, result.stderr +def test_generated_project_ci_has_no_template_only_references(tmp_path: Path) -> None: + _require_git_checkout() + target = tmp_path / "new-proj" + + _create(target, "sample-tool") + + workflows = target / ".github" / "workflows" + # The template-only self-test workflow is dropped; the everyday workflows stay. + assert not (workflows / "template-chassis.yml").exists() + assert {path.name for path in workflows.glob("*.yml")} == { + "tests.yml", + "code-quality.yml", + "coverage.yml", + } + + # No surviving workflow may invoke the deleted generator scripts or their + # smoke test; a lingering reference is exactly what ships a generated + # project with red CI. + for path in workflows.glob("*.yml"): + text = path.read_text(encoding="utf-8") + for token in ("create-project", "rename-project", "test_create_project"): + assert token not in text, f"{path.name} still references {token}" + + def test_prefers_uv_and_writes_project_lock(tmp_path: Path) -> None: _require_git_checkout() target = tmp_path / "new-proj"