Skip to content
99 changes: 39 additions & 60 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,61 @@

## Purpose

This file provides **project-specific guidance for AI agents** (and other automated tools) working on the `commitizen` repository.
Follow these instructions in addition to any higher-level system or tool rules.
This file is the auto-loaded entry point for AI agents working on the `commitizen` repository. It holds the rules an agent needs in **every** session. Deeper guidance lives in:

## Project Overview
- [Contributing](docs/contributing/contributing.md) — setup, dev workflow, PR lifecycle.
- [Contributing TL;DR](docs/contributing/contributing_tldr.md) — poe command cheat sheet.
- [Pull Request Guidelines](docs/contributing/pull_request.md) — PR etiquette and AI-assisted policy.
- [Architecture Overview](docs/contributing/architecture.md) — codebase topology and extension points.
- [For AI Agents](docs/contributing/agents/index.md) — agent-shaped recipes, validation map, playbooks.

- **Project**: `commitizen` - a tool to help enforce and automate conventional commits, version bumps, and changelog generation.
- **Primary language**: Python (library + CLI).
- **Cross-platform**: Tests run on Linux, macOS, and Windows. Avoid POSIX-only assumptions in code (paths, subprocesses, line endings).
- **Key entrypoints**:
- `commitizen/cli.py` - main CLI implementation.
- `commitizen/commands/` - subcommands such as `bump`, `commit`, `changelog`, `check`, etc.
- `commitizen/config/` - configuration discovery and loading.
- `commitizen/providers/` - version providers (e.g., `pep621`, `poetry`, `npm`, `uv`).
- **Config sources**: `pyproject.toml` (project config, poe tasks, ruff, mypy), `.pre-commit-config.yaml` (hooks), `.github/workflows/` (CI).

## General Expectations

