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
81 changes: 81 additions & 0 deletions spec/issue-8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
Should be `ci.yaml` for more precise naming.

---

## Spec

**What:** Rename `template/.github/workflows/cicd.yaml` → `template/.github/workflows/ci.yaml`,
change its internal `name: CI/CD` → `name: CI`, and update the two references to the old filename:
`template/.github/workflows/release.yaml:28` (`uses: ./.github/workflows/cicd.yaml`) and the badge
in `template/README.md.jinja:3`. No `_migrations` entry, no test changes beyond keeping the suite
green.

**Why:** The workflow contains only `lint`, `test` and `docs` jobs — there is no deployment in it.
Releasing lives in `release.yaml` and publishing in `publish.yaml`, so `cicd.yaml` / `name: CI/CD`
claims a responsibility the file does not have, and generated projects inherit that misnomer. `ci`
is what the file actually is, and it matches this repo's own `.github/workflows/ci.yaml`.

### Copier-update behaviour (the open question, answered)

Verified empirically with copier 9.11.3 by generating a project from `origin/main`, committing the
rename in a template clone, and running `copier update`:

- **A clean `copier update` does perform the rename.** `cicd.yaml` is deleted, `ci.yaml` is created,
and the `release.yaml` / `README.md` references are rewritten. This holds whether or not the
template carries git tags. **So this ticket needs no `_migrations` entry.**
- **A downstream project's local edits to `cicd.yaml` are silently dropped** — they are not carried
into `ci.yaml`, and no conflict or `.rej` file is produced. Recoverable from the project's own git
history, but the update will not warn.
- Two pre-existing template defects gate the above; both are **out of scope here** (see below) and
neither blocks this rename:
1. The template ships no `{{ _copier_conf.answers_file }}.jinja`, so generated projects have no
`.copier-answers.yml` and `copier update` refuses to run at all: *"Cannot update because cannot
obtain old template references from `.copier-answers.yml`."* Every project generated to date is
therefore un-updatable, which is why this rename cannot strand anyone today.
2. `_tasks` (`git init`, `git checkout -b main`) also run on `update` and fail there
(`fatal: a branch named 'main' already exists`, exit 128). The abort lands *after* `ci.yaml` is
written and *before* `cicd.yaml` is deleted — a half-applied rename leaving both files, with the
stale `cicd.yaml` still triggering on `push`/`pull_request` and producing duplicate CI runs.

### Notes for the implementer

- Use `git mv` so the rename is recorded as a rename.
- `cicd.yaml` has no `.jinja` suffix and is copied verbatim (`_templates_suffix: .jinja`); keep it
that way — its `${{ matrix.python-version }}` would collide with Jinja.
- Branch rulesets require **job** names (`lint`, `test`, `docs`), which do not change, so no ruleset
breaks. The README **badge URL is per-filename**, so existing generated projects keep a badge
pointing at `cicd.yaml` until they re-render; acceptable, and noted rather than mitigated.
- #7 (CI gate) edits the same file and declares this rename out of its own scope — whichever lands
first, the other rebases. Not a dependency.

## Acceptance criteria

- [ ] `template/.github/workflows/ci.yaml` exists with the previous `cicd.yaml` content and no
`.jinja` suffix; `template/.github/workflows/cicd.yaml` no longer exists.
- [ ] The rename is recorded as a rename in the commit (`git show --stat` shows R, not add+delete).
- [ ] `ci.yaml` line 1 reads `name: CI`.
- [ ] `template/.github/workflows/release.yaml` uses `./.github/workflows/ci.yaml`.
- [ ] The `Tests` badge in `template/README.md.jinja` points at `actions/workflows/ci.yaml` in both
the image URL and the link target.
- [ ] `git grep -i cicd` returns nothing under `template/`.
- [ ] `uv run pytest tests` is green.
- [ ] Commit uses a `refactor:` prefix and carries no AI attribution trailer.

## Out of scope

