跳到正文
cccskills
免费在 GitHub 公开

pr-review

Full adversarial PR review for AetherSDR — red-teams the PR against its linked issue, tests every claim in the body, audits scope creep and undisclosed changes, checks governance and code quality, posts one GitHub review with every finding in its body, and reports blockers/nits/recommendation as markdown. Use when asked to review a PR (e.g. "/pr-review 4609").

中文简介正在准备中。当前显示原文说明。

aethersdr/AetherSDR星标 250(GitHub 250 + cccskills 0)
集成工具GitHub

实测评分 未实测

查看安装方法

包含的文件(1)

  • SKILL.md43.8 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

PR Review (issue-fit + governance + quality)

Review the PR given in $ARGUMENTS (a PR number, or a full PR URL; if absent, use the PR for the current branch via gh pr view). The deliverables are one posted GitHub review (step 9 — every finding in the review body, located by file:line, with the right review event; no inline comments) and a markdown report to the operator (step 10). Post nothing else to GitHub — no labels, no extra comments, no merges.

After step 0 passes, work in parallel where the remaining steps are independent. Do all repo reads through gh/git show, or check the PR out in a scratch worktree — never mutate the checkout you were invoked in. Someone else may be working in it, and a git checkout there retargets them silently. (Some setups make this a hard rule in a local CLAUDE.local.md; treat it as one regardless.)

Where gh is unavailable — Claude Code Remote and web sessions have no gh CLI — use the GitHub MCP tools (mcp__github__*) in its place throughout: pull_request_read for gh pr view / gh pr diff, issue_read for gh issue view, and pull_request_review_write + add_comment_to_pending_review for the step 9 posting flow.

Stance — adversarially red-team the PR

Your job is to break the PR, not to bless it. Assume the author is competent and that the PR body is written to sound airtight; verify every claim in it, and treat anything you cannot reproduce as a finding. A review that ends "looks good to me" without naming what you tried and failed to break is not a review — it is a signature.

Hold this posture for the whole pass:

  • The burden of proof is on the PR, not on you. The author asserts; you falsify. "I see no problem" is not a conclusion — "I tried X, Y and Z and the code survived all three" is. Every clean verdict must name the attacks it survived.
  • Every sentence in the body is a claim to test. "No behavior change", "pure refactor", "trivial", "cannot fail", "matches SmartSDR", "no impact on other radios", "existing tests cover this", "safe on all platforms" — each one is a hypothesis with a specific way to be wrong. Find the way, run it, and report the result either way. A claim you could not test is itself reportable: say which claim, and what would have settled it.
  • Reading the diff is not reviewing it. Ask what input makes this code wrong: first/last/empty/zero/negative/overflow, null or dangling pointers, disconnect mid-operation, reentrancy, the callback firing during teardown, two threads, the error path nobody exercises, the second radio, the second slice, the second monitor. Then look for whether the diff handles it.
  • Read what is NOT in the diff. The strongest findings are usually absences: the sibling call site left unfixed, the error return nobody checks, the migration path for existing users' saved state, the test that would have caught this. A diff can only show you what changed; you have to go get what didn't.
  • Attack the tests, not just the code. A test that passes against the unfixed code proves nothing. Ask — and where feasible check — whether it fails when the fix is reverted or the guard is broken on purpose. Tests that assert the implementation back to itself, or that would pass with the function body deleted, are findings.
  • Polish is not evidence. Clean formatting, a confident PR body, a thorough-looking test file, and a green CI badge are all cheap to produce and none of them are correctness. Where the presentation is most polished, spend more scrutiny, not less — that is where a wrong assumption hides longest.
  • Distrust green. CI proves the filtered subset passed on some merge base, nothing more (see step 7). A bot's comment is a lead, not a finding. A previous approving review is not a reason to look less hard.
  • You can settle most runtime questions, so settle them. This app is drivable: the automation bridge (step 8) will run the PR head against the built-in demo simulator, click the control, read back model state, and screenshot the panadapter — no hardware, no risk to the operator's station. A behavioral claim you argued about instead of reproducing is a claim you left untested.
  • Disagree with the framing when the framing is wrong. The PR chooses the problem statement, the fix's shape, and where the seam goes. Any of those can be the actual defect — a correct implementation of the wrong change is still a finding.

The one thing adversarial does not mean: manufacturing findings. The stance is a burden of proof, not a quota, and a PR that survives it earns a clean report stated plainly and without hedging. Severity stays calibrated (step 10), tone stays collegial and evidence-first, and every finding cites what you actually observed — file:line, command output, failing scenario. Break the code, not the author.

Nor does it mean asking for defense the code does not need. Principle VII puts validation at the boundary "so the interior can treat parsed values as sound": a request to re-check, inside our own code, a value that already crossed a boundary or that our own code produced is over-engineering, not rigor. So is an abstraction, option or fallback the PR's stated intent does not call for. Raise either as a nit at most, never a blocker, unless you can name the concrete input that reaches the interior unvalidated. That input is the finding; the missing check is not.

0. Test-boundary preflight — before parallel work

Complete this preflight before building or running any test. It overrides the parallel-work instruction below, and it gates executing a socket-owning target — it does not suspend the requirement to finish and post the review.

