@ericrisco/rsc 0.1.48 → 0.1.50

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: electron
3
- description: "Use when building, hardening, or shipping a cross-platform desktop app with Electron — wiring the main/renderer/preload process model, exposing OS capabilities to a web UI over typed IPC, locking down an insecure shell, or packaging with code signing and auto-update. Triggers: 'build a desktop app for Mac/Windows/Linux from my web app', 'let my renderer read a file safely', 'audit my Electron security (nodeIntegration, sandbox, CSP)', 'my preload exposes ipcRenderer and the renderer can require(\"fs\")', 'Squirrel.Mac won\\'t auto-update my app', 'migrate off @electron/remote and BrowserView', 'empaquetar mi app de escritorio para Windows y Mac con firma de código y auto-actualización', 'empaquetar app d\\'escriptori amb auto-actualització'. NOT a smaller Rust-backed shell on the OS native webview (that is tauri)."
3
+ description: "Use when building, hardening, or shipping a cross-platform Electron desktop app — main/renderer/preload process model, typed contextBridge IPC, locking down nodeIntegration/contextIsolation/sandbox/CSP, or packaging with signing and auto-update. NOT a Rust-backed shell on the native webview (that is tauri), nor the web UI inside it (that is react)."
4
4
  tags: [electron, desktop, ipc, security, packaging, auto-update, code-signing]
5
5
  recommends: [tauri, react, nodejs, github-actions, secure-coding]
6
6
  origin: risco
@@ -8,13 +8,9 @@ origin: risco
8
8
 
9
9
  # Electron — desktop shell, typed IPC, hardening, signing
10
10
 
11
- > Build, harden, and ship cross-platform desktop apps. Treat the renderer as hostile
12
- > territory and the IPC channel as an attack surface — that is the dominant failure mode
13
- > in real Electron apps, so the whole skill is organized around it.
14
-
15
11
  This skill owns the **desktop shell**: process model, IPC, security, packaging, signing,
16
- auto-update. It does **not** own the web UI inside the window (`../react/SKILL.md` if it
17
- existed), the Node backend logic, or the CI runner matrix.
12
+ auto-update. It does **not** own the web UI inside the window (`../react/SKILL.md`), the
13
+ Node backend logic, or the CI runner matrix.
18
14
 
19
15
  ## The mental model — three processes, one rule
20
16
 
@@ -28,7 +24,8 @@ wrong one is the root cause of most security holes.
28
24
  | preload | isolated world, runs before page JS | semi-trusted | window | the **only** bridge: `contextBridge` exposes a tiny API |
29
25
 
30
26
  **The governing rule: the renderer is untrusted, the main process holds all privilege, and
31
- the preload is the only sanctioned bridge between them.** Everything below is a corollary.
27
+ the preload is the only sanctioned bridge between them.** Renderer-to-OS escalation is the
28
+ dominant failure mode in real Electron apps, so everything below is a corollary.
32
29
 
33
30
  ## Start right
34
31
 
@@ -55,7 +52,7 @@ src/
55
52
  ipc/types.ts # IPC contract shared by main + preload
56
53
  ```
57
54
 
58
- ## The security baseline — non-negotiable
55
+ ## The security baseline
59
56
 
60
57
  Modern Electron defaults are already secure (`nodeIntegration:false`,
61
58
  `contextIsolation:true`, `sandbox:true` since Electron 20). **Assert them explicitly anyway**
@@ -185,32 +182,23 @@ Authenticode, and `electron-updater` + GitHub Releases wiring: `references/packa
185
182
  The CI matrix that *runs* these builds across three OSes is `github-actions`' job; this skill
186
183
  defines *what* to build and sign.
187
184
 
188
- ## Migration smells — and the fix
189
-
190
- | Smell in the code | Why it's dangerous | Fix |
191
- |--------------------------------------------|---------------------------------------------|--------------------------------------------------|
192
- | `nodeIntegration: true` | Page JS gets `require('fs')`, full Node | Set `false`; move the capability behind IPC |
193
- | `contextIsolation: false` | Page can rewrite the preload's globals | Set `true` (default) |
194
- | `@electron/remote` import | Sync main-object access = renderer→main RCE | Replace with explicit `ipcMain.handle` channels |
195
- | `new BrowserView(...)` | Deprecated since 30, will be removed | `new WebContentsView(...)` |
196
- | `exposeInMainWorld('api', ipcRenderer)` | Universal IPC weapon (empty object now) | One function per channel |
197
-
198
- ## Anti-patterns
185
+ ## Anti-patterns and migration smells
199
186
 
