Files
amethyst/.claude/skills/amy-expert/references/extraction-recipe.md
T
Claude 9b9084ffaf docs(cli): split amy docs by responsibility + add amy-expert skill
Separate the single DEVELOPMENT.md into focused docs per audience:

- cli/README.md — trim agent/interop sub-sections; user-facing contract,
  commands, data-dir, troubleshooting only.
- cli/DEVELOPMENT.md — pared down to architecture, how-to-add-a-command,
  output conventions, testing, housekeeping.
- cli/ROADMAP.md (new) — north-star, parity matrix, ordered milestones,
  non-goals. The live checklist for CLI feature parity.
- cli/plans/2026-04-21-cli-distribution.md (new) — packaging strategy
  (Homebrew / winget / Scoop / deb / rpm / AUR / AppImage).
- commons/plans/2026-04-21-event-renderer.md (new) — cross-cutting
  renderer design owned by commons, consumed by cli + desktop + android.

Agentic routing:

- .claude/skills/amy-expert/SKILL.md (new) — routing triggers + the
  five hard rules (thin-layer, JSON contract, non-interactive,
  data-dir-is-world, extract-before-adding).
- references/command-template.md, extraction-recipe.md,
  output-conventions.md — bundled copy-paste references.

Root .claude/CLAUDE.md:

- Adds cli/ to the module list with its sharing rule.
- Registers amy-expert in the skills table.
- Documents per-module plans/ convention; freezes docs/plans/.

https://claude.ai/code/session_01BQ5ZHwa8BAgEQ9zeM4CKhW
2026-04-22 00:23:00 +00:00

5.5 KiB

Extract-from-Android recipe

The single most common reason an Amy feature request stalls: the logic it needs lives in amethyst/ with Android-only imports. You cannot call it from cli/. You have to move it first.

This recipe is how.

When to extract

Before writing a new command, ask:

  1. Does the piece of logic I need exist in quartz/ or commons/?

    • Yes → use it.
    • No, but it's in amethyst/ → extract. This file.
    • No, it doesn't exist anywhere → design it in commons/ directly. Write a plan doc under commons/plans/ if it's a new subsystem.
  2. Never duplicate amethyst/ logic into cli/. That's a debt you will pay later when the Android caller drifts.

Recipe

Land this as its own commit, before the commit that adds the CLI command.

Step 1 — Find the class

grep -rn "fun followUser\|class FollowListManager" amethyst/src/main/java/

Identify the minimum unit to move. Sometimes it's a whole file, sometimes one function. Prefer the smallest unit that makes the command possible.

Step 2 — List Android-only dependencies

Walk the imports. The usual offenders:

Dependency Treatment
android.content.Context Often accidental — inline if only used for logging or preferences. Otherwise, invert as constructor arg.
android.content.SharedPreferences Abstract behind an interface in commons/; Android actual uses SharedPreferences, JVM actual uses a JSON file.
androidx.work.WorkManager Rarely shareable — if the CLI needs it, simplify the flow to not require background scheduling.
android.util.Log Replace with quartz PlatformLog (already multiplatform).
android.graphics.Bitmap Almost never needed by Amy. Keep in Android and split the function.
android.net.Uri Replace with kotlinx.io path types or a plain String.
androidx.compose.* Must stay out of commons/commonMain unless you're in a Compose-Multiplatform module. Amy doesn't depend on Compose.

Step 3 — Pick a migration strategy per dependency

  • Inline-able. One call, trivial. Delete it.
  • Platform-abstractable. Add expect in commons/commonMain/ + actual in commons/androidMain/ + actual in commons/jvmMain/. See kotlin-multiplatform skill for the mechanics and for the jvmAndroid source-set pattern used throughout this repo.
  • Inversion-of-control. Take the Android dependency as a constructor arg with an interface type. Amy supplies a JVM flavour; Android supplies the Context-backed one.

Step 4 — Move the code

# Target location depends on what it is:
# - Protocol → quartz/src/commonMain/kotlin/…
# - Business logic → commons/src/commonMain/kotlin/…
# - UI → commons/src/commonMain/… (needs Compose Multiplatform)
git mv amethyst/src/main/java/com/.../FollowListManager.kt \
       commons/src/commonMain/kotlin/com/.../FollowListManager.kt

Update package declarations. Run ./gradlew spotlessApply.

Step 5 — Update the Android caller

The amethyst/ caller now imports from the new location. Often this is the only code change visible in the Android app.

If the Android caller was using a concrete Android-backed dependency, it now supplies that concrete dependency explicitly.

Step 6 — Add a JVM test

In commons/src/commonTest/kotlin/… or commons/src/jvmTest/kotlin/… (depending on what the code exercises), add a test that runs on JVM. This guards against Android-only imports sneaking back in, and it's the only way to be sure Amy can now call the code.

Step 7 — Commit

Single commit, descriptive:

refactor(follow): extract FollowListManager to commons for CLI reuse

Move FollowListManager from amethyst/model/nip02FollowLists/ to commons/commonMain/.../followLists/. Android's SharedPreferences dependency is inverted behind FollowListStore (interface); Android keeps the SharedPreferences-backed actual, new JvmFollowListStore writes JSON to disk. No behaviour change on Android.

Step 8 — Now add the CLI command

Separate commit. Follows the pattern in command-template.md.

Cautionary notes

  • Don't extract speculatively. Only extract what the current command needs. A feature-complete port can happen later; right now the goal is to unblock one command without adding surface area you don't have a second caller for.
  • Android is allowed to keep side-effects. Notifications, background services, Intents, camera, permissions dialogs — those stay in amethyst/. Amy's job isn't to replicate UX, it's to exercise the protocol underneath.
  • Check the consumers. Sometimes the "logic" you want is already partially in commons/, and the amethyst/ class is just a thin wrapper. In that case, re-use the commons/ class directly and delete the wrapper or keep it if Android genuinely needs it.
  • Tests first if you're nervous. Copy the existing amethyst/-side test (if any), make it JVM-only by removing Android imports, and watch it pass after the move.

Red flags during extraction

Stop and reconsider if:

  • The Android class is 1000+ lines. Extract only the piece the CLI needs; leave the rest for a follow-up.
  • You need Context in 30 places. It's probably being used as a grab-bag; sort by actual use (strings, preferences, services, …) and abstract those individually.
  • You find yourself writing expect class with a dozen methods. A fine-grained interface is usually clearer than a monolithic expect-actual.
  • You're about to add a Compose import to cli/. Stop.