@linchpinagency/skills 0.1.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 +337 -0
- package/bin/install.mjs +231 -0
- package/package.json +44 -0
- package/skills/browser-automation/SKILL.md +93 -0
- package/skills/commit-and-release/SKILL.md +135 -0
- package/skills/dependency-updates/SKILL.md +102 -0
- package/skills/design-previews/SKILL.md +118 -0
- package/skills/engagement-types/SKILL.md +108 -0
- package/skills/investigate/SKILL.md +95 -0
- package/skills/project-context/SKILL.md +89 -0
- package/skills/quality-gates/SKILL.md +94 -0
- package/skills/quality-gates/references/toolchain.md +104 -0
- package/skills/safety-hooks/SKILL.md +121 -0
- package/skills/safety-hooks/scripts/check-destructive.sh +80 -0
- package/skills/safety-hooks/scripts/check-edit-boundary.sh +65 -0
- package/skills/support-triage/SKILL.md +103 -0
- package/skills/task-tracking/SKILL.md +243 -0
- package/skills/web-qa/SKILL.md +108 -0
- package/skills/web-qa/references/qa-checklist.md +98 -0
- package/skills/wordpress-blocks/SKILL.md +110 -0
- package/skills/wordpress-blocks/references/block-grammar.md +94 -0
- package/skills/wordpress-blocks/references/core-blocks.md +123 -0
- package/skills/wordpress-blocks/references/patterns-and-parts.md +70 -0
- package/skills/wordpress-blocks/references/recipes/faq.md +56 -0
- package/skills/wordpress-blocks/references/recipes/hero.md +74 -0
- package/skills/wordpress-blocks/references/recipes/pricing-table.md +77 -0
- package/skills/wordpress-blocks/references/tool-contract.md +167 -0
- package/skills/wordpress-blocks/references/validation.md +38 -0
- package/skills/wp-audit/SKILL.md +115 -0
- package/skills/wp-block-conventions/SKILL.md +134 -0
- package/skills/wp-block-conventions/references/block-anatomy.md +175 -0
- package/skills/wp-implementation-choice/SKILL.md +88 -0
- package/skills/wp-local-setup/SKILL.md +262 -0
- package/skills/wp-pressable/SKILL.md +172 -0
- package/skills/wp-studio-cli/SKILL.md +165 -0
- package/skills/write-a-linchpin-skill/SKILL.md +195 -0
- package/skills/write-a-linchpin-skill/references/template.md +83 -0
- package/upstream.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Linchpin Skills
|
|
2
|
+
|
|
3
|
+
A shared library of **AI agent skills** for the kinds of projects Linchpin builds —
|
|
4
|
+
**WordPress**, **React**, and **Cloudflare Workers**. Skills are agent-agnostic
|
|
5
|
+
instruction sets ([Agent Skills](https://agentskills.io) format) that work in Claude Code,
|
|
6
|
+
GitHub Copilot, and other compatible coding agents.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Getting started
|
|
11
|
+
|
|
12
|
+
### What a skill actually is
|
|
13
|
+
|
|
14
|
+
A skill is a markdown file of instructions that your coding agent loads **when your request
|
|
15
|
+
matches its description**. It's how we make an agent behave like a Linchpin developer
|
|
16
|
+
instead of a generic one: it knows we run WordPress Studio, that PHPCS only applies where
|
|
17
|
+
`phpcs.xml.dist` exists, that release-please owns `CHANGELOG.md`, and that every commit
|
|
18
|
+
carries a ClickUp key.
|
|
19
|
+
|
|
20
|
+
Three things worth knowing up front:
|
|
21
|
+
|
|
22
|
+
- **You mostly don't invoke skills.** Ask for what you want — "this page is broken on
|
|
23
|
+
mobile", "get this ready to commit" — and the agent loads the matching skill on its own.
|
|
24
|
+
- **Skills are loaded by the tool, not the model.** Claude, GPT, or anything else running
|
|
25
|
+
inside Claude Code all read the same `.claude/skills` directory. Switching models changes
|
|
26
|
+
nothing about which skills exist; switching *tools* does.
|
|
27
|
+
- **They are just files.** Nothing is hosted, nothing phones home. You can read every one of
|
|
28
|
+
them in `skills/`.
|
|
29
|
+
|
|
30
|
+
### Install (2 minutes)
|
|
31
|
+
|
|
32
|
+
Install once, globally, and every project you open gets them:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx -y github:linchpin/skills --global
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Verify:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx -y github:linchpin/skills --list
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
You need GitHub access to this (private) repo, which everyone on the team has. There is no
|
|
45
|
+
npm package yet — see [publishing](#for-maintainers--publishing) for why that's a deliberate
|
|
46
|
+
open question rather than an oversight.
|
|
47
|
+
|
|
48
|
+
Then start a new session in your project and ask for something real — "what kind of project
|
|
49
|
+
is this?" should pull in `project-context` and get you a summary of the repo shape, local
|
|
50
|
+
environment, and host.
|
|
51
|
+
|
|
52
|
+
**Re-run the same command to update.** There's no upgrade command; the installer overwrites
|
|
53
|
+
in place and always pulls the current `main`. Do it every few weeks, or when someone
|
|
54
|
+
announces a new skill.
|
|
55
|
+
|
|
56
|
+
Full flag reference: [Install options](#install-options).
|
|
57
|
+
|
|
58
|
+
### Your first day
|
|
59
|
+
|
|
60
|
+
The fastest way to understand the library is to run one loop end to end:
|
|
61
|
+
|
|
62
|
+
| You want to… | Ask for it naturally | Skill that fires |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| Understand a repo you just cloned | "what am I working with here?" | `project-context` |
|
|
65
|
+
| Find out why something's broken | "the hero image 404s on mobile" | `investigate` |
|
|
66
|
+
| Test a site like a user, and fix what's found | "QA the checkout flow" | `web-qa` |
|
|
67
|
+
| Check it's ready to commit | "is this ready to commit?" | `quality-gates` |
|
|
68
|
+
| Commit and open the PR properly | "commit this and open a PR" | `commit-and-release` + `task-tracking` |
|
|
69
|
+
| Handle a client support ticket | "Vinfen says their form isn't sending" | `support-triage` |
|
|
70
|
+
| Add guardrails before touching prod | "careful mode — I'm on production" | `safety-hooks` |
|
|
71
|
+
|
|
72
|
+
The full list is in [Available skills](#available-skills) — 20 of them, each with a
|
|
73
|
+
`When to use` section that says exactly when it applies and which skill to use instead.
|
|
74
|
+
|
|
75
|
+
**When you want to be explicit**, name the skill: *"use the wp-audit skill on the homepage."*
|
|
76
|
+
Worth doing when a task straddles two skills, or when you want a specific procedure followed.
|
|
77
|
+
|
|
78
|
+
### Working in Conductor
|
|
79
|
+
|
|
80
|
+
Conductor runs each workspace in its own **git worktree** — a separate checkout of the repo.
|
|
81
|
+
A project-level install (`./.claude/skills/`) therefore exists only in the workspace where
|
|
82
|
+
you ran it, and disappears the moment you create a new one.
|
|
83
|
+
|
|
84
|
+
**So for Conductor, install globally** (`--global`). `~/.claude/skills/` is outside the
|
|
85
|
+
worktree, so every workspace picks it up automatically with no per-workspace setup.
|
|
86
|
+
|
|
87
|
+
Two other things to expect: interactive prompts can behave differently than in a terminal
|
|
88
|
+
session — if a skill seems to be waiting on you, just answer in the chat — and because
|
|
89
|
+
workspaces are independent checkouts, a skill that reports on git state is describing *that*
|
|
90
|
+
workspace only.
|
|
91
|
+
|
|
92
|
+
### Other tools and models
|
|
93
|
+
|
|
94
|
+
Claude Code is our primary. If you use something else, install into its directory too:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx -y github:linchpin/skills --global --agent all # Claude Code, Copilot, Codex, Cursor
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Skills are read by the **harness**, so Copilot running a Claude model still needs them in
|
|
101
|
+
Copilot's own folder. Everything here is plain markdown with no Claude-specific syntax, with
|
|
102
|
+
one deliberate exception: `safety-hooks` uses Claude Code hooks to *enforce* confirmation on
|
|
103
|
+
destructive commands, and degrades to documentation elsewhere.
|
|
104
|
+
|
|
105
|
+
### When a skill is wrong
|
|
106
|
+
|
|
107
|
+
These encode how we work, so they go stale when how we work changes. If a skill tells the
|
|
108
|
+
agent something outdated, that's a bug worth fixing — open a PR, or an issue if you'd rather
|
|
109
|
+
someone else write it. [`write-a-linchpin-skill`](skills/write-a-linchpin-skill/SKILL.md) is
|
|
110
|
+
the standard, and `npm run validate` checks your work.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Three tiers — and what belongs here
|
|
115
|
+
|
|
116
|
+
Knowledge about building WordPress lives at three altitudes. This repo owns **only the
|
|
117
|
+
middle one**:
|
|
118
|
+
|
|
119
|
+
| Tier | Where it lives | What it owns |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| **Base layer** (upstream) | [`WordPress/agent-skills`](https://github.com/WordPress/agent-skills), vendored + pinned via [`upstream.json`](upstream.json) | Generic "how WordPress works" — block.json, theme.json mechanics, the Interactivity API, performance, WP-CLI ops. |
|
|
122
|
+
| **Linchpin tooling** (**this repo**) | `skills/` | **Portable, cross-project** ways the agency works — operating Studio/Pressable, tying work to ClickUp. Things true on *every* Linchpin project. |
|
|
123
|
+
| **Project layer** (per-repo) | that project's own `AGENTS.md` / `CLAUDE.md` | One project's specific blocks, theme conventions, file paths, and quirks. |
|
|
124
|
+
|
|
125
|
+
> **Project-specific conventions do NOT go here.** A given site's block/theme conventions
|
|
126
|
+
> (e.g. linchpin.com's color slugs, spacing scale, custom blocks, pattern structure) belong
|
|
127
|
+
> in **that project's repo** — next to the code, in its `AGENTS.md`/`CLAUDE.md` — not in the
|
|
128
|
+
> shared library. The test for "does it belong in this repo?" is: *would it be true on a
|
|
129
|
+
> different client's WordPress project?* If not, it's project layer.
|
|
130
|
+
|
|
131
|
+
The base layer is pinned to a commit SHA in [`upstream.json`](upstream.json) (upstream has
|
|
132
|
+
no releases yet) and fetched at install time. Bump the `ref` there deliberately and re-test;
|
|
133
|
+
don't float it, or agent behavior changes silently. New **generic** WordPress knowledge
|
|
134
|
+
should be contributed **upstream**, not added here.
|
|
135
|
+
|
|
136
|
+
## Install options
|
|
137
|
+
|
|
138
|
+
Skills install via our own zero-dependency CLI, run straight from this repo with `npx`.
|
|
139
|
+
Run it from a project root:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Install every skill into this project's Claude Code skills dir (./.claude/skills)
|
|
143
|
+
npx -y github:linchpin/skills
|
|
144
|
+
|
|
145
|
+
# Install a specific skill
|
|
146
|
+
npx -y github:linchpin/skills wp-studio-cli
|
|
147
|
+
|
|
148
|
+
# List what's available
|
|
149
|
+
npx -y github:linchpin/skills --list
|
|
150
|
+
|
|
151
|
+
# Install for GitHub Copilot instead (-> ./.agents/skills + ./.github/skills)
|
|
152
|
+
npx -y github:linchpin/skills --agent github-copilot
|
|
153
|
+
|
|
154
|
+
# Install into every agent's directory at once (Claude Code, Copilot, Codex, Cursor)
|
|
155
|
+
npx -y github:linchpin/skills --agent all
|
|
156
|
+
|
|
157
|
+
# Install to your user-global dir (-> ~/.claude/skills) instead of the project
|
|
158
|
+
npx -y github:linchpin/skills --global
|
|
159
|
+
|
|
160
|
+
# Install the Linchpin skills only, without the upstream base layer
|
|
161
|
+
npx -y github:linchpin/skills --skip-upstream
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
> `npx` installs from the repo's default branch, so you always get current `main`. There is
|
|
165
|
+
> no version pinning on this route — pin by publishing to a registry (below) if you ever
|
|
166
|
+
> need it.
|
|
167
|
+
|
|
168
|
+
> By default the installer also vendors the pinned upstream base layer (see
|
|
169
|
+
> [Three tiers](#three-tiers--and-what-belongs-here)) into the same directory. It needs
|
|
170
|
+
> network access and a system `tar`; if either is missing it warns and still installs the
|
|
171
|
+
> Linchpin skills. Pass `--skip-upstream` to install the Linchpin skills alone.
|
|
172
|
+
|
|
173
|
+
**Updating:** re-run the same command. The installer overwrites each skill in place, so a
|
|
174
|
+
fresh run always pulls the current `main`.
|
|
175
|
+
|
|
176
|
+
### Where skills land
|
|
177
|
+
|
|
178
|
+
| Agent (`--agent`) | Project scope | Global scope (`--global`) |
|
|
179
|
+
| --- | --- | --- |
|
|
180
|
+
| `claude-code` (default) | `./.claude/skills/` | `~/.claude/skills/` |
|
|
181
|
+
| `github-copilot` | `./.agents/skills/` + `./.github/skills/` | `~/.copilot/skills/` |
|
|
182
|
+
| `codex` | `./.codex/skills/` | `~/.codex/skills/` |
|
|
183
|
+
| `cursor` | `./.cursor/skills/` | `~/.cursor/skills/` |
|
|
184
|
+
| `all` | every directory above | every directory above |
|
|
185
|
+
|
|
186
|
+
A project that wants skills in more than one agent's directory should run
|
|
187
|
+
`--agent all` rather than copying directories around by hand.
|
|
188
|
+
|
|
189
|
+
> Skills are loaded by the **agent/harness**, not the model — so "Copilot running Claude"
|
|
190
|
+
> still needs the skill installed in Copilot's own directory. The installer handles that.
|
|
191
|
+
|
|
192
|
+
## Available skills
|
|
193
|
+
|
|
194
|
+
### Linchpin tooling (this repo)
|
|
195
|
+
|
|
196
|
+
| Skill | Domain | What it does |
|
|
197
|
+
| --- | --- | --- |
|
|
198
|
+
| `wp-local-setup` | WordPress | Stand up the Linchpin baseline local environment — scaffold a new wp-content-shaped project repo (Composer plugins from wpackagist + packagist.linchpin.com, theme from `base-wp-theme-2026`) and/or wire a repo into a WordPress Studio site by symlinking it in as `wp-content`. |
|
|
199
|
+
| `wp-studio-cli` | WordPress | Operate a local WordPress Studio site — the wordpress-studio MCP first (`wp_cli`, `validate_blocks`, `take_screenshot`), the `studio` CLI as fallback — including the PHP-WASM `ABSPATH` rule. |
|
|
200
|
+
| `wp-audit` | WordPress | Audit a site for performance, accessibility, and visible frontend quality against Core Web Vitals thresholds; prioritized findings with labeled evidence, and before/after re-measurement. |
|
|
201
|
+
| `wp-pressable` | WordPress | Operate a Pressable-hosted site (prod/staging) via the Pressable MCP or SSH+WP-CLI; safely diagnose and fix the "renders locally but not on prod" FSE bug where DB template overrides shadow deployed theme files. |
|
|
202
|
+
| `wordpress-blocks` | WordPress | Author and edit page/post content as valid Gutenberg block markup from a chat interface — pattern-first (reuse the site's synced/registered patterns before composing core blocks), with the grammar rules and the validate-before-insert contract. |
|
|
203
|
+
| `wp-block-conventions` | WordPress | Build custom blocks the Linchpin way — apiVersion 3 under `linchpin/`, dynamic `render.php` + Interactivity API `view.js`, parent/child block context, and the `wp-scripts` build/registration chain shared by `linchpin-blocks` and project functionality plugins. |
|
|
204
|
+
| `wp-implementation-choice` | WordPress | Decide what a request should become — theme work, content, a custom block, a functionality plugin, or an existing plugin — before any code is written. |
|
|
205
|
+
| `design-previews` | Design | Generate three genuinely different visual directions as self-contained HTML previews, screenshot them at desktop and mobile via the Chrome DevTools MCP (or Playwright), and get a pick before theme or block work starts. |
|
|
206
|
+
| `project-context` | Workflow | Orient before acting — repo shape, local environment, host, ClickUp space, and release model, read from the project's own config rather than assumed. Referenced by other skills' Preflight. |
|
|
207
|
+
| `quality-gates` | Workflow | Run a project's own lint, PHPCS, PHPStan, and test gates before committing — detected from `composer.json`, `package.json`, `phpcs.xml.dist`, and `lint-staged`, never assumed. |
|
|
208
|
+
| `web-qa` | Workflow | QA like a real user and fix what you find — front end, wp-admin, and block editor, with severity, evidence, one atomic commit per fix, and a report-only mode. |
|
|
209
|
+
| `investigate` | Workflow | Root-cause a bug before changing anything — reproduce, read the real error, isolate the layer, explain the mechanism, with WordPress first checks. |
|
|
210
|
+
| `browser-automation` | Workflow | The browser ladder, owned once: Chrome DevTools MCP against real Chrome first, Playwright headless as fallback, plus auth handling and WordPress specifics. |
|
|
211
|
+
| `safety-hooks` | Workflow | Enforced guardrails — a `PreToolUse` hook that makes destructive commands (`wp db drop`, `search-replace` without `--dry-run`, force-push, `rm -rf`) require confirmation, plus an optional edit boundary. Claude Code only. |
|
|
212
|
+
| `engagement-types` | Project mgmt | Tell support, site maintenance, projects, product/plugin work, and pre-sales apart — each lives somewhere different in ClickUp and is planned and closed differently. |
|
|
213
|
+
| `support-triage` | Project mgmt | Run a client support request end to end — clarify the real need, reproduce, judge urgency and scope, fix in the right layer, verify, and close the loop with the requester. |
|
|
214
|
+
| `dependency-updates` | Workflow | Handle the dependency work Renovate can't automerge — majors, breaking changes, failing or conflicted bot PRs, security advisories, `@wordpress/*` package sets. |
|
|
215
|
+
| `commit-and-release` | Workflow | Write commits, branches, and PR titles that satisfy the repo's own commitlint rules, and stay out of release-please's way (it owns versions and `CHANGELOG.md`). |
|
|
216
|
+
| `task-tracking` | Workflow | Tie every unit of work to a ClickUp task (or explicit `NO-TASK`) with minimal friction via the ClickUp MCP — resolve/search a task, offer to create one before committing, update it when the work lands, and carry the task key in the conventional-commit scope. |
|
|
217
|
+
| `write-a-linchpin-skill` | Meta | The house standard for authoring skills in this library — placement test, tier model, required frontmatter, the section skeleton, and the four house rules. Enforced by `scripts/validate-skills.mjs`. |
|
|
218
|
+
|
|
219
|
+
_(More WordPress, React, Cloudflare Workers, marketing, and design skills to come.)_
|
|
220
|
+
|
|
221
|
+
### Base layer (vendored from upstream, pinned)
|
|
222
|
+
|
|
223
|
+
Fetched at install time from [`WordPress/agent-skills`](https://github.com/WordPress/agent-skills)
|
|
224
|
+
at the SHA pinned in [`upstream.json`](upstream.json). Curate the set there. Currently:
|
|
225
|
+
`wp-block-development`, `wp-block-themes`, `wp-interactivity-api`, `wp-performance`,
|
|
226
|
+
`wp-wpcli-and-ops`, `wp-plugin-development`, `wp-rest-api`.
|
|
227
|
+
|
|
228
|
+
## Adding a skill
|
|
229
|
+
|
|
230
|
+
**The standard lives in [`skills/write-a-linchpin-skill/`](skills/write-a-linchpin-skill/SKILL.md)** —
|
|
231
|
+
load that skill and follow it. It owns the placement test, the tier model (A: `SKILL.md`
|
|
232
|
+
only → B: `+ references/` → C: `+ scripts/`), required frontmatter, the section skeleton,
|
|
233
|
+
and the four house rules. It isn't restated here on purpose: one owner per concern.
|
|
234
|
+
|
|
235
|
+
The short version:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
skills/
|
|
239
|
+
<name>/
|
|
240
|
+
SKILL.md # required — frontmatter: name, description, version
|
|
241
|
+
references/*.md # Tier B — detail promoted out of SKILL.md
|
|
242
|
+
scripts/*.mjs # Tier C — only when determinism is genuinely needed
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- Directory name = frontmatter `name`, and names are **globally unique once installed**.
|
|
246
|
+
Prefix by domain — `wp-`, `react-`, `cf-`, `seo-`, `design-` — and leave cross-cutting
|
|
247
|
+
workflow skills (`task-tracking`, `quality-gates`) un-prefixed.
|
|
248
|
+
- Every `SKILL.md` needs `## When to use`, `## Guardrails`, and `## Done`.
|
|
249
|
+
- Add a row to the **Available skills** table above.
|
|
250
|
+
|
|
251
|
+
Then validate — CI runs the same command on every PR:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
npm run validate # every skill
|
|
255
|
+
node scripts/validate-skills.mjs <name> # just the one you touched
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
> **Keep it portable.** Every skill here must be true on *any* Linchpin project of its kind
|
|
259
|
+
> — don't bake in one site's blocks, palette, or file paths. Project-specific conventions
|
|
260
|
+
> belong in that project's own `AGENTS.md`/`CLAUDE.md` (see
|
|
261
|
+
> [Three tiers](#three-tiers--and-what-belongs-here)).
|
|
262
|
+
|
|
263
|
+
## For maintainers — publishing
|
|
264
|
+
|
|
265
|
+
**Nothing is published today.** `@linchpinagency/skills` does not exist on npm; the install
|
|
266
|
+
route is `npx -y github:linchpin/skills`, which works for anyone with access to this repo.
|
|
267
|
+
`package.json` is *configured* to publish (`publishConfig.access: "public"`), but running
|
|
268
|
+
`npm publish` is a deliberate decision that hasn't been made — for a good reason:
|
|
269
|
+
|
|
270
|
+
> **A public package makes this content public.** These skills describe how we run client
|
|
271
|
+
> work: engagement and support workflows, hosting and deploy specifics, and our internal
|
|
272
|
+
> conventions. Before anything is published publicly, someone needs to decide that is
|
|
273
|
+
> acceptable, and the content needs a pass for client-identifying detail (names, ClickUp
|
|
274
|
+
> ids, hostnames) — which belongs in project repos, not here, regardless of publishing.
|
|
275
|
+
|
|
276
|
+
### How releases work
|
|
277
|
+
|
|
278
|
+
Releases follow the house convention — **release-please**, same as every other Linchpin repo:
|
|
279
|
+
|
|
280
|
+
1. Merge conventional commits to `main`. `release-please.yml` keeps a rolling
|
|
281
|
+
**:gem: Automated Release** PR up to date, with the changelog it would write.
|
|
282
|
+
2. Merge that PR when you want to cut a release. It bumps `package.json`, writes
|
|
283
|
+
`CHANGELOG.md`, tags `vX.Y.Z`, and publishes a GitHub Release.
|
|
284
|
+
3. That release flips `release_created`, which triggers the `publish` job:
|
|
285
|
+
`npm run validate`, then `npm publish --provenance --access public`.
|
|
286
|
+
|
|
287
|
+
**Never hand-edit `package.json`'s version or `CHANGELOG.md`** — release-please owns both
|
|
288
|
+
(see [`commit-and-release`](skills/commit-and-release/SKILL.md)).
|
|
289
|
+
|
|
290
|
+
### One-time setup before the first release
|
|
291
|
+
|
|
292
|
+
The automation is wired but has never run. Three things are needed:
|
|
293
|
+
|
|
294
|
+
1. **Decide the content question above.** Everything else is plumbing; this isn't.
|
|
295
|
+
2. **Create the package with one manual publish.** npm can't attach automation to a package
|
|
296
|
+
that doesn't exist yet:
|
|
297
|
+
```bash
|
|
298
|
+
npm login # must be a member of the @linchpinagency org
|
|
299
|
+
npm publish --access public # prepublishOnly runs the validator first
|
|
300
|
+
```
|
|
301
|
+
3. **Give CI credentials**, either:
|
|
302
|
+
- **Trusted Publishing (preferred)** — on npmjs.com, set this repo and
|
|
303
|
+
`.github/workflows/release-please.yml` as a trusted publisher for the package. No
|
|
304
|
+
stored secret, and provenance is automatic.
|
|
305
|
+
- **`NPM_TOKEN` secret** — an npm *automation* token added to this repo's Actions
|
|
306
|
+
secrets. Simpler to set up, but it's a long-lived credential to rotate.
|
|
307
|
+
|
|
308
|
+
Until step 3 is done the `publish` job fails rather than skipping, so a release never looks
|
|
309
|
+
published when it isn't.
|
|
310
|
+
|
|
311
|
+
Options worth weighing first:
|
|
312
|
+
|
|
313
|
+
- **Stay on the GitHub route** (status quo). Private, zero setup, no registry to maintain.
|
|
314
|
+
Costs: needs repo access, so external collaborators can't install, and there's no version
|
|
315
|
+
pinning.
|
|
316
|
+
- **Publish privately** (paid npm org, or GitHub Packages). Keeps content private and adds
|
|
317
|
+
versioning — but consumers must `npm login` anyway, which removes most of the convenience
|
|
318
|
+
that made publishing attractive.
|
|
319
|
+
- **Publish publicly** after a content review. Best ergonomics; only appropriate if we're
|
|
320
|
+
comfortable with the playbook being readable by anyone.
|
|
321
|
+
- **Scope migration.** If the `@linchpin` npm scope is ever acquired, prefer it and deprecate
|
|
322
|
+
the old name with `npm deprecate @linchpinagency/skills "moved to @linchpin/skills"`.
|
|
323
|
+
|
|
324
|
+
The package would ship only `bin/`, `skills/`, `upstream.json`, and `README.md` (see `files`
|
|
325
|
+
in `package.json`).
|
|
326
|
+
|
|
327
|
+
## License & attribution
|
|
328
|
+
|
|
329
|
+
- **This repo (overlay + installer):** UNLICENSED — internal Linchpin tooling. Update
|
|
330
|
+
`license` in `package.json` if this is ever released externally.
|
|
331
|
+
- **Base layer:** the upstream skills are **not stored in this repo** — the installer
|
|
332
|
+
fetches them from [`WordPress/agent-skills`](https://github.com/WordPress/agent-skills) at
|
|
333
|
+
the pinned SHA, onto the user's machine, at install time. They are
|
|
334
|
+
**GPL-2.0-or-later**, © WordPress Contributors. Credit to that project for the generic
|
|
335
|
+
WordPress expertise our overlay builds on. (Upstream is v1 and AI-authored then
|
|
336
|
+
human-reviewed — treat it as a strong baseline, which is exactly why house rules win on
|
|
337
|
+
conflict.)
|
package/bin/install.mjs
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @linchpinagency/skills installer
|
|
3
|
+
// Copies bundled Linchpin skills into a coding agent's skills directory, and (by
|
|
4
|
+
// default) vendors a pinned base layer of upstream WordPress/agent-skills alongside them.
|
|
5
|
+
// Zero runtime dependencies — pure Node (>=18, uses global fetch) + the system `tar`.
|
|
6
|
+
// Re-run to update (it overwrites in place).
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
16
|
+
const SKILLS_ROOT = path.join(PKG_ROOT, 'skills');
|
|
17
|
+
const UPSTREAM_MANIFEST = path.join(PKG_ROOT, 'upstream.json');
|
|
18
|
+
|
|
19
|
+
// Per-agent install locations. `project` paths are relative to cwd, `global` to home.
|
|
20
|
+
// These follow the Agent Skills conventions each tool reads from. An agent may read more
|
|
21
|
+
// than one directory (Copilot honors both `.agents/skills` and `.github/skills`), so every
|
|
22
|
+
// entry is a list.
|
|
23
|
+
const AGENTS = {
|
|
24
|
+
'claude-code': { label: 'Claude Code', project: ['.claude/skills'], global: ['.claude/skills'] },
|
|
25
|
+
'github-copilot': { label: 'GitHub Copilot', project: ['.agents/skills', '.github/skills'], global: ['.copilot/skills'] },
|
|
26
|
+
codex: { label: 'Codex', project: ['.codex/skills'], global: ['.codex/skills'] },
|
|
27
|
+
cursor: { label: 'Cursor', project: ['.cursor/skills'], global: ['.cursor/skills'] },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function resolveAgents(id) {
|
|
31
|
+
if (id === 'all') return Object.keys(AGENTS);
|
|
32
|
+
return AGENTS[id] ? [id] : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseArgs(argv) {
|
|
36
|
+
const opts = { agent: 'claude-code', global: false, list: false, help: false, skipUpstream: false, skills: [] };
|
|
37
|
+
for (let i = 0; i < argv.length; i++) {
|
|
38
|
+
const a = argv[i];
|
|
39
|
+
if (a === '--global' || a === '-g') opts.global = true;
|
|
40
|
+
else if (a === '--list' || a === '-l') opts.list = true;
|
|
41
|
+
else if (a === '--help' || a === '-h') opts.help = true;
|
|
42
|
+
else if (a === '--skip-upstream') opts.skipUpstream = true;
|
|
43
|
+
else if (a === '--agent') opts.agent = argv[++i];
|
|
44
|
+
else if (a.startsWith('--agent=')) opts.agent = a.slice('--agent='.length);
|
|
45
|
+
else if (a.startsWith('-')) {
|
|
46
|
+
console.error(`Unknown option: ${a} (try --help)`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
} else opts.skills.push(a);
|
|
49
|
+
}
|
|
50
|
+
return opts;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readDescription(skillDir) {
|
|
54
|
+
try {
|
|
55
|
+
const md = fs.readFileSync(path.join(skillDir, 'SKILL.md'), 'utf8');
|
|
56
|
+
const fm = md.match(/^---\n([\s\S]*?)\n---/);
|
|
57
|
+
if (!fm) return '';
|
|
58
|
+
const d = fm[1].match(/^description:\s*(.*)$/m);
|
|
59
|
+
return d ? d[1].replace(/^["']|["']$/g, '').trim() : '';
|
|
60
|
+
} catch {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function availableSkills() {
|
|
66
|
+
if (!fs.existsSync(SKILLS_ROOT)) return [];
|
|
67
|
+
return fs
|
|
68
|
+
.readdirSync(SKILLS_ROOT, { withFileTypes: true })
|
|
69
|
+
.filter((e) => e.isDirectory() && fs.existsSync(path.join(SKILLS_ROOT, e.name, 'SKILL.md')))
|
|
70
|
+
.map((e) => e.name)
|
|
71
|
+
.sort();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function readUpstreamManifest() {
|
|
75
|
+
try {
|
|
76
|
+
const m = JSON.parse(fs.readFileSync(UPSTREAM_MANIFEST, 'utf8'));
|
|
77
|
+
return Array.isArray(m.sources) ? m.sources : [];
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Fetch a repo tarball at a pinned ref, extract it once, and copy the requested skill
|
|
84
|
+
// dirs into every directory in `bases`. Best-effort: any failure (offline, no `tar`,
|
|
85
|
+
// missing skill) warns and returns false rather than aborting the Linchpin install.
|
|
86
|
+
async function installUpstreamSource(source, bases) {
|
|
87
|
+
const { repo, ref, skills = [] } = source;
|
|
88
|
+
if (!repo || !ref || !skills.length) return false;
|
|
89
|
+
|
|
90
|
+
const url = `https://codeload.github.com/${repo}/tar.gz/${ref}`;
|
|
91
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'lp-skills-'));
|
|
92
|
+
const tarPath = path.join(tmp, 'src.tar.gz');
|
|
93
|
+
const extractDir = path.join(tmp, 'x');
|
|
94
|
+
fs.mkdirSync(extractDir);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch(url);
|
|
98
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
|
|
99
|
+
fs.writeFileSync(tarPath, Buffer.from(await res.arrayBuffer()));
|
|
100
|
+
|
|
101
|
+
// System tar handles gunzip + long paths + pax headers robustly (bsdtar/gnutar).
|
|
102
|
+
execFileSync('tar', ['-xzf', tarPath, '-C', extractDir], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
103
|
+
|
|
104
|
+
// GitHub tarballs wrap everything in a single top-level dir (`<repo>-<ref>`); find it
|
|
105
|
+
// rather than reconstruct its name.
|
|
106
|
+
const topdir = fs.readdirSync(extractDir).find((n) => fs.statSync(path.join(extractDir, n)).isDirectory());
|
|
107
|
+
if (!topdir) throw new Error('unexpected tarball layout (no top-level dir)');
|
|
108
|
+
|
|
109
|
+
let count = 0;
|
|
110
|
+
for (const name of skills) {
|
|
111
|
+
const from = path.join(extractDir, topdir, 'skills', name);
|
|
112
|
+
if (!fs.existsSync(path.join(from, 'SKILL.md'))) {
|
|
113
|
+
console.warn(` ! ${repo}:${name} not found at ${ref} — skipped`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
for (const base of bases) {
|
|
117
|
+
const dest = path.join(base, name);
|
|
118
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
119
|
+
fs.cpSync(from, dest, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
console.log(` ✓ ${name} (${repo})`);
|
|
122
|
+
count++;
|
|
123
|
+
}
|
|
124
|
+
return count > 0;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.warn(` ! Skipped base layer from ${repo}: ${err.message}`);
|
|
127
|
+
console.warn(` (Linchpin skills installed fine. Re-run online, or use --skip-upstream to silence.)`);
|
|
128
|
+
return false;
|
|
129
|
+
} finally {
|
|
130
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function help() {
|
|
135
|
+
console.log(
|
|
136
|
+
`
|
|
137
|
+
@linchpinagency/skills — install Linchpin AI agent skills
|
|
138
|
+
|
|
139
|
+
Linchpin skills are portable, cross-project tooling. By default the installer also vendors
|
|
140
|
+
a pinned base layer of upstream WordPress/agent-skills (generic WordPress expertise)
|
|
141
|
+
alongside them.
|
|
142
|
+
|
|
143
|
+
Usage:
|
|
144
|
+
npx @linchpinagency/skills [skills...] [options]
|
|
145
|
+
|
|
146
|
+
Arguments:
|
|
147
|
+
skills One or more Linchpin skill names to install (default: all)
|
|
148
|
+
|
|
149
|
+
Options:
|
|
150
|
+
-l, --list List available skills (Linchpin + pinned base layer) and exit
|
|
151
|
+
-g, --global Install to the user-global skills dir instead of the project
|
|
152
|
+
--agent <id> Target agent: claude-code (default) | github-copilot | codex | cursor
|
|
153
|
+
| all (installs into every agent's directory)
|
|
154
|
+
--skip-upstream Install only Linchpin skills; don't vendor the upstream base layer
|
|
155
|
+
-h, --help Show this help
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
npx @linchpinagency/skills # Linchpin skills + base layer -> ./.claude/skills
|
|
159
|
+
npx @linchpinagency/skills wp-studio-cli # one Linchpin skill (+ base layer)
|
|
160
|
+
npx @linchpinagency/skills --skip-upstream # Linchpin skills only
|
|
161
|
+
npx @linchpinagency/skills --agent github-copilot
|
|
162
|
+
npx @linchpinagency/skills --agent all # every agent dir in this project
|
|
163
|
+
npx @linchpinagency/skills --global # -> ~/.claude/skills
|
|
164
|
+
`.trimStart()
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function main() {
|
|
169
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
170
|
+
if (opts.help) return help();
|
|
171
|
+
|
|
172
|
+
const all = availableSkills();
|
|
173
|
+
const sources = readUpstreamManifest();
|
|
174
|
+
|
|
175
|
+
if (opts.list) {
|
|
176
|
+
console.log('Linchpin skills (portable, cross-project):\n');
|
|
177
|
+
if (!all.length) console.log(' (none found in this package)');
|
|
178
|
+
for (const name of all) {
|
|
179
|
+
const desc = readDescription(path.join(SKILLS_ROOT, name));
|
|
180
|
+
console.log(` ${name}${desc ? ` — ${desc}` : ''}`);
|
|
181
|
+
}
|
|
182
|
+
for (const s of sources) {
|
|
183
|
+
console.log(`\nBase layer — ${s.repo} @ ${String(s.ref).slice(0, 12)} (${s.license || 'see repo'}):\n`);
|
|
184
|
+
for (const name of s.skills || []) console.log(` ${name}`);
|
|
185
|
+
}
|
|
186
|
+
console.log('\nThe base layer is fetched at install time unless --skip-upstream is passed.');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const agentIds = resolveAgents(opts.agent);
|
|
191
|
+
if (!agentIds) {
|
|
192
|
+
console.error(`Unknown agent "${opts.agent}". Known: ${Object.keys(AGENTS).join(', ')}, all`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// One flat list of destination dirs across every selected agent.
|
|
197
|
+
const bases = agentIds.flatMap((id) =>
|
|
198
|
+
(opts.global ? AGENTS[id].global : AGENTS[id].project).map((rel) =>
|
|
199
|
+
opts.global ? path.join(os.homedir(), rel) : path.join(process.cwd(), rel)
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const wanted = opts.skills.length ? opts.skills : all;
|
|
204
|
+
const unknown = wanted.filter((s) => !all.includes(s));
|
|
205
|
+
if (unknown.length) {
|
|
206
|
+
console.error(`Unknown skill(s): ${unknown.join(', ')}`);
|
|
207
|
+
console.error('Run with --list to see available skills.');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
if (!wanted.length && !sources.length) return console.log('No skills to install.');
|
|
211
|
+
|
|
212
|
+
for (const base of bases) {
|
|
213
|
+
fs.mkdirSync(base, { recursive: true });
|
|
214
|
+
for (const name of wanted) {
|
|
215
|
+
const dest = path.join(base, name);
|
|
216
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
217
|
+
fs.cpSync(path.join(SKILLS_ROOT, name), dest, { recursive: true });
|
|
218
|
+
console.log(`✓ ${name} -> ${dest}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const labels = agentIds.map((id) => AGENTS[id].label).join(', ');
|
|
222
|
+
console.log(`\nInstalled ${wanted.length} Linchpin skill(s) for ${labels}.`);
|
|
223
|
+
|
|
224
|
+
if (!opts.skipUpstream && sources.length) {
|
|
225
|
+
console.log('\nVendoring pinned base layer (upstream WordPress/agent-skills):');
|
|
226
|
+
for (const s of sources) await installUpstreamSource(s, bases);
|
|
227
|
+
console.log('\nTip: --skip-upstream installs Linchpin skills only.');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@linchpinagency/skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Linchpin's library of reusable AI agent skills for WordPress projects.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skills": "bin/install.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"skills",
|
|
12
|
+
"upstream.json",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"list": "node bin/install.mjs --list",
|
|
20
|
+
"validate": "node scripts/validate-skills.mjs",
|
|
21
|
+
"test": "npm run validate",
|
|
22
|
+
"prepublishOnly": "npm run validate"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"claude",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"github-copilot",
|
|
28
|
+
"agent-skills",
|
|
29
|
+
"wordpress",
|
|
30
|
+
"wordpress-studio"
|
|
31
|
+
],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/linchpin/skills.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/linchpin/skills#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/linchpin/skills/issues"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"license": "UNLICENSED"
|
|
44
|
+
}
|