portolan-cli 0.1.1__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.
Files changed (40) hide show
  1. portolan_cli-0.1.1/.claude/hooks/post-bash-remind.sh +30 -0
  2. portolan_cli-0.1.1/.claude/hooks/pre-read-check.sh +30 -0
  3. portolan_cli-0.1.1/.claude/hooks/prompt-inject.sh +13 -0
  4. portolan_cli-0.1.1/.coderabbit.yaml +38 -0
  5. portolan_cli-0.1.1/.github/CODEOWNERS +1 -0
  6. portolan_cli-0.1.1/.github/dependabot.yml +33 -0
  7. portolan_cli-0.1.1/.github/pull_request_template.md +15 -0
  8. portolan_cli-0.1.1/.github/workflows/ci.yml +156 -0
  9. portolan_cli-0.1.1/.github/workflows/docs.yml +59 -0
  10. portolan_cli-0.1.1/.github/workflows/nightly.yml +169 -0
  11. portolan_cli-0.1.1/.github/workflows/release.yml +102 -0
  12. portolan_cli-0.1.1/.gitignore +207 -0
  13. portolan_cli-0.1.1/.pre-commit-config.yaml +59 -0
  14. portolan_cli-0.1.1/.python-version +1 -0
  15. portolan_cli-0.1.1/CHANGELOG.md +6 -0
  16. portolan_cli-0.1.1/CLAUDE.md +265 -0
  17. portolan_cli-0.1.1/LICENSE +201 -0
  18. portolan_cli-0.1.1/PKG-INFO +49 -0
  19. portolan_cli-0.1.1/README.md +2 -0
  20. portolan_cli-0.1.1/SECURITY.md +116 -0
  21. portolan_cli-0.1.1/context/architecture.md +0 -0
  22. portolan_cli-0.1.1/context/shared/adr/0000-template.md +18 -0
  23. portolan_cli-0.1.1/context/shared/adr/0001-agentic-first-development.md +117 -0
  24. portolan_cli-0.1.1/context/shared/adr/0002-click-for-cli.md +70 -0
  25. portolan_cli-0.1.1/context/shared/documentation/ci.md +210 -0
  26. portolan_cli-0.1.1/context/shared/documentation/distill-mcp.md +125 -0
  27. portolan_cli-0.1.1/context/shared/known-issues/example.md +18 -0
  28. portolan_cli-0.1.1/docs/changelog.md +0 -0
  29. portolan_cli-0.1.1/docs/contributing.md +161 -0
  30. portolan_cli-0.1.1/docs/index.md +0 -0
  31. portolan_cli-0.1.1/docs/roadmap.md +30 -0
  32. portolan_cli-0.1.1/mkdocs.yml +61 -0
  33. portolan_cli-0.1.1/portolan_cli/__init__.py +0 -0
  34. portolan_cli-0.1.1/portolan_cli/cli.py +0 -0
  35. portolan_cli-0.1.1/portolan_cli/output.py +128 -0
  36. portolan_cli-0.1.1/pyproject.toml +163 -0
  37. portolan_cli-0.1.1/tests/conftest.py +0 -0
  38. portolan_cli-0.1.1/tests/specs/README.md +43 -0
  39. portolan_cli-0.1.1/tests/test_placeholder.py +6 -0
  40. portolan_cli-0.1.1/uv.lock +1975 -0
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ # Distill - PostToolUse Hook for Bash
3
+ # Reminds to use MCP tools for large outputs
4
+
5
+ INPUT=$(cat)
6
+ TOOL_RESPONSE=$(echo "$INPUT" | jq -r '.tool_response // empty')
7
+ RESPONSE_SIZE=${#TOOL_RESPONSE}
8
+
9
+ # Threshold: 5000 chars ~ 1250 tokens
10
+ THRESHOLD=5000
11
+
12
+ # Skip if output is small
13
+ if [ "$RESPONSE_SIZE" -lt "$THRESHOLD" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ # Detect content type and suggest appropriate tool
18
+ if echo "$TOOL_RESPONSE" | grep -qiE "(error TS|warning TS|error\[E|npm ERR|ERROR in|failed|FAILED)"; then
19
+ echo '{"systemMessage": "TIP: Large build output detected. Use mcp__distill__auto_optimize to compress errors (95%+ reduction)."}'
20
+ exit 0
21
+ fi
22
+
23
+ if echo "$TOOL_RESPONSE" | grep -qiE "(\[INFO\]|\[ERROR\]|\[WARN\]|\[DEBUG\]|[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2})"; then
24
+ echo '{"systemMessage": "TIP: Large log output detected. Use mcp__distill__summarize_logs to compress (80-90% reduction)."}'
25
+ exit 0
26
+ fi
27
+
28
+ # Generic large output
29
+ echo '{"systemMessage": "TIP: Large output ('$RESPONSE_SIZE' chars). Consider using mcp__distill__auto_optimize for compression (40-60% reduction)."}'
30
+ exit 0
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ # Distill - PreToolUse Hook for Read
3
+ # Suggests smart_file_read for code files (non-blocking to allow Edit to work)
4
+
5
+ INPUT=$(cat)
6
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
7
+ BASENAME=$(basename "$FILE_PATH")
8
+
9
+ # Skip suggestion for configuration files
10
+ if echo "$BASENAME" | grep -qiE "^(CLAUDE|README|CHANGELOG|LICENSE)"; then
11
+ exit 0
12
+ fi
13
+ if echo "$BASENAME" | grep -qiE "\.(md|json|yaml|yml|toml|ini|config)$"; then
14
+ exit 0
15
+ fi
16
+ if echo "$BASENAME" | grep -qiE "^(Dockerfile|Makefile|\.gitignore|\.env|\.prettierrc|\.eslintrc)"; then
17
+ exit 0
18
+ fi
19
+
20
+ # Suggest smart_file_read for source code files (non-blocking)
21
+ if echo "$FILE_PATH" | grep -qE "\.(ts|tsx|js|jsx|py|go|rs|java|cpp|c|h|hpp)$"; then
22
+ # Use systemMessage to suggest without blocking (allows Edit to work)
23
+ cat << EOF
24
+ {"systemMessage": "TIP: Consider using mcp__distill__smart_file_read for '$BASENAME' to save 50-70% tokens. Example: mcp__distill__smart_file_read filePath=\"$FILE_PATH\" target={\"type\":\"function\",\"name\":\"myFunc\"}"}
25
+ EOF
26
+ exit 0
27
+ fi
28
+
29
+ # Allow everything else
30
+ exit 0
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # Distill - UserPromptSubmit Hook
3
+ # Injects MCP tool reminders at the start of each prompt
4
+
5
+ cat << 'EOF'
6
+ <user-prompt-submit-hook>
7
+ DISTILL REMINDER: Use MCP tools for token optimization:
8
+ - Code files: mcp__distill__smart_file_read (50-70% savings vs Read)
9
+ - Build/test output: mcp__distill__auto_optimize
10
+ - Session stats: mcp__distill__session_stats
11
+ </user-prompt-submit-hook>
12
+ EOF
13
+ exit 0
@@ -0,0 +1,38 @@
1
+ language: en-US
2
+
3
+ tone_instructions: |
4
+ Be extremely concise. Only flag major bugs, security issues, or critical
5
+ performance problems. Skip style nits, minor suggestions, and anything
6
+ that doesn't meaningfully impact correctness or maintainability.
7
+
8
+ reviews:
9
+ profile: chill
10
+ high_level_summary: true
11
+ high_level_summary_in_walkthrough: false
12
+ poem: false
13
+ sequence_diagrams: false
14
+ collapse_walkthrough: true
15
+ changed_files_summary: false
16
+ estimate_code_review_effort: false
17
+ related_issues: false
18
+ related_prs: false
19
+ suggested_labels: false
20
+ suggested_reviewers: false
21
+ in_progress_fortune: false
22
+ review_details: false
23
+
24
+ auto_review:
25
+ enabled: true
26
+ drafts: false
27
+
28
+ tools:
29
+ ruff:
30
+ enabled: false
31
+ flake8:
32
+ enabled: false
33
+ pylint:
34
+ enabled: false
35
+
36
+ chat:
37
+ art: false
38
+ auto_reply: true
@@ -0,0 +1 @@
1
+ @cholmes @nlebovits
@@ -0,0 +1,33 @@
1
+ version: 2
2
+ updates:
3
+ # GitHub Actions dependencies
4
+ - package-ecosystem: "github-actions"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ labels:
9
+ - "dependencies"
10
+ - "github-actions"
11
+ open-pull-requests-limit: 5
12
+
13
+ # Python dependencies
14
+ - package-ecosystem: "pip"
15
+ directory: "/"
16
+ schedule:
17
+ interval: "weekly"
18
+ labels:
19
+ - "dependencies"
20
+ - "python"
21
+ open-pull-requests-limit: 10
22
+ # Group minor and patch updates together
23
+ groups:
24
+ development-dependencies:
25
+ dependency-type: "development"
26
+ update-types:
27
+ - "minor"
28
+ - "patch"
29
+ production-dependencies:
30
+ dependency-type: "production"
31
+ update-types:
32
+ - "minor"
33
+ - "patch"
@@ -0,0 +1,15 @@
1
+ ## PR Title
2
+ Write a clear, action-oriented title. This will be used for changelog and release notes.
3
+
4
+ ## Description
5
+ <!-- Concise summary of what changed and why. This may be used in release notes. -->
6
+
7
+ ## Technical Details
8
+ <!-- Implementation notes, breaking changes, migration steps. For reviewers. -->
9
+
10
+ ## Related Issue(s)
11
+ - #
12
+
13
+ ## Checklist
14
+ - [ ] Code is formatted
15
+ - [ ] Tests pass
@@ -0,0 +1,156 @@
1
+ # Tier 2 CI - Runs on every PR and push to main
2
+ # See context/shared/documentation/ci.md for the full CI strategy
3
+ #
4
+ # Philosophy: Starting from zero, all quality gates are STRICT.
5
+ # Nothing passes with warnings - fix issues or they block the PR.
6
+ name: CI
7
+ on:
8
+ push:
9
+ branches: [main]
10
+ pull_request:
11
+ branches: [main]
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint & Format
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v7
22
+
23
+ - name: Set up Python
24
+ run: uv python install 3.11
25
+
26
+ - name: Install dependencies
27
+ run: uv sync --all-extras
28
+
29
+ - name: Check code formatting
30
+ run: uv run ruff format --check .
31
+
32
+ - name: Run linter
33
+ run: uv run ruff check .
34
+
35
+ - name: Type check
36
+ run: uv run mypy portolan_cli
37
+
38
+ - name: Spell check
39
+ run: uv run codespell --skip "*.parquet,*.tif,.git,*.lock" portolan_cli tests docs
40
+
41
+ security:
42
+ name: Security Scan
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v6
46
+
47
+ - name: Install uv
48
+ uses: astral-sh/setup-uv@v7
49
+
50
+ - name: Set up Python
51
+ run: uv python install 3.11
52
+
53
+ - name: Install dependencies
54
+ run: uv sync --all-extras
55
+
56
+ - name: Run bandit security scan
57
+ run: uv run bandit -r portolan_cli -c pyproject.toml
58
+
59
+ - name: Audit dependencies
60
+ run: uv run pip-audit
61
+
62
+ test:
63
+ name: Test (Python ${{ matrix.python-version }} / ${{ matrix.os }})
64
+ runs-on: ${{ matrix.os }}
65
+ strategy:
66
+ fail-fast: false
67
+ matrix:
68
+ os: [ubuntu-latest, macos-latest, windows-latest]
69
+ python-version: ['3.10', '3.11', '3.12', '3.13']
70
+ exclude:
71
+ # Reduce matrix size - test older Python only on Linux
72
+ - os: macos-latest
73
+ python-version: '3.10'
74
+ - os: windows-latest
75
+ python-version: '3.10'
76
+ steps:
77
+ - uses: actions/checkout@v6
78
+
79
+ - name: Install uv
80
+ uses: astral-sh/setup-uv@v7
81
+
82
+ - name: Set up Python ${{ matrix.python-version }}
83
+ run: uv python install ${{ matrix.python-version }}
84
+
85
+ - name: Install dependencies
86
+ run: uv sync --all-extras
87
+
88
+ - name: Run tests (unit + integration, no network)
89
+ run: uv run pytest tests/ -v --tb=short -m "not network and not slow and not benchmark" --cov=portolan_cli --cov-report=term-missing --cov-report=xml
90
+
91
+ - name: Upload coverage to Codecov
92
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
93
+ uses: codecov/codecov-action@v5
94
+ with:
95
+ file: ./coverage.xml
96
+ flags: unittests
97
+ name: codecov-umbrella
98
+ fail_ci_if_error: false
99
+
100
+ dead-code:
101
+ name: Dead Code & Complexity
102
+ runs-on: ubuntu-latest
103
+ steps:
104
+ - uses: actions/checkout@v6
105
+
106
+ - name: Install uv
107
+ uses: astral-sh/setup-uv@v7
108
+
109
+ - name: Set up Python
110
+ run: uv python install 3.11
111
+
112
+ - name: Install dependencies
113
+ run: uv sync --all-extras
114
+
115
+ - name: Check for dead code
116
+ run: uv run vulture portolan_cli tests --min-confidence 80
117
+
118
+ - name: Check code complexity
119
+ run: uv run xenon --max-absolute=C --max-modules=B --max-average=A portolan_cli tests
120
+
121
+ docs:
122
+ name: Documentation Build
123
+ runs-on: ubuntu-latest
124
+ steps:
125
+ - uses: actions/checkout@v6
126
+
127
+ - name: Install uv
128
+ uses: astral-sh/setup-uv@v7
129
+
130
+ - name: Set up Python
131
+ run: uv python install 3.11
132
+
133
+ - name: Install dependencies
134
+ run: uv sync --all-extras
135
+
136
+ - name: Build documentation
137
+ run: uv run mkdocs build --strict
138
+
139
+ build:
140
+ name: Build Package
141
+ runs-on: ubuntu-latest
142
+ steps:
143
+ - uses: actions/checkout@v6
144
+
145
+ - name: Install uv
146
+ uses: astral-sh/setup-uv@v7
147
+
148
+ - name: Set up Python
149
+ run: uv python install 3.11
150
+
151
+ - name: Build package
152
+ run: uv build
153
+
154
+ - name: Check package contents
155
+ run: |
156
+ uv run python -c "import tarfile; t = tarfile.open(list(__import__('pathlib').Path('dist').glob('*.tar.gz'))[0]); print('\n'.join(t.getnames()))"
@@ -0,0 +1,59 @@
1
+ name: Deploy Documentation
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+ pages: write
15
+ id-token: write
16
+
17
+ concurrency:
18
+ group: "pages"
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ build:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+
27
+ - name: Set up Python
28
+ uses: actions/setup-python@v6
29
+ with:
30
+ python-version: '3.11'
31
+
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@v7
34
+ with:
35
+ enable-cache: true
36
+
37
+ - name: Install dependencies
38
+ run: |
39
+ uv pip install --system mkdocs mkdocs-material mkdocstrings[python]
40
+
41
+ - name: Build documentation
42
+ run: mkdocs build
43
+
44
+ - name: Upload artifact
45
+ uses: actions/upload-pages-artifact@v4
46
+ with:
47
+ path: ./site
48
+
49
+ deploy:
50
+ needs: build
51
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
52
+ runs-on: ubuntu-latest
53
+ environment:
54
+ name: github-pages
55
+ url: ${{ steps.deployment.outputs.page_url }}
56
+ steps:
57
+ - name: Deploy to GitHub Pages
58
+ id: deployment
59
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,169 @@
1
+ # Tier 3 CI - Scheduled nightly checks
2
+ # See context/shared/documentation/ci.md for the full CI strategy
3
+ #
4
+ # Philosophy: These are the "expensive" checks that would slow down PRs.
5
+ # They still fail the build - we track trends but don't accept regressions.
6
+ name: Nightly
7
+ on:
8
+ schedule:
9
+ - cron: '0 4 * * *' # 4 AM UTC daily
10
+ workflow_dispatch: # Allow manual triggering
11
+
12
+ jobs:
13
+ mutation:
14
+ name: Mutation Testing
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v7
21
+
22
+ - name: Set up Python
23
+ run: uv python install 3.11
24
+
25
+ - name: Install dependencies
26
+ run: uv sync --all-extras
27
+
28
+ - name: Run mutation testing
29
+ run: |
30
+ uv run mutmut run \
31
+ --paths-to-mutate=portolan_cli/ \
32
+ --tests-dir=tests/ \
33
+ --no-progress
34
+
35
+ - name: Check mutation score
36
+ run: |
37
+ # Get mutation results as JSON and check score
38
+ uv run mutmut results --json > mutation-results.json
39
+
40
+ # Calculate survival rate (lower is better)
41
+ SURVIVED=$(jq '.survived // 0' mutation-results.json)
42
+ KILLED=$(jq '.killed // 0' mutation-results.json)
43
+ TOTAL=$((SURVIVED + KILLED))
44
+
45
+ if [ "$TOTAL" -eq 0 ]; then
46
+ echo "No mutations generated - check if source code is testable"
47
+ exit 0
48
+ fi
49
+
50
+ # Calculate kill rate (higher is better)
51
+ KILL_RATE=$(echo "scale=2; $KILLED * 100 / $TOTAL" | bc)
52
+ echo "Mutation kill rate: $KILL_RATE% ($KILLED killed, $SURVIVED survived)"
53
+
54
+ # Fail if kill rate drops below 60%
55
+ # This threshold should increase as the codebase matures
56
+ MIN_KILL_RATE=60
57
+ if [ $(echo "$KILL_RATE < $MIN_KILL_RATE" | bc) -eq 1 ]; then
58
+ echo "::error::Mutation kill rate ($KILL_RATE%) is below threshold ($MIN_KILL_RATE%)"
59
+ echo "Tests are not catching enough bugs. Review survived mutants."
60
+ exit 1
61
+ fi
62
+
63
+ - name: Generate mutation report
64
+ if: always()
65
+ run: uv run mutmut html || true
66
+
67
+ - name: Upload mutation report
68
+ uses: actions/upload-artifact@v4
69
+ with:
70
+ name: mutation-report
71
+ path: html/
72
+ retention-days: 30
73
+ if: always()
74
+
75
+ benchmark:
76
+ name: Performance Benchmarks
77
+ runs-on: ubuntu-latest
78
+ steps:
79
+ - uses: actions/checkout@v6
80
+ with:
81
+ fetch-depth: 0 # Full history to compare with previous runs
82
+
83
+ - name: Install uv
84
+ uses: astral-sh/setup-uv@v7
85
+
86
+ - name: Set up Python
87
+ run: uv python install 3.11
88
+
89
+ - name: Install dependencies
90
+ run: uv sync --all-extras
91
+
92
+ - name: Run benchmarks
93
+ run: |
94
+ # Run benchmarks and save results
95
+ uv run pytest tests/ \
96
+ -m benchmark \
97
+ --benchmark-json=benchmark-results.json \
98
+ --benchmark-autosave \
99
+ -v
100
+
101
+ - name: Check for performance regression
102
+ run: |
103
+ # If baseline exists, compare and fail on >20% regression
104
+ if [ -f .benchmarks/baseline.json ]; then
105
+ uv run pytest-benchmark compare \
106
+ benchmark-results.json \
107
+ .benchmarks/baseline.json \
108
+ --fail-on=20
109
+ else
110
+ echo "No baseline found. This run will establish the baseline."
111
+ mkdir -p .benchmarks
112
+ cp benchmark-results.json .benchmarks/baseline.json
113
+ fi
114
+
115
+ - name: Upload benchmark results
116
+ uses: actions/upload-artifact@v4
117
+ with:
118
+ name: benchmark-results
119
+ path: benchmark-results.json
120
+ retention-days: 90
121
+ if: always()
122
+
123
+ network-live:
124
+ name: Live Network Tests
125
+ runs-on: ubuntu-latest
126
+ steps:
127
+ - uses: actions/checkout@v6
128
+
129
+ - name: Install uv
130
+ uses: astral-sh/setup-uv@v7
131
+
132
+ - name: Set up Python
133
+ run: uv python install 3.11
134
+
135
+ - name: Install dependencies
136
+ run: uv sync --all-extras
137
+
138
+ - name: Run network tests against live services
139
+ run: |
140
+ uv run pytest tests/ \
141
+ -m network \
142
+ --timeout=120 \
143
+ -v
144
+
145
+ dependency-check:
146
+ name: Dependency Audit
147
+ runs-on: ubuntu-latest
148
+ steps:
149
+ - uses: actions/checkout@v6
150
+
151
+ - name: Install uv
152
+ uses: astral-sh/setup-uv@v7
153
+
154
+ - name: Set up Python
155
+ run: uv python install 3.11
156
+
157
+ - name: Install dependencies
158
+ run: uv sync --all-extras
159
+
160
+ - name: Full security audit
161
+ run: |
162
+ echo "=== Dependency security audit ==="
163
+ uv run pip-audit --strict
164
+
165
+ - name: Check for outdated dependencies
166
+ run: |
167
+ echo "=== Outdated dependencies ==="
168
+ uv pip list --outdated
169
+ # Note: This is informational, not a failure condition
@@ -0,0 +1,102 @@
1
+ # Automated release workflow using commitizen
2
+ # See context/shared/documentation/ci.md for the full CI strategy
3
+ #
4
+ # This workflow:
5
+ # 1. Analyzes commits since last release
6
+ # 2. Bumps version based on conventional commits
7
+ # 3. Generates changelog entry
8
+ # 4. Creates git tag
9
+ # 5. Publishes to PyPI
10
+ #
11
+ # Triggered on push to main (after PR merge)
12
+ name: Release
13
+ on:
14
+ push:
15
+ branches: [main]
16
+ workflow_dispatch: # Allow manual triggering
17
+
18
+ jobs:
19
+ release:
20
+ name: Release
21
+ runs-on: ubuntu-latest
22
+ # Only run if the commit message doesn't start with "bump:" (avoid infinite loop)
23
+ if: "!startsWith(github.event.head_commit.message, 'bump:')"
24
+ permissions:
25
+ contents: write # Required for creating tags and pushing
26
+ id-token: write # Required for trusted PyPI publishing
27
+ steps:
28
+ - uses: actions/checkout@v6
29
+ with:
30
+ fetch-depth: 0 # Full history for commitizen
31
+ token: ${{ secrets.GITHUB_TOKEN }}
32
+
33
+ - name: Install uv
34
+ uses: astral-sh/setup-uv@v7
35
+
36
+ - name: Set up Python
37
+ run: uv python install 3.11
38
+
39
+ - name: Install dependencies
40
+ run: uv sync --all-extras
41
+
42
+ - name: Configure git
43
+ run: |
44
+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
45
+ git config --local user.name "github-actions[bot]"
46
+
47
+ - name: Check if release needed
48
+ id: check
49
+ run: |
50
+ # Check if there are any commits that would trigger a version bump
51
+ # Exit code 21 = NO_COMMITS_TO_BUMP (commits exist but none are feat/fix/breaking)
52
+ # Exit code 3 = NO_COMMITS_FOUND (no commits at all since last tag)
53
+ # --yes flag auto-confirms "is this the first tag?" prompt in CI
54
+ set +e
55
+ output=$(uv run cz bump --dry-run --yes 2>&1)
56
+ exit_code=$?
57
+ set -e
58
+
59
+ echo "$output"
60
+
61
+ if [ $exit_code -eq 0 ]; then
62
+ echo "release_needed=true" >> $GITHUB_OUTPUT
63
+ echo "✓ Release-worthy commits found"
64
+ elif [ $exit_code -eq 21 ]; then
65
+ echo "release_needed=false" >> $GITHUB_OUTPUT
66
+ echo "✓ No release needed (commits are docs/refactor/test/chore only)"
67
+ elif [ $exit_code -eq 3 ]; then
68
+ echo "release_needed=false" >> $GITHUB_OUTPUT
69
+ echo "✓ No commits found since last release"
70
+ else
71
+ echo "✗ Unexpected error from commitizen (exit code: $exit_code)"
72
+ exit $exit_code
73
+ fi
74
+
75
+ - name: Bump version and update changelog
76
+ if: steps.check.outputs.release_needed == 'true'
77
+ run: |
78
+ uv run cz bump --changelog --yes
79
+ git push --follow-tags
80
+
81
+ - name: Build package
82
+ if: steps.check.outputs.release_needed == 'true'
83
+ run: uv build
84
+
85
+ - name: Publish to PyPI
86
+ if: steps.check.outputs.release_needed == 'true'
87
+ uses: pypa/gh-action-pypi-publish@release/v1
88
+ with:
89
+ print-hash: true
90
+
91
+ - name: Create GitHub Release
92
+ if: steps.check.outputs.release_needed == 'true'
93
+ run: |
94
+ # Get the latest tag
95
+ TAG=$(git describe --tags --abbrev=0)
96
+ # Create a GitHub release from the tag
97
+ gh release create "$TAG" \
98
+ --title "$TAG" \
99
+ --notes "See [CHANGELOG](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details." \
100
+ dist/*
101
+ env:
102
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}