diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..a4a29b1 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,4 @@ +> **Read `AGENTS.md` first.** This file is kept minimal on purpose; +> `AGENTS.md` (at the repo root) is the canonical source of project +> conventions, build commands, and agent guidance for any AI coding +> assistant working on this repository. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f59a272 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,321 @@ +# AGENTS.md — bq25723 + +Authoritative guidance for AI coding assistants (and humans) working on this +repository. Keep this file in sync with reality: when CI, layout, or +conventions change, update this file in the same commit. + +## What this crate is + +`bq25723` is a `#[no_std]`, platform‑agnostic Rust driver for the Texas +Instruments [BQ25723] buck‑boost battery charge controller (2–5 cell +batteries). The low‑level register interface is generated from +[`bq25723.yaml`](bq25723.yaml) by [`device-driver-cli`][device-driver-cli] +and committed as [`src/device.rs`](src/device.rs). A thin async wrapper in +[`src/lib.rs`](src/lib.rs) implements the +[`embedded-batteries-async`][embedded-batteries] `Charger` trait on top of +the `embedded-hal-async` I²C trait. + +Key facts (verify before editing): + +- Edition: `2024` (`Cargo.toml:12`). +- MSRV: **Rust 1.88** (`README.md:10`, `.github/workflows/check.yml:145`). +- I²C address: `0x6B` (`src/lib.rs:31`, constant `BQ_ADDR`). +- Largest register width: 2 bytes (`src/lib.rs:32`). +- Async only: the driver uses `embedded-hal-async` and exposes `*_async` + register accessors generated by `device-driver`. +- Optional `defmt-03` feature pulls in `defmt` and the matching features in + `device-driver` and `embedded-batteries-async` (`Cargo.toml:37-41`). +- License: MIT (`LICENSE`, `Cargo.toml:5`). + +[BQ25723]: https://www.ti.com/lit/ds/symlink/bq25723.pdf +[device-driver-cli]: https://crates.io/crates/device-driver-cli +[embedded-batteries]: https://github.com/OpenDevicePartnership/embedded-batteries + +## Repository layout + +``` +. +├── AGENTS.md # this file +├── Cargo.toml # package + lints + features +├── Cargo.lock # committed; CI uses --locked +├── README.md # short crate description, MSRV, license +├── CONTRIBUTING.md # commit / PR etiquette +├── CODE_OF_CONDUCT.md +├── CODEOWNERS +├── SECURITY.md +├── LICENSE # MIT +├── rust-toolchain.toml # pins rustfmt + clippy components only +├── rustfmt.toml # max_width=120 + nightly-only options +├── deny.toml # cargo-deny config (licenses/advisories/bans) +├── bq25723.yaml # device-driver manifest (single source of truth) +├── supply-chain/ # cargo-vet data +├── src/ +│ ├── lib.rs # public API, interface, Bq25723, tests +│ └── device.rs # GENERATED from bq25723.yaml (do not hand-edit) +├── .github/ +│ ├── workflows/ +│ │ ├── check.yml # fmt, doc, hack-clippy, deny, test, msrv, machete +│ │ ├── nostd.yml # cargo check on thumbv8m.main-none-eabihf +│ │ ├── device-driver.yml # verifies device.rs matches regenerated output +│ │ ├── cargo-vet.yml +│ │ └── cargo-vet-pr-comment.yml +│ └── (no copilot-instructions.md as of writing) +└── .vscode/settings.json # rust-analyzer cfg (thumbv8m target, clippy on save) +``` + +There is no `examples/`, no `tests/` directory, and no `build.rs`. All unit +tests live in `src/lib.rs` under `mod tests`. + +## Building and testing + +The toolchain file (`rust-toolchain.toml`) does **not** pin a Rust version; +it only requests `rustfmt` + `clippy`. Use the host‑installed stable +toolchain, except where CI uses nightly (`doc`) or the MSRV (`check`). + +All commands below are run from the repository root and are mirrored from +`.github/workflows/*.yml`. They are verified on Windows with stable Rust. + +| Purpose | Command | CI job (`.github/workflows/check.yml`) | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------| +| Format check | `cargo fmt --check` | `fmt` | +| Build (host, locked) | `cargo check --locked` | `msrv` (with `+1.88`) | +| Build (no‑std target) | `cargo check --target thumbv8m.main-none-eabihf --locked` | `nostd.yml` | +| Tests across all feature combos | `cargo hack --feature-powerset test --locked` | `test` | +| Quick tests (host only) | `cargo test --locked` | (subset of `test`) | +| Clippy across feature combos & targets | `cargo hack --feature-powerset --target clippy --locked -- -Dwarnings -D clippy::suspicious -D clippy::correctness -D clippy::perf -D clippy::style` | `hack-clippy` (targets: `x86_64-unknown-linux-gnu`, `thumbv8m.main-none-eabihf`) | +| Clippy on tests | `cargo hack --feature-powerset clippy --tests --locked -- -Dwarnings -D clippy::suspicious -D clippy::correctness -D clippy::perf -D clippy::style` | `test` | +| Docs (nightly recommended; stable works) | `RUSTDOCFLAGS=--cfg docsrs cargo doc --no-deps --all-features` | `doc` (nightly) | +| Dependency hygiene | `cargo deny --all-features --locked check` | `deny` | +| Unused dependencies | `cargo machete` | `machete` | +| Regenerate `src/device.rs` from manifest | `device-driver-cli --manifest bq25723.yaml --device-name Device -o src/device.rs && rustfmt --edition 2024 src/device.rs` | `device-driver.yml` (compares against committed) | + +Tooling not in `cargo` itself: + +- `cargo install cargo-hack` — required for feature‑powerset commands. +- `cargo install cargo-deny` — required for the `deny` job. +- `cargo install cargo-machete` — required for the `machete` job. +- `cargo install device-driver-cli` — required only when regenerating + `src/device.rs`. + +Notes: + +- `rustfmt.toml` enables nightly‑only options (`group_imports`, + `imports_granularity`); on stable, `cargo fmt --check` prints warnings + about them but still exits 0. Do not remove these options — running + `rustfmt` under nightly enforces them. +- CI always passes `--locked`; do not commit changes that require + regenerating `Cargo.lock` without intending to update dependencies. +- The `device-driver.yml` workflow regenerates `src/device.rs` into a + temporary file and fails if it differs from the committed file. After + editing `bq25723.yaml`, regenerate and commit `src/device.rs` in the + same change. + +## Code conventions + +Enforced by `Cargo.toml` lints and `rustfmt.toml`: + +- `unsafe_code = "deny"` and `missing_docs = "deny"` at crate level + (`Cargo.toml:24-26`). The single allowed pocket of `unsafe`/missing‑docs + is the generated `mod device`, which is gated with + `#[allow(unsafe_code)]` and `#![allow(missing_docs)]` + (`src/lib.rs:13`, `src/lib.rs:17-20`). Do not add `unsafe` elsewhere and + do not add `#![allow(missing_docs)]` to new modules. +- Clippy: `correctness`, `suspicious`, `perf`, `style` are `forbid`; + `pedantic` is `deny` (`Cargo.toml:29-34`). `forbid` cannot be overridden + with `#[allow(...)]` — code must comply. +- Formatting: `max_width = 120`, `imports_granularity = "Module"`, + `group_imports = "StdExternalCrate"` (`rustfmt.toml`). Run `cargo fmt` + before committing; ideally on nightly to apply the import rules. +- Edition `2024` everywhere, including when regenerating `src/device.rs` + (`rustfmt --edition 2024`; see `.github/workflows/device-driver.yml:33`). +- Error type: wrap underlying I²C errors in `BQ25723Error::Bus(_)` + (`src/lib.rs:27-29`). Map the kind via `charger::Error` to + `charger::ErrorKind::CommError` (`src/lib.rs:79-85`). +- `defmt` support is feature‑gated: derive `defmt::Format` behind + `#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]` + (see `src/lib.rs:24-29`). Never add an unconditional `defmt` dependency. +- Unit conversions use the helpers `ma_to_reg_value`, `reg_value_to_ma`, + `mv_to_reg_value`, `reg_value_to_mv` on `Bq25723` + (`src/lib.rs:97-115`). Use these rather than open‑coding the scaling + factors (64 mA/LSB for current, 8 mV/LSB for voltage). + +## Driver / HAL specifics + +- The I²C protocol is implemented in + `impl AsyncRegisterInterface for DeviceInterface` + (`src/lib.rs:40-77`). Writes build a `[addr, byte0, byte1?]` buffer of + the exact register width — never write past `data.len()`. Reads use + `i2c.write_read`. Both map errors with `BQ25723Error::Bus`. +- The `Bq25723` wrapper exposes the generated `device` field publicly + (`src/lib.rs:87-89`) so callers can reach register accessors directly, + in addition to the `Charger` trait implementation + (`src/lib.rs:121-143`). +- The `Charger` trait methods write a setpoint and then read it back, + returning the value actually programmed into the register (see + `charging_current`/`charging_voltage`). Preserve this contract — it is + what tests assert against in `tests::charging_current_trait_test` + (`src/lib.rs:188-206`). +- `src/device.rs` is **generated**. To change registers, fields, or + conversions, edit `bq25723.yaml` and regenerate (see Building section). + CI in `.github/workflows/device-driver.yml` will fail any PR where the + committed `src/device.rs` does not match the regenerated output. +- The crate targets bare‑metal `thumbv8m.main-none-eabihf` in CI. New code + must remain `no_std`‑compatible: only the `tests` module may use `std` + (the `#![cfg_attr(not(test), no_std)]` attribute, `src/lib.rs:12`). +- Tests use `embedded-hal-mock`'s `eh1::i2c::Mock` and the `tokio` + `current_thread` runtime via `#[tokio::test]` (`Cargo.toml:20-22`, + `src/lib.rs:145-207`). Drive every mock to `done()` to ensure all + expected transactions occurred. + +## Commit and PR conventions + +From `CONTRIBUTING.md` and observed history +(`git log --pretty=%s`: `Bump device-driver to 1.0.9 (#10)`, +`Sync CI workflows with embedded-rust-template (#8)`, +`Pregenerate manifest file (#7)`, `Update dependencies and CODEOWNERS (#6)`, +`add initial driver commit`): + +- Subjects are short, imperative, capitalized, no trailing period; PR + numbers are appended in parentheses on merge. +- Squash‑merge is disabled. Maintain a clean, bisectable history: every + commit must build without warnings, and typo / formatting fix‑ups must + be squashed into the commit they belong to (`CONTRIBUTING.md:26-31`). +- Open PRs as **draft** first; only mark ready for review once all CI + checks are green on the draft (`CONTRIBUTING.md:22-24`). +- License of contributions: MIT, matching the repository + (`CONTRIBUTING.md:6-10`). Flag non‑MIT contributions explicitly in the + PR description. +- When reporting regressions, `git bisect` and include the first bad + commit (`CONTRIBUTING.md:33-35`). + +## What not to do + +- Do not hand‑edit `src/device.rs`. Edit `bq25723.yaml` and regenerate. +- Do not add `unsafe` code outside the generated module; the crate denies + `unsafe_code` globally. +- Do not add `#![allow(missing_docs)]` to new modules; document all public + items. +- Do not introduce blocking I²C or `std`‑only dependencies in the library + crate; everything must build for `thumbv8m.main-none-eabihf`. +- Do not pull in `defmt` unconditionally — gate it behind the `defmt-03` + feature exactly as existing items do. +- Do not loosen the clippy lint configuration in `Cargo.toml`. +- Do not force‑push to shared branches; do not enable squash merging on PRs. +- Do not commit changes that require `Cargo.lock` updates unless the + dependency bump is the intent of the change. + +## How to find more context + +- Datasheet: — register + semantics and electrical behavior. +- `device-driver` crate docs: + — explains the macros and the + `AsyncRegisterInterface` trait implemented in `src/lib.rs`. +- `embedded-batteries-async`: + — defines + the `Charger` trait this crate implements. +- `embedded-hal-async`: + — defines the async I²C trait the + driver consumes. +- CI sources of truth: `.github/workflows/check.yml`, `nostd.yml`, + `device-driver.yml`. When in doubt about the “official” command to run, + read the workflow. + +## Flags (state of this repo at AGENTS.md creation time) + +- No `copilot-instructions.md` exists anywhere in the tree; this AGENTS.md + is the first authoritative agent‑guidance file. A pointer file is being + added under `.github/copilot-instructions.md` referring back to here. +- No existing `AGENTS.md` was overwritten. +- `rustfmt.toml` references nightly‑only options; CI runs `cargo fmt + --check` on stable, which warns about (but does not fail on) those + options. + +## Model selection & cost discipline + +Premium models (Opus, GPT-5 family, "high"/"xhigh" reasoning variants) +cost an order of magnitude more than standard models (Sonnet, Haiku, +mini). Most steps in a typical task do not need premium reasoning, +and over-using premium models wastes credits without improving +outcomes. The rules below apply to *all* model selection: your own +session, sub-agents launched via the `task` tool, and parallel work +launched via `/fleet`. + +### Default posture + +- **Default to the cheapest model that can do the job.** Reach for a + premium model only when one of the escalation triggers below is hit. +- **Plan with premium, execute with cheap.** Spend at most one or two + premium turns on design / planning, then downshift to a cheaper + model for mechanical execution of the plan. +- **Never bump the model "just in case."** If you cannot articulate + *why* a cheaper model would fail, use the cheaper model. + +### Escalation triggers (use a premium model) + +Reach for a premium model when *any* of these are true: + +- Cross-module refactor, architectural design, or API design from + scratch. +- Subtle correctness reasoning: concurrency, lifetimes, `unsafe`, + FFI ABI, cryptography, safety-critical control paths. +- Debugging a failure that survived one prior cheap-model attempt. +- Reviewing code on a safety-, security-, or money-critical path. +- The diff cannot be predicted in advance — i.e. there is genuine + creative or design work to do, not just typing. + +### De-escalation triggers (use a cheap model) + +Use the cheapest available model when *any* of these are true: + +- Searching, reading, summarising files or docs. +- Single-file mechanical edits: rename, format, lint fix, dependency + bump, boilerplate, scaffolding from a known template. +- Generating tests for code that already works. +- Running builds, tests, linters, or other commands where the model + only needs to report success/failure. +- Routine commits, PR descriptions, changelog entries. +- The diff is essentially predictable before generation. + +### Sub-agent routing (the `task` tool) + +When delegating with the `task` tool, set `model:` explicitly. Do not +let sub-agents inherit a premium default for cheap work. + +| Sub-agent type | Default model | Override to | +|-------------------|---------------------------|-------------------------------------------------| +| `explore` | cheap | keep cheap (`claude-haiku-4.5` or `gpt-5-mini`) | +| `task` (run cmd) | cheap | keep cheap | +| `research` | cheap for breadth | premium only for the final synthesis | +| `general-purpose` | match task | cheap for mechanical work; premium for design | +| `rubber-duck` | premium | keep premium — this is where reasoning pays off | +| `code-review` | premium on critical paths | cheap on cosmetic / mechanical diffs | + +### `/fleet` (parallel sub-agents) rules + +- Fleet mode multiplies cost by the fleet width. Apply the rules + above *per worker*, not in aggregate. +- Split a fleet job along complexity lines: route the cheap, + parallelisable workers (file edits, test runs, doc updates) to a + cheap model; reserve premium models for the small number of + workers that need real reasoning. +- If every worker in a fleet would need a premium model, the work is + probably not a good fit for fleet mode — reconsider the + decomposition before paying N× premium. + +### Session hygiene + +- Keep sessions short and focused. Long premium sessions are the + single largest source of waste because every turn re-processes the + full history. +- Use `/compact` when the conversation grows long, and `/new` for + unrelated work. +- Prefer `/ask` for one-off side questions so they don't extend the + main session. + +### When in doubt + +Ask: *"If a cheaper model produced the wrong answer here, would I +catch it in seconds (compiler, tests, my own review) or in +weeks (production incident)?"* If the former, use the cheap model +and let the feedback loop do its job.