@ericrisco/rsc 0.1.47 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: elixir
3
- description: "Use when writing or refactoring Elixir/OTP code — GenServers, supervision trees, processes, pattern matching, mix projects and releases — or when BEAM behaviour misbehaves (restart loops, mailbox growth, timeouts). Triggers: 'write a GenServer', 'design a supervision tree', 'restart strategy one_for_one', 'let it crash instead of try/rescue', 'mix release for production', non-obvious 'my process mailbox keeps growing', 'GenServer call timeout / restart loop', Catalan 'arbre de supervisio en Elixir / processos que peten', Spanish 'arbol de supervision / procesos en Elixir / que es let it crash'. NOT building a Phoenix web app, LiveView, Ecto schemas, controllers or channels (that is phoenix)."
3
+ description: "Use when writing or refactoring Elixir/OTP on the BEAM — GenServers, supervision trees and restart strategies, pattern matching, mix projects and releases — or when processes misbehave (restart loops, mailbox growth, call timeouts). NOT a Phoenix web app, LiveView, Ecto or channels (that is `phoenix`)."
4
4
  tags: [elixir, otp, genserver, supervision, beam, mix, concurrency, functional]
5
5
  recommends: [phoenix, postgresdb, docker]
6
6
  origin: risco
@@ -14,7 +14,7 @@ Default to **pure functions**. Most of your code is data transformation and need
14
14
 
15
15
  ## Decision: do you even need a process?
16
16
 
17
- A process is not "an object". Spawning one to hold a value you could pass as an argument is the most common beginner mistake — it adds a serialization bottleneck and a failure mode for nothing.
17
+ A process is not "an object": spawning one to hold a value you could pass as an argument is the most common beginner mistake, and it adds a serialization bottleneck and a failure mode for nothing.
18
18
 
19
19
  | Situation | Use | Why |
20
20
  |---|---|---|
@@ -28,8 +28,6 @@ Rule: if two pieces of code never run at the same time and share no mutable stat
28
28
 
29
29
  ## The functional core
30
30
 
31
- Each rule below earns its place; the why is one line.
32
-
33
31
  **Match in function heads, not with `if`** — branches become exhaustive and self-documenting, and a non-match crashes loudly instead of silently falling through.
34
32
 
35
33
  ```elixir
@@ -80,13 +78,11 @@ end
80
78
 
81
79
  ## Processes and message passing
82
80
 
83
- Raw `spawn`/`send`/`receive` exists, but in production you almost always want an OTP behaviour (GenServer/Task/Agent) so you get `child_spec`, supervision, and shutdown handling for free.
81
+ Raw `spawn`/`send`/`receive` exists and is fine for a throwaway fire-and-forget where supervision genuinely does not matter, but in production you almost always want an OTP behaviour (GenServer/Task/Agent) so you get `child_spec`, supervision, and shutdown handling for free.
84
82
 
85
83
  - **Link** (`spawn_link`) couples lifetimes: if one dies abnormally, the other gets an exit signal. This is how supervision works.
86
84
  - **Monitor** (`Process.monitor/1`) is one-directional and non-fatal: you get a `{:DOWN, ...}` message but you do not die. Use it when you care *that* something died but should survive it.
87
85
 
88
- Drop to raw processes only for a throwaway fire-and-forget where supervision genuinely does not matter; otherwise reach for OTP.
89
-
90
86
  ## GenServer
91
87
 
92
88
  Split the **client API** (runs in the caller) from the **server callbacks** (run in the GenServer process). Callers never touch state directly.
@@ -124,11 +120,9 @@ defmodule Counter do
124
120
  end
125
121
  ```
126
122
 
127
- Rules, each with its why:
128
-
129
123
  - **Never block in `init/1`.** `start_link` blocks until `init` returns, so a slow `init` stalls the whole supervision tree boot. Return `{:ok, state, {:continue, term}}` and do the work in `handle_continue/2`.
130
124
  - **`use GenServer` auto-defines `child_spec/1`** — you rarely write one by hand; override only to change `:restart` or `:shutdown`.
131
- - **`call` is synchronous (with a 5s default timeout), `cast` is fire-and-forget.** Use `call` when the caller needs the result or backpressure; `cast` when it does not. A flood of `cast`s with no backpressure is the classic mailbox-growth bug — see references/otp-patterns.md.
125
+ - **`call` is synchronous (with a 5s default timeout), `cast` is fire-and-forget.** Use `call` when the caller needs the result or backpressure; `cast` when it does not. A flood of `cast`s with no backpressure is the classic mailbox-growth bug — timeouts and backpressure recipes in `references/otp-patterns.md`.
132
126
  - **Name via `Registry`, not a global atom**, when you have many dynamic instances — atoms are never garbage-collected (see anti-patterns).
133
127
 
134
128
  ## Supervision trees
@@ -164,7 +158,7 @@ end
164
158
 
165
159
  **Let it crash**: do not wrap business logic in `try/rescue` to keep a process alive. Validate inputs at the boundary, then trust the happy path; if an invariant breaks, crashing and restarting from a known-good `init` state is the recovery mechanism. Reserve `rescue` for boundaries where you must convert an exception into a tagged tuple for a caller.
166
160
 
167
- For runtime-spawned children (a worker per connection/job) use `DynamicSupervisor` + `Registry` for lookup. Full recipe in references/otp-patterns.md.
161
+ For runtime-spawned children (a worker per connection/job) use `DynamicSupervisor` + `Registry` for lookup. Full recipe — plus `Task`, `Agent`, `:ets` and how to choose an OTP behaviour — in `references/otp-patterns.md`.
168
162
 
169
163
  ## mix project
170
164
 
@@ -189,7 +183,7 @@ MIX_ENV=prod mix release
189
183
  _build/prod/rel/my_app/bin/my_app start
