fushiguro-mcp 0.2.0 → 0.6.0

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 (40) hide show
  1. package/README.md +135 -3
  2. package/catalog/agents/content-editor.md +1 -1
  3. package/catalog/agents/ux-ui-specialist.md +9 -0
  4. package/catalog/skills/design-taste.md +57 -0
  5. package/catalog/skills/ios-native-craft.md +64 -0
  6. package/catalog/skills/landing-page.md +75 -0
  7. package/catalog/skills/motion-and-animation.md +70 -0
  8. package/catalog/topics/marketing-content.md +1 -1
  9. package/catalog/topics/product-engineering.md +1 -1
  10. package/dist/catalog.js +54 -4
  11. package/dist/catalog.js.map +1 -1
  12. package/dist/config.d.ts +17 -0
  13. package/dist/config.js +22 -0
  14. package/dist/config.js.map +1 -1
  15. package/dist/embeddings.d.ts +38 -0
  16. package/dist/embeddings.js +147 -0
  17. package/dist/embeddings.js.map +1 -0
  18. package/dist/index.js +6 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/knowledge.d.ts +28 -4
  21. package/dist/knowledge.js +222 -6
  22. package/dist/knowledge.js.map +1 -1
  23. package/dist/router.d.ts +2 -2
  24. package/dist/router.js +2 -2
  25. package/dist/router.js.map +1 -1
  26. package/dist/server.js +188 -10
  27. package/dist/server.js.map +1 -1
  28. package/dist/skill-install.d.ts +29 -0
  29. package/dist/skill-install.js +114 -0
  30. package/dist/skill-install.js.map +1 -0
  31. package/dist/skill-sources.d.ts +23 -0
  32. package/dist/skill-sources.js +76 -0
  33. package/dist/skill-sources.js.map +1 -0
  34. package/dist/skills-cli.d.ts +2 -0
  35. package/dist/skills-cli.js +90 -0
  36. package/dist/skills-cli.js.map +1 -0
  37. package/dist/threads.d.ts +83 -0
  38. package/dist/threads.js +274 -0
  39. package/dist/threads.js.map +1 -0
  40. package/package.json +5 -2
package/README.md CHANGED
@@ -144,7 +144,7 @@ Seven kinds of entry, each a markdown file with YAML frontmatter.
144
144
  | Kind | What it is |
145
145
  |---|---|
146
146
  | `agents/` | A specialist: a system prompt plus the tools, skills, runbooks, and connectors it works with. The router picks one per task. |
147
- | `skills/` | A reusable procedure attached to an agent's prompt when relevant. `always: true` attaches it to every agent. |
147
+ | `skills/` | A reusable procedure attached to an agent's prompt when relevant. `always: true` attaches it to every agent. Ships with design craft (motion, taste, landing pages, iOS), grounding, and analysis skills. |
148
148
  | `knowledge/` | Your own documentation, chunked and full-text indexed. The folder name is the collection, which is how agent access is scoped. |
149
149
  | `runbooks/` | A team or project process guide — steps, owner, SLA, escalation. **Shipped as templates for you to complete.** |
150
150
  | `topics/` | A business domain that ties agents, skills, knowledge, runbooks, and connectors together. |
@@ -234,6 +234,122 @@ Agents declare `intake:` questions to put to the user before work of that kind s
234
234
 
235
235
  Questions the request already answers are dropped, so answering in the first message means you are not asked again. Asking about a stack the user already named is the fastest way to get an intake step ignored entirely.
236
236
 