- **Preserve public behavior and CLI UX** — no breaking changes to APIs, CLI flags, or exit codes unless explicitly requested.
- **Update or add tests/docs** when you change user-facing behavior.
- **Commit messages** must follow [Conventional Commits](https://www.conventionalcommits.org/) (enforced by commitizen itself).
- **Pull requests** must follow the [Pull Request Guidelines](docs/contributing/pull_request.md) and the template in `.github/pull_request_template.md`.

## Setup and Validation

> Full contributor guidelines (prerequisites, workflow, PR process): [`docs/contributing/contributing.md`](docs/contributing/contributing.md).

### Bootstrap

```bash
uv sync --frozen --group base --group test --group linters
uv run poe setup-pre-commit # install git hooks (uses prek, a pre-commit runner)
```

### Local commands

- **Format**: `uv run poe format` (runs `ruff check --fix` then `ruff format`)
- **Lint**: `uv run poe lint` (runs `ruff check` then `mypy`)
- **Test**: `uv run poe test` (runs `pytest -n auto`)
- **CI-equivalent**: `uv run poe ci` (commit check + pre-commit hooks via `prek` + test with coverage)
- **Full local check**: `uv run poe all` (format + lint + check-commit + coverage)
Follow these instructions in addition to any higher-level tool or system rules.

Always run at least `uv run ruff check --fix . && uv run ruff format .` before pushing. CI will fail if the formatter modifies any files.
## Project at a glance

### CI pipeline

- CI runs `poe ci` on a matrix of Python 3.10–3.14 × ubuntu/macos/windows.
- Pre-commit hooks are defined in `.pre-commit-config.yaml` and run via [`prek`](https://github.com/j178/prek) (a `pre-commit` compatible runner).
- The matrix is **fail-fast**: inspect the earliest failing job that completed; others are cancelled.

### Common CI failure patterns

- **"Format Python code...Failed"**: Run `uv run poe format` and commit the result.
- **mypy `[arg-type]` on TypedDict**: Dynamically-constructed dicts (e.g., from `pytest.mark.parametrize`) passed to TypedDict-typed params need `# type: ignore[arg-type]`.
- **"pathspec 'vX.Y.Z' did not match"**: `.pre-commit-config.yaml` pins a tag of this repo. Rebase onto master to pick up the tag.
- **`VersionProtocol` + `issubclass`**: This Protocol has non-method members (properties), so `issubclass()` raises `TypeError`. Use `hasattr` checks for runtime validation.
- **Project**: `commitizen` — Python CLI for enforcing Conventional Commits, automating version bumps, and generating changelogs.
- **Library + CLI**: code is reachable both via `cz` and `import commitizen`.
- **Cross-platform**: tests run on Linux/macOS/Windows × Python 3.10–3.14. Avoid POSIX-only assumptions (paths, subprocesses, line endings).
- **Key entrypoints**:
- `commitizen/cli.py` — CLI definition (decli + argparse).
- `commitizen/commands/` — one module per `cz` subcommand.
- `commitizen/config/` — configuration discovery and parsing.
- `commitizen/providers/` — version providers.
- `commitizen/changelog_formats/` — changelog file formats.

## What to Read Before Changing
## Read before changing

| Changing... | Read first |
|---|---|
| CLI flags/arguments | `commitizen/cli.py`, `docs/commands/<cmd>.md`, `tests/test_cli/` |
| Bump logic | `commitizen/bump.py`, `commitizen/commands/bump.py`, `docs/commands/bump.md` |
| Changelog generation | `commitizen/changelog.py`, `commitizen/changelog_formats/`, `docs/commands/changelog.md` |
| Version schemes | `commitizen/version_schemes.py`, `tests/test_version_schemes.py` |
| Version providers | `commitizen/providers/`, `tests/test_providers.py`, `docs/config/version_provider.md` |
| Version providers | `commitizen/providers/`, `tests/providers/`, `docs/config/version_provider.md` |
| Config resolution | `commitizen/config/`, `tests/test_conf.py`, `docs/config/` |
| Tag handling | `commitizen/tags.py`, `tests/test_tags.py` |
| Pre-commit / CI | `.pre-commit-config.yaml`, `.github/workflows/`, `pyproject.toml` (poe tasks) |

## Coding Guidelines
For recurring task types (add a provider, deprecate an API, regenerate snapshots, ...), use the matching playbook in [For AI Agents § Playbooks](docs/contributing/agents/index.md#playbooks) instead of reinventing the workflow.

## Do not touch

These files are regenerated by Commitizen-specific tooling, so hand-edits get reverted on the next release or doc rebuild:

- `CHANGELOG.md` — produced by `cz changelog`. Hand-edits will be overwritten on the next release.
- `commitizen/__version__.py` — bumped by `cz bump` via the configured version provider.
- `.pre-commit-config.yaml:rev:` lines under the `Commitizen` repo — bumped by `cz bump` (`version_files` in `pyproject.toml`).
- `docs/images/cli_help/*.svg` and `docs/images/cli_interactive/*.gif` — regenerated by `uv run poe doc:screenshots`. See the [update-snapshots playbook](docs/contributing/agents/playbooks/update-snapshots.md).
- `tests/**/*` snapshot files used by `pytest-regressions` — regenerated via `uv run poe test:regen`.

## Mandatory PR reminders

These are easy to miss when working from an agent and are required by the PR template:

- **Types**: Preserve or improve existing type hints.
- **Errors**: Prefer `commitizen/exceptions.py` error types; keep messages clear for CLI users.
- **Output**: Use `commitizen/out.py`; do not add noisy logging.
1. **Complete the AI disclosure**. Check "Was generative AI tooling used to co-author this PR?" and fill in the `Generated-by:` trailer with the tool name. Details: [Pull Request Guidelines § AI-Assisted Contributions](docs/contributing/pull_request.md#ai-assisted-contributions).
2. **Run `uv run poe all` before pushing**. This is the command named in the PR template; it auto-formats then runs the same lint/check/test pipeline as CI. To mirror CI exactly afterwards, run `uv run poe ci` (uses `prek`, does not auto-format).
3. **Fill in "Steps to Test This Pull Request"** with the exact commands you ran locally — the maintainers re-run them.

## When Unsure
## When unsure

- Prefer **reading tests and documentation first** to understand the expected behavior.
- When behavior is ambiguous, **assume backward compatibility** with current tests and docs is required.
When behavior is ambiguous, assume **backward compatibility with current tests and docs** is required. Add a deprecation window instead of breaking it; see the [deprecate-public-api playbook](docs/contributing/agents/playbooks/deprecate-public-api.md).
38 changes: 38 additions & 0 deletions docs/contributing/agents/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# For AI Agents

These pages are written for AI agents contributing to Commitizen. Human contributors may also find them useful as a quick reference. They **complement** the existing human-facing contributor docs rather than replace them — anything covered by the human docs is linked, not restated.

> If you are an AI agent looking to **use** Commitizen as a tool (validate
> commit messages, bump versions in a downstream project), see the skill
> definition at `.agents/skills/commitizen/SKILL.md` in the repo root.
> The pages here are for working **on** Commitizen itself.

## When to read what

| You want to... | Read |
|---|---|
| Set up a local dev environment | [Contributing](../contributing.md#prerequisites-setup) |
| Look up a poe command | [Contributing TL;DR](../contributing_tldr.md#command-cheat-sheet) |
| Understand the codebase layout and extension points | [Architecture Overview](../architecture.md) |
| Open a pull request | [Pull Request Guidelines](../pull_request.md) and the [PR template](https://github.com/commitizen-tools/commitizen/blob/master/.github/pull_request_template.md) |
| Pick the right test selector for a change | [Validation Guide](validation.md#targeted-test-map) |
| Recover from a CI failure | [Validation Guide](validation.md#ci-failure-recipes) |
| Implement a recurring task type | [Playbooks](#playbooks) |

The repo-root [`AGENTS.md`](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md) is the auto-loaded entry point for most agent tools. It holds the rules an agent needs in every session; this page is the deeper reference.

## Playbooks

Recipes for recurring task types. Each playbook is self-contained: trigger, files to read first, ordered steps, verification commands, and known pitfalls. They link out to the human-facing concept docs rather than restating concepts.

- [Add a version provider](playbooks/add-version-provider.md)
- [Add a changelog format](playbooks/add-changelog-format.md)
- [Add or modify a CLI flag](playbooks/add-cli-flag.md)
- [Deprecate a public API](playbooks/deprecate-public-api.md)
- [Update generated snapshots and screenshots](playbooks/update-snapshots.md)

If no playbook matches, read the [Architecture Overview](../architecture.md) for the relevant subsystem and follow 1-2 existing examples in the same directory before changing code.

## Updating these pages

Treat these pages like any other code change: open a PR, follow the template, run `uv run poe doc:build` to verify the mkdocs build, and check internal links. If you find yourself restating something that already lives in a human-facing doc, link to it instead and shorten the agent doc.
69 changes: 69 additions & 0 deletions docs/contributing/agents/playbooks/add-changelog-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Playbook: Add a Changelog Format

A changelog format handles parsing and rendering a `CHANGELOG.<ext>` file in a specific markup language. End-user documentation: [Changelog command](../../../commands/changelog.md). Built-ins are `markdown` (default), `asciidoc`, `textile`, `restructuredtext`.

Architectural context: [Architecture § Extension points](../../architecture.md#extension-points).

## Trigger

- "Support `<markup>` changelogs."
- A user wants `cz changelog` to emit something other than Markdown.
- An incremental-changelog use case fails because the user's existing `CHANGELOG` file is not Markdown.

## Read first

- `commitizen/changelog_formats/__init__.py` — `ChangelogFormat` Protocol, entry-point group `commitizen.changelog_format`, `KNOWN_CHANGELOG_FORMATS` registry, `_guess_changelog_format` extension-based fallback.
- `commitizen/changelog_formats/base.py:BaseFormat` — provides a default `get_metadata_from_file` that walks the file once for **line-anchored** titles. You override `parse_version_from_title` and `parse_title_level` to fit your markup.
- A close-match existing format:
- Line-anchored heading prefix (`#`, `=`, `h1.`): `commitizen/changelog_formats/markdown.py`, `asciidoc.py`, or `textile.py` — each overrides only the two `parse_*` methods.
- Multi-line titles (overline/underline, as in reStructuredText): `commitizen/changelog_formats/restructuredtext.py` — overrides `get_metadata_from_file` directly because the base class assumes one-line titles.
- `commitizen/templates/` — Jinja2 templates named `CHANGELOG.<ext>.j2` (e.g. `CHANGELOG.md.j2`, `CHANGELOG.rst.j2`, `CHANGELOG.adoc.j2`); the file extension comes from the `extension` class attribute, not the format name.
- `tests/test_changelog_format_<name>.py` — every format has parity tests; copy the closest one.

## Steps

1. **Create the format module** at `commitizen/changelog_formats/<name>.py`. Subclass `BaseFormat`. Set the class attributes:
- `extension: ClassVar[str]` — primary file extension (no dot).
- `alternative_extensions: ClassVar[set[str]]` — other accepted extensions for the same format.
2. **Implement two methods** for line-anchored formats:
- `parse_version_from_title(line: str) -> VersionTag | None` — given one line, return a `VersionTag` if the line is a release heading.
- `parse_title_level(line: str) -> int | None` — return the heading level (1, 2, 3, ...) if the line is a heading. The base class `BaseFormat.get_metadata_from_file` walks the file once and calls both methods per line.

If your format's titles span multiple lines (overline/title/underline, as in reStructuredText), override `get_metadata_from_file` directly instead — see `RestructuredText` for the algorithm.
3. **Add the Jinja2 template** at `commitizen/templates/CHANGELOG.<ext>.j2`. Mirror the structure of `CHANGELOG.md.j2` — same blocks, different markup. Make sure the loops over `tree`, `entries`, and `change_type` match.
4. **Register the built-in** in `pyproject.toml` under `[project.entry-points."commitizen.changelog_format"]`:

```toml
<name> = "commitizen.changelog_formats.<name>:<Name>"
```

5. **Add tests** at `tests/test_changelog_format_<name>.py`. Copy the closest existing test file and adapt the fixtures.
6. **Update the cross-format suite** `tests/test_changelog_formats.py` if it parametrizes over all formats — add the new one to its lists.
7. **Update user docs** at `docs/commands/changelog.md` and `docs/customization/changelog_template.md` — list the new format and show how to opt in via `changelog_format`.
8. **Re-run the install** so the entry point registers:

```bash
uv sync --frozen --group base --group test --group linters
```

## Validate

```bash
uv run pytest tests/test_changelog_format_<name>.py tests/test_changelog_formats.py tests/test_changelog.py tests/test_incremental_build.py -n auto
uv run poe lint
uv run poe doc:build # if docs changed
uv run poe all # final pre-push
```

## Pitfalls

- **`KNOWN_CHANGELOG_FORMATS` is populated at import time** from entry points, so you must re-run `uv sync` after editing `pyproject.toml` before tests can see your new format.
- **Forgetting `alternative_extensions`** — `_guess_changelog_format` uses both `extension` and `alternative_extensions` when the user does not set `changelog_format` explicitly. If a user has `CHANGELOG.<alt-ext>`, your format will not auto-detect without it.
- **Template encoding** — Jinja2 reads templates with the active encoding; keep them ASCII-safe or test with non-UTF-8 `encoding` settings.
- **Heading regex anchoring** — match the whole line (`^...$`) when the markup is line-anchored (Markdown headings); a substring match will pick up non-heading lines that mention `unreleased`.
- **Snapshot updates** — many changelog tests use `pytest-regressions`. See the [update-snapshots playbook](update-snapshots.md) when output intentionally changes.

## Stop and ask if

- The target format requires structured metadata that does not fit the `parse_title_*` Protocol (e.g., front-matter in YAML).
- The format implies a fundamentally different rendering tree (e.g., one file per release) — that is a bigger change than a format addition.
Loading
Loading