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.
- package/README.md +135 -3
- package/catalog/agents/content-editor.md +1 -1
- package/catalog/agents/ux-ui-specialist.md +9 -0
- package/catalog/skills/design-taste.md +57 -0
- package/catalog/skills/ios-native-craft.md +64 -0
- package/catalog/skills/landing-page.md +75 -0
- package/catalog/skills/motion-and-animation.md +70 -0
- package/catalog/topics/marketing-content.md +1 -1
- package/catalog/topics/product-engineering.md +1 -1
- package/dist/catalog.js +54 -4
- package/dist/catalog.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.js +22 -0
- package/dist/config.js.map +1 -1
- package/dist/embeddings.d.ts +38 -0
- package/dist/embeddings.js +147 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/knowledge.d.ts +28 -4
- package/dist/knowledge.js +222 -6
- package/dist/knowledge.js.map +1 -1
- package/dist/router.d.ts +2 -2
- package/dist/router.js +2 -2
- package/dist/router.js.map +1 -1
- package/dist/server.js +188 -10
- package/dist/server.js.map +1 -1
- package/dist/skill-install.d.ts +29 -0
- package/dist/skill-install.js +114 -0
- package/dist/skill-install.js.map +1 -0
- package/dist/skill-sources.d.ts +23 -0
- package/dist/skill-sources.js +76 -0
- package/dist/skill-sources.js.map +1 -0
- package/dist/skills-cli.d.ts +2 -0
- package/dist/skills-cli.js +90 -0
- package/dist/skills-cli.js.map +1 -0
- package/dist/threads.d.ts +83 -0
- package/dist/threads.js +274 -0
- package/dist/threads.js.map +1 -0
- 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
|
|
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 passages — see 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
|
|
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
|
- "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
|
|
157
|
-
|
|
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
|
-
/**
|
|
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);
|