237
+ ## Installing skills from the ecosystem
238
+
239
+ There is a growing body of open Agent Skills, and this reads that format directly. Seven collections are curated in:
240
+
241
+ ```bash
242
+ fushiguro-mcp skills # what is available
243
+ fushiguro-mcp skills show emil-kowalski # what is in one
244
+ fushiguro-mcp skills add emil-kowalski # install all of them
245
+ fushiguro-mcp skills add jakub-krehel better-typography better-layout
246
+ ```
247
+
248
+ | Source | Author | Covers |
249
+ |---|---|---|
250
+ | `emil-kowalski` | Emil Kowalski | Animation, motion craft, Apple-flavoured design engineering |
251
+ | `jakub-krehel` | Jakub Krehel | Typography, colour, layout, accessibility, interface review |
252
+ | `meng-to` | Meng To | Design+Code skills for UI, web, media, games |
253
+ | `garden` | ConardLi | Web design engineering, knowledge retrieval, image generation |
254
+ | `landing-page-design` | Elaya Design | Landing page design |
255
+ | `tastemaker` | codeswithroh | Grounding UI in reference images and a persistent taste profile |
256
+ | `designer-skills` | Owl-Listener | Research, systems, UI, interaction, delivery |
257
+
258
+ All MIT licensed. **Nothing is vendored into this package** — installing fetches from the author's repository, so they keep authorship and you get the current version rather than a fork frozen at whenever this was published. Each installed skill carries a `_SOURCE.md` recording its origin, licence, and fetch date.
259
+
260
+ Between them these sources offer **around 270 skills**. Install selectively: skills are chosen per task, but a catalog of hundreds makes that choice noisier and gives you more to review. Start with the handful matching work you actually do.
261
+
262
+ Set `GITHUB_TOKEN` if you install several collections in one sitting — unauthenticated GitHub allows 60 requests an hour.
263
+
264
+ ### How they fit
265
+
266
+ A folder containing `SKILL.md` is one skill; the files beside it are resources it references, not separate entries. Skills in that format declare only `name` and `description`, so keywords are derived from those — without that they would sit below the selection floor and never attach to anything.
267
+
268
+ Installed skills land in your custom catalog, so they merge and layer like anything else you write.
269
+
270
+ ## Conversation threads
271
+
272
+ A long conversation about one subject costs its full length on **every** subsequent turn. Switch topic and you keep paying for context nobody is using.
273
+
274
+ **What this server cannot do:** clear your context. No MCP server can — none of them has access to your conversation window. Only you can run `/clear`.
275
+
276
+ **What it does instead** is make clearing safe, by remembering the subject for you:
277
+
278
+ ```
279
+ User has been working on a booking site, then switches subject.
280
+
281
+ brief → NEW TOPIC — this does not look like a continuation of
282
+ "Dental clinic booking site" (similarity 0).
283
+ To save tokens: summarise what was covered and call `save_thread`,
284
+ then tell the user they can run /clear.
285
+
286
+ ... much later, the subject comes back ...
287
+
288
+ brief → RESUMES thread #1 "Dental clinic booking site" (score 1) —
289
+ you discussed this before. Context recovered:
290
+ Building a booking site for a dental clinic. Next.js + Postgres,
291
+ Google Calendar sync for slots.
292
+ Decided:
293
+ - Next.js + Postgres
294
+ - Google Calendar for availability
295
+ Still open:
296
+ - Which payment provider?
297
+ ```
298
+
299
+ The decisions are the part that matters. A cleared conversation loses the *reasoning*, and re-deriving "why Postgres" costs far more than storing the sentence.
300
+
301
+ `brief` reports one of three relations on every call: **continues** the current subject, **new** subject, or **resumes** an older one. Topic matching uses the same keyword-and-semantic machinery as knowledge retrieval, so "the clinic appointment app" finds a thread titled "Dental clinic booking site".
302
+
303
+ Tools: `save_thread`, `recall_thread`, `resume_thread`, `forget_thread`.
304
+
305
+ **The thresholds lean toward staying put.** Wrongly declaring a new topic suggests clearing context that was still useful; wrongly missing one just means the conversation runs a bit long. The first mistake is much more expensive, so `FUSHIGURO_SAME_TOPIC` defaults low (0.25).
306
+
307
+ Summaries are written by the host, not this server — it cannot see your conversation, so it cannot summarise it for you.
308
+
309
+ ## Retrieval: keyword and semantic
310
+
311
+ By default `search_knowledge` uses **BM25** — keyword matching. That fails on a question phrased differently to the document that answers it:
312
+
313
+ > **Asked:** "how long do I have to get my money back on the yearly plan"
314
+ > **Document:** "Annual: pro-rated refund within 30 days of the initial charge."
315
+
316
+ Not one meaningful word in common. Enabling embeddings turns retrieval **hybrid**: both retrievers run, and results are fused by rank.
317
+
318
+ ### Enabling it
319
+
320
+ ```bash
321
+ npm install @huggingface/transformers # local model, no key, no bill
322
+ ```
323
+
324
+ That is the whole setup — the server detects it and starts embedding. Or use a hosted embedder instead, which needs no install:
325
+
326
+ ```bash
327
+ export VOYAGE_API_KEY=... # or OPENAI_API_KEY
328
+ ```
329
+
330
+ `FUSHIGURO_EMBEDDINGS` takes `auto` (default — hosted key if set, else local if installed, else off), `local`, `voyage`, `openai`, or `off`.
331
+
332
+ **The model runtime is deliberately not a dependency.** It is ~390MB, and a business that only needs keyword search should not pay for it. Without it, the package stays under 100KB and retrieval works exactly as it did before.
333
+
334
+ ### What it does and does not fix
335
+
336
+ Measured against the sample corpus with `all-MiniLM-L6-v2`:
337
+
338
+ | | Keyword only | Hybrid |
339
+ |---|---|---|
340
+ | "money back on the yearly plan" | ranks an unrelated doc first | refund policy first |
341
+ | `ERR_4021`, "Pro annual plan" | correct | still correct |
342
+
343
+ Both halves matter. A small embedding model is **noise on exact tokens** — error codes and plan names score ~0.07 against everything — and it **blurs discriminating detail**, ranking the Starter passage above the Annual one for an annual-plan question. BM25 is precise about exactly those. Neither retriever is good enough alone, which is why this fuses rather than replaces.
344
+
345
+ Fusion is Reciprocal Rank Fusion on **rank**, not score. BM25 scores and cosine similarities are incomparable scales, and normalising between them means a fudge factor that needs retuning whenever the corpus changes.
346
+
347
+ ### Operationally
348
+
349
+ Embedding runs **in the background**. The first run downloads a model (~90MB, roughly 80 seconds), and during that time searches are keyword-only — a worse answer, never a hung tool call. `status` reports which mode is live, and every result says which retriever found it (`keyword`, `semantic`, or `keyword+semantic`).
350
+
351
+ Vectors are cached in the same SQLite file and reused across restarts. Changing model or corpus re-embeds rather than mixing incompatible vector spaces. Search is brute-force cosine over normalised vectors — 3ms on a small corpus, and fine into the low tens of thousands of chunks.
352
+
237
353
  ## How routing works