190
184
  ```
191
185
 
192
- Deps anatomy, environments, env vars, and the umbrella decision live in references/mix-and-releases.md.
186
+ `mix.exs` anatomy, deps, environments, env vars, releases and the umbrella decision live in `references/mix-and-releases.md`.
193
187
 
194
188
  ## Types and modern stdlib
195
189
 
@@ -213,5 +207,3 @@ Deps anatomy, environments, env vars, and the umbrella decision live in referenc
213
207
  ## Verify
214
208
 
215
209
  Run `scripts/verify.sh` from your mix project root (the directory containing `mix.exs`). It checks `mix format --check-formatted` and `mix compile --warnings-as-errors`, and skips cleanly with exit 0 when Elixir/mix is not installed so it never blocks a toolchain-free CI.
216
-
217
- See references/otp-patterns.md (DynamicSupervisor+Registry, Task, Agent, ETS, timeouts/backpressure, choosing an OTP behaviour) and references/mix-and-releases.md (mix.exs anatomy, config vs runtime, releases, umbrellas).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: email-connector
3
- description: "Use when wiring an app to send transactional or bulk email through an HTTP provider (Resend, Twilio SendGrid, Postmark) from server code: welcome/reset/receipt sends, a provider-agnostic sendEmail() seam, React Email or dynamic templates, idempotent retries, 100-cap batch with partial-failure handling, transactional-vs-broadcast stream split, and the bounce/complaint webhook feeding a suppression list. Triggers: 'send a password-reset email with Resend', 'emails double-send when the queue retries', 'switch us off SendGrid onto Postmark without rewriting every call site', 'send a digest to 4000 users and handle the ones that fail', 'el webhook de bounces no actualiza la lista de supresión', 'montar el envío de correos con Resend'. NOT writing the subject line or the open/click growth system (that is newsletter)."
3
+ description: "Use when wiring server code to send transactional or bulk email via Resend, SendGrid, or Postmark: a provider-agnostic sendEmail() seam, idempotent retries, 100-cap batches with partial failures, transactional-vs-broadcast streams, bounce webhooks feeding a suppression list. NOT SPF/DKIM/DMARC inbox reputation (that is `email-deliverability`)."
4
4
  tags: [email, transactional-email, resend, sendgrid, postmark, webhooks, idempotency]
5
5
  recommends: [email-deliverability, newsletter, webhooks, secure-coding, api-connector-builder, automation-flows]
6
6
  origin: risco
@@ -13,30 +13,16 @@ digest — your job is the server code that hands it to a provider, makes it saf
13
13
  to retry, and keeps the suppression list honest. You do **not** own the inbox
14
14
  (SPF/DKIM/DMARC/reputation is `../email-deliverability/SKILL.md`) and you do
15
15
  **not** own the words (subject lines and growth are `../newsletter/SKILL.md`,
16
- launch copy is `../marketing/SKILL.md`). The deliverable is idempotent,
17
- secret-safe, stream-correct server code — not subject lines, DNS records, or
18
- audience strategy.
16
+ launch copy is `../marketing/SKILL.md`). Generic typed clients for *any* REST API
17
+ are `../api-connector-builder/SKILL.md`; deciding *when* a multi-step sequence
18
+ fires is `../automation-flows/SKILL.md`.
19
19
 
20
20
  Stack as of June 2026: `resend` 6.12.4, `@sendgrid/mail` 8.1.6, Postmark via its
21
21
  HTTP API, React Email 5.0 (React 19.2 / Next.js 16, Tailwind 4), Node 20+ / TS.
22
22
 
23
- ## The four non-negotiables
24
-
25
- Every email integration you produce must satisfy these. `scripts/verify.sh`
26
- greps for the first three; the fourth is the design constraint behind them.
27
-
28
- 1. **API key from env, never a literal.** `process.env.RESEND_API_KEY`, not
29
- `re_xxx` / `SG.xxx` / a server token in source. *Why:* a committed key is a
30
- send-as-you credential — instant abuse and reputation burn.
31
- 2. **Idempotency key on every transactional send.** *Why:* queues retry,
32
- serverless functions re-fire, users double-click. Without a stable key one
33
- password reset becomes three.
34
- 3. **Send on the correct stream.** Transactional and broadcast traffic go on
35
- distinct From + subdomain + stream. *Why:* a marketing reputation hit must
36
- never push password resets to spam.
37
- 4. **Verify the inbound webhook signature against the raw body.** *Why:* the
38
- bounce/complaint hook mutates your suppression list; an unverified hook lets
39
- anyone suppress (or un-suppress) your users.
23
+ `scripts/verify.sh` is read-only and greps a target for the four invariants this
24
+ skill exists to hold: env-sourced key, idempotency, a single `sendEmail()` seam,
25
+ and a webhook signature checked on the raw body.
40
26
 
41
27
  ## Step 1 — pick a provider
42
28
 
@@ -47,14 +33,18 @@ greps for the first three; the fourth is the design constraint behind them.
47
33
  | **Postmark** | Pure transactional, deliverability-first | No — self-dedupe via your key + webhooks | Postmark server templates | per-stream | Receipts/resets must never queue behind marketing |
48
34
 
49
35
  Idempotency support changes your strategy, not just your config — see Step 4.
50
- Full per-provider matrix (auth headers, exact signatures, webhook event names,
51
- rate limits) is in `references/providers.md`.
36
+ Full per-provider matrix (auth header, SDK + version, single/batch signatures,
37
+ idempotency model, stream/subdomain model, dynamic-template syntax, webhook event
38
+ names, rate limits, when to pick each) plus a suppression-webhook handler skeleton
39
+ per provider is in `references/providers.md`.
52
40
 
53
41
  ## Step 2 — the `sendEmail()` seam
54
42
 
55
43
  One provider-agnostic function. The rest of the app calls `sendEmail(...)` and
56
44
  never imports a provider SDK. *Why:* swapping SendGrid→Postmark is then one file,
57
- not a grep across every call site.
45
+ not a grep across every call site. That one file reads the key from
46
+ `process.env` — never a `re_…` / `SG.…` / server-token literal, because a
47
+ committed key is a send-as-you credential and burns your reputation with it.
58
48
 
59
49
  ```ts
60
50
  // lib/email/index.ts — the only place a provider SDK is imported
@@ -174,7 +164,9 @@ const html = '<h1>Hi ' + req.body.name + '</h1>'; // XSS + broken layout risk
174
164
 
