Read CONTRIBUTING.md for contribution policy, review gates, and coding
style. This file is only the local command/automation convention sheet for
coding agents.
notes/PLAN.md (implementation spec) and notes/DECISIONS.md (decision log)
are maintainer-local working notes, not in the repository; code comments cite
them by section and decision number.
- ALWAYS use
uv runfor every Python invocation. Never barepython,python3, orpip. - Use
uv pipfor packages,uv syncfor envs, anduv runfor scripts. - Per-family reference environments live under
scripts/envs/<family>/and are invoked as:
uv run --project scripts/envs/<family> scripts/<script>.py ...- Tools with PEP-723 headers run as
uv run scripts/<tool>.py.
- After C++ changes, run:
cmake --build build --target langid-cli- The default posture is a static
liblangid+ static ggml, sobuild/bin/langid-cliis self-contained.-DLANGID_BUILD_SHARED=ONis the packaging/binding configuration.
- Format our C/C++ before committing. The formatter is pinned and fetched via
uvx, so do not rely on a system clang-format:
scripts/ci/clang-format.sh # format our tree in place (default)
scripts/ci/clang-format.sh --check # verify, no changes- Scope is our code only. The vendored tree (
ggml/) and verbatim upstream copies (examples/common/dr_wav.h) are never formatted. CI gates our C/C++ in.github/workflows/clang-format.yml.
No C++ exception may escape a public entry point.
- A new public entry point must either route through an
api_guard_*wrapper (src/langid.cpp) or be nothrow by construction. Accessors are not pure reads; guard them. - Entry points with ownership out-params enforce "non-OK =>
*out == NULL, nothing leaked" in their forwarders on every error return. - Teardown never uses raw
ggml_backend_free/ggml_backend_buffer_free/ggml_backend_sched_freein library code: uselangid::safe_*fromsrc/langid-backend.h.tests/lint_teardown.cmakefails CI on violations. - Host log callbacks are contained at the emission site (
langid_log_invoke). - The
LANGID_TEST_*fault hooks (_DEV_INIT_THROW,_TEARDOWN_THROW) intentionally ship in release artifacts. Present-but-empty values are inert. - Caller-supplied enum fields are read as raw ints (
enum_field_raw) and validated before any enum-typed load: a C caller can store any int there.
CONTRIBUTING.md has the review-gate table; docs/validation.md explains
what each gate proves. The commands:
- Default suite (CI runs this on Linux and macOS, plus an ASan+UBSan lane):
ctest --test-dir build --output-on-failure- Install + link manifest, both postures (CI runs both):
cmake --install build --prefix /tmp/langid-static
python3 scripts/ci/link_smoke.py --prefix /tmp/langid-static- End-to-end numerical checks (local only: needs a converted GGUF and the pinned reference env). Required for any change that can move numerics — graph, front end, converter, loader, ggml pin:
uv run scripts/validate.py all --family <f>- Dataset gate (local only): does the C++ engine make the same decisions as the reference over the full FLEURS sweep?
uv run scripts/eval/compare.py \
reports/eval/ref-speechbrain-untrimmed.jsonl \
reports/eval/cpp-f32-untrimmed.jsonl- Manual tensor debugging:
uv run scripts/compare_tensors.py ...- Never suppress test failures without root cause analysis. A tolerance is
widened only with the mechanism named in the tolerance file's
_comment.
- Do not commit, push, tag, create remotes, create pull requests, or comment on pull requests unless the user explicitly asks for that action.
- Do not reformat unrelated code in the same change as a behavior fix.
- Model files, GGUFs, reference dumps, and dataset audio are never committed.