Skip to content

Fix panadapter layout and floating restore#2780

Closed
rfoust wants to merge 1 commit into
aethersdr:mainfrom
rfoust:codex/panadapter-layout-persistence
Closed

Fix panadapter layout and floating restore#2780
rfoust wants to merge 1 commit into
aethersdr:mainfrom
rfoust:codex/panadapter-layout-persistence

Conversation

@rfoust

@rfoust rfoust commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fix panadapter layout/floating persistence, FFT sizing after layout changes, and the macOS GPU panadapter reparent/shutdown crash path.

  • Persist PanadapterLayout and restore it after restart when the restored pan count matches.
  • Avoid overwriting the saved PanadapterLayout when the app temporarily falls back after pan removal or radio resource limits.
  • Expand panadapter layout count/default handling from 4 to 8 pans so 5-8 pan saved layouts restore correctly and fallback layouts choose the right shape.
  • Persist FloatingPanIds and restore detached panadapters after restart.
  • Preserve saved floating IDs for pans not seen in the current run so unavailable/restored-later pans do not lose detached state.
  • Choose sensible docked splitter layouts when some pans are floating or the saved layout count does not match the docked count.
  • Use the actual FFT pane height for radio ypixels instead of total widget height.
  • Reacquire auto FFT floor lock after geometry/ypixels changes so detach/redock does not reuse a stale floor baseline.

macOS GPU crash fix

Fix the QRhiWidget lifecycle crash during panadapter float/dock/rearrange/shutdown on macOS GPU builds:

  • Send WindowAboutToChangeInternal before top-level reparenting so Qt unregisters the widget from the old backing-store QRhi.
  • Release QRhi resources and destroy the native child window during shutdown while parent backing stores are still valid.
  • Avoid sending the same backing-store notification again during the delayed post-reparent GPU refresh; the refresh now only recreates the GPU surface for the new native view.
  • Explicitly prepare/destroy panadapter UI before MainWindow teardown.
  • Guard late pan-stream and S-History callbacks during shutdown.

Testing

  • git diff --check upstream/main...HEAD
  • cmake --build build
  • Manual: multi-pan layout, detached panadapter, Cmd-Q, restart, confirm layout and detached state restore.
  • Manual: two detached panadapter windows, quit, restart, confirm both restore without the QRhi cleanup callback crash.
  • Manual: detach/redock panadapter and confirm auto FFT floor relocks to the correct level.

Copilot AI review requested due to automatic review settings May 17, 2026 00:35
@rfoust rfoust requested review from jensenpat and ten9876 as code owners May 17, 2026 00:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves multi-panadapter UI persistence and stability by restoring saved layouts and floating pan state across restarts, correcting FFT sizing inputs after layout changes, and hardening the macOS GPU (QRhiWidget) reparent/shutdown lifecycle to avoid crashes.

Changes:

  • Persist/restore panadapter layout and floating-pan IDs across sessions, with improved fallback behavior for 1–8 pan layouts.
  • Use FFT-pane height (not total widget height) for radio ypixels, and relock the auto noise-floor baseline after geometry/ypixels changes.
  • Add macOS GPU reparent/shutdown preparations (backing-store notification + QRhi/native resource teardown) and guard late callbacks during shutdown.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/gui/SpectrumWidget.h Adds shutdown/reparent preparation APIs and FFT-pane height accessor used for dimension reporting.