175
165
  ## Step 4 — idempotency & retries
176
166
 
177
- Derive a deterministic key from the *event*, not the clock. Same event → same
167
+ Every transactional send carries a key, because queues retry, serverless
168
+ functions re-fire, and users double-click — without a stable key one password
169
+ reset becomes three. Derive it from the *event*, not the clock. Same event → same
178
170
  key → provider (or your table) collapses the duplicate.
179
171
 
180
172
  ```ts
@@ -245,7 +237,9 @@ down with it. The DNS/auth setup for those subdomains is
245
237
 
246
238
  The provider POSTs bounce and complaint events. Verify the signature on the
247
239
  **raw** body (parse after verifying), then write the address to a suppression
248
- list and check that list before every future send.
240
+ list and check that list before every future send. The verification is absolute
241
+ because this hook mutates the suppression list: unverified, anyone can suppress
242
+ — or un-suppress — your users.
249
243
 
250
244
  ```ts
251
245
  // app/api/email/webhook/route.ts (Next.js 16) — verify BEFORE parsing
@@ -281,14 +275,3 @@ before I ever send) is `../lead-gen/SKILL.md` / `../email-deliverability/SKILL.m
281
275
  | Trusting the webhook without signature check | Anyone can poison your suppression list | Verify signature on raw body, then parse |
282
276
  | Sending to a bounced/complained address | Reputation damage, ISP penalties | Filter against suppression list before send |
283
277
  | Calling the provider SDK at scattered call sites | Provider swap = grep across the app | One `sendEmail()` seam (Step 2) |
284
-
285
- ## Reference
286
-
287
- `references/providers.md` — full side-by-side matrix (auth header, SDK +
288
- version, single/batch signatures, idempotency model, stream/subdomain model,
289
- dynamic-template syntax, webhook event names, rate limits, when to pick each)
290
- plus a suppression-webhook handler skeleton per provider.
291
-
292
- Generic typed HTTP-client wrappers for *any* REST API →
293
- `../api-connector-builder/SKILL.md`. The orchestration that decides *when* to
294
- fire a multi-step sequence → `../automation-flows/SKILL.md`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: email-deliverability
3
- description: "Use when mail you send lands in spam, gets rejected, or fails the Gmail/Yahoo bulk-sender checks, and you must fix authentication and reputation rather than the sending code — setting SPF/DKIM/DMARC DNS records so they align, raising a cold domain through warmup, keeping the spam-complaint rate under threshold, adding one-click unsubscribe, getting the brand logo and verified checkmark to show via BIMI, and reading why 'soft' rejections and Postmaster Tools say you are throttled. Triggers: 'our emails go to spam since the Gmail update', 'DMARC fails even though SPF passes', 'how do I warm up a new sending domain', 'do I need one-click unsubscribe to send to gmail', '550 5.7.26 unauthenticated', 'how do I get our logo and the blue checkmark to show in Gmail with BIMI', 'los correos caen en spam y el SPF está bien', 'configurar BIMI i el logo verificat al correu'. NOT putting transactional/bulk email on the wire through a provider API (that is email-connector)."
3
+ description: "Use when mail already sent lands in spam, is rejected, or fails the Gmail/Yahoo bulk-sender checks and the fix is authentication and reputation, not the sending code: SPF/DKIM/DMARC alignment, domain warmup, spam-complaint rate, one-click unsubscribe, BIMI. NOT putting mail on the wire through a provider API (that is `email-connector`)."
4
4
  tags: [email, deliverability, spf, dkim, dmarc, bimi, sender-reputation, bulk-sender]
5
5
  recommends: [email-connector, newsletter, cold-outreach, gdpr-privacy, data-policy]
6
6
  origin: risco
@@ -9,34 +9,26 @@ origin: risco
9
9
  # email-deliverability — make mail authenticate and land in the inbox
10
10
 
11
11
  This skill owns one layer: **why mail you already send gets filtered, deferred,
12
- or rejected, and the DNS + reputation work that fixes it.** It does not put mail
13
- on the wire — that is `email-connector`. It starts where the send technically
14
- succeeds but the message never reaches the inbox.
12
+ or rejected, and the DNS + reputation work that fixes it.** It starts where the
13
+ send technically succeeds but the message never reaches the inbox.
15
14
 
16
15
  The lever is almost never the sending code. It is three DNS records that must
17
16
  *align*, a complaint rate you must hold down, and a reputation you build slowly
18
17
  and lose fast. Treat this as ops on a domain, not a code change.
19
18
 
20
- ## When to use / When NOT to use
21
-
22
- **Use when:** mail lands in spam or Promotions and you suspect auth/reputation,
23
- not copy; a receiver returns `550 5.7.26` (unauthenticated), `421`/`451`
24
- deferrals, or `dmarc=fail`; you must meet the Gmail/Yahoo bulk-sender rules
25
- before a launch; you are standing up a brand-new sending domain and need a
26
- warmup plan; Google Postmaster Tools shows a rising spam rate or "Bad" domain
27
- reputation; you want the brand logo and a verified checkmark to show in Gmail
28
- (BIMI + VMC/CMC).
29
-
30
- **Do NOT use for:**
19
+ ## Not this skill
31
20
 
32
21
  - **Sending the mail at all** — provider SDK, `sendEmail()` seam, templates,
33
- retries, batch caps, bounce/complaint webhooks → `email-connector`. That skill
34
- feeds the suppression list; this one explains the reputation it protects.
22
+ retries, batch caps, bounce/complaint webhooks → `../email-connector/SKILL.md`.
23
+ That skill feeds the suppression list; this one explains the reputation it
24
+ protects.
35
25
  - **Subject lines, open/click optimization, list growth** → `newsletter`.
36
26
  - **Outbound prospecting sequences, lead lists** → `cold-outreach` (deliverability
37
27
  is a *constraint* on it, not the same job).
38
28
  - **Consent, lawful basis, retention of the address list** → `gdpr-privacy` and
39
29
  `data-policy`. Unsubscribe *mechanics* live here; consent *law* does not.
30
+ - **Handling the DKIM private key and provider API secrets** →
31
+ `../secure-coding/SKILL.md`.
40
32
 
41
33
  ## The three records that must align
42
34
 
@@ -154,15 +146,3 @@ low-complaint sending.
154
146
  | Faking transactional headers to dodge Promotions | Violates bulk rules; risks rejection, not just foldering | Accept Promotions placement; earn Primary via engagement |
155
147
  | Routing the unsubscribe to a form that takes a week | Breaks the 2-day one-click honor rule; raises complaints | Honor `List-Unsubscribe-Post` one-click within 2 days, automatically |
156
148
  | Adding BIMI to fix spam-foldering | BIMI needs DMARC enforcement you do not have yet, and it changes logo display, not placement | Fix auth + reputation first; add BIMI last as a branding layer |
157
-
158
- ## See also
159
-
160
- - `../email-connector/SKILL.md` — the provider/SDK send path, suppression list,
161
- and bounce/complaint webhook that this skill's reputation rules sit on top of.
162
- - `../secure-coding/SKILL.md` — handling the DKIM private key and provider API
163
- secrets without leaking them.
164
-
165
- Recommended companions (siblings): `email-connector` for the actual send,
166
- `newsletter` for the copy/engagement side that drives complaint rate down,
167
- `cold-outreach` (deliverability gates it), and `gdpr-privacy` / `data-policy`
168
- for the consent and retention behind a clean list.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: embeddings-search
3
- description: "Use when results from a semantic search feel irrelevant, when you don't know which embedding model or chunk size to use, when you need to add a reranker or hybrid scoring, or when you have no way to tell whether a retrieval change actually helped. Symptoms: search returns garbage, exact-match queries miss, paraphrases work but keywords don't, recall looks fine but the top result is wrong, every chunking idea is a guess. Triggers: 'which embedding model and chunk size should I use', 'my semantic search returns irrelevant results', 'how do I measure if retrieval is good — recall@k, nDCG', 'add a reranker and hybrid BM25+vector', 'exact product codes return nothing but paraphrases work fine', 'recall@k is fine but the top answer is wrong', 'quin model d'embeddings i quina mida de chunk faig servir per a aquests documents', 'la cerca semàntica retorna resultats irrellevants'. NOT operating the vector store (that is vector-db)."
3
+ description: "Use when choosing an embedding model, chunk size, or query form, when semantic search returns irrelevant results, when adding hybrid BM25+vector or a reranker, or when a retrieval change needs a number (recall@k, nDCG, MRR). NOT operating the store — index tuning, quantization (that is `vector-db`) — nor the retrieve-to-answer loop (that is `rag`)."
4
4
  tags: [embeddings, semantic-search, chunking, hybrid-search, reranking, rrf, retrieval-eval, mteb, matryoshka]
5
5
  recommends: [vector-db, rag, structured-extraction, prompt-engineering, postgresdb]
6
6
  origin: risco
@@ -9,10 +9,9 @@ origin: risco
9
9
  # embeddings-search — make and judge the vectors
10
10
 
11
11
  You own the **embedding technique layer**: turn a corpus into searchable vectors, turn a
12
- question into a good retrieval, and **measure whether that retrieval is any good**. When the
13
- symptom is "results are irrelevant," "I don't know which model or chunk size," or "how do I
14
- even tell if search improved," this is the skill. You stop the moment the right chunks come
15
- back, measured by a number. You do not assemble a prompt or generate an answer.
12
+ question into a good retrieval, and **measure whether that retrieval is any good**. You stop the
13
+ moment the right chunks come back, measured by a number. You do not assemble a prompt or
14
+ generate an answer.
16
15
 
17
16
  Route the adjacent surfaces away:
18
17
 
@@ -41,7 +40,9 @@ cost — where cost is set by **dimensions**, because dims set storage and memor
41
40
 
42
41
  Quality anchor (mid-2026 MTEB English retrieval): Gemini ~68.3, Cohere `embed-v4` ~65.2,
43
42
  OpenAI `3-large` ~64.6, `BGE-M3` ~63.0. MTEB is the standard comparison, not a verdict on
44
- *your* domain (see `references/models.md` for the over-trust caveat).
43
+ *your* domain — [`references/models.md`](references/models.md) carries the full model matrix
44
+ (dims, max tokens, price, `input_type` convention, Matryoshka support) and how to read MTEB
45
+ without over-trusting it.
45
46
 
46
47
  Two hard rules — each is a **silent** failure, no error, just worse results:
47
48
 
@@ -153,7 +154,9 @@ This is the rigor of the skill. "Search is bad" is not actionable; "recall@10 is
153
154
 
154
155
  1. **Build a golden query set** — 30–50+ labeled `query → relevant doc ids` pairs drawn from
155
156
  real questions. This is the asset; everything else is reproducible from it.
156
- 2. **Pick metrics** (definitions + runnable skeleton in `references/evaluation.md`):
157
+ 2. **Pick metrics** — definitions, a runnable Python eval skeleton, golden-set construction and
158
+ the A/B-one-change methodology are in
159
+ [`references/evaluation.md`](references/evaluation.md):
157
160
  - **recall@k** — of the truly relevant docs, how many landed in the top-k. Catches "the
158
161
  right chunk never came back."
159
162
  - **nDCG@k** — rewards relevant docs ranked higher. Catches "right docs, wrong order."
@@ -179,15 +182,3 @@ flags hybrid/rerank artifacts that mention no recall/nDCG/MRR for exactly this r
179
182
  | Over-large dims "for safety" | Doubles storage/RAM, little recall gain | Right-size; truncate via Matryoshka |
180
183
  | Re-embedding the corpus to change dims | Wasteful when the model is Matryoshka-trained | Truncate dims, don't re-embed |
181
184
  | Embedding raw HTML/boilerplate | Match signal drowns in markup | Embed clean text; keep the rest as metadata |
182
-
183
- ## References & siblings
184
-
185
- - `references/models.md` — current model matrix (dims, max tokens, price, `input_type`
186
- convention, Matryoshka support) and how to read MTEB without over-trusting it.
187
- - `references/evaluation.md` — golden-set construction, recall@k / nDCG@k / MRR definitions, a
188
- runnable Python eval skeleton, and the A/B-one-change methodology.
189
-
190
- Siblings: store ops → [`../vector-db/SKILL.md`](../vector-db/SKILL.md) · full answer loop →
191
- [`../rag/SKILL.md`](../rag/SKILL.md) · typed extraction →
192
- [`../structured-extraction/SKILL.md`](../structured-extraction/SKILL.md) · prompt design →
193
- [`../prompt-engineering/SKILL.md`](../prompt-engineering/SKILL.md).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: error-handling
3
- description: "Use when designing how code recognizes, recovers from, and reports failure — typed error taxonomies, retry/backoff/timeout policy, circuit breakers, React/Next error boundaries, and the user message + operator log line. Triggers: 'add retries with backoff', 'set up an error boundary', 'standardize our error types', 'stop swallowing exceptions', 'this catch block hides the real error', 'degrade gracefully when the payment provider is down', 'why does our retry storm take the database down', 'maneja los errores sin tragarte la excepción', 'afegeix reintents amb backoff i tallacircuits'. NOT finding why one specific thing broke (that is debug), NOT wiring logs/metrics/traces (that is observability), NOT the on-the-wire error envelope contract (that is api-design)."
3
+ description: "Use when designing the reaction to a class of failures — typed error taxonomies, retry/backoff/timeout policy, circuit breakers, React/Next error boundaries, and the user-message vs operator-log split. NOT diagnosing one specific crash (that is debug), NOT logs/metrics/traces (that is observability), NOT the wire error envelope (that is api-design)."
4
4
  tags: [error-handling, resilience, retry, circuit-breaker, error-boundary, typed-errors, fault-tolerance]
5
5
  recommends: [debug, observability, monitoring, api-design, secure-coding, testing-web, nextjs]
6
6
  origin: risco
@@ -9,23 +9,11 @@ origin: risco
9
9
  # Error handling — classify, contain, surface
10
10
 
11
11
  You are designing what happens *whenever anything in a class breaks*, not chasing
12
- one crash (that is [`debug`](../debug/SKILL.md)). The deliverable is a typed error
13
- taxonomy, a retry policy with caps and jitter, boundary placement, and a
12
+ one crash (that is [`debug`](../debug/SKILL.md)). Every failure gets classified,
13
+ contained, and surfaced — never swallowed. The deliverable, in that order: a typed
14
+ error taxonomy, a retry policy with caps and jitter, boundary placement, and a
14
15
  two-audience message contract — never a pile of `try { … } catch {}`.
15
16
 
16
- ## The one rule
17
-
18
- **Every failure gets classified, contained, and surfaced — never swallowed.**
19
-
20
- - **Classify** — give the failure a type so the right code can react to the right
21
- kind. An untyped `catch (e)` cannot decide anything.
22
- - **Contain** — stop one failing dependency from taking the process (or the DB)
23
- with it. No timeout, no cap = unbounded blast radius.
24
- - **Surface** — emit two things: a human message that never leaks internals, and
25
- an operator log with the full cause chain.
26
-
27
- The five steps below are those three jobs in order. Do them in order.
28
-
29
17
  ## Step 1 — Model failure as a taxonomy
30
18
 
31
19
  Bucket every failure into one of three kinds. The bucket dictates the reaction;
@@ -257,19 +245,6 @@ GOOD → 503 { "code": "upstream_unavailable", "correlationId": "a1b2c3" }
257
245
  More before/after rewrites and the copy contract are in
258
246
  [`references/boundaries-and-messaging.md`](references/boundaries-and-messaging.md).
259
247
 
260
- ## Decision table
261
-
262
- | Situation | Reaction |
263
- |---|---|
264
- | Expected domain outcome the caller must handle | `Result<T,E>` |
265
- | Programmer bug / invariant violation | `throw` → nearest boundary |
266
- | Transient failure on an idempotent call | retry (capped + full jitter) |
267
- | Transient failure on a mutation without a key | do **not** retry; require idempotency key first |
268
- | Dependency failing repeatedly | circuit-break + fallback |
269
- | Failure during React render | `error.tsx` boundary |
270
- | Failure in an event handler / async callback | explicit `try/catch` + state |
271
- | Unhandled at the top of the process | process handler → log + exit |
272
-
273
248
  ## Anti-patterns
274
249
 
275
250
  | Anti-pattern | Why it bites | Fix |
@@ -289,19 +264,10 @@ More before/after rewrites and the copy contract are in
289
264
  | One giant `try` around 200 lines | you cannot tell which call failed | scope `try` to the fallible call |
290
265
  | Treat every error as a retryable transient | masks real bugs as "flaky" | classify first (Step 1), then react |
291
266
 
292
- ## Verification checklist
293
-
294
- Before claiming the failure path is handled:
295
-
296
- - [ ] Every failure maps to one of the three buckets (Step 1).
297
- - [ ] No empty `catch {}` / `except: pass` / bare `except:`.
298
- - [ ] Retries are capped, jittered, transient-only, and idempotent-only.
299
- - [ ] Every outbound call has a per-attempt timeout.
300
- - [ ] Repeatedly-failing dependencies have a circuit breaker + fallback.
301
- - [ ] `error.tsx` / `global-error.tsx` placed correctly; render-only limits known.
302
- - [ ] User message has no internals + carries a correlation id; the log has the cause chain.
303
- - [ ] Cause is preserved on every wrap (no lost root error).
267
+ ## The gate
304
268
 
305
- Run `scripts/verify.sh <path>` to scan the working tree for the highest-signal
306
- anti-patterns above. It is **advisory** (exit 0 with warnings); pass `--strict` to
307
- make any hit fail. It is heuristic — it flags, you judge.
269
+ Before claiming the failure path is handled, read your diff against the table
270
+ above — those rows *are* the checklist. `scripts/verify.sh <path>` scans the
271
+ working tree for the highest-signal ones. It is **advisory** (exit 0 with
272
+ warnings); pass `--strict` to make any hit fail. It is heuristic — it flags,
273
+ you judge.
File without changes
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: expo
3
- description: "Use when building, shipping, or hot-patching a React Native app with Expo — EAS Build/Submit/Update, eas.json build profiles, config plugins, prebuild/CNG, the managed workflow, confirming you are on the New Architecture (mandatory since SDK 55). Triggers: 'eas build', 'eas.json', 'config plugin', 'expo prebuild', 'my OTA update isn't showing up on devices', 'runtime version mismatch', 'how do I add a permission without editing Xcode/Gradle', 'newArchEnabled is gone from app.json', 'com publico una actualització OTA sense passar per la store', 'subir la app a la Play Store con EAS'. NOT generic React Native components/navigation/native-module authoring (that is react-native), NOT a Dart mobile app (that is flutter)."
3
+ description: "Use when shipping a React Native app with Expo — EAS Build/Submit/Update, eas.json profiles and channels, config plugins, prebuild/CNG, runtime-version policy, OTA updates that never land, SDK upgrades, the New Architecture. NOT RN UI, navigation or native-module authoring (that is `react-native`), NOT a Dart app (that is `flutter`)."
4
4
  tags: [expo, eas, react-native, mobile, ota-updates, app-store]
5
5
  recommends: [react-native, github-actions, ship, deployment, secure-coding]
6
6
  origin: risco
@@ -13,29 +13,19 @@ origin: risco
13
13
  Expo is the toolchain and **EAS cloud platform** layered on React Native: cloud
14
14
  builds, store submission, over-the-air JS updates, and **native configuration
15
15
  declared in JavaScript** instead of hand-edited Xcode/Gradle projects. This skill
16
- owns the *shipping pipeline* and *native-config-via-JS*. It does **not** own RN UI,
17
- navigation, animation, or native-module authoring — that is `react-native`.
16
+ owns the *shipping pipeline* and *native-config-via-JS*.
18
17
 
19
18
  The verb rule of thumb: if the verb is **build / submit / update / prebuild /
20
19
  plugin / EAS / channel / runtime-version**, you are in `expo`. If it is **render /
21
- navigate / animate / bridge / write a native module**, route to react-native.
22
- (Current stable as of 2026-06-02: **Expo SDK 55** — React Native 0.83.1, React
20
+ navigate / animate / bridge / write a native module**, route to `react-native`.
21
+ Other exits: web React/hooks/state → `react`, a Dart app → `flutter`, native-only
22
+ Swift/Kotlin → `swift-ios`/`kotlin-android`, a desktop wrapper → `tauri`/`electron`,
23
+ CI unrelated to EAS → `github-actions`.
24
+
25
+ Current stable as of 2026-06-02: **Expo SDK 55** — React Native 0.83.1, React
23
26
  19.2.0, shipped 2026-02-25. **SDK 56 is in beta** (beta opened 2026-05-06, ~2-week
24
27
  window; RN 0.85.2, React 19.2.3) — upcoming, not yet shipped stable. Both run
25
- exclusively on the New Architecture; the Legacy Architecture was removed in SDK 55.)
26
-
27
- ## When to use / when NOT to use
28
-
29
- **Use when:** wiring `eas.json` profiles or channels; cloud-building `.ipa`/`.aab`/
30
- `.apk` and debugging EAS Build/credentials/queues; shipping EAS Update OTA JS
31
- (runtime policy, branches, channels, rollouts/rollbacks); submitting with EAS
32
- Submit; writing/debugging config plugins or `app.config.{js,ts}`/prebuild; building
33
- a dev client; EAS Workflows YAML; migrating to the New Architecture or upgrading SDK.
34
-
35
- **Do NOT use when** the work is pure RN code with no Expo/EAS angle (route to
36
- react-native), web React/hooks/state (`react`), a Dart app (`flutter`), native-only
37
- Swift/Kotlin (`swift-ios`/`kotlin-android`), a desktop wrapper (`tauri`/`electron`),
38
- or generic CI unrelated to EAS (`github-actions`).
28
+ exclusively on the New Architecture; the Legacy Architecture was removed in SDK 55.
39
29
 
40
30
  ## Decision rules
41
31
 
@@ -91,6 +81,9 @@ stamped with a `channel`; a channel maps to a same-named EAS Update branch by de
91
81
  }
92
82
  ```
