devstuff 1.17.0__tar.gz → 1.18.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devstuff-1.17.0 → devstuff-1.18.0}/.github/workflows/test-installs.yml +1 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/CHANGELOG.md +6 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/CLAUDE.md +43 -4
- {devstuff-1.17.0 → devstuff-1.18.0}/PKG-INFO +98 -1
- {devstuff-1.17.0 → devstuff-1.18.0}/README.md +97 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/docs/specs/README.md +1 -0
- devstuff-1.18.0/docs/specs/commitizen-config/development-plan.md +80 -0
- devstuff-1.18.0/docs/specs/commitizen-config/specifications.md +119 -0
- devstuff-1.18.0/docs/specs/commitizen-config/stack-decisions.md +183 -0
- devstuff-1.18.0/pyproject.toml +174 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/configure/__init__.py +6 -0
- devstuff-1.18.0/src/dev_setup/configure/commitizen/__init__.py +1 -0
- devstuff-1.18.0/src/dev_setup/configure/commitizen/detect.py +213 -0
- devstuff-1.18.0/src/dev_setup/configure/commitizen/model.py +510 -0
- devstuff-1.18.0/src/dev_setup/configure/commitizen/render.py +552 -0
- devstuff-1.18.0/src/dev_setup/configure/commitizen/validate.py +321 -0
- devstuff-1.18.0/src/dev_setup/configure/commitizen/wizard.py +613 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/tools.yaml +12 -0
- devstuff-1.18.0/tests/integration/test_commitizen_config.py +135 -0
- devstuff-1.18.0/tests/test_configure_commitizen.py +759 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/uv.lock +1 -1
- devstuff-1.17.0/pyproject.toml +0 -75
- {devstuff-1.17.0 → devstuff-1.18.0}/.claude/skills/run-stuff/SKILL.md +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/.claude/skills/run-stuff/smoke.sh +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/.github/workflows/bump.yml +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/.github/workflows/ci.yml +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/.github/workflows/publish.yml +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/.gitignore +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/dev/Dockerfile +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/dev/Dockerfile.ci +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/dev/Makefile +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/dev/docker-compose.yml +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/dev-setup +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/docs/specs/agent/development-plan.md +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/docs/specs/agent/specifications.md +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/docs/specs/agent/stack-decisions.md +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/docs/specs/starship-config/development-plan.md +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/docs/specs/starship-config/specifications.md +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/docs/specs/starship-config/stack-decisions.md +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/install.sh +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/__init__.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/__main__.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/__init__.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/approval.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/bridges.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/catalog.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/completion.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/config.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/loop.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/ollama.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/preflight.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/primitives.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/registry.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/sandbox.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/session.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/transcript.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent/wizard.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent_tools.schema.json +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/agent_tools.yaml +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/base.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/catalog.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/cli.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/__init__.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/add_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/agent_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/catalog_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/configure_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/delete_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/docs_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/functions_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/help_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/install_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/list_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/remove_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/run_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/skills_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/commands/update_cmd.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/configure/starship/__init__.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/configure/starship/fonts.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/configure/starship/model.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/configure/starship/preview.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/configure/starship/render.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/configure/starship/wizard.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/function_runner.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/functions.schema.json +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/functions.yaml +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/functions_catalog.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/functions_registry.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/generic.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/registry.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/src/dev_setup/ui.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/__init__.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/integration/__init__.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/integration/conftest.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/integration/test_agent_smoke.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/integration/test_agent_tui.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/integration/test_tools.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_agent.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_agent_completion.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_agent_loop.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_agent_sandbox.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_agent_tools.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_agent_wizard.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_catalog.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_configure_starship.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_functions.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_generic.py +0 -0
- {devstuff-1.17.0 → devstuff-1.18.0}/tests/test_registry.py +0 -0
|
@@ -173,8 +173,9 @@ ones tools already have.
|
|
|
173
173
|
## Configurators (`configure/`) — tool-specific wizards, deliberately *not* catalog-driven
|
|
174
174
|
|
|
175
175
|
`src/dev_setup/configure/` holds per-tool setup wizards (`devstuff configure <tool>`), registered
|
|
176
|
-
in a `CONFIGURATORS` dict keyed by catalog tool key.
|
|
177
|
-
`configure/starship/{model,render,preview,wizard}.py
|
|
176
|
+
in a `CONFIGURATORS` dict keyed by catalog tool key. There are two:
|
|
177
|
+
`configure/starship/{model,render,preview,wizard}.py` and
|
|
178
|
+
`configure/commitizen/{model,render,detect,validate,wizard}.py`.
|
|
178
179
|
|
|
179
180
|
**Why this one breaks the YAML-catalog rule.** Installation generalises into ~7 mechanisms, which
|
|
180
181
|
is what makes `GenericTool` possible. Configuration does not: starship's config is a TOML file of
|
|
@@ -244,8 +245,46 @@ or palette is one entry; no other file changes.
|
|
|
244
245
|
the ordinary catalog path (`nerd-font` in `tools.yaml` → `install_cmd.install_by_key`), never a
|
|
245
246
|
private download (SD-10).
|
|
246
247
|
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
**Within the commitizen configurator, the object being configured is a list of commit types, not
|
|
249
|
+
a settings sheet.** `TYPES` in `model.py` is ordered (declaration order *is* prompt order, changelog
|
|
250
|
+
order and regex-alternation order), and `render.py` derives all nine `cz_customize` settings from
|
|
251
|
+
it — so adding a type is one `ChangeType` record and it reaches `bump_pattern`, `bump_map`,
|
|
252
|
+
`schema_pattern`, `change_type_map`, `change_type_order`, `commit_parser` and `questions` with no
|
|
253
|
+
other edit. Full reasoning in `docs/specs/commitizen-config/`.
|
|
254
|
+
|
|
255
|
+
**Things learned from the real binary — don't "simplify" these away:**
|
|
256
|
+
- `bump_map` is an **ordered** map and commitizen `break`s at the first key that `re.match`es, so
|
|
257
|
+
the two breaking-change rules (`^.+!$`, `^BREAKING[\-\ ]CHANGE`) must be emitted first. Reorder
|
|
258
|
+
them and `feat(api)!:` silently ships as a MINOR.
|
|
259
|
+
- What `bump_map`'s keys are matched against is **group 1 of `bump_pattern`** (`feat(api)!`), not
|
|
260
|
+
the commit message. That is why `^.+!$` works at all, and why every selected type belongs in the
|
|
261
|
+
pattern even when it has no map entry.
|
|
262
|
+
- `schema_pattern` must always accept the `bump:` prefix (`ALWAYS_ACCEPTED` in `model.py`):
|
|
263
|
+
`cz bump` writes its own commit with that prefix, and `cz check --rev-range` over a release
|
|
264
|
+
otherwise rejects commitizen's own commit. `cz_conventional_commits` accepts `bump` for exactly
|
|
265
|
+
this reason without ever offering it in the picker.
|
|
266
|
+
- `commit_parser`'s trailing `|\w+!` alternative is load-bearing: it is what keeps `docs!: …` (a
|
|
267
|
+
breaking change on a type with no changelog section) in the release notes at all. Verified — it
|
|
268
|
+
lands in an unlabelled group rather than vanishing.
|
|
269
|
+
- The `BREAKING CHANGE` changelog heading only collects commits with a **footer**. A `feat!:` still
|
|
270
|
+
bumps the major but is written up under Features, because that is the type it declared.
|
|
271
|
+
- Regexes are emitted as TOML **literal** strings (`'…'`) so no backslash needs doubling;
|
|
272
|
+
`message_template` and `schema` are the exceptions (they carry real newlines, so they are basic
|
|
273
|
+
strings). A user-supplied value containing a quote falls back to a basic string automatically.
|
|
274
|
+
- `config_path()` mirrors `commitizen.config.read_cfg`: search order *and* the rule that a file
|
|
275
|
+
without a `commitizen` section doesn't count — otherwise every Python project on disk looks
|
|
276
|
+
already-configured because it has a `pyproject.toml`.
|
|
277
|
+
- The `pyproject.toml` splice is line-based, so it verifies itself by parsing the result back and
|
|
278
|
+
comparing the settings; a mismatch returns `None` and the caller writes `.cz.toml` instead. Don't
|
|
279
|
+
replace that check with reasoning about which files it can handle.
|
|
280
|
+
- `validate.py` is the "measured, not assumed" half: it replays commits through the real
|
|
281
|
+
`cz bump --dry-run` in a throwaway repo. It runs on an explicit menu action plus once at save
|
|
282
|
+
time — not on every redraw (~3s, unlike starship's millisecond preview) — and a disagreement
|
|
283
|
+
*warns*, it never vetoes a save. Keep that distinction in the comments.
|
|
284
|
+
|
|
285
|
+
Not yet built: configurators for anything other than starship and commitizen, and round-tripping an
|
|
286
|
+
existing hand-edited config back into wizard state (the timestamped backup is the safety net
|
|
287
|
+
instead).
|
|
249
288
|
|
|
250
289
|
## Specs (`docs/specs/`)
|
|
251
290
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devstuff
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.0
|
|
4
4
|
Summary: Development environment setup CLI for Linux
|
|
5
5
|
Project-URL: Repository, https://github.com/thesawdawg/dev-setup-py
|
|
6
6
|
Author-email: Sawyer <sawyerksu@gmail.com>
|
|
@@ -203,8 +203,104 @@ devstuff configure starship --output /tmp/try.toml # write elsewhere, leave th
|
|
|
203
203
|
|
|
204
204
|
| Tool | What it configures |
|
|
205
205
|
|------|-------------------|
|
|
206
|
+
| `commitizen` | Commit types, what each one bumps, git tags, and changelog sections |
|
|
206
207
|
| `starship` | Prompt style, colour palette, which sections appear, and layout |
|
|
207
208
|
|
|
209
|
+
#### Commitizen wizard
|
|
210
|
+
|
|
211
|
+
Commitizen needs a config file before it does anything, and everything past its built-in
|
|
212
|
+
Conventional Commits rules means `cz_customize` — nine coupled regexes where `bump_map`'s keys
|
|
213
|
+
are matched against **group 1** of `bump_pattern`, the two breaking-change rules have to come
|
|
214
|
+
*first* because commitizen stops at the first key that matches, and `schema_pattern` has to
|
|
215
|
+
accept whatever `message_template` produces. Get the order wrong and `feat(api)!:` quietly ships
|
|
216
|
+
as a minor release.
|
|
217
|
+
|
|
218
|
+
The wizard asks about commit types and version rules, and derives all nine settings from the
|
|
219
|
+
answers:
|
|
220
|
+
|
|
221
|
+
| Step | Options |
|
|
222
|
+
|------|---------|
|
|
223
|
+
| **Convention** | `cz_conventional_commits` (commitizen's fixed rules — nothing to configure) · `cz_customize` (your types, your bump rules) |
|
|
224
|
+
| **Commit types** | Checkbox over `feat`, `fix`, `refactor`, `perf`, `docs`, `style`, `test`, `build`, `ci` (all on, matching Conventional Commits) plus `chore`, `revert`, `deps`, `security` — and you can add your own |
|
|
225
|
+
| **Per type** | What it bumps (`MAJOR` / `MINOR` / `PATCH` / no release), whether it appears in the changelog, under which heading, and its hotkey in `cz commit` |
|
|
226
|
+
| **Versioning** | Where the version lives (`commitizen`, `scm`, `pep621`, `uv`, `poetry`, `cargo`, `npm`, `composer`) · scheme (`semver`, `semver2`, `pep440`) · tag format · other files carrying the version |
|
|
227
|
+
| **Release** | Changelog on bump · `0.x` mode · annotated tags · GPG signing · incremental changelog · prerelease merging · `allow_abort` · hotkeys · changelog file · bump commit message |
|
|
228
|
+
| **Prompt** | Which questions `cz commit` asks — scope, body, footer |
|
|
229
|
+
| **Destination** | `pyproject.toml` (spliced in place) or a standalone `.cz.toml` |
|
|
230
|
+
|
|
231
|
+
The custom branch **starts as an exact copy of Conventional Commits** — the built-in type table
|
|
232
|
+
reproduces `commitizen.defaults.BUMP_MAP` and its picker, read out of the installed package and
|
|
233
|
+
pinned by a test. Switching to custom to add one type does not cost you the convention.
|
|
234
|
+
|
|
235
|
+
Before it asks anything, the wizard reads the project: git root, any existing commitizen config
|
|
236
|
+
(in commitizen's own search order, ignoring files without a `commitizen` section), the version
|
|
237
|
+
provider and current version from `pyproject.toml`/`package.json`/`Cargo.toml`/`composer.json`,
|
|
238
|
+
the latest git tag, and an existing changelog.
|
|
239
|
+
|
|
240
|
+
The review screen shows a sample `cz commit` message, the bump table, and the changelog sections:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
what each type does to 1.4.2
|
|
244
|
+
|
|
245
|
+
commit increment new version
|
|
246
|
+
feat: … MINOR 1.5.0
|
|
247
|
+
fix: … PATCH 1.4.3
|
|
248
|
+
docs: … — 1.4.2
|
|
249
|
+
feat!: … MAJOR 2.0.0
|
|
250
|
+
BREAKING CHANGE: … (footer) MAJOR 2.0.0
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**That table is checked against the real thing.** "Check these rules against the real cz" builds
|
|
254
|
+
a throwaway git repo tagged at 1.4.2, replays one commit per bump level plus both breaking forms
|
|
255
|
+
through `cz bump --dry-run`, and reports whether commitizen agrees rule by rule. The same check
|
|
256
|
+
runs automatically before saving — a disagreement is reported and asks for confirmation, but
|
|
257
|
+
never blocks the save. "Preview a generated changelog" runs the real `cz changelog --dry-run`
|
|
258
|
+
over one sample commit per type. Without `cz` installed the wizard still works; the checks say so
|
|
259
|
+
rather than failing.
|
|
260
|
+
|
|
261
|
+
On save:
|
|
262
|
+
|
|
263
|
+
- Into `pyproject.toml`, every `[tool.commitizen…]` table is replaced and **the rest of the file
|
|
264
|
+
is left byte for byte**. The result is parsed back and compared to what was meant; if it does
|
|
265
|
+
not match, the write falls back to a standalone `.cz.toml` and the original is untouched.
|
|
266
|
+
- Any existing file is copied to `<name>.bak.<timestamp>` first, and a config that wasn't written
|
|
267
|
+
by this wizard asks before being replaced.
|
|
268
|
+
- If more than one commitizen config now exists, you're told which one commitizen will actually
|
|
269
|
+
read — `.cz.toml` beats `pyproject.toml`, so a new dotfile can silently shadow settings you
|
|
270
|
+
forgot about.
|
|
271
|
+
- In a git repo with no `commit-msg` hook, you're offered one running `cz check`. An existing
|
|
272
|
+
hook is never replaced.
|
|
273
|
+
|
|
274
|
+
The output is meant to be hand-edited afterwards: commented, ordered, and with regexes written as
|
|
275
|
+
TOML literal strings so `^((BREAKING[\-\ ]CHANGE|feat)(\(.+\))?!?):` reads as itself instead of
|
|
276
|
+
with every backslash doubled.
|
|
277
|
+
|
|
278
|
+
```toml
|
|
279
|
+
# Commitizen configuration
|
|
280
|
+
# Generated by `devstuff configure commitizen` — edit freely, or re-run the wizard.
|
|
281
|
+
# Convention: Custom types and bump rules · Types: feat, fix, docs, deps
|
|
282
|
+
# Option reference: https://commitizen-tools.github.io/commitizen/config/
|
|
283
|
+
[tool.commitizen]
|
|
284
|
+
name = 'cz_customize'
|
|
285
|
+
version_provider = 'pep621'
|
|
286
|
+
version_scheme = 'pep440'
|
|
287
|
+
tag_format = 'v$version'
|
|
288
|
+
update_changelog_on_bump = true
|
|
289
|
+
# …
|
|
290
|
+
|
|
291
|
+
# Prefix -> version increment. Order matters: commitizen stops at the first
|
|
292
|
+
# key that matches, so the two breaking-change rules have to lead.
|
|
293
|
+
[tool.commitizen.customize.bump_map]
|
|
294
|
+
'^.+!$' = 'MAJOR'
|
|
295
|
+
'^BREAKING[\-\ ]CHANGE' = 'MAJOR'
|
|
296
|
+
'^feat' = 'MINOR'
|
|
297
|
+
'^fix' = 'PATCH'
|
|
298
|
+
'^deps' = 'PATCH'
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Design notes, including why this one isn't catalog-driven and why the checks warn rather than
|
|
302
|
+
veto: [`docs/specs/commitizen-config/`](docs/specs/commitizen-config/).
|
|
303
|
+
|
|
208
304
|
#### Starship prompt wizard
|
|
209
305
|
|
|
210
306
|
Four questions, with the prompt re-rendered after each one:
|
|
@@ -685,6 +781,7 @@ Optional utilities you may want on some machines.
|
|
|
685
781
|
| Key | Name | Description | Help |
|
|
686
782
|
|-----|------|-------------|------|
|
|
687
783
|
| `aws` | AWS CLI | Amazon Web Services CLI v2 | `aws help` |
|
|
784
|
+
| `commitizen` | Commitizen | Conventional-commit prompt, semantic version bumping, and changelog generation (`devstuff configure commitizen`) | `cz --help` |
|
|
688
785
|
| `eza` | eza | Modern ls replacement with git status, icons, and tree view | `eza --help` |
|
|
689
786
|
| `gh` | GitHub CLI | GitHub's official CLI | `gh --help` |
|
|
690
787
|
| `htop` | htop | Interactive process and resource monitor | `man htop` |
|
|
@@ -176,8 +176,104 @@ devstuff configure starship --output /tmp/try.toml # write elsewhere, leave th
|
|
|
176
176
|
|
|
177
177
|
| Tool | What it configures |
|
|
178
178
|
|------|-------------------|
|
|
179
|
+
| `commitizen` | Commit types, what each one bumps, git tags, and changelog sections |
|
|
179
180
|
| `starship` | Prompt style, colour palette, which sections appear, and layout |
|
|
180
181
|
|
|
182
|
+
#### Commitizen wizard
|
|
183
|
+
|
|
184
|
+
Commitizen needs a config file before it does anything, and everything past its built-in
|
|
185
|
+
Conventional Commits rules means `cz_customize` — nine coupled regexes where `bump_map`'s keys
|
|
186
|
+
are matched against **group 1** of `bump_pattern`, the two breaking-change rules have to come
|
|
187
|
+
*first* because commitizen stops at the first key that matches, and `schema_pattern` has to
|
|
188
|
+
accept whatever `message_template` produces. Get the order wrong and `feat(api)!:` quietly ships
|
|
189
|
+
as a minor release.
|
|
190
|
+
|
|
191
|
+
The wizard asks about commit types and version rules, and derives all nine settings from the
|
|
192
|
+
answers:
|
|
193
|
+
|
|
194
|
+
| Step | Options |
|
|
195
|
+
|------|---------|
|
|
196
|
+
| **Convention** | `cz_conventional_commits` (commitizen's fixed rules — nothing to configure) · `cz_customize` (your types, your bump rules) |
|
|
197
|
+
| **Commit types** | Checkbox over `feat`, `fix`, `refactor`, `perf`, `docs`, `style`, `test`, `build`, `ci` (all on, matching Conventional Commits) plus `chore`, `revert`, `deps`, `security` — and you can add your own |
|
|
198
|
+
| **Per type** | What it bumps (`MAJOR` / `MINOR` / `PATCH` / no release), whether it appears in the changelog, under which heading, and its hotkey in `cz commit` |
|
|
199
|
+
| **Versioning** | Where the version lives (`commitizen`, `scm`, `pep621`, `uv`, `poetry`, `cargo`, `npm`, `composer`) · scheme (`semver`, `semver2`, `pep440`) · tag format · other files carrying the version |
|
|
200
|
+
| **Release** | Changelog on bump · `0.x` mode · annotated tags · GPG signing · incremental changelog · prerelease merging · `allow_abort` · hotkeys · changelog file · bump commit message |
|
|
201
|
+
| **Prompt** | Which questions `cz commit` asks — scope, body, footer |
|
|
202
|
+
| **Destination** | `pyproject.toml` (spliced in place) or a standalone `.cz.toml` |
|
|
203
|
+
|
|
204
|
+
The custom branch **starts as an exact copy of Conventional Commits** — the built-in type table
|
|
205
|
+
reproduces `commitizen.defaults.BUMP_MAP` and its picker, read out of the installed package and
|
|
206
|
+
pinned by a test. Switching to custom to add one type does not cost you the convention.
|
|
207
|
+
|
|
208
|
+
Before it asks anything, the wizard reads the project: git root, any existing commitizen config
|
|
209
|
+
(in commitizen's own search order, ignoring files without a `commitizen` section), the version
|
|
210
|
+
provider and current version from `pyproject.toml`/`package.json`/`Cargo.toml`/`composer.json`,
|
|
211
|
+
the latest git tag, and an existing changelog.
|
|
212
|
+
|
|
213
|
+
The review screen shows a sample `cz commit` message, the bump table, and the changelog sections:
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
what each type does to 1.4.2
|
|
217
|
+
|
|
218
|
+
commit increment new version
|
|
219
|
+
feat: … MINOR 1.5.0
|
|
220
|
+
fix: … PATCH 1.4.3
|
|
221
|
+
docs: … — 1.4.2
|
|
222
|
+
feat!: … MAJOR 2.0.0
|
|
223
|
+
BREAKING CHANGE: … (footer) MAJOR 2.0.0
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**That table is checked against the real thing.** "Check these rules against the real cz" builds
|
|
227
|
+
a throwaway git repo tagged at 1.4.2, replays one commit per bump level plus both breaking forms
|
|
228
|
+
through `cz bump --dry-run`, and reports whether commitizen agrees rule by rule. The same check
|
|
229
|
+
runs automatically before saving — a disagreement is reported and asks for confirmation, but
|
|
230
|
+
never blocks the save. "Preview a generated changelog" runs the real `cz changelog --dry-run`
|
|
231
|
+
over one sample commit per type. Without `cz` installed the wizard still works; the checks say so
|
|
232
|
+
rather than failing.
|
|
233
|
+
|
|
234
|
+
On save:
|
|
235
|
+
|
|
236
|
+
- Into `pyproject.toml`, every `[tool.commitizen…]` table is replaced and **the rest of the file
|
|
237
|
+
is left byte for byte**. The result is parsed back and compared to what was meant; if it does
|
|
238
|
+
not match, the write falls back to a standalone `.cz.toml` and the original is untouched.
|
|
239
|
+
- Any existing file is copied to `<name>.bak.<timestamp>` first, and a config that wasn't written
|
|
240
|
+
by this wizard asks before being replaced.
|
|
241
|
+
- If more than one commitizen config now exists, you're told which one commitizen will actually
|
|
242
|
+
read — `.cz.toml` beats `pyproject.toml`, so a new dotfile can silently shadow settings you
|
|
243
|
+
forgot about.
|
|
244
|
+
- In a git repo with no `commit-msg` hook, you're offered one running `cz check`. An existing
|
|
245
|
+
hook is never replaced.
|
|
246
|
+
|
|
247
|
+
The output is meant to be hand-edited afterwards: commented, ordered, and with regexes written as
|
|
248
|
+
TOML literal strings so `^((BREAKING[\-\ ]CHANGE|feat)(\(.+\))?!?):` reads as itself instead of
|
|
249
|
+
with every backslash doubled.
|
|
250
|
+
|
|
251
|
+
```toml
|
|
252
|
+
# Commitizen configuration
|
|
253
|
+
# Generated by `devstuff configure commitizen` — edit freely, or re-run the wizard.
|
|
254
|
+
# Convention: Custom types and bump rules · Types: feat, fix, docs, deps
|
|
255
|
+
# Option reference: https://commitizen-tools.github.io/commitizen/config/
|
|
256
|
+
[tool.commitizen]
|
|
257
|
+
name = 'cz_customize'
|
|
258
|
+
version_provider = 'pep621'
|
|
259
|
+
version_scheme = 'pep440'
|
|
260
|
+
tag_format = 'v$version'
|
|
261
|
+
update_changelog_on_bump = true
|
|
262
|
+
# …
|
|
263
|
+
|
|
264
|
+
# Prefix -> version increment. Order matters: commitizen stops at the first
|
|
265
|
+
# key that matches, so the two breaking-change rules have to lead.
|
|
266
|
+
[tool.commitizen.customize.bump_map]
|
|
267
|
+
'^.+!$' = 'MAJOR'
|
|
268
|
+
'^BREAKING[\-\ ]CHANGE' = 'MAJOR'
|
|
269
|
+
'^feat' = 'MINOR'
|
|
270
|
+
'^fix' = 'PATCH'
|
|
271
|
+
'^deps' = 'PATCH'
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Design notes, including why this one isn't catalog-driven and why the checks warn rather than
|
|
275
|
+
veto: [`docs/specs/commitizen-config/`](docs/specs/commitizen-config/).
|
|
276
|
+
|
|
181
277
|
#### Starship prompt wizard
|
|
182
278
|
|
|
183
279
|
Four questions, with the prompt re-rendered after each one:
|
|
@@ -658,6 +754,7 @@ Optional utilities you may want on some machines.
|
|
|
658
754
|
| Key | Name | Description | Help |
|
|
659
755
|
|-----|------|-------------|------|
|
|
660
756
|
| `aws` | AWS CLI | Amazon Web Services CLI v2 | `aws help` |
|
|
757
|
+
| `commitizen` | Commitizen | Conventional-commit prompt, semantic version bumping, and changelog generation (`devstuff configure commitizen`) | `cz --help` |
|
|
661
758
|
| `eza` | eza | Modern ls replacement with git status, icons, and tree view | `eza --help` |
|
|
662
759
|
| `gh` | GitHub CLI | GitHub's official CLI | `gh --help` |
|
|
663
760
|
| `htop` | htop | Interactive process and resource monitor | `man htop` |
|
|
@@ -40,3 +40,4 @@ They are most useful when they capture:
|
|
|
40
40
|
|---------|--------|
|
|
41
41
|
| [`agent/`](agent/) — `devstuff agent`, the local-model agent | Complete (v1) |
|
|
42
42
|
| [`starship-config/`](starship-config/) — `devstuff configure starship`, the prompt wizard | Complete (v1) |
|
|
43
|
+
| [`commitizen-config/`](commitizen-config/) — `devstuff configure commitizen`, the commit-convention and bump-rule wizard | Complete (v1) |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Development Plan: `devstuff configure commitizen`
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-07-31
|
|
4
|
+
**Status:** Milestones 1–5 complete
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Milestones
|
|
9
|
+
|
|
10
|
+
| # | Milestone | Deliverable | Done when |
|
|
11
|
+
|---|-----------|-------------|-----------|
|
|
12
|
+
| 1 | Catalog entry | `commitizen` in `src/dev_setup/tools.yaml` (`type: uvx`), CI matrix row, README table | `devstuff install commitizen` puts `cz` on the PATH; `devstuff list` shows its version |
|
|
13
|
+
| 2 | Data model | `configure/commitizen/model.py` — `ChangeType`, the ordered `TYPES` table, providers/schemes/tag formats, `CommitizenConfig` | Defaults reproduce `commitizen.defaults.BUMP_MAP` and the conventional picker, with no I/O |
|
|
14
|
+
| 3 | Emitter | `configure/commitizen/render.py` — the nine derived rules, `to_toml()`, `splice_pyproject()` | Output parses under `tomllib` for every convention × provider; splicing this repo's own `pyproject.toml` leaves every other table byte for byte |
|
|
15
|
+
| 4 | Live check | `configure/commitizen/validate.py` — sandbox repo, `cz bump --dry-run` replay, `cz check`, `cz changelog --dry-run` | Seven configs replay through the real `cz` with every rule agreeing |
|
|
16
|
+
| 5 | Wizard + wiring | `configure/commitizen/{detect,wizard}.py`, registry entry, README, CLAUDE.md, this spec | `devstuff configure commitizen` walks the steps, previews, checks, and saves with a backup |
|
|
17
|
+
|
|
18
|
+
## Testing Strategy
|
|
19
|
+
|
|
20
|
+
**`tests/test_configure_commitizen.py` — unit only, no `cz` required (NFR-5).** 114 tests:
|
|
21
|
+
|
|
22
|
+
- **Model invariants:** every type key is regex-safe (it is spliced raw into four patterns);
|
|
23
|
+
shortcuts unique; bump levels known; `SAMPLE_BUMPS` is a real semver walk.
|
|
24
|
+
- **Agreement with commitizen's shipped defaults:** the selected set, bump map and changelog set
|
|
25
|
+
are compared against constants transcribed from the installed package, and `CONFIG_FILES` is
|
|
26
|
+
compared against `commitizen.defaults.CONFIG_FILES` directly — the precedence warning is only
|
|
27
|
+
correct if that list is.
|
|
28
|
+
- **TOML validity:** every convention × provider parses; regexes come back out of the parser
|
|
29
|
+
byte-identical (the literal-string guarantee); a quote in a user-supplied section name does not
|
|
30
|
+
produce an unparseable file.
|
|
31
|
+
- **The generated rules, executed in Python:** `bump_pattern` group 1 is fed through `bump_map`
|
|
32
|
+
the way commitizen does it, and the result is asserted for `feat`, `feat(api)`, `docs`,
|
|
33
|
+
`feat(api)!`, `docs!`, and both spellings of the breaking footer. This is the offline half of
|
|
34
|
+
the FR-0 guarantee; the sandbox is the other half.
|
|
35
|
+
- **Cross-consistency:** the bump table shown on screen is re-derived from the emitted map and
|
|
36
|
+
compared; `schema_pattern` is matched against messages produced by `message_template`; turning
|
|
37
|
+
off a question removes both the prompt and its template slot.
|
|
38
|
+
- **Splicing:** append, replace, idempotency, other tables preserved, a similarly named table
|
|
39
|
+
(`[tool.commitizen_helper]`) not mistaken for ours, and the fail-closed case — a table header
|
|
40
|
+
inside a multi-line string returns `None` and the original file is untouched.
|
|
41
|
+
- **Detection:** provider and version read off six project shapes; a `pyproject.toml` with no
|
|
42
|
+
commitizen section is not a config; a malformed file is not a config; JSON and YAML configs
|
|
43
|
+
recognised.
|
|
44
|
+
- **Interactive steps:** the prompt helpers are driven through a scripted `ui` double, covering
|
|
45
|
+
the add-a-type validation paths, bump-level overrides, the empty-selection guard, the release
|
|
46
|
+
toggles, the commit hook (written executable; an existing one never touched) and the shadowing
|
|
47
|
+
warning.
|
|
48
|
+
|
|
49
|
+
**`tests/integration/test_commitizen_config.py` — marked `integration`, needs `cz` and `git` but
|
|
50
|
+
*not* sudo or the network** (unlike the rest of that directory — noted in its docstring). 12
|
|
51
|
+
tests, ~24s. Seven configs (conventional, custom defaults, `major_version_zero`, pep440 with bare
|
|
52
|
+
tags, no scope/body/footer, a two-type minimum, renamed sections with `docs` promoted to PATCH)
|
|
53
|
+
are each replayed through `cz bump --dry-run`; plus a user-added type, the changelog headings,
|
|
54
|
+
the exclusion of non-changelog types, a negative `cz check`, and `cz info` round-tripping the
|
|
55
|
+
file through commitizen's own parser.
|
|
56
|
+
|
|
57
|
+
**Manual verification.** The full wizard was driven end to end through a forked pty (the
|
|
58
|
+
technique CLAUDE.md describes for the starship wizard) in a scratch git repo tagged `v0.3.0`:
|
|
59
|
+
version and tag format were picked up from the tag, the review screen rendered, the automatic
|
|
60
|
+
pre-save check reported "8 rules agree", and the written file was accepted by `cz --config …
|
|
61
|
+
example`.
|
|
62
|
+
|
|
63
|
+
## Risks
|
|
64
|
+
|
|
65
|
+
| Risk | Mitigation |
|
|
66
|
+
|------|-----------|
|
|
67
|
+
| commitizen changes `find_increment`'s first-match-wins semantics, or how `bump_map` keys are matched | The integration suite replays real commits; a change shows up as a failed check rather than as wrong version numbers in the field. The wizard's pre-save check gives the same signal to users. |
|
|
68
|
+
| commitizen changes its shipped defaults | `test_defaults_reproduce_commitizens_own_rules` and `test_config_file_order_matches_commitizens` compare against the installed package. |
|
|
69
|
+
| The pyproject splice damages a user's file | The result is parsed back and compared to the intended settings; a mismatch aborts to `.cz.toml` and leaves the original untouched (FR-14). Every write is backed up first. |
|
|
70
|
+
| `cz_customize` is renamed or removed upstream (commitizen's own docs warn of this — issue #1385) | The convention name lives in one place, `CONVENTIONS` in `model.py`. A rename is a one-line change plus a spec note. |
|
|
71
|
+
| A user's own `commit-msg` hook is clobbered | The offer is only made when no hook exists (FR-18). |
|
|
72
|
+
| The live check makes the wizard feel slow | It is an explicit action plus one automatic run at save (NFR-2), never on redraw. |
|
|
73
|
+
|
|
74
|
+
## Follow-ups (not built)
|
|
75
|
+
|
|
76
|
+
- Reading an existing config back into wizard state (SD-6 — deliberately out).
|
|
77
|
+
- `pre_bump_hooks` / `post_bump_hooks` prompts (see the open questions in `specifications.md`).
|
|
78
|
+
- Changelog template (`template`/`extras`) configuration.
|
|
79
|
+
- A `.pre-commit-config.yaml` entry alongside the git hook (SD-9).
|
|
80
|
+
- Configurators for anything other than starship and commitizen.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Specification: `devstuff configure commitizen`
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-07-31
|
|
4
|
+
**Status:** Implemented (v1)
|
|
5
|
+
**Authors:** Sawyer + Claude
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Problem Statement & Goals
|
|
10
|
+
|
|
11
|
+
`devstuff install commitizen` puts `cz` on the PATH. That is where it stops. Commitizen does
|
|
12
|
+
nothing at all until a config file exists, and its `cz init` writes a five-key stub for
|
|
13
|
+
`cz_conventional_commits` — which is fine right up to the moment somebody wants a type
|
|
14
|
+
commitizen does not ship, or wants `docs:` to stop triggering releases, or wants their
|
|
15
|
+
changelog headings to say something other than "Feat".
|
|
16
|
+
|
|
17
|
+
Everything past that stub means `cz_customize`, and `cz_customize` is not a setting — it is
|
|
18
|
+
nine coupled regexes and an ordered map. The user has to write a `bump_pattern` whose **group
|
|
19
|
+
1** is what `bump_map`'s keys get `re.match`ed against, keep a `schema_pattern` in step with a
|
|
20
|
+
Jinja2 `message_template` they also wrote, and put the two breaking-change rules at the *top*
|
|
21
|
+
of `bump_map`, because commitizen stops at the first key that matches. Get the order wrong and
|
|
22
|
+
`feat(api)!:` quietly ships as a minor release. Nothing tells you; the version is just wrong.
|
|
23
|
+
|
|
24
|
+
`devstuff configure commitizen` replaces that with a wizard: choose the commit types, say what
|
|
25
|
+
each one does to the version and where it lands in the changelog, and see the resulting bump
|
|
26
|
+
table and changelog sections before anything is written — with the rules replayed through the
|
|
27
|
+
real `cz` to confirm commitizen agrees.
|
|
28
|
+
|
|
29
|
+
**Success criteria**
|
|
30
|
+
- A user who has never read commitizen's config reference gets a working `cz_customize` setup,
|
|
31
|
+
with their own types and bump rules, in under two minutes.
|
|
32
|
+
- The bump table the wizard shows is the bump commitizen actually performs — checked by
|
|
33
|
+
replaying commits through `cz bump --dry-run`, not asserted from intent.
|
|
34
|
+
- Nothing is written until the user confirms; an existing config is never lost, and a
|
|
35
|
+
`pyproject.toml` is edited in place rather than rewritten.
|
|
36
|
+
- The generated file is one a human can then hand-edit: commented, ordered, and using
|
|
37
|
+
commitizen's own key names rather than a devstuff-specific encoding.
|
|
38
|
+
- Zero new runtime dependencies.
|
|
39
|
+
|
|
40
|
+
**Non-goals**
|
|
41
|
+
- A general-purpose config-wizard DSL declared in `tools.yaml` (SD-1, inherited from starship).
|
|
42
|
+
- Round-tripping a hand-written config back into wizard state (SD-6).
|
|
43
|
+
- `cz_jira` and third-party commitizen plugins (SD-8).
|
|
44
|
+
- Writing JSON or YAML configs. commitizen reads `.cz.json`/`.cz.yaml`, but TOML is the form
|
|
45
|
+
every commitizen example is written in and the only one `pyproject.toml` can hold (SD-7).
|
|
46
|
+
- Changelog *templates* (commitizen's `template`/`extras` Jinja2 hooks). Section names and
|
|
47
|
+
ordering are configurable here; the markdown around them is not.
|
|
48
|
+
- Running `cz bump` for the user. The wizard configures releases; it does not cut one.
|
|
49
|
+
|
|
50
|
+
## 2. Users & Personas
|
|
51
|
+
|
|
52
|
+
| Persona | Description | Primary needs |
|
|
53
|
+
|---------|-------------|---------------|
|
|
54
|
+
| New commitizen user | Just ran `devstuff install commitizen` | A conventional-commits setup that works, without reading the manual |
|
|
55
|
+
| Team lead | Wants house rules — a `deps:` type, `docs:` never releasing | Custom types and per-type bump levels, enforced on commit |
|
|
56
|
+
| Polyglot maintainer | Rust/Node/PHP repo, not Python | A standalone config, the right version provider, semver not pep440 |
|
|
57
|
+
| Existing user | Has a hand-written `[tool.commitizen]` | To not lose the rest of `pyproject.toml`, and to be told what is being replaced |
|
|
58
|
+
|
|
59
|
+
## 3. Functional Requirements
|
|
60
|
+
|
|
61
|
+
| ID | Requirement | Priority |
|
|
62
|
+
|----|-------------|----------|
|
|
63
|
+
| FR-0 | Every generated config is loaded by the real `cz` without error, and produces exactly the increments the wizard's bump table claims, for every convention × provider × scheme × tag-format combination under test. | Must |
|
|
64
|
+
| FR-1 | `commitizen` is a built-in catalog tool (`type: uvx`, `pip_name: commitizen`, `check_cmd: cz`, requires `uv`), so `devstuff install commitizen` works and `devstuff configure` offers the wizard afterwards. | Must |
|
|
65
|
+
| FR-2 | The wizard covers: commit convention, which commit types exist, what each bumps, changelog membership and heading per type, version provider/scheme/tag format/version files, release toggles, the questions `cz commit` asks, and the destination file. | Must |
|
|
66
|
+
| FR-3 | Two conventions — `cz_conventional_commits` (commitizen's own fixed rules, no `customize` block emitted) and `cz_customize` (everything configurable). | Must |
|
|
67
|
+
| FR-4 | The built-in type table reproduces `commitizen.defaults.BUMP_MAP` and `ConventionalCommitsCz`'s picker exactly: the nine conventional types selected, `feat`→MINOR, `fix`/`refactor`/`perf`→PATCH, everything else no-release, and `feat`/`fix`/`refactor`/`perf` in the changelog. | Must |
|
|
68
|
+
| FR-5 | Additional types (`chore`, `revert`, `deps`, `security`) are offered unselected; the user can also add their own with a key, description, bump level, changelog heading and hotkey. | Must |
|
|
69
|
+
| FR-6 | The generated `bump_map` lists the two breaking-change rules (`^.+!$`, `^BREAKING[\-\ ]CHANGE`) first, since commitizen stops at the first matching key. | Must |
|
|
70
|
+
| FR-7 | `bump_map_major_version_zero` is always emitted, derived from `bump_map` by demoting MAJOR to MINOR. | Must |
|
|
71
|
+
| FR-8 | `schema_pattern` accepts every message `message_template` can produce, including the breaking form, and always accepts the `bump:` prefix commitizen's own release commit uses. | Must |
|
|
72
|
+
| FR-9 | The review screen shows: a summary, a sample `cz commit` message, a bump table (`type → increment → resulting version` against 1.4.2), and the changelog sections with the types filed under each. | Must |
|
|
73
|
+
| FR-10 | "Check these rules against the real cz" builds a throwaway git repo tagged at 1.4.2, replays one commit per distinct bump level plus both breaking forms through `cz bump --dry-run`, and reports agreement or disagreement per rule. | Must |
|
|
74
|
+
| FR-11 | The same check runs automatically before saving. A disagreement is reported and requires confirmation, but never blocks the save — the user's config is the user's. | Must |
|
|
75
|
+
| FR-12 | "Preview a generated changelog" runs the real `cz changelog --dry-run` over one sample commit per selected type. | Should |
|
|
76
|
+
| FR-13 | The wizard detects, and defaults from: git root, existing commitizen configs (in commitizen's own search order, skipping files without a commitizen section), version provider and current version from project files, latest git tag, and an existing changelog file. | Must |
|
|
77
|
+
| FR-14 | Writing to `pyproject.toml` replaces every `[tool.commitizen…]` table and leaves the rest of the file byte for byte. The result is verified by parsing it back; if it does not match, the write falls back to a standalone `.cz.toml` and the original file is untouched. | Must |
|
|
78
|
+
| FR-15 | Any file about to be overwritten is copied to `<name>.bak.<timestamp>` first. A config not written by this wizard prompts before being replaced. | Must |
|
|
79
|
+
| FR-16 | Nothing is written until the user picks "Save"; "Cancel" writes nothing. | Must |
|
|
80
|
+
| FR-17 | After saving, if more than one commitizen config now exists in the project, the wizard names them and says which one commitizen will actually read. | Must |
|
|
81
|
+
| FR-18 | After saving into a git repo with no `commit-msg` hook, the wizard offers to install one running `cz check --allow-abort --commit-msg-file "$1"`. An existing hook is never replaced. | Should |
|
|
82
|
+
| FR-19 | `devstuff configure commitizen --path` prints the config file commitizen would read here, or where the wizard would create one. | Must |
|
|
83
|
+
| FR-20 | `devstuff configure commitizen --output PATH` writes a standalone config to `PATH` and touches nothing in the project — no destination question, no shadowing warning, no git hook. | Must |
|
|
84
|
+
| FR-21 | Without `cz` installed, the wizard still runs end to end; the live checks say so rather than failing. | Must |
|
|
85
|
+
| FR-22 | Two selected types sharing a hotkey is reported — commitizen does not validate it, and the second type simply becomes unreachable. | Should |
|
|
86
|
+
| FR-23 | Deselecting every type keeps the previous selection: an empty alternation would match everything. | Must |
|
|
87
|
+
|
|
88
|
+
## 4. Non-Functional Requirements
|
|
89
|
+
|
|
90
|
+
| ID | Requirement |
|
|
91
|
+
|----|-------------|
|
|
92
|
+
| NFR-1 | No new runtime dependencies. TOML is emitted as text and read back with stdlib `tomllib`. |
|
|
93
|
+
| NFR-2 | A live check completes in a few seconds (~8 `cz` invocations); it is an explicit menu action plus one automatic run at save time, never on every redraw. |
|
|
94
|
+
| NFR-3 | Every live-check failure path degrades to the offline table. A check must never be able to end the wizard. |
|
|
95
|
+
| NFR-4 | The generated file is stable: the same answers produce the same bytes, and re-splicing an already-spliced `pyproject.toml` is a no-op. |
|
|
96
|
+
| NFR-5 | Unit tests run without `cz` installed. The commitizen-backed tests are marked `integration`. |
|
|
97
|
+
|
|
98
|
+
## 5. Verification
|
|
99
|
+
|
|
100
|
+
| Requirement | How it is verified |
|
|
101
|
+
|-------------|--------------------|
|
|
102
|
+
| FR-0, FR-10 | `tests/integration/test_commitizen_config.py` — seven configs replayed through the real `cz` |
|
|
103
|
+
| FR-4 | `test_defaults_reproduce_commitizens_own_rules`, against constants read out of the installed package |
|
|
104
|
+
| FR-6, FR-7 | `test_bump_map_leads_with_the_breaking_rules`, `test_major_version_zero_demotes_only_the_breaking_rules` |
|
|
105
|
+
| FR-8 | `test_schema_pattern_accepts_the_wizards_own_messages`, `test_schema_pattern_always_accepts_commitizens_own_bump_commit` |
|
|
106
|
+
| FR-13 | `test_the_version_provider_is_read_off_the_project`, `test_existing_configs_follow_commitizens_search_order` |
|
|
107
|
+
| FR-14 | `test_splice_*` — including the fail-closed case and idempotency |
|
|
108
|
+
| FR-15, FR-18 | `test_save_backs_up_what_was_there`, `test_an_existing_commit_hook_is_never_touched` |
|
|
109
|
+
| FR-17 | `test_shadowing_another_config_is_called_out` |
|
|
110
|
+
| FR-22, FR-23 | `test_duplicate_shortcuts_are_reported`, `test_deselecting_everything_keeps_the_previous_types` |
|
|
111
|
+
|
|
112
|
+
## 6. Open Questions
|
|
113
|
+
|
|
114
|
+
| Question | Status |
|
|
115
|
+
|----------|--------|
|
|
116
|
+
| Should the wizard read an existing config back into its state? | **Resolved 2026-07-31 — no.** See SD-6: partial reconstruction is worse than none, and the timestamped backup covers the real need. |
|
|
117
|
+
| Should a disagreement from the live check block saving? | **Resolved 2026-07-31 — no.** FR-11: it warns and asks. A check that can veto a save turns a helpful cross-check into an obstacle the moment commitizen changes. |
|
|
118
|
+
| Should `pre_bump_hooks`/`post_bump_hooks` be configurable? | **Deferred.** They are shell commands with eight environment variables; a text prompt for them would be a worse editor than `$EDITOR`. Hand-editable in the generated file. |
|
|
119
|
+
| Should the wizard offer a `.pre-commit-config.yaml` entry as well as a git hook? | **Deferred.** It means owning a second file's merge semantics; the git hook covers the same need with no new file format. |
|