200
187
  | Anti-pattern | Why it's wrong | Do instead |
201
188
  |-------------------------------------------------------|------------------------------------------------------------|-------------------------------------------------------|
202
- | `nodeIntegration: true` | Any XSS in the renderer becomes OS-level RCE | `false` + IPC for every privileged op |
203
- | `contextIsolation: false` | Renderer can tamper with preload internals | `true` (the default) |
189
+ | `nodeIntegration: true` | Page JS gets `require('fs')`; any XSS becomes OS-level RCE | `false`; move the capability behind IPC |
190
+ | `contextIsolation: false` | Page can rewrite the preload's globals | `true` (the default) |
204
191
  | `sandbox: false` without a reason | Drops the OS sandbox around the renderer | `true`; only relax for a measured, isolated need |
205
- | Exposing `ipcRenderer`/its methods over the bridge | Page can call any channel with any payload | One typed function per channel |
192
+ | `exposeInMainWorld('api', ipcRenderer)` or its methods | Universal IPC weapon — any channel, any payload (and an empty object now) | One typed function per channel |
206
193
  | No arg validation in `ipcMain.handle` | Renderer is an untrusted client; you trust its input | Type-check + bound every arg before acting |
207
194
  | `webSecurity: false` to "fix CORS" | Disables same-origin policy app-wide | Keep `true`; proxy/handle CORS in main |
208
195
  | CSP only in a `<meta>` tag | Doesn't cover the initial document; bypassable | Set CSP in `onHeadersReceived` |
209
196
  | Loading a remote URL into a Node-enabled window | Remote site runs with your app's privilege | Bundle UI locally; sandbox + nav lockdown for remote |
197
+ | `@electron/remote` import | Sync main-object access = renderer→main RCE | Replace with explicit `ipcMain.handle` channels |
198
+ | `new BrowserView(...)` | Deprecated since Electron 30, will be removed | `new WebContentsView(...)` |
210
199
  | Auto-update with an unsigned/un-notarized build | Squirrel.Mac silently refuses; no updates ship | Sign + notarize (mac), Authenticode (win) first |
211
200
  | Shipping on an EOL Electron major | Unpatched Chromium CVEs in your users' hands | Stay within the latest 3 majors |
212
201
  | Heavy CPU work in the main process | Blocks the event loop → the whole UI freezes | `utilityProcess`/worker, or do it in the renderer |
213
- | Keeping `@electron/remote` | A known renderer→main escalation path | Migrate to explicit IPC |
214
202
 
215
203
  ## Verify
216
204
 
@@ -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-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: 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**.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: financial-model
3
- description: "Use when a founder needs the multi-period, driver-based projection that raises a round and steers the company — build 3-year projections, model revenue + costs + headcount + runway, size the raise, or stress-test scenarios. Triggers: 'build a financial model for our seed', 'model our revenue costs and runway', 'when do we run out of cash', 'how much should we raise to get 24 months', 'what runway does $2M buy us', 'model the hiring plan's effect on burn', 'add a downside scenario if we miss plan by 30%', 'modelo financiero a 3 años', 'cuánto runway nos da la ronda', 'model financer i projeccions', 'quant de pista de despegament'. The engine is assumptions → revenue/cost/headcount builds → cash/burn/runway/scenarios, 18–36 months forward, every output a formula. NOT the next-13-weeks liquidity reconciled to the actual bank balance (that is finance-ops), NOT the isolated LTV/CAC math (that is unit-economics, which this model consumes as drivers), NOT the slide story (that is pitch-deck)."
3
+ description: "Use when a founder needs the driver-based 18–36 month projection: revenue, cost and headcount builds resolving to net burn, runway, cash-out date, scenarios and the raise size. NOT the 13-week bank-reconciled cash view (that is `finance-ops`), NOT single-series forecasting (that is `forecasting`), NOT running the round (that is `fundraising`)."
4
4
  tags: [financial-model, projections, runway, burn-rate, fundraising, fp-and-a, forecast]
5
5
  recommends: [finance-ops, unit-economics, pricing, forecasting, pitch-deck, investor-materials, fundraising]
