Skip to content

Commit c146ea2

Browse files
mwebbersclaude
andcommitted
Add CONTRIBUTING, coverage gate, ruff, LICENSE, dependabot & release CI
Bring ClaudeCodeCommons to the portfolio-wide shared-library standard: self-contained trunk-on-main CONTRIBUTING, 80% coverage gate (pytest-cov), ruff lint+format gate, MIT LICENSE, release.yml (tag->Release) and weekly dependabot. Code reformatted by ruff (no behaviour change). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent fc9d8cf commit c146ea2

11 files changed

Lines changed: 231 additions & 34 deletions

File tree

.github/dependabot.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
version: 2
2+
updates:
3+
# Python dependencies declared in pyproject.toml (pytest, ruff, pytest-cov, …).
4+
# This package has no runtime dependencies; only the dev/test tools are bumped.
5+
- package-ecosystem: "pip"
6+
directory: "/"
7+
schedule:
8+
interval: "weekly"
9+
open-pull-requests-limit: 5
10+
groups:
11+
python:
12+
patterns: ["*"]
13+
# Keep GitHub Actions (checkout, setup-python, …) current.
14+
- package-ecosystem: "github-actions"
15+
directory: "/"
16+
schedule:
17+
interval: "weekly"
18+
open-pull-requests-limit: 5
19+
groups:
20+
actions:
21+
patterns: ["*"]

.github/workflows/release.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: release
2+
3+
# Turn an annotated vX.Y.Z tag into a GitHub Release, using that version's
4+
# CHANGELOG.md section as the release notes. Fires after `git push --tags`.
5+
on:
6+
push:
7+
tags: ["v*"]
8+
9+
permissions:
10+
contents: write
11+
12+
jobs:
13+
release:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v5
17+
- name: Extract changelog section
18+
run: |
19+
version="${GITHUB_REF_NAME#v}"
20+
awk -v v="$version" '
21+
$0 ~ "^## \\[" v "\\]" { flag = 1; next }
22+
flag && /^## \[/ { exit }
23+
flag { print }
24+
' CHANGELOG.md > release-notes.md
25+
if [ ! -s release-notes.md ]; then
26+
echo "Release ${GITHUB_REF_NAME}" > release-notes.md
27+
fi
28+
- name: Create GitHub Release
29+
uses: softprops/action-gh-release@v2
30+
with:
31+
name: ${{ github.ref_name }}
32+
body_path: release-notes.md

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ jobs:
1212
matrix:
1313
python-version: ["3.9", "3.11", "3.12"]
1414
steps:
15-
- uses: actions/checkout@v4
15+
- uses: actions/checkout@v5
1616
- uses: actions/setup-python@v5
1717
with:
1818
python-version: ${{ matrix.python-version }}
1919
- run: pip install -e ".[test]"
20+
- run: ruff check .
21+
- run: ruff format --check .
2022
- run: pytest

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ venv/
77
build/
88
dist/
99
.DS_Store
10+
.coverage
11+
.coverage.*
12+
htmlcov/
1013

1114
# Secrets and local runtime config — never commit.
1215
.env
1316
.env.*
1417
!.env.example
18+
.ruff_cache/

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ adheres to semantic versioning.
88

99
## [Unreleased]
1010

11+
### Added
12+
- Self-contained trunk-on-main `CONTRIBUTING.md`, `LICENSE` (MIT) + `license`
13+
field, `.github/workflows/release.yml` (tag -> GitHub Release) and
14+
`.github/dependabot.yml` (weekly pip + actions PRs).
15+
- Code-coverage gate: `pytest-cov`, **fail_under = 80** in `[tool.coverage]`.
16+
- Ruff lint + format gate (`[tool.ruff]`), run in CI before the tests.
17+
18+
### Changed
19+
- CI: `ruff check` + `ruff format --check` before `pytest`; `actions/checkout` -> v5.
20+
- Code reformatted by `ruff format` (no behaviour change); `.gitignore` ignores
21+
coverage artefacts.
22+
1123
## [0.3.0] - 2026-06-01
1224

1325
### Changed