- Shipping `{{ _copier_conf.answers_file }}.jinja` so generated projects become updatable (defect 1
above) — its own ticket.
- Guarding `_tasks` with `when: "{{ _copier_conf.operation == 'copy' }}"` so `copier update` is not
aborted by `git init` / `git checkout -b main` (defect 2 above) — its own ticket.
- Renaming the `template/docs/source/contributing/ci_cd.md` stub or its `# CI/CD` heading: that page
documents the whole pipeline including release and publish, so its name is not the misnomer.
- Any `_migrations` entry — the empirical result above shows the rename propagates without one.

## Verification

```bash
uv run pytest tests # needs network; renders a project and runs its lint/test/pre-commit
git grep -i cicd -- template/ ; echo "exit=$?" # expect no matches
```

No `Depends-on:` — every file this spec touches is present on `origin/main`.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI/CD
name: CI

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct and complete: this is the only content change in the renamed file (similarity index 99%), and git show --name-status -M records it as R099, so history follows the file.

Nit, no action needed here: template/docs/source/contributing/ci_cd.md still carries # CI/CD, and contributing/index.md:8 still lists CI/CD in the toc. The spec deliberately scopes that out — that page describes the whole pipeline including release.yaml and publish.yaml, so its name isn't the misnomer this ticket targets. Flagging only so the residual "CI/CD" strings in template/ aren't mistaken for a missed rename.

~Written by Claude, run via the agentic engineering loop


on:
push:
Expand Down
2 changes: 1 addition & 1 deletion template/.github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ on:

jobs:
ci:
uses: ./.github/workflows/cicd.yaml
uses: ./.github/workflows/ci.yaml

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the reference that would actually break the generated project if missed — a stale uses: ./.github/workflows/cicd.yaml makes the whole release workflow fail to load, not just skip a job. Updated correctly, and confirmed in rendered output.

Nit / follow-up candidate: tests/test_template.py asserts nothing about .github/workflows/ contents, so neither this uses: nor the README badge has regression coverage — a future rename could silently break both again. The spec explicitly forbids test changes here ("no test changes beyond keeping the suite green"), so leaving it out was the right call in this PR; it's a follow-up ticket, not a defect in this change.

~Written by Claude, run via the agentic engineering loop


release:
needs: ci
Expand Down
2 changes: 1 addition & 1 deletion template/README.md.jinja
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# {{ project_name }}

