Goal is your release autopilot: a CLI that runs tests, writes conventional commits, updates changelogs, bumps versions, and publishes packages for youβwhile keeping you in control with clear prompts or full automation.
goal) or fire-and-forget in CI with goal --all.# 1. First-time setup in a folder without git
goal # Goal offers to init git, add a remote, then re-run the workflow.
# 2. Everyday feature release
goal push # Runs tests, suggests a commit, bumps patch, updates changelog, pushes, tags.
# 3. CI/CD or cron-driven release
goal --all --bump minor # Non-interactive; perfect for nightly builds or release pipelines.
TODO Management & Publishing Fixes β Automatic issue tracking and reliable PyPI publishing
π« Automatic TODO Management
goal doctor --todo adds detected issues to TODO.mdπ§ Publishing Improvements
~/.pypirc authenticationπ οΈ Enhanced Diagnostics
# Check version
goal -v
# Diagnose and track issues
goal doctor --todo
goal doctor -t
# Full workflow with TODO tracking
goal --all --todo
goal -a -t
# Standard workflow
goal --all
# Check project health
goal doctor
## π Previous Features (v2.1)
> **Smart Commit Intelligence** β commit bodies that answer *what changed, what was tested, and at what scale*
β BEFORE: refactor(core): add testing, logging, validation
β
AFTER: feat(goal): intelligent code analysis pipeline
changes:
- file: cli.py
area: cli
added: [ensure_git_repository, ensure_remote, _run_git_verbose]
modified: [push]
- file: test_clone_repo.py
area: test
new_tests: 3
testing:
new_tests: 3
scenarios:
- auto_mode_skips
- init_and_add_remote
- clone_option_with_valid_url
stats:
lines: "+210/-45 (net +165)"
files: 2
complexity: "Stable complexity"
Interactive Git Setup β
goalnow guides you through repository configuration when no git repo is found
β Not a git repository.
What would you like to do?
[1] Initialize git here and connect to a remote (keeps local files in 'myproject/')
[2] Clone a remote repository into this directory (downloads remote files)
[3] Initialize a local-only git repository (no remote)
[4] Exit
| π Full Documentation | π Examples |
~/.goal)--yes flag for automated workflowspip install goal
pip install goal
goal in any project directorycd my-project/
goal
Goal detects your situation and guides you interactively:
β Not a git repository.
What would you like to do?
[1] Initialize git here and connect to a remote (keeps local files in 'my-project/')
[2] Clone a remote repository into this directory (downloads remote files)
[3] Initialize a local-only git repository (no remote)
[4] Exit
Choose [1]: 1
β git init
β Initialized git repository.
Enter repository URL:
SSH example: git@github.com:user/repo.git
HTTP example: https://github.com/user/repo.git
URL: git@github.com:wronai/my-project.git
β git remote add origin git@github.com:wronai/my-project.git
β Remote 'origin' β git@github.com:wronai/my-project.git
Fetching remote branches...
β git fetch origin
Remote is empty (no branches yet). Your local files will be the first push.
β Ready. Run 'goal' again to commit and push.
β No git remote configured.
Would you like to add a remote?
[1] Add remote origin (connect to GitHub/GitLab/etc.)
[2] Skip β commit locally without pushing
Choose [1]: 1
β git remote add origin git@github.com:wronai/my-project.git
Verifying connection...
β Remote is reachable.
β Remote 'origin' β git@github.com:wronai/my-project.git
Detected project types: python
Will commit 5 files (+120/-15 lines, NET 105, 11% churn deletions)
Commit message: feat(api): add user authentication endpoint
Run tests? [Y/n]
Commit changes? [Y/n]
Push to remote? [Y/n]
Publish version 1.2.3? [Y/n]
Every git command is shown transparently (lines starting with β) so you always know what goal is doing.
# Full automation β tests, commit, push, publish (skips if no repo/remote)
goal -a
# Skip prompts but don't auto-publish
goal --yes
# Preview what would happen
goal --dry-run
Note:
goal -ain a directory without a git repository will skip gracefully instead of failing. This is safe for CI/CD pipelines.
If youβre working with the goal source code:
git clone https://github.com/wronai/goal.git
cd goal
# Use the local version without installing
python3 -m goal
# Or install in development mode
pip install -e .
π Complete Documentation: docs/README.md
# Run full interactive workflow with default patch bump
goal
# Run with minor version bump
goal --bump minor
# Run without prompts (for CI/CD)
goal --yes
# Automate ALL stages without any prompts
goal --all
# Make changes to your Python code
git add .
# Run the full workflow
goal
# Output:
# β Detected project types: python
# β Running tests: pytest tests/ -v
# β Tests passed (23/23)
# β Generated commit: feat(api): add user authentication endpoint
# β Updated VERSION to 1.2.3
# β Updated CHANGELOG.md
# β Created tag v1.2.3
# β Pushed to origin
# ? Publish to PyPI? [Y/n]: Y
# β Published version 1.2.3
# Quick patch release for bug fix
goal push --yes -m "fix: resolve memory leak in parser"
# Output:
# β Detected project types: nodejs
# β Running tests: npm test
# β Tests passed
# β Committed: fix: resolve memory leak in parser
# β Updated package.json to 1.0.1
# β Updated CHANGELOG.md
# β Created tag v1.0.1
# β Pushed to origin
# Major release with breaking changes
goal push --bump major --yes
# Output:
# β Detected project types: rust
# β Running tests: cargo test
# β All tests passed
# β Generated commit: feat!: change public API structure
# β Updated Cargo.toml to 2.0.0
# β Updated CHANGELOG.md
# β Created tag v2.0.0
# β Pushed to origin
# ? Publish to crates.io? [Y/n]: Y
# β Published crate v2.0.0
# Publish only (after committing)
goal publish
# Or include publishing in the full workflow
goal --all # Will publish automatically
Important: Goal automatically filters artifacts to upload only the current version, avoiding βFile already existsβ errors on PyPI.
If you see a βFile already existsβ error:
rm -rf dist build && python -m buildgoal --bump patchpython3 -m goal (in goal repo)# Project with both Python backend and Node.js frontend
goal info
# Output:
# === Project Information ===
# Project types: python, nodejs
# Current version: 1.5.0
#
# Version files:
# β VERSION: 1.5.0
# β package.json: 1.5.0
# β pyproject.toml: 1.5.0
# Run release - updates both package.json and pyproject.toml
goal push --yes
# Skip tests for docs-only changes
goal push --no-test -m "docs: update API documentation"
# Or let goal detect it's docs only
git add README.md docs/
goal push --yes
# Output:
# β Detected project types: python
# β Generated commit: docs: update API documentation
# β Updated VERSION to 1.5.1
# β Updated CHANGELOG.md
# β Created tag v1.5.1
# β Pushed to origin
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Goal
run: pip install goal
- name: Configure PyPI token
run: echo "__token__=$" > ~/.pypirc
- name: Release
run: goal --all --bump patch
# Preview what will happen
goal push --dry-run --bump minor
# Output:
# === DRY RUN ===
# Project types: python
# Files to commit: 12 (+342/-15 lines)
# - src/auth.py (+120/-5)
# - tests/test_auth.py (+85/-0)
# - docs/api.md (+137/-10)
# Commit message: feat(auth): add OAuth2 integration
# Version: 1.5.0 -> 1.6.0
# Version sync: VERSION, pyproject.toml
# Tag: v1.6.0
# Override auto-generated message
goal push -m "feat: implement real-time notifications"
# Or use conventional commit format
goal push -m "fix(auth): resolve JWT token expiration issue"
# Split changes into logical commits
goal push --split --yes
# Output:
# β Committed docs: update README and API docs
# β Committed feat: add user authentication system
# β Committed test: add comprehensive test coverage
# β Committed chore: update dependencies and CI config
# β Committed release: v2.0.0
# Skip version bump for hotfix
goal push --yes --no-version-sync -m "hotfix: critical security patch"
# Skip changelog for minor tweak
goal push --yes --no-changelog -m "style: fix linting issues"
# Skip tag creation for experimental feature
goal push --yes --no-tag -m "feat: experimental AI integration"
# Interactive push with prompts
goal push
# Automatic push without prompts
goal push --yes
# Dry run to see what would happen
goal push --dry-run
# Custom commit message
goal push -m "feat: add new authentication system"
# Skip specific steps
goal push --no-tag --no-changelog
# With version bump
goal push --bump minor
# Markdown output for CI/CD logs
goal push --markdown
# Check current version
goal version
# Bump specific version type
goal version --bump minor
goal version --bump major
# Check repository status
goal status
Goal automatically detects your project type and uses appropriate commands:
| Language | Test Command | Publish Command | Version Files |
|---|---|---|---|
| Python | pytest |
python -m build && twine upload dist/* |
pyproject.toml, setup.py |
| Node.js | npm test |
npm publish |
package.json |
| Rust | cargo test |
cargo publish |
Cargo.toml |
| Go | go test ./... |
git push origin --tags |
go.mod (uses git tags) |
| Ruby | bundle exec rspec |
gem build *.gemspec && gem push *.gem |
*.gemspec |
| PHP | composer test |
composer publish |
composer.json |
| .NET | dotnet test |
dotnet pack && dotnet nuget push *.nupkg |
*.csproj |
| Java | mvn test |
mvn deploy |
pom.xml, build.gradle |
Goal outputs structured markdown by default (perfect for LLM consumption and CI/CD logs). Use --ascii to get the legacy console output.
# Default: markdown output
goal push
goal status
# Force legacy output
goal push --ascii
goal status --ascii
# Use with automation
goal --all > release.log
The markdown output includes:
Example output:
---
command: goal push
project_types: ["python"]
version_bump: "1.0.1 -> 1.0.2"
file_count: 7
---
# Goal Push Result
## Overview
**Project Type:** python
**Files Changed:** 7 (+1140/-99 lines)
**Version:** 1.0.1 β 1.0.2
...
See docs/markdown-output.md for detailed examples.
goal or goal pushMain command for the complete workflow.
Options:
-v, --version: Show version and exit--bump, -b: Version bump type [patch |
minor | major] (default: patch) |
--yes, -y: Skip all prompts (run automatically)--all, -a: Automate all stages including tests, commit, push, and publish--todo/--no-todo, -t: Add unfixed issues to TODO.md during doctor phase (default: no)--markdown/--ascii: Output format (default: markdown)--split: Create separate commits per change type (docs/code/ci/examples)--ticket: Ticket prefix to include in commit titles (overrides TICKERT)--no-tag: Skip creating git tag--no-changelog: Skip updating changelog--no-version-sync: Skip syncing version to project files--message, -m: Custom commit message--dry-run: Show what would be done without doing itgoal doctorDiagnose and auto-fix common project configuration issues.
Options:
--fix/--no-fix: Auto-fix issues (default: yes)--path, -p: Root directory to scan (default: .)--todo/--no-todo, -t: Add unfixed issues to TODO.md (default: no)Examples:
goal doctor # Diagnose and auto-fix
goal doctor --no-fix # Diagnose only
goal doctor --todo # Add issues to TODO.md
goal doctor -t # Add issues to TODO.md (short form)
When --split is enabled, Goal will create multiple commits:
goal/, src/, lib/, *.pydocs/*, README.md, *.md.github/*, .gitlab/*, *.yml/*.yamlexamples/*Then it will create a final release metadata commit with version bump + changelog (unless disabled).
Create a TICKET file in repository root:
prefix=ABC-123
format=[{ticket}] {title}
You can override it per run:
goal push --ticket ABC-123
goal push --split --ticket ABC-123
goal commit --ticket ABC-123
goal initInitialize goal in current repository.
Creates:
VERSION file with initial version 1.0.0CHANGELOG.md with standard templategoal statusShow current git status and version info.
Displays:
goal versionShow or bump version.
Options:
--type, -t: Version bump type [patch |
minor | major] (default: patch) |
# Make your changes...
git add some/files
# Run goal with interactive prompts
goal
# Prompts will appear:
# Run tests? [Y/n] - Runs pytest for Python projects
# Commit changes? [Y/n] - Creates smart commit message
# Push to remote? [Y/n] - Pushes to origin with tags
# Publish version 1.2.3? [Y/n] - Publishes to PyPI/npm/etc
# Automate everything - tests, commit, push, publish
goal --all
# Short form
goal -a
# With specific version bump
goal --all --bump minor
# GitHub Actions example
- name: Deploy with Goal
run: |
goal push --yes --bump minor
# Or with --all flag
- name: Full release
run: |
goal --all --bump patch
# Skip tests for documentation changes
goal push --yes -m "docs: update README"
# Check what will be done
goal push --dry-run --bump minor
# Run with specific version bump
goal push --bump minor
# Or skip publishing for internal releases
goal push --yes --no-tag
# Monorepo with frontend and backend
# Structure:
# /backend (Python)
# /frontend (Node.js)
# /docs
# From root directory
goal info
# Output:
# Project types: python, nodejs
# Version files:
# β VERSION: 2.1.0
# β backend/pyproject.toml: 2.1.0
# β frontend/package.json: 2.1.0
# Release updates all projects
goal push --yes
# Critical fix - skip tests and version bump
goal push --yes --no-test --no-version-sync -m "hotfix: security patch"
# Then create proper release
goal push --bump patch -m "release: v1.2.1 with security fix"
# On feature branch
goal push --yes --no-tag --no-publish
# After merge to main
goal push --bump minor --yes
# Create RC version
goal push --bump patch -m "release: v2.0.0-rc1"
# After testing, promote to stable
goal push --bump patch -m "release: v2.0.0 stable"
Goal uses goal.yaml for configuration. Run goal init to create it automatically with detected settings.
# goal.yaml - Goal configuration file
version: "1.0"
project:
name: "my-project" # Auto-detected from pyproject.toml/package.json
type: ["python"] # Auto-detected project types
description: "My project" # Auto-detected description
versioning:
strategy: "semver" # semver, calver, or date
files: # Files to sync version to
- "VERSION"
- "pyproject.toml:version"
- "package.json:version"
bump_rules: # Auto-bump thresholds
patch: 10 # Files changed
minor: 50 # Lines added
major: 200 # Large changes
git:
commit:
strategy: "conventional" # conventional, semantic, custom
scope: "my-project" # Default scope for commits
templates: # Custom commit templates
feat: "feat({scope}): {description}"
fix: "fix({scope}): {description}"
docs: "docs({scope}): {description}"
classify_by: # Classification methods
- "file_extensions"
- "directory_paths"
- "line_stats"
- "keywords_diff"
changelog:
enabled: true
template: "keep-a-changelog"
output: "CHANGELOG.md"
sections: ["Added", "Changed", "Fixed", "Deprecated"]
tag:
enabled: true
prefix: "v"
format: "{prefix}{version}"
strategies:
python:
test: "pytest tests/ -v"
build: "python -m build"
publish: "twine upload dist/*"
nodejs:
test: "npm test"
build: "npm run build"
publish: "npm publish"
registries:
pypi:
url: "https://pypi.org/simple/"
token_env: "PYPI_TOKEN"
npm:
url: "https://registry.npmjs.org/"
token_env: "NPM_TOKEN"
hooks:
pre_commit: "" # Command to run before commit
post_commit: "" # Command to run after commit
pre_push: "" # Command to run before push
post_push: "" # Command to run after push
advanced:
auto_update_config: true # Auto-update config on detection changes
performance:
max_files: 50 # Split commits if > N files
timeout_test: 300 # Test timeout in seconds
# Show full configuration
goal config show
# Show specific key (dot notation)
goal config show -k git.commit.strategy
# Get a value
goal config get project.name
# Set a value
goal config set git.commit.scope "my-app"
# Validate configuration
goal config validate
# Update config based on project detection
goal config update
# Use a custom config file
goal --config custom-goal.yaml push
# Or in CI/CD
goal -c .goal/ci.yaml --all
Goal also works without configuration based on conventions:
VERSION file first, then project-specific files.PHONY: release patch minor major
# Interactive release
release:
goal
# Automatic patch release
patch:
goal push --yes
# Full automation release
all:
goal --all
# Automatic minor release
minor:
goal push --yes --bump minor
# Automatic major release
major:
goal push --yes --bump major
# Dry run
dry-run:
goal push --dry-run
# Release with custom message
release-message:
goal push --yes -m "$(MSG)"
# Release from specific branch
release-branch:
git checkout main
git pull
goal --all --bump $(BUMP)
{
"scripts": {
"release": "goal",
"release:patch": "goal push --yes",
"release:all": "goal --all",
"release:minor": "goal push --yes --bump minor",
"release:major": "goal push --yes --bump major",
"release:dry": "goal push --dry-run",
"release:docs": "goal push --yes -m 'docs: update documentation'"
}
}
#!/bin/sh
# .git/hooks/pre-commit
echo "Running goal pre-commit check..."
goal push --dry-run
if [ $? -eq 0 ]; then
echo "β
Ready to commit!"
else
echo "β Issues found. Fix them before committing."
exit 1
fi
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Install goal
RUN pip install goal
# Copy source code
COPY . .
# Run tests and release
CMD ["sh", "-c", "goal --all"]
Goal analyzes your changes to generate appropriate commit messages:
Examples:
feat: add user authenticationfix: resolve memory leak in parserdocs: update API documentationtest: add coverage for payment moduleThe interactive workflow will ask if you want to continue when tests fail:
Tests failed. Continue anyway? [y/N]
If Goal doesnβt detect your test command correctly, you can run them manually before using goal push --yes.
Goal can automatically add detected issues to TODO.md without duplicates:
# Diagnose and add unfixed issues to TODO.md
goal doctor --todo
goal doctor -t
# Diagnose without auto-fix but add to TODO.md
goal doctor --todo --no-fix
goal doctor -t --no-fix
# Full workflow with TODO tracking
goal --all --todo
goal -a -t
Features:
[CODE-identifier] for each issueExample TODO.md entry:
## Issues Found - 2026-02-24
- [PY002-missingbuildsysteminpyprojecttoml] π΄ **Missing [build-system] in pyproject.toml** (`pyproject.toml`)
- pyproject.toml has no [build-system] section.
- pip and build tools need this to know how to build your package.
- Adding a default setuptools build-system.
Ensure youβre authenticated with the appropriate package manager:
PyPI Authentication Options:
~/.pypirc file (development)PYPI_TOKEN environment variabletwine configure~/.pypirc setup (recommended for development):
[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmcC...
Environment variable setup (for CI/CD):
export PYPI_TOKEN=your_token_here
Other package managers:
npm logincargo login# Check what's detected
goal info
# Manually specify in goal.yaml
project:
type: ["python", "nodejs"]
# Check version files
goal info
# Manually configure in goal.yaml
versioning:
files:
- "VERSION"
- "my-app/__init__.py:__version__"
# In goal.yaml
advanced:
performance:
timeout_test: 600 # 10 minutes
# Use custom message
goal push -m "your custom message"
# Or configure templates in goal.yaml
git:
commit:
templates:
feat: "feat({scope}): {description}"
Goal automatically manages your project metadata using your git configuration and license preferences.
On first run, Goal will:
user.name and user.email~/.goal for future use$ goal
======================================================================
π― Goal - First Time Setup
======================================================================
β Detected git user.name: Tom Sapletta
β Detected git user.email: info@softreck.com
======================================================================
π License Selection
======================================================================
1. Apache License 2.0
2. MIT License
3. GNU General Public License v3.0
4. BSD 3-Clause License
5. GNU General Public License v2.0
6. GNU Lesser General Public License v3.0
7. GNU Affero General Public License v3.0
8. Mozilla Public License 2.0
Enter your choice [1]: 1
β Configuration saved to ~/.goal
Every time you run goal, it updates:
Project Files:
pyproject.toml - authors, license, classifierpackage.json - author, license, contributorsCargo.toml - authors, licenseVERSION - version number__init__.py - __version__ variable (in all packages)README.md:
## License section## Author sectionGoal adds authors instead of replacing them:
# Before (existing author)
authors = [{name = "Original Author", email = "original@example.com"}]
# After (Goal adds you)
authors = [
{name = "Original Author", email = "original@example.com"},
{name = "Tom Sapletta", email = "info@softreck.com"},
]
# View current configuration
goal config
# Reset and reconfigure
goal config --reset
# Configuration file location
~/.goal
$ goal config
======================================================================
π Goal User Configuration
======================================================================
Config file: /home/tom/.goal
Current settings:
Author name: Tom Sapletta
Author email: info@softreck.com
License: Apache License 2.0 (Apache-2.0)
π‘ Tip: Run 'goal config --reset' to reconfigure
# Add to ~/.bashrc or ~/.zshrc
alias g='goal'
alias gp='goal push --yes'
alias ga='goal --all'
alias gd='goal push --dry-run'
# For team development, use ticket prefixes
echo "prefix=PROJ-123" > TICKET
goal push --split # Creates commits like "PROJ-123: feat: add feature"
# Before release
goal status # Check status
goal push --dry-run # Preview changes
# Release
goal --all # Full automation
# Commit feature flag changes
goal push -m "feat: enable beta feature flag"
# Later, remove the flag
goal push -m "feat: launch feature to all users"
# .github/workflows/scheduled-release.yml
name: Scheduled Release
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9 AM
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Goal
run: pip install goal
- name: Weekly Release
run: goal --all --bump patch
Apache License 2.0 - see LICENSE for details.
Created by Tom Sapletta - tom@sapletta.com