CONTRIBUTING.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Contributing — branching & releases (trunk-on-main)
2+
3+
`ClaudeCodeCommons` is a shared library for the Claude routine family. It uses **trunk-based
4+
development**: `main` is always releasable, and every release is an annotated `vX.Y.Z`
5+
tag on `main` — no `develop` / `release` / `hotfix` branches. The plain `git` commands
6+
below are the whole workflow.
7+
8+
The family deliberately picks its branching model by **deploy model**: pip-installed
9+
libraries and scheduled routines use trunk-on-main (this guide); gated multi-environment
10+
apps use Git Flow. The *release gate* — version = changelog = tag — is the same either
11+
way; only the branching differs.
12+
13+
## Branch model
14+
15+
| Branch | Purpose |
16+
| --- | --- |
17+
| `main` | The line. Always releasable, always green. Every release is a tag here. Default branch. |
18+
| `feature/*` (optional) | A short-lived branch for one unit of work, merged back with `git merge --no-ff`. For small changes, committing straight to `main` is fine. |
19+
20+
Tags are always `vX.Y.Z` (a `v` prefix on the version).
21+
22+
## Daily work
23+
24+
Follow the **SCOPE → test → code → changelog** loop in `CLAUDE.md` (if present), or at
25+
minimum: add/adjust the test, change the code, keep `pytest` green, and add a line under
26+
`[Unreleased]` in `CHANGELOG.md`. Every public symbol downstream routines import is part
27+
of this library's contract — change it deliberately and note breaking changes.
28+
29+
## Quality gates (run before every commit)
30+
31+
```bash
32+
ruff check .
33+
ruff format --check .
34+
pytest # tests + line coverage >= 80% (use pytest --no-cov for subsets)
35+
```
36+
37+
CI runs exactly these on every push and PR. `ruff format` owns formatting; the linter is
38+
a small, stable rule set (`E`, `F`, `I`).
39+
40+
## Cutting a release (the release gate lives here)
41+
42+
The version in `pyproject.toml`, the topmost `CHANGELOG.md` heading, and the annotated
43+
tag must be the same `X.Y.Z`:
44+
45+
```bash
46+
# on a clean, green main:
47+
# 1) bump "version" in pyproject.toml
48+
# 2) move CHANGELOG.md [Unreleased] entries under a new "## [X.Y.Z] - YYYY-MM-DD" heading
49+
# 3) ruff check . && ruff format --check . && pytest
50+
git commit -am "Added vX.Y.Z — <summary>"
51+
git tag -a vX.Y.Z -m "vX.Y.Z"
52+
git push origin main --tags
53+
```
54+
55+
Downstream routines pin this library by tag (`...@vX.Y.Z`), so a release is only "done"
56+
once the tag is pushed. Pushing the tag triggers `release.yml`, which creates the GitHub
57+
Release from the changelog. Release this package before the routines that need the new
58+
version, and don't break the public API without a major-version bump.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Michael Webbers (MiWebb)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,9 @@ pytest
4545

4646
A coverage test (`tests/test_scope_coverage.py`) fails if any `SCOPE.md` feature
4747
lacks a test or a test references a feature not in `SCOPE.md`.
48+
49+
## Contributing
50+
51+
Branching is **trunk-on-main**, and releases go through a strict
52+
version = changelog = tag gate. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full
53+
procedure and the `ruff` + `pytest` quality gates.

pyproject.toml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ build-backend = "setuptools.build_meta"
66
name = "claude-code-commons"
77
version = "0.3.0"
88
description = "Vendor-agnostic, standard-library-only helpers (env lookup, tolerant parsing, currency symbols, remote-path builder, run log) shared by Claude routine repos"
9+
license = { text = "MIT" }
910
# Standard library only — a broad floor so it installs in any routine runtime,
1011
# including a Python 3.11 scheduled-task sandbox. Verified on 3.9 and 3.12.
1112
requires-python = ">=3.9"
1213
dependencies = []
1314

1415
[project.optional-dependencies]
15-
test = ["pytest>=8"]
16+
test = ["pytest>=8", "pytest-cov>=5", "ruff>=0.6"]
1617

1718
# A single top-level module, `code_commons`, living under src/. Installing this
1819
# package makes `from code_commons import env_required, parse_num, ...` work in
@@ -26,7 +27,25 @@ py-modules = ["code_commons"]
2627
[tool.pytest.ini_options]
2728
pythonpath = ["src"]
2829
testpaths = ["tests"]
29-
addopts = "--strict-markers"
30+
addopts = "--strict-markers --cov --cov-report=term-missing"
3031
markers = [
3132
"feature: link a test to a SCOPE.md feature ID, e.g. feature('F-001')",
3233
]
34+
35+
[tool.coverage.run]
36+
source = ["code_commons"]
37+
branch = true
38+
39+
[tool.coverage.report]
40+
fail_under = 80
41+
show_missing = true
42+
43+
[tool.ruff]
44+
line-length = 88
45+
target-version = "py39"
46+
47+
[tool.ruff.lint]
48+
# Deliberately small + stable: pyflakes (F), import-order (I), and the subset of
49+
# pycodestyle errors ruff enables by default (E4/E7/E9). `ruff format` owns
50+
# formatting; keep the linter low-churn.
51+
select = ["E4", "E7", "E9", "F", "I"]

src/code_commons.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from datetime import datetime
2222
from typing import Any
2323

24-
2524
# ---------------------------------------------------------------------------
2625
# Environment: project prefix with shared fallback
2726
# ---------------------------------------------------------------------------
@@ -182,11 +181,23 @@ def log(msg: str) -> None:
182181
# Known currency code -> display symbol. Codes not in the table fall back to the
183182
# 3-letter code itself, which is always valid in Excel number formats.
184183
CURRENCY_SYMBOLS = {
185-
"EUR": "€", "USD": "$", "GBP": "£", "CHF": "CHF",
186-
"DKK": "kr", "SEK": "kr", "NOK": "kr", "ISK": "kr",
187-
"JPY": "¥", "CNY": "¥",
188-
"CAD": "$", "AUD": "$", "NZD": "$", "HKD": "$",
189-
"PLN": "zł", "CZK": "Kč", "HUF": "Ft",
184+
"EUR": "€",
185+
"USD": "$",
186+
"GBP": "£",
187+
"CHF": "CHF",
188+
"DKK": "kr",
189+
"SEK": "kr",
190+
"NOK": "kr",
191+
"ISK": "kr",
192+
"JPY": "¥",
193+
"CNY": "¥",
194+
"CAD": "$",
195+
"AUD": "$",
196+
"NZD": "$",
197+
"HKD": "$",
198+
"PLN": "zł",
199+
"CZK": "Kč",
200+
"HUF": "Ft",
190201
}
191202

192203

0 commit comments

Comments
 (0)