Pattern Factory
::pattern-authoring-standard says what a good Pattern is. This skill is the
runnable procedure that produces one (or fifteen) without asking a human
anything, and proves the result rather than asserting it.
Read both. Where they overlap, the standard wins on judgment and this skill wins
on mechanics.
The contract of autonomy
You are finished when node .gen/_validate.mjs --only <slugs> exits 0 and
every version-sensitive claim you wrote has a current source you actually
checked. Until then, keep working: a failing gate is a task, not a question.
Three things you never do without a human:
git push, git tag, or anything that publishes to npm (that is
::pathrule-release-patterns, an owner-gated flow).
- Edit or delete an existing pattern's content while adding new ones. Additions
are additive; fixing published content is
::pattern-freshness-audit.
- Invent a "close enough" fact. If a claim cannot be verified and cannot be
written in a version-independent way, cut the claim.
Repo map
| Thing |
Where |
| Content model (the only contract) |
src/types.ts |
| One pattern per file, generated |
src/patterns/<slug>.ts |
| Catalog + derived helpers, generated |
src/index.ts |
| Your authoring input |
.gen/<slug>.json |
| JSON to TS + index generator |
node .gen/_build.mjs |
| README catalog generator |
node .gen/_readme.mjs |
| The machine gate |
node .gen/_validate.mjs --only <slugs> |
| Web: brand logos (svgl at build time) |
../pathrule-web/scripts/fetch-pattern-logos.mjs |
| Web: lucide name registry |
../pathrule-web/src/lib/pattern-icons.ts |
| Web: SEO rewriter you must satisfy |
../pathrule-web/src/lib/pattern-metadata.ts |
| Web: per-pattern OG card |
../pathrule-web/src/app/patterns/[slug]/opengraph-image.tsx |
PATHRULE_WEB=/path/to/pathrule-web overrides the web location for the gate.
Stage 0 — Inventory before ideas
Never propose a topic before you know what is already there. Run:
npm run build >/dev/null 2>&1
node -e '
const {getAllPatterns,countByKind}=require("./dist/index.js");
for(const p of getAllPatterns()){const c=countByKind(p);
console.log([p.category.padEnd(9),p.slug.padEnd(30),`${c.rule}R/${c.memory}M/${c.skill}S`,"|",(p.appliesTo?.stacks||[]).join(",")].join(" "));}'
node .gen/_validate.mjs --report
The report tells you the current composition spread and which pages are outside
the SEO window. Both are inputs to your decisions: do not add a bundle shape
that is already the catalog's most common one, and never copy the metaTitle
style of a page the report says needs a rewrite.
Stage 1 — Topic selection
Rank candidates by (real 2026 pain frequency) x (search demand) x (catalog gap).
A topic qualifies only if all three hold:
- Gap: no existing pattern covers it, and it does not overlap one by more
than ~25% of its bundle. Adjacent-but-distinct is fine and good
(
drizzle-orm next to a Prisma pattern); a second pattern about the same
advice is not.
- Pain: the mistake it prevents is one an AI assistant actually makes today
(stale API, changed default, missing boundary check), not textbook trivia.
- Demand: the slug reads like something a human types into a search box.
prisma-orm beats type-safe-db-access; kubernetes-helm beats
container-orchestration-standards. The slug IS the URL and the token, so it
is the single most load-bearing SEO decision in the pattern.
Then run the anti-overlap check, out loud, against the Stage 0 inventory: name
the closest existing pattern and one sentence on why the new one is not it.
Ecosystem coverage is a legitimate tiebreaker: a huge developer population with
zero patterns (a language, a framework, a cloud) beats a marginal addition to a
category that already has six.
Stage 2 — Narrow verification
Cheap by default (::pattern-authoring-standard Principle 6):
- Draft from knowledge. For mainstream topics you already know the durable
advice. Do not spend tokens re-reading docs to write it.
- List the version-sensitive claims before searching: current major, renamed
config, changed default, removed API, new required step, threshold numbers.
That list, and only that list, is your search plan.
- One
WebSearch per topic is the default budget. Read the snippets, not
the pages.
WebFetch only for high-risk specifics — an exact generator block, an
exact import path, a threshold you will print as a number. One page, a
precise prompt.
- Prefer official sources for anything you will state as a fact: docs,
release notes, upgrade guides. A blog is a signal to check, never the source.
- Version-independent phrasing beats a verified version number. "Every
database connection goes through a driver adapter" survives the next release;
"as of 7.3.0" is a liability. Print a version only where the version IS the
point (a breaking change, a removed API, a threshold).
If a search contradicts what you were going to write, the search wins. If two
sources conflict and neither is official, drop the claim.
Stage 3 — Composition, designed per topic
The catalog's historic failure is the cookie-cutter: 42 of 43 patterns shipped
with exactly one skill. The gate now enforces spread across a batch, but the
real job is to derive each bundle from its subject:
| The topic is mostly... |
Compose it as |
Typical total |
| Hard constraints, security, money, data loss |
rule-heavy (3-5 rules) |
4-7 |
| Conventions, architecture, "how this ecosystem works now" |
memory-heavy (3-5 memories) |
4-7 |
| A procedure people repeat (review, audit, migration, incident) |
skill-heavy (2-3 skills) |
3-6 |
| A migration with a known before/after |
1-2 rules + 1 memory + 1 skill |
3-4 |
| A broad ecosystem with several distinct sub-surfaces |
mixed, 7-9 pieces |
7-9 |
| A tight single-mechanism topic |
2-3 pieces, no filler |
2-3 |
Rules that keep it honest:
- Some topics get no skill. If there is no repeatable procedure, adding a
checklist is padding. The gate fails a batch of 8+ where every pattern has one.
- Vary the total, not only the mix. Four different bundle sizes in a batch of
15 is the floor, not the target.
- Every piece earns its place. If you cannot say what breaks without it, cut it.
Path scoping is the product
Each piece lands on the most specific path it governs. This is what makes a
Pattern a Pattern instead of a pile of markdown:
- Framework conventions to the framework's directory (
/app, /src/app, /lib).
- Server/API constraints to
/server, /api, /app/api, /src/api.
- Infra to
/infra, /k8s, /terraform, /.github/workflows, /deploy.
- Data layer to
/prisma, /db, /src/db, /migrations, /models.
- Mobile to
/ios, /android, /app, /lib (Flutter), /src.
- Only workspace-wide truths go to
/. Skills usually live at /; memories and
rules usually must not (the gate requires 60% of memories+rules off root, and
2+ distinct paths per bundle).
Use paths that real projects have. Invented paths (/my-app/core) make the
pattern feel generated.
Stage 4 — Authoring each kind
Voice: direct, specific, second person, present tense. Explain the why in one
clause, then the what concretely. No marketing, no hedging, no "it is
important to". Name real APIs, flags, files, and numbers. No em dashes and no
en dashes anywhere (the gate fails on them, and the web strips them).
Rule — a hard constraint, honestly scoped.
- Body: one short paragraph naming the failure mode, then 3-5 bullets that are
each independently actionable. 500-1800 characters.
scopeType: folder when it governs a directory, file_type when it governs
a file kind, project when it is workspace-wide. folder at / is dishonest
and the gate rejects it.
priority: high only when a violation causes a bug, a regression, a
security hole, or a money/data error. Not every rule in a bundle is high; if
they all are, none is.
enforcement: strict for true must-nots, advisory for strong defaults.
Memory — one non-obvious decision or convention, and why it holds here.
- Not a doc summary. The test: could a competent developer who read the official
docs still get this wrong? If no, it is not a memory.
- One fact per memory. End architecture/convention memories with a router line
(
See /server for the transaction rule; see the observability pattern for tracing.) so a workspace's knowledge tree links up instead of floating.
Skill — a procedure someone repeats.
title is the kebab-case skill name and MUST equal the frontmatter name.
- Body opens with
---\nname: ...\ndescription: ...\n---, then a heading, then
the actual steps or checklist. Checkboxes for review skills, numbered steps for
migrations/incidents.
skillTags: 3-6 lowercase tags.
- Write it so it is useful to a human reading it cold, because that is exactly
how an agent reads it.
Summaries: 40-200 characters, one line, says what the piece decides. Not a
restatement of the title.
Stage 5 — The SEO contract (measured, not vibes)
../pathrule-web/src/lib/pattern-metadata.ts silently replaces any title or
description that falls outside its windows with a generic template. Missing the
window means your page ranks with boilerplate copy. As of this writing only 3 of
43 pages are inside it, so this is where new patterns win.
| Field |
Hard requirement |
Why |
metaTitle |
39-49 characters |
The web renders "<metaTitle> | Pathrule" and demands a 50-60 rendered length |
metaDescription |
<= 160, aim 145-160 |
Over 160 the web rewrites it; under 145 wastes the snippet |
tagline |
< 110 chars, sentence case, ends in a period |
Card subtitle, also the OG card line |
description |
280+ chars, real intro |
The only long-form copy a crawler and an LLM see |
problem |
1-2 sentences on what agents get wrong |
Renders under "Why this pattern" |
audience |
Reads after "Built for ..." |
Entity/relevance signal |
prevents |
3-5 concrete mistakes |
The highest-signal block on the page |
appliesTo.stacks |
2+ lowercase tags |
Rendered as tags, used by the browser filter |
Writing them:
- metaTitle: lead with the searched term, then intent.
"<Tech> <surface> pattern for AI coding agents" fits the window for most names; drop words from
the tail (for AI agents, pattern) to fit, never the tech name. Count the
characters. Every title must be unique in the catalog.
- metaDescription: one sentence naming the concrete things inside the bundle
(real API names, the actual constraints), then who it is for. Bad: "best
practices for X". Good: "Server-side deny-by-default authorization,
parameterized queries, and secure headers, scoped to the paths that enforce
them." Never open the same way as another pattern's description.
- description (page intro): 2-4 sentences. Sentence 1 names the current
reality (version, default, threshold) so the page reads as maintained.
Sentence 2 says what the bundle actually contains. Sentence 3 draws the
boundary against the neighbouring pattern (this is what stops cannibalisation
and tells a crawler the pages are distinct).
- Discoverability beyond the title: the searched term should appear in
slug, name, metaTitle, metaDescription, the first sentence of
description, and at least one piece title. Naturally, not stuffed. Synonyms
people actually type belong in appliesTo.stacks (which renders as tags) and
in the body, e.g. k8s next to kubernetes, nextauth next to auth.js.
- The page auto-emits
TechArticle + BreadcrumbList JSON-LD, a per-pattern OG
card, a sitemap row keyed on updatedAt, and a /raw markdown view. You get
all of that for free, but updatedAt must be the real date because it is the
freshness signal in the sitemap.
Stage 6 — Visual identity
Never leave a pattern on the silent Boxes fallback. Resolution order:
Brand/stack topic → real logo from svgl.app, downloaded at build time to
public/pattern-logos/<slug>.svg. Find the exact filename first:
curl -s https://api.svgl.app | python3 -c '
import json,sys
for i in json.load(sys.stdin):
t=i["title"]; r=i["route"]
if "SEARCH" in t.lower(): print(t, "|", r if isinstance(r,str) else r["light"])'
Pick the light-theme variant (a dark-coloured mark): it renders on a
neutral white tile in both themes. Add "<slug>": "<file>.svg" to
BRAND_LOGOS in ../pathrule-web/scripts/fetch-pattern-logos.mjs, then run
node scripts/fetch-pattern-logos.mjs in the web repo (it rewrites
src/lib/pattern-logos.ts). If svgl has no logo for the technology, fall to
step 2 rather than shipping a wrong mark.
Abstract/workflow topic → a meaningful lucide glyph. icon is a
kebab-case lucide name and MUST be registered in
../pathrule-web/src/lib/pattern-icons.ts (import + map entry). The gate
fails an unregistered name. Pick a glyph that depicts the mechanism, and do
not reuse a glyph another pattern already owns.
color is always required (it colours the lucide glyph): exactly
bg-<hue>-500/10 text-<hue>-600 dark:bg-<hue>-400/15 dark:text-<hue>-400.
Choose the hue nearest the technology's brand colour, or by meaning for
abstract topics (rose/red for security and risk, amber for workflow, emerald
for data, blue for platform, violet for AI).
installs stays 0 (it is a placeholder until real effectiveness scoring).
Stage 7 — Register, build, gate
Author .gen/<slug>.json with the full object (no nulls; omit optional fields
you are not setting):
{
"slug": "kebab-case-slug",
"version": "1.0.0",
"name": "Display Name",
"tagline": "One sentence, sentence case, ends with a period.",
"description": "Two to four sentences of real intro.",
"category": "Framework | Frontend | Backend | Billing | Infra | Workflow | AI | Security",
"icon": "lucide-name",
"color": "bg-sky-500/10 text-sky-600 dark:bg-sky-400/15 dark:text-sky-400",
"installs": 0,
"updatedAt": "YYYY-MM-DD",
"changelog": [{ "version": "1.0.0", "date": "YYYY-MM-DD", "note": "First release." }],
"metaTitle": "39 to 49 characters, unique",
"metaDescription": "145 to 160 characters, unique, concrete.",
"problem": "What agents get wrong today.",
"audience": "Teams that ...",
"prevents": ["Concrete mistake", "Concrete mistake", "Concrete mistake"],
"appliesTo": { "paths": ["/src"], "stacks": ["lowercase", "tags"], "packages": ["dep-name"] },
"pieces": [
{
"kind": "rule",
"nodePath": "/src",
"title": "Imperative sentence naming the constraint",
"summary": "One line, 40 to 200 chars.",
"body": "Paragraph naming the failure mode.\n\n- Bullet.\n- Bullet.\n- Bullet.",
"scopeType": "folder",
"priority": "high",
"enforcement": "strict"
},
{
"kind": "memory",
"nodePath": "/server",
"title": "Sentence-case title of the decision",
"summary": "One line.",
"body": "Why it holds here, then specifics.\n\nSee /src for the related rule."
},
{
"kind": "skill",
"nodePath": "/",
"title": "kebab-case-skill-name",
"summary": "When to run it.",
"body": "---\nname: kebab-case-skill-name\ndescription: When to run it and what it checks.\n---\n\n# Heading\n\n- [ ] Check.\n",
"skillTags": ["three", "or", "more"]
}
]
}
Then, in order:
node .gen/_build.mjs # JSON -> src/patterns/*.ts + src/index.ts
npm run build # tsc; type errors are gate failures
node .gen/_validate.mjs --only <slug,slug> # the machine gate
node .gen/_readme.mjs # regenerate the README catalog
.gen/ is gitignored, so a clone has no authoring JSON. _build.mjs therefore
builds src/index.ts from the union of the JSON it just generated and the
patterns already committed under src/patterns/, which means a missing JSON no
longer drops a pattern from the catalog. (It used to: twelve patterns were in
that state until their JSON was restored from dist/ on 2026-08-24.) Two habits
still matter: write .gen/<slug>.json for anything you author so the input
survives on this machine, and always confirm the catalog count after building:
node -e 'console.log(require("./dist/index.js").getAllPatterns().length)'
When the gate reports a failure, fix the content, not the gate. The only
legitimate reason to edit .gen/_validate.mjs is that the web's own contract
changed (check pattern-metadata.ts before you believe that).
Stage 8 — Hand-off (do not ship)
Leave the tree clean and the next step explicit:
- New/changed files:
.gen/<slug>.json, src/patterns/<slug>.ts, src/index.ts,
README.md, plus the two web files if you wired a logo or icon.
- Report: what you added, the composition and paths of each bundle, what you
verified and against which source, the gate result, and the exact remaining
steps (version bump, tag, web dep bump) that belong to
::pathrule-release-patterns.
- Do not bump
package.json version, tag, or push.
Batch mode (producing many at once)
For a batch of N:
- Stage 0 once. Then pick all N topics up front and write the list down with
category, slug, closest existing pattern, and intended composition. Check the
whole list for internal overlap before writing any content.
- Group by ecosystem and research per group, not per pattern. One search
serves several patterns in the same stack.
- Balance the batch across categories so the catalog's browse page stays
navigable, and deliberately spread bundle shapes (the gate enforces a 25% cap
per composition signature, 4+ distinct totals, and at least one skill-free
pattern in a batch of 8+).
- Author, build, and gate incrementally (every 3-5 patterns) so a systematic
mistake is caught early instead of 15 times.
- Final pass: run the gate over the whole batch at once (
--only with all
slugs) so the set-level diversity and uniqueness checks apply, then re-read
the three weakest bundles and ask whether each piece still earns its place.
Anti-patterns
- The cookie-cutter: same mix, same total, one skill, every time.
- Padding a bundle to look symmetric.
- A metaTitle or metaDescription outside the window (the web replaces it and your
copy never ships).
- Em dashes, en dashes, invisible characters, "best practices" as the whole idea.
- Memories that restate the official docs; rules with dishonest
high priority;
skills with no real procedure.
- Everything scoped to
/.
- Version numbers sprinkled everywhere for freshness theatre.
- A brand pattern wearing a generic glyph, or an unregistered icon name.
- Editing published patterns while adding new ones.
- Declaring done without the gate exiting 0.
Definition of Done
Machine-checked (run it; do not assert it):
Judgment (state your evidence in the report):
See also: ::pattern-authoring-standard (the quality bar and its rationale),
::pattern-freshness-audit (re-verify published patterns), the "Patterns
architecture" memory (repo/web/importer topology), ::pathrule-release-patterns
(shipping), ::web-design-guidelines (web UI).
1---2name: pattern-factory3description: Pattern Factory4---56# Pattern Factory78`::pattern-authoring-standard` says what a good Pattern *is*. This skill is the9**runnable procedure** that produces one (or fifteen) without asking a human10anything, and proves the result rather than asserting it.1112Read both. Where they overlap, the standard wins on judgment and this skill wins13on mechanics.1415## The contract of autonomy1617You are finished when `node .gen/_validate.mjs --only <slugs>` exits 0 **and**18every version-sensitive claim you wrote has a current source you actually19checked. Until then, keep working: a failing gate is a task, not a question.2021Three things you never do without a human:22231. `git push`, `git tag`, or anything that publishes to npm (that is24 `::pathrule-release-patterns`, an owner-gated flow).252. Edit or delete an existing pattern's content while adding new ones. Additions26 are additive; fixing published content is `::pattern-freshness-audit`.273. Invent a "close enough" fact. If a claim cannot be verified and cannot be28 written in a version-independent way, cut the claim.2930## Repo map3132| Thing | Where |33| --- | --- |34| Content model (the only contract) | `src/types.ts` |35| One pattern per file, generated | `src/patterns/<slug>.ts` |36| Catalog + derived helpers, generated | `src/index.ts` |37| Your authoring input | `.gen/<slug>.json` |38| JSON to TS + index generator | `node .gen/_build.mjs` |39| README catalog generator | `node .gen/_readme.mjs` |40| **The machine gate** | `node .gen/_validate.mjs --only <slugs>` |41| Web: brand logos (svgl at build time) | `../pathrule-web/scripts/fetch-pattern-logos.mjs` |42| Web: lucide name registry | `../pathrule-web/src/lib/pattern-icons.ts` |43| Web: SEO rewriter you must satisfy | `../pathrule-web/src/lib/pattern-metadata.ts` |44| Web: per-pattern OG card | `../pathrule-web/src/app/patterns/[slug]/opengraph-image.tsx` |4546`PATHRULE_WEB=/path/to/pathrule-web` overrides the web location for the gate.4748## Stage 0 — Inventory before ideas4950Never propose a topic before you know what is already there. Run:5152```sh53npm run build >/dev/null 2>&154node -e '55const {getAllPatterns,countByKind}=require("./dist/index.js");56for(const p of getAllPatterns()){const c=countByKind(p);57console.log([p.category.padEnd(9),p.slug.padEnd(30),`${c.rule}R/${c.memory}M/${c.skill}S`,"|",(p.appliesTo?.stacks||[]).join(",")].join(" "));}'58node .gen/_validate.mjs --report59```6061The report tells you the current composition spread and which pages are outside62the SEO window. Both are inputs to your decisions: do not add a bundle shape63that is already the catalog's most common one, and never copy the metaTitle64style of a page the report says needs a rewrite.6566## Stage 1 — Topic selection6768Rank candidates by **(real 2026 pain frequency) x (search demand) x (catalog gap)**.69A topic qualifies only if all three hold:7071- **Gap**: no existing pattern covers it, and it does not overlap one by more72 than ~25% of its bundle. Adjacent-but-distinct is fine and good73 (`drizzle-orm` next to a Prisma pattern); a second pattern about the same74 advice is not.75- **Pain**: the mistake it prevents is one an AI assistant *actually makes today*76 (stale API, changed default, missing boundary check), not textbook trivia.77- **Demand**: the slug reads like something a human types into a search box.78 `prisma-orm` beats `type-safe-db-access`; `kubernetes-helm` beats79 `container-orchestration-standards`. The slug IS the URL and the token, so it80 is the single most load-bearing SEO decision in the pattern.8182Then run the anti-overlap check, out loud, against the Stage 0 inventory: name83the closest existing pattern and one sentence on why the new one is not it.8485Ecosystem coverage is a legitimate tiebreaker: a huge developer population with86zero patterns (a language, a framework, a cloud) beats a marginal addition to a87category that already has six.8889## Stage 2 — Narrow verification9091Cheap by default (`::pattern-authoring-standard` Principle 6):92931. **Draft from knowledge.** For mainstream topics you already know the durable94 advice. Do not spend tokens re-reading docs to write it.952. **List the version-sensitive claims** before searching: current major, renamed96 config, changed default, removed API, new required step, threshold numbers.97 That list, and only that list, is your search plan.983. **One `WebSearch` per topic** is the default budget. Read the snippets, not99 the pages.1004. **`WebFetch` only for high-risk specifics** — an exact generator block, an101 exact import path, a threshold you will print as a number. One page, a102 precise prompt.1035. **Prefer official sources** for anything you will state as a fact: docs,104 release notes, upgrade guides. A blog is a signal to check, never the source.1056. **Version-independent phrasing beats a verified version number.** "Every106 database connection goes through a driver adapter" survives the next release;107 "as of 7.3.0" is a liability. Print a version only where the version IS the108 point (a breaking change, a removed API, a threshold).109110If a search contradicts what you were going to write, the search wins. If two111sources conflict and neither is official, drop the claim.112113## Stage 3 — Composition, designed per topic114115The catalog's historic failure is the cookie-cutter: 42 of 43 patterns shipped116with exactly one skill. The gate now enforces spread across a batch, but the117real job is to derive each bundle from its subject:118119| The topic is mostly... | Compose it as | Typical total |120| --- | --- | --- |121| Hard constraints, security, money, data loss | rule-heavy (3-5 rules) | 4-7 |122| Conventions, architecture, "how this ecosystem works now" | memory-heavy (3-5 memories) | 4-7 |123| A procedure people repeat (review, audit, migration, incident) | skill-heavy (2-3 skills) | 3-6 |124| A migration with a known before/after | 1-2 rules + 1 memory + 1 skill | 3-4 |125| A broad ecosystem with several distinct sub-surfaces | mixed, 7-9 pieces | 7-9 |126| A tight single-mechanism topic | 2-3 pieces, no filler | 2-3 |127128Rules that keep it honest:129130- **Some topics get no skill.** If there is no repeatable procedure, adding a131 checklist is padding. The gate fails a batch of 8+ where every pattern has one.132- **Vary the total**, not only the mix. Four different bundle sizes in a batch of133 15 is the floor, not the target.134- **Every piece earns its place.** If you cannot say what breaks without it, cut it.135136### Path scoping is the product137138Each piece lands on the most specific path it governs. This is what makes a139Pattern a Pattern instead of a pile of markdown:140141- Framework conventions to the framework's directory (`/app`, `/src/app`, `/lib`).142- Server/API constraints to `/server`, `/api`, `/app/api`, `/src/api`.143- Infra to `/infra`, `/k8s`, `/terraform`, `/.github/workflows`, `/deploy`.144- Data layer to `/prisma`, `/db`, `/src/db`, `/migrations`, `/models`.145- Mobile to `/ios`, `/android`, `/app`, `/lib` (Flutter), `/src`.146- Only workspace-wide truths go to `/`. Skills usually live at `/`; memories and147 rules usually must not (the gate requires 60% of memories+rules off root, and148 2+ distinct paths per bundle).149150Use paths that real projects have. Invented paths (`/my-app/core`) make the151pattern feel generated.152153## Stage 4 — Authoring each kind154155Voice: direct, specific, second person, present tense. Explain the *why* in one156clause, then the *what* concretely. No marketing, no hedging, no "it is157important to". Name real APIs, flags, files, and numbers. **No em dashes and no158en dashes anywhere** (the gate fails on them, and the web strips them).159160**Rule** — a hard constraint, honestly scoped.161- Body: one short paragraph naming the failure mode, then 3-5 bullets that are162 each independently actionable. 500-1800 characters.163- `scopeType`: `folder` when it governs a directory, `file_type` when it governs164 a file kind, `project` when it is workspace-wide. `folder` at `/` is dishonest165 and the gate rejects it.166- `priority: high` **only** when a violation causes a bug, a regression, a167 security hole, or a money/data error. Not every rule in a bundle is high; if168 they all are, none is.169- `enforcement: strict` for true must-nots, `advisory` for strong defaults.170171**Memory** — one non-obvious decision or convention, and why it holds here.172- Not a doc summary. The test: could a competent developer who read the official173 docs still get this wrong? If no, it is not a memory.174- One fact per memory. End architecture/convention memories with a router line175 (`See /server for the transaction rule; see the observability pattern for176 tracing.`) so a workspace's knowledge tree links up instead of floating.177178**Skill** — a procedure someone repeats.179- `title` is the kebab-case skill name and MUST equal the frontmatter `name`.180- Body opens with `---\nname: ...\ndescription: ...\n---`, then a heading, then181 the actual steps or checklist. Checkboxes for review skills, numbered steps for182 migrations/incidents.183- `skillTags`: 3-6 lowercase tags.184- Write it so it is useful to a human reading it cold, because that is exactly185 how an agent reads it.186187**Summaries**: 40-200 characters, one line, says what the piece decides. Not a188restatement of the title.189190## Stage 5 — The SEO contract (measured, not vibes)191192`../pathrule-web/src/lib/pattern-metadata.ts` **silently replaces** any title or193description that falls outside its windows with a generic template. Missing the194window means your page ranks with boilerplate copy. As of this writing only 3 of19543 pages are inside it, so this is where new patterns win.196197| Field | Hard requirement | Why |198| --- | --- | --- |199| `metaTitle` | **39-49 characters** | The web renders `"<metaTitle> \| Pathrule"` and demands a 50-60 rendered length |200| `metaDescription` | **<= 160**, aim 145-160 | Over 160 the web rewrites it; under 145 wastes the snippet |201| `tagline` | < 110 chars, sentence case, ends in a period | Card subtitle, also the OG card line |202| `description` | 280+ chars, real intro | The only long-form copy a crawler and an LLM see |203| `problem` | 1-2 sentences on what agents get wrong | Renders under "Why this pattern" |204| `audience` | Reads after "Built for ..." | Entity/relevance signal |205| `prevents` | 3-5 concrete mistakes | The highest-signal block on the page |206| `appliesTo.stacks` | 2+ lowercase tags | Rendered as tags, used by the browser filter |207208Writing them:209210- **metaTitle**: lead with the searched term, then intent. `"<Tech> <surface>211 pattern for AI coding agents"` fits the window for most names; drop words from212 the tail (`for AI agents`, `pattern`) to fit, never the tech name. Count the213 characters. Every title must be unique in the catalog.214- **metaDescription**: one sentence naming the concrete things inside the bundle215 (real API names, the actual constraints), then who it is for. Bad: "best216 practices for X". Good: "Server-side deny-by-default authorization,217 parameterized queries, and secure headers, scoped to the paths that enforce218 them." Never open the same way as another pattern's description.219- **description** (page intro): 2-4 sentences. Sentence 1 names the current220 reality (version, default, threshold) so the page reads as maintained.221 Sentence 2 says what the bundle actually contains. Sentence 3 draws the222 boundary against the neighbouring pattern (this is what stops cannibalisation223 and tells a crawler the pages are distinct).224- **Discoverability beyond the title**: the searched term should appear in225 `slug`, `name`, `metaTitle`, `metaDescription`, the first sentence of226 `description`, and at least one piece title. Naturally, not stuffed. Synonyms227 people actually type belong in `appliesTo.stacks` (which renders as tags) and228 in the body, e.g. `k8s` next to `kubernetes`, `nextauth` next to `auth.js`.229- The page auto-emits `TechArticle` + `BreadcrumbList` JSON-LD, a per-pattern OG230 card, a sitemap row keyed on `updatedAt`, and a `/raw` markdown view. You get231 all of that for free, but `updatedAt` must be the real date because it is the232 freshness signal in the sitemap.233234## Stage 6 — Visual identity235236Never leave a pattern on the silent `Boxes` fallback. Resolution order:2372381. **Brand/stack topic → real logo from svgl.app**, downloaded at build time to239 `public/pattern-logos/<slug>.svg`. Find the exact filename first:240241 ```sh242 curl -s https://api.svgl.app | python3 -c '243 import json,sys244 for i in json.load(sys.stdin):245 t=i["title"]; r=i["route"]246 if "SEARCH" in t.lower(): print(t, "|", r if isinstance(r,str) else r["light"])'247 ```248249 Pick the **light-theme variant** (a dark-coloured mark): it renders on a250 neutral white tile in both themes. Add `"<slug>": "<file>.svg"` to251 `BRAND_LOGOS` in `../pathrule-web/scripts/fetch-pattern-logos.mjs`, then run252 `node scripts/fetch-pattern-logos.mjs` in the web repo (it rewrites253 `src/lib/pattern-logos.ts`). If svgl has no logo for the technology, fall to254 step 2 rather than shipping a wrong mark.2552. **Abstract/workflow topic → a meaningful lucide glyph.** `icon` is a256 kebab-case lucide name and MUST be registered in257 `../pathrule-web/src/lib/pattern-icons.ts` (import + map entry). The gate258 fails an unregistered name. Pick a glyph that depicts the mechanism, and do259 not reuse a glyph another pattern already owns.2603. `color` is always required (it colours the lucide glyph): exactly261 `bg-<hue>-500/10 text-<hue>-600 dark:bg-<hue>-400/15 dark:text-<hue>-400`.262 Choose the hue nearest the technology's brand colour, or by meaning for263 abstract topics (rose/red for security and risk, amber for workflow, emerald264 for data, blue for platform, violet for AI).265266`installs` stays `0` (it is a placeholder until real effectiveness scoring).267268## Stage 7 — Register, build, gate269270Author `.gen/<slug>.json` with the full object (no nulls; omit optional fields271you are not setting):272273```json274{275 "slug": "kebab-case-slug",276 "version": "1.0.0",277 "name": "Display Name",278 "tagline": "One sentence, sentence case, ends with a period.",279 "description": "Two to four sentences of real intro.",280 "category": "Framework | Frontend | Backend | Billing | Infra | Workflow | AI | Security",281 "icon": "lucide-name",282 "color": "bg-sky-500/10 text-sky-600 dark:bg-sky-400/15 dark:text-sky-400",283 "installs": 0,284 "updatedAt": "YYYY-MM-DD",285 "changelog": [{ "version": "1.0.0", "date": "YYYY-MM-DD", "note": "First release." }],286 "metaTitle": "39 to 49 characters, unique",287 "metaDescription": "145 to 160 characters, unique, concrete.",288 "problem": "What agents get wrong today.",289 "audience": "Teams that ...",290 "prevents": ["Concrete mistake", "Concrete mistake", "Concrete mistake"],291 "appliesTo": { "paths": ["/src"], "stacks": ["lowercase", "tags"], "packages": ["dep-name"] },292 "pieces": [293 {294 "kind": "rule",295 "nodePath": "/src",296 "title": "Imperative sentence naming the constraint",297 "summary": "One line, 40 to 200 chars.",298 "body": "Paragraph naming the failure mode.\n\n- Bullet.\n- Bullet.\n- Bullet.",299 "scopeType": "folder",300 "priority": "high",301 "enforcement": "strict"302 },303 {304 "kind": "memory",305 "nodePath": "/server",306 "title": "Sentence-case title of the decision",307 "summary": "One line.",308 "body": "Why it holds here, then specifics.\n\nSee /src for the related rule."309 },310 {311 "kind": "skill",312 "nodePath": "/",313 "title": "kebab-case-skill-name",314 "summary": "When to run it.",315 "body": "---\nname: kebab-case-skill-name\ndescription: When to run it and what it checks.\n---\n\n# Heading\n\n- [ ] Check.\n",316 "skillTags": ["three", "or", "more"]317 }318 ]319}320```321322Then, in order:323324```sh325node .gen/_build.mjs # JSON -> src/patterns/*.ts + src/index.ts326npm run build # tsc; type errors are gate failures327node .gen/_validate.mjs --only <slug,slug> # the machine gate328node .gen/_readme.mjs # regenerate the README catalog329```330331`.gen/` is gitignored, so a clone has no authoring JSON. `_build.mjs` therefore332builds `src/index.ts` from the **union** of the JSON it just generated and the333patterns already committed under `src/patterns/`, which means a missing JSON no334longer drops a pattern from the catalog. (It used to: twelve patterns were in335that state until their JSON was restored from `dist/` on 2026-08-24.) Two habits336still matter: write `.gen/<slug>.json` for anything you author so the input337survives on this machine, and always confirm the catalog count after building:338339```sh340node -e 'console.log(require("./dist/index.js").getAllPatterns().length)'341```342343When the gate reports a failure, fix the content, not the gate. The only344legitimate reason to edit `.gen/_validate.mjs` is that the web's own contract345changed (check `pattern-metadata.ts` before you believe that).346347## Stage 8 — Hand-off (do not ship)348349Leave the tree clean and the next step explicit:350351- New/changed files: `.gen/<slug>.json`, `src/patterns/<slug>.ts`, `src/index.ts`,352 `README.md`, plus the two web files if you wired a logo or icon.353- Report: what you added, the composition and paths of each bundle, what you354 verified and against which source, the gate result, and the exact remaining355 steps (version bump, tag, web dep bump) that belong to356 `::pathrule-release-patterns`.357- Do not bump `package.json` version, tag, or push.358359## Batch mode (producing many at once)360361For a batch of N:3623631. Stage 0 once. Then pick all N topics up front and write the list down with364 category, slug, closest existing pattern, and intended composition. Check the365 whole list for internal overlap before writing any content.3662. **Group by ecosystem** and research per group, not per pattern. One search367 serves several patterns in the same stack.3683. Balance the batch across categories so the catalog's browse page stays369 navigable, and deliberately spread bundle shapes (the gate enforces a 25% cap370 per composition signature, 4+ distinct totals, and at least one skill-free371 pattern in a batch of 8+).3724. Author, build, and gate **incrementally** (every 3-5 patterns) so a systematic373 mistake is caught early instead of 15 times.3745. Final pass: run the gate over the whole batch at once (`--only` with all375 slugs) so the set-level diversity and uniqueness checks apply, then re-read376 the three weakest bundles and ask whether each piece still earns its place.377378## Anti-patterns379380- The cookie-cutter: same mix, same total, one skill, every time.381- Padding a bundle to look symmetric.382- A metaTitle or metaDescription outside the window (the web replaces it and your383 copy never ships).384- Em dashes, en dashes, invisible characters, "best practices" as the whole idea.385- Memories that restate the official docs; rules with dishonest `high` priority;386 skills with no real procedure.387- Everything scoped to `/`.388- Version numbers sprinkled everywhere for freshness theatre.389- A brand pattern wearing a generic glyph, or an unregistered icon name.390- Editing published patterns while adding new ones.391- Declaring done without the gate exiting 0.392393## Definition of Done394395Machine-checked (run it; do not assert it):396397- [ ] `node .gen/_validate.mjs --only <slugs>` exits 0.398- [ ] `npm run build` is clean and the catalog count is what you expect.399- [ ] `node .gen/_readme.mjs` ran and the README table includes the new rows.400- [ ] Every new brand slug is in `fetch-pattern-logos.mjs` and every new icon401 name is in `pattern-icons.ts`.402403Judgment (state your evidence in the report):404405- [ ] Each topic is a real 2026 pain with search demand and no catalog overlap.406- [ ] Every version-sensitive claim was checked against a current source, and the407 sources are named.408- [ ] Each bundle's composition and size came from its topic; no filler piece.409- [ ] Paths are ones real projects have, and the knowledge sits off root.410- [ ] The batch is spread across categories and bundle shapes.411412See also: `::pattern-authoring-standard` (the quality bar and its rationale),413`::pattern-freshness-audit` (re-verify published patterns), the "Patterns414architecture" memory (repo/web/importer topology), `::pathrule-release-patterns`415(shipping), `::web-design-guidelines` (web UI).