Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# AGENTS.md

Guidance for AI agents (and humans) working in this repository.

## Two branches, two jobs

- **`main`** (default) — the installable **plugin** for Claude / Codex / Gemini. Holds the `.claude-plugin/` manifests and the `skills/skill-patterns/` skill.
- **`gh-pages`** — the **website** that publishes [skillpatterns.ai](https://skillpatterns.ai) (a Jekyll site). It holds the **canonical pattern catalog** in `_patterns/*.md` and generates `/llms.txt` and `/patterns.json` on build.

## The catalog lives on gh-pages; the plugin carries a snapshot

The source of truth for the patterns is `_patterns/*.md` on **gh-pages**. The plugin on **main** ships a *generated snapshot* at `skills/skill-patterns/references/patterns.md` — never hand-edit it.

## ⚠️ On ANY catalog change, update BOTH branches

When you add, edit, rename, or remove a pattern (or change categories), do both:

1. **`gh-pages`** — edit the source in `_patterns/` (and `_data/categories.yml` for category changes), then rebuild and verify:
```
bundle exec jekyll build
```
2. **`main`** — regenerate the plugin's snapshot from the rebuilt catalog and commit:
```
curl -s https://skillpatterns.ai/llms.txt > skills/skill-patterns/references/patterns.md
```
(Before the site is live, copy the freshly built `_site/llms.txt` instead.)

Commit on **both** branches. A change that lands on only one branch leaves the site and the plugin out of sync.

## Conventions

- Patterns sort **alphabetically by title** within their category; the `order` frontmatter field is unused.
- `/llms.txt` and `/patterns.json` regenerate from `_patterns/` automatically on build — don't edit them by hand.
- Pattern frontmatter: `title`, `slug`, `icon` (FontAwesome Free), `category` (a key in `_data/categories.yml`), `summary`, `adds` (list), `prompt` (block scalar).

## Deploy

GitHub Pages builds from the **`gh-pages`** branch (`CNAME` → skillpatterns.ai lives there). `main` is the default branch and the plugin source.
1 change: 1 addition & 0 deletions CLAUDE.md
119 changes: 119 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Design System — skillpatterns.ai

The site's visual language, captured so changes stay consistent. The source of
truth is `assets/css/style.css`; this file explains the intent behind it.

## Principles

- **Semantic tokens, never hard-coded color.** Every color is a CSS variable on
`:root` with a `[data-theme="dark"]` override. New components must use tokens
so dark mode works for free.
- **Restraint.** Subtraction default — an element earns its pixels or it's cut.
- **Accessibility is not optional.** Text meets WCAG AA contrast (≥4.5:1);
interactive chrome meets the 44px touch target (AAA 2.5.5) where practical, and
never less than 24px (AA 2.5.8). Keyboard focus is always visible.

## Color tokens

Defined in `:root` (light) and overridden under `[data-theme="dark"]`.

| Token | Light | Dark | Use |
|-------|-------|------|-----|
| `--bg` | `#ffffff` | `#0f1115` | Page background |
| `--bg-elevated` | `#f7f8fa` | `#14161c` | Cards, chips, inputs, hovers |
| `--border` | `#e7e9ee` | `#23262f` | Hairline dividers |
| `--border-strong` | `#dfe2e8` | `#2a2e38` | Input borders, emphasis |
| `--text` | `#0b1020` | `#f1f3f8` | Primary text, headings |
| `--text-muted` | `#4b5263` | `#a9b0be` | Body copy, secondary text |
| `--text-faint` | `#6b7280` | `#8b93a3` | Tertiary labels, captions, footer |
| `--accent` | `#3858E9` | `#6b8aff` | Links, active state, highlights |
| `--accent-contrast` | `#ffffff` | `#0b1020` | Text on an accent fill |

The `--prompt-*` tokens style the example-prompt card (a warm "code" surface).

**Contrast rule:** `--text-faint` is the lightest text allowed on body copy and
clears 4.5:1 on both `--bg` and `--bg-elevated`. Do not introduce a lighter gray
for text. Verify any new text/background pair at ≥4.5:1 (≥3:1 for ≥18px bold).

### Category accents (`--cat`)

Color is **wayfinding**: each of the six categories owns a hue, set as a `--cat`
variable on `[data-cat="<key>"]` (the home section, sidebar group, jump-nav chip,
and pattern-detail wrapper). Because custom properties inherit, descendants just
reference `var(--cat, var(--accent))` — section titles, sidebar labels, chip text,
card icon tiles, the detail eyebrow/title/bullets, and the active-pattern tint all
pick it up automatically.

| Category | Light | Dark |
|----------|-------|------|
| `grounding` | `#3858E9` | `#6b8aff` |
| `decision` | `#0B7D6F` | `#3fd6c0` |
| `output` | `#683FE6` | `#a48bff` |
| `critique` | `#C5314C` | `#ff7d94` |
| `control` | `#8F5E12` | `#e0a64a` |
| `composition` | `#23783A` | `#5fc06f` |

Light hues are tuned to clear **4.5:1 on `--bg-elevated`** (the chip background, the
strictest case); dark hues clear it comfortably. Adding/recoloring a category means
adding a `--cat` pair here and re-verifying contrast. Tints (icon tiles, active
state) are derived with `color-mix(in srgb, var(--cat) N%, …)`, so they track the
hue automatically — never hard-code a tint.

## Typography

- **Sans:** system stack (`--sans`). **Mono:** system mono (`--mono`), used for the
hero eyebrow and prompt bodies.
- **Scale (px):** hero `clamp(40,7vw,68)` · page h1 `36` · intro h1 `34` · pattern
title `22` · card/section titles `16` · body `15.5–16` · meta/labels `11–14`.
- **Weight:** 800 for display/brand, 700 for titles, 500–600 for UI, 400 body.
- Uppercase micro-labels use `letter-spacing: .05–.07em` and `--text-faint`/`--accent`.

## Spacing

Informal scale; prefer multiples of ~4 (`4, 6, 8, 10, 14, 16, 18, 22, 26`).
Section rhythm leans on `padding-top` + a `--border` top rule. Radii: `--radius`
(10px) for surfaces, `8px` buttons/inputs, `12–16px` cards, `999px` pills.

## Components

- **Header** (`.site-header`) — sticky, `--header-h` (57px) tall. Nav links are
44px tap targets; the GitHub link is a FontAwesome brand glyph with an
`aria-label`.
- **Theme toggle** (`.theme-toggle`) — 40px button; FontAwesome moon/sun swapped
by `[data-theme]` via `::before`.
- **Cards** (`.pcard`) — bordered, `--card-bg`, lift on hover (gated by
`prefers-reduced-motion`).
- **Sidebar** (`.sidebar`) — desktop: sticky index. Mobile (≤820px): collapses
behind a `.sidebar-toggle` disclosure (`aria-expanded`/`aria-controls`), with an
animated reveal and rotating chevron.
- **Category nav** (`.cat-nav`) — sticky, horizontally-scrollable pill chips that
jump to `#cat-<key>` sections on the home page.
- **Prompt card** (`.prompt`) — the warm-toned copyable example partial.

## Icons

FontAwesome 6 Free (loaded via CDN, `preconnect`ed). Use `<i class="fa-solid …">`
or `fa-brands` for brand marks. Don't mix in unicode glyphs or emoji — they render
inconsistently across platforms.

## Motion

- Transitions ≤ ~250ms, `ease`/`ease-out`. Every animation has a purpose
(affordance, confirmation, reveal) — no decorative motion.
- **Always gate motion** behind `@media (prefers-reduced-motion: reduce)` — see the
block at the top of `style.css`.

## Responsive

- **≤820px:** layout stacks; sidebar becomes the disclosure; nav labels shorten
("What?", "Using"). This range serves tablets.
- **≤560px:** phone tightening — smaller hero, reduced padding, tighter card gap.

## Accessibility checklist for new work

1. Color pairs ≥4.5:1 (text) / ≥3:1 (large bold, non-text).
2. Interactive targets ≥44px (≥24px minimum for dense lists).
3. Visible `:focus-visible` ring (inherited globally — don't remove it).
4. Decorative icons get `aria-hidden="true"`; icon-only controls get `aria-label`.
5. Any motion respects `prefers-reduced-motion`.

5 changes: 5 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ description: "A catalog of reusable patterns for composing AI Skills."
url: "https://skillpatterns.ai"
baseurl: ""
repo_url: "https://github.com/borkweb/skill-patterns"
google_analytics: "G-X6BTVP0R5T"

collections:
patterns:
Expand All @@ -22,9 +23,13 @@ exclude:
- Gemfile
- Gemfile.lock
- README.md
- DESIGN.md
- LICENSE
- docs/
- .superpowers/
- vendor/
- skills/
- AGENTS.md
- CLAUDE.md
- "*.pdf"
- "*.sh"
11 changes: 11 additions & 0 deletions _includes/analytics.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{%- if jekyll.environment == "production" and site.google_analytics -%}
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', '{{ site.google_analytics }}');
</script>
{%- endif -%}
13 changes: 11 additions & 2 deletions _includes/footer.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
<footer class="site-footer">
<p>Reusable patterns for composing AI Skills.</p>
<p><a href="{{ site.repo_url }}" rel="noopener">Source on GitHub</a> · MIT License</p>
<nav class="footer-nav" aria-label="Footer">
<a href="{{ '/patterns/' | relative_url }}">Patterns</a>
<a href="{{ '/docs/' | relative_url }}">Docs</a>
<a href="{{ '/using/' | relative_url }}">Install the Skill</a>
<a href="{{ '/manual/' | relative_url }}">Manual usage</a>
<a href="{{ site.repo_url }}" rel="noopener">Source on GitHub</a>
</nav>
<p>Reusable patterns for composing AI Skills. · MIT License</p>
<button class="theme-toggle footer-theme" type="button" aria-label="Toggle dark mode">
<span class="theme-toggle__icon" aria-hidden="true"></span>
</button>
</footer>
4 changes: 3 additions & 1 deletion _includes/head.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{%- include analytics.html -%}
{% assign page_title = page.title | default: site.title %}
{% assign page_desc = page.summary | default: page.description | default: site.description %}
<title>{% if page.title and page.title != site.title %}{{ page_title }} · {{ site.title }}{% else %}{{ site.title }}{% endif %}</title>
Expand All @@ -11,8 +12,9 @@
<meta property="og:description" content="{{ page_desc | strip_newlines | escape }}">
<meta property="og:url" content="{{ page.url | absolute_url }}">
<meta name="twitter:card" content="summary">
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}?v={{ site.time | date: '%s' }}">
<link rel="icon" href="{{ '/assets/favicon.svg' | relative_url }}" type="image/svg+xml">
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/css/all.min.css">
<script>
(function () {
Expand Down
10 changes: 7 additions & 3 deletions _includes/header.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<header class="site-header">
<a class="brand" href="{{ '/' | relative_url }}">Skill<span>Patterns</span></a>
<nav class="header-nav" aria-label="Primary">
<a href="{{ '/about/' | relative_url }}">What are these?</a>
<a href="{{ '/using/' | relative_url }}">How to use</a>
<a href="{{ site.repo_url }}" rel="noopener">GitHub</a>
<button id="nav-hamburger" class="nav-hamburger" type="button" aria-label="Open navigation" aria-controls="sidebar" aria-expanded="false">
<span class="nav-hamburger__icon" aria-hidden="true"></span>
</button>
<a class="header-link" href="{{ '/patterns/' | relative_url }}"><span class="nav-full">Patterns</span><span class="nav-short">Patterns</span></a>
<a class="header-link" href="{{ '/docs/' | relative_url }}"><span class="nav-full">Docs</span><span class="nav-short">Docs</span></a>
<a class="header-link" href="{{ '/using/' | relative_url }}"><span class="nav-full">Install the Skill</span><span class="nav-short">Install</span></a>
<a class="header-link" href="{{ site.repo_url }}" rel="noopener" aria-label="GitHub"><i class="fa-brands fa-github" aria-hidden="true"></i></a>
<button id="theme-toggle" class="theme-toggle" type="button" aria-label="Toggle dark mode">
<span class="theme-toggle__icon" aria-hidden="true"></span>
</button>
Expand Down
14 changes: 13 additions & 1 deletion _includes/sidebar.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<aside class="sidebar" id="sidebar">
<nav class="sidebar-nav" aria-label="Patterns">
<nav class="sidebar-nav" id="sidebar-nav" aria-label="Patterns">
<div class="sidebar-search search">
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<input type="search" id="pattern-search" placeholder="Filter patterns…" aria-label="Filter patterns" autocomplete="off">
</div>
<ul class="sidebar-pages">
<li><a href="{{ '/docs/' | relative_url }}"{% if page.url == '/docs/' %} class="is-active" aria-current="page"{% endif %}><i class="fa-solid fa-circle-info" aria-hidden="true"></i><span>What are these?</span></a></li>
<li><a href="{{ '/using/' | relative_url }}"{% if page.url == '/using/' %} class="is-active" aria-current="page"{% endif %}><i class="fa-solid fa-download" aria-hidden="true"></i><span>Install the Skill</span></a></li>
<li><a href="{{ '/manual/' | relative_url }}"{% if page.url == '/manual/' %} class="is-active" aria-current="page"{% endif %}><i class="fa-solid fa-screwdriver-wrench" aria-hidden="true"></i><span>Manual usage</span></a></li>
<li><a href="{{ '/patterns/' | relative_url }}"{% if page.url == '/patterns/' %} class="is-active" aria-current="page"{% endif %}><i class="fa-solid fa-layer-group" aria-hidden="true"></i><span>Patterns</span></a></li>
</ul>
{% assign cats = site.data.categories | sort: "order" %}
{% for cat in cats %}
<div class="sidebar-cat" data-cat="{{ cat.key }}">
Expand All @@ -12,5 +22,7 @@ <h2 class="sidebar-cat__title">{{ cat.title | escape }}</h2>
</ul>
</div>
{% endfor %}
<p class="sidebar-empty is-hidden" id="sidebar-empty">No patterns match that filter.</p>
</nav>
</aside>
<div class="sidebar-backdrop" id="sidebar-backdrop"></div>
4 changes: 2 additions & 2 deletions _layouts/default.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<html lang="en" data-theme="light"{% if page.layout == 'pattern' or page.layout == 'doc' or page.sidebar %} class="has-sidebar"{% endif %}>
<head>
{%- include head.html -%}
</head>
<body>
{%- include header.html -%}
{{ content }}
{%- include footer.html -%}
<script src="{{ '/assets/js/app.js' | relative_url }}" defer></script>
<script src="{{ '/assets/js/app.js' | relative_url }}?v={{ site.time | date: '%s' }}" defer></script>
</body>
</html>
9 changes: 9 additions & 0 deletions _layouts/doc.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
layout: default
---
<div class="layout">
{%- include sidebar.html -%}
<main class="page" id="content">
{{ content }}
</main>
</div>
47 changes: 32 additions & 15 deletions _layouts/home.html
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
---
layout: default
---
{%- comment -%} Sidebar isn't shown as a column on the home page (hidden on desktop);
it exists only to power the mobile hamburger drawer. {%- endcomment -%}
{%- include sidebar.html -%}
<header class="hero">
<div class="hero__bg" aria-hidden="true"></div>
<div class="hero__blobs" aria-hidden="true">
<span class="hero__blob a"></span><span class="hero__blob b"></span><span class="hero__blob c"></span>
</div>
<div class="hero__inner">
<div class="hero__eyebrow mono">{{ site.patterns | size }} composable patterns</div>
<div class="hero__eyebrow mono"><i class="fa-solid fa-cubes-stacked" aria-hidden="true"></i>{{ site.patterns | size }} composable patterns</div>
{{ content }}
<div class="search search--home">
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<input type="search" id="pattern-search" placeholder="Search patterns…" aria-label="Search patterns" autocomplete="off">
<div class="hero__cta">
<a class="btn btn--primary" href="{{ '/patterns/' | relative_url }}"><i class="fa-solid fa-compass" aria-hidden="true"></i> Browse the catalog</a>
<a class="btn btn--ghost" href="{{ '/using/' | relative_url }}"><i class="fa-solid fa-download" aria-hidden="true"></i> Install the Skill</a>
</div>
<dl class="hero__stats">
<div class="hero__stat"><dt>{{ site.patterns | size }}</dt><dd>Patterns</dd></div>
<div class="hero__stat"><dt>{{ site.data.categories | size }}</dt><dd>Categories</dd></div>
<div class="hero__stat"><dt>0</dt><dd>Dependencies</dd></div>
</dl>
</div>
</header>
<div class="home">
{% assign cats = site.data.categories | sort: "order" %}
{% for cat in cats %}
<section class="cat-section" id="cat-{{ cat.key }}" data-cat="{{ cat.key }}">
<h2 class="cat-section__title">{{ cat.title | escape }}</h2>
<p class="cat-section__desc">{{ cat.description | escape }}</p>
<div class="card-grid">
{% assign pats = site.patterns | where: "category", cat.key | sort_natural: "title" %}
{% for p in pats %}{% include pattern-card.html pattern=p %}{% endfor %}
</div>
</section>
{% endfor %}
<p class="no-results is-hidden" id="no-results">No patterns match your search.</p>
<div class="cat-overview">
{% for cat in cats %}
{% assign pats = site.patterns | where: "category", cat.key | sort_natural: "title" %}
<article class="cat-box" data-cat="{{ cat.key }}">
<a class="cat-box__head" href="{{ '/patterns/' | relative_url }}#cat-{{ cat.key }}">
<h2 class="cat-box__title">{{ cat.title | escape }}</h2>
<span class="cat-box__count">{{ pats | size }}</span>
</a>
<p class="cat-box__desc">{{ cat.description | escape }}</p>
<ul class="cat-box__list">
{% for p in pats %}
<li><a href="{{ p.url | relative_url }}">{% if p.icon %}<i class="{{ p.icon }}" aria-hidden="true"></i>{% endif %}<span>{{ p.title }}</span></a></li>
{% endfor %}
</ul>
</article>
{% endfor %}
</div>
</div>
6 changes: 3 additions & 3 deletions _layouts/pattern.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
<div class="layout">
{%- include sidebar.html -%}
<main class="content" id="content">
<a class="back-link" href="{{ '/' | relative_url }}">← All patterns</a>
<a class="back-link" href="{{ '/patterns/' | relative_url }}">← All patterns</a>
{% assign cat = site.data.categories | where: "key", page.category | first %}
<div class="pattern-detail">
<div class="pattern__eyebrow"><a href="{{ '/#cat-' | append: page.category | relative_url }}">{{ cat.title | escape }}</a></div>
<div class="pattern-detail" data-cat="{{ page.category }}">
<div class="pattern__eyebrow"><a href="{{ '/patterns/#cat-' | append: page.category | relative_url }}">{{ cat.title | escape }}</a></div>
{% include pattern.html pattern=page %}
</div>
</main>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Adversarial push back"
slug: adversarial-push-back
title: "Adversarial pushback"
slug: adversarial-pushback
icon: "fa-solid fa-gavel"
category: critique
order: 3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Codified judgment"
slug: codified-judgment
title: "Decision delegation"
slug: decision-delegation
icon: "fa-solid fa-scale-balanced"
category: control
summary: "Encodes when the agent should decide on its own versus pause — so routine choices auto-resolve and only genuinely high-stakes calls escalate."
Expand Down
Loading
Loading