From b6259f953f95a4f8108608f50c6dd3c810a5dd13 Mon Sep 17 00:00:00 2001 From: ritchie <4462072+repentsinner@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:32:15 -0700 Subject: [PATCH 1/2] docs(spec): address sections by slug instead of position MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SPEC.md predates the notation contract: headings carried numeric ordinals, none carried a §spec: slug, and status lines appended provenance the contract does not allow. Renames every ## heading to `Title §spec:slug`, drops the ordinals, and reduces the status lines to the three permitted values. The provenance they carried moves to a `Delivered in ...` line rather than being lost. No prose changes. 23 governance errors to 0. Nothing referenced the old numbering, so this is the cheapest moment to do it — a slug is a permanent address, and every later reference would have had to move. --- SPEC.md | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/SPEC.md b/SPEC.md index d0cf06a..baae2b0 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,13 +1,17 @@ # Spec: hidapi Dart bindings -## Problem +## Problem §spec:problem + +*Status: complete* No Dart package provides complete, cross-platform access to USB and Bluetooth HID devices. Developers resort to platform channels (Flutter only) or incomplete FFI wrappers that require prebuilt binaries or system-installed libraries. -## Scope +## Scope §spec:scope + +*Status: complete* This package wraps the upstream [hidapi](https://github.com/libusb/hidapi) C library. It shall expose every public function, struct, and enum from @@ -20,9 +24,11 @@ consuming packages. --- -## 1. API surface +## API surface §spec:api-surface -*Status: complete — PR #1, #4, 2026-02-22* +*Status: complete* + +Delivered in PR #1, #4, 2026-02-22. The package shall expose an idiomatic Dart API that maps 1:1 to every public function, struct, and enum in upstream `hidapi/hidapi.h`. @@ -43,9 +49,11 @@ Deviations from the C API: --- -## 2. Platform support +## Platform support §spec:platform-support + +*Status: complete* -*Status: complete — PR #1, 2026-02-22* +Delivered in PR #1, 2026-02-22. The package shall support every platform supported by upstream hidapi. The build hook shall select the correct backend source file, frameworks, @@ -53,9 +61,11 @@ and link libraries as defined by the upstream build system. --- -## 3. Native source acquisition +## Native source acquisition §spec:native-source-acquisition -*Status: complete — PR #2, 2026-02-22* +*Status: complete* + +Delivered in PR #2, 2026-02-22. Git submodules are not viable: `dart pub get` does not recursively init submodules, so consumers that depend on this package via pub.dev or a git @@ -74,9 +84,11 @@ Instead, the package acquires native source via a Dart 3.10 build hook --- -## 4. Testing +## Testing §spec:testing + +*Status: complete* -*Status: complete — PR #1, #7, 2026-02-22* +Delivered in PR #1, #7, 2026-02-22. HID devices require physical hardware. CI runners have none, so the test strategy splits into two tiers: @@ -89,9 +101,11 @@ strategy splits into two tiers: --- -## 5. CI/CD +## CI/CD §spec:ci-cd + +*Status: complete* -*Status: complete — PR #1, #5, #7, #8, #9, #11, 2026-02-22* +Delivered in PR #1, #5, #7, #8, #9, #11, 2026-02-22. This is a single-maintainer project. Releases should not require manual steps beyond merging a PR. pub.dev requires semver; conventional commits @@ -109,7 +123,7 @@ let release-please derive the correct version bump automatically. --- -## 6. macOS exclusive device access +## macOS exclusive device access §spec:macos-exclusive-device-access *Status: in progress* @@ -138,7 +152,7 @@ imports. --- -## 7. Publishing +## Publishing §spec:publishing *Status: not started* From 38de950b3184060fe23d33f53fa273269e5a0454 Mon Sep 17 00:00:00 2001 From: ritchie <4462072+repentsinner@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:33:28 -0700 Subject: [PATCH 2/2] ci: replace release-please with flywheel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Converges this repository on the release tooling used by the other projects maintained alongside it. Removed: release-please.yml, release-please-config.json, .release-please-manifest.json, auto-merge-release.yml, and ci.yml. Added: .flywheel.yml plus flywheel-pr.yml and flywheel-push.yml. The action is SHA-pinned, not tagged: it receives a GitHub App private key, and a floating tag would let its repository repoint that credential at new code without review. ci.yml becomes quality.yml with the job id `quality`, the context the ruleset registers. It stays a single job because it is a single setup — splitting analyze, format and test would pay for the Dart SDK and libudev three times for no extra signal. The Flywheel classify step guards each step rather than the job, so a release commit skips the work while the check still reports success; a job-level skip would report nothing and leave a pull request pending forever. governance-lint.yml is new, calling the notation contract at @notation--v0 with the `library` profile. It emits `governance / lint`, the second required context. release_files keeps pubspec.yaml current, which release-please owned under its `dart` release type. Tags change shape from hidapi-vX.Y.Z to vX.Y.Z; nothing consumes the old form, since this package is not auto-published. --- .flywheel.yml | 28 +++++++++++++++ .github/workflows/auto-merge-release.yml | 18 ---------- .github/workflows/ci.yml | 34 ------------------ .github/workflows/flywheel-pr.yml | 35 +++++++++++++++++++ .github/workflows/flywheel-push.yml | 27 +++++++++++++++ .github/workflows/governance-lint.yml | 16 +++++++++ .github/workflows/quality.yml | 44 ++++++++++++++++++++++++ .github/workflows/release-please.yml | 21 ----------- .release-please-manifest.json | 3 -- release-please-config.json | 10 ------ 10 files changed, 150 insertions(+), 86 deletions(-) create mode 100644 .flywheel.yml delete mode 100644 .github/workflows/auto-merge-release.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/flywheel-pr.yml create mode 100644 .github/workflows/flywheel-push.yml create mode 100644 .github/workflows/governance-lint.yml create mode 100644 .github/workflows/quality.yml delete mode 100644 .github/workflows/release-please.yml delete mode 100644 .release-please-manifest.json delete mode 100644 release-please-config.json diff --git a/.flywheel.yml b/.flywheel.yml new file mode 100644 index 0000000..5548a83 --- /dev/null +++ b/.flywheel.yml @@ -0,0 +1,28 @@ +# Flywheel release configuration. +# +# `release: production` cuts a release on every push to the managed branch. +# `auto_merge` lists the conventional-commit types that merge without review; +# every other type gates on a human. `!`-suffixed types are separate entries, +# so `fix` does not imply `fix!`. +# +# Every version-bumping type — feat, fix, perf, and any breaking variant — +# routes to human review, so a release is never cut unattended. Non-bumping +# types auto-merge once required checks pass. +# +# `chore` covers dependency bumps, so listing it means Dependabot updates can +# land unreviewed. Flywheel gates that separately: those PRs auto-merge only +# once the App private key is also registered in the Dependabot secret store. +flywheel: + streams: + - name: main-line + branches: + - name: main + release: production + auto_merge: [chore, docs, style, test, ci, build, refactor] + + release_files: + # release-please owned pubspec.yaml under its `dart` release type; + # flywheel keeps it current for the same reader — pub.dev. + - path: pubspec.yaml + pattern: '^version: .*' + replacement: 'version: ${version}' diff --git a/.github/workflows/auto-merge-release.yml b/.github/workflows/auto-merge-release.yml deleted file mode 100644 index 0d01cb4..0000000 --- a/.github/workflows/auto-merge-release.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Auto-merge release-please - -on: - pull_request_target: - types: [opened, synchronize, reopened, labeled] - -jobs: - auto-merge: - if: "contains(github.event.pull_request.labels.*.name, 'autorelease: pending')" - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - run: gh pr merge --auto --squash "$PR_URL" - env: - GH_TOKEN: ${{ secrets.RELEASE_PLEASE_PAT }} - PR_URL: ${{ github.event.pull_request.html_url }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 83b960e..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - lint: - uses: repentsinner/bug-free-happiness/.github/workflows/spec-lint.yml@v1 - - analyze: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1 - with: - sdk: '3.10.0' - - run: dart pub get - - run: sudo apt-get update && sudo apt-get install -y libudev-dev - - run: dart analyze --fatal-infos - - run: dart format --output=none --set-exit-if-changed . - - run: dart test - - test-macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1 - with: - sdk: '3.10.0' - - run: dart pub get - - run: dart test diff --git a/.github/workflows/flywheel-pr.yml b/.github/workflows/flywheel-pr.yml new file mode 100644 index 0000000..49befa7 --- /dev/null +++ b/.github/workflows/flywheel-pr.yml @@ -0,0 +1,35 @@ +name: Flywheel — PR + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review, edited] + +concurrency: + group: flywheel-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + conduct: + # Only run on 'edited' events when a human triggered the edit. Bot-driven + # edits (the Flywheel App rewriting titles/bodies, push-flow upserting + # promotion PR bodies) would otherwise flap conduct <-> push-flow writes on + # every promotion-source push. + if: | + github.event.pull_request.draft == false && + (github.event.action != 'edited' || github.event.sender.type == 'User') + # The degraded empty-key path (Dependabot PRs, where the App key is absent) + # posts the conventional-commit check with the built-in token, so the token + # needs checks:write. + permissions: + contents: read + checks: write + runs-on: ubuntu-latest + steps: + # SHA-pinned, not @v2: this action receives a GitHub App private key, + # and a floating tag lets its repository repoint that credential at new + # code without review. Bump the pin deliberately. + - uses: point-source/flywheel@59758d1940ebe054ce9fdd4b7a7dc79a0cf235e0 # v2.1.0 + with: + event: pull_request + app-id: ${{ vars.FLYWHEEL_GH_APP_ID }} + app-private-key: ${{ secrets.FLYWHEEL_GH_APP_PRIVATE_KEY }} diff --git a/.github/workflows/flywheel-push.yml b/.github/workflows/flywheel-push.yml new file mode 100644 index 0000000..e4f1c86 --- /dev/null +++ b/.github/workflows/flywheel-push.yml @@ -0,0 +1,27 @@ +name: Flywheel — Push + +on: + push: + branches: ["**"] + +concurrency: + group: flywheel-push-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + release: + # Flywheel mints its own App installation token for every write, so the + # built-in token needs read only. Without this block the job inherits the + # repository default, which on older repos is write on everything. + permissions: + contents: read + runs-on: ubuntu-latest + steps: + # SHA-pinned, not @v2: this action receives a GitHub App private key, + # and a floating tag lets its repository repoint that credential at new + # code without review. Bump the pin deliberately. + - uses: point-source/flywheel@59758d1940ebe054ce9fdd4b7a7dc79a0cf235e0 # v2.1.0 + with: + event: push + app-id: ${{ vars.FLYWHEEL_GH_APP_ID }} + app-private-key: ${{ secrets.FLYWHEEL_GH_APP_PRIVATE_KEY }} diff --git a/.github/workflows/governance-lint.yml b/.github/workflows/governance-lint.yml new file mode 100644 index 0000000..4198aff --- /dev/null +++ b/.github/workflows/governance-lint.yml @@ -0,0 +1,16 @@ +name: Governance Lint + +on: + push: + branches: [main] + pull_request: + merge_group: # required for merge-queue compatibility + +jobs: + # Job id `governance` makes the check-run name `governance / lint`, the + # context registered as a required status check. Renaming it means + # re-pointing the ruleset entry. + governance: + uses: repentsinner/symphonize/.github/workflows/governance-lint.yml@notation--v0 + with: + readme-type: "library" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..b402e1d --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,44 @@ +# quality — everything this repository verifies, behind one required check. +# +# GitHub names a check run after the JOB, so the job id `quality` is the +# context registered in the branch ruleset. The work is one job because it +# is one setup: splitting analyze, format and test would pay for the Dart +# SDK and libudev three times over for no extra signal. +name: quality + +on: + pull_request: + merge_group: # required for merge-queue compatibility + +jobs: + quality: + runs-on: ubuntu-latest + steps: + # Classify the triggering commit so Flywheel's own commits + # (chore(release):, back-merge) skip the suite. A step-level `if:` + # that evaluates false still reports `success`, so the required check + # stays satisfied rather than hanging — which is why this is a single + # job with guarded steps and not a job-level `if:`. + - id: classify + uses: point-source/flywheel/classify@59758d1940ebe054ce9fdd4b7a7dc79a0cf235e0 # v2.1.0 + - uses: actions/checkout@v4 + if: steps.classify.outputs.derived_release_commit != 'true' + - uses: dart-lang/setup-dart@v1 + if: steps.classify.outputs.derived_release_commit != 'true' + with: + sdk: '3.10.0' + - name: Install libudev + if: steps.classify.outputs.derived_release_commit != 'true' + run: sudo apt-get update && sudo apt-get install -y libudev-dev + - name: Resolve dependencies + if: steps.classify.outputs.derived_release_commit != 'true' + run: dart pub get + - name: Analyze + if: steps.classify.outputs.derived_release_commit != 'true' + run: dart analyze --fatal-infos + - name: Format + if: steps.classify.outputs.derived_release_commit != 'true' + run: dart format --output=none --set-exit-if-changed . + - name: Test + if: steps.classify.outputs.derived_release_commit != 'true' + run: dart test diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml deleted file mode 100644 index a596251..0000000 --- a/.github/workflows/release-please.yml +++ /dev/null @@ -1,21 +0,0 @@ -on: - push: - branches: - - main - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -name: release-please - -jobs: - release-please: - runs-on: ubuntu-latest - steps: - - uses: googleapis/release-please-action@v4 - with: - config-file: release-please-config.json - manifest-file: .release-please-manifest.json - token: ${{ secrets.RELEASE_PLEASE_PAT }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json deleted file mode 100644 index 18e45d5..0000000 --- a/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "0.1.5" -} diff --git a/release-please-config.json b/release-please-config.json deleted file mode 100644 index dac44f7..0000000 --- a/release-please-config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "packages": { - ".": { - "release-type": "dart", - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": true - } - }, - "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" -}