[![Tests](https://github.com/{{ github_username }}/{{ project_slug }}/actions/workflows/cicd.yaml/badge.svg)](https://github.com/{{ github_username }}/{{ project_slug }}/actions/workflows/cicd.yaml)
[![Tests](https://github.com/{{ github_username }}/{{ project_slug }}/actions/workflows/ci.yaml/badge.svg)](https://github.com/{{ github_username }}/{{ project_slug }}/actions/workflows/ci.yaml)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both the image URL and the link target were updated — I checked the rendered output too, not just the template source:

README.md:3 → https://github.com/tu/test_project/actions/workflows/ci.yaml/badge.svg
            → https://github.com/tu/test_project/actions/workflows/ci.yaml

Worth remembering when this lands: badge URLs are per-filename, so already-generated projects keep a badge pointing at cicd.yaml — and, since GitHub 404s a badge for a workflow file that no longer exists, it will render broken once those projects re-render the workflow but not the README. The spec accepts this explicitly and chose to note rather than mitigate it; recording it here so the decision is visible at the line it affects.

~Written by Claude, run via the agentic engineering loop

[![PyPI version](https://badge.fury.io/py/{{ project_slug | replace('_', '-') }}.svg)](https://pypi.org/project/{{ project_slug | replace('_', '-') }}/)


Expand Down
6 changes: 3 additions & 3 deletions tests/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@

# Both CI workflows carry the aggregate `ci-gate` job. They can be parsed
# straight from disk: `_templates_suffix: .jinja` means only `.jinja` files are
# rendered, so the template's cicd.yaml is copied verbatim (which is also why
# rendered, so the template's ci.yaml is copied verbatim (which is also why

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: now that both files are named ci.yaml, "the template's ci.yaml" is doing all the disambiguating work in this comment. It is still correct — and the possessive does distinguish it from the repo's own workflow — but template/.github/workflows/ci.yaml would leave nothing to infer.

~Written by Claude, run via the agentic engineering loop

# its `${{ matrix.python-version }}` survives generation).
CI_WORKFLOWS = [
TEMPLATE_ROOT / ".github" / "workflows" / "ci.yaml",
TEMPLATE_ROOT / "template" / ".github" / "workflows" / "cicd.yaml",
TEMPLATE_ROOT / "template" / ".github" / "workflows" / "ci.yaml",

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ids=lambda p: p.name was written in #7 when these two paths had distinct filenames (ci.yaml vs cicd.yaml). After this rename both entries render the same id, so pytest disambiguates positionally:

tests/test_template.py::test_ci_gate_covers_every_job[ci.yaml0]
tests/test_template.py::test_ci_gate_covers_every_job[ci.yaml1]
tests/test_template.py::test_ci_workflow_triggers[ci.yaml0]
tests/test_template.py::test_ci_workflow_triggers[ci.yaml1]

A failure now reports [ci.yaml0] with no way to tell whether this repo's own workflow or the template's regressed — you have to count list entries to find out. It also removes the signal that would catch a future edit accidentally pointing both entries at the same file.

Non-blocking. ids=lambda p: str(p.relative_to(TEMPLATE_ROOT)) at lines 342 and 364 restores distinct, self-describing ids (.github/workflows/ci.yaml vs template/.github/workflows/ci.yaml).

~Written by Claude, run via the agentic engineering loop

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (legibility, introduced by this diff): both CI_WORKFLOWS entries are now called ci.yaml, so the two parametrized tests that use ids=lambda p: p.name (lines 342 and 364) no longer produce distinguishable test IDs. Confirmed by collection on this HEAD:

test_ci_gate_covers_every_job[ci.yaml0]
test_ci_gate_covers_every_job[ci.yaml1]
test_ci_workflow_triggers[ci.yaml0]
test_ci_workflow_triggers[ci.yaml1]

Before the rename these read [ci.yaml] and [cicd.yaml], so a failure named the offending workflow. Now the reader has to count list order to know whether the repo's own workflow or the template's broke. Correctness is unaffected — I verified the two paths still resolve to two different files, so the ci-gate tests have not silently collapsed to asserting one file twice.

Suggested fix (one line, no behaviour change):

ids=lambda p: str(p.relative_to(TEMPLATE_ROOT))

Not blocking, and the spec's "no test changes beyond keeping the suite green" arguably scopes it out of this PR.

~Written by Claude, run via the agentic engineering loop

]


Expand Down Expand Up @@ -310,7 +310,7 @@ def test_generated_release_keeps_local_workflow_ref_unpinned(project):
for path, _, ref, _ in _uses_refs(project.path / ".github" / "workflows")
if ref.startswith("./")
]
assert local == [("release.yaml", "./.github/workflows/cicd.yaml")]
assert local == [("release.yaml", "./.github/workflows/ci.yaml")]

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (pre-existing gap, not introduced here): this asserts the text of the local uses: ref but never that the referenced file exists in the generated project. A future rename that updated release.yaml and this assertion in lockstep, but missed the file itself, would still pass. Same for the README badge — nothing asserts it points at a workflow that exists.

The PR description already flags this as a follow-up candidate, and the spec explicitly scopes test additions out ("no test changes beyond keeping the suite green"), so this is a note for the human reviewer rather than a request. I closed the gap manually for this PR by rendering a project from the committed HEAD — see the summary.

~Written by Claude, run via the agentic engineering loop



def test_default_slug_is_valid_package_name(tmp_path):
Expand Down