6
6
  origin: risco
@@ -8,7 +8,7 @@ origin: risco
8
8
 
9
9
  # Financial model — the projection engine that raises and steers
10
10
 
11
- You are a startup FP&A analyst building the **projection engine**: an assumptions layer that feeds a monthly revenue build, a cost build (COGS + OpEx by function + a headcount plan), and a cash projection that resolves to **net burn, runway, and the cash-out date** — plus a scenario switch (base / downside / upside). It is forward-looking and assumption-driven, 18–36 months out. It is not the controller's short-horizon liquidity tool, not the deck narrative, not the LTV/CAC deep dive. It produces the numbers the fundraising siblings present.
11
+ You are a startup FP&A analyst building the **projection engine**: an assumptions layer that feeds a monthly revenue build, a cost build (COGS + OpEx by function + a headcount plan), and a cash projection that resolves to **net burn, runway, and the cash-out date** — plus a scenario switch (base / downside / upside). It is forward-looking and assumption-driven, 18–36 months out. It produces the numbers the fundraising siblings present.
12
12
 
13
13
  The test of a model is not "does it look right" but **"does it re-flow when I change three input cells."** A spreadsheet of typed-in numbers is a picture, not a model.
14
14
 
@@ -20,18 +20,21 @@ Three connected artifacts, every output traced to an input:
20
20
  2. **A monthly projection grid** (CSV/spreadsheet, 18–36 columns) — month index, revenue, COGS, gross margin, OpEx by function, headcount, net burn, starting/ending cash, runway-months. This is what `scripts/verify.sh` checks for shape and internal consistency.
21
21
  3. **A scenario + runway summary** — one screen: ending-cash trajectory and runway under base/downside/upside, the raise number, and the burn-multiple / Rule-of-40 cross-check.
22
22
 
23
- ## What it does NOT do — route out first
23
+ ## Route out first
24
24
 
25
- This skill answers **"how much / what runway / does the plan tie out."** The moment the real ask is something else, stop and route:
25
+ This skill answers **"how much / what runway / does the plan tie out."** The moment the real ask is something else, stop and route — at the intake gate or mid-build:
26
26
 
27
- - Next-13-weeks liquidity reconciled to the **actual bank balance**, this-month burn, are-we-solvent-now → `finance-ops`.
28
- - Recording actual ledger entries, journals, double-entry, payroll postings → `bookkeeping`.
29
- - The isolated **LTV / CAC / payback / contribution-margin** math → `unit-economics` (the model imports these as drivers, it does not derive them).
30
- - Setting the **price / packaging / tier / margin floor** itself → `pricing` (the model takes price as input).
31
- - Statistical / time-series **forecasting** of a single series (churn, demand, ARR) from history → `forecasting`.
32
- - The slide **story** and decision-grade headline numbers → `pitch-deck`. Packaging the model into the data room / sending it → `investor-materials`.
33
- - Round **strategy**, investor pipeline, SAFE-vs-priced, term-sheet mechanics → `fundraising`.
34
- - Per-unit COGS / infra / AI spend as actuals → `cost-tracking`.
27
+ | The real ask | Route to |
28
+ | --- | --- |
29
+ | Next-13-weeks liquidity reconciled to the **actual bank balance**, this-month burn, are-we-solvent-now | `finance-ops` |
30
+ | Recording actual ledger entries, journals, double-entry, payroll postings | `bookkeeping` |
31
+ | The isolated **LTV / CAC / payback / contribution-margin** math — the model imports these as drivers, it does not derive them | `unit-economics` |
32
+ | Setting the **price / packaging / tier / margin floor** itself — the model takes price as input | `pricing` |
33
+ | Statistical / time-series **forecast** of a single series (churn, demand, ARR) from history | `forecasting` |
34
+ | The slide **story** and decision-grade headline numbers | `pitch-deck` |
35
+ | Packaging the model into the data room / sending it | `investor-materials` |
36
+ | Round **strategy**, investor pipeline, SAFE-vs-priced, term-sheet mechanics | `fundraising` |
37
+ | Per-unit COGS / infra / AI spend as actuals | `cost-tracking` |
35
38
 
36
39
  ## The intake gate
37
40
 
@@ -44,12 +47,6 @@ Pin four inputs before you build a single cell. Without them the model is fictio
44
47
  | **Current revenue / MRR + recent growth** | the base the revenue build grows from, not a fresh hockey stick |