238
354
 
239
355
  Each agent is scored on: keyword matches weighted by how distinctive the keyword is across the catalog (IDF), hand-written regex patterns, similarity to its example triggers, whether a matched topic vouches for it, and how past outcomes for those terms have gone.
@@ -259,6 +375,8 @@ When the top two agents score within 85% of each other, the briefing says so ins
259
375
  | `search_knowledge` | Direct retrieval from your documentation, for a factual lookup. |
260
376
  | `remember` / `recall` / `forget` | Manage learned preferences. |
261
377
  | `record_outcome` | Report how a briefed task went, so routing improves. |
378
+ | `save_thread` | Store a summary of the current topic so context can be cleared safely. |
379
+ | `recall_thread` / `resume_thread` / `forget_thread` | Find, pick back up, or delete a past conversation. |
262
380
  | `status` | What is loaded, what is indexed, and which runbooks are still templates. |
263
381
 
264
382
  Also exposes the `delegate` prompt and a `fushiguro://catalog` resource.
@@ -268,6 +386,9 @@ Also exposes the `delegate` prompt and a `fushiguro://catalog` resource.
268
386
  ```
269
387
  fushiguro-mcp Start the MCP server on stdio (what a host runs)
270
388
  fushiguro-mcp init [dir] Scaffold a business catalog, safe to re-run
389
+ fushiguro-mcp skills List curated skill collections
390
+ fushiguro-mcp skills show <source>
391
+ fushiguro-mcp skills add <source> [skill...] [--force]
271
392
  fushiguro-mcp --help
272
393
  ```
273
394
 
@@ -281,12 +402,19 @@ fushiguro-mcp --help
281
402
  | `FUSHIGURO_PROJECT_ROOT` | `cwd` | Keys `project`-scoped memories. |
282
403
  | `FUSHIGURO_MIN_SCORE` | `0.12` | Below this, no confident match. |
283
404
  | `FUSHIGURO_AMBIGUITY_RATIO` | `0.85` | Top two within this ratio are reported ambiguous. |
405
+ | `FUSHIGURO_EMBEDDINGS` | `auto` | `auto`, `local`, `voyage`, `openai`, or `off`. |
406
+ | `FUSHIGURO_EMBEDDING_MODEL` | per provider | Override the model. |
407
+ | `FUSHIGURO_EMBEDDING_BATCH` | `32` | Texts per embedding call. |
408
+ | `FUSHIGURO_SAME_TOPIC` | `0.25` | Above this similarity, a task continues the active thread. |
409
+ | `FUSHIGURO_RESUME_TOPIC` | `0.45` | Above this, an older thread is offered as a resumption. |
284
410
 
285
411
  Catalog files are re-read when they change on disk, and the knowledge index rebuilds when the corpus hash changes — edit an agent or a policy document and the next `brief` picks it up without a restart.
286
412
 
287
413
  ## Limitations, stated plainly
288
414
 
289
- - **Retrieval is lexical, not semantic.** BM25 with stemming, no embeddings. It is fast, has no external dependency, and finds documents that share vocabulary with the question it will miss a passage that answers the question in entirely different words.
415
+ - **Retrieval is keyword-only until you enable embeddings**, and a keyword search misses passages phrased differently to the question. With them enabled it is hybrid, but a small local model still blurs the details that distinguish two similar passagessee the table above. A hosted embedder is meaningfully better on domain jargon.
416
+ - **Topic detection is a similarity score, not comprehension.** A task that changes subject while reusing the old vocabulary reads as a continuation; a genuine continuation phrased in fresh words can read as new. Both thresholds are tunable, and the similarity is always reported so the call can be second-guessed.
417
+ - **Vector search is brute force.** Every query scores every chunk. That is milliseconds up to the low tens of thousands of chunks and will not scale past that without a real vector index.
290
418
  - **Complexity assessment is lexical.** It reads the shape of the request, not its meaning. An innocuous-sounding sentence hiding a quarter of work will be under-scored, and a verbose request for something small will be over-scored. The signals are always shown and `mode` always overrides.
291
419
  - **Routing is lexical too**, plus learned outcome weights. It has no understanding of the task; it matches configured signal. A catalog with thin keywords routes badly, and the fix is better keywords and more `record_outcome` calls.
292
420
  - **Deduplication of memories is lexical.** Two preferences that overlap heavily in wording but differ in object may merge. Requiring three shared distinct tokens keeps this rare, not impossible.
@@ -304,7 +432,11 @@ src/
304
432
  catalog.ts loads and layers the seven entity kinds
305
433
  router.ts scoring, selection, and prompt composition
306
434
  memory.ts preferences, reinforcement, outcome learning
307
- knowledge.ts chunk index and retrieval
435
+ knowledge.ts chunk index, hybrid retrieval, rank fusion
436
+ embeddings.ts pluggable embedding providers (local, voyage, openai)
437
+ threads.ts conversation topics, continuity detection, resumption
438
+ skill-sources.ts / skill-install.ts / skills-cli.ts
439
+ curated ecosystem skill collections and their installer
308
440
  text.ts tokenising and similarity helpers
309
441
  catalog/ the packaged base catalog
310
442
  examples/ a worked custom catalog, and an .mcp.json template
@@ -36,7 +36,7 @@ triggers:
36
36
  patterns:
37
37
  - "\\b(tone\\s+of\\s+voice|brand\\s+voice|style\\s+guide)\\b"
38
38
  - "\\b(press\\s+release|release\\s+notes|newsletter)\\b"
39
- skills: [stakeholder-summary]
39
+ skills: [stakeholder-summary, landing-page]
40
40
  tools: [Read, Write, Edit, Grep]
41
41
  ---
42
42
 
@@ -42,6 +42,13 @@ keywords:
42
42
  - booking
43
43
  - checkout
44
44
  - onboarding flow
45
+ - screen
46
+ - page
47
+ - polish
48
+ - looks off
49
+ - feels off
50
+ - looks cheap
51
+ - visual
45
52
  triggers:
46
53
  - "design a settings screen"
47
54
  - "make this look better"
@@ -54,6 +61,8 @@ patterns:
54
61
  - "\\b(liquid\\s*glass|glassmorph\\w*|frosted)\\b"
55
62
  - "\\b(figma|design\\s*system|design\\s*tokens?)\\b"
56
63
  - "\\b(hig|human\\s+interface\\s+guidelines)\\b"
64
+ skills:
65
+ - design-taste
57
66
  tools:
58
67
  - Read
59
68
  - Write
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: design-taste
3
+ title: Design Taste
4
+ description: The judgement that separates competent from good — restraint, hierarchy, defaults worth overriding, and how to tell a real critique from a preference.
5
+ keywords:
6
+ - taste
7
+ - aesthetic
8
+ - polish
9
+ - refine
10
+ - critique
11
+ - feedback
12
+ - looks off
13
+ - feels cheap
14
+ - visual quality
15
+ - craft
16
+ - detail
17
+ - restraint
18
+ - hierarchy
19
+ applies_to: []
20
+ always: false
21
+ ---
22
+
23
+ Taste is not decoration. It is the accumulated judgement about what to leave out, and it is mostly learnable by knowing what to look at.
24
+
25
+ ## When something "looks off" and nobody can say why
26
+
27
+ Check these, in this order. The answer is nearly always in the first three.
28
+
29
+ 1. **Spacing is inconsistent.** Nothing makes work look amateur faster than 13px here and 16px there. Everything sits on one scale — 4 or 8px — with no exceptions you cannot justify in a sentence.
30
+ 2. **Too many type sizes and weights.** Most interfaces need four sizes and two weights. If a screen has nine, the hierarchy has stopped meaning anything, because everything is emphasised.
31
+ 3. **Contrast is doing no work.** If the primary action, a secondary action, and body text all sit at similar visual weight, the eye has nowhere to go. Something must be clearly loudest.
32
+ 4. **Too many competing accents.** One accent colour, used for the thing you want clicked. A second accent is a decision to have no accent.
33
+ 5. **Corner radii disagree.** A 4px card containing an 8px button reads as broken even to people who cannot name why. Nested radii should share a centre: inner radius = outer radius minus the padding between them.
34
+ 6. **Borders where shadows belong, or both.** Pick one system for separating surfaces and use it throughout.
35
+ 7. **Text touching its container.** Padding is almost always too tight before it is too loose, and the gap under a heading should be smaller than the gap above it — the heading belongs to what follows.
36
+
37
+ ## Restraint
38
+
39
+ The instinct that most reliably improves a design is deletion. Remove the divider and see whether spacing alone separates the groups. Remove the icon and see whether the label was already clear. Remove the card and see whether the content needed a container at all.
40
+
41
+ Every border, shadow, gradient, and icon must earn its place by doing a job that whitespace and typography could not do. Most of them cannot.
42
+
43
+ **Defaults are usually right.** The platform's system font, its native controls, its standard easing. Override them when you have a specific reason you can articulate — not to look distinctive. Distinctiveness that comes from overriding defaults reads as noise; distinctiveness that comes from a genuinely different structure reads as design.
44
+
45
+ ## Density
46
+
47
+ Match the density to the user, not to your screenshot. A tool someone lives in all day should be denser than its marketing site suggests — generous spacing looks better in a portfolio and wastes the screen of someone reconciling three hundred rows. Ask how often a person uses this, and how much they need on screen at once.
48
+
49
+ ## Critique that is worth receiving
50
+
51
+ **Describe the effect before prescribing the fix.** "The primary action is competing with the filter row, so my eye lands on the filters first" is useful. "Make the button blue" skips the reasoning and is just your preference wearing a suit.
52
+
53
+ **Separate the three kinds of comment** and label them: this is broken, this is inconsistent with the system, this is my preference. Conflating the third with the first is how design review becomes an argument about nothing.
54
+
55
+ **Say what is working.** Not to soften anything — because the author needs to know which parts to leave alone in the next revision.
56
+
57
+ When you cannot articulate why something is wrong, say that too, and describe what you notice instead. "Something about the header feels heavy — possibly the logo size against the nav" is honest and actionable. Inventing a confident rationale for an instinct is how bad rules get propagated.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: ios-native-craft
3
+ title: iOS Native Craft
4
+ description: Making an iOS app feel native rather than ported — system materials, navigation idioms, gestures, haptics, and the details that give away a web app in a shell.
5
+ keywords:
6
+ - ios
7
+ - iphone
8
+ - ipad
9
+ - swiftui
10
+ - uikit
11
+ - native
12
+ - apple
13
+ - hig
14
+ - human interface
15
+ - sheet
16
+ - navigation stack
17
+ - tab bar
18
+ - haptic
19
+ - dynamic type
20
+ - safe area
21
+ - san francisco
22
+ - sf symbols
23
+ - liquid glass
24
+ applies_to: [ux-ui-specialist]
25
+ always: false
26
+ ---
27
+
28
+ An app feels native when it behaves the way every other app on the device behaves. Most of that is not visual — it is gesture, timing, and the system doing work you did not reimplement.
29
+
30
+ ## Use the system's own components
31
+
32
+ The single largest source of "this feels wrong" is a reimplemented system control. Native sheets, navigation stacks, tab bars, context menus, and pickers carry behaviour that is genuinely hard to rebuild: interruptible dismiss gestures that track your finger, rubber-banding at scroll boundaries, correct focus and VoiceOver order, keyboard avoidance, Dynamic Type response, and automatic adaptation to Reduce Transparency and Reduce Motion.
33
+
34
+ Reach for a custom control only when the system has no equivalent — not to make it look different. A restyled system component keeps the behaviour; a rebuilt one loses it.
35
+
36
+ ## The details that give a port away
37
+
38
+ - **Scroll physics.** iOS deceleration and rubber-banding are specific. Anything else reads as foreign immediately, before a user could tell you why.
39
+ - **Interruptible gestures.** A sheet must follow the finger and settle from wherever it is when released, with velocity. A sheet that plays a fixed dismiss animation is the clearest tell there is.
40
+ - **Safe areas.** Content scrolls *under* the status bar and home indicator; it does not stop short of them. Interactive elements stay clear of both.
41
+ - **Dynamic Type.** Text scales with the user's setting, and the layout absorbs it. Test at the largest accessibility size — that is where fixed-height rows and truncated labels break.
42
+ - **Haptics on state change, not on every tap.** A selection change, a success, an error. Haptics on ordinary buttons become noise, and users turn them off system-wide.
43
+ - **Back gesture from the left edge.** Never intercept it. If it does not work, the app is broken to a large share of users.
44
+ - **SF Symbols** over custom icons wherever one exists. They align optically with San Francisco, respond to weight and scale, and stay correct across OS versions.
45
+
46
+ ## Navigation
47
+
48
+ Match structure to the app. A tab bar is for a handful of peer destinations that people move between constantly — not a place to hide a settings screen. A navigation stack is for drilling into a hierarchy. A sheet is for a self-contained task the user will finish or abandon, and it should be dismissible by gesture.
49
+
50
+ Modal presentation is a commitment: it says "finish this or cancel it". Using a sheet for something the user needs to reference other screens for is a design error that surfaces as people repeatedly dismissing and reopening it.
51
+
52
+ ## Liquid Glass
53
+
54
+ The current material system floats chrome above content on a translucent, refractive surface. Use the platform's own material rather than reimplementing blur — the real one responds to scroll position and wallpaper, and degrades correctly under Reduce Transparency.
55
+
56
+ Content stays opaque; only navigation, toolbars, tab bars, sheets, and controls take the material. Layer it at most once — two stacked translucent surfaces destroy legibility. Nested corner radii share a centre so the curves stay parallel.
57
+
58
+ ## Before calling it done
59
+
60
+ Run it at the largest Dynamic Type size, with Reduce Motion on, with Reduce Transparency on, and under VoiceOver. Each of those is a real user setting, and each one is where an app that looked finished turns out not to be.
61
+
62
+ ---
63
+
64
+ *Influences worth reading directly: Apple's Human Interface Guidelines, and Meng To's Design+Code material on iOS and SwiftUI design.*
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: landing-page
3
+ title: Landing Page Craft
4
+ description: Landing pages that earn the next click — message hierarchy, proof, the fold, calls to action, and the sections that can usually be deleted.
5
+ keywords:
6
+ - landing page
7
+ - hero
8
+ - above the fold
9
+ - headline
10
+ - subheadline
11
+ - cta
12
+ - call to action
13
+ - conversion
14
+ - social proof
15
+ - testimonial
16
+ - pricing page
17
+ - waitlist
18
+ - signup
19
+ - marketing site
20
+ - homepage
21
+ - value proposition
22
+ applies_to: []
23
+ always: false
24
+ ---
25
+
26
+ A landing page has one job: get a specific visitor to take one specific next step. Every section either moves them toward it or is costing you.
27
+
28
+ ## Before writing a word
29
+
30
+ Answer three questions, out loud, and put the answers at the top of your work:
31
+
32
+ 1. **Who is arriving, and from where?** Someone from a targeted ad already knows the category. Someone from a general search does not. The same page cannot serve both well — pick.
33
+ 2. **What is the one action?** Sign up, book a demo, install, buy. One. A page with three equally-weighted calls to action converts on none of them.
34
+ 3. **What do they need to believe to take it?** That list is your section outline. Nothing else belongs on the page.
35
+
36
+ ## The fold
37
+
38
+ Assume you get one glance. In it, the visitor must learn **what this is**, **who it is for**, and **what to do next**.
39
+
40
+ - **The headline says what the product does, not how the company feels about it.** "Schedule patients without the phone calls" beats "Reimagining dental practice management." If the headline would work equally well on a competitor's site, it says nothing.
41
+ - **The subheadline handles the objection the headline raises**, or names the audience precisely. It is the only place you get to add a clause.
42
+ - **One primary call to action**, visually dominant. A secondary action may exist, but it must look secondary — a link, not a second button of equal weight.
43
+ - **Show the product.** A real screenshot, a short loop of actual use. Not an abstract illustration, not a stock photo, and not a laptop mockup at a 30-degree angle that renders the interface unreadable.
44
+
45
+ ## Proof, in order of strength
46
+
47
+ 1. Specific numbers from named customers
48
+ 2. Named testimonials with a role and a company
49
+ 3. Logos of recognisable users — only if genuinely users
50
+ 4. Aggregate stats ("10,000 teams")
51
+ 5. Unattributed quotes — nearly worthless; the reader assumes you wrote them
52
+
53
+ Put proof directly after the first claim it supports, not in a lonely "Testimonials" section at the bottom that nobody scrolls to.
54
+
55
+ **Never fabricate proof.** No invented testimonials, no logos of companies that are not customers, no statistics you cannot source. Beyond being dishonest, it is the single fastest way to lose a visitor who checks.
56
+
57
+ ## Sections that usually earn their place
58
+
59
+ The fold. How it works, in three steps. The main objection, answered. Proof. Pricing, if it exists — hiding it costs more trust than the number does. A final call to action.
60
+
61
+ ## Sections that usually do not
62
+
63
+ A mission statement. A team photo, on a page for a product nobody has bought yet. A feature grid of twelve icons with three-word labels. A blog feed. An FAQ answering questions nobody asked, used to pad the page. "As seen in" logos for a single unremarkable mention.
64
+
65
+ Delete a section, read the page, and see whether anything is missing. Usually nothing is.
66
+
67
+ ## Writing
68
+
69
+ Second person. Present tense. Short sentences. Specific verbs.
70
+
71
+ Cut every instance of *seamless*, *robust*, *powerful*, *revolutionise*, *leverage*, *solutions*, and *empower*. They appear on every competitor's page, which means they cannot distinguish yours. If a sentence survives having its adjectives removed, it was carrying real information.
72
+
73
+ ## Before it ships
74
+
75
+ Check it on a phone first, not last — most visitors arrive on one. Check the page still makes sense with images blocked. Check that the primary action is reachable without scrolling on a small screen. Check the contrast on any text over an image, which is where it always fails.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: motion-and-animation
3
+ title: Motion and Animation
4
+ description: Interface animation that explains rather than decorates — easing, duration, spring physics, orchestration, interruptibility, and knowing when not to animate.
5
+ keywords:
6
+ - animation
7
+ - animate
8
+ - motion
9
+ - transition
10
+ - easing
11
+ - spring
12
+ - keyframe
13
+ - framer motion
14
+ - gsap
15
+ - interpolate
16
+ - gesture
17
+ - drag
18
+ - scroll animation
19
+ - micro-interaction
20
+ - loading state
21
+ - skeleton
22
+ - reduced motion
23
+ applies_to: [ux-ui-specialist]
24
+ always: false
25
+ ---
26
+
27
+ Animation has one job: explain a change the user would otherwise have to reconstruct. Where did this come from, where did it go, what is related to what. Anything that does not do that job is decoration, and decoration in an interface people use daily becomes an irritation by the fiftieth viewing.
28
+
29
+ ## The defaults you start from
30
+
31
+ - **Duration scales with distance and size.** A 40px toggle settles in ~150ms. A full-screen sheet takes ~350ms. Using one duration for everything makes small things feel sluggish and large things feel abrupt.
32
+ - **Ease-out for things entering, ease-in for things leaving.** Entering elements decelerate into place, so they arrive under control. Leaving elements accelerate away, because nobody needs to watch an exit finish. Ease-in-out is for things moving between two on-screen positions.
33
+ - **Never animate with linear easing** except for continuous, non-physical motion — a spinner, a progress bar, a marquee. Linear reads as mechanical because nothing in the physical world moves that way.
34
+ - **Animate transforms and opacity.** `transform` and `opacity` are composited; `width`, `height`, `top`, and `left` trigger layout on every frame. If you must animate size, prefer `scale` and correct the distortion, or accept the cost knowingly.
35
+
36
+ ## Springs
37
+
38
+ Springs beat duration-based curves for anything a user can interrupt — dragging, dismissing, toggling — because they carry velocity. A sheet flicked hard should settle faster than one nudged gently, and only a spring can express that.
39
+
40
+ Tune with damping and stiffness, not by converting to a duration. A useful starting point: stiffness ~200, damping ~25 for UI-scale movement. Increase damping until the overshoot stops being charming and starts looking like a bug — for most product UI, that is one small overshoot at most, and often none.
41
+
42
+ Reach for a duration curve when the motion is not interruptible and must be exactly repeatable: a page transition, a scripted sequence.
43
+
44
+ ## Orchestration
45
+
46
+ When several things animate together, stagger them by 20–50ms rather than firing them at once. Simultaneous motion reads as one indivisible block; staggered motion reads as a list, which is usually the truth you want to convey.
47
+
48
+ Order matters. Animate in the direction attention should travel — top to bottom for a list, outward from the element the user clicked. Reverse the order on exit.
49
+
50
+ Never stagger more than about seven items. Past that, animate the container and let the contents appear with it, or the last item arrives long after the user has started reading the first.
51
+
52
+ ## Interruptibility
53
+
54
+ **An animation the user cannot interrupt is a bug.** If someone opens a menu and immediately closes it, the close must start from wherever the open animation had reached, not from its finished state and not after it completes. Anything else feels like the interface arguing with them.
55
+
56
+ This is the single most common thing separating animation that feels good from animation that feels expensive-but-annoying. Test it deliberately: trigger every animation and immediately trigger its opposite.
57
+
58
+ ## When not to animate
59
+
60
+ - **Anything on the critical path of a frequent action.** A 300ms transition on a button someone presses forty times a day is two minutes a year taken from them.
61
+ - **Content that has arrived.** Text and data should appear, not fade in. A fade on real content delays reading for no informational gain.
62
+ - **State the user caused directly and expects instantly.** Checkbox ticks, focus rings, character input.
63
+
64
+ ## Accessibility, non-negotiable
65
+
66
+ Honour `prefers-reduced-motion`. It is not a request to disable feedback — it is a request to remove *movement*. Replace a slide with a cross-fade, remove parallax and auto-playing motion entirely, and keep the state change visible. An interface that becomes unintelligible under reduced motion was relying on animation to carry meaning that should have been in the layout.
67
+
68
+ ---
69
+
70
+ *Influences worth reading directly: Emil Kowalski's public writing and courses on web animation, and the motion sections of the Material and Apple Human Interface Guidelines.*
@@ -8,7 +8,7 @@ triggers:
8
8
  - "we need an announcement for"
9
9
  - "make this page convert better"
10
10
  agents: [content-editor, research-analyst]
11
- skills: [stakeholder-summary]
11
+ skills: [stakeholder-summary, landing-page, design-taste]
12
12
  knowledge: [brand, product]
13
13
  ---
14
14
 
@@ -8,7 +8,7 @@ triggers:
8
8
  - "fix this bug"
9
9
  - "review this before we ship"
10
10
  agents: [ux-ui-specialist, backend-api, database, testing, security, docs]
11
- skills: [clarify-scope]
11
+ skills: [clarify-scope, design-taste]
12
12
  runbooks: [change-release]
13
13
  knowledge: [engineering, product]
14
14
  ---
package/dist/catalog.js CHANGED
@@ -14,6 +14,41 @@ function strings(value) {
14
14
  }
15
15
  return [];
16
16
  }
17
+ /** "better-typography" -> "Better Typography", for entries with no title. */
18
+ function titleFromName(name) {
19
+ return name
20
+ .split("-")
21
+ .map((word) => (word.length > 0 ? word[0].toUpperCase() + word.slice(1) : word))
22
+ .join(" ");
23
+ }
24
+ const KEYWORD_STOPWORDS = new Set([
25
+ "use", "uses", "using", "when", "the", "and", "for", "with", "that", "this",
26
+ "your", "you", "from", "into", "not", "are", "any", "all", "its", "it", "or",
27
+ "a", "an", "to", "of", "in", "on", "is", "be", "as", "by", "at", "other",
28
+ "than", "them", "they", "each", "user", "asks", "wants", "make", "makes",
29
+ "made", "does", "do", "can", "will", "should", "instead", "rather", "across",
30
+ "helps", "help", "focuses", "focus", "covers", "provides", "includes",
31
+ "ensures", "turns", "like", "such", "these", "those", "more", "most", "very",
32
+ "want", "need", "needs", "get", "gets", "put", "one", "two", "way", "ways",
33
+ ]);
34
+ /** Distinctive words from a name and description, for entries with no keywords. */
35
+ function deriveKeywords(name, description) {
36
+ const words = `${name.replace(/-/g, " ")} ${description}`
37
+ .toLowerCase()
38
+ .match(/[a-z][a-z0-9+#.-]{2,}/g) ?? [];
39
+ const seen = new Set();
40
+ const out = [];
41
+ for (const raw of words) {
42
+ const word = raw.replace(/[.-]+$/, "");
43
+ if (word.length < 3 || KEYWORD_STOPWORDS.has(word) || seen.has(word))
44
+ continue;
45
+ seen.add(word);
46
+ out.push(word);
47
+ if (out.length >= 20)
48
+ break;
49
+ }
50
+ return out;
51
+ }
17
52
  function slugify(input) {
18
53
  return input
19
54
  .toLowerCase()
@@ -148,13 +183,18 @@ function buildEntity(kind, raw) {
148
183
  throw new Error(`${file}: invalid regex '${src}' (${err.message})`);
149
184
  }
150
185
  }
186
+ const description = String(meta.description ?? "");
187
+ const declared = strings(meta.keywords).map((k) => k.toLowerCase());
151
188
  const base = {
152
189
  kind,
153
190
  layer,
154
191
  name,
155
- title: String(meta.title ?? name),
156
- description: String(meta.description ?? ""),
157
- keywords: strings(meta.keywords).map((k) => k.toLowerCase()),
192
+ title: String(meta.title ?? titleFromName(name)),
193
+ description,
194
+ // Skills imported from the wider ecosystem declare only name and
195
+ // description. Deriving keywords from those is what makes them reachable —
196
+ // without it they sit below the selection floor and never attach.
197
+ keywords: declared.length > 0 ? declared : deriveKeywords(name, description),
158
198
  triggers: strings(meta.triggers),
159
199
  patterns,
160
200
  topics: strings(meta.topics),
@@ -247,7 +287,14 @@ function buildEntity(kind, raw) {
247
287
  };
248
288
  }
249
289
  }
250
- /** Every markdown file under `dir`, recursively, with its path relative to `dir`. */
290
+ /**
291
+ * Every markdown file under `dir`, recursively, with its path relative to `dir`.
292
+ *
293
+ * A directory containing `SKILL.md` is treated as a single packaged skill: only
294
+ * that file is an entity, and its siblings (cheat sheets, templates, audit
295
+ * checklists) are resources it references. This is the layout the Agent Skills
296
+ * ecosystem uses, so skills can be installed from upstream repositories intact.
297
+ */
251
298
  function walk(dir) {
252
299
  let entries;
253
300
  try {
@@ -256,6 +303,9 @@ function walk(dir) {
256
303
  catch {
257
304
  return [];
258
305
  }
306
+ const packaged = entries.find((e) => e.isFile() && e.name === "SKILL.md");
307
+ if (packaged)
308
+ return [{ path: join(dir, packaged.name), rel: packaged.name }];
259
309
  const out = [];
260
310
  for (const entry of entries) {
261
311
  const full = join(dir, entry.name);