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
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview

`semvertag` auto-tags a GitLab or GitHub repository with a semantic-version git tag from CI.
[`CONTEXT.md`](CONTEXT.md) opens with what it does and owns the vocabulary — read it before naming a
[`CONTEXT.md`](CONTEXT.md) opens with what it does and owns the vocabulary. Read it before naming a
concept in code, a test name, or an issue title. It ships as a Python CLI plus two thin wrappers over
it: a GitHub Actions composite (`action.yml`) and a GitLab CI Catalog component
(`templates/semvertag.yml`).

## Commands

`just` (task runner) and `uv` (package manager). The [`justfile`](justfile) is the source of truth —
`just --list`, or read it. The one thing it does not say: `just test` passes args through, so
`just` (task runner) and `uv` (package manager). The [`justfile`](justfile) is the source of truth:
run `just --list`, or read it. The one thing it does not say: `just test` passes args through, so
`just test tests/unit/test_branch_prefix_strategy.py -q` works.

## Architecture
Expand All @@ -23,7 +23,7 @@ A human at a shell, the GitHub Action, and the GitLab CI component all invoke th

## Cutting a release (maintainers)

Push a signed, annotated bare semver tag off green `main` —
Push a signed, annotated bare semver tag off green `main`:
`git tag -s 0.9.0 -m "semvertag 0.9.0" origin/main && git push origin 0.9.0`;
[`release.yml`](.github/workflows/release.yml) does the rest and its comments say in what order and
why. What that file cannot tell you:
Expand All @@ -34,7 +34,7 @@ why. What that file cannot tell you:
patch otherwise.
- The Release body is generated from the squashed PR titles. When a change alters behaviour on
upgrade, prepend a `## Upgrading` section with `gh release edit`.
- If `just publish` succeeds but a later step fails, do **not** re-push the tag — PyPI rejects
- If `just publish` succeeds but a later step fails, do **not** re-push the tag. PyPI rejects
re-uploading an existing version, so create the Release and move `v0` by hand, or cut a new patch
tag.

Expand Down
16 changes: 8 additions & 8 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
Auto-tags a GitLab or GitHub repository with a semantic-version git tag from CI:
it reads the head commit of the default branch and the tag history through the
forge's REST API, asks a strategy how far the version should move, and creates
the new tag. Shipped as a Python CLI plus two thin wrappers over it — a GitHub
the new tag. Shipped as a Python CLI plus two thin wrappers over it: a GitHub
Actions composite (`action.yml`) and a GitLab CI Catalog component
(`templates/semvertag.yml`).

## Language

A term is listed only when there is a synonym to reject, or a meaning subtle enough that code and
docs must agree on it. General semver and CI vocabulary — tag, version, release, major/minor/patch —
docs must agree on it. General semver and CI vocabulary (tag, version, release, major/minor/patch)
does not belong here, however heavily this project uses it.

**Head commit**:
Expand All @@ -21,21 +21,21 @@ _Avoid_: latest commit. "Latest" is already pinned by *latest tag* to mean highe
not newest by date, and the head is newest by position.

**Forge**:
A repository-hosting service semvertag talks to over REST — GitLab or GitHub today.
A repository-hosting service semvertag talks to over REST: GitLab or GitHub today.
_Avoid_: host. `host` is already spoken for by the same-origin pagination guard, whose two
user-visible error strings say a `Link` header "points to a different host"; there it means scheme +
netloc, not a forge.

**Provider**:
The adapter for one forge — `GitHubProvider` / `GitLabProvider` under `semvertag/providers/`, matched
The adapter for one forge: `GitHubProvider` / `GitLabProvider` under `semvertag/providers/`, matched
structurally against the `Provider` protocol. Also the user-facing selector value
(`SEMVERTAG_PROVIDER=github`). Note the collision: `semvertag/ioc.py` imports `modern_di.providers`
in the same file, where a *provider* is a DI recipe. Say **provider** for the forge adapter and
**modern-di provider** for the other.

**Strategy**:
The rule that turns **one** commit into a `Bump` or a **decline**. It receives the head commit of the
default branch and nothing else — no network, no tag history, no commit range. A strategy never picks
default branch and nothing else: no network, no tag history, no commit range. A strategy never picks
a tag or a version; the use-case does that around it.

**Decline**:
Expand All @@ -46,7 +46,7 @@ _Avoid_: "no bump" for the strategy's answer. `NoBump` is the **Outcome** varian

**Latest tag**:
The bump baseline: the **highest by SemVer precedence** among the repo's SemVer-form tags, bare
(`1.2.0`) or **`v`-prefixed** (`v1.2.0`) — not the most recently created one, and not the tag
(`1.2.0`) or **`v`-prefixed** (`v1.2.0`). It is not the most recently created one, nor the tag
reachable from HEAD. `_select_latest_semver_tag` sorts by `semver.Version` and takes the maximum.
The new tag inherits the latest tag's **tag prefix**: after `v1.2.0` comes `v1.3.0`, after `1.2.0`
comes `1.3.0`.
Expand All @@ -60,7 +60,7 @@ _Avoid_: bare "prefix". The `branch-prefix` strategy owns that word for `feature

**Outcome**:
What a run did, as the closed sum `Created | DryRun | NoTags | AlreadyTagged | NoBump` in
`semvertag/_outcome.py`. It is internal and free to grow — the renderers `match` it exhaustively, so
`semvertag/_outcome.py`. It is internal and free to grow: the renderers `match` it exhaustively, so
a new variant is a type error until handled. Distinct from **`RunResult`**, the JSON wire DTO it
projects onto, and from **status**, the one string field on that DTO. The wire status tokens are a
frozen public contract (`schema_version` `"1.0"`, parsed by `jq` in `action.yml`): a token never
Expand All @@ -70,7 +70,7 @@ is **reason**, the human-facing sentence beside the status, which may be reworde
**Prerelease**:
Two incompatible spellings live in this repo and only one is recognized as a bump baseline.
**SemVer-form** (`1.0.0-rc.1`) parses, sorts by SemVer precedence, and finalizes on the next bump.
**PEP 440** (`0.9.0rc1`) does not parse as SemVer and is skipped from selection entirely — yet that
**PEP 440** (`0.9.0rc1`) does not parse as SemVer and is skipped from selection entirely, yet that
is the form semvertag publishes *itself* under, and what `release.yml` means by "pre-release". Say
which form you mean.

Expand Down
Loading