45
48
  | **Target raise & horizon** (or "size it for me") | the model either takes the raise as input or solves for it from a runway target |
46
49
 
47
- STOP-and-route at the gate:
48
-
49
- - If the ask is "what does next quarter's cash look like against the bank" → that is the 13-week tool, route to `finance-ops`.
50
- - If the ask is "what's our LTV and CAC" with no projection attached → route to `unit-economics`; come back when you need them as drivers.
51
- - If the ask is "what should we charge" → route to `pricing`; the model takes the price it sets.
52
-
53
50
  ## Rule 1 — three layers, no hardcoded outputs
54
51
 
55
52
  The model is a connected system: **assumptions → drivers → outputs.** Changing a few input cells must re-flow the whole plan. Hardcoded outputs that don't recompute are the cardinal modeling sin — they pass inspection and then silently lie the moment an assumption moves.
@@ -131,7 +128,7 @@ Most companies miss Rule of 40 — McKinsey's run of 200+ software firms (2011
131
128
 
132
129
  ## Anti-patterns
133
130
 
134
- | Bad | Good | Why |
131
+ | Anti-pattern | Do instead | Why |
135
132
  | --- | --- | --- |
136
133
  | Hockey-stick revenue, one line, no downside | base / downside / upside off toggled drivers | investors stress-test the downside first; no downside reads as naive |
137
134
  | Runway computed from net income / P&L | runway from cash balance ÷ net cash burn | depreciation, prepaids, AR make profit ≠ cash; cash is the survival line |
@@ -152,19 +149,4 @@ The model emits a checkable artifact, so `scripts/verify.sh` runs against your g
152
149
  ./scripts/verify.sh --strict # treat warnings as failures (CI gate)
153
150
  ```
154
151
 
155
- It checks: required columns present (month, revenue, cogs, gross_margin, opex, net_burn, ending_cash, runway_months); **cash continuity** (ending_cash[m] == starting_cash[m+1]); **gross_margin == (revenue − COGS)/revenue** recomputes; **net_burn == gross_burn − revenue** ties; runway_months ties to cash ÷ net_burn; ≥1 scenario present; and a defect lint for placeholders (`TBD`, `XX`, `#REF`, `[assumption]`) and impossible values (gross margin >100%/<−100%, negative headcount). It exits 0 on a clean or empty target — a missing file is a skip, never a false failure. The schema it enforces is documented in `references/model-structure.md`.
156
-
157
- ## Hand-offs
158
-
159
- - Need the LTV/CAC/payback that feed the drivers → `unit-economics`.
160
- - Price/packaging the model assumes → `pricing`.
161
- - Single-series statistical forecast of churn/ARR → `forecasting`.
162
- - The near-term bank-reconciled cash view → `finance-ops`.
163
- - Turn the numbers into the slide story → `pitch-deck`; into the data room → `investor-materials`.
164
- - Run the actual round off the raise number → `fundraising`.
165
-
166
- ## references/
167
-
168
- - `references/revenue-build.md` — bottom-up funnel template, the MRR new/expansion/contraction/churn waterfall, the top-down TAM cross-check, and a worked monthly build.
169
- - `references/benchmarks-and-scenarios.md` — the 2025/26 benchmark sanity-table, the base/downside/upside driver-toggle recipe, and a worked runway-under-scenarios example.
170
- - `references/model-structure.md` — the assumptions/projection row+column contract `verify.sh` enforces, the cash-continuity rule, and the `model.csv` schema with a filled mini-example.
152
+ It checks: required columns present (month, revenue, cogs, gross_margin, opex, net_burn, ending_cash, runway_months); **cash continuity** (ending_cash[m] == starting_cash[m+1]); **gross_margin == (revenue − COGS)/revenue** recomputes; **net_burn == gross_burn − revenue** ties; runway_months ties to cash ÷ net_burn; ≥1 scenario present; and a defect lint for placeholders (`TBD`, `XX`, `#REF`, `[assumption]`) and impossible values (gross margin >100%/<−100%, negative headcount). It exits 0 on a clean or empty target — a missing file is a skip, never a false failure. The row+column contract it enforces, the cash-continuity rule, and the `model.csv` schema with a filled mini-example are in `references/model-structure.md`.