93
83
 
84
+ Run `scripts/verify.sh` inside an Expo project to gate `eas.json`, the runtime
85
+ policy, committed secrets, and New-Arch readiness.
86
+
94
87
  ## EAS Update mental model
95
88
 
96
89
  Runtime version is the **compatibility gate**. An update applies to a build only
@@ -220,34 +213,26 @@ $50/concurrency/month, up to 5 extra. If a user complains about build queue wait
220
213
  the fix is usually the plan, not the config. (Pricing per expo.dev/pricing, verified
221
214
  2026-06-02; re-check before quoting — Expo adjusts tiers and dollar figures.)
222
215
 
223
- ## Anti-patterns → STOP
216
+ ## Anti-patterns
224
217
 
225
- | Rationalization | Reality |
218
+ | Anti-pattern | Do instead |
226
219
  |---|---|
227
- | "I'll just edit `ios/Info.plist` directly" | `prebuild --clean` overwrites it; write a config plugin / `withInfoPlist`. |
228
- | "Hardcode `runtimeVersion: '1.0.0'`, simpler" | it drifts from the binary; updates silently stop matching. Use the `fingerprint` policy. |
229
- | "I bumped a native dep, the OTA update will deliver it" | EAS Update is JS-only; native changes need a new `eas build`. |
230
- | "Published the update, devices just need to refresh" | check the channel→branch and exact runtime match first — wrong channel = no delivery. |
231
- | "Test this custom native module in Expo Go" | Expo Go can't load arbitrary native code; build a dev client. |
232
- | "Upgrade the SDK, then build straight to production" | run `expo-doctor` + a preview build first; there is no Legacy-Arch fallback to catch a New-Arch-incompatible dep. |
233
- | "Set `newArchEnabled: false` to dodge the broken native dep" | the flag was removed in SDK 55 and the Legacy Architecture is gone; fix or replace the dep. |
234
- | "Commit the keystore so CI can sign" | never; let EAS manage credentials or use EAS secrets. |
235
- | "Put the API key in `app.config` extra" | app config ships in the public bundle; use EAS env vars / a backend. |
236
- | "Use GitHub Actions to call `eas build`" | EAS Workflows is the native CI; only reach for github-actions if explicitly required. |
220
+ | Editing `ios/Info.plist` directly | `prebuild --clean` overwrites it; write a config plugin / `withInfoPlist`. |
221
+ | Hardcoding `runtimeVersion: '1.0.0'` because it is simpler | it drifts from the binary; updates silently stop matching. Use the `fingerprint` policy. |
222
+ | Expecting an OTA update to deliver a bumped native dep | EAS Update is JS-only; native changes need a new `eas build`. |
223
+ | Telling users to "just refresh" when a published update does not land | check the channel→branch and exact runtime match first — wrong channel = no delivery. |
224
+ | Testing a custom native module in Expo Go | Expo Go can't load arbitrary native code; build a dev client. |
225
+ | Upgrading the SDK and building straight to production | run `expo-doctor` + a preview build first; there is no Legacy-Arch fallback to catch a New-Arch-incompatible dep. |
226
+ | Setting `newArchEnabled: false` to dodge a broken native dep | the flag was removed in SDK 55 and the Legacy Architecture is gone; fix or replace the dep. |
227
+ | Committing the keystore so CI can sign | never; let EAS manage credentials or use EAS secrets. |
228
+ | Putting the API key in `app.config` extra | app config ships in the public bundle; use EAS env vars / a backend. |
229
+ | Reaching for GitHub Actions to call `eas build` | EAS Workflows is the native CI; only reach for github-actions if explicitly required. |
237
230
 
