Second audit pass over the remaining skills (amy-expert, auth-signers,
find-*, nostr-expert, quartz-integration, vendored technique skills),
verifying every concrete claim against the code:
- auth-signers: bunker login goes through NostrSignerRemote.fromBunkerUri
+ connect(), not the nonexistent RemoteSignerManager.connect(url)
- nostr-expert: NIP count 57 -> 80+; replace invented Nip44v2/Nip19
static APIs with the real Nip44 facade, ByteArray bech32 extensions,
entity create() helpers, and Nip19Parser.uriToRoute()?.entity
- nip-catalog: heading counts corrected to 87 standard + 23 experimental
packages with a ground-truth pointer
- quartz-integration: NIP-19 example rewritten for ParseReturn.entity;
Event Store is commonMain (all platforms), not Android-only, with the
real store.sqlite.EventStore import and suspend query<T> API
amy-expert, find-missing-translations, find-non-lambda-logs, the rest of
auth-signers, and the vendored technique skills audited clean.
https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
Audit pass that verified every concrete claim in .claude/ against the
repository:
- account-state: Account.kt no longer exposes followListFlow-style
StateFlows; document the state-object pattern (kind3FollowList,
muteList, bookmarkState, ... each exposing .flow) and rewrite the
catalog reference from the real Account.kt
- feed-patterns: filter bases (FeedFilter, AdditiveFeedFilter,
ChangesFlowFilter, FeedContentState) moved to commons/ui/feeds;
ui/dal keeps AdditiveComplexFeedFilter/FilterByListParams plus
back-compat typealiases; fix recipe example signatures
- relay-client: add nip17Dm/, eoseManagers and subscriptions entries
to the layout tree
- gradle-expert: 4-module claim -> 10 modules; refresh compose/kotlin/
BOM versions; rewrite dependency graph with verified edges for cli,
geode, quic, nestsClient, quic-interop, benchmark
- desktop-expert: drop drifted Main.kt line numbers; sidebar is the
custom MainSidebar in DeckSidebar.kt, not a NavigationRail in
SinglePaneLayout.kt
- android-expert: compileSdk/targetSdk 36 -> 37, versionName via
generateVersionName()
- kotlin-expert: remove reference to nonexistent commit 258c4e011
- CLAUDE.md: add missing geode/benchmark/quic-interop modules
- desktop-run: packageRpm + correct binaries output path; extract.md:
drop duplicated find clause
- session-start.sh: /home/user/Amber fallback was a copy-paste from
another repo; fall back to CLAUDE_PROJECT_DIR
https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
- CLAUDE.md: drop the 5-step skill-approval workflow (skills auto-trigger
and the approval loop blocked autonomous sessions), condense Verify-Don't-
Guess to the repo-specific tooling pointers, remove references to the
uncommitted /bugfix and /investigate skills, and replace the mandated
emoji survey matrix with one-line guidance
- android-expert / desktop-expert: add missing YAML frontmatter so the
skills carry trigger descriptions and can actually auto-invoke
- extract.md: fix stale shared-ui/ module name -> commons/
- delete skills/quartz-kmp.md breadcrumb (migration long complete)
- gate the Stop spotlessApply hook on modified Kotlin files via
hooks/stop-spotless.sh so Q&A-only turns skip the Gradle run
- condense core-skills-plan.md to a historical changelog
https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
The Step 2.5 git "sync-timestamp" heuristic (skip keys added before the
last Crowdin export commit) produced false negatives: a key added shortly
before an export that translators hadn't reached yet is genuinely missing,
but the filter classified it as "Crowdin already decided" and dropped real
work. Replace it with the raw on-disk diff reconciled against the Crowdin
web UI's untranslated count; source-identical entries are skipped by
inspection instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the commons module's purpose, source-set layout, and the CLI-safe vs
UI boundary in commons/ARCHITECTURE.md, then clean up the clearest package
overlaps that had accumulated:
- merge duplicate util/utils -> util (all source sets)
- unify service/services -> service (jvmAndroid)
- move data/UserMetadataCache -> model/cache
- fold compose/ into ui/ (ui/article, editor, elements, layouts, markdown,
nip53LiveActivities, and Compose helpers in ui/state + ui/text)
- move ProfileBroadcastBanner composable into profile/ui
All changes are whole-file/whole-package moves with import rewrites; no logic
changed. The chess logic/UI split is documented as deferred debt (it needs
file-level surgery, not moves). Marks docs/shared-ui-analysis.md superseded.
https://claude.ai/code/session_01KXLzsvx9Gyrm3Yz4Rims55
Re-add the nostr-protocol/nips reference (dropped in the cleanup) as a
one-liner in the overview, tied to the /nip command so it points at the
exact spec file rather than the bare index.
- Add a standing instruction to test hypotheses before diagnosing
(state guesses as guesses, reproduce-first, predict-then-run).
- Remove content duplicated by the harness-injected skill list (skills
tables, Commands section) and generic expect/actual examples.
- Condense the Feature Workflow (removed the duplicated share/keep-native
tables and the hardcoded grep block) and the skill-handshake example.
- Fix stale facts: drop pinned tool versions (now point to
libs.versions.toml) and correct the nestsClient tree comment to match
the overview (production runs on moq-lite).
The find-missing-translations skill only diffed <string name=, so missing
<plurals> resources slipped through. Updated Steps 2, 2.5, 3 to diff
<plurals> independently and added 3 missing music playlist plurals
across cs/de/sv with correct CLDR category coverage (Czech: one/few/many/other).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A "missing" key in values-<locale>/strings.xml is not always actionable:
Crowdin omits source-identical translations on export (translator chose
"use English" for brand terms like "Nowhere X", loanwords like "Apps",
or version prefixes like "v%1$s"). Adding source-identical fallbacks
locally is noise that the next Crowdin sync strips again; Android already
falls back to values/strings.xml at runtime.
Add a Step 2.5 sync-timestamp filter that uses the latest
"New Crowdin translations by GitHub Action" commit reachable from HEAD as
the cutoff. Keys added to values/strings.xml after that commit are
genuinely new (Crowdin hasn't exported them yet); anything older is
Crowdin's responsibility. The reachable-from-HEAD check survives the
common workflow of deleting the l10n_crowdin_translations branch after
merging.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bundled Material Symbols font ships only the codepoints referenced
from MaterialSymbols.kt. Without regenerating the subset, newly added
icons render as tofu at runtime. Document this as a mandatory step in
CLAUDE.md so agents pick it up automatically.
The :ammolite module contained no production Kotlin/Java sources (just
a manifest, build.gradle, and proguard stubs) and no module in the
codebase imports com.vitorpamplona.ammolite.*.
Removes:
- ammolite/ directory (5 files)
- :ammolite project include in settings.gradle
- implementation project(':ammolite') from :amethyst
- androidTestImplementation project(':ammolite') from :benchmark
- :ammolite:testDebugUnitTest from CI workflow and pre-push hook
- -keep class com.vitorpamplona.ammolite.** rules from
:amethyst, :commons, and :desktopApp proguard files
- Stale references in CONTRIBUTING.md, CLAUDE.md, and the
gradle-expert skill dependency-graph doc
Small build-graph win: one fewer module to configure, compile, lint,
and spotless-check on every build, and one fewer unit-test target in
both CI and the local pre-push hook.
Adds technique-oriented Compose and Kotlin skills from chrisbanes/skills
to complement the existing codebase-oriented skills. Codebase skills cover
"where is X in Amethyst"; these cover "what is the correct Compose/Kotlin
design". Descriptions tagged as technique-layer and cross-links trimmed to
the vendored subset. CLAUDE.md skill table updated with a dedicated section.
https://claude.ai/code/session_01QE8CjoJXUt7RKtwGgzeMrb
Add Step 3 to the audit: catch-block Log.w/e calls that interpolate
${e.message} but don't pass `e` lose the stack trace and log "...: null"
when the exception's message is null. Document the throwable-overload
fix alongside the existing lambda-overload conversion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The custom C secp256k1 implementation under quartz/src/main/c/ was never
wired into Gradle (no externalNativeBuild block) and only existed for the
3-way benchmark + cross-validation test against ACINQ on JVM/Android.
The C library has been split out to vitorpamplona/libschnorr256k1{,-kmp},
so the in-repo copy is dead weight.
This change replaces it with the published Maven Central artifact
`com.vitorpamplona:schnorr256k1-kmp:1.0.0`, scoped to test/benchmark
configurations only — production crypto continues to use ACINQ
secp256k1-kmp on JVM/Android and the pure-Kotlin Secp256k1 on native.
The Android AAR ships libschnorr256k1_jni.so for arm64-v8a and x86_64,
so the Android benchmark now exercises the C row automatically (no more
manual build_android.sh). On JVM the .so still has to be installed by the
developer; the cross-validation test and triple-benchmark gracefully skip
the C row when System.loadLibrary fails, matching prior behavior.
Verified on JVM: all 13 secp256k1 jvmTest classes pass (188 tests), and
ACINQ vs pure-Kotlin benchmark numbers match the documented baseline
within sandbox noise (verifySchnorr ~15k ops/s, signSchnorr cached
~33k ops/s, pubkeyCreate+Compress ~36k ops/s).
Removes ~5,100 LOC: quartz/src/main/c/ (4,500), Secp256k1InstanceC.*
expect/actual shim (560), Secp256k1C JNI declaration object.
https://claude.ai/code/session_01KnvpK2amcVZKfFiZJvHjVe
Production speaker path now runs on moq-lite, so connectNestsSpeaker
exchanges real moq-lite framing with the nostrnests reference relay.
Transport layer:
- WebTransportSession.incomingBidiStreams() — peer-initiated bidi
flow. moq-lite publishers receive Announce + Subscribe bidis from
the relay (rs/moq-lite/src/lite/publisher.rs:40 uses
Stream::accept(session)), so the abstraction grew the
accept-bidi-from-peer surface.
- WebTransportSession.openUniStream() — locally-opened uni stream
for group push (rs/moq-lite/src/lite/publisher.rs:338 uses
session.open_uni()).
- :quic WtPeerStreamDemux StrippedWtStream now carries optional
send/finish closures. The demux takes the QuicConnectionDriver
so wakeups fire after each app-level write on a peer-initiated
bidi.
- FakeWebTransport now exposes incomingBidiStreams + openUniStream
directly; the openPeerUniStream test helper went away (production
flow covers it).
Session layer:
- MoqLiteSession.publish(suffix) — claims a broadcast suffix and
lazily launches a relay→us bidi pump. ControlType=Announce reads
AnnouncePlease, replies Active(suffix). ControlType=Subscribe reads
body, replies SubscribeOk, registers the inbound subscription.
- MoqLitePublisherHandle — startGroup / send / endGroup / close
semantics. send opens a uni stream per group with DataType=0 +
GroupHeader and pushes varint(size)+payload frames. close emits
Announce(Ended) on every active announce bidi, FINs the uni.
Application layer:
- AudioRoomMoqLiteBroadcaster — sibling of AudioRoomBroadcaster but
drives MoqLitePublisherHandle (keeps IETF broadcaster intact for
its unit tests).
- MoqLiteNestsSpeaker — NestsSpeaker adapter, mirror of
MoqLiteNestsListener on the publish side.
- connectNestsSpeaker now opens a MoqLiteSession (no SETUP) and
returns MoqLiteNestsSpeaker.
Tests:
- 4 new MoqLiteSessionTest cases:
publisher_replies_to_announcePlease_with_active_announce,
publisher_acks_subscribe_and_pushes_group_data_on_uni_stream,
publisher_send_returns_false_when_no_inbound_subscriber,
publisher_close_emits_ended_announce.
Verified :commons:compileKotlinJvm + :amethyst:compilePlayDebugKotlin
both still compile against the swap.
Docs (plans + CLAUDE.md) refreshed to reflect speaker-side landing.
Phase 5d wrapped, so the doc set now reflects "listener path done,
speaker pending":
- nestsClient/plans/2026-04-26-moq-lite-gap.md — new "Implementation
status" section maps phases 5a → 5d to commits, calls out the
speaker side as blocked on a small `WebTransportSession.acceptBidi`
extension (since publisher.rs:40 uses Stream::accept), and points
at the existing :quic primitive (QuicConnection.awaitIncomingPeerStream)
that the bridge can lean on.
- nestsClient/plans/2026-04-26-audio-rooms-completion.md — Phase M1
is no longer "on hold for moq-lite"; manual nostrnests.com
validation should now work end-to-end on the listener path.
- .claude/CLAUDE.md — :nestsClient now hosts both IETF MoQ-transport
and moq-lite Lite-03; production listener path uses moq-lite.
Background research turned up the complete moq-lite (Lite-03) wire
format from kixelated/moq-rs and @moq/lite v0.1.7. Folded the spec
into nestsClient/plans/2026-04-26-moq-lite-gap.md as a phase-5
implementation plan:
- ALPN ("moq-lite-03"); no SETUP/control message in Lite-03 (the WT
handshake IS the handshake)
- Per-request bidi streams keyed by ControlType varint (Announce=1,
Subscribe=2, Fetch=3, Probe=4)
- AnnouncePlease(prefix) / Announce(status, suffix, hops) shape
- Subscribe with priority (raw u8), ordered, maxLatency (ms),
startGroup/endGroup (off-by-one None-encoded), reply Ok/Drop
- Group = uni stream with (DataType=0, subscribeId, sequence)
header followed by varint-length frames until QUIC FIN
- No datagrams; no per-frame envelope beyond size
- Mandatory path normalisation; FIN-as-unsubscribe; RESET_STREAM
for errors
Phase-5a..e implementation plan included (~1 week scope).
Doc + KDoc updates so the IETF MoQ-transport code is correctly
labelled and the moq-lite gap is discoverable from every entry point:
- .claude/CLAUDE.md project description and architecture diagram
- nestsClient/plans/2026-04-26-audio-rooms-completion.md status block
- MoqSession.kt, MoqMessage.kt, MoqObject.kt, MoqCodec.kt KDoc — flag
these as "IETF draft-ietf-moq-transport-17, NOT moq-lite", with
pointers to the gap doc
- NestsConnect.kt — note that step 3 of the listener handshake
(SETUP) does NOT match nostrnests's relay framing
Two new module-local plan docs (per CLAUDE.md's "plans live in the owning
module" rule) and a sweep of stale inline phase references.
quic/plans/2026-04-26-quic-stack-status.md:
Post-mortem of the original docs/plans/2026-04-22 plan. Documents
what shipped vs what was estimated, the actual package layout (~8.5k
LoC, 39 test files, 5 audit rounds), the crypto delegation surface
(Quartz only — no BouncyCastle, no JNI), interop verification status
(aioquic + picoquic; nests not yet), and known deferred items
(STREAM retransmit, Initial-key discard, etc.).
nestsClient/plans/2026-04-26-audio-rooms-completion.md:
Punch list to ship audio rooms end-to-end:
M1 Listener wire-up in Amethyst UI
M2 Multi-speaker audience UX
M3 Foreground service for backgrounded playback
M4 Manual interop pass against nostrnests.com
M5 MoQ publisher path (ANNOUNCE / TrackPublisher)
M6 Capture → encode → publish pipeline
M7 NestsSpeaker API
M8 App polish (reconnect, leave cleanup)
M9 Foreground service for speakers
~6 weeks for full audio rooms; ~2 weeks for listener-only MVP.
Inline doc cleanup:
* Removed "Phase 3a/3c-1/3c-2/3c-3" / "Phase B/C/D-K/L" references
from active code; replaced with "today" or pointers to the
completion plan
* Removed "Kwik-based stub" references; QuicWebTransportFactory and
surrounding docs now describe :quic as the production path
* TlsClient header reflects non-null certificateValidator + the
JdkCertificateValidator / PermissiveCertificateValidator split
* SendBuffer header documents the best-effort no-retransmit mode
explicitly (was hidden behind a "Phase L will fix this" note)
* MoqMessage / MoqObject / MoqSession reflect listener-side as
shipped + publisher-side as Phase M5
CLAUDE.md:
* Module list now includes :quic and :nestsClient (was 5 modules,
now 7)
* Architecture diagram + sharing philosophy explain what each new
module owns
No production behaviour changes; doc + comment-only edits. Tests green.
https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
USAGE.md was the better README — entry-point users want examples and
quick start, not the public-API contract. Flip them and refresh the
amy-expert skill so it matches the post-refactor reality.
cli/README.md (was USAGE.md):
- Install, quick start, seven worked examples, full command reference,
output modes, multi-account workflows, agent recipes, troubleshooting.
- Cross-refs point at DEVELOPMENT.md for the contract / architecture
and ROADMAP.md for what's coming.
cli/DEVELOPMENT.md absorbs the old README's architecture sections:
- New "Public contract" section at the top — the stable promises
(text-default + --json contract, stderr for humans, exit codes,
~/.amy/ as the world).
- "Local event store" deep-dive with the cache-helper API.
- "Relay routing" rules table.
- "Full on-disk layout" tree with annotations.
cli/ROADMAP.md, cli/USAGE.md:
- ROADMAP cross-refs collapsed (no more USAGE.md row).
- USAGE.md deleted — content lives in README now.
.claude/skills/amy-expert refreshed end-to-end:
- SKILL.md description + Rules 2 and 4 rewritten for the dual-output
contract (text default, --json opt-in) and the ~/.amy/ layout.
- "Where things live" listing matches the current source tree
(Output.kt, Aliases.kt, UseCommand.kt, secrets/, all the new
command files).
- "Common mistakes" lists the new traps: don't read user.home
directly, don't add a global flag that collides with subcommand
--name, don't use Json.writeLine (it's gone).
- references/command-template.md uses Output.emit / Output.error
(Json.writeLine / Json.error helpers no longer exist).
- references/output-conventions.md rewritten around the dual-mode
contract — same JSON shape rules, but framed as "this is what
--json emits" rather than "this is stdout."
After the harness move from tools/marmot-interop/ to cli/tests/,
three doc locations were still describing the old layout:
- cli/DEVELOPMENT.md § Testing — the "round-trip" row pointed at a
non-existent `cli/src/test/resources/scripts/`, and the "interop
with other clients" row claimed it was out of scope. Both now name
`cli/tests/marmot/` and `cli/tests/dm/` with what each covers, and
the interop-script template points at the concrete test files
rather than re-inventing a smaller example.
- cli/README.md — the "For an interop-test script template" pointer
now links to `cli/tests/README.md` alongside the DEVELOPMENT.md
section.
- .claude/skills/amy-expert/SKILL.md — the "Where things live" tree
omitted the new `tests/` subtree entirely. Added it with a
one-line description per suite. Also extended the wire-up checklist
with a step 7: add a harness case when a new verb changes observable
wire behaviour.
No content changes elsewhere; just path corrections.
The project's compileSdk/targetSdk was bumped to 37 in
gradle/libs.versions.toml, but the web-session hook was still fetching
platform-36 and build-tools 36.0.0. Point it at platform-37.0_r01.zip
and build-tools_r37_linux.zip so Gradle finds the platform it needs
when Claude Code on the web bootstraps.
macosArm64 target cannot be enabled until negentropy-kmp publishes a
macosArm64 artifact — commented out with explanation. Reverted run_all.sh
back to Linux-only for K/Native benchmark.
Added Kotlin/Native toolchain dependencies (GCC sysroot, LLDB, LLVM,
libffi) to session-start.sh so K/N compilation works in Claude Code
remote environments where Gradle's own downloader fails through the
proxy.
https://claude.ai/code/session_01WSNE6QKiYM2ZutQD2UihCW