Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .claude/skills/verify-architecture-docs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
name: verify-architecture-docs
description: Fact-check docs/architecture/*.md against the actual code — catch correctness drift (a doc says X, the code does Y), not just untouched files. Fans out one adversarial checker per doc and writes a drift report.
argument-hint: "[--since <ref> | --doc <path>]"
model: fable
effort: max
allowed-tools:
- Read
- Grep
- Glob
- Agent
- Write
- Bash(git diff:*)
- Bash(git log:*)
- Bash(git merge-base:*)
- Bash(git show:*)
---

# Verify Architecture Docs

ultrathink

You are fact-checking the architecture docs in `docs/architecture/` against the real codebase. The deliverable is **one drift report** at `.claude/plans/<YYYY-MM-DD>-doc-drift.md`. This catches **correctness drift** — a doc that is present but *wrong* — which the path-based upkeep check (the CLAUDE.md rule) cannot.

## Hard rules

1. **No claim without a citation.** Every finding cites a `file.swift:NN` (or command output) that contradicts the doc. If you can't cite it, it's not a finding.
2. **The code wins.** When a doc and the code disagree, the *doc* is wrong — propose the doc fix, never "change the code to match the doc."
3. **Verify contested claims yourself.** If a checker's finding conflicts with CLAUDE.md or another agent, open the cited file and confirm it before recording — never trust a single agent's claim. Stale CLAUDE.md facts are expected; flag them.
4. **Read source in full, not snippets**, when confirming a behavioral assertion.
5. **Don't fix without approval.** The report is the review artifact. Apply doc edits only after the user approves.

## Steps

### 1. Determine scope
- No args → all 12 docs (`README.md`, `01`–`10`, `features/README.md`).
- `--doc <path>` → just that doc.
- `--since <ref>` → only docs whose watch paths changed since `<ref>`. Map changed paths (`git diff --name-only <ref>...HEAD`) to docs using the "Architecture Docs" trigger list in `CLAUDE.md` and the index in `docs/architecture/README.md`. Include `README.md` / `10-separation-of-concerns.md` only when a *structural/module* change appears (they're cross-cutting syntheses).

### 2. Fan out one checker per in-scope doc (parallel)
Dispatch an adversarial fact-check subagent per doc (read-only). Each one:
- Reads its doc in full.
- For **every concrete checkable claim** — file/dir paths, type & symbol names, numeric/constant values, behavioral assertions — verifies it against the source.
- Returns ONLY evidence-backed discrepancies, each classified **ERROR** (factually wrong) or **GAP** (important omission — high bar; the docs are intentionally concise), formatted: `[ERROR|GAP] § "<quote>" — <what's wrong> — evidence: <file:NN> — fix: <correction>`. A clean doc returns `CLEAN`.

### 3. Reconcile
- For any finding that conflicts with CLAUDE.md or looks surprising, open the cited file and confirm it yourself.
- Drop false positives; keep only findings you can stand behind with a citation. Watch for agents that *invent* symbols — verify a symbol exists before recording a fix that references it.

### 4. Write the report
`.claude/plans/<YYYY-MM-DD>-doc-drift.md`, grouped by doc, each finding as `claim → cited code (file.swift:NN) → verdict (matches | stale | wrong) → fix`. End with a per-doc count and a TOTAL. If everything is clean, say so plainly.

### 5. Offer to apply
List the fixes and ask whether to apply them. Don't edit the docs until the user says go. If asked to apply, re-verify the corrected docs (a second pass) before declaring zero errors.

## Notes
- This is the **correctness** half of doc upkeep; the CLAUDE.md "Architecture Docs" rule is the **omission** half ("you forgot to touch the doc"). Both are needed.
- The methodology mirrors how these docs were originally verified: parallel per-doc checkers → reconcile contested claims against source → report → fix → re-verify. Two passes converge fast.
- Expect to surface stale facts in **CLAUDE.md** too (its Key Constants / Common Pitfalls cite concrete files and symbols). Note them in the report; don't edit CLAUDE.md without approval.
43 changes: 35 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,32 @@ When adding new information, place it in the appropriate existing section. Remov

---

## Architecture Docs

**`docs/architecture/` is the canonical map of how the app fits together.** Read the relevant doc before changing a subsystem, and update it in the **same change** when your work alters what it describes — treat the doc edit as part of the diff, not a follow-up.

**Update the matching doc when you:**

- Add/remove a package or change a module boundary → `01-modules-and-boundaries.md`
- Change DI, state ownership, or the session/auth lifecycle → `02-state-and-dependency-injection.md`
- Add/restructure a router sheet, stack, destination, or deeplink → `03-navigation.md`
- Add an RPC, change `CallOptions`, or touch request signing/streaming → `04-networking.md`
- Change the schema, a cached table, or the no-migration policy → `05-persistence.md`
- Change intent submission, `VerifiedState`, the give/grab flow, or a funding operation → `06-payments-and-operations.md`
- Change Quarks/fiat math, the bonding curve, a domain model, or a `Validator` → `07-currency-and-domain-model.md`
- Add/rename a FlipcashUI component or design token → `08-design-system.md`
- Change logging, error reporting, analytics, or crypto/scanning plumbing → `09-cross-cutting-concerns.md`
- Add, remove, or materially change a user-facing feature → `features/README.md`
- Change chat/conversation behavior → the doc for the layer you touched: conversation sheets/deeplinks `03-navigation.md`, chat services `04-networking.md`, conversation tables `05-persistence.md`, the user-facing flow `features/README.md`

The full concern→doc index and per-doc watch paths live in [`docs/architecture/README.md`](docs/architecture/README.md). `README.md` and `10-separation-of-concerns.md` are cross-cutting syntheses — revisit them when the structure they summarize shifts, not on routine edits.

**Scope (mirror the docs' own style):** describe *structure and intent*, not every line — usually a one-line table row or a corrected sentence is the whole change. If a doc and the code disagree, the code wins — fix the doc. Don't add per-doc "last updated" stamps; git history is the timestamp.

**Verifying correctness:** this rule catches *missing* updates only. To check whether a doc is actually *correct* against the code, run `/verify-architecture-docs` — it fact-checks each doc's claims against the source and writes a drift report. Run it after large changes or periodically.

---

## Behavior & Approach

### Working Style
Expand Down Expand Up @@ -602,14 +628,15 @@ Database:
### Key Constants

```swift
// USDC
PublicKey.usdc // Main stablecoin mint
PublicKey.usdc.mintDecimals // 6

// Bonding Curve
BondingCurve.startPrice // $0.01
BondingCurve.endPrice // $1,000,000
BondingCurve.maxSupply // 21,000,000 tokens
// Mints
PublicKey.usdf // Main stablecoin mint
PublicKey.mintDecimals // .usdf → 6; every other mint INCLUDING .usdc → 10
// (USDC's real 6-decimal precision comes from MintMetadata.usdc.decimals)

// Bonding Curve (DiscreteBondingCurve — step-based, mirrors the on-chain program)
DiscreteBondingCurve.maxSupply // 21,000,000 tokens
DiscreteBondingCurve.stepSize // 100 tokens/step
DiscreteBondingCurve.tableSize // 210,001 entries (pre-computed .bin resources)
```

### Xcode MCP Server
Expand Down
77 changes: 77 additions & 0 deletions docs/architecture/01-modules-and-boundaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Modules & Boundaries

The codebase is split into 5 SPM packages plus the app target and two app extensions (NotificationService, NotificationContent); a share helper is embedded in the app target rather than being its own target. The split enforces a strict, acyclic dependency layering: business logic never imports UI, UI never imports the network layer, and generated code is isolated behind one package.

## Module inventory

| Module | Type | Purpose |
|--------|------|---------|
| **CodeCurves** | SPM package | Ed25519 crypto — pure-C implementation (zero Swift; the `KeyPair` wrapper lives in FlipcashCore) |
| **CodeScanner** | SPM package | C++/OpenCV "Kik Code" circular-2D encode/decode/scan; bundles OpenCV 4.10 |
| **FlipcashAPI** | SPM package | **All** generated gRPC/protobuf Swift bindings (both backends), 100% generated |
| **FlipcashCore** | SPM package | Business logic, models, gRPC service wrappers, logging, validation, formatters — **no SwiftUI** |
| **FlipcashUI** | SPM package | Reusable SwiftUI/UIKit components + design tokens — **no networking/persistence** |
| **Flipcash** | Xcode app target | Screens, navigation, controllers (Database, Session, Rates…), 3rd-party SDKs |
| **NotificationService** | Xcode extension | `UNNotificationServiceExtension` — resolves contact names on-device, renders communication notifications, prefetches chat transcripts into the shared cache |
| **NotificationContent** | Xcode extension | `UNNotificationContentExtension` — expanded (long-press) chat push panel; lightweight SwiftUI transcript fed from the shared cache |
| **Flipcash/Share** | Files in app target | System share-sheet wrapper (`UIActivityViewController`) for cash links |
| **FlipcashTests** | Xcode test target | Unit/integration tests (Swift Testing) for app + Core |
| **FlipcashUITests** | Xcode test target | Black-box XCUITest smoke tests |

## Dependency graph

```mermaid
graph TD
App["Flipcash app"] --> UI["FlipcashUI"]
App --> Scanner["CodeScanner"]
App --> SQLite["SQLite fork"]
App --> SDKs["Firebase · Bugsnag · Mixpanel · Kingfisher · tweetnacl"]
UI --> Core["FlipcashCore"]
UI --> ChatLibs["ChatLayout · DifferenceKit"]
Core --> API["FlipcashAPI"]
Core --> Curves["CodeCurves"]
Core --> Libs["BigDecimal · PhoneNumberKit · swift-log"]
API --> gRPC["grpc-swift 2.x"]
Core --> gRPC
NSE["NotificationService extension"] --> Core
NSE --> API
NCE["NotificationContent extension"] --> Core
NCE --> UI
```
*Arrows point to dependencies; the graph is acyclic.*

### Third-party dependencies (where they live)

| Dependency | Pin | Used by |
|------------|-----|---------|
| grpc-swift-2 | ≥2.4 (**v2**; resolved 2.4.1) | FlipcashAPI + FlipcashCore (`GRPCCore`) |
| grpc-swift-protobuf | ≥2.0 | FlipcashAPI (`GRPCProtobuf`) |
| grpc-swift-nio-transport (+ swift-nio) | ≥2.0 / ≥2.81 | FlipcashCore (`GRPCNIOTransportHTTP2` — Network.framework TransportServices) |
| swift-log | ≥1.6 | FlipcashCore |
| BigDecimal | ≥3.0.2 | FlipcashCore (quark/fiat math) |
| PhoneNumberKit | ≥4.1.4 | FlipcashCore |
| ChatLayout / DifferenceKit | ≥2.4.2 / ≥1.3.0 | FlipcashUI (chat transcript layout + diffing) |
| SQLite.swift (**dbart01 fork**, `master`) | — | app only |
| Firebase, Bugsnag, Mixpanel, Kingfisher, tweetnacl | various | app only |
| opencv2 (bundled XCFramework) | ~4.10 | CodeScanner only |

The observability/analytics SDKs (Firebase, Bugsnag, Mixpanel) are confined to the **app target** — they never leak into Core or UI.

## Boundary rules (enforced by convention)

- **`FlipcashAPI/**/Generated/` is never hand-edited.** Regenerate via `Scripts/run -a flipcashPayments` / `flipcashCore`. Edits are overwritten. Wrap generated stubs in hand-written service files instead.
- **FlipcashCore is SwiftUI-free** — zero `import SwiftUI`. Models, clients, logging, validation, formatting only. Anything UI-facing crosses into FlipcashUI or the app.
- **FlipcashUI has no business logic** — it imports FlipcashCore for *model types and formatters* only. No network calls, no session state, no persistence.
- **SQLite belongs to the app layer only** — `import SQLite` appears exclusively under `Flipcash/Core/Controllers/Database/` (plus its tests). Core has no SQLite dependency.
- **CodeScanner is used at exactly two call sites** (`CodeExtractor.swift`, `CashCode.Payload+Encoding.swift`); it never reaches Core or UI.
- **Directory placement**: screens → `Flipcash/Core/Screens/`; domain models → `FlipcashCore/.../Models/`; DB row models → `Flipcash/Core/Controllers/Database/Models/`; test support → `FlipcashTests/TestSupport/`.

## Embedded targets

- **NotificationService** — runs in a tight memory/time budget. Server pushes E.164 numbers + positional placeholders; the extension queries `CNContactStore` to resolve names, and renders "Sent You Cash" pushes as `INSendMessageIntent` communication notifications (sender avatar in the banner). For chat pushes it also prefetches the recent transcript over a transient gRPC connection into `NotificationPreviewCache` (App Group `group.com.flipcash.shared`). Imports FlipcashCore + FlipcashAPI only.
- **NotificationContent** — `UNNotificationContentExtension` behind the expanded (long-press) chat push. Renders a fixed-height, bottom-anchored SwiftUI bubble transcript (`NotificationTranscriptView`) — deliberately **not** the in-app `ChatViewController`, whose footprint exceeds the extension's memory budget and gets it jetsam-killed. Reads `NotificationPreviewCache` first; on a cache miss falls back to a live fetch over a transient connection. Imports FlipcashCore + FlipcashUI only.
- **Share** — `ShareCashLinkItem` (`UIActivityItemSource` providing a cash-link URL) + `ShareSheet` (an enum whose static `present(activityItem:completion:)` builds a `UIActivityViewController` and presents it on the root view controller). Embedded in the app target, not a separate bundle.

## Why this matters

The layering is the single biggest structural guarantee in the project: because Core can't import UI and the app can't reach into generated code, a change to a proto, a UI component, or a screen each has a bounded blast radius. Keep new code on the right layer — that's what keeps the graph acyclic.
Loading