With the Gradle distribution now pre-seeded in the web sandbox
(session-start.sh) and dependencies resolving through the proxy, spotlessApply
no longer fails for infra reasons. Remove the network-error skip branch so any
spotlessApply failure blocks the push: a failure now means a real
formatting/compile error, and the narrow regex can no longer mask one.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7bocRTSPyXwz6zVMzDbKR
Claude Code Web routes github.com through a git-only proxy, so the Gradle
wrapper's distributionUrl (services.gradle.org, which 307-redirects to a
github.com release asset) can't bootstrap — it 403s even at Full network
access, and `./gradlew` fails before running any task.
Seed the pinned distribution in the existing web-only SessionStart hook,
reusing the same idempotent curl-download pattern already used there for the
Android SDK and Kotlin/Native deps: skip if already installed, else fetch
Gradle's OFFICIAL sha256 (served from services.gradle.org, reachable here),
download the zip from a mirror, and verify before extracting so a tampered or
wrong mirror file is rejected and never executed. The wrapper cache dir is
derived as base36(md5(distributionUrl)) so it survives version bumps.
Also pin distributionSha256Sum in gradle-wrapper.properties as defense in
depth: Gradle then verifies any distribution it installs (mirror-seeded, CI,
or local) against the known-good hash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7bocRTSPyXwz6zVMzDbKR
The pre-push spotless gate already skips itself (warn + exit 0) when
`./gradlew spotlessApply` fails for infra reasons, so a restricted sandbox
can't strand a push over formatting. But its detection regex only covered
dependency-resolution/network errors, not the case where the Gradle wrapper
can't download the pinned distribution itself — e.g. an egress-proxy 40x on
`gradle-<ver>-bin.zip`. That surfaced as a hard BLOCK on an XML-only change
even though CI's spotlessCheck would pass.
Add the distribution-bootstrap markers (the `gradle-<ver>-bin.zip` URL, the
Java `Server returned HTTP response code` download exception, and "could not
install gradle") to the infra-skip regex. These strings only appear when
gradlew failed before running any task, so they can't mask a real
formatting/compile failure (verified: a spotlessKotlinCheck violation still
blocks).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157AWJNEzybTuegstbWiQ6B
The one-shot/high-level relay ops (fetchAll, fetchFirst, fetchAllPages,
publishAndConfirm, count, negentropy sync/reconcile, …) are INostrClient
extension functions spread across ~8 files with no index, so they don't surface
under "usages of NostrClient" or in completion — easy to miss and re-implement
(as just happened with a bespoke fetchRaw duplicating fetchAll).
- Add accessories/README.md cataloging each public extension with a one-line
"use when".
- CLAUDE.md (Feature Workflow): point at that package/README before hand-rolling
a subscribe/REQ/publish loop.
- relay-client skill: add a Related note steering headless/one-shot callers to
the accessories instead of Subscribable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgL1WTV4Hkp2uuXcUHCHGt
New :relayBench module that boots relay implementations as real external
processes under equivalent setups (persistent storage, sig verification on,
no auth) and compares them on the same corpus:
- Ingest: receipt->queryable-by-REQ latency (publish on one connection,
hammer-poll REQ{ids} on another), OK-ack latency, and pipelined corpus
replay throughput over N connections.
- Queries: client-realistic filters derived from the corpus (home feed,
thread, notifications, profiles, hashtag, by-ids, ...), time-to-EOSE
percentiles plus aggregate events/sec under concurrency; result-set
counts are cross-checked between relays and mismatches flagged.
- NIP-77 negentropy sync between every relay pair: 80%/80% slices with 60%
overlap, reconcile timing/rounds/wire-bytes per server side, delta
transfer, steady-state identical-set reconcile, convergence verified.
- Storage footprint after full ingest.
Corpora (all cached as NDJSON + manifest with a sha256 id fingerprint so
results are comparable across runs and machines):
- synthetic (default): deterministic to the byte — seeded keys, fixed
timestamps, seed-derived BIP-340 aux nonces — with a realistic social
shape (zipf authors, threads, reactions, reposts, zap request/receipt
pairs, hashtags);
- the checked-in real dump (quartz test fixture, ~31k unique 2024 events);
- external dumps (NDJSON or JSON array, gzip sniffed by magic bytes),
e.g. the 2.1M contact-list archive, with --max-event-bytes/--max-tags
raising both the corpus filter and the strfry config together;
- live download from public relays.
Every source runs through the same preparation: dedup, drop unsigned/
ephemeral/kind-5, enforce relay ingest caps, parallel Schnorr verify,
chronological sort.
relayBench/run.sh is the one-command entry point: builds geode + harness,
resolves strfry (STRFRY_BIN, PATH, or source build into .cache), runs the
suite and renders an ANSI report with per-metric bars and winners, plus
report.md and results.json under relayBench/results/<timestamp>/.
The harness client disables Nagle (TCP_NODELAY): with the JDK default, a
REQ following the previous round's CLOSE stalls a full delayed-ACK
interval and every latency floors at ~44 ms against both geode and strfry
(verified: ~0.3 ms with it off).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeoCvXnTxsKzqurkmjdC46
Czech was consolidated onto values-cs in PR #3461 (values-cs-rCZ deleted,
cs: cs languages_mapping). Update the reference-locale table, diff commands,
and prose accordingly, and note that the remaining locales keep their
region-qualified dirs until consolidated the same way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhEN93aKyQCFVpLUgRNXid
Add a "NIP-11 Relay Information Document" section to the quartz-integration
skill (the guide AI consults when using Quartz in external projects),
covering the relayInformation { } DSL, serving it over
application/nostr+json, the nested limitation/fees/retention builders, and
the limitation(RelayLimits) sync overload. Also add Quick Reference rows
and extend the skill trigger description to the run-a-relay case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C64Yy2d3na7Y28u7GRrHMX
AI agents were hand-rolling their own hex/npub/nprofile/NIP-05 resolvers
(e.g. a bespoke `resolveObserver` with its own well-known fetch and JSON
parse) instead of using `resolveUserHexOrNull` in
`quartz/nip05DnsIdentifiers/`, which already handles every identifier form.
Add discoverable explainers so the canonical functions surface before an
agent reaches for a hand-rolled version:
- New reference `references/nip05-identifiers.md`: full API surface
(`resolveUserHexOrNull`, `Nip05Client`, `Nip05Id`, `Nip05Parser`,
`KeyInfoSet`, Namecoin `.bit`, `OkHttpNip05Fetcher`) plus the
hand-rolled anti-pattern to avoid and the ✅ replacement.
- SKILL.md: new "Resolving User Input to a Pubkey" section, a
When-to-Use bullet, Bundled Resources + Quick Reference rows, and the
identifier-resolution trigger added to the skill description.
- nip-catalog.md: fix the stale NIP-05 entry (referenced a nonexistent
`Nip05Verifier.kt`) to point at `UserHexResolver.kt` / `Nip05Client.kt`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bb8txetygxSnLXHJhPZSwH
Add a PreToolUse hook (.claude/hooks/pre-push-spotless.sh, wired in
.claude/settings.json) that runs spotlessApply before a git-push subcommand
or the create_pull_request MCP tool. If spotless reformats tracked Kotlin,
or reports a non-autofixable lint, the call is blocked so the fix is
committed first -- turning CI's spotlessCheck failure into an in-session
block. Gradle infra/network failures warn and allow, leaving CI as the
backstop. Boundary detection tokenizes the command so the words appearing
in a commit message do not trip the gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015nWg1Wwq5xCE7kPFzWY95o
Same discoverability treatment as the Hex helpers: KDoc on the reusable,
heavily-used primitives external integrators and AIs kept missing, plus
skill coverage.
- TimeUtils: object + key-fn KDoc emphasizing Unix *seconds* (created_at)
vs the millisecond nowMillis() exception.
- RandomInstance: object + per-fn KDoc (secure random; use over kotlin.random).
- sha256(): KDoc pointing event-id work to EventHasher.
- EventHasher: object + fn KDoc on canonical id serialization / verification.
- StringUtils: KDoc on the allocation-free case-insensitive matchers + DualCase.
- Bech32: "use NIP-19 helpers unless you need a custom prefix" pointer; KDoc
on bechToBytes.
- quartz-integration skill: new "Everyday utilities" section (time/random/
hashing/bech32/base64) + quick-reference rows.
- nostr-expert skill: "Core Utilities" section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HM5uueF4a17umGpjC8wcz
AI agents integrating Quartz were re-implementing hex encoding or pulling
in third-party codecs because the built-in helpers weren't discoverable.
- Add KDoc to the `HexKey` typealias, its extension functions
(`toHexKey`/`hexToByteArray`/`hexToByteArrayOrNull`/`isValid`) and the
`PUBKEY_LENGTH`/`EVENT_ID_LENGTH` constants.
- Add KDoc to the `Hex` object and its public API
(`isHex`/`isHex64`/`decode`/`encode`/`isEqual`).
- Add a dedicated "Hex utilities" section + quick-reference rows to the
quartz-integration skill, and fix the wrong `HexKey.decodeHex(hex)`
snippet (no such API) to the real `hex.hexToByteArray()`.
- Add a "Hex Encoding" section to the nostr-expert skill.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HM5uueF4a17umGpjC8wcz
Adds a `.claude/skills/ngit-pr` skill and a pointer from CLAUDE.md so
agents know how to create, review, revise, and merge PRs in this repo.
This repo can publish a PR two ways and the difference is easy to get
wrong: a GitHub remote (canonical `main`, normal `gh` flow) and a
git-over-nostr remote (`ngit`, where PRs are nostr proposals on
gitworkshop.dev and a push fans out to GitHub + the GRASP servers).
The skill identifies remotes by URL (names vary per clone; a collaborator
may have only one), explains which path to use, and documents the
three-mains alignment gate (GitHub vs the lagging nostr `main` vs local
`main`) that the nostr create/revise/merge flows all depend on — the
thing that otherwise causes rejected pushes and revisions that never
appear on gitworkshop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `:napplet` sandbox runtime (NappletHostActivity, NappletContentServer,
NappletIpc, NappletKeyActions) now lives in a new :nappletHost Android library
that depends only on :commons + :quartz — NEVER :amethyst. So the sandbox code
is compile-time incapable of importing Amethyst.instance / LocalCache / Account,
turning the "two-process, no secrets in the sandbox" rule from a convention into
a build-graph guarantee.
- New module + NappletHostContract (Intent-extra keys + broker service FQN), so
the launcher (amethyst) and activity (module) share the launch contract with no
dependency cycle. The activity binds the broker by class name.
- Capability labels for the "what it can access" sheet are resolved by the
launcher (which has app resources) and passed in, so the module needs no
capability string resources. Host-only strings moved into the module.
- amethyst depends on :nappletHost; the broker-side (NappletBrokerService,
gateways, NappletLaunchRegistry) stays in :amethyst. Manifest declares the
activity by FQN (keeps @style/Theme.Amethyst resolvable).
- Docs updated (CLAUDE.md + security plan).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
Add a heads-up to the Amethyst Application KDoc and .claude/CLAUDE.md that the
app runs in two OS processes (main + :napplet), that Android reuses the single
Application class in both, and that statics/objects (Amethyst.instance,
LocalCache, NappletLaunchRegistry) are per-process — so code must not assume
`instance` exists off the main process or share singletons across the boundary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
Update the amy docs to reflect the new command surface:
- cli/README.md — add the Cashu (NIP-60/61), Relay management (NIP-86
admin), and Run-a-relay (serve) sections; document fetch's nip19/nip05
code mode and key validate.
- cli/DEVELOPMENT.md — add cashu.json to the on-disk layout and pin the
command-family --json contracts (cashu keys/error codes + pointer to the
cashu plan, admin {relay,method,result}, serve startup object).
- .claude/skills/amy-expert/SKILL.md — extend the "where things live" tree
with AdminCommand/ServeCommand/cashu/, the commons/cashu + relayManagement
shared modules, and the allowed :geode dependency.
- .claude/CLAUDE.md — note cli may depend on :geode (for serve), never on
:amethyst/:desktopApp.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SapGdtAc1j7woifoCZ9fY
Four mechanical simplifications to amy with no change to the public
CLI/JSON contract:
1. Drop the Commands.kt pass-through layer. Main.kt now calls each
command object directly; the file is repurposed into Router.kt,
holding a single shared `route(name, tail, usage, routes)` helper.
2. Replace the `Context.open(dataDir)` + `try { } finally { ctx.close() }`
boilerplate (~46 sites) with `Context.open(dataDir).use { ctx -> }`
now that Context is AutoCloseable.
3. Remove the reflection-based `storeIsInitialized()` in Context; track
the lazy event store via `Lazy.isInitialized()` instead.
4. Route every `*Commands.dispatch` through the `route` helper, dropping
the repeated empty-check + unknown-verb `when` boilerplate.
Net -282 lines. Docs (cli/DEVELOPMENT.md, amy-expert skill + template)
updated to the new wiring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjEvS812aPLZ6nM2XLobzF
Make BUILDING.md everything a fork needs to build and release, and add a
maintainer-facing ship checklist.
- BUILDING.md: full CI secrets inventory (Android keystore vs GPG/Maven, with
generation commands), a distribution-channels table (GitHub, Maven, Play,
F-Droid pull, Zapstore, Homebrew/Winget), and a "generated & vendored
artifacts" section (Material Symbols subset, Arti native libs).
- RELEASE_OPS.md (new, repo root): Amethyst-specific shipping — pre-tag
checklist, per-channel rollout (manual Play AAB, Zapstore `zsp publish` with
our nsec, F-Droid build-from-source), the push-notification server
(amethyst-push-notif-server), secret ownership, and verification.
- README.md: slim the Deploying section to point at both docs, fix the stale
quartz dependency version, and add a Maven Central badge plus JitPack
snapshot docs.
- android-expert skill: drop the duplicated build-config block, delegate to
gradle-expert, keep only the Android-specific flavor note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump the app version to 1.12.1 (versionCode 449) in the catalog, which now
drives every module. Add the v1.12.01 changelog (Health Connect workouts,
share-as-image, immersive system bars, deterministic Tor Active fix) and index
it. Refresh the quartz-integration skill's artifact-version references.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.