@@ -84,7 +84,7 @@ release.
8484 output written by the child's interrupt handler is drained into ` stop() ` 's
8585 result (the no-truncated-capture contract), and a child that ignores the
8686 interrupt is still reaped and reported unclean. The Windows CI leg proves
87- the Windows path on the next push. (plan: # 4 , Phase 3a)
87+ the Windows path on the next push.
8888- OS dead-man for handle-mode children (closes the long-standing
8989 ` TODO(os-deadman) ` in ` core.py ` ): the atexit backstop never runs on
9090 SIGKILL / TerminateProcess, so a hard-killed parent could leave a
@@ -99,13 +99,32 @@ release.
9999 ` backstop_rx=False ` . ` tests/test_deadman.py ` proves it with a real
100100 SIGKILL of an intermediate parent on POSIX -- the child dies AND its
101101 interrupt handler ran -- plus the opt-out gate; the Windows Job Object
102- leg proves on the next CI push. (plan:#4 , Phase 3b)
103- - SigMF spec compliance is now tested, not trusted (plan:#8 ): the official
102+ leg proves on the next CI push.
103+ - No-board detection reported nothing useful on Linux: ` hackrf_info ` there
104+ prints its version lines and "No HackRF boards found." to STDOUT and
105+ exits 1 with an empty stderr, so ` detect() ` raised on the exit code and
106+ discarded everything -- ` problem ` came back blank and ` tools_version `
107+ ` None ` . ` detect() ` now parses whatever the tool printed regardless of
108+ exit code and reports the tool's own words, and tool errors in general
109+ fall back to the last stdout line when stderr is empty. Found and
110+ verified against the real 2023.01.1 Linux binaries (no board attached);
111+ regression tests pin both behaviors.
112+ - Documentation refresh across both READMEs and CONTRIBUTING: the root
113+ README's claim that an OS-level dead-man was "not yet implemented" (it now
114+ is, and is tested), four dangling links to a ` project_summary.md ` that does
115+ not exist (scope statements are now inline), the CLI reference extended
116+ with ` monitor ` , ` scan ` , ` sweep -o/-B/-I ` , and ` tx --cw ` , the
117+ ` monitor_frequencies ` notes now state the covering-bin semantics, the
118+ package README's features/platform/transmit sections updated for the
119+ lifecycle-safety work, CONTRIBUTING's "mypy (advisory for now)" corrected
120+ to blocking, and the PR checklist extended (changelog entry, docstring
121+ gate, coverage gate).
122+ - SigMF spec compliance is now tested, not trusted: the official
104123 ` sigmf ` package joined the dev dependency group, and the sidecar writer's
105124 output is validated with it -- including that the ` hackrf ` extension is
106125 DECLARED, not just used (a strict-validator rejection that regressed once
107126 pre-1.0). GNU Radio / IQEngine interop is a tested property.
108- - Docstrings across the entire public API (plan: # 1 ) : every public method on
127+ - Docstrings across the entire public API: every public method on
109128 ` HackRF ` and ` PersistentReceiver ` , both classes, and the module-level
110129 functions -- 60 docstrings where ` help() ` previously returned nothing.
111130 The documentation used to live only in ` # ` comments, invisible to
@@ -115,7 +134,7 @@ release.
115134 enforced parent-side, so a copied ` --print-cmd ` argv carries NO time
116135 bound. ` tests/test_docstrings.py ` gates the whole surface so it cannot
117136 drift back to undocumented.
118- - CLI catch- up with the library (plan: # 5 ) , each command with tests:
137+ - CLI caught up with the library, each command with tests:
119138 ` hrf tx --cw ` (a bounded CW test tone; requires ` -d/--duration ` because an
120139 unbounded carrier is exactly the orphan-transmitter risk the library
121140 exists to prevent; ` --cw-amplitude ` defaults below full scale),
@@ -128,13 +147,13 @@ release.
128147 threads -- per-instance mutable state (` last_params ` , persisted mode,
129148 logging wiring) and per-child drain threads. One instance per thread;
130149 instances are cheap. In the README and on the class.
131- - Coverage policy recorded in CONTRIBUTING (plan: # 11 ) : CI gates at
150+ - Coverage policy recorded in CONTRIBUTING: CI gates at
132151 ` --cov-fail-under=82 ` , deliberately under the measured 85% because that
133152 figure counts Windows-only and hardware-only code as missed on Linux legs;
134153 to be revisited upward after the docstring pass.
135154- ` hackrfpy.__version__ ` , resolved from installed package metadata
136155 (` importlib.metadata ` ), with a ` 0.0.0+unknown ` fallback for uninstalled
137- checkouts. (plan: # 7 )
156+ checkouts.
138157- ` examples/fm_demod_to_wav.py ` : the missing last mile -- demodulate a
139158 captured broadcast-FM IQ file to an audible mono 16-bit WAV using only
140159 numpy and the stdlib. Channelize (staged windowed-sinc decimation to
@@ -148,6 +167,29 @@ release.
148167 modulation, and spectral flatness 0.09 (structured content, not noise).
149168
150169### Changed
170+ - CI platform policy: the test matrix now contains hardware-verified
171+ platforms only -- currently Windows (primary) and macOS -- and Linux was
172+ removed until it is verified against a real board, at which point it
173+ returns under the same standards. The coverage gate rose from 82% to an
174+ 85% minimum on every remaining leg, and the codecov upload moved from the
175+ removed Linux leg to the Windows 3.12 leg. Known consequence, recorded in
176+ the workflow comment: the Linux ` pdeathsig ` dead-man path only executes on
177+ a Linux runner, so it is regression-untested until Linux re-enters the
178+ matrix.
179+ - CONTRIBUTING policy rewrite to match: 85% minimum coverage, the
180+ hardware-verified-platforms rule, and a new requirement that any change
181+ under ` src/hackrfpy/ ` include evidence of a full-suite run (hardware tests
182+ passing) on Windows with a real board -- CI cannot attach hardware, so
183+ that gate is enforced by review. The ` needs_tools ` test category (real
184+ binaries on PATH, no board needed) is now documented, which is why
185+ passed/skipped counts differ between machines.
186+ - Second documentation pass: the root README's local-install line pinned a
187+ two-versions-stale wheel name (now a wildcard), its testing notes still
188+ described the Windows CTRL_BREAK path as untested (it now points at the
189+ tests that prove it), and its repo tree was missing three examples; the
190+ package README's platform section now records the real-binaries Linux
191+ verification and the Windows-EXE-on-Linux non-goal, and gained a CLI
192+ quick-start.
151193- Library diagnostics now go through the standard ` logging ` module instead of
152194 ` print() ` . Records are emitted on the ` hackrfpy ` logger: warnings at
153195 ` WARNING ` , verbose progress messages at ` INFO ` . A consumer can now route,
@@ -240,30 +282,32 @@ release.
240282 ` 433:434 ` and never covered 434.0-434.1 MHz even while warning about the
241283 snap. Edges now snap OUTWARD (floor the low edge, ceil the high edge) so
242284 the swept range always contains the requested band; the warning text says
243- so. (plan: # 2 )
285+ so.
244286- ` monitor_frequencies() ` reported the MEAN dB of the whole covering sweep
245287 segment as the power "at" a frequency, diluting a narrowband carrier
246288 toward the noise floor (a -20 dB carrier in one bin of a 10-bin segment
247289 read as -74). It now reads the bin covering the frequency (max of that
248290 bin +/-1 for tuning slop). Behavior change for ` examples/channel_monitor.py `
249291 and any monitor consumer: readings of narrowband signals rise to their
250- true level. (plan: # 3 )
292+ true level.
251293- Handle-mode output was not visible to the parent until process exit unless
252294 the child flooded: the drain thread read pipes with ` BufferedReader.read(N) ` ,
253295 which blocks until N bytes (64 KB) accumulate, so small periodic writes --
254296 e.g. hackrf_transfer's ~ 60-byte-per-second stats lines -- sat invisible for
255297 what would be ~ 18 minutes of real capture. Now ` read1() ` : bytes appear in
256298 the drain as soon as the child writes them. Found by the new clean-interrupt
257299 tests; also cut the test suite's wall time roughly in half by removing the
258- same latency from every stubbed lifecycle test. (Phase 3a finding)
300+ same latency from every stubbed lifecycle test. (Found by the new
301+ clean-interrupt tests.)
259302- ` _Process.stop() ` 's escalation fired ` terminate() ` and returned without
260303 reaping: a child that ignored the interrupt could outlive ` stop() ` , and
261304 ` result() ` reported ` returncode None ` . The escalation is now a bounded,
262305 reaped ladder (interrupt -> terminate -> kill), so ` stop() ` always returns
263- with the child dead and a real exit status. (Phase 3a finding)
306+ with the child dead and a real exit status. (Found by the new
307+ clean-interrupt tests.)
264308- ` from_device() ` guarded its parsed-info invariant with a bare ` assert ` ,
265309 which ` python -O ` strips, letting raw text flow onward; it now raises
266- ` HackRFDeviceError ` . (plan: # 9 )
310+ ` HackRFDeviceError ` .
267311
268312## [ 1.0.0] - 2026-06-16
269313
0 commit comments