src/gui/SpectrumWidget.cpp Implements QRhiWidget lifecycle safeguards, FFT-pane height calculation, dimension change emission updates, and shutdown render/resource guards.
src/gui/PanadapterStack.h Tracks seen pan IDs to preserve floating state for pans not present in the current run; adds shutdown signaling.
src/gui/PanadapterStack.cpp Refines layout selection for docked vs floating scenarios, preserves/restores floating IDs, and adds QRhiWidget reparent preparation in layout/float/dock paths.
src/gui/MainWindow.h Adds explicit panadapter UI shutdown preparation hook/state.
src/gui/MainWindow.cpp Updates layout restore/persistence logic (incl. up to 8 pans), uses FFT-pane ypixels, reacquires noise-floor lock on dimension changes, and centralizes panadapter shutdown prep.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/gui/MainWindow.cpp
Comment on lines 2589 to 2656
// Debounced layout restore: after all pans are added on connect,
// rearrange to the saved layout (e.g. 2h instead of default vertical).
if (!m_layoutRestoreTimer) {
m_layoutRestoreTimer = new QTimer(this);
m_layoutRestoreTimer->setSingleShot(true);
m_layoutRestoreTimer->setInterval(1000);
connect(m_layoutRestoreTimer, &QTimer::timeout, this, [this]() {
m_layoutRestoreTimer->setProperty("fired", true);
if (m_shuttingDown || !m_panStack) {
return;
}
// The radio restores pans from the GUIClientID session.
// Accept whatever the radio gives and arrange based on count.
const int panCount = m_panStack->count();
if (panCount > 1) {
// Pick a layout based on the number of pans the radio restored
const QString saved = AppSettings::instance()
.value("PanadapterLayout", "1").toString();
// Only rearrange if the saved layout matches the pan count
static const QMap<QString, int> layoutPanCount = {
{"1", 1}, {"2v", 2}, {"2h", 2}, {"2h1", 3}, {"12h", 3}, {"3v", 3}, {"2x2", 4}, {"4v", 4}
};
if (layoutPanCount.value(saved, 1) == panCount)
m_panStack->rearrangeLayout(saved);
else if (panCount == 2)
m_panStack->rearrangeLayout("2v"); // default 2-pan to vertical
else if (panCount == 3)
m_panStack->rearrangeLayout("2h1"); // default 3-pan
else if (panCount >= 4)
m_panStack->rearrangeLayout("2x2"); // default 4-pan
const QString layoutId = panCountForLayoutId(saved) == panCount
? saved
: defaultPanLayoutForCount(panCount);
const QString floatingPanIds = AppSettings::instance()
.value("FloatingPanIds", "").toString();
m_panStack->rearrangeLayout(layoutId);
AppSettings::instance().setValue("FloatingPanIds", floatingPanIds);

// Optimistically set local yPixels immediately so FFT frames
// arriving before the radio echoes back use correct scaling (#1511).
for (auto* a : m_panStack->allApplets()) {
auto* s = a->spectrumWidget();
auto* p = m_radioModel.panadapter(a->panId());
if (!s || !p || !p->panStreamId()) continue;
int y = s->height();
if (y >= 100)
m_radioModel.panStream()->setYPixels(p->panStreamId(), y);
if (panPixelDimensionsReady(s)) {
m_radioModel.panStream()->setYPixels(
p->panStreamId(), panYpixelsFor(s));
s->reacquireNoiseFloorLock();
}
}

// Defensive re-push xpixels for all pans after layout settles.
// Covers race where radio hadn't finished pan init when first push arrived.
QTimer::singleShot(500, this, [this]() {
if (m_shuttingDown || !m_panStack) {
return;
}
for (auto* applet : m_panStack->allApplets()) {
auto* sw = applet->spectrumWidget();
auto* pan = m_radioModel.panadapter(applet->panId());
if (!sw || !pan) continue;
int xpix = qMax(sw->width(), 1024);
int ypix = qMax(sw->height(), 200);
const int xpix = panXpixelsFor(sw);
const int ypix = panYpixelsFor(sw);
m_radioModel.sendCommand(
QString("display pan set %1 xpixels=%2 ypixels=%3")
.arg(pan->panId()).arg(xpix).arg(ypix));
if (pan->panStreamId())
if (pan->panStreamId()) {
m_radioModel.panStream()->setYPixels(pan->panStreamId(), ypix);
sw->reacquireNoiseFloorLock();
}
}
});
}

// Restore floating-pan state saved from the previous session.
// Runs for any pan count so a single floated pan is also restored.
m_panStack->restoreFloatingState();
});
}
if (!m_layoutRestoreTimer->property("fired").toBool()) {
m_layoutRestoreTimer->start();
}
m_layoutRestoreTimer->start();
});
Comment on lines +49 to +61
QString defaultDockedLayoutForCount(int panCount)
{
static const QMap<int, QString> kDefaultLayouts = {
{1, QStringLiteral("1")},
{2, QStringLiteral("2h")},
{3, QStringLiteral("2h1")},
{4, QStringLiteral("2x2")},
{5, QStringLiteral("3h2")},
{6, QStringLiteral("2x3")},
{7, QStringLiteral("4h3")},
{8, QStringLiteral("2x4")}
};
return kDefaultLayouts.value(panCount, QStringLiteral("1"));
@rfoust rfoust marked this pull request as draft May 17, 2026 00:52
@rfoust

rfoust commented May 17, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 9d4af9f: removed the stale post-dBm-drag FFT preview offset so display scaling now relies on the immediate PanadapterStream range update plus the pending echo guard and FFT smoothing reset. Verified with git diff --check and cmake --build build.

@rfoust rfoust marked this pull request as ready for review May 17, 2026 02:10
Persist selected and floating panadapter layouts across restarts while preserving saved state for pans that are unavailable in a given session.

Use the actual FFT pane height for pan ypixels, clamp decoded FFT ranges, and stabilize dBm/noise-floor updates around range changes.

Prepare QRhiWidget-backed spectrum widgets before top-level reparenting and shutdown so macOS GPU cleanup callbacks do not crash during float/dock/quit paths.
@rfoust rfoust force-pushed the codex/panadapter-layout-persistence branch from 9d4af9f to e72d7e4 Compare May 17, 2026 02:13
@rfoust

rfoust commented May 17, 2026

Copy link
Copy Markdown
Collaborator Author

Squashed the PR down to one commit: e72d7e4 (Fix panadapter layout persistence and dBm scaling). Re-ran git diff --check against upstream/main and cmake --build build after the squash.

@ten9876

ten9876 commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Claude here. First off — thanks for this work, Robbie. The panadapter persistence + macOS GPU lifecycle fixes are genuinely valuable and well-scoped, and the PR description is excellent.

Before merging, I ran a stale-base audit and found that the Codex-generated commit (`e72d7e48`) silently reverts two recently-landed PRs — almost certainly an unintentional stale-snapshot pattern from how Codex captures source for editing. Specifically, the diff against current main shows:

I verified the reverts by running a three-way merge dry-run; the merge succeeded textually but the resulting tree had #2770 and #2772 undone. The same pattern (file-level stale snapshot) hit Claude Code earlier and is documented in ten9876/aetherclaude#34 — sounds like Codex has a similar capture-then-write pattern that doesn't reconcile with files changed between snapshot and patch.

Since the unintentional reverts touch hot files and would have undone deliberate consolidation work, I extracted just your panadapter work into a fresh PR rather than try to merge-then-fix on top. PR #2786 contains the cherry-pick with you set as commit author so the attribution flows correctly in `git log` and the contributor stats.

Setting this PR back to Draft so it stays as a clean reference point — feel free to update it (rebase onto current main) if you'd like a new round, or just leave the draft sitting if you'd rather work from #2786 as the merged result.

The actual changes you proposed are all preserved in #2786:

  • MainWindow.{cpp,h} panadapter layout persistence + 8-pan handling
  • PanadapterStack.{cpp,h} fallback splitter layouts
  • SpectrumWidget.{cpp,h} macOS GPU lifecycle + auto-floor relock
  • PanadapterStream.cpp dBm clamping
  • RadioModel.cpp `ypixels` from actual FFT pane height

Welcome to the project, and thanks again — really good first contribution.

73, Jeremy KK7GWY & Claude (AI dev partner)

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rfoust — the core panadapter work is solid: the PanadapterLayout/FloatingPanIds persistence rules, the up-to-8-pan default tables, the m_shuttingDown && m_panStack guards on the stream callbacks, the panPixelDimensionsReady/panXpixelsFor/panYpixelsFor helpers, the FFT-pane spectrumPixelHeight() accounting, and the WindowAboutToChangeInternal + prepareForShutdown() lifecycle around the QRhiWidget all look well-considered. A few things to flag:

Scope — please split the unrelated changes into their own PRs

The PR title is "Fix panadapter layout and floating restore," but several substantial changes are unrelated and should be reviewed on their own:

  1. src/gui/RadioSetupDialog.{cpp,h} rewrites the dialog from PersistentDialog to a hand-rolled QDialog + FramelessWindowTitleBar/FramelessResizer, and the Radio Setup... action in MainWindow.cpp drops the showOrRaisePersistent snapshot-with-wasFresh pattern that #2769 specifically introduced. None of this is mentioned in the description, it duplicates what PersistentDialog is meant to provide centrally, and it risks regressing the audio-compression-snapshot behavior #2769 fixed. Recommend reverting from this PR and proposing it separately.
  2. src/core/tnc/AetherAx25LibmodemShim.cpp and src/gui/Ax25HfPacketDecodeDialog.cpp swap lcAx25 (defined in LogManager.cpp with QtWarningMsg default) for two new Q_LOGGING_CATEGORY(lcAetherAx25Shim, "aether.ax25") / Q_LOGGING_CATEGORY(lcAetherAx25Dialog, "aether.ax25"). Neither carries the QtWarningMsg default, so qCDebug(...) calls that were previously suppressed by default will now emit — a behavior change. You also end up with three QLoggingCategory instances all bound to the string "aether.ax25" (the original in LogManager.cpp plus the two new ones). The CMakeLists.txt trimming for ax25_libmodem_shim_test falls out of that. If the goal is to break the shim's LogManager link dependency, please do it in a dedicated PR and either add the QtWarningMsg default or move the single category somewhere shared.
  3. src/core/PanadapterStream.cpp (the kMinSpectrumDbm clamp, maxDbm >= minDbm+10 widening, bin/pixel clamps, and range <= 0 early-out) is a reasonable defensive change but it's behavior shaping unrelated to layout/floating restore — worth landing on its own with its own justification.

Out-of-scope behavior change worth a second look

src/models/RadioModel.cpp:4484 raises the y_pixels acceptance threshold from > 0 to > kMinUsablePanYpixels (> 100), so any status with y_pixels between 1–100 is silently ignored. I assume the intent is to filter the radio's default 20, but the hard cliff at 100 also drops legitimate updates between 21–100 (e.g. a very compact split). Consider rejecting only the known-bogus default(s) or making the gate explicit (e.g. <= 20).

Copilot findings

  • MainWindow.cpp:2668 (re-running layout restore on later panadapterAdded): false positive in practice. m_layoutRestoreUntilMs is reset to kPanLayoutRestoreWaitingForFirstPan only inside onConnectionStateChanged(true), then locked to nowMs + 5000 on the first pan and cleared to 0 on disconnect — pans added after the 5-second window won't re-arm the timer. The new mechanism does gate to the connect/restore phase, just by time-window instead of the old fired property.
  • PanadapterStack.cpp:61 (defaults differ for 2 pans): false positive — both defaultDockedLayoutForCount(2) and defaultPanLayoutForCount(2) return "2v". That said, the two maps are now identical duplicates living in different TUs; a small follow-up to centralize them (e.g. a free function in a shared header, or have PanadapterStack consume MainWindow's helper) would prevent them drifting apart later.

Minor

SpectrumWidget::mouseReleaseEvent now calls refreshNoiseFloorTarget(true, /*persist=*/true) on every dBm drag release, which writes DisplayNoiseFloorPosition and AppSettings::save() on each release. Probably fine, but if the user fine-tunes by dragging repeatedly it'll thrash the settings file — consider deferring the save or only persisting on a debounce.

The macOS lifecycle work and the layout/floating persistence are the right shape — once the unrelated pieces are pulled out this should be in good shape to land.

ten9876 added a commit that referenced this pull request May 17, 2026
…ust/#2780) (#2786)

## Summary

Cherry-pick of @rfoust's panadapter persistence and macOS GPU lifecycle
work from PR #2780, applied cleanly onto current main with the
stale-snapshot reverts left out.

## Why a cherry-pick rather than merge of #2780

PR #2780 was based on commit \`a0512d99\` (PR #2772 merge), and its
single Codex-generated commit included **stale-snapshot content for
files outside the intended scope**. Squash-merging #2780 as-is would
have silently reverted two recently-landed PRs:

- **PR #2770** (\`aether.ax25\` Q_LOGGING_CATEGORY consolidation) —
\`AetherAx25LibmodemShim.cpp\`, \`Ax25HfPacketDecodeDialog.cpp\`, plus
the test-target linkage in \`CMakeLists.txt\`
- **PR #2772** (RadioSetupDialog → PersistentDialog migration) —
\`RadioSetupDialog.{cpp,h}\`

Verified by three-way merge dry run: post-merge state showed \`class
RadioSetupDialog : public QDialog\` (pre-#2772) and 5 occurrences of
\`lcAetherAx25Shim\` (pre-#2770).

The actual panadapter work in PR #2780 is real and valuable, so this PR
extracts it onto a fresh branch from current main and skips the
unintentional reverts.

## What's in this PR (= the intended scope of #2780)

Cherry-picked verbatim from #2780:

| File | Change |
|---|---|
| \`src/gui/MainWindow.{cpp,h}\` | Persist/restore PanadapterLayout +
FloatingPanIds; expand layout count handling from 4 to 8 pans; preserve
unseen floating IDs across reconnect |
| \`src/gui/PanadapterStack.{cpp,h}\` | Sensible-fallback
docked-splitter layouts when some pans are floating or saved layout
count mismatches |
| \`src/gui/SpectrumWidget.{cpp,h}\` | macOS QRhiWidget lifecycle:
\`WindowAboutToChangeInternal\` before reparent; QRhi resource release
during shutdown while parent backing stores are valid; guarded late
callbacks; auto FFT floor relock after geometry/ypixels changes |
| \`src/core/PanadapterStream.cpp\` | Defensive dBm-range clamping (-180
floor, ≥10 dB span); FFT decode clamps pixel input and dbm output to
range |
| \`src/models/RadioModel.cpp\` | Use actual FFT pane height for radio
\`ypixels\` rather than total widget height |

Net: **+548 / -215** across 8 files.

## What was NOT cherry-picked (stale-snapshot reverts)

| File | Reason |
|---|---|
| \`CMakeLists.txt\` | -7 lines reverting #2770's test-target linkage I
added to fix the link error |
| \`src/core/tnc/AetherAx25LibmodemShim.cpp\` | Robbie's content has
local \`Q_LOGGING_CATEGORY(lcAetherAx25Shim, ...)\` — pre-#2770. Main
has the consolidated \`lcAx25\` from LogManager |
| \`src/gui/Ax25HfPacketDecodeDialog.cpp\` | Same pattern —
\`lcAetherAx25Dialog\` local, pre-#2770 |
| \`src/gui/RadioSetupDialog.{cpp,h}\` | Robbie's diff was **100% pure
revert** of #2772 — no new content, no panadapter-related additions.
Confirmed by inspecting the full \`git diff a0512d9..e72d7e4 --
RadioSetupDialog.cpp\` output |

## Attribution

Commit author is set to **Robbie Foust** so his contribution shows in
\`git log\` and the contributor stats. PR #2780 is being reset to
**Draft** (not closed) so Robbie has a clean reference point + can
update with fresh work if he wants.

## Test plan

- [x] Local build clean (402/402 link, only pre-existing warnings)
- [x] Three-way merge dry-run against current main: clean
- [ ] Manual on Linux: multi-pan layout, detached panadapter, restart,
confirm layout + detached state restore
- [ ] Manual on macOS GPU build: two detached panadapter windows, Cmd-Q,
restart, confirm both restore without the QRhi cleanup callback crash
- [ ] Manual: detach/redock panadapter and confirm auto FFT floor
relocks

## Cross-references

- Replaces #2780 (will be set to Draft after this PR opens)
- Same stale-snapshot pattern as aetherclaude #34 (Claude Code), but
from OpenAI Codex this time — worth a heads-up to whoever's watching
agent-output quality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Robbie Foust <rfoust@duke.edu>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rfoust rfoust closed this May 17, 2026
ten9876 pushed a commit that referenced this pull request May 17, 2026
## Summary

Fix spectrum display jumps around dBm range changes.

- Rebase a few incoming FFT frames after dBm scale release so stale
radio-encoded frames do not briefly snap the trace back to the old
display location.
- Apply the same protection to vertical span / scale adjustments, not
just moving the top dBm reference.
- Split manual dBm-range echo handling from auto FFT floor echo handling
so auto-floor can keep its local smooth animation running while waiting
for the radio echo.
- Avoid applying an older auto-floor echo if the local animation has
already moved past it; reissue the current local range instead.

## Cause

The likely regression came from the later dBm stabilization work in PR
#2786 / #2780. That path made the stream decoder switch to the requested
dBm range immediately while the radio could still send several FFT
frames encoded against the old range. It also caused auto-floor movement
to pause behind the pending echo guard, which made the smooth motion
appear as steps.

## Test Plan

- [x] git diff --check
- [x] cmake --build build --parallel 8
- [ ] Manual: drag dBm reference line and release; confirm no snap-back
- [ ] Manual: Ctrl-drag vertical dBm span/scale and release; confirm no
snap-back
- [ ] Manual: enable/open panadapter with auto FFT floor adjustment;
confirm movement is smooth instead of stepping

Build passes. Existing warnings remain unrelated.
ten9876 added a commit that referenced this pull request May 22, 2026
… Principle XIII. (#2932)

Restructures AetherSDR's contributor-onboarding documentation around
the project's actual multi-agent contribution model: at least six
distinct AI tools touch the codebase (AetherClaude orchestrator,
Claude Code, OpenAI Codex, GPT 5.5 Pro, GitHub Copilot, contributor-
side IDE agents). Each tool has its own well-known file at a
different path, and the previous topology (CLAUDE.md as the sole
canonical) left every other agent reading conventions by accident.

Changes:

- AGENTS.md (new): canonical project guide. All previous CLAUDE.md
  content moved here, retitled to be agent-neutral. Single source of
  truth for project-wide guidance.
- CLAUDE.md: slimmed 311 → 68 lines. Now a thin pointer to AGENTS.md
  plus Claude-Code-specific notes (git ship alias, ~/.claude paths).
- GEMINI.md (new): thin pointer for Gemini Code Assist with 6 inline
  must-knows.
- .github/copilot-instructions.md (new): same pattern for GitHub
  Copilot.
- All tool-specific files point directly to AGENTS.md — no
  pointer-to-pointer chains.

Constitution v1.0.1 → 1.1.0 (MINOR amendment):

- Adopts 7 new principles from Cisco's Foundry Constitution
  (https://github.com/CiscoDevNet/foundry-security-spec):
  VIII Evidence Over Assertion, IX Surface Only What Survives,
  X Claims Are Atomic And Mortal, XI Fixes Are Demonstrated,
  XII Sandbox By Infrastructure Not By Prompt,
  XIII The Operator Outranks Every Agent (promoted from Governance),
  XIV Persist Atomically.
- Total principle count: 7 → 14. First 7 unchanged (AetherSDR-
  domain conventions); last 7 codify defensive engineering for the
  multi-agent contribution model.
- CONSTITUTION.md (new): byte-identical root mirror of
  .specify/memory/constitution.md so AI tools reading the repo
  root find it without spec-kit path knowledge.
- Each new principle is grounded in a real AetherSDR incident
  (PR #2780 stale-snapshot revert → Principle X; PR #2816 alloc-
  free claim verification → Principle VIII; etc.).

New: Issue / PR Claim Protocol (AGENTS.md):

- AI agents reviewing/commenting on or merging PRs must assign
  themselves via gh issue edit / gh pr edit --add-assignee @me.
- Carve-out: AetherClaude (@aethersdr-agent) auto-triages every
  new issue/PR, so adding yourself alongside its assignment is
  expected — only avoid double-assigning when another non-AetherClaude
  agent is already engaged.
- Operational implementation of Principle X (Claims Are Atomic And
  Mortal): assignees list is the visible cross-agent claim signal.

New: docs/COMMIT-SIGNING.md:

- Walks contributors through SSH or GPG signing setup on Windows /
  macOS / Linux / WSL / Raspberry Pi.
- Top of doc has explicit AI Assistant Instructions section so an
  agent pointed at the doc can walk the contributor through setup
  in their actual environment without further prompting.
- Per-OS copy-paste prompts also included for contributors using AI
  IDEs (Claude Code, Cursor, Copilot chat, ChatGPT) who want a
  ready-made invocation.

Updated: CONTRIBUTING.md + CLAUDE.md / AGENTS.md pointers:

- Constitution reference promoted to step 2 in the Submitting Code
  checklist.
- Commit signing section rewritten to lead with the new doc and
  AI-assistant invocation, with SSH quick-reference inline.
- Principle counts updated everywhere (7 → 14).

Principle XIII (Operator Outranks Every Agent) is cited because
the entire restructure exists to ensure the operator's authoritative
voice reaches every agent reading the project, regardless of which
tool the agent runs as.

Foundry-spec conformance verified: all 14 structural elements
(SYNC IMPACT REPORT, Field table, Purpose, Core Principles, "Why
inviolable" rationales, Governance subsections Amendment/Precedence/
Scope/Versioning/Compliance review/Downstream artifacts, MAJOR/MINOR/
PATCH versioning, Foundry reference) present in CONSTITUTION.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
… Principle XIII. (aethersdr#2932)

Restructures AetherSDR's contributor-onboarding documentation around
the project's actual multi-agent contribution model: at least six
distinct AI tools touch the codebase (AetherClaude orchestrator,
Claude Code, OpenAI Codex, GPT 5.5 Pro, GitHub Copilot, contributor-
side IDE agents). Each tool has its own well-known file at a
different path, and the previous topology (CLAUDE.md as the sole
canonical) left every other agent reading conventions by accident.

Changes:

- AGENTS.md (new): canonical project guide. All previous CLAUDE.md
  content moved here, retitled to be agent-neutral. Single source of
  truth for project-wide guidance.
- CLAUDE.md: slimmed 311 → 68 lines. Now a thin pointer to AGENTS.md
  plus Claude-Code-specific notes (git ship alias, ~/.claude paths).
- GEMINI.md (new): thin pointer for Gemini Code Assist with 6 inline
  must-knows.
- .github/copilot-instructions.md (new): same pattern for GitHub
  Copilot.
- All tool-specific files point directly to AGENTS.md — no
  pointer-to-pointer chains.

Constitution v1.0.1 → 1.1.0 (MINOR amendment):

- Adopts 7 new principles from Cisco's Foundry Constitution
  (https://github.com/CiscoDevNet/foundry-security-spec):
  VIII Evidence Over Assertion, IX Surface Only What Survives,
  X Claims Are Atomic And Mortal, XI Fixes Are Demonstrated,
  XII Sandbox By Infrastructure Not By Prompt,
  XIII The Operator Outranks Every Agent (promoted from Governance),
  XIV Persist Atomically.
- Total principle count: 7 → 14. First 7 unchanged (AetherSDR-
  domain conventions); last 7 codify defensive engineering for the
  multi-agent contribution model.
- CONSTITUTION.md (new): byte-identical root mirror of
  .specify/memory/constitution.md so AI tools reading the repo
  root find it without spec-kit path knowledge.
- Each new principle is grounded in a real AetherSDR incident
  (PR aethersdr#2780 stale-snapshot revert → Principle X; PR aethersdr#2816 alloc-
  free claim verification → Principle VIII; etc.).

New: Issue / PR Claim Protocol (AGENTS.md):

- AI agents reviewing/commenting on or merging PRs must assign
  themselves via gh issue edit / gh pr edit --add-assignee @me.
- Carve-out: AetherClaude (@aethersdr-agent) auto-triages every
  new issue/PR, so adding yourself alongside its assignment is
  expected — only avoid double-assigning when another non-AetherClaude
  agent is already engaged.
- Operational implementation of Principle X (Claims Are Atomic And
  Mortal): assignees list is the visible cross-agent claim signal.

New: docs/COMMIT-SIGNING.md:

- Walks contributors through SSH or GPG signing setup on Windows /
  macOS / Linux / WSL / Raspberry Pi.
- Top of doc has explicit AI Assistant Instructions section so an
  agent pointed at the doc can walk the contributor through setup
  in their actual environment without further prompting.
- Per-OS copy-paste prompts also included for contributors using AI
  IDEs (Claude Code, Cursor, Copilot chat, ChatGPT) who want a
  ready-made invocation.

Updated: CONTRIBUTING.md + CLAUDE.md / AGENTS.md pointers:

- Constitution reference promoted to step 2 in the Submitting Code
  checklist.
- Commit signing section rewritten to lead with the new doc and
  AI-assistant invocation, with SSH quick-reference inline.
- Principle counts updated everywhere (7 → 14).

Principle XIII (Operator Outranks Every Agent) is cited because
the entire restructure exists to ensure the operator's authoritative
voice reaches every agent reading the project, regardless of which
tool the agent runs as.

Foundry-spec conformance verified: all 14 structural elements
(SYNC IMPACT REPORT, Field table, Purpose, Core Principles, "Why
inviolable" rationales, Governance subsections Amendment/Precedence/
Scope/Versioning/Compliance review/Downstream artifacts, MAJOR/MINOR/
PATCH versioning, Foundry reference) present in CONSTITUTION.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants