@ericrisco/rsc 1.0.27 → 1.0.29

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.
Files changed (45) hide show
  1. package/README.md +22 -4
  2. package/manifest.json +230 -2
  3. package/package.json +1 -1
  4. package/scripts/detect-repo.js +27 -1
  5. package/scripts/drift-check.js +40 -5
  6. package/scripts/lib/domains.js +3 -2
  7. package/skills/animate/SKILL.md +228 -0
  8. package/skills/animate/evals/cases.yaml +36 -0
  9. package/skills/animate/references/recipes.md +324 -0
  10. package/skills/animate-expo/SKILL.md +281 -0
  11. package/skills/animate-expo/evals/cases.yaml +36 -0
  12. package/skills/animate-expo/references/recipes.md +385 -0
  13. package/skills/animation-vocabulary/SKILL.md +189 -0
  14. package/skills/animation-vocabulary/evals/cases.yaml +31 -0
  15. package/skills/apple-design/SKILL.md +301 -0
  16. package/skills/apple-design/evals/cases.yaml +33 -0
  17. package/skills/ask-sonner/SKILL.md +104 -0
  18. package/skills/ask-sonner/evals/cases.yaml +33 -0
  19. package/skills/ask-sonner/references/api.md +64 -0
  20. package/skills/design/SKILL.md +8 -0
  21. package/skills/design/references/motion-and-interaction.md +14 -3
  22. package/skills/design/references/visual-system.md +4 -3
  23. package/skills/design-eng/SKILL.md +724 -0
  24. package/skills/design-eng/evals/cases.yaml +36 -0
  25. package/skills/expo/SKILL.md +6 -0
  26. package/skills/find-animation-opportunities/SKILL.md +150 -0
  27. package/skills/find-animation-opportunities/evals/cases.yaml +31 -0
  28. package/skills/improve-animations/SKILL.md +126 -0
  29. package/skills/improve-animations/evals/cases.yaml +31 -0
  30. package/skills/improve-animations/references/audit.md +115 -0
  31. package/skills/improve-animations/references/plan-template.md +73 -0
  32. package/skills/pick-ui-library/SKILL.md +100 -0
  33. package/skills/pick-ui-library/evals/cases.yaml +33 -0
  34. package/skills/prototype/SKILL.md +113 -0
  35. package/skills/prototype/evals/cases.yaml +31 -0
  36. package/skills/prototype/references/picker.md +197 -0
  37. package/skills/react/SKILL.md +6 -0
  38. package/skills/react-native/SKILL.md +5 -0
  39. package/skills/review-animations/SKILL.md +137 -0
  40. package/skills/review-animations/evals/cases.yaml +34 -0
  41. package/skills/review-animations/references/standards.md +187 -0
  42. package/skills/review-animations/scripts/verify.sh +164 -0
  43. package/skills/swift-ios/SKILL.md +7 -0
  44. package/skills/write-swift/SKILL.md +413 -0
  45. package/skills/write-swift/evals/cases.yaml +36 -0
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  <div align="center">
2
2
 
3
- <img src="https://raw.githubusercontent.com/ericrisco/rsc-harness/main/site/og.png" alt="rsc — your agent needs a memory (02-DOCS/, or it invents what it cannot remember), arms (01-TOOLS/, or it never touches your database), a trade (264 skills, or it guesses how the job is done), and reflexes (deterministic hooks, or it forgets the rule mid-session). One meta-harness for 17 coding assistants." width="960">
3
+ <img src="https://raw.githubusercontent.com/ericrisco/rsc-harness/main/site/og.png" alt="rsc — your agent needs a memory (02-DOCS/, or it invents what it cannot remember), arms (01-TOOLS/, or it never touches your database), a trade (276 skills, or it guesses how the job is done), and reflexes (deterministic hooks, or it forgets the rule mid-session). One meta-harness for 17 coding assistants." width="960">
4
4
 
5
- # `rsc` — 264 agent skills, one CLI, zero bloat
5
+ # `rsc` — 276 agent skills, one CLI, zero bloat
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/@ericrisco/rsc?color=63d68a&labelColor=12161c&label=npm)](https://www.npmjs.com/package/@ericrisco/rsc)
8
8
  [![downloads](https://img.shields.io/npm/dm/@ericrisco/rsc?color=63d68a&labelColor=12161c&label=downloads)](https://www.npmjs.com/package/@ericrisco/rsc)
@@ -344,11 +344,11 @@ Train and adapt open models end to end: classic ML, deep learning, NLP, fine-tun
344
344
 
345
345
  ### 🗣️ Languages
346
346
 
347
- [typescript](skills/typescript/) · [python](skills/python/) · [java](skills/java/) · [csharp-dotnet](skills/csharp-dotnet/) · [php](skills/php/) · [ruby](skills/ruby/) · [cpp](skills/cpp/) · [elixir](skills/elixir/) · [bash-scripting](skills/bash-scripting/) · [sql](skills/sql/) · [go](skills/go/)
347
+ [typescript](skills/typescript/) · [python](skills/python/) · [java](skills/java/) · [csharp-dotnet](skills/csharp-dotnet/) · [php](skills/php/) · [ruby](skills/ruby/) · [cpp](skills/cpp/) · [elixir](skills/elixir/) · [bash-scripting](skills/bash-scripting/) · [sql](skills/sql/) · [go](skills/go/) · [write-swift](skills/write-swift/)
348
348
 
349
349
  ### 🏗️ Frameworks & app stacks
350
350
 
351
- [fastapi](skills/fastapi/) · [nextjs](skills/nextjs/) · [react](skills/react/) · [react-native](skills/react-native/) · [vue-nuxt](skills/vue-nuxt/) · [angular](skills/angular/) · [svelte](skills/svelte/) · [astro](skills/astro/) · [solid-js](skills/solid-js/) · [htmx](skills/htmx/) · [nodejs](skills/nodejs/) · [nestjs](skills/nestjs/) · [django](skills/django/) · [laravel](skills/laravel/) · [rails](skills/rails/) · [spring-boot](skills/spring-boot/) · [phoenix](skills/phoenix/) · [flutter](skills/flutter/) · [swift-ios](skills/swift-ios/) · [kotlin-android](skills/kotlin-android/) · [compose-multiplatform](skills/compose-multiplatform/) · [expo](skills/expo/) · [tauri](skills/tauri/) · [electron](skills/electron/) · [rust](skills/rust/) · [wordpress](skills/wordpress/) · [shopify](skills/shopify/) · [no-code-app](skills/no-code-app/) · [chrome-extension](skills/chrome-extension/) · [api-design](skills/api-design/)
351
+ [fastapi](skills/fastapi/) · [nextjs](skills/nextjs/) · [react](skills/react/) · [react-native](skills/react-native/) · [vue-nuxt](skills/vue-nuxt/) · [angular](skills/angular/) · [svelte](skills/svelte/) · [astro](skills/astro/) · [solid-js](skills/solid-js/) · [htmx](skills/htmx/) · [nodejs](skills/nodejs/) · [nestjs](skills/nestjs/) · [django](skills/django/) · [laravel](skills/laravel/) · [rails](skills/rails/) · [spring-boot](skills/spring-boot/) · [phoenix](skills/phoenix/) · [flutter](skills/flutter/) · [swift-ios](skills/swift-ios/) · [kotlin-android](skills/kotlin-android/) · [compose-multiplatform](skills/compose-multiplatform/) · [expo](skills/expo/) · [tauri](skills/tauri/) · [electron](skills/electron/) · [rust](skills/rust/) · [wordpress](skills/wordpress/) · [shopify](skills/shopify/) · [no-code-app](skills/no-code-app/) · [chrome-extension](skills/chrome-extension/) · [api-design](skills/api-design/) · [pick-ui-library](skills/pick-ui-library/) · [ask-sonner](skills/ask-sonner/)
352
352
 
353
353
  ### 🎮 Game development
354
354
 
@@ -372,6 +372,10 @@ Three engines + engine-agnostic disciplines. Every engine skill pins the current
372
372
 
373
373
  [code-review](skills/code-review/) · [security-scan](skills/security-scan/) · [secure-coding](skills/secure-coding/) · [testing-py](skills/testing-py/) · [testing-web](skills/testing-web/) · [testing-go](skills/testing-go/) · [e2e-testing](skills/e2e-testing/) · [accessibility](skills/accessibility/) · [performance](skills/performance/) · [error-handling](skills/error-handling/) · [observability](skills/observability/)
374
374
 
375
+ ### 🌀 Motion & interface craft
376
+
377
+ [design-eng](skills/design-eng/) · [animate](skills/animate/) · [animate-expo](skills/animate-expo/) · [review-animations](skills/review-animations/) · [improve-animations](skills/improve-animations/) · [find-animation-opportunities](skills/find-animation-opportunities/) · [animation-vocabulary](skills/animation-vocabulary/) · [apple-design](skills/apple-design/) · [prototype](skills/prototype/)
378
+
375
379
  ### 🎨 Design & content craft
376
380
 
377
381
  [design](skills/design/) · [presentations](skills/presentations/) · [course-storytelling](skills/course-storytelling/) · [course-builder](skills/course-builder/) · [technical-writing](skills/technical-writing/) · [translation-l10n](skills/translation-l10n/)
@@ -455,6 +459,20 @@ the rubric to hold it to is `scripts/skill-rubric.md`.
455
459
  This is a personal catalog. Bug reports welcome via GitHub issues; PRs fixing
456
460
  detector patterns, provider endpoints, or typos are appreciated.
457
461
 
462
+ ## Third-party skills
463
+
464
+ Most of this catalog is written here. These are not, and they keep their author's credit:
465
+
466
+ | Skills | Source | License |
467
+ | --- | --- | --- |
468
+ | `design-eng` · `animate` · `animate-expo` · `review-animations` · `improve-animations` · `find-animation-opportunities` · `animation-vocabulary` · `apple-design` · `prototype` · `pick-ui-library` · `write-swift` · `ask-sonner` | [emilkowalski/skills](https://github.com/emilkowalski/skills) by Emil Kowalski, commit `d23d7f8` | MIT |
469
+ | part of the AI-tell corpus in `design` | [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill) | MIT |
470
+
471
+ Adapted, not mirrored: each one carries rsc frontmatter, routing evals, hand-offs to its siblings
472
+ and — where it declares a binding rule — a checker with a test. The craft bar in them is the
473
+ original author's. If you want the source of the motion material rather than this adaptation, go to
474
+ [animations.dev](https://animations.dev/).
475
+
458
476
  ## License
459
477
 
460
478
  MIT. See [LICENSE](LICENSE).
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "1.0.27",
2
+ "version": "1.0.29",
3
3
  "counts": {
4
- "skills": 264
4
+ "skills": 276
5
5
  },
6
6
  "skills": [
7
7
  {
@@ -182,6 +182,62 @@
182
182
  ],
183
183
  "profiles": []
184
184
  },
185
+ {
186
+ "id": "animate",
187
+ "description": "Build a web animation: whether it should animate at all, the purpose, the tool, the properties, the easing curve, the duration, interruption and exit. Writes the CSS or Motion code for dropdowns, modals, toasts, tooltips, accordions, press feedback, stagger and scroll reveals. Use to animate something or make a component feel alive.",
188
+ "tags": [
189
+ "motion",
190
+ "animation",
191
+ "css",
192
+ "transitions",
193
+ "ui"
194
+ ],
195
+ "recommends": [
196
+ "review-animations",
197
+ "pick-ui-library",
198
+ "apple-design"
199
+ ],
200
+ "profiles": [
201
+ "full"
202
+ ]
203
+ },
204
+ {
205
+ "id": "animate-expo",
206
+ "description": "Build animation and gestures in React Native and Expo with Reanimated worklets on the UI thread — sheets, drawers, screen transitions, swipe, drag, press feedback, haptics. Use when adding gestures to an Expo app, or when motion stutters, janks or drops frames on a real Android or iOS device.",
207
+ "tags": [
208
+ "motion",
209
+ "animation",
210
+ "expo",
211
+ "react-native",
212
+ "gestures"
213
+ ],
214
+ "recommends": [
215
+ "expo",
216
+ "react-native",
217
+ "animate"
218
+ ],
219
+ "profiles": [
220
+ "full"
221
+ ]
222
+ },
223
+ {
224
+ "id": "animation-vocabulary",
225
+ "description": "Reverse glossary: turn a vague description of a motion effect into its exact name — a popover growing from its trigger is origin-aware, iOS overscroll is rubber-banding, items appearing one after another with a delay is a stagger. Use for \\\"what's it called when…\\\" or \\\"what do you call it when…\\\".",
226
+ "tags": [
227
+ "motion",
228
+ "animation",
229
+ "glossary",
230
+ "terminology",
231
+ "naming"
232
+ ],
233
+ "recommends": [
234
+ "animate",
235
+ "apple-design"
236
+ ],
237
+ "profiles": [
238
+ "full"
239
+ ]
240
+ },
185
241
  {
186
242
  "id": "api-connector-builder",
187
243
  "description": "Use when writing a client for someone else's REST or GraphQL API: auth flow choice and token refresh, pagination to exhaustion, retry-with-jitter on transient failures only, rate-limit-aware throttling. NOT inbound callbacks (that is `webhooks`), NOT chaining services (that is `automation-flows`), NOT designing your own API (that is `api-design`).",
@@ -233,6 +289,25 @@
233
289
  ],
234
290
  "profiles": []
235
291
  },
292
+ {
293
+ "id": "apple-design",
294
+ "description": "Apple's fluid-interface craft on the web: spring damping and response, velocity handoff, momentum projection on a flick, rubber-banding at a boundary, interruptible motion the user can grab mid-flight, translucent materials and backdrop blur, letter-spacing and leading by size. Use for iOS-grade feel, sheets and drag.",
295
+ "tags": [
296
+ "design",
297
+ "motion",
298
+ "springs",
299
+ "gestures",
300
+ "apple"
301
+ ],
302
+ "recommends": [
303
+ "animate",
304
+ "design",
305
+ "swift-ios"
306
+ ],
307
+ "profiles": [
308
+ "full"
309
+ ]
310
+ },
236
311
  {
237
312
  "id": "article-writing",
238
313
  "description": "Use when writing one long-form article end to end — answer-first lede, question-shaped headings, plus its on-page surface (title, meta, slug, FAQ, Article/FAQPage JSON-LD) — or fixing a draft that buries the answer or reads AI-padded. NOT keyword research or topic selection (that is `seo-geo`), NOT the editorial calendar (that is `content-engine`).",
@@ -256,6 +331,25 @@
256
331
  ],
257
332
  "profiles": []
258
333
  },
334
+ {
335
+ "id": "ask-sonner",
336
+ "description": "Sonner, the React toast library: mounting Toaster once, toast() calls, promise and loading toasts, updating, dismissing, styling, theming, dark mode, positioning. Use when toasts do not appear, appear twice, sit behind a modal, ignore Tailwind classes, or do nothing from a server action.",
337
+ "tags": [
338
+ "sonner",
339
+ "toasts",
340
+ "notifications",
341
+ "react",
342
+ "library"
343
+ ],
344
+ "recommends": [
345
+ "react",
346
+ "nextjs",
347
+ "pick-ui-library"
348
+ ],
349
+ "profiles": [
350
+ "full"
351
+ ]
352
+ },
259
353
  {
260
354
  "id": "astro",
261
355
  "description": "Use when building a content-driven or marketing site with Astro 6: static-first pages, islands and partial hydration, content collections, server islands, per-route on-demand rendering, deploy adapters, and Astro 5→6 migration. NOT app-router React with server actions and heavy client interactivity (that is `nextjs`).",
@@ -1315,6 +1409,27 @@
1315
1409
  ],
1316
1410
  "profiles": []
1317
1411
  },
1412
+ {
1413
+ "id": "design-eng",
1414
+ "description": "Umbrella for frontend craft: routes a UI request to the sibling that owns it — build motion, review it, audit it, find where it is missing, name an effect, gesture physics, prototype variants, pick a library. Use when an app works but feels cheap or unpolished, or when a frontend request could belong to several of them.",
1415
+ "tags": [
1416
+ "design",
1417
+ "frontend",
1418
+ "ui",
1419
+ "routing",
1420
+ "craft"
1421
+ ],
1422
+ "recommends": [
1423
+ "animate",
1424
+ "review-animations",
1425
+ "apple-design",
1426
+ "design"
1427
+ ],
1428
+ "profiles": [
1429
+ "core",
1430
+ "full"
1431
+ ]
1432
+ },
1318
1433
  {
1319
1434
  "id": "digitalocean",
1320
1435
  "description": "Use when deploying or operating a workload on DigitalOcean — Droplet vs App Platform vs Functions, doctl, app spec YAML, Managed Postgres/MySQL/Valkey on the VPC, S3-compatible Spaces + CDN. NOT host-agnostic CI/CD or rollback strategy (that is `deployment`), NOT a bare Hetzner VPS (that is `hetzner`), NOT another managed PaaS (that is `railway`).",
@@ -1790,6 +1905,24 @@
1790
1905
  ],
1791
1906
  "profiles": []
1792
1907
  },
1908
+ {
1909
+ "id": "find-animation-opportunities",
1910
+ "description": "Sweep a UI for what does not animate but should, and reject what should not — each suggestion with an exact curve and duration, plus the rejected candidates. Read-only. Use for 'what could be animated here', 'where would motion help', or an interface that feels static and dead.",
1911
+ "tags": [
1912
+ "motion",
1913
+ "animation",
1914
+ "restraint",
1915
+ "opportunities",
1916
+ "ui"
1917
+ ],
1918
+ "recommends": [
1919
+ "animate",
1920
+ "improve-animations"
1921
+ ],
1922
+ "profiles": [
1923
+ "full"
1924
+ ]
1925
+ },
1793
1926
  {
1794
1927
  "id": "finetuning",
1795
1928
  "description": "Use when adapting an open-weight model to a target form or behavior — tone, output format, reasoning pattern — via LoRA/QLoRA or full fine-tuning with TRL SFTTrainer, then preference optimization (DPO/ORPO/KTO/GRPO), and for fine-tune vs prompt vs RAG. NOT adding facts to a model (that is rag); NOT the single-GPU Unsloth backend or GGUF export (that is unsloth).",
@@ -2383,6 +2516,25 @@
2383
2516
  "full"
2384
2517
  ]
2385
2518
  },
2519
+ {
2520
+ "id": "improve-animations",
2521
+ "description": "Audit a whole codebase's animation and motion, then write prioritized findings and self-contained plans another agent can execute. Read-only. Use for 'improve the animations', 'make the app feel better', or a roadmap of motion fixes — rather than a verdict on one diff.",
2522
+ "tags": [
2523
+ "motion",
2524
+ "animation",
2525
+ "audit",
2526
+ "plan",
2527
+ "codebase"
2528
+ ],
2529
+ "recommends": [
2530
+ "review-animations",
2531
+ "animate",
2532
+ "plan"
2533
+ ],
2534
+ "profiles": [
2535
+ "full"
2536
+ ]
2537
+ },
2386
2538
  {
2387
2539
  "id": "init",
2388
2540
  "description": "Use when starting from nothing or pointing rsc at an existing project — the front door. Gauges technical level and the accompaniment dial first (non-technical by default), discovers what the user wants to build or govern (any stack, or a non-code harness: company/ops, research, knowledge, content), writes the profile to 02-DOCS, and installs the skills discovery justified. NOT the scaffolder (that is `harness`), NOT a stack skill.",
@@ -3458,6 +3610,25 @@
3458
3610
  ],
3459
3611
  "profiles": []
3460
3612
  },
3613
+ {
3614
+ "id": "pick-ui-library",
3615
+ "description": "Pick the frontend library for a task from a curated list: dialogs and popovers, command palette, toasts, one-time-password input, charts, virtualization, drag and drop, state, conditional classNames, variants, dark mode. Use instead of hand-rolling a component or installing an abandoned package.",
3616
+ "tags": [
3617
+ "frontend",
3618
+ "libraries",
3619
+ "dependencies",
3620
+ "react",
3621
+ "ui"
3622
+ ],
3623
+ "recommends": [
3624
+ "react",
3625
+ "nextjs",
3626
+ "animate"
3627
+ ],
3628
+ "profiles": [
3629
+ "full"
3630
+ ]
3631
+ },
3461
3632
  {
3462
3633
  "id": "pitch-deck",
3463
3634
  "description": "Use when building or fixing an investor fundraising deck — the narrative arc, the slide-by-slide story, and the few numbers that actually move an investment decision, for a pre-seed, seed or Series A raise (or an accelerator application deck). Decides what goes on each slide, in what order, with which metric, sequenced the way an investor evaluates risk. NOT rendering, theming or exporting the slides into a designed PDF or PPTX (that is `presentations`), NOT the projection spreadsheet behind the numbers (that is `financial-model`).",
@@ -3732,6 +3903,25 @@
3732
3903
  ],
3733
3904
  "profiles": []
3734
3905
  },
3906
+ {
3907
+ "id": "prototype",
3908
+ "description": "Build several genuinely different versions — variants, takes, options, directions — of one UI piece behind a visual picker, so you flip through them live and promote the winner. Use to explore a look before committing, or when you do not yet know what you want something to look like.",
3909
+ "tags": [
3910
+ "design",
3911
+ "variants",
3912
+ "exploration",
3913
+ "picker",
3914
+ "ui"
3915
+ ],
3916
+ "recommends": [
3917
+ "design",
3918
+ "animate",
3919
+ "nextjs"
3920
+ ],
3921
+ "profiles": [
3922
+ "full"
3923
+ ]
3924
+ },
3735
3925
  {
3736
3926
  "id": "python",
3737
3927
  "description": "Use when the task is Python itself, in any framework or none: PEP 695 generics, mypy --strict typing, dataclass/Protocol/TypedDict/Enum choices, asyncio.TaskGroup, stdlib idioms, src/ layout + pyproject.toml with uv, ruff+mypy+pytest gate. NOT a FastAPI/ASGI service (that is fastapi), NOT a deep pytest suite (that is testing-py).",
@@ -4060,6 +4250,25 @@
4060
4250
  "full"
4061
4251
  ]
4062
4252
  },
4253
+ {
4254
+ "id": "review-animations",
4255
+ "description": "Review, critique or grade existing animation and motion code against a strict craft bar — easing, duration, transform-origin, interruptibility, GPU-only properties, reduced motion, enter/exit asymmetry, stagger. Default is to flag; approval is earned. Use on the transitions in a diff or a component.",
4256
+ "tags": [
4257
+ "motion",
4258
+ "animation",
4259
+ "review",
4260
+ "craft",
4261
+ "ui"
4262
+ ],
4263
+ "recommends": [
4264
+ "animate",
4265
+ "improve-animations",
4266
+ "code-review"
4267
+ ],
4268
+ "profiles": [
4269
+ "full"
4270
+ ]
4271
+ },
4063
4272
  {
4064
4273
  "id": "review-management",
4065
4274
  "description": "Use when a public review needs an on-voice reply, when you need to earn more reviews legally, or when an aggregate rating is slipping across Google Business Profile, Trustpilot, the App Store or Google Play — including deciding reply versus flag, getting an unfair one-star removed rather than merely answered, and wiring programmatic replies. NOT a private support ticket against an SLA (that is `customer-support`), NOT churn, NPS or save plays (that is `retention`).",
@@ -5370,6 +5579,25 @@
5370
5579
  "full"
5371
5580
  ]
5372
5581
  },
5582
+ {
5583
+ "id": "write-swift",
5584
+ "description": "Modern Swift at the language level: structs and value types versus classes, Swift 6 data-race safety, actors, async/await and task groups, `some` versus `any`, ARC and retain cycles, Swift Testing, macros. Use for a deadlock, a hang, a data race, a concurrency error, or migrating to Swift 6.",
5585
+ "tags": [
5586
+ "swift",
5587
+ "language",
5588
+ "concurrency",
5589
+ "generics",
5590
+ "testing"
5591
+ ],
5592
+ "recommends": [
5593
+ "swift-ios",
5594
+ "apple-design",
5595
+ "testing-web"
5596
+ ],
5597
+ "profiles": [
5598
+ "full"
5599
+ ]
5600
+ },
5373
5601
  {
5374
5602
  "id": "youtube-api",
5375
5603
  "description": "Use when wiring code to a real YouTube channel: user OAuth 2.0, resumable videos.insert uploads, editing metadata after publish, pulling views/watch time/retention/traffic from the Analytics API v2 into a dated 02-DOCS/wiki/youtube/ feedback log. NOT what to publish or how to title and thumbnail it (that is youtube-strategy / youtube-packaging).",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "Eric Risco's agent-skills catalog as a granular, self-recommending CLI installer.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,13 +1,39 @@
1
1
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
 
4
+ // What counts as a frontend, and which stack skill owns it. Before this table, "frontend" meant
5
+ // React or Next and nothing else, so a Vue/Svelte/Astro/Angular/Solid repo got no design skill at
6
+ // all — even though the catalog ships a skill for each of them. Every entry here also earns the
7
+ // `design-eng` umbrella, which is the one skill that routes a UI request to the sibling that owns
8
+ // it. Kept as a table, not a chain of `if`s, so adding a framework is one line and the test
9
+ // iterates it. Spec: 02-DOCS/wiki/sdd/specs/motion-craft-skills.md
10
+ const FRONTEND_DEPS = [
11
+ ['next', 'nextjs'],
12
+ ['react', 'nextjs'],
13
+ ['vue', 'vue-nuxt'],
14
+ ['nuxt', 'vue-nuxt'],
15
+ ['svelte', 'svelte'],
16
+ ['@sveltejs/kit', 'svelte'],
17
+ ['astro', 'astro'],
18
+ ['@angular/core', 'angular'],
19
+ ['solid-js', 'solid-js'],
20
+ ];
21
+
4
22
  export function detectRepo(dir = process.cwd()) {
5
23
  const found = new Set();
6
24
  const has = (f) => existsSync(join(dir, f));
7
25
  const pkg = has('package.json') ? safeJson(join(dir, 'package.json')) : null;
8
26
  if (pkg) {
9
27
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
10
- if (deps.next || deps.react) { found.add('nextjs'); found.add('design'); }
28
+ for (const [dep, stack] of FRONTEND_DEPS) {
29
+ if (!deps[dep]) continue;
30
+ found.add(stack);
31
+ found.add('design');
32
+ // The umbrella, not a motion skill: it decides which of the craft siblings answers. A repo
33
+ // that never animates anything still benefits from the routing, and one that does gets sent
34
+ // to the right place instead of having `design` guess.
35
+ found.add('design-eng');
36
+ }
11
37
  }
12
38
  if (has('pubspec.yaml')) { found.add('flutter'); found.add('design'); }
13
39
  if (has('requirements.txt') || has('pyproject.toml')) found.add('fastapi');
@@ -146,18 +146,50 @@ export function basesFor(docPath, { mode, root }) {
146
146
  return [dirname(docPath), root, join(root, '02-DOCS'), join(root, '02-DOCS', 'wiki'), join(root, 'skills')];
147
147
  }
148
148
 
149
- const resolves = (target, bases) => bases.some((b) => existsSync(resolve(b, target)));
149
+ // `existsSync` asks the filesystem, and on APFS/HFS+ (macOS) and NTFS (Windows) the filesystem
150
+ // ignores case. So a link to `AUDIT.md` resolves against a file named `audit.md` and this gate goes
151
+ // GREEN on the maintainer's machine while Linux CI — and every installed user — sees a broken link.
152
+ // That is P2 in its most expensive form: the gate fails open exactly where the work is done. It
153
+ // happened for real (the motion-craft port moved references and one link kept its old casing).
154
+ //
155
+ // So existence is checked one path segment at a time against the parent's actual directory listing,
156
+ // which is byte-exact on every filesystem. The walk starts at `root`, which is real by
157
+ // construction; the declared hole is a target that resolves OUTSIDE the repo, where there is no
158
+ // listing we can trust — the repo's own path may sit under a differently-cased volume or behind a
159
+ // symlink, and a false positive in a blocking gate costs more than a named gap.
160
+ // Spec: 02-DOCS/wiki/sdd/specs/drift-check-case.md
161
+ function existsExact(absTarget, root, cache) {
162
+ const rel = relative(root, absTarget);
163
+ // Outside the repo (or the root itself): keep the old behaviour, and say so rather than guess.
164
+ if (rel === '' || rel.startsWith('..') || rel.startsWith('/')) return existsSync(absTarget);
165
+
166
+ let dir = root;
167
+ for (const segment of rel.split('/')) {
168
+ let entries = cache.get(dir);
169
+ if (entries === undefined) {
170
+ // An unreadable directory is not evidence of drift; fall back rather than accuse.
171
+ try { entries = new Set(readdirSync(dir)); } catch { return existsSync(absTarget); }
172
+ cache.set(dir, entries);
173
+ }
174
+ if (!entries.has(segment)) return false;
175
+ dir = join(dir, segment);
176
+ }
177
+ return true;
178
+ }
179
+
180
+ const resolves = (target, bases, root, cache) =>
181
+ bases.some((b) => existsExact(resolve(b, target), root, cache));
150
182
 
151
183
  // Knowledge mode only. The authoring standards write per-skill paths generically — "every skill
152
184
  // carries `evals/cases.yaml`" — which is a claim about a shape shared by 258 directories, not a
153
185
  // location. Resolving it against any one skill is the honest reading; treating it as drift
154
186
  // produced five findings that were never wrong.
155
- function resolvesInAnySkill(target, root) {
187
+ function resolvesInAnySkill(target, root, cache) {
156
188
  if (target.startsWith('.') || target.startsWith('/')) return false;
157
189
  const skillsDir = join(root, 'skills');
158
190
  if (!existsSync(skillsDir)) return false;
159
191
  for (const e of readdirSync(skillsDir, { withFileTypes: true })) {
160
- if (e.isDirectory() && existsSync(join(skillsDir, e.name, target))) return true;
192
+ if (e.isDirectory() && existsExact(join(skillsDir, e.name, target), root, cache)) return true;
161
193
  }
162
194
  return false;
163
195
  }
@@ -186,6 +218,9 @@ export function scanTree(dir, { mode, root = REPO, label = null } = {}) {
186
218
  const findings = [];
187
219
  let claims = 0;
188
220
  let skipped = 0;
221
+ // Directory listings, memoized per scan rather than per module: 824 claims share a few hundred
222
+ // directories, and a cache that outlived the scan would answer from a stale listing.
223
+ const cache = new Map();
189
224
  for (const doc of markdownFiles(dir)) {
190
225
  const text = readFileSync(doc, 'utf8');
191
226
  const raw = mode === 'catalog'
@@ -199,8 +234,8 @@ export function scanTree(dir, { mode, root = REPO, label = null } = {}) {
199
234
  continue;
200
235
  }
201
236
  claims++;
202
- if (mode === 'knowledge' && resolvesInAnySkill(c.target, root)) continue;
203
- if (!resolves(c.target, bases)) {
237
+ if (mode === 'knowledge' && resolvesInAnySkill(c.target, root, cache)) continue;
238
+ if (!resolves(c.target, bases, root, cache)) {
204
239
  findings.push({
205
240
  doc: relative(root, doc),
206
241
  line: c.line,
@@ -17,13 +17,14 @@ export const DOMAINS = [
17
17
  { title: 'AI — build it in', ids: ['building-agents', 'rag', 'embeddings-search', 'prompt-engineering', 'llm-pipeline', 'agent-eval', 'chatbot', 'ai-media', 'replicate-images', 'structured-extraction', 'agent-safety', 'cost-tracking'] },
18
18
  { title: 'AI — run it on', ids: ['replicate', 'runpod', 'modal', 'huggingface', 'ollama', 'together-fireworks', 'fal'] },
19
19
  { title: 'AI — train it', ids: ['machine-learning', 'deep-learning', 'nlp', 'finetuning', 'training-data', 'unsloth', 'open-weights', 'vllm'] },
20
- { title: 'Languages', ids: ['typescript', 'python', 'java', 'csharp-dotnet', 'php', 'ruby', 'cpp', 'elixir', 'bash-scripting', 'sql', 'go'] },
21
- { title: 'Frameworks & app stacks', ids: ['fastapi', 'nextjs', 'react', 'react-native', 'vue-nuxt', 'angular', 'svelte', 'astro', 'solid-js', 'htmx', 'nodejs', 'nestjs', 'django', 'laravel', 'rails', 'spring-boot', 'phoenix', 'flutter', 'swift-ios', 'kotlin-android', 'compose-multiplatform', 'expo', 'tauri', 'electron', 'rust', 'wordpress', 'shopify', 'no-code-app', 'chrome-extension', 'api-design'] },
20
+ { title: 'Languages', ids: ['typescript', 'python', 'java', 'csharp-dotnet', 'php', 'ruby', 'cpp', 'elixir', 'bash-scripting', 'sql', 'go', 'write-swift'] },
21
+ { title: 'Frameworks & app stacks', ids: ['fastapi', 'nextjs', 'react', 'react-native', 'vue-nuxt', 'angular', 'svelte', 'astro', 'solid-js', 'htmx', 'nodejs', 'nestjs', 'django', 'laravel', 'rails', 'spring-boot', 'phoenix', 'flutter', 'swift-ios', 'kotlin-android', 'compose-multiplatform', 'expo', 'tauri', 'electron', 'rust', 'wordpress', 'shopify', 'no-code-app', 'chrome-extension', 'api-design', 'pick-ui-library', 'ask-sonner'] },
22
22
  { title: 'Game development', ids: ['godot', 'unity', 'unreal', 'game-design', 'game-storytelling', 'level-design', 'gamedev-shaders', 'gamedev-multiplayer', 'gamedev-physics', 'gamedev-pathing', 'gamedev-shipping'] },
23
23
  { title: 'Databases & data layer', ids: ['postgresdb', 'mysql', 'mongodb', 'redis', 'supabase', 'neon', 'planetscale', 'sqlite-turso', 'prisma-orm', 'drizzle-orm', 'firebase', 'dynamodb', 'vector-db', 'clickhouse-analytics', 'duckdb', 'db-migrations', 'backups'] },
24
24
  { title: 'Ship & operate — platforms', ids: ['vercel', 'netlify', 'cloudflare', 'railway', 'render', 'fly-io', 'coolify', 'hetzner', 'digitalocean', 'aws-essentials', 'gcp-essentials'] },
25
25
  { title: 'Ship & operate — devops', ids: ['docker', 'github-actions', 'git-workflow', 'domains-dns', 'monitoring', 'email-deliverability', 'scaling', 'deployment', 'deprecation'] },
26
26
  { title: 'Ship & operate — quality & security', ids: ['code-review', 'security-scan', 'secure-coding', 'testing-py', 'testing-web', 'testing-go', 'e2e-testing', 'accessibility', 'performance', 'error-handling', 'observability'] },
27
+ { title: 'Motion & interface craft', ids: ['design-eng', 'animate', 'animate-expo', 'review-animations', 'improve-animations', 'find-animation-opportunities', 'animation-vocabulary', 'apple-design', 'prototype'] },
27
28
  { title: 'Design & content craft', ids: ['design', 'presentations', 'course-storytelling', 'course-builder', 'technical-writing', 'translation-l10n'] },
28
29
  { title: 'Knowledge & meta', ids: ['knowledge-ops', 'codebase-onboarding', 'research-ops', 'decision-records', 'continuous-learning', 'skill-scout', 'context-budget', 'roast-me', 'fable-operator'] },
29
30
  ];