Read "Test-layer boundary" in docs/agents/tests-ci.md. Fetch the PR's own sources before inspecting anything — gh pr diff <PR>, or git fetch origin pull/<PR>/head then git show FETCH_HEAD:<path>; never read the working tree, which is the base branch and will pass this preflight vacuously on a PR that does add a socket test. Inspect the PR's added or modified test sources — not only tests.cmake — for socket ownership or a synthetic peer. Look for QTcpServer, QTcpSocket, QUdpSocket, QLocalServer, QWebSocketServer, bind(), listen(), connectToHost(), peer processes, and Fake* radio, amplifier, or tuner classes. Also check whether an existing registered target has quietly gained network behavior. These are inspection candidates, not proof by themselves; distinguish a socket object used only as an inert value from a test that opens, binds, listens, or connects.

If a new or modified socket-based test is found, stop before executing it:

  • Notify the operator with the test, socket type, target, and whether CI runs it.
  • Do not build or run that target. Get explicit, PR-specific operator direction before you do.
  • Continue the rest of the review and post it, recording the socket test and the notification in the review body. AGENTS.md requires the operator be notified before continuing, not that the review be abandoned — and an unattended run has nobody to supply the direction, so aborting there would mean the PR is never reviewed at all.
  • A PR that removes a socket test or fake peer is the remediation #5254 asks for. Notify, then review it normally; do not treat it as a stop.

Classify acceptable coverage before requesting a regression test:

  • Wire encoding, parsing, model tables, scheduling, DSP, capabilities, and safety policy: a socket-free CTest.
  • Refusals, malformed or disconnected input, dropped messages, non-events, and TX guards: socket-free transport or state-machine injection.
  • Race or lifetime behavior: the appropriate sanitizer lane.
  • Positive session, RX, control, or meter convergence: the automation bridge plus radiocert against real firmware.
  • A necessary simulator closed loop: explicit opt-in, never the default graph.

Do not request or accept a synthetic peer standing in for third-party radio, amplifier, tuner, or other external-device firmware. Tests of AetherSDR's own server surfaces (rigctld, CAT, the TCI server, the automation bridge transport) are legitimate per AGENTS.md, but still trigger the notify rule. For those, check the three obligations canon puts on them and report any that are missing:

  • the test is disclosed in the PR body;
  • its tests.cmake block names the socket it binds;
  • it fails fast, or skips with exit 77 (SKIP_RETURN_CODE 77), when it cannot bind — rather than consuming its timeout.

Bracket-commented, retired, or otherwise unexecuted tests do not count as delivered regression coverage. Deliberately opt-in targets are different and do count, provided the PR says how to run them: an option()-gated or EXCLUDE_FROM_ALL target carrying the # not registered: <reason> marker is the shape the bullet above routes a simulator closed loop to — see hl2_tx_loopback_test in tests/tests.cmake.

1. Gather

  • gh pr view <PR> --json title,body,author,baseRefName,headRefName,state,mergeable,statusCheckRollup,closingIssuesReferences,reviews,comments

  • gh pr diff <PR> — the full diff is the primary evidence. For context beyond the hunks, read the touched files at the PR head (git fetch origin pull/<PR>/head then git show FETCH_HEAD:<path>), or check the branch out in a new scratch worktree if you need to build.

  • CI: note failing/passing checks and whether CI ran against a stale merge base. Check the workflow trigger before drawing conclusions: a pull_request trigger tests the merge result — main + this PR as main stood when that run was created, which is not the same as current main; a push-triggered check on the branch never included main at all. Creation, not job start: github.sha is frozen when the run is created (static-checks.yml:297) and no checkout overrides it, so a job that sat in the queue still built the older tree. Compare the earliest run creation on the head against main's tip:

    gh api --paginate \
      "repos/aethersdr/AetherSDR/actions/runs?head_sha=<headOid>" \
      --jq '.workflow_runs[] | "\(.created_at)\t\(.name)"' | sort | head -1
    gh api repos/aethersdr/AetherSDR/commits/main --jq .commit.committer.date
    

    If main is newer, the green describes a merge that no longer exists, and any conclusion you draw from it inherits that. main has strict: false, so nothing forces a rerun to close the gap — say so in the report rather than reporting the checks as green without qualification. /pr-land records the same comparison in its report but does not gate on it.

