qauvern 0.2.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.
- qauvern-0.2.0/.github/workflows/ci.yaml +22 -0
- qauvern-0.2.0/.github/workflows/release.yml +154 -0
- qauvern-0.2.0/.gitignore +80 -0
- qauvern-0.2.0/.pre-commit-config.yaml +8 -0
- qauvern-0.2.0/.secrets.baseline +141 -0
- qauvern-0.2.0/AGENTS.md +65 -0
- qauvern-0.2.0/CHANGELOG.md +30 -0
- qauvern-0.2.0/CONTRIBUTING.md +69 -0
- qauvern-0.2.0/Design.md +199 -0
- qauvern-0.2.0/Justfile +15 -0
- qauvern-0.2.0/LICENSE.txt +203 -0
- qauvern-0.2.0/PKG-INFO +324 -0
- qauvern-0.2.0/README.md +297 -0
- qauvern-0.2.0/examples/config-example.yaml +87 -0
- qauvern-0.2.0/examples/config-simple.yaml +27 -0
- qauvern-0.2.0/pyproject.toml +55 -0
- qauvern-0.2.0/src/qauvern/__init__.py +13 -0
- qauvern-0.2.0/src/qauvern/api_client.py +710 -0
- qauvern-0.2.0/src/qauvern/cli.py +1098 -0
- qauvern-0.2.0/src/qauvern/config.py +179 -0
- qauvern-0.2.0/src/qauvern/limit_resolver.py +82 -0
- qauvern-0.2.0/src/qauvern/models.py +243 -0
- qauvern-0.2.0/src/qauvern/optimizer.py +352 -0
- qauvern-0.2.0/tests/__init__.py +11 -0
- qauvern-0.2.0/tests/mock_api.py +278 -0
- qauvern-0.2.0/tests/test_cli_rendering.py +490 -0
- qauvern-0.2.0/tests/test_configure.py +443 -0
- qauvern-0.2.0/tests/test_create.py +386 -0
- qauvern-0.2.0/tests/test_daily_usage.py +89 -0
- qauvern-0.2.0/tests/test_limit_resolver.py +336 -0
- qauvern-0.2.0/tests/test_models.py +422 -0
- qauvern-0.2.0/tests/test_optimizer.py +533 -0
- qauvern-0.2.0/uv.lock +627 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
test:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: ${{ matrix.python-version }}
|
|
17
|
+
- name: install
|
|
18
|
+
run: |
|
|
19
|
+
uv tool install rust-just
|
|
20
|
+
uv sync
|
|
21
|
+
- run: just lint
|
|
22
|
+
- run: just test
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- 'pyproject.toml'
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
validate:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
outputs:
|
|
14
|
+
version: ${{ steps.get-version.outputs.version }}
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Get version from pyproject.toml
|
|
20
|
+
id: get-version
|
|
21
|
+
run: |
|
|
22
|
+
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
23
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
24
|
+
echo "Version to release: $VERSION"
|
|
25
|
+
|
|
26
|
+
- name: Validate version format
|
|
27
|
+
run: |
|
|
28
|
+
VERSION="${{ steps.get-version.outputs.version }}"
|
|
29
|
+
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
30
|
+
echo "Error: Invalid version format: $VERSION"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
- name: Check if tag exists
|
|
35
|
+
run: |
|
|
36
|
+
VERSION="${{ steps.get-version.outputs.version }}"
|
|
37
|
+
if git ls-remote --tags origin "v$VERSION" | grep -q .; then
|
|
38
|
+
echo "Error: Tag v$VERSION already exists"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
test:
|
|
43
|
+
needs: validate
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
strategy:
|
|
46
|
+
matrix:
|
|
47
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
48
|
+
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
|
|
52
|
+
- uses: astral-sh/setup-uv@v5
|
|
53
|
+
with:
|
|
54
|
+
python-version: ${{ matrix.python-version }}
|
|
55
|
+
|
|
56
|
+
- name: Install dependencies
|
|
57
|
+
run: |
|
|
58
|
+
uv tool install rust-just
|
|
59
|
+
uv sync
|
|
60
|
+
|
|
61
|
+
- name: Run tests
|
|
62
|
+
run: just test
|
|
63
|
+
|
|
64
|
+
build:
|
|
65
|
+
needs: [validate, test]
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
|
|
71
|
+
- uses: astral-sh/setup-uv@v5
|
|
72
|
+
|
|
73
|
+
- name: Build distribution
|
|
74
|
+
run: uv build
|
|
75
|
+
|
|
76
|
+
- name: Store the distribution packages
|
|
77
|
+
uses: actions/upload-artifact@v4
|
|
78
|
+
with:
|
|
79
|
+
name: python-package-distributions
|
|
80
|
+
path: dist/
|
|
81
|
+
|
|
82
|
+
publish-to-pypi:
|
|
83
|
+
needs: build
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
environment:
|
|
86
|
+
name: pypi
|
|
87
|
+
url: https://pypi.org/project/qauvern/
|
|
88
|
+
permissions:
|
|
89
|
+
id-token: write
|
|
90
|
+
|
|
91
|
+
steps:
|
|
92
|
+
- name: Download all the dists
|
|
93
|
+
uses: actions/download-artifact@v4
|
|
94
|
+
with:
|
|
95
|
+
name: python-package-distributions
|
|
96
|
+
path: dist/
|
|
97
|
+
|
|
98
|
+
- name: Publish distribution to PyPI
|
|
99
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
100
|
+
|
|
101
|
+
create-release:
|
|
102
|
+
needs: [validate, publish-to-pypi]
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
permissions:
|
|
105
|
+
contents: write
|
|
106
|
+
|
|
107
|
+
steps:
|
|
108
|
+
- uses: actions/checkout@v4
|
|
109
|
+
|
|
110
|
+
- name: Create and push tag
|
|
111
|
+
run: |
|
|
112
|
+
VERSION="${{ needs.validate.outputs.version }}"
|
|
113
|
+
git config user.name "github-actions[bot]"
|
|
114
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
115
|
+
git tag -a "v$VERSION" -m "Release version $VERSION"
|
|
116
|
+
git push origin "v$VERSION"
|
|
117
|
+
|
|
118
|
+
- name: Extract changelog for this version
|
|
119
|
+
id: changelog
|
|
120
|
+
run: |
|
|
121
|
+
VERSION="${{ needs.validate.outputs.version }}"
|
|
122
|
+
python3 << 'EOF' > release-notes.md
|
|
123
|
+
import re
|
|
124
|
+
import os
|
|
125
|
+
VERSION = os.environ.get('VERSION', '')
|
|
126
|
+
with open('CHANGELOG.md', 'r') as f:
|
|
127
|
+
content = f.read()
|
|
128
|
+
pattern = rf'## \[{re.escape(VERSION)}\].*?\n(.*?)(?=\n## \[|$)'
|
|
129
|
+
match = re.search(pattern, content, re.DOTALL)
|
|
130
|
+
if match:
|
|
131
|
+
notes = match.group(1).strip()
|
|
132
|
+
print(notes)
|
|
133
|
+
else:
|
|
134
|
+
print(f'Release version {VERSION}')
|
|
135
|
+
EOF
|
|
136
|
+
cat release-notes.md
|
|
137
|
+
env:
|
|
138
|
+
VERSION: ${{ needs.validate.outputs.version }}
|
|
139
|
+
|
|
140
|
+
- name: Download distribution artifacts
|
|
141
|
+
uses: actions/download-artifact@v4
|
|
142
|
+
with:
|
|
143
|
+
name: python-package-distributions
|
|
144
|
+
path: dist/
|
|
145
|
+
|
|
146
|
+
- name: Create GitHub Release
|
|
147
|
+
env:
|
|
148
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
149
|
+
VERSION: ${{ needs.validate.outputs.version }}
|
|
150
|
+
run: |
|
|
151
|
+
gh release create "v$VERSION" \
|
|
152
|
+
--title "Release v$VERSION" \
|
|
153
|
+
--notes-file release-notes.md \
|
|
154
|
+
dist/*
|
qauvern-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
env.sh
|
|
10
|
+
env.staging.sh
|
|
11
|
+
|
|
12
|
+
# Distribution / packaging
|
|
13
|
+
.Python
|
|
14
|
+
build/
|
|
15
|
+
develop-eggs/
|
|
16
|
+
dist/
|
|
17
|
+
downloads/
|
|
18
|
+
eggs/
|
|
19
|
+
.eggs/
|
|
20
|
+
lib/
|
|
21
|
+
lib64/
|
|
22
|
+
parts/
|
|
23
|
+
sdist/
|
|
24
|
+
var/
|
|
25
|
+
wheels/
|
|
26
|
+
pip-wheel-metadata/
|
|
27
|
+
share/python-wheels/
|
|
28
|
+
*.egg-info/
|
|
29
|
+
.installed.cfg
|
|
30
|
+
*.egg
|
|
31
|
+
MANIFEST
|
|
32
|
+
|
|
33
|
+
# PyInstaller
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.nox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
*.py,cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
|
|
51
|
+
# Virtual environments
|
|
52
|
+
.venv/
|
|
53
|
+
venv/
|
|
54
|
+
env/
|
|
55
|
+
ENV/
|
|
56
|
+
env.bak/
|
|
57
|
+
venv.bak/
|
|
58
|
+
|
|
59
|
+
# IDEs
|
|
60
|
+
.vscode/
|
|
61
|
+
.idea/
|
|
62
|
+
*.swp
|
|
63
|
+
*.swo
|
|
64
|
+
*~
|
|
65
|
+
/.claude
|
|
66
|
+
!/.claude/commands
|
|
67
|
+
|
|
68
|
+
# OS
|
|
69
|
+
.DS_Store
|
|
70
|
+
Thumbs.db
|
|
71
|
+
|
|
72
|
+
# Project specific
|
|
73
|
+
config.yaml
|
|
74
|
+
*.log
|
|
75
|
+
openapi.json
|
|
76
|
+
/test_*.py
|
|
77
|
+
/test_*.sh
|
|
78
|
+
env.sh
|
|
79
|
+
env.staging.sh
|
|
80
|
+
/config/
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.5.0",
|
|
3
|
+
"plugins_used": [
|
|
4
|
+
{
|
|
5
|
+
"name": "ArtifactoryDetector"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"name": "AWSKeyDetector"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "AzureStorageKeyDetector"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Base64HighEntropyString",
|
|
15
|
+
"limit": 4.5
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "BasicAuthDetector"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "CloudantDetector"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "DiscordBotTokenDetector"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "GitHubTokenDetector"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "GitLabTokenDetector"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "HexHighEntropyString",
|
|
34
|
+
"limit": 3.0
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "IbmCloudIamDetector"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "IbmCosHmacDetector"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "IPPublicDetector"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "JwtTokenDetector"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "KeywordDetector",
|
|
50
|
+
"keyword_exclude": ""
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "MailchimpDetector"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "NpmDetector"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "OpenAIDetector"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "PrivateKeyDetector"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "PypiTokenDetector"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "SendGridDetector"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "SlackDetector"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "SoftlayerDetector"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "SquareOAuthDetector"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "StripeDetector"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "TelegramBotTokenDetector"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "TwilioKeyDetector"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"filters_used": [
|
|
90
|
+
{
|
|
91
|
+
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"path": "detect_secrets.filters.common.is_baseline_file",
|
|
95
|
+
"filename": ".secrets.baseline"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
|
|
99
|
+
"min_level": 2
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"path": "detect_secrets.filters.heuristic.is_lock_file"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"path": "detect_secrets.filters.heuristic.is_sequential_string"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"path": "detect_secrets.filters.heuristic.is_swagger_file"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"path": "detect_secrets.filters.heuristic.is_templated_secret"
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"results": {
|
|
130
|
+
"README.md": [
|
|
131
|
+
{
|
|
132
|
+
"type": "Secret Keyword",
|
|
133
|
+
"filename": "README.md",
|
|
134
|
+
"hashed_secret": "1042188d51afe0c0b267d5c98e5ac2f2c741b28f",
|
|
135
|
+
"is_verified": false,
|
|
136
|
+
"line_number": 113
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"generated_at": "2026-05-08T20:48:11Z"
|
|
141
|
+
}
|
qauvern-0.2.0/AGENTS.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This is a Python CLI tool for optimizing IBM Quantum resource allocations across cloud accounts,
|
|
4
|
+
using fairness-based scheduling to rebalance QAU allocations among service instances. See
|
|
5
|
+
`Design.md` for full architecture, algorithm details, and API endpoint documentation.
|
|
6
|
+
|
|
7
|
+
## Development Setup
|
|
8
|
+
|
|
9
|
+
Requires [Just](https://just.systems/man/en/) and [uv](https://docs.astral.sh/uv/).
|
|
10
|
+
|
|
11
|
+
## Running Tests and Linting
|
|
12
|
+
|
|
13
|
+
Always run both before committing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
just lint
|
|
17
|
+
just test
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
To auto-format:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
just fmt
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Testing Rules
|
|
27
|
+
|
|
28
|
+
- **Never make live IBM Cloud API calls to test code.** Use `tests/mock_api.py`, which provides
|
|
29
|
+
a complete configurable mock of `IBMQuantumAPIClient`.
|
|
30
|
+
- Tests do not require real credentials or network access.
|
|
31
|
+
- The mock supports setting account allocation, per-instance allocations/limits, and usage across
|
|
32
|
+
five time windows (28d, 14d, 7d, 3d, 24h).
|
|
33
|
+
|
|
34
|
+
## Committing
|
|
35
|
+
|
|
36
|
+
All commits must be DCO signed-off. Use `git commit -s`.
|
|
37
|
+
|
|
38
|
+
## Key Prohibitions
|
|
39
|
+
|
|
40
|
+
- Do not run `qauvern optimize` against a real account to verify behavior — use `analyze`
|
|
41
|
+
with `--dry-run` or write a test with `mock_api.py`.
|
|
42
|
+
- Do not hardcode API keys or CRNs in source files.
|
|
43
|
+
|
|
44
|
+
## Release Process
|
|
45
|
+
|
|
46
|
+
Releases are automated via GitHub Actions. To release a new version:
|
|
47
|
+
|
|
48
|
+
1. Update the `version` in `pyproject.toml` (semver format: `X.Y.Z`)
|
|
49
|
+
2. Add a `## [X.Y.Z] - YYYY-MM-DD` section to `CHANGELOG.md` with release notes
|
|
50
|
+
3. Add the version link at the bottom of `CHANGELOG.md`
|
|
51
|
+
4. Merge to `main`
|
|
52
|
+
|
|
53
|
+
The release workflow triggers on any push to `main` that changes `pyproject.toml`. It will:
|
|
54
|
+
- Validate the version format and check no tag already exists
|
|
55
|
+
- Run the full test suite across all supported Python versions
|
|
56
|
+
- Build the distribution
|
|
57
|
+
- Publish to PyPI via trusted publishing
|
|
58
|
+
- Create a git tag and GitHub Release with changelog notes
|
|
59
|
+
|
|
60
|
+
PyPI publishing uses trusted publishing (OIDC), configured via the `pypi` GitHub environment.
|
|
61
|
+
|
|
62
|
+
## Maintenance Rule
|
|
63
|
+
|
|
64
|
+
After any design change (algorithm, API endpoints, data model, config format), update `Design.md`
|
|
65
|
+
so the project can be reconstructed from that document alone.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to qauvern will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-05-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Release workflow for automated PyPI publishing via GitHub Actions
|
|
13
|
+
- CI test matrix across Python 3.10, 3.11, 3.12, 3.13, 3.14
|
|
14
|
+
- Python 3.12, 3.13, 3.14 support
|
|
15
|
+
|
|
16
|
+
## [0.1.0] - 2026-05-06
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- CLI with `analyze`, `optimize`, `configure`, `instances`, `show`, and `create` commands
|
|
21
|
+
- Activity-score-based allocation algorithm with exponential time weighting
|
|
22
|
+
- Rolling-window-aware `net_grants` for additive budget boosts
|
|
23
|
+
- IBM Cloud IAM authentication (API key via `IBMCLOUD_API_KEY`)
|
|
24
|
+
- Regional endpoint support (auto-extracted from CRN)
|
|
25
|
+
- Staging environment support (`--staging` flag)
|
|
26
|
+
- YAML configuration with `configure` command for auto-discovery
|
|
27
|
+
- Configurable minimum allocation floor
|
|
28
|
+
|
|
29
|
+
[0.2.0]: https://github.com/ibm/qauvern/releases/tag/v0.2.0
|
|
30
|
+
[0.1.0]: https://github.com/ibm/qauvern/releases/tag/v0.1.0
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Pre-requisites
|
|
4
|
+
|
|
5
|
+
- [Just](https://just.systems/man/en/)
|
|
6
|
+
- [uv](https://docs.astral.sh/uv/getting-started/installation/)`
|
|
7
|
+
|
|
8
|
+
## Signing Commits
|
|
9
|
+
|
|
10
|
+
All commits must be signed off under the [Developer Certificate of Origin](https://developercertificate.org/).
|
|
11
|
+
Use `git commit -s` (or `--signoff`) to append a `Signed-off-by` trailer to your commit message.
|
|
12
|
+
PRs without sign-off will fail the DCO check.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git commit -s -m "Your commit message"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
To sign off existing commits, amend or rebase with `--signoff`:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git commit --amend --signoff
|
|
22
|
+
git rebase --signoff main
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Pre-commit hooks (detect-secrets)
|
|
26
|
+
|
|
27
|
+
This project uses `detect-secrets` to prevent accidental commits of sensitive information.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uvx pre-commit install
|
|
31
|
+
|
|
32
|
+
# First time only: create the secrets baseline
|
|
33
|
+
uvx detect-secrets scan > .secrets.baseline
|
|
34
|
+
|
|
35
|
+
# Verify hooks work
|
|
36
|
+
uvx pre-commit run --all-files
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If the hook detects a potential secret, either remove it from your code or add it to the baseline if it's a false positive:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uvx detect-secrets scan --baseline .secrets.baseline
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Running Tests
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Run all tests
|
|
49
|
+
just test
|
|
50
|
+
|
|
51
|
+
# Run with coverage
|
|
52
|
+
just test --cov=qauvern --cov-report=term-missing
|
|
53
|
+
|
|
54
|
+
# Run a specific file
|
|
55
|
+
just test tests/test_models.py
|
|
56
|
+
|
|
57
|
+
# Verbose output
|
|
58
|
+
just test -v
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Code Quality
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
just fmt
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
just lint
|
|
69
|
+
```
|