238
231
  ## Project grounding (02-DOCS + CLAUDE.md)
239
232
 
240
- When this skill runs in a project with a `02-DOCS/` layer (the
241
- [`harness`](../harness/SKILL.md) wiki), record this app's shipping decisions —
242
- managed-vs-bare, the runtime-version policy, channel/branch map, and the SDK/New-Arch
243
- status — in `02-DOCS/wiki/stack/expo.md` and link it from the root `CLAUDE.md`
244
- `## Knowledge map`. Read it first on every use; bump its `Updated` date when a
245
- convention changes. No `02-DOCS/`? Skip silently. Conventions are *recorded, not
246
- gated* — never block the task on this.
247
-
248
- ## See also
249
-
250
- - `references/eas-update.md` — runtime version policies, channel↔branch ops, rollouts/rollbacks, republish, "update not applying" debug flow.
251
- - `references/config-plugins.md` — plugin anatomy, mods/dangerous-mods/ordering, common community plugins, prebuild troubleshooting, dynamic `app.config.ts`.
252
- - `scripts/verify.sh` — run in your Expo project to gate `eas.json`, runtime policy, committed-secret, and New-Arch readiness.
253
- - Sibling skills: `react-native` (RN UI/navigation/native modules — the other half of every Expo app), `secure-coding` (credential & secret handling), `ship`/`deployment` (release process), `github-actions` (only if you want non-Expo CI).
233
+ In a project with a `02-DOCS/` layer (the [`harness`](../harness/SKILL.md) wiki),
234
+ read `02-DOCS/wiki/stack/expo.md` first and record this app's shipping decisions
235
+ there — managed-vs-bare, runtime-version policy, channel/branch map, SDK/New-Arch
236
+ status — linked from the root `CLAUDE.md` `## Knowledge map`, bumping its `Updated`
237
+ date when a convention changes. No `02-DOCS/`? Skip silently. Conventions are
238
+ *recorded, not gated* — never block the task on this.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: fal
3
- description: "Use when calling a fal.ai model endpoint by id to generate an image, audio, or video from JS/Python/curl, wiring the queue (subscribe vs submit), receiving results by webhook and verifying the ED25519 signature, estimating per-call cost, or migrating off the deprecated @fal-ai/serverless-client. Use when a fal request is stuck IN_QUEUE, a webhook never arrives or its signature won't verify, or a long video gen drops mid-call. Triggers: 'generate an image with fal-ai/flux/dev', 'subscribe vs submit on fal', 'my fal webhook signature won't verify', 'fal request stuck IN_QUEUE', 'how much will 500 Seedream images cost on fal', 'migrate off @fal-ai/serverless-client', 'genera esta imagen con fal y guarda la URL', 'munta el webhook de fal i verifica la firma'. NOT picking the model or art direction (that is ai-media)."
3
+ description: "Use when calling a fal.ai endpoint by id to generate image, audio, or video from JS/Python/curl: subscribe vs submit, queue states, ED25519 webhook signature verification, per-call cost, or migrating off @fal-ai/serverless-client. NOT which model or art direction (that is ai-media); NOT the same models on another platform (that is replicate)."
4
4
  tags: [fal, fal-ai, inference, image-generation, video-generation, queue, webhooks, serverless]