2. Linked issue → does the PR actually solve it?

  • Start with intent. Read the PR body's ## Intent section (the PR template asks for it) and state in one line what the PR is for. Check that against the linked issue before reading any hunk: a correct diff for the wrong goal is the most expensive finding a review can miss, and the one line-by-line reading misses most. If the section is absent, reconstruct the intent from the body and issue, say you did, and note the missing section as a nit.
  • Linked issues = closingIssuesReferences plus any #NNNN referenced in the PR title/body as "fixes/closes/resolves". Read each with gh issue view <N> --json title,body,comments (skim comments for accepted repro steps, maintainer rulings, or scope changes — the issue thread often redefines the ask).
  • Build a short requirements list from the issue (symptom, repro, acceptance expectations, any explicit non-goals), then map each requirement to the diff: which hunk addresses it? Flag requirements the diff does not touch, and diff changes that no requirement explains — those feed the scope audit in step 3, which is mandatory on every review.
  • After completing the test-boundary preflight, check whether admissible coverage fails without the fix and passes with it. Prefer the smallest socket-free behavioral seam and require mutation evidence when practical.
  • Missing coverage is a blocker only when the reported behavior has a deterministic, policy-compliant test seam or project canon explicitly makes that coverage merge-gating. If the only apparent approach is a synthetic firmware peer, do not request it; describe the honest coverage boundary and route positive convergence to bridge/radiocert evidence. Report the gap as a nit either way — an untestable gap is still worth naming, it just is not a reason to withhold a merge.
  • Never count additions to a retired target, or to an unregistered target that lacks the # not registered: <reason> marker AGENTS.md recognizes, as coverage.
  • If there is NO linked issue: say so, review the PR against its own stated intent, and note whether project process wanted an issue/RFC first (GOVERNANCE.md — architectural changes need an RFC; "bug fixes with a clear root cause" explicitly do not).

3. Scope audit — does the PR do only what it says? (always)

Run this on every review, whether or not the operator asked for it, and give it a section in both deliverables. A PR is a claim ("this does X"), and the diff is the evidence; anything in the diff that X does not explain is a finding. This is the check that most often turns up something real, and it is cheap.

Start from the file and commit lists, not the prose:

gh pr view <PR> --json files   -q '.files[] | "\(.additions)+ \(.deletions)-  \(.path)"'
gh pr view <PR> --json commits -q '.commits[] | "\(.oid[0:8]) \(.authoredDate[0:10]) \(.messageHeadline)"'

Use authoredDate, not committedDate — a rebase rewrites the commit date to the day the branch was pushed, so committedDate flattens the whole branch to one date and hides exactly the outlier you are looking for. The author date survives a rebase.

Then build a scope table — one row per file or coherent group: what it changes, whether the title/body claims it, and a verdict. Put that table in the posted review body and in the operator report. It is the artifact; prose alone lets things slide past.

