From 197ca17b447c0a3c0bfd46e77e5f34fcef2503df Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 30 May 2026 13:25:43 +0800 Subject: [PATCH 1/8] docs(contributing): add architecture overview A general-audience map of Commitizen's subsystems (cli, commands, config, providers, changelog_formats, version_schemes, plugins) and how they wire together. Pairs with the existing contributor guide and is also the anchor for the new agent-facing docs that follow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/contributing/architecture.md | 126 ++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 docs/contributing/architecture.md diff --git a/docs/contributing/architecture.md b/docs/contributing/architecture.md new file mode 100644 index 000000000..2c8271ecf --- /dev/null +++ b/docs/contributing/architecture.md @@ -0,0 +1,126 @@ +# Architecture Overview + +This page is a map of Commitizen's subsystems and extension points. It is aimed +at contributors (human or AI) who are about to change code and need to know +where things live and how they fit together. + +For end-user concepts, see the [Commands](../commands/init.md) and +[Configuration](../config/configuration_file.md) sections. + +## Top-level layout + +``` +commitizen/ +├── cli.py # CLI entry point and argument parsing (uses decli) +├── commands/ # One module per CLI subcommand (commit, bump, changelog, ...) +├── config/ # Configuration discovery, parsing, and base classes +├── providers/ # Version providers (where to read/write the version) +├── changelog_formats/ # Changelog file format handlers (markdown, asciidoc, ...) +├── cz/ # Built-in commit conventions (conventional_commits, jira, customize) +├── version_schemes.py # Version schemes (pep440, semver, semver2) +├── tags.py # Tag format parsing and matching +├── changelog.py # Changelog generation engine +├── bump.py # Version-bump engine +├── defaults.py # Default settings and the Settings TypedDict +├── exceptions.py # CLI-facing exception types and exit codes +├── out.py # Standard output helpers +├── git.py # Git wrapper used by all commands +├── hooks.py # Pre/post bump hook execution +└── templates/ # Built-in Jinja2 templates for changelog rendering +``` + +## Extension points + +Commitizen is plugin-friendly. Four kinds of extensions can be registered by +external packages via Python entry points; the built-in implementations use +the same mechanism. + +| Kind | Entry-point group | Built-ins registered in `pyproject.toml` | Base class / Protocol | +|---|---|---|---| +| Commit convention | `commitizen.plugin` | `cz_conventional_commits`, `cz_jira`, `cz_customize` | `commitizen/cz/base.py:BaseCommitizen` | +| Version provider | `commitizen.provider` | `cargo`, `commitizen`, `composer`, `npm`, `pep621`, `poetry`, `scm`, `uv` | `commitizen/providers/base_provider.py:VersionProvider` | +| Version scheme | `commitizen.scheme` | `pep440`, `semver`, `semver2` | `commitizen/version_schemes.py:VersionProtocol` | +| Changelog format | `commitizen.changelog_format` | `markdown`, `asciidoc`, `textile`, `restructuredtext` | `commitizen/changelog_formats/base.py:BaseFormat` | + +Each kind is loaded lazily via `importlib.metadata.entry_points(...)`. To add +a new built-in implementation you register it in `pyproject.toml` under the +appropriate `[project.entry-points."..."]` table. + +End-user documentation for these extension points lives elsewhere — see +[Version Provider](../config/version_provider.md), +[Customized Python Class](../customization/python_class.md), and +[Changelog Template](../customization/changelog_template.md). This page +focuses on where the source lives and how it is wired together. + +## Configuration layering + +Configuration is discovered, parsed, and exposed as a `Settings` TypedDict. + +1. **Discovery** — `commitizen/config/__init__.py:read_cfg` searches the + working directory (and the git project root when different) for known + config files in a defined order (see + `commitizen/defaults.py:CONFIG_FILES`). +2. **Format-specific parsing** — `commitizen/config/factory.py:create_config` + dispatches to one of: + - `commitizen/config/toml_config.py:TomlConfig` (TOML; includes + `pyproject.toml` under `[tool.commitizen]`) + - `commitizen/config/json_config.py:JsonConfig` + - `commitizen/config/yaml_config.py:YAMLConfig` +3. **Defaults merge** — every parser inherits from + `commitizen/config/base_config.py:BaseConfig`, which starts from + `commitizen/defaults.py:DEFAULT_SETTINGS` and overlays the user values. +4. **Consumption** — commands read `config.settings[...]`; providers and + formats receive the live `BaseConfig` so they can react to settings such + as `encoding`, `tag_format`, and `version_scheme`. + +The `Settings` TypedDict in `defaults.py` is the authoritative list of +recognized keys. Adding a new setting almost always means touching this file. + +## Command flow + +`cli.py` parses `argv` with [decli](https://github.com/woile/decli), resolves +the chosen subcommand to a class under `commitizen/commands/`, then +instantiates and calls it. A typical command: + +1. Reads `config.settings`. +2. Resolves dependencies (provider, scheme, changelog format) via the + `get_*` helpers in the respective subpackages. +3. Does its work, surfacing user-visible text through `commitizen/out.py` + and errors through `commitizen/exceptions.py` (each exception carries an + exit code defined in `commitizen/exceptions.py` and documented in + [Exit Codes](../exit_codes.md)). + +`cz commit` and `cz bump` are the most stateful commands — they call `git` +through `commitizen/git.py`, run user-defined `pre_bump_hooks`/`post_bump_hooks` +via `commitizen/hooks.py`, and may mutate version files through the active +provider. + +## Templates and changelog rendering + +Changelog rendering uses Jinja2. Built-in templates live under +`commitizen/templates/`. The template loader is a `ChoiceLoader` whose first +loader is `FileSystemLoader(".")` and whose second loader is provided by the +active commit-convention class (default: a `PackageLoader` for built-in +templates), so a repository can override any built-in template by placing a +file of the same name at the project root or in the configured template +directory. + +## Tests mirror the source tree + +Tests are organized to mirror the source modules: + +| Source | Test | +|---|---| +| `commitizen/providers/*` | `tests/providers/`, `tests/test_factory.py` | +| `commitizen/changelog_formats/*` | `tests/test_changelog_format_*.py`, `tests/test_changelog_formats.py` | +| `commitizen/version_schemes.py` | `tests/test_version_schemes.py`, `tests/test_version_scheme_*.py` | +| `commitizen/commands/*` | `tests/commands/`, `tests/test_cli/` | +| `commitizen/config/*` | `tests/test_conf.py` | +| `commitizen/bump.py` | `tests/test_bump_*.py` | +| `commitizen/changelog.py` | `tests/test_changelog.py`, `tests/test_incremental_build.py` | +| `commitizen/tags.py` | `tests/test_tags.py` | + +When you add or modify a subsystem, the targeted test file is usually +obvious from this mirror. The +[targeted-test map for agents](agents/validation.md#targeted-test-map) +captures the most useful selectors. From cf16a8764b0f742062b78ee3e339ab019f3b6562 Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 30 May 2026 13:26:12 +0800 Subject: [PATCH 2/8] docs(contributing): add agent-facing playbooks and validation guide Adds docs/contributing/agents/ with a router index, a validation guide (targeted-test map + CI failure recipes), and five task playbooks for recurring contributions: add a version provider, add a changelog format, add or modify a CLI flag, deprecate a public API, and update snapshots/screenshots. Aggressively deduplicates against existing human docs (contributing.md, contributing_tldr.md, pull_request.md, command pages) by linking rather than restating. Adds one-line `For AI agents'' callouts in contributing.md and pull_request.md so human contributors discover the parallel track. Hooks the new pages into mkdocs nav under a new `For AI Agents'' section and into the mkdocs-llmstxt plugin so they appear in llms-full.txt. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/contributing/agents/index.md | 92 ++++++++++++ .../agents/playbooks/add-changelog-format.md | 107 ++++++++++++++ .../agents/playbooks/add-cli-flag.md | 106 +++++++++++++ .../agents/playbooks/add-version-provider.md | 105 +++++++++++++ .../agents/playbooks/deprecate-public-api.md | 124 ++++++++++++++++ .../agents/playbooks/update-snapshots.md | 133 +++++++++++++++++ docs/contributing/agents/validation.md | 139 ++++++++++++++++++ docs/contributing/contributing.md | 5 + docs/contributing/pull_request.md | 5 + mkdocs.yml | 19 +++ 10 files changed, 835 insertions(+) create mode 100644 docs/contributing/agents/index.md create mode 100644 docs/contributing/agents/playbooks/add-changelog-format.md create mode 100644 docs/contributing/agents/playbooks/add-cli-flag.md create mode 100644 docs/contributing/agents/playbooks/add-version-provider.md create mode 100644 docs/contributing/agents/playbooks/deprecate-public-api.md create mode 100644 docs/contributing/agents/playbooks/update-snapshots.md create mode 100644 docs/contributing/agents/validation.md diff --git a/docs/contributing/agents/index.md b/docs/contributing/agents/index.md new file mode 100644 index 000000000..51f634a72 --- /dev/null +++ b/docs/contributing/agents/index.md @@ -0,0 +1,92 @@ +# 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. + +## Source-of-truth map + +When two documents could host a piece of guidance, this table is the +tie-breaker. Agent pages that drift from it should be fixed, not the +human pages. + +| Topic | Lives in | Why | +|---|---|---| +| Setup, dev workflow, PR lifecycle, labels | [Contributing](../contributing.md) | Same for humans and agents | +| poe command reference | [Contributing TL;DR](../contributing_tldr.md) | One canonical cheat sheet | +| PR etiquette, AI-assisted policy | [Pull Request Guidelines](../pull_request.md) and [PR template](https://github.com/commitizen-tools/commitizen/blob/master/.github/pull_request_template.md) | One canonical policy | +| Codebase topology and extension points | [Architecture Overview](../architecture.md) | Useful to humans too | +| Always-loaded agent rules | [`AGENTS.md`](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md) | Auto-loaded as system context every session | +| Targeted test selectors and CI failure recipes | [Validation Guide](validation.md) | Agent-specific | +| Recipes for recurring task types | [Playbooks](#playbooks) | Agent-specific | + +## 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. + +## Agent-specific deltas + +Humans absorb these rules through review; agents need them stated: + +1. **Complete the PR template fully**, including the AI-disclosure checkbox + and the `Generated-by:` trailer. The maintainers re-run the commands you + list under "Steps to Test This Pull Request" — make them exact. +2. **`uv run poe all` is the pre-push verification command** named in the + PR template. `poe ci` is the CI-equivalent runner (uses `prek` and does + not auto-format); run it too if you want to mirror CI exactly. See the + [Validation Guide](validation.md#choosing-a-final-check) for the + distinction. +3. **Do not touch generated artifacts.** See the do-not-touch list in + [`AGENTS.md`](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md). +4. **Prefer targeted test selectors during iteration** — see the + [targeted-test map](validation.md#targeted-test-map). The full suite + is fine for a final pre-push run. + +## 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 your task does not match a playbook, fall back to the general flow: + +1. Read the [Architecture Overview](../architecture.md) for the relevant + subsystem. +2. Read 1–2 existing examples in the same directory to match local + conventions. +3. Make the change, plus tests, plus user-facing docs. +4. Iterate with targeted tests; finish with `uv run poe all`. +5. Open the PR using the template; check the AI-disclosure box. + +## 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 for breakage. If you find yourself restating +something that already lives in a human-facing doc, link to it instead +and shorten the agent doc. diff --git a/docs/contributing/agents/playbooks/add-changelog-format.md b/docs/contributing/agents/playbooks/add-changelog-format.md new file mode 100644 index 000000000..498d058ef --- /dev/null +++ b/docs/contributing/agents/playbooks/add-changelog-format.md @@ -0,0 +1,107 @@ +# Playbook: Add a Changelog Format + +A changelog format handles parsing and rendering a `CHANGELOG.` 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 `` 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` — abstract + implementation; you only need to override `parse_version_from_title` and + `parse_title_level`. +- A close-match existing format: + - Heading-prefix-based: `commitizen/changelog_formats/markdown.py` + (uses `#`, `##` prefixes). + - Underline-based: `commitizen/changelog_formats/restructuredtext.py` + (uses `===`, `---` lines). +- `commitizen/templates/` — Jinja2 templates named `CHANGELOG..j2` + control rendering. +- `tests/test_changelog_format_.py` — every format has parity tests; + copy the closest one. + +## Steps + +1. **Create the format module** at + `commitizen/changelog_formats/.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**: + - `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. +3. **Add the Jinja2 template** at + `commitizen/templates/CHANGELOG..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 + = "commitizen.changelog_formats.:" + ``` + +5. **Add tests** at `tests/test_changelog_format_.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_.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.`, 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. diff --git a/docs/contributing/agents/playbooks/add-cli-flag.md b/docs/contributing/agents/playbooks/add-cli-flag.md new file mode 100644 index 000000000..95822cd10 --- /dev/null +++ b/docs/contributing/agents/playbooks/add-cli-flag.md @@ -0,0 +1,106 @@ +# Playbook: Add or Modify a CLI Flag + +Commitizen's CLI is built declaratively with [decli](https://github.com/woile/decli) +and `argparse` in `commitizen/cli.py`. Flags are dicts inside a `data["subcommands"]` +list. End-user documentation: [Commands](../../../commands/init.md). + +## Trigger + +- "Add a `--` flag to `cz `." +- "Make `--` configurable via the config file." +- "Change the default of `--`." + +## Read first + +- `commitizen/cli.py` — the entire CLI schema. Search for the subcommand + name in the `subcommands` block to find where its `arguments` list + lives. +- `commitizen/commands/.py` — the command class that receives + the parsed arguments via `self.arguments`. +- `commitizen/defaults.py:Settings` — TypedDict of all settings; required + if your flag should also be config-file-readable. +- `tests/test_cli.py` and `tests/test_cli/` — flag-parsing tests. +- `tests/commands/test__command.py` — behavior tests. +- `docs/commands/.md` — user-facing reference for the + subcommand. +- `scripts/gen_cli_help_screenshots.py` — regenerates `--help` SVGs. + +## Steps + +1. **Add the flag** in `commitizen/cli.py` inside the relevant subcommand's + `arguments` list. Follow the existing dict shape: + + ```python + { + "name": ["--my-flag", "-m"], # or just "--my-flag" if no short + "action": "store_true", # or "store", "store_false", ParseKwargs, ... + "default": False, # only when not store_true + "help": "", + } + ``` + + Look at neighboring flags in the same subcommand to match style + (option grouping, help-text tone). +2. **Consume the flag** in `commitizen/commands/.py`. It will + arrive as `self.arguments["my_flag"]` (dashes become underscores). +3. **Config-file support (optional)**. If the flag should also be settable + in the user's config file: + - Add the key to `commitizen/defaults.py:Settings` (and to + `DEFAULT_SETTINGS` if there is a non-`None` default). + - In the command, fall back to `self.config.settings["my_flag"]` when + the CLI value is `None`. + - Document the setting in the relevant `docs/config/.md` page. +4. **Add tests**: + - CLI parsing: extend `tests/test_cli/` or `tests/test_cli.py` with a + case that invokes `cz --my-flag` and asserts the + parsed namespace. + - Behavior: extend `tests/commands/test__command.py`. +5. **Update user docs** at `docs/commands/.md`. If the flag + has a corresponding config setting, also update + `docs/config/.md`. +6. **Regenerate the help SVGs** so the new flag appears in the rendered + docs. See the [update-snapshots playbook](update-snapshots.md) for the + `poe doc:screenshots` workflow. + +## Validate + +```bash +uv run pytest tests/test_cli/ tests/test_cli.py tests/commands/test__command.py -n auto +uv run poe lint +uv run poe doc:build +uv run poe all # final pre-push +``` + +## Pitfalls + +- **Underscores vs dashes** — argparse converts `--my-flag` to + `my_flag` in the namespace, but `decli` accepts both. Be consistent + with neighboring flags. +- **`store_true` with explicit `default`** — argparse uses `False` as the + implicit default for `store_true`; do not set `default` unless you + need `None` to detect "user did not pass the flag" (which matters for + config-file fallback). +- **Mutually exclusive flags** — argparse does not enforce mutual + exclusion through the `decli` dict schema; validate in the command + class and raise `commitizen.exceptions.InvalidCommandArgumentError` + with a clear message. +- **Forgetting the `Settings` TypedDict** when adding a config-file key + — `read_cfg` will accept the value but `mypy` will flag every read of + `self.config.settings["my_flag"]`. +- **Breaking flag removals** — see the + [deprecate-public-api playbook](deprecate-public-api.md). A flag is + user-facing surface; do not remove it without a deprecation window. +- **Stale `--help` screenshots** — CI does not regenerate them. Run + `uv run poe doc:screenshots` after any flag change and commit the + result. + +## Stop and ask if + +- The flag would change the **exit code** of an existing success path — + that breaks scripts that depend on exit codes. See + [Exit Codes](../../../exit_codes.md). +- The flag's behavior overlaps with an existing flag with subtly + different semantics — propose a deprecation plan first. +- The flag controls something that is currently determined by config + precedence rules (CLI > env > config); make the precedence explicit + in the issue. diff --git a/docs/contributing/agents/playbooks/add-version-provider.md b/docs/contributing/agents/playbooks/add-version-provider.md new file mode 100644 index 000000000..f4f24efd7 --- /dev/null +++ b/docs/contributing/agents/playbooks/add-version-provider.md @@ -0,0 +1,105 @@ +# Playbook: Add a Version Provider + +A version provider tells Commitizen where to read and write the project's +version (e.g., `pyproject.toml` for PEP 621, `Cargo.toml` for Cargo, +`package.json` for npm). End-user documentation: +[Version Provider](../../../config/version_provider.md). + +Architectural context: +[Architecture § Extension points](../../architecture.md#extension-points). + +## Trigger + +- "Add support for `` version files." +- "Read the version from `` instead of asking the user." +- Issue or feature request mentions a packaging system that is not in the + list of built-ins (`cargo`, `commitizen`, `composer`, `npm`, `pep621`, + `poetry`, `scm`, `uv`). + +## Read first + +- `commitizen/providers/__init__.py` — registration helper `get_provider`, + entry-point group `commitizen.provider`, default provider name. +- `commitizen/providers/base_provider.py` — `VersionProvider`, + `FileProvider`, `JsonProvider`, `TomlProvider` base classes. +- An existing provider that resembles your target: + - JSON file with non-standard layout: `commitizen/providers/composer_provider.py` + - TOML file with multi-file updates: `commitizen/providers/uv_provider.py` + - SCM tag-based, no file write: `commitizen/providers/scm_provider.py` +- Test for the closest existing provider: + `tests/providers/test__provider.py`. +- `commitizen/config/base_config.py:BaseConfig` — what your provider's + `__init__(config)` will receive. + +## Steps + +1. **Create the provider module** at + `commitizen/providers/_provider.py`. Subclass the closest base: + - `TomlProvider` if your file is TOML and `[project].version` is + sufficient — override only `get`/`set` if the version lives at a + different path. + - `JsonProvider` for JSON files; same override pattern. + - `FileProvider` directly when the format is neither TOML nor JSON. + - `VersionProvider` when there is no file (e.g., SCM-tag-based). +2. **Honor the configured encoding** for every file read and write — call + `self._get_encoding()` (provided by `FileProvider`) rather than + relying on system defaults. See + `commitizen/providers/base_provider.py` for examples. +3. **Register the built-in** by adding one line to + `pyproject.toml` under `[project.entry-points."commitizen.provider"]`: + + ```toml + = "commitizen.providers:" + ``` + +4. **Export the class** from `commitizen/providers/__init__.py`: import it + and add it to `__all__`. +5. **Add tests** at `tests/providers/test__provider.py`. The + existing tests demonstrate the patterns — most use + `pytest-regressions` for file snapshots and `pytest-mock` to substitute + the working directory. +6. **Update user docs** at `docs/config/version_provider.md` — add a row + to the providers table and an example block if the configuration is + non-trivial. +7. **Re-run the editable install** so the new entry point is picked up: + + ```bash + uv sync --frozen --group base --group test --group linters + ``` + +## Validate + +```bash +uv run pytest tests/providers/test__provider.py tests/test_factory.py -n auto +uv run poe lint +uv run poe doc:build # if docs/config/version_provider.md changed +uv run poe all # final pre-push (PR template requirement) +``` + +## Pitfalls + +- **Forgetting `pyproject.toml` registration** — the provider class will + exist but `cz bump` will raise `VersionProviderUnknown` because + `get_provider` looks it up by entry point, not by import path. +- **Hardcoded `open(path)`** — drops the user's configured encoding. Use + `self._get_encoding()` and `Path.read_text(encoding=...)`. +- **Mutating files outside `set_version`** — providers should be idempotent + and side-effect-free on `get_version`. Multi-file updates (like + `UvProvider` updating both `pyproject.toml` and `uv.lock`) belong inside + `set_version`. +- **Not testing the missing-file path** — `cz bump --get-next` runs + `get_version` on a fresh checkout. Make sure your provider returns a + reasonable default or raises a clear exception when its file is absent. +- **Cross-platform line endings** — write with `Path.write_text(...)` and + add a trailing newline; do not assume `\n`. + +## Stop and ask if + +- The packaging ecosystem requires authentication to discover the version + (e.g., reading from a registry). Network-dependent providers are out of + scope for built-ins; suggest packaging it as a third-party plugin. +- The version is split across two unrelated files with no clear "primary" + source. +- The setting would require a new key in the `Settings` TypedDict + (`commitizen/defaults.py`) — that is a config schema change, surface it + in the issue. diff --git a/docs/contributing/agents/playbooks/deprecate-public-api.md b/docs/contributing/agents/playbooks/deprecate-public-api.md new file mode 100644 index 000000000..9842dfc53 --- /dev/null +++ b/docs/contributing/agents/playbooks/deprecate-public-api.md @@ -0,0 +1,124 @@ +# Playbook: Deprecate a Public API + +Commitizen ships a stable Python API on top of the CLI. Removing or +renaming anything importable from `commitizen.*` is a breaking change. +Use this playbook to add a deprecation window before removal in the next +major version. + +## Trigger + +- "Rename `` to ``." +- "Remove the old ``." +- "Change the signature of ``." +- A refactor PR proposes removing a class, function, attribute, or + module-level constant that is reachable via `import commitizen.`. + +## Read first + +- `commitizen/changelog_formats/__init__.py` — example of a module-level + `__getattr__` that warns and forwards (look at the + `guess_changelog_format` → `_guess_changelog_format` deprecation). +- Any existing `warnings.warn(..., DeprecationWarning, stacklevel=2)` + call site in the codebase — `grep -rn DeprecationWarning commitizen/`. +- `tests/test_deprecated.py` — pattern for asserting the warning is + raised and the old path still works. +- `pyproject.toml:filterwarnings` — examples of deprecations that the + test suite explicitly silences (currently `get_smart_tag_range`). + +## Deprecation message convention + +Match the phrasing used elsewhere in the codebase: + +``` + is deprecated and will be removed in v. Use instead. +``` + +Example (from `commitizen/changelog_formats/__init__.py`): + +```python +warnings.warn( + "guess_changelog_format is deprecated and will be removed in v5. " + "Use _guess_changelog_format instead.", + DeprecationWarning, + stacklevel=2, +) +``` + +## Steps + +1. **Pick the deprecation shape** based on what you are changing: + - **Renamed module-level symbol** → add a module `__getattr__` that + returns the new symbol after issuing a `DeprecationWarning`. See + `commitizen/changelog_formats/__init__.py` for the template. + - **Renamed function/method** → keep the old name as a thin wrapper + that warns and delegates. + - **Changed function signature** → add a `typing.overload` for the + old signature; internally route old usage to the new path and warn. + - **Renamed class** → keep the old class as a subclass of the new one + and emit a warning from `__init_subclass__` or `__init__`. +2. **Issue the warning** with `warnings.warn(, DeprecationWarning, + stacklevel=2)`. `stacklevel=2` points the warning at the caller, not + at the wrapper. +3. **Decide the removal version**. Use the next major (current version + is in `commitizen/__version__.py`). Put the version in the warning + message and in the changelog entry. +4. **Add tests** in `tests/test_deprecated.py`: + + ```python + def test_old_name_is_deprecated() -> None: + with pytest.warns(DeprecationWarning, match="will be removed in v"): + result = commitizen.old_name(...) + assert result == expected + ``` + +5. **Silence the warning in the test suite** if the deprecated path is + still exercised by unrelated tests. Add to + `pyproject.toml:tool.pytest.filterwarnings`: + + ```toml + "ignore: is deprecated:DeprecationWarning", + ``` + +6. **Update all internal callers** to use the new name. Run + `git grep -n ` to find them all — search **docs**, **tests**, + and `.github/` too, not just source. +7. **Update user docs** if the symbol is documented. For module-private + symbols (leading underscore), this step is usually unnecessary. +8. **Note the removal target** in the PR description so the maintainers + can track it. + +## Validate + +```bash +uv run pytest tests/test_deprecated.py -n auto +uv run pytest # confirm new and old paths both work +uv run poe lint # mypy will warn if you import deprecated names internally +uv run poe all # final pre-push +git grep -n # zero hits expected outside the deprecation shim +``` + +## Pitfalls + +- **Hard removal in a non-major release** — refuse. The deprecation must + ship in version N, and the removal in N+1 (major). +- **Wrong `stacklevel`** — `stacklevel=1` points at the warning call itself + and is unhelpful. Almost always use `2`. +- **Missing `filterwarnings` entry** — internal callers that still use the + old name will turn the suite noisy (or break `-W error::DeprecationWarning` + invocations). +- **Forgetting to grep docs/tests/CI** — the + [PR scope rule](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md) + applies. A deprecation PR that leaves the old name referenced in + `docs/` is incomplete. +- **`DeprecationWarning` is hidden by default** — Python suppresses it + outside `__main__`. The PR description should mention how a downstream + user will see it (test runners surface it, `-W default` shows it). + +## Stop and ask if + +- The symbol is documented as part of the public API and has no obvious + successor. Propose a migration path in the issue first. +- Multiple symbols would deprecate at once with cross-dependencies — + sequence them carefully so users can migrate in steps. +- The removal target would be the **same major** as the deprecation — + that defeats the purpose of the window. diff --git a/docs/contributing/agents/playbooks/update-snapshots.md b/docs/contributing/agents/playbooks/update-snapshots.md new file mode 100644 index 000000000..28d94fbcc --- /dev/null +++ b/docs/contributing/agents/playbooks/update-snapshots.md @@ -0,0 +1,133 @@ +# Playbook: Update Generated Snapshots and Screenshots + +Commitizen has two kinds of generated artifacts that look like regular +files in `git status` but are produced by poe tasks: + +1. **Test regression snapshots** — used by `pytest-regressions`. Stored + under `tests/` next to the tests that produce them. +2. **CLI `--help` screenshots and demo GIFs** — used in the rendered + docs. Stored under `docs/images/cli_help/` and + `docs/images/cli_interactive/`. + +Both need to be regenerated whenever the underlying behavior changes — +test snapshots when output intentionally changes, and CLI screenshots +when flags or help text change. + +## Trigger + +- A test fails with `pytest-regressions` complaining about a snapshot + mismatch, **and** the new output is the intended output. +- A CLI flag was added or renamed (see + [add-cli-flag playbook](add-cli-flag.md)). +- A subcommand's `--help` text changed. + +## Read first + +- `pyproject.toml:tool.poe.tasks` — see the `test:regen`, + `doc:screenshots`, and `test:all` task definitions. +- `scripts/gen_cli_help_screenshots.py` and + `scripts/gen_cli_interactive_gifs.py` — the screenshot generators. +- `docs/images/*.tape` — `vhs` tape files that drive the GIF generation. +- The failing test, to confirm the snapshot file path and that the diff + is intentional. + +## Regenerating test snapshots + +The `test:regen` task runs both single-Python and all-Python regenerations +because some snapshots are Python-version-specific (their names start +with `py_`): + +```bash +uv run poe test:regen +``` + +Under the hood: + +```toml +"test:regen".parallel = [ + { ref = "test -k 'not py_' --regen-all" }, # version-agnostic + { ref = "test:all -- -k py_ --regen-all" }, # per-Python-version via tox +] +``` + +The `test:all` half requires `tox` and the supported Python interpreters +to be installed locally. If only the version-agnostic snapshots changed, +run just the first half: + +```bash +uv run pytest -k 'not py_' --regen-all -n auto +``` + +After regeneration: + +1. Inspect every changed file with `git --no-pager diff`. The new + content must be what the code was **supposed** to produce — do not + blanket-commit snapshot diffs without reading them. +2. Stage and commit the snapshots in the same commit as the code change + that motivated them. Reviewers need both halves to validate + intentionality. + +## Regenerating CLI help screenshots + +After any flag, help-text, or subcommand change, regenerate the SVGs and +GIFs displayed in the docs: + +```bash +uv run poe doc:screenshots +``` + +This runs two scripts in parallel: + +- `scripts/gen_cli_help_screenshots.py` → updates SVGs under + `docs/images/cli_help/`. +- `scripts/gen_cli_interactive_gifs.py` → updates GIFs under + `docs/images/cli_interactive/` using + [`vhs`](https://github.com/charmbracelet/vhs) and the `.tape` files + under `docs/images/`. + +GIF generation requires `vhs` to be installed on `PATH`. If it is not +available locally, run only the SVG half: + +```bash +uv run python -m scripts.gen_cli_help_screenshots +``` + +and skip the GIFs — surface in the PR description that the GIFs need to +be regenerated by a maintainer. + +## Validate + +```bash +git --no-pager diff # read every snapshot diff before committing +uv run pytest -n auto # snapshots now match +uv run poe doc:build # mkdocs renders new images without broken links +uv run poe all # final pre-push +``` + +## Pitfalls + +- **Regenerating without reading the diff.** `--regen-all` overwrites + every snapshot the test touches. If a bug in your code changed the + output, the snapshot will faithfully record the bug. +- **Committing snapshots separately from the code change.** Reviewers + cannot tell intent from a snapshot-only commit. +- **`vhs` not installed.** The GIF script silently fails or produces an + empty file. Inspect `docs/images/cli_interactive/*.gif` after running + and check file sizes are non-zero. +- **Python-version-specific snapshots.** Tests whose names start with + `py_` (e.g., `tests/test_x.py::test_py_3_12_specific`) need + `test:all` to regenerate across all interpreters. Skipping that step + passes locally but breaks CI on the other matrix rows. +- **Encoding-dependent snapshots.** On Windows, `pytest-regressions` may + write CRLF line endings. Configure git or your editor to normalize, or + the diff will be all-lines-changed. + +## Stop and ask if + +- A snapshot regenerates with a diff that does not match what your code + change should produce — there is a bug. Do not commit the snapshot. +- The CI failure asks for snapshot regeneration but you cannot reproduce + the diff locally — Python-version mismatch is the most likely cause; + flag it in the PR. +- `doc:screenshots` requires installing `vhs` and you are running in a + sandboxed environment that cannot install system packages. diff --git a/docs/contributing/agents/validation.md b/docs/contributing/agents/validation.md new file mode 100644 index 000000000..f6032d01d --- /dev/null +++ b/docs/contributing/agents/validation.md @@ -0,0 +1,139 @@ +# Validation Guide + +How to verify a change to Commitizen without running the full CI matrix every +time. This page is the agent-facing counterpart to the human +[Contributing TL;DR](../contributing_tldr.md), focused on **which** selector +to run for a given change and **how** to recognize CI failures. + +For the full poe command reference, see +[Contributing TL;DR](../contributing_tldr.md#command-cheat-sheet). + +## Targeted test map + +During iteration, prefer running only the tests that cover what you changed. +The full suite is for the final pre-push run. Tests mirror the source tree +(see [Architecture Overview § Tests mirror the source tree](../architecture.md#tests-mirror-the-source-tree)); +the table below picks the most useful selectors. + +| Changing... | Targeted selector | +|---|---| +| A version provider in `commitizen/providers/_provider.py` | `uv run pytest tests/providers/test__provider.py -n auto` | +| The provider lookup or registration | `uv run pytest tests/providers/ tests/test_factory.py -n auto` | +| A changelog format in `commitizen/changelog_formats/.py` | `uv run pytest tests/test_changelog_format_.py tests/test_changelog_formats.py -n auto` | +| The changelog generation engine | `uv run pytest tests/test_changelog.py tests/test_incremental_build.py -n auto` | +| A version scheme | `uv run pytest tests/test_version_scheme_.py tests/test_version_schemes.py -n auto` | +| Tag parsing / format | `uv run pytest tests/test_tags.py -n auto` | +| Bump logic | `uv run pytest "tests/test_bump_*.py" tests/commands/test_bump_command.py -n auto` | +| A CLI subcommand `commitizen/commands/.py` | `uv run pytest tests/commands/test__command.py tests/test_cli/ -n auto` | +| CLI argument parsing (`commitizen/cli.py`) | `uv run pytest tests/test_cli/ tests/test_cli.py -n auto` | +| Configuration loading | `uv run pytest tests/test_conf.py -n auto` | +| A built-in commit convention (`commitizen/cz/.py`) | `uv run pytest "tests/test_cz_*.py" -n auto` | +| A deprecation | `uv run pytest tests/test_deprecated.py -n auto` plus the affected subsystem's tests | +| Exception classes | `uv run pytest tests/test_exceptions.py -n auto` | + +Run mypy against the oldest supported Python version when adding type +annotations: + +```bash +uv run mypy --python-version 3.10 +``` + +This catches `typing-extensions` vs stdlib import issues that the default +mypy run does not flag. + +## Choosing a final check + +| Command | When to run | What it does | +|---|---|---| +| `uv run poe all` | Before pushing a PR (named in the PR template) | `format` -> `lint` -> `check-commit` -> `cover`. Auto-formats your files. | +| `uv run poe ci` | When you want to mirror CI exactly | `check-commit` -> `prek run --all-files` -> `cover`. Does **not** auto-format; fails if files need formatting. | +| `uv run poe doc:build` | After any docs change | `mkdocs build`. Prints broken-link warnings. Finite, not a server. | +| `uv run poe doc` | When iteratively editing docs | `mkdocs serve --livereload`. Runs until killed; not a verification step. | + +Recommended sequence: + +1. Iterate with targeted tests + `uv run poe format`. +2. Before push: `uv run poe all` (PR template requirement). +3. Optional: `uv run poe ci` to catch anything that `prek` will block in CI. +4. If docs changed: `uv run poe doc:build`. + +## CI failure recipes + +The CI matrix is fail-fast across Python 3.10–3.14 × ubuntu/macos/windows +(see `.github/workflows/`). Inspect the earliest failing job; the others are +cancelled. + +### "Format Python code...Failed" + +The `prek` formatting hook modified files. Run locally and commit the result: + +```bash +uv run poe format +git add -u && git commit --amend --no-edit +``` + +### mypy `[arg-type]` on a TypedDict construction + +Dynamically constructed dicts (e.g., from `pytest.mark.parametrize`) passed +to a TypedDict-typed parameter need an explicit ignore: + +```python +@pytest.mark.parametrize("settings", [{"version_scheme": "pep440"}]) +def test_x(settings: Settings) -> None: # type: ignore[arg-type] + ... +``` + +### `pathspec 'vX.Y.Z' did not match` + +`.pre-commit-config.yaml` pins a specific tag of this repo as a hook source. +When your branch is older than that tag, the hook fails because the tag is +unknown. Fix by rebasing onto the latest master: + +```bash +git fetch origin master +git rebase origin/master +``` + +### `VersionProtocol` + `issubclass` `TypeError` + +`commitizen/version_schemes.py:VersionProtocol` has non-method members +(properties), so it cannot be passed to `issubclass()`. For runtime +validation, use `hasattr` checks against the concrete members or duck-type +the value instead of subclass-checking. + +### Tests pass locally but fail in CI on Windows only + +Most often a path-separator or encoding assumption: + +- Use `pathlib.Path` and `Path(...).as_posix()` for string comparisons. +- Read files with `encoding=` (the convention is to honor + `config.settings["encoding"]`; see + `commitizen/providers/base_provider.py:_get_encoding`). +- Avoid hardcoded `"\n"` when comparing file contents — use `splitlines()` + or set `newline=""` when writing. + +### `cz check` rejects a fixup or merge commit on the branch + +`poe check-commit` runs `cz --no-raise 3 check --rev-range origin/master..`. +Squash or amend the offending commit so the branch contains only +Conventional-Commit-shaped messages, or rebase to drop it. + +### Coverage drop on CodeCov + +The `cover` task generates `coverage.xml` consumed by CodeCov. If coverage +drops, add tests for the new code paths before pushing. Inspect +`coverage.xml` locally or re-run `uv run poe cover` and inspect the +terminal report. + +## Pre-commit hooks + +Hooks are defined in `.pre-commit-config.yaml` and executed via +[`prek`](https://github.com/j178/prek), a `pre-commit`-compatible runner. +Install once: + +```bash +uv run poe setup-pre-commit +``` + +After install, `prek` runs on every `git commit`. `poe ci` invokes +`prek run --all-files` to mirror what CI will do. diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 446410533..de62352d3 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -2,6 +2,11 @@ First, thank you for taking the time to contribute! 🎉 Your contributions help make Commitizen better for everyone. +!!! tip "Using an AI assistant?" + See [For AI Agents](agents/index.md) for agent-shaped recipes, a + targeted-test map, and CI-failure playbooks that complement (and + aggressively link back to) this human-facing guide. + When contributing to Commitizen, we encourage you to: 1. First, check out the [issues](https://github.com/commitizen-tools/commitizen/issues) and [Features we won't add](../features_wont_add.md) to see if there's already a discussion about the change you wish to make. diff --git a/docs/contributing/pull_request.md b/docs/contributing/pull_request.md index 4682496b4..ddffcb311 100644 --- a/docs/contributing/pull_request.md +++ b/docs/contributing/pull_request.md @@ -26,6 +26,11 @@ We welcome contributions that use AI tools for assistance, but we have strict qu !!! note Most of our new documentation changes are, of course, generated by AI, but we still need to review it and make sure it's correct. +!!! tip "AI agents driving the PR" + See [For AI Agents](agents/index.md) for agent-shaped recipes, + targeted-test selectors, and playbooks for recurring task types + (adding a provider, deprecating an API, regenerating snapshots, etc.). + ![when bro's code is filled with "🔥 🚀 💥 ❌ ✅"](https://images3.memedroid.com/images/UPLOADED78/69501f1c23cab.webp) ### Guidelines for AI-Assisted PRs diff --git a/mkdocs.yml b/mkdocs.yml index 5f3190f7d..729d4ebbf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -89,6 +89,16 @@ nav: - "contributing/contributing_tldr.md" - "contributing/contributing.md" - "contributing/pull_request.md" + - Architecture Overview: "contributing/architecture.md" + - "For AI Agents": + - "contributing/agents/index.md" + - Validation Guide: "contributing/agents/validation.md" + - Playbooks: + - Add a version provider: "contributing/agents/playbooks/add-version-provider.md" + - Add a changelog format: "contributing/agents/playbooks/add-changelog-format.md" + - Add or modify a CLI flag: "contributing/agents/playbooks/add-cli-flag.md" + - Deprecate a public API: "contributing/agents/playbooks/deprecate-public-api.md" + - Update snapshots and screenshots: "contributing/agents/playbooks/update-snapshots.md" - "history.md" - Resources: "external_links.md" @@ -167,3 +177,12 @@ plugins: - contributing/contributing_tldr.md: Fast path for setting up a local dev workflow. - contributing/contributing.md: Full contributor guide. - contributing/pull_request.md: Expectations for preparing and submitting PRs. + - contributing/architecture.md: Map of Commitizen's subsystems and extension points. + For AI Agents: + - contributing/agents/index.md: Router and source-of-truth map for agent-shaped contributor docs. + - contributing/agents/validation.md: Targeted-test map, choosing a final check, and CI failure recipes. + - contributing/agents/playbooks/add-version-provider.md: Recipe for adding a built-in version provider. + - contributing/agents/playbooks/add-changelog-format.md: Recipe for adding a built-in changelog format. + - contributing/agents/playbooks/add-cli-flag.md: Recipe for adding or modifying a CLI flag. + - contributing/agents/playbooks/deprecate-public-api.md: Recipe for adding a deprecation window before removal. + - contributing/agents/playbooks/update-snapshots.md: Regenerating pytest-regressions snapshots and CLI help screenshots. From 157cb1881b82ec4d572c33a96b6d2a21836330cc Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 30 May 2026 13:26:32 +0800 Subject: [PATCH 3/8] docs(agents): slim AGENTS.md to a routing layer AGENTS.md is auto-loaded into every agent session, so its budget is tight. Trim it to the cross-cutting rules that an agent needs every time and link out to the new docs/contributing/architecture.md, docs/contributing/agents/index.md, and the validation guide and playbooks for everything else. Drops content that now duplicates the human contributor docs (bootstrap commands, poe cheat sheet, thin coding-guidelines section, CI-failure patterns) and replaces it with a Do-Not-Touch list of generated/managed files and a short list of mandatory PR reminders (AI disclosure, `poe all` before push, Steps to Test, Conventional Commits). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 140 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 63 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2e08e188a..95066814b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,63 +2,33 @@ ## 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 +Follow these instructions in addition to any higher-level tool or system rules. -- **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) +## Project at a glance -Always run at least `uv run ruff check --fix . && uv run ruff format .` before pushing. CI will fail if the formatter modifies any files. - -### 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 | |---|---| @@ -66,18 +36,62 @@ Always run at least `uv run ruff check --fix . && uv run ruff format .` before p | 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 - -- **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. - -## 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. +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 generated, tracked, or otherwise managed automatically. +Do not edit them directly: + +- `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`. +- `uv.lock` — only modify by changing dependencies in `pyproject.toml` + and running `uv sync`, never by hand-editing. +- Anything under `__pycache__/`, `.mypy_cache/`, `.ruff_cache/`, `.venv/`, + `site/`, `coverage.xml`, `junit.xml`. + +## Mandatory PR reminders + +These are easy to miss when working from an agent and are required by +the PR template: + +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. +4. **Follow Conventional Commits** — the project uses itself to validate + commit messages. + +## When unsure + +- Read the existing tests and user docs to understand the expected + behavior before changing code. +- 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). +- Cross-platform parity matters — if you cannot test on macOS or + Windows locally, surface that in the PR description. From 741ee63821e30fc368384981981652a28c74988d Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 30 May 2026 13:34:30 +0800 Subject: [PATCH 4/8] style(docs): unwrap hard-wrapped paragraphs in new agent docs The repository convention is unwrapped paragraphs (one line per logical paragraph) - see existing contributing.md and pull_request.md where lines run to ~150-250 chars. The newly added AGENTS.md, architecture.md, and docs/contributing/agents/ files were authored with a ~70-char hard wrap, breaking the convention. Unwrap them to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 74 +++++---------- docs/contributing/agents/index.md | 47 +++------- .../agents/playbooks/add-changelog-format.md | 90 +++++------------- .../agents/playbooks/add-cli-flag.md | 84 +++++------------ .../agents/playbooks/add-version-provider.md | 81 +++++----------- .../agents/playbooks/deprecate-public-api.md | 84 +++++------------ .../agents/playbooks/update-snapshots.md | 92 ++++++------------- docs/contributing/agents/validation.md | 58 +++--------- docs/contributing/architecture.md | 75 ++++----------- docs/contributing/contributing.md | 4 +- docs/contributing/pull_request.md | 4 +- 11 files changed, 188 insertions(+), 505 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 95066814b..477ff0491 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,9 +2,7 @@ ## Purpose -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: +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: - [Contributing](docs/contributing/contributing.md) — setup, dev workflow, PR lifecycle. - [Contributing TL;DR](docs/contributing/contributing_tldr.md) — poe command cheat sheet. @@ -16,11 +14,9 @@ Follow these instructions in addition to any higher-level tool or system rules. ## Project at a glance -- **Project**: `commitizen` — Python CLI for enforcing Conventional Commits, - automating version bumps, and generating changelogs. +- **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). +- **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. @@ -41,57 +37,31 @@ Follow these instructions in addition to any higher-level tool or system rules. | Tag handling | `commitizen/tags.py`, `tests/test_tags.py` | | Pre-commit / CI | `.pre-commit-config.yaml`, `.github/workflows/`, `pyproject.toml` (poe tasks) | -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. +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 generated, tracked, or otherwise managed automatically. -Do not edit them directly: - -- `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`. -- `uv.lock` — only modify by changing dependencies in `pyproject.toml` - and running `uv sync`, never by hand-editing. -- Anything under `__pycache__/`, `.mypy_cache/`, `.ruff_cache/`, `.venv/`, - `site/`, `coverage.xml`, `junit.xml`. +These files are generated, tracked, or otherwise managed automatically. Do not edit them directly: + +- `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`. +- `uv.lock` — only modify by changing dependencies in `pyproject.toml` and running `uv sync`, never by hand-editing. +- Anything under `__pycache__/`, `.mypy_cache/`, `.ruff_cache/`, `.venv/`, `site/`, `coverage.xml`, `junit.xml`. ## Mandatory PR reminders -These are easy to miss when working from an agent and are required by -the PR template: - -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. -4. **Follow Conventional Commits** — the project uses itself to validate - commit messages. +These are easy to miss when working from an agent and are required by the PR template: + +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. +4. **Follow Conventional Commits** — the project uses itself to validate commit messages. ## When unsure -- Read the existing tests and user docs to understand the expected - behavior before changing code. -- 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). -- Cross-platform parity matters — if you cannot test on macOS or - Windows locally, surface that in the PR description. +- Read the existing tests and user docs to understand the expected behavior before changing code. +- 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). +- Cross-platform parity matters — if you cannot test on macOS or Windows locally, surface that in the PR description. diff --git a/docs/contributing/agents/index.md b/docs/contributing/agents/index.md index 51f634a72..e40f27cde 100644 --- a/docs/contributing/agents/index.md +++ b/docs/contributing/agents/index.md @@ -1,9 +1,6 @@ # 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. +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 @@ -12,9 +9,7 @@ replace them — anything covered by the human docs is linked, not restated. ## Source-of-truth map -When two documents could host a piece of guidance, this table is the -tie-breaker. Agent pages that drift from it should be fixed, not the -human pages. +When two documents could host a piece of guidance, this table is the tie-breaker. Agent pages that drift from it should be fixed, not the human pages. | Topic | Lives in | Why | |---|---|---| @@ -38,34 +33,20 @@ human pages. | 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. +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. ## Agent-specific deltas Humans absorb these rules through review; agents need them stated: -1. **Complete the PR template fully**, including the AI-disclosure checkbox - and the `Generated-by:` trailer. The maintainers re-run the commands you - list under "Steps to Test This Pull Request" — make them exact. -2. **`uv run poe all` is the pre-push verification command** named in the - PR template. `poe ci` is the CI-equivalent runner (uses `prek` and does - not auto-format); run it too if you want to mirror CI exactly. See the - [Validation Guide](validation.md#choosing-a-final-check) for the - distinction. -3. **Do not touch generated artifacts.** See the do-not-touch list in - [`AGENTS.md`](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md). -4. **Prefer targeted test selectors during iteration** — see the - [targeted-test map](validation.md#targeted-test-map). The full suite - is fine for a final pre-push run. +1. **Complete the PR template fully**, including the AI-disclosure checkbox and the `Generated-by:` trailer. The maintainers re-run the commands you list under "Steps to Test This Pull Request" — make them exact. +2. **`uv run poe all` is the pre-push verification command** named in the PR template. `poe ci` is the CI-equivalent runner (uses `prek` and does not auto-format); run it too if you want to mirror CI exactly. See the [Validation Guide](validation.md#choosing-a-final-check) for the distinction. +3. **Do not touch generated artifacts.** See the do-not-touch list in [`AGENTS.md`](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md). +4. **Prefer targeted test selectors during iteration** — see the [targeted-test map](validation.md#targeted-test-map). The full suite is fine for a final pre-push run. ## 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. +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) @@ -75,18 +56,12 @@ restating concepts. If your task does not match a playbook, fall back to the general flow: -1. Read the [Architecture Overview](../architecture.md) for the relevant - subsystem. -2. Read 1–2 existing examples in the same directory to match local - conventions. +1. Read the [Architecture Overview](../architecture.md) for the relevant subsystem. +2. Read 1–2 existing examples in the same directory to match local conventions. 3. Make the change, plus tests, plus user-facing docs. 4. Iterate with targeted tests; finish with `uv run poe all`. 5. Open the PR using the template; check the AI-disclosure box. ## 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 for breakage. If you find yourself restating -something that already lives in a human-facing doc, link to it instead -and shorten the agent doc. +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 for breakage. If you find yourself restating something that already lives in a human-facing doc, link to it instead and shorten the agent doc. diff --git a/docs/contributing/agents/playbooks/add-changelog-format.md b/docs/contributing/agents/playbooks/add-changelog-format.md index 498d058ef..b73279bcc 100644 --- a/docs/contributing/agents/playbooks/add-changelog-format.md +++ b/docs/contributing/agents/playbooks/add-changelog-format.md @@ -1,71 +1,43 @@ # Playbook: Add a Changelog Format -A changelog format handles parsing and rendering a `CHANGELOG.` file -in a specific markup language. End-user documentation: -[Changelog command](../../../commands/changelog.md). Built-ins are -`markdown` (default), `asciidoc`, `textile`, `restructuredtext`. +A changelog format handles parsing and rendering a `CHANGELOG.` 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). +Architectural context: [Architecture § Extension points](../../architecture.md#extension-points). ## Trigger - "Support `` 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. +- 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` — abstract - implementation; you only need to override `parse_version_from_title` and - `parse_title_level`. +- `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` — abstract implementation; you only need to override `parse_version_from_title` and `parse_title_level`. - A close-match existing format: - - Heading-prefix-based: `commitizen/changelog_formats/markdown.py` - (uses `#`, `##` prefixes). - - Underline-based: `commitizen/changelog_formats/restructuredtext.py` - (uses `===`, `---` lines). -- `commitizen/templates/` — Jinja2 templates named `CHANGELOG..j2` - control rendering. -- `tests/test_changelog_format_.py` — every format has parity tests; - copy the closest one. + - Heading-prefix-based: `commitizen/changelog_formats/markdown.py` (uses `#`, `##` prefixes). + - Underline-based: `commitizen/changelog_formats/restructuredtext.py` (uses `===`, `---` lines). +- `commitizen/templates/` — Jinja2 templates named `CHANGELOG..j2` control rendering. +- `tests/test_changelog_format_.py` — every format has parity tests; copy the closest one. ## Steps -1. **Create the format module** at - `commitizen/changelog_formats/.py`. Subclass `BaseFormat`. Set - the class attributes: +1. **Create the format module** at `commitizen/changelog_formats/.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. + - `alternative_extensions: ClassVar[set[str]]` — other accepted extensions for the same format. 2. **Implement two methods**: - - `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. -3. **Add the Jinja2 template** at - `commitizen/templates/CHANGELOG..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"]`: + - `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. +3. **Add the Jinja2 template** at `commitizen/templates/CHANGELOG..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 = "commitizen.changelog_formats.:" ``` -5. **Add tests** at `tests/test_changelog_format_.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`. +5. **Add tests** at `tests/test_changelog_format_.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 @@ -83,25 +55,13 @@ 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.`, 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. +- **`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.`, 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. +- 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. diff --git a/docs/contributing/agents/playbooks/add-cli-flag.md b/docs/contributing/agents/playbooks/add-cli-flag.md index 95822cd10..58e5f9758 100644 --- a/docs/contributing/agents/playbooks/add-cli-flag.md +++ b/docs/contributing/agents/playbooks/add-cli-flag.md @@ -1,8 +1,6 @@ # Playbook: Add or Modify a CLI Flag -Commitizen's CLI is built declaratively with [decli](https://github.com/woile/decli) -and `argparse` in `commitizen/cli.py`. Flags are dicts inside a `data["subcommands"]` -list. End-user documentation: [Commands](../../../commands/init.md). +Commitizen's CLI is built declaratively with [decli](https://github.com/woile/decli) and `argparse` in `commitizen/cli.py`. Flags are dicts inside a `data["subcommands"]` list. End-user documentation: [Commands](../../../commands/init.md). ## Trigger @@ -12,23 +10,17 @@ list. End-user documentation: [Commands](../../../commands/init.md). ## Read first -- `commitizen/cli.py` — the entire CLI schema. Search for the subcommand - name in the `subcommands` block to find where its `arguments` list - lives. -- `commitizen/commands/.py` — the command class that receives - the parsed arguments via `self.arguments`. -- `commitizen/defaults.py:Settings` — TypedDict of all settings; required - if your flag should also be config-file-readable. +- `commitizen/cli.py` — the entire CLI schema. Search for the subcommand name in the `subcommands` block to find where its `arguments` list lives. +- `commitizen/commands/.py` — the command class that receives the parsed arguments via `self.arguments`. +- `commitizen/defaults.py:Settings` — TypedDict of all settings; required if your flag should also be config-file-readable. - `tests/test_cli.py` and `tests/test_cli/` — flag-parsing tests. - `tests/commands/test__command.py` — behavior tests. -- `docs/commands/.md` — user-facing reference for the - subcommand. +- `docs/commands/.md` — user-facing reference for the subcommand. - `scripts/gen_cli_help_screenshots.py` — regenerates `--help` SVGs. ## Steps -1. **Add the flag** in `commitizen/cli.py` inside the relevant subcommand's - `arguments` list. Follow the existing dict shape: +1. **Add the flag** in `commitizen/cli.py` inside the relevant subcommand's `arguments` list. Follow the existing dict shape: ```python { @@ -39,28 +31,17 @@ list. End-user documentation: [Commands](../../../commands/init.md). } ``` - Look at neighboring flags in the same subcommand to match style - (option grouping, help-text tone). -2. **Consume the flag** in `commitizen/commands/.py`. It will - arrive as `self.arguments["my_flag"]` (dashes become underscores). -3. **Config-file support (optional)**. If the flag should also be settable - in the user's config file: - - Add the key to `commitizen/defaults.py:Settings` (and to - `DEFAULT_SETTINGS` if there is a non-`None` default). - - In the command, fall back to `self.config.settings["my_flag"]` when - the CLI value is `None`. + Look at neighboring flags in the same subcommand to match style (option grouping, help-text tone). +2. **Consume the flag** in `commitizen/commands/.py`. It will arrive as `self.arguments["my_flag"]` (dashes become underscores). +3. **Config-file support (optional)**. If the flag should also be settable in the user's config file: + - Add the key to `commitizen/defaults.py:Settings` (and to `DEFAULT_SETTINGS` if there is a non-`None` default). + - In the command, fall back to `self.config.settings["my_flag"]` when the CLI value is `None`. - Document the setting in the relevant `docs/config/.md` page. 4. **Add tests**: - - CLI parsing: extend `tests/test_cli/` or `tests/test_cli.py` with a - case that invokes `cz --my-flag` and asserts the - parsed namespace. + - CLI parsing: extend `tests/test_cli/` or `tests/test_cli.py` with a case that invokes `cz --my-flag` and asserts the parsed namespace. - Behavior: extend `tests/commands/test__command.py`. -5. **Update user docs** at `docs/commands/.md`. If the flag - has a corresponding config setting, also update - `docs/config/.md`. -6. **Regenerate the help SVGs** so the new flag appears in the rendered - docs. See the [update-snapshots playbook](update-snapshots.md) for the - `poe doc:screenshots` workflow. +5. **Update user docs** at `docs/commands/.md`. If the flag has a corresponding config setting, also update `docs/config/.md`. +6. **Regenerate the help SVGs** so the new flag appears in the rendered docs. See the [update-snapshots playbook](update-snapshots.md) for the `poe doc:screenshots` workflow. ## Validate @@ -73,34 +54,15 @@ uv run poe all # final pre-push ## Pitfalls -- **Underscores vs dashes** — argparse converts `--my-flag` to - `my_flag` in the namespace, but `decli` accepts both. Be consistent - with neighboring flags. -- **`store_true` with explicit `default`** — argparse uses `False` as the - implicit default for `store_true`; do not set `default` unless you - need `None` to detect "user did not pass the flag" (which matters for - config-file fallback). -- **Mutually exclusive flags** — argparse does not enforce mutual - exclusion through the `decli` dict schema; validate in the command - class and raise `commitizen.exceptions.InvalidCommandArgumentError` - with a clear message. -- **Forgetting the `Settings` TypedDict** when adding a config-file key - — `read_cfg` will accept the value but `mypy` will flag every read of - `self.config.settings["my_flag"]`. -- **Breaking flag removals** — see the - [deprecate-public-api playbook](deprecate-public-api.md). A flag is - user-facing surface; do not remove it without a deprecation window. -- **Stale `--help` screenshots** — CI does not regenerate them. Run - `uv run poe doc:screenshots` after any flag change and commit the - result. +- **Underscores vs dashes** — argparse converts `--my-flag` to `my_flag` in the namespace, but `decli` accepts both. Be consistent with neighboring flags. +- **`store_true` with explicit `default`** — argparse uses `False` as the implicit default for `store_true`; do not set `default` unless you need `None` to detect "user did not pass the flag" (which matters for config-file fallback). +- **Mutually exclusive flags** — argparse does not enforce mutual exclusion through the `decli` dict schema; validate in the command class and raise `commitizen.exceptions.InvalidCommandArgumentError` with a clear message. +- **Forgetting the `Settings` TypedDict** when adding a config-file key — `read_cfg` will accept the value but `mypy` will flag every read of `self.config.settings["my_flag"]`. +- **Breaking flag removals** — see the [deprecate-public-api playbook](deprecate-public-api.md). A flag is user-facing surface; do not remove it without a deprecation window. +- **Stale `--help` screenshots** — CI does not regenerate them. Run `uv run poe doc:screenshots` after any flag change and commit the result. ## Stop and ask if -- The flag would change the **exit code** of an existing success path — - that breaks scripts that depend on exit codes. See - [Exit Codes](../../../exit_codes.md). -- The flag's behavior overlaps with an existing flag with subtly - different semantics — propose a deprecation plan first. -- The flag controls something that is currently determined by config - precedence rules (CLI > env > config); make the precedence explicit - in the issue. +- The flag would change the **exit code** of an existing success path — that breaks scripts that depend on exit codes. See [Exit Codes](../../../exit_codes.md). +- The flag's behavior overlaps with an existing flag with subtly different semantics — propose a deprecation plan first. +- The flag controls something that is currently determined by config precedence rules (CLI > env > config); make the precedence explicit in the issue. diff --git a/docs/contributing/agents/playbooks/add-version-provider.md b/docs/contributing/agents/playbooks/add-version-provider.md index f4f24efd7..706a9f8e9 100644 --- a/docs/contributing/agents/playbooks/add-version-provider.md +++ b/docs/contributing/agents/playbooks/add-version-provider.md @@ -1,66 +1,43 @@ # Playbook: Add a Version Provider -A version provider tells Commitizen where to read and write the project's -version (e.g., `pyproject.toml` for PEP 621, `Cargo.toml` for Cargo, -`package.json` for npm). End-user documentation: -[Version Provider](../../../config/version_provider.md). +A version provider tells Commitizen where to read and write the project's version (e.g., `pyproject.toml` for PEP 621, `Cargo.toml` for Cargo, `package.json` for npm). End-user documentation: [Version Provider](../../../config/version_provider.md). -Architectural context: -[Architecture § Extension points](../../architecture.md#extension-points). +Architectural context: [Architecture § Extension points](../../architecture.md#extension-points). ## Trigger - "Add support for `` version files." - "Read the version from `` instead of asking the user." -- Issue or feature request mentions a packaging system that is not in the - list of built-ins (`cargo`, `commitizen`, `composer`, `npm`, `pep621`, - `poetry`, `scm`, `uv`). +- Issue or feature request mentions a packaging system that is not in the list of built-ins (`cargo`, `commitizen`, `composer`, `npm`, `pep621`, `poetry`, `scm`, `uv`). ## Read first -- `commitizen/providers/__init__.py` — registration helper `get_provider`, - entry-point group `commitizen.provider`, default provider name. -- `commitizen/providers/base_provider.py` — `VersionProvider`, - `FileProvider`, `JsonProvider`, `TomlProvider` base classes. +- `commitizen/providers/__init__.py` — registration helper `get_provider`, entry-point group `commitizen.provider`, default provider name. +- `commitizen/providers/base_provider.py` — `VersionProvider`, `FileProvider`, `JsonProvider`, `TomlProvider` base classes. - An existing provider that resembles your target: - JSON file with non-standard layout: `commitizen/providers/composer_provider.py` - TOML file with multi-file updates: `commitizen/providers/uv_provider.py` - SCM tag-based, no file write: `commitizen/providers/scm_provider.py` -- Test for the closest existing provider: - `tests/providers/test__provider.py`. -- `commitizen/config/base_config.py:BaseConfig` — what your provider's - `__init__(config)` will receive. +- Test for the closest existing provider: `tests/providers/test__provider.py`. +- `commitizen/config/base_config.py:BaseConfig` — what your provider's `__init__(config)` will receive. ## Steps -1. **Create the provider module** at - `commitizen/providers/_provider.py`. Subclass the closest base: - - `TomlProvider` if your file is TOML and `[project].version` is - sufficient — override only `get`/`set` if the version lives at a - different path. +1. **Create the provider module** at `commitizen/providers/_provider.py`. Subclass the closest base: + - `TomlProvider` if your file is TOML and `[project].version` is sufficient — override only `get`/`set` if the version lives at a different path. - `JsonProvider` for JSON files; same override pattern. - `FileProvider` directly when the format is neither TOML nor JSON. - `VersionProvider` when there is no file (e.g., SCM-tag-based). -2. **Honor the configured encoding** for every file read and write — call - `self._get_encoding()` (provided by `FileProvider`) rather than - relying on system defaults. See - `commitizen/providers/base_provider.py` for examples. -3. **Register the built-in** by adding one line to - `pyproject.toml` under `[project.entry-points."commitizen.provider"]`: +2. **Honor the configured encoding** for every file read and write — call `self._get_encoding()` (provided by `FileProvider`) rather than relying on system defaults. See `commitizen/providers/base_provider.py` for examples. +3. **Register the built-in** by adding one line to `pyproject.toml` under `[project.entry-points."commitizen.provider"]`: ```toml = "commitizen.providers:" ``` -4. **Export the class** from `commitizen/providers/__init__.py`: import it - and add it to `__all__`. -5. **Add tests** at `tests/providers/test__provider.py`. The - existing tests demonstrate the patterns — most use - `pytest-regressions` for file snapshots and `pytest-mock` to substitute - the working directory. -6. **Update user docs** at `docs/config/version_provider.md` — add a row - to the providers table and an example block if the configuration is - non-trivial. +4. **Export the class** from `commitizen/providers/__init__.py`: import it and add it to `__all__`. +5. **Add tests** at `tests/providers/test__provider.py`. The existing tests demonstrate the patterns — most use `pytest-regressions` for file snapshots and `pytest-mock` to substitute the working directory. +6. **Update user docs** at `docs/config/version_provider.md` — add a row to the providers table and an example block if the configuration is non-trivial. 7. **Re-run the editable install** so the new entry point is picked up: ```bash @@ -78,28 +55,14 @@ uv run poe all # final pre-push (PR template requirement) ## Pitfalls -- **Forgetting `pyproject.toml` registration** — the provider class will - exist but `cz bump` will raise `VersionProviderUnknown` because - `get_provider` looks it up by entry point, not by import path. -- **Hardcoded `open(path)`** — drops the user's configured encoding. Use - `self._get_encoding()` and `Path.read_text(encoding=...)`. -- **Mutating files outside `set_version`** — providers should be idempotent - and side-effect-free on `get_version`. Multi-file updates (like - `UvProvider` updating both `pyproject.toml` and `uv.lock`) belong inside - `set_version`. -- **Not testing the missing-file path** — `cz bump --get-next` runs - `get_version` on a fresh checkout. Make sure your provider returns a - reasonable default or raises a clear exception when its file is absent. -- **Cross-platform line endings** — write with `Path.write_text(...)` and - add a trailing newline; do not assume `\n`. +- **Forgetting `pyproject.toml` registration** — the provider class will exist but `cz bump` will raise `VersionProviderUnknown` because `get_provider` looks it up by entry point, not by import path. +- **Hardcoded `open(path)`** — drops the user's configured encoding. Use `self._get_encoding()` and `Path.read_text(encoding=...)`. +- **Mutating files outside `set_version`** — providers should be idempotent and side-effect-free on `get_version`. Multi-file updates (like `UvProvider` updating both `pyproject.toml` and `uv.lock`) belong inside `set_version`. +- **Not testing the missing-file path** — `cz bump --get-next` runs `get_version` on a fresh checkout. Make sure your provider returns a reasonable default or raises a clear exception when its file is absent. +- **Cross-platform line endings** — write with `Path.write_text(...)` and add a trailing newline; do not assume `\n`. ## Stop and ask if -- The packaging ecosystem requires authentication to discover the version - (e.g., reading from a registry). Network-dependent providers are out of - scope for built-ins; suggest packaging it as a third-party plugin. -- The version is split across two unrelated files with no clear "primary" - source. -- The setting would require a new key in the `Settings` TypedDict - (`commitizen/defaults.py`) — that is a config schema change, surface it - in the issue. +- The packaging ecosystem requires authentication to discover the version (e.g., reading from a registry). Network-dependent providers are out of scope for built-ins; suggest packaging it as a third-party plugin. +- The version is split across two unrelated files with no clear "primary" source. +- The setting would require a new key in the `Settings` TypedDict (`commitizen/defaults.py`) — that is a config schema change, surface it in the issue. diff --git a/docs/contributing/agents/playbooks/deprecate-public-api.md b/docs/contributing/agents/playbooks/deprecate-public-api.md index 9842dfc53..5885b497e 100644 --- a/docs/contributing/agents/playbooks/deprecate-public-api.md +++ b/docs/contributing/agents/playbooks/deprecate-public-api.md @@ -1,29 +1,20 @@ # Playbook: Deprecate a Public API -Commitizen ships a stable Python API on top of the CLI. Removing or -renaming anything importable from `commitizen.*` is a breaking change. -Use this playbook to add a deprecation window before removal in the next -major version. +Commitizen ships a stable Python API on top of the CLI. Removing or renaming anything importable from `commitizen.*` is a breaking change. Use this playbook to add a deprecation window before removal in the next major version. ## Trigger - "Rename `` to ``." - "Remove the old ``." - "Change the signature of ``." -- A refactor PR proposes removing a class, function, attribute, or - module-level constant that is reachable via `import commitizen.`. +- A refactor PR proposes removing a class, function, attribute, or module-level constant that is reachable via `import commitizen.`. ## Read first -- `commitizen/changelog_formats/__init__.py` — example of a module-level - `__getattr__` that warns and forwards (look at the - `guess_changelog_format` → `_guess_changelog_format` deprecation). -- Any existing `warnings.warn(..., DeprecationWarning, stacklevel=2)` - call site in the codebase — `grep -rn DeprecationWarning commitizen/`. -- `tests/test_deprecated.py` — pattern for asserting the warning is - raised and the old path still works. -- `pyproject.toml:filterwarnings` — examples of deprecations that the - test suite explicitly silences (currently `get_smart_tag_range`). +- `commitizen/changelog_formats/__init__.py` — example of a module-level `__getattr__` that warns and forwards (look at the `guess_changelog_format` → `_guess_changelog_format` deprecation). +- Any existing `warnings.warn(..., DeprecationWarning, stacklevel=2)` call site in the codebase — `grep -rn DeprecationWarning commitizen/`. +- `tests/test_deprecated.py` — pattern for asserting the warning is raised and the old path still works. +- `pyproject.toml:filterwarnings` — examples of deprecations that the test suite explicitly silences (currently `get_smart_tag_range`). ## Deprecation message convention @@ -47,21 +38,12 @@ warnings.warn( ## Steps 1. **Pick the deprecation shape** based on what you are changing: - - **Renamed module-level symbol** → add a module `__getattr__` that - returns the new symbol after issuing a `DeprecationWarning`. See - `commitizen/changelog_formats/__init__.py` for the template. - - **Renamed function/method** → keep the old name as a thin wrapper - that warns and delegates. - - **Changed function signature** → add a `typing.overload` for the - old signature; internally route old usage to the new path and warn. - - **Renamed class** → keep the old class as a subclass of the new one - and emit a warning from `__init_subclass__` or `__init__`. -2. **Issue the warning** with `warnings.warn(, DeprecationWarning, - stacklevel=2)`. `stacklevel=2` points the warning at the caller, not - at the wrapper. -3. **Decide the removal version**. Use the next major (current version - is in `commitizen/__version__.py`). Put the version in the warning - message and in the changelog entry. + - **Renamed module-level symbol** → add a module `__getattr__` that returns the new symbol after issuing a `DeprecationWarning`. See `commitizen/changelog_formats/__init__.py` for the template. + - **Renamed function/method** → keep the old name as a thin wrapper that warns and delegates. + - **Changed function signature** → add a `typing.overload` for the old signature; internally route old usage to the new path and warn. + - **Renamed class** → keep the old class as a subclass of the new one and emit a warning from `__init_subclass__` or `__init__`. +2. **Issue the warning** with `warnings.warn(, DeprecationWarning, stacklevel=2)`. `stacklevel=2` points the warning at the caller, not at the wrapper. +3. **Decide the removal version**. Use the next major (current version is in `commitizen/__version__.py`). Put the version in the warning message and in the changelog entry. 4. **Add tests** in `tests/test_deprecated.py`: ```python @@ -71,21 +53,15 @@ warnings.warn( assert result == expected ``` -5. **Silence the warning in the test suite** if the deprecated path is - still exercised by unrelated tests. Add to - `pyproject.toml:tool.pytest.filterwarnings`: +5. **Silence the warning in the test suite** if the deprecated path is still exercised by unrelated tests. Add to `pyproject.toml:tool.pytest.filterwarnings`: ```toml "ignore: is deprecated:DeprecationWarning", ``` -6. **Update all internal callers** to use the new name. Run - `git grep -n ` to find them all — search **docs**, **tests**, - and `.github/` too, not just source. -7. **Update user docs** if the symbol is documented. For module-private - symbols (leading underscore), this step is usually unnecessary. -8. **Note the removal target** in the PR description so the maintainers - can track it. +6. **Update all internal callers** to use the new name. Run `git grep -n ` to find them all — search **docs**, **tests**, and `.github/` too, not just source. +7. **Update user docs** if the symbol is documented. For module-private symbols (leading underscore), this step is usually unnecessary. +8. **Note the removal target** in the PR description so the maintainers can track it. ## Validate @@ -99,26 +75,14 @@ git grep -n # zero hits expected outside the deprecation ## Pitfalls -- **Hard removal in a non-major release** — refuse. The deprecation must - ship in version N, and the removal in N+1 (major). -- **Wrong `stacklevel`** — `stacklevel=1` points at the warning call itself - and is unhelpful. Almost always use `2`. -- **Missing `filterwarnings` entry** — internal callers that still use the - old name will turn the suite noisy (or break `-W error::DeprecationWarning` - invocations). -- **Forgetting to grep docs/tests/CI** — the - [PR scope rule](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md) - applies. A deprecation PR that leaves the old name referenced in - `docs/` is incomplete. -- **`DeprecationWarning` is hidden by default** — Python suppresses it - outside `__main__`. The PR description should mention how a downstream - user will see it (test runners surface it, `-W default` shows it). +- **Hard removal in a non-major release** — refuse. The deprecation must ship in version N, and the removal in N+1 (major). +- **Wrong `stacklevel`** — `stacklevel=1` points at the warning call itself and is unhelpful. Almost always use `2`. +- **Missing `filterwarnings` entry** — internal callers that still use the old name will turn the suite noisy (or break `-W error::DeprecationWarning` invocations). +- **Forgetting to grep docs/tests/CI** — the [PR scope rule](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md) applies. A deprecation PR that leaves the old name referenced in `docs/` is incomplete. +- **`DeprecationWarning` is hidden by default** — Python suppresses it outside `__main__`. The PR description should mention how a downstream user will see it (test runners surface it, `-W default` shows it). ## Stop and ask if -- The symbol is documented as part of the public API and has no obvious - successor. Propose a migration path in the issue first. -- Multiple symbols would deprecate at once with cross-dependencies — - sequence them carefully so users can migrate in steps. -- The removal target would be the **same major** as the deprecation — - that defeats the purpose of the window. +- The symbol is documented as part of the public API and has no obvious successor. Propose a migration path in the issue first. +- Multiple symbols would deprecate at once with cross-dependencies — sequence them carefully so users can migrate in steps. +- The removal target would be the **same major** as the deprecation — that defeats the purpose of the window. diff --git a/docs/contributing/agents/playbooks/update-snapshots.md b/docs/contributing/agents/playbooks/update-snapshots.md index 28d94fbcc..709b61c5b 100644 --- a/docs/contributing/agents/playbooks/update-snapshots.md +++ b/docs/contributing/agents/playbooks/update-snapshots.md @@ -1,41 +1,28 @@ # Playbook: Update Generated Snapshots and Screenshots -Commitizen has two kinds of generated artifacts that look like regular -files in `git status` but are produced by poe tasks: +Commitizen has two kinds of generated artifacts that look like regular files in `git status` but are produced by poe tasks: -1. **Test regression snapshots** — used by `pytest-regressions`. Stored - under `tests/` next to the tests that produce them. -2. **CLI `--help` screenshots and demo GIFs** — used in the rendered - docs. Stored under `docs/images/cli_help/` and - `docs/images/cli_interactive/`. +1. **Test regression snapshots** — used by `pytest-regressions`. Stored under `tests/` next to the tests that produce them. +2. **CLI `--help` screenshots and demo GIFs** — used in the rendered docs. Stored under `docs/images/cli_help/` and `docs/images/cli_interactive/`. -Both need to be regenerated whenever the underlying behavior changes — -test snapshots when output intentionally changes, and CLI screenshots -when flags or help text change. +Both need to be regenerated whenever the underlying behavior changes — test snapshots when output intentionally changes, and CLI screenshots when flags or help text change. ## Trigger -- A test fails with `pytest-regressions` complaining about a snapshot - mismatch, **and** the new output is the intended output. -- A CLI flag was added or renamed (see - [add-cli-flag playbook](add-cli-flag.md)). +- A test fails with `pytest-regressions` complaining about a snapshot mismatch, **and** the new output is the intended output. +- A CLI flag was added or renamed (see [add-cli-flag playbook](add-cli-flag.md)). - A subcommand's `--help` text changed. ## Read first -- `pyproject.toml:tool.poe.tasks` — see the `test:regen`, - `doc:screenshots`, and `test:all` task definitions. -- `scripts/gen_cli_help_screenshots.py` and - `scripts/gen_cli_interactive_gifs.py` — the screenshot generators. +- `pyproject.toml:tool.poe.tasks` — see the `test:regen`, `doc:screenshots`, and `test:all` task definitions. +- `scripts/gen_cli_help_screenshots.py` and `scripts/gen_cli_interactive_gifs.py` — the screenshot generators. - `docs/images/*.tape` — `vhs` tape files that drive the GIF generation. -- The failing test, to confirm the snapshot file path and that the diff - is intentional. +- The failing test, to confirm the snapshot file path and that the diff is intentional. ## Regenerating test snapshots -The `test:regen` task runs both single-Python and all-Python regenerations -because some snapshots are Python-version-specific (their names start -with `py_`): +The `test:regen` task runs both single-Python and all-Python regenerations because some snapshots are Python-version-specific (their names start with `py_`): ```bash uv run poe test:regen @@ -50,9 +37,7 @@ Under the hood: ] ``` -The `test:all` half requires `tox` and the supported Python interpreters -to be installed locally. If only the version-agnostic snapshots changed, -run just the first half: +The `test:all` half requires `tox` and the supported Python interpreters to be installed locally. If only the version-agnostic snapshots changed, run just the first half: ```bash uv run pytest -k 'not py_' --regen-all -n auto @@ -60,17 +45,12 @@ uv run pytest -k 'not py_' --regen-all -n auto After regeneration: -1. Inspect every changed file with `git --no-pager diff`. The new - content must be what the code was **supposed** to produce — do not - blanket-commit snapshot diffs without reading them. -2. Stage and commit the snapshots in the same commit as the code change - that motivated them. Reviewers need both halves to validate - intentionality. +1. Inspect every changed file with `git --no-pager diff`. The new content must be what the code was **supposed** to produce — do not blanket-commit snapshot diffs without reading them. +2. Stage and commit the snapshots in the same commit as the code change that motivated them. Reviewers need both halves to validate intentionality. ## Regenerating CLI help screenshots -After any flag, help-text, or subcommand change, regenerate the SVGs and -GIFs displayed in the docs: +After any flag, help-text, or subcommand change, regenerate the SVGs and GIFs displayed in the docs: ```bash uv run poe doc:screenshots @@ -78,22 +58,16 @@ uv run poe doc:screenshots This runs two scripts in parallel: -- `scripts/gen_cli_help_screenshots.py` → updates SVGs under - `docs/images/cli_help/`. -- `scripts/gen_cli_interactive_gifs.py` → updates GIFs under - `docs/images/cli_interactive/` using - [`vhs`](https://github.com/charmbracelet/vhs) and the `.tape` files - under `docs/images/`. +- `scripts/gen_cli_help_screenshots.py` → updates SVGs under `docs/images/cli_help/`. +- `scripts/gen_cli_interactive_gifs.py` → updates GIFs under `docs/images/cli_interactive/` using [`vhs`](https://github.com/charmbracelet/vhs) and the `.tape` files under `docs/images/`. -GIF generation requires `vhs` to be installed on `PATH`. If it is not -available locally, run only the SVG half: +GIF generation requires `vhs` to be installed on `PATH`. If it is not available locally, run only the SVG half: ```bash uv run python -m scripts.gen_cli_help_screenshots ``` -and skip the GIFs — surface in the PR description that the GIFs need to -be regenerated by a maintainer. +and skip the GIFs — surface in the PR description that the GIFs need to be regenerated by a maintainer. ## Validate @@ -106,28 +80,14 @@ uv run poe all # final pre-push ## Pitfalls -- **Regenerating without reading the diff.** `--regen-all` overwrites - every snapshot the test touches. If a bug in your code changed the - output, the snapshot will faithfully record the bug. -- **Committing snapshots separately from the code change.** Reviewers - cannot tell intent from a snapshot-only commit. -- **`vhs` not installed.** The GIF script silently fails or produces an - empty file. Inspect `docs/images/cli_interactive/*.gif` after running - and check file sizes are non-zero. -- **Python-version-specific snapshots.** Tests whose names start with - `py_` (e.g., `tests/test_x.py::test_py_3_12_specific`) need - `test:all` to regenerate across all interpreters. Skipping that step - passes locally but breaks CI on the other matrix rows. -- **Encoding-dependent snapshots.** On Windows, `pytest-regressions` may - write CRLF line endings. Configure git or your editor to normalize, or - the diff will be all-lines-changed. +- **Regenerating without reading the diff.** `--regen-all` overwrites every snapshot the test touches. If a bug in your code changed the output, the snapshot will faithfully record the bug. +- **Committing snapshots separately from the code change.** Reviewers cannot tell intent from a snapshot-only commit. +- **`vhs` not installed.** The GIF script silently fails or produces an empty file. Inspect `docs/images/cli_interactive/*.gif` after running and check file sizes are non-zero. +- **Python-version-specific snapshots.** Tests whose names start with `py_` (e.g., `tests/test_x.py::test_py_3_12_specific`) need `test:all` to regenerate across all interpreters. Skipping that step passes locally but breaks CI on the other matrix rows. +- **Encoding-dependent snapshots.** On Windows, `pytest-regressions` may write CRLF line endings. Configure git or your editor to normalize, or the diff will be all-lines-changed. ## Stop and ask if -- A snapshot regenerates with a diff that does not match what your code - change should produce — there is a bug. Do not commit the snapshot. -- The CI failure asks for snapshot regeneration but you cannot reproduce - the diff locally — Python-version mismatch is the most likely cause; - flag it in the PR. -- `doc:screenshots` requires installing `vhs` and you are running in a - sandboxed environment that cannot install system packages. +- A snapshot regenerates with a diff that does not match what your code change should produce — there is a bug. Do not commit the snapshot. +- The CI failure asks for snapshot regeneration but you cannot reproduce the diff locally — Python-version mismatch is the most likely cause; flag it in the PR. +- `doc:screenshots` requires installing `vhs` and you are running in a sandboxed environment that cannot install system packages. diff --git a/docs/contributing/agents/validation.md b/docs/contributing/agents/validation.md index f6032d01d..c5a799501 100644 --- a/docs/contributing/agents/validation.md +++ b/docs/contributing/agents/validation.md @@ -1,19 +1,12 @@ # Validation Guide -How to verify a change to Commitizen without running the full CI matrix every -time. This page is the agent-facing counterpart to the human -[Contributing TL;DR](../contributing_tldr.md), focused on **which** selector -to run for a given change and **how** to recognize CI failures. +How to verify a change to Commitizen without running the full CI matrix every time. This page is the agent-facing counterpart to the human [Contributing TL;DR](../contributing_tldr.md), focused on **which** selector to run for a given change and **how** to recognize CI failures. -For the full poe command reference, see -[Contributing TL;DR](../contributing_tldr.md#command-cheat-sheet). +For the full poe command reference, see [Contributing TL;DR](../contributing_tldr.md#command-cheat-sheet). ## Targeted test map -During iteration, prefer running only the tests that cover what you changed. -The full suite is for the final pre-push run. Tests mirror the source tree -(see [Architecture Overview § Tests mirror the source tree](../architecture.md#tests-mirror-the-source-tree)); -the table below picks the most useful selectors. +During iteration, prefer running only the tests that cover what you changed. The full suite is for the final pre-push run. Tests mirror the source tree (see [Architecture Overview § Tests mirror the source tree](../architecture.md#tests-mirror-the-source-tree)); the table below picks the most useful selectors. | Changing... | Targeted selector | |---|---| @@ -31,15 +24,13 @@ the table below picks the most useful selectors. | A deprecation | `uv run pytest tests/test_deprecated.py -n auto` plus the affected subsystem's tests | | Exception classes | `uv run pytest tests/test_exceptions.py -n auto` | -Run mypy against the oldest supported Python version when adding type -annotations: +Run mypy against the oldest supported Python version when adding type annotations: ```bash uv run mypy --python-version 3.10 ``` -This catches `typing-extensions` vs stdlib import issues that the default -mypy run does not flag. +This catches `typing-extensions` vs stdlib import issues that the default mypy run does not flag. ## Choosing a final check @@ -59,9 +50,7 @@ Recommended sequence: ## CI failure recipes -The CI matrix is fail-fast across Python 3.10–3.14 × ubuntu/macos/windows -(see `.github/workflows/`). Inspect the earliest failing job; the others are -cancelled. +The CI matrix is fail-fast across Python 3.10–3.14 × ubuntu/macos/windows (see `.github/workflows/`). Inspect the earliest failing job; the others are cancelled. ### "Format Python code...Failed" @@ -74,8 +63,7 @@ git add -u && git commit --amend --no-edit ### mypy `[arg-type]` on a TypedDict construction -Dynamically constructed dicts (e.g., from `pytest.mark.parametrize`) passed -to a TypedDict-typed parameter need an explicit ignore: +Dynamically constructed dicts (e.g., from `pytest.mark.parametrize`) passed to a TypedDict-typed parameter need an explicit ignore: ```python @pytest.mark.parametrize("settings", [{"version_scheme": "pep440"}]) @@ -85,9 +73,7 @@ def test_x(settings: Settings) -> None: # type: ignore[arg-type] ### `pathspec 'vX.Y.Z' did not match` -`.pre-commit-config.yaml` pins a specific tag of this repo as a hook source. -When your branch is older than that tag, the hook fails because the tag is -unknown. Fix by rebasing onto the latest master: +`.pre-commit-config.yaml` pins a specific tag of this repo as a hook source. When your branch is older than that tag, the hook fails because the tag is unknown. Fix by rebasing onto the latest master: ```bash git fetch origin master @@ -96,44 +82,30 @@ git rebase origin/master ### `VersionProtocol` + `issubclass` `TypeError` -`commitizen/version_schemes.py:VersionProtocol` has non-method members -(properties), so it cannot be passed to `issubclass()`. For runtime -validation, use `hasattr` checks against the concrete members or duck-type -the value instead of subclass-checking. +`commitizen/version_schemes.py:VersionProtocol` has non-method members (properties), so it cannot be passed to `issubclass()`. For runtime validation, use `hasattr` checks against the concrete members or duck-type the value instead of subclass-checking. ### Tests pass locally but fail in CI on Windows only Most often a path-separator or encoding assumption: - Use `pathlib.Path` and `Path(...).as_posix()` for string comparisons. -- Read files with `encoding=` (the convention is to honor - `config.settings["encoding"]`; see - `commitizen/providers/base_provider.py:_get_encoding`). -- Avoid hardcoded `"\n"` when comparing file contents — use `splitlines()` - or set `newline=""` when writing. +- Read files with `encoding=` (the convention is to honor `config.settings["encoding"]`; see `commitizen/providers/base_provider.py:_get_encoding`). +- Avoid hardcoded `"\n"` when comparing file contents — use `splitlines()` or set `newline=""` when writing. ### `cz check` rejects a fixup or merge commit on the branch -`poe check-commit` runs `cz --no-raise 3 check --rev-range origin/master..`. -Squash or amend the offending commit so the branch contains only -Conventional-Commit-shaped messages, or rebase to drop it. +`poe check-commit` runs `cz --no-raise 3 check --rev-range origin/master..`. Squash or amend the offending commit so the branch contains only Conventional-Commit-shaped messages, or rebase to drop it. ### Coverage drop on CodeCov -The `cover` task generates `coverage.xml` consumed by CodeCov. If coverage -drops, add tests for the new code paths before pushing. Inspect -`coverage.xml` locally or re-run `uv run poe cover` and inspect the -terminal report. +The `cover` task generates `coverage.xml` consumed by CodeCov. If coverage drops, add tests for the new code paths before pushing. Inspect `coverage.xml` locally or re-run `uv run poe cover` and inspect the terminal report. ## Pre-commit hooks -Hooks are defined in `.pre-commit-config.yaml` and executed via -[`prek`](https://github.com/j178/prek), a `pre-commit`-compatible runner. -Install once: +Hooks are defined in `.pre-commit-config.yaml` and executed via [`prek`](https://github.com/j178/prek), a `pre-commit`-compatible runner. Install once: ```bash uv run poe setup-pre-commit ``` -After install, `prek` runs on every `git commit`. `poe ci` invokes -`prek run --all-files` to mirror what CI will do. +After install, `prek` runs on every `git commit`. `poe ci` invokes `prek run --all-files` to mirror what CI will do. diff --git a/docs/contributing/architecture.md b/docs/contributing/architecture.md index 2c8271ecf..6169bd050 100644 --- a/docs/contributing/architecture.md +++ b/docs/contributing/architecture.md @@ -1,11 +1,8 @@ # Architecture Overview -This page is a map of Commitizen's subsystems and extension points. It is aimed -at contributors (human or AI) who are about to change code and need to know -where things live and how they fit together. +This page is a map of Commitizen's subsystems and extension points. It is aimed at contributors (human or AI) who are about to change code and need to know where things live and how they fit together. -For end-user concepts, see the [Commands](../commands/init.md) and -[Configuration](../config/configuration_file.md) sections. +For end-user concepts, see the [Commands](../commands/init.md) and [Configuration](../config/configuration_file.md) sections. ## Top-level layout @@ -31,9 +28,7 @@ commitizen/ ## Extension points -Commitizen is plugin-friendly. Four kinds of extensions can be registered by -external packages via Python entry points; the built-in implementations use -the same mechanism. +Commitizen is plugin-friendly. Four kinds of extensions can be registered by external packages via Python entry points; the built-in implementations use the same mechanism. | Kind | Entry-point group | Built-ins registered in `pyproject.toml` | Base class / Protocol | |---|---|---|---| @@ -42,68 +37,37 @@ the same mechanism. | Version scheme | `commitizen.scheme` | `pep440`, `semver`, `semver2` | `commitizen/version_schemes.py:VersionProtocol` | | Changelog format | `commitizen.changelog_format` | `markdown`, `asciidoc`, `textile`, `restructuredtext` | `commitizen/changelog_formats/base.py:BaseFormat` | -Each kind is loaded lazily via `importlib.metadata.entry_points(...)`. To add -a new built-in implementation you register it in `pyproject.toml` under the -appropriate `[project.entry-points."..."]` table. +Each kind is loaded lazily via `importlib.metadata.entry_points(...)`. To add a new built-in implementation you register it in `pyproject.toml` under the appropriate `[project.entry-points."..."]` table. -End-user documentation for these extension points lives elsewhere — see -[Version Provider](../config/version_provider.md), -[Customized Python Class](../customization/python_class.md), and -[Changelog Template](../customization/changelog_template.md). This page -focuses on where the source lives and how it is wired together. +End-user documentation for these extension points lives elsewhere — see [Version Provider](../config/version_provider.md), [Customized Python Class](../customization/python_class.md), and [Changelog Template](../customization/changelog_template.md). This page focuses on where the source lives and how it is wired together. ## Configuration layering Configuration is discovered, parsed, and exposed as a `Settings` TypedDict. -1. **Discovery** — `commitizen/config/__init__.py:read_cfg` searches the - working directory (and the git project root when different) for known - config files in a defined order (see - `commitizen/defaults.py:CONFIG_FILES`). -2. **Format-specific parsing** — `commitizen/config/factory.py:create_config` - dispatches to one of: - - `commitizen/config/toml_config.py:TomlConfig` (TOML; includes - `pyproject.toml` under `[tool.commitizen]`) +1. **Discovery** — `commitizen/config/__init__.py:read_cfg` searches the working directory (and the git project root when different) for known config files in a defined order (see `commitizen/defaults.py:CONFIG_FILES`). +2. **Format-specific parsing** — `commitizen/config/factory.py:create_config` dispatches to one of: + - `commitizen/config/toml_config.py:TomlConfig` (TOML; includes `pyproject.toml` under `[tool.commitizen]`) - `commitizen/config/json_config.py:JsonConfig` - `commitizen/config/yaml_config.py:YAMLConfig` -3. **Defaults merge** — every parser inherits from - `commitizen/config/base_config.py:BaseConfig`, which starts from - `commitizen/defaults.py:DEFAULT_SETTINGS` and overlays the user values. -4. **Consumption** — commands read `config.settings[...]`; providers and - formats receive the live `BaseConfig` so they can react to settings such - as `encoding`, `tag_format`, and `version_scheme`. +3. **Defaults merge** — every parser inherits from `commitizen/config/base_config.py:BaseConfig`, which starts from `commitizen/defaults.py:DEFAULT_SETTINGS` and overlays the user values. +4. **Consumption** — commands read `config.settings[...]`; providers and formats receive the live `BaseConfig` so they can react to settings such as `encoding`, `tag_format`, and `version_scheme`. -The `Settings` TypedDict in `defaults.py` is the authoritative list of -recognized keys. Adding a new setting almost always means touching this file. +The `Settings` TypedDict in `defaults.py` is the authoritative list of recognized keys. Adding a new setting almost always means touching this file. ## Command flow -`cli.py` parses `argv` with [decli](https://github.com/woile/decli), resolves -the chosen subcommand to a class under `commitizen/commands/`, then -instantiates and calls it. A typical command: +`cli.py` parses `argv` with [decli](https://github.com/woile/decli), resolves the chosen subcommand to a class under `commitizen/commands/`, then instantiates and calls it. A typical command: 1. Reads `config.settings`. -2. Resolves dependencies (provider, scheme, changelog format) via the - `get_*` helpers in the respective subpackages. -3. Does its work, surfacing user-visible text through `commitizen/out.py` - and errors through `commitizen/exceptions.py` (each exception carries an - exit code defined in `commitizen/exceptions.py` and documented in - [Exit Codes](../exit_codes.md)). - -`cz commit` and `cz bump` are the most stateful commands — they call `git` -through `commitizen/git.py`, run user-defined `pre_bump_hooks`/`post_bump_hooks` -via `commitizen/hooks.py`, and may mutate version files through the active -provider. +2. Resolves dependencies (provider, scheme, changelog format) via the `get_*` helpers in the respective subpackages. +3. Does its work, surfacing user-visible text through `commitizen/out.py` and errors through `commitizen/exceptions.py` (each exception carries an exit code defined in `commitizen/exceptions.py` and documented in [Exit Codes](../exit_codes.md)). + +`cz commit` and `cz bump` are the most stateful commands — they call `git` through `commitizen/git.py`, run user-defined `pre_bump_hooks`/`post_bump_hooks` via `commitizen/hooks.py`, and may mutate version files through the active provider. ## Templates and changelog rendering -Changelog rendering uses Jinja2. Built-in templates live under -`commitizen/templates/`. The template loader is a `ChoiceLoader` whose first -loader is `FileSystemLoader(".")` and whose second loader is provided by the -active commit-convention class (default: a `PackageLoader` for built-in -templates), so a repository can override any built-in template by placing a -file of the same name at the project root or in the configured template -directory. +Changelog rendering uses Jinja2. Built-in templates live under `commitizen/templates/`. The template loader is a `ChoiceLoader` whose first loader is `FileSystemLoader(".")` and whose second loader is provided by the active commit-convention class (default: a `PackageLoader` for built-in templates), so a repository can override any built-in template by placing a file of the same name at the project root or in the configured template directory. ## Tests mirror the source tree @@ -120,7 +84,4 @@ Tests are organized to mirror the source modules: | `commitizen/changelog.py` | `tests/test_changelog.py`, `tests/test_incremental_build.py` | | `commitizen/tags.py` | `tests/test_tags.py` | -When you add or modify a subsystem, the targeted test file is usually -obvious from this mirror. The -[targeted-test map for agents](agents/validation.md#targeted-test-map) -captures the most useful selectors. +When you add or modify a subsystem, the targeted test file is usually obvious from this mirror. The [targeted-test map for agents](agents/validation.md#targeted-test-map) captures the most useful selectors. diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index de62352d3..165f87130 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -3,9 +3,7 @@ First, thank you for taking the time to contribute! 🎉 Your contributions help make Commitizen better for everyone. !!! tip "Using an AI assistant?" - See [For AI Agents](agents/index.md) for agent-shaped recipes, a - targeted-test map, and CI-failure playbooks that complement (and - aggressively link back to) this human-facing guide. + See [For AI Agents](agents/index.md) for agent-shaped recipes, a targeted-test map, and CI-failure playbooks that complement (and aggressively link back to) this human-facing guide. When contributing to Commitizen, we encourage you to: diff --git a/docs/contributing/pull_request.md b/docs/contributing/pull_request.md index ddffcb311..4a808f7ff 100644 --- a/docs/contributing/pull_request.md +++ b/docs/contributing/pull_request.md @@ -27,9 +27,7 @@ We welcome contributions that use AI tools for assistance, but we have strict qu Most of our new documentation changes are, of course, generated by AI, but we still need to review it and make sure it's correct. !!! tip "AI agents driving the PR" - See [For AI Agents](agents/index.md) for agent-shaped recipes, - targeted-test selectors, and playbooks for recurring task types - (adding a provider, deprecating an API, regenerating snapshots, etc.). + See [For AI Agents](agents/index.md) for agent-shaped recipes, targeted-test selectors, and playbooks for recurring task types (adding a provider, deprecating an API, regenerating snapshots, etc.). ![when bro's code is filled with "🔥 🚀 💥 ❌ ✅"](https://images3.memedroid.com/images/UPLOADED78/69501f1c23cab.webp) From 801fc2aaa6d88a4173ca78e08f5413ab91265d75 Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 30 May 2026 13:41:07 +0800 Subject: [PATCH 5/8] docs(agents): trim Do-Not-Touch to commitizen-specific traps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop `uv.lock` and the cache/build-artifact list (.mypy_cache, .ruff_cache, .venv, site, coverage.xml, ...) — those are generic lockfile/gitignore conventions that any modern agent already knows. Keep only the commitizen-specific files that get regenerated by cz bump, cz changelog, poe doc:screenshots, and poe test:regen, which an agent could not infer from convention alone. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 477ff0491..f933fe952 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,15 +41,13 @@ For recurring task types (add a provider, deprecate an API, regenerate snapshots ## Do not touch -These files are generated, tracked, or otherwise managed automatically. Do not edit them directly: +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`. -- `uv.lock` — only modify by changing dependencies in `pyproject.toml` and running `uv sync`, never by hand-editing. -- Anything under `__pycache__/`, `.mypy_cache/`, `.ruff_cache/`, `.venv/`, `site/`, `coverage.xml`, `junit.xml`. ## Mandatory PR reminders From f1d1fcd04a1f38077a5b8b92b27a1dcc4cc031b1 Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 30 May 2026 13:46:12 +0800 Subject: [PATCH 6/8] docs(agents): drop redundant `Agent-specific deltas` section The four items in this section all duplicated content from AGENTS.md's Mandatory PR reminders (PR template, poe all vs poe ci, do-not-touch) or pointed at validation.md (targeted tests). The `When to read what` table above already routes readers to the same destinations in a more compact form, so the section was pure noise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/contributing/agents/index.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/contributing/agents/index.md b/docs/contributing/agents/index.md index e40f27cde..39a4d9b7a 100644 --- a/docs/contributing/agents/index.md +++ b/docs/contributing/agents/index.md @@ -35,15 +35,6 @@ When two documents could host a piece of guidance, this table is the tie-breaker 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. -## Agent-specific deltas - -Humans absorb these rules through review; agents need them stated: - -1. **Complete the PR template fully**, including the AI-disclosure checkbox and the `Generated-by:` trailer. The maintainers re-run the commands you list under "Steps to Test This Pull Request" — make them exact. -2. **`uv run poe all` is the pre-push verification command** named in the PR template. `poe ci` is the CI-equivalent runner (uses `prek` and does not auto-format); run it too if you want to mirror CI exactly. See the [Validation Guide](validation.md#choosing-a-final-check) for the distinction. -3. **Do not touch generated artifacts.** See the do-not-touch list in [`AGENTS.md`](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md). -4. **Prefer targeted test selectors during iteration** — see the [targeted-test map](validation.md#targeted-test-map). The full suite is fine for a final pre-push run. - ## 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. From ac589a8e9f80e77d625a73662b294ee27fdd7e1d Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 30 May 2026 13:49:54 +0800 Subject: [PATCH 7/8] docs(agents): cut more redundant content Second redundancy audit. Drop content that duplicated other docs: * agents/index.md: remove `Source-of-truth map` table; it duplicated the `When to read what` table below it. The dedup rule is still enforced by the `Updating these pages` section. * agents/index.md: collapse the `fall back to the general flow` numbered list to a single sentence; the steps were generic engineering advice already covered by `AGENTS.md` and the pull-request template. * validation.md: drop the `mypy --python-version 3.10` snippet; the identical command already lives in `contributing_tldr.md`. * validation.md: drop the `Pre-commit hooks` section; `contributing.md` already covers `poe setup-pre-commit` and how hooks run. * AGENTS.md: trim `When unsure` to the backward-compatibility rule; the other two bullets were generic agent etiquette. * AGENTS.md: drop the `Follow Conventional Commits` reminder; every commitizen contributor knows this and prek enforces it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 5 +---- docs/contributing/agents/index.md | 24 ++---------------------- docs/contributing/agents/validation.md | 18 ------------------ 3 files changed, 3 insertions(+), 44 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f933fe952..2e310e581 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,10 +56,7 @@ These are easy to miss when working from an agent and are required by the PR tem 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. -4. **Follow Conventional Commits** — the project uses itself to validate commit messages. ## When unsure -- Read the existing tests and user docs to understand the expected behavior before changing code. -- 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). -- Cross-platform parity matters — if you cannot test on macOS or Windows locally, surface that in the PR description. +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). diff --git a/docs/contributing/agents/index.md b/docs/contributing/agents/index.md index 39a4d9b7a..277631c5b 100644 --- a/docs/contributing/agents/index.md +++ b/docs/contributing/agents/index.md @@ -7,20 +7,6 @@ These pages are written for AI agents contributing to Commitizen. Human contribu > definition at `.agents/skills/commitizen/SKILL.md` in the repo root. > The pages here are for working **on** Commitizen itself. -## Source-of-truth map - -When two documents could host a piece of guidance, this table is the tie-breaker. Agent pages that drift from it should be fixed, not the human pages. - -| Topic | Lives in | Why | -|---|---|---| -| Setup, dev workflow, PR lifecycle, labels | [Contributing](../contributing.md) | Same for humans and agents | -| poe command reference | [Contributing TL;DR](../contributing_tldr.md) | One canonical cheat sheet | -| PR etiquette, AI-assisted policy | [Pull Request Guidelines](../pull_request.md) and [PR template](https://github.com/commitizen-tools/commitizen/blob/master/.github/pull_request_template.md) | One canonical policy | -| Codebase topology and extension points | [Architecture Overview](../architecture.md) | Useful to humans too | -| Always-loaded agent rules | [`AGENTS.md`](https://github.com/commitizen-tools/commitizen/blob/master/AGENTS.md) | Auto-loaded as system context every session | -| Targeted test selectors and CI failure recipes | [Validation Guide](validation.md) | Agent-specific | -| Recipes for recurring task types | [Playbooks](#playbooks) | Agent-specific | - ## When to read what | You want to... | Read | @@ -45,14 +31,8 @@ Recipes for recurring task types. Each playbook is self-contained: trigger, file - [Deprecate a public API](playbooks/deprecate-public-api.md) - [Update generated snapshots and screenshots](playbooks/update-snapshots.md) -If your task does not match a playbook, fall back to the general flow: - -1. Read the [Architecture Overview](../architecture.md) for the relevant subsystem. -2. Read 1–2 existing examples in the same directory to match local conventions. -3. Make the change, plus tests, plus user-facing docs. -4. Iterate with targeted tests; finish with `uv run poe all`. -5. Open the PR using the template; check the AI-disclosure box. +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 for breakage. If you find yourself restating something that already lives in a human-facing doc, link to it instead and shorten the agent doc. +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. diff --git a/docs/contributing/agents/validation.md b/docs/contributing/agents/validation.md index c5a799501..a6a457b01 100644 --- a/docs/contributing/agents/validation.md +++ b/docs/contributing/agents/validation.md @@ -24,14 +24,6 @@ During iteration, prefer running only the tests that cover what you changed. The | A deprecation | `uv run pytest tests/test_deprecated.py -n auto` plus the affected subsystem's tests | | Exception classes | `uv run pytest tests/test_exceptions.py -n auto` | -Run mypy against the oldest supported Python version when adding type annotations: - -```bash -uv run mypy --python-version 3.10 -``` - -This catches `typing-extensions` vs stdlib import issues that the default mypy run does not flag. - ## Choosing a final check | Command | When to run | What it does | @@ -99,13 +91,3 @@ Most often a path-separator or encoding assumption: ### Coverage drop on CodeCov The `cover` task generates `coverage.xml` consumed by CodeCov. If coverage drops, add tests for the new code paths before pushing. Inspect `coverage.xml` locally or re-run `uv run poe cover` and inspect the terminal report. - -## Pre-commit hooks - -Hooks are defined in `.pre-commit-config.yaml` and executed via [`prek`](https://github.com/j178/prek), a `pre-commit`-compatible runner. Install once: - -```bash -uv run poe setup-pre-commit -``` - -After install, `prek` runs on every `git commit`. `poe ci` invokes `prek run --all-files` to mirror what CI will do. From 6dda294021fc063f4b64eb9fc5507658bb862261 Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 30 May 2026 14:00:05 +0800 Subject: [PATCH 8/8] docs(agents): fix incorrect playbook examples Validation against the actual source code surfaced three inaccuracies: * add-version-provider.md: `composer_provider.py` was labelled `JSON file with non-standard layout`, but it is actually the simplest `JsonProvider` (just `filename` and `indent`). Replace with `npm_provider.py` for the non-standard layout / multi-file case, and re-introduce composer as the simplest-case example. * add-changelog-format.md: pointed agents at `restructuredtext.py` as an example of overriding only `parse_version_from_title` + `parse_title_level`. RST actually overrides `get_metadata_from_file` because its titles span multiple lines. Re-route the line-anchored example to `markdown.py` / `asciidoc.py` / `textile.py` and keep RST as the multi-line example. Add the multi-line escape hatch to Step 2 and clarify that the template extension comes from the class attribute, not the format name. * architecture.md: claimed entry points are loaded `lazily`. Only providers are looked up at call time; `KNOWN_CHANGELOG_FORMATS` is populated at module import time. Drop the misleading qualifier. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agents/playbooks/add-changelog-format.md | 12 +++++++----- .../agents/playbooks/add-version-provider.md | 7 ++++--- docs/contributing/architecture.md | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/contributing/agents/playbooks/add-changelog-format.md b/docs/contributing/agents/playbooks/add-changelog-format.md index b73279bcc..60c630548 100644 --- a/docs/contributing/agents/playbooks/add-changelog-format.md +++ b/docs/contributing/agents/playbooks/add-changelog-format.md @@ -13,11 +13,11 @@ Architectural context: [Architecture § Extension points](../../architecture.md# ## 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` — abstract implementation; you only need to override `parse_version_from_title` and `parse_title_level`. +- `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: - - Heading-prefix-based: `commitizen/changelog_formats/markdown.py` (uses `#`, `##` prefixes). - - Underline-based: `commitizen/changelog_formats/restructuredtext.py` (uses `===`, `---` lines). -- `commitizen/templates/` — Jinja2 templates named `CHANGELOG..j2` control rendering. + - 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..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_.py` — every format has parity tests; copy the closest one. ## Steps @@ -25,9 +25,11 @@ Architectural context: [Architecture § Extension points](../../architecture.md# 1. **Create the format module** at `commitizen/changelog_formats/.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**: +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..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"]`: diff --git a/docs/contributing/agents/playbooks/add-version-provider.md b/docs/contributing/agents/playbooks/add-version-provider.md index 706a9f8e9..ce9bf0d67 100644 --- a/docs/contributing/agents/playbooks/add-version-provider.md +++ b/docs/contributing/agents/playbooks/add-version-provider.md @@ -15,9 +15,10 @@ Architectural context: [Architecture § Extension points](../../architecture.md# - `commitizen/providers/__init__.py` — registration helper `get_provider`, entry-point group `commitizen.provider`, default provider name. - `commitizen/providers/base_provider.py` — `VersionProvider`, `FileProvider`, `JsonProvider`, `TomlProvider` base classes. - An existing provider that resembles your target: - - JSON file with non-standard layout: `commitizen/providers/composer_provider.py` - - TOML file with multi-file updates: `commitizen/providers/uv_provider.py` - - SCM tag-based, no file write: `commitizen/providers/scm_provider.py` + - Simplest JSON case (single file, top-level `version` key): `commitizen/providers/composer_provider.py` — only sets `filename` and `indent`. + - JSON with multi-file updates (package + lockfile + shrinkwrap): `commitizen/providers/npm_provider.py` — overrides `get_version`/`set_version`. + - TOML with multi-file updates (`pyproject.toml` + `uv.lock`): `commitizen/providers/uv_provider.py`. + - SCM tag-based, no file write: `commitizen/providers/scm_provider.py`. - Test for the closest existing provider: `tests/providers/test__provider.py`. - `commitizen/config/base_config.py:BaseConfig` — what your provider's `__init__(config)` will receive. diff --git a/docs/contributing/architecture.md b/docs/contributing/architecture.md index 6169bd050..90550fe3f 100644 --- a/docs/contributing/architecture.md +++ b/docs/contributing/architecture.md @@ -37,7 +37,7 @@ Commitizen is plugin-friendly. Four kinds of extensions can be registered by ext | Version scheme | `commitizen.scheme` | `pep440`, `semver`, `semver2` | `commitizen/version_schemes.py:VersionProtocol` | | Changelog format | `commitizen.changelog_format` | `markdown`, `asciidoc`, `textile`, `restructuredtext` | `commitizen/changelog_formats/base.py:BaseFormat` | -Each kind is loaded lazily via `importlib.metadata.entry_points(...)`. To add a new built-in implementation you register it in `pyproject.toml` under the appropriate `[project.entry-points."..."]` table. +Each kind is registered as a Python entry point and resolved through `importlib.metadata.entry_points(...)`. To add a new built-in implementation you register it in `pyproject.toml` under the appropriate `[project.entry-points."..."]` table. End-user documentation for these extension points lives elsewhere — see [Version Provider](../config/version_provider.md), [Customized Python Class](../customization/python_class.md), and [Changelog Template](../customization/changelog_template.md). This page focuses on where the source lives and how it is wired together.