Files
wickra/CONTRIBUTING.md
T
kingchenc 5b23b36261 ci: pin CI dependency installs by hash (Scorecard PinnedDependencies) (#114)
* ci: use npm ci instead of npm install for reproducible installs

Pins the node binding dependency install to the committed package-lock.json
integrity hashes (OpenSSF Scorecard PinnedDependencies). npm ci installs
strictly from the lockfile; npm install could resolve newer patch versions.
Covers ci.yml and both release.yml node steps.

* ci: hash-pin Python dev tooling in ci.yml (Scorecard #19)

Replaces the unpinned 'pip install maturin pytest numpy hypothesis' with a
hash-locked '--require-hashes -r' install (OpenSSF Scorecard PinnedDependencies).

Two lock files are needed because numpy publishes no single release with wheels
for both cp39 and cp313 (<=2.0.2 has cp39 only, >=2.1 drops cp39):
  ci-dev-py39.txt  numpy 2.0.2  (Python 3.9, + tomli/exceptiongroup)
  ci-dev-py3.txt   numpy 2.4.6  (Python 3.10+)

The step selects the file by matrix.python-version under shell: bash. Both are
generated from ci-dev.in via uv (scripts/update-lockfiles.sh, added next).

* ci: hash-pin Python deps in bench.yml (Scorecard #16)

Replaces the unpinned 'pip install maturin numpy pandas talipp finta' with a
hash-locked '--require-hashes -r .github/requirements/bench.txt' install.
bench.yml runs on a single Python version (3.11), so one lock file (generated
from bench.in via uv) is sufficient.

* build: add scripts/update-lockfiles.sh to regenerate all lockfiles

One command refreshes every committed lockfile across languages: Cargo.lock and
fuzz/Cargo.lock (cargo update), the Node binding package-lock.json, and the
hash-pinned Python requirements under .github/requirements/ (uv pip compile
--generate-hashes). Uses uv for the Python locks so a target Python version's
hashed transitive closure can be resolved without that interpreter installed
(needed for the numpy cp39/cp313 split); bootstraps uv if absent.

.gitattributes pins *.sh to LF so the script stays runnable on Linux/macOS.

* ci: split ci-dev requirements per Python version + Dependabot rehash

Splits the single ci-dev.in into ci-dev-py39.in (numpy <2.1, the last series
with cp39 wheels) and ci-dev-py3.in (3.10+), giving a 1:1 .in->.txt layout.
The cap keeps Python 3.9 permanently installable and stops Dependabot from
proposing 3.9-breaking numpy bumps.

Adds a Dependabot pip entry on /.github/requirements so the hash-locked tooling
is kept current automatically; the canonical manual refresh stays
scripts/update-lockfiles.sh. Only the '# via -r' provenance lines in the .txt
change; no package versions or hashes move.

* ci: cache pip and npm downloads in the PR-loop jobs

Adds setup-python cache: pip (ci.yml python matrix + bench.yml, keyed on the
hash-locked requirements) and setup-node cache: npm (ci.yml node job, keyed on
bindings/node/package-lock.json), on both the primary and retry setup steps.

Scoped to jobs that actually install dependencies; the examples-smoke and
clippy-bindings jobs install nothing and are left uncached. release.yml is
intentionally left out: it runs only on tag push (not the PR loop) and is the
publish-critical path, so no caching is added there.

* docs: document hash-pinned requirements and update-lockfiles.sh

Updates the lockfile-policy table: the bindings/python row no longer claims CI
installs tooling unpinned, and a new .github/requirements row documents the
hash-locked CI/bench tooling and the per-Python-version ci-dev split. Adds a
paragraph pointing contributors at scripts/update-lockfiles.sh (uv-based,
self-bootstrapping) as the canonical lockfile refresh.

* docs: changelog entry for hash-pinned CI dependency installs
2026-06-01 17:58:31 +02:00

5.9 KiB

Contributing to Wickra

Thanks for your interest in improving Wickra. This document explains how to build the project, the standards a change must meet, and how to get it merged.

License of contributions

Wickra is licensed under the PolyForm Noncommercial License 1.0.0 (see LICENSE). By submitting a contribution you agree that it is licensed to the project under those same terms. The Noncommercial license permits use for any purpose other than a commercial one; keep that in mind when proposing features or depending on Wickra elsewhere.

Project layout

Path Contents
crates/wickra-core The indicator engine — every indicator lives here.
crates/wickra Thin umbrella crate re-exporting wickra-core.
crates/wickra-data CSV reader, tick aggregator, resampler, Binance feed.
bindings/python PyO3 bindings (wickra on PyPI).
bindings/node napi-rs bindings (wickra on npm).
bindings/wasm wasm-bindgen bindings (wickra-wasm on npm).
examples/ Runnable examples.
docs/ Pointer to the project Wiki, which holds all documentation.

Building and testing

Rust

cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo test -p wickra-data --features live-binance

The minimum supported Rust version is 1.86 for the workspace crates and 1.88 for bindings/node; the msrv CI job enforces both. These floors are not chosen freely — they are the lowest versions our dependencies allow (criterion 0.8.2, the bench dev-dependency, requires 1.86; napi-build 2.3.2 requires 1.88). We keep the MSRV at that dependency-forced floor on purpose so the library builds for the widest possible audience; please don't raise it without a dependency that actually requires it.

Python

cd bindings/python
python -m maturin build --release --out dist
python -m pip install --force-reinstall --no-deps dist/wickra-*.whl
python -m pytest -q

Node

cd bindings/node
npm install
npx napi build --platform --release
node --test __tests__/

WASM

wasm-pack build bindings/wasm --target web --release --features panic-hook
wasm-pack test --node bindings/wasm

Lockfile policy

Component Lockfile Tracked? Why
Workspace (Rust) Cargo.lock yes The workspace ships binaries (examples, fuzz harness) and CI builds, so the dependency graph is pinned for reproducible builds.
bindings/node package-lock.json yes Reproducible npm install for the native binding.
examples/node package-lock.json yes Same — the runnable Node examples link the binding via a file: dependency.
bindings/python n/a (no lockfile) The published package pins only numpy>=1.22 at runtime; its native code is pinned through the workspace Cargo.lock. The CI/bench dev tooling it installs is hash-locked separately — see the .github/requirements row.
.github/requirements *.txt (hash-pinned) yes CI/bench Python tooling, locked with uv pip compile --generate-hashes (OpenSSF Scorecard PinnedDependencies). ci-dev is split per Python version — ci-dev-py39.txt and ci-dev-py3.txt — because numpy ships no single release with wheels for both cp39 and cp313; bench.txt covers the single-version bench job.
fuzz fuzz/Cargo.lock no (ignored) fuzz/ is a detached crate; cargo-fuzz init generates fuzz/.gitignore which ignores its Cargo.lock. The fuzz smoke job resolves dependencies fresh, so the lock is not needed for reproducibility here.
site (marketing) package-lock.json no (ghost-ignored) The VitePress site is a local-only project excluded via .git/info/exclude; its lockfile stays local.

When adding a new committed Node package, commit its package-lock.json too and remove any matching ignore rule. Do not add a top-level package-lock.json — the repository root is not an npm package.

To refresh every committed lockfile in the workspace — Cargo.lock, fuzz/Cargo.lock, the Node binding lock, and the hash-pinned Python requirements — run ./scripts/update-lockfiles.sh. It uses uv for the Python locks (and bootstraps it on Linux/macOS if absent) so each target Python version's hashed transitive closure can be regenerated without that interpreter installed. Dependabot also keeps the .github/requirements pins current.

Standards for a change

  • Formatting & lints. cargo fmt must leave the tree unchanged and cargo clippy ... -D warnings must be clean. CI gates both.
  • Tests. New behaviour needs tests; bug fixes need a regression test.
  • Indicator correctness. A new or changed indicator must have a reference-value test against a known-good source (TA-Lib, pandas-ta, or a hand-computed value) and a reset test.
  • Streaming parity. An indicator's batch output must equal the sequence of update calls.
  • Bindings. A change to a public indicator API must be mirrored across the Python, Node, and WASM bindings, including their type stubs / .d.ts.
  • Docs. Update the relevant page on the documentation site and the README.md when behaviour or the public API changes. The docs live in a separate git repository: https://github.com/wickra-lib/wickra-docs.
  • Changelog. Add an entry under ## [Unreleased] in CHANGELOG.md.

Commit and pull-request workflow

  1. Branch off main.
  2. Keep commits focused — one logical change per commit, with an imperative subject line and a body explaining why.
  3. Open a pull request against main and fill in the template.
  4. CI must be green before review.

Reporting bugs and proposing features

Use the issue templates under .github/ISSUE_TEMPLATE. For security-sensitive reports, follow SECURITY.md instead of opening a public issue.