Skip to content

Commit c8573e2

Browse files
mwebbersclaude
andcommitted
Added v0.1 — vendor-agnostic stdlib core extracted from commons (T-1 / option C)
code_commons: env_required/env_opt/env_get (F-001), parse_num (F-002), build_remote_path (F-003), currency_symbol (F-004), log (F-005). Standard library only, requires-python >=3.9, CI 3.9/3.11/3.12 matrix. 24 tests, green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0 parents  commit c8573e2

10 files changed

Lines changed: 598 additions & 0 deletions

File tree

.github/workflows/test.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
python-version: ["3.9", "3.11", "3.12"]
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-python@v5
17+
with:
18+
python-version: ${{ matrix.python-version }}
19+
- run: pip install -e ".[test]"
20+
- run: pytest

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
__pycache__/
2+
*.pyc
3+
.pytest_cache/
4+
.venv/
5+
venv/
6+
*.egg-info/
7+
build/
8+
dist/
9+
.DS_Store
10+
11+
# Secrets and local runtime config — never commit.
12+
.env
13+
.env.*
14+
!.env.example

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Changelog
2+
3+
All notable changes to this package are documented here. Each behaviour change
4+
references the SCOPE.md feature ID(s) it implements.
5+
6+
The format follows [Keep a Changelog](https://keepachangelog.com/); the project
7+
adheres to semantic versioning.
8+
9+
## [Unreleased]
10+
11+
## [0.1.0] - 2026-05-31
12+
13+
Initial release: the vendor-agnostic, standard-library-only core, extracted from
14+
`claude-woocommerce-commons` so every routine — not only the WooCommerce family —
15+
can share one tested copy of the generic plumbing instead of re-implementing it
16+
(review thread T-1 / option C: a dependency-light core + a thin vendor toolkit on
17+
top).
18+
19+
### Added
20+
- **F-001** `env_required` / `env_opt` / `env_get` environment helpers with the
21+
project-prefix-with-fallback lookup.
22+
- **F-002** Tolerant `parse_num()` (comma thousands separators; comma-decimal
23+
locale input documented as out of scope).
24+
- **F-003** `build_remote_path(base, folder, filename)` remote-path builder.
25+
- **F-004** `currency_symbol()` 3-letter-code → display symbol.
26+
- **F-005** `log()` timestamped run-log line.

CLAUDE.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# CLAUDE.md
2+
3+
Context for Claude Code in this repo. Keep this short — it loads on every session.
4+
5+
## What this is
6+
7+
`claude-code-commons` is a **library**, not a routine. It holds the small,
8+
vendor-agnostic, **standard-library-only** helpers shared by Claude routine
9+
repos: `env_required`/`env_opt`/`env_get`, `parse_num`, `currency_symbol`,
10+
`build_remote_path`, `log`. It has **no vendor or report logic and no third-party
11+
dependency**.
12+
13+
There is no scheduled-task/operator section here — this package does not run on
14+
its own. It is consumed by the routine repos and by vendor toolkits (e.g.
15+
`claude-woocommerce-commons`), which own their own runtime contracts.
16+
17+
## Read first
18+
19+
Read `SCOPE.md` before changing anything. Every feature has an ID (`F-001`…) and
20+
must stay covered by a test (`tests/test_scope_coverage.py` enforces it).
21+
22+
## Working rules
23+
24+
- Any behaviour change starts in `SCOPE.md`: add/edit a feature ID, then write or
25+
update its test, then change the code. Never the other way around.
26+
- Every test is tagged `@pytest.mark.feature("F-00X")`.
27+
- Run `pytest` after any change.
28+
- After a behaviour change, add a line under `[Unreleased]` in `CHANGELOG.md`.
29+
- **No third-party dependency, ever.** The whole point is that a routine can
30+
install this without pulling in heavier libraries. Anything needing `requests`,
31+
`openpyxl`, etc. belongs in a vendor toolkit that depends on this, not here.
32+
- **This is a dependency of routines and vendor toolkits.** A breaking change to a
33+
public name must bump the version and be rolled out to each consumer by bumping
34+
its pin. Prefer additive changes.
35+
36+
## Deployment floor
37+
38+
`requires-python` is `>=3.9` and CI runs a `3.9` / `3.11` / `3.12` matrix. The
39+
Cowork sandbox that runs scheduled routines is **Python 3.11**, so a consumer that
40+
`pip install`s this must be able to; keeping the floor at the lowest interpreter
41+
any routine runs on (and testing it) guarantees that.
42+
43+
## Commands
44+
45+
- Install (editable, with tests): `pip install -e ".[test]"`
46+
- Run tests: `pytest`
47+
- The package exposes a single top-level module:
48+
`from code_commons import env_required, env_opt, parse_num, build_remote_path, currency_symbol, log`

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Claude Code Commons
2+
3+
Vendor-agnostic, **standard-library-only** plumbing shared by Claude routine
4+
repos. One tiny installable package, versioned and tested in isolation, so a fix
5+
lands in every routine at once instead of being copy-pasted — and with **no
6+
third-party dependency**, so a routine can adopt it without pulling in a web
7+
client or spreadsheet engine.
8+
9+
It contains **no vendor or report logic** — only the generic helpers routines
10+
have in common. Heavier, vendor-specific toolkits (e.g.
11+
`claude-woocommerce-commons`) build on top of it.
12+
13+
## What's inside
14+
15+
| Area | API |
16+
|------|-----|
17+
| Env | `env_required(key, *, prefix)`, `env_opt(key, default, *, prefix)`, `env_get` (alias of `env_opt`) |
18+
| Parsing | `parse_num()` — tolerant of loose JSON (comma thousands separators) |
19+
| Currency | `currency_symbol(code)` — 3-letter code → display symbol |
20+
| Paths | `build_remote_path(base, folder, filename)` — slash-normalised join |
21+
| Logging | `log(msg)``[HH:MM:SS] msg`, flushed |
22+
23+
See `SCOPE.md` for the full feature contract.
24+
25+
## Install
26+
27+
```bash
28+
pip install -e ".[test]" # local dev (editable) + pytest
29+
```
30+
31+
The package exposes a single top-level module:
32+
33+
```python
34+
from code_commons import env_required, env_opt, parse_num, build_remote_path, log
35+
36+
url = env_required("WC_URL", prefix="STOCK") # STOCK_WC_URL, else WC_URL
37+
path = build_remote_path("/Reports", "Stock", "report.xlsx") # /Reports/Stock/report.xlsx
38+
```
39+
40+
## Tests
41+
42+
```bash
43+
pytest
44+
```
45+
46+
A coverage test (`tests/test_scope_coverage.py`) fails if any `SCOPE.md` feature
47+
lacks a test or a test references a feature not in `SCOPE.md`.

SCOPE.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Scope
2+
3+
This document is the **single source of truth** for what this package does and
4+
why. The README explains *how to use* it; this file defines *what it must do*.
5+
Every feature below has an ID. Every feature ID must be covered by at least one
6+
test (enforced by `tests/test_scope_coverage.py`). Do not add, change, or remove
7+
a feature here without updating the tests — that is how current behaviour is
8+
guaranteed across future changes.
9+
10+
## Purpose
11+
12+
`claude-code-commons` is the small, **vendor-agnostic, standard-library-only**
13+
toolkit shared by Claude routine repos. It owns the generic helpers every routine
14+
tends to re-implement — environment lookup, tolerant number parsing, currency
15+
symbols, remote-path building and a run-log line — with **no third-party
16+
dependency**, so any routine can install it without dragging in a web client or a
17+
spreadsheet engine. Vendor-specific toolkits (e.g. a WooCommerce REST client)
18+
build on top of it.
19+
20+
## Why it exists
21+
22+
Every routine needs the same handful of helpers. Copying them into each repo lets
23+
the copies drift — a fix made in one is missed in the others. One tiny,
24+
dependency-free package, versioned and tested in isolation, removes that hazard
25+
and keeps the heavier vendor toolkits thin: they import the core and add only
26+
their vendor-specific parts.
27+
28+
## Features (acceptance criteria)
29+
30+
Each feature is testable. The ID in brackets is referenced by tests via
31+
`@pytest.mark.feature("F-00X")`.
32+
33+
- **[F-001] Environment helpers with project-prefix fallback.** `env_required(key,
34+
*, prefix="")` and `env_opt(key, default, *, prefix="")` — with `env_get` as a
35+
convenience alias of `env_opt` — resolve a variable by trying `<prefix>_<key>`
36+
first and falling back to the unprefixed `<key>` (plain `<key>` with no prefix,
37+
backward compatible). `env_required` aborts with a clear `SystemExit` naming the
38+
variable when neither form is set; `env_opt`/`env_get` return `default`. This
39+
lets a family of routines share one environment: shared values set once
40+
unprefixed, per-routine values set prefixed so they never collide.
41+
42+
- **[F-002] Tolerant number parsing.** `parse_num()` accepts comma **thousands**
43+
separators (`"1,234"``1234.0`), plain numbers and numeric strings, and
44+
returns `0.0` for `None`, `""` or unparseable input — it never raises. A comma
45+
is always a thousands separator (dot-decimal domain); comma-**decimal** locale
46+
input (`"1,5"``15.0`, not `1.5`) is out of scope and handled by per-routine
47+
locale parsers.
48+
49+
- **[F-003] Remote-path builder.** `build_remote_path(base, folder, filename)`
50+
joins an optional base directory, an optional sub-folder and a filename into one
51+
absolute, slash-normalised path. Lets a family share one base
52+
(`DROPBOX_PATH`) while each writes into its own `<PREFIX>_DROPBOX_FOLDER`
53+
sub-folder: base `/Reports` + folder `Stock``/Reports/Stock/<filename>`. An
54+
empty/None folder drops the file straight into `base`; an empty/None base falls
55+
back to the root — so an unset folder reproduces the previous single-directory
56+
behaviour (backward compatible).
57+
58+
- **[F-004] Currency symbols.** `currency_symbol(code)` maps a 3-letter currency
59+
code to a display symbol (case-insensitive), falling back to the upper-cased
60+
code itself for unmapped currencies.
61+
62+
- **[F-005] Timestamped run log.** `log(msg)` prints a flushed `[HH:MM:SS] msg`
63+
line to stdout — the shared run-log format the routines use.
64+
65+
## Out of scope
66+
67+
Anything vendor-specific (a WooCommerce/Shopify/… client, shop meta keys);
68+
network access of any kind (this package makes no network calls); spreadsheet or
69+
file I/O; currency *conversion* (only symbol labelling); and locale-aware
70+
comma-decimal parsing. Vendor toolkits and the routines themselves own those. If a
71+
new generic, dependency-free helper is ever needed, add a feature ID here first,
72+
then a test, then the code.

pyproject.toml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[build-system]
2+
requires = ["setuptools>=61"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "claude-code-commons"
7+
version = "0.1.0"
8+
description = "Vendor-agnostic, standard-library-only helpers (env lookup, tolerant parsing, currency symbols, remote-path builder, run log) shared by Claude routine repos"
9+
# Standard library only — a broad floor so it installs in any routine runtime,
10+
# including a Python 3.11 scheduled-task sandbox. Verified on 3.9 and 3.12.
11+
requires-python = ">=3.9"
12+
dependencies = []
13+
14+
[project.optional-dependencies]
15+
test = ["pytest>=8"]
16+
17+
# A single top-level module, `code_commons`, living under src/. Installing this
18+
# package makes `from code_commons import env_required, parse_num, ...` work in
19+
# the consuming routines and vendor toolkits.
20+
[tool.setuptools]
21+
py-modules = ["code_commons"]
22+
23+
[tool.setuptools.package-dir]
24+
"" = "src"
25+
26+
[tool.pytest.ini_options]
27+
pythonpath = ["src"]
28+
testpaths = ["tests"]
29+
addopts = "--strict-markers"
30+
markers = [
31+
"feature: link a test to a SCOPE.md feature ID, e.g. feature('F-001')",
32+
]

0 commit comments

Comments
 (0)