Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1169263
chore(deps): upgrade criterion 0.5.1 → 0.8
kalwalt Jun 10, 2026
a30b033
doc(benches): refresh BENCHMARKS.md for criterion 0.8
kalwalt Jun 10, 2026
1b3a3e4
fix(core): clean up trivial clippy lints under --all-targets --all-fe…
kalwalt Jun 10, 2026
224d766
test(core): add unit tests for relocated math/pattern helpers (#180)
kalwalt Jun 10, 2026
f8521eb
fix(kpm): tighten dual-mode FFI extern cfg to match caller cfg (#180)
kalwalt Jun 11, 2026
a0e4388
refactor(ar): convert Default::default() + field reassign to struct-i…
kalwalt Jun 11, 2026
e1b036c
chore(ar2): allow too_many_arguments on SIMD get_similarity variants …
kalwalt Jun 11, 2026
b14df4c
chore: clear strict clippy lints in examples/tests/benches (#180)
kalwalt Jun 11, 2026
09e5904
chore(codecov): exclude examples/benches from coverage targets (#180)
kalwalt Jun 11, 2026
53aeb4a
ci(coverage): fix tarpaulin tests/ glob and mirror in codecov (#180)
kalwalt Jun 11, 2026
a5e185a
ci: tighten clippy gate to --all-targets --all-features (#180)
kalwalt Jun 11, 2026
2cf9467
ci: relocate strict clippy gate to kpm-build (ubuntu) job (#180)
kalwalt Jun 11, 2026
5d244aa
docs(agents): codify #180 clippy conventions and un-ignore .agents/
kalwalt Jun 12, 2026
b94a720
ci(miri): add Miri UB validation for pure-Rust code paths (#182)
kalwalt Jun 12, 2026
0e2eba9
ci(miri): drop -Zmiri-strict-provenance (trips on third-party deps) (…
kalwalt Jun 12, 2026
b9a49ce
ci(miri): switch to Tree Borrows to avoid crossbeam-epoch false posit…
kalwalt Jun 12, 2026
8185703
ci(miri): enable -Zmiri-disable-isolation for tempfile-using tests (#…
kalwalt Jun 12, 2026
6e064d3
fix(kpm): replace unaligned transmute in hamming_distance_96 (#192)
kalwalt Jun 12, 2026
5952fd6
perf(ci): scope Miri job to unsafe boundaries via cfg_attr(miri, igno…
kalwalt Jun 12, 2026
55e0f46
perf(ci): annotate remaining real-image tests (#194 follow-up)
kalwalt Jun 13, 2026
e3d31b7
test(homography): widen cauchy_cost tolerance to 1e-5 for Miri (#194)
kalwalt Jun 13, 2026
960d7ae
ci(miri): enable -Zmiri-ignore-leaks for rayon thread pool (#194)
kalwalt Jun 13, 2026
66f836b
ci(miri): promote to required gate, split workflow, add README badge …
kalwalt Jun 13, 2026
97d6bfb
ci(miri): skip docs-only changes via paths-ignore + same-name shim
kalwalt Jun 14, 2026
db70e09
perf(kpm): Gaussian scale-space pyramid — benchmark + rayon paralleli…
kalwalt Jun 15, 2026
ec28cc9
perf(kpm): box-filter Pyramid downsample — criterion benchmark + SIMD…
kalwalt Jun 15, 2026
64d1170
doc(kpm): mark box-filter Pyramid as kept-but-unused reference (#203)
kalwalt Jun 22, 2026
45c79ca
ci(benchmarks): harden against flaky external C-library downloads (#204)
kalwalt Jun 22, 2026
94e7b2d
build(wasm): make cc/bindgen optional, fix wasm dead-code (#161 goal 1)
kalwalt Jun 22, 2026
5da470c
feat(wasm): wire console logger + clean SIMD wasm build; add .fset3 a…
kalwalt Jun 22, 2026
c088fa9
feat(wasm): KPM detection binding + browser NFT demo (#161 goal 4)
kalwalt Jun 22, 2026
d511d00
doc(wasm): note .fset3 / KPM detection in the NFT demo description (#…
kalwalt Jun 22, 2026
03fd9d7
feat(kpm): facade-parity convenience accessors on VisualDatabase + Fe…
kalwalt Jun 23, 2026
5b6fbee
refactor(kpm): factor VisualDatabase::query matching loop + add query…
kalwalt Jun 26, 2026
2fcefa2
test(kpm): raise M9 coverage — exclude examples + edge tests (#177) (…
kalwalt Jun 26, 2026
f756e15
feat(example): nft_marker_gen .fset3 via RustFreakMatcher — drop ffi-…
kalwalt Jun 24, 2026
63bda44
fix(ci): disable cargo-bin cache on kpm-build to stop stale rustup-in…
kalwalt Jun 26, 2026
d7d48b1
test(ci): verify ffi-backend tests + example link on macOS (#119)
kalwalt Jun 26, 2026
02651b3
doc: update README + ARCHITECTURE for the v0.8.0 milestone
kalwalt Jun 26, 2026
28c75f6
chore(release): v0.8.0
kalwalt Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .agents/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Agent Instructions: Porting Core Data Structures (`include/AR/*.h`)

## Phase 1: Header Triage & Analysis
1. Analyze in the https://github.com/webarkit/WebARKitLib the `include/AR/` directory. Consider code from lib/src/AR, lib/SRC/AR2, lib/SRC/ARUtils
2. **IGNORE** the following headers: `video.h`, `gsub.h`, `gsub_lite.h`, `arMulti.h`, `arGL.h`, `arOSG.h`.
3. Focus strictly on: `ar.h`, `param.h`, `matrix.h`, `arFilterTransMat.h`.

## Phase 2: Struct Extraction & Translation
1. Identify ALL core data structures in `ar.h` and related math headers. This includes, but is not limited to:
- Calibration: `ARParam`, `ARParamLT`
- Tracking & State: `ARHandle`, `AR3DHandle`, `ARMarkerInfo`
- Math: `ARMat`, `ARVec`
2. Create the corresponding Rust modules (e.g., `crates/core/src/types.rs`, `crates/core/src/math.rs`).
3. Translate the C `struct`s into Rust. Ensure correct memory alignment and types.
4. Add `#[derive(Debug, Default, Clone)]` where applicable.

## Phase 3: Validation
1. Write unit tests for every function and struct created to ensure their default instantiation behaves as expected and mathematical equivalence is maintained.
2. Ensure all generated documentation and inline comments are in English.

## Phase 4: Clippy hygiene (post-#180)

The strict clippy gate (`cargo clippy --workspace --all-targets --all-features -- -D warnings`)
is enforced in CI by the `kpm-build (ubuntu-latest)` job. When writing
or modifying any code in this repo, follow the conventions below to
avoid recreating lints cleared during the #180 series. Full reference
in [CLAUDE.md §7](../CLAUDE.md).

1. **Struct-init over `Default::default()` + field reassign.**
`let x = T { f: ..., ..Default::default() };`, not
`let mut x = T::default(); x.f = ...;`. Refs: PR #186, #188.

2. **FFI shim cfg must match its caller's cfg.** When declaring
`extern "C" { fn webarkit_cpp_*(...) }` only for dual-mode parity
tests, gate the block with `#[cfg(all(test, feature = "dual-mode"))]`
— same as the caller module. A `#[cfg(feature = "dual-mode")]`
alone leaks the extern into the non-test lib build, where it has
no caller and trips `dead_code`. Ref: PR #185.

3. **Place new items above `#[cfg(test)] mod tests`.** Clippy's
`items_after_test_module` fires otherwise. Ref: PR #184.

4. **SIMD runtime-dispatch variants get inline
`#[allow(clippy::too_many_arguments)]` + `// rationale:`** — not
a `clippy.toml` threshold raise. Their signatures are locked by
the dispatcher contract. Ref: PR #187.

5. **Auto-generated test fixtures from external sources** (e.g.
`tests/kpm_regression.rs` constants captured from C++) get
file-scoped `#![allow(clippy::excessive_precision)]` + rationale
to preserve source-traceability. Ref: PR #188.

6. **Re-run strict clippy after every clean.** Cargo halts on lib
errors before checking `--all-targets`. Lints in
examples/tests/benches stay masked until the lib is clean. Refs:
PR #187 → #188 (26 lints surfaced this way).

When you must add a lint suppression, write it as `#[allow(...)]` with
a `// rationale:` comment explaining *why* — not `#[expect(...)]` or a
bare allow. The rationale is the contract for future contributors.
21 changes: 21 additions & 0 deletions .agents/rules/rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
trigger: always_on
---

# Antigravity Agent Rules for WebARKitLib.rs

## 1. Role & Identity
You are an expert systems programmer porting WebARKitLib from C/C++ to Rust. The ultimate target is a pure, side-effect-free WASM module and native library.

## 2. Strict Exclusions (Out of Scope)
- **NO Video Handling**: Completely ignore all files and functions related to video capture (e.g., V4L2, DirectShow, QuickTime, `video.h`, `arVideo.h`). Video buffering will be handled externally (e.g., via JavaScript `Uint8Array` passed to WASM).
- **NO Rendering/OpenGL**: Completely ignore all files and functions related to OpenGL, GLUT, or 3D rendering (e.g., `gsub.h`, `gsub_lite.h`, `arGL.h`).
- **NO arMulti**: Skip all code related to multi-marker tracking (`arMulti.h`, `arMulti*.c`) for now.

## 3. Code Standards & Language
- **Language**: ALL code comments, explanations, docstrings, and commit messages MUST be written in English.
- **Safety**: Prefer safe Rust. Use `unsafe` only for specific WASM SIMD intrinsics (`std::arch::wasm32`).

## 4. C to Rust Translation Guidelines
- Translate C structs from headers into idiomatic Rust. Use exact primitive types (e.g., `f64` for `ARdouble` if configured so).
- Replace C arrays inside structs with Rust arrays or `Vec` depending on whether the size is known at compile time.
37 changes: 37 additions & 0 deletions .agents/skills/license-header-adder/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
name: license-header-adder
description: Automatically adds a license header to source files with a filename placeholder.
---

# License Header Adder Skill

This skill allows you to add a consistent LGPLv3 license header to all source files in the project.

## Instructions

1. **Read the Template**: Locate the license template in `resources/HEADER.txt`.
2. **Placeholder Replacement**: Before applying the header to a file, replace the `{{FILENAME}}` placeholder with the basename of the target file.
3. **Supported Files**: apply this header to:
- `.rs` files
- `.c` and `.h` files
- `.js` and `.ts` files
4. **Application Logic**:
- If the file already has a license header, skip it.
- **Exclusions**: Never apply this to files in `benchmarks/c_benchmark/src/WebARKitLib` or third-party vendor directories.
- Insert the header at the very top of the file.
- Ensure there is a blank line after the header before the original content starts.

## Example Usage

When applying to `core/src/lib.rs`:
```rust
/*
* lib.rs
*
* WebARKitLib.rs - High-performance AR tracking library
...
*/

use wasm_bindgen::prelude::*;
...
```
36 changes: 36 additions & 0 deletions .agents/skills/license-header-adder/resources/HEADER.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* {{FILENAME}}
* WebARKitLib-rs
*
* This file is part of WebARKitLib-rs - WebARKit.
*
* WebARKitLib-rs is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WebARKitLib-rs is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with WebARKitLib-rs. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent modules, and to
* copy and distribute the resulting executable under terms of your choice,
* provided that you also meet, for each linked independent module, the terms and
* conditions of the license of that module. An independent module is a module
* which is neither derived from nor based on this library. If you modify this
* library, you may extend this exception to your version of the library, but you
* are not obligated to do so. If you do not wish to do so, delete this exception
* statement from your version.
*
* Copyright 2026 WebARKit.
*
* Author(s): Walter Perdan @kalwalt https://github.com/kalwalt
*
*/

173 changes: 173 additions & 0 deletions .claude/issue-180-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Issue #180 — Tighten CI Clippy Gate to `--all-targets --all-features`

**Status**: Planning complete, ready for implementation.
**Branch base**: `dev` (HEAD `a30b033` at planning time, post-PR #183 criterion 0.8 merge).
**Tracking issue**: [#180](https://github.com/webarkit/WebARKitLib-rs/issues/180).

---

## Understanding Summary

- **What**: Close the gap between CI's clippy gate (`--workspace -- -D warnings`, default features, lib targets only) and the strict local check (`--all-targets --all-features -- -D warnings`) — a ~70-lint delta on `dev` HEAD.
- **Why**: Contributor onboarding (new contributors run the strict check and assume the codebase is broken); the strict gate catches real lints in `ffi-backend` / `dual-mode` / `simd-*` paths CI never sees today.
- **Who**: WebARKitLib-rs contributors; no end-user behavior change.
- **How**: Execute issue #180's 5-PR sequence as the spec — trivial wins → FFI shim annotations → `ar/marker.rs` field-reassign cleanup → SIMD `#[allow]` with rationale → tighten CI in `.github/workflows/ci.yml`.
- **Non-goals**:
- No `clippy.toml` file (deferred to M10+ if SIMD surface grows).
- No `FeatureMapConfig` / struct refactor (SIMD signatures are locked by runtime-dispatch contract).
- No deletion of `webarkit_cpp_*` FFI shims unless truly orphaned.
- No `CHANGELOG.md` edits (project rule).
- Not blocking v0.7.0 (already released).

---

## Decision Log

| # | Decision | Alternatives considered | Why this choice |
|---|---|---|---|
| 1 | Follow #180's 5-PR sequence verbatim | Refine ordering; merge PRs; re-baseline lint counts first | Issue is well-scoped; counts may drift but plan structure is sound |
| 2 | **(Updated during PR 2)** Tighten each extern block's cfg from `#[cfg(feature = "dual-mode")]` to `#[cfg(all(test, feature = "dual-mode"))]` — matches caller cfg, lint disappears because the extern only exists when callers do. Originally planned `#[allow(dead_code)]` + rationale. | Original plan (allow + rationale); wire up callers (scope creep); bulk delete (irreversible) | Evidence at PR 2 time: all 17 "dead" shims have callers, but the callers are gated `cfg(all(test, feature = "dual-mode"))` while the extern blocks were gated only on `cfg(feature = "dual-mode")`. The cfg mismatch was the real root cause; tightening fixes it honestly. Same effort per block (one line), zero behavior change. |
| 3 | PR 4: inline `#[allow(clippy::too_many_arguments)]` + rationale on `get_similarity_sse41` / `get_similarity_avx2` | `clippy.toml` threshold raise; bundle args into struct | SIMD signatures locked by runtime-dispatch contract; inline is scoped, threshold raise loses signal everywhere |
| 4 | Defer `clippy.toml` | Add now with `too-many-arguments-threshold = 9` | Only 2 sites today; revisit when SIMD surface grows in M10+ |
| 5 | Keep CI on `dtolnay/rust-toolchain@stable` after PR 5 | Pin to a specific rustc version; document known-good rustc | Accept the churn; v0.7.0 is shipped, future rustc lint additions fix in follow-up PRs |
| 6 | Branch off `dev`, one PR per branch | Stacked PRs; single mega-PR | Project convention (CLAUDE.md §4); keeps reviews focused |

---

## Assumptions

- **A1**: CI keeps `dtolnay/rust-toolchain@stable` after PR 5 tightens the gate. When a new rustc adds lints, fix in a follow-up PR. No rustc pin.
- **A2**: PR 4 stays its own PR (not folded into PR 1) for review clarity, even though it's only 2 `#[allow]` lines. Revisit at execution time if it feels artificial.
- **A3**: Branch naming follows `chore/clippy-180-pr<N>-<slug>` — branched off `dev`, one PR per branch, conventional commits per CLAUDE.md §6.
- **A4**: Re-baseline the lint inventory at the start of PR 1, not now. Counts may differ from #180's ~70 due to rustc drift since #180 was filed.

---

## Open Risks

- **R1** — *Rustc drift since #180*: lints may have been promoted/demoted between #180's filing and today. PR 1's scope could expand or contract. **Mitigation**: re-baseline before opening PR 1; update #180 with actual counts.
- **R2** — *FFI shim mislabeled*: a `webarkit_cpp_*` extern annotated as "kept for dual-mode parity" might actually be orphaned. **Mitigation**: run `cargo test --features dual-mode` and `cargo test --features ffi-backend` after PR 2; any annotated shim that has no caller under any feature combo gets deleted in the same PR.
- **R3** — *`ar/marker.rs` init-order dependency*: 44 mechanical `field-reassign-with-default` fixes touch many lines. Struct-init syntax could surface a subtle init-order dependency in `ARHandle` construction. **Mitigation**: rely on existing tests; if any fail, revert that specific site to the default+reassign pattern with an `#[allow]` + rationale.
- **R4** — *CI tightening fails after merge*: PR 5 flips the gate, then unrelated work introduces new lints under `--all-features` that the contributor didn't catch locally. **Mitigation**: PR 5 description includes a one-liner of the exact local command (`cargo clippy --all-targets --all-features -- -D warnings`); update CLAUDE.md §5 to match (already aligned) and add a §6 checklist note.

---

## Implementation Plan — 5 PRs

All PRs: branched off `dev`, opened against `dev`, conventional commits, pre-commit checklist (CLAUDE.md §5) green before push.

### PR 1 — Trivial wins (~10 lints)

**Branch**: `chore/clippy-180-pr1-trivial-wins`
**Scope**:
- Unused variables: `crates/core/src/ar/image_proc.rs:217` (`image_temp_u16`), `:218` (`image2`) — prefix `_` or remove.
- `items_after_test_module`: move items above `#[cfg(test)] mod tests` in `crates/core/src/ar/math.rs:831` and `ar/pattern.rs:1050`.
- `needless_deref`: `crates/core/src/kpm/freak/homography.rs:2645`.
- `needless_range_loop` (4 sites): convert to `iter().enumerate()` in `ar2/feature_map.rs`, `ar2/image_set.rs`, `ar/pattern.rs`.

**Verification**:
```bash
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings # the new lints here pass
cargo test --all-features
```

**Commit**: `fix(core): clean up trivial clippy lints under --all-targets --all-features (#180)`

---

### PR 2 — Dead FFI shim hygiene (~14 lints)

**Branch**: `chore/clippy-180-pr2-ffi-shims`
**Scope**: `crates/core/src/kpm/freak/{clustering,homography,math,hough,matcher}.rs` — for each dead `extern "C" { fn webarkit_cpp_*(...) }` block:

**Implemented (Decision #2 updated)**: change each block's cfg gate from `#[cfg(feature = "dual-mode")]` to `#[cfg(all(test, feature = "dual-mode"))]` so the extern only exists when its caller (also `cfg(all(test, feature = "dual-mode"))`) does. Add a one-line comment above each block referencing #180 for traceability. Verified all 17 callers still resolve and dual-mode tests still pass.

**Verification**: same triad as PR 1, plus `cargo test --features dual-mode --lib` and `cargo test --features ffi-backend` green.

**Commit**: `chore(kpm): document or remove dead webarkit_cpp_* FFI shims (#180)`

---

### PR 3 — `ar/marker.rs` field-reassign-with-default (44 lints)

**Branch**: `chore/clippy-180-pr3-ar-marker-init`
**Scope**: `crates/core/src/ar/marker.rs` — mechanical conversion of `let mut h = ARHandle::default(); h.field = x; h.other = y;` to struct-init syntax `let h = ARHandle { field: x, other: y, ..Default::default() };`.

**Process**:
- One commit per logical group (e.g. one per call site / constructor) to make review tractable.
- If any site fails tests after conversion, revert that specific site with `#[allow(clippy::field_reassign_with_default)]` + `// rationale: init order matters here because <reason>`.

**Verification**: same triad as PR 1, with extra attention to `cargo test --all-features` since `ARHandle` is core path.

**Commit**: `refactor(ar): convert ARHandle construction to struct-init syntax (#180)`

---

### PR 4 — SIMD `too_many_arguments` `#[allow]` (2 lints)

**Branch**: `chore/clippy-180-pr4-simd-too-many-args`
**Scope**: `crates/core/src/ar2/feature_map.rs:275` and `:387` — add to each:

```rust
#[allow(clippy::too_many_arguments)]
// rationale: SIMD variant of get_similarity; signature locked to match
// scalar fallback and sibling SIMD impl for runtime dispatch via
// is_x86_feature_detected!.
```

**Verification**: same triad as PR 1.

**Commit**: `chore(ar2): allow too_many_arguments on SIMD get_similarity variants (#180)`

---

### PR 4.5 — Examples/tests/benches cleanup (unplanned, discovered during PR 4)

**Branch**: `chore/clippy-180-pr4-5-examples-tests-benches`

**Surfaced**: PR 4 cleared the final two **library** lints. Cargo had been halting on lib errors before checking `--all-targets`, so 26 examples/tests/benches lints were hidden until the lib went strict-clean. PR 5 couldn't tighten CI safely until these were also cleared.

**Categories**:
- 26 `excessive_precision` in `tests/kpm_regression.rs` — handled via file-scoped `#![allow(clippy::excessive_precision)]` with rationale (C++ baseline fixtures, extra digits document source-traceability).
- 9 `field_reassign_with_default` in examples + benches — same struct-init conversion as PR 3.
- 7 `unnecessary_cast usize→usize` in `generate_patt.rs` — drop redundant `as usize`.
- 3 `or_fun_call inside expect` — `unwrap_or_else(|_| panic!(...))`.
- 3 `ptr_arg &Vec<u8>` → `&[u8]`.
- 3 `redundant_closure` (`|| String::new()` → `String::new`).
- 5 `needless_range_loop` — iterator forms.
- 5 singletons (`type_complexity` via `type RunReport = ...`, `redundant_pattern_matching` (`if let Ok(_)` → `.is_ok()`), `manual_range_contains`, `manual_is_multiple_of`, `let_unit_value`).

**Coverage workflow tangent**: PR 4.5 also fixed an unrelated bug in `.github/workflows/coverage.yml` — tarpaulin's `--exclude-files "tests/*"` glob was workspace-root-relative and never matched our nested `crates/core/tests/`. Fixed to `**/tests/*`, and added a project `codecov.yml` mirroring the exclusions so the patch gate doesn't fail on PRs that land entirely in uninstrumented paths.

**Commits**:
- `chore: clear strict clippy lints in examples/tests/benches (#180)`
- `chore(codecov): exclude examples/benches from coverage targets (#180)`
- `ci(coverage): fix tarpaulin tests/ glob and mirror in codecov (#180)`

---

### PR 5 — Tighten CI

**Branch**: `chore/clippy-180-pr5-ci-tighten`
**Pre-req**: PRs 1–4.5 merged; `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean on `dev`.

**Scope**: `.github/workflows/ci.yml`
- `kpm-build (ubuntu-latest)` job: add a new step `cargo clippy --workspace --all-targets --all-features -- -D warnings` (Linux-only). This job already has submodules + libclang, which `--all-features` requires (`ffi-backend` triggers C++ compilation in `build.rs`).
- `build-and-test` job: keep `cargo clippy --workspace -- -D warnings` (default features, no submodules). This job's checkout intentionally skips submodules to verify the pure-Rust path doesn't accidentally depend on them — adding `--all-features` here would conflict with that invariant.
- `pure-rust-build` job: keep `cargo clippy -p webarkitlib-rs -- -D warnings` (no-features path stays clean — a third invariant).

**Discovery**: the original plan had the strict gate going into `build-and-test`. First push of PR 5 failed because that job's checkout doesn't include git submodules, so `--all-features` enabled `ffi-backend` and `build.rs` panicked looking for the C++ sources. Moved to `kpm-build (ubuntu-latest)` where the prerequisites already exist. Lints are OS-agnostic, so Linux-only is sufficient.

**Verification**: open PR, watch CI go green. If a lint slips in between PR 4 merge and PR 5 (rustc drift, A1), fix in this PR.

**Commit**: `ci: tighten clippy gate to --all-targets --all-features (#180)`

---

## Closure

When PR 5 merges:
- Close issue #180.
- Note in the closing comment whether actual lint count matched #180's ~70 estimate (data point for future audits).
- Confirm CLAUDE.md §5 is still accurate (it already prescribes the strict command — no update needed).
Loading
Loading