From 27226c5f38be2ae6a03411a3010ed2af7a27f744 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:28:14 +0530 Subject: [PATCH 01/10] sdk%lint: shave down CodeCov config, enable comments, specify ignorelist --- .codecov.yml | 47 ++++++++++++++++++++++++++++++ codecov.yml | 82 ---------------------------------------------------- 2 files changed, 47 insertions(+), 82 deletions(-) create mode 100644 .codecov.yml delete mode 100644 codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 00000000..2e7005bf --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,47 @@ +comment: + behavior: "default" + layout: "condensed_header, condensed_files, condensed_footer" + hide_project_coverage: true + require_changes: false + show_carryforward_flags: true +coverage: + status: + project: + default: + target: auto + patch: + default: + target: auto +flags: + dash-num: + paths: + - pkgs/num/ + dash-script: + paths: + - pkgs/script/ + dash-p2p-core: + paths: + - pkgs/p2p_core/ + dash-params: + paths: + - pkgs/params/ + dash-pkc: + paths: + - pkgs/pkc/ + dash-pow: + paths: + - pkgs/pow/ + dash-primitives: + paths: + - pkgs/primitives/ + dash-types: + paths: + - pkgs/types/ + - pkgs/types/marker/ +flag_management: + default_rules: + carryforward: true +ignore: + - "**/bench/**" + - "**/corpus/**" + - "**/tests/**" diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 3d0dcdba..00000000 --- a/codecov.yml +++ /dev/null @@ -1,82 +0,0 @@ -coverage: - status: - project: - default: - target: auto - dash-num: - flags: [dash-num] - target: auto - dash-p2p-core: - flags: [dash-p2p-core] - target: auto - dash-params: - flags: [dash-params] - target: auto - dash-pkc: - flags: [dash-pkc] - target: auto - dash-pow: - flags: [dash-pow] - target: auto - dash-primitives: - flags: [dash-primitives] - target: auto - dash-script: - flags: [dash-script] - target: auto - dash-types: - flags: [dash-types] - target: auto - patch: - default: - target: auto - dash-num: - flags: [dash-num] - target: auto - dash-p2p-core: - flags: [dash-p2p-core] - target: auto - dash-params: - flags: [dash-params] - target: auto - dash-pkc: - flags: [dash-pkc] - target: auto - dash-pow: - flags: [dash-pow] - target: auto - dash-primitives: - flags: [dash-primitives] - target: auto - dash-script: - flags: [dash-script] - target: auto - dash-types: - flags: [dash-types] - target: auto - -flags: - dash-num: - paths: [pkgs/num/] - carryforward: true - dash-script: - paths: [pkgs/script/] - carryforward: true - dash-p2p-core: - paths: [pkgs/p2p_core/] - carryforward: true - dash-params: - paths: [pkgs/params/] - carryforward: true - dash-pkc: - paths: [pkgs/pkc/] - carryforward: true - dash-pow: - paths: [pkgs/pow/] - carryforward: true - dash-primitives: - paths: [pkgs/primitives/] - carryforward: true - dash-types: - paths: [pkgs/types/] - carryforward: true From 825d28f3dd16070b4927d47df63feaaefbc04ed4 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:27:15 +0530 Subject: [PATCH 02/10] sdk%lint: move CodeRabbit definitions from UI to source tree --- .coderabbit.yml | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .coderabbit.yml diff --git a/.coderabbit.yml b/.coderabbit.yml new file mode 100644 index 00000000..9a505ef6 --- /dev/null +++ b/.coderabbit.yml @@ -0,0 +1,55 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json + +early_access: false +enable_free_tier: true +inheritance: false +chat: + allow_non_org_members: false + auto_reply: true + art: false +issue_enrichment: + auto_enrich: + enabled: false + planning: + enabled: false + labeling: + enabled: false +knowledge_base: + code_guidelines: + enabled: true + filePatterns: + - "AGENTS.md" + - "docs/dev/guide_rust.md" +language: en-US +reviews: + auto_apply_labels: false + auto_assign_reviewers: false + auto_review: + enabled: true + base_branches: + - "develop" + drafts: false + changed_files_summary: false + collapse_walkthrough: true + estimate_code_review_effort: false + finishing_touches: + docstrings: + enabled: false + unit_tests: + enabled: false + high_level_summary_in_walkthrough: true + in_progress_fortune: false + path_filters: + - "!**/*.json" + - "!**/*.json5" + - "!**/*.lock" + poem: false + pre_merge_checks: + docstrings: + mode: "off" + profile: chill + related_issues: false + related_prs: false + request_changes_workflow: false + sequence_diagrams: true + suggested_labels: false From b7f17321b3f2214a4985d4c990aaed07ddc5ffa3 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:49:57 +0530 Subject: [PATCH 03/10] sdk%lint: use `nextest` to generate test failure coverage for CodeCov --- .github/workflows/build_nightly.yml | 26 +++++++++++++++++++++----- contrib/README.md | 9 +++++++-- contrib/nix/mods/nixpkgs.nix | 1 + maint/nextest.toml | 5 +++++ 4 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 maint/nextest.toml diff --git a/.github/workflows/build_nightly.yml b/.github/workflows/build_nightly.yml index a9d3a8a9..8a3cad98 100644 --- a/.github/workflows/build_nightly.yml +++ b/.github/workflows/build_nightly.yml @@ -26,6 +26,7 @@ jobs: runs-on: ubuntu-24.04-arm env: RUSTFLAGS: -D warnings + HAS_CODECOV: ${{ secrets.CODECOV_TOKEN != '' }} strategy: fail-fast: false matrix: @@ -81,7 +82,9 @@ jobs: - name: Manage build artifacts uses: actions/cache@v5 with: - path: target + path: | + target + !target/nextest key: cargo-build-nightly-${{ runner.os }}-${{ runner.arch }}-${{ inputs.package }}-${{ matrix.config.name }}-${{ github.sha }} restore-keys: | cargo-build-nightly-${{ runner.os }}-${{ runner.arch }}-${{ inputs.package }}-${{ matrix.config.name }}- @@ -94,7 +97,9 @@ jobs: - name: Test package if: matrix.config.name != 'full' - run: cargo test -p ${{ inputs.package }} ${{ matrix.config.args }} + run: | + cargo nextest run --config-file maint/nextest.toml --profile=ci -p ${{ inputs.package }} ${{ matrix.config.args }} + cargo test --doc -p ${{ inputs.package }} ${{ matrix.config.args }} - name: Check formatting if: matrix.config.name == 'full' @@ -102,7 +107,11 @@ jobs: - name: Test package (with coverage) if: matrix.config.name == 'full' - run: cargo llvm-cov ${{ matrix.config.args }} --package ${{ inputs.package }} --lcov --output-path lcov.info + run: | + cargo llvm-cov clean --workspace + cargo llvm-cov --no-report nextest --config-file maint/nextest.toml --profile=ci ${{ matrix.config.args }} --package ${{ inputs.package }} + cargo llvm-cov report --lcov --output-path lcov.info + cargo test --doc -p ${{ inputs.package }} ${{ matrix.config.args }} - name: Upload coverage to Codecov if: matrix.config.name == 'full' && env.HAS_CODECOV == 'true' @@ -111,8 +120,15 @@ jobs: files: lcov.info flags: ${{ inputs.package }} token: ${{ secrets.CODECOV_TOKEN }} - env: - HAS_CODECOV: ${{ secrets.CODECOV_TOKEN != '' }} + + - name: Upload test results to Codecov + if: ${{ !cancelled() && matrix.config.name == 'full' && env.HAS_CODECOV == 'true' && hashFiles('target/nextest/ci/junit.xml') != '' }} + uses: codecov/codecov-action@v5 + with: + files: target/nextest/ci/junit.xml + report_type: test_results + flags: ${{ inputs.package }} + token: ${{ secrets.CODECOV_TOKEN }} - name: Sanity check benchmarks run: cargo bench ${{ matrix.config.args }} --package ${{ inputs.package }} --no-run diff --git a/contrib/README.md b/contrib/README.md index fadd33a5..1fee4b5d 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -33,6 +33,7 @@ source .venv/bin/activate packages need to be additionally sourced. * [cargo-deny](https://github.com/EmbarkStudios/cargo-deny) +* [cargo-nextest](https://github.com/nextest-rs/nextest) * [CodeQL 2.27 or higher](https://github.com/github/codeql-cli-binaries/releases) * [Git](https://git-scm.com/install/) * [Node.js 24 or higher](https://nodejs.org/en/download) (current LTS, @@ -46,7 +47,7 @@ packages need to be additionally sourced. > ([source](https://docs.brew.sh/FAQ#what-does-keg-only-mean)). ```bash -brew install cargo-deny codeql git node@24 +brew install cargo-deny cargo-nextest codeql git node@24 ``` ### Linux/WSL @@ -57,7 +58,11 @@ you may need to update your shell to add your installation path to `PATH` so tha lint script. Neither CodeQL nor taplo are available in official Debian or Fedora repositories and must be sourced per vendor -guidance. +guidance. `cargo-nextest` is likewise best installed as a Rust binary crate. + +```bash +cargo install --locked cargo-nextest +``` #### Installing `taplo` diff --git a/contrib/nix/mods/nixpkgs.nix b/contrib/nix/mods/nixpkgs.nix index 62f7b593..cb9b8b0d 100644 --- a/contrib/nix/mods/nixpkgs.nix +++ b/contrib/nix/mods/nixpkgs.nix @@ -26,6 +26,7 @@ in packages = [ pkgs.cargo-deny pkgs.cargo-llvm-cov + pkgs.cargo-nextest pkgs.git pkgs.nixfmt pkgs.nodejs_24 diff --git a/maint/nextest.toml b/maint/nextest.toml new file mode 100644 index 00000000..6a109e17 --- /dev/null +++ b/maint/nextest.toml @@ -0,0 +1,5 @@ +[profile.ci] +fail-fast = false + +[profile.ci.junit] +path = "junit.xml" From acc043f87b79836ae57cbbc4e2e5e6c5ae1e7820 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 03:26:35 +0530 Subject: [PATCH 04/10] sdk%lint: use `--locked` for `taplo-cli`, move config to `maint/` --- .vscode/settings.json | 2 ++ contrib/README.md | 2 +- maint/README.md | 2 +- maint/lint/lint_cargo.py | 5 +++-- .taplo.toml => maint/taplo.toml | 0 5 files changed, 7 insertions(+), 4 deletions(-) rename .taplo.toml => maint/taplo.toml (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5f7eace3..b5238601 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,6 +22,8 @@ "editor.formatOnSave": true, "editor.minimap.enabled": true, "editor.rulers": [120], + "evenBetterToml.taplo.configFile.enabled": true, + "evenBetterToml.taplo.configFile.path": "maint/taplo.toml", "files.associations": { "*.json5": "jsonc" }, diff --git a/contrib/README.md b/contrib/README.md index 1fee4b5d..881bc0a6 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -75,7 +75,7 @@ An alternative to procuring releases from the maintainers ([source](https://gith install it as a Rust binary crate. ```bash -cargo install taplo-cli +cargo install --locked taplo-cli ``` #### Debian diff --git a/maint/README.md b/maint/README.md index f69a717e..2a571588 100644 --- a/maint/README.md +++ b/maint/README.md @@ -14,7 +14,7 @@ use [`lint_all.py`](./lint_all.py). | Name | Purpose | Verbs | Depends on | | ---- | ------- | ---------- | ---------- | -| [`lint_cargo.py`](./lint/lint_cargo.py) | Deny dependencies per [`deny.toml`](../deny.toml), check/format TOML files against [`.taplo.toml`](../.taplo.toml) | `check` , `apply`, `apply-all` | `cargo-deny` (deny dependencies), `taplo` (TOML formatting) | +| [`lint_cargo.py`](./lint/lint_cargo.py) | Deny dependencies per [`deny.toml`](../deny.toml), check/format TOML files against [`taplo.toml`](./taplo.toml) | `check` , `apply`, `apply-all` | `cargo-deny` (deny dependencies), `taplo` (TOML formatting) | | [`lint_codeql.py`](./lint/lint_codeql.py) | Query Rust sources against [`maint/codeql/rust/*.ql`](./codeql/rust) | `check`, `apply`, `apply-all`, `run`, `run-all` | `codeql`, `rustc` | | [`lint_javascript.py`](./lint/lint_javascript.py) | Lint Javascript sources against [`eslint.config.mjs`](js/eslint.config.mjs) | *None* | `npx` (part of Node.js), `eslint` (auto-retrieved by script) | | [`lint_markdown.py`](./lint/lint_markdown.py) | Lint Markdown [documentation](../docs/dev/about_docs.md) | *None* | `pymarkdownlnt` | diff --git a/maint/lint/lint_cargo.py b/maint/lint/lint_cargo.py index ada9c227..d0797d69 100755 --- a/maint/lint/lint_cargo.py +++ b/maint/lint/lint_cargo.py @@ -10,7 +10,7 @@ """Validate and enforce constraints across Rust's build system, cargo. Includes a TOML formatter using taplo that affects all TOML files regardless of -provenance or origin, exclusions must be defined in '.taplo.toml' +provenance or origin, exclusions must be defined in 'maint/taplo.toml' """ from __future__ import annotations @@ -61,13 +61,14 @@ def shorten(out: str, err: str) -> None: ) # None, not an empty list: with no paths taplo finds its own through - # '.taplo.toml', so there is no count to report for the whole tree. + # 'maint/taplo.toml', so there is no count to report for the whole tree. return formatted( SCRIPT, "TOML file", None if only is None else [Path(name) for name in only], lambda paths: [ taplo, "fmt", + "--config", str(repo_root / "maint" / "taplo.toml"), *([] if fix else ["--check", "--diff"]), *[str(p) for p in paths], ], diff --git a/.taplo.toml b/maint/taplo.toml similarity index 100% rename from .taplo.toml rename to maint/taplo.toml From 95c37054a5e0c1330949597cab0ac1cf58674510 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 03:19:30 +0530 Subject: [PATCH 05/10] sdk%fix(doc): repair `cargo doc` violations --- pkgs/dev/src/corpus.rs | 2 +- pkgs/pow/src/keccak/consts.rs | 4 ++-- pkgs/pow/src/keccak/scalar.rs | 4 ++-- pkgs/pow/src/keccak/simd.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkgs/dev/src/corpus.rs b/pkgs/dev/src/corpus.rs index 3ac42389..c429c8a1 100644 --- a/pkgs/dev/src/corpus.rs +++ b/pkgs/dev/src/corpus.rs @@ -16,7 +16,7 @@ use std::fs; /// Verifies the serde round-trip for a set of corpus entries. /// -/// Writes `items` to JSON via [`write_corpus`], reads them back through +/// Writes `items` to JSON via `write_corpus`, reads them back through /// [`Corpus::entries`] (no-op check), and asserts equality. /// /// # Panics diff --git a/pkgs/pow/src/keccak/consts.rs b/pkgs/pow/src/keccak/consts.rs index f1b2697e..137d861c 100644 --- a/pkgs/pow/src/keccak/consts.rs +++ b/pkgs/pow/src/keccak/consts.rs @@ -4,7 +4,7 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Keccak-f[1600] constants. +//! Keccak-f\[1600\] constants. //! //! Round constants are generated by the LFSR defined in FIPS 202 Algorithm 1. //! Rotation offsets follow Algorithm 2 (Section 3.2.2). @@ -36,7 +36,7 @@ const fn rc(t: usize) -> bool { /// Sponge rate in bytes (576 bits / 8). pub(crate) const RATE: usize = 72; -/// 24 round constants for Keccak-f[1600] (FIPS 202, Algorithm 5). +/// 24 round constants for Keccak-f\[1600\] (FIPS 202, Algorithm 5). /// /// Each RC[ir] has bits set only at positions 2^j - 1 for j in 0..=6, i.e. at /// bit indices {0, 1, 3, 7, 15, 31, 63}. diff --git a/pkgs/pow/src/keccak/scalar.rs b/pkgs/pow/src/keccak/scalar.rs index 22d5bd95..ac081b63 100644 --- a/pkgs/pow/src/keccak/scalar.rs +++ b/pkgs/pow/src/keccak/scalar.rs @@ -4,12 +4,12 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Scalar Keccak-f[1600] permutation and sponge. +//! Scalar Keccak-f\[1600\] permutation and sponge. use super::consts::{RATE, RC, ROTC}; use crate::util::memops::{extract, load_u64_le, store_u64_le}; -/// Applies the Keccak-f[1600] permutation in place (24 rounds). +/// Applies the Keccak-f\[1600\] permutation in place (24 rounds). /// /// State is a 5x5 matrix of 64-bit lanes stored in row-major order as /// `state[x + 5*y]`. diff --git a/pkgs/pow/src/keccak/simd.rs b/pkgs/pow/src/keccak/simd.rs index 70c12fd0..6c8f1f43 100644 --- a/pkgs/pow/src/keccak/simd.rs +++ b/pkgs/pow/src/keccak/simd.rs @@ -4,7 +4,7 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! SIMD Keccak-f[1600] permutation and sponge. +//! SIMD Keccak-f\[1600\] permutation and sponge. use super::consts::RC; From c4f5572d6bd6d46959a6125f13c979e506a66a71 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 03:37:31 +0530 Subject: [PATCH 06/10] sdk%build: add `Justfile`, update docs, run `cargo test --doc` --- .editorconfig | 3 +++ .github/workflows/build_msrv.yml | 7 +---- .vscode/extensions.json | 1 + Justfile | 44 ++++++++++++++++++++++++++++++++ contrib/README.md | 18 ++++++------- contrib/nix/mods/nixpkgs.nix | 2 ++ 6 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 Justfile diff --git a/.editorconfig b/.editorconfig index 2c4eef29..4bc07da6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,6 @@ indent_size = 4 [*.md] trim_trailing_whitespace = false + +[Justfile] +indent_size = 4 diff --git a/.github/workflows/build_msrv.yml b/.github/workflows/build_msrv.yml index d437b794..c2072775 100644 --- a/.github/workflows/build_msrv.yml +++ b/.github/workflows/build_msrv.yml @@ -68,12 +68,7 @@ jobs: key: codeql-packs-${{ hashFiles('maint/codeql/*/codeql-pack.lock.yml') }} - name: Run linters - run: | - python3 maint/lint_all.py --exclude lint_codeql - python3 maint/lint/lint_codeql.py check - - - name: Run CodeQL - run: python3 maint/lint/lint_codeql.py run --lang=rust --with-suite=rust-security-and-quality + run: just lint - name: Check PR commit messages if: github.event_name == 'pull_request' diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 91a5e507..5e89630d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "ms-python.python", "ms-python.vscode-pylance", "ms-vscode-remote.remote-containers", + "nefrob.vscode-just-syntax", "rust-lang.rust-analyzer", "tamasfe.even-better-toml", "jnoortheen.nix-ide", diff --git a/Justfile b/Justfile new file mode 100644 index 00000000..d788cd6c --- /dev/null +++ b/Justfile @@ -0,0 +1,44 @@ +set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] + +python := if os() == "windows" { "python" } else { "python3" } + +# `[env]` attribute unavailable on just 1.40 packaged with Debian trixie +# (see https://packages.debian.org/trixie/just) +export RUSTDOCFLAGS := "-D warnings" +export RUSTFLAGS := "-D warnings" + +nextest := "cargo nextest run --config-file maint/nextest.toml --profile=ci" + +[private] +default: + @just --list + +bench: + cargo bench --workspace --features full + +build: + cargo build --workspace --features full + cargo build --workspace --no-default-features + +comb: + {{ python }} contrib/git_filter.py --fast-fail develop HEAD -- just test lint + +lint: + @just --fmt --check + {{ python }} maint/lint_all.py --exclude lint_codeql + {{ python }} maint/lint/lint_codeql.py check + {{ python }} maint/lint/lint_codeql.py run --lang=rust --with-suite=rust-security-and-quality + +sh: + nix develop ./contrib/nix#dev + +preview: + {{ python }} docs/build_docs.py preview + +test: + cargo clippy --all-targets --no-default-features + cargo clippy --all-targets --features full + {{ nextest }} --fail-fast --workspace --features full + cargo test --doc --workspace --features full + cargo doc --workspace --no-deps --features full + cargo bench --workspace --features full --no-run diff --git a/contrib/README.md b/contrib/README.md index 881bc0a6..05327e54 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -36,6 +36,8 @@ packages need to be additionally sourced. * [cargo-nextest](https://github.com/nextest-rs/nextest) * [CodeQL 2.27 or higher](https://github.com/github/codeql-cli-binaries/releases) * [Git](https://git-scm.com/install/) +* [just](https://just.systems) (task runner, see [`Justfile`](../Justfile)) and optionally, its + language server, [`just-lsp`](https://www.just-lsp.systems) * [Node.js 24 or higher](https://nodejs.org/en/download) (current LTS, [source](https://nodejs.org/en/blog/release/v24.11.0)) @@ -47,7 +49,7 @@ packages need to be additionally sourced. > ([source](https://docs.brew.sh/FAQ#what-does-keg-only-mean)). ```bash -brew install cargo-deny cargo-nextest codeql git node@24 +brew install cargo-deny cargo-nextest codeql git just just-lsp node@24 ``` ### Linux/WSL @@ -58,10 +60,10 @@ you may need to update your shell to add your installation path to `PATH` so tha lint script. Neither CodeQL nor taplo are available in official Debian or Fedora repositories and must be sourced per vendor -guidance. `cargo-nextest` is likewise best installed as a Rust binary crate. +guidance. `cargo-nextest` and `just-lsp` are likewise best installed as Rust binary crates. ```bash -cargo install --locked cargo-nextest +cargo install --locked cargo-nextest just-lsp ``` #### Installing `taplo` @@ -83,7 +85,7 @@ cargo install --locked taplo-cli ```bash # Required because Debian trixie ships Node 20.x, deprecated in April 2026 curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - -sudo apt install git nodejs -y +sudo apt install git just nodejs -y # `cargo-deny` is currently unavailable on Debian (https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=951368) cargo install --locked cargo-deny @@ -92,7 +94,7 @@ cargo install --locked cargo-deny #### Fedora ```bash -sudo dnf install -y cargo-deny git nodejs24 +sudo dnf install -y cargo-deny git just nodejs24 ``` @@ -106,11 +108,7 @@ script, [`git_filter.py`](./git_filter.py) that creates a temporary worktree and commit in a specified range so the worktree isn't blocked by the validation run. ```bash -# Replace 'branch_name' with the name of your branch -./contrib/git_filter.py --fast-fail develop branch_name -- bash -c 'cargo clippy --all-targets --no-default-features -- -D warnings && -cargo clippy --all-targets --features full -- -D warnings && -cargo test --all-targets --features full && -./maint/lint_all.py' +just comb ``` diff --git a/contrib/nix/mods/nixpkgs.nix b/contrib/nix/mods/nixpkgs.nix index cb9b8b0d..da28cda9 100644 --- a/contrib/nix/mods/nixpkgs.nix +++ b/contrib/nix/mods/nixpkgs.nix @@ -28,6 +28,8 @@ in pkgs.cargo-llvm-cov pkgs.cargo-nextest pkgs.git + pkgs.just + pkgs.just-lsp pkgs.nixfmt pkgs.nodejs_24 pkgs.wasm-pack From 7ce1ed8ff862f208c98e653384272b2ef77eab7a Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:40:17 +0530 Subject: [PATCH 07/10] sdk%doc: refine agentic guidance --- AGENTS.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8795eb28..bbddced9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,13 +16,21 @@ The full guide is at [`docs/dev/guide_rust.md`](./docs/dev/guide_rust.md). Key p decide when to clone. - **Conversions**: `as_` (free, borrow), `to_` (allocates), `into_` (consumes). Implement `From`/`TryFrom`, never `Into` directly. -- **Comments**: inline comments max 80 chars, 3 lines. Rustdoc summary max 3 lines, don't restate the signature. - Document `# Errors` for `Result`-returning functions. +- **Comments**: + - Inline comments max 80 chars, 3 lines. + - Rustdoc summary max 3 lines, don't restate the signature. + - Document `# Errors` for `Result`-returning functions. + - Comments are brief and either directly relevant to the reader or actionable. + - Avoid colons; prefer semicolons, periods and commas. + - Split run-on sentences and multi-clause sentences to prevent reading fatigue, balance against sentence + fragmentation. + - No vague references (`the other`, `that thing`); name the item. + - No exposition, history, or conversational tone. - **Code segmentation**: organise code through modules (in-file or separate files) and naming prefixes. Never use decorative separator comments (`// ----`, `// ====`, `// -- Section --`). Latin-1/ISO 8859-1 characters in source files only; no Unicode dashes, arrows, box drawing, or other decoration in comments or identifiers. -- **Security**: never log secrets, custom `Debug` for sensitive types, constant-time comparison for secrets, zeroize - after use. +- **Security**: never log secrets, custom `Debug` for sensitive types, use `subtle` for constant-time comparison of + secrets, `zeroize` after use. ## Crate standards @@ -120,11 +128,8 @@ dash-num = { version = "0.0.0", path = "../num" } ## Verification -All changes must pass before merge. Use `full` for the widest coverage. +All changes must pass before merge. ```sh -cargo fmt --check -cargo test --features full -cargo bench --features full -cargo clippy --features full --tests +just test lint ``` From ab0593be1b07732d772e07ec0698405273f768c0 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 03:11:08 +0530 Subject: [PATCH 08/10] sdk%lint: broaden `lint_cargo` to include license rejection --- maint/lint/lint_cargo.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/maint/lint/lint_cargo.py b/maint/lint/lint_cargo.py index d0797d69..9f5b2b98 100755 --- a/maint/lint/lint_cargo.py +++ b/maint/lint/lint_cargo.py @@ -89,7 +89,14 @@ def _check_deny(repo_root: Path) -> int | None: print("checking yanked and banned: every crate the graph resolves") result = subprocess.run( # noqa: S603 - [deny_bin, "check", "--hide-inclusion-graph", "advisories", "bans"], + [ + deny_bin, + "check", + "--hide-inclusion-graph", + "advisories", + "bans", + "licenses", + ], capture_output=True, check=False, cwd=str(repo_root), From 43fa4daee1a68d055f6212a6288f8f26e37e66ed Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:31:00 +0530 Subject: [PATCH 09/10] sdk%ci: reconcile pull request labels with the project boards --- .github/board.json | 21 ++++ .github/scripts/pr_board.js | 232 +++++++++++++++++++++++++++++++++++ .github/scripts/util.js | 5 +- .github/workflows/pr_tag.yml | 12 +- maint/js/eslint.config.mjs | 2 + 5 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 .github/board.json create mode 100644 .github/scripts/pr_board.js diff --git a/.github/board.json b/.github/board.json new file mode 100644 index 00000000..7742d8b3 --- /dev/null +++ b/.github/board.json @@ -0,0 +1,21 @@ +{ + "projects": { + "defaults": { + "owner": "dashpay", + "field": "Status" + }, + "allow": [ + { "number": 13 }, + { "number": 14 } + ] + }, + "topics": [ + "Build/CI", + "Codec", + "Crypto", + "Documentation", + "FFI", + "P2P", + "Primitives" + ] +} diff --git a/.github/scripts/pr_board.js b/.github/scripts/pr_board.js new file mode 100644 index 00000000..3f5af30a --- /dev/null +++ b/.github/scripts/pr_board.js @@ -0,0 +1,232 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// @ts-check + +// Syncs pull request topic labels with the project board buckets, with the +// board as the source of truth. Boards and topics come from `board.json`. + +const fs = require("node:fs"); +const path = require("node:path"); + +const { listOpenPulls } = require("./util"); + +const CONFIG_PATH = path.join(__dirname, "..", "board.json"); + +// Color of newly created topic labels. +const TOPIC_COLOR = "1d76db"; + +const BOARD_QUERY = ` + query($owner: String!, $number: Int!, $field: String!, $cursor: String) { + organization(login: $owner) { + projectV2(number: $number) { + id + field(name: $field) { + ... on ProjectV2SingleSelectField { id options { id name } } + } + items(first: 100, after: $cursor) { + pageInfo { hasNextPage endCursor } + nodes { + id + fieldValueByName(name: $field) { + ... on ProjectV2ItemFieldSingleSelectValue { name } + } + content { + ... on PullRequest { + number + repository { nameWithOwner } + labels(first: 50) { nodes { name } } + } + } + } + } + } + } + } +`; + +const SET_TOPIC = ` + mutation($project: ID!, $item: ID!, $field: ID!, $option: String!) { + updateProjectV2ItemFieldValue(input: { + projectId: $project, itemId: $item, fieldId: $field, + value: { singleSelectOptionId: $option } + }) { projectV2Item { id } } + } +`; + +// `github` always sends the workflow token, which cannot reach organization +// projects, so board queries use `PROJECT_TOKEN` directly. +async function graphql(token, query, variables) { + const response = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, + body: JSON.stringify({ query, variables }), + }); + const body = await response.json().catch(() => ({})); + if (body.errors?.length) { + throw new Error(body.errors.map((e) => e.message).join("; ")); + } + if (!response.ok) { + throw new Error(`board refused the query with ${response.status}`); + } + return body.data; +} + +// Returns topic option ids and the *slug* pull requests on the board by number. +// Throws if the board lacks a topic option. +async function readBoard(token, { owner, number, field }, topics, slug, core) { + const where = `${owner}/${number}`; + const options = new Map(); + const items = new Map(); + let board; + let cursor = null; + do { + const data = await graphql(token, BOARD_QUERY, { owner, number, field, cursor }); + board = data.organization?.projectV2; + if (!board) { + throw new Error(`${owner} states no project ${number}`); + } + if (!board.field?.id) { + throw new Error(`${field} is not a single-select field on ${where}`); + } + for (const { id, fieldValueByName, content } of board.items.nodes) { + // Skip issues, drafts and other repositories. + if (content?.number && content.repository.nameWithOwner === slug) { + items.set(content.number, { + itemId: id, + bucket: fieldValueByName?.name ?? null, + labels: content.labels.nodes.map((l) => l.name), + }); + } + } + const { hasNextPage, endCursor } = board.items.pageInfo; + cursor = hasNextPage ? endCursor : null; + } while (cursor); + + for (const { id, name } of board.field.options) { + if (topics.has(name)) { + options.set(name, id); + } else { + core.notice(`${where}: option ${name} is not a topic`); + } + } + for (const topic of topics) { + if (!options.has(topic)) { + throw new Error(`${where}: topic ${topic} is not offered by ${field}`); + } + } + return { where, id: board.id, fieldId: board.field.id, options, items }; +} + +// Runs a write, returning false when the target is no longer available +async function settle(write, core, what) { + try { + await write; + return true; + } catch (error) { + if (error.status !== 404) { + throw error; + } + core.info(`${what} no longer exists`); + return false; + } +} + +// Creates missing topic labels. GitHub label names are case-insensitive, so a +// label differing only in case is renamed to the topic. +async function mintLabels(github, owner, repo, topics, core) { + const labels = await github.paginate(github.rest.issues.listLabelsForRepo, { owner, repo, per_page: 100 }); + const held = new Map(labels.map((l) => [l.name.toLowerCase(), l.name])); + for (const name of topics) { + const present = held.get(name.toLowerCase()); + if (present === name) { + continue; + } + if (present === undefined) { + const description = `Pull requests concerning ${name}`; + await github.rest.issues.createLabel({ owner, repo, name, color: TOPIC_COLOR, description }); + core.info(`minted label ${name}`); + } else { + const call = github.rest.issues.updateLabel({ owner, repo, name: present, new_name: name }); + if (await settle(call, core, `label ${present}`)) { + core.info(`renamed label ${present} to ${name}`); + } + } + } +} + +/** + * @param {{ github: any, context: import("@actions/github").context, core: any }} params + */ +module.exports = async ({ github, context, core }) => { + const token = process.env.PROJECT_TOKEN; + if (!token) { + throw new Error("PROJECT_TOKEN reaches the boards; it is unset"); + } + const { owner, repo } = context.repo; + + // Allowed order breaks ties between boards. + const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8")); + const topics = new Set(config.topics); + const boards = []; + for (const entry of config.projects.allow) { + const project = { ...config.projects.defaults, ...entry }; + boards.push(await readBoard(token, project, topics, `${owner}/${repo}`, core)); + } + + await mintLabels(github, owner, repo, topics, core); + + // Apply board buckets to labels for all pull requests, open or not. + const settled = new Map(); + for (const { where, items } of boards) { + for (const [number, { bucket, labels }] of items) { + if (!topics.has(bucket)) { + continue; + } + const prior = settled.get(number); + if (prior !== undefined) { + if (prior !== bucket) { + core.warning(`PR #${number}: ${where} buckets ${bucket}, an earlier board ${prior}`); + } + continue; + } + settled.set(number, bucket); + if (!labels.includes(bucket)) { + const call = github.rest.issues.addLabels({ owner, repo, issue_number: number, labels: [bucket] }); + if (await settle(call, core, `PR #${number}`)) { + core.info(`PR #${number}: labelled ${bucket}`); + } + } + for (const name of labels) { + if (name !== bucket && topics.has(name)) { + const call = github.rest.issues.removeLabel({ owner, repo, issue_number: number, name }); + if (await settle(call, core, `PR #${number} label ${name}`)) { + core.info(`PR #${number}: pruned ${name}`); + } + } + } + } + } + + // Bucket unbucketed board items from their label. Never add items to a board. + for (const pr of await listOpenPulls({ github, owner, repo })) { + const unbucketed = boards.filter((b) => b.items.get(pr.number)?.bucket === null); + if (unbucketed.length === 0) { + continue; + } + const held = pr.labels.map((l) => l.name).filter((n) => topics.has(n)); + const topic = settled.get(pr.number) ?? (held.length === 1 ? held[0] : undefined); + if (topic === undefined) { + core.warning(`PR #${pr.number} is unbucketed and names ${held.length} topics`); + continue; + } + for (const { where, id, fieldId, options, items } of unbucketed) { + const item = items.get(pr.number).itemId; + await graphql(token, SET_TOPIC, { project: id, item, field: fieldId, option: options.get(topic) }); + core.info(`PR #${pr.number}: bucketed ${topic} on ${where}`); + } + } +}; diff --git a/.github/scripts/util.js b/.github/scripts/util.js index 1f412e01..184c7797 100644 --- a/.github/scripts/util.js +++ b/.github/scripts/util.js @@ -24,15 +24,14 @@ function sleep(ms) { * @param {{ github: any, owner: string, repo: string }} params * @returns {Promise} */ -async function listOpenPulls({ github, owner, repo }) { - const { data: pulls } = await github.rest.pulls.list({ +function listOpenPulls({ github, owner, repo }) { + return github.paginate(github.rest.pulls.list, { owner, repo, state: "open", base: BASE_BRANCH, per_page: 100, }); - return pulls; } /** diff --git a/.github/workflows/pr_tag.yml b/.github/workflows/pr_tag.yml index bdddedb2..05d098f4 100644 --- a/.github/workflows/pr_tag.yml +++ b/.github/workflows/pr_tag.yml @@ -2,7 +2,7 @@ name: Tag PRs on: pull_request_target: - types: [synchronize, opened, reopened, closed] + types: [synchronize, opened, reopened, closed, labeled, unlabeled] schedule: - cron: "0 6 * * 1" @@ -12,6 +12,7 @@ concurrency: permissions: contents: read + issues: write pull-requests: write jobs: @@ -35,3 +36,12 @@ jobs: script: | const script = require("./.github/scripts/pr_tag.js"); await script({ github, context }); + + - name: Sync labels with the project board + uses: actions/github-script@v8 + env: + PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }} + with: + script: | + const script = require("./.github/scripts/pr_board.js"); + await script({ github, context, core }); diff --git a/maint/js/eslint.config.mjs b/maint/js/eslint.config.mjs index 514cc444..f3332ef9 100644 --- a/maint/js/eslint.config.mjs +++ b/maint/js/eslint.config.mjs @@ -52,7 +52,9 @@ export default [ ecmaVersion: 2022, sourceType: "commonjs", globals: { + __dirname: "readonly", console: "readonly", + fetch: "readonly", module: "readonly", process: "readonly", require: "readonly", From 5c5f0da3c27b0f745719441aa97e9a3159636712 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:22:57 +0530 Subject: [PATCH 10/10] sdk%lint: `lint_unconv` -> `lint_commit`, set maximum width and height --- .github/workflows/build_msrv.yml | 8 ++- maint/README.md | 2 +- maint/lint/{lint_unconv.py => lint_commit.py} | 66 ++++++++++++++----- 3 files changed, 59 insertions(+), 17 deletions(-) rename maint/lint/{lint_unconv.py => lint_commit.py} (75%) diff --git a/.github/workflows/build_msrv.yml b/.github/workflows/build_msrv.yml index c2072775..41f5ccab 100644 --- a/.github/workflows/build_msrv.yml +++ b/.github/workflows/build_msrv.yml @@ -70,10 +70,16 @@ jobs: - name: Run linters run: just lint + - name: Check PR title + if: github.event_name == 'pull_request' + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: python3 maint/lint/lint_commit.py -m "${PR_TITLE}" + - name: Check PR commit messages if: github.event_name == 'pull_request' run: > - python3 maint/lint/lint_unconv.py + python3 maint/lint/lint_commit.py -r "${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}" build: diff --git a/maint/README.md b/maint/README.md index 2a571588..94a5fc9c 100644 --- a/maint/README.md +++ b/maint/README.md @@ -14,6 +14,7 @@ use [`lint_all.py`](./lint_all.py). | Name | Purpose | Verbs | Depends on | | ---- | ------- | ---------- | ---------- | +| [`lint_commit.py`](./lint/lint_commit.py) | Lint commit names against [`unconv.toml`](./unconv.toml) | `run` | `git` | | [`lint_cargo.py`](./lint/lint_cargo.py) | Deny dependencies per [`deny.toml`](../deny.toml), check/format TOML files against [`taplo.toml`](./taplo.toml) | `check` , `apply`, `apply-all` | `cargo-deny` (deny dependencies), `taplo` (TOML formatting) | | [`lint_codeql.py`](./lint/lint_codeql.py) | Query Rust sources against [`maint/codeql/rust/*.ql`](./codeql/rust) | `check`, `apply`, `apply-all`, `run`, `run-all` | `codeql`, `rustc` | | [`lint_javascript.py`](./lint/lint_javascript.py) | Lint Javascript sources against [`eslint.config.mjs`](js/eslint.config.mjs) | *None* | `npx` (part of Node.js), `eslint` (auto-retrieved by script) | @@ -23,7 +24,6 @@ use [`lint_all.py`](./lint_all.py). | [`lint_rust.py`](./lint/lint_rust.py) | Lint Rust sources against [`rustfmt.toml`](../rustfmt.toml) | *None* | `cargo`, `rustfmt` | | [`lint_semgrep.py`](./lint/lint_semgrep.py) | Lint source code against [`maint/semgrep`](./semgrep/rust) definitions | *None* | `semgrep` | | [`lint_symlinks.py`](./lint/lint_symlinks.py) | Lint symbolic links | *None* | `git` | -| [`lint_unconv.py`](./lint/lint_unconv.py) | Lint commit names in ranges specified against [`unconv.toml`](./unconv.toml) | `run` | `git` | ## Generating lockfiles diff --git a/maint/lint/lint_unconv.py b/maint/lint/lint_commit.py similarity index 75% rename from maint/lint/lint_unconv.py rename to maint/lint/lint_commit.py index 75895d43..42a14e9b 100755 --- a/maint/lint/lint_unconv.py +++ b/maint/lint/lint_commit.py @@ -27,6 +27,12 @@ # File in the repository root the accepted vocabulary is read from. CONFIG_FILENAME = "unconv.toml" +# Maximum line width for a commit. +MAX_WIDTH = 73 + +# Maximum number of lines for a commit, subject inclusive. +MAX_HEIGHT = 5 + # Matches a commit subject, 'namespace%type[(scope)][!]: description'. _PATTERN = re.compile( r"^(?P[^\s%]+)" @@ -80,11 +86,33 @@ def _allowed_types(namespace: str, config: Config) -> frozenset[str]: return base | ns.types -def _lint_subject(subject: str, config: Config) -> list[str]: - """Return error strings for one commit subject line; empty list means valid""" - subject = subject.strip() +def _lint_shape(lines: list[str]) -> list[str]: + """Return error strings for the width and height of a commit message.""" + errors: list[str] = [] + if len(lines) > MAX_HEIGHT: + errors.append(f"{len(lines)} lines tall, over {MAX_HEIGHT}") + wide = [n for n, line in enumerate(lines, 1) if len(line) > MAX_WIDTH] + if wide: + where = ", ".join(str(n) for n in wide) + errors.append(f"line {where} wider than {MAX_WIDTH} characters") + return errors + + +def _lint_message( + message: str, config: Config, *, title: bool = False +) -> list[str]: + """Return error strings for a commit message or, if *title*, a PR title""" + lines = message.strip().splitlines() + subject = lines[0].strip() if lines else "" + if title: + return _lint_subject(subject, config) if not subject or subject.startswith("#") or subject.startswith("Merge "): return [] + return _lint_subject(subject, config) + _lint_shape(lines) + + +def _lint_subject(subject: str, config: Config) -> list[str]: + """Return error strings for one commit subject line""" m = _PATTERN.match(subject) if m is None: @@ -143,14 +171,20 @@ def _default_range() -> str: return "HEAD~1..HEAD" -def _subjects_from_commit(ref: str) -> list[str]: - out = git_out(root_dir(), "log", "-1", "--format=%s", ref) - return [line for line in out.splitlines() if line.strip()] +def _messages(*args: str) -> list[str]: + out = git_out(root_dir(), "log", "--no-merges", "--format=%B%x00", *args) + return [m.strip() for m in out.split("\0") if m.strip()] + + +def _messages_from_commit(ref: str) -> list[str]: + parents = git_out(root_dir(), "rev-list", "--parents", "-n", "1", ref) + if len(parents.split()) > 2: + return [] + return _messages("-1", ref) -def _subjects_from_range(git_range: str) -> list[str]: - out = git_out(root_dir(), "log", "--format=%s", git_range) - return [line for line in out.splitlines() if line.strip()] +def _messages_from_range(git_range: str) -> list[str]: + return _messages(git_range) def main() -> int: @@ -200,18 +234,20 @@ def main() -> int: config = Config.load(config_path) if args.m is not None: - subjects = [args.m] + messages = [args.m] elif args.r is not None: - subjects = _subjects_from_range(args.r) + messages = _messages_from_range(args.r) elif args.c is not None: - subjects = _subjects_from_commit(args.c) + messages = _messages_from_commit(args.c) else: - subjects = _subjects_from_range(_default_range()) + messages = _messages_from_range(_default_range()) - results = [(s, _lint_subject(s, config)) for s in subjects] + title = args.m is not None + results = [(m, _lint_message(m, config, title=title)) for m in messages] failed = False - for subject, errors in results: + for message, errors in results: + subject = message.strip().splitlines()[0] if message.strip() else "" if errors: print(f"[FAIL] {subject} (reason: {'; '.join(errors)})") failed = True