5
5
  recommends: [ai-media, replicate, replicate-images, modal, webhooks]
6
6
  origin: risco
@@ -10,16 +10,7 @@ origin: risco
10
10
 
11
11
  The wire to fal.ai's fast, pre-warmed media endpoints: call a model by id, control the queue, get the file back. fal is the **fast-media path** — latency-optimized image (FLUX, Seedream, SD), audio (TTS, music), and video (Veo, Wan, Kling, Hailuo) endpoints you invoke by id with `FAL_KEY`.
12
12
 
13
- You own the *mechanics*: auth, call mode, queue states, webhook signatures, file I/O, per-call cost. You do not decide *what* to generate or which model is artistically right — that is the strategy layer above you.
14
-
15
- ## When to use
16
-
17
- - Generating media by calling a fal endpoint (`fal-ai/flux/dev`, `fal-ai/veo3`, `fal-ai/wan-2.5`) from JS, Python, or curl.
18
- - Choosing the call mode: `subscribe` (block + auto-poll) vs `submit` (instant `request_id`) + queue polling or webhook.
19
- - Receiving a result by webhook and verifying its ED25519 signature; handling retries and idempotency.
20
- - Uploading local inputs (image-to-image, image-to-video, ref audio) and reading output URLs.
21
- - Estimating and capping cost: per-image / per-second / per-megapixel / GPU-hour, plus the 50%-off batch path.
22
- - Migrating off the deprecated `@fal-ai/serverless-client`.
13
+ You own the *mechanics*: auth, call mode, queue states, webhook signatures, file I/O, per-call cost.
23
14
 
