Deterministic CLI to discover, check, and bump a project version without importing user code or writing
regex. Optional autogit (stage/commit/push). Supports PEP 440 and SemVer. Includes an auto mode that
infers bump size from public API changes (__all__).
Version values drift across pyproject.toml, setup.cfg, setup.py, and module files. Many tools import your package
or ask you to hand-write regex. This one does neither.
-
Discovery across common sources:
pyproject.toml→[project].version(PEP 621) or[tool.setuptools].versionsetup.cfg→[metadata] versionsetup.py→ static AST ofsetup(version="...")- Python modules → top-level
__version__ = "...", plus_version.py,__version__.py,__about__.py, package__init__.py
-
Agreement check (CI-friendly, no writes)
-
Bump:
major | minor | patch | autowith--scheme pep440|semver -
Auto mode: diffs the union of
__all__symbols to infer major/minor/patch; persists digest in.jiggle_version.config -
Autogit:
--autogit off|stage|commit|pushwith templated commit message -
Git-aware discovery: honors
.gitignore, repo excludes, and global gitignore; supports extra ignore paths -
Zero-import of target project; AST + safe text updates only
-
Deterministic exit codes for automation
pipx install jiggle_version
# or
python -m pip install --user jiggle_versionPython: >=3.8
Runtime deps (runtime or conditional): packaging, tomlkit, pathspec, rich-argparse (help styling), tomli
on Python <3.11.
# From your project root
jiggle_version check
jiggle_version print
jiggle_version bump --increment patch --scheme pep440 --dry-run
jiggle_version bump --increment auto --autogit commitInitialize default config:
jiggle_version init[tool.jiggle_version]
scheme = "pep440" # "pep440" | "semver"
default_increment = "patch" # "major" | "minor" | "patch" | "auto"
project_root = "."
ignore = ["docs/_build", "dist", ".venv"] # optional
# Optional autogit defaults
autogit = "off" # "off" | "stage" | "commit" | "push"
commit_message = "Release: {version}"
allow_dirty = falseNotes:
- CLI overrides config. Missing CLI args are filled from config.
ignoreis normalized to a list of relative paths.
Discover versions across sources and verify agreement. No writes.
jiggle_version check [--project-root .] [--ignore path ...]Print the normalized version if all sources agree.
jiggle_version printList all candidate files and run check.
jiggle_version inspectCompute next version and update all writable sources.
jiggle_version bump \
[--increment major|minor|patch|auto] \
[--scheme pep440|semver] \
[--set X.Y.Z] \
[--force-write] \
[--dry-run] \
[--autogit off|stage|commit|push] \
[--commit-message "Release: {version}"] \
[--allow-dirty]Behavior:
- If sources disagree, operation fails unless
--force-writeor--setis provided. --setskips bump logic and writes the explicit version everywhere.
Compute and persist API digest used by auto mode.
jiggle_version hash-all
# writes .jiggle_version.config (TOML)Append a default [tool.jiggle_version] section to pyproject.toml.
-
Walk project for
__all__in Python modules (respecting.gitignore+ignore). -
Build the set union of exported symbols; compare to last stored set in
.jiggle_version.config. -
Decide:
- major if any previously exported symbol was removed
- minor if new symbols were added (and nothing removed)
- patch if identical or no
__all__anywhere
-
After a successful, non–
--dry-runbump, the digest is updated.
You can pre-seed the digest with jiggle_version hash-all.
-
No shelling out to
gitfor ignore logic; usespathspecwith:<root>/.gitignore<root>/.git/info/exclude~/.config/git/ignoreor~/.gitignore
-
Autogit uses
subprocess.run(..., check=True):stage→git add <changed files>commit→ stage +git commit -m "<message>"push→ commit +git push origin <current-branch>
-
Refuses to proceed if repo is dirty and autogit is requested, unless
--allow-dirty.
User / project issues (treated as “expected” for tests):
100— no version declarations found102— discovered versions disagree103— git repo dirty and--allow-dirtynot set104— config not found where required
Tool / unexpected failures:
1— unexpected error2— discovery error (I/O, traversal)3— auto-increment analysis error4— bump calculation error (invalid version/scheme)5— failed to update a file6— autogit failed7— hash/digest generation failed8— argparse error (invalid CLI)
Contract for test runners:
- Treat >=100 as user error (assertable, not a tool crash).
- Treat <100 as application failure (potential bug).
- Never imports or executes target project code.
- Python parsed via
ast; setup parsing limited to literalversion="...". - TOML via
tomllib/tomli, INI viaconfigparser,tomlkitused to preserve formatting on write.
- PEP 440 (default): increments numeric release segments; drops pre/dev/post markers on standard bump.
- SemVer: enforces
MAJOR.MINOR.PATCH; strips pre-release/build on standard bump. (Flags to preserve/annotate can be added later.)
- Typo suggestions for choice arguments (e.g., wrong subcommand/value).
- Verbose logging:
-v→ INFO,-vv→ DEBUG; or--log-level DEBUG. - Rich help text when
rich-argparseis available.
Drift check (no writes):
- run: pipx install jiggle_version
- run: jiggle_version checkRelease bump (auto + autogit):
- run: pipx install jiggle_version
- run: jiggle_version hash-all
- run: jiggle_version bump --increment auto --autogit push- Won’t evaluate dynamic
setup.pylogic (files, env, computed constants). - Only updates known patterns; exotic version locations aren’t modified.
- Single, project-wide version policy (per-module versioning is out-of-scope for now).
- “No version found”: ensure one of the supported sources exists and is literal.
- “Versions disagree”: run
jiggle_version inspectto see sources; reconcile or use--force-writeonce. - Auto mode always “patch”: ensure you actually export a public API via
__all__. - Ignored paths not respected: confirm entries in
pyproject.tomlunder[tool.jiggle_version].ignore(list or string), and that.gitignorecovers generated trees.
- Add/adjust unit tests (no tests for logging needed).
- Keep exit codes and CLI surfaces stable.
- Prefer AST/TOML/INI approaches over regex.
- Windows paths: avoid
shell=True, preferPathAPIs.
MIT. See LICENSE.
This is a CLI-first tool. Internal modules may change. If you import, prefer:
jiggle_version.__about__.__version__- Running via
python -m jiggle_version