What to look for:

  • Commit dates that predate the PR's own first commit, or a commit whose message has nothing to do with the linked issue. A local build workaround cherry-picked onto the branch shows up exactly this way.
  • Files no requirement explains. Build config, CI, unrelated plugins, vendored trees, formatting-only churn in files the fix does not need.
  • New public surface: a new protocol verb, wire message, config key, CLI flag, exported API, settings key, or capability field. Third parties bind to these and they outlive the fix. Even when the linked issue motivates it, a protocol addition arriving as a side effect of a bug fix is a maintainer call — name it as one (step 5's GOVERNANCE.md framing) rather than either waving it through or calling it a violation.
  • Deleted behavior, not just added code. Read the - lines as carefully as the + lines. A removed guard, early return, or confirmation means a previously-fixed bug may be back. Grep the removed side for it: gh pr diff <PR> | grep '^-' | grep -iE 'guard|return|if \(' The finding is the removed code, not the removed prose. Deleting or shortening a comment while its guard and test stay is cleanup the project wants (AGENTS.md §"Comments") — do not flag it. When the guard itself is gone, check the PR's - side and git log -S for what it protected, and ask what now prevents it.
  • Sibling implementations left behind. If the fix touches one of several parallel copies (one of N plugins, one of N backends, one of N call sites), grep for the others and say which remain broken. That is a completeness note, not usually a blocker — but the PR should not read as "fixed" when two of three surfaces still carry the defect.
  • Dead additions. An added file or target that nothing references is still scope, and it is worse than scope — verify reachability before believing it works (grep for whatever registers it: the module path, the target list, the dispatch table). "It's harmless, it's only for my machine" is an argument for a separate PR, not for merging it here.
  • The body's own checklist. Many templates carry claims like "Changes are limited to the scope of this issue" / "No unrelated files or formatting changes". Check them against the diff. A self-certification that is false is itself worth reporting — plainly and without moralizing.

Verdicts — apply these consistently:

FindingVerdict
Unrelated to the issue and to the stated fixBlocker. Ask to unbundle: drop the commit, open its own PR
Explained by the issue thread but absent from the PR bodyNot a blocker on its own — but name it, and ask for the body to be updated so it is reviewable and searchable later
New public/protocol surfaceNeeds maintainer decision, flagged to the maintainer by name
A removed guard whose symptom can recurBlocker (a regression), naming the symptom and what used to prevent it
A comment that narrates history (bug retelling, "used to", review attribution) or runs past ~5 linesNit — ask to move the history to the commit/PR body (AGENTS.md §"Comments")
User-visible default changed, correct but undisclosedNit — plus a request to state it in the body

Distinguish this from step 6. Scope is about whether the change belongs in this PR; step 6 is about whether the change is legitimate at all. A change can be perfectly correct and still be out of scope, and that is the common case — say so without implying bad faith. Bundling is usually convenience, not concealment. Report what the diff does and let the maintainer rule.

4. Automated pass

If a code-review skill is available and model-invocable, run it at medium effort on this PR and fold its findings into the report — attribute them ("automated pass found …"), keep only findings you verified yourself, and drop anything you can refute with evidence.

It may not be invocable (some builds mark it disable-model-invocation, and the call fails). That is not a blocker: do the equivalent pass yourself under step 7 and say in the report that the automated pass was unavailable. Never imply an automated pass ran when it did not.

If a review bot has already commented on the PR, read it — but verify its claims rather than adopting them. Bots miss things and occasionally assert things that are not true; both are worth catching.

5. Governance audit (project canon, in priority order)

Read the diff against each of these; cite the specific rule when flagging:

  • CONSTITUTION.md — the principles are binding. Most commonly implicated: I (FlexLib is protocol/model authority — reference/FlexLib_* is the answer, and for HL2 the gateware RTL is the analogue), II/III (radio-authoritative state: the client never re-asserts what the radio owns; persistence must be capability-shaped, never family-checked), V (feature-owned config: one versioned JSON document, one owner, one migration point), VI (TX safety: nothing restores or automates into a keyed transmitter).
  • AGENTS.md — Settings Persistence (SQLite store; flat keys are app-global only; per-radio state goes in radio_settings feature documents via RadioModel::settingsScope(); check the write result), Settings Migration (one-shot claim-and-freeze; no perpetual legacy fallbacks), credentials (never in the store — SettingsCredentialPolicy.h is THE table; QtKeychain only), capability declarations (RadioCapabilities + caps-map doc + gating test, per that file's ADDING-A-FIELD contract).
  • CMake contract — a test needing AppSettings without aethercore takes $<TARGET_OBJECTS:aether_test_settings> and joins AETHER_SETTINGS_CONSUMERS; tests isolate via TestSettingsProfile.h (AETHER_SETTINGS_DIR).
  • docs/agents/backends.md (the aetherd / engine-boundary sub-doc of AGENTS.md) — the migration ratchets, which the settings and capability rules above do not reach: EB1/EB2/EB3 (tools/check_engine_boundary.py), the capability-record and command-plane freezes, the build-target link rules (aethercore never gui/ or QtWidgets, aetherd never QtWidgets), the routing table for radio-facing code, and the THREADING AND LIFETIME CONTRACT at the top of IRadioBackend.h. Read the whole section on the PR's head; it moves as the RFC's staged order advances. A green Static checks is not this audit — findings against tracked EB2/EB3 baselines only warn (about a hundred ride on a green run), and EB2 is a per-file count, so a lateral swap inside a tracked file passes flat. Run the gates on the merge base and on the head and diff the per-file findings, and diff the touchpoint manifest too: a new gui/→engine include only makes --check red until it is regenerated, so the grown burndown then rides green with nothing flagging the added touchpoint. On the baselines, read the direction. A PR that shrinks one — drops a stem whose coupling it removed, deletes an emptied row, lowers FROZEN_BOOL_COUNT because a bool became a record, lowers a converted file's command-plane count — is doing what canon demands of a conversion; verify the coupling really went and pass it. A PR that grows one, or retags a header in docs/architecture/aetherd-touchpoint-tags.json, is weakening the enforcement rather than passing it — EB3 derives its vendor vocabulary from that file at runtime, and a de-classification also has to release the stem from VENDOR_STEMS_PINNED, so a PR touching both is telling you exactly what it did — and that is a blocker unless it is one of canon's two documented carveouts: a vocabulary reclassification with merge-base proof and explicit maintainer review, or a FROZEN_BOOL_COUNT raise on a maintainer ruling. Also walk the #5554 notice at the top of § "AI Agent Guidelines" when the PR touches src/core/backends/, RadioModel, RadioSession, TransmitModel, ConnectionPanel, discovery or RadioCapabilities; most of its items — a new family-string branch, a dynamic_cast to a concrete backend, a capability with no verb behind it — are invisible to every checker.
  • docs/style/dialog-patterns.md — new dialogs ride PersistentDialog (#2605); geometry base64; frameless propagation.
  • docs/a11y.md — accessible names on interactive widgets, throttled updateAccessibility, no interactive QLabels.
  • CONTRIBUTING.md — tests for behavior changes, touchpoint manifest regen when applicable, cross-platform unless solving a platform-specific problem.
  • GOVERNANCE.md — should this change have had an RFC/issue first? Is it within the scope a maintainer already ruled on?

CHANGELOG.md: never ask for an entry

CHANGELOG.md is a release-prep file (AGENTS.md, the five-places version table). An ordinary PR must not add an entry, however user-visible the change is — the PR body and commit message are where it gets described. Do not ask for one, and flag one as a change to remove if a PR adds it.

Every entry prepends to the top of the same ## [Unreleased] list, so any two PRs that both add one conflict with each other, and every open PR goes stale when one of them merges. That churn is the whole reason for the rule.

Cite the sentence, or downgrade it to a nit

Before calling anything a governance blocker, find the sentence in canon that states the rule and quote it. If you cannot, it is a convention at most, and conventions are nits.

Do not infer a rule from git log. If you do try to measure how universal a practice is, note that git log -- <path> returns only commits that touched that path — using it to compute "N of the last N commits did X" is circular and will always return 100%. Iterate over unfiltered history and test each commit. This exact error once turned a nonexistent CHANGELOG rule into a merge blocker on someone else's PR.

6. Personal-preference / bias check

Some contributors code their personal preferences into the app without going through the RFC process. Any modification to an EXISTING UI element or behavior (new features notwithstanding) gets classified: is this a fix (restores documented/intended behavior, corrects a defect, matches SmartSDR/ FlexLib reference behavior, closes an accessibility gap) or a preference (changes a default, reorders/rewords/restyles working UI, alters workflow because the author likes it better)?

How to tell them apart — a fix can point at an authority; a preference can't:

  • The linked issue describes it as broken, with a repro — not "I find it annoying".
  • FlexLib / SmartSDR reference behavior, the HL2 gateware, a spec, or project docs say what SHOULD happen, and the PR moves toward that.
  • The old behavior contradicts the Constitution, a11y doc, or an explicit maintainer ruling.

Red flags for smuggled preference: changed default values (an existing settings key's default, a slider range, a timer interval) with no issue citing the old default as a defect; visual restyles (colors, spacing, order, labels) bundled into an unrelated fix; keyboard/mouse behavior changes described as "improvements"; removed confirmations or notices; scope-audit rows from step 3 that touch UI. Bundling is itself the tell — a genuine fix rarely needs to adjust neighboring working UI.

Verdict handling: a preference change inside a fix PR is a blocker — not because the preference is necessarily wrong, but because it needs its own issue/RFC and a maintainer ruling per GOVERNANCE.md (cite it). Suggest the split: keep the fix hunks, move the preference to a proposal. If the whole PR is a preference change presented as a fix, say so plainly and route it to Needs maintainer decision. Never let polished code quality launder an unratified behavior change.

7. Code-quality audit

Beyond the automated pass, read for: correctness of the actual state machine/lifecycle being touched (connect/disconnect, slice recreate, radio swap are the recurring minefields), thread-safety (audio callback vs main thread; AppSettings is thread-safe but save() does I/O — never on the render callback), Qt object lifetime (QPointer/WA_DeleteOnClose, parenting), error handling per house style (no exceptions; check returns; qWarning with category), silent failure modes (unchecked writes, swallowed errors), and whether comments explain why (constraints), not what, and state the current invariant rather than its history (AGENTS.md §"Comments").

Read hostilely: for each non-trivial hunk, spend a moment constructing the input, ordering, or lifecycle event that makes it misbehave before you accept that it doesn't. Walk the failure paths as carefully as the happy path — what the code does when the write fails, the pointer is stale, the radio drops mid-call, or the user does it twice.

Verify at least one non-trivial claim empirically when feasible (build the PR head in a scratch worktree, run the PR's own tests, and drive the result through the automation bridge — step 8, which is the default verification path for anything user-visible). Re-run the step-0 preflight against the exact target before invoking it, prefer focused socket-free targets, and do not start a broad suite merely to find something empirical to report. Prefer verifying the claim the PR most depends on and would most like you to take on faith. Say in the report what you verified vs. only read, and say plainly when a load-bearing claim went unverified — an untested assertion reported as untested is honest; one reported as fine is not. Never trust a green CI badge over a local reproduction when they disagree.

Build only what you run. Configure a lean review build, then build named targets — never the default all:

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \
  -DAETHER_SHARED_CORE=ON \
  "-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=-O2 -g1 -DNDEBUG" \
  "-DCMAKE_C_FLAGS_RELWITHDEBINFO=-O2 -g1 -DNDEBUG"
cmake --build build --parallel --target AetherSDR <test_a> <test_b>
ctest --test-dir build -R '^(test_a|test_b)$' --no-tests=error --output-on-failure

AETHER_SHARED_CORE=ON links every target against one libaethercore instead of a static copy each. -g1 keeps line tables and function names, so backtraces still symbolize, with a fraction of the debug info; it is spelled out so the recipe does not depend on the build-type default. Together they shrink a test binary roughly twentyfold. The app then loads the core as a shared library, which is not how it ships. That is fine for driving behaviour, but name it if a finding could depend on link mode. Both are for Linux and macOS: on Windows, drop the shared core (it is a configure error) and the GCC/Clang flags. A test cannot call WDSP directly: the shared core does not export it (a private, hidden-visibility dependency), and linking aether_wdsp gives the test a second WDSP the core never touches. It goes through a *ForTest hook in core/dsp/WdspChannel.h; tools/check_wdsp_test_links.py enforces that in Static checks. Add -DCMAKE_{C,CXX}_COMPILER_LAUNCHER=ccache when ccache is installed.

Pick the tests from the diff: the ones the PR adds or modifies, the ones its body names, and the ones whose tests/tests.cmake block compiles a file the PR touches (grep -n 'Touched.cpp' tests/tests.cmake). When the hit is an OBJECT library (aether_test_*) or a set(..._SOURCES list rather than a test, grep again for that target or variable name. A file compiled into aethercore reaches every test that links it, so no grep finds them all; pick the ones that exercise the touched code. -R matches the add_test(NAME ...) name, which is usually the target name but not always (settings_browser_dialog_test registers as settings_browser_dialog), and one binary can be registered several times under different arguments or environment (deepfist_cw_model_test is registered five times). Take the names from grep -n 'COMMAND <target>' tests/tests.cmake for every target you build. --no-tests=error turns a filter that matched nothing into a failure instead of a silent pass, but it cannot see a registration the filter left out. Leave out AetherSDR only when you will not drive the app. The default target builds every test executable — several hundred, most linking a large slice of the app and each carrying its own debug info — which costs tens of gigabytes and most of the build time for binaries the review never runs. An unfiltered ctest then reports the unbuilt ones as "Not Run"; that is a missing -R, not a failure. Build the whole tree only when the claim under test is about the tree itself (a CMake or link-contract change), and say in the report that you did.

Where the PR adds or changes a test, break the code on purpose and confirm the test notices. A regression test that still passes with the fix reverted is a blocker in its own right — it pins nothing, and it will read as coverage forever after.

"CI is green" is not "the suite passes." Every ctest call in ci.yml is -R-filtered to a handful of named tests, so only a few of the ~240 tests gate a merge. If you run the suite locally, expect failures that have nothing to do with the PR.

Before blaming the PR for a test failure, prove it. Build the same targets on the PR's merge base in a separate worktree and run the same test there. For a genuinely intermittent, socket-free failure, compare repeated runs on each side using a proportionate sample. For a bind, sandbox, permission, or unavailable-peer failure, do not repeat it — but do check the merge base once before classifying it. If the merge base fails the same way it is an environment or test-layer boundary; stop under step 0 and say so. If only the PR head fails to bind, that is a finding about the PR, not the sandbox — our own server surfaces regress exactly that way. Report the comparison, not the impression.

8. Drive the app — verify findings against the running GUI

Any claim about runtime behavior gets tested against the running app, not argued from the diff. UI state, a control's effect, a dialog's lifecycle, what the panadapter actually renders, connect/disconnect and slice-recreate paths, whether a setting survives a restart — the automation bridge can reach all of it, so "I think this breaks X" is a hypothesis you are equipped to settle. Drive it before you post. This is what turns a suspicion into a finding a maintainer can act on, and what kills the plausible-but-wrong ones before they reach the author.

docs/automation-bridge.md is the reference; it is written for agents and is copy-pasteable.

Demo mode, never the live radio

Every bridge session in a review runs against the built-in demo simulator (SimBackend, RFC #4288) — never the operator's FLEX-8600. The demo is a synthetic backend that generates its own RX audio and matching panadapter, it exercises the same RadioModel / slice / pan / settings paths the GUI uses with real hardware, and by construction it cannot key (Principle VI). It is the correct target for a review: reproducible, no hardware contention, no emissions, and nothing you do to it can disturb the operator's station.

The trap is that this is opt-out, not opt-in: AutoConnectToLastRadio defaults on, and on the operator's machine the saved settings point at the real radio — so a bridge instance launched with the default config will connect itself to the live FLEX before you have issued a single verb. Two things prevent it, and you do both:

  1. Isolate the settings store, so there is no saved radio to autoconnect to. AETHER_SETTINGS_DIR is honoured at runtime (SettingsPaths.cpp); point it at a fresh scratch dir. A per-session scratch HOME / XDG_CONFIG_HOME does the same job. A clean store also means ShowDemoRadio is at its default (on), so the demo entry is in the list.
  2. Connect explicitly to the demo, and to nothing else. Its discovery serial is DEMO-0001 (SimBackend::demoSerial()), family sim, shown as "Simulator (not on the air)".
export SCRATCH=<your scratch dir>
AETHER_AUTOMATION=1 QT_QPA_PLATFORM=offscreen \
AETHER_SETTINGS_DIR="$SCRATCH/settings" \
AETHER_AUTOMATION_IDENTITY=pr-<PR>-review \
AETHER_AUTOMATION_SOCKET=aethersdr-pr<PR> \
AETHER_AUTOMATION_NO_TX=1 \
nohup ./build/AetherSDR >"$SCRATCH/app.log" 2>&1 &

Detach so a shell exit cannot SIGHUP the instance — prefix setsid on Linux, where it exists. On macOS the binary is inside the bundle, ./build/AetherSDR.app/Contents/MacOS/AetherSDR; docs/automation-bridge.md gives both paths.

Then, before anything else, confirm where you are pointed and attach the demo:

connect list                      → verify DEMO-0001 is offered
connect local serial DEMO-0001
connect wait 30000                → asserts connected, returns the radio block
get radio                         → confirm model "AetherSDR Demo", family sim

If get radio ever shows a FLEX serial, you are on the operator's hardware: disconnect immediately, fix the isolation, and say so in the report.

The demo also gives you failure paths that live hardware will not: the sim verb injects faults (swr, dropslice, stallscope, disconnect, malformed, clear) while it is connected — that is how you exercise the error handling in step 7 rather than only reading it.

Its limits, so you don't mistake one for a finding: it advertises a single slice, it cannot transmit, and it does not implement every Flex-specific protocol surface. When a PR's behavior genuinely cannot be reached in demo mode, that is a legitimate "could not verify" — report it as one (see the end of this step). It is not a licence to reach for the real radio; using live hardware needs the operator's explicit go-ahead in that review, and even then never for TX.

Driving it

With the demo attached, use the aethersdr-automation MCP tools: bridge_status → dump_tree filter=<widget> → invoke / gesture / shortcut / tune / slice / menu → assert_state / wait_for on the model property that should have changed → grab_widget when the claim is visual. bridge_command is the escape hatch for verbs without a typed tool (hover, clickAt, contextMenu, layout, tci, …). If MCP is unavailable, python3 tools/automation_probe.py or raw line-delimited JSON over the socket does the same job — the bridge being awkward to reach is not a reason to skip it.

The loop that produces evidence:

  1. Reproduce on the merge base first when the PR claims a fix. A "before" you cannot make fail is itself a finding — either the repro is wrong or the fix is fixing nothing. Same worktree layout, same steps, different binary.
  2. Re-run identically on the PR head. Prefer assert_state/wait_for — they read model truth and report pass/fail — over eyeballing a screenshot. Grab pixels when the claim is about pixels.
  3. Then attack past the happy path, which is the part the author almost certainly did not drive: the second slice, the second pan, disconnect mid-operation, the dialog closed and reopened, the value at its range limits, the same action twice. Persistence claims are only proven across a process boundary — relaunch and re-read, don't trust the in-session value.
  4. Quote what the app returned — the get_state/assert_state JSON, the get_log lines, the PNG — in the finding. Bridge output is the strongest evidence available in a review; use it verbatim rather than paraphrasing.

Use it on the body's runtime claims too, not only on your own suspicions. "No behavior change", "the panel still remembers its geometry", "other radios are unaffected", "the shortcut still works" are all one bridge session away from being confirmed or refuted.

Non-negotiable operating rules:

  • pgrep -lf AetherSDR first. Instances that are not yours — especially any launched from the shared checkout — are the operator's session. Never drive, close, or kill one. -lf is the portable spelling: BSD and macOS read a bare -a as "include ancestors" and print no command line to judge by.
  • Always pass an explicit socket. The discovery file <temp>/aethersdr-automation.json is last-writer-wins and will happily point you at another agent's instance.
  • Never key TX. The transmit verbs stay gated behind AETHER_AUTOMATION_ALLOW_TX; a review never needs them. Launch with AETHER_AUTOMATION_NO_TX=1 (or via the MCP app_instance tool, which pins it) so the gate is off regardless of the saved preference.
  • Demo mode, offscreen, isolated settings — always. Never point a review instance at the FLEX-8600. It is single-occupancy and it is the operator's station, not test equipment; a review has no business on it. If a finding truly cannot be reached in demo mode, ask the operator rather than connecting.
  • Close your instance when done, and say in the report which instance you drove and that it was the demo.

When you genuinely cannot drive it — the change is headless, the path needs hardware you must not touch, the build fails — say so explicitly and label the affected findings as reasoned-from-code rather than reproduced. An unverified finding reported as unverified is honest; one reported as observed is not.

9. Post the review to the PR

Post exactly ONE review, with every finding in its body, each located by file:line. Do not post inline comments. main requires every conversation to be resolved before merge, so each inline thread is a merge blocker that someone has to answer and resolve, which costs the author a round trip even for a nit. One body is read once and answered once.

  • Post with gh pr review <PR> --request-changes|--comment --body-file review.md. A body file survives the newlines, backticks and code fences that shell quoting does not.
  • Locate every finding as path/to/file.cpp:123 (or a line range). That includes findings about untouched code and out-of-scope files.
  • For a mechanical fix, put the replacement in a fenced code block under the finding, drop-in correct (matching indentation, compiling in context, never pseudocode), so the author can paste it.
  • The one exception: an inline comment is acceptable only when a finding cannot be understood from a file:line reference plus a quoted snippet. That is rare. Never for nits, suggestions or anything the body already says. If you post one, say why in the body.
  • Review event: any blocker → REQUEST_CHANGES. No blockers → COMMENT (verdict and nits in the body). Approval stays the operator's call unless they have said otherwise for this PR.
  • The review body, in this order: the issue-fit verdict (one short paragraph), the scope table from step 3 (always — write "everything in the diff is explained by the issue" and move on when it is clean), the numbered blockers (each with its file:line), then nits marked explicitly non-blocking. State what was verified empirically vs. read, and — briefly — what you tried to break that held up, so the author can see the review was adversarial rather than cursory, and can correct you if you attacked the wrong thing. Where a finding came from driving the app, paste the bridge evidence with it: the state JSON, the log line, or the grab_widget PNG (attach images by URL). A reproduced finding with app output attached rarely gets argued with.
  • Branch protection has dismiss_stale_reviews enabled: any push dismisses an existing approval. If you are also pushing commits, approve after the last push, and re-approve after any later merge-from-main. Check reviewDecision afterwards rather than assuming it stuck.

10. Report (markdown, to the operator only)

## PR #NNNN — <title> (@author)

**Issue:** #MMMM — one-paragraph summary of the problem as reported.
**Intent:** the PR's stated goal in one line, and whether it matches the issue.
**Proposed fix:** one-paragraph summary of the approach the diff takes.
**Does it solve the issue?** Yes / Partially / No — with the requirement→hunk
mapping and anything unaddressed.

### Scope
The step 3 table: file/group → claimed in the title or body? → verdict. Then
one line on whether the body's own checklist claims hold up. If nothing is
out of scope, say so in a sentence — never omit the section, because "I
checked and it is clean" and "I did not check" must not look the same.

### Blockers
Numbered. Each: what's wrong, the evidence (file:line, build output, failing
scenario), which rule it violates (if governance), and what a fix looks like.
"None." if none.

### Nits
Bulleted, explicitly non-blocking. Style, naming, missed niceties, stale docs.

### What I tried to break
The attacks that did NOT produce a finding — the claims from the body you
tested and that held, the edge cases and failure paths you walked, the tests
you inverted, what you built or ran, and **the bridge session**: which build
you drove against the demo, the verbs/tools you used, and the state you
asserted. Two to five
bullets. This is what makes a clean review trustworthy; without it, "no
blockers" and "I did not look" are indistinguishable. Name anything you could
not test and why — including "did not drive the app because …".

### Recommendation
One of: **Approve** / **Approve with nits** / **Request changes** /
**Needs maintainer decision** — plus 2–3 sentences of reasoning, what you
verified empirically, and the concrete next step (e.g. "admin-merge after CI",
"ask author for X", "needs a ruling on Y before review can conclude").

Calibrate severity honestly: a blocker is something that breaks users, violates canon, or will fail on current main — not a preference. When the verdict hinges on a scope call only the maintainer can make, say exactly that instead of manufacturing a verdict.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

fb

免费

Fix an AetherSDR bug end-to-end and prove the fix with the agent automation bridge. Use when the user says $fb or /fb ISSUE_NUMBER (e.g. /fb 2211), or asks to "fix bug 2211", "take the next bug", "reproduce and fix issue 3340", or otherwise wants a GitHub issue investigated, root-caused, fixed in a worktree, built, proven via the bridge, and turned into a PR. This is the dogfooding loop for the agent automation bridge — reach for it whenever a UX, meter, transmit, audio, or control bug is reported with an issue number.

中文简介正在准备中。当前显示原文说明。

aethersdr/AetherSDR2502026年10月29日 更新

papercuts

免费

Read-only AetherSDR issue triage that surfaces the UX papercuts, bugs, and regressions you can fix AND prove today with the agent automation bridge. Use when the user says $papercuts or /papercuts, or asks to "find papercuts", "what can I fix and prove today", "triage bridge-provable issues", "what UX bugs can the bridge close", "sort issues by priority I can knock out", or otherwise wants a curated, lifecycle-aware (design→code→prove) shortlist of issues — NOT the label-and-respond /triage workflow. This is the discovery step that feeds /fb. Strictly read-only: it never labels, comments, closes, or edits issues.

中文简介正在准备中。当前显示原文说明。

aethersdr/AetherSDR2502026年10月29日 更新

pr-land

免费

Post-review remediation pass for AetherSDR — takes a PR whose proposed change the maintainer has already accepted, fixes every outstanding finding with proof, answers and resolves every review thread, dismisses blocking reviews with evidence, posts the approving review and arms auto-merge. Every judgment call goes to the maintainer through AskUserQuestion. Use after /pr-review, e.g. "/pr-land 5601".

中文简介正在准备中。当前显示原文说明。

aethersdr/AetherSDR2502026年10月29日 更新

The AetherSDR release-prep pass — takes a CalVer version and drives the release from "main is where we want to cut" to "the prep PR is merged on main and ready to tag". Maps the range through the commit-to-PR API, writes the CHANGELOG section from PR bodies, refreshes README and ROADMAP, edits the six release files, validates, opens the prep PR, folds in what merges after the cutoff, and hands the merged squash commit to /tag-release for the tag, the release and the asset verification. Every judgment call goes to the maintainer through AskUserQuestion. Use when the user says "/release-prep 26.9.4", "/release-prep v26.9.4", "prep the release", "release prep", "write the changelog for 26.9.4", or otherwise wants a version prepped; the cut itself is "/tag-release".

中文简介正在准备中。当前显示原文说明。

aethersdr/AetherSDR2502026年10月29日 更新

The AetherSDR cut pass — takes a CalVer version whose prep PR has merged on main and drives it from "the prep is on main" to "the release is published, every asset is signed and verified, the Store draft is staged, the website post is up and the report is written". Finds the squash-merge commit, checks the six release files at that SHA, tags it with a signed annotated tag, creates the release before CI attaches anything, watches the three build workflows and the signing runs, verifies the seventeen-asset set on the files, reads the Store staging step, drafts the website post, and reports the first 48 hours. Every judgment call goes to the maintainer through AskUserQuestion. Use when the user says "/tag-release 26.9.5", "/tag-release v26.9.5", "tag the release", "cut the tag", "publish v26.9.5", "the prep merged, tag it", or otherwise wants a prepped version tagged and published. /release-prep ends where this begins.

中文简介正在准备中。当前显示原文说明。

aethersdr/AetherSDR2502026年10月29日 更新

查看 aethersdr 的全部技能

报告此技能的问题