24
15
  ## When NOT to use
25
16
 
@@ -203,8 +194,3 @@ The full model-family map and per-modality knob list live in [`references/models
203
194
  | Ignoring the model's pricing unit | "$0.05" is per-second, not per-video — surprise bill | Read the pricing tab; pick the right knob |
204
195
  | Tight `while` loop on `queue.status` | Hammers the API, gains nothing | Back off, or use a webhook |
205
196
  | `@fal-ai/serverless-client` | Deprecated; missing fixes and APIs | `@fal-ai/client` (v1.10.1) |
206
-
207
- ## References
208
-
209
- - [`references/queue-and-webhooks.md`](references/queue-and-webhooks.md) — full async lifecycle, complete ED25519 verification (Node + Python), JWKS caching, IP allowlist, idempotent handler, retry/timeout handling.
210
- - [`references/models-and-cost.md`](references/models-and-cost.md) — model-family map, pricing-unit cheatsheet with 2026 examples, batch path, per-modality spend knobs, finding an endpoint id and its schema.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: finance-ops
3
- description: "Use when running the money side of a small company on a cash basis — building a rolling cash-flow forecast, reconciling the bank against the books, fixing a messy expense-category map, or driving a month-end close. Triggers: 'are we going to run out of cash', 'what's our runway', '13-week cash flow forecast', 'reconcile the bank statement', 'these two numbers don't match', 'the ending balance doesn't roll into next week', 'everything landed in Other expenses', 'close the books for May', 'what's our burn this month', 'tancar el mes', 'conciliar el banc amb els llibres', 'cuánto runway nos queda al ritmo actual'. NOT recording journal entries or payroll/depreciation postings (that is bookkeeping)."
3
+ description: "Use when running the money side of a small company on a cash basis: a 13-week rolling cash-flow forecast and runway, bank-vs-books reconciliation, mapping spend to tax-return expense categories, or a month-end close. NOT posting journal entries, payroll or depreciation (that is `bookkeeping`); NOT multi-year projections (that is `financial-model`)."
4
4
  tags: [cash-flow, reconciliation, month-close, expense-categories, runway, small-business-finance]
5
5
  recommends: [bookkeeping, invoicing, financial-model, unit-economics, cost-tracking, forecasting, spreadsheet-ops]
6
6
  origin: risco
@@ -10,13 +10,7 @@ origin: risco
10
10
 
11
11
  You are the controller for a small company run on a **cash basis**. Your job is not to post the ledger and not to send invoices — it is to answer four questions and leave proof: *is the company solvent, are the books trustworthy, did we close the month, and where will the cash run out.*
12
12
 
13
- Every engagement leaves behind one or more of **three checkable artifacts**:
14
-
15
- 1. A **13-week rolling cash-flow forecast** (CSV/sheet) — the runway answer.
16
- 2. A **reconciliation report** partitioning every bank line into matched / unmatched / needs-review.
17
- 3. A **month-close checklist** with every gate marked done or blocked.
18
-
19
- `scripts/verify.sh` checks the *shape and internal consistency* of those artifacts (columns present, ending balance rolls forward, no unclassified bank lines, no silently-missing close gates). It does not judge whether the dollar figures are right — that is your job.
13
+ Every engagement leaves behind at least one checkable artifact — which one depends on the job you pick below. `scripts/verify.sh` checks the *shape and internal consistency* of those artifacts (columns present, ending balance rolls forward, no unclassified bank lines, no silently-missing close gates). It does not judge whether the dollar figures are right — that is your job.
20
14
 
21
15
  ## Pick the job first
22
16
 
@@ -24,10 +18,10 @@ The flow genuinely branches. Decide which of the four you are doing before touch
24
18
 
25
19
  | If the ask is… | You are doing | Artifact you produce | Reference |
26
20
  |---|---|---|---|
27
- | "will we run out of cash", "what's our runway", "13-week forecast", "burn rate" | **Forecast** | 13-week rolling forecast CSV | `references/cash-flow-forecast.md` |
28
- | "reconcile the bank", "these don't match", "what cleared" | **Reconcile** | matched/unmatched/needs-review report | `references/reconciliation.md` |
21
+ | "will we run out of cash", "what's our runway", "13-week forecast", "burn rate" | **Forecast** | 13-week rolling forecast CSV/sheet — the runway answer | `references/cash-flow-forecast.md` |
22
+ | "reconcile the bank", "these don't match", "what cleared" | **Reconcile** | report partitioning every bank line into matched / unmatched / needs-review | `references/reconciliation.md` |
29
23
  | "set up categories", "everything's in Other", "how do I categorize this" | **Categorize** | tax-return-aligned category map | (inline below) |
30
- | "close the books for May", "what's left before we close" | **Close** | 5-day gate checklist | `references/month-close.md` |
24
+ | "close the books for May", "what's left before we close" | **Close** | 5-day checklist, every gate done or blocked | `references/month-close.md` |
31
25
 
32
26
  If the ask is actually about posting entries, sending invoices, or a multi-year model, stop and route — see **Hand-offs** at the bottom. Do not silently do another skill's job.
33
27
 
@@ -105,8 +99,6 @@ A line that is silently dropped is a hole in the books. `verify.sh` fails if any
105
99
  - Business meals deductible at **50%** (book them at a rate that preserves the 50% haircut, e.g. category L24b).
106
100
  - **1099-NEC required for any contractor paid ≥ $600/yr** — flag contractors crossing that line during categorization so nothing is missed at filing.
107
101
 
108
- Categories are the one job here with no separate reference file — the rule (mirror the return, no junk drawer, confirm current-year numbers) is the whole skill.
109
-
110
102
  ## Month-close
111
103
 
112
104
  Closing is a **sequenced 5-day checklist, not an event.** Target for a small business: books closed in **≤5 days**.