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.
- portolan_cli-0.1.1/.claude/hooks/post-bash-remind.sh +30 -0
- portolan_cli-0.1.1/.claude/hooks/pre-read-check.sh +30 -0
- portolan_cli-0.1.1/.claude/hooks/prompt-inject.sh +13 -0
- portolan_cli-0.1.1/.coderabbit.yaml +38 -0
- portolan_cli-0.1.1/.github/CODEOWNERS +1 -0
- portolan_cli-0.1.1/.github/dependabot.yml +33 -0
- portolan_cli-0.1.1/.github/pull_request_template.md +15 -0
- portolan_cli-0.1.1/.github/workflows/ci.yml +156 -0
- portolan_cli-0.1.1/.github/workflows/docs.yml +59 -0
- portolan_cli-0.1.1/.github/workflows/nightly.yml +169 -0
- portolan_cli-0.1.1/.github/workflows/release.yml +102 -0
- portolan_cli-0.1.1/.gitignore +207 -0
- portolan_cli-0.1.1/.pre-commit-config.yaml +59 -0
- portolan_cli-0.1.1/.python-version +1 -0
- portolan_cli-0.1.1/CHANGELOG.md +6 -0
- portolan_cli-0.1.1/CLAUDE.md +265 -0
- portolan_cli-0.1.1/LICENSE +201 -0
- portolan_cli-0.1.1/PKG-INFO +49 -0
- portolan_cli-0.1.1/README.md +2 -0
- portolan_cli-0.1.1/SECURITY.md +116 -0
- portolan_cli-0.1.1/context/architecture.md +0 -0
- portolan_cli-0.1.1/context/shared/adr/0000-template.md +18 -0
- portolan_cli-0.1.1/context/shared/adr/0001-agentic-first-development.md +117 -0
- portolan_cli-0.1.1/context/shared/adr/0002-click-for-cli.md +70 -0
- portolan_cli-0.1.1/context/shared/documentation/ci.md +210 -0
- portolan_cli-0.1.1/context/shared/documentation/distill-mcp.md +125 -0
- portolan_cli-0.1.1/context/shared/known-issues/example.md +18 -0
- portolan_cli-0.1.1/docs/changelog.md +0 -0
- portolan_cli-0.1.1/docs/contributing.md +161 -0
- portolan_cli-0.1.1/docs/index.md +0 -0
- portolan_cli-0.1.1/docs/roadmap.md +30 -0
- portolan_cli-0.1.1/mkdocs.yml +61 -0
- portolan_cli-0.1.1/portolan_cli/__init__.py +0 -0
- portolan_cli-0.1.1/portolan_cli/cli.py +0 -0
- portolan_cli-0.1.1/portolan_cli/output.py +128 -0
- portolan_cli-0.1.1/pyproject.toml +163 -0
- portolan_cli-0.1.1/tests/conftest.py +0 -0
- portolan_cli-0.1.1/tests/specs/README.md +43 -0
- portolan_cli-0.1.1/tests/test_placeholder.py +